@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.
@@ -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":"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"}
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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=compile-refresh-manifest.test.d.ts.map
@@ -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":"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"}
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;IACH;;;;;;;;OAQG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAID;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,aAAa,EAAE;IAClD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB,GAAG,MAAM,CAcT;AAID;;;;GAIG;AACH,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,uBAAuB,CAAC;IACpC,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,uBAAuB,CACrC,gBAAgB,EAAE,GAAG,CAAC,MAAM,EAAE,qBAAqB,CAAC,EACpD,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,GACzB;IAAE,KAAK,EAAE,kBAAkB,EAAE,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAclD;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,SAAS,YAAY,EAAE,EAC9B,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,GACzB;IAAE,KAAK,EAAE,YAAY,EAAE,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAG3C;AAED;;;;;;;;;GASG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,YAAY,EAAE,EAAE,IAAI,EAAE,YAAY,GAAG,IAAI,CAO1E;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,YAAY,EAAE,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAGhF;AAsBD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EACzC,MAAM,EAAE,UAAU,GAAG,SAAS,GAAG,QAAQ,GAAG,MAAM,EAClD,UAAU,EAAE,uBAAuB,GAAG,SAAS,EAC/C,KAAK,EAAE,SAAS,eAAe,EAAE,GAAG,SAAS,GAC5C,MAAM,CAiDR;AAwDD,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,cAAc,SAAS,CAAC,CAAC;IAC7B,IAAI,EAAE,cAAc,WAAW,CAAC,CAAC;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC1B,GAAG,EAAE;QAAE,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;KAAE,CAAC;IAClD,mBAAmB,EAAE,cAAc,cAAc,EAAE,mBAAmB,CAAC;IACvE,qBAAqB,EAAE,cAAc,cAAc,EAAE,qBAAqB,CAAC;IAC3E,eAAe,EAAE,cAAc,cAAc,EAAE,eAAe,CAAC;IAC/D,mBAAmB,EAAE,cAAc,cAAc,EAAE,mBAAmB,CAAC;CACxE;AAED,iEAAiE;AACjE,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,WAAW,EACnB,IAAI,EAAE,YAAY,EAClB,IAAI,EAAE,gBAAgB,GACrB,OAAO,CAyBT;AAID,wBAAsB,cAAc,CAClC,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,cAAc,GAAG,cAAc,EAAE,GAAG,IAAI,CAAC,CAu8BnD"}
@@ -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
- const existingFile = options.force
409
- ? { version: 1, rules: [], nonCompilable: [] }
410
- : loadCompiledRulesFile(rulesPath);
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
- const nonCompilableMap = new Map((existingFile.nonCompilable ?? []).map((entry) => [
420
- entry.hash,
421
- { title: entry.title, reasonCode: entry.reasonCode, reason: entry.reason },
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
- if (existingByHash.has(hash))
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.push(manualResult.rule);
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.push(ruleResult.rule);
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 targets: remove the stale copy from newRules for any
767
- // terminal outcome where the rule's state CHANGES (compiled -> new
768
- // pattern replaces old; skipped -> rule moves to nonCompilable and
769
- // must no longer appear as an active rule). For `failed`
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 delete
772
- // work (mmnto/totem#1234 GCA finding).
773
- if (upgradeTargets?.has(lesson.hash) &&
774
- (result.status === 'compiled' || result.status === 'skipped')) {
775
- const staleIdx = newRules.findIndex((r) => r.lessonHash === lesson.hash);
776
- if (staleIdx >= 0)
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.push(result.rule);
913
+ upsertRule(newRules, result.rule);
817
914
  compiled++;
818
915
  logCompiledRule(log, lesson, result.rule);
819
916
  break;