mneme-ai 1.9.0 → 1.17.2

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 (66) hide show
  1. package/bin/git-mneme.js +0 -0
  2. package/bin/mneme.js +0 -0
  3. package/dist/commands/audit-log-cmd.d.ts +24 -0
  4. package/dist/commands/audit-log-cmd.d.ts.map +1 -0
  5. package/dist/commands/audit-log-cmd.js +131 -0
  6. package/dist/commands/audit-log-cmd.js.map +1 -0
  7. package/dist/commands/bench.d.ts +23 -0
  8. package/dist/commands/bench.d.ts.map +1 -0
  9. package/dist/commands/bench.js +85 -0
  10. package/dist/commands/bench.js.map +1 -0
  11. package/dist/commands/constitution.d.ts +45 -0
  12. package/dist/commands/constitution.d.ts.map +1 -0
  13. package/dist/commands/constitution.js +204 -0
  14. package/dist/commands/constitution.js.map +1 -0
  15. package/dist/commands/daemon.d.ts.map +1 -1
  16. package/dist/commands/daemon.js +41 -3
  17. package/dist/commands/daemon.js.map +1 -1
  18. package/dist/commands/ecosystem.d.ts +13 -0
  19. package/dist/commands/ecosystem.d.ts.map +1 -0
  20. package/dist/commands/ecosystem.js +44 -0
  21. package/dist/commands/ecosystem.js.map +1 -0
  22. package/dist/commands/index-cmd.d.ts.map +1 -1
  23. package/dist/commands/index-cmd.js +21 -1
  24. package/dist/commands/index-cmd.js.map +1 -1
  25. package/dist/commands/init.d.ts.map +1 -1
  26. package/dist/commands/init.js +53 -2
  27. package/dist/commands/init.js.map +1 -1
  28. package/dist/commands/key.d.ts +22 -0
  29. package/dist/commands/key.d.ts.map +1 -0
  30. package/dist/commands/key.js +60 -0
  31. package/dist/commands/key.js.map +1 -0
  32. package/dist/commands/security.d.ts +18 -0
  33. package/dist/commands/security.d.ts.map +1 -0
  34. package/dist/commands/security.js +126 -0
  35. package/dist/commands/security.js.map +1 -0
  36. package/dist/commands/security.test.d.ts +5 -0
  37. package/dist/commands/security.test.d.ts.map +1 -0
  38. package/dist/commands/security.test.js +198 -0
  39. package/dist/commands/security.test.js.map +1 -0
  40. package/dist/commands/session.d.ts +67 -0
  41. package/dist/commands/session.d.ts.map +1 -0
  42. package/dist/commands/session.js +216 -0
  43. package/dist/commands/session.js.map +1 -0
  44. package/dist/commands/session.test.d.ts +5 -0
  45. package/dist/commands/session.test.d.ts.map +1 -0
  46. package/dist/commands/session.test.js +270 -0
  47. package/dist/commands/session.test.js.map +1 -0
  48. package/dist/commands/upgrade.d.ts.map +1 -1
  49. package/dist/commands/upgrade.js +12 -2
  50. package/dist/commands/upgrade.js.map +1 -1
  51. package/dist/commands/webhook.d.ts +59 -0
  52. package/dist/commands/webhook.d.ts.map +1 -0
  53. package/dist/commands/webhook.js +220 -0
  54. package/dist/commands/webhook.js.map +1 -0
  55. package/dist/commands/webhook.test.d.ts +5 -0
  56. package/dist/commands/webhook.test.d.ts.map +1 -0
  57. package/dist/commands/webhook.test.js +192 -0
  58. package/dist/commands/webhook.test.js.map +1 -0
  59. package/dist/index.d.ts.map +1 -1
  60. package/dist/index.js +235 -0
  61. package/dist/index.js.map +1 -1
  62. package/package.json +5 -5
  63. package/dist/commands/wild-stubs.d.ts +0 -6
  64. package/dist/commands/wild-stubs.d.ts.map +0 -1
  65. package/dist/commands/wild-stubs.js +0 -112
  66. package/dist/commands/wild-stubs.js.map +0 -1
package/bin/git-mneme.js CHANGED
File without changes
package/bin/mneme.js CHANGED
File without changes
@@ -0,0 +1,24 @@
1
+ /**
2
+ * `mneme audit-log` — manage the HMAC-chained tamper-evident audit log.
3
+ *
4
+ * Subcommands:
5
+ * - enable — turn on append-only logging of every mutating action
6
+ * - disable — turn off (existing log preserved)
7
+ * - status — show current state + entry count + last entry
8
+ * - verify — re-walk the chain; report tamper, if any
9
+ * - rotate — archive current log + start fresh chain
10
+ * - show — dump entries (JSON or pretty)
11
+ *
12
+ * Banking / SOC2 / PCI-DSS audit requirement: `mneme audit-log verify`
13
+ * is the compliance checkpoint. CI exit code 0 = chain intact.
14
+ */
15
+ export type AuditLogAction = "enable" | "disable" | "status" | "verify" | "rotate" | "show";
16
+ export interface AuditLogOptions {
17
+ cwd: string;
18
+ action: AuditLogAction;
19
+ actor?: string;
20
+ limit?: number;
21
+ json?: boolean;
22
+ }
23
+ export declare function auditLogCommand(opts: AuditLogOptions): Promise<number>;
24
+ //# sourceMappingURL=audit-log-cmd.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit-log-cmd.d.ts","sourceRoot":"","sources":["../../src/commands/audit-log-cmd.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAMH,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,SAAS,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,MAAM,CAAC;AAE5F,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,cAAc,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,wBAAsB,eAAe,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC,CAyH5E"}
@@ -0,0 +1,131 @@
1
+ /**
2
+ * `mneme audit-log` — manage the HMAC-chained tamper-evident audit log.
3
+ *
4
+ * Subcommands:
5
+ * - enable — turn on append-only logging of every mutating action
6
+ * - disable — turn off (existing log preserved)
7
+ * - status — show current state + entry count + last entry
8
+ * - verify — re-walk the chain; report tamper, if any
9
+ * - rotate — archive current log + start fresh chain
10
+ * - show — dump entries (JSON or pretty)
11
+ *
12
+ * Banking / SOC2 / PCI-DSS audit requirement: `mneme audit-log verify`
13
+ * is the compliance checkpoint. CI exit code 0 = chain intact.
14
+ */
15
+ import kleur from "kleur";
16
+ import { ui } from "../ui.js";
17
+ import { git, security } from "@mneme-ai/core";
18
+ export async function auditLogCommand(opts) {
19
+ if (!(await git.isGitRepo(opts.cwd))) {
20
+ ui.error("Not in a git repo. Run `mneme init` first.");
21
+ return 1;
22
+ }
23
+ const meta = await git.getRepoMeta(opts.cwd);
24
+ const root = meta.rootPath;
25
+ switch (opts.action) {
26
+ case "enable": {
27
+ security.auditLog.enable(root);
28
+ security.auditLog.appendEntry(root, {
29
+ actor: opts.actor ?? "cli",
30
+ action: "audit-log-enable",
31
+ details: { enabledAt: new Date().toISOString() },
32
+ });
33
+ if (opts.json) {
34
+ process.stdout.write(JSON.stringify({ enabled: true }, null, 2) + "\n");
35
+ }
36
+ else {
37
+ ui.banner();
38
+ process.stdout.write(kleur.bold("\n 🔒 Audit log: ENABLED\n\n") +
39
+ " Every mutating action is now appended to .mneme/audit.log\n" +
40
+ " with an HMAC-SHA-256 chain. Any modification to the log will\n" +
41
+ " be detected by `mneme audit-log verify`.\n\n" +
42
+ " Set " + kleur.cyan("MNEME_AUDIT_SECRET") + " (>= 32 chars) for production.\n\n");
43
+ }
44
+ return 0;
45
+ }
46
+ case "disable": {
47
+ security.auditLog.disable(root);
48
+ if (opts.json)
49
+ process.stdout.write(JSON.stringify({ enabled: false }, null, 2) + "\n");
50
+ else
51
+ ui.success("Audit log disabled. Existing log preserved.");
52
+ return 0;
53
+ }
54
+ case "status": {
55
+ const enabled = security.auditLog.isEnabled(root);
56
+ const entries = security.auditLog.readAll(root);
57
+ const last = entries[entries.length - 1];
58
+ const data = {
59
+ enabled,
60
+ totalEntries: entries.length,
61
+ lastEntry: last ? { ts: last.ts, actor: last.actor, action: last.action, hmac: last.hmac.slice(0, 12) + "…" } : null,
62
+ };
63
+ if (opts.json)
64
+ process.stdout.write(JSON.stringify(data, null, 2) + "\n");
65
+ else {
66
+ ui.banner();
67
+ process.stdout.write(kleur.bold("\n 🔒 Audit log status\n\n") +
68
+ ` Enabled: ${enabled ? kleur.green("yes") : kleur.gray("no")}\n` +
69
+ ` Entries: ${entries.length}\n` +
70
+ (last ? ` Last: ${last.ts} · ${last.actor} · ${last.action}\n` : "") +
71
+ "\n");
72
+ }
73
+ return 0;
74
+ }
75
+ case "verify": {
76
+ const result = security.auditLog.verify(root);
77
+ if (opts.json) {
78
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
79
+ return result.ok ? 0 : 1;
80
+ }
81
+ ui.banner();
82
+ if (result.ok) {
83
+ process.stdout.write(kleur.bold(`\n ${kleur.green("✓")} Audit chain INTACT\n\n`) +
84
+ ` ${result.totalEntries} entries verified · HMAC-SHA-256 chain unbroken\n\n`);
85
+ return 0;
86
+ }
87
+ process.stdout.write(kleur.bold(`\n ${kleur.red("✗")} Audit chain BROKEN\n\n`) +
88
+ ` Total entries: ${result.totalEntries}\n` +
89
+ ` Broken at index: ${result.brokenAtIndex}\n` +
90
+ ` Reason: ${result.brokenReason}\n\n` +
91
+ kleur.yellow(" ⚠ Tamper detected. Investigate immediately.\n\n"));
92
+ return 1;
93
+ }
94
+ case "rotate": {
95
+ const r = security.auditLog.rotate(root, opts.actor ?? "cli");
96
+ if (opts.json)
97
+ process.stdout.write(JSON.stringify(r, null, 2) + "\n");
98
+ else if (r.rotated)
99
+ ui.success(`Rotated. Archived to ${r.archivedPath}`);
100
+ else
101
+ ui.success("Fresh log started.");
102
+ return 0;
103
+ }
104
+ case "show": {
105
+ const entries = security.auditLog.readAll(root);
106
+ const limit = opts.limit ?? entries.length;
107
+ const slice = entries.slice(-limit);
108
+ if (opts.json) {
109
+ process.stdout.write(JSON.stringify(slice, null, 2) + "\n");
110
+ return 0;
111
+ }
112
+ if (slice.length === 0) {
113
+ ui.info("No audit entries.");
114
+ return 0;
115
+ }
116
+ ui.banner();
117
+ process.stdout.write(kleur.bold(`\n 🔒 Audit log — last ${slice.length} entr${slice.length === 1 ? "y" : "ies"}\n\n`));
118
+ for (const e of slice) {
119
+ process.stdout.write(` ${kleur.gray(e.ts)} · ${kleur.cyan(e.actor)} · ${kleur.bold(e.action)}` +
120
+ (e.target ? ` → ${e.target}` : "") +
121
+ kleur.gray(` [${e.hmac.slice(0, 8)}]`) +
122
+ "\n");
123
+ }
124
+ process.stdout.write("\n");
125
+ return 0;
126
+ }
127
+ }
128
+ ui.error(`Unknown audit-log action: ${opts.action}`);
129
+ return 1;
130
+ }
131
+ //# sourceMappingURL=audit-log-cmd.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit-log-cmd.js","sourceRoot":"","sources":["../../src/commands/audit-log-cmd.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * `mneme bench` — AI-Memory-Bench CLI.
3
+ *
4
+ * Two modes:
5
+ * 1. `--probes-out <file>` → emit JSON of probes for the AI to answer
6
+ * 2. `--score <answers.json>` → score AI's answers, render leaderboard
7
+ *
8
+ * Workflow:
9
+ * $ mneme bench --probes-out probes.json
10
+ * $ <feed probes.json questions to your AI; collect answers as JSON map>
11
+ * $ mneme bench --score answers.json --label "claude-code-with-mneme"
12
+ */
13
+ import { bench } from "@mneme-ai/core";
14
+ export interface BenchOptions {
15
+ cwd: string;
16
+ probesOut?: string;
17
+ score?: string;
18
+ label?: string;
19
+ category?: bench.ProbeCategory;
20
+ json?: boolean;
21
+ }
22
+ export declare function benchCommand(opts: BenchOptions): Promise<number>;
23
+ //# sourceMappingURL=bench.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bench.d.ts","sourceRoot":"","sources":["../../src/commands/bench.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAKH,OAAO,EAAO,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAE5C,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAC/B,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,wBAAsB,YAAY,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,CA8EtE"}
@@ -0,0 +1,85 @@
1
+ /**
2
+ * `mneme bench` — AI-Memory-Bench CLI.
3
+ *
4
+ * Two modes:
5
+ * 1. `--probes-out <file>` → emit JSON of probes for the AI to answer
6
+ * 2. `--score <answers.json>` → score AI's answers, render leaderboard
7
+ *
8
+ * Workflow:
9
+ * $ mneme bench --probes-out probes.json
10
+ * $ <feed probes.json questions to your AI; collect answers as JSON map>
11
+ * $ mneme bench --score answers.json --label "claude-code-with-mneme"
12
+ */
13
+ import { readFileSync, writeFileSync, existsSync } from "node:fs";
14
+ import kleur from "kleur";
15
+ import { ui } from "../ui.js";
16
+ import { git, bench } from "@mneme-ai/core";
17
+ export async function benchCommand(opts) {
18
+ if (!(await git.isGitRepo(opts.cwd))) {
19
+ ui.error("Not in a git repo. Run `mneme init` first.");
20
+ return 1;
21
+ }
22
+ const meta = await git.getRepoMeta(opts.cwd);
23
+ const probes = opts.category
24
+ ? bench.STANDARD_PROBES.filter((p) => p.category === opts.category)
25
+ : bench.STANDARD_PROBES;
26
+ // ── Mode 1: emit probes for AI to answer ──────────────────────────
27
+ if (opts.probesOut) {
28
+ const out = probes.map((p) => ({
29
+ id: p.id,
30
+ category: p.category,
31
+ question: p.question,
32
+ tags: p.tags,
33
+ }));
34
+ writeFileSync(opts.probesOut, JSON.stringify(out, null, 2), "utf8");
35
+ if (opts.json) {
36
+ process.stdout.write(JSON.stringify({ probesEmitted: out.length, path: opts.probesOut }, null, 2) + "\n");
37
+ }
38
+ else {
39
+ ui.banner();
40
+ process.stdout.write(kleur.bold(`\n 📐 AI-Memory-Bench — ${out.length} probe(s) written to ${opts.probesOut}\n\n`) +
41
+ " Next:\n" +
42
+ " 1. Feed each `question` to your AI client (with or without Mneme attached)\n" +
43
+ " 2. Collect responses into a JSON map: { \"<probe-id>\": \"<answer>\", ... }\n" +
44
+ " 3. Run: " + kleur.cyan("mneme bench --score answers.json --label '<your-config>'") + "\n\n");
45
+ }
46
+ return 0;
47
+ }
48
+ // ── Mode 2: score AI answers ──────────────────────────────────────
49
+ if (opts.score) {
50
+ if (!existsSync(opts.score)) {
51
+ ui.error(`No such answers file: ${opts.score}`);
52
+ return 1;
53
+ }
54
+ let answers;
55
+ try {
56
+ answers = JSON.parse(readFileSync(opts.score, "utf8"));
57
+ }
58
+ catch (err) {
59
+ ui.error(`Could not parse answers JSON: ${err.message}`);
60
+ return 1;
61
+ }
62
+ const result = await bench.runBench(probes, answers, meta.rootPath);
63
+ const label = opts.label ?? "unnamed-run";
64
+ if (opts.json) {
65
+ process.stdout.write(JSON.stringify({ label, ...result }, null, 2) + "\n");
66
+ return 0;
67
+ }
68
+ ui.banner();
69
+ process.stdout.write("\n" + bench.renderLeaderboard(result, label) + "\n\n");
70
+ process.stdout.write(kleur.gray(" Tip: pipe " + kleur.cyan("--json") + " into your CI to fail builds when " +
71
+ "Wilson lower bound drops below your threshold.\n\n"));
72
+ return result.hallucinationRate > 0.1 ? 1 : 0;
73
+ }
74
+ // ── No mode → print help ──────────────────────────────────────────
75
+ ui.banner();
76
+ process.stdout.write(kleur.bold("\n 📐 AI-Memory-Bench — the first reproducible benchmark for AI memory layers\n\n") +
77
+ ` Probes available: ${kleur.green(`${probes.length}`)} (across ${kleur.cyan(`${new Set(probes.map((p) => p.category)).size}`)} categories)\n\n` +
78
+ " Usage:\n" +
79
+ ` ${kleur.cyan("mneme bench --probes-out probes.json")} emit probes for the AI to answer\n` +
80
+ ` ${kleur.cyan("mneme bench --score answers.json --label X")} score the AI's answers\n\n` +
81
+ " Why measure: if you can't quantify hallucination, you can't reduce it.\n" +
82
+ " Run with + without Mneme attached, compare the two leaderboards.\n\n");
83
+ return 0;
84
+ }
85
+ //# sourceMappingURL=bench.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bench.js","sourceRoot":"","sources":["../../src/commands/bench.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * `mneme constitution` — Codebase Constitution synthesizer.
3
+ *
4
+ * Synthesises a living "constitution" document from the repo's history:
5
+ * never-do rules from past regrets · expert-pairing constraints from
6
+ * atrophy · file-level safety constraints from incidents · architectural
7
+ * decisions auto-extracted from commit messages.
8
+ *
9
+ * Output: a Markdown document AI tools can prepend to their system prompt
10
+ * (via the MCP `mneme.constitution.get` tool) so AI literally cannot
11
+ * suggest things that contradict the constitution.
12
+ *
13
+ * v1.10.0 ships rule-extraction from 4 sources:
14
+ * • regret patterns (rules from "we tried X, broke Y")
15
+ * • atrophy / bus-factor (pairing constraints)
16
+ * • forensics anomalies (security must-not-do rules)
17
+ * • decisions (ADR-style "must do" rules)
18
+ */
19
+ export interface ConstitutionOptions {
20
+ cwd: string;
21
+ out?: string;
22
+ json?: boolean;
23
+ }
24
+ export interface ConstitutionRule {
25
+ id: string;
26
+ source: "regret" | "atrophy" | "forensics" | "decision";
27
+ rule: string;
28
+ evidence: string;
29
+ severity: "must-not" | "must" | "should" | "consider";
30
+ }
31
+ export interface Constitution {
32
+ generatedAt: string;
33
+ generatedByMneme: string;
34
+ repoName: string;
35
+ rules: ConstitutionRule[];
36
+ }
37
+ declare function extractRegretRules(commits: import("@mneme-ai/core").Commit[]): ConstitutionRule[];
38
+ declare function extractDecisionRules(commits: import("@mneme-ai/core").Commit[]): ConstitutionRule[];
39
+ export declare function renderConstitutionMarkdown(c: Constitution): string;
40
+ export declare function constitutionCommand(opts: ConstitutionOptions): Promise<number>;
41
+ export declare const _renderConstitutionMarkdownForTests: typeof renderConstitutionMarkdown;
42
+ export declare const _extractRegretRulesForTests: typeof extractRegretRules;
43
+ export declare const _extractDecisionRulesForTests: typeof extractDecisionRules;
44
+ export {};
45
+ //# sourceMappingURL=constitution.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constitution.d.ts","sourceRoot":"","sources":["../../src/commands/constitution.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AASH,MAAM,WAAW,mBAAmB;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,QAAQ,GAAG,SAAS,GAAG,WAAW,GAAG,UAAU,CAAC;IACxD,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,UAAU,GAAG,MAAM,GAAG,QAAQ,GAAG,UAAU,CAAC;CACvD;AAED,MAAM,WAAW,YAAY;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,MAAM,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,gBAAgB,EAAE,CAAC;CAC3B;AAeD,iBAAS,kBAAkB,CAAC,OAAO,EAAE,OAAO,gBAAgB,EAAE,MAAM,EAAE,GAAG,gBAAgB,EAAE,CAe1F;AA6CD,iBAAS,oBAAoB,CAAC,OAAO,EAAE,OAAO,gBAAgB,EAAE,MAAM,EAAE,GAAG,gBAAgB,EAAE,CAe5F;AAED,wBAAgB,0BAA0B,CAAC,CAAC,EAAE,YAAY,GAAG,MAAM,CA8ClE;AAED,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,MAAM,CAAC,CAmDpF;AAGD,eAAO,MAAM,mCAAmC,mCAA6B,CAAC;AAC9E,eAAO,MAAM,2BAA2B,2BAAqB,CAAC;AAC9D,eAAO,MAAM,6BAA6B,6BAAuB,CAAC"}
@@ -0,0 +1,204 @@
1
+ /**
2
+ * `mneme constitution` — Codebase Constitution synthesizer.
3
+ *
4
+ * Synthesises a living "constitution" document from the repo's history:
5
+ * never-do rules from past regrets · expert-pairing constraints from
6
+ * atrophy · file-level safety constraints from incidents · architectural
7
+ * decisions auto-extracted from commit messages.
8
+ *
9
+ * Output: a Markdown document AI tools can prepend to their system prompt
10
+ * (via the MCP `mneme.constitution.get` tool) so AI literally cannot
11
+ * suggest things that contradict the constitution.
12
+ *
13
+ * v1.10.0 ships rule-extraction from 4 sources:
14
+ * • regret patterns (rules from "we tried X, broke Y")
15
+ * • atrophy / bus-factor (pairing constraints)
16
+ * • forensics anomalies (security must-not-do rules)
17
+ * • decisions (ADR-style "must do" rules)
18
+ */
19
+ import { existsSync, writeFileSync, mkdirSync } from "node:fs";
20
+ import { join, dirname } from "node:path";
21
+ import kleur from "kleur";
22
+ import { ui } from "../ui.js";
23
+ import { git, store, util, people } from "@mneme-ai/core";
24
+ import { dbPath } from "../paths.js";
25
+ function safeReadVersion() {
26
+ try {
27
+ const here = new URL(".", import.meta.url).pathname;
28
+ const fs = require("node:fs");
29
+ const path = require("node:path");
30
+ const pkgPath = path.resolve(here, "..", "..", "package.json");
31
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
32
+ return pkg.version ?? "0.0.0";
33
+ }
34
+ catch {
35
+ return "0.0.0";
36
+ }
37
+ }
38
+ function extractRegretRules(commits) {
39
+ const rules = [];
40
+ // Find revert/hotfix patterns
41
+ const reverts = commits.filter((c) => /\b(revert|hotfix|rollback)\b/i.test(c.subject));
42
+ for (let i = 0; i < Math.min(5, reverts.length); i++) {
43
+ const r = reverts[i];
44
+ rules.push({
45
+ id: `regret-${r.shortHash}`,
46
+ source: "regret",
47
+ rule: `Be cautious with patterns similar to: ${r.subject.slice(0, 100)}`,
48
+ evidence: `Commit ${r.shortHash} (${r.authorDate.slice(0, 10)}) — past regret`,
49
+ severity: "should",
50
+ });
51
+ }
52
+ return rules;
53
+ }
54
+ function extractAtrophyRules(s) {
55
+ const rules = [];
56
+ try {
57
+ const report = people.atrophy(s, { topN: 10 });
58
+ // For records below 30 freshness, add a pairing constraint
59
+ const records = report.records ?? [];
60
+ for (let i = 0; i < Math.min(5, records.length); i++) {
61
+ const rec = records[i];
62
+ if (rec.score < 30 && rec.authorEmail && rec.area) {
63
+ rules.push({
64
+ id: `atrophy-${i}`,
65
+ source: "atrophy",
66
+ rule: `Pair with ${rec.authorEmail} when modifying ${rec.area} (knowledge atrophy ${rec.score}/100)`,
67
+ evidence: `Atrophy report — ${rec.authorEmail}'s ${rec.area} expertise is fading`,
68
+ severity: "should",
69
+ });
70
+ }
71
+ }
72
+ }
73
+ catch { }
74
+ return rules;
75
+ }
76
+ function extractForensicsRules(s) {
77
+ const rules = [];
78
+ try {
79
+ const incidents = s.db.prepare("SELECT * FROM incidents LIMIT 10").all();
80
+ for (let i = 0; i < incidents.length; i++) {
81
+ const inc = incidents[i];
82
+ const files = inc.affected_files ? JSON.parse(inc.affected_files) : [];
83
+ if (inc.title && files.length > 0) {
84
+ rules.push({
85
+ id: `forensics-${i}`,
86
+ source: "forensics",
87
+ rule: `Apply extra scrutiny to ${files[0]} — past incident: ${inc.title.slice(0, 80)}`,
88
+ evidence: `Incident affected ${files.length} file(s)`,
89
+ severity: "must",
90
+ });
91
+ }
92
+ }
93
+ }
94
+ catch { }
95
+ return rules;
96
+ }
97
+ function extractDecisionRules(commits) {
98
+ const rules = [];
99
+ const decisionMarkers = /\b(adr|decision|chose|migrate|switch from .+ to .+)\b/i;
100
+ const filtered = commits.filter((c) => decisionMarkers.test(c.subject));
101
+ for (let i = 0; i < Math.min(5, filtered.length); i++) {
102
+ const r = filtered[i];
103
+ rules.push({
104
+ id: `decision-${r.shortHash}`,
105
+ source: "decision",
106
+ rule: `Prior decision: ${r.subject.slice(0, 100)}`,
107
+ evidence: `Commit ${r.shortHash} (${r.authorDate.slice(0, 10)}) — architectural decision`,
108
+ severity: "should",
109
+ });
110
+ }
111
+ return rules;
112
+ }
113
+ export function renderConstitutionMarkdown(c) {
114
+ const lines = [];
115
+ lines.push(`# ${c.repoName} — Codebase Constitution`);
116
+ lines.push("");
117
+ lines.push(`> AI tools: prepend this to your system prompt before answering. ` +
118
+ `These rules are derived from this repo's history of regrets, decisions, ` +
119
+ `incidents, and knowledge-atrophy patterns. **Contradicting them is a hallucination signal.**`);
120
+ lines.push("");
121
+ lines.push(`Generated: ${c.generatedAt} · By Mneme ${c.generatedByMneme}`);
122
+ lines.push("");
123
+ const bySource = new Map();
124
+ for (const r of c.rules) {
125
+ if (!bySource.has(r.source))
126
+ bySource.set(r.source, []);
127
+ bySource.get(r.source).push(r);
128
+ }
129
+ for (const [source, rules] of bySource) {
130
+ const heading = source === "regret" ? "## ⚠ Past regrets — patterns to avoid" :
131
+ source === "atrophy" ? "## 👥 Knowledge atrophy — pairing constraints" :
132
+ source === "forensics" ? "## 🛡 Security — must-do scrutiny zones" :
133
+ "## 📋 Architectural decisions";
134
+ lines.push(heading);
135
+ lines.push("");
136
+ for (const r of rules) {
137
+ const tag = r.severity === "must-not" ? "❌ MUST NOT" :
138
+ r.severity === "must" ? "✅ MUST" :
139
+ r.severity === "should" ? "▸ SHOULD" : "○ CONSIDER";
140
+ lines.push(`- ${tag}: ${r.rule}`);
141
+ lines.push(` - *Evidence: ${r.evidence}*`);
142
+ }
143
+ lines.push("");
144
+ }
145
+ if (c.rules.length === 0) {
146
+ lines.push("## (No rules synthesized)");
147
+ lines.push("");
148
+ lines.push("Run `mneme index` first to build the corpus, then re-run `mneme constitution` to derive rules.");
149
+ }
150
+ lines.push("---");
151
+ lines.push("");
152
+ lines.push("> *This document is auto-synthesized. Re-run `mneme constitution` after major commits.*");
153
+ return lines.join("\n");
154
+ }
155
+ export async function constitutionCommand(opts) {
156
+ if (!(await git.isGitRepo(opts.cwd))) {
157
+ ui.error("Not in a git repo. Run `mneme init` first.");
158
+ return 1;
159
+ }
160
+ const meta = await git.getRepoMeta(opts.cwd);
161
+ const dbPathStr = dbPath(meta.rootPath);
162
+ if (!existsSync(dbPathStr)) {
163
+ ui.error("No Mneme index found. Run `mneme index` first.");
164
+ return 1;
165
+ }
166
+ const s = new store.MnemeStore(dbPathStr);
167
+ const commits = util.loadAllCommits(s);
168
+ const rules = [
169
+ ...extractForensicsRules(s),
170
+ ...extractRegretRules(commits),
171
+ ...extractAtrophyRules(s),
172
+ ...extractDecisionRules(commits),
173
+ ];
174
+ const constitution = {
175
+ generatedAt: new Date().toISOString(),
176
+ generatedByMneme: safeReadVersion(),
177
+ repoName: meta.repo ?? "(unknown)",
178
+ rules,
179
+ };
180
+ const md = renderConstitutionMarkdown(constitution);
181
+ // Always cache the result for the MCP tool to read
182
+ const cachePath = join(meta.rootPath, ".mneme", "constitution.md");
183
+ if (!existsSync(dirname(cachePath)))
184
+ mkdirSync(dirname(cachePath), { recursive: true });
185
+ writeFileSync(cachePath, md, "utf8");
186
+ if (opts.out) {
187
+ writeFileSync(opts.out, md, "utf8");
188
+ }
189
+ if (opts.json) {
190
+ process.stdout.write(JSON.stringify(constitution, null, 2) + "\n");
191
+ return 0;
192
+ }
193
+ ui.banner();
194
+ process.stdout.write(kleur.bold(`\n 📜 Constitution synthesised — ${rules.length} rule(s)\n\n`) +
195
+ ` ${kleur.green("✓")} cached at .mneme/constitution.md\n` +
196
+ (opts.out ? ` ${kleur.green("✓")} written to ${opts.out}\n` : "") +
197
+ `\n AI tools can fetch via the MCP tool ${kleur.cyan("mneme.constitution.get")}.\n\n`);
198
+ return 0;
199
+ }
200
+ // Test exports
201
+ export const _renderConstitutionMarkdownForTests = renderConstitutionMarkdown;
202
+ export const _extractRegretRulesForTests = extractRegretRules;
203
+ export const _extractDecisionRulesForTests = extractDecisionRules;
204
+ //# sourceMappingURL=constitution.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constitution.js","sourceRoot":"","sources":["../../src/commands/constitution.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"daemon.d.ts","sourceRoot":"","sources":["../../src/commands/daemon.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAUH,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,QAAQ,GAAG,MAAM,CAAC;IAC7C,mEAAmE;IACnE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAsTD,wBAAsB,aAAa,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CAUxE"}
1
+ {"version":3,"file":"daemon.d.ts","sourceRoot":"","sources":["../../src/commands/daemon.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAUH,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,QAAQ,GAAG,MAAM,CAAC;IAC7C,mEAAmE;IACnE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAyVD,wBAAsB,aAAa,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CAUxE"}
@@ -17,7 +17,7 @@
17
17
  * Cross-platform: PID-file approach works on win32 + darwin + linux.
18
18
  * fs.watch is supported on all three. No native deps.
19
19
  */
20
- import { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync, watch, renameSync } from "node:fs";
20
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync, watch, renameSync, statSync } from "node:fs";
21
21
  import { join } from "node:path";
22
22
  import { spawn, spawnSync } from "node:child_process";
23
23
  import { setTimeout as wait } from "node:timers/promises";
@@ -44,6 +44,23 @@ function isAlive(pid) {
44
44
  return err.code === "EPERM";
45
45
  }
46
46
  }
47
+ /** v1.11.0 security: verify the PID file is owned by the current user.
48
+ * On Windows, file UID is not meaningful — we trust the per-user repo
49
+ * path (typically under %USERPROFILE%) instead. */
50
+ function isPidFileOwnedByMe(pidPath) {
51
+ try {
52
+ if (process.platform === "win32")
53
+ return true; // POSIX-only check
54
+ const st = statSync(pidPath);
55
+ const me = process.getuid?.();
56
+ if (typeof me !== "number")
57
+ return true; // can't check, fail open
58
+ return st.uid === me;
59
+ }
60
+ catch {
61
+ return true; // missing → not a security issue, fail open for the caller
62
+ }
63
+ }
47
64
  function readStatus(repoRoot) {
48
65
  const p = paths(repoRoot);
49
66
  if (!existsSync(p.status))
@@ -185,6 +202,18 @@ async function startDaemon(opts) {
185
202
  const p = paths(meta.rootPath);
186
203
  if (!existsSync(p.dir))
187
204
  mkdirSync(p.dir, { recursive: true });
205
+ // v1.11.0 security hardening: refuse to read/trust a PID file that's
206
+ // not owned by the current user. Mitigates the "another user / attacker
207
+ // plants a PID file in a shared workspace, hijacks our daemon control"
208
+ // attack on multi-user POSIX boxes.
209
+ if (existsSync(p.pid) && !isPidFileOwnedByMe(p.pid)) {
210
+ if (opts.json) {
211
+ process.stdout.write(JSON.stringify({ started: false, error: "pid-file-owned-by-other-user", path: p.pid }, null, 2) + "\n");
212
+ return 1;
213
+ }
214
+ ui.error(`Refusing to start: ${p.pid} is owned by a different user. Investigate before removing.`);
215
+ return 1;
216
+ }
188
217
  // Already running?
189
218
  if (existsSync(p.pid)) {
190
219
  const existing = parseInt(readFileSync(p.pid, "utf8").trim(), 10);
@@ -203,8 +232,9 @@ async function startDaemon(opts) {
203
232
  catch { }
204
233
  }
205
234
  if (opts.attached) {
206
- // We ARE the daemon — write PID file and run loop
207
- writeFileSync(p.pid, String(process.pid), "utf8");
235
+ // We ARE the daemon — write PID file and run loop. Restrictive perms
236
+ // (0600) so other users on a shared box can't read/forge.
237
+ writeFileSync(p.pid, String(process.pid), { encoding: "utf8", mode: 0o600 });
208
238
  await runDaemonLoop(meta.rootPath);
209
239
  return 0;
210
240
  }
@@ -246,6 +276,14 @@ async function stopDaemon(opts) {
246
276
  ui.warn("No daemon running.");
247
277
  return 0;
248
278
  }
279
+ // v1.11.0 security hardening: refuse to act on a foreign PID file.
280
+ if (!isPidFileOwnedByMe(p.pid)) {
281
+ if (opts.json)
282
+ process.stdout.write(JSON.stringify({ stopped: false, error: "pid-file-owned-by-other-user", path: p.pid }, null, 2) + "\n");
283
+ else
284
+ ui.error(`Refusing to stop: ${p.pid} is owned by a different user.`);
285
+ return 1;
286
+ }
249
287
  const pid = parseInt(readFileSync(p.pid, "utf8").trim(), 10);
250
288
  if (!isAlive(pid)) {
251
289
  try {