@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.
Files changed (134) hide show
  1. package/dist/commands/add-lesson.d.ts.map +1 -1
  2. package/dist/commands/add-lesson.js +12 -0
  3. package/dist/commands/add-lesson.js.map +1 -1
  4. package/dist/commands/add-lesson.test.d.ts +2 -0
  5. package/dist/commands/add-lesson.test.d.ts.map +1 -0
  6. package/dist/commands/add-lesson.test.js +63 -0
  7. package/dist/commands/add-lesson.test.js.map +1 -0
  8. package/dist/commands/add-secret.d.ts +5 -0
  9. package/dist/commands/add-secret.d.ts.map +1 -0
  10. package/dist/commands/add-secret.js +85 -0
  11. package/dist/commands/add-secret.js.map +1 -0
  12. package/dist/commands/add-secret.test.d.ts +2 -0
  13. package/dist/commands/add-secret.test.d.ts.map +1 -0
  14. package/dist/commands/add-secret.test.js +97 -0
  15. package/dist/commands/add-secret.test.js.map +1 -0
  16. package/dist/commands/doctor.d.ts +10 -1
  17. package/dist/commands/doctor.d.ts.map +1 -1
  18. package/dist/commands/doctor.js +177 -2
  19. package/dist/commands/doctor.js.map +1 -1
  20. package/dist/commands/doctor.test.js +284 -2
  21. package/dist/commands/doctor.test.js.map +1 -1
  22. package/dist/commands/extract.d.ts.map +1 -1
  23. package/dist/commands/extract.js +4 -1
  24. package/dist/commands/extract.js.map +1 -1
  25. package/dist/commands/extract.test.js +53 -0
  26. package/dist/commands/extract.test.js.map +1 -1
  27. package/dist/commands/init.d.ts.map +1 -1
  28. package/dist/commands/init.js +16 -0
  29. package/dist/commands/init.js.map +1 -1
  30. package/dist/commands/ledger-analyzer.d.ts +24 -0
  31. package/dist/commands/ledger-analyzer.d.ts.map +1 -0
  32. package/dist/commands/ledger-analyzer.js +64 -0
  33. package/dist/commands/ledger-analyzer.js.map +1 -0
  34. package/dist/commands/ledger-analyzer.test.d.ts +2 -0
  35. package/dist/commands/ledger-analyzer.test.d.ts.map +1 -0
  36. package/dist/commands/ledger-analyzer.test.js +163 -0
  37. package/dist/commands/ledger-analyzer.test.js.map +1 -0
  38. package/dist/commands/list-secrets.d.ts +15 -0
  39. package/dist/commands/list-secrets.d.ts.map +1 -0
  40. package/dist/commands/list-secrets.js +104 -0
  41. package/dist/commands/list-secrets.js.map +1 -0
  42. package/dist/commands/list-secrets.test.d.ts +2 -0
  43. package/dist/commands/list-secrets.test.d.ts.map +1 -0
  44. package/dist/commands/list-secrets.test.js +85 -0
  45. package/dist/commands/list-secrets.test.js.map +1 -0
  46. package/dist/commands/remove-secret.d.ts +2 -0
  47. package/dist/commands/remove-secret.d.ts.map +1 -0
  48. package/dist/commands/remove-secret.js +53 -0
  49. package/dist/commands/remove-secret.js.map +1 -0
  50. package/dist/commands/remove-secret.test.d.ts +2 -0
  51. package/dist/commands/remove-secret.test.d.ts.map +1 -0
  52. package/dist/commands/remove-secret.test.js +85 -0
  53. package/dist/commands/remove-secret.test.js.map +1 -0
  54. package/dist/commands/review-learn-templates.d.ts +7 -0
  55. package/dist/commands/review-learn-templates.d.ts.map +1 -0
  56. package/dist/commands/review-learn-templates.js +36 -0
  57. package/dist/commands/review-learn-templates.js.map +1 -0
  58. package/dist/commands/review-learn-templates.test.d.ts +2 -0
  59. package/dist/commands/review-learn-templates.test.d.ts.map +1 -0
  60. package/dist/commands/review-learn-templates.test.js +29 -0
  61. package/dist/commands/review-learn-templates.test.js.map +1 -0
  62. package/dist/commands/review-learn.d.ts +13 -0
  63. package/dist/commands/review-learn.d.ts.map +1 -0
  64. package/dist/commands/review-learn.js +260 -0
  65. package/dist/commands/review-learn.js.map +1 -0
  66. package/dist/commands/review-learn.test.d.ts +2 -0
  67. package/dist/commands/review-learn.test.d.ts.map +1 -0
  68. package/dist/commands/review-learn.test.js +218 -0
  69. package/dist/commands/review-learn.test.js.map +1 -0
  70. package/dist/commands/rule-mutator.d.ts +17 -0
  71. package/dist/commands/rule-mutator.d.ts.map +1 -0
  72. package/dist/commands/rule-mutator.js +33 -0
  73. package/dist/commands/rule-mutator.js.map +1 -0
  74. package/dist/commands/rule-mutator.test.d.ts +2 -0
  75. package/dist/commands/rule-mutator.test.d.ts.map +1 -0
  76. package/dist/commands/rule-mutator.test.js +104 -0
  77. package/dist/commands/rule-mutator.test.js.map +1 -0
  78. package/dist/commands/run-compiled-rules.d.ts.map +1 -1
  79. package/dist/commands/run-compiled-rules.js +49 -5
  80. package/dist/commands/run-compiled-rules.js.map +1 -1
  81. package/dist/commands/run-compiled-rules.test.js +107 -1
  82. package/dist/commands/run-compiled-rules.test.js.map +1 -1
  83. package/dist/commands/shield-hints.d.ts +16 -2
  84. package/dist/commands/shield-hints.d.ts.map +1 -1
  85. package/dist/commands/shield-hints.js +35 -20
  86. package/dist/commands/shield-hints.js.map +1 -1
  87. package/dist/commands/shield-hints.test.js +70 -1
  88. package/dist/commands/shield-hints.test.js.map +1 -1
  89. package/dist/commands/shield.d.ts.map +1 -1
  90. package/dist/commands/shield.js +21 -2
  91. package/dist/commands/shield.js.map +1 -1
  92. package/dist/commands/triage-pr.d.ts +21 -0
  93. package/dist/commands/triage-pr.d.ts.map +1 -0
  94. package/dist/commands/triage-pr.js +231 -0
  95. package/dist/commands/triage-pr.js.map +1 -0
  96. package/dist/commands/triage-pr.test.d.ts +2 -0
  97. package/dist/commands/triage-pr.test.d.ts.map +1 -0
  98. package/dist/commands/triage-pr.test.js +163 -0
  99. package/dist/commands/triage-pr.test.js.map +1 -0
  100. package/dist/index.js +74 -4
  101. package/dist/index.js.map +1 -1
  102. package/dist/parsers/bot-review-parser.d.ts +48 -0
  103. package/dist/parsers/bot-review-parser.d.ts.map +1 -0
  104. package/dist/parsers/bot-review-parser.js +139 -0
  105. package/dist/parsers/bot-review-parser.js.map +1 -0
  106. package/dist/parsers/bot-review-parser.test.d.ts +2 -0
  107. package/dist/parsers/bot-review-parser.test.d.ts.map +1 -0
  108. package/dist/parsers/bot-review-parser.test.js +240 -0
  109. package/dist/parsers/bot-review-parser.test.js.map +1 -0
  110. package/dist/parsers/triage-dedup.d.ts +8 -0
  111. package/dist/parsers/triage-dedup.d.ts.map +1 -0
  112. package/dist/parsers/triage-dedup.js +134 -0
  113. package/dist/parsers/triage-dedup.js.map +1 -0
  114. package/dist/parsers/triage-dedup.test.d.ts +2 -0
  115. package/dist/parsers/triage-dedup.test.d.ts.map +1 -0
  116. package/dist/parsers/triage-dedup.test.js +209 -0
  117. package/dist/parsers/triage-dedup.test.js.map +1 -0
  118. package/dist/parsers/triage-severity-mapper.d.ts +9 -0
  119. package/dist/parsers/triage-severity-mapper.d.ts.map +1 -0
  120. package/dist/parsers/triage-severity-mapper.js +86 -0
  121. package/dist/parsers/triage-severity-mapper.js.map +1 -0
  122. package/dist/parsers/triage-severity-mapper.test.d.ts +2 -0
  123. package/dist/parsers/triage-severity-mapper.test.d.ts.map +1 -0
  124. package/dist/parsers/triage-severity-mapper.test.js +62 -0
  125. package/dist/parsers/triage-severity-mapper.test.js.map +1 -0
  126. package/dist/parsers/triage-types.d.ts +12 -0
  127. package/dist/parsers/triage-types.d.ts.map +1 -0
  128. package/dist/parsers/triage-types.js +2 -0
  129. package/dist/parsers/triage-types.js.map +1 -0
  130. package/dist/utils.d.ts +3 -1
  131. package/dist/utils.d.ts.map +1 -1
  132. package/dist/utils.js +1 -1
  133. package/dist/utils.js.map +1 -1
  134. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"add-lesson.d.ts","sourceRoot":"","sources":["../../src/commands/add-lesson.ts"],"names":[],"mappings":"AAAA,wBAAsB,gBAAgB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAqGxE"}
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":"AAAA,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,SAAkB;IACvD,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,MAAM,CAAC,oBAAoB,CAAC,CAAC;IACrD,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,CAAC;IACnC,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,CAAC;IACvC,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,cAAc,CAAC,CAAC;IACtE,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,wBAAwB,CAAC,CAAC;IACxD,MAAM,EAAE,qBAAqB,EAAE,eAAe,EAAE,GAAG,MAAM,MAAM,CAAC,cAAc,CAAC,CAAC,CAAC,eAAe;IAChG,MAAM,EAAE,GAAG,EAAE,GAAG,MAAM,MAAM,CAAC,UAAU,CAAC,CAAC;IACzC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,iBAAiB,EAAE,QAAQ,EAAE,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,CAAC;IAEjG,SAAS,iBAAiB,CAAC,GAAW;QACpC,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,gBAAgB,CAAC,CAAC,EAAE,CAAC;YACpD,OAAO;gBACL,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,MAAM;gBACjC,IAAI,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,eAAe,CAAC;aACjD,CAAC;QACJ,CAAC;QACD,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC,EAAE,CAAC;YAC/C,OAAO,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,eAAe,CAAC,EAAE,CAAC;QACzF,CAAC;QACD,OAAO,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,eAAe,CAAC,EAAE,CAAC;IACvF,CAAC;IAED,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAC1B,MAAM,UAAU,GAAG,iBAAiB,CAAC,GAAG,CAAC,CAAC;IAC1C,OAAO,CAAC,GAAG,CAAC,CAAC;IACb,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,UAAU,CAAC,CAAC;IAE5C,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;IACjD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC7B,EAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9C,CAAC;IAED,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;IAElD,IAAI,UAAU,GAAG,SAAS,CAAC;IAC3B,MAAM,IAAI,GAAa,EAAE,CAAC;IAE1B,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,MAAM,EAAE,GAAG,QAAQ,CAAC,eAAe,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC;QACvD,IAAI,CAAC;YACH,OAAO,CAAC,GAAG,CAAC,6BAA6B,CAAC,CAAC;YAC3C,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,oDAAoD,CAAC,CAAC;YACxF,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,8CAA8C,CAAC,CAAC;YAClF,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,0DAA0D,CAAC,CAAC;YAC1F,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,4BAA4B,CAAC,CAAC;YAE/D,IAAI,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC;gBAClB,IAAI,CAAC,IAAI,CACP,GAAG,MAAM;qBACN,KAAK,CAAC,GAAG,CAAC;qBACV,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;qBACpB,MAAM,CAAC,OAAO,CAAC,CACnB,CAAC;YACJ,CAAC;YAED,MAAM,KAAK,GAAa,EAAE,CAAC;YAC3B,IAAI,OAAO,CAAC,IAAI,EAAE;gBAAE,KAAK,CAAC,IAAI,CAAC,gBAAgB,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YACjE,IAAI,OAAO,CAAC,IAAI,EAAE;gBAAE,KAAK,CAAC,IAAI,CAAC,gBAAgB,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YACjE,IAAI,GAAG,CAAC,IAAI,EAAE;gBAAE,KAAK,CAAC,IAAI,CAAC,iBAAiB,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YAE1D,UAAU,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAChC,CAAC;gBAAS,CAAC;YACT,EAAE,CAAC,KAAK,EAAE,CAAC;QACb,CAAC;IACH,CAAC;IAED,IAAI,CAAC,UAAU,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,EAAE,CAAC;QACtC,GAAG,CAAC,KAAK,CAAC,aAAa,EAAE,8BAA8B,CAAC,CAAC;QACzD,OAAO;IACT,CAAC;IAED,MAAM,UAAU,GAAG,QAAQ,CAAC,UAAU,CAAC,CAAC;IACxC,MAAM,aAAa,GACjB,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;IAC3F,MAAM,OAAO,GAAG,qBAAqB,CAAC,UAAU,CAAC,CAAC;IAElD,MAAM,KAAK,GAAG,eAAe,OAAO,iBAAiB,aAAa,OAAO,UAAU,CAAC,IAAI,EAAE,IAAI,CAAC;IAE/F,MAAM,WAAW,GAAG,eAAe,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;IACvD,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;IAC5C,GAAG,CAAC,OAAO,CAAC,OAAO,EAAE,mBAAmB,MAAM,CAAC,QAAQ,YAAY,QAAQ,EAAE,CAAC,CAAC,CAAC,eAAe;IAE/F,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,cAAc,CAAC,CAAC;IACpD,GAAG,CAAC,GAAG,CAAC,OAAO,EAAE,mCAAmC,CAAC,CAAC;IACtD,IAAI,CAAC;QACH,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,iBAAiB,CAAC,GAAG,CAAC,CAAC;QAC7C,MAAM,KAAK,GAAG,EAAE,CAAC,QAAQ,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;QACxC,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,EAAE,IAAI,EAAE;YAC7B,GAAG;YACH,QAAQ,EAAE,IAAI;YACd,KAAK,EAAE,CAAC,QAAQ,EAAE,KAAK,EAAE,KAAK,CAAC;YAC/B,KAAK,EAAE,MAAM;YACb,WAAW,EAAE,IAAI;SAClB,CAAC,CAAC;QACH,KAAK,CAAC,KAAK,EAAE,CAAC;QACd,EAAE,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IACtB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACjE,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE,sCAAsC,OAAO,EAAE,CAAC,CAAC;IACrE,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"add-lesson.js","sourceRoot":"","sources":["../../src/commands/add-lesson.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=add-lesson.test.d.ts.map
@@ -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":"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"}
@@ -0,0 +1,5 @@
1
+ export interface AddSecretOptions {
2
+ pattern?: boolean;
3
+ }
4
+ export declare function addSecretCommand(value: string, opts: AddSecretOptions, cwd?: string): Promise<void>;
5
+ //# sourceMappingURL=add-secret.d.ts.map
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=add-secret.test.d.ts.map
@@ -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 doctorCommand(): Promise<DiagnosticResult[]>;
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":"AAUA,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,CAwEnF;AA2CD,wBAAsB,aAAa,IAAI,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAmCjE"}
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"}
@@ -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 SECRET_PATTERNS) {
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