@mmnto/cli 1.5.4 → 1.5.5

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/commands/doctor.d.ts +9 -1
  2. package/dist/commands/doctor.d.ts.map +1 -1
  3. package/dist/commands/doctor.js +141 -1
  4. package/dist/commands/doctor.js.map +1 -1
  5. package/dist/commands/doctor.test.js +200 -2
  6. package/dist/commands/doctor.test.js.map +1 -1
  7. package/dist/commands/ledger-analyzer.d.ts +24 -0
  8. package/dist/commands/ledger-analyzer.d.ts.map +1 -0
  9. package/dist/commands/ledger-analyzer.js +64 -0
  10. package/dist/commands/ledger-analyzer.js.map +1 -0
  11. package/dist/commands/ledger-analyzer.test.d.ts +2 -0
  12. package/dist/commands/ledger-analyzer.test.d.ts.map +1 -0
  13. package/dist/commands/ledger-analyzer.test.js +163 -0
  14. package/dist/commands/ledger-analyzer.test.js.map +1 -0
  15. package/dist/commands/rule-mutator.d.ts +17 -0
  16. package/dist/commands/rule-mutator.d.ts.map +1 -0
  17. package/dist/commands/rule-mutator.js +33 -0
  18. package/dist/commands/rule-mutator.js.map +1 -0
  19. package/dist/commands/rule-mutator.test.d.ts +2 -0
  20. package/dist/commands/rule-mutator.test.d.ts.map +1 -0
  21. package/dist/commands/rule-mutator.test.js +104 -0
  22. package/dist/commands/rule-mutator.test.js.map +1 -0
  23. package/dist/commands/triage-pr.d.ts +21 -0
  24. package/dist/commands/triage-pr.d.ts.map +1 -0
  25. package/dist/commands/triage-pr.js +231 -0
  26. package/dist/commands/triage-pr.js.map +1 -0
  27. package/dist/commands/triage-pr.test.d.ts +2 -0
  28. package/dist/commands/triage-pr.test.d.ts.map +1 -0
  29. package/dist/commands/triage-pr.test.js +163 -0
  30. package/dist/commands/triage-pr.test.js.map +1 -0
  31. package/dist/index.js +17 -3
  32. package/dist/index.js.map +1 -1
  33. package/dist/parsers/triage-dedup.d.ts +8 -0
  34. package/dist/parsers/triage-dedup.d.ts.map +1 -0
  35. package/dist/parsers/triage-dedup.js +134 -0
  36. package/dist/parsers/triage-dedup.js.map +1 -0
  37. package/dist/parsers/triage-dedup.test.d.ts +2 -0
  38. package/dist/parsers/triage-dedup.test.d.ts.map +1 -0
  39. package/dist/parsers/triage-dedup.test.js +209 -0
  40. package/dist/parsers/triage-dedup.test.js.map +1 -0
  41. package/dist/parsers/triage-severity-mapper.d.ts +9 -0
  42. package/dist/parsers/triage-severity-mapper.d.ts.map +1 -0
  43. package/dist/parsers/triage-severity-mapper.js +86 -0
  44. package/dist/parsers/triage-severity-mapper.js.map +1 -0
  45. package/dist/parsers/triage-severity-mapper.test.d.ts +2 -0
  46. package/dist/parsers/triage-severity-mapper.test.d.ts.map +1 -0
  47. package/dist/parsers/triage-severity-mapper.test.js +62 -0
  48. package/dist/parsers/triage-severity-mapper.test.js.map +1 -0
  49. package/dist/parsers/triage-types.d.ts +12 -0
  50. package/dist/parsers/triage-types.d.ts.map +1 -0
  51. package/dist/parsers/triage-types.js +2 -0
  52. package/dist/parsers/triage-types.js.map +1 -0
  53. package/package.json +2 -2
@@ -0,0 +1,64 @@
1
+ import * as fs from 'node:fs';
2
+ import * as readline from 'node:readline';
3
+ import { LedgerEventSchema } from '@mmnto/totem';
4
+ // ─── Public API ────────────────────────────────────
5
+ /**
6
+ * Analyze the Trap Ledger to compute bypass rates per rule.
7
+ *
8
+ * Reads bypass events from the ledger (source of truth for exceptions)
9
+ * and trigger counts from rule-metrics (only source for trigger data).
10
+ */
11
+ export async function analyzeLedger(totemDir, onWarn) {
12
+ // 1. Read bypass counts from ledger (streaming)
13
+ const bypassCounts = await readLedgerBypassCounts(totemDir, onWarn);
14
+ // 2. Read trigger counts from rule-metrics
15
+ const { loadRuleMetrics } = await import('@mmnto/totem');
16
+ const metrics = loadRuleMetrics(totemDir, onWarn);
17
+ // 3. Merge into stats
18
+ const stats = new Map();
19
+ const allRuleIds = new Set([...bypassCounts.keys(), ...Object.keys(metrics.rules)]);
20
+ for (const ruleId of allRuleIds) {
21
+ const bypasses = bypassCounts.get(ruleId) ?? 0;
22
+ const triggers = metrics.rules[ruleId]?.triggerCount ?? 0;
23
+ const total = triggers + bypasses;
24
+ stats.set(ruleId, {
25
+ ruleId,
26
+ triggerCount: triggers,
27
+ bypassCount: bypasses,
28
+ bypassRate: total === 0 ? 0 : bypasses / total,
29
+ totalEvents: total,
30
+ });
31
+ }
32
+ return stats;
33
+ }
34
+ // ─── Internal ──────────────────────────────────────
35
+ /**
36
+ * Stream the ledger NDJSON file and count bypass events per ruleId.
37
+ * Uses readline for memory-efficient parsing of large ledgers.
38
+ */
39
+ export async function readLedgerBypassCounts(totemDir, onWarn) {
40
+ const path = await import('node:path');
41
+ const ledgerPath = path.join(totemDir, 'ledger', 'events.ndjson');
42
+ const counts = new Map();
43
+ if (!fs.existsSync(ledgerPath))
44
+ return counts;
45
+ const stream = fs.createReadStream(ledgerPath, 'utf-8');
46
+ const rl = readline.createInterface({ input: stream });
47
+ for await (const line of rl) {
48
+ if (!line.trim())
49
+ continue;
50
+ try {
51
+ const parsed = JSON.parse(line);
52
+ const result = LedgerEventSchema.safeParse(parsed);
53
+ if (!result.success)
54
+ continue;
55
+ const event = result.data;
56
+ counts.set(event.ruleId, (counts.get(event.ruleId) ?? 0) + 1);
57
+ }
58
+ catch {
59
+ onWarn?.('Skipping malformed ledger line');
60
+ }
61
+ }
62
+ return counts;
63
+ }
64
+ //# sourceMappingURL=ledger-analyzer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ledger-analyzer.js","sourceRoot":"","sources":["../../src/commands/ledger-analyzer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAC;AAE1C,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAgBjD,sDAAsD;AAEtD;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,QAAgB,EAChB,MAA8B;IAE9B,gDAAgD;IAChD,MAAM,YAAY,GAAG,MAAM,sBAAsB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAEpE,2CAA2C;IAC3C,MAAM,EAAE,eAAe,EAAE,GAAG,MAAM,MAAM,CAAC,cAAc,CAAC,CAAC;IACzD,MAAM,OAAO,GAAG,eAAe,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAElD,sBAAsB;IACtB,MAAM,KAAK,GAAG,IAAI,GAAG,EAA2B,CAAC;IAEjD,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,YAAY,CAAC,IAAI,EAAE,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAEpF,KAAK,MAAM,MAAM,IAAI,UAAU,EAAE,CAAC;QAChC,MAAM,QAAQ,GAAG,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAC/C,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,YAAY,IAAI,CAAC,CAAC;QAC1D,MAAM,KAAK,GAAG,QAAQ,GAAG,QAAQ,CAAC;QAElC,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE;YAChB,MAAM;YACN,YAAY,EAAE,QAAQ;YACtB,WAAW,EAAE,QAAQ;YACrB,UAAU,EAAE,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,GAAG,KAAK;YAC9C,WAAW,EAAE,KAAK;SACnB,CAAC,CAAC;IACL,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED,sDAAsD;AAEtD;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,QAAgB,EAChB,MAA8B;IAE9B,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,CAAC;IACvC,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,QAAQ,EAAE,eAAe,CAAC,CAAC;IAClE,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;IAEzC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC;QAAE,OAAO,MAAM,CAAC;IAE9C,MAAM,MAAM,GAAG,EAAE,CAAC,gBAAgB,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;IACxD,MAAM,EAAE,GAAG,QAAQ,CAAC,eAAe,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC;IAEvD,IAAI,KAAK,EAAE,MAAM,IAAI,IAAI,EAAE,EAAE,CAAC;QAC5B,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE;YAAE,SAAS;QAC3B,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAChC,MAAM,MAAM,GAAG,iBAAiB,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;YACnD,IAAI,CAAC,MAAM,CAAC,OAAO;gBAAE,SAAS;YAC9B,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC;YAC1B,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QAChE,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,EAAE,CAAC,gCAAgC,CAAC,CAAC;QAC7C,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=ledger-analyzer.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ledger-analyzer.test.d.ts","sourceRoot":"","sources":["../../src/commands/ledger-analyzer.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,163 @@
1
+ import * as fs from 'node:fs';
2
+ import * as os from 'node:os';
3
+ import * as path from 'node:path';
4
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
5
+ import { analyzeLedger, readLedgerBypassCounts } from './ledger-analyzer.js';
6
+ // ─── Helpers ────────────────────────────────────────────
7
+ function makeTmpDir() {
8
+ return fs.mkdtempSync(path.join(os.tmpdir(), 'totem-ledger-analyzer-'));
9
+ }
10
+ function makeLedgerEvent(ruleId, type = 'suppress') {
11
+ return JSON.stringify({
12
+ timestamp: '2026-03-25T12:00:00.000Z',
13
+ type,
14
+ ruleId,
15
+ file: 'src/index.ts',
16
+ justification: type === 'override' ? 'Legacy code' : '',
17
+ source: 'lint',
18
+ });
19
+ }
20
+ function writeLedger(totemDir, lines) {
21
+ const ledgerDir = path.join(totemDir, 'ledger');
22
+ fs.mkdirSync(ledgerDir, { recursive: true });
23
+ fs.writeFileSync(path.join(ledgerDir, 'events.ndjson'), lines.join('\n') + '\n', 'utf-8');
24
+ }
25
+ function writeMetrics(totemDir, rules) {
26
+ const cacheDir = path.join(totemDir, 'cache');
27
+ fs.mkdirSync(cacheDir, { recursive: true });
28
+ const metricsData = {
29
+ version: 1,
30
+ rules: Object.fromEntries(Object.entries(rules).map(([id, counts]) => [
31
+ id,
32
+ {
33
+ triggerCount: counts.triggerCount,
34
+ suppressCount: counts.suppressCount,
35
+ lastTriggeredAt: '2026-03-25T12:00:00.000Z',
36
+ lastSuppressedAt: null,
37
+ },
38
+ ])),
39
+ };
40
+ fs.writeFileSync(path.join(cacheDir, 'rule-metrics.json'), JSON.stringify(metricsData, null, 2) + '\n', 'utf-8');
41
+ }
42
+ // ─── readLedgerBypassCounts ─────────────────────────────
43
+ describe('readLedgerBypassCounts', () => {
44
+ let tmpDir;
45
+ beforeEach(() => {
46
+ tmpDir = makeTmpDir();
47
+ });
48
+ afterEach(() => {
49
+ fs.rmSync(tmpDir, { recursive: true, force: true });
50
+ });
51
+ it('returns empty map when ledger does not exist', async () => {
52
+ const counts = await readLedgerBypassCounts(tmpDir);
53
+ expect(counts.size).toBe(0);
54
+ });
55
+ it('counts bypass events per ruleId', async () => {
56
+ writeLedger(tmpDir, [
57
+ makeLedgerEvent('rule-a'),
58
+ makeLedgerEvent('rule-b'),
59
+ makeLedgerEvent('rule-a'),
60
+ makeLedgerEvent('rule-a', 'override'),
61
+ ]);
62
+ const counts = await readLedgerBypassCounts(tmpDir);
63
+ expect(counts.get('rule-a')).toBe(3);
64
+ expect(counts.get('rule-b')).toBe(1);
65
+ });
66
+ it('skips malformed lines and warns', async () => {
67
+ const onWarn = vi.fn();
68
+ writeLedger(tmpDir, [makeLedgerEvent('rule-a'), '{{{invalid json', makeLedgerEvent('rule-b')]);
69
+ const counts = await readLedgerBypassCounts(tmpDir, onWarn);
70
+ expect(counts.get('rule-a')).toBe(1);
71
+ expect(counts.get('rule-b')).toBe(1);
72
+ expect(onWarn).toHaveBeenCalledWith('Skipping malformed ledger line');
73
+ });
74
+ it('skips events that fail schema validation', async () => {
75
+ writeLedger(tmpDir, [
76
+ makeLedgerEvent('rule-a'),
77
+ JSON.stringify({ type: 'suppress' }), // missing required fields
78
+ ]);
79
+ const counts = await readLedgerBypassCounts(tmpDir);
80
+ expect(counts.get('rule-a')).toBe(1);
81
+ expect(counts.size).toBe(1);
82
+ });
83
+ it('skips blank lines', async () => {
84
+ const ledgerDir = path.join(tmpDir, 'ledger');
85
+ fs.mkdirSync(ledgerDir, { recursive: true });
86
+ fs.writeFileSync(path.join(ledgerDir, 'events.ndjson'), makeLedgerEvent('rule-a') + '\n\n\n' + makeLedgerEvent('rule-b') + '\n', 'utf-8');
87
+ const counts = await readLedgerBypassCounts(tmpDir);
88
+ expect(counts.get('rule-a')).toBe(1);
89
+ expect(counts.get('rule-b')).toBe(1);
90
+ });
91
+ });
92
+ // ─── analyzeLedger ──────────────────────────────────────
93
+ describe('analyzeLedger', () => {
94
+ let tmpDir;
95
+ beforeEach(() => {
96
+ tmpDir = makeTmpDir();
97
+ });
98
+ afterEach(() => {
99
+ fs.rmSync(tmpDir, { recursive: true, force: true });
100
+ });
101
+ it('returns empty map when no ledger or metrics exist', async () => {
102
+ const stats = await analyzeLedger(tmpDir);
103
+ expect(stats.size).toBe(0);
104
+ });
105
+ it('counts bypass events from ledger', async () => {
106
+ writeLedger(tmpDir, [
107
+ makeLedgerEvent('rule-a'),
108
+ makeLedgerEvent('rule-a'),
109
+ makeLedgerEvent('rule-b'),
110
+ ]);
111
+ const stats = await analyzeLedger(tmpDir);
112
+ expect(stats.get('rule-a')?.bypassCount).toBe(2);
113
+ expect(stats.get('rule-b')?.bypassCount).toBe(1);
114
+ });
115
+ it('merges trigger counts from rule-metrics', async () => {
116
+ writeLedger(tmpDir, [makeLedgerEvent('rule-a')]);
117
+ writeMetrics(tmpDir, {
118
+ 'rule-a': { triggerCount: 10, suppressCount: 1 },
119
+ 'rule-c': { triggerCount: 5, suppressCount: 0 },
120
+ });
121
+ const stats = await analyzeLedger(tmpDir);
122
+ // rule-a: 10 triggers + 1 bypass from ledger
123
+ expect(stats.get('rule-a')?.triggerCount).toBe(10);
124
+ expect(stats.get('rule-a')?.bypassCount).toBe(1);
125
+ expect(stats.get('rule-a')?.totalEvents).toBe(11);
126
+ // rule-c: 5 triggers, 0 bypasses (not in ledger)
127
+ expect(stats.get('rule-c')?.triggerCount).toBe(5);
128
+ expect(stats.get('rule-c')?.bypassCount).toBe(0);
129
+ expect(stats.get('rule-c')?.totalEvents).toBe(5);
130
+ });
131
+ it('calculates correct bypass rate', async () => {
132
+ writeLedger(tmpDir, [makeLedgerEvent('rule-a'), makeLedgerEvent('rule-a')]);
133
+ writeMetrics(tmpDir, {
134
+ 'rule-a': { triggerCount: 8, suppressCount: 2 },
135
+ });
136
+ const stats = await analyzeLedger(tmpDir);
137
+ const ruleA = stats.get('rule-a');
138
+ // 2 bypasses / (8 triggers + 2 bypasses) = 0.2
139
+ expect(ruleA.bypassRate).toBeCloseTo(0.2);
140
+ expect(ruleA.totalEvents).toBe(10);
141
+ });
142
+ it('handles div-by-zero: 0 events yields 0% rate', async () => {
143
+ // Rule exists in metrics with 0 triggers and 0 suppressions
144
+ writeMetrics(tmpDir, {
145
+ 'rule-empty': { triggerCount: 0, suppressCount: 0 },
146
+ });
147
+ const stats = await analyzeLedger(tmpDir);
148
+ const ruleEmpty = stats.get('rule-empty');
149
+ expect(ruleEmpty.bypassRate).toBe(0);
150
+ expect(ruleEmpty.totalEvents).toBe(0);
151
+ });
152
+ it('handles missing ledger file gracefully', async () => {
153
+ writeMetrics(tmpDir, {
154
+ 'rule-a': { triggerCount: 3, suppressCount: 0 },
155
+ });
156
+ const stats = await analyzeLedger(tmpDir);
157
+ const ruleA = stats.get('rule-a');
158
+ expect(ruleA.triggerCount).toBe(3);
159
+ expect(ruleA.bypassCount).toBe(0);
160
+ expect(ruleA.bypassRate).toBe(0);
161
+ });
162
+ });
163
+ //# sourceMappingURL=ledger-analyzer.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ledger-analyzer.test.js","sourceRoot":"","sources":["../../src/commands/ledger-analyzer.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ export interface DowngradeResult {
2
+ /** Whether the downgrade was applied */
3
+ downgraded: boolean;
4
+ /** Previous severity before the change (undefined if rule not found) */
5
+ previousSeverity?: string;
6
+ /** Human-readable rule heading (undefined if rule not found) */
7
+ ruleHeading?: string;
8
+ }
9
+ /**
10
+ * Downgrade a rule from error to warning in compiled-rules.json.
11
+ *
12
+ * Returns whether the downgrade was applied.
13
+ * Idempotent — skips rules already at warning severity.
14
+ * Never deletes rules (ADR-027).
15
+ */
16
+ export declare function downgradeRuleToWarning(rulesPath: string, ruleId: string): DowngradeResult;
17
+ //# sourceMappingURL=rule-mutator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rule-mutator.d.ts","sourceRoot":"","sources":["../../src/commands/rule-mutator.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,eAAe;IAC9B,wCAAwC;IACxC,UAAU,EAAE,OAAO,CAAC;IACpB,wEAAwE;IACxE,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gEAAgE;IAChE,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAID;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,eAAe,CAyBzF"}
@@ -0,0 +1,33 @@
1
+ import * as fs from 'node:fs';
2
+ // ─── Public API ────────────────────────────────────
3
+ /**
4
+ * Downgrade a rule from error to warning in compiled-rules.json.
5
+ *
6
+ * Returns whether the downgrade was applied.
7
+ * Idempotent — skips rules already at warning severity.
8
+ * Never deletes rules (ADR-027).
9
+ */
10
+ export function downgradeRuleToWarning(rulesPath, ruleId) {
11
+ const content = fs.readFileSync(rulesPath, 'utf-8');
12
+ const data = JSON.parse(content);
13
+ const rule = data.rules?.find((r) => r.lessonHash === ruleId);
14
+ if (!rule)
15
+ return { downgraded: false };
16
+ const currentSeverity = rule.severity ?? 'error';
17
+ if (currentSeverity !== 'error') {
18
+ return {
19
+ downgraded: false,
20
+ previousSeverity: currentSeverity,
21
+ ruleHeading: rule.lessonHeading,
22
+ };
23
+ }
24
+ rule.severity = 'warning';
25
+ // Preserve 2-space indent formatting
26
+ fs.writeFileSync(rulesPath, JSON.stringify(data, null, 2) + '\n', 'utf-8');
27
+ return {
28
+ downgraded: true,
29
+ previousSeverity: 'error',
30
+ ruleHeading: rule.lessonHeading,
31
+ };
32
+ }
33
+ //# sourceMappingURL=rule-mutator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rule-mutator.js","sourceRoot":"","sources":["../../src/commands/rule-mutator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAa9B,sDAAsD;AAEtD;;;;;;GAMG;AACH,MAAM,UAAU,sBAAsB,CAAC,SAAiB,EAAE,MAAc;IACtE,MAAM,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IACpD,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAEjC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,CAAyB,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,KAAK,MAAM,CAAC,CAAC;IACtF,IAAI,CAAC,IAAI;QAAE,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC;IAExC,MAAM,eAAe,GAAW,IAAI,CAAC,QAAQ,IAAI,OAAO,CAAC;IACzD,IAAI,eAAe,KAAK,OAAO,EAAE,CAAC;QAChC,OAAO;YACL,UAAU,EAAE,KAAK;YACjB,gBAAgB,EAAE,eAAe;YACjC,WAAW,EAAE,IAAI,CAAC,aAAa;SAChC,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,QAAQ,GAAG,SAAS,CAAC;IAC1B,qCAAqC;IACrC,EAAE,CAAC,aAAa,CAAC,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,OAAO,CAAC,CAAC;IAE3E,OAAO;QACL,UAAU,EAAE,IAAI;QAChB,gBAAgB,EAAE,OAAO;QACzB,WAAW,EAAE,IAAI,CAAC,aAAa;KAChC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=rule-mutator.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rule-mutator.test.d.ts","sourceRoot":"","sources":["../../src/commands/rule-mutator.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,104 @@
1
+ import * as fs from 'node:fs';
2
+ import * as os from 'node:os';
3
+ import * as path from 'node:path';
4
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
5
+ import { downgradeRuleToWarning } from './rule-mutator.js';
6
+ // ─── Helpers ────────────────────────────────────────────
7
+ function makeTmpDir() {
8
+ return fs.mkdtempSync(path.join(os.tmpdir(), 'totem-rule-mutator-'));
9
+ }
10
+ function makeCompiledRules(rules) {
11
+ return {
12
+ version: 1,
13
+ rules: rules.map((r) => ({
14
+ lessonHash: r.lessonHash,
15
+ lessonHeading: r.lessonHeading,
16
+ pattern: r.pattern ?? '\\bconsole\\.log\\b',
17
+ message: `Violation: ${r.lessonHeading}`,
18
+ engine: 'regex',
19
+ compiledAt: '2026-03-25T12:00:00.000Z',
20
+ ...(r.severity !== undefined ? { severity: r.severity } : {}),
21
+ })),
22
+ };
23
+ }
24
+ function writeRules(dir, data) {
25
+ const rulesPath = path.join(dir, 'compiled-rules.json');
26
+ fs.writeFileSync(rulesPath, JSON.stringify(data, null, 2) + '\n', 'utf-8');
27
+ return rulesPath;
28
+ }
29
+ // ─── downgradeRuleToWarning ─────────────────────────────
30
+ describe('downgradeRuleToWarning', () => {
31
+ let tmpDir;
32
+ beforeEach(() => {
33
+ tmpDir = makeTmpDir();
34
+ });
35
+ afterEach(() => {
36
+ fs.rmSync(tmpDir, { recursive: true, force: true });
37
+ });
38
+ it('changes error to warning', () => {
39
+ const rulesPath = writeRules(tmpDir, makeCompiledRules([
40
+ { lessonHash: 'abc123', lessonHeading: 'No console.log', severity: 'error' },
41
+ ]));
42
+ const result = downgradeRuleToWarning(rulesPath, 'abc123');
43
+ expect(result.downgraded).toBe(true);
44
+ expect(result.previousSeverity).toBe('error');
45
+ expect(result.ruleHeading).toBe('No console.log');
46
+ // Verify the file was updated
47
+ const updated = JSON.parse(fs.readFileSync(rulesPath, 'utf-8'));
48
+ expect(updated.rules[0].severity).toBe('warning');
49
+ });
50
+ it('downgrades rules with implicit error severity (no severity field)', () => {
51
+ const rulesPath = writeRules(tmpDir, makeCompiledRules([
52
+ { lessonHash: 'abc123', lessonHeading: 'No console.log' }, // no severity = defaults to error
53
+ ]));
54
+ const result = downgradeRuleToWarning(rulesPath, 'abc123');
55
+ expect(result.downgraded).toBe(true);
56
+ expect(result.previousSeverity).toBe('error');
57
+ const updated = JSON.parse(fs.readFileSync(rulesPath, 'utf-8'));
58
+ expect(updated.rules[0].severity).toBe('warning');
59
+ });
60
+ it('skips rules already at warning (idempotent)', () => {
61
+ const rulesPath = writeRules(tmpDir, makeCompiledRules([
62
+ { lessonHash: 'abc123', lessonHeading: 'No console.log', severity: 'warning' },
63
+ ]));
64
+ const result = downgradeRuleToWarning(rulesPath, 'abc123');
65
+ expect(result.downgraded).toBe(false);
66
+ expect(result.previousSeverity).toBe('warning');
67
+ expect(result.ruleHeading).toBe('No console.log');
68
+ });
69
+ it('returns false for unknown ruleId', () => {
70
+ const rulesPath = writeRules(tmpDir, makeCompiledRules([
71
+ { lessonHash: 'abc123', lessonHeading: 'No console.log', severity: 'error' },
72
+ ]));
73
+ const result = downgradeRuleToWarning(rulesPath, 'nonexistent');
74
+ expect(result.downgraded).toBe(false);
75
+ expect(result.previousSeverity).toBeUndefined();
76
+ expect(result.ruleHeading).toBeUndefined();
77
+ });
78
+ it('preserves JSON formatting (2-space indent)', () => {
79
+ const rulesPath = writeRules(tmpDir, makeCompiledRules([
80
+ { lessonHash: 'abc123', lessonHeading: 'No console.log', severity: 'error' },
81
+ ]));
82
+ downgradeRuleToWarning(rulesPath, 'abc123');
83
+ const content = fs.readFileSync(rulesPath, 'utf-8');
84
+ // Verify 2-space indentation is preserved
85
+ expect(content).toContain(' "version"');
86
+ expect(content).toContain(' "lessonHash"');
87
+ // Verify trailing newline
88
+ expect(content.endsWith('\n')).toBe(true);
89
+ });
90
+ it('preserves other rules unchanged', () => {
91
+ const rulesPath = writeRules(tmpDir, makeCompiledRules([
92
+ { lessonHash: 'rule-1', lessonHeading: 'First rule', severity: 'error' },
93
+ { lessonHash: 'rule-2', lessonHeading: 'Second rule', severity: 'error' },
94
+ { lessonHash: 'rule-3', lessonHeading: 'Third rule', severity: 'warning' },
95
+ ]));
96
+ downgradeRuleToWarning(rulesPath, 'rule-1');
97
+ const updated = JSON.parse(fs.readFileSync(rulesPath, 'utf-8'));
98
+ expect(updated.rules[0].severity).toBe('warning'); // downgraded
99
+ expect(updated.rules[1].severity).toBe('error'); // unchanged
100
+ expect(updated.rules[2].severity).toBe('warning'); // unchanged
101
+ expect(updated.rules).toHaveLength(3); // no rules deleted (ADR-027)
102
+ });
103
+ });
104
+ //# sourceMappingURL=rule-mutator.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rule-mutator.test.js","sourceRoot":"","sources":["../../src/commands/rule-mutator.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * `totem triage-pr <pr-number>` — Categorized triage view of bot review
3
+ * comments on a pull request.
4
+ *
5
+ * Fetches inline review comments, filters to bot authors, normalizes
6
+ * into structured findings, deduplicates, categorizes by blast radius,
7
+ * and renders a compact inbox to stdout.
8
+ */
9
+ import type { CategorizedFinding } from '../parsers/triage-types.js';
10
+ /**
11
+ * Format the complete triage output. Exported for testing.
12
+ */
13
+ export declare function formatTriageOutput(prNumber: number, findings: CategorizedFinding[], totalComments: number, colorize: {
14
+ red: (s: string) => string;
15
+ yellow: (s: string) => string;
16
+ blue: (s: string) => string;
17
+ gray: (s: string) => string;
18
+ bold: (s: string) => string;
19
+ }): string;
20
+ export declare function triagePrCommand(prNumber: string): Promise<void>;
21
+ //# sourceMappingURL=triage-pr.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"triage-pr.d.ts","sourceRoot":"","sources":["../../src/commands/triage-pr.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,KAAK,EAAE,kBAAkB,EAAkB,MAAM,4BAA4B,CAAC;AAkKrF;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,kBAAkB,EAAE,EAC9B,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE;IACR,GAAG,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IAC3B,MAAM,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IAC9B,IAAI,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IAC5B,IAAI,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IAC5B,IAAI,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;CAC7B,GACA,MAAM,CAyER;AAID,wBAAsB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAqFrE"}