mneme-ai 0.16.0 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/forensics.d.ts +29 -0
- package/dist/commands/forensics.d.ts.map +1 -0
- package/dist/commands/forensics.js +472 -0
- package/dist/commands/forensics.js.map +1 -0
- package/dist/commands/guardian.d.ts.map +1 -1
- package/dist/commands/guardian.js +18 -29
- package/dist/commands/guardian.js.map +1 -1
- package/dist/commands/insights-cli.d.ts.map +1 -1
- package/dist/commands/insights-cli.js +116 -42
- package/dist/commands/insights-cli.js.map +1 -1
- package/dist/commands/quant-cli.d.ts.map +1 -1
- package/dist/commands/quant-cli.js +68 -47
- package/dist/commands/quant-cli.js.map +1 -1
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +78 -21
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/why.d.ts.map +1 -1
- package/dist/commands/why.js +71 -12
- package/dist/commands/why.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +50 -0
- package/dist/index.js.map +1 -1
- package/dist/ui.d.ts +67 -0
- package/dist/ui.d.ts.map +1 -1
- package/dist/ui.js +224 -0
- package/dist/ui.js.map +1 -1
- package/dist/ui.test.d.ts +2 -0
- package/dist/ui.test.d.ts.map +1 -0
- package/dist/ui.test.js +184 -0
- package/dist/ui.test.js.map +1 -0
- package/package.json +5 -5
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export interface ForensicsMatchOptions {
|
|
2
|
+
cwd: string;
|
|
3
|
+
commitHash: string;
|
|
4
|
+
authorEmail: string;
|
|
5
|
+
json?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export declare function forensicsMatchCommand(opts: ForensicsMatchOptions): Promise<number>;
|
|
8
|
+
export interface ForensicsAttributeOptions {
|
|
9
|
+
cwd: string;
|
|
10
|
+
commitHash: string;
|
|
11
|
+
topN?: number;
|
|
12
|
+
json?: boolean;
|
|
13
|
+
}
|
|
14
|
+
export declare function forensicsAttributeCommand(opts: ForensicsAttributeOptions): Promise<number>;
|
|
15
|
+
export interface ForensicsVulnsOptions {
|
|
16
|
+
cwd: string;
|
|
17
|
+
since?: string;
|
|
18
|
+
topN?: number;
|
|
19
|
+
json?: boolean;
|
|
20
|
+
}
|
|
21
|
+
export declare function forensicsVulnsCommand(opts: ForensicsVulnsOptions): Promise<number>;
|
|
22
|
+
export interface ForensicsAnomalyOptions {
|
|
23
|
+
cwd: string;
|
|
24
|
+
threshold?: number;
|
|
25
|
+
topN?: number;
|
|
26
|
+
json?: boolean;
|
|
27
|
+
}
|
|
28
|
+
export declare function forensicsAnomalyCommand(opts: ForensicsAnomalyOptions): Promise<number>;
|
|
29
|
+
//# sourceMappingURL=forensics.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"forensics.d.ts","sourceRoot":"","sources":["../../src/commands/forensics.ts"],"names":[],"mappings":"AAwEA,MAAM,WAAW,qBAAqB;IACpC,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,wBAAsB,qBAAqB,CAAC,IAAI,EAAE,qBAAqB,GAAG,OAAO,CAAC,MAAM,CAAC,CAuFxF;AAID,MAAM,WAAW,yBAAyB;IACxC,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,wBAAsB,yBAAyB,CAC7C,IAAI,EAAE,yBAAyB,GAC9B,OAAO,CAAC,MAAM,CAAC,CAuGjB;AAID,MAAM,WAAW,qBAAqB;IACpC,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,wBAAsB,qBAAqB,CAAC,IAAI,EAAE,qBAAqB,GAAG,OAAO,CAAC,MAAM,CAAC,CAoJxF;AAID,MAAM,WAAW,uBAAuB;IACtC,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,wBAAsB,uBAAuB,CAC3C,IAAI,EAAE,uBAAuB,GAC5B,OAAO,CAAC,MAAM,CAAC,CAoGjB"}
|
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `mneme forensics` — applied forensic science for code.
|
|
3
|
+
*
|
|
4
|
+
* Subcommands:
|
|
5
|
+
* match <commit> <author> — STR-loci LR matching, ENFSI verbal scale
|
|
6
|
+
* attribute <commit> — anonymous attribution: rank candidates by LR
|
|
7
|
+
* vulns — pattern-match security vulnerabilities in history
|
|
8
|
+
* anomaly — insider-threat detection (compromised credentials)
|
|
9
|
+
*
|
|
10
|
+
* Pure data extraction lives in @mneme-ai/core/forensics. This file
|
|
11
|
+
* does the CLI plumbing + rendering.
|
|
12
|
+
*/
|
|
13
|
+
import kleur from "kleur";
|
|
14
|
+
import { git, store, forensics, util, } from "@mneme-ai/core";
|
|
15
|
+
import { dbPath } from "../paths.js";
|
|
16
|
+
import { ui, header, section, severityBadge, logMeter, meter, emptyState, nextSteps, verdictBadge, kv, pill, } from "../ui.js";
|
|
17
|
+
// Shared withStore wrapper inline (mirrors insights-cli pattern)
|
|
18
|
+
async function withStore(cwd, f) {
|
|
19
|
+
if (!(await git.isGitRepo(cwd))) {
|
|
20
|
+
ui.error("Not in a git repo. Run `mneme init` first.");
|
|
21
|
+
return 1;
|
|
22
|
+
}
|
|
23
|
+
const meta = await git.getRepoMeta(cwd);
|
|
24
|
+
const s = new store.MnemeStore(dbPath(meta.rootPath));
|
|
25
|
+
if (s.countCommits() === 0) {
|
|
26
|
+
ui.error("Memory is empty. Run `mneme index` first.");
|
|
27
|
+
s.close();
|
|
28
|
+
return 1;
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
return await f(s);
|
|
32
|
+
}
|
|
33
|
+
finally {
|
|
34
|
+
s.close();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
// Map forensic severity strings to UI Level type
|
|
38
|
+
const sevToLevel = {
|
|
39
|
+
critical: "critical",
|
|
40
|
+
high: "high",
|
|
41
|
+
medium: "medium",
|
|
42
|
+
low: "low",
|
|
43
|
+
info: "info",
|
|
44
|
+
};
|
|
45
|
+
export async function forensicsMatchCommand(opts) {
|
|
46
|
+
const result = await withStore(opts.cwd, (s) => {
|
|
47
|
+
const allCommits = util.loadAllCommits(s);
|
|
48
|
+
const evidenceCommit = allCommits.find((c) => c.hash.startsWith(opts.commitHash)) ?? null;
|
|
49
|
+
if (!evidenceCommit)
|
|
50
|
+
return { error: `Commit ${opts.commitHash} not found in index.` };
|
|
51
|
+
const byAuthor = new Map();
|
|
52
|
+
for (const c of allCommits) {
|
|
53
|
+
const a = c.authorEmail.toLowerCase();
|
|
54
|
+
const arr = byAuthor.get(a);
|
|
55
|
+
if (arr)
|
|
56
|
+
arr.push(c);
|
|
57
|
+
else
|
|
58
|
+
byAuthor.set(a, [c]);
|
|
59
|
+
}
|
|
60
|
+
const profiles = [];
|
|
61
|
+
for (const [, list] of byAuthor)
|
|
62
|
+
profiles.push(forensics.extractLoci(list));
|
|
63
|
+
const population = forensics.buildPopulationStats(profiles);
|
|
64
|
+
const evidenceLoci = forensics.extractLoci([evidenceCommit]);
|
|
65
|
+
const suspectCommits = byAuthor.get(opts.authorEmail.toLowerCase());
|
|
66
|
+
if (!suspectCommits || suspectCommits.length < 5) {
|
|
67
|
+
return { error: `Author ${opts.authorEmail} has fewer than 5 commits — not enough baseline.` };
|
|
68
|
+
}
|
|
69
|
+
const suspectLoci = forensics.extractLoci(suspectCommits);
|
|
70
|
+
const report = forensics.compareLoci(evidenceLoci, suspectLoci, population);
|
|
71
|
+
return { evidenceCommit, suspect: opts.authorEmail, report, suspectCommits: suspectCommits.length };
|
|
72
|
+
});
|
|
73
|
+
if (typeof result === "number")
|
|
74
|
+
return result;
|
|
75
|
+
if ("error" in result) {
|
|
76
|
+
ui.error(result.error ?? "Unknown error");
|
|
77
|
+
return 1;
|
|
78
|
+
}
|
|
79
|
+
if (opts.json) {
|
|
80
|
+
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
81
|
+
return 0;
|
|
82
|
+
}
|
|
83
|
+
ui.banner();
|
|
84
|
+
process.stdout.write(header("🧬", "Forensic Match — STR-loci likelihood ratio", "Bayesian author attribution · ENFSI 2015 verbal scale") + "\n\n");
|
|
85
|
+
process.stdout.write(kv("commit", `${kleur.bold(result.evidenceCommit.shortHash)} ${kleur.gray(result.evidenceCommit.subject)}`) + "\n");
|
|
86
|
+
process.stdout.write(kv("suspect", `${kleur.bold(result.suspect)} ${kleur.gray(`(${result.suspectCommits} prior commits)`)}`) + "\n\n");
|
|
87
|
+
const lr = result.report.combinedLR;
|
|
88
|
+
process.stdout.write(section("✦ Combined Likelihood Ratio") + "\n");
|
|
89
|
+
process.stdout.write(` ${kleur.bold("LR")} = ${kleur.bold(lr.toExponential(2))} ${kleur.gray(`log10 = ${result.report.log10LR}`)}\n`);
|
|
90
|
+
process.stdout.write(` ${verdictBadge(result.report.verdict)}\n`);
|
|
91
|
+
process.stdout.write(` ${kleur.gray(verdictPlainEnglish(result.report.verdict, result.suspect))}\n\n`);
|
|
92
|
+
process.stdout.write(section("◆ Per-locus contribution", "(12 STR loci · meter shows log-LR)") + "\n\n");
|
|
93
|
+
// Sort: largest |log-LR| first — drives the eye to the most informative loci.
|
|
94
|
+
const sortedLoci = [...result.report.perLocus].sort((a, b) => Math.abs(Math.log10(b.lr)) - Math.abs(Math.log10(a.lr)));
|
|
95
|
+
for (const l of sortedLoci) {
|
|
96
|
+
process.stdout.write(` ${logMeter(l.lr)} ${kleur.bold(l.name.padEnd(20))} ${kleur.gray("LR=")}${formatLR(l.lr).padStart(9)}\n`);
|
|
97
|
+
process.stdout.write(` ${kleur.gray(l.note)}\n`);
|
|
98
|
+
}
|
|
99
|
+
process.stdout.write("\n");
|
|
100
|
+
// ─── Smart next steps ────────────────────────────────────────────
|
|
101
|
+
const isMatch = lr >= 100;
|
|
102
|
+
const isAgainst = lr <= 0.01;
|
|
103
|
+
const acts = [];
|
|
104
|
+
if (isAgainst) {
|
|
105
|
+
acts.push({
|
|
106
|
+
cmd: `mneme forensics attribute ${result.evidenceCommit.shortHash} --top 5`,
|
|
107
|
+
why: "Strong evidence against this suspect — find the most-likely actual author.",
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
else if (!isMatch) {
|
|
111
|
+
acts.push({
|
|
112
|
+
cmd: `mneme forensics attribute ${result.evidenceCommit.shortHash} --top 5`,
|
|
113
|
+
why: "LR is uninformative — let attribute rank ALL authors by likelihood.",
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
acts.push({
|
|
117
|
+
cmd: `mneme forensics anomaly --threshold 1.5`,
|
|
118
|
+
why: "Hunt for OTHER suspicious commits in history (insider-threat scan).",
|
|
119
|
+
});
|
|
120
|
+
process.stdout.write(nextSteps(acts) + "\n\n");
|
|
121
|
+
return 0;
|
|
122
|
+
}
|
|
123
|
+
export async function forensicsAttributeCommand(opts) {
|
|
124
|
+
const result = await withStore(opts.cwd, (s) => {
|
|
125
|
+
const allCommits = util.loadAllCommits(s);
|
|
126
|
+
const evidenceCommit = allCommits.find((c) => c.hash.startsWith(opts.commitHash)) ?? null;
|
|
127
|
+
if (!evidenceCommit)
|
|
128
|
+
return { error: `Commit ${opts.commitHash} not found in index.` };
|
|
129
|
+
const byAuthor = new Map();
|
|
130
|
+
for (const c of allCommits) {
|
|
131
|
+
const a = c.authorEmail.toLowerCase();
|
|
132
|
+
const arr = byAuthor.get(a);
|
|
133
|
+
if (arr)
|
|
134
|
+
arr.push(c);
|
|
135
|
+
else
|
|
136
|
+
byAuthor.set(a, [c]);
|
|
137
|
+
}
|
|
138
|
+
const profiles = [];
|
|
139
|
+
for (const [, list] of byAuthor)
|
|
140
|
+
profiles.push(forensics.extractLoci(list));
|
|
141
|
+
const population = forensics.buildPopulationStats(profiles);
|
|
142
|
+
const evidenceLoci = forensics.extractLoci([evidenceCommit]);
|
|
143
|
+
const candidates = [];
|
|
144
|
+
for (const [author, list] of byAuthor) {
|
|
145
|
+
if (list.length < 5)
|
|
146
|
+
continue;
|
|
147
|
+
const suspectLoci = forensics.extractLoci(list);
|
|
148
|
+
const report = forensics.compareLoci(evidenceLoci, suspectLoci, population);
|
|
149
|
+
candidates.push({ author, commitCount: list.length, report });
|
|
150
|
+
}
|
|
151
|
+
candidates.sort((a, b) => b.report.combinedLR - a.report.combinedLR);
|
|
152
|
+
return { evidenceCommit, candidates };
|
|
153
|
+
});
|
|
154
|
+
if (typeof result === "number")
|
|
155
|
+
return result;
|
|
156
|
+
if ("error" in result) {
|
|
157
|
+
ui.error(result.error ?? "Unknown error");
|
|
158
|
+
return 1;
|
|
159
|
+
}
|
|
160
|
+
if (opts.json) {
|
|
161
|
+
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
162
|
+
return 0;
|
|
163
|
+
}
|
|
164
|
+
ui.banner();
|
|
165
|
+
process.stdout.write(header("🧬", "Forensic Attribution — anonymous commit → most likely author", "Bayesian ranking across ALL authors with ≥5 prior commits") + "\n\n");
|
|
166
|
+
process.stdout.write(kv("commit", `${kleur.bold(result.evidenceCommit.shortHash)} ${kleur.gray(result.evidenceCommit.subject)}`) + "\n\n");
|
|
167
|
+
if (result.candidates.length === 0) {
|
|
168
|
+
process.stdout.write(emptyState("Not enough author history to attribute.", [
|
|
169
|
+
"Need ≥5 prior commits per author for a baseline profile.",
|
|
170
|
+
"Run `mneme index` if you've added commits since the last index.",
|
|
171
|
+
]));
|
|
172
|
+
return 0;
|
|
173
|
+
}
|
|
174
|
+
// ─── Top-line insight (the "AI from the future" feel) ───────────
|
|
175
|
+
const top = result.candidates[0];
|
|
176
|
+
const second = result.candidates[1];
|
|
177
|
+
const ratio = second ? top.report.combinedLR / Math.max(second.report.combinedLR, 1e-9) : Infinity;
|
|
178
|
+
const insightStr = (() => {
|
|
179
|
+
if (top.report.combinedLR >= 1000 && (!second || ratio >= 100)) {
|
|
180
|
+
return `🎯 ${kleur.bold(top.author)} is the overwhelming match (LR ratio vs runner-up: ${ratio.toExponential(1)}).`;
|
|
181
|
+
}
|
|
182
|
+
if (top.report.combinedLR >= 100) {
|
|
183
|
+
return `🎯 ${kleur.bold(top.author)} is the most-likely author — strong support, but ${ratio < 10 ? "the runner-up is close" : "well above the field"}.`;
|
|
184
|
+
}
|
|
185
|
+
if (top.report.combinedLR >= 2) {
|
|
186
|
+
return `⚠ Weak signal — top candidate ${kleur.bold(top.author)} only modestly above population, treat as a hint not proof.`;
|
|
187
|
+
}
|
|
188
|
+
return `🌫 Inconclusive — no candidate's loci match the evidence with meaningful support.`;
|
|
189
|
+
})();
|
|
190
|
+
process.stdout.write(` ${insightStr}\n\n`);
|
|
191
|
+
process.stdout.write(section("◆ Ranked candidates") + "\n\n");
|
|
192
|
+
const showN = Math.min(opts.topN ?? 5, result.candidates.length);
|
|
193
|
+
for (let i = 0; i < showN; i++) {
|
|
194
|
+
const c = result.candidates[i];
|
|
195
|
+
const lr = c.report.combinedLR;
|
|
196
|
+
const rank = `#${i + 1}`;
|
|
197
|
+
process.stdout.write(` ${kleur.bold(rank.padStart(3))} ${kleur.bold(c.author.padEnd(32))} ${kleur.gray("LR=")}${formatLR(lr).padStart(10)} ${verdictBadge(c.report.verdict)}\n`);
|
|
198
|
+
process.stdout.write(` ${logMeter(lr)} ${kleur.gray(`${c.commitCount} prior commits · log10(LR)=${c.report.log10LR}`)}\n\n`);
|
|
199
|
+
}
|
|
200
|
+
process.stdout.write(nextSteps([
|
|
201
|
+
{
|
|
202
|
+
cmd: `mneme forensics match ${result.evidenceCommit.shortHash} ${top.author}`,
|
|
203
|
+
why: `See per-locus breakdown for the top candidate.`,
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
cmd: `mneme dna ${top.author} --depth 5`,
|
|
207
|
+
why: `Inspect the top candidate's coding DNA fingerprint.`,
|
|
208
|
+
},
|
|
209
|
+
]) + "\n\n");
|
|
210
|
+
return 0;
|
|
211
|
+
}
|
|
212
|
+
export async function forensicsVulnsCommand(opts) {
|
|
213
|
+
if (!(await git.isGitRepo(opts.cwd))) {
|
|
214
|
+
ui.error("Not in a git repo. Run `mneme init` first.");
|
|
215
|
+
return 1;
|
|
216
|
+
}
|
|
217
|
+
const meta = await git.getRepoMeta(opts.cwd);
|
|
218
|
+
// Use git directly so we don't require an index — vulnerability
|
|
219
|
+
// hunting works on raw commit history.
|
|
220
|
+
const sinceArg = opts.since ? ["--since", opts.since] : [];
|
|
221
|
+
const log = await git.execGitOk(["log", "-n", String(opts.topN ?? 500), ...sinceArg, "--no-color", "--pretty=format:::commit::%H::%aI::%an::%ae::%s"], { cwd: meta.rootPath });
|
|
222
|
+
const inputs = [];
|
|
223
|
+
for (const line of log.split("\n")) {
|
|
224
|
+
if (!line.startsWith("::commit::"))
|
|
225
|
+
continue;
|
|
226
|
+
const parts = line.split("::");
|
|
227
|
+
const hash = parts[2] ?? "";
|
|
228
|
+
if (!hash)
|
|
229
|
+
continue;
|
|
230
|
+
const commit = {
|
|
231
|
+
hash,
|
|
232
|
+
shortHash: hash.slice(0, 7),
|
|
233
|
+
authorName: parts[4] ?? "",
|
|
234
|
+
authorEmail: parts[5] ?? "",
|
|
235
|
+
authorDate: parts[3] ?? "",
|
|
236
|
+
committerDate: parts[3] ?? "",
|
|
237
|
+
subject: parts.slice(6).join("::"),
|
|
238
|
+
body: "",
|
|
239
|
+
files: [],
|
|
240
|
+
parents: [],
|
|
241
|
+
};
|
|
242
|
+
let diff = "";
|
|
243
|
+
try {
|
|
244
|
+
diff = await git.execGitOk(["show", "--no-color", "--pretty=format:", hash], {
|
|
245
|
+
cwd: meta.rootPath,
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
catch {
|
|
249
|
+
// ignore — skip diff if it fails
|
|
250
|
+
}
|
|
251
|
+
inputs.push({ commit, diff });
|
|
252
|
+
}
|
|
253
|
+
const report = forensics.huntVulnerabilities(inputs);
|
|
254
|
+
if (opts.json) {
|
|
255
|
+
process.stdout.write(JSON.stringify(report, null, 2) + "\n");
|
|
256
|
+
return 0;
|
|
257
|
+
}
|
|
258
|
+
ui.banner();
|
|
259
|
+
process.stdout.write(header("🛡", "Vulnerability Hunt — pattern-matched security findings", "11 CWE-aligned classes · scans full diff bodies, additions only") + "\n\n");
|
|
260
|
+
// ─── Scan summary ─────────────────────────────────────────────────
|
|
261
|
+
const totalSev = report.bySeverity.critical + report.bySeverity.high;
|
|
262
|
+
const summaryLine = (() => {
|
|
263
|
+
if (report.hits.length === 0) {
|
|
264
|
+
return `${kleur.green("✓")} No vulnerable patterns detected in ${kleur.bold(String(report.scanned))} commits.`;
|
|
265
|
+
}
|
|
266
|
+
if (totalSev > 0) {
|
|
267
|
+
return `${kleur.red("⚠")} ${kleur.red().bold(String(totalSev))} critical/high finding(s) across ${kleur.bold(String(report.scanned))} commits — investigate immediately.`;
|
|
268
|
+
}
|
|
269
|
+
return `${kleur.yellow("!")} ${kleur.bold(String(report.hits.length))} candidate(s) found across ${kleur.bold(String(report.scanned))} commits — review before action.`;
|
|
270
|
+
})();
|
|
271
|
+
process.stdout.write(` ${summaryLine}\n\n`);
|
|
272
|
+
if (report.hits.length === 0) {
|
|
273
|
+
if (report.silentFixes.length > 0) {
|
|
274
|
+
process.stdout.write(section("◐ Silent fixes", `(security-keyword commits with no rule hits — verify intent)`) + "\n\n");
|
|
275
|
+
for (const sf of report.silentFixes.slice(0, 5)) {
|
|
276
|
+
process.stdout.write(` ${kleur.gray("●")} ${kleur.bold(sf.shortHash)} ${kleur.gray(sf.authorDate.slice(0, 10))} ${sf.subject}\n`);
|
|
277
|
+
}
|
|
278
|
+
process.stdout.write("\n");
|
|
279
|
+
}
|
|
280
|
+
return 0;
|
|
281
|
+
}
|
|
282
|
+
// Severity tally with percentage bars
|
|
283
|
+
process.stdout.write(section("✦ By severity") + "\n");
|
|
284
|
+
const sevOrder = ["critical", "high", "medium", "low", "info"];
|
|
285
|
+
const maxCount = Math.max(...sevOrder.map((s) => report.bySeverity[s]));
|
|
286
|
+
for (const s of sevOrder) {
|
|
287
|
+
const n = report.bySeverity[s];
|
|
288
|
+
if (n === 0)
|
|
289
|
+
continue;
|
|
290
|
+
const ratio = maxCount > 0 ? n / maxCount : 0;
|
|
291
|
+
process.stdout.write(` ${severityBadge(s)} ${meter(ratio, { width: 12, level: s })} ${kleur.bold(String(n).padStart(4))}\n`);
|
|
292
|
+
}
|
|
293
|
+
process.stdout.write("\n");
|
|
294
|
+
// CWE breakdown — group hits by reference (CWE)
|
|
295
|
+
const byCwe = new Map();
|
|
296
|
+
for (const h of report.hits) {
|
|
297
|
+
byCwe.set(h.reference, (byCwe.get(h.reference) ?? 0) + 1);
|
|
298
|
+
}
|
|
299
|
+
if (byCwe.size > 1) {
|
|
300
|
+
process.stdout.write(section("◇ By CWE class", "(top 5)") + "\n");
|
|
301
|
+
const topCwes = [...byCwe.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5);
|
|
302
|
+
for (const [cwe, n] of topCwes) {
|
|
303
|
+
process.stdout.write(` ${kleur.cyan(cwe.padEnd(12))} ${kleur.bold(String(n).padStart(3))}\n`);
|
|
304
|
+
}
|
|
305
|
+
process.stdout.write("\n");
|
|
306
|
+
}
|
|
307
|
+
// Top hits
|
|
308
|
+
process.stdout.write(section("◆ Top findings") + "\n\n");
|
|
309
|
+
const sevWeight = {
|
|
310
|
+
critical: 0, high: 1, medium: 2, low: 3, info: 4,
|
|
311
|
+
};
|
|
312
|
+
const sortedHits = [...report.hits].sort((a, b) => {
|
|
313
|
+
const w = sevWeight[a.severity] - sevWeight[b.severity];
|
|
314
|
+
if (w !== 0)
|
|
315
|
+
return w;
|
|
316
|
+
return b.commit.authorDate.localeCompare(a.commit.authorDate);
|
|
317
|
+
});
|
|
318
|
+
for (const h of sortedHits.slice(0, 20)) {
|
|
319
|
+
const fix = h.looksLikeFix ? ` ${pill("fixed", "ok")}` : "";
|
|
320
|
+
process.stdout.write(` ${severityBadge(sevToLevel[h.severity] ?? "info")} ${kleur.bold(h.commit.shortHash)} ${kleur.gray(h.commit.authorDate.slice(0, 10))} ${kleur.cyan(h.reference)}${fix}\n`);
|
|
321
|
+
process.stdout.write(` ${h.summary}\n`);
|
|
322
|
+
process.stdout.write(` ${kleur.gray("evidence: " + truncateOneLine(h.evidence, 100))}\n\n`);
|
|
323
|
+
}
|
|
324
|
+
// Silent fixes (security-keyword commits without hits)
|
|
325
|
+
if (report.silentFixes.length > 0) {
|
|
326
|
+
process.stdout.write(section("◐ Silent fixes", `(${report.silentFixes.length} commit(s) mention security but no patterns matched — verify intent)`) + "\n\n");
|
|
327
|
+
for (const sf of report.silentFixes.slice(0, 5)) {
|
|
328
|
+
process.stdout.write(` ${kleur.gray("●")} ${kleur.bold(sf.shortHash)} ${kleur.gray(sf.authorDate.slice(0, 10))} ${sf.subject}\n`);
|
|
329
|
+
}
|
|
330
|
+
process.stdout.write("\n");
|
|
331
|
+
}
|
|
332
|
+
process.stdout.write(nextSteps([
|
|
333
|
+
{
|
|
334
|
+
cmd: `mneme forensics anomaly --threshold 1.5`,
|
|
335
|
+
why: `Cross-reference: which commits are BOTH vulnerable AND anomalous?`,
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
cmd: `mneme forensics vulns --json | jq '.hits[] | select(.severity=="critical")'`,
|
|
339
|
+
why: `Pipe critical findings to your tracker.`,
|
|
340
|
+
},
|
|
341
|
+
]) + "\n\n");
|
|
342
|
+
return 0;
|
|
343
|
+
}
|
|
344
|
+
export async function forensicsAnomalyCommand(opts) {
|
|
345
|
+
const result = await withStore(opts.cwd, (s) => {
|
|
346
|
+
const allCommits = util.loadAllCommits(s);
|
|
347
|
+
const fileChanges = util.loadAllFileChanges(s);
|
|
348
|
+
const baselines = forensics.buildBaselines(allCommits, fileChanges);
|
|
349
|
+
const findings = forensics.detectAnomalies(allCommits, baselines, fileChanges, opts.threshold ?? 0.9);
|
|
350
|
+
return { findings, scanned: allCommits.length, baselines: baselines.size };
|
|
351
|
+
});
|
|
352
|
+
if (typeof result === "number")
|
|
353
|
+
return result;
|
|
354
|
+
if (opts.json) {
|
|
355
|
+
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
356
|
+
return 0;
|
|
357
|
+
}
|
|
358
|
+
ui.banner();
|
|
359
|
+
process.stdout.write(header("🕵", "Anomaly Detection — insider-threat / credential-compromise hunt", "Per-author baselines · 4-axis deviation: TIME · FILES · STYLE · SIZE") + "\n\n");
|
|
360
|
+
const counts = countBySeverity(result.findings);
|
|
361
|
+
const topLine = (() => {
|
|
362
|
+
if (result.findings.length === 0) {
|
|
363
|
+
return `${kleur.green("✓")} No anomalous commits detected at threshold ${kleur.bold((opts.threshold ?? 0.9).toFixed(2))} across ${kleur.bold(String(result.scanned))} commits.`;
|
|
364
|
+
}
|
|
365
|
+
if (counts.critical > 0) {
|
|
366
|
+
return `${kleur.red("⚠")} ${kleur.red().bold(String(counts.critical))} CRITICAL anomalies across ${kleur.bold(String(result.baselines))} authors — verify identities out-of-band.`;
|
|
367
|
+
}
|
|
368
|
+
return `${kleur.yellow("!")} ${kleur.bold(String(result.findings.length))} anomalous commit(s) — review before merge.`;
|
|
369
|
+
})();
|
|
370
|
+
process.stdout.write(` ${topLine}\n\n`);
|
|
371
|
+
if (result.findings.length === 0) {
|
|
372
|
+
process.stdout.write(emptyState("Clean — no commits exceeded the deviation threshold.", [
|
|
373
|
+
`Lower threshold to investigate borderline cases: --threshold 0.5`,
|
|
374
|
+
`Run after every push as part of CI for continuous oversight.`,
|
|
375
|
+
]));
|
|
376
|
+
return 0;
|
|
377
|
+
}
|
|
378
|
+
// Severity tally
|
|
379
|
+
process.stdout.write(section("✦ By severity") + "\n");
|
|
380
|
+
const sevs = [
|
|
381
|
+
{ key: "critical", level: "critical" },
|
|
382
|
+
{ key: "high", level: "high" },
|
|
383
|
+
{ key: "medium", level: "medium" },
|
|
384
|
+
{ key: "low", level: "low" },
|
|
385
|
+
];
|
|
386
|
+
const maxC = Math.max(counts.critical, counts.high, counts.medium, counts.low, 1);
|
|
387
|
+
for (const { key, level } of sevs) {
|
|
388
|
+
const n = counts[key];
|
|
389
|
+
if (n === 0)
|
|
390
|
+
continue;
|
|
391
|
+
process.stdout.write(` ${severityBadge(level)} ${meter(n / maxC, { width: 12, level })} ${kleur.bold(String(n).padStart(4))}\n`);
|
|
392
|
+
}
|
|
393
|
+
process.stdout.write("\n");
|
|
394
|
+
process.stdout.write(section("⚠ Anomalous commits", "(sorted by deviation)") + "\n\n");
|
|
395
|
+
for (const f of result.findings.slice(0, opts.topN ?? 10)) {
|
|
396
|
+
const c = f.commit;
|
|
397
|
+
const sevLevel = sevToLevel[f.severity] ?? "info";
|
|
398
|
+
process.stdout.write(` ${severityBadge(sevLevel)} ${kleur.bold(c.shortHash)} ${kleur.gray(c.authorDate.slice(0, 16) + " · " + c.authorName)}\n`);
|
|
399
|
+
process.stdout.write(` ${kleur.bold(`deviation = ${f.totalDeviation.toFixed(2)}`)} ${kleur.gray(`(≈ ${f.approxSigma}σ)`)}\n`);
|
|
400
|
+
process.stdout.write(` ${kleur.white(c.subject)}\n`);
|
|
401
|
+
for (const a of f.axes) {
|
|
402
|
+
if (a.score < 0.1)
|
|
403
|
+
continue;
|
|
404
|
+
// Each axis maps 0..1 onto the meter directly with auto color.
|
|
405
|
+
const axisLevel = a.score >= 0.8 ? "critical" : a.score >= 0.5 ? "high" : a.score >= 0.3 ? "medium" : "low";
|
|
406
|
+
process.stdout.write(` ${meter(a.score, { width: 10, level: axisLevel })} ${kleur.cyan(a.axis.padEnd(6))} ${kleur.gray(a.note)}\n`);
|
|
407
|
+
}
|
|
408
|
+
process.stdout.write(` ${kleur.yellow("→ " + f.recommendation)}\n\n`);
|
|
409
|
+
}
|
|
410
|
+
// Smart next steps based on top finding
|
|
411
|
+
const topFinding = result.findings[0];
|
|
412
|
+
process.stdout.write(nextSteps([
|
|
413
|
+
{
|
|
414
|
+
cmd: `mneme forensics match ${topFinding.commit.shortHash} ${topFinding.commit.authorEmail}`,
|
|
415
|
+
why: `Verify the top anomaly's author with STR-loci LR matching.`,
|
|
416
|
+
},
|
|
417
|
+
{
|
|
418
|
+
cmd: `mneme forensics vulns --since ${topFinding.commit.authorDate.slice(0, 10)}`,
|
|
419
|
+
why: `Cross-reference vulnerabilities introduced around the anomalous window.`,
|
|
420
|
+
},
|
|
421
|
+
]) + "\n\n");
|
|
422
|
+
return 0;
|
|
423
|
+
}
|
|
424
|
+
// ─── helpers ──────────────────────────────────────────────────────────
|
|
425
|
+
function countBySeverity(findings) {
|
|
426
|
+
const c = { critical: 0, high: 0, medium: 0, low: 0 };
|
|
427
|
+
for (const f of findings) {
|
|
428
|
+
if (f.severity in c)
|
|
429
|
+
c[f.severity]++;
|
|
430
|
+
}
|
|
431
|
+
return c;
|
|
432
|
+
}
|
|
433
|
+
function formatLR(lr) {
|
|
434
|
+
if (lr >= 0.01 && lr < 1000)
|
|
435
|
+
return lr.toFixed(2);
|
|
436
|
+
return lr.toExponential(2);
|
|
437
|
+
}
|
|
438
|
+
function truncateOneLine(s, n) {
|
|
439
|
+
const oneLine = s.replace(/\s+/g, " ").trim();
|
|
440
|
+
if (oneLine.length <= n)
|
|
441
|
+
return oneLine;
|
|
442
|
+
return oneLine.slice(0, n - 1) + "…";
|
|
443
|
+
}
|
|
444
|
+
function verdictPlainEnglish(verdict, suspect) {
|
|
445
|
+
const v = verdict.toLowerCase();
|
|
446
|
+
if (v.includes("extremely strong support against")) {
|
|
447
|
+
return `In plain English: this is overwhelming evidence ${suspect} did NOT write this commit.`;
|
|
448
|
+
}
|
|
449
|
+
if (v.includes("very strong support against")) {
|
|
450
|
+
return `In plain English: very strong evidence ${suspect} is NOT the author.`;
|
|
451
|
+
}
|
|
452
|
+
if (v.includes("strong support against")) {
|
|
453
|
+
return `In plain English: ${suspect} is unlikely to be the author.`;
|
|
454
|
+
}
|
|
455
|
+
if (v.includes("moderate support against") || v.includes("weak support against")) {
|
|
456
|
+
return `In plain English: weak signal that ${suspect} is not the author — not conclusive.`;
|
|
457
|
+
}
|
|
458
|
+
if (v === "uninformative") {
|
|
459
|
+
return `In plain English: the evidence doesn't favor or rule out ${suspect}. Need more loci or context.`;
|
|
460
|
+
}
|
|
461
|
+
if (v.includes("extremely strong support")) {
|
|
462
|
+
return `In plain English: overwhelming evidence ${suspect} wrote this commit.`;
|
|
463
|
+
}
|
|
464
|
+
if (v.includes("very strong support")) {
|
|
465
|
+
return `In plain English: very strong evidence ${suspect} is the author.`;
|
|
466
|
+
}
|
|
467
|
+
if (v.includes("strong support")) {
|
|
468
|
+
return `In plain English: strong support that ${suspect} wrote this commit.`;
|
|
469
|
+
}
|
|
470
|
+
return `In plain English: the loci weakly favor ${suspect} as the author.`;
|
|
471
|
+
}
|
|
472
|
+
//# sourceMappingURL=forensics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"forensics.js","sourceRoot":"","sources":["../../src/commands/forensics.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EACL,GAAG,EACH,KAAK,EACL,SAAS,EACT,IAAI,GAEL,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EACL,EAAE,EACF,MAAM,EACN,OAAO,EAEP,aAAa,EACb,QAAQ,EACR,KAAK,EAEL,UAAU,EACV,SAAS,EACT,YAAY,EACZ,EAAE,EACF,IAAI,GAEL,MAAM,UAAU,CAAC;AAElB,iEAAiE;AACjE,KAAK,UAAU,SAAS,CACtB,GAAW,EACX,CAA0C;IAE1C,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;QAChC,EAAE,CAAC,KAAK,CAAC,4CAA4C,CAAC,CAAC;QACvD,OAAO,CAAC,CAAC;IACX,CAAC;IACD,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IACxC,MAAM,CAAC,GAAG,IAAI,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;IACtD,IAAI,CAAC,CAAC,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC;QAC3B,EAAE,CAAC,KAAK,CAAC,2CAA2C,CAAC,CAAC;QACtD,CAAC,CAAC,KAAK,EAAE,CAAC;QACV,OAAO,CAAC,CAAC;IACX,CAAC;IACD,IAAI,CAAC;QACH,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;YAAS,CAAC;QACT,CAAC,CAAC,KAAK,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,iDAAiD;AACjD,MAAM,UAAU,GAA0B;IACxC,QAAQ,EAAE,UAAU;IACpB,IAAI,EAAE,MAAM;IACZ,MAAM,EAAE,QAAQ;IAChB,GAAG,EAAE,KAAK;IACV,IAAI,EAAE,MAAM;CACb,CAAC;AAWF,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,IAA2B;IACrE,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE;QAC7C,MAAM,UAAU,GAAG,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC;QAC1C,MAAM,cAAc,GAClB,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,IAAI,IAAI,CAAC;QACrE,IAAI,CAAC,cAAc;YAAE,OAAO,EAAE,KAAK,EAAE,UAAU,IAAI,CAAC,UAAU,sBAAsB,EAAE,CAAC;QAEvF,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAoB,CAAC;QAC7C,KAAK,MAAM,CAAC,IAAI,UAAU,EAAE,CAAC;YAC3B,MAAM,CAAC,GAAG,CAAC,CAAC,WAAW,CAAC,WAAW,EAAE,CAAC;YACtC,MAAM,GAAG,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YAC5B,IAAI,GAAG;gBAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;;gBAChB,QAAQ,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QAC5B,CAAC;QAED,MAAM,QAAQ,GAA6B,EAAE,CAAC;QAC9C,KAAK,MAAM,CAAC,EAAE,IAAI,CAAC,IAAI,QAAQ;YAAE,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC;QAC5E,MAAM,UAAU,GAAG,SAAS,CAAC,oBAAoB,CAAC,QAAQ,CAAC,CAAC;QAE5D,MAAM,YAAY,GAAG,SAAS,CAAC,WAAW,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC;QAC7D,MAAM,cAAc,GAAG,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,WAAW,EAAE,CAAC,CAAC;QACpE,IAAI,CAAC,cAAc,IAAI,cAAc,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACjD,OAAO,EAAE,KAAK,EAAE,UAAU,IAAI,CAAC,WAAW,kDAAkD,EAAE,CAAC;QACjG,CAAC;QACD,MAAM,WAAW,GAAG,SAAS,CAAC,WAAW,CAAC,cAAc,CAAC,CAAC;QAC1D,MAAM,MAAM,GAAG,SAAS,CAAC,WAAW,CAAC,YAAY,EAAE,WAAW,EAAE,UAAU,CAAC,CAAC;QAE5E,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,IAAI,CAAC,WAAW,EAAE,MAAM,EAAE,cAAc,EAAE,cAAc,CAAC,MAAM,EAAE,CAAC;IACtG,CAAC,CAAC,CAAC;IACH,IAAI,OAAO,MAAM,KAAK,QAAQ;QAAE,OAAO,MAAM,CAAC;IAC9C,IAAI,OAAO,IAAI,MAAM,EAAE,CAAC;QACtB,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,IAAI,eAAe,CAAC,CAAC;QAC1C,OAAO,CAAC,CAAC;IACX,CAAC;IAED,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;QACd,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;QAC7D,OAAO,CAAC,CAAC;IACX,CAAC;IAED,EAAE,CAAC,MAAM,EAAE,CAAC;IACZ,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,4CAA4C,EAC5E,uDAAuD,CAAC,GAAG,MAAM,CAAC,CAAC;IAErE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,SAAS,CAAC,KAAK,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,OAAO,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC;IAC1I,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,KAAK,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,cAAc,iBAAiB,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,CAAC;IAEzI,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC;IACpC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,6BAA6B,CAAC,GAAG,IAAI,CAAC,CAAC;IACpE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,IAAI,CAAC,WAAW,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,CAAC;IAC1I,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACrE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,KAAK,CAAC,IAAI,CAAC,mBAAmB,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC;IAE1G,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,0BAA0B,EAAE,oCAAoC,CAAC,GAAG,MAAM,CAAC,CAAC;IACzG,8EAA8E;IAC9E,MAAM,UAAU,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,CACjD,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAClE,CAAC;IACF,KAAK,MAAM,CAAC,IAAI,UAAU,EAAE,CAAC;QAC3B,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,OAAO,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAC9G,CAAC;QACF,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,WAAW,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1D,CAAC;IACD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAE3B,oEAAoE;IACpE,MAAM,OAAO,GAAG,EAAE,IAAI,GAAG,CAAC;IAC1B,MAAM,SAAS,GAAG,EAAE,IAAI,IAAI,CAAC;IAC7B,MAAM,IAAI,GAAwC,EAAE,CAAC;IACrD,IAAI,SAAS,EAAE,CAAC;QACd,IAAI,CAAC,IAAI,CAAC;YACR,GAAG,EAAE,6BAA6B,MAAM,CAAC,cAAc,CAAC,SAAS,UAAU;YAC3E,GAAG,EAAE,4EAA4E;SAClF,CAAC,CAAC;IACL,CAAC;SAAM,IAAI,CAAC,OAAO,EAAE,CAAC;QACpB,IAAI,CAAC,IAAI,CAAC;YACR,GAAG,EAAE,6BAA6B,MAAM,CAAC,cAAc,CAAC,SAAS,UAAU;YAC3E,GAAG,EAAE,qEAAqE;SAC3E,CAAC,CAAC;IACL,CAAC;IACD,IAAI,CAAC,IAAI,CAAC;QACR,GAAG,EAAE,yCAAyC;QAC9C,GAAG,EAAE,qEAAqE;KAC3E,CAAC,CAAC;IACH,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,CAAC;IAC/C,OAAO,CAAC,CAAC;AACX,CAAC;AAWD,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAC7C,IAA+B;IAE/B,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE;QAC7C,MAAM,UAAU,GAAG,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC;QAC1C,MAAM,cAAc,GAClB,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,IAAI,IAAI,CAAC;QACrE,IAAI,CAAC,cAAc;YAAE,OAAO,EAAE,KAAK,EAAE,UAAU,IAAI,CAAC,UAAU,sBAAsB,EAAE,CAAC;QAEvF,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAoB,CAAC;QAC7C,KAAK,MAAM,CAAC,IAAI,UAAU,EAAE,CAAC;YAC3B,MAAM,CAAC,GAAG,CAAC,CAAC,WAAW,CAAC,WAAW,EAAE,CAAC;YACtC,MAAM,GAAG,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YAC5B,IAAI,GAAG;gBAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;;gBAChB,QAAQ,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QAC5B,CAAC;QAED,MAAM,QAAQ,GAA6B,EAAE,CAAC;QAC9C,KAAK,MAAM,CAAC,EAAE,IAAI,CAAC,IAAI,QAAQ;YAAE,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC;QAC5E,MAAM,UAAU,GAAG,SAAS,CAAC,oBAAoB,CAAC,QAAQ,CAAC,CAAC;QAE5D,MAAM,YAAY,GAAG,SAAS,CAAC,WAAW,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC;QAE7D,MAAM,UAAU,GAAqG,EAAE,CAAC;QACxH,KAAK,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,QAAQ,EAAE,CAAC;YACtC,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC;gBAAE,SAAS;YAC9B,MAAM,WAAW,GAAG,SAAS,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;YAChD,MAAM,MAAM,GAAG,SAAS,CAAC,WAAW,CAAC,YAAY,EAAE,WAAW,EAAE,UAAU,CAAC,CAAC;YAC5E,UAAU,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;QAChE,CAAC;QACD,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,GAAG,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QAErE,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,CAAC;IACxC,CAAC,CAAC,CAAC;IACH,IAAI,OAAO,MAAM,KAAK,QAAQ;QAAE,OAAO,MAAM,CAAC;IAC9C,IAAI,OAAO,IAAI,MAAM,EAAE,CAAC;QACtB,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,IAAI,eAAe,CAAC,CAAC;QAC1C,OAAO,CAAC,CAAC;IACX,CAAC;IAED,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;QACd,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;QAC7D,OAAO,CAAC,CAAC;IACX,CAAC;IAED,EAAE,CAAC,MAAM,EAAE,CAAC;IACZ,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,8DAA8D,EAC9F,2DAA2D,CAAC,GAAG,MAAM,CAAC,CAAC;IAEzE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,SAAS,CAAC,KAAK,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,OAAO,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,CAAC;IAE5I,IAAI,MAAM,CAAC,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACnC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,CAC7B,yCAAyC,EACzC;YACE,0DAA0D;YAC1D,iEAAiE;SAClE,CACF,CAAC,CAAC;QACH,OAAO,CAAC,CAAC;IACX,CAAC;IAED,mEAAmE;IACnE,MAAM,GAAG,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC,CAAE,CAAC;IAClC,MAAM,MAAM,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;IACpC,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;IACnG,MAAM,UAAU,GAAG,CAAC,GAAG,EAAE;QACvB,IAAI,GAAG,CAAC,MAAM,CAAC,UAAU,IAAI,IAAI,IAAI,CAAC,CAAC,MAAM,IAAI,KAAK,IAAI,GAAG,CAAC,EAAE,CAAC;YAC/D,OAAO,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,sDAAsD,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC;QACvH,CAAC;QACD,IAAI,GAAG,CAAC,MAAM,CAAC,UAAU,IAAI,GAAG,EAAE,CAAC;YACjC,OAAO,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,oDAAoD,KAAK,GAAG,EAAE,CAAC,CAAC,CAAC,wBAAwB,CAAC,CAAC,CAAC,sBAAsB,GAAG,CAAC;QAC5J,CAAC;QACD,IAAI,GAAG,CAAC,MAAM,CAAC,UAAU,IAAI,CAAC,EAAE,CAAC;YAC/B,OAAO,kCAAkC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,6DAA6D,CAAC;QAC/H,CAAC;QACD,OAAO,oFAAoF,CAAC;IAC9F,CAAC,CAAC,EAAE,CAAC;IACL,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,UAAU,MAAM,CAAC,CAAC;IAE5C,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,qBAAqB,CAAC,GAAG,MAAM,CAAC,CAAC;IAC9D,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;IACjE,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC;QAC/B,MAAM,CAAC,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC,CAAE,CAAC;QAChC,MAAM,EAAE,GAAG,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC;QAC/B,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QACzB,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,KAAK,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,KAAK,YAAY,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CACjK,CAAC;QACF,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,YAAY,QAAQ,CAAC,EAAE,CAAC,KAAK,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,WAAW,8BAA8B,CAAC,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,MAAM,CAChH,CAAC;IACJ,CAAC;IAED,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC;QAC7B;YACE,GAAG,EAAE,yBAAyB,MAAM,CAAC,cAAc,CAAC,SAAS,IAAI,GAAG,CAAC,MAAM,EAAE;YAC7E,GAAG,EAAE,gDAAgD;SACtD;QACD;YACE,GAAG,EAAE,aAAa,GAAG,CAAC,MAAM,YAAY;YACxC,GAAG,EAAE,qDAAqD;SAC3D;KACF,CAAC,GAAG,MAAM,CAAC,CAAC;IACb,OAAO,CAAC,CAAC;AACX,CAAC;AAWD,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,IAA2B;IACrE,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;QACrC,EAAE,CAAC,KAAK,CAAC,4CAA4C,CAAC,CAAC;QACvD,OAAO,CAAC,CAAC;IACX,CAAC;IACD,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAE7C,gEAAgE;IAChE,uCAAuC;IACvC,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAC3D,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,SAAS,CAC7B,CAAC,KAAK,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,CAAC,EAAE,GAAG,QAAQ,EAAE,YAAY,EAAE,iDAAiD,CAAC,EACrH,EAAE,GAAG,EAAE,IAAI,CAAC,QAAQ,EAAE,CACvB,CAAC;IAEF,MAAM,MAAM,GAA6C,EAAE,CAAC;IAC5D,KAAK,MAAM,IAAI,IAAI,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACnC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC;YAAE,SAAS;QAC7C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC/B,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,CAAC,IAAI;YAAE,SAAS;QACpB,MAAM,MAAM,GAAW;YACrB,IAAI;YACJ,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC;YAC3B,UAAU,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE;YAC1B,WAAW,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE;YAC3B,UAAU,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE;YAC1B,aAAa,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE;YAC7B,OAAO,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;YAClC,IAAI,EAAE,EAAE;YACR,KAAK,EAAE,EAAE;YACT,OAAO,EAAE,EAAE;SACZ,CAAC;QACF,IAAI,IAAI,GAAG,EAAE,CAAC;QACd,IAAI,CAAC;YACH,IAAI,GAAG,MAAM,GAAG,CAAC,SAAS,CAAC,CAAC,MAAM,EAAE,YAAY,EAAE,kBAAkB,EAAE,IAAI,CAAC,EAAE;gBAC3E,GAAG,EAAE,IAAI,CAAC,QAAQ;aACnB,CAAC,CAAC;QACL,CAAC;QAAC,MAAM,CAAC;YACP,iCAAiC;QACnC,CAAC;QACD,MAAM,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;IAChC,CAAC;IAED,MAAM,MAAM,GAAG,SAAS,CAAC,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAErD,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;QACd,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;QAC7D,OAAO,CAAC,CAAC;IACX,CAAC;IAED,EAAE,CAAC,MAAM,EAAE,CAAC;IACZ,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,wDAAwD,EACxF,iEAAiE,CAAC,GAAG,MAAM,CAAC,CAAC;IAE/E,qEAAqE;IACrE,MAAM,QAAQ,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,GAAG,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC;IACrE,MAAM,WAAW,GAAG,CAAC,GAAG,EAAE;QACxB,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC7B,OAAO,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,wCAAwC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,WAAW,CAAC;QAClH,CAAC;QACD,IAAI,QAAQ,GAAG,CAAC,EAAE,CAAC;YACjB,OAAO,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,oCAAoC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,qCAAqC,CAAC;QAC7K,CAAC;QACD,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,8BAA8B,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,kCAAkC,CAAC;IAC3K,CAAC,CAAC,EAAE,CAAC;IACL,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,WAAW,MAAM,CAAC,CAAC;IAE7C,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC7B,IAAI,MAAM,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAClC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,gBAAgB,EAC3C,8DAA8D,CAAC,GAAG,MAAM,CAAC,CAAC;YAC5E,KAAK,MAAM,EAAE,IAAI,MAAM,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;gBAChD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,SAAS,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,OAAO,IAAI,CAAC,CAAC;YACxI,CAAC;YACD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC7B,CAAC;QACD,OAAO,CAAC,CAAC;IACX,CAAC;IAED,sCAAsC;IACtC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,eAAe,CAAC,GAAG,IAAI,CAAC,CAAC;IACtD,MAAM,QAAQ,GAA0C,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;IACtG,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACxE,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;QACzB,MAAM,CAAC,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;QAC/B,IAAI,CAAC,KAAK,CAAC;YAAE,SAAS;QACtB,MAAM,KAAK,GAAG,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;QAC9C,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,OAAO,aAAa,CAAC,CAAU,CAAC,KAAK,KAAK,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,CAAU,EAAE,CAAC,KAAK,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAC9H,CAAC;IACJ,CAAC;IACD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAE3B,gDAAgD;IAChD,MAAM,KAAK,GAAG,IAAI,GAAG,EAAkB,CAAC;IACxC,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;QAC5B,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAC5D,CAAC;IACD,IAAI,KAAK,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;QACnB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,gBAAgB,EAAE,SAAS,CAAC,GAAG,IAAI,CAAC,CAAC;QAClE,MAAM,OAAO,GAAG,CAAC,GAAG,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAC7E,KAAK,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,OAAO,EAAE,CAAC;YAC/B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,KAAK,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QACpG,CAAC;QACD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC7B,CAAC;IAED,WAAW;IACX,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,gBAAgB,CAAC,GAAG,MAAM,CAAC,CAAC;IACzD,MAAM,SAAS,GAA2D;QACxE,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC;KACjD,CAAC;IACF,MAAM,UAAU,GAAG,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QAChD,MAAM,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;QACxD,IAAI,CAAC,KAAK,CAAC;YAAE,OAAO,CAAC,CAAC;QACtB,OAAO,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IAChE,CAAC,CAAC,CAAC;IACH,KAAK,MAAM,CAAC,IAAI,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC;QACxC,MAAM,GAAG,GAAG,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC7D,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,OAAO,aAAa,CAAC,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,MAAM,CAAC,KAAK,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,GAAG,IAAI,CAC/K,CAAC;QACF,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC;QAC/C,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,WAAW,KAAK,CAAC,IAAI,CAAC,YAAY,GAAG,eAAe,CAAC,CAAC,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC;IACrG,CAAC;IAED,uDAAuD;IACvD,IAAI,MAAM,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAClC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,gBAAgB,EAC3C,IAAI,MAAM,CAAC,WAAW,CAAC,MAAM,sEAAsE,CAAC,GAAG,MAAM,CAAC,CAAC;QACjH,KAAK,MAAM,EAAE,IAAI,MAAM,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;YAChD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,SAAS,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,OAAO,IAAI,CAAC,CAAC;QACxI,CAAC;QACD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC7B,CAAC;IAED,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC;QAC7B;YACE,GAAG,EAAE,yCAAyC;YAC9C,GAAG,EAAE,mEAAmE;SACzE;QACD;YACE,GAAG,EAAE,6EAA6E;YAClF,GAAG,EAAE,yCAAyC;SAC/C;KACF,CAAC,GAAG,MAAM,CAAC,CAAC;IACb,OAAO,CAAC,CAAC;AACX,CAAC;AAWD,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAC3C,IAA6B;IAE7B,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE;QAC7C,MAAM,UAAU,GAAG,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC;QAC1C,MAAM,WAAW,GAAG,IAAI,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC;QAC/C,MAAM,SAAS,GAAG,SAAS,CAAC,cAAc,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;QACpE,MAAM,QAAQ,GAAG,SAAS,CAAC,eAAe,CACxC,UAAU,EACV,SAAS,EACT,WAAW,EACX,IAAI,CAAC,SAAS,IAAI,GAAG,CACtB,CAAC;QACF,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,UAAU,CAAC,MAAM,EAAE,SAAS,EAAE,SAAS,CAAC,IAAI,EAAE,CAAC;IAC7E,CAAC,CAAC,CAAC;IACH,IAAI,OAAO,MAAM,KAAK,QAAQ;QAAE,OAAO,MAAM,CAAC;IAE9C,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;QACd,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;QAC7D,OAAO,CAAC,CAAC;IACX,CAAC;IAED,EAAE,CAAC,MAAM,EAAE,CAAC;IACZ,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,iEAAiE,EACjG,sEAAsE,CAAC,GAAG,MAAM,CAAC,CAAC;IAEpF,MAAM,MAAM,GAAG,eAAe,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IAChD,MAAM,OAAO,GAAG,CAAC,GAAG,EAAE;QACpB,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACjC,OAAO,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,gDAAgD,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,SAAS,IAAI,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,WAAW,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,WAAW,CAAC;QACnL,CAAC;QACD,IAAI,MAAM,CAAC,QAAQ,GAAG,CAAC,EAAE,CAAC;YACxB,OAAO,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,8BAA8B,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,2CAA2C,CAAC;QACtL,CAAC;QACD,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,6CAA6C,CAAC;IAC1H,CAAC,CAAC,EAAE,CAAC;IACL,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,OAAO,MAAM,CAAC,CAAC;IAEzC,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACjC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,CAC7B,sDAAsD,EACtD;YACE,kEAAkE;YAClE,8DAA8D;SAC/D,CACF,CAAC,CAAC;QACH,OAAO,CAAC,CAAC;IACX,CAAC;IAED,iBAAiB;IACjB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,eAAe,CAAC,GAAG,IAAI,CAAC,CAAC;IACtD,MAAM,IAAI,GAAsD;QAC9D,EAAE,GAAG,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,EAAE;QACtC,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE;QAC9B,EAAE,GAAG,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE;QAClC,EAAE,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE;KAC7B,CAAC;IACF,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;IAClF,KAAK,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,IAAI,EAAE,CAAC;QAClC,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;QACtB,IAAI,CAAC,KAAK,CAAC;YAAE,SAAS;QACtB,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,OAAO,aAAa,CAAC,KAAK,CAAC,KAAK,KAAK,CAAC,CAAC,GAAG,IAAI,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,KAAK,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAChH,CAAC;IACJ,CAAC;IACD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAE3B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,qBAAqB,EAAE,uBAAuB,CAAC,GAAG,MAAM,CAAC,CAAC;IACvF,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC,EAAE,CAAC;QAC1D,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC;QACnB,MAAM,QAAQ,GAAU,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,MAAM,CAAC;QACzD,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,OAAO,aAAa,CAAC,QAAQ,CAAC,KAAK,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,KAAK,GAAG,CAAC,CAAC,UAAU,CAAC,IAAI,CAC/H,CAAC;QACF,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,WAAW,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,WAAW,IAAI,CAAC,IAAI,CAChH,CAAC;QACF,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,WAAW,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAC5D,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC;YACvB,IAAI,CAAC,CAAC,KAAK,GAAG,GAAG;gBAAE,SAAS;YAC5B,+DAA+D;YAC/D,MAAM,SAAS,GAAU,CAAC,CAAC,KAAK,IAAI,GAAG,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC;YACnH,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,aAAa,KAAK,CAAC,CAAC,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,KAAK,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CACxH,CAAC;QACJ,CAAC;QACD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,WAAW,KAAK,CAAC,MAAM,CAAC,IAAI,GAAG,CAAC,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC;IAC/E,CAAC;IAED,wCAAwC;IACxC,MAAM,UAAU,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAE,CAAC;IACvC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC;QAC7B;YACE,GAAG,EAAE,yBAAyB,UAAU,CAAC,MAAM,CAAC,SAAS,IAAI,UAAU,CAAC,MAAM,CAAC,WAAW,EAAE;YAC5F,GAAG,EAAE,4DAA4D;SAClE;QACD;YACE,GAAG,EAAE,iCAAiC,UAAU,CAAC,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE;YACjF,GAAG,EAAE,yEAAyE;SAC/E;KACF,CAAC,GAAG,MAAM,CAAC,CAAC;IACb,OAAO,CAAC,CAAC;AACX,CAAC;AAED,yEAAyE;AAEzE,SAAS,eAAe,CAAC,QAAqC;IAG5D,MAAM,CAAC,GAAG,EAAE,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC;IACtD,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;QACzB,IAAI,CAAC,CAAC,QAAQ,IAAI,CAAC;YAAG,CAA4B,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC;IACnE,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC;AAED,SAAS,QAAQ,CAAC,EAAU;IAC1B,IAAI,EAAE,IAAI,IAAI,IAAI,EAAE,GAAG,IAAI;QAAE,OAAO,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IAClD,OAAO,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;AAC7B,CAAC;AAED,SAAS,eAAe,CAAC,CAAS,EAAE,CAAS;IAC3C,MAAM,OAAO,GAAG,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IAC9C,IAAI,OAAO,CAAC,MAAM,IAAI,CAAC;QAAE,OAAO,OAAO,CAAC;IACxC,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC;AACvC,CAAC;AAED,SAAS,mBAAmB,CAAC,OAAe,EAAE,OAAe;IAC3D,MAAM,CAAC,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAChC,IAAI,CAAC,CAAC,QAAQ,CAAC,kCAAkC,CAAC,EAAE,CAAC;QACnD,OAAO,mDAAmD,OAAO,6BAA6B,CAAC;IACjG,CAAC;IACD,IAAI,CAAC,CAAC,QAAQ,CAAC,6BAA6B,CAAC,EAAE,CAAC;QAC9C,OAAO,0CAA0C,OAAO,qBAAqB,CAAC;IAChF,CAAC;IACD,IAAI,CAAC,CAAC,QAAQ,CAAC,wBAAwB,CAAC,EAAE,CAAC;QACzC,OAAO,qBAAqB,OAAO,gCAAgC,CAAC;IACtE,CAAC;IACD,IAAI,CAAC,CAAC,QAAQ,CAAC,0BAA0B,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,sBAAsB,CAAC,EAAE,CAAC;QACjF,OAAO,sCAAsC,OAAO,sCAAsC,CAAC;IAC7F,CAAC;IACD,IAAI,CAAC,KAAK,eAAe,EAAE,CAAC;QAC1B,OAAO,4DAA4D,OAAO,8BAA8B,CAAC;IAC3G,CAAC;IACD,IAAI,CAAC,CAAC,QAAQ,CAAC,0BAA0B,CAAC,EAAE,CAAC;QAC3C,OAAO,2CAA2C,OAAO,qBAAqB,CAAC;IACjF,CAAC;IACD,IAAI,CAAC,CAAC,QAAQ,CAAC,qBAAqB,CAAC,EAAE,CAAC;QACtC,OAAO,0CAA0C,OAAO,iBAAiB,CAAC;IAC5E,CAAC;IACD,IAAI,CAAC,CAAC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,CAAC;QACjC,OAAO,yCAAyC,OAAO,qBAAqB,CAAC;IAC/E,CAAC;IACD,OAAO,2CAA2C,OAAO,iBAAiB,CAAC;AAC7E,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"guardian.d.ts","sourceRoot":"","sources":["../../src/commands/guardian.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"guardian.d.ts","sourceRoot":"","sources":["../../src/commands/guardian.ts"],"names":[],"mappings":"AA2CA,MAAM,WAAW,sBAAsB;IACrC,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,2DAA2D;IAC3D,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,wBAAsB,eAAe,CACnC,IAAI,EAAE,sBAAsB,GAC3B,OAAO,CAAC,MAAM,CAAC,CAsFjB"}
|
|
@@ -24,7 +24,7 @@ import { writeFileSync, existsSync, mkdirSync, appendFileSync, readFileSync } fr
|
|
|
24
24
|
import { dirname } from "node:path";
|
|
25
25
|
import { git, store, guardian, indexer, util, } from "@mneme-ai/core";
|
|
26
26
|
import { dbPath } from "../paths.js";
|
|
27
|
-
import { ui } from "../ui.js";
|
|
27
|
+
import { ui, header, kv, pill, section, severityBadge, } from "../ui.js";
|
|
28
28
|
export async function guardianCommand(opts) {
|
|
29
29
|
if (!(await git.isGitRepo(opts.cwd))) {
|
|
30
30
|
ui.error("Not in a git repo. Run `mneme init` first.");
|
|
@@ -37,12 +37,11 @@ export async function guardianCommand(opts) {
|
|
|
37
37
|
const maxIter = opts.maxIterations ?? (watch ? Infinity : 1);
|
|
38
38
|
if (!opts.json) {
|
|
39
39
|
ui.banner();
|
|
40
|
-
process.stdout.write(
|
|
41
|
-
process.stdout.write(
|
|
42
|
-
process.stdout.write(
|
|
43
|
-
process.stdout.write(` ${kleur.gray("apply ")} ${apply ? kleur.green("yes (auto-fix enabled)") : kleur.yellow("no (observe-only)")}\n`);
|
|
40
|
+
process.stdout.write(header("🛡", "Guardian — 24/7 self-healing daemon", "diagnose → fix → learn loop · safe by default") + "\n\n");
|
|
41
|
+
process.stdout.write(kv("mode", watch ? pill("WATCH", "ok") : pill("ONCE", "low")) + "\n");
|
|
42
|
+
process.stdout.write(kv("apply", apply ? pill("AUTO-FIX", "ok") : pill("OBSERVE-ONLY", "warn")) + "\n");
|
|
44
43
|
if (watch) {
|
|
45
|
-
process.stdout.write(
|
|
44
|
+
process.stdout.write(kv("interval", `${opts.intervalSeconds ?? 300}s`) + "\n");
|
|
46
45
|
}
|
|
47
46
|
process.stdout.write("\n");
|
|
48
47
|
}
|
|
@@ -171,47 +170,37 @@ async function collectInput(cwd, rootPath, lastQualityScore) {
|
|
|
171
170
|
}
|
|
172
171
|
function renderTick(iteration, report) {
|
|
173
172
|
const ts = report.generatedAt.replace("T", " ").slice(0, 19);
|
|
174
|
-
|
|
173
|
+
const tickLabel = `tick #${iteration}`;
|
|
174
|
+
process.stdout.write(` ${kleur.gray(`┄┄┄ ${tickLabel} · ${ts} ┄┄┄`)}\n`);
|
|
175
175
|
if (report.findings.length === 0) {
|
|
176
|
-
process.stdout.write(` ${kleur.green("✓")}
|
|
176
|
+
process.stdout.write(` ${kleur.green("✓")} ${kleur.green("All systems healthy — no findings.")}\n\n`);
|
|
177
177
|
return;
|
|
178
178
|
}
|
|
179
179
|
for (const f of report.findings) {
|
|
180
|
-
const sev =
|
|
180
|
+
const sev = (f.severity in { critical: 0, high: 0, medium: 0, low: 0, info: 0 }
|
|
181
|
+
? f.severity
|
|
182
|
+
: "info");
|
|
181
183
|
const policy = policyLabel(f.policy);
|
|
182
|
-
process.stdout.write(` ${sev} ${policy} ${f.message}\n`);
|
|
184
|
+
process.stdout.write(` ${severityBadge(sev)} ${policy} ${f.message}\n`);
|
|
183
185
|
if (f.suggestedAction) {
|
|
184
186
|
process.stdout.write(` ${kleur.gray("→")} ${kleur.cyan(f.suggestedAction)}\n`);
|
|
185
187
|
}
|
|
186
188
|
}
|
|
187
|
-
process.stdout.write(
|
|
188
|
-
}
|
|
189
|
-
function severityColor(sev) {
|
|
190
|
-
switch (sev) {
|
|
191
|
-
case "critical":
|
|
192
|
-
return kleur.red().bold("CRIT");
|
|
193
|
-
case "high":
|
|
194
|
-
return kleur.red("HIGH");
|
|
195
|
-
case "medium":
|
|
196
|
-
return kleur.yellow("MED ");
|
|
197
|
-
case "low":
|
|
198
|
-
return kleur.cyan("LOW ");
|
|
199
|
-
default:
|
|
200
|
-
return kleur.gray(sev);
|
|
201
|
-
}
|
|
189
|
+
process.stdout.write(`\n ${kleur.gray(`summary: ${report.summary.findings} findings · ${report.summary.autoActions} auto · ${report.summary.recommendations} suggested`)}\n\n`);
|
|
202
190
|
}
|
|
203
191
|
function policyLabel(policy) {
|
|
204
192
|
switch (policy) {
|
|
205
193
|
case "auto":
|
|
206
|
-
return
|
|
194
|
+
return pill("AUTO", "ok");
|
|
207
195
|
case "recommended":
|
|
208
|
-
return
|
|
196
|
+
return pill("SUGGEST", "low");
|
|
209
197
|
case "observe":
|
|
210
|
-
return
|
|
198
|
+
return pill("OBSERVE", "info");
|
|
211
199
|
default:
|
|
212
|
-
return
|
|
200
|
+
return pill(policy, "info");
|
|
213
201
|
}
|
|
214
202
|
}
|
|
203
|
+
void section;
|
|
215
204
|
function appendLog(rootPath, entry) {
|
|
216
205
|
const logDir = `${rootPath}/.mneme`;
|
|
217
206
|
if (!existsSync(logDir))
|