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,136 @@
1
+ /**
2
+ * Verification & guardrail commands: verify, check, brief.
3
+ */
4
+ import { MemoryStore, findRepoRoot } from "../../db/store.js";
5
+ import { verifyAll } from "../../verify/engine.js";
6
+ import { fail, autoSync, maybeRegenerateContext, createPrompter } from "../shared.js";
7
+ export function registerVerifyCommands(program) {
8
+ program
9
+ .command("verify")
10
+ .description("Re-run evidence and update memory statuses (cheap tier; --deep adds tests/exec)")
11
+ .option("-i, --id <ids...>", "Only verify specific memory ids (prefix ok)")
12
+ .option("-d, --deep", "Also run expensive evidence (TEST_RESULT, EXEC_TRACE)")
13
+ .option("--trust", "Review evidence commands that arrived via team sync and approve them to run on this machine")
14
+ .option("-q, --quiet", "Only print status changes (for git hooks)")
15
+ .action(async (opts) => {
16
+ const root = findRepoRoot() ?? fail("not inside a repo");
17
+ const store = MemoryStore.open(root);
18
+ if (opts.trust) {
19
+ const pending = store.untrustedEvidence();
20
+ if (!pending.length) {
21
+ console.log("No untrusted evidence โ€” everything runnable was authored or approved on this machine.");
22
+ }
23
+ else {
24
+ console.log(`${pending.length} synced-in evidence command(s) are NOT yet approved to execute here:\n`);
25
+ for (const u of pending) {
26
+ console.log(` [${u.type}] ${u.payload}`);
27
+ console.log(` for: "${u.claim.slice(0, 90)}"\n`);
28
+ }
29
+ const { ask, close } = await createPrompter("n");
30
+ const ans = (await ask("Approve ALL of the above to run on this machine? [y/N] ")).trim().toLowerCase();
31
+ close();
32
+ if (ans === "y" || ans === "yes") {
33
+ console.log(`โœ“ approved ${store.trustAllEvidence()} command(s). They'll run on the next verify.`);
34
+ }
35
+ else {
36
+ console.log("Nothing approved โ€” they stay skipped during verification.");
37
+ }
38
+ }
39
+ }
40
+ const report = verifyAll(store, root, { ids: opts.id, deep: Boolean(opts.deep) });
41
+ for (const r of report.results) {
42
+ const changed = r.after !== r.before || r.decayed;
43
+ if (opts.quiet && !changed)
44
+ continue;
45
+ const arrow = r.after !== r.before ? `${r.before} โ†’ ${r.after}` : r.after;
46
+ const icon = r.after === "VERIFIED" ? "โœ“" : r.after === "STALE" ? "~" : "?";
47
+ const decayNote = r.decayed ? " (decayed)" : "";
48
+ console.log(`${icon} [${arrow}] conf ${r.confidenceBefore.toFixed(2)}โ†’${r.confidenceAfter.toFixed(2)}${decayNote} ${r.claim.slice(0, 90)}`);
49
+ for (const o of r.outcomes) {
50
+ if (opts.quiet && o.result !== "FAIL")
51
+ continue;
52
+ console.log(` ${o.type}: ${o.result} (${o.detail})`);
53
+ }
54
+ }
55
+ if (opts.quiet) {
56
+ // hook mode: machine-stable output โ€” only speak when something went stale
57
+ if (report.stale > 0) {
58
+ console.log(`\nchecked ${report.checked}: ${report.verified} verified, ${report.stale} stale, ${report.decayed} decayed, ${report.unchanged} unchanged`);
59
+ }
60
+ }
61
+ else if (report.checked === 0) {
62
+ console.log("Nothing to verify yet โ€” store something with `dim remember` first.");
63
+ }
64
+ else if (report.stale > 0) {
65
+ console.log(`\nโš  ${report.stale} memor${report.stale === 1 ? "y" : "ies"} went stale โ€” the code changed under ${report.stale === 1 ? "it" : "them"}. ` +
66
+ `Stale memories are down-ranked in recall until they recover.\n` +
67
+ `(checked ${report.checked}: ${report.verified} verified, ${report.stale} stale, ${report.decayed} decayed, ${report.unchanged} unchanged)`);
68
+ }
69
+ else {
70
+ console.log(`\nโœ“ All good โ€” ${report.verified} verified, ${report.unchanged} unchanged${report.decayed ? `, ${report.decayed} aging (decayed)` : ""} of ${report.checked} checked.`);
71
+ }
72
+ await autoSync(store);
73
+ // keep generated context in sync when a status actually flipped
74
+ if (report.results.some((r) => r.after !== r.before))
75
+ await maybeRegenerateContext(store);
76
+ store.close();
77
+ if (report.stale > 0)
78
+ process.exitCode = 2; // signal staleness to scripts
79
+ });
80
+ program
81
+ .command("check")
82
+ .description("Pre-commit contradiction check: scan the staged diff against active memories and guardrails")
83
+ .option("-r, --ref <ref>", "Diff against a ref instead of the staged index (e.g. HEAD~1)")
84
+ .option("--block", "Exit 1 when a hard violation is found (default: warn only)")
85
+ .option("--pre-commit", "Run in hook mode: behavior follows preCommitCheck in .aidimag/config.json (no-op if unset)", false)
86
+ .action(async (opts) => {
87
+ const root = findRepoRoot() ?? fail("not inside a git repo");
88
+ let block = Boolean(opts.block);
89
+ if (opts.preCommit) {
90
+ const { readConfig } = await import("../../config.js");
91
+ const mode = readConfig(root).preCommitCheck;
92
+ if (!mode)
93
+ return; // hook installed but feature disabled โ€” silent no-op
94
+ block = mode === "block";
95
+ }
96
+ const store = MemoryStore.open(root);
97
+ const { checkDiff } = await import("../../verify/check.js");
98
+ const report = checkDiff(store, root, { ref: opts.ref });
99
+ store.close();
100
+ if (report.changedFiles.length === 0) {
101
+ if (!opts.preCommit)
102
+ console.log("dim check: no changes to check.");
103
+ return;
104
+ }
105
+ const fails = report.violations.filter((v) => v.severity === "fail");
106
+ const warns = report.violations.filter((v) => v.severity === "warn");
107
+ if (report.violations.length === 0) {
108
+ if (!opts.preCommit) {
109
+ console.log(`โœ“ dim check: ${report.checked} memorie(s) considered across ${report.changedFiles.length} file(s) โ€” no conflicts.`);
110
+ }
111
+ return;
112
+ }
113
+ for (const v of fails) {
114
+ console.error(`โœ— [${v.memory.kind}] ${v.detail}\n "${v.memory.claim}"`);
115
+ }
116
+ for (const v of warns) {
117
+ console.error(`~ [${v.memory.kind}] ${v.detail}\n "${v.memory.claim}"`);
118
+ }
119
+ if (fails.length && block) {
120
+ console.error(`\ndim check: ${fails.length} blocking violation(s). Resolve them or commit with --no-verify.`);
121
+ process.exit(1);
122
+ }
123
+ });
124
+ program
125
+ .command("brief")
126
+ .description("Print a session-start briefing: in-scope memory, guardrails, stale warnings, and questions to ask")
127
+ .action(async () => {
128
+ const root = findRepoRoot() ?? fail("not inside a git repo");
129
+ const store = MemoryStore.open(root);
130
+ const { buildSessionBriefing, renderBriefing } = await import("../../capture/session-briefing.js");
131
+ const briefing = buildSessionBriefing(store, root);
132
+ process.stdout.write(renderBriefing(briefing));
133
+ store.close();
134
+ });
135
+ }
136
+ //# sourceMappingURL=verify.js.map
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * dim โ€” the aidimag CLI.
4
+ *
5
+ * Thin entry point: builds the commander program and delegates to the command
6
+ * modules in ./commands/ (one file per domain):
7
+ *
8
+ * memory.ts init, remember, recall, reindex, status, log, gaps,
9
+ * refute, pin, unpin, forget
10
+ * capture.ts mine (commits/PRs), bootstrap, harvest, review, proposals gc
11
+ * verify.ts verify, check, brief
12
+ * sync.ts serve, cloud, login, logout, sync, keys
13
+ * tickets.ts ticket, branch, branch-check
14
+ * knowledge.ts knowledge sync | status | list | watch
15
+ * hosts.ts ui, generate-context, mcp
16
+ *
17
+ * Shared helpers (printers, prompter, auto-sync, fail) live in ../shared.ts.
18
+ */
19
+ export {};
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * dim โ€” the aidimag CLI.
4
+ *
5
+ * Thin entry point: builds the commander program and delegates to the command
6
+ * modules in ./commands/ (one file per domain):
7
+ *
8
+ * memory.ts init, remember, recall, reindex, status, log, gaps,
9
+ * refute, pin, unpin, forget
10
+ * capture.ts mine (commits/PRs), bootstrap, harvest, review, proposals gc
11
+ * verify.ts verify, check, brief
12
+ * sync.ts serve, cloud, login, logout, sync, keys
13
+ * tickets.ts ticket, branch, branch-check
14
+ * knowledge.ts knowledge sync | status | list | watch
15
+ * hosts.ts ui, generate-context, mcp
16
+ *
17
+ * Shared helpers (printers, prompter, auto-sync, fail) live in ../shared.ts.
18
+ */
19
+ import { Command } from "commander";
20
+ import { readFileSync } from "node:fs";
21
+ import path from "node:path";
22
+ import { fileURLToPath } from "node:url";
23
+ import { fail } from "./shared.js";
24
+ import { registerMemoryCommands } from "./commands/memory.js";
25
+ import { registerCaptureCommands } from "./commands/capture.js";
26
+ import { registerVerifyCommands } from "./commands/verify.js";
27
+ import { registerSyncCommands } from "./commands/sync.js";
28
+ import { registerTicketCommands } from "./commands/tickets.js";
29
+ import { registerKnowledgeCommands } from "./commands/knowledge.js";
30
+ import { registerHostCommands } from "./commands/hosts.js";
31
+ /** Version comes from package.json โ€” single source of truth. */
32
+ const PKG_VERSION = JSON.parse(readFileSync(path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../package.json"), "utf8")).version;
33
+ const program = new Command();
34
+ program
35
+ .name("dim")
36
+ .description("aidimag โ€” persistent, verified memory for AI coding agents")
37
+ .version(PKG_VERSION, "-v, --version", "print the aidimag version");
38
+ registerMemoryCommands(program);
39
+ registerCaptureCommands(program);
40
+ registerVerifyCommands(program);
41
+ registerSyncCommands(program);
42
+ registerTicketCommands(program);
43
+ registerKnowledgeCommands(program);
44
+ registerHostCommands(program);
45
+ program.parseAsync().catch((err) => fail(err instanceof Error ? err.message : String(err)));
46
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Shared CLI helpers โ€” used by every command module in src/cli/commands/.
3
+ *
4
+ * Nothing here registers commands; it's the common vocabulary: fail-fast
5
+ * error exit, printers for memories/proposals/ingest reports, the
6
+ * line-buffering prompter, best-effort auto-sync and context regeneration.
7
+ */
8
+ import { MemoryStore } from "../db/store.js";
9
+ import type { GuardrailLevel, MemoryEntry, MemoryKind, Proposal } from "../types.js";
10
+ export declare const KINDS: MemoryKind[];
11
+ export declare const GUARDRAIL_LEVELS: GuardrailLevel[];
12
+ export declare const GUARDRAIL_ICON: Record<GuardrailLevel, string>;
13
+ export declare function fail(msg: string): never;
14
+ /** Debounced best-effort sync after local mutations (no-op unless cloud-linked). */
15
+ export declare function autoSync(store: MemoryStore): Promise<void>;
16
+ /**
17
+ * Regenerate the static context file(s) after a memory-set change, but only when
18
+ * the repo opted in via `generateContext.auto` in .aidimag/config.json. Keeps
19
+ * CLAUDE.md / .cursorrules / copilot-instructions in lock-step with verified
20
+ * memory so non-MCP tools never read a stale spec. Best-effort: never throws.
21
+ */
22
+ export declare function maybeRegenerateContext(store: MemoryStore): Promise<void>;
23
+ export declare function printMemory(m: MemoryEntry, verbose?: boolean): void;
24
+ export declare function printProposal(p: Proposal): void;
25
+ /** Human-readable summary of a knowledge-inbox ingest run. */
26
+ export declare function printIngestReport(report: import("../knowledge/ingest.js").IngestReport): void;
27
+ /**
28
+ * Line-buffering prompt for interactive flows (review, ticket connect).
29
+ * Unlike readline/promises, lines arriving between questions (piped input)
30
+ * are queued, not dropped โ€” so scripted/agent-driven input works too.
31
+ */
32
+ export declare function createPrompter(closedValue?: string): Promise<{
33
+ ask: (prompt: string) => Promise<string>;
34
+ close: () => void;
35
+ }>;
36
+ /** Open a URL in the default browser, best-effort (matches `dim login` / `dim ui`). */
37
+ export declare function openBrowser(url: string): Promise<void>;
@@ -0,0 +1,175 @@
1
+ /**
2
+ * Shared CLI helpers โ€” used by every command module in src/cli/commands/.
3
+ *
4
+ * Nothing here registers commands; it's the common vocabulary: fail-fast
5
+ * error exit, printers for memories/proposals/ingest reports, the
6
+ * line-buffering prompter, best-effort auto-sync and context regeneration.
7
+ */
8
+ import { findRepoRoot } from "../db/store.js";
9
+ import { debugLog } from "../debug.js";
10
+ export const KINDS = [
11
+ "DECISION", "CONVENTION", "GOTCHA", "FAILED_APPROACH",
12
+ "ARCHITECTURE", "INVARIANT", "TODO_CONTEXT", "GUARDRAIL", "SKILL",
13
+ ];
14
+ export const GUARDRAIL_LEVELS = ["never", "always", "ask-first"];
15
+ export const GUARDRAIL_ICON = { never: "๐Ÿšซ", always: "โœ…", "ask-first": "๐Ÿคš" };
16
+ export function fail(msg) {
17
+ console.error(`dim: ${msg}`);
18
+ process.exit(1);
19
+ }
20
+ /** Debounced best-effort sync after local mutations (no-op unless cloud-linked). */
21
+ export async function autoSync(store) {
22
+ const root = findRepoRoot();
23
+ if (!root)
24
+ return;
25
+ const { maybeAutoSync } = await import("../sync/client.js");
26
+ const r = await maybeAutoSync(store, root);
27
+ if (r)
28
+ console.log(`(auto-synced: sent ${r.memoriesPushed} memories, pulled ${r.pulled}, events ${r.eventsPushed})`);
29
+ }
30
+ /**
31
+ * Regenerate the static context file(s) after a memory-set change, but only when
32
+ * the repo opted in via `generateContext.auto` in .aidimag/config.json. Keeps
33
+ * CLAUDE.md / .cursorrules / copilot-instructions in lock-step with verified
34
+ * memory so non-MCP tools never read a stale spec. Best-effort: never throws.
35
+ */
36
+ export async function maybeRegenerateContext(store) {
37
+ const root = findRepoRoot();
38
+ if (!root)
39
+ return;
40
+ try {
41
+ const { readConfig } = await import("../config.js");
42
+ const cfg = readConfig(root).generateContext;
43
+ if (!cfg?.auto)
44
+ return;
45
+ const { generateContext } = await import("../context/generate.js");
46
+ const r = generateContext(store, root, cfg.format ?? "claude");
47
+ console.log(`(regenerated ${r.files.join(", ")} โ€” ${r.total} memories)`);
48
+ }
49
+ catch (err) {
50
+ // context regen is advisory; failures must not break the command
51
+ debugLog("context regeneration", err);
52
+ }
53
+ }
54
+ export function printMemory(m, verbose = false) {
55
+ const statusIcon = m.status === "VERIFIED" ? "โœ“" : m.status === "REFUTED" ? "โœ—" : m.status === "STALE" ? "~" : "?";
56
+ const guard = m.kind === "GUARDRAIL" && m.guardrailLevel
57
+ ? ` ${GUARDRAIL_ICON[m.guardrailLevel]} ${m.guardrailLevel.toUpperCase()}`
58
+ : "";
59
+ console.log(`${statusIcon} ${m.pinned ? "๐Ÿ“Œ " : ""}[${m.kind}${guard}] ${m.claim}`);
60
+ const scope = [...m.scope.paths, ...m.scope.symbols];
61
+ console.log(` id=${m.id.slice(0, 8)} status=${m.status} conf=${m.confidence.toFixed(2)}` +
62
+ (m.pinned ? " pinned" : "") +
63
+ (scope.length ? ` scope=${scope.join(",")}` : ""));
64
+ if (verbose && m.grounding.length) {
65
+ for (const e of m.grounding) {
66
+ console.log(` evidence: ${e.type}(${e.result}) ${e.payload}`);
67
+ }
68
+ }
69
+ }
70
+ export function printProposal(p) {
71
+ console.log(`โ—† [${p.id.slice(0, 8)}] ${p.kind} (via ${p.source}${p.sourceRef ? ` @ ${p.sourceRef.slice(0, 8)}` : ""})`);
72
+ console.log(` ${p.claim}`);
73
+ if (p.paths.length || p.symbols.length) {
74
+ console.log(` scope: ${[...p.paths, ...p.symbols].join(", ")}`);
75
+ }
76
+ if (p.evidence.length) {
77
+ console.log(` evidence: ${p.evidence.map((e) => `${e.type}:${e.payload}`).join(" ")}`);
78
+ }
79
+ if (p.ticketRef)
80
+ console.log(` ticket: ${p.ticketRef}`);
81
+ if (p.rationale)
82
+ console.log(` rationale: ${p.rationale}`);
83
+ }
84
+ /** Human-readable summary of a knowledge-inbox ingest run. */
85
+ export function printIngestReport(report) {
86
+ if (report.processed.length) {
87
+ const claims = report.processed.reduce((n, d) => n + d.claimCount, 0);
88
+ const pinned = report.processed.filter((d) => d.pinned).length;
89
+ console.log(`๐Ÿ“š Processed ${report.processed.length} doc(s) โ†’ ${claims} claim(s) ` +
90
+ (pinned ? `(${pinned} auto-pinned)` : "queued as proposals โ€” review with `dim review`") +
91
+ (report.summarizer ? ` ยท via ${report.summarizer}` : ""));
92
+ for (const d of report.processed) {
93
+ console.log(` โ€ข ${d.file}: ${d.claimCount} claim(s)${d.pinned ? " (pinned)" : ""}`);
94
+ }
95
+ }
96
+ if (report.duplicates.length) {
97
+ console.log(`โ†ฉ๏ธŽ ${report.duplicates.length} unchanged duplicate(s) retired: ${report.duplicates.join(", ")}`);
98
+ }
99
+ if (report.skipped.length) {
100
+ console.log(`โš ๏ธ Skipped ${report.skipped.length} unsupported file(s) (moved to .aidimag/knowledge/skipped/):`);
101
+ for (const s of report.skipped)
102
+ console.log(` โ€ข ${s.file} โ€” ${s.reason}`);
103
+ }
104
+ if (report.pendingNoSummarizer.length) {
105
+ console.log(`โณ ${report.pendingNoSummarizer.length} doc(s) waiting in the inbox โ€” no summarizer available.\n` +
106
+ `\n` +
107
+ ` ๐Ÿ“‹ Files waiting:\n`);
108
+ for (const f of report.pendingNoSummarizer)
109
+ console.log(` โ€ข ${f}`);
110
+ console.log(`\n` +
111
+ ` ๐Ÿ’ก To summarize, choose one:\n` +
112
+ `\n` +
113
+ ` 1. Use Cursor AI (if you have MCP configured):\n` +
114
+ ` Ask Cursor: "Process the knowledge inbox and create memory proposals"\n` +
115
+ ` (Cursor will use the knowledge_ingest prompt automatically)\n` +
116
+ `\n` +
117
+ ` 2. Set OpenAI API key:\n` +
118
+ ` export OPENAI_API_KEY="sk-your-key-here"\n` +
119
+ ` dim knowledge sync\n` +
120
+ `\n` +
121
+ ` 3. Use Ollama (free/local):\n` +
122
+ ` brew install ollama && ollama serve &\n` +
123
+ ` ollama pull llama3.2\n` +
124
+ ` export AIDIMAG_LLM=ollama\n` +
125
+ ` dim knowledge sync`);
126
+ }
127
+ if (!report.processed.length && !report.duplicates.length &&
128
+ !report.skipped.length && !report.pendingNoSummarizer.length) {
129
+ console.log("Knowledge inbox is empty โ€” nothing to process.");
130
+ }
131
+ }
132
+ /**
133
+ * Line-buffering prompt for interactive flows (review, ticket connect).
134
+ * Unlike readline/promises, lines arriving between questions (piped input)
135
+ * are queued, not dropped โ€” so scripted/agent-driven input works too.
136
+ */
137
+ export async function createPrompter(closedValue = "") {
138
+ const { createInterface } = await import("node:readline");
139
+ const rl = createInterface({ input: process.stdin });
140
+ const queued = [];
141
+ const waiters = [];
142
+ let closed = false;
143
+ rl.on("line", (l) => {
144
+ const w = waiters.shift();
145
+ if (w)
146
+ w(l);
147
+ else
148
+ queued.push(l);
149
+ });
150
+ rl.on("close", () => {
151
+ closed = true;
152
+ while (waiters.length)
153
+ waiters.shift()(closedValue);
154
+ });
155
+ const ask = (prompt) => {
156
+ process.stdout.write(prompt);
157
+ if (queued.length)
158
+ return Promise.resolve(queued.shift());
159
+ if (closed)
160
+ return Promise.resolve(closedValue);
161
+ return new Promise((resolve) => waiters.push(resolve));
162
+ };
163
+ return { ask, close: () => rl.close() };
164
+ }
165
+ /** Open a URL in the default browser, best-effort (matches `dim login` / `dim ui`). */
166
+ export async function openBrowser(url) {
167
+ const { execFile } = await import("node:child_process");
168
+ const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
169
+ const args = process.platform === "win32" ? ["/c", "start", "", url] : [url];
170
+ execFile(opener, args, (err) => {
171
+ if (err)
172
+ process.stderr.write(`Could not open browser: ${err.message}\n`);
173
+ });
174
+ }
175
+ //# sourceMappingURL=shared.js.map
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Generic reader/writer for the committed, secret-free repo config at
3
+ * <repo>/.aidimag/config.json. Ticket + sync sections have their own typed
4
+ * helpers; this covers the rest (generateContext, preCommitCheck, ...).
5
+ */
6
+ import type { GuardrailLevel } from "./types.js";
7
+ export type ContextFormat = "claude" | "cursorrules" | "copilot" | "windsurfrules" | "agents" | "all";
8
+ export interface GenerateContextConfig {
9
+ /** which file(s) to write โ€” defaults to "claude" */
10
+ format?: ContextFormat;
11
+ /** regenerate automatically after verify/sync/review */
12
+ auto?: boolean;
13
+ }
14
+ /** "block" โ†’ exit 1 on violations; true โ†’ warn (exit 0); falsy โ†’ hook is a no-op. */
15
+ export type PreCommitCheckConfig = boolean | "warn" | "block";
16
+ /** Who summarizes dropped knowledge docs: auto (agentโ†’llm), agent-only, llm-only, or off. */
17
+ export type KnowledgeSummarizer = "auto" | "agent" | "llm" | "off";
18
+ export interface KnowledgeConfig {
19
+ /** inbox folder (repo-relative) where docs are dropped โ€” default "knowledge" */
20
+ folder?: string;
21
+ /** summarizer strategy โ€” default "auto" */
22
+ summarizer?: KnowledgeSummarizer;
23
+ /** require `dim review` approval before pinning โ€” default true */
24
+ requireReview?: boolean;
25
+ /** keep a backup of the original in .aidimag/knowledge/processed/ โ€” default true */
26
+ backup?: boolean;
27
+ /** text extensions we will summarize โ€” default DEFAULT_KNOWLEDGE_EXTENSIONS */
28
+ extensions?: string[];
29
+ /** hard cap; larger files are skipped โ€” default 1 MiB */
30
+ maxBytes?: number;
31
+ /** soft threshold; larger text docs are chunked โ€” default 16 KiB */
32
+ chunkBytes?: number;
33
+ }
34
+ export interface AidimagConfig {
35
+ generateContext?: GenerateContextConfig;
36
+ preCommitCheck?: PreCommitCheckConfig;
37
+ knowledge?: KnowledgeConfig;
38
+ [k: string]: unknown;
39
+ }
40
+ export declare const DEFAULT_KNOWLEDGE_EXTENSIONS: string[];
41
+ export interface ResolvedKnowledgeConfig {
42
+ folder: string;
43
+ summarizer: KnowledgeSummarizer;
44
+ requireReview: boolean;
45
+ backup: boolean;
46
+ extensions: string[];
47
+ maxBytes: number;
48
+ chunkBytes: number;
49
+ }
50
+ /** Knowledge config with every field filled in from defaults. */
51
+ export declare function resolveKnowledgeConfig(repoRoot: string): ResolvedKnowledgeConfig;
52
+ export declare function readConfig(repoRoot: string): AidimagConfig;
53
+ /** Shallow-merge a patch into config.json, never clobbering sibling sections. */
54
+ export declare function writeConfig(repoRoot: string, patch: Partial<AidimagConfig>): void;
55
+ export type { GuardrailLevel };
package/dist/config.js ADDED
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Generic reader/writer for the committed, secret-free repo config at
3
+ * <repo>/.aidimag/config.json. Ticket + sync sections have their own typed
4
+ * helpers; this covers the rest (generateContext, preCommitCheck, ...).
5
+ */
6
+ import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
7
+ import path from "node:path";
8
+ export const DEFAULT_KNOWLEDGE_EXTENSIONS = [
9
+ ".md", ".markdown", ".txt", ".rst", ".adoc", ".org",
10
+ ".json", ".yaml", ".yml", ".toml", ".csv", ".html",
11
+ ".pdf", ".docx", // binary docs โ€” text is extracted before summarization
12
+ ];
13
+ /** Reject repo-relative knowledge inbox paths that escape or touch sensitive dirs. */
14
+ function safeKnowledgeFolder(folder) {
15
+ const normalized = folder.replace(/\\/g, "/").replace(/^\/+/, "").trim();
16
+ if (!normalized || normalized.includes("..") || normalized.startsWith(".aidimag"))
17
+ return "knowledge";
18
+ return normalized;
19
+ }
20
+ /** Knowledge config with every field filled in from defaults. */
21
+ export function resolveKnowledgeConfig(repoRoot) {
22
+ const k = readConfig(repoRoot).knowledge ?? {};
23
+ return {
24
+ folder: safeKnowledgeFolder(k.folder ?? "knowledge"),
25
+ summarizer: k.summarizer ?? "auto",
26
+ requireReview: k.requireReview ?? true,
27
+ backup: k.backup ?? true,
28
+ extensions: (k.extensions ?? DEFAULT_KNOWLEDGE_EXTENSIONS).map((e) => e.startsWith(".") ? e.toLowerCase() : "." + e.toLowerCase()),
29
+ maxBytes: k.maxBytes ?? 1024 * 1024,
30
+ chunkBytes: k.chunkBytes ?? 16 * 1024,
31
+ };
32
+ }
33
+ function configPath(repoRoot) {
34
+ return path.join(repoRoot, ".aidimag", "config.json");
35
+ }
36
+ export function readConfig(repoRoot) {
37
+ try {
38
+ return JSON.parse(readFileSync(configPath(repoRoot), "utf8"));
39
+ }
40
+ catch {
41
+ return {};
42
+ }
43
+ }
44
+ /** Shallow-merge a patch into config.json, never clobbering sibling sections. */
45
+ export function writeConfig(repoRoot, patch) {
46
+ const p = configPath(repoRoot);
47
+ mkdirSync(path.dirname(p), { recursive: true });
48
+ const existing = readConfig(repoRoot);
49
+ writeFileSync(p, JSON.stringify({ ...existing, ...patch }, null, 2) + "\n");
50
+ }
51
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1,24 @@
1
+ /**
2
+ * `dim generate-context` (KARPATHY_LAYERS Feature 1) โ€” Environment layer.
3
+ *
4
+ * Renders the repo's trustworthy memory (VERIFIED + UNVERIFIED + pinned, never
5
+ * STALE/REFUTED) into the static context file that non-MCP AI tools read at
6
+ * session start: CLAUDE.md, .cursorrules, .windsurfrules, AGENTS.md, or
7
+ * .github/copilot-instructions.md.
8
+ *
9
+ * This is what makes aidimag useful to *every* AI tool, not just MCP clients.
10
+ */
11
+ import type { MemoryStore } from "../db/store.js";
12
+ import type { ContextFormat } from "../config.js";
13
+ export interface RenderResult {
14
+ markdown: string;
15
+ total: number;
16
+ pinned: number;
17
+ }
18
+ /** Build the markdown body shared by all output formats. */
19
+ export declare function renderContext(store: MemoryStore): RenderResult;
20
+ export interface WriteResult extends RenderResult {
21
+ files: string[];
22
+ }
23
+ /** Render + write the configured context file(s). Returns repo-relative paths written. */
24
+ export declare function generateContext(store: MemoryStore, repoRoot: string, format?: ContextFormat): WriteResult;
@@ -0,0 +1,115 @@
1
+ /**
2
+ * `dim generate-context` (KARPATHY_LAYERS Feature 1) โ€” Environment layer.
3
+ *
4
+ * Renders the repo's trustworthy memory (VERIFIED + UNVERIFIED + pinned, never
5
+ * STALE/REFUTED) into the static context file that non-MCP AI tools read at
6
+ * session start: CLAUDE.md, .cursorrules, .windsurfrules, AGENTS.md, or
7
+ * .github/copilot-instructions.md.
8
+ *
9
+ * This is what makes aidimag useful to *every* AI tool, not just MCP clients.
10
+ */
11
+ import { writeFileSync, mkdirSync } from "node:fs";
12
+ import path from "node:path";
13
+ const GENERATED_HEADER = "aidimag โ€” do not edit manually; regenerate with `dim generate-context`";
14
+ /** Files written per format. `all` writes every target. */
15
+ const FORMAT_FILES = {
16
+ claude: "CLAUDE.md",
17
+ cursorrules: ".cursorrules",
18
+ copilot: path.join(".github", "copilot-instructions.md"),
19
+ windsurfrules: ".windsurfrules",
20
+ agents: "AGENTS.md",
21
+ };
22
+ const GUARDRAIL_ICON = {
23
+ never: "๐Ÿšซ NEVER",
24
+ always: "โœ… ALWAYS",
25
+ "ask-first": "๐Ÿคš ASK FIRST",
26
+ };
27
+ // Order matters: guardrails + failed approaches are the highest-signal sections.
28
+ const SECTIONS = [
29
+ { kind: "GUARDRAIL", heading: "Guardrails โš ๏ธ" },
30
+ { kind: "DECISION", heading: "Decisions" },
31
+ { kind: "CONVENTION", heading: "Conventions" },
32
+ { kind: "INVARIANT", heading: "Invariants" },
33
+ { kind: "ARCHITECTURE", heading: "Architecture" },
34
+ { kind: "GOTCHA", heading: "Gotchas" },
35
+ { kind: "SKILL", heading: "Skills (reusable procedures)" },
36
+ { kind: "FAILED_APPROACH", heading: "Failed Approaches (don't retry these)" },
37
+ { kind: "TODO_CONTEXT", heading: "Open Context (unfinished work)" },
38
+ ];
39
+ function statusTag(m) {
40
+ const bits = [m.status === "VERIFIED" ? "VERIFIED" : m.status];
41
+ if (m.pinned)
42
+ bits.push("๐Ÿ“Œ");
43
+ return `[${bits.join(" ")}]`;
44
+ }
45
+ /** SKILL claims often embed "1) โ€ฆ 2) โ€ฆ" steps โ€” render them as an ordered list. */
46
+ function renderSkill(m) {
47
+ const steps = m.claim.split(/\s*\d+\)\s+/).map((s) => s.trim()).filter(Boolean);
48
+ if (steps.length < 2)
49
+ return `- ${m.claim} ${statusTag(m)}`;
50
+ const [title, ...rest] = m.claim.includes(":") ? [m.claim.split(":")[0]] : [""];
51
+ const head = title ? `**${title.trim()}** ${statusTag(m)}` : statusTag(m);
52
+ const items = (title ? steps.slice(1) : steps).map((s, i) => ` ${i + 1}. ${s}`);
53
+ return `- ${head}\n${items.join("\n")}`;
54
+ }
55
+ function renderGuardrail(m) {
56
+ const level = m.guardrailLevel ?? "ask-first";
57
+ return `- ${GUARDRAIL_ICON[level]}: ${m.claim} ${statusTag(m)}`;
58
+ }
59
+ function renderMemory(m) {
60
+ if (m.kind === "GUARDRAIL")
61
+ return renderGuardrail(m);
62
+ if (m.kind === "SKILL")
63
+ return renderSkill(m);
64
+ const scope = m.scope.paths.length ? ` _(scope: ${m.scope.paths.join(", ")})_` : "";
65
+ return `- ${m.claim}${scope} ${statusTag(m)}`;
66
+ }
67
+ /** Pinned first, then by confidence desc โ€” the most load-bearing memory leads. */
68
+ function sortForContext(a, b) {
69
+ if (a.pinned !== b.pinned)
70
+ return a.pinned ? -1 : 1;
71
+ return b.confidence - a.confidence;
72
+ }
73
+ /** Build the markdown body shared by all output formats. */
74
+ export function renderContext(store) {
75
+ const memories = store
76
+ .list(10_000)
77
+ .filter((m) => m.status === "VERIFIED" || m.status === "UNVERIFIED");
78
+ const pinned = memories.filter((m) => m.pinned).length;
79
+ const lines = [
80
+ `<!-- ${GENERATED_HEADER} -->`,
81
+ `# Project Memory`,
82
+ `> Auto-generated by aidimag ยท ${new Date().toISOString()} ยท ${memories.length} memories, ${pinned} pinned`,
83
+ "",
84
+ "Treat the items below as the project's verified beliefs. Respect every **Guardrail**:",
85
+ "๐Ÿšซ NEVER = refuse and explain why ยท โœ… ALWAYS = do it without asking ยท ๐Ÿคš ASK FIRST = check with the user first.",
86
+ "",
87
+ ];
88
+ for (const section of SECTIONS) {
89
+ const items = memories.filter((m) => m.kind === section.kind).sort(sortForContext);
90
+ if (!items.length)
91
+ continue;
92
+ lines.push(`## ${section.heading}`, ...items.map(renderMemory), "");
93
+ }
94
+ if (memories.length === 0) {
95
+ lines.push("_No verified memories yet โ€” run `dim remember` or approve proposals with `dim review`._", "");
96
+ }
97
+ return { markdown: lines.join("\n").replace(/\n{3,}/g, "\n\n").trimEnd() + "\n", total: memories.length, pinned };
98
+ }
99
+ function targetsFor(format) {
100
+ return format === "all" ? ["claude", "cursorrules", "copilot", "windsurfrules", "agents"] : [format];
101
+ }
102
+ /** Render + write the configured context file(s). Returns repo-relative paths written. */
103
+ export function generateContext(store, repoRoot, format = "claude") {
104
+ const rendered = renderContext(store);
105
+ const files = [];
106
+ for (const target of targetsFor(format)) {
107
+ const rel = FORMAT_FILES[target];
108
+ const abs = path.join(repoRoot, rel);
109
+ mkdirSync(path.dirname(abs), { recursive: true });
110
+ writeFileSync(abs, rendered.markdown);
111
+ files.push(rel);
112
+ }
113
+ return { ...rendered, files };
114
+ }
115
+ //# sourceMappingURL=generate.js.map