@mmnto/cli 1.5.3 → 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.
- package/dist/commands/add-lesson.d.ts.map +1 -1
- package/dist/commands/add-lesson.js +12 -0
- package/dist/commands/add-lesson.js.map +1 -1
- package/dist/commands/add-lesson.test.d.ts +2 -0
- package/dist/commands/add-lesson.test.d.ts.map +1 -0
- package/dist/commands/add-lesson.test.js +63 -0
- package/dist/commands/add-lesson.test.js.map +1 -0
- package/dist/commands/add-secret.d.ts +5 -0
- package/dist/commands/add-secret.d.ts.map +1 -0
- package/dist/commands/add-secret.js +85 -0
- package/dist/commands/add-secret.js.map +1 -0
- package/dist/commands/add-secret.test.d.ts +2 -0
- package/dist/commands/add-secret.test.d.ts.map +1 -0
- package/dist/commands/add-secret.test.js +97 -0
- package/dist/commands/add-secret.test.js.map +1 -0
- package/dist/commands/doctor.d.ts +10 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +177 -2
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/doctor.test.js +284 -2
- package/dist/commands/doctor.test.js.map +1 -1
- package/dist/commands/extract.d.ts.map +1 -1
- package/dist/commands/extract.js +4 -1
- package/dist/commands/extract.js.map +1 -1
- package/dist/commands/extract.test.js +53 -0
- package/dist/commands/extract.test.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +16 -0
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/ledger-analyzer.d.ts +24 -0
- package/dist/commands/ledger-analyzer.d.ts.map +1 -0
- package/dist/commands/ledger-analyzer.js +64 -0
- package/dist/commands/ledger-analyzer.js.map +1 -0
- package/dist/commands/ledger-analyzer.test.d.ts +2 -0
- package/dist/commands/ledger-analyzer.test.d.ts.map +1 -0
- package/dist/commands/ledger-analyzer.test.js +163 -0
- package/dist/commands/ledger-analyzer.test.js.map +1 -0
- package/dist/commands/list-secrets.d.ts +15 -0
- package/dist/commands/list-secrets.d.ts.map +1 -0
- package/dist/commands/list-secrets.js +104 -0
- package/dist/commands/list-secrets.js.map +1 -0
- package/dist/commands/list-secrets.test.d.ts +2 -0
- package/dist/commands/list-secrets.test.d.ts.map +1 -0
- package/dist/commands/list-secrets.test.js +85 -0
- package/dist/commands/list-secrets.test.js.map +1 -0
- package/dist/commands/remove-secret.d.ts +2 -0
- package/dist/commands/remove-secret.d.ts.map +1 -0
- package/dist/commands/remove-secret.js +53 -0
- package/dist/commands/remove-secret.js.map +1 -0
- package/dist/commands/remove-secret.test.d.ts +2 -0
- package/dist/commands/remove-secret.test.d.ts.map +1 -0
- package/dist/commands/remove-secret.test.js +85 -0
- package/dist/commands/remove-secret.test.js.map +1 -0
- package/dist/commands/review-learn-templates.d.ts +7 -0
- package/dist/commands/review-learn-templates.d.ts.map +1 -0
- package/dist/commands/review-learn-templates.js +36 -0
- package/dist/commands/review-learn-templates.js.map +1 -0
- package/dist/commands/review-learn-templates.test.d.ts +2 -0
- package/dist/commands/review-learn-templates.test.d.ts.map +1 -0
- package/dist/commands/review-learn-templates.test.js +29 -0
- package/dist/commands/review-learn-templates.test.js.map +1 -0
- package/dist/commands/review-learn.d.ts +13 -0
- package/dist/commands/review-learn.d.ts.map +1 -0
- package/dist/commands/review-learn.js +260 -0
- package/dist/commands/review-learn.js.map +1 -0
- package/dist/commands/review-learn.test.d.ts +2 -0
- package/dist/commands/review-learn.test.d.ts.map +1 -0
- package/dist/commands/review-learn.test.js +218 -0
- package/dist/commands/review-learn.test.js.map +1 -0
- package/dist/commands/rule-mutator.d.ts +17 -0
- package/dist/commands/rule-mutator.d.ts.map +1 -0
- package/dist/commands/rule-mutator.js +33 -0
- package/dist/commands/rule-mutator.js.map +1 -0
- package/dist/commands/rule-mutator.test.d.ts +2 -0
- package/dist/commands/rule-mutator.test.d.ts.map +1 -0
- package/dist/commands/rule-mutator.test.js +104 -0
- package/dist/commands/rule-mutator.test.js.map +1 -0
- package/dist/commands/run-compiled-rules.d.ts.map +1 -1
- package/dist/commands/run-compiled-rules.js +49 -5
- package/dist/commands/run-compiled-rules.js.map +1 -1
- package/dist/commands/run-compiled-rules.test.js +107 -1
- package/dist/commands/run-compiled-rules.test.js.map +1 -1
- package/dist/commands/shield-hints.d.ts +16 -2
- package/dist/commands/shield-hints.d.ts.map +1 -1
- package/dist/commands/shield-hints.js +35 -20
- package/dist/commands/shield-hints.js.map +1 -1
- package/dist/commands/shield-hints.test.js +70 -1
- package/dist/commands/shield-hints.test.js.map +1 -1
- package/dist/commands/shield.d.ts.map +1 -1
- package/dist/commands/shield.js +21 -2
- package/dist/commands/shield.js.map +1 -1
- package/dist/commands/triage-pr.d.ts +21 -0
- package/dist/commands/triage-pr.d.ts.map +1 -0
- package/dist/commands/triage-pr.js +231 -0
- package/dist/commands/triage-pr.js.map +1 -0
- package/dist/commands/triage-pr.test.d.ts +2 -0
- package/dist/commands/triage-pr.test.d.ts.map +1 -0
- package/dist/commands/triage-pr.test.js +163 -0
- package/dist/commands/triage-pr.test.js.map +1 -0
- package/dist/index.js +74 -4
- package/dist/index.js.map +1 -1
- package/dist/parsers/bot-review-parser.d.ts +48 -0
- package/dist/parsers/bot-review-parser.d.ts.map +1 -0
- package/dist/parsers/bot-review-parser.js +139 -0
- package/dist/parsers/bot-review-parser.js.map +1 -0
- package/dist/parsers/bot-review-parser.test.d.ts +2 -0
- package/dist/parsers/bot-review-parser.test.d.ts.map +1 -0
- package/dist/parsers/bot-review-parser.test.js +240 -0
- package/dist/parsers/bot-review-parser.test.js.map +1 -0
- package/dist/parsers/triage-dedup.d.ts +8 -0
- package/dist/parsers/triage-dedup.d.ts.map +1 -0
- package/dist/parsers/triage-dedup.js +134 -0
- package/dist/parsers/triage-dedup.js.map +1 -0
- package/dist/parsers/triage-dedup.test.d.ts +2 -0
- package/dist/parsers/triage-dedup.test.d.ts.map +1 -0
- package/dist/parsers/triage-dedup.test.js +209 -0
- package/dist/parsers/triage-dedup.test.js.map +1 -0
- package/dist/parsers/triage-severity-mapper.d.ts +9 -0
- package/dist/parsers/triage-severity-mapper.d.ts.map +1 -0
- package/dist/parsers/triage-severity-mapper.js +86 -0
- package/dist/parsers/triage-severity-mapper.js.map +1 -0
- package/dist/parsers/triage-severity-mapper.test.d.ts +2 -0
- package/dist/parsers/triage-severity-mapper.test.d.ts.map +1 -0
- package/dist/parsers/triage-severity-mapper.test.js +62 -0
- package/dist/parsers/triage-severity-mapper.test.js.map +1 -0
- package/dist/parsers/triage-types.d.ts +12 -0
- package/dist/parsers/triage-types.d.ts.map +1 -0
- package/dist/parsers/triage-types.js +2 -0
- package/dist/parsers/triage-types.js.map +1 -0
- package/dist/utils.d.ts +3 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +1 -1
- package/dist/utils.js.map +1 -1
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"add-lesson.d.ts","sourceRoot":"","sources":["../../src/commands/add-lesson.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"add-lesson.d.ts","sourceRoot":"","sources":["../../src/commands/add-lesson.ts"],"names":[],"mappings":"AAEA,wBAAsB,gBAAgB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAsHxE"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const TAG = 'AddLesson';
|
|
1
2
|
export async function addLessonCommand(lessonArg) {
|
|
2
3
|
const { spawn } = await import('node:child_process');
|
|
3
4
|
const fs = await import('node:fs');
|
|
@@ -19,10 +20,13 @@ export async function addLessonCommand(lessonArg) {
|
|
|
19
20
|
}
|
|
20
21
|
return { cmd: IS_WIN ? 'npx.cmd' : 'npx', args: ['totem', 'sync', '--incremental'] };
|
|
21
22
|
}
|
|
23
|
+
const { loadCustomSecrets, maskSecrets } = await import('@mmnto/totem'); // totem-ignore
|
|
22
24
|
const cwd = process.cwd();
|
|
23
25
|
const configPath = resolveConfigPath(cwd);
|
|
24
26
|
loadEnv(cwd);
|
|
25
27
|
const config = await loadConfig(configPath);
|
|
28
|
+
// Load user-defined custom secrets for DLP (#921)
|
|
29
|
+
const customSecrets = loadCustomSecrets(cwd, config.totemDir, (msg) => log.warn(TAG, msg));
|
|
26
30
|
const totemDir = path.join(cwd, config.totemDir);
|
|
27
31
|
if (!fs.existsSync(totemDir)) {
|
|
28
32
|
fs.mkdirSync(totemDir, { recursive: true });
|
|
@@ -61,6 +65,14 @@ export async function addLessonCommand(lessonArg) {
|
|
|
61
65
|
log.error('Totem Error', 'Lesson text cannot be empty.');
|
|
62
66
|
return;
|
|
63
67
|
}
|
|
68
|
+
// Warn and redact if lesson text contains custom secret patterns (#921)
|
|
69
|
+
if (customSecrets.length > 0) {
|
|
70
|
+
const redacted = maskSecrets(lessonText, customSecrets);
|
|
71
|
+
if (redacted !== lessonText) {
|
|
72
|
+
log.warn(TAG, 'Custom secret pattern detected in lesson text. The text will be automatically redacted.');
|
|
73
|
+
lessonText = redacted;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
64
76
|
const safeLesson = sanitize(lessonText);
|
|
65
77
|
const safeTagString = tags.length > 0 ? tags.map((t) => sanitize(t).replace(/\n/g, ' ')).join(', ') : 'manual';
|
|
66
78
|
const heading = generateLessonHeading(safeLesson);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"add-lesson.js","sourceRoot":"","sources":["../../src/commands/add-lesson.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"add-lesson.js","sourceRoot":"","sources":["../../src/commands/add-lesson.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"add-lesson.test.d.ts","sourceRoot":"","sources":["../../src/commands/add-lesson.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { compileCustomSecrets, maskSecrets } from '@mmnto/totem';
|
|
3
|
+
// ─── Custom secrets DLP in add-lesson pipeline (#921) ──
|
|
4
|
+
describe('add-lesson custom secrets detection and redaction', () => {
|
|
5
|
+
const customSecrets = [
|
|
6
|
+
{ type: 'literal', value: 'MY_INTERNAL_TOKEN_XYZ' },
|
|
7
|
+
{ type: 'pattern', value: 'corp-secret-[0-9a-f]{8}' },
|
|
8
|
+
];
|
|
9
|
+
it('detects custom secret in lesson text', () => {
|
|
10
|
+
const lessonText = 'When authenticating, use MY_INTERNAL_TOKEN_XYZ in the header.';
|
|
11
|
+
const compiled = compileCustomSecrets(customSecrets);
|
|
12
|
+
const hasMatch = compiled.some((re) => {
|
|
13
|
+
re.lastIndex = 0;
|
|
14
|
+
return re.test(lessonText);
|
|
15
|
+
});
|
|
16
|
+
expect(hasMatch).toBe(true);
|
|
17
|
+
});
|
|
18
|
+
it('detects pattern-based custom secret in lesson text', () => {
|
|
19
|
+
const lessonText = 'The service uses corp-secret-abcd1234 for internal auth.';
|
|
20
|
+
const compiled = compileCustomSecrets(customSecrets);
|
|
21
|
+
const hasMatch = compiled.some((re) => {
|
|
22
|
+
re.lastIndex = 0;
|
|
23
|
+
return re.test(lessonText);
|
|
24
|
+
});
|
|
25
|
+
expect(hasMatch).toBe(true);
|
|
26
|
+
});
|
|
27
|
+
it('does not flag text without custom secrets', () => {
|
|
28
|
+
const lessonText = 'Always validate input before writing to disk.';
|
|
29
|
+
const compiled = compileCustomSecrets(customSecrets);
|
|
30
|
+
const hasMatch = compiled.some((re) => {
|
|
31
|
+
re.lastIndex = 0;
|
|
32
|
+
return re.test(lessonText);
|
|
33
|
+
});
|
|
34
|
+
expect(hasMatch).toBe(false);
|
|
35
|
+
});
|
|
36
|
+
it('redacts literal custom secrets before saving', () => {
|
|
37
|
+
const lessonText = 'Auth token is MY_INTERNAL_TOKEN_XYZ in the config.';
|
|
38
|
+
const redacted = maskSecrets(lessonText, customSecrets);
|
|
39
|
+
expect(redacted).not.toContain('MY_INTERNAL_TOKEN_XYZ');
|
|
40
|
+
expect(redacted).toContain('[REDACTED_CUSTOM]');
|
|
41
|
+
});
|
|
42
|
+
it('redacts pattern-based custom secrets before saving', () => {
|
|
43
|
+
const lessonText = 'Found corp-secret-deadbeef in the environment.';
|
|
44
|
+
const redacted = maskSecrets(lessonText, customSecrets);
|
|
45
|
+
expect(redacted).not.toContain('corp-secret-deadbeef');
|
|
46
|
+
expect(redacted).toContain('[REDACTED_CUSTOM]');
|
|
47
|
+
});
|
|
48
|
+
it('preserves lesson text when no custom secrets match', () => {
|
|
49
|
+
const lessonText = 'Use structured error handling in async functions.';
|
|
50
|
+
const redacted = maskSecrets(lessonText, customSecrets);
|
|
51
|
+
expect(redacted).toBe(lessonText);
|
|
52
|
+
expect(redacted).not.toContain('[REDACTED_CUSTOM]');
|
|
53
|
+
});
|
|
54
|
+
it('redacts multiple occurrences of the same secret', () => {
|
|
55
|
+
const lessonText = 'First use MY_INTERNAL_TOKEN_XYZ, then verify MY_INTERNAL_TOKEN_XYZ again.';
|
|
56
|
+
const redacted = maskSecrets(lessonText, customSecrets);
|
|
57
|
+
expect(redacted).not.toContain('MY_INTERNAL_TOKEN_XYZ');
|
|
58
|
+
// Should have two [REDACTED_CUSTOM] replacements
|
|
59
|
+
const matches = redacted.match(/\[REDACTED_CUSTOM\]/g);
|
|
60
|
+
expect(matches).toHaveLength(2);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
//# sourceMappingURL=add-lesson.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"add-lesson.test.js","sourceRoot":"","sources":["../../src/commands/add-lesson.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAG9C,OAAO,EAAE,oBAAoB,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEjE,0DAA0D;AAE1D,QAAQ,CAAC,mDAAmD,EAAE,GAAG,EAAE;IACjE,MAAM,aAAa,GAAmB;QACpC,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,uBAAuB,EAAE;QACnD,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,yBAAyB,EAAE;KACtD,CAAC;IAEF,EAAE,CAAC,sCAAsC,EAAE,GAAG,EAAE;QAC9C,MAAM,UAAU,GAAG,+DAA+D,CAAC;QACnF,MAAM,QAAQ,GAAG,oBAAoB,CAAC,aAAa,CAAC,CAAC;QACrD,MAAM,QAAQ,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE;YACpC,EAAE,CAAC,SAAS,GAAG,CAAC,CAAC;YACjB,OAAO,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAC7B,CAAC,CAAC,CAAC;QACH,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC9B,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oDAAoD,EAAE,GAAG,EAAE;QAC5D,MAAM,UAAU,GAAG,0DAA0D,CAAC;QAC9E,MAAM,QAAQ,GAAG,oBAAoB,CAAC,aAAa,CAAC,CAAC;QACrD,MAAM,QAAQ,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE;YACpC,EAAE,CAAC,SAAS,GAAG,CAAC,CAAC;YACjB,OAAO,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAC7B,CAAC,CAAC,CAAC;QACH,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC9B,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2CAA2C,EAAE,GAAG,EAAE;QACnD,MAAM,UAAU,GAAG,+CAA+C,CAAC;QACnE,MAAM,QAAQ,GAAG,oBAAoB,CAAC,aAAa,CAAC,CAAC;QACrD,MAAM,QAAQ,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE;YACpC,EAAE,CAAC,SAAS,GAAG,CAAC,CAAC;YACjB,OAAO,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAC7B,CAAC,CAAC,CAAC;QACH,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC/B,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8CAA8C,EAAE,GAAG,EAAE;QACtD,MAAM,UAAU,GAAG,oDAAoD,CAAC;QACxE,MAAM,QAAQ,GAAG,WAAW,CAAC,UAAU,EAAE,aAAa,CAAC,CAAC;QACxD,MAAM,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,uBAAuB,CAAC,CAAC;QACxD,MAAM,CAAC,QAAQ,CAAC,CAAC,SAAS,CAAC,mBAAmB,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oDAAoD,EAAE,GAAG,EAAE;QAC5D,MAAM,UAAU,GAAG,gDAAgD,CAAC;QACpE,MAAM,QAAQ,GAAG,WAAW,CAAC,UAAU,EAAE,aAAa,CAAC,CAAC;QACxD,MAAM,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,sBAAsB,CAAC,CAAC;QACvD,MAAM,CAAC,QAAQ,CAAC,CAAC,SAAS,CAAC,mBAAmB,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oDAAoD,EAAE,GAAG,EAAE;QAC5D,MAAM,UAAU,GAAG,mDAAmD,CAAC;QACvE,MAAM,QAAQ,GAAG,WAAW,CAAC,UAAU,EAAE,aAAa,CAAC,CAAC;QACxD,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAClC,MAAM,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,mBAAmB,CAAC,CAAC;IACtD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iDAAiD,EAAE,GAAG,EAAE;QACzD,MAAM,UAAU,GAAG,2EAA2E,CAAC;QAC/F,MAAM,QAAQ,GAAG,WAAW,CAAC,UAAU,EAAE,aAAa,CAAC,CAAC;QACxD,MAAM,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,uBAAuB,CAAC,CAAC;QACxD,iDAAiD;QACjD,MAAM,OAAO,GAAG,QAAQ,CAAC,KAAK,CAAC,sBAAsB,CAAC,CAAC;QACvD,MAAM,CAAC,OAAO,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IAClC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"add-secret.d.ts","sourceRoot":"","sources":["../../src/commands/add-secret.ts"],"names":[],"mappings":"AAkCA,MAAM,WAAW,gBAAgB;IAC/B,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,gBAAgB,EACtB,GAAG,SAAgB,GAClB,OAAO,CAAC,IAAI,CAAC,CAsEf"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { SecretsFileSchema } from '@mmnto/totem';
|
|
4
|
+
// ─── Constants ──────────────────────────────────────────
|
|
5
|
+
const TAG = 'AddSecret';
|
|
6
|
+
const MIN_LENGTH = 4;
|
|
7
|
+
const SECRETS_REL_PATH = '.totem/secrets.json';
|
|
8
|
+
const GITIGNORE_ENTRY = '.totem/secrets.json';
|
|
9
|
+
// ─── Helpers ────────────────────────────────────────────
|
|
10
|
+
function ensureGitignore(cwd) {
|
|
11
|
+
const gitignorePath = path.join(cwd, '.gitignore');
|
|
12
|
+
if (fs.existsSync(gitignorePath)) {
|
|
13
|
+
const content = fs.readFileSync(gitignorePath, 'utf-8');
|
|
14
|
+
const lines = content.split(/\r?\n/);
|
|
15
|
+
if (lines.some((line) => line.trim() === GITIGNORE_ENTRY)) {
|
|
16
|
+
return; // Already present
|
|
17
|
+
}
|
|
18
|
+
// Append with a preceding newline if file doesn't end with one
|
|
19
|
+
const separator = content.endsWith('\n') ? '' : '\n';
|
|
20
|
+
fs.writeFileSync(gitignorePath, `${content}${separator}${GITIGNORE_ENTRY}\n`, 'utf-8');
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
fs.writeFileSync(gitignorePath, `${GITIGNORE_ENTRY}\n`, 'utf-8');
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export async function addSecretCommand(value, opts, cwd = process.cwd()) {
|
|
27
|
+
const { log } = await import('../ui.js');
|
|
28
|
+
// 1. Validate length
|
|
29
|
+
if (value.length < MIN_LENGTH) {
|
|
30
|
+
log.error('Totem Error', `Secret must be at least ${MIN_LENGTH} characters to prevent over-redaction.`);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
// 2. Validate regex if --pattern
|
|
34
|
+
const type = opts.pattern ? 'pattern' : 'literal';
|
|
35
|
+
if (type === 'pattern') {
|
|
36
|
+
try {
|
|
37
|
+
new RegExp(value);
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
41
|
+
log.error('Totem Error', `Invalid regex pattern: ${msg}`);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
// 3. Read existing secrets.json
|
|
46
|
+
const secretsPath = path.join(cwd, SECRETS_REL_PATH);
|
|
47
|
+
const totemDir = path.join(cwd, '.totem');
|
|
48
|
+
let data = { secrets: [] };
|
|
49
|
+
if (fs.existsSync(secretsPath)) {
|
|
50
|
+
try {
|
|
51
|
+
const content = fs.readFileSync(secretsPath, 'utf-8');
|
|
52
|
+
const parsed = JSON.parse(content);
|
|
53
|
+
const result = SecretsFileSchema.safeParse(parsed);
|
|
54
|
+
if (result.success) {
|
|
55
|
+
data = result.data;
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
log.warn(TAG, 'secrets.json has invalid structure; starting fresh.');
|
|
59
|
+
data = { secrets: [] };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
log.warn(TAG, `Existing secrets.json was corrupted; starting fresh. ${err instanceof Error ? err.message : String(err)}`);
|
|
64
|
+
data = { secrets: [] };
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// 4. Check for duplicates
|
|
68
|
+
const isDuplicate = data.secrets.some((entry) => entry.type === type && entry.value === value);
|
|
69
|
+
if (isDuplicate) {
|
|
70
|
+
log.warn(TAG, `Duplicate: a ${type} secret with this value already exists.`);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
// 5. Append new secret
|
|
74
|
+
data.secrets.push({ type, value });
|
|
75
|
+
// 6. Write back
|
|
76
|
+
if (!fs.existsSync(totemDir)) {
|
|
77
|
+
fs.mkdirSync(totemDir, { recursive: true });
|
|
78
|
+
}
|
|
79
|
+
fs.writeFileSync(secretsPath, JSON.stringify(data, null, 2) + '\n', 'utf-8');
|
|
80
|
+
// 7. Ensure .gitignore contains secrets.json path
|
|
81
|
+
ensureGitignore(cwd);
|
|
82
|
+
// 8. Success message
|
|
83
|
+
log.success(TAG, `Added ${type} secret (${value.length} chars) → ${SECRETS_REL_PATH}`);
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=add-secret.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"add-secret.js","sourceRoot":"","sources":["../../src/commands/add-secret.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"add-secret.test.d.ts","sourceRoot":"","sources":["../../src/commands/add-secret.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,97 @@
|
|
|
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 { addSecretCommand } from './add-secret.js';
|
|
6
|
+
// ─── Helpers ────────────────────────────────────────────
|
|
7
|
+
function makeTmpDir() {
|
|
8
|
+
return fs.mkdtempSync(path.join(os.tmpdir(), 'totem-add-secret-'));
|
|
9
|
+
}
|
|
10
|
+
function readSecrets(cwd) {
|
|
11
|
+
const content = fs.readFileSync(path.join(cwd, '.totem', 'secrets.json'), 'utf-8');
|
|
12
|
+
return JSON.parse(content);
|
|
13
|
+
}
|
|
14
|
+
// ─── Tests ──────────────────────────────────────────────
|
|
15
|
+
describe('addSecretCommand', () => {
|
|
16
|
+
let tmpDir;
|
|
17
|
+
let stderrSpy;
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
tmpDir = makeTmpDir();
|
|
20
|
+
stderrSpy = vi.spyOn(console, 'error').mockImplementation(() => { });
|
|
21
|
+
});
|
|
22
|
+
afterEach(() => {
|
|
23
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
24
|
+
stderrSpy.mockRestore();
|
|
25
|
+
});
|
|
26
|
+
it('creates secrets.json with new literal secret', async () => {
|
|
27
|
+
await addSecretCommand('my-secret-value', {}, tmpDir);
|
|
28
|
+
const data = readSecrets(tmpDir);
|
|
29
|
+
expect(data.secrets).toHaveLength(1);
|
|
30
|
+
expect(data.secrets[0]).toEqual({ type: 'literal', value: 'my-secret-value' });
|
|
31
|
+
});
|
|
32
|
+
it('appends to existing secrets.json', async () => {
|
|
33
|
+
const totemDir = path.join(tmpDir, '.totem');
|
|
34
|
+
fs.mkdirSync(totemDir, { recursive: true });
|
|
35
|
+
const existing = { secrets: [{ type: 'literal', value: 'existing-secret' }] };
|
|
36
|
+
fs.writeFileSync(path.join(totemDir, 'secrets.json'), JSON.stringify(existing, null, 2));
|
|
37
|
+
await addSecretCommand('second-secret', {}, tmpDir);
|
|
38
|
+
const data = readSecrets(tmpDir);
|
|
39
|
+
expect(data.secrets).toHaveLength(2);
|
|
40
|
+
expect(data.secrets[0]).toEqual({ type: 'literal', value: 'existing-secret' });
|
|
41
|
+
expect(data.secrets[1]).toEqual({ type: 'literal', value: 'second-secret' });
|
|
42
|
+
});
|
|
43
|
+
it('rejects values shorter than 4 characters', async () => {
|
|
44
|
+
await addSecretCommand('abc', {}, tmpDir);
|
|
45
|
+
const output = stderrSpy.mock.calls.map((args) => String(args[0])).join('\n');
|
|
46
|
+
expect(output).toContain('at least 4 characters');
|
|
47
|
+
expect(fs.existsSync(path.join(tmpDir, '.totem', 'secrets.json'))).toBe(false);
|
|
48
|
+
});
|
|
49
|
+
it('rejects invalid regex with --pattern', async () => {
|
|
50
|
+
await addSecretCommand('[invalid(', { pattern: true }, tmpDir);
|
|
51
|
+
const output = stderrSpy.mock.calls.map((args) => String(args[0])).join('\n');
|
|
52
|
+
expect(output).toContain('Invalid regex');
|
|
53
|
+
expect(fs.existsSync(path.join(tmpDir, '.totem', 'secrets.json'))).toBe(false);
|
|
54
|
+
});
|
|
55
|
+
it('stores pattern type with --pattern flag', async () => {
|
|
56
|
+
await addSecretCommand('ACME_[A-Z0-9]{8}', { pattern: true }, tmpDir);
|
|
57
|
+
const data = readSecrets(tmpDir);
|
|
58
|
+
expect(data.secrets).toHaveLength(1);
|
|
59
|
+
expect(data.secrets[0]).toEqual({ type: 'pattern', value: 'ACME_[A-Z0-9]{8}' });
|
|
60
|
+
});
|
|
61
|
+
it('ensures .gitignore contains secrets.json path', async () => {
|
|
62
|
+
// Create a .gitignore without the secrets entry
|
|
63
|
+
fs.writeFileSync(path.join(tmpDir, '.gitignore'), 'node_modules\n.env\n');
|
|
64
|
+
await addSecretCommand('test-secret', {}, tmpDir);
|
|
65
|
+
const gitignore = fs.readFileSync(path.join(tmpDir, '.gitignore'), 'utf-8');
|
|
66
|
+
expect(gitignore).toContain('.totem/secrets.json');
|
|
67
|
+
// Should not duplicate existing lines
|
|
68
|
+
expect(gitignore).toContain('node_modules');
|
|
69
|
+
expect(gitignore).toContain('.env');
|
|
70
|
+
});
|
|
71
|
+
it('creates .gitignore if it does not exist', async () => {
|
|
72
|
+
expect(fs.existsSync(path.join(tmpDir, '.gitignore'))).toBe(false);
|
|
73
|
+
await addSecretCommand('test-secret', {}, tmpDir);
|
|
74
|
+
expect(fs.existsSync(path.join(tmpDir, '.gitignore'))).toBe(true);
|
|
75
|
+
const gitignore = fs.readFileSync(path.join(tmpDir, '.gitignore'), 'utf-8');
|
|
76
|
+
expect(gitignore.trim()).toBe('.totem/secrets.json');
|
|
77
|
+
});
|
|
78
|
+
it('rejects duplicate entries', async () => {
|
|
79
|
+
// First add
|
|
80
|
+
await addSecretCommand('duplicate-val', {}, tmpDir);
|
|
81
|
+
// Second add — same type + value
|
|
82
|
+
await addSecretCommand('duplicate-val', {}, tmpDir);
|
|
83
|
+
const output = stderrSpy.mock.calls.map((args) => String(args[0])).join('\n');
|
|
84
|
+
expect(output).toContain('Duplicate');
|
|
85
|
+
// Should still only have one entry
|
|
86
|
+
const data = readSecrets(tmpDir);
|
|
87
|
+
expect(data.secrets).toHaveLength(1);
|
|
88
|
+
});
|
|
89
|
+
it('does not duplicate .gitignore entry when already present', async () => {
|
|
90
|
+
fs.writeFileSync(path.join(tmpDir, '.gitignore'), '.totem/secrets.json\n');
|
|
91
|
+
await addSecretCommand('test-secret', {}, tmpDir);
|
|
92
|
+
const gitignore = fs.readFileSync(path.join(tmpDir, '.gitignore'), 'utf-8');
|
|
93
|
+
const matches = gitignore.split('\n').filter((line) => line.trim() === '.totem/secrets.json');
|
|
94
|
+
expect(matches).toHaveLength(1);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
//# sourceMappingURL=add-secret.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"add-secret.test.js","sourceRoot":"","sources":["../../src/commands/add-secret.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -11,5 +11,14 @@ export declare function checkGitHooks(cwd: string): DiagnosticResult;
|
|
|
11
11
|
export declare function checkEmbeddingConfig(cwd: string): DiagnosticResult;
|
|
12
12
|
export declare function checkIndex(cwd: string, lanceDir?: string): DiagnosticResult;
|
|
13
13
|
export declare function checkSecretLeaks(cwd: string, totemDir?: string): DiagnosticResult;
|
|
14
|
-
export declare function
|
|
14
|
+
export declare function checkSecretsFileTracked(cwd: string, totemDir?: string): DiagnosticResult;
|
|
15
|
+
/** Bypass rate above which a rule is considered "struggling" and eligible for downgrade. */
|
|
16
|
+
export declare const BYPASS_THRESHOLD = 0.3;
|
|
17
|
+
/** Minimum total events (triggers + bypasses) required before acting on a rule. */
|
|
18
|
+
export declare const MIN_EVENTS = 5;
|
|
19
|
+
export interface DoctorOptions {
|
|
20
|
+
pr?: boolean;
|
|
21
|
+
}
|
|
22
|
+
export declare function runSelfHealing(cwd: string): Promise<void>;
|
|
23
|
+
export declare function doctorCommand(options?: DoctorOptions): Promise<DiagnosticResult[]>;
|
|
15
24
|
//# sourceMappingURL=doctor.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAaA,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;AAE5D,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,WAAW,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAyBD,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,gBAAgB,CAiBzD;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,SAAW,GAAG,gBAAgB,CAiCrF;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,gBAAgB,CAmD3D;AAsBD,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,gBAAgB,CA2FlE;AAED,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,SAAa,GAAG,gBAAgB,CA4C/E;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,SAAW,GAAG,gBAAgB,CA6EnF;AAED,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,SAAW,GAAG,gBAAgB,CAyB1F;AA2CD,4FAA4F;AAC5F,eAAO,MAAM,gBAAgB,MAAM,CAAC;AAEpC,mFAAmF;AACnF,eAAO,MAAM,UAAU,IAAI,CAAC;AAI5B,MAAM,WAAW,aAAa;IAC5B,EAAE,CAAC,EAAE,OAAO,CAAC;CACd;AAID,wBAAsB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA+J/D;AAID,wBAAsB,aAAa,CAAC,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAyC5F"}
|
package/dist/commands/doctor.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
1
2
|
import * as fs from 'node:fs';
|
|
2
3
|
import * as path from 'node:path';
|
|
3
4
|
import pc from 'picocolors';
|
|
5
|
+
import { compileCustomSecrets, loadCustomSecrets } from '@mmnto/totem';
|
|
4
6
|
import { resolveGitRoot } from '../git.js';
|
|
5
7
|
import { CONFIG_FILES } from '../utils.js';
|
|
6
8
|
// ─── Secret leak patterns ───────────────────────────────
|
|
@@ -301,11 +303,15 @@ export function checkSecretLeaks(cwd, totemDir = '.totem') {
|
|
|
301
303
|
message: 'No files to scan',
|
|
302
304
|
};
|
|
303
305
|
}
|
|
306
|
+
// Load user-defined custom secrets
|
|
307
|
+
const customSecrets = loadCustomSecrets(cwd, totemDir);
|
|
308
|
+
const customPatterns = compileCustomSecrets(customSecrets);
|
|
309
|
+
const allPatterns = [...SECRET_PATTERNS, ...customPatterns];
|
|
304
310
|
const leaks = [];
|
|
305
311
|
for (const filePath of filesToScan) {
|
|
306
312
|
try {
|
|
307
313
|
const content = fs.readFileSync(filePath, 'utf-8');
|
|
308
|
-
for (const pattern of
|
|
314
|
+
for (const pattern of allPatterns) {
|
|
309
315
|
const matches = content.match(new RegExp(pattern.source, 'g'));
|
|
310
316
|
if (matches) {
|
|
311
317
|
for (const match of matches) {
|
|
@@ -337,6 +343,34 @@ export function checkSecretLeaks(cwd, totemDir = '.totem') {
|
|
|
337
343
|
message: 'No leaked keys detected',
|
|
338
344
|
};
|
|
339
345
|
}
|
|
346
|
+
export function checkSecretsFileTracked(cwd, totemDir = '.totem') {
|
|
347
|
+
const secretsPath = path.join(totemDir, 'secrets.json');
|
|
348
|
+
try {
|
|
349
|
+
const result = spawnSync('git', ['ls-files', '--recurse-submodules', secretsPath], {
|
|
350
|
+
cwd,
|
|
351
|
+
encoding: 'utf-8',
|
|
352
|
+
});
|
|
353
|
+
if (result.error)
|
|
354
|
+
throw result.error;
|
|
355
|
+
const output = (result.stdout ?? '').trim();
|
|
356
|
+
if (output.length > 0) {
|
|
357
|
+
return {
|
|
358
|
+
name: 'Secrets File Security',
|
|
359
|
+
status: 'fail',
|
|
360
|
+
message: `${secretsPath} is tracked by git — secrets may be exposed`,
|
|
361
|
+
remediation: `Run: git rm --cached ${secretsPath}`,
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
catch {
|
|
366
|
+
// git not available or not a repo — skip
|
|
367
|
+
}
|
|
368
|
+
return {
|
|
369
|
+
name: 'Secrets File Security',
|
|
370
|
+
status: 'pass',
|
|
371
|
+
message: 'secrets.json is not tracked by git',
|
|
372
|
+
};
|
|
373
|
+
}
|
|
340
374
|
// ─── Output formatting ──────────────────────────────────
|
|
341
375
|
function statusIcon(status) {
|
|
342
376
|
switch (status) {
|
|
@@ -372,8 +406,144 @@ function formatResult(result) {
|
|
|
372
406
|
}
|
|
373
407
|
return line;
|
|
374
408
|
}
|
|
409
|
+
// ─── Self-healing constants ─────────────────────────────
|
|
410
|
+
/** Bypass rate above which a rule is considered "struggling" and eligible for downgrade. */
|
|
411
|
+
export const BYPASS_THRESHOLD = 0.3;
|
|
412
|
+
/** Minimum total events (triggers + bypasses) required before acting on a rule. */
|
|
413
|
+
export const MIN_EVENTS = 5;
|
|
414
|
+
// ─── Self-healing flow ──────────────────────────────────
|
|
415
|
+
export async function runSelfHealing(cwd) {
|
|
416
|
+
// Load config to get totemDir
|
|
417
|
+
const { loadConfig, resolveConfigPath } = await import('../utils.js');
|
|
418
|
+
const configPath = resolveConfigPath(cwd);
|
|
419
|
+
const config = await loadConfig(configPath);
|
|
420
|
+
const totemDir = path.join(cwd, config.totemDir);
|
|
421
|
+
const rulesPath = path.join(totemDir, 'compiled-rules.json');
|
|
422
|
+
console.error(`\n${pc.cyan('[Auto-Healing]')} Analyzing Trap Ledger...`);
|
|
423
|
+
const { analyzeLedger } = await import('./ledger-analyzer.js');
|
|
424
|
+
const stats = await analyzeLedger(totemDir, (msg) => console.error(pc.dim(` ${msg}`)));
|
|
425
|
+
if (stats.size === 0) {
|
|
426
|
+
console.error(pc.dim(' No ledger data. Run totem lint with some // totem-context: overrides first.'));
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
// Find struggling rules
|
|
430
|
+
const struggling = [...stats.entries()]
|
|
431
|
+
.filter(([, s]) => s.bypassRate > BYPASS_THRESHOLD && s.totalEvents >= MIN_EVENTS)
|
|
432
|
+
.sort((a, b) => b[1].bypassRate - a[1].bypassRate);
|
|
433
|
+
if (struggling.length === 0) {
|
|
434
|
+
console.error(pc.green(' No rules exceed the 30% bypass threshold. All healthy.'));
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
console.error(` Found ${struggling.length} rule(s) exceeding ${BYPASS_THRESHOLD * 100}% bypass rate:\n`);
|
|
438
|
+
// Check git status before modifying files
|
|
439
|
+
try {
|
|
440
|
+
const gitResult = spawnSync('git', ['status', '--porcelain', rulesPath], {
|
|
441
|
+
cwd,
|
|
442
|
+
encoding: 'utf-8',
|
|
443
|
+
});
|
|
444
|
+
const gitStatus = (gitResult.stdout ?? '').trim();
|
|
445
|
+
if (gitStatus) {
|
|
446
|
+
console.error(pc.red(' ERROR: compiled-rules.json has uncommitted changes. Commit or stash first.'));
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
catch {
|
|
451
|
+
// Not a git repo or git not available — proceed anyway
|
|
452
|
+
}
|
|
453
|
+
// Downgrade each struggling rule
|
|
454
|
+
const { downgradeRuleToWarning } = await import('./rule-mutator.js');
|
|
455
|
+
const downgraded = [];
|
|
456
|
+
for (const [ruleId, ruleStats] of struggling) {
|
|
457
|
+
const result = downgradeRuleToWarning(rulesPath, ruleId);
|
|
458
|
+
if (result.downgraded) {
|
|
459
|
+
const pct = (ruleStats.bypassRate * 100).toFixed(0);
|
|
460
|
+
console.error(` ${pc.yellow('↓')} ${result.ruleHeading ?? ruleId} — ${pct}% bypass rate (${ruleStats.bypassCount}/${ruleStats.totalEvents} events)`);
|
|
461
|
+
downgraded.push({
|
|
462
|
+
ruleId,
|
|
463
|
+
heading: result.ruleHeading ?? ruleId,
|
|
464
|
+
rate: ruleStats.bypassRate,
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
else {
|
|
468
|
+
console.error(pc.dim(` - ${result.ruleHeading ?? ruleId} — already at warning, skipping`));
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
if (downgraded.length === 0) {
|
|
472
|
+
console.error(pc.green('\n All struggling rules already downgraded. Nothing to do.'));
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
console.error(`\n ${pc.green(`Downgraded ${downgraded.length} rule(s) from error → warning.`)}`);
|
|
476
|
+
// Create branch and PR
|
|
477
|
+
const branchName = `totem/auto-downgrade-${Date.now()}`;
|
|
478
|
+
// Capture current branch so we can restore on failure
|
|
479
|
+
const currentBranchRes = spawnSync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], {
|
|
480
|
+
cwd,
|
|
481
|
+
stdio: 'pipe',
|
|
482
|
+
encoding: 'utf-8',
|
|
483
|
+
});
|
|
484
|
+
const originalBranch = currentBranchRes.error ? null : (currentBranchRes.stdout ?? '').trim();
|
|
485
|
+
let branchCreated = false;
|
|
486
|
+
/** Run a shell command via spawnSync, throw on failure */
|
|
487
|
+
function run(cmd, args) {
|
|
488
|
+
const res = spawnSync(cmd, args, { cwd, stdio: 'pipe', encoding: 'utf-8' });
|
|
489
|
+
if (res.error)
|
|
490
|
+
throw res.error;
|
|
491
|
+
if (res.status !== 0) {
|
|
492
|
+
const stderr = (res.stderr ?? '').trim();
|
|
493
|
+
throw new Error(`${cmd} ${args[0]} failed (exit ${res.status})${stderr ? ': ' + stderr : ''}`);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
try {
|
|
497
|
+
run('git', ['checkout', '-b', branchName]);
|
|
498
|
+
branchCreated = true;
|
|
499
|
+
run('git', ['add', rulesPath]);
|
|
500
|
+
// Build commit message
|
|
501
|
+
const ruleList = downgraded
|
|
502
|
+
.map((d) => `- ${d.heading} (${(d.rate * 100).toFixed(0)}% bypass)`)
|
|
503
|
+
.join('\n');
|
|
504
|
+
const commitMsg = `fix: auto-downgrade ${downgraded.length} noisy rule(s)\n\n${ruleList}\n\nGenerated by totem doctor --pr`;
|
|
505
|
+
run('git', ['commit', '-m', commitMsg]);
|
|
506
|
+
run('git', ['push', '-u', 'origin', branchName]);
|
|
507
|
+
// Build PR body
|
|
508
|
+
const prBody = [
|
|
509
|
+
'## Auto-Healing: Rule Downgrade',
|
|
510
|
+
'',
|
|
511
|
+
`${downgraded.length} compiled rule(s) exceeded the 30% bypass rate threshold and have been downgraded from \`error\` to \`warning\`.`,
|
|
512
|
+
'',
|
|
513
|
+
'| Rule | Bypass Rate | Events |',
|
|
514
|
+
'|---|---|---|',
|
|
515
|
+
...downgraded.map((d) => {
|
|
516
|
+
const ruleStats = struggling.find(([id]) => id === d.ruleId)?.[1];
|
|
517
|
+
return `| ${d.heading} | ${(d.rate * 100).toFixed(0)}% | ${ruleStats?.totalEvents ?? '?'} |`;
|
|
518
|
+
}),
|
|
519
|
+
'',
|
|
520
|
+
'These rules are not deleted (ADR-027). They continue to fire as warnings, feeding into local telemetry. If the underlying issue is fixed, the rule can be re-promoted to error.',
|
|
521
|
+
'',
|
|
522
|
+
'Generated by `totem doctor --pr`',
|
|
523
|
+
].join('\n');
|
|
524
|
+
const prTitle = `fix: auto-downgrade ${downgraded.length} noisy rule(s)`;
|
|
525
|
+
run('gh', ['pr', 'create', '--title', prTitle, '--body', prBody]);
|
|
526
|
+
console.error(pc.green(`\n PR created on branch ${branchName}`));
|
|
527
|
+
}
|
|
528
|
+
catch (err) {
|
|
529
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
530
|
+
console.error(pc.red(`\n Failed to create PR: ${msg}`));
|
|
531
|
+
if (branchCreated) {
|
|
532
|
+
console.error(pc.dim(` Changes are committed on branch ${branchName}. Push manually with: git push -u origin ${branchName}`));
|
|
533
|
+
}
|
|
534
|
+
else {
|
|
535
|
+
console.error(pc.dim(' The compiled-rules.json changes are in your working tree. Commit manually.'));
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
finally {
|
|
539
|
+
// Only switch back if we created the branch (otherwise we'd change the user's branch)
|
|
540
|
+
if (branchCreated && originalBranch) {
|
|
541
|
+
spawnSync('git', ['checkout', originalBranch], { cwd, stdio: 'pipe' });
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
}
|
|
375
545
|
// ─── Main command ───────────────────────────────────────
|
|
376
|
-
export async function doctorCommand() {
|
|
546
|
+
export async function doctorCommand(options = {}) {
|
|
377
547
|
const cwd = process.cwd();
|
|
378
548
|
console.error(`${pc.cyan('[Totem]')} Running diagnostics...\n`);
|
|
379
549
|
const results = [
|
|
@@ -383,6 +553,7 @@ export async function doctorCommand() {
|
|
|
383
553
|
checkEmbeddingConfig(cwd),
|
|
384
554
|
checkIndex(cwd),
|
|
385
555
|
checkSecretLeaks(cwd),
|
|
556
|
+
checkSecretsFileTracked(cwd),
|
|
386
557
|
];
|
|
387
558
|
for (const result of results) {
|
|
388
559
|
console.error(formatResult(result));
|
|
@@ -404,6 +575,10 @@ export async function doctorCommand() {
|
|
|
404
575
|
else
|
|
405
576
|
parts.push(`${counts.fail} failures`);
|
|
406
577
|
console.error(`\n${pc.cyan('[Totem]')} ${parts.join(', ')}`);
|
|
578
|
+
// After diagnostics, if --pr is passed, run self-healing
|
|
579
|
+
if (options.pr) {
|
|
580
|
+
await runSelfHealing(cwd);
|
|
581
|
+
}
|
|
407
582
|
return results;
|
|
408
583
|
}
|
|
409
584
|
//# sourceMappingURL=doctor.js.map
|