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.
Files changed (91) hide show
  1. package/LICENSE +102 -0
  2. package/README.md +113 -0
  3. package/dist/capture/bootstrap.d.ts +25 -0
  4. package/dist/capture/bootstrap.js +188 -0
  5. package/dist/capture/commit-miner.d.ts +59 -0
  6. package/dist/capture/commit-miner.js +381 -0
  7. package/dist/capture/harvest.d.ts +50 -0
  8. package/dist/capture/harvest.js +207 -0
  9. package/dist/capture/pr-miner.d.ts +57 -0
  10. package/dist/capture/pr-miner.js +185 -0
  11. package/dist/capture/session-briefing.d.ts +36 -0
  12. package/dist/capture/session-briefing.js +150 -0
  13. package/dist/capture/session-extraction.d.ts +23 -0
  14. package/dist/capture/session-extraction.js +54 -0
  15. package/dist/capture/triage.d.ts +30 -0
  16. package/dist/capture/triage.js +108 -0
  17. package/dist/cli/commands/capture.d.ts +5 -0
  18. package/dist/cli/commands/capture.js +357 -0
  19. package/dist/cli/commands/hosts.d.ts +5 -0
  20. package/dist/cli/commands/hosts.js +98 -0
  21. package/dist/cli/commands/knowledge.d.ts +5 -0
  22. package/dist/cli/commands/knowledge.js +121 -0
  23. package/dist/cli/commands/memory.d.ts +6 -0
  24. package/dist/cli/commands/memory.js +392 -0
  25. package/dist/cli/commands/sync.d.ts +5 -0
  26. package/dist/cli/commands/sync.js +307 -0
  27. package/dist/cli/commands/tickets.d.ts +6 -0
  28. package/dist/cli/commands/tickets.js +328 -0
  29. package/dist/cli/commands/verify.d.ts +5 -0
  30. package/dist/cli/commands/verify.js +136 -0
  31. package/dist/cli/index.d.ts +19 -0
  32. package/dist/cli/index.js +46 -0
  33. package/dist/cli/shared.d.ts +37 -0
  34. package/dist/cli/shared.js +175 -0
  35. package/dist/config.d.ts +55 -0
  36. package/dist/config.js +51 -0
  37. package/dist/context/generate.d.ts +24 -0
  38. package/dist/context/generate.js +115 -0
  39. package/dist/critique/critique.d.ts +40 -0
  40. package/dist/critique/critique.js +110 -0
  41. package/dist/db/schema.d.ts +28 -0
  42. package/dist/db/schema.js +269 -0
  43. package/dist/db/store.d.ts +182 -0
  44. package/dist/db/store.js +906 -0
  45. package/dist/debug.d.ts +14 -0
  46. package/dist/debug.js +25 -0
  47. package/dist/embeddings/provider.d.ts +16 -0
  48. package/dist/embeddings/provider.js +100 -0
  49. package/dist/embeddings/search.d.ts +22 -0
  50. package/dist/embeddings/search.js +95 -0
  51. package/dist/index.d.ts +11 -0
  52. package/dist/index.js +12 -0
  53. package/dist/knowledge/chunk.d.ts +9 -0
  54. package/dist/knowledge/chunk.js +78 -0
  55. package/dist/knowledge/extract.d.ts +34 -0
  56. package/dist/knowledge/extract.js +113 -0
  57. package/dist/knowledge/ingest.d.ts +79 -0
  58. package/dist/knowledge/ingest.js +305 -0
  59. package/dist/knowledge/llm.d.ts +21 -0
  60. package/dist/knowledge/llm.js +110 -0
  61. package/dist/mcp/server.d.ts +11 -0
  62. package/dist/mcp/server.js +532 -0
  63. package/dist/security/evidence.d.ts +11 -0
  64. package/dist/security/evidence.js +15 -0
  65. package/dist/security/seal.d.ts +3 -0
  66. package/dist/security/seal.js +28 -0
  67. package/dist/security/sync-push.d.ts +2 -0
  68. package/dist/security/sync-push.js +37 -0
  69. package/dist/security/url.d.ts +6 -0
  70. package/dist/security/url.js +67 -0
  71. package/dist/sync/client.d.ts +84 -0
  72. package/dist/sync/client.js +391 -0
  73. package/dist/sync/server.d.ts +75 -0
  74. package/dist/sync/server.js +659 -0
  75. package/dist/tickets/provider.d.ts +80 -0
  76. package/dist/tickets/provider.js +375 -0
  77. package/dist/types.d.ts +133 -0
  78. package/dist/types.js +5 -0
  79. package/dist/ui/page.d.ts +5 -0
  80. package/dist/ui/page.js +841 -0
  81. package/dist/ui/server.d.ts +10 -0
  82. package/dist/ui/server.js +437 -0
  83. package/dist/verify/check.d.ts +38 -0
  84. package/dist/verify/check.js +121 -0
  85. package/dist/verify/engine.d.ts +39 -0
  86. package/dist/verify/engine.js +178 -0
  87. package/dist/verify/hooks.d.ts +24 -0
  88. package/dist/verify/hooks.js +125 -0
  89. package/dist/verify/runners.d.ts +26 -0
  90. package/dist/verify/runners.js +193 -0
  91. package/package.json +78 -0
@@ -0,0 +1,357 @@
1
+ /**
2
+ * Capture & review commands: mine (commits/PRs), bootstrap, harvest, review.
3
+ */
4
+ import { existsSync } from "node:fs";
5
+ import path from "node:path";
6
+ import { MemoryStore, findRepoRoot } from "../../db/store.js";
7
+ import { mineCommits, describeMineResult } from "../../capture/commit-miner.js";
8
+ import { fail, autoSync, maybeRegenerateContext, printProposal, createPrompter } from "../shared.js";
9
+ /**
10
+ * Conversational review: walk the queue one proposal at a time —
11
+ * keep / reword / drop / skip. The human gate, made friendly.
12
+ */
13
+ async function interactiveReview(store) {
14
+ const { triagePending } = await import("../../capture/triage.js");
15
+ const triaged = triagePending(store, 1000);
16
+ const pending = triaged.map((t) => t.proposal);
17
+ const scoreOf = new Map(triaged.map((t) => [t.proposal.id, t]));
18
+ if (pending.length === 0) {
19
+ console.log("✨ Nothing waiting on you — the review queue is empty.");
20
+ return { kept: 0, rejected: 0 };
21
+ }
22
+ const { ask, close } = await createPrompter("q"); // closed stdin = quit
23
+ let kept = 0;
24
+ let rejected = 0;
25
+ let skipped = 0;
26
+ // T2: lazy ticket enrichment — fetched at review time, never at capture time
27
+ const reviewRoot = findRepoRoot();
28
+ const { ticketProviderFor } = await import("../../tickets/provider.js");
29
+ const provider = reviewRoot ? ticketProviderFor(reviewRoot) : null;
30
+ const plural = pending.length === 1 ? "proposal is" : "proposals are";
31
+ console.log(`🧠 ${pending.length} memory ${plural} waiting for your review (best first).\n`);
32
+ try {
33
+ for (let i = 0; i < pending.length; i++) {
34
+ const p = pending[i];
35
+ const src = p.source === "commit-miner" ? `mined from commit ${p.sourceRef?.slice(0, 8) ?? "?"}` : `proposed by ${p.source}`;
36
+ const tri = scoreOf.get(p.id);
37
+ console.log(`── ${i + 1} of ${pending.length} ── ${p.kind} · ${src}${tri ? ` · score ${tri.score.toFixed(2)}` : ""}`);
38
+ if (tri?.reasons.length)
39
+ console.log(` (${tri.reasons.join(", ")})`);
40
+ console.log(`\n “${p.claim}”\n`);
41
+ if (p.paths.length || p.symbols.length)
42
+ console.log(` applies to: ${[...p.paths, ...p.symbols].join(", ")}`);
43
+ if (p.evidence.length)
44
+ console.log(` evidence: ${p.evidence.map((e) => `${e.type}:${e.payload.slice(0, 60)}`).join(" ")}`);
45
+ if (p.ticketRef) {
46
+ let ticketLine = p.ticketRef;
47
+ if (provider) {
48
+ const t = await provider.getTicket(p.ticketRef).catch(() => null);
49
+ if (t) {
50
+ ticketLine = `${t.id} “${t.title}” (${t.type}, ${t.status}) — ${t.url}`;
51
+ if (t.body)
52
+ console.log(` ticket: ${ticketLine}\n ${t.body.slice(0, 200).replace(/\s+/g, " ")}${t.body.length > 200 ? "…" : ""}`);
53
+ else
54
+ console.log(` ticket: ${ticketLine}`);
55
+ }
56
+ else {
57
+ console.log(` ticket: ${ticketLine} (couldn't fetch — provider offline or ticket missing)`);
58
+ }
59
+ }
60
+ else {
61
+ console.log(` ticket: ${ticketLine}`);
62
+ }
63
+ }
64
+ if (p.rationale)
65
+ console.log(` why: ${p.rationale}`);
66
+ const ans = (await ask("\n Keep this? [y]es · [e]dit wording · [n]o, drop it · [s]kip · [q]uit "))
67
+ .trim()
68
+ .toLowerCase();
69
+ if (ans === "q" || ans === "quit") {
70
+ skipped += pending.length - i;
71
+ break;
72
+ }
73
+ else if (ans === "y" || ans === "yes") {
74
+ const m = store.approveProposal(p.id);
75
+ kept++;
76
+ console.log(` ✓ Remembered (${m.id.slice(0, 8)}).\n`);
77
+ }
78
+ else if (ans === "e" || ans === "edit") {
79
+ const claim = (await ask(" Your wording (enter keeps the original):\n › ")).trim();
80
+ const m = store.approveProposal(p.id, claim ? { claim } : undefined);
81
+ kept++;
82
+ console.log(claim ? ` ✓ Remembered with your wording (${m.id.slice(0, 8)}).\n` : ` ✓ Remembered as-is (${m.id.slice(0, 8)}).\n`);
83
+ }
84
+ else if (ans === "n" || ans === "no") {
85
+ store.rejectProposal(p.id);
86
+ rejected++;
87
+ console.log(" ✗ Dropped — it won't be proposed again.\n");
88
+ }
89
+ else {
90
+ skipped++;
91
+ console.log(" ↷ Skipped — it'll be here next time.\n");
92
+ }
93
+ }
94
+ }
95
+ finally {
96
+ close();
97
+ }
98
+ const bits = [
99
+ kept ? `${kept} remembered` : null,
100
+ rejected ? `${rejected} dropped` : null,
101
+ skipped ? `${skipped} left for later` : null,
102
+ ].filter(Boolean);
103
+ console.log(`Done — ${bits.length ? bits.join(", ") : "no changes"}.${kept ? " Run `dim verify` to put the new memories to the test." : ""}`);
104
+ return { kept, rejected };
105
+ }
106
+ export function registerCaptureCommands(program) {
107
+ program
108
+ .command("mine")
109
+ .description("Mine git history for memory candidates (queued for review, never auto-saved)")
110
+ .option("-n, --max <n>", "Max commits to scan", "500")
111
+ .option("--full", "Rescan from the beginning of history (ignore cursor)")
112
+ .option("--llm", "Deep mining: LLM reads each commit's message AND diff, synthesizes claims + suggested checks (needs Ollama/OPENAI_API_KEY; slower, much higher quality)")
113
+ .option("--prs", "Mine merged GitHub PRs + review comments instead of commits (needs the `gh` CLI and an LLM provider; review threads carry the unwritten rules)")
114
+ .option("-q, --quiet", "Only speak up when candidates are found (for the post-commit hook)")
115
+ .action(async (opts) => {
116
+ const root = findRepoRoot() ?? fail("not inside a git repo");
117
+ if (!existsSync(path.join(root, ".git")))
118
+ fail("commit mining requires a git repo");
119
+ const store = MemoryStore.open(root, { create: true });
120
+ if (opts.prs) {
121
+ const { minePrs, ghAvailable } = await import("../../capture/pr-miner.js");
122
+ if (!ghAvailable(root)) {
123
+ store.close();
124
+ fail("PR mining needs the GitHub CLI — install `gh` and run `gh auth login`");
125
+ }
126
+ const r = await minePrs(store, root, { max: opts.max ? parseInt(opts.max, 10) : undefined, all: Boolean(opts.full) });
127
+ if (!r.provider) {
128
+ store.close();
129
+ fail("no LLM provider available — review threads need synthesis; run Ollama or set OPENAI_API_KEY (see AIDIMAG_LLM)");
130
+ }
131
+ console.log(`Scanned ${r.scanned} merged PR(s) with ${r.provider}: ${r.proposed.length} proposal(s) queued` +
132
+ (r.skippedDuplicates ? `, ${r.skippedDuplicates} duplicate(s) skipped` : ""));
133
+ for (const p of r.proposed)
134
+ printProposal(p);
135
+ if (r.proposed.length)
136
+ console.log(`\nReview with \`dim review\`.`);
137
+ else if (r.scanned === 0)
138
+ console.log("No newly merged PRs since the last run (use --full to rescan).");
139
+ store.close();
140
+ return;
141
+ }
142
+ let res;
143
+ let llmProvider = null;
144
+ if (opts.llm) {
145
+ const { mineCommitsLlm } = await import("../../capture/commit-miner.js");
146
+ const r = await mineCommitsLlm(store, root, {
147
+ maxCommits: parseInt(opts.max, 10),
148
+ full: Boolean(opts.full),
149
+ });
150
+ res = r;
151
+ llmProvider = r.provider;
152
+ if (!llmProvider && !opts.quiet) {
153
+ console.log("(no LLM provider available — fell back to keyword mining; run Ollama or set OPENAI_API_KEY)");
154
+ }
155
+ }
156
+ else {
157
+ res = mineCommits(store, root, {
158
+ maxCommits: parseInt(opts.max, 10),
159
+ full: Boolean(opts.full),
160
+ });
161
+ }
162
+ if (opts.quiet) {
163
+ // post-commit hook mode: a single gentle nudge, nothing else
164
+ if (res.proposed.length > 0) {
165
+ const total = store.listProposals("PENDING", 1000).length;
166
+ console.log(`🧠 aidimag: this commit looks memory-worthy — ${res.proposed.length} proposal(s) queued` +
167
+ ` (${total} pending). Review with \`dim review\`.`);
168
+ }
169
+ store.close();
170
+ return;
171
+ }
172
+ if (res.noCommits || res.noNewCommits) {
173
+ console.log(describeMineResult(res, { llmProvider, llmRequested: Boolean(opts.llm) }));
174
+ store.close();
175
+ return;
176
+ }
177
+ console.log(describeMineResult(res, { llmProvider, llmRequested: Boolean(opts.llm) }));
178
+ for (const p of res.proposed)
179
+ printProposal(p);
180
+ store.close();
181
+ });
182
+ program
183
+ .command("bootstrap")
184
+ .description("Give a fresh repo an instant brain: survey README/docs/manifests/structure/churn and LLM-extract an initial memory set (queued for review)")
185
+ .option("--force", "Re-run even if this repo was already bootstrapped")
186
+ .action(async (opts) => {
187
+ const root = findRepoRoot() ?? fail("not inside a git repo");
188
+ const store = MemoryStore.open(root, { create: true });
189
+ const { bootstrapRepo } = await import("../../capture/bootstrap.js");
190
+ console.log("Surveying the repo (docs, manifests, structure, churn)…");
191
+ const res = await bootstrapRepo(store, root, { force: Boolean(opts.force) });
192
+ if (res.alreadyBootstrapped) {
193
+ console.log("Already bootstrapped — use --force to re-run (dedupe absorbs repeats).");
194
+ }
195
+ else if (!res.provider) {
196
+ fail("no LLM provider available — run Ollama locally or set OPENAI_API_KEY (see AIDIMAG_LLM)");
197
+ }
198
+ else {
199
+ console.log(`Surveyed ${res.surveyedFiles.length} file(s) with ${res.provider}: ` +
200
+ `${res.proposed} proposal(s) queued${res.duplicates ? `, ${res.duplicates} duplicate(s) skipped` : ""}.`);
201
+ if (res.proposed) {
202
+ console.log(`\nYour repo's starter brain is ready for review: \`dim review\``);
203
+ console.log(`(then \`dim verify\` to put the suggested checks to the test)`);
204
+ }
205
+ else {
206
+ console.log("No durable claims extracted — the survey found little written-down knowledge. Feed docs into knowledge/ or use `dim mine --llm`.");
207
+ }
208
+ }
209
+ store.close();
210
+ });
211
+ program
212
+ .command("harvest")
213
+ .description("Harvest durable facts YOU typed into AI chats (Claude Code transcripts) into the review queue — local-only, secrets redacted")
214
+ .option("--all", "Rescan every session (ignore cursor; dedupe absorbs repeats)")
215
+ .option("--install-hook", "Wire `dim harvest -q` into this repo's Claude Code SessionEnd hook (.claude/settings.json)")
216
+ .option("-q, --quiet", "Only speak up when proposals are queued (for the SessionEnd hook)")
217
+ .action(async (opts) => {
218
+ const root = findRepoRoot() ?? fail("not inside a git repo");
219
+ const { harvestClaudeSessions, installClaudeSessionEndHook, claudeProjectDir } = await import("../../capture/harvest.js");
220
+ if (opts.installHook) {
221
+ const { installed, settingsPath } = installClaudeSessionEndHook(root);
222
+ console.log(installed
223
+ ? `✓ SessionEnd hook installed in ${settingsPath} — every Claude Code session is now harvested on close.`
224
+ : `Hook already present in ${settingsPath} — nothing to do.`);
225
+ return;
226
+ }
227
+ const store = MemoryStore.open(root, { create: true });
228
+ const res = await harvestClaudeSessions(store, root, { all: Boolean(opts.all) });
229
+ if (opts.quiet) {
230
+ if (res.proposed > 0) {
231
+ console.log(`🧠 aidimag: harvested ${res.proposed} memory candidate(s) from your AI chat — review with \`dim review\`.`);
232
+ }
233
+ store.close();
234
+ return;
235
+ }
236
+ if (!res.transcriptDir) {
237
+ console.log(`No Claude Code transcripts found for this repo (${claudeProjectDir(root) ?? "~/.claude/projects/<repo-slug>"} missing).`);
238
+ console.log("Transcripts appear after your first Claude Code session here. Cursor/Copilot chat harvesting: planned.");
239
+ }
240
+ else if (!res.provider) {
241
+ fail("no LLM provider available — run Ollama locally or set OPENAI_API_KEY (see AIDIMAG_LLM)");
242
+ }
243
+ else if (res.sessionsScanned === 0) {
244
+ console.log("No new sessions since the last harvest. Use --all to rescan everything.");
245
+ }
246
+ else {
247
+ console.log(`Scanned ${res.sessionsScanned} session(s), ${res.messagesConsidered} user message(s) via ${res.provider}: ` +
248
+ `${res.proposed} proposal(s) queued` +
249
+ (res.duplicates ? `, ${res.duplicates} duplicate(s) skipped` : "") +
250
+ ".");
251
+ if (res.proposed)
252
+ console.log(`Review with \`dim review\`.`);
253
+ }
254
+ if (!opts.installHook && res.transcriptDir) {
255
+ console.log(`(tip: \`dim harvest --install-hook\` runs this automatically when each Claude Code session ends)`);
256
+ }
257
+ store.close();
258
+ });
259
+ program
260
+ .command("review")
261
+ .description("Review pending memory proposals — interactive walkthrough by default (list | approve | reject for scripting)")
262
+ .argument("[action]", "interactive (default in a terminal) | list | approve | reject")
263
+ .argument("[id]", "Proposal id (8-char prefix ok); 'all' with approve/reject applies to every pending proposal")
264
+ .option("-n, --limit <n>", "Max proposals to list", "50")
265
+ .option("--min-score <s>", "With 'approve all': only approve proposals triaged at or above this score (0–1)")
266
+ .action(async (action, id, opts) => {
267
+ const store = MemoryStore.open();
268
+ const effective = action ?? (process.stdin.isTTY && process.stdout.isTTY ? "interactive" : "list");
269
+ switch (effective) {
270
+ case "interactive": {
271
+ const { kept, rejected } = await interactiveReview(store);
272
+ if (kept + rejected > 0)
273
+ await autoSync(store);
274
+ break;
275
+ }
276
+ case "list": {
277
+ const { triagePending } = await import("../../capture/triage.js");
278
+ const triaged = triagePending(store, parseInt(opts.limit, 10));
279
+ if (triaged.length === 0)
280
+ console.log("No pending proposals.");
281
+ for (const t of triaged) {
282
+ console.log(` score ${t.score.toFixed(2)}${t.reasons.length ? ` (${t.reasons.join(", ")})` : ""}`);
283
+ printProposal(t.proposal);
284
+ }
285
+ if (triaged.length) {
286
+ console.log(`\nApprove: dim review approve <id> | Reject: dim review reject <id> | Walkthrough: dim review`);
287
+ console.log(`Batch: dim review approve all --min-score 0.7`);
288
+ }
289
+ break;
290
+ }
291
+ case "approve": {
292
+ if (!id)
293
+ fail("usage: dim review approve <id|all> [--min-score <s>]");
294
+ let targets;
295
+ if (id === "all") {
296
+ const { triagePending } = await import("../../capture/triage.js");
297
+ const minScore = opts.minScore !== undefined ? parseFloat(opts.minScore) : null;
298
+ const triaged = triagePending(store, 1000);
299
+ const chosen = minScore === null ? triaged : triaged.filter((t) => t.score >= minScore);
300
+ targets = chosen.map((t) => t.proposal.id);
301
+ if (minScore !== null) {
302
+ console.log(`${chosen.length} of ${triaged.length} pending proposal(s) scored ≥ ${minScore}.`);
303
+ }
304
+ }
305
+ else {
306
+ targets = [id];
307
+ }
308
+ for (const t of targets) {
309
+ const entry = store.approveProposal(t);
310
+ console.log(`✓ approved → memory ${entry.id.slice(0, 8)}: ${entry.claim}`);
311
+ }
312
+ break;
313
+ }
314
+ case "reject": {
315
+ if (!id)
316
+ fail("usage: dim review reject <id|all>");
317
+ const targets = id === "all" ? store.listProposals("PENDING", 1000).map((p) => p.id) : [id];
318
+ for (const t of targets) {
319
+ const p = store.rejectProposal(t);
320
+ console.log(`✗ rejected ${p.id.slice(0, 8)}: ${p.claim}`);
321
+ }
322
+ break;
323
+ }
324
+ default:
325
+ fail(`unknown action '${action}'. Use: list | approve | reject (or no action for the walkthrough)`);
326
+ }
327
+ if (effective === "approve" || effective === "reject")
328
+ await autoSync(store);
329
+ await maybeRegenerateContext(store);
330
+ store.close();
331
+ });
332
+ const proposalsCmd = program.command("proposals").description("Proposal housekeeping");
333
+ proposalsCmd
334
+ .command("gc")
335
+ .description("Remove resolved (approved/rejected) proposal rows and tombstone for team sync")
336
+ .option("--dry-run", "Report how many rows would be removed without deleting")
337
+ .action(async (opts) => {
338
+ const store = MemoryStore.open();
339
+ try {
340
+ const { removed } = store.gcResolvedProposals({ dryRun: Boolean(opts.dryRun) });
341
+ if (removed === 0) {
342
+ console.log("No resolved proposals to remove.");
343
+ }
344
+ else if (opts.dryRun) {
345
+ console.log(`${removed} resolved proposal row(s) would be removed. Run without --dry-run to apply.`);
346
+ }
347
+ else {
348
+ console.log(`Removed ${removed} resolved proposal row(s). Run \`dim sync\` to propagate deletions.`);
349
+ await autoSync(store);
350
+ }
351
+ }
352
+ finally {
353
+ store.close();
354
+ }
355
+ });
356
+ }
357
+ //# sourceMappingURL=capture.js.map
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Host-process & integration commands: ui (dashboard), generate-context, mcp.
3
+ */
4
+ import type { Command } from "commander";
5
+ export declare function registerHostCommands(program: Command): void;
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Host-process & integration commands: ui (dashboard), generate-context, mcp.
3
+ */
4
+ import { MemoryStore, findRepoRoot } from "../../db/store.js";
5
+ import { fail, openBrowser } from "../shared.js";
6
+ export function registerHostCommands(program) {
7
+ program
8
+ .command("ui")
9
+ .argument("[action]", "start (default) | stop")
10
+ .description("Manage the local web dashboard (memory list, review queue, visual graph)")
11
+ .option("-p, --port <n>", "Port", "4517")
12
+ .option("--no-open", "Don't open the browser automatically (start only)")
13
+ .action(async (action, opts) => {
14
+ const effectiveAction = action ?? "start";
15
+ if (effectiveAction === "stop") {
16
+ const port = parseInt(opts.port, 10);
17
+ const { exec } = await import("node:child_process");
18
+ const { promisify } = await import("node:util");
19
+ const execAsync = promisify(exec);
20
+ try {
21
+ // Find process listening on the port
22
+ const cmd = process.platform === "win32"
23
+ ? `netstat -ano | findstr :${port}`
24
+ : `lsof -ti:${port}`;
25
+ const { stdout } = await execAsync(cmd);
26
+ if (!stdout.trim()) {
27
+ console.log(`No server found running on port ${port}`);
28
+ return;
29
+ }
30
+ // Kill the process
31
+ const pids = process.platform === "win32"
32
+ ? stdout.split("\n").map(line => line.trim().split(/\s+/).pop()).filter(Boolean)
33
+ : stdout.trim().split("\n");
34
+ for (const pid of pids) {
35
+ const killCmd = process.platform === "win32" ? `taskkill /F /PID ${pid}` : `kill ${pid}`;
36
+ await execAsync(killCmd);
37
+ }
38
+ console.log(`✓ Stopped server on port ${port}`);
39
+ }
40
+ catch (err) {
41
+ if (err instanceof Error && "code" in err && err.code === 1) {
42
+ console.log(`No server found running on port ${port}`);
43
+ }
44
+ else {
45
+ fail(`Failed to stop server: ${err instanceof Error ? err.message : String(err)}`);
46
+ }
47
+ }
48
+ }
49
+ else if (effectiveAction === "start") {
50
+ const root = findRepoRoot() ?? fail("not inside a repo");
51
+ const store = MemoryStore.open(root);
52
+ const { startUiServer } = await import("../../ui/server.js");
53
+ const url = await startUiServer(store, root, parseInt(opts.port, 10));
54
+ console.log(`aidimag dashboard: ${url} (Ctrl+C to stop)`);
55
+ if (opts.open)
56
+ await openBrowser(url);
57
+ }
58
+ else {
59
+ fail(`unknown action '${action}'. Use: start | stop`);
60
+ }
61
+ });
62
+ program
63
+ .command("generate-context")
64
+ .description("Render trustworthy memory into a static context file (CLAUDE.md, .cursorrules, .windsurfrules, AGENTS.md, copilot-instructions) for non-MCP AI tools")
65
+ .option("-f, --format <format>", "claude | cursorrules | copilot | windsurfrules | agents | all", "claude")
66
+ .option("--auto", "Also persist generateContext.auto in .aidimag/config.json so verify/review/sync keep it fresh")
67
+ .option("--no-auto", "Disable auto-regeneration (clears generateContext.auto)")
68
+ .action(async (opts) => {
69
+ const root = findRepoRoot() ?? fail("not inside a repo");
70
+ const store = MemoryStore.open(root);
71
+ const { generateContext } = await import("../../context/generate.js");
72
+ const format = String(opts.format).toLowerCase();
73
+ if (!["claude", "cursorrules", "copilot", "windsurfrules", "agents", "all"].includes(format)) {
74
+ fail(`invalid --format '${opts.format}'. Use: claude | cursorrules | copilot | windsurfrules | agents | all`);
75
+ }
76
+ const r = generateContext(store, root, format);
77
+ console.log(`📝 Wrote ${r.files.join(", ")} — ${r.total} memories (${r.pinned} pinned).`);
78
+ if (r.total === 0) {
79
+ console.log(" (no verified memories yet — run `dim remember` or approve proposals with `dim review`)");
80
+ }
81
+ // commander sets opts.auto=false only when --no-auto is passed; undefined otherwise
82
+ if (opts.auto === true || opts.auto === false) {
83
+ const { writeConfig } = await import("../../config.js");
84
+ writeConfig(root, { generateContext: opts.auto ? { auto: true, format: format } : { auto: false } });
85
+ console.log(opts.auto
86
+ ? `🔄 Auto-regeneration ON — verify/review/sync will refresh ${format === "all" ? "the context files" : r.files[0]}.`
87
+ : `Auto-regeneration OFF.`);
88
+ }
89
+ store.close();
90
+ });
91
+ program
92
+ .command("mcp")
93
+ .description("Run the aidimag MCP server (stdio)")
94
+ .action(async () => {
95
+ await import("../../mcp/server.js");
96
+ });
97
+ }
98
+ //# sourceMappingURL=hosts.js.map
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Knowledge-inbox commands: knowledge sync | status | list | watch.
3
+ */
4
+ import type { Command } from "commander";
5
+ export declare function registerKnowledgeCommands(program: Command): void;
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Knowledge-inbox commands: knowledge sync | status | list | watch.
3
+ */
4
+ import { mkdirSync } from "node:fs";
5
+ import path from "node:path";
6
+ import { MemoryStore, findRepoRoot } from "../../db/store.js";
7
+ import { resolveKnowledgeConfig } from "../../config.js";
8
+ import { fail, autoSync, maybeRegenerateContext, printIngestReport } from "../shared.js";
9
+ export function registerKnowledgeCommands(program) {
10
+ const knowledge = program
11
+ .command("knowledge")
12
+ .description("Manage the knowledge inbox: summarize dropped docs into reviewed, pinned memories")
13
+ .action(() => knowledge.help());
14
+ knowledge
15
+ .command("sync", { isDefault: true })
16
+ .description("Process the knowledge inbox now: summarize new docs into proposals (review with `dim review`)")
17
+ .action(async () => {
18
+ const root = findRepoRoot() ?? fail("not inside a repo");
19
+ const store = MemoryStore.open(root);
20
+ const { ingestAll } = await import("../../knowledge/ingest.js");
21
+ const cfg = resolveKnowledgeConfig(root);
22
+ const report = await ingestAll(store, root, cfg);
23
+ printIngestReport(report);
24
+ // Auto-approved knowledge (requireReview=false) becomes active memory → keep context fresh.
25
+ if (report.processed.some((d) => d.pinned)) {
26
+ await autoSync(store);
27
+ await maybeRegenerateContext(store);
28
+ }
29
+ store.close();
30
+ });
31
+ knowledge
32
+ .command("status")
33
+ .description("Show pending / skipped / processed counts for the knowledge inbox")
34
+ .action(async () => {
35
+ const root = findRepoRoot() ?? fail("not inside a repo");
36
+ const { knowledgeStatus } = await import("../../knowledge/ingest.js");
37
+ const s = await knowledgeStatus(root, resolveKnowledgeConfig(root));
38
+ console.log(`Knowledge inbox: ${s.folder}/`);
39
+ console.log(` pending ${s.pending.length}${s.pending.length ? " → run \`dim knowledge sync\`" : ""}`);
40
+ for (const p of s.pending)
41
+ console.log(` • ${p.file} (${p.bytes} bytes)`);
42
+ console.log(` unsupported ${s.unsupported.length}${s.unsupported.length ? " (will move to skipped/ on next sync)" : ""}`);
43
+ for (const u of s.unsupported)
44
+ console.log(` • ${u.file} — ${u.reason}`);
45
+ console.log(` skipped/ ${s.skippedOnDisk.length}`);
46
+ for (const f of s.skippedOnDisk)
47
+ console.log(` • ${f}`);
48
+ console.log(` processed ${s.processed.length}`);
49
+ });
50
+ knowledge
51
+ .command("list")
52
+ .description("List processed knowledge docs and the memories they produced")
53
+ .action(async () => {
54
+ const root = findRepoRoot() ?? fail("not inside a repo");
55
+ const { readManifest } = await import("../../knowledge/ingest.js");
56
+ const docs = readManifest(root).docs;
57
+ if (!docs.length) {
58
+ console.log("No knowledge docs processed yet. Drop files in the inbox and run `dim knowledge sync`.");
59
+ return;
60
+ }
61
+ for (const d of docs) {
62
+ const memo = d.memoryIds.length ? `${d.memoryIds.length} pinned` : `${d.proposalIds.length} proposed`;
63
+ console.log(`📄 ${d.file} · ${d.claimCount} claim(s) (${memo}) · via ${d.via} · ${d.date.slice(0, 10)}`);
64
+ }
65
+ });
66
+ knowledge
67
+ .command("watch")
68
+ .description("Foreground watcher: process the inbox automatically whenever a doc is dropped (Ctrl-C to stop)")
69
+ .option("-d, --debounce <ms>", "Settle time before processing a batch of drops", "750")
70
+ .action(async (opts) => {
71
+ const root = findRepoRoot() ?? fail("not inside a repo");
72
+ const cfg = resolveKnowledgeConfig(root);
73
+ const inbox = path.join(root, cfg.folder);
74
+ mkdirSync(inbox, { recursive: true });
75
+ const { ingestAll } = await import("../../knowledge/ingest.js");
76
+ const debounceMs = Math.max(100, parseInt(opts.debounce, 10) || 750);
77
+ let running = false;
78
+ let queued = false;
79
+ const run = async () => {
80
+ if (running) {
81
+ queued = true;
82
+ return;
83
+ }
84
+ running = true;
85
+ try {
86
+ const store = MemoryStore.open(root);
87
+ const report = await ingestAll(store, root, cfg);
88
+ if (report.processed.length || report.skipped.length || report.duplicates.length) {
89
+ printIngestReport(report);
90
+ if (report.processed.some((d) => d.pinned)) {
91
+ await autoSync(store);
92
+ await maybeRegenerateContext(store);
93
+ }
94
+ }
95
+ store.close();
96
+ }
97
+ catch (err) {
98
+ console.error(`dim knowledge watch: ${err instanceof Error ? err.message : String(err)}`);
99
+ }
100
+ finally {
101
+ running = false;
102
+ if (queued) {
103
+ queued = false;
104
+ void run();
105
+ }
106
+ }
107
+ };
108
+ let timer;
109
+ const { watch } = await import("node:fs");
110
+ const watcher = watch(inbox, { persistent: true }, () => {
111
+ if (timer)
112
+ clearTimeout(timer);
113
+ timer = setTimeout(() => void run(), debounceMs);
114
+ });
115
+ console.log(`👀 Watching ${cfg.folder}/ for dropped docs — Ctrl-C to stop.`);
116
+ await run(); // catch up on anything already sitting in the inbox
117
+ process.on("SIGINT", () => { watcher.close(); console.log("\nStopped."); process.exit(0); });
118
+ await new Promise(() => { });
119
+ });
120
+ }
121
+ //# sourceMappingURL=knowledge.js.map
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Core memory commands: init, remember, recall, reindex, status, log, gaps,
3
+ * refute, pin, unpin, forget.
4
+ */
5
+ import type { Command } from "commander";
6
+ export declare function registerMemoryCommands(program: Command): void;