aidimag 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +102 -0
- package/README.md +113 -0
- package/dist/capture/bootstrap.d.ts +25 -0
- package/dist/capture/bootstrap.js +188 -0
- package/dist/capture/commit-miner.d.ts +59 -0
- package/dist/capture/commit-miner.js +381 -0
- package/dist/capture/harvest.d.ts +50 -0
- package/dist/capture/harvest.js +207 -0
- package/dist/capture/pr-miner.d.ts +57 -0
- package/dist/capture/pr-miner.js +185 -0
- package/dist/capture/session-briefing.d.ts +36 -0
- package/dist/capture/session-briefing.js +150 -0
- package/dist/capture/session-extraction.d.ts +23 -0
- package/dist/capture/session-extraction.js +54 -0
- package/dist/capture/triage.d.ts +30 -0
- package/dist/capture/triage.js +108 -0
- package/dist/cli/commands/capture.d.ts +5 -0
- package/dist/cli/commands/capture.js +357 -0
- package/dist/cli/commands/hosts.d.ts +5 -0
- package/dist/cli/commands/hosts.js +98 -0
- package/dist/cli/commands/knowledge.d.ts +5 -0
- package/dist/cli/commands/knowledge.js +121 -0
- package/dist/cli/commands/memory.d.ts +6 -0
- package/dist/cli/commands/memory.js +392 -0
- package/dist/cli/commands/sync.d.ts +5 -0
- package/dist/cli/commands/sync.js +307 -0
- package/dist/cli/commands/tickets.d.ts +6 -0
- package/dist/cli/commands/tickets.js +328 -0
- package/dist/cli/commands/verify.d.ts +5 -0
- package/dist/cli/commands/verify.js +136 -0
- package/dist/cli/index.d.ts +19 -0
- package/dist/cli/index.js +46 -0
- package/dist/cli/shared.d.ts +37 -0
- package/dist/cli/shared.js +175 -0
- package/dist/config.d.ts +55 -0
- package/dist/config.js +51 -0
- package/dist/context/generate.d.ts +24 -0
- package/dist/context/generate.js +115 -0
- package/dist/critique/critique.d.ts +40 -0
- package/dist/critique/critique.js +110 -0
- package/dist/db/schema.d.ts +28 -0
- package/dist/db/schema.js +269 -0
- package/dist/db/store.d.ts +182 -0
- package/dist/db/store.js +906 -0
- package/dist/debug.d.ts +14 -0
- package/dist/debug.js +25 -0
- package/dist/embeddings/provider.d.ts +16 -0
- package/dist/embeddings/provider.js +100 -0
- package/dist/embeddings/search.d.ts +22 -0
- package/dist/embeddings/search.js +95 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +12 -0
- package/dist/knowledge/chunk.d.ts +9 -0
- package/dist/knowledge/chunk.js +78 -0
- package/dist/knowledge/extract.d.ts +34 -0
- package/dist/knowledge/extract.js +113 -0
- package/dist/knowledge/ingest.d.ts +79 -0
- package/dist/knowledge/ingest.js +305 -0
- package/dist/knowledge/llm.d.ts +21 -0
- package/dist/knowledge/llm.js +110 -0
- package/dist/mcp/server.d.ts +11 -0
- package/dist/mcp/server.js +532 -0
- package/dist/security/evidence.d.ts +11 -0
- package/dist/security/evidence.js +15 -0
- package/dist/security/seal.d.ts +3 -0
- package/dist/security/seal.js +28 -0
- package/dist/security/sync-push.d.ts +2 -0
- package/dist/security/sync-push.js +37 -0
- package/dist/security/url.d.ts +6 -0
- package/dist/security/url.js +67 -0
- package/dist/sync/client.d.ts +84 -0
- package/dist/sync/client.js +391 -0
- package/dist/sync/server.d.ts +75 -0
- package/dist/sync/server.js +659 -0
- package/dist/tickets/provider.d.ts +80 -0
- package/dist/tickets/provider.js +375 -0
- package/dist/types.d.ts +133 -0
- package/dist/types.js +5 -0
- package/dist/ui/page.d.ts +5 -0
- package/dist/ui/page.js +841 -0
- package/dist/ui/server.d.ts +10 -0
- package/dist/ui/server.js +437 -0
- package/dist/verify/check.d.ts +38 -0
- package/dist/verify/check.js +121 -0
- package/dist/verify/engine.d.ts +39 -0
- package/dist/verify/engine.js +178 -0
- package/dist/verify/hooks.d.ts +24 -0
- package/dist/verify/hooks.js +125 -0
- package/dist/verify/runners.d.ts +26 -0
- package/dist/verify/runners.js +193 -0
- package/package.json +78 -0
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core memory commands: init, remember, recall, reindex, status, log, gaps,
|
|
3
|
+
* refute, pin, unpin, forget.
|
|
4
|
+
*/
|
|
5
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync, appendFileSync } from "node:fs";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { MemoryStore, findRepoRoot, dbPathFor, AIDIMAG_DIR } from "../../db/store.js";
|
|
8
|
+
import { installGitHooks } from "../../verify/hooks.js";
|
|
9
|
+
import { hybridSearch, indexMemory, reindexAll } from "../../embeddings/search.js";
|
|
10
|
+
import { resolveKnowledgeConfig } from "../../config.js";
|
|
11
|
+
import { KINDS, GUARDRAIL_LEVELS, fail, autoSync, printMemory } from "../shared.js";
|
|
12
|
+
import { debugLog } from "../../debug.js";
|
|
13
|
+
export function registerMemoryCommands(program) {
|
|
14
|
+
program
|
|
15
|
+
.command("init")
|
|
16
|
+
.description("Initialize aidimag in the current repo")
|
|
17
|
+
.action(async () => {
|
|
18
|
+
const root = findRepoRoot() ?? process.cwd();
|
|
19
|
+
const dir = path.join(root, AIDIMAG_DIR);
|
|
20
|
+
const fresh = !existsSync(dbPathFor(root));
|
|
21
|
+
mkdirSync(dir, { recursive: true });
|
|
22
|
+
const store = new MemoryStore(dbPathFor(root));
|
|
23
|
+
store.close();
|
|
24
|
+
// keep the DB out of git by default (team-sync mode comes later)
|
|
25
|
+
const gitignore = path.join(dir, ".gitignore");
|
|
26
|
+
if (!existsSync(gitignore)) {
|
|
27
|
+
writeFileSync(gitignore, "memory.db\nmemory.db-wal\nmemory.db-shm\nknowledge/\n");
|
|
28
|
+
}
|
|
29
|
+
else if (!readFileSync(gitignore, "utf8").includes("knowledge/")) {
|
|
30
|
+
appendFileSync(gitignore, "knowledge/\n");
|
|
31
|
+
}
|
|
32
|
+
// knowledge inbox: a drop folder for project docs (summaries/backups live in .aidimag/)
|
|
33
|
+
const knowledgeInbox = path.join(root, resolveKnowledgeConfig(root).folder);
|
|
34
|
+
mkdirSync(knowledgeInbox, { recursive: true });
|
|
35
|
+
const gitkeep = path.join(knowledgeInbox, ".gitkeep");
|
|
36
|
+
if (!existsSync(gitkeep)) {
|
|
37
|
+
writeFileSync(gitkeep, "# Drop project docs here (design docs, ADRs, style guides, runbooks).\n" +
|
|
38
|
+
"# aidimag summarizes them into reviewed, pinned memories — see `dim knowledge`.\n");
|
|
39
|
+
}
|
|
40
|
+
// create .cursorrules for automatic MCP integration
|
|
41
|
+
const cursorrules = path.join(root, ".cursorrules");
|
|
42
|
+
if (!existsSync(cursorrules)) {
|
|
43
|
+
writeFileSync(cursorrules, `# Project Memory Integration
|
|
44
|
+
|
|
45
|
+
At the start of EVERY new chat session, you MUST:
|
|
46
|
+
1. Read the \`aidimag://session-briefing\` resource to load project memory, conventions, and guardrails
|
|
47
|
+
2. Review all GUARDRAILS before making any code changes
|
|
48
|
+
3. Search project memory using \`memory_search\` when working on specific features
|
|
49
|
+
|
|
50
|
+
Before making any changes to code:
|
|
51
|
+
- Check if there are relevant memories using \`memory_search\`
|
|
52
|
+
- Respect all GUARDRAIL rules (ALWAYS = block, ASK-FIRST = confirm, NEVER = refuse)
|
|
53
|
+
- Use \`context_note\` to capture any new conventions or decisions the user mentions
|
|
54
|
+
|
|
55
|
+
This project uses aiDimag for persistent memory. Always consult memory before proceeding.
|
|
56
|
+
`);
|
|
57
|
+
console.log(`Created ${cursorrules} (tells Cursor/Claude to auto-load memory)`);
|
|
58
|
+
}
|
|
59
|
+
// suggest MCP wiring
|
|
60
|
+
console.log(fresh ? `Initialized aidimag in ${dir}` : `aidimag already initialized in ${dir}`);
|
|
61
|
+
const hooks = installGitHooks(root);
|
|
62
|
+
if (hooks.installed.length) {
|
|
63
|
+
console.log(`Installed git hooks: ${hooks.installed.join(", ")} (re-verify on pull/checkout)`);
|
|
64
|
+
}
|
|
65
|
+
else if (hooks.alreadyPresent.length) {
|
|
66
|
+
console.log(`Git hooks already installed: ${hooks.alreadyPresent.join(", ")}`);
|
|
67
|
+
}
|
|
68
|
+
console.log(`\nAdd the MCP server to your agent config, e.g. for Claude Code (.mcp.json):`);
|
|
69
|
+
console.log(JSON.stringify({ mcpServers: { aidimag: { command: "npx", args: ["-y", "aidimag", "mcp"], env: { AIDIMAG_REPO: root } } } }, null, 2));
|
|
70
|
+
// append .aidimag DB files to repo .gitignore if a git repo
|
|
71
|
+
const rootIgnore = path.join(root, ".gitignore");
|
|
72
|
+
if (existsSync(path.join(root, ".git"))) {
|
|
73
|
+
const current = existsSync(rootIgnore) ? readFileSync(rootIgnore, "utf8") : "";
|
|
74
|
+
const folder = resolveKnowledgeConfig(root).folder;
|
|
75
|
+
const additions = [];
|
|
76
|
+
if (!current.includes(".aidimag/memory.db"))
|
|
77
|
+
additions.push(".aidimag/memory.db*");
|
|
78
|
+
// keep dropped knowledge docs (may contain secrets) out of git, but track the folder
|
|
79
|
+
if (!current.includes(`${folder}/*`))
|
|
80
|
+
additions.push(`${folder}/*`, `!${folder}/.gitkeep`);
|
|
81
|
+
// generated context files (users can commit them if they want, but default is gitignored)
|
|
82
|
+
if (!current.includes("CLAUDE.md"))
|
|
83
|
+
additions.push("CLAUDE.md");
|
|
84
|
+
if (!current.includes(".cursorrules"))
|
|
85
|
+
additions.push(".cursorrules");
|
|
86
|
+
if (!current.includes(".windsurfrules"))
|
|
87
|
+
additions.push(".windsurfrules");
|
|
88
|
+
if (!current.includes("AGENTS.md"))
|
|
89
|
+
additions.push("AGENTS.md");
|
|
90
|
+
if (additions.length) {
|
|
91
|
+
appendFileSync(rootIgnore, `${current.endsWith("\n") || current === "" ? "" : "\n"}${additions.join("\n")}\n`);
|
|
92
|
+
console.log(`\nUpdated ${rootIgnore} (ignored memory.db + ${folder}/ drops + generated context files)`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
console.log(`\nNext: \`dim bootstrap\` gives this repo an instant starter brain (surveys docs/structure/history, queues reviewable memories).`);
|
|
96
|
+
// Ask if user wants to generate context files for non-MCP tools
|
|
97
|
+
const { createPrompter } = await import("../shared.js");
|
|
98
|
+
const prompter = await createPrompter();
|
|
99
|
+
console.log(`\n📝 Generate context files for non-MCP AI tools?`);
|
|
100
|
+
console.log(` This creates CLAUDE.md, .cursorrules, .windsurfrules, AGENTS.md, and copilot-instructions.md`);
|
|
101
|
+
console.log(` (useful for Copilot, Cursor, Windsurf, and other tools that read static files)`);
|
|
102
|
+
console.log(` Options: y (all) | n (skip) | claude | cursorrules | copilot | windsurfrules | agents\n`);
|
|
103
|
+
const choice = (await prompter.ask("Generate now? ")).trim().toLowerCase();
|
|
104
|
+
prompter.close();
|
|
105
|
+
if (choice === "y" || choice === "all" || ["claude", "cursorrules", "copilot", "windsurfrules", "agents"].includes(choice)) {
|
|
106
|
+
const { generateContext } = await import("../../context/generate.js");
|
|
107
|
+
const { writeConfig } = await import("../../config.js");
|
|
108
|
+
const format = choice === "y" || choice === "all" ? "all" : choice;
|
|
109
|
+
const store = MemoryStore.open(root);
|
|
110
|
+
const r = generateContext(store, root, format);
|
|
111
|
+
store.close();
|
|
112
|
+
writeConfig(root, { generateContext: { auto: true, format: format } });
|
|
113
|
+
console.log(`\n✅ Generated ${r.files.join(", ")} with auto-regeneration enabled.`);
|
|
114
|
+
if (r.total === 0) {
|
|
115
|
+
console.log(` (no memories yet — files will populate after \`dim bootstrap\` or \`dim remember\`)`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
console.log(`\n Skipped. Run \`dim generate-context --auto --format all\` later if needed.`);
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
program
|
|
123
|
+
.command("remember")
|
|
124
|
+
.description("Store a memory (write the claim as a falsifiable statement)")
|
|
125
|
+
.argument("<claim>", "The claim to remember")
|
|
126
|
+
.option("-k, --kind <kind>", `Memory kind: ${KINDS.join("|")}`, "GOTCHA")
|
|
127
|
+
.option("-p, --path <paths...>", "Paths this memory applies to")
|
|
128
|
+
.option("-s, --symbol <symbols...>", "Symbols this memory applies to")
|
|
129
|
+
.option("-e, --evidence <spec...>", "Evidence as TYPE:payload, e.g. COMMIT_REF:abc123 or STATIC_CHECK:'grep ...'")
|
|
130
|
+
.option("-g, --guardrail-level <level>", `For kind=GUARDRAIL: ${GUARDRAIL_LEVELS.join("|")}`)
|
|
131
|
+
.option("--pin", "Pin the memory: it never decays with age (evidence failure can still mark it stale)")
|
|
132
|
+
.action(async (claim, opts) => {
|
|
133
|
+
const kind = String(opts.kind).toUpperCase();
|
|
134
|
+
if (!KINDS.includes(kind))
|
|
135
|
+
fail(`invalid kind '${opts.kind}'. Use one of: ${KINDS.join(", ")}`);
|
|
136
|
+
let guardrailLevel;
|
|
137
|
+
if (kind === "GUARDRAIL") {
|
|
138
|
+
guardrailLevel = (opts.guardrailLevel ?? "ask-first");
|
|
139
|
+
if (!GUARDRAIL_LEVELS.includes(guardrailLevel)) {
|
|
140
|
+
fail(`invalid --guardrail-level '${opts.guardrailLevel}'. Use one of: ${GUARDRAIL_LEVELS.join(", ")}`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
else if (opts.guardrailLevel) {
|
|
144
|
+
fail("--guardrail-level only applies to --kind GUARDRAIL");
|
|
145
|
+
}
|
|
146
|
+
const evidence = opts.evidence?.map((spec) => {
|
|
147
|
+
const idx = spec.indexOf(":");
|
|
148
|
+
if (idx < 1)
|
|
149
|
+
fail(`invalid evidence '${spec}'. Format: TYPE:payload`);
|
|
150
|
+
const type = spec.slice(0, idx).toUpperCase();
|
|
151
|
+
return { type, payload: spec.slice(idx + 1) };
|
|
152
|
+
});
|
|
153
|
+
const store = MemoryStore.open(process.cwd(), { create: true });
|
|
154
|
+
const entry = store.write({
|
|
155
|
+
kind, claim, paths: opts.path, symbols: opts.symbol, evidence,
|
|
156
|
+
createdBy: "human", pinned: Boolean(opts.pin), guardrailLevel,
|
|
157
|
+
trustExecutableEvidence: true,
|
|
158
|
+
});
|
|
159
|
+
console.log("🧠 Got it — I'll remember:");
|
|
160
|
+
printMemory(entry, true);
|
|
161
|
+
if (!evidence?.length) {
|
|
162
|
+
console.log(`\nTip: claims with evidence re-verify themselves as the code evolves —\n` +
|
|
163
|
+
` e.g. -e "STATIC_CHECK:grep -q something src/file.ts"`);
|
|
164
|
+
}
|
|
165
|
+
await indexMemory(store, entry).catch(() => false);
|
|
166
|
+
await autoSync(store);
|
|
167
|
+
store.close();
|
|
168
|
+
});
|
|
169
|
+
program
|
|
170
|
+
.command("recall")
|
|
171
|
+
.description("Search memories — hybrid keyword + semantic when embeddings are configured")
|
|
172
|
+
.argument("[query...]", "Keywords to search")
|
|
173
|
+
.option("-p, --path <paths...>", "Restrict to memories scoped to these paths")
|
|
174
|
+
.option("-k, --kind <kind>", "Filter by kind")
|
|
175
|
+
.option("-n, --limit <n>", "Max results", "10")
|
|
176
|
+
.option("--all", "Include refuted memories")
|
|
177
|
+
.action(async (query, opts) => {
|
|
178
|
+
const store = MemoryStore.open();
|
|
179
|
+
const { results, semantic } = await hybridSearch(store, {
|
|
180
|
+
query: query.join(" "),
|
|
181
|
+
paths: opts.path,
|
|
182
|
+
kind: opts.kind ? String(opts.kind).toUpperCase() : undefined,
|
|
183
|
+
limit: parseInt(opts.limit, 10),
|
|
184
|
+
includeRefuted: Boolean(opts.all),
|
|
185
|
+
});
|
|
186
|
+
if (query.length) {
|
|
187
|
+
try {
|
|
188
|
+
store.logSearch(query.join(" "), opts.path ?? [], results.length, "cli");
|
|
189
|
+
}
|
|
190
|
+
catch (err) {
|
|
191
|
+
// gap logging is best-effort; never break recall
|
|
192
|
+
debugLog("cli search-gap logging", err);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
if (results.length === 0)
|
|
196
|
+
console.log("No matching memories.");
|
|
197
|
+
for (const m of results)
|
|
198
|
+
printMemory(m, true);
|
|
199
|
+
if (query.length && !semantic) {
|
|
200
|
+
console.log("\n(keyword search only — set up Ollama or OPENAI_API_KEY for semantic recall, then `dim reindex`)");
|
|
201
|
+
}
|
|
202
|
+
store.close();
|
|
203
|
+
});
|
|
204
|
+
program
|
|
205
|
+
.command("reindex")
|
|
206
|
+
.description("Build/refresh semantic embeddings for all memories")
|
|
207
|
+
.action(async () => {
|
|
208
|
+
const store = MemoryStore.open();
|
|
209
|
+
if (!store.vecAvailable)
|
|
210
|
+
fail("sqlite-vec extension failed to load on this platform");
|
|
211
|
+
const { indexed, provider } = await reindexAll(store);
|
|
212
|
+
if (!provider) {
|
|
213
|
+
fail("no embedding provider available — run Ollama locally or set OPENAI_API_KEY (see AIDIMAG_EMBEDDINGS)");
|
|
214
|
+
}
|
|
215
|
+
console.log(`Indexed ${indexed} memorie(s) with ${provider.name}/${provider.model} (${provider.dim}d).`);
|
|
216
|
+
store.close();
|
|
217
|
+
});
|
|
218
|
+
program
|
|
219
|
+
.command("status")
|
|
220
|
+
.description("Memory store summary")
|
|
221
|
+
.action(() => {
|
|
222
|
+
const store = MemoryStore.open();
|
|
223
|
+
const s = store.statusSummary();
|
|
224
|
+
console.log(`aidimag @ ${s.dbPath}`);
|
|
225
|
+
console.log(`total memories: ${s.total}`);
|
|
226
|
+
console.log(` by status: ${Object.entries(s.byStatus).map(([k, v]) => `${k}=${v}`).join(" ")}`);
|
|
227
|
+
if (Object.keys(s.byKind).length) {
|
|
228
|
+
console.log(` by kind: ${Object.entries(s.byKind).map(([k, v]) => `${k}=${v}`).join(" ")}`);
|
|
229
|
+
}
|
|
230
|
+
if (s.pinned) {
|
|
231
|
+
console.log(` pinned: ${s.pinned} (exempt from time decay)`);
|
|
232
|
+
}
|
|
233
|
+
if (s.pendingProposals) {
|
|
234
|
+
console.log(`\n${s.pendingProposals} proposal(s) awaiting review — run \`dim review\``);
|
|
235
|
+
}
|
|
236
|
+
store.close();
|
|
237
|
+
});
|
|
238
|
+
program
|
|
239
|
+
.command("log")
|
|
240
|
+
.description("Show recent memories")
|
|
241
|
+
.option("-n, --limit <n>", "Max entries", "20")
|
|
242
|
+
.action((opts) => {
|
|
243
|
+
const store = MemoryStore.open();
|
|
244
|
+
const memories = store.list(parseInt(opts.limit, 10));
|
|
245
|
+
if (memories.length === 0)
|
|
246
|
+
console.log("No memories yet. Try `dim remember \"...\"`.");
|
|
247
|
+
for (const m of memories)
|
|
248
|
+
printMemory(m);
|
|
249
|
+
store.close();
|
|
250
|
+
});
|
|
251
|
+
program
|
|
252
|
+
.command("gaps")
|
|
253
|
+
.description("Knowledge gaps: searches agents/you ran that returned NOTHING — the facts your brain is missing")
|
|
254
|
+
.option("-d, --days <n>", "Look-back window in days", "30")
|
|
255
|
+
.option("-n, --limit <n>", "Max entries", "20")
|
|
256
|
+
.option("--clear", "Clear the search log after showing")
|
|
257
|
+
.action((opts) => {
|
|
258
|
+
const store = MemoryStore.open();
|
|
259
|
+
const gaps = store.searchGaps({ sinceDays: parseInt(opts.days, 10), limit: parseInt(opts.limit, 10) });
|
|
260
|
+
if (gaps.length === 0) {
|
|
261
|
+
console.log(`No knowledge gaps in the last ${opts.days} day(s) — every search found something.`);
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
console.log(`${gaps.length} knowledge gap(s) in the last ${opts.days} day(s) — most-asked first:\n`);
|
|
265
|
+
for (const g of gaps) {
|
|
266
|
+
const scope = g.paths.length ? ` [scope: ${g.paths.join(", ")}]` : "";
|
|
267
|
+
console.log(` ${String(g.misses).padStart(3)}× "${g.query}"${scope} (last: ${g.lastAsked.slice(0, 10)})`);
|
|
268
|
+
}
|
|
269
|
+
console.log(`\nFill a gap: dim remember "<the answer>" -k <kind> [-e TYPE:proof]`);
|
|
270
|
+
}
|
|
271
|
+
if (opts.clear) {
|
|
272
|
+
const n = store.clearSearchGaps();
|
|
273
|
+
console.log(`\nCleared ${n} logged search(es).`);
|
|
274
|
+
}
|
|
275
|
+
store.close();
|
|
276
|
+
});
|
|
277
|
+
program
|
|
278
|
+
.command("refute")
|
|
279
|
+
.description("Mark a memory REFUTED (kept as negative knowledge, unlike forget)")
|
|
280
|
+
.argument("<id>", "Memory id (full or 8-char prefix)")
|
|
281
|
+
.option("-s, --superseded-by <id>", "Id of a newer memory replacing it")
|
|
282
|
+
.action(async (id, opts) => {
|
|
283
|
+
const store = MemoryStore.open();
|
|
284
|
+
const match = store.list(1000).find((m) => m.id === id || m.id.startsWith(id));
|
|
285
|
+
if (!match)
|
|
286
|
+
fail(`no memory matching id '${id}'`);
|
|
287
|
+
store.refute(match.id, opts.supersededBy);
|
|
288
|
+
console.log(`✗ refuted ${match.id.slice(0, 8)}: "${match.claim}"`);
|
|
289
|
+
await autoSync(store);
|
|
290
|
+
store.close();
|
|
291
|
+
});
|
|
292
|
+
program
|
|
293
|
+
.command("pin")
|
|
294
|
+
.description("Pin a memory: it stays with the project forever — never decays with age (evidence failure can still mark it stale)")
|
|
295
|
+
.argument("<id>", "Memory id (full or 8-char prefix)")
|
|
296
|
+
.action(async (id) => {
|
|
297
|
+
const store = MemoryStore.open();
|
|
298
|
+
const match = store.list(1000).find((m) => m.id === id || m.id.startsWith(id));
|
|
299
|
+
if (!match)
|
|
300
|
+
fail(`no memory matching id '${id}'`);
|
|
301
|
+
store.setPinned(match.id, true);
|
|
302
|
+
console.log(`📌 pinned ${match.id.slice(0, 8)}: "${match.claim}"`);
|
|
303
|
+
console.log(` It won't decay with age. Evidence checks still apply — a failing check marks it stale.`);
|
|
304
|
+
await autoSync(store);
|
|
305
|
+
store.close();
|
|
306
|
+
});
|
|
307
|
+
program
|
|
308
|
+
.command("unpin")
|
|
309
|
+
.description("Unpin a memory — normal confidence decay resumes")
|
|
310
|
+
.argument("<id>", "Memory id (full or 8-char prefix)")
|
|
311
|
+
.action(async (id) => {
|
|
312
|
+
const store = MemoryStore.open();
|
|
313
|
+
const match = store.list(1000).find((m) => m.id === id || m.id.startsWith(id));
|
|
314
|
+
if (!match)
|
|
315
|
+
fail(`no memory matching id '${id}'`);
|
|
316
|
+
store.setPinned(match.id, false);
|
|
317
|
+
console.log(`unpinned ${match.id.slice(0, 8)}: "${match.claim}" — normal decay resumes.`);
|
|
318
|
+
await autoSync(store);
|
|
319
|
+
store.close();
|
|
320
|
+
});
|
|
321
|
+
program
|
|
322
|
+
.command("update")
|
|
323
|
+
.description("Update a memory's claim, kind, or add/remove evidence")
|
|
324
|
+
.argument("<id>", "Memory id (full or 8-char prefix)")
|
|
325
|
+
.option("-c, --claim <text>", "Update the claim text")
|
|
326
|
+
.option("-k, --kind <kind>", `Change memory kind: ${KINDS.join("|")}`)
|
|
327
|
+
.option("-g, --guardrail-level <level>", `For kind=GUARDRAIL: ${GUARDRAIL_LEVELS.join("|")}`)
|
|
328
|
+
.option("-e, --evidence <ev...>", "Add evidence (format: TYPE:payload)")
|
|
329
|
+
.option("--remove-evidence <id>", "Remove evidence by id prefix")
|
|
330
|
+
.action(async (id, opts) => {
|
|
331
|
+
const store = MemoryStore.open();
|
|
332
|
+
const match = store.list(1000).find((m) => m.id === id || m.id.startsWith(id));
|
|
333
|
+
if (!match)
|
|
334
|
+
fail(`no memory matching id '${id}'`);
|
|
335
|
+
const updates = {};
|
|
336
|
+
if (opts.claim)
|
|
337
|
+
updates.claim = opts.claim.trim();
|
|
338
|
+
if (opts.kind) {
|
|
339
|
+
if (!KINDS.includes(opts.kind))
|
|
340
|
+
fail(`invalid kind '${opts.kind}' — must be one of: ${KINDS.join(", ")}`);
|
|
341
|
+
updates.kind = opts.kind;
|
|
342
|
+
}
|
|
343
|
+
if (opts.guardrailLevel) {
|
|
344
|
+
if (updates.kind !== "GUARDRAIL" && match.kind !== "GUARDRAIL") {
|
|
345
|
+
fail("--guardrail-level only applies to GUARDRAIL memories");
|
|
346
|
+
}
|
|
347
|
+
if (!GUARDRAIL_LEVELS.includes(opts.guardrailLevel)) {
|
|
348
|
+
fail(`invalid --guardrail-level '${opts.guardrailLevel}' — must be one of: ${GUARDRAIL_LEVELS.join(", ")}`);
|
|
349
|
+
}
|
|
350
|
+
updates.guardrailLevel = opts.guardrailLevel;
|
|
351
|
+
}
|
|
352
|
+
if (Object.keys(updates).length > 0) {
|
|
353
|
+
store.update(match.id, updates);
|
|
354
|
+
console.log(`✓ Updated memory ${match.id.slice(0, 8)}`);
|
|
355
|
+
}
|
|
356
|
+
// Add evidence
|
|
357
|
+
for (const ev of opts.evidence || []) {
|
|
358
|
+
const [type, ...payloadParts] = ev.split(":");
|
|
359
|
+
const payload = payloadParts.join(":");
|
|
360
|
+
if (!payload)
|
|
361
|
+
fail(`evidence format: TYPE:payload (e.g. STATIC_CHECK:grep -r "foo" src/)`);
|
|
362
|
+
store.addEvidence(match.id, { type: type, payload });
|
|
363
|
+
console.log(`✓ Added evidence: ${type}`);
|
|
364
|
+
}
|
|
365
|
+
// Remove evidence
|
|
366
|
+
if (opts.removeEvidence) {
|
|
367
|
+
const evidence = match.grounding.find((e) => e.id === opts.removeEvidence || e.id.startsWith(opts.removeEvidence));
|
|
368
|
+
if (!evidence)
|
|
369
|
+
fail(`no evidence matching id '${opts.removeEvidence}'`);
|
|
370
|
+
store.removeEvidence(evidence.id);
|
|
371
|
+
console.log(`✓ Removed evidence ${evidence.id.slice(0, 8)}`);
|
|
372
|
+
}
|
|
373
|
+
await autoSync(store);
|
|
374
|
+
store.close();
|
|
375
|
+
});
|
|
376
|
+
program
|
|
377
|
+
.command("forget")
|
|
378
|
+
.description("Delete a memory permanently (prefer refuting via agents)")
|
|
379
|
+
.argument("<id>", "Memory id (full or 8-char prefix)")
|
|
380
|
+
.action(async (id) => {
|
|
381
|
+
const store = MemoryStore.open();
|
|
382
|
+
// allow prefix match
|
|
383
|
+
const match = store.list(1000).find((m) => m.id === id || m.id.startsWith(id));
|
|
384
|
+
if (!match)
|
|
385
|
+
fail(`no memory matching id '${id}'`);
|
|
386
|
+
store.forget(match.id);
|
|
387
|
+
console.log(`Forgot memory ${match.id}: "${match.claim}"`);
|
|
388
|
+
await autoSync(store);
|
|
389
|
+
store.close();
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
//# sourceMappingURL=memory.js.map
|