@mmnto/cli 1.14.11 → 1.14.12

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,9 @@
1
1
  import { describe, expect, it } from 'vitest';
2
2
  import { pruneStaleNonCompilable, pruneStaleRules } from './compile.js';
3
+ // ─── 4-tuple helpers (mmnto-ai/totem#1481) ───────────
4
+ function entry(title, reasonCode = 'out-of-scope', reason) {
5
+ return reason === undefined ? { title, reasonCode } : { title, reasonCode, reason };
6
+ }
3
7
  // ─── Test helpers ────────────────────────────────────
4
8
  function makeRule(lessonHash, heading = `Heading for ${lessonHash}`) {
5
9
  return {
@@ -19,51 +23,64 @@ describe('pruneStaleNonCompilable', () => {
19
23
  });
20
24
  it('returns all entries when every hash is still current', () => {
21
25
  const map = new Map([
22
- ['abc', 'First lesson'],
23
- ['def', 'Second lesson'],
26
+ ['abc', entry('First lesson')],
27
+ ['def', entry('Second lesson', 'missing-badexample')],
24
28
  ]);
25
29
  const current = new Set(['abc', 'def']);
26
30
  const result = pruneStaleNonCompilable(map, current);
27
31
  expect(result.fresh).toEqual([
28
- { hash: 'abc', title: 'First lesson' },
29
- { hash: 'def', title: 'Second lesson' },
32
+ { hash: 'abc', title: 'First lesson', reasonCode: 'out-of-scope' },
33
+ { hash: 'def', title: 'Second lesson', reasonCode: 'missing-badexample' },
30
34
  ]);
31
35
  expect(result.drained).toBe(0);
32
36
  });
33
37
  it('drops entries whose hashes are no longer present in current lessons', () => {
34
38
  const map = new Map([
35
- ['abc', 'Kept lesson'],
36
- ['stale1', 'Removed lesson A'],
37
- ['stale2', 'Removed lesson B'],
39
+ ['abc', entry('Kept lesson')],
40
+ ['stale1', entry('Removed lesson A')],
41
+ ['stale2', entry('Removed lesson B')],
38
42
  ]);
39
43
  const current = new Set(['abc']);
40
44
  const result = pruneStaleNonCompilable(map, current);
41
- expect(result.fresh).toEqual([{ hash: 'abc', title: 'Kept lesson' }]);
45
+ expect(result.fresh).toEqual([
46
+ { hash: 'abc', title: 'Kept lesson', reasonCode: 'out-of-scope' },
47
+ ]);
42
48
  expect(result.drained).toBe(2);
43
49
  });
44
50
  it('drains everything when no hashes are current', () => {
45
51
  const map = new Map([
46
- ['stale1', 'Removed A'],
47
- ['stale2', 'Removed B'],
52
+ ['stale1', entry('Removed A')],
53
+ ['stale2', entry('Removed B')],
48
54
  ]);
49
55
  const current = new Set();
50
56
  const result = pruneStaleNonCompilable(map, current);
51
57
  expect(result.fresh).toEqual([]);
52
58
  expect(result.drained).toBe(2);
53
59
  });
54
- it('preserves tuple shape including titles from legacy-normalized entries', () => {
55
- // Legacy string-form entries get normalized to {hash, title: '(legacy entry)'}
56
- // by the schema transform. The prune helper must preserve that title verbatim.
57
- const map = new Map([['legacy-hash', '(legacy entry)']]);
58
- const current = new Set(['legacy-hash']);
60
+ it('preserves reasonCode and reason fields through the prune', () => {
61
+ // mmnto-ai/totem#1481 invariant #8: the 4-tuple must survive the prune
62
+ // intact, not collapse back to a 2-tuple.
63
+ const map = new Map([
64
+ ['hash-a', entry('Legacy entry', 'legacy-unknown')],
65
+ ['hash-b', entry('Modern entry', 'out-of-scope', 'Architectural principle.')],
66
+ ]);
67
+ const current = new Set(['hash-a', 'hash-b']);
59
68
  const result = pruneStaleNonCompilable(map, current);
60
- expect(result.fresh).toEqual([{ hash: 'legacy-hash', title: '(legacy entry)' }]);
69
+ expect(result.fresh).toEqual([
70
+ { hash: 'hash-a', title: 'Legacy entry', reasonCode: 'legacy-unknown' },
71
+ {
72
+ hash: 'hash-b',
73
+ title: 'Modern entry',
74
+ reasonCode: 'out-of-scope',
75
+ reason: 'Architectural principle.',
76
+ },
77
+ ]);
61
78
  expect(result.drained).toBe(0);
62
79
  });
63
80
  it('does not mutate the input map', () => {
64
81
  const map = new Map([
65
- ['abc', 'Kept'],
66
- ['stale', 'Removed'],
82
+ ['abc', entry('Kept')],
83
+ ['stale', entry('Removed')],
67
84
  ]);
68
85
  const current = new Set(['abc']);
69
86
  pruneStaleNonCompilable(map, current);
@@ -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-verbose-trace.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compile-verbose-trace.test.d.ts","sourceRoot":"","sources":["../../src/commands/compile-verbose-trace.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,128 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { formatVerboseTraceBlock } from './compile.js';
3
+ // ─── Format verbose trace block (mmnto-ai/totem#1482) ──
4
+ describe('formatVerboseTraceBlock', () => {
5
+ const lesson = { heading: 'No console.log in production', hash: 'abc12345def67890' };
6
+ it('renders a pipeline 1 single-result trace as a header plus result line', () => {
7
+ const trace = [{ layer: 1, action: 'result', outcome: 'compiled' }];
8
+ const block = formatVerboseTraceBlock(lesson, 'compiled', undefined, trace);
9
+ const lines = block.split('\n');
10
+ expect(lines[0]).toBe('lesson-abc12345 "No console.log in production":');
11
+ expect(lines[1]).toBe(' result: compiled');
12
+ expect(lines).toHaveLength(2);
13
+ });
14
+ it('renders a pipeline 2 first-try success with generate + verify + result', () => {
15
+ const trace = [
16
+ { layer: 3, action: 'generate', outcome: 'attempt-1', patternHash: 'deadbeefcafebabe' },
17
+ { layer: 3, action: 'verify', outcome: 'MATCH' },
18
+ { layer: 3, action: 'result', outcome: 'compiled' },
19
+ ];
20
+ const block = formatVerboseTraceBlock(lesson, 'compiled', undefined, trace);
21
+ expect(block).toContain('lesson-abc12345');
22
+ expect(block).toContain('Layer 3 (Pipeline 3 (LLM + verify-retry)) -> attempt-1 (patternHash=deadbeefcafebabe)');
23
+ expect(block).toContain('verify on example: MATCH');
24
+ expect(block).toContain('result: compiled');
25
+ });
26
+ it('renders a verify-retry-exhausted trace with retry counters and reasonCode', () => {
27
+ const trace = [
28
+ { layer: 3, action: 'generate', outcome: 'attempt-1', patternHash: 'a'.repeat(16) },
29
+ { layer: 3, action: 'verify', outcome: 'example-hit-miss' },
30
+ { layer: 3, action: 'retry', outcome: 'attempt-2-scheduled' },
31
+ { layer: 3, action: 'generate', outcome: 'attempt-2', patternHash: 'b'.repeat(16) },
32
+ { layer: 3, action: 'verify', outcome: 'example-hit-miss' },
33
+ { layer: 3, action: 'retry', outcome: 'attempt-3-scheduled' },
34
+ { layer: 3, action: 'generate', outcome: 'attempt-3', patternHash: 'c'.repeat(16) },
35
+ { layer: 3, action: 'verify', outcome: 'example-hit-miss' },
36
+ {
37
+ layer: 3,
38
+ action: 'result',
39
+ outcome: 'skipped',
40
+ reasonCode: 'verify-retry-exhausted',
41
+ },
42
+ ];
43
+ const block = formatVerboseTraceBlock(lesson, 'skipped', 'verify-retry-exhausted', trace);
44
+ expect(block).toContain('retry 1: attempt-2-scheduled');
45
+ expect(block).toContain('retry 2: attempt-3-scheduled');
46
+ expect(block).toContain('result: skipped (verify-retry-exhausted)');
47
+ // Exactly three generate lines (one per attempt)
48
+ const genCount = (block.match(/Layer 3 \(Pipeline 3 \(LLM \+ verify-retry\)\) -> attempt-/g) ?? []).length;
49
+ expect(genCount).toBe(3);
50
+ });
51
+ it('renders pipeline 3 (layer 2) with the correct pipeline label', () => {
52
+ const trace = [
53
+ { layer: 2, action: 'generate', outcome: 'produced', patternHash: '0'.repeat(16) },
54
+ { layer: 2, action: 'verify', outcome: 'passed' },
55
+ { layer: 2, action: 'result', outcome: 'compiled' },
56
+ ];
57
+ const block = formatVerboseTraceBlock(lesson, 'compiled', undefined, trace);
58
+ expect(block).toContain('Layer 2 (Pipeline 2 (example-based)) -> produced');
59
+ });
60
+ it('falls back gracefully when trace is undefined or empty', () => {
61
+ const undefinedBlock = formatVerboseTraceBlock(lesson, 'failed', undefined, undefined);
62
+ expect(undefinedBlock).toContain('(no trace events recorded)');
63
+ expect(undefinedBlock).toContain('result: failed');
64
+ const emptyBlock = formatVerboseTraceBlock(lesson, 'noop', undefined, []);
65
+ expect(emptyBlock).toContain('(no trace events recorded)');
66
+ });
67
+ it('emits a single contiguous multi-line string (no intermediate newlines before content)', () => {
68
+ // Invariant: the caller writes this block via one process.stdout.write
69
+ // call, so the block must already contain its internal newlines and
70
+ // must not start or end with bare whitespace that would collide with
71
+ // the trailing \n the caller appends.
72
+ const trace = [{ layer: 1, action: 'result', outcome: 'compiled' }];
73
+ const block = formatVerboseTraceBlock(lesson, 'compiled', undefined, trace);
74
+ expect(block.startsWith('lesson-')).toBe(true);
75
+ expect(block.endsWith('\n')).toBe(false);
76
+ });
77
+ it('renders a skipped lesson with reasonCode from the terminal event', () => {
78
+ const trace = [
79
+ {
80
+ layer: 3,
81
+ action: 'result',
82
+ outcome: 'skipped',
83
+ reasonCode: 'out-of-scope',
84
+ },
85
+ ];
86
+ const block = formatVerboseTraceBlock(lesson, 'skipped', 'out-of-scope', trace);
87
+ expect(block).toContain('result: skipped (out-of-scope)');
88
+ });
89
+ it('tolerates unknown layer numbers via the fallback label', () => {
90
+ const trace = [
91
+ {
92
+ layer: 99,
93
+ action: 'generate',
94
+ outcome: 'produced',
95
+ },
96
+ {
97
+ layer: 99,
98
+ action: 'result',
99
+ outcome: 'compiled',
100
+ },
101
+ ];
102
+ const block = formatVerboseTraceBlock(lesson, 'compiled', undefined, trace);
103
+ expect(block).toContain('Layer 99');
104
+ });
105
+ });
106
+ // ─── Atomic stdout.write invocation ──────────────────
107
+ describe('verbose trace atomic emission', () => {
108
+ it('guarantees the trace block formats as a single string the caller can ship via one stdout.write call', () => {
109
+ // The compile command invokes process.stdout.write(block + '\n') once
110
+ // per lesson so concurrent lessons cannot interleave inside the block.
111
+ // That invariant reduces to: formatVerboseTraceBlock returns a single
112
+ // string. The test below pins the shape so a refactor that splits the
113
+ // render across multiple returns fails loudly.
114
+ const lesson = { heading: 'Atomic test lesson', hash: 'aaaa1111bbbb2222' };
115
+ const trace = [
116
+ { layer: 3, action: 'generate', outcome: 'attempt-1', patternHash: 'f'.repeat(16) },
117
+ { layer: 3, action: 'verify', outcome: 'MATCH' },
118
+ { layer: 3, action: 'result', outcome: 'compiled' },
119
+ ];
120
+ const out = formatVerboseTraceBlock(lesson, 'compiled', undefined, trace);
121
+ expect(typeof out).toBe('string');
122
+ // Block must contain every event's outcome; no event gets dropped.
123
+ for (const ev of trace) {
124
+ expect(out).toContain(ev.outcome);
125
+ }
126
+ });
127
+ });
128
+ //# sourceMappingURL=compile-verbose-trace.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compile-verbose-trace.test.js","sourceRoot":"","sources":["../../src/commands/compile-verbose-trace.test.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import type { CompiledRule, LessonInput } from '@mmnto/totem';
1
+ import type { CompiledRule, LayerTraceEvent, LessonInput, NonCompilableEntry, NonCompilableReasonCode } from '@mmnto/totem';
2
2
  /**
3
3
  * Terminal outcome of a `--upgrade <hash>` run, returned by `compileCommand`
4
4
  * so callers (like `totem doctor --pr` self-healing) can distinguish an actual
@@ -69,16 +69,18 @@ export declare function buildTelemetryPrefix(contextCounts: {
69
69
  unknown: number;
70
70
  }): string;
71
71
  /**
72
- * Non-compilable entry as it lives on disk after the schema transform in
73
- * `@mmnto/totem` normalizes legacy `string[]` entries to `{hash, title}` tuples.
72
+ * Value side of the in-memory `nonCompilableMap`. Carries the title plus the
73
+ * machine-readable reasonCode (mmnto-ai/totem#1481) so prune / serialize
74
+ * steps round-trip the full 4-tuple without a lookup.
74
75
  */
75
- export interface NonCompilableTuple {
76
- hash: string;
76
+ export interface NonCompilableMapValue {
77
77
  title: string;
78
+ reasonCode: NonCompilableReasonCode;
79
+ reason?: string;
78
80
  }
79
81
  /**
80
82
  * Filter stale entries from a non-compilable map against the current set of
81
- * lesson hashes. Returns the fresh tuple-form list and a count of how many
83
+ * lesson hashes. Returns the fresh 4-tuple list and a count of how many
82
84
  * entries were drained.
83
85
  *
84
86
  * Extracted for mmnto/totem#1281 so the no-op compile path can drain stale
@@ -86,9 +88,14 @@ export interface NonCompilableTuple {
86
88
  * leaving stale entries stranded on no-op runs (e.g. after a lesson was
87
89
  * removed or after a parser-bug fix invalidated old non-compilable hashes).
88
90
  * Pure function; does not mutate the input map.
91
+ *
92
+ * mmnto-ai/totem#1481: preserves `reasonCode` and `reason` through the
93
+ * prune so ledger entries stay 4-tuple-shaped on disk. Dropping them back
94
+ * to 2-tuple would silently reintroduce `'legacy-unknown'` on the next
95
+ * load via the Read transform.
89
96
  */
90
- export declare function pruneStaleNonCompilable(nonCompilableMap: Map<string, string>, currentHashes: Set<string>): {
91
- fresh: NonCompilableTuple[];
97
+ export declare function pruneStaleNonCompilable(nonCompilableMap: Map<string, NonCompilableMapValue>, currentHashes: Set<string>): {
98
+ fresh: NonCompilableEntry[];
92
99
  drained: number;
93
100
  };
94
101
  /**
@@ -105,6 +112,24 @@ export declare function pruneStaleRules(rules: readonly CompiledRule[], currentH
105
112
  fresh: CompiledRule[];
106
113
  pruned: number;
107
114
  };
115
+ /**
116
+ * Format a lesson's trace array into a single multi-line block for the
117
+ * `--verbose` renderer. Returns a string (no trailing newline — caller
118
+ * controls that). Output shape:
119
+ *
120
+ * lesson-<hash8> "<heading>":
121
+ * Layer <N> (<pipeline label>) -> <outcome> (<patternHash?>)
122
+ * verify on example: <outcome>
123
+ * retry N: scheduled
124
+ * result: <status> (<reasonCode or detail>)
125
+ *
126
+ * The renderer is defensive: malformed / unknown layer numbers render as
127
+ * "(unknown)" rather than throwing.
128
+ */
129
+ export declare function formatVerboseTraceBlock(lesson: {
130
+ heading: string;
131
+ hash: string;
132
+ }, status: 'compiled' | 'skipped' | 'failed' | 'noop', reasonCode: NonCompilableReasonCode | undefined, trace: readonly LayerTraceEvent[] | undefined): string;
108
133
  export interface AutoScaffoldDeps {
109
134
  fs: typeof import('node:fs');
110
135
  path: typeof import('node:path');
@@ -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;CACJ;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;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,CA43BnD"}
@@ -28,7 +28,7 @@ export function buildTelemetryPrefix(contextCounts) {
28
28
  }
29
29
  /**
30
30
  * Filter stale entries from a non-compilable map against the current set of
31
- * lesson hashes. Returns the fresh tuple-form list and a count of how many
31
+ * lesson hashes. Returns the fresh 4-tuple list and a count of how many
32
32
  * entries were drained.
33
33
  *
34
34
  * Extracted for mmnto/totem#1281 so the no-op compile path can drain stale
@@ -36,12 +36,24 @@ export function buildTelemetryPrefix(contextCounts) {
36
36
  * leaving stale entries stranded on no-op runs (e.g. after a lesson was
37
37
  * removed or after a parser-bug fix invalidated old non-compilable hashes).
38
38
  * Pure function; does not mutate the input map.
39
+ *
40
+ * mmnto-ai/totem#1481: preserves `reasonCode` and `reason` through the
41
+ * prune so ledger entries stay 4-tuple-shaped on disk. Dropping them back
42
+ * to 2-tuple would silently reintroduce `'legacy-unknown'` on the next
43
+ * load via the Read transform.
39
44
  */
40
45
  export function pruneStaleNonCompilable(nonCompilableMap, currentHashes) {
41
46
  const fresh = [];
42
- for (const [hash, title] of nonCompilableMap) {
47
+ for (const [hash, value] of nonCompilableMap) {
43
48
  if (currentHashes.has(hash)) {
44
- fresh.push({ hash, title });
49
+ const entry = {
50
+ hash,
51
+ title: value.title,
52
+ reasonCode: value.reasonCode,
53
+ };
54
+ if (value.reason !== undefined)
55
+ entry.reason = value.reason;
56
+ fresh.push(entry);
45
57
  }
46
58
  }
47
59
  return { fresh, drained: nonCompilableMap.size - fresh.length };
@@ -60,6 +72,88 @@ export function pruneStaleRules(rules, currentHashes) {
60
72
  const fresh = rules.filter((r) => currentHashes.has(r.lessonHash));
61
73
  return { fresh, pruned: rules.length - fresh.length };
62
74
  }
75
+ // ─── Verbose trace renderer (mmnto-ai/totem#1482) ──
76
+ /**
77
+ * Map a numeric layer from a trace event to its pipeline label. Tolerates
78
+ * unknown values so a future ADR-088 phase can introduce new layers without
79
+ * breaking the renderer.
80
+ */
81
+ function pipelineLabel(layer) {
82
+ switch (layer) {
83
+ case 1:
84
+ return 'Pipeline 1 (manual)';
85
+ case 2:
86
+ return 'Pipeline 2 (example-based)';
87
+ case 3:
88
+ return 'Pipeline 3 (LLM + verify-retry)';
89
+ default:
90
+ return `Layer ${layer}`;
91
+ }
92
+ }
93
+ /**
94
+ * Format a lesson's trace array into a single multi-line block for the
95
+ * `--verbose` renderer. Returns a string (no trailing newline — caller
96
+ * controls that). Output shape:
97
+ *
98
+ * lesson-<hash8> "<heading>":
99
+ * Layer <N> (<pipeline label>) -> <outcome> (<patternHash?>)
100
+ * verify on example: <outcome>
101
+ * retry N: scheduled
102
+ * result: <status> (<reasonCode or detail>)
103
+ *
104
+ * The renderer is defensive: malformed / unknown layer numbers render as
105
+ * "(unknown)" rather than throwing.
106
+ */
107
+ export function formatVerboseTraceBlock(lesson, status, reasonCode, trace) {
108
+ const lines = [];
109
+ const shortHash = lesson.hash.slice(0, 8);
110
+ lines.push(`lesson-${shortHash} "${lesson.heading}":`);
111
+ if (!trace || trace.length === 0) {
112
+ lines.push(` (no trace events recorded)`);
113
+ const resultSuffix = reasonCode ? ` (${reasonCode})` : '';
114
+ lines.push(' result: ' + status + resultSuffix);
115
+ return lines.join('\n');
116
+ }
117
+ // Separate events into prelude (generate / verify / retry) and terminal
118
+ // (result). The terminal lives on its own line with full framing.
119
+ let retryCounter = 0;
120
+ let sawResult = false;
121
+ for (const ev of trace) {
122
+ const label = pipelineLabel(ev.layer);
123
+ if (ev.action === 'generate') {
124
+ const detail = ev.patternHash ? ` (patternHash=${ev.patternHash})` : '';
125
+ lines.push(` Layer ${ev.layer} (${label}) -> ` + ev.outcome + detail);
126
+ }
127
+ else if (ev.action === 'verify') {
128
+ lines.push(` verify on example: ${ev.outcome}`);
129
+ }
130
+ else if (ev.action === 'retry') {
131
+ retryCounter++;
132
+ lines.push(` retry ${retryCounter}: ${ev.outcome}`);
133
+ }
134
+ else if (ev.action === 'result') {
135
+ const detail = ev.reasonCode ? ` (${ev.reasonCode})` : '';
136
+ lines.push(' result: ' + ev.outcome + detail);
137
+ sawResult = true;
138
+ }
139
+ else {
140
+ lines.push(` (unknown) ${String(ev.action)}: ${String(ev.outcome)}`);
141
+ }
142
+ }
143
+ // Defense in depth: if the trace somehow never emitted a terminal result
144
+ // event, synthesize one from the caller-supplied `status` so the verbose
145
+ // block always carries a final line. `compileLesson` pushes a result
146
+ // event on every return path, but a future refactor could regress that;
147
+ // this guard keeps the rendered block well-formed regardless. We use
148
+ // `status` directly rather than the last event's outcome because that
149
+ // outcome is an intermediate marker like 'MATCH' or 'attempt-1', not the
150
+ // lesson's final state.
151
+ if (!sawResult) {
152
+ const resultSuffix = reasonCode ? ` (${reasonCode})` : '';
153
+ lines.push(' result: ' + status + resultSuffix);
154
+ }
155
+ return lines.join('\n');
156
+ }
63
157
  // ─── Logging helpers ────────────────────────────────
64
158
  function logCompiledRule(log, lesson, rule) {
65
159
  const engine = rule.engine;
@@ -316,10 +410,16 @@ export async function compileCommand(options) {
316
410
  : loadCompiledRulesFile(rulesPath);
317
411
  const existingRules = existingFile.rules;
318
412
  const existingByHash = new Map(existingRules.map((r) => [r.lessonHash, r]));
319
- // mmnto/totem#1280: nonCompilable is now Map<hash, title> internally for observability.
320
- // The schema's transform normalizes legacy string entries to {hash, title} tuples
321
- // on read, so existingFile.nonCompilable is always tuple-shaped here.
322
- const nonCompilableMap = new Map((existingFile.nonCompilable ?? []).map((entry) => [entry.hash, entry.title]));
413
+ // mmnto/totem#1280 + mmnto-ai/totem#1481: in-memory nonCompilable is
414
+ // `Map<hash, {title, reasonCode, reason?}>` so every write path carries
415
+ // the full 4-tuple. The schema's Read transform normalizes legacy
416
+ // strings and 2-tuples to the 4-tuple shape (reasonCode:
417
+ // 'legacy-unknown') before we reach this block, so existingFile.
418
+ // 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
+ ]));
323
423
  // Note: we do NOT delete the --upgrade target from existingByHash here.
324
424
  // buildCompiledRule in @mmnto/totem looks up the old entry to preserve
325
425
  // metadata (createdAt, audit lineage). Deleting would make the upgraded
@@ -581,8 +681,15 @@ export async function compileCommand(options) {
581
681
  continue;
582
682
  }
583
683
  if (!parsed.compilable) {
584
- // mmnto/totem#1280: capture title alongside hash for observability
585
- nonCompilableMap.set(lesson.hash, lesson.heading);
684
+ // mmnto/totem#1280: capture title alongside hash for observability.
685
+ // mmnto-ai/totem#1481: the cloud worker currently classifies every
686
+ // compilable:false outcome as out-of-scope. Granular cloud-side
687
+ // reasonCodes are out of scope here and track via mmnto/totem#1221.
688
+ nonCompilableMap.set(lesson.hash, {
689
+ title: lesson.heading,
690
+ reasonCode: 'out-of-scope',
691
+ reason: parsed.reason,
692
+ });
586
693
  skippedLessons.push({ heading: lesson.heading, reason: parsed.reason });
587
694
  skipped++;
588
695
  continue;
@@ -645,6 +752,17 @@ export async function compileCommand(options) {
645
752
  });
646
753
  }));
647
754
  for (const { lesson, result } of results) {
755
+ // mmnto-ai/totem#1482: emit the per-lesson layer-trace block when
756
+ // --verbose is active. The whole block ships via one stdout.write
757
+ // call so concurrent lessons cannot interleave their output.
758
+ // Non-trace verbose behavior (skipped-lesson reasons) still fires
759
+ // further down; this renders the structured trace alongside.
760
+ if (options.verbose) {
761
+ const resultTrace = 'trace' in result ? result.trace : undefined;
762
+ const reasonCode = result.status === 'skipped' ? result.reasonCode : undefined;
763
+ const block = formatVerboseTraceBlock(lesson, result.status, reasonCode, resultTrace);
764
+ process.stdout.write(block + '\n');
765
+ }
648
766
  // Upgrade targets: remove the stale copy from newRules for any
649
767
  // terminal outcome where the rule's state CHANGES (compiled -> new
650
768
  // pattern replaces old; skipped -> rule moves to nonCompilable and
@@ -700,8 +818,15 @@ export async function compileCommand(options) {
700
818
  logCompiledRule(log, lesson, result.rule);
701
819
  break;
702
820
  case 'skipped':
703
- // mmnto/totem#1280: capture title alongside hash for observability
704
- nonCompilableMap.set(result.hash, lesson.heading);
821
+ // mmnto/totem#1280 + mmnto-ai/totem#1481: capture the full
822
+ // 4-tuple so ledger reads downstream (doctor, telemetry) see
823
+ // a specific reasonCode rather than normalizing to
824
+ // 'legacy-unknown'.
825
+ nonCompilableMap.set(result.hash, {
826
+ title: lesson.heading,
827
+ reasonCode: result.reasonCode,
828
+ reason: result.reason,
829
+ });
705
830
  skippedLessons.push({ heading: lesson.heading, reason: result.reason });
706
831
  skipped++;
707
832
  break;