Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
f583426
feat: semantic memory and cross-session context retrieval
May 22, 2026
054e1e1
fix(semantic-memory): move package to src/features/ to fix typecheck …
May 23, 2026
ed78737
test(semantic-memory): add comprehensive tests for memory system
May 23, 2026
7a64007
fix: remove non-existent workspaces from package.json
May 23, 2026
e9b0b5b
test(semantic-memory): fix tests to match actual API
May 23, 2026
c17b369
fix(hooks): add missing agent-analytics and semantic-memory hooks
May 23, 2026
821e44e
fix(hooks): correct hook registration for semantic-memory feature
May 23, 2026
3644213
fix(hooks): use correct pattern in semantic-memory hook and remove du…
May 23, 2026
c9566d7
fix: remove cross-branch contamination from semantic-memory branch
May 23, 2026
114303f
fix: rewrite semantic-memory hook to use tool.execute.after and wire it
May 23, 2026
477aeba
fix: add semanticMemory to ToolGuardHooks type and registration
May 23, 2026
09cae4d
fix: use memory-context-injector as hook name to match schema
May 23, 2026
f4bd11a
fix: remove dead memory-context-injector.ts file
May 23, 2026
62c35cb
fix(semantic-memory): use dynamic import for bun:sqlite to fix bundle…
May 23, 2026
df09acf
Merge branch 'feat/semantic-memory' into feat/runtime-config-reload-4369
May 24, 2026
a4c0e53
fix(semantic-memory): async getBunSqlite, add context injector hook
May 24, 2026
01f20f4
fix: remove duplicate import in create-plugin-module.ts
May 24, 2026
cd3c7c7
feat(skills): native find_skills tool, /create-skill, /update-skill-r…
May 24, 2026
8c69494
feat: register find_skills tool in tool-registry
May 24, 2026
94b1966
feat: register create-skill and update-skill-registry commands as bui…
May 24, 2026
d74c2a6
fix: remove configStore/PluginConfigStore from merged code
May 24, 2026
5ad929b
Merge remote-tracking branch 'origin/dev' into feat/native-memory-fou…
May 26, 2026
dfc40b6
chore: update bun.lock after merging origin/dev
May 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 22 additions & 22 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"packages/hashline-core",
"packages/boulder-state",
"packages/agents-md-core"

],
"bin": {
"oh-my-opencode": "bin/oh-my-opencode.js",
Expand Down
2 changes: 2 additions & 0 deletions src/cli/cli-program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { doctor } from "./doctor"
import { refreshModelCapabilities } from "./refresh-model-capabilities"
import { createMcpOAuthCommand } from "./mcp-oauth"
import { boulder } from "./boulder"
import { createMemoryCommand } from "./memory"
import type { InstallArgs } from "./types"
import type { RunOptions } from "./run"
import type { GetLocalVersionOptions } from "./get-local-version/types"
Expand Down Expand Up @@ -221,6 +222,7 @@ program
})

program.addCommand(createMcpOAuthCommand())
program.addCommand(createMemoryCommand())

export function runCli(): void {
program.parse()
Expand Down
199 changes: 199 additions & 0 deletions src/cli/memory/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
import { Command } from "commander"
import {
retrieveMemories,
getRecentMemories,
storeMemory,
deleteMemory,
clearAllMemories,
getMemoryStats,
} from "../../features/semantic-memory"
import type { MemoryEntry } from "../../features/semantic-memory"

interface MemoryOptions {
agent?: string
type?: string
limit?: string
format?: string
minImportance?: string
hours?: string
}

function formatMemoryEntry(entry: MemoryEntry, index: number): string {
const lines = [
`[${index + 1}] ${entry.memoryType.toUpperCase()} (importance: ${entry.importance})`,
` Content: ${entry.content.substring(0, 100)}${entry.content.length > 100 ? "..." : ""}`,
` Agent: ${entry.agentName ?? "unknown"} | Session: ${entry.sessionId ?? "unknown"}`,
` Created: ${entry.createdAt.toISOString()} | Accessed: ${entry.accessCount} times`,
"",
]
return lines.join("\n")
}

function formatAsJson(data: unknown): string {
return JSON.stringify(data, null, 2)
}

export function createMemoryCommand(): Command {
const command = new Command("memory")
.description("Semantic memory and cross-session context retrieval")

command
.command("search <query>")
.description("Search memories by semantic similarity")
.option("-a, --agent <agent>", "Filter by agent name")
.option("-t, --type <type>", "Filter by memory type (context, decision, error, pattern, insight)")
.option("-l, --limit <n>", "Maximum number of results", "5")
.option("-f, --format <format>", "Output format (text, json)", "text")
.option("-m, --min-importance <score>", "Minimum importance threshold", "0")
.action(async (query: string, options: MemoryOptions) => {
try {
const results = await retrieveMemories({
query,
agentName: options.agent,
memoryType: options.type as MemoryEntry["memoryType"],
limit: parseInt(options.limit ?? "5", 10),
minImportance: parseFloat(options.minImportance ?? "0"),
})

if (options.format === "json") {
console.log(formatAsJson(results))
return
}

if (results.length === 0) {
console.log("No memories found matching your query.")
return
}

console.log(`Found ${results.length} memories:\n`)
for (let i = 0; i < results.length; i++) {
const result = results[i]
console.log(`[${i + 1}] ${result.entry.memoryType.toUpperCase()} (similarity: ${(result.similarity * 100).toFixed(1)}%, importance: ${result.entry.importance})`)
console.log(` Content: ${result.entry.content}`)
console.log(` Agent: ${result.entry.agentName ?? "unknown"} | Session: ${result.entry.sessionId ?? "unknown"}`)
console.log(` Created: ${result.entry.createdAt.toISOString()}`)
console.log("")
}
} catch (error) {
console.error("Error searching memories:", error)
process.exit(1)
}
})

command
.command("recent")
.description("Show recent memories")
.option("-a, --agent <agent>", "Filter by agent name")
.option("-t, --type <type>", "Filter by memory type")
.option("-l, --limit <n>", "Maximum number of results", "10")
.option("-f, --format <format>", "Output format (text, json)", "text")
.option("-h, --hours <n>", "Only show memories from last N hours")
.action(async (options: MemoryOptions) => {
try {
const memories = await getRecentMemories({
agentName: options.agent,
memoryType: options.type as MemoryEntry["memoryType"],
limit: parseInt(options.limit ?? "10", 10),
hours: options.hours ? parseInt(options.hours, 10) : undefined,
})

if (options.format === "json") {
console.log(formatAsJson(memories))
return
}

if (memories.length === 0) {
console.log("No recent memories found.")
return
}

console.log(`Recent memories (${memories.length}):\n`)
for (let i = 0; i < memories.length; i++) {
console.log(formatMemoryEntry(memories[i], i))
}
} catch (error) {
console.error("Error retrieving memories:", error)
process.exit(1)
}
})

command
.command("store <content>")
.description("Store a new memory")
.option("-a, --agent <agent>", "Agent name")
.option("-t, --type <type>", "Memory type", "context")
.option("-i, --importance <score>", "Importance score (0-5)", "1.0")
.option("-s, --session <session>", "Session ID")
.action(async (content: string, options: MemoryOptions & { importance?: string; session?: string }) => {
try {
const entry = await storeMemory(content, {
agentName: options.agent,
sessionId: options.session,
memoryType: options.type as MemoryEntry["memoryType"],
importance: parseFloat(options.importance ?? "1.0"),
})
console.log(`Memory stored with ID: ${entry.id}`)
} catch (error) {
console.error("Error storing memory:", error)
process.exit(1)
}
})

command
.command("delete <id>")
.description("Delete a memory by ID")
.action(async (id: string) => {
try {
const deleted = await deleteMemory(id)
if (deleted) {
console.log(`Memory ${id} deleted successfully.`)
} else {
console.log(`Memory ${id} not found.`)
}
} catch (error) {
console.error("Error deleting memory:", error)
process.exit(1)
}
})

command
.command("stats")
.description("Show memory statistics")
.option("-f, --format <format>", "Output format (text, json)", "text")
.action(async (options: MemoryOptions) => {
try {
const stats = await getMemoryStats()

if (options.format === "json") {
console.log(formatAsJson(stats))
return
}

console.log("Memory Statistics")
console.log("=================")
console.log(`Total Memories: ${stats.totalMemories}`)
console.log(`Average Importance: ${stats.avgImportance.toFixed(2)}`)
console.log("\nBy Type:")
for (const [type, count] of Object.entries(stats.byType)) {
console.log(` ${type}: ${count}`)
}
console.log("\nBy Agent:")
for (const [agent, count] of Object.entries(stats.byAgent)) {
console.log(` ${agent}: ${count}`)
}
} catch (error) {
console.error("Error getting memory stats:", error)
process.exit(1)
}
})

command
.command("clear")
.description("Clear all memories (use with caution)")
.action(async () => {
await clearAllMemories()
console.log("All memories cleared.")
})

return command
}
1 change: 1 addition & 0 deletions src/config/schema/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export const HookNameSchema = z.enum([
"webfetch-redirect-guard",
"fsync-skip-warning",
"plan-format-validator",
"memory-context-injector",
"legacy-plugin-toast",
])

Expand Down
43 changes: 42 additions & 1 deletion src/features/builtin-commands/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import { START_WORK_TEMPLATE } from "./templates/start-work"
import { HANDOFF_TEMPLATE } from "./templates/handoff"
import { REMOVE_AI_SLOPS_TEMPLATE, REMOVE_AI_SLOPS_TEAM_MODE_ADDENDUM } from "./templates/remove-ai-slops"
import { HYPERPLAN_TEMPLATE } from "./templates/hyperplan"

interface LoadBuiltinCommandsOptions {
useRegisteredAgents?: boolean
teamModeEnabled?: boolean
Expand Down Expand Up @@ -142,6 +141,48 @@ ${HYPERPLAN_TEMPLATE}
</command-instruction>`,
argumentHint: "[planning-request]",
},
"create-skill": {
description: "(builtin) Create a new AI agent skill following the Agent Skills spec. Usage: /create-skill <name> [description]",
template: `<command-instruction>
Create a new skill at .agents/skills/{name}/SKILL.md with the standard Agent Skills template.

The skill name must use kebab-case and should describe the domain or task.

Steps:
1. Validate the skill name (kebab-case, no spaces)
2. Create .agents/skills/{name}/ directory
3. Generate SKILL.md from the standard template
4. Register in .atl/skill-registry.md
</command-instruction>

<user-request>
$ARGUMENTS
</user-request>`,
argumentHint: "<skill-name> [description]",
},
"update-skill-registry": {
description: "(builtin) Scan all skill directories and rebuild .atl/skill-registry.md with compact rules",
template: `<command-instruction>
Scan all skill directories and rebuild the skill registry at .atl/skill-registry.md.

Scan directories:
- .agents/skills/
- .opencode/skills/
- ~/.config/opencode/skills/
- ~/.claude/skills/

For each skill found:
- Extract name from frontmatter
- Extract trigger words from description
- Generate compact rules (5-15 lines) from Critical Patterns section
- Write to .atl/skill-registry.md
</command-instruction>

<user-request>
$ARGUMENTS
</user-request>`,
argumentHint: "",
},
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/features/builtin-commands/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { CommandDefinition } from "../claude-code-command-loader"

export type BuiltinCommandName = "init-deep" | "ralph-loop" | "cancel-ralph" | "ulw-loop" | "refactor" | "start-work" | "stop-continuation" | "handoff" | "remove-ai-slops" | "hyperplan"
export type BuiltinCommandName = "init-deep" | "ralph-loop" | "cancel-ralph" | "ulw-loop" | "refactor" | "start-work" | "stop-continuation" | "handoff" | "remove-ai-slops" | "hyperplan" | "create-skill" | "update-skill-registry"

export interface BuiltinCommandConfig {
disabled_commands?: BuiltinCommandName[]
Expand Down
Loading
Loading