@mneme-ai/core 0.15.0 → 0.17.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 (53) hide show
  1. package/dist/forensics/anomaly.d.ts +83 -0
  2. package/dist/forensics/anomaly.d.ts.map +1 -0
  3. package/dist/forensics/anomaly.js +218 -0
  4. package/dist/forensics/anomaly.js.map +1 -0
  5. package/dist/forensics/forensics.test.d.ts +2 -0
  6. package/dist/forensics/forensics.test.d.ts.map +1 -0
  7. package/dist/forensics/forensics.test.js +281 -0
  8. package/dist/forensics/forensics.test.js.map +1 -0
  9. package/dist/forensics/index.d.ts +5 -0
  10. package/dist/forensics/index.d.ts.map +1 -0
  11. package/dist/forensics/index.js +5 -0
  12. package/dist/forensics/index.js.map +1 -0
  13. package/dist/forensics/likelihood.d.ts +120 -0
  14. package/dist/forensics/likelihood.d.ts.map +1 -0
  15. package/dist/forensics/likelihood.js +161 -0
  16. package/dist/forensics/likelihood.js.map +1 -0
  17. package/dist/forensics/loci.d.ts +54 -0
  18. package/dist/forensics/loci.d.ts.map +1 -0
  19. package/dist/forensics/loci.js +164 -0
  20. package/dist/forensics/loci.js.map +1 -0
  21. package/dist/forensics/vulnhunt.d.ts +62 -0
  22. package/dist/forensics/vulnhunt.d.ts.map +1 -0
  23. package/dist/forensics/vulnhunt.js +217 -0
  24. package/dist/forensics/vulnhunt.js.map +1 -0
  25. package/dist/guardian/guardian.d.ts +93 -0
  26. package/dist/guardian/guardian.d.ts.map +1 -0
  27. package/dist/guardian/guardian.js +170 -0
  28. package/dist/guardian/guardian.js.map +1 -0
  29. package/dist/guardian/guardian.test.d.ts +2 -0
  30. package/dist/guardian/guardian.test.d.ts.map +1 -0
  31. package/dist/guardian/guardian.test.js +154 -0
  32. package/dist/guardian/guardian.test.js.map +1 -0
  33. package/dist/guardian/index.d.ts +2 -0
  34. package/dist/guardian/index.d.ts.map +1 -0
  35. package/dist/guardian/index.js +2 -0
  36. package/dist/guardian/index.js.map +1 -0
  37. package/dist/index.d.ts +2 -0
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +2 -0
  40. package/dist/index.js.map +1 -1
  41. package/dist/retrieve/index.d.ts +1 -0
  42. package/dist/retrieve/index.d.ts.map +1 -1
  43. package/dist/retrieve/index.js +1 -0
  44. package/dist/retrieve/index.js.map +1 -1
  45. package/dist/retrieve/novel-scoring.d.ts +146 -0
  46. package/dist/retrieve/novel-scoring.d.ts.map +1 -0
  47. package/dist/retrieve/novel-scoring.js +256 -0
  48. package/dist/retrieve/novel-scoring.js.map +1 -0
  49. package/dist/retrieve/novel-scoring.test.d.ts +2 -0
  50. package/dist/retrieve/novel-scoring.test.d.ts.map +1 -0
  51. package/dist/retrieve/novel-scoring.test.js +221 -0
  52. package/dist/retrieve/novel-scoring.test.js.map +1 -0
  53. package/package.json +1 -1
@@ -0,0 +1,217 @@
1
+ const RULES = [
2
+ // ── CRYPTO ──────────────────────────────────────────────────────────
3
+ {
4
+ class: "crypto-weakness",
5
+ severity: "high",
6
+ reference: "CWE-327",
7
+ pattern: /\b(?:MD5|SHA-?1)\s*\(/i,
8
+ summary: "MD5 / SHA-1 used as a hash function (broken for security)",
9
+ },
10
+ {
11
+ class: "crypto-weakness",
12
+ severity: "high",
13
+ reference: "CWE-327",
14
+ pattern: /\b(?:DES|3DES|RC4|Blowfish)\b/,
15
+ summary: "Weak / deprecated cipher referenced",
16
+ },
17
+ {
18
+ class: "crypto-weakness",
19
+ severity: "critical",
20
+ reference: "CWE-330",
21
+ pattern: /Math\.random\s*\(\s*\)|new\s+Random\s*\(\s*\)/,
22
+ summary: "Non-cryptographic RNG used (Math.random / Random) — use crypto.randomBytes",
23
+ },
24
+ {
25
+ class: "crypto-weakness",
26
+ severity: "high",
27
+ reference: "CWE-321",
28
+ pattern: /(?:secret|api[_-]?key|password|token|private[_-]?key)\s*=\s*["'][a-zA-Z0-9+/=_-]{16,}["']/i,
29
+ summary: "Hardcoded credential / secret in source",
30
+ },
31
+ // ── INJECTION ──────────────────────────────────────────────────────
32
+ {
33
+ class: "injection-sql",
34
+ severity: "critical",
35
+ reference: "CWE-89",
36
+ pattern: /(?:SELECT|INSERT|UPDATE|DELETE)\s+[^;]*?\$\{[^}]+\}|"\s*SELECT[^"]*"\s*\+\s*\w+|f"\s*SELECT[^"]*\{/i,
37
+ summary: "SQL string concatenation / interpolation — possible injection",
38
+ },
39
+ {
40
+ class: "injection-shell",
41
+ severity: "critical",
42
+ reference: "CWE-78",
43
+ pattern: /(?:exec|spawn|system|popen|os\.system|subprocess\.\w+)\s*\(\s*[^,)]*(?:\$\{|\+\s*\w|input\(|argv\[)/,
44
+ summary: "Shell exec with concatenated / interpolated input",
45
+ },
46
+ {
47
+ class: "injection-xss",
48
+ severity: "high",
49
+ reference: "CWE-79",
50
+ pattern: /\bdangerouslySetInnerHTML\b|\binnerHTML\s*=\s*[^"'][^;]+(?:input|req\.|params|body)/,
51
+ summary: "innerHTML / dangerouslySetInnerHTML with user-controlled data",
52
+ },
53
+ {
54
+ class: "injection-xss",
55
+ severity: "medium",
56
+ reference: "CWE-95",
57
+ pattern: /\beval\s*\(\s*[^"')]*(?:input|req\.|params|body|argv)/,
58
+ summary: "eval() with user-controlled input",
59
+ },
60
+ // ── AUTH FLAWS ─────────────────────────────────────────────────────
61
+ {
62
+ class: "auth-flaw",
63
+ severity: "critical",
64
+ reference: "CWE-798",
65
+ pattern: /\b(?:Bearer|Basic)\s+[A-Za-z0-9+/=._-]{16,}/,
66
+ summary: "Hardcoded bearer / basic auth token",
67
+ },
68
+ {
69
+ class: "auth-flaw",
70
+ severity: "critical",
71
+ reference: "CWE-347",
72
+ pattern: /jwt\.(?:decode|verify)\s*\([^,)]+,\s*null|jwt\.decode\s*\(/i,
73
+ summary: "JWT decoded without signature verification",
74
+ },
75
+ {
76
+ class: "auth-flaw",
77
+ severity: "high",
78
+ reference: "CWE-942",
79
+ pattern: /Access-Control-Allow-Origin[^;]*\*[^;]*Access-Control-Allow-Credentials\s*:\s*true|cors\s*\(\s*\{\s*origin\s*:\s*["']\*/i,
80
+ summary: "CORS wildcard origin combined with credentials enabled",
81
+ },
82
+ // ── FINANCIAL LOGIC (bank-grade) ──────────────────────────────────
83
+ {
84
+ class: "financial-logic",
85
+ severity: "critical",
86
+ reference: "CWE-190",
87
+ pattern: /(?:amount|balance|price|cents|usd|thb|eur)\s*[+\-*]\s*(?:amount|balance|price|cents|usd|thb|eur)/i,
88
+ summary: "Arithmetic on money-typed names — verify overflow + precision",
89
+ },
90
+ {
91
+ class: "financial-logic",
92
+ severity: "high",
93
+ reference: "CWE-682",
94
+ pattern: /Number\s*\(\s*(?:amount|balance|price|fee|tax)|parseFloat\s*\(\s*(?:amount|balance|price|fee|tax)/i,
95
+ summary: "Money cast to JS Number — precision loss; use BigInt or money lib",
96
+ },
97
+ {
98
+ class: "financial-logic",
99
+ severity: "high",
100
+ reference: "CWE-840",
101
+ pattern: /amount\s*[<>]=?\s*0\b/i, // We want to see if it's a guard or a flaw
102
+ summary: "Direct comparison of `amount` to 0 — verify negative-amount handling",
103
+ },
104
+ // ── SUPPLY CHAIN ───────────────────────────────────────────────────
105
+ {
106
+ class: "supply-chain",
107
+ severity: "medium",
108
+ reference: "CWE-1357",
109
+ pattern: /^\+\s*"[^"]+":\s*"(\^|~)?[0-9]+\.[0-9]+\.[0-9]+(?:-[a-zA-Z0-9.-]+)?",?\s*$/m,
110
+ summary: "Dependency added or version changed — verify it's locked + scanned",
111
+ },
112
+ // ── INFO LEAKAGE ───────────────────────────────────────────────────
113
+ {
114
+ class: "info-leakage",
115
+ severity: "medium",
116
+ reference: "CWE-209",
117
+ pattern: /console\.log\s*\([^)]*(?:password|token|secret|key|jwt|cookie|session)/i,
118
+ summary: "Sensitive value logged to console",
119
+ },
120
+ {
121
+ class: "info-leakage",
122
+ severity: "high",
123
+ reference: "CWE-209",
124
+ pattern: /\b(?:err|error|e)\.stack\b.*?(?:res\.send|res\.json|return)/,
125
+ summary: "Stack trace exposed to client response",
126
+ },
127
+ // ── RACE CONDITIONS ────────────────────────────────────────────────
128
+ {
129
+ class: "race-condition",
130
+ severity: "medium",
131
+ reference: "CWE-362",
132
+ pattern: /if\s*\([^)]*await[^)]*\)\s*\{[\s\S]{0,200}?await/,
133
+ summary: "Check-then-await pattern — possible TOCTOU race",
134
+ },
135
+ // ── PRIVILEGE ──────────────────────────────────────────────────────
136
+ {
137
+ class: "privilege",
138
+ severity: "high",
139
+ reference: "CWE-269",
140
+ pattern: /\bsetuid\s*\(\s*0\s*\)|\bos\.setuid\s*\(/,
141
+ summary: "Privilege escalation to root via setuid",
142
+ },
143
+ ];
144
+ const FIX_KEYWORDS = /\b(fix|fixes|fixed|patch|patched|secure|security|cve-?\d+|vuln(?:erability)?|exploit|hotfix)\b/i;
145
+ const SECURITY_SUBJECT = /\b(security|cve-?\d+|vuln|exploit|csrf|xss|sqli|rce|sssrf|xxe|auth\s+bypass)\b/i;
146
+ /**
147
+ * Scan a set of (commit, diffText) pairs for vulnerability patterns.
148
+ *
149
+ * If diff text isn't available, we still scan subject + body — useful
150
+ * for catching "silent fix" commits (commits whose subject says nothing
151
+ * but whose diff would have matched).
152
+ */
153
+ export function huntVulnerabilities(inputs) {
154
+ const hits = [];
155
+ const silentFixes = [];
156
+ for (const { commit, diff } of inputs) {
157
+ const subjectAndBody = `${commit.subject}\n${commit.body || ""}`;
158
+ const text = `${subjectAndBody}\n${diff || ""}`;
159
+ const looksLikeFix = FIX_KEYWORDS.test(subjectAndBody);
160
+ for (const rule of RULES) {
161
+ const m = rule.pattern.exec(text);
162
+ if (!m)
163
+ continue;
164
+ // For diff-only rules (additionsOnly), require the matched line to
165
+ // appear after a leading "+" in a diff context.
166
+ if (rule.additionsOnly !== false && diff) {
167
+ const line = extractLineAround(text, m.index);
168
+ if (line && !line.startsWith("+")) {
169
+ continue;
170
+ }
171
+ }
172
+ hits.push({
173
+ commit,
174
+ class: rule.class,
175
+ severity: rule.severity,
176
+ summary: rule.summary,
177
+ evidence: truncate(m[0], 200),
178
+ reference: rule.reference,
179
+ looksLikeFix,
180
+ });
181
+ }
182
+ // Silent-fix detection: subject looks security-related but didn't
183
+ // raise any rule hits in the same pass — we still record it because
184
+ // even a "subject hint" is useful for compliance.
185
+ if (SECURITY_SUBJECT.test(subjectAndBody)) {
186
+ silentFixes.push(commit);
187
+ }
188
+ }
189
+ const bySeverity = {
190
+ info: 0,
191
+ low: 0,
192
+ medium: 0,
193
+ high: 0,
194
+ critical: 0,
195
+ };
196
+ const byClass = {};
197
+ for (const h of hits) {
198
+ bySeverity[h.severity] += 1;
199
+ byClass[h.class] = (byClass[h.class] ?? 0) + 1;
200
+ }
201
+ return {
202
+ hits,
203
+ bySeverity,
204
+ byClass,
205
+ silentFixes,
206
+ scanned: inputs.length,
207
+ };
208
+ }
209
+ function extractLineAround(text, index) {
210
+ const start = text.lastIndexOf("\n", index) + 1;
211
+ const end = text.indexOf("\n", index);
212
+ return text.slice(start, end < 0 ? undefined : end);
213
+ }
214
+ function truncate(s, n) {
215
+ return s.length <= n ? s : s.slice(0, n - 1) + "…";
216
+ }
217
+ //# sourceMappingURL=vulnhunt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vulnhunt.js","sourceRoot":"","sources":["../../src/forensics/vulnhunt.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Mneme Guardian — the 24/7 self-healing engine.
3
+ *
4
+ * while (true) {
5
+ * diagnose();
6
+ * fix();
7
+ * learn();
8
+ * sleep(interval);
9
+ * }
10
+ *
11
+ * The Guardian is a long-running diagnostic + auto-remediation loop. It
12
+ * watches for weaknesses (regressions in index quality, drift between
13
+ * HEAD and the index, missing embeddings, schema-version mismatch) and
14
+ * threats (data corruption signals, token expiry on remote APIs, secret
15
+ * patterns slipping through redaction) and applies safe corrective
16
+ * actions automatically. Anything risky is *logged with a recommendation*
17
+ * rather than auto-applied.
18
+ *
19
+ * Pure data structures + dispatch — no side effects in this module
20
+ * (file I/O lives in the CLI command). Caller decides whether to
21
+ * actually apply the recommended action or just observe.
22
+ *
23
+ * Design principles:
24
+ * • SAFE BY DEFAULT — auto-apply only the actions that are demonstrably
25
+ * reversible (re-index, calibrate). Anything that could lose data
26
+ * gets recommended, not executed.
27
+ * • OBSERVABLE — every diagnosis + action is appended to a JSONL log so
28
+ * the operator can audit what the daemon did.
29
+ * • DETERMINISTIC — same inputs → same diagnosis. No flaky heuristics.
30
+ */
31
+ import type { Commit } from "../types.js";
32
+ import type { IndexQualityReport } from "../indexer/quality.js";
33
+ export type WeaknessKind = "drift" | "missing-embeddings" | "low-quality" | "stale-calibration" | "schema-drift" | "token-expiry" | "redaction-gap";
34
+ export type ThreatKind = "tamper" | "secret-leak" | "outlier-author" | "deletion-storm";
35
+ export type Severity = "low" | "medium" | "high" | "critical";
36
+ export type ActionPolicy = "auto" | "recommended" | "observe";
37
+ export interface GuardianFinding {
38
+ kind: WeaknessKind | ThreatKind;
39
+ severity: Severity;
40
+ /** One-line summary for the operator. */
41
+ message: string;
42
+ /** Suggested CLI command to apply the fix (if any). */
43
+ suggestedAction?: string;
44
+ /** Whether the daemon should auto-apply. */
45
+ policy: ActionPolicy;
46
+ /** Free-form structured detail. */
47
+ detail?: Record<string, unknown>;
48
+ }
49
+ export interface GuardianInput {
50
+ /** Latest HEAD commits in git (most recent first). */
51
+ headCommits: Commit[];
52
+ /** Commits currently indexed (most recent first). */
53
+ indexedCommits: Commit[];
54
+ /** Most recent index quality report (if available). */
55
+ quality: IndexQualityReport | null;
56
+ /** Last-known good index quality (for regression detection). */
57
+ lastQualityScore: number | null;
58
+ /** Schema version recorded in the store. */
59
+ storeSchemaVersion: number;
60
+ /** Schema version expected by the running binary. */
61
+ expectedSchemaVersion: number;
62
+ /** Number of feedback events since last `mneme calibrate`. */
63
+ feedbackEventsSinceCalibrate: number;
64
+ /** Threshold above which calibrate is recommended. Default 25. */
65
+ feedbackCalibrateThreshold?: number;
66
+ /** Threshold below which low-quality is flagged (0..1). Default 0.55 (= grade C). */
67
+ qualityFloor?: number;
68
+ /** Quality regression threshold (drop in score). Default 0.1. */
69
+ qualityRegressionThreshold?: number;
70
+ }
71
+ export interface GuardianReport {
72
+ generatedAt: string;
73
+ findings: GuardianFinding[];
74
+ summary: {
75
+ findings: number;
76
+ autoActions: number;
77
+ recommendations: number;
78
+ threats: number;
79
+ };
80
+ }
81
+ /**
82
+ * Run one diagnostic pass. Pure function — same input → same output.
83
+ *
84
+ * The CLI wraps this in `while (true)` with a sleep, but the diagnosis
85
+ * itself is stateless.
86
+ */
87
+ export declare function diagnose(input: GuardianInput): GuardianReport;
88
+ /**
89
+ * Decide which findings the daemon should auto-apply this tick. Returns
90
+ * findings ordered by severity (high → low).
91
+ */
92
+ export declare function selectAutoActions(report: GuardianReport): GuardianFinding[];
93
+ //# sourceMappingURL=guardian.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guardian.d.ts","sourceRoot":"","sources":["../../src/guardian/guardian.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAEhE,MAAM,MAAM,YAAY,GACpB,OAAO,GACP,oBAAoB,GACpB,aAAa,GACb,mBAAmB,GACnB,cAAc,GACd,cAAc,GACd,eAAe,CAAC;AAEpB,MAAM,MAAM,UAAU,GAClB,QAAQ,GACR,aAAa,GACb,gBAAgB,GAChB,gBAAgB,CAAC;AAErB,MAAM,MAAM,QAAQ,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,UAAU,CAAC;AAE9D,MAAM,MAAM,YAAY,GACpB,MAAM,GACN,aAAa,GACb,SAAS,CAAC;AAEd,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,YAAY,GAAG,UAAU,CAAC;IAChC,QAAQ,EAAE,QAAQ,CAAC;IACnB,yCAAyC;IACzC,OAAO,EAAE,MAAM,CAAC;IAChB,uDAAuD;IACvD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,4CAA4C;IAC5C,MAAM,EAAE,YAAY,CAAC;IACrB,mCAAmC;IACnC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAED,MAAM,WAAW,aAAa;IAC5B,sDAAsD;IACtD,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,qDAAqD;IACrD,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,uDAAuD;IACvD,OAAO,EAAE,kBAAkB,GAAG,IAAI,CAAC;IACnC,gEAAgE;IAChE,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,4CAA4C;IAC5C,kBAAkB,EAAE,MAAM,CAAC;IAC3B,qDAAqD;IACrD,qBAAqB,EAAE,MAAM,CAAC;IAC9B,8DAA8D;IAC9D,4BAA4B,EAAE,MAAM,CAAC;IACrC,kEAAkE;IAClE,0BAA0B,CAAC,EAAE,MAAM,CAAC;IACpC,qFAAqF;IACrF,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iEAAiE;IACjE,0BAA0B,CAAC,EAAE,MAAM,CAAC;CACrC;AAED,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,OAAO,EAAE;QACP,QAAQ,EAAE,MAAM,CAAC;QACjB,WAAW,EAAE,MAAM,CAAC;QACpB,eAAe,EAAE,MAAM,CAAC;QACxB,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;CACH;AAED;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,aAAa,GAAG,cAAc,CA0H7D;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,cAAc,GAAG,eAAe,EAAE,CAU3E"}
@@ -0,0 +1,170 @@
1
+ /**
2
+ * Mneme Guardian — the 24/7 self-healing engine.
3
+ *
4
+ * while (true) {
5
+ * diagnose();
6
+ * fix();
7
+ * learn();
8
+ * sleep(interval);
9
+ * }
10
+ *
11
+ * The Guardian is a long-running diagnostic + auto-remediation loop. It
12
+ * watches for weaknesses (regressions in index quality, drift between
13
+ * HEAD and the index, missing embeddings, schema-version mismatch) and
14
+ * threats (data corruption signals, token expiry on remote APIs, secret
15
+ * patterns slipping through redaction) and applies safe corrective
16
+ * actions automatically. Anything risky is *logged with a recommendation*
17
+ * rather than auto-applied.
18
+ *
19
+ * Pure data structures + dispatch — no side effects in this module
20
+ * (file I/O lives in the CLI command). Caller decides whether to
21
+ * actually apply the recommended action or just observe.
22
+ *
23
+ * Design principles:
24
+ * • SAFE BY DEFAULT — auto-apply only the actions that are demonstrably
25
+ * reversible (re-index, calibrate). Anything that could lose data
26
+ * gets recommended, not executed.
27
+ * • OBSERVABLE — every diagnosis + action is appended to a JSONL log so
28
+ * the operator can audit what the daemon did.
29
+ * • DETERMINISTIC — same inputs → same diagnosis. No flaky heuristics.
30
+ */
31
+ /**
32
+ * Run one diagnostic pass. Pure function — same input → same output.
33
+ *
34
+ * The CLI wraps this in `while (true)` with a sleep, but the diagnosis
35
+ * itself is stateless.
36
+ */
37
+ export function diagnose(input) {
38
+ const findings = [];
39
+ const feedbackThreshold = input.feedbackCalibrateThreshold ?? 25;
40
+ const qualityFloor = input.qualityFloor ?? 0.55;
41
+ const qualityRegression = input.qualityRegressionThreshold ?? 0.1;
42
+ // ── Weakness 1: index drift ────────────────────────────────────────
43
+ const indexedHashes = new Set(input.indexedCommits.map((c) => c.hash));
44
+ const driftingCommits = input.headCommits.filter((c) => !indexedHashes.has(c.hash));
45
+ if (driftingCommits.length > 0) {
46
+ const sev = driftingCommits.length > 50
47
+ ? "high"
48
+ : driftingCommits.length > 10
49
+ ? "medium"
50
+ : "low";
51
+ findings.push({
52
+ kind: "drift",
53
+ severity: sev,
54
+ message: `${driftingCommits.length} commit(s) on HEAD not yet indexed.`,
55
+ suggestedAction: "mneme index",
56
+ policy: "auto",
57
+ detail: {
58
+ driftingCount: driftingCommits.length,
59
+ headNewest: input.headCommits[0]?.hash,
60
+ indexNewest: input.indexedCommits[0]?.hash,
61
+ },
62
+ });
63
+ }
64
+ // ── Weakness 2: missing embeddings ─────────────────────────────────
65
+ if (input.quality) {
66
+ const embedRatio = input.quality.metrics.embedRatio;
67
+ if (embedRatio < 0.95 && input.quality.indexedChunks > 0) {
68
+ findings.push({
69
+ kind: "missing-embeddings",
70
+ severity: embedRatio < 0.5 ? "high" : "medium",
71
+ message: `${Math.round((1 - embedRatio) * 100)}% of chunks have no embedding (${input.quality.indexedChunks - input.quality.embeddedChunks} chunks).`,
72
+ suggestedAction: "mneme index",
73
+ policy: "auto",
74
+ detail: { embedRatio },
75
+ });
76
+ }
77
+ }
78
+ // ── Weakness 3: low overall quality ────────────────────────────────
79
+ if (input.quality && input.quality.overallScore < qualityFloor) {
80
+ findings.push({
81
+ kind: "low-quality",
82
+ severity: input.quality.overallScore < 0.4 ? "high" : "medium",
83
+ message: `Index quality grade ${input.quality.grade} (${Math.round(input.quality.overallScore * 100)}/100) is below recommended floor.`,
84
+ suggestedAction: "mneme heal",
85
+ policy: "recommended",
86
+ detail: {
87
+ score: input.quality.overallScore,
88
+ grade: input.quality.grade,
89
+ },
90
+ });
91
+ }
92
+ // ── Weakness 3b: quality regression ────────────────────────────────
93
+ if (input.quality &&
94
+ input.lastQualityScore !== null &&
95
+ input.lastQualityScore - input.quality.overallScore >= qualityRegression) {
96
+ findings.push({
97
+ kind: "low-quality",
98
+ severity: "medium",
99
+ message: `Index quality dropped from ${Math.round(input.lastQualityScore * 100)} to ${Math.round(input.quality.overallScore * 100)}.`,
100
+ suggestedAction: "mneme index --analyze",
101
+ policy: "observe",
102
+ detail: {
103
+ before: input.lastQualityScore,
104
+ after: input.quality.overallScore,
105
+ regression: input.lastQualityScore - input.quality.overallScore,
106
+ },
107
+ });
108
+ }
109
+ // ── Weakness 4: stale calibration ──────────────────────────────────
110
+ if (input.feedbackEventsSinceCalibrate >= feedbackThreshold) {
111
+ findings.push({
112
+ kind: "stale-calibration",
113
+ severity: "low",
114
+ message: `${input.feedbackEventsSinceCalibrate} feedback events since last calibrate — retrieval knobs may need re-tuning.`,
115
+ suggestedAction: "mneme calibrate",
116
+ policy: "auto",
117
+ detail: {
118
+ eventsSinceCalibrate: input.feedbackEventsSinceCalibrate,
119
+ threshold: feedbackThreshold,
120
+ },
121
+ });
122
+ }
123
+ // ── Weakness 5: schema drift ───────────────────────────────────────
124
+ if (input.storeSchemaVersion < input.expectedSchemaVersion) {
125
+ findings.push({
126
+ kind: "schema-drift",
127
+ severity: "high",
128
+ message: `Store schema is v${input.storeSchemaVersion}, binary expects v${input.expectedSchemaVersion}.`,
129
+ suggestedAction: "mneme index",
130
+ policy: "auto",
131
+ detail: {
132
+ storeVersion: input.storeSchemaVersion,
133
+ expectedVersion: input.expectedSchemaVersion,
134
+ },
135
+ });
136
+ }
137
+ const summary = {
138
+ findings: findings.length,
139
+ autoActions: findings.filter((f) => f.policy === "auto").length,
140
+ recommendations: findings.filter((f) => f.policy === "recommended").length,
141
+ threats: findings.filter((f) => isThreat(f.kind)).length,
142
+ };
143
+ return {
144
+ generatedAt: new Date().toISOString(),
145
+ findings,
146
+ summary,
147
+ };
148
+ }
149
+ /**
150
+ * Decide which findings the daemon should auto-apply this tick. Returns
151
+ * findings ordered by severity (high → low).
152
+ */
153
+ export function selectAutoActions(report) {
154
+ const order = {
155
+ critical: 0,
156
+ high: 1,
157
+ medium: 2,
158
+ low: 3,
159
+ };
160
+ return report.findings
161
+ .filter((f) => f.policy === "auto")
162
+ .sort((a, b) => order[a.severity] - order[b.severity]);
163
+ }
164
+ function isThreat(kind) {
165
+ return (kind === "tamper" ||
166
+ kind === "secret-leak" ||
167
+ kind === "outlier-author" ||
168
+ kind === "deletion-storm");
169
+ }
170
+ //# sourceMappingURL=guardian.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guardian.js","sourceRoot":"","sources":["../../src/guardian/guardian.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=guardian.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guardian.test.d.ts","sourceRoot":"","sources":["../../src/guardian/guardian.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,154 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { diagnose, selectAutoActions } from "./guardian.js";
3
+ function mkCommit(hash, date = "2024-01-01") {
4
+ return {
5
+ hash,
6
+ shortHash: hash.slice(0, 7),
7
+ authorName: "A",
8
+ authorEmail: "a@x.com",
9
+ authorDate: date,
10
+ committerDate: date,
11
+ subject: "x",
12
+ body: "",
13
+ files: [],
14
+ parents: [],
15
+ };
16
+ }
17
+ function mkQuality(score, embedRatio = 1) {
18
+ return {
19
+ indexedCommits: 10,
20
+ indexedChunks: 30,
21
+ embeddedChunks: Math.round(30 * embedRatio),
22
+ metrics: {
23
+ chunkDensity: 1,
24
+ embedRatio,
25
+ subjectQuality: 1,
26
+ bodyRatio: 1,
27
+ prRatio: 1,
28
+ issueRatio: 1,
29
+ duplicateRatio: 0,
30
+ tokenizerHealth: 1,
31
+ },
32
+ overallScore: score,
33
+ grade: score >= 0.85 ? "A" : score >= 0.7 ? "B" : score >= 0.55 ? "C" : score >= 0.4 ? "D" : "F",
34
+ recommendations: [],
35
+ };
36
+ }
37
+ const baseInput = {
38
+ headCommits: [],
39
+ indexedCommits: [],
40
+ quality: mkQuality(0.9),
41
+ lastQualityScore: 0.9,
42
+ storeSchemaVersion: 3,
43
+ expectedSchemaVersion: 3,
44
+ feedbackEventsSinceCalibrate: 0,
45
+ };
46
+ describe("diagnose", () => {
47
+ it("returns no findings when everything is healthy", () => {
48
+ const r = diagnose(baseInput);
49
+ expect(r.findings).toHaveLength(0);
50
+ expect(r.summary.autoActions).toBe(0);
51
+ });
52
+ it("detects index drift when HEAD has commits not in index", () => {
53
+ const r = diagnose({
54
+ ...baseInput,
55
+ headCommits: [mkCommit("new1"), mkCommit("new2"), mkCommit("indexed")],
56
+ indexedCommits: [mkCommit("indexed")],
57
+ });
58
+ const drift = r.findings.find((f) => f.kind === "drift");
59
+ expect(drift).toBeDefined();
60
+ expect(drift.policy).toBe("auto");
61
+ expect(drift.suggestedAction).toBe("mneme index");
62
+ });
63
+ it("escalates drift severity by count", () => {
64
+ const lots = Array.from({ length: 60 }, (_, i) => mkCommit("c" + i));
65
+ const r = diagnose({
66
+ ...baseInput,
67
+ headCommits: lots,
68
+ indexedCommits: [],
69
+ });
70
+ const drift = r.findings.find((f) => f.kind === "drift");
71
+ expect(drift.severity).toBe("high");
72
+ });
73
+ it("detects missing embeddings", () => {
74
+ const r = diagnose({
75
+ ...baseInput,
76
+ quality: mkQuality(0.9, 0.7),
77
+ });
78
+ const missing = r.findings.find((f) => f.kind === "missing-embeddings");
79
+ expect(missing).toBeDefined();
80
+ expect(missing.policy).toBe("auto");
81
+ });
82
+ it("flags low quality below floor", () => {
83
+ const r = diagnose({
84
+ ...baseInput,
85
+ quality: mkQuality(0.4),
86
+ });
87
+ const low = r.findings.find((f) => f.kind === "low-quality");
88
+ expect(low).toBeDefined();
89
+ expect(low.policy).toBe("recommended");
90
+ });
91
+ it("flags quality regression even when above floor", () => {
92
+ const r = diagnose({
93
+ ...baseInput,
94
+ quality: mkQuality(0.7),
95
+ lastQualityScore: 0.9,
96
+ });
97
+ const reg = r.findings.find((f) => f.kind === "low-quality" &&
98
+ f.message.toLowerCase().includes("dropped"));
99
+ expect(reg).toBeDefined();
100
+ });
101
+ it("recommends calibrate when feedback accumulates", () => {
102
+ const r = diagnose({
103
+ ...baseInput,
104
+ feedbackEventsSinceCalibrate: 50,
105
+ });
106
+ const stale = r.findings.find((f) => f.kind === "stale-calibration");
107
+ expect(stale).toBeDefined();
108
+ expect(stale.policy).toBe("auto");
109
+ expect(stale.suggestedAction).toBe("mneme calibrate");
110
+ });
111
+ it("flags schema drift when store < expected", () => {
112
+ const r = diagnose({
113
+ ...baseInput,
114
+ storeSchemaVersion: 2,
115
+ expectedSchemaVersion: 3,
116
+ });
117
+ const sd = r.findings.find((f) => f.kind === "schema-drift");
118
+ expect(sd).toBeDefined();
119
+ expect(sd.severity).toBe("high");
120
+ });
121
+ it("returns deterministic output for same input", () => {
122
+ const a = diagnose({
123
+ ...baseInput,
124
+ headCommits: [mkCommit("a")],
125
+ indexedCommits: [],
126
+ });
127
+ const b = diagnose({
128
+ ...baseInput,
129
+ headCommits: [mkCommit("a")],
130
+ indexedCommits: [],
131
+ });
132
+ expect(a.findings).toEqual(b.findings);
133
+ });
134
+ });
135
+ describe("selectAutoActions", () => {
136
+ it("returns only auto-policy findings, sorted by severity", () => {
137
+ const report = diagnose({
138
+ ...baseInput,
139
+ headCommits: [mkCommit("new1")], // low/medium drift → auto
140
+ indexedCommits: [],
141
+ storeSchemaVersion: 2, // high schema-drift → auto
142
+ expectedSchemaVersion: 3,
143
+ quality: mkQuality(0.4), // medium low-quality → recommended (not auto)
144
+ feedbackEventsSinceCalibrate: 30, // low stale-calib → auto
145
+ });
146
+ const actions = selectAutoActions(report);
147
+ // High severity should come first
148
+ expect(actions[0].severity).toBe("high");
149
+ // No "recommended" findings should be in the auto list
150
+ for (const a of actions)
151
+ expect(a.policy).toBe("auto");
152
+ });
153
+ });
154
+ //# sourceMappingURL=guardian.test.js.map