@mmnto/cli 1.15.1 → 1.15.2
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/compile-prune.test.js +56 -1
- package/dist/commands/compile-prune.test.js.map +1 -1
- package/dist/commands/compile-refresh-manifest.test.d.ts +2 -0
- package/dist/commands/compile-refresh-manifest.test.d.ts.map +1 -0
- package/dist/commands/compile-refresh-manifest.test.js +163 -0
- package/dist/commands/compile-refresh-manifest.test.js.map +1 -0
- package/dist/commands/compile.d.ts +30 -0
- package/dist/commands/compile.d.ts.map +1 -1
- package/dist/commands/compile.js +120 -23
- package/dist/commands/compile.js.map +1 -1
- package/dist/commands/lesson-archive.test.d.ts +2 -0
- package/dist/commands/lesson-archive.test.d.ts.map +1 -0
- package/dist/commands/lesson-archive.test.js +192 -0
- package/dist/commands/lesson-archive.test.js.map +1 -0
- package/dist/commands/lesson.d.ts +23 -0
- package/dist/commands/lesson.d.ts.map +1 -1
- package/dist/commands/lesson.js +124 -0
- package/dist/commands/lesson.js.map +1 -1
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { pruneStaleNonCompilable, pruneStaleRules } from './compile.js';
|
|
2
|
+
import { pruneStaleNonCompilable, pruneStaleRules, removeRuleByHash, upsertRule, } from './compile.js';
|
|
3
3
|
// ─── 4-tuple helpers (mmnto-ai/totem#1481) ───────────
|
|
4
4
|
function entry(title, reasonCode = 'out-of-scope', reason) {
|
|
5
5
|
return reason === undefined ? { title, reasonCode } : { title, reasonCode, reason };
|
|
@@ -130,4 +130,59 @@ describe('pruneStaleRules', () => {
|
|
|
130
130
|
expect(rules[1].lessonHash).toBe('stale');
|
|
131
131
|
});
|
|
132
132
|
});
|
|
133
|
+
// ─── upsertRule (mmnto-ai/totem#1587) ────────────────
|
|
134
|
+
describe('upsertRule', () => {
|
|
135
|
+
it('appends when no entry with the same lessonHash exists', () => {
|
|
136
|
+
const rules = [makeRule('abc'), makeRule('def')];
|
|
137
|
+
const fresh = makeRule('ghi', 'New rule');
|
|
138
|
+
upsertRule(rules, fresh);
|
|
139
|
+
expect(rules).toHaveLength(3);
|
|
140
|
+
expect(rules[2]).toBe(fresh);
|
|
141
|
+
});
|
|
142
|
+
it('replaces in-place when an entry with the same lessonHash exists', () => {
|
|
143
|
+
// Order preservation is load-bearing: re-compiling a mid-array rule
|
|
144
|
+
// under --force must NOT move it to the end. Appending instead of
|
|
145
|
+
// replacing causes diff churn in compiled-rules.json on every force
|
|
146
|
+
// run (CR finding on PR mmnto-ai/totem#1629).
|
|
147
|
+
const ruleA = makeRule('abc', 'Old A');
|
|
148
|
+
const ruleB = makeRule('def', 'B');
|
|
149
|
+
const ruleC = makeRule('ghi', 'C');
|
|
150
|
+
const rules = [ruleA, ruleB, ruleC];
|
|
151
|
+
const replacement = makeRule('abc', 'New A');
|
|
152
|
+
upsertRule(rules, replacement);
|
|
153
|
+
expect(rules).toHaveLength(3);
|
|
154
|
+
expect(rules[0]).toBe(replacement);
|
|
155
|
+
expect(rules[0].lessonHeading).toBe('New A');
|
|
156
|
+
expect(rules[1]).toBe(ruleB);
|
|
157
|
+
expect(rules[2]).toBe(ruleC);
|
|
158
|
+
});
|
|
159
|
+
it('is idempotent when called twice with the same rule object', () => {
|
|
160
|
+
const rules = [makeRule('abc', 'Original')];
|
|
161
|
+
const replacement = makeRule('abc', 'Updated');
|
|
162
|
+
upsertRule(rules, replacement);
|
|
163
|
+
upsertRule(rules, replacement);
|
|
164
|
+
expect(rules).toHaveLength(1);
|
|
165
|
+
expect(rules[0]).toBe(replacement);
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
// ─── removeRuleByHash (mmnto-ai/totem#1587 + mmnto-ai/totem#1629 cloud parity) ──
|
|
169
|
+
describe('removeRuleByHash', () => {
|
|
170
|
+
it('removes the matching rule in place and preserves order of the rest', () => {
|
|
171
|
+
const ruleA = makeRule('abc', 'A');
|
|
172
|
+
const ruleB = makeRule('def', 'B');
|
|
173
|
+
const ruleC = makeRule('ghi', 'C');
|
|
174
|
+
const rules = [ruleA, ruleB, ruleC];
|
|
175
|
+
removeRuleByHash(rules, 'def');
|
|
176
|
+
expect(rules).toHaveLength(2);
|
|
177
|
+
expect(rules[0]).toBe(ruleA);
|
|
178
|
+
expect(rules[1]).toBe(ruleC);
|
|
179
|
+
});
|
|
180
|
+
it('is a no-op when the hash does not match any rule', () => {
|
|
181
|
+
const rules = [makeRule('abc'), makeRule('def')];
|
|
182
|
+
removeRuleByHash(rules, 'missing');
|
|
183
|
+
expect(rules).toHaveLength(2);
|
|
184
|
+
expect(rules[0].lessonHash).toBe('abc');
|
|
185
|
+
expect(rules[1].lessonHash).toBe('def');
|
|
186
|
+
});
|
|
187
|
+
});
|
|
133
188
|
//# sourceMappingURL=compile-prune.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compile-prune.test.js","sourceRoot":"","sources":["../../src/commands/compile-prune.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAK9C,OAAO,
|
|
1
|
+
{"version":3,"file":"compile-prune.test.js","sourceRoot":"","sources":["../../src/commands/compile-prune.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compile-refresh-manifest.test.d.ts","sourceRoot":"","sources":["../../src/commands/compile-refresh-manifest.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as os from 'node:os';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
5
|
+
import { generateOutputHash, hashLesson, readCompileManifest } from '@mmnto/totem';
|
|
6
|
+
import { cleanTmpDir } from '../test-utils.js';
|
|
7
|
+
import { compileCommand } from './compile.js';
|
|
8
|
+
// ─── Helpers ─────────────────────────────────────────
|
|
9
|
+
//
|
|
10
|
+
// These tests exercise the `--refresh-manifest` primitive (mmnto-ai/totem#1587).
|
|
11
|
+
// The flag must recompute output_hash from the current compiled-rules.json
|
|
12
|
+
// state without invoking the LLM or touching any lessons. It is the non-LLM
|
|
13
|
+
// complement to #1348's input-hash drift refresh, covering the postmerge
|
|
14
|
+
// inline-archive workflow where a rule's `status: 'archived'` field is set
|
|
15
|
+
// directly by a curation script.
|
|
16
|
+
function makeTmpDir() {
|
|
17
|
+
return fs.mkdtempSync(path.join(os.tmpdir(), 'totem-refresh-manifest-'));
|
|
18
|
+
}
|
|
19
|
+
function lessonMarkdown(heading, body) {
|
|
20
|
+
return `## Lesson — ${heading}\n\n**Tags:** test\n\n${body}\n`;
|
|
21
|
+
}
|
|
22
|
+
function setupWorkspace(tmpDir, options) {
|
|
23
|
+
fs.writeFileSync(path.join(tmpDir, 'totem.config.ts'), [
|
|
24
|
+
'export default {',
|
|
25
|
+
' targets: [{ glob: "**/*.ts", type: "code", strategy: "typescript-ast" }],',
|
|
26
|
+
' totemDir: ".totem",',
|
|
27
|
+
' orchestrator: {',
|
|
28
|
+
' provider: "shell",',
|
|
29
|
+
' command: "echo should-never-run",',
|
|
30
|
+
' defaultModel: "test-model",',
|
|
31
|
+
' },',
|
|
32
|
+
'};',
|
|
33
|
+
'',
|
|
34
|
+
].join('\n'), 'utf-8');
|
|
35
|
+
const totemDir = path.join(tmpDir, '.totem');
|
|
36
|
+
const lessonsDir = path.join(totemDir, 'lessons');
|
|
37
|
+
fs.mkdirSync(lessonsDir, { recursive: true });
|
|
38
|
+
for (const [name, body] of Object.entries(options.lessons)) {
|
|
39
|
+
fs.writeFileSync(path.join(lessonsDir, name), body, 'utf-8');
|
|
40
|
+
}
|
|
41
|
+
const rulesPath = path.join(totemDir, 'compiled-rules.json');
|
|
42
|
+
const now = '2026-04-22T00:00:00Z';
|
|
43
|
+
fs.writeFileSync(rulesPath, JSON.stringify({
|
|
44
|
+
version: 1,
|
|
45
|
+
rules: options.rules.map((r) => ({
|
|
46
|
+
lessonHash: r.lessonHash,
|
|
47
|
+
lessonHeading: r.lessonHeading,
|
|
48
|
+
pattern: 'dummy-never-matches',
|
|
49
|
+
message: r.lessonHeading,
|
|
50
|
+
engine: 'regex',
|
|
51
|
+
compiledAt: now,
|
|
52
|
+
...(r.status ? { status: r.status } : {}),
|
|
53
|
+
})),
|
|
54
|
+
nonCompilable: [],
|
|
55
|
+
}, null, 2) + '\n', 'utf-8');
|
|
56
|
+
// Write manifest with an output_hash that matches the rules file as written.
|
|
57
|
+
// Tests that want drift will mutate the rules file AFTER setupWorkspace.
|
|
58
|
+
const manifestPath = path.join(totemDir, 'compile-manifest.json');
|
|
59
|
+
fs.writeFileSync(manifestPath, JSON.stringify({
|
|
60
|
+
compiled_at: now,
|
|
61
|
+
model: 'test-model',
|
|
62
|
+
input_hash: '0000000000000000000000000000000000000000000000000000000000000000',
|
|
63
|
+
output_hash: generateOutputHash(rulesPath),
|
|
64
|
+
rule_count: options.rules.length,
|
|
65
|
+
}, null, 2) + '\n', 'utf-8');
|
|
66
|
+
}
|
|
67
|
+
describe('compileCommand --refresh-manifest (#1587)', () => {
|
|
68
|
+
let tmpDir;
|
|
69
|
+
let originalCwd;
|
|
70
|
+
beforeEach(() => {
|
|
71
|
+
tmpDir = makeTmpDir();
|
|
72
|
+
originalCwd = process.cwd();
|
|
73
|
+
process.chdir(tmpDir);
|
|
74
|
+
});
|
|
75
|
+
afterEach(() => {
|
|
76
|
+
process.chdir(originalCwd);
|
|
77
|
+
cleanTmpDir(tmpDir);
|
|
78
|
+
});
|
|
79
|
+
it('updates manifest output_hash without invoking compilation when --refresh-manifest is passed', async () => {
|
|
80
|
+
// Scenario: a postmerge archive script mutates compiled-rules.json in
|
|
81
|
+
// place (flipping status to 'archived'). The output_hash in the manifest
|
|
82
|
+
// is now stale. `--refresh-manifest` must recompute it without running
|
|
83
|
+
// the LLM or touching lessons.
|
|
84
|
+
const heading = 'Use err in catch';
|
|
85
|
+
const body = 'Do not use the identifier "error" in catch blocks.';
|
|
86
|
+
const lessonHash = hashLesson(heading, body);
|
|
87
|
+
setupWorkspace(tmpDir, {
|
|
88
|
+
lessons: { 'use-err.md': lessonMarkdown(heading, body) },
|
|
89
|
+
rules: [{ lessonHash, lessonHeading: heading }],
|
|
90
|
+
});
|
|
91
|
+
const totemDir = path.join(tmpDir, '.totem');
|
|
92
|
+
const rulesPath = path.join(totemDir, 'compiled-rules.json');
|
|
93
|
+
const manifestPath = path.join(totemDir, 'compile-manifest.json');
|
|
94
|
+
const manifestBefore = readCompileManifest(manifestPath);
|
|
95
|
+
const hashBefore = manifestBefore.output_hash;
|
|
96
|
+
// Simulate the postmerge archive-in-place: mutate compiled-rules.json
|
|
97
|
+
// directly (flip status to 'archived' on the only rule).
|
|
98
|
+
const rulesJson = JSON.parse(fs.readFileSync(rulesPath, 'utf-8'));
|
|
99
|
+
rulesJson.rules[0].status = 'archived';
|
|
100
|
+
rulesJson.rules[0].archivedReason = 'test archive';
|
|
101
|
+
rulesJson.rules[0].archivedAt = '2026-04-23T00:00:00Z';
|
|
102
|
+
fs.writeFileSync(rulesPath, JSON.stringify(rulesJson, null, 2) + '\n', 'utf-8');
|
|
103
|
+
const rulesFileHashAfterMutation = generateOutputHash(rulesPath);
|
|
104
|
+
expect(rulesFileHashAfterMutation).not.toBe(hashBefore);
|
|
105
|
+
await compileCommand({ refreshManifest: true });
|
|
106
|
+
const manifestAfter = readCompileManifest(manifestPath);
|
|
107
|
+
expect(manifestAfter.output_hash).toBe(rulesFileHashAfterMutation);
|
|
108
|
+
});
|
|
109
|
+
it('is a no-op when the manifest is already fresh', async () => {
|
|
110
|
+
// Baseline: rules and manifest agree. `--refresh-manifest` must not
|
|
111
|
+
// write the manifest back (no compiled_at bump, no byte change).
|
|
112
|
+
const heading = 'Use err in catch';
|
|
113
|
+
const body = 'Do not use the identifier "error" in catch blocks.';
|
|
114
|
+
const lessonHash = hashLesson(heading, body);
|
|
115
|
+
setupWorkspace(tmpDir, {
|
|
116
|
+
lessons: { 'use-err.md': lessonMarkdown(heading, body) },
|
|
117
|
+
rules: [{ lessonHash, lessonHeading: heading }],
|
|
118
|
+
});
|
|
119
|
+
const totemDir = path.join(tmpDir, '.totem');
|
|
120
|
+
const manifestPath = path.join(totemDir, 'compile-manifest.json');
|
|
121
|
+
const manifestBytesBefore = fs.readFileSync(manifestPath, 'utf-8');
|
|
122
|
+
await compileCommand({ refreshManifest: true });
|
|
123
|
+
const manifestBytesAfter = fs.readFileSync(manifestPath, 'utf-8');
|
|
124
|
+
expect(manifestBytesAfter).toBe(manifestBytesBefore);
|
|
125
|
+
});
|
|
126
|
+
it('throws TotemConfigError when combined with --force', async () => {
|
|
127
|
+
// Strict exclusivity: --refresh-manifest is a no-LLM primitive and
|
|
128
|
+
// cannot combine with --force (an LLM-invoking regenerate). The combo
|
|
129
|
+
// is incoherent; fail loud rather than pick a silent resolution.
|
|
130
|
+
const heading = 'Use err in catch';
|
|
131
|
+
const body = 'Do not use the identifier "error" in catch blocks.';
|
|
132
|
+
const lessonHash = hashLesson(heading, body);
|
|
133
|
+
setupWorkspace(tmpDir, {
|
|
134
|
+
lessons: { 'use-err.md': lessonMarkdown(heading, body) },
|
|
135
|
+
rules: [{ lessonHash, lessonHeading: heading }],
|
|
136
|
+
});
|
|
137
|
+
await expect(compileCommand({ refreshManifest: true, force: true })).rejects.toMatchObject({
|
|
138
|
+
code: 'CONFIG_INVALID',
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
it('leaves the rules file untouched on a successful refresh', async () => {
|
|
142
|
+
// The primitive must be read-only w.r.t. compiled-rules.json. Only the
|
|
143
|
+
// manifest is written. Byte-for-byte equality is the strictest check.
|
|
144
|
+
const heading = 'Use err in catch';
|
|
145
|
+
const body = 'Do not use the identifier "error" in catch blocks.';
|
|
146
|
+
const lessonHash = hashLesson(heading, body);
|
|
147
|
+
setupWorkspace(tmpDir, {
|
|
148
|
+
lessons: { 'use-err.md': lessonMarkdown(heading, body) },
|
|
149
|
+
rules: [{ lessonHash, lessonHeading: heading, status: 'archived' }],
|
|
150
|
+
});
|
|
151
|
+
const totemDir = path.join(tmpDir, '.totem');
|
|
152
|
+
const rulesPath = path.join(totemDir, 'compiled-rules.json');
|
|
153
|
+
// Mutate the rules file so there is drift to refresh against.
|
|
154
|
+
const rulesJson = JSON.parse(fs.readFileSync(rulesPath, 'utf-8'));
|
|
155
|
+
rulesJson.rules[0].archivedReason = 'test archive drift';
|
|
156
|
+
fs.writeFileSync(rulesPath, JSON.stringify(rulesJson, null, 2) + '\n', 'utf-8');
|
|
157
|
+
const rulesBytesBefore = fs.readFileSync(rulesPath, 'utf-8');
|
|
158
|
+
await compileCommand({ refreshManifest: true });
|
|
159
|
+
const rulesBytesAfter = fs.readFileSync(rulesPath, 'utf-8');
|
|
160
|
+
expect(rulesBytesAfter).toBe(rulesBytesBefore);
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
//# sourceMappingURL=compile-refresh-manifest.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compile-refresh-manifest.test.js","sourceRoot":"","sources":["../../src/commands/compile-refresh-manifest.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -52,6 +52,16 @@ export interface CompileOptions {
|
|
|
52
52
|
/** Telemetry directive to inject into the Pipeline 2 prompt for this lesson. */
|
|
53
53
|
telemetryPrefix?: string;
|
|
54
54
|
}>;
|
|
55
|
+
/**
|
|
56
|
+
* Recompute `compile-manifest.json`'s `output_hash` from the current
|
|
57
|
+
* `compiled-rules.json` state without invoking the LLM or touching any
|
|
58
|
+
* lessons (mmnto-ai/totem#1587). Exists to support the postmerge
|
|
59
|
+
* inline-archive workflow where a curation script mutates
|
|
60
|
+
* `status: 'archived'` on a rule directly; `--refresh-manifest` is the
|
|
61
|
+
* blessed way to re-sync the manifest afterwards. Cannot combine with
|
|
62
|
+
* `--force`.
|
|
63
|
+
*/
|
|
64
|
+
refreshManifest?: boolean;
|
|
55
65
|
}
|
|
56
66
|
/**
|
|
57
67
|
* Build the directive injected into the Sonnet system prompt for `--upgrade`.
|
|
@@ -112,6 +122,26 @@ export declare function pruneStaleRules(rules: readonly CompiledRule[], currentH
|
|
|
112
122
|
fresh: CompiledRule[];
|
|
113
123
|
pruned: number;
|
|
114
124
|
};
|
|
125
|
+
/**
|
|
126
|
+
* Replace-by-lessonHash if an entry with the same hash is already in the
|
|
127
|
+
* array; otherwise append. Preserves array order for existing entries so
|
|
128
|
+
* the compile loop's output stays stable across runs.
|
|
129
|
+
*
|
|
130
|
+
* Used by the --force durability path (mmnto-ai/totem#1587) and the
|
|
131
|
+
* non-force add-new-rule path: all success-side pushes go through this
|
|
132
|
+
* helper so transient compile failures leave old rules intact and
|
|
133
|
+
* repeated successes do not double-insert.
|
|
134
|
+
*/
|
|
135
|
+
export declare function upsertRule(rules: CompiledRule[], rule: CompiledRule): void;
|
|
136
|
+
/**
|
|
137
|
+
* Remove the first rule matching `lessonHash` from `rules`, in place.
|
|
138
|
+
* No-op when no match. Used on the `--force` / upgrade skipped paths in
|
|
139
|
+
* both local and cloud workers: when a lesson transitions to
|
|
140
|
+
* non-compilable, any pre-existing rule for the same hash must be evicted
|
|
141
|
+
* from the active set, otherwise --force leaves the old rule alive while
|
|
142
|
+
* also marking the hash non-compilable (mmnto-ai/totem#1629 CR finding).
|
|
143
|
+
*/
|
|
144
|
+
export declare function removeRuleByHash(rules: CompiledRule[], lessonHash: string): void;
|
|
115
145
|
/**
|
|
116
146
|
* Format a lesson's trace array into a single multi-line block for the
|
|
117
147
|
* `--verbose` renderer. Returns a string (no trailing newline — caller
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compile.d.ts","sourceRoot":"","sources":["../../src/commands/compile.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EAEZ,eAAe,EACf,WAAW,EACX,kBAAkB,EAClB,uBAAuB,EACxB,MAAM,cAAc,CAAC;AAYtB;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,aAAa,GAAG,UAAU,GAAG,SAAS,GAAG,MAAM,GAAG,QAAQ,CAAC;AAEvE,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,aAAa,CAAC;CACvB;AAED,MAAM,WAAW,cAAc;IAC7B,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;OAKG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;;;OAKG;IACH,YAAY,CAAC,EAAE,KAAK,CAAC;QACnB,IAAI,EAAE,MAAM,CAAC;QACb,gFAAgF;QAChF,eAAe,CAAC,EAAE,MAAM,CAAC;KAC1B,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"compile.d.ts","sourceRoot":"","sources":["../../src/commands/compile.ts"],"names":[],"mappings":"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"}
|
package/dist/commands/compile.js
CHANGED
|
@@ -72,6 +72,38 @@ export function pruneStaleRules(rules, currentHashes) {
|
|
|
72
72
|
const fresh = rules.filter((r) => currentHashes.has(r.lessonHash));
|
|
73
73
|
return { fresh, pruned: rules.length - fresh.length };
|
|
74
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* Replace-by-lessonHash if an entry with the same hash is already in the
|
|
77
|
+
* array; otherwise append. Preserves array order for existing entries so
|
|
78
|
+
* the compile loop's output stays stable across runs.
|
|
79
|
+
*
|
|
80
|
+
* Used by the --force durability path (mmnto-ai/totem#1587) and the
|
|
81
|
+
* non-force add-new-rule path: all success-side pushes go through this
|
|
82
|
+
* helper so transient compile failures leave old rules intact and
|
|
83
|
+
* repeated successes do not double-insert.
|
|
84
|
+
*/
|
|
85
|
+
export function upsertRule(rules, rule) {
|
|
86
|
+
const idx = rules.findIndex((r) => r.lessonHash === rule.lessonHash);
|
|
87
|
+
if (idx >= 0) {
|
|
88
|
+
rules[idx] = rule;
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
rules.push(rule);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Remove the first rule matching `lessonHash` from `rules`, in place.
|
|
96
|
+
* No-op when no match. Used on the `--force` / upgrade skipped paths in
|
|
97
|
+
* both local and cloud workers: when a lesson transitions to
|
|
98
|
+
* non-compilable, any pre-existing rule for the same hash must be evicted
|
|
99
|
+
* from the active set, otherwise --force leaves the old rule alive while
|
|
100
|
+
* also marking the hash non-compilable (mmnto-ai/totem#1629 CR finding).
|
|
101
|
+
*/
|
|
102
|
+
export function removeRuleByHash(rules, lessonHash) {
|
|
103
|
+
const idx = rules.findIndex((r) => r.lessonHash === lessonHash);
|
|
104
|
+
if (idx >= 0)
|
|
105
|
+
rules.splice(idx, 1);
|
|
106
|
+
}
|
|
75
107
|
// ─── Verbose trace renderer (mmnto-ai/totem#1482) ──
|
|
76
108
|
/**
|
|
77
109
|
* Map a numeric layer from a trace event to its pipeline label. Tolerates
|
|
@@ -247,6 +279,41 @@ export async function compileCommand(options) {
|
|
|
247
279
|
const config = await loadConfig(configPath);
|
|
248
280
|
const totemDir = path.join(cwd, config.totemDir);
|
|
249
281
|
const rulesPath = path.join(totemDir, COMPILED_RULES_FILE);
|
|
282
|
+
// ─── --refresh-manifest primitive (mmnto-ai/totem#1587) ─────────
|
|
283
|
+
// No-LLM path that recomputes `output_hash` from current
|
|
284
|
+
// `compiled-rules.json` state. Supports the postmerge inline-archive
|
|
285
|
+
// workflow where a curation script mutates `status: 'archived'` on a
|
|
286
|
+
// rule directly. Preflights the manifest read before any write
|
|
287
|
+
// (mmnto-ai/totem#1601 CR pattern): missing/corrupt manifest fails
|
|
288
|
+
// loud without side effects.
|
|
289
|
+
if (options.refreshManifest) {
|
|
290
|
+
if (options.force) {
|
|
291
|
+
throw new TotemConfigError('--refresh-manifest cannot be combined with --force.', '--refresh-manifest is a no-LLM primitive that only recomputes output_hash. Use one or the other, not both.', 'CONFIG_INVALID');
|
|
292
|
+
}
|
|
293
|
+
const manifestPath = path.join(totemDir, 'compile-manifest.json');
|
|
294
|
+
if (!fs.existsSync(rulesPath)) {
|
|
295
|
+
throw new TotemError('NO_RULES', `No compiled-rules.json at ${rulesPath}.`, "Run 'totem lesson compile' first to generate the rules file.");
|
|
296
|
+
}
|
|
297
|
+
// Validate compiled-rules.json by parsing it through the schema
|
|
298
|
+
// BEFORE refreshing the manifest. Without this, a corrupt rules file
|
|
299
|
+
// gets its new byte-level hash written to the manifest and
|
|
300
|
+
// verify-manifest stops surfacing the corruption — silent drift.
|
|
301
|
+
// loadCompiledRulesFile throws TotemParseError on malformed JSON or
|
|
302
|
+
// schema violations (CR finding on PR mmnto-ai/totem#1629).
|
|
303
|
+
const compiledRulesFile = loadCompiledRulesFile(rulesPath);
|
|
304
|
+
const compileManifest = readCompileManifest(manifestPath);
|
|
305
|
+
const freshOutputHash = generateOutputHash(rulesPath);
|
|
306
|
+
if (compileManifest.output_hash === freshOutputHash) {
|
|
307
|
+
log.info(TAG, 'Manifest already fresh — no changes.');
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
compileManifest.output_hash = freshOutputHash;
|
|
311
|
+
compileManifest.compiled_at = new Date().toISOString();
|
|
312
|
+
compileManifest.rule_count = compiledRulesFile.rules.length;
|
|
313
|
+
writeCompileManifest(manifestPath, compileManifest);
|
|
314
|
+
log.success(TAG, `Manifest refreshed: output_hash ${freshOutputHash.slice(0, 8)}…`);
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
250
317
|
const lessons = readAllLessons(totemDir);
|
|
251
318
|
// Ingest cursor instructions if --from-cursor
|
|
252
319
|
if (options.fromCursor) {
|
|
@@ -405,9 +472,13 @@ export async function compileCommand(options) {
|
|
|
405
472
|
const upgradeOutcomes = new Map(upgradeTargets ? [...upgradeTargets.keys()].map((h) => [h, 'noop']) : []);
|
|
406
473
|
// ─── Phase 1: Regex compilation (requires orchestrator) ──
|
|
407
474
|
if (config.orchestrator) {
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
475
|
+
// Always load the existing file so lifecycle fields (status,
|
|
476
|
+
// archivedReason, archivedAt) survive --force recompile (mmnto-ai/
|
|
477
|
+
// totem#1587). The cache-skip logic below gates on !options.force so
|
|
478
|
+
// every lesson still goes through the compile loop under --force;
|
|
479
|
+
// buildCompiledRule then pulls the lifecycle fields from `existing`
|
|
480
|
+
// onto the new rule via preserveLifecycleFields.
|
|
481
|
+
const existingFile = loadCompiledRulesFile(rulesPath);
|
|
411
482
|
const existingRules = existingFile.rules;
|
|
412
483
|
const existingByHash = new Map(existingRules.map((r) => [r.lessonHash, r]));
|
|
413
484
|
// mmnto/totem#1280 + mmnto-ai/totem#1481: in-memory nonCompilable is
|
|
@@ -416,10 +487,16 @@ export async function compileCommand(options) {
|
|
|
416
487
|
// strings and 2-tuples to the 4-tuple shape (reasonCode:
|
|
417
488
|
// 'legacy-unknown') before we reach this block, so existingFile.
|
|
418
489
|
// nonCompilable is always 4-tuple-shaped here.
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
490
|
+
//
|
|
491
|
+
// --force resets the nonCompilable ledger so previously-failed
|
|
492
|
+
// lessons get re-attempted with whatever prompt improvements landed.
|
|
493
|
+
// Failures that happen this pass re-populate the map.
|
|
494
|
+
const nonCompilableMap = new Map(options.force
|
|
495
|
+
? []
|
|
496
|
+
: (existingFile.nonCompilable ?? []).map((entry) => [
|
|
497
|
+
entry.hash,
|
|
498
|
+
{ title: entry.title, reasonCode: entry.reasonCode, reason: entry.reason },
|
|
499
|
+
]));
|
|
423
500
|
// Note: we do NOT delete the --upgrade target from existingByHash here.
|
|
424
501
|
// buildCompiledRule in @mmnto/totem looks up the old entry to preserve
|
|
425
502
|
// metadata (createdAt, audit lineage). Deleting would make the upgraded
|
|
@@ -434,9 +511,13 @@ export async function compileCommand(options) {
|
|
|
434
511
|
for (const lesson of lessonsInScope) {
|
|
435
512
|
const hash = hashLesson(lesson.heading, lesson.body);
|
|
436
513
|
if (!upgradeTargets?.has(hash)) {
|
|
437
|
-
|
|
514
|
+
// --force bypasses both caches: every lesson re-enters the
|
|
515
|
+
// compile loop so pattern regenerates, while buildCompiledRule
|
|
516
|
+
// pulls lifecycle fields forward from the existingByHash lookup
|
|
517
|
+
// (mmnto-ai/totem#1587).
|
|
518
|
+
if (!options.force && existingByHash.has(hash))
|
|
438
519
|
continue; // already compiled
|
|
439
|
-
if (nonCompilableMap.has(hash))
|
|
520
|
+
if (!options.force && nonCompilableMap.has(hash))
|
|
440
521
|
continue; // cached as non-compilable
|
|
441
522
|
}
|
|
442
523
|
toCompile.push({ index: lesson.index, heading: lesson.heading, body: lesson.body, hash });
|
|
@@ -554,6 +635,13 @@ export async function compileCommand(options) {
|
|
|
554
635
|
let skipped = 0;
|
|
555
636
|
let failed = 0;
|
|
556
637
|
const skippedLessons = [];
|
|
638
|
+
// Always initialize newRules from existingRules so transient compile
|
|
639
|
+
// failures (network/rate-limit/manual reject/example-verification/
|
|
640
|
+
// cloud parse) under --force do NOT silently drop rules. Each push
|
|
641
|
+
// site uses upsertRule below to replace-by-lessonHash on successful
|
|
642
|
+
// compile, so the old rule survives when a new rule fails to
|
|
643
|
+
// produce (CR finding on PR mmnto-ai/totem#1629). Dangling-archive guard still
|
|
644
|
+
// runs via the currentHashes filter below.
|
|
557
645
|
const newRules = [...existingRules];
|
|
558
646
|
const currentHashes = new Set(lessons.map((l) => hashLesson(l.heading, l.body)));
|
|
559
647
|
const freshRules = newRules.filter((r) => currentHashes.has(r.lessonHash));
|
|
@@ -612,7 +700,7 @@ export async function compileCommand(options) {
|
|
|
612
700
|
log.warn(TAG, `[${lesson.heading}] Downgraded to warning — no test fixture (ADR-065)`);
|
|
613
701
|
}
|
|
614
702
|
}
|
|
615
|
-
newRules
|
|
703
|
+
upsertRule(newRules, manualResult.rule);
|
|
616
704
|
compiled++;
|
|
617
705
|
logCompiledRule(log, lesson, manualResult.rule);
|
|
618
706
|
}
|
|
@@ -685,6 +773,14 @@ export async function compileCommand(options) {
|
|
|
685
773
|
// mmnto-ai/totem#1481: the cloud worker currently classifies every
|
|
686
774
|
// compilable:false outcome as out-of-scope. Granular cloud-side
|
|
687
775
|
// reasonCodes are out of scope here and track via mmnto/totem#1221.
|
|
776
|
+
//
|
|
777
|
+
// Under --force / upgrade, evict any stale active-rule entry so
|
|
778
|
+
// we don't leave the old rule alive while also marking the same
|
|
779
|
+
// hash non-compilable (mmnto-ai/totem#1629 CR finding — symmetry
|
|
780
|
+
// with the local skipped path).
|
|
781
|
+
if (upgradeTargets?.has(lesson.hash) || options.force) {
|
|
782
|
+
removeRuleByHash(newRules, lesson.hash);
|
|
783
|
+
}
|
|
688
784
|
nonCompilableMap.set(lesson.hash, {
|
|
689
785
|
title: lesson.heading,
|
|
690
786
|
reasonCode: 'out-of-scope',
|
|
@@ -703,7 +799,7 @@ export async function compileCommand(options) {
|
|
|
703
799
|
failed++;
|
|
704
800
|
continue;
|
|
705
801
|
}
|
|
706
|
-
newRules
|
|
802
|
+
upsertRule(newRules, ruleResult.rule);
|
|
707
803
|
compiled++;
|
|
708
804
|
logCompiledRule(log, lesson, ruleResult.rule);
|
|
709
805
|
}
|
|
@@ -763,18 +859,19 @@ export async function compileCommand(options) {
|
|
|
763
859
|
const block = formatVerboseTraceBlock(lesson, result.status, reasonCode, resultTrace);
|
|
764
860
|
process.stdout.write(block + '\n');
|
|
765
861
|
}
|
|
766
|
-
// Upgrade
|
|
767
|
-
//
|
|
768
|
-
//
|
|
769
|
-
//
|
|
862
|
+
// Upgrade and --force: remove the stale copy from newRules when
|
|
863
|
+
// the rule moves to nonCompilable (status === 'skipped') and
|
|
864
|
+
// must no longer appear as an active rule. The `compiled` case
|
|
865
|
+
// is handled by upsertRule below (replace-by-lessonHash in
|
|
866
|
+
// place, preserving array order — splicing here would defeat
|
|
867
|
+
// that by forcing upsertRule to append). For `failed`
|
|
770
868
|
// (transient error) and `noop` (no change), leave the old rule
|
|
771
|
-
// intact so a flaky network / rate-limit doesn't silently
|
|
772
|
-
// work (mmnto/totem#1234 GCA finding
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
newRules.splice(staleIdx, 1);
|
|
869
|
+
// intact so a flaky network / rate-limit doesn't silently
|
|
870
|
+
// delete work (mmnto/totem#1234 GCA finding; mmnto-ai/totem#1587
|
|
871
|
+
// extension to cover --force).
|
|
872
|
+
if ((upgradeTargets?.has(lesson.hash) || options.force) &&
|
|
873
|
+
result.status === 'skipped') {
|
|
874
|
+
removeRuleByHash(newRules, lesson.hash);
|
|
778
875
|
}
|
|
779
876
|
// Record the terminal outcome for each upgrade target. Used by
|
|
780
877
|
// `totem doctor --pr` to distinguish real replacements from
|
|
@@ -813,7 +910,7 @@ export async function compileCommand(options) {
|
|
|
813
910
|
if (upgradeTargets?.has(lesson.hash)) {
|
|
814
911
|
nonCompilableMap.delete(lesson.hash);
|
|
815
912
|
}
|
|
816
|
-
newRules
|
|
913
|
+
upsertRule(newRules, result.rule);
|
|
817
914
|
compiled++;
|
|
818
915
|
logCompiledRule(log, lesson, result.rule);
|
|
819
916
|
break;
|