@mmnto/cli 1.5.1 → 1.5.4
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 +33 -18
- 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/audit.d.ts.map +1 -1
- package/dist/commands/audit.js +4 -4
- package/dist/commands/audit.js.map +1 -1
- package/dist/commands/bridge.d.ts +1 -1
- package/dist/commands/bridge.d.ts.map +1 -1
- package/dist/commands/bridge.js +7 -7
- package/dist/commands/bridge.js.map +1 -1
- package/dist/commands/briefing.d.ts.map +1 -1
- package/dist/commands/briefing.js +8 -6
- package/dist/commands/briefing.js.map +1 -1
- package/dist/commands/compile-templates.d.ts +1 -1
- package/dist/commands/compile-templates.d.ts.map +1 -1
- package/dist/commands/compile-templates.js +4 -3
- package/dist/commands/compile-templates.js.map +1 -1
- package/dist/commands/compile.d.ts.map +1 -1
- package/dist/commands/compile.js +2 -2
- package/dist/commands/compile.js.map +1 -1
- package/dist/commands/docs.d.ts.map +1 -1
- package/dist/commands/docs.js +22 -14
- package/dist/commands/docs.js.map +1 -1
- package/dist/commands/doctor.d.ts +1 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +36 -1
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/doctor.test.js +86 -2
- package/dist/commands/doctor.test.js.map +1 -1
- package/dist/commands/drift.d.ts.map +1 -1
- package/dist/commands/drift.js +3 -3
- package/dist/commands/drift.js.map +1 -1
- package/dist/commands/eject.d.ts.map +1 -1
- package/dist/commands/eject.js +3 -3
- package/dist/commands/eject.js.map +1 -1
- package/dist/commands/explain.d.ts.map +1 -1
- package/dist/commands/explain.js +4 -4
- package/dist/commands/explain.js.map +1 -1
- package/dist/commands/extract.d.ts.map +1 -1
- package/dist/commands/extract.js +8 -5
- 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/handoff.d.ts.map +1 -1
- package/dist/commands/handoff.js +4 -3
- package/dist/commands/handoff.js.map +1 -1
- package/dist/commands/init.d.ts +1 -2
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +24 -6
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/install-hooks.d.ts.map +1 -1
- package/dist/commands/install-hooks.js +15 -0
- package/dist/commands/install-hooks.js.map +1 -1
- package/dist/commands/install-hooks.test.js +20 -0
- package/dist/commands/install-hooks.test.js.map +1 -1
- package/dist/commands/link.d.ts.map +1 -1
- package/dist/commands/link.js +1 -1
- package/dist/commands/link.js.map +1 -1
- package/dist/commands/lint.d.ts.map +1 -1
- package/dist/commands/lint.js +22 -1
- package/dist/commands/lint.js.map +1 -1
- package/dist/commands/lint.test.d.ts +2 -0
- package/dist/commands/lint.test.d.ts.map +1 -0
- package/dist/commands/lint.test.js +107 -0
- package/dist/commands/lint.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/list.d.ts.map +1 -1
- package/dist/commands/list.js +1 -1
- package/dist/commands/list.js.map +1 -1
- package/dist/commands/migrate-lessons.d.ts.map +1 -1
- package/dist/commands/migrate-lessons.js +6 -6
- package/dist/commands/migrate-lessons.js.map +1 -1
- 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/run-compiled-rules.d.ts +2 -0
- package/dist/commands/run-compiled-rules.d.ts.map +1 -1
- package/dist/commands/run-compiled-rules.js +53 -8
- 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/search.d.ts.map +1 -1
- package/dist/commands/search.js +3 -3
- package/dist/commands/search.js.map +1 -1
- package/dist/commands/shield-classify.d.ts +10 -0
- package/dist/commands/shield-classify.d.ts.map +1 -0
- package/dist/commands/shield-classify.js +137 -0
- package/dist/commands/shield-classify.js.map +1 -0
- package/dist/commands/shield-classify.test.d.ts +2 -0
- package/dist/commands/shield-classify.test.d.ts.map +1 -0
- package/dist/commands/shield-classify.test.js +180 -0
- package/dist/commands/shield-classify.test.js.map +1 -0
- 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-templates.d.ts +66 -0
- package/dist/commands/shield-templates.d.ts.map +1 -1
- package/dist/commands/shield-templates.js +122 -0
- package/dist/commands/shield-templates.js.map +1 -1
- package/dist/commands/shield.d.ts +23 -4
- package/dist/commands/shield.d.ts.map +1 -1
- package/dist/commands/shield.js +232 -65
- package/dist/commands/shield.js.map +1 -1
- package/dist/commands/shield.test.js +225 -1
- package/dist/commands/shield.test.js.map +1 -1
- package/dist/commands/spec.d.ts +2 -3
- package/dist/commands/spec.d.ts.map +1 -1
- package/dist/commands/spec.js +11 -8
- package/dist/commands/spec.js.map +1 -1
- package/dist/commands/spec.test.js +18 -18
- package/dist/commands/spec.test.js.map +1 -1
- package/dist/commands/stats.d.ts.map +1 -1
- package/dist/commands/stats.js +3 -3
- package/dist/commands/stats.js.map +1 -1
- package/dist/commands/sync.d.ts.map +1 -1
- package/dist/commands/sync.js +14 -8
- package/dist/commands/sync.js.map +1 -1
- package/dist/commands/triage.d.ts.map +1 -1
- package/dist/commands/triage.js +5 -4
- package/dist/commands/triage.js.map +1 -1
- package/dist/commands/wrap.d.ts.map +1 -1
- package/dist/commands/wrap.js +1 -1
- package/dist/commands/wrap.js.map +1 -1
- package/dist/index.js +62 -3
- 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/utils.d.ts +5 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +4 -3
- package/dist/utils.js.map +1 -1
- package/package.json +2 -2
package/dist/commands/list.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"list.js","sourceRoot":"","sources":["../../src/commands/list.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"list.js","sourceRoot":"","sources":["../../src/commands/list.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migrate-lessons.d.ts","sourceRoot":"","sources":["../../src/commands/migrate-lessons.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"migrate-lessons.d.ts","sourceRoot":"","sources":["../../src/commands/migrate-lessons.ts"],"names":[],"mappings":"AAEA;;;;;;;;;GASG;AACH,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC,CAmF3D"}
|
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
import * as fs from 'node:fs'; // totem-ignore
|
|
2
|
-
import * as path from 'node:path'; // totem-ignore
|
|
3
|
-
import { parseLessonsFile, writeLessonFile } from '@mmnto/totem'; // totem-ignore
|
|
4
|
-
import { BASELINE_MARKER } from '../assets/universal-lessons.js'; // totem-ignore
|
|
5
|
-
import { log } from '../ui.js'; // totem-ignore
|
|
6
|
-
import { loadConfig, resolveConfigPath } from '../utils.js'; // totem-ignore
|
|
7
1
|
const TAG = 'Migrate';
|
|
8
2
|
/**
|
|
9
3
|
* Migrate from `.totem/lessons.md` (single file) to `.totem/lessons/` (directory of discrete files).
|
|
@@ -16,6 +10,12 @@ const TAG = 'Migrate';
|
|
|
16
10
|
* 6. Log summary
|
|
17
11
|
*/
|
|
18
12
|
export async function migrateLessonsCommand() {
|
|
13
|
+
const fs = await import('node:fs'); // totem-ignore
|
|
14
|
+
const path = await import('node:path'); // totem-ignore
|
|
15
|
+
const { parseLessonsFile, writeLessonFile } = await import('@mmnto/totem'); // totem-ignore
|
|
16
|
+
const { BASELINE_MARKER } = await import('../assets/universal-lessons.js'); // totem-ignore
|
|
17
|
+
const { log } = await import('../ui.js'); // totem-ignore
|
|
18
|
+
const { loadConfig, resolveConfigPath } = await import('../utils.js'); // totem-ignore
|
|
19
19
|
const cwd = process.cwd();
|
|
20
20
|
const configPath = resolveConfigPath(cwd);
|
|
21
21
|
const config = await loadConfig(configPath);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migrate-lessons.js","sourceRoot":"","sources":["../../src/commands/migrate-lessons.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"migrate-lessons.js","sourceRoot":"","sources":["../../src/commands/migrate-lessons.ts"],"names":[],"mappings":"AAAA,MAAM,GAAG,GAAG,SAAS,CAAC;AAEtB;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB;IACzC,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,eAAe;IACnD,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,eAAe;IACvD,MAAM,EAAE,gBAAgB,EAAE,eAAe,EAAE,GAAG,MAAM,MAAM,CAAC,cAAc,CAAC,CAAC,CAAC,eAAe;IAC3F,MAAM,EAAE,eAAe,EAAE,GAAG,MAAM,MAAM,CAAC,gCAAgC,CAAC,CAAC,CAAC,eAAe;IAC3F,MAAM,EAAE,GAAG,EAAE,GAAG,MAAM,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,eAAe;IACzD,MAAM,EAAE,UAAU,EAAE,iBAAiB,EAAE,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,eAAe;IAEtF,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAC1B,MAAM,UAAU,GAAG,iBAAiB,CAAC,GAAG,CAAC,CAAC;IAC1C,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,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC;IACrD,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;IAElD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC/B,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,kDAAkD,CAAC,CAAC,CAAC,eAAe;QACjF,OAAO;IACT,CAAC;IAED,MAAM,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;IACrD,MAAM,OAAO,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC;IAE1C,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,6DAA6D,CAAC,CAAC,CAAC,eAAe;QAC5F,OAAO;IACT,CAAC;IAED,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,OAAO,CAAC,MAAM,iCAAiC,CAAC,CAAC,CAAC,eAAe;IAExF,2BAA2B;IAC3B,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC/B,EAAE,CAAC,SAAS,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAChD,CAAC;IAED,IAAI,aAAa,GAAG,CAAC,CAAC;IACtB,IAAI,WAAW,GAAG,CAAC,CAAC;IAEpB,MAAM,UAAU,GAAG,IAAI,MAAM,CAAC,IAAI,eAAe,CAAC,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;IACjG,MAAM,WAAW,GAAG,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,mDAAmD;IACjG,IAAI,WAAW,IAAI,WAAW,CAAC,KAAK,IAAI,IAAI,EAAE,CAAC;QAC7C,iFAAiF;QACjF,MAAM,SAAS,GAAG,WAAW,CAAC,KAAK,CAAC;QACpC,MAAM,SAAS,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;QAC9C,MAAM,eAAe,GAAG,gBAAgB,CAAC,OAAO,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC;QACnE,MAAM,kBAAkB,GAAG,gBAAgB,CAAC,SAAS,CAAC,CAAC;QAEvD,wCAAwC;QACxC,IAAI,eAAe,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC/B,MAAM,eAAe,GACnB,eAAe,GAAG,MAAM,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC1E,EAAE,CAAC,aAAa,CACd,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,aAAa,CAAC,EACpC,eAAe,CAAC,IAAI,EAAE,GAAG,IAAI,EAC7B,OAAO,CACR,CAAC;YACF,aAAa,GAAG,eAAe,CAAC,MAAM,CAAC;QACzC,CAAC;QAED,iDAAiD;QACjD,KAAK,MAAM,MAAM,IAAI,kBAAkB,EAAE,CAAC;YACxC,eAAe,CAAC,UAAU,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC;YACxC,WAAW,EAAE,CAAC;QAChB,CAAC;IACH,CAAC;SAAM,CAAC;QACN,sDAAsD;QACtD,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;YAC7B,eAAe,CAAC,UAAU,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC;YACxC,WAAW,EAAE,CAAC;QAChB,CAAC;IACH,CAAC;IAED,qBAAqB;IACrB,MAAM,OAAO,GAAG,UAAU,GAAG,MAAM,CAAC;IACpC,EAAE,CAAC,UAAU,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;IAEnC,GAAG,CAAC,OAAO,CACT,GAAG,EACH,YAAY,WAAW,aAAa,aAAa,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,aAAa,WAAW,CAAC,CAAC,CAAC,EAAE,OAAO,MAAM,CAAC,QAAQ,WAAW,CAC7H,CAAC;IACF,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,4BAA4B,MAAM,CAAC,QAAQ,iBAAiB,CAAC,CAAC,CAAC,eAAe;IAC3F,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,+BAA+B,CAAC,CAAC;AAChD,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remove-secret.d.ts","sourceRoot":"","sources":["../../src/commands/remove-secret.ts"],"names":[],"mappings":"AAYA,wBAAsB,mBAAmB,CACvC,QAAQ,EAAE,MAAM,EAChB,GAAG,SAAgB,EACnB,QAAQ,SAAW,GAClB,OAAO,CAAC,IAAI,CAAC,CA+Df"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
// ─── Constants ──────────────────────────────────────────
|
|
4
|
+
const TAG = 'RemoveSecret';
|
|
5
|
+
const SECRETS_REL_PATH = '.totem/secrets.json';
|
|
6
|
+
// ─── Main ───────────────────────────────────────────────
|
|
7
|
+
export async function removeSecretCommand(indexStr, cwd = process.cwd(), totemDir = '.totem') {
|
|
8
|
+
const { log } = await import('../ui.js');
|
|
9
|
+
const { buildSecretEntries } = await import('./list-secrets.js');
|
|
10
|
+
// 1. Build the full list to resolve index → source (silent, no output)
|
|
11
|
+
const entries = await buildSecretEntries(cwd, totemDir);
|
|
12
|
+
const index = parseInt(indexStr, 10);
|
|
13
|
+
if (isNaN(index) || index < 1 || index > entries.length) {
|
|
14
|
+
log.error('Totem Error', `Index ${indexStr} is out of range. Valid range: 1–${entries.length || 0}.`);
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
const target = entries[index - 1];
|
|
18
|
+
// 2. Reject shared/yaml secrets
|
|
19
|
+
if (target.source === 'shared/yaml') {
|
|
20
|
+
log.error('Totem Error', 'Cannot remove shared secrets from CLI. Edit your totem.config.yaml directly.');
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
// 3. Read secrets.json
|
|
24
|
+
const secretsPath = path.join(cwd, SECRETS_REL_PATH);
|
|
25
|
+
let data = { secrets: [] };
|
|
26
|
+
try {
|
|
27
|
+
const content = fs.readFileSync(secretsPath, 'utf-8');
|
|
28
|
+
data = JSON.parse(content);
|
|
29
|
+
if (!Array.isArray(data.secrets)) {
|
|
30
|
+
data.secrets = [];
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
log.error('Totem Error', `Failed to read ${SECRETS_REL_PATH}.`);
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
// 4. Compute the local index within secrets.json
|
|
38
|
+
// The entry's position among local/json entries maps to the JSON array.
|
|
39
|
+
// Count how many shared/yaml entries come before this entry.
|
|
40
|
+
const yamlCount = entries.filter((e) => e.source === 'shared/yaml').length;
|
|
41
|
+
const localIndex = index - 1 - yamlCount;
|
|
42
|
+
if (localIndex < 0 || localIndex >= data.secrets.length) {
|
|
43
|
+
log.error('Totem Error', `Index ${indexStr} is out of range for local secrets.`);
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
// 5. Remove
|
|
47
|
+
const [removed] = data.secrets.splice(localIndex, 1);
|
|
48
|
+
// 6. Write back
|
|
49
|
+
fs.writeFileSync(secretsPath, JSON.stringify(data, null, 2) + '\n', 'utf-8');
|
|
50
|
+
// 7. Confirm
|
|
51
|
+
log.success(TAG, `Removed ${removed.type} secret: "${removed.type === 'literal' ? removed.value.slice(0, 4) + '***' : removed.value}"`);
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=remove-secret.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remove-secret.js","sourceRoot":"","sources":["../../src/commands/remove-secret.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remove-secret.test.d.ts","sourceRoot":"","sources":["../../src/commands/remove-secret.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,85 @@
|
|
|
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 { removeSecretCommand } from './remove-secret.js';
|
|
6
|
+
// ─── Helpers ────────────────────────────────────────────
|
|
7
|
+
let tmpDirs = [];
|
|
8
|
+
function makeTmpDir() {
|
|
9
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'totem-remove-secret-'));
|
|
10
|
+
tmpDirs.push(dir);
|
|
11
|
+
return dir;
|
|
12
|
+
}
|
|
13
|
+
function writeSecretsJson(cwd, totemDir, data) {
|
|
14
|
+
const dirPath = path.join(cwd, totemDir);
|
|
15
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
16
|
+
fs.writeFileSync(path.join(dirPath, 'secrets.json'), JSON.stringify(data, null, 2) + '\n', 'utf-8');
|
|
17
|
+
}
|
|
18
|
+
function readSecretsJson(cwd, totemDir) {
|
|
19
|
+
const content = fs.readFileSync(path.join(cwd, totemDir, 'secrets.json'), 'utf-8');
|
|
20
|
+
return JSON.parse(content);
|
|
21
|
+
}
|
|
22
|
+
// ─── Tests ──────────────────────────────────────────────
|
|
23
|
+
describe('removeSecretCommand', () => {
|
|
24
|
+
let stderrSpy;
|
|
25
|
+
let exitSpy;
|
|
26
|
+
beforeEach(() => {
|
|
27
|
+
stderrSpy = vi.spyOn(console, 'error').mockImplementation(() => { });
|
|
28
|
+
exitSpy = vi.spyOn(process, 'exit').mockImplementation(() => {
|
|
29
|
+
throw new Error('process.exit called');
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
afterEach(() => {
|
|
33
|
+
stderrSpy.mockRestore();
|
|
34
|
+
exitSpy.mockRestore();
|
|
35
|
+
for (const dir of tmpDirs) {
|
|
36
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
37
|
+
}
|
|
38
|
+
tmpDirs = [];
|
|
39
|
+
});
|
|
40
|
+
it('removes entry at specified index', async () => {
|
|
41
|
+
const cwd = makeTmpDir();
|
|
42
|
+
const totemDir = '.totem';
|
|
43
|
+
writeSecretsJson(cwd, totemDir, {
|
|
44
|
+
secrets: [
|
|
45
|
+
{ type: 'literal', value: 'first-secret' },
|
|
46
|
+
{ type: 'pattern', value: 'PATTERN-\\d+' },
|
|
47
|
+
{ type: 'literal', value: 'third-secret' },
|
|
48
|
+
],
|
|
49
|
+
});
|
|
50
|
+
// Remove the second entry (index 2, which is the second local/json secret)
|
|
51
|
+
await removeSecretCommand('2', cwd, totemDir);
|
|
52
|
+
const updated = readSecretsJson(cwd, totemDir);
|
|
53
|
+
expect(updated.secrets).toHaveLength(2);
|
|
54
|
+
expect(updated.secrets[0].value).toBe('first-secret');
|
|
55
|
+
expect(updated.secrets[1].value).toBe('third-secret');
|
|
56
|
+
const output = stderrSpy.mock.calls.map((c) => c[0]).join('\n');
|
|
57
|
+
expect(output).toContain('Removed');
|
|
58
|
+
});
|
|
59
|
+
it('rejects out-of-range index', async () => {
|
|
60
|
+
const cwd = makeTmpDir();
|
|
61
|
+
const totemDir = '.totem';
|
|
62
|
+
writeSecretsJson(cwd, totemDir, {
|
|
63
|
+
secrets: [{ type: 'literal', value: 'only-secret' }],
|
|
64
|
+
});
|
|
65
|
+
await expect(removeSecretCommand('5', cwd, totemDir)).rejects.toThrow('process.exit called');
|
|
66
|
+
const output = stderrSpy.mock.calls.map((c) => c[0]).join('\n');
|
|
67
|
+
expect(output).toContain('out of range');
|
|
68
|
+
});
|
|
69
|
+
it('rejects removal of shared yaml secrets', async () => {
|
|
70
|
+
const cwd = makeTmpDir();
|
|
71
|
+
const totemDir = '.totem';
|
|
72
|
+
// Write a YAML config with a shared secret
|
|
73
|
+
fs.writeFileSync(path.join(cwd, 'totem.yaml'), 'secrets:\n - type: literal\n value: "yaml-secret-val"\n', 'utf-8');
|
|
74
|
+
// Write local secrets.json
|
|
75
|
+
writeSecretsJson(cwd, totemDir, {
|
|
76
|
+
secrets: [{ type: 'literal', value: 'local-secret' }],
|
|
77
|
+
});
|
|
78
|
+
// Index 1 should be the yaml secret
|
|
79
|
+
await expect(removeSecretCommand('1', cwd, totemDir)).rejects.toThrow('process.exit called');
|
|
80
|
+
const output = stderrSpy.mock.calls.map((c) => c[0]).join('\n');
|
|
81
|
+
expect(output).toContain('Cannot remove shared secrets from CLI');
|
|
82
|
+
expect(output).toContain('totem.config.yaml');
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
//# sourceMappingURL=remove-secret.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remove-secret.test.js","sourceRoot":"","sources":["../../src/commands/remove-secret.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Maximum existing lessons to include for dedup context */
|
|
2
|
+
export declare const MAX_EXISTING_LESSONS = 10;
|
|
3
|
+
/** Maximum assembled prompt size in characters */
|
|
4
|
+
export declare const MAX_PROMPT_CHARS = 100000;
|
|
5
|
+
/** System prompt for review-learn — instructs LLM to extract lessons from resolved bot findings */
|
|
6
|
+
export declare const REVIEW_LEARN_SYSTEM_PROMPT = "You are Totem's lesson extractor for bot code review findings.\n\nYou receive a set of code review findings from automated bots (CodeRabbit, Gemini Code Assist) that were RESOLVED (the developer accepted and fixed them). Your job is to extract reusable architectural lessons from these findings.\n\nRULES:\n1. Only extract lessons that represent reusable patterns \u2014 NOT one-off fixes.\n2. Each lesson must be actionable: what the symptom is, what the fix is, and why it matters.\n3. Every lesson MUST include lifecycle: nursery in its metadata \u2014 these are unproven until validated.\n4. Deduplicate against the provided existing lessons. Do NOT repeat known patterns.\n5. Focus on architectural and security findings. Skip pure style/formatting nits unless they represent a real pattern.\n6. If no findings warrant a lesson, return an empty array.\n\nOUTPUT FORMAT:\nReturn a JSON array of lesson objects. Each lesson has:\n- \"tags\": string[] \u2014 relevant tags (e.g., [\"security\", \"typescript\", \"architecture\"])\n- \"text\": string \u2014 the lesson body. Start with the symptom/pattern, then the fix.\n- \"lifecycle\": \"nursery\" \u2014 REQUIRED, always \"nursery\"\n\nExample:\n[\n {\n \"tags\": [\"security\", \"shell\"],\n \"text\": \"Using execSync with string interpolation for shell commands creates injection risk. Use spawnSync with an args array to pass arguments safely without shell interpretation.\",\n \"lifecycle\": \"nursery\"\n }\n]\n\nIf no lessons should be extracted, return: []";
|
|
7
|
+
//# sourceMappingURL=review-learn-templates.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review-learn-templates.d.ts","sourceRoot":"","sources":["../../src/commands/review-learn-templates.ts"],"names":[],"mappings":"AAEA,4DAA4D;AAC5D,eAAO,MAAM,oBAAoB,KAAK,CAAC;AAEvC,kDAAkD;AAClD,eAAO,MAAM,gBAAgB,SAAU,CAAC;AAIxC,mGAAmG;AACnG,eAAO,MAAM,0BAA0B,+/CA2BO,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// ─── Constants ──────────────────────────────────────────
|
|
2
|
+
/** Maximum existing lessons to include for dedup context */
|
|
3
|
+
export const MAX_EXISTING_LESSONS = 10;
|
|
4
|
+
/** Maximum assembled prompt size in characters */
|
|
5
|
+
export const MAX_PROMPT_CHARS = 100_000;
|
|
6
|
+
// ─── System prompt ──────────────────────────────────────
|
|
7
|
+
/** System prompt for review-learn — instructs LLM to extract lessons from resolved bot findings */
|
|
8
|
+
export const REVIEW_LEARN_SYSTEM_PROMPT = `You are Totem's lesson extractor for bot code review findings.
|
|
9
|
+
|
|
10
|
+
You receive a set of code review findings from automated bots (CodeRabbit, Gemini Code Assist) that were RESOLVED (the developer accepted and fixed them). Your job is to extract reusable architectural lessons from these findings.
|
|
11
|
+
|
|
12
|
+
RULES:
|
|
13
|
+
1. Only extract lessons that represent reusable patterns — NOT one-off fixes.
|
|
14
|
+
2. Each lesson must be actionable: what the symptom is, what the fix is, and why it matters.
|
|
15
|
+
3. Every lesson MUST include lifecycle: nursery in its metadata — these are unproven until validated.
|
|
16
|
+
4. Deduplicate against the provided existing lessons. Do NOT repeat known patterns.
|
|
17
|
+
5. Focus on architectural and security findings. Skip pure style/formatting nits unless they represent a real pattern.
|
|
18
|
+
6. If no findings warrant a lesson, return an empty array.
|
|
19
|
+
|
|
20
|
+
OUTPUT FORMAT:
|
|
21
|
+
Return a JSON array of lesson objects. Each lesson has:
|
|
22
|
+
- "tags": string[] — relevant tags (e.g., ["security", "typescript", "architecture"])
|
|
23
|
+
- "text": string — the lesson body. Start with the symptom/pattern, then the fix.
|
|
24
|
+
- "lifecycle": "nursery" — REQUIRED, always "nursery"
|
|
25
|
+
|
|
26
|
+
Example:
|
|
27
|
+
[
|
|
28
|
+
{
|
|
29
|
+
"tags": ["security", "shell"],
|
|
30
|
+
"text": "Using execSync with string interpolation for shell commands creates injection risk. Use spawnSync with an args array to pass arguments safely without shell interpretation.",
|
|
31
|
+
"lifecycle": "nursery"
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
If no lessons should be extracted, return: []`;
|
|
36
|
+
//# sourceMappingURL=review-learn-templates.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review-learn-templates.js","sourceRoot":"","sources":["../../src/commands/review-learn-templates.ts"],"names":[],"mappings":"AAAA,2DAA2D;AAE3D,4DAA4D;AAC5D,MAAM,CAAC,MAAM,oBAAoB,GAAG,EAAE,CAAC;AAEvC,kDAAkD;AAClD,MAAM,CAAC,MAAM,gBAAgB,GAAG,OAAO,CAAC;AAExC,2DAA2D;AAE3D,mGAAmG;AACnG,MAAM,CAAC,MAAM,0BAA0B,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;8CA2BI,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review-learn-templates.test.d.ts","sourceRoot":"","sources":["../../src/commands/review-learn-templates.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { MAX_EXISTING_LESSONS, MAX_PROMPT_CHARS, REVIEW_LEARN_SYSTEM_PROMPT, } from './review-learn-templates.js';
|
|
3
|
+
describe('REVIEW_LEARN_SYSTEM_PROMPT', () => {
|
|
4
|
+
it('includes lifecycle nursery instruction', () => {
|
|
5
|
+
expect(REVIEW_LEARN_SYSTEM_PROMPT).toContain('lifecycle: nursery');
|
|
6
|
+
expect(REVIEW_LEARN_SYSTEM_PROMPT).toContain('"lifecycle": "nursery"');
|
|
7
|
+
});
|
|
8
|
+
it('includes dedup instruction', () => {
|
|
9
|
+
expect(REVIEW_LEARN_SYSTEM_PROMPT).toContain('Deduplicate');
|
|
10
|
+
expect(REVIEW_LEARN_SYSTEM_PROMPT).toContain('Do NOT repeat known patterns');
|
|
11
|
+
});
|
|
12
|
+
it('instructs to return empty array when no lessons', () => {
|
|
13
|
+
expect(REVIEW_LEARN_SYSTEM_PROMPT).toContain('return an empty array');
|
|
14
|
+
expect(REVIEW_LEARN_SYSTEM_PROMPT).toContain('return: []');
|
|
15
|
+
});
|
|
16
|
+
it('mentions CodeRabbit and Gemini Code Assist', () => {
|
|
17
|
+
expect(REVIEW_LEARN_SYSTEM_PROMPT).toContain('CodeRabbit');
|
|
18
|
+
expect(REVIEW_LEARN_SYSTEM_PROMPT).toContain('Gemini Code Assist');
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
describe('constants', () => {
|
|
22
|
+
it('MAX_EXISTING_LESSONS is a positive number', () => {
|
|
23
|
+
expect(MAX_EXISTING_LESSONS).toBeGreaterThan(0);
|
|
24
|
+
});
|
|
25
|
+
it('MAX_PROMPT_CHARS is a positive number', () => {
|
|
26
|
+
expect(MAX_PROMPT_CHARS).toBeGreaterThan(0);
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
//# sourceMappingURL=review-learn-templates.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review-learn-templates.test.js","sourceRoot":"","sources":["../../src/commands/review-learn-templates.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAE9C,OAAO,EACL,oBAAoB,EACpB,gBAAgB,EAChB,0BAA0B,GAC3B,MAAM,6BAA6B,CAAC;AAErC,QAAQ,CAAC,4BAA4B,EAAE,GAAG,EAAE;IAC1C,EAAE,CAAC,wCAAwC,EAAE,GAAG,EAAE;QAChD,MAAM,CAAC,0BAA0B,CAAC,CAAC,SAAS,CAAC,oBAAoB,CAAC,CAAC;QACnE,MAAM,CAAC,0BAA0B,CAAC,CAAC,SAAS,CAAC,wBAAwB,CAAC,CAAC;IACzE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4BAA4B,EAAE,GAAG,EAAE;QACpC,MAAM,CAAC,0BAA0B,CAAC,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC;QAC5D,MAAM,CAAC,0BAA0B,CAAC,CAAC,SAAS,CAAC,8BAA8B,CAAC,CAAC;IAC/E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iDAAiD,EAAE,GAAG,EAAE;QACzD,MAAM,CAAC,0BAA0B,CAAC,CAAC,SAAS,CAAC,uBAAuB,CAAC,CAAC;QACtE,MAAM,CAAC,0BAA0B,CAAC,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC;IAC7D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4CAA4C,EAAE,GAAG,EAAE;QACpD,MAAM,CAAC,0BAA0B,CAAC,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC;QAC3D,MAAM,CAAC,0BAA0B,CAAC,CAAC,SAAS,CAAC,oBAAoB,CAAC,CAAC;IACrE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,WAAW,EAAE,GAAG,EAAE;IACzB,EAAE,CAAC,2CAA2C,EAAE,GAAG,EAAE;QACnD,MAAM,CAAC,oBAAoB,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uCAAuC,EAAE,GAAG,EAAE;QAC/C,MAAM,CAAC,gBAAgB,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;IAC9C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { SearchResult } from '@mmnto/totem';
|
|
2
|
+
import type { NormalizedBotFinding } from '../parsers/bot-review-parser.js';
|
|
3
|
+
export declare function assembleReviewLearnPrompt(findings: NormalizedBotFinding[], existingLessons: SearchResult[], systemPrompt: string): string;
|
|
4
|
+
export interface ReviewLearnOptions {
|
|
5
|
+
raw?: boolean;
|
|
6
|
+
out?: string;
|
|
7
|
+
model?: string;
|
|
8
|
+
fresh?: boolean;
|
|
9
|
+
dryRun?: boolean;
|
|
10
|
+
yes?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export declare function reviewLearnCommand(prNumber: string, options: ReviewLearnOptions): Promise<void>;
|
|
13
|
+
//# sourceMappingURL=review-learn.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review-learn.d.ts","sourceRoot":"","sources":["../../src/commands/review-learn.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAmB,YAAY,EAAE,MAAM,cAAc,CAAC;AAelE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iCAAiC,CAAC;AAyE5E,wBAAgB,yBAAyB,CACvC,QAAQ,EAAE,oBAAoB,EAAE,EAChC,eAAe,EAAE,YAAY,EAAE,EAC/B,YAAY,EAAE,MAAM,GACnB,MAAM,CA4BR;AA6BD,MAAM,WAAW,kBAAkB;IACjC,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAED,wBAAsB,kBAAkB,CACtC,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,IAAI,CAAC,CAwOf"}
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import { createEmbedder, deduplicateLessons, flagSuspiciousLessons, generateLessonHeading, LanceStore, loadCustomSecrets, runSync, TotemConfigError, truncateHeading, writeLessonFile, } from '@mmnto/totem';
|
|
2
|
+
import { log } from '../ui.js';
|
|
3
|
+
import { formatResults, getSystemPrompt, loadConfig, loadEnv, requireEmbedding, resolveConfigPath, runOrchestrator, sanitize, wrapUntrustedXml, } from '../utils.js';
|
|
4
|
+
// Reuse parsing and selection helpers from extract (they are exported)
|
|
5
|
+
import { parseLessons, selectLessons } from './extract.js';
|
|
6
|
+
import { MAX_EXISTING_LESSONS, MAX_PROMPT_CHARS, REVIEW_LEARN_SYSTEM_PROMPT, } from './review-learn-templates.js';
|
|
7
|
+
const TAG = 'ReviewLearn';
|
|
8
|
+
function groupIntoThreads(comments) {
|
|
9
|
+
const byId = new Map();
|
|
10
|
+
for (const c of comments)
|
|
11
|
+
byId.set(c.id, c);
|
|
12
|
+
const threadMap = new Map();
|
|
13
|
+
for (const c of comments) {
|
|
14
|
+
const rootId = c.inReplyToId ?? c.id;
|
|
15
|
+
const thread = threadMap.get(rootId) ?? [];
|
|
16
|
+
thread.push(c);
|
|
17
|
+
threadMap.set(rootId, thread);
|
|
18
|
+
}
|
|
19
|
+
const threads = [];
|
|
20
|
+
for (const [rootId, threadComments] of threadMap) {
|
|
21
|
+
threadComments.sort((a, b) => {
|
|
22
|
+
if (!a.createdAt || !b.createdAt)
|
|
23
|
+
return 0;
|
|
24
|
+
return a.createdAt.localeCompare(b.createdAt);
|
|
25
|
+
});
|
|
26
|
+
const root = byId.get(rootId) ?? threadComments[0];
|
|
27
|
+
threads.push({
|
|
28
|
+
path: root.path,
|
|
29
|
+
diffHunk: root.diffHunk,
|
|
30
|
+
comments: threadComments.map((c) => ({ author: c.author, body: c.body })),
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
return threads;
|
|
34
|
+
}
|
|
35
|
+
// ─── LanceDB retrieval ───────────────────────────────
|
|
36
|
+
async function retrieveExistingLessons(store) {
|
|
37
|
+
return store.search({
|
|
38
|
+
query: 'lesson trap pattern decision',
|
|
39
|
+
typeFilter: 'spec',
|
|
40
|
+
maxResults: MAX_EXISTING_LESSONS,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
// ─── Prompt assembly ─────────────────────────────────
|
|
44
|
+
export function assembleReviewLearnPrompt(findings, existingLessons, systemPrompt) {
|
|
45
|
+
const sections = [systemPrompt];
|
|
46
|
+
sections.push('\n=== RESOLVED BOT FINDINGS ===');
|
|
47
|
+
for (let i = 0; i < findings.length; i++) {
|
|
48
|
+
const f = findings[i];
|
|
49
|
+
sections.push(`\n--- Finding ${i + 1} [${f.tool}/${f.severity}] ${sanitize(f.file)} ---`);
|
|
50
|
+
sections.push(wrapUntrustedXml('finding_body', f.body));
|
|
51
|
+
if (f.suggestion) {
|
|
52
|
+
sections.push('Suggestion:');
|
|
53
|
+
sections.push(wrapUntrustedXml('suggestion', f.suggestion));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// Existing lessons for dedup context
|
|
57
|
+
const lessonSection = formatResults(existingLessons, 'EXISTING LESSONS (do NOT duplicate)');
|
|
58
|
+
if (lessonSection) {
|
|
59
|
+
sections.push('\n=== DEDUP CONTEXT ===');
|
|
60
|
+
sections.push(lessonSection);
|
|
61
|
+
}
|
|
62
|
+
// Truncate if needed
|
|
63
|
+
let prompt = sections.join('\n');
|
|
64
|
+
if (prompt.length > MAX_PROMPT_CHARS) {
|
|
65
|
+
prompt = prompt.slice(0, MAX_PROMPT_CHARS) + '\n\n... [content truncated] ...';
|
|
66
|
+
}
|
|
67
|
+
return prompt;
|
|
68
|
+
}
|
|
69
|
+
// ─── Nursery lesson writer ───────────────────────────
|
|
70
|
+
/**
|
|
71
|
+
* Append lessons with `lifecycle: nursery` YAML frontmatter.
|
|
72
|
+
* Wraps each lesson entry with frontmatter before writing.
|
|
73
|
+
*/
|
|
74
|
+
function appendNurseryLessons(lessons, lessonsDir) {
|
|
75
|
+
for (const l of lessons) {
|
|
76
|
+
const heading = l.heading || generateLessonHeading(l.text);
|
|
77
|
+
const tags = l.tags;
|
|
78
|
+
// Build YAML frontmatter with lifecycle: nursery
|
|
79
|
+
const frontmatter = [
|
|
80
|
+
'---',
|
|
81
|
+
`tags: [${tags.map((t) => `"${t}"`).join(', ')}]`,
|
|
82
|
+
'lifecycle: nursery',
|
|
83
|
+
'---',
|
|
84
|
+
].join('\n');
|
|
85
|
+
const body = `## Lesson — ${truncateHeading(heading) || 'Lesson'}\n\n**Tags:** ${tags.join(', ')}\n\n${l.text}\n`;
|
|
86
|
+
const entry = `${frontmatter}\n\n${body}`;
|
|
87
|
+
writeLessonFile(lessonsDir, entry);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
export async function reviewLearnCommand(prNumber, options) {
|
|
91
|
+
const path = await import('node:path');
|
|
92
|
+
const { GitHubCliPrAdapter } = await import('../adapters/github-cli-pr.js');
|
|
93
|
+
const { isBotComment, extractResolvedBotFindings } = await import('../parsers/bot-review-parser.js');
|
|
94
|
+
// 1. Parse and validate PR number
|
|
95
|
+
const num = parseInt(prNumber, 10);
|
|
96
|
+
if (isNaN(num) || num <= 0 || String(num) !== prNumber) {
|
|
97
|
+
throw new TotemConfigError(`Invalid PR number: '${prNumber}'. Must be a positive integer.`, 'Pass a numeric PR number, e.g. `totem review-learn 123`.', 'CONFIG_INVALID');
|
|
98
|
+
}
|
|
99
|
+
// 2. Load config, env, connect to LanceDB for dedup
|
|
100
|
+
const cwd = process.cwd();
|
|
101
|
+
const configPath = resolveConfigPath(cwd);
|
|
102
|
+
loadEnv(cwd);
|
|
103
|
+
const config = await loadConfig(configPath);
|
|
104
|
+
// Load user-defined custom secrets for DLP (#921)
|
|
105
|
+
const customSecrets = loadCustomSecrets(cwd, config.totemDir, (msg) => log.warn(TAG, msg));
|
|
106
|
+
// Connect to LanceDB for dedup context
|
|
107
|
+
const embedding = requireEmbedding(config);
|
|
108
|
+
const embedder = createEmbedder(embedding);
|
|
109
|
+
const store = new LanceStore(path.join(cwd, config.lanceDir), embedder);
|
|
110
|
+
await store.connect();
|
|
111
|
+
log.info(TAG, 'Querying existing lessons for dedup...');
|
|
112
|
+
const existingLessons = await retrieveExistingLessons(store);
|
|
113
|
+
log.info(TAG, `Found ${existingLessons.length} existing lessons for context`);
|
|
114
|
+
// 3. Fetch PR — error if not MERGED/CLOSED
|
|
115
|
+
log.info(TAG, `Fetching PR #${num}...`);
|
|
116
|
+
const adapter = new GitHubCliPrAdapter(cwd);
|
|
117
|
+
const pr = adapter.fetchPr(num);
|
|
118
|
+
log.info(TAG, `Title: ${pr.title}`);
|
|
119
|
+
const prState = pr.state.toUpperCase();
|
|
120
|
+
if (prState !== 'MERGED' && prState !== 'CLOSED') {
|
|
121
|
+
throw new TotemConfigError(`PR #${num} is ${pr.state}. review-learn only works on merged or closed PRs.`, 'Wait for the PR to be merged, or use `totem extract` for open PRs.', 'CONFIG_INVALID');
|
|
122
|
+
}
|
|
123
|
+
// 4. Fetch review comments
|
|
124
|
+
log.info(TAG, 'Fetching review comments...');
|
|
125
|
+
const reviewComments = adapter.fetchReviewComments(num);
|
|
126
|
+
log.info(TAG, `Found ${reviewComments.length} inline review comments`);
|
|
127
|
+
// 5. Group ALL comments into threads first (need human replies for resolution detection)
|
|
128
|
+
const allThreads = groupIntoThreads(reviewComments);
|
|
129
|
+
// 6. Filter to threads that START with a bot comment
|
|
130
|
+
const threads = allThreads.filter((t) => t.comments.length > 0 && isBotComment(t.comments[0].author));
|
|
131
|
+
if (threads.length === 0) {
|
|
132
|
+
log.dim(TAG, 'No bot review comments found. Nothing to learn from.');
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
log.info(TAG, `Found ${threads.length} bot review thread(s)`);
|
|
136
|
+
// 7. Apply resolution filter
|
|
137
|
+
const findings = extractResolvedBotFindings(threads);
|
|
138
|
+
if (findings.length === 0) {
|
|
139
|
+
log.dim(TAG, 'No resolved bot findings found. Only fixed findings produce lessons.');
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
log.info(TAG, `Found ${findings.length} resolved bot finding(s)`);
|
|
143
|
+
// 8. Resolve system prompt (allow .totem/prompts/review-learn.md override)
|
|
144
|
+
const systemPrompt = getSystemPrompt('review-learn', REVIEW_LEARN_SYSTEM_PROMPT, cwd, config.totemDir);
|
|
145
|
+
// 9. Assemble prompt
|
|
146
|
+
const prompt = assembleReviewLearnPrompt(findings, existingLessons, systemPrompt);
|
|
147
|
+
log.dim(TAG, `Prompt: ${(prompt.length / 1024).toFixed(0)}KB`);
|
|
148
|
+
// 10. Run orchestrator
|
|
149
|
+
const content = await runOrchestrator({
|
|
150
|
+
prompt,
|
|
151
|
+
tag: TAG,
|
|
152
|
+
options,
|
|
153
|
+
config,
|
|
154
|
+
cwd,
|
|
155
|
+
temperature: 0.4,
|
|
156
|
+
customSecrets,
|
|
157
|
+
});
|
|
158
|
+
if (content == null)
|
|
159
|
+
return; // --raw mode — prompt already output
|
|
160
|
+
// 11. Parse lessons from LLM output
|
|
161
|
+
const lessons = parseLessons(content);
|
|
162
|
+
if (lessons.length === 0) {
|
|
163
|
+
log.dim(TAG, 'No lessons extracted from resolved bot findings.');
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
log.success(TAG, `Extracted ${lessons.length} lesson(s)`);
|
|
167
|
+
// 12. Semantic dedup against existing lessons and intra-batch
|
|
168
|
+
log.info(TAG, 'Deduplicating against existing lessons...');
|
|
169
|
+
const { kept: novelLessons, dropped: dupLessons } = await deduplicateLessons(lessons, store, embedder);
|
|
170
|
+
if (dupLessons.length > 0) {
|
|
171
|
+
log.dim(TAG, `Dropped ${dupLessons.length} semantically duplicate lesson(s)`);
|
|
172
|
+
}
|
|
173
|
+
if (novelLessons.length === 0) {
|
|
174
|
+
log.dim(TAG, 'All extracted lessons are duplicates of existing ones.');
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
// 13. Flag suspicious lessons
|
|
178
|
+
const flaggedLessons = flagSuspiciousLessons(novelLessons);
|
|
179
|
+
const suspiciousCount = flaggedLessons.filter((l) => l.suspiciousFlags?.length).length;
|
|
180
|
+
if (suspiciousCount > 0) {
|
|
181
|
+
log.warn(TAG, `${suspiciousCount} lesson(s) flagged as suspicious`);
|
|
182
|
+
}
|
|
183
|
+
log.success(TAG, `Total: ${flaggedLessons.length} nursery lesson(s) from PR #${num}`);
|
|
184
|
+
// --dry-run mode: preview lessons to stdout without writing
|
|
185
|
+
if (options.dryRun) {
|
|
186
|
+
log.dim(TAG, 'Dry run — lessons not written.');
|
|
187
|
+
for (const lesson of flaggedLessons) {
|
|
188
|
+
const prefix = lesson.suspiciousFlags?.length ? '[!] ' : '';
|
|
189
|
+
console.log(`\n ${prefix}Tags: ${sanitize(lesson.tags.join(', ')).replace(/\n/g, ' ')}`); // totem-ignore — stdout for piping
|
|
190
|
+
console.log(` Lifecycle: nursery`); // totem-ignore — stdout for piping
|
|
191
|
+
console.log(` ${sanitize(lesson.text).replace(/\n/g, '\n ')}`); // totem-ignore — stdout for piping
|
|
192
|
+
if (lesson.suspiciousFlags?.length) {
|
|
193
|
+
for (const flag of lesson.suspiciousFlags) {
|
|
194
|
+
console.log(` [!] ${flag}`); // totem-ignore — stdout for piping
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
if (options.yes && suspiciousCount > 0) {
|
|
199
|
+
process.exitCode = 1;
|
|
200
|
+
}
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
// 14. Interactive selection
|
|
204
|
+
if (!options.yes) {
|
|
205
|
+
console.error('');
|
|
206
|
+
log.warn(TAG, 'WARNING: These lessons were extracted from bot review comments. Review each carefully before accepting.');
|
|
207
|
+
log.warn(TAG, 'All accepted lessons will have lifecycle: nursery (unproven until validated).\n');
|
|
208
|
+
for (let i = 0; i < flaggedLessons.length; i++) {
|
|
209
|
+
const lesson = flaggedLessons[i];
|
|
210
|
+
const prefix = lesson.suspiciousFlags?.length ? `[!] ` : '';
|
|
211
|
+
console.error(` [${i + 1}] ${prefix}Tags: ${sanitize(lesson.tags.join(', ')).replace(/\n/g, ' ')}`);
|
|
212
|
+
console.error(` Lifecycle: nursery`);
|
|
213
|
+
console.error(` ${sanitize(lesson.text).replace(/\n/g, '\n ')}`);
|
|
214
|
+
if (lesson.suspiciousFlags?.length) {
|
|
215
|
+
for (const flag of lesson.suspiciousFlags) {
|
|
216
|
+
console.error(` [!] ${flag}`);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
console.error('');
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
const selected = await selectLessons(flaggedLessons, {
|
|
223
|
+
yes: options.yes,
|
|
224
|
+
isTTY: !!process.stdin.isTTY,
|
|
225
|
+
});
|
|
226
|
+
if (selected.length === 0) {
|
|
227
|
+
log.dim(TAG, 'No lessons selected — nothing written.');
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
// Sanitize before persisting
|
|
231
|
+
const sanitizedLessons = selected.map((l) => ({
|
|
232
|
+
tags: l.tags.map((t) => sanitize(t)),
|
|
233
|
+
text: sanitize(l.text),
|
|
234
|
+
heading: l.heading,
|
|
235
|
+
}));
|
|
236
|
+
// 15. Write lessons with lifecycle: nursery frontmatter
|
|
237
|
+
const lessonsDir = path.join(cwd, config.totemDir, 'lessons');
|
|
238
|
+
appendNurseryLessons(sanitizedLessons, lessonsDir);
|
|
239
|
+
log.success(TAG, `Appended ${sanitizedLessons.length} nursery lesson(s) to ${config.totemDir}/lessons/`); // totem-ignore
|
|
240
|
+
// 16. Incremental sync
|
|
241
|
+
log.info(TAG, 'Running incremental sync...');
|
|
242
|
+
const syncResult = await runSync(config, {
|
|
243
|
+
projectRoot: cwd,
|
|
244
|
+
incremental: true,
|
|
245
|
+
onProgress: (msg) => log.dim(TAG, msg),
|
|
246
|
+
});
|
|
247
|
+
log.success(TAG, `Sync complete: ${syncResult.chunksProcessed} chunks from ${syncResult.filesProcessed} files`);
|
|
248
|
+
// Print summary
|
|
249
|
+
console.log(`\nExtracted ${sanitizedLessons.length} nursery lesson(s) from PR #${num}:`);
|
|
250
|
+
for (const lesson of sanitizedLessons) {
|
|
251
|
+
console.log(`\n Tags: ${lesson.tags.join(', ').replace(/\n/g, ' ')}`);
|
|
252
|
+
console.log(` Lifecycle: nursery`);
|
|
253
|
+
console.log(` ${lesson.text.replace(/\n/g, '\n ')}`);
|
|
254
|
+
}
|
|
255
|
+
// Exit non-zero if --yes mode dropped suspicious lessons
|
|
256
|
+
if (options.yes && suspiciousCount > 0) {
|
|
257
|
+
process.exitCode = 1;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
//# sourceMappingURL=review-learn.js.map
|