mneme-ai 0.9.0 โ 0.11.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/insights-cli.d.ts +56 -0
- package/dist/commands/insights-cli.d.ts.map +1 -1
- package/dist/commands/insights-cli.js +504 -7
- package/dist/commands/insights-cli.js.map +1 -1
- package/dist/commands/quant-cli.d.ts +61 -0
- package/dist/commands/quant-cli.d.ts.map +1 -0
- package/dist/commands/quant-cli.js +373 -0
- package/dist/commands/quant-cli.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +211 -3
- package/dist/index.js.map +1 -1
- package/package.json +28 -9
|
@@ -47,4 +47,60 @@ export interface ChatOptions {
|
|
|
47
47
|
noLlm?: boolean;
|
|
48
48
|
}
|
|
49
49
|
export declare function chatCommand(opts: ChatOptions): Promise<number>;
|
|
50
|
+
export interface RegretOptions {
|
|
51
|
+
cwd: string;
|
|
52
|
+
windowDays?: number;
|
|
53
|
+
json?: boolean;
|
|
54
|
+
}
|
|
55
|
+
export declare function regretCommand(opts: RegretOptions): Promise<number>;
|
|
56
|
+
export interface BusFactorOptions {
|
|
57
|
+
cwd: string;
|
|
58
|
+
topN?: number;
|
|
59
|
+
minTouches?: number;
|
|
60
|
+
json?: boolean;
|
|
61
|
+
}
|
|
62
|
+
export declare function busFactorCommand(opts: BusFactorOptions): Promise<number>;
|
|
63
|
+
export interface ParadoxOptions {
|
|
64
|
+
cwd: string;
|
|
65
|
+
json?: boolean;
|
|
66
|
+
}
|
|
67
|
+
export declare function paradoxCommand(opts: ParadoxOptions): Promise<number>;
|
|
68
|
+
export interface CommitCoachOptions {
|
|
69
|
+
cwd: string;
|
|
70
|
+
diffFile?: string;
|
|
71
|
+
fromStdin?: boolean;
|
|
72
|
+
json?: boolean;
|
|
73
|
+
noLlm?: boolean;
|
|
74
|
+
}
|
|
75
|
+
export declare function commitCoachCommand(opts: CommitCoachOptions): Promise<number>;
|
|
76
|
+
export interface CrystalBallOptions {
|
|
77
|
+
cwd: string;
|
|
78
|
+
diffFile?: string;
|
|
79
|
+
fromStdin?: boolean;
|
|
80
|
+
windowDays?: number;
|
|
81
|
+
json?: boolean;
|
|
82
|
+
}
|
|
83
|
+
export declare function crystalBallCommand(opts: CrystalBallOptions): Promise<number>;
|
|
84
|
+
export interface TimeMachineOptions {
|
|
85
|
+
cwd: string;
|
|
86
|
+
filePath: string;
|
|
87
|
+
plateauDays?: number;
|
|
88
|
+
json?: boolean;
|
|
89
|
+
}
|
|
90
|
+
export declare function timeMachineCommand(opts: TimeMachineOptions): Promise<number>;
|
|
91
|
+
export interface PremortemOptions {
|
|
92
|
+
cwd: string;
|
|
93
|
+
intent: string;
|
|
94
|
+
similarityFloor?: number;
|
|
95
|
+
windowDays?: number;
|
|
96
|
+
json?: boolean;
|
|
97
|
+
}
|
|
98
|
+
export declare function premortemCommand(opts: PremortemOptions): Promise<number>;
|
|
99
|
+
export interface GhostOptions {
|
|
100
|
+
cwd: string;
|
|
101
|
+
topN?: number;
|
|
102
|
+
staleDays?: number;
|
|
103
|
+
json?: boolean;
|
|
104
|
+
}
|
|
105
|
+
export declare function ghostCommand(opts: GhostOptions): Promise<number>;
|
|
50
106
|
//# sourceMappingURL=insights-cli.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"insights-cli.d.ts","sourceRoot":"","sources":["../../src/commands/insights-cli.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAoDH,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,wBAAsB,eAAe,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC,
|
|
1
|
+
{"version":3,"file":"insights-cli.d.ts","sourceRoot":"","sources":["../../src/commands/insights-cli.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAoDH,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,wBAAsB,eAAe,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC,CA+D5E;AA0BD,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,OAAO,GAAG,UAAU,GAAG,MAAM,GAAG,UAAU,CAAC;IACpD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,MAAM,CAAC,CAiE9E;AAID,MAAM,WAAW,iBAAiB;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,CAyFhF;AAgDD,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,+EAA+E;IAC/E,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,wBAAsB,YAAY,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,CAyGtE;AAID,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,wBAAsB,YAAY,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,CAgEtE;AAeD,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,wBAAsB,WAAW,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CA4HpE;AAcD,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,wBAAsB,aAAa,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CAmDxE;AAiBD,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,MAAM,CAAC,CAyC9E;AAiBD,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,wBAAsB,cAAc,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAuC1E;AAID,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,kBAAkB,GAAG,OAAO,CAAC,MAAM,CAAC,CA8ElF;AAID,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,kBAAkB,GAAG,OAAO,CAAC,MAAM,CAAC,CA8DlF;AA0DD,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,kBAAkB,GAAG,OAAO,CAAC,MAAM,CAAC,CAwDlF;AAyBD,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,MAAM,CAAC,CA4D9E;AAID,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,wBAAsB,YAAY,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,CA4CtE"}
|
|
@@ -53,17 +53,40 @@ export async function whoKnowsCommand(opts) {
|
|
|
53
53
|
return 0;
|
|
54
54
|
}
|
|
55
55
|
ui.banner();
|
|
56
|
-
|
|
57
|
-
if (
|
|
56
|
+
const verdict = insights.whoKnowsVerdict(candidates);
|
|
57
|
+
if (opts.json) {
|
|
58
|
+
process.stdout.write(JSON.stringify({ topic: opts.topic, verdict, candidates }, null, 2) + "\n");
|
|
59
|
+
return 0;
|
|
60
|
+
}
|
|
61
|
+
// โโโ Header โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
62
|
+
process.stdout.write(`\n ${kleur.bold().cyan("๐ค Who knows about")} ${kleur.bold(`"${opts.topic}"`)}\n`);
|
|
63
|
+
process.stdout.write(` ${kleur.gray("โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ")}\n\n`);
|
|
64
|
+
if (candidates.length === 0 || !verdict.topExpert) {
|
|
58
65
|
process.stdout.write(` ${kleur.gray(`No commits matched "${opts.topic}". Try a broader topic.`)}\n\n`);
|
|
59
66
|
return 0;
|
|
60
67
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
68
|
+
// โโโ VERDICT (the answer the user is here for) โโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
69
|
+
const top = verdict.topExpert;
|
|
70
|
+
process.stdout.write(` ${kleur.bold().magenta("โฆ Verdict")}\n\n`);
|
|
71
|
+
process.stdout.write(` ${kleur.bold(top.name)} ${kleur.gray(`<${top.email}>`)}\n`);
|
|
72
|
+
process.stdout.write(` ${kleur.cyan(verdict.confidencePct + "%")} confidence โ ${top.commitCount} of ${verdict.totalCommits} relevant commits\n`);
|
|
73
|
+
process.stdout.write(` last touch ${kleur.bold(daysAgoFromIso(top.lastTouch))} ago ยท ${top.filesTouched} files ยท ${renderTier(top.tier)}\n`);
|
|
74
|
+
if (verdict.risk) {
|
|
75
|
+
process.stdout.write(`\n ${kleur.yellow("โ ")}${kleur.yellow(verdict.risk)}\n`);
|
|
76
|
+
}
|
|
77
|
+
if (verdict.backup) {
|
|
78
|
+
process.stdout.write(`\n ${kleur.gray("backup:")} ${kleur.bold(verdict.backup.name)} ${kleur.gray(`(${verdict.backup.commitCount} commits, last touch ${daysAgoFromIso(verdict.backup.lastTouch)} ago)`)}\n`);
|
|
79
|
+
}
|
|
80
|
+
// โโโ All candidates โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
81
|
+
if (candidates.length > 1) {
|
|
82
|
+
process.stdout.write(`\n ${kleur.bold().magenta("โ All candidates")} ${kleur.gray(`(${candidates.length})`)}\n\n`);
|
|
83
|
+
for (const c of candidates) {
|
|
84
|
+
const tier = renderTier(c.tier);
|
|
85
|
+
process.stdout.write(` ${tier} ${kleur.bold(c.name)} ${kleur.gray(`<${c.email}>`)}\n`);
|
|
86
|
+
process.stdout.write(` ${kleur.gray(`${c.commitCount} commits ยท ${c.filesTouched} files ยท last touch ${daysAgoFromIso(c.lastTouch)} ago ยท score ${c.score.toFixed(2)}`)}\n`);
|
|
87
|
+
}
|
|
66
88
|
}
|
|
89
|
+
process.stdout.write("\n");
|
|
67
90
|
return 0;
|
|
68
91
|
}
|
|
69
92
|
function renderTier(tier) {
|
|
@@ -541,6 +564,301 @@ export async function chatCommand(opts) {
|
|
|
541
564
|
process.stdout.write(`\n ${kleur.gray(`bye โ ${transcript.length} turn(s) recorded`)}\n\n`);
|
|
542
565
|
return 0;
|
|
543
566
|
}
|
|
567
|
+
// โโโ divider helper โ used across all insights output โโโโโโโโโโโโโโโโโ
|
|
568
|
+
const DIV_WIDTH = 64;
|
|
569
|
+
function divider(label = "") {
|
|
570
|
+
if (!label)
|
|
571
|
+
return kleur.gray("โ".repeat(DIV_WIDTH));
|
|
572
|
+
const padded = `โโโ ${label} `;
|
|
573
|
+
const tail = "โ".repeat(Math.max(4, DIV_WIDTH - padded.length));
|
|
574
|
+
return kleur.gray(padded + tail);
|
|
575
|
+
}
|
|
576
|
+
export async function regretCommand(opts) {
|
|
577
|
+
const result = await withStore(opts.cwd, (s) => {
|
|
578
|
+
const commits = util.loadAllCommits(s);
|
|
579
|
+
const regrets = insights.detectRegrets(commits, { windowDays: opts.windowDays ?? 7 });
|
|
580
|
+
const summary = insights.summarizeRegrets(commits, regrets);
|
|
581
|
+
return { regrets, summary };
|
|
582
|
+
});
|
|
583
|
+
if (typeof result === "number")
|
|
584
|
+
return result;
|
|
585
|
+
const { regrets, summary } = result;
|
|
586
|
+
if (opts.json) {
|
|
587
|
+
process.stdout.write(JSON.stringify({ regrets, summary }, null, 2) + "\n");
|
|
588
|
+
return 0;
|
|
589
|
+
}
|
|
590
|
+
ui.banner();
|
|
591
|
+
process.stdout.write(`\n ${kleur.bold().cyan("๐ฌ Regrets โ what we shipped and immediately fixed")}\n`);
|
|
592
|
+
process.stdout.write(` ${divider()}\n\n`);
|
|
593
|
+
// Verdict
|
|
594
|
+
process.stdout.write(` ${kleur.bold().magenta("โฆ Summary")}\n\n`);
|
|
595
|
+
process.stdout.write(` ${kleur.bold(String(summary.totalRegrets))} regrets across ${kleur.bold(String(summary.totalShipped))} shipped commits ${kleur.gray(`(rate: ${(summary.regretRate * 100).toFixed(1)}%)`)}\n`);
|
|
596
|
+
if (summary.totalRegrets > 0) {
|
|
597
|
+
process.stdout.write(` average days-to-fix: ${kleur.bold(summary.averageDaysToFix.toFixed(1))}\n`);
|
|
598
|
+
const breakdown = Object.entries(summary.byKind)
|
|
599
|
+
.filter(([, n]) => n > 0)
|
|
600
|
+
.map(([k, n]) => `${k}: ${n}`)
|
|
601
|
+
.join(" ยท ");
|
|
602
|
+
if (breakdown)
|
|
603
|
+
process.stdout.write(` ${kleur.gray("breakdown: " + breakdown)}\n`);
|
|
604
|
+
}
|
|
605
|
+
if (regrets.length === 0) {
|
|
606
|
+
process.stdout.write(`\n ${kleur.green("โ")} No regrets detected โ clean shipping history.\n\n`);
|
|
607
|
+
return 0;
|
|
608
|
+
}
|
|
609
|
+
// Listing
|
|
610
|
+
process.stdout.write(`\n ${kleur.bold().magenta("โ Recent regrets")} ${kleur.gray(`(showing ${Math.min(20, regrets.length)} of ${regrets.length})`)}\n\n`);
|
|
611
|
+
for (const r of regrets.slice(0, 20)) {
|
|
612
|
+
const kindBadge = renderRegretKind(r.kind);
|
|
613
|
+
const shippedHash = r.shipped.shortHash || r.shipped.hash.slice(0, 7);
|
|
614
|
+
const followupHash = r.followup.shortHash || r.followup.hash.slice(0, 7);
|
|
615
|
+
process.stdout.write(` ${kindBadge} shipped ${kleur.bold(r.shipped.authorDate.slice(0, 10))} ${kleur.gray("โ fixed in " + r.daysToFix + "d")}\n`);
|
|
616
|
+
process.stdout.write(` ${kleur.cyan(shippedHash)} ${kleur.bold(r.shipped.subject)}\n`);
|
|
617
|
+
process.stdout.write(` ${kleur.gray("โณ " + followupHash + " " + r.followup.subject)}\n`);
|
|
618
|
+
if (r.lesson)
|
|
619
|
+
process.stdout.write(` ${kleur.gray("lesson: " + r.lesson)}\n`);
|
|
620
|
+
process.stdout.write("\n");
|
|
621
|
+
}
|
|
622
|
+
return 0;
|
|
623
|
+
}
|
|
624
|
+
function renderRegretKind(kind) {
|
|
625
|
+
switch (kind) {
|
|
626
|
+
case "revert":
|
|
627
|
+
return kleur.red().bold("REVERT ");
|
|
628
|
+
case "hotfix":
|
|
629
|
+
return kleur.yellow().bold("HOTFIX ");
|
|
630
|
+
case "fix":
|
|
631
|
+
return kleur.cyan().bold("FIX ");
|
|
632
|
+
default:
|
|
633
|
+
return kleur.gray().bold("similar ");
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
export async function busFactorCommand(opts) {
|
|
637
|
+
const result = await withStore(opts.cwd, (s) => {
|
|
638
|
+
return insights.busFactor(s, { topN: opts.topN ?? 20, minTouches: opts.minTouches ?? 3 });
|
|
639
|
+
});
|
|
640
|
+
if (typeof result === "number")
|
|
641
|
+
return result;
|
|
642
|
+
const risks = result;
|
|
643
|
+
if (opts.json) {
|
|
644
|
+
process.stdout.write(JSON.stringify({ risks }, null, 2) + "\n");
|
|
645
|
+
return 0;
|
|
646
|
+
}
|
|
647
|
+
ui.banner();
|
|
648
|
+
process.stdout.write(`\n ${kleur.bold().cyan("๐จ Bus-factor risks โ knowledge fragility")}\n`);
|
|
649
|
+
process.stdout.write(` ${divider()}\n\n`);
|
|
650
|
+
if (risks.length === 0) {
|
|
651
|
+
process.stdout.write(` ${kleur.green("โ")} No high-risk files detected โ knowledge is well distributed.\n\n`);
|
|
652
|
+
return 0;
|
|
653
|
+
}
|
|
654
|
+
const counts = { critical: 0, high: 0, medium: 0, low: 0 };
|
|
655
|
+
for (const r of risks)
|
|
656
|
+
counts[r.tier] += 1;
|
|
657
|
+
process.stdout.write(` ${kleur.bold().magenta("โฆ Summary")}\n\n`);
|
|
658
|
+
process.stdout.write(` ${kleur.red().bold(String(counts.critical))} critical ยท ${kleur.yellow().bold(String(counts.high))} high ยท ${kleur.cyan().bold(String(counts.medium))} medium ยท ${kleur.gray(counts.low + " low")}\n\n`);
|
|
659
|
+
process.stdout.write(` ${kleur.bold().magenta("โ Files at risk")} ${kleur.gray(`(top ${risks.length})`)}\n\n`);
|
|
660
|
+
for (const r of risks) {
|
|
661
|
+
const tier = renderBusFactorTier(r.tier);
|
|
662
|
+
process.stdout.write(` ${tier} ${kleur.bold(r.filePath)}\n`);
|
|
663
|
+
process.stdout.write(` ${kleur.bold(r.topOwner.name)} ${kleur.gray(`<${r.topOwner.email}>`)} ${kleur.cyan(r.topOwner.sharePct + "%")} ${kleur.gray(`(${r.topOwner.touches} of ${r.totalTouches} commits)`)}\n`);
|
|
664
|
+
if (r.backup) {
|
|
665
|
+
process.stdout.write(` ${kleur.gray(`backup: ${r.backup.name} (${r.backup.touches} commits)`)}\n`);
|
|
666
|
+
}
|
|
667
|
+
process.stdout.write(` ${kleur.gray("โ " + r.recommendation)}\n\n`);
|
|
668
|
+
}
|
|
669
|
+
return 0;
|
|
670
|
+
}
|
|
671
|
+
function renderBusFactorTier(tier) {
|
|
672
|
+
switch (tier) {
|
|
673
|
+
case "critical":
|
|
674
|
+
return kleur.red().bold("CRITICAL");
|
|
675
|
+
case "high":
|
|
676
|
+
return kleur.yellow().bold("HIGH ");
|
|
677
|
+
case "medium":
|
|
678
|
+
return kleur.cyan().bold("MEDIUM ");
|
|
679
|
+
default:
|
|
680
|
+
return kleur.gray().bold("LOW ");
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
export async function paradoxCommand(opts) {
|
|
684
|
+
const result = await withStore(opts.cwd, (s) => {
|
|
685
|
+
const commits = util.loadAllCommits(s);
|
|
686
|
+
const decisions = commits.flatMap(insights.extractDecisions);
|
|
687
|
+
return insights.detectParadoxes(decisions);
|
|
688
|
+
});
|
|
689
|
+
if (typeof result === "number")
|
|
690
|
+
return result;
|
|
691
|
+
const flipFlops = result;
|
|
692
|
+
if (opts.json) {
|
|
693
|
+
process.stdout.write(JSON.stringify({ flipFlops }, null, 2) + "\n");
|
|
694
|
+
return 0;
|
|
695
|
+
}
|
|
696
|
+
ui.banner();
|
|
697
|
+
process.stdout.write(`\n ${kleur.bold().cyan("๐ Paradoxes โ architectural flip-flops")}\n`);
|
|
698
|
+
process.stdout.write(` ${divider()}\n\n`);
|
|
699
|
+
if (flipFlops.length === 0) {
|
|
700
|
+
process.stdout.write(` ${kleur.green("โ")} No flip-flops detected. Either the team is consistent, or commit messages are too thin to extract decisions.\n\n`);
|
|
701
|
+
return 0;
|
|
702
|
+
}
|
|
703
|
+
process.stdout.write(` ${kleur.bold().magenta("โฆ Summary")}\n\n`);
|
|
704
|
+
process.stdout.write(` ${kleur.bold(String(flipFlops.length))} topic(s) flip-flopped over time\n\n`);
|
|
705
|
+
for (const f of flipFlops) {
|
|
706
|
+
process.stdout.write(` ${divider("topic: " + f.topic)}\n`);
|
|
707
|
+
process.stdout.write(` ${kleur.bold(String(f.flips))} reversal${f.flips === 1 ? "" : "s"} ${kleur.gray(`over ${f.spanMonths} months ยท ${f.chain.length} decisions`)}\n\n`);
|
|
708
|
+
for (const d of f.chain) {
|
|
709
|
+
process.stdout.write(` ${kleur.gray("โ")} ${kleur.bold(d.date)} ${kleur.cyan(d.shortHash)} ${d.summary} ${kleur.gray(`(${d.author})`)}\n`);
|
|
710
|
+
}
|
|
711
|
+
process.stdout.write(`\n ${kleur.yellow("โ ")}${kleur.yellow(f.question)}\n\n`);
|
|
712
|
+
}
|
|
713
|
+
return 0;
|
|
714
|
+
}
|
|
715
|
+
export async function commitCoachCommand(opts) {
|
|
716
|
+
// Read the diff: from file, stdin, or `git diff --staged`.
|
|
717
|
+
let diffText = "";
|
|
718
|
+
if (opts.diffFile) {
|
|
719
|
+
try {
|
|
720
|
+
diffText = readFileSync(opts.diffFile, "utf8");
|
|
721
|
+
}
|
|
722
|
+
catch (err) {
|
|
723
|
+
ui.error(`Cannot read ${opts.diffFile}: ${err.message}`);
|
|
724
|
+
return 1;
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
else if (opts.fromStdin) {
|
|
728
|
+
diffText = await readStdin();
|
|
729
|
+
}
|
|
730
|
+
else {
|
|
731
|
+
// Default: shell out to git diff --staged
|
|
732
|
+
try {
|
|
733
|
+
const { spawnSync } = await import("node:child_process");
|
|
734
|
+
const r = spawnSync("git", ["diff", "--staged"], { cwd: opts.cwd, encoding: "utf8" });
|
|
735
|
+
if (r.status === 0)
|
|
736
|
+
diffText = r.stdout;
|
|
737
|
+
}
|
|
738
|
+
catch {
|
|
739
|
+
// git not available โ let user know
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
if (!diffText.trim()) {
|
|
743
|
+
ui.error("No staged diff. Pass --from <file>, --stdin, or `git add` something first.");
|
|
744
|
+
return 1;
|
|
745
|
+
}
|
|
746
|
+
const result = await withStore(opts.cwd, (s) => insights.coach(s, diffText));
|
|
747
|
+
if (typeof result === "number")
|
|
748
|
+
return result;
|
|
749
|
+
const advice = result;
|
|
750
|
+
if (opts.json) {
|
|
751
|
+
process.stdout.write(JSON.stringify(advice, null, 2) + "\n");
|
|
752
|
+
return 0;
|
|
753
|
+
}
|
|
754
|
+
ui.banner();
|
|
755
|
+
process.stdout.write(`\n ${kleur.bold().cyan("๐ชถ Commit coach โ pre-commit review")}\n`);
|
|
756
|
+
process.stdout.write(` ${divider()}\n\n`);
|
|
757
|
+
// Diff section
|
|
758
|
+
process.stdout.write(` ${kleur.bold().magenta("โฆ Diff")}\n\n`);
|
|
759
|
+
process.stdout.write(` ${kleur.bold(String(advice.diff.files.length))} file(s) ยท ${kleur.green("+" + advice.diff.added)} ${kleur.red("-" + advice.diff.removed)} ยท shape: ${kleur.cyan(advice.diff.shape)}\n`);
|
|
760
|
+
for (const m of advice.diff.modules.slice(0, 5))
|
|
761
|
+
process.stdout.write(` ${kleur.gray("ยท")} ${m}\n`);
|
|
762
|
+
process.stdout.write("\n");
|
|
763
|
+
// Suggested message
|
|
764
|
+
process.stdout.write(` ${kleur.bold().magenta("โฆ Suggested commit message")}\n\n`);
|
|
765
|
+
process.stdout.write(` ${kleur.bold(advice.suggestedSubject)}\n\n`);
|
|
766
|
+
// Reviewers
|
|
767
|
+
if (advice.reviewers.length > 0) {
|
|
768
|
+
process.stdout.write(` ${kleur.bold().magenta("โ Reviewers")} ${kleur.gray("(top experts on touched files)")}\n\n`);
|
|
769
|
+
for (const r of advice.reviewers) {
|
|
770
|
+
process.stdout.write(` ${kleur.cyan("โ")} ${kleur.bold(r.name)} ${kleur.gray(`<${r.email}>`)} ${kleur.cyan(r.ownership + "%")} ${kleur.gray(`(${r.ownedFiles.length} owned files)`)}\n`);
|
|
771
|
+
}
|
|
772
|
+
process.stdout.write("\n");
|
|
773
|
+
}
|
|
774
|
+
// Scope
|
|
775
|
+
process.stdout.write(` ${kleur.bold().magenta("โ Scope")}\n\n`);
|
|
776
|
+
process.stdout.write(` ${advice.scopeOK ? kleur.green("โ") : kleur.yellow("โ ")} ${advice.scopeMessage}\n\n`);
|
|
777
|
+
// Warnings
|
|
778
|
+
if (advice.warnings.length > 0) {
|
|
779
|
+
process.stdout.write(` ${kleur.bold().magenta("โ Past warnings")}\n\n`);
|
|
780
|
+
for (const w of advice.warnings) {
|
|
781
|
+
process.stdout.write(` ${kleur.yellow("โ ")}${kleur.yellow(w.pattern)}\n`);
|
|
782
|
+
process.stdout.write(` ${kleur.gray(`${w.pastDate} ยท ${w.pastCommitHash} ยท ${w.outcome}`)}\n\n`);
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
else {
|
|
786
|
+
process.stdout.write(` ${kleur.green("โ")} ${kleur.gray("No past-regret warnings for these files.")}\n\n`);
|
|
787
|
+
}
|
|
788
|
+
return 0;
|
|
789
|
+
}
|
|
790
|
+
export async function crystalBallCommand(opts) {
|
|
791
|
+
// Same diff-acquisition pattern as commit-coach.
|
|
792
|
+
let diffText = "";
|
|
793
|
+
if (opts.diffFile) {
|
|
794
|
+
try {
|
|
795
|
+
diffText = readFileSync(opts.diffFile, "utf8");
|
|
796
|
+
}
|
|
797
|
+
catch (err) {
|
|
798
|
+
ui.error(`Cannot read ${opts.diffFile}: ${err.message}`);
|
|
799
|
+
return 1;
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
else if (opts.fromStdin) {
|
|
803
|
+
diffText = await readStdin();
|
|
804
|
+
}
|
|
805
|
+
else {
|
|
806
|
+
try {
|
|
807
|
+
const { spawnSync } = await import("node:child_process");
|
|
808
|
+
const r = spawnSync("git", ["diff", "--staged"], { cwd: opts.cwd, encoding: "utf8" });
|
|
809
|
+
if (r.status === 0)
|
|
810
|
+
diffText = r.stdout;
|
|
811
|
+
}
|
|
812
|
+
catch {
|
|
813
|
+
/* ignore */
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
if (!diffText.trim()) {
|
|
817
|
+
ui.error("No staged diff. Pass --from <file>, --stdin, or `git add` something first.");
|
|
818
|
+
return 1;
|
|
819
|
+
}
|
|
820
|
+
const result = await withStore(opts.cwd, (s) => insights.predict(s, diffText, opts.windowDays ?? 14));
|
|
821
|
+
if (typeof result === "number")
|
|
822
|
+
return result;
|
|
823
|
+
const p = result;
|
|
824
|
+
if (opts.json) {
|
|
825
|
+
process.stdout.write(JSON.stringify(p, null, 2) + "\n");
|
|
826
|
+
return 0;
|
|
827
|
+
}
|
|
828
|
+
ui.banner();
|
|
829
|
+
process.stdout.write(`\n ${kleur.bold().cyan("๐ฎ Crystal ball โ CI / follow-up failure prediction")}\n`);
|
|
830
|
+
process.stdout.write(` ${divider()}\n\n`);
|
|
831
|
+
// Verdict
|
|
832
|
+
process.stdout.write(` ${kleur.bold().magenta("โฆ Verdict")}\n\n`);
|
|
833
|
+
process.stdout.write(` ${renderCrystalBallVerdict(p.verdict)} ${kleur.cyan((p.pClean * 100).toFixed(0) + "%")} clean rate ${kleur.gray(`(${p.cleanN}/${p.similarN} similar past changes)`)}\n\n`);
|
|
834
|
+
// Fingerprint
|
|
835
|
+
process.stdout.write(` ${kleur.bold().magenta("โ Diff fingerprint")}\n\n`);
|
|
836
|
+
process.stdout.write(` modules: ${p.fingerprint.modules.join(", ") || kleur.gray("(none)")}\n`);
|
|
837
|
+
process.stdout.write(` extensions: ${p.fingerprint.extensions.join(", ") || kleur.gray("(none)")}\n`);
|
|
838
|
+
process.stdout.write(` shape: ${kleur.cyan(p.fingerprint.shape)} ยท ${p.fingerprint.size} ยท tests ${p.fingerprint.hasTests ? "yes" : "no"}\n\n`);
|
|
839
|
+
// Most-similar
|
|
840
|
+
if (p.mostSimilar) {
|
|
841
|
+
const outcome = p.mostSimilar.outcome === "clean" ? kleur.green("clean") : kleur.red("trouble");
|
|
842
|
+
process.stdout.write(` ${kleur.bold().magenta("โ Most similar past change")}\n\n`);
|
|
843
|
+
process.stdout.write(` ${kleur.bold(p.mostSimilar.hash)} ${kleur.gray(p.mostSimilar.date)} ${p.mostSimilar.subject}\n`);
|
|
844
|
+
process.stdout.write(` outcome: ${outcome}\n\n`);
|
|
845
|
+
}
|
|
846
|
+
process.stdout.write(` ${kleur.bold().magenta("โ Recommendation")}\n\n`);
|
|
847
|
+
process.stdout.write(` ${p.recommendation}\n\n`);
|
|
848
|
+
return 0;
|
|
849
|
+
}
|
|
850
|
+
function renderCrystalBallVerdict(v) {
|
|
851
|
+
switch (v) {
|
|
852
|
+
case "clear":
|
|
853
|
+
return kleur.green().bold("โ CLEAR ");
|
|
854
|
+
case "moderate":
|
|
855
|
+
return kleur.yellow().bold("โ MODERATE ");
|
|
856
|
+
case "risky":
|
|
857
|
+
return kleur.red().bold("โ RISKY ");
|
|
858
|
+
default:
|
|
859
|
+
return kleur.gray().bold("โ UNKNOWN ");
|
|
860
|
+
}
|
|
861
|
+
}
|
|
544
862
|
async function narrateAct(enricher, topic, commits) {
|
|
545
863
|
if (commits.length === 0)
|
|
546
864
|
return "";
|
|
@@ -576,4 +894,183 @@ function wrap(text, width, indent) {
|
|
|
576
894
|
out.push(line);
|
|
577
895
|
return out;
|
|
578
896
|
}
|
|
897
|
+
export async function timeMachineCommand(opts) {
|
|
898
|
+
const result = await withStore(opts.cwd, (s) => {
|
|
899
|
+
const allCommits = util.loadAllCommits(s);
|
|
900
|
+
// Filter to commits that touched the target file
|
|
901
|
+
const target = opts.filePath;
|
|
902
|
+
const filtered = allCommits.filter((c) => c.files.includes(target));
|
|
903
|
+
const fileChanges = util.loadFileChangesForPath(s, target);
|
|
904
|
+
const changeMap = new Map();
|
|
905
|
+
for (const ch of fileChanges)
|
|
906
|
+
changeMap.set(ch.commitHash, ch);
|
|
907
|
+
return insights.buildTimeMachine(target, filtered, changeMap, {
|
|
908
|
+
plateauDays: opts.plateauDays ?? 60,
|
|
909
|
+
});
|
|
910
|
+
});
|
|
911
|
+
if (typeof result === "number")
|
|
912
|
+
return result;
|
|
913
|
+
if (opts.json) {
|
|
914
|
+
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
915
|
+
return 0;
|
|
916
|
+
}
|
|
917
|
+
ui.banner();
|
|
918
|
+
process.stdout.write(`\n ${kleur.bold().cyan("๐ฐ Time Machine โ life of a file")}\n`);
|
|
919
|
+
process.stdout.write(` ${divider()}\n\n`);
|
|
920
|
+
process.stdout.write(` ${kleur.bold(opts.filePath)}\n`);
|
|
921
|
+
if (result.totalCommits === 0) {
|
|
922
|
+
process.stdout.write(`\n ${kleur.yellow("โ")} No commits found for this path. Check the file exists in history.\n\n`);
|
|
923
|
+
return 0;
|
|
924
|
+
}
|
|
925
|
+
process.stdout.write(` ${kleur.gray(String(result.totalCommits) + " commits across " + result.totalSpanDays + " days")}\n\n`);
|
|
926
|
+
// Health line
|
|
927
|
+
const h = result.health;
|
|
928
|
+
const pct = (n) => Math.round(n * 100);
|
|
929
|
+
process.stdout.write(` ${kleur.bold().magenta("โฆ Health")}\n\n`);
|
|
930
|
+
process.stdout.write(` rewrite ${kleur.bold(pct(h.rewriteRatio) + "%")} ยท firefight ${kleur.bold(pct(h.firefightRatio) + "%")} ยท polish/plateau ${kleur.bold(pct(h.polishRatio) + "%")}\n\n`);
|
|
931
|
+
// Epochs timeline
|
|
932
|
+
process.stdout.write(` ${kleur.bold().magenta("โ Epochs")}\n\n`);
|
|
933
|
+
for (const e of result.epochs) {
|
|
934
|
+
const badge = renderEpochKind(e.kind);
|
|
935
|
+
const range = e.fromDate === e.toDate ? e.fromDate : `${e.fromDate} โ ${e.toDate}`;
|
|
936
|
+
process.stdout.write(` ${badge} ${kleur.gray(range)} ${kleur.gray(`(${e.spanDays}d)`)}\n`);
|
|
937
|
+
process.stdout.write(` ${e.label}\n`);
|
|
938
|
+
if (e.commits.length > 0) {
|
|
939
|
+
const churn = e.insertions + e.deletions;
|
|
940
|
+
process.stdout.write(` ${kleur.gray(`${e.commits.length} commits ยท +${e.insertions}/-${e.deletions} (${churn} lines)`)}\n`);
|
|
941
|
+
}
|
|
942
|
+
process.stdout.write("\n");
|
|
943
|
+
}
|
|
944
|
+
return 0;
|
|
945
|
+
}
|
|
946
|
+
function renderEpochKind(kind) {
|
|
947
|
+
switch (kind) {
|
|
948
|
+
case "birth":
|
|
949
|
+
return kleur.green().bold("BIRTH ");
|
|
950
|
+
case "rewrite":
|
|
951
|
+
return kleur.magenta().bold("REWRITE ");
|
|
952
|
+
case "evolution":
|
|
953
|
+
return kleur.cyan().bold("EVOLVE ");
|
|
954
|
+
case "firefight":
|
|
955
|
+
return kleur.red().bold("FIREFIGHT");
|
|
956
|
+
case "polish":
|
|
957
|
+
return kleur.blue().bold("POLISH ");
|
|
958
|
+
case "plateau":
|
|
959
|
+
return kleur.gray().bold("PLATEAU ");
|
|
960
|
+
case "twilight":
|
|
961
|
+
return kleur.gray().bold("TWILIGHT ");
|
|
962
|
+
default:
|
|
963
|
+
return kleur.gray().bold(kind.padEnd(9));
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
export async function premortemCommand(opts) {
|
|
967
|
+
const result = await withStore(opts.cwd, (s) => {
|
|
968
|
+
const commits = util.loadAllCommits(s);
|
|
969
|
+
return insights.buildPremortem(opts.intent, commits, {
|
|
970
|
+
similarityFloor: opts.similarityFloor ?? 0.25,
|
|
971
|
+
windowDays: opts.windowDays ?? 14,
|
|
972
|
+
});
|
|
973
|
+
});
|
|
974
|
+
if (typeof result === "number")
|
|
975
|
+
return result;
|
|
976
|
+
if (opts.json) {
|
|
977
|
+
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
978
|
+
return 0;
|
|
979
|
+
}
|
|
980
|
+
ui.banner();
|
|
981
|
+
process.stdout.write(`\n ${kleur.bold().cyan("๐ฎ Pre-mortem โ what your repo's history says about this")}\n`);
|
|
982
|
+
process.stdout.write(` ${divider()}\n\n`);
|
|
983
|
+
process.stdout.write(` ${kleur.gray("intent:")} ${kleur.bold(opts.intent)}\n\n`);
|
|
984
|
+
const verdictColor = (() => {
|
|
985
|
+
switch (result.verdict) {
|
|
986
|
+
case "very_high": return kleur.red().bold;
|
|
987
|
+
case "high": return kleur.yellow().bold;
|
|
988
|
+
case "medium": return kleur.cyan().bold;
|
|
989
|
+
case "low": return kleur.green().bold;
|
|
990
|
+
}
|
|
991
|
+
})();
|
|
992
|
+
process.stdout.write(` ${kleur.bold().magenta("โฆ Verdict")}\n\n`);
|
|
993
|
+
process.stdout.write(` risk: ${verdictColor(result.verdict.toUpperCase().replace("_", " "))} ${kleur.gray(`(P(regret) = ${(result.regretProbability * 100).toFixed(0)}%)`)}\n\n`);
|
|
994
|
+
for (const line of wrap(result.summary, 76, " ")) {
|
|
995
|
+
process.stdout.write(line + "\n");
|
|
996
|
+
}
|
|
997
|
+
if (result.topRisks.length > 0) {
|
|
998
|
+
process.stdout.write(`\n ${kleur.bold().magenta("โ Top risks")}\n\n`);
|
|
999
|
+
for (const r of result.topRisks) {
|
|
1000
|
+
process.stdout.write(` ${kleur.bold("โข")} ${r.label}\n`);
|
|
1001
|
+
for (const ev of r.evidence.slice(0, 3)) {
|
|
1002
|
+
const hash = ev.shortHash || ev.hash.slice(0, 7);
|
|
1003
|
+
process.stdout.write(` ${kleur.gray(hash + " " + ev.subject)}\n`);
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
if (result.pastAttempts.length > 0) {
|
|
1008
|
+
const sample = result.pastAttempts.slice(0, 5);
|
|
1009
|
+
process.stdout.write(`\n ${kleur.bold().magenta("โ Similar past attempts")} ${kleur.gray(`(${result.pastAttempts.length} found)`)}\n\n`);
|
|
1010
|
+
for (const a of sample) {
|
|
1011
|
+
const hash = a.attempt.shortHash || a.attempt.hash.slice(0, 7);
|
|
1012
|
+
const status = a.riskKind === "none"
|
|
1013
|
+
? kleur.green("ok")
|
|
1014
|
+
: kleur.red(a.riskKind);
|
|
1015
|
+
process.stdout.write(` ${kleur.gray(a.attempt.authorDate.slice(0, 10))} ${kleur.cyan(hash)} [${status}] ${a.attempt.subject}\n`);
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
process.stdout.write("\n");
|
|
1019
|
+
return 0;
|
|
1020
|
+
}
|
|
1021
|
+
export async function ghostCommand(opts) {
|
|
1022
|
+
const result = await withStore(opts.cwd, (s) => {
|
|
1023
|
+
const commits = util.loadAllCommits(s);
|
|
1024
|
+
const changes = util.loadAllFileChanges(s);
|
|
1025
|
+
return insights.buildGhostReport(commits, changes, {
|
|
1026
|
+
staleDays: opts.staleDays ?? 180,
|
|
1027
|
+
minGhostliness: 0.4,
|
|
1028
|
+
});
|
|
1029
|
+
});
|
|
1030
|
+
if (typeof result === "number")
|
|
1031
|
+
return result;
|
|
1032
|
+
if (opts.json) {
|
|
1033
|
+
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
1034
|
+
return 0;
|
|
1035
|
+
}
|
|
1036
|
+
ui.banner();
|
|
1037
|
+
process.stdout.write(`\n ${kleur.bold().cyan("๐ป Ghost Code โ what's haunting your repo")}\n`);
|
|
1038
|
+
process.stdout.write(` ${divider()}\n\n`);
|
|
1039
|
+
process.stdout.write(` ${kleur.bold(String(result.totalFiles))} files analyzed ยท ${kleur.bold(result.ghostFiles.length + " ghosts")} surfaced ยท avg ghostliness ${kleur.bold((result.averageGhostliness * 100).toFixed(0) + "%")}\n\n`);
|
|
1040
|
+
if (result.ghostFiles.length === 0) {
|
|
1041
|
+
process.stdout.write(` ${kleur.green("โ")} Repo looks active โ no significant ghost code detected.\n\n`);
|
|
1042
|
+
return 0;
|
|
1043
|
+
}
|
|
1044
|
+
process.stdout.write(` ${kleur.bold().magenta("โ Ghost files")} ${kleur.gray(`(top ${Math.min(opts.topN ?? 10, result.ghostFiles.length)})`)}\n\n`);
|
|
1045
|
+
for (const g of result.ghostFiles.slice(0, opts.topN ?? 10)) {
|
|
1046
|
+
const score = (g.ghostliness * 100).toFixed(0) + "%";
|
|
1047
|
+
const meter = renderMeter(g.ghostliness);
|
|
1048
|
+
process.stdout.write(` ${kleur.bold(g.path)}\n`);
|
|
1049
|
+
process.stdout.write(` ${meter} ${kleur.bold(score)} ${kleur.gray(g.reason)}\n`);
|
|
1050
|
+
process.stdout.write(` ${kleur.gray(`${g.totalCommits} commits ยท ${g.daysSinceLastTouch}d quiet ยท last: "${truncateOneLine(g.lastCommitSubject, 60)}"`)}\n\n`);
|
|
1051
|
+
}
|
|
1052
|
+
if (result.staleTodos.length > 0) {
|
|
1053
|
+
process.stdout.write(` ${kleur.bold().magenta("โ Stale TODOs")} ${kleur.gray(`(${result.staleTodos.length} ignored markers)`)}\n\n`);
|
|
1054
|
+
for (const t of result.staleTodos.slice(0, 5)) {
|
|
1055
|
+
process.stdout.write(` ${kleur.bold(t.filePath)}\n`);
|
|
1056
|
+
process.stdout.write(` ${kleur.gray(`${t.ageDays}d old ยท ignored ${t.ignoredCount}ร since`)}\n`);
|
|
1057
|
+
process.stdout.write(` ${kleur.gray("โณ " + truncateOneLine(t.hint, 70))}\n\n`);
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
return 0;
|
|
1061
|
+
}
|
|
1062
|
+
function renderMeter(value) {
|
|
1063
|
+
const blocks = Math.round(value * 10);
|
|
1064
|
+
const filled = "โ".repeat(blocks);
|
|
1065
|
+
const empty = "โ".repeat(10 - blocks);
|
|
1066
|
+
if (value >= 0.7)
|
|
1067
|
+
return kleur.red(filled) + kleur.gray(empty);
|
|
1068
|
+
if (value >= 0.4)
|
|
1069
|
+
return kleur.yellow(filled) + kleur.gray(empty);
|
|
1070
|
+
return kleur.green(filled) + kleur.gray(empty);
|
|
1071
|
+
}
|
|
1072
|
+
function truncateOneLine(s, n) {
|
|
1073
|
+
const oneLine = s.replace(/\s+/g, " ").trim();
|
|
1074
|
+
return oneLine.length <= n ? oneLine : oneLine.slice(0, n - 1).trimEnd() + "โฆ";
|
|
1075
|
+
}
|
|
579
1076
|
//# sourceMappingURL=insights-cli.js.map
|