@mmnto/cli 1.123.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/dist/commands/doctor.d.ts +16 -10
  2. package/dist/commands/doctor.d.ts.map +1 -1
  3. package/dist/commands/doctor.js +162 -54
  4. package/dist/commands/doctor.js.map +1 -1
  5. package/dist/commands/doctor.test.js +444 -28
  6. package/dist/commands/doctor.test.js.map +1 -1
  7. package/dist/commands/extract-shared.d.ts.map +1 -1
  8. package/dist/commands/extract-shared.js +2 -6
  9. package/dist/commands/extract-shared.js.map +1 -1
  10. package/dist/commands/extract.test.js +19 -1
  11. package/dist/commands/extract.test.js.map +1 -1
  12. package/dist/commands/hook-totemdir-render.test.js +55 -1
  13. package/dist/commands/hook-totemdir-render.test.js.map +1 -1
  14. package/dist/commands/init.d.ts.map +1 -1
  15. package/dist/commands/init.js +39 -0
  16. package/dist/commands/init.js.map +1 -1
  17. package/dist/commands/init.test.js +32 -0
  18. package/dist/commands/init.test.js.map +1 -1
  19. package/dist/commands/install-hooks-exit-contract.test.js +32 -1
  20. package/dist/commands/install-hooks-exit-contract.test.js.map +1 -1
  21. package/dist/commands/install-hooks.d.ts +143 -10
  22. package/dist/commands/install-hooks.d.ts.map +1 -1
  23. package/dist/commands/install-hooks.js +441 -69
  24. package/dist/commands/install-hooks.js.map +1 -1
  25. package/dist/commands/install-hooks.test.js +1075 -7
  26. package/dist/commands/install-hooks.test.js.map +1 -1
  27. package/dist/commands/review-learn.d.ts +3 -0
  28. package/dist/commands/review-learn.d.ts.map +1 -1
  29. package/dist/commands/review-learn.js +4 -7
  30. package/dist/commands/review-learn.js.map +1 -1
  31. package/dist/commands/review-learn.test.js +19 -2
  32. package/dist/commands/review-learn.test.js.map +1 -1
  33. package/dist/commands/shield-learn.test.js +27 -5
  34. package/dist/commands/shield-learn.test.js.map +1 -1
  35. package/dist/commands/shield-retrieve.test.d.ts +2 -0
  36. package/dist/commands/shield-retrieve.test.d.ts.map +1 -0
  37. package/dist/commands/shield-retrieve.test.js +91 -0
  38. package/dist/commands/shield-retrieve.test.js.map +1 -0
  39. package/dist/commands/shield-templates.d.ts +7 -0
  40. package/dist/commands/shield-templates.d.ts.map +1 -1
  41. package/dist/commands/shield-templates.js +7 -0
  42. package/dist/commands/shield-templates.js.map +1 -1
  43. package/dist/commands/shield.d.ts +3 -1
  44. package/dist/commands/shield.d.ts.map +1 -1
  45. package/dist/commands/shield.js +17 -9
  46. package/dist/commands/shield.js.map +1 -1
  47. package/dist/commands/spec-templates.d.ts +17 -4
  48. package/dist/commands/spec-templates.d.ts.map +1 -1
  49. package/dist/commands/spec-templates.js +23 -3
  50. package/dist/commands/spec-templates.js.map +1 -1
  51. package/dist/commands/spec.d.ts +84 -10
  52. package/dist/commands/spec.d.ts.map +1 -1
  53. package/dist/commands/spec.js +169 -35
  54. package/dist/commands/spec.js.map +1 -1
  55. package/dist/commands/spec.test.js +487 -48
  56. package/dist/commands/spec.test.js.map +1 -1
  57. package/dist/commands/triage-retrieve.test.d.ts +2 -0
  58. package/dist/commands/triage-retrieve.test.d.ts.map +1 -0
  59. package/dist/commands/triage-retrieve.test.js +90 -0
  60. package/dist/commands/triage-retrieve.test.js.map +1 -0
  61. package/dist/commands/triage.d.ts +14 -1
  62. package/dist/commands/triage.d.ts.map +1 -1
  63. package/dist/commands/triage.js +21 -6
  64. package/dist/commands/triage.js.map +1 -1
  65. package/dist/utils.d.ts +10 -8
  66. package/dist/utils.d.ts.map +1 -1
  67. package/dist/utils.js +5 -6
  68. package/dist/utils.js.map +1 -1
  69. package/dist/utils.test.js +50 -51
  70. package/dist/utils.test.js.map +1 -1
  71. package/package.json +2 -2
@@ -50,19 +50,25 @@ export declare function doctorGateFailed(results: readonly DiagnosticResult[], t
50
50
  export declare function checkConfig(cwd: string): DiagnosticResult;
51
51
  export declare function checkCompiledRules(cwd: string, totemDir?: string): DiagnosticResult;
52
52
  /**
53
- * Sense the four managed git hooks.
53
+ * Sense the four managed git hooks — presence AND currency.
54
54
  *
55
- * Marker-presence only on the default `totemDir` the always-on, zero-cost
56
- * shape this row has always had. When the repo CONFIGURES a different
57
- * `totemDir` (mmnto-ai/totem#2692 C6), the row additionally regenerates the
58
- * canonical four and compares whole-file: a hook still naming `.totem/` there
59
- * reads its evidence and its gate flags out of a tree nothing writes, and
60
- * `doctor --parity` which would otherwise catch it is pin-gated behind
61
- * `orient.parityManifest`. Sensor, never a gate: the non-pass state stays
62
- * `warn`, with the same remediation the parity row emits.
55
+ * Presence is the marker check it has always been. Currency (mmnto-ai/totem#2753)
56
+ * regenerates the canonical four from the running `@mmnto/cli` at this repo's
57
+ * effective `totemDir` and compares each hook's MANAGED BLOCK against it, on EVERY
58
+ * install. Until mmnto-ai/totem#2753 the compare ran only when the repo configured a NON-default
59
+ * `totemDir` (mmnto-ai/totem#2692 C6) so on the overwhelmingly common default the
60
+ * row reported presence and called a hook frozen at an older template "installed"
61
+ * (measured: `totem doctor` printed "All 4 hooks installed" over two 1.121.0 hooks
62
+ * under a 1.123.0 CLI, mmnto-ai/liquid-city#1174). Presence was never the question
63
+ * the row is asked.
64
+ *
65
+ * Sensor, never a gate: the non-pass state stays `warn`, with the remediation that
66
+ * actually fits the file's shape — including the one-command `totem hook install`
67
+ * for a block whose attested extension the installer now carries through.
63
68
  *
64
69
  * `config` is threaded from `doctorCommand`'s single config load — this check
65
- * never opens the config itself.
70
+ * never opens the config itself. Its `totemDir` may be undefined; the compare runs
71
+ * against the installer's default in that case rather than being skipped.
66
72
  */
67
73
  export declare function checkGitHooks(cwd: string, config?: {
68
74
  totemDir?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"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"}
@@ -109,19 +109,25 @@ export function checkCompiledRules(cwd, totemDir = '.totem') {
109
109
  }
110
110
  }
111
111
  /**
112
- * Sense the four managed git hooks.
112
+ * Sense the four managed git hooks — presence AND currency.
113
113
  *
114
- * Marker-presence only on the default `totemDir` the always-on, zero-cost
115
- * shape this row has always had. When the repo CONFIGURES a different
116
- * `totemDir` (mmnto-ai/totem#2692 C6), the row additionally regenerates the
117
- * canonical four and compares whole-file: a hook still naming `.totem/` there
118
- * reads its evidence and its gate flags out of a tree nothing writes, and
119
- * `doctor --parity` which would otherwise catch it is pin-gated behind
120
- * `orient.parityManifest`. Sensor, never a gate: the non-pass state stays
121
- * `warn`, with the same remediation the parity row emits.
114
+ * Presence is the marker check it has always been. Currency (mmnto-ai/totem#2753)
115
+ * regenerates the canonical four from the running `@mmnto/cli` at this repo's
116
+ * effective `totemDir` and compares each hook's MANAGED BLOCK against it, on EVERY
117
+ * install. Until mmnto-ai/totem#2753 the compare ran only when the repo configured a NON-default
118
+ * `totemDir` (mmnto-ai/totem#2692 C6) so on the overwhelmingly common default the
119
+ * row reported presence and called a hook frozen at an older template "installed"
120
+ * (measured: `totem doctor` printed "All 4 hooks installed" over two 1.121.0 hooks
121
+ * under a 1.123.0 CLI, mmnto-ai/liquid-city#1174). Presence was never the question
122
+ * the row is asked.
123
+ *
124
+ * Sensor, never a gate: the non-pass state stays `warn`, with the remediation that
125
+ * actually fits the file's shape — including the one-command `totem hook install`
126
+ * for a block whose attested extension the installer now carries through.
122
127
  *
123
128
  * `config` is threaded from `doctorCommand`'s single config load — this check
124
- * never opens the config itself.
129
+ * never opens the config itself. Its `totemDir` may be undefined; the compare runs
130
+ * against the installer's default in that case rather than being skipped.
125
131
  */
126
132
  export async function checkGitHooks(cwd, config) {
127
133
  const gitRoot = resolveGitRoot(cwd);
@@ -171,39 +177,95 @@ export async function checkGitHooks(cwd, config) {
171
177
  missing.push(file);
172
178
  }
173
179
  if (missing.length === 0) {
174
- const sensed = await hooksRenderedForWrongTotemDir(gitRoot, hooksDir, config);
175
- if (!Array.isArray(sensed)) {
180
+ const sensed = await hooksStaleAgainstCanonical(gitRoot, hooksDir, config);
181
+ if (sensed.kind === 'unavailable') {
176
182
  // The sensor could not run (a builder threw, a hook file was unreadable):
177
183
  // say so, never pass on an unexamined tree (Greptile P1 on mmnto-ai/totem#2701).
178
184
  return {
179
185
  name: 'Git Hooks',
180
186
  status: 'warn',
181
- message: `All ${markers.length} hooks installed, but they could not be compared against the canonical rendered for totemDir '${config?.totemDir}': ${sensed.failure}`,
187
+ message: `All ${markers.length} hooks installed, but they could not be compared against the canonical rendered for totemDir '${sensed.totemDir}': ${sensed.failure}`,
182
188
  remediation: 'totem hook install --force (re-renders the four hooks from the current config)',
183
189
  };
184
190
  }
185
- const staleForTotemDir = sensed;
186
- if (staleForTotemDir.length > 0) {
187
- const files = staleForTotemDir.map((row) => row.file);
188
- // `--force` rewrites the WHOLE file (`installGitHook`): the right remedy for
189
- // a totem-owned whole file, and the ONE remedy install-hooks.ts prescribes
190
- // for a LEGACY hook (no end marker drift-repair cannot bound it); the
191
- // wrong remedy for a user hook with an APPENDED block, which would lose its
192
- // own lines to it (mmnto-ai/totem#2692 amendments A4 / A10).
193
- const appended = staleForTotemDir.filter((row) => row.kind === 'appended').map((r) => r.file);
194
- const legacy = staleForTotemDir.filter((row) => row.kind === 'legacy').map((r) => r.file);
191
+ const stale = sensed.stale;
192
+ if (stale.length > 0) {
193
+ const files = stale.map((row) => row.file);
194
+ // The remedy follows the file's SHAPE, because the four shapes take four
195
+ // different cures (mmnto-ai/totem#2692 amendments A4 / A10, mmnto-ai/totem#2753):
196
+ // owned-whole / legacy → `--force`: it rewrites the WHOLE file, which is
197
+ // right for a file totem authored and the ONE cure
198
+ // install-hooks.ts prescribes for an unboundable
199
+ // legacy hook.
200
+ // appended-attested → bare `totem hook install`: the block is rewritten
201
+ // in place and the attested extension survives, so
202
+ // `--force` would needlessly destroy it.
203
+ // appended → neither: `--force` would eat the user's own lines,
204
+ // so the block is deleted and re-appended by hand.
205
+ // non-utf8 → an attested-extension hook whose MANAGED REGION does
206
+ // not decode as UTF-8: the installer reports that
207
+ // shape and leaves the file alone, so prescribing the
208
+ // bare install would be inert (mmnto-ai/totem#2760
209
+ // leg F13) — re-save as UTF-8, or `--force`.
210
+ const attested = stale.filter((row) => row.kind === 'appended-attested').map((r) => r.file);
211
+ const nonUtf8 = stale.filter((row) => row.kind === 'non-utf8').map((r) => r.file);
212
+ const appended = stale.filter((row) => row.kind === 'appended').map((r) => r.file);
213
+ const legacy = stale.filter((row) => row.kind === 'legacy').map((r) => r.file);
214
+ const forceable = stale
215
+ .filter((row) => row.kind === 'owned-whole' || row.kind === 'legacy')
216
+ .map((r) => r.file);
217
+ // One clause per shape present, each naming its own files unless it covers
218
+ // every stale hook (then the bare command reads cleaner).
219
+ const clauses = [];
220
+ // The `--force` clause names its files whenever it does not cover all of them.
221
+ // The note drops its own names ONLY when that clause named exactly the legacy
222
+ // files — there the repetition ("…--force for pre-commit (pre-commit: a legacy
223
+ // hook…)") reads as two hooks where there is one. When the clause names a
224
+ // WIDER set, the note must keep the names or the reader cannot tell WHICH of
225
+ // the listed hooks is the legacy one (fold F6, narrowed on fold 3 F5).
226
+ const forceClauseNamesFiles = forceable.length !== files.length;
227
+ const forceClauseNamesOnlyLegacy = forceClauseNamesFiles && forceable.length === legacy.length;
228
+ const legacyKind = legacy.length === 1
229
+ ? 'a legacy hook with no end marker'
230
+ : 'legacy hooks with no end marker';
195
231
  const legacyNote = legacy.length === 0
196
232
  ? ''
197
- : ` (${legacy.join(', ')}: a legacy hook with no end marker — back up any lines of your own first)`;
198
- const remediation = appended.length === 0
199
- ? `totem hook install --force${legacyNote}`
200
- : `delete the totem block (from its start marker through its end marker) in ${appended.join(', ')} and re-run \`totem hook install\` to re-append it${appended.length === files.length
201
- ? ''
202
- : `; \`totem hook install --force\` for the rest${legacyNote}`} — \`--force\` rewrites the whole file and would overwrite your own hook content`;
233
+ : forceClauseNamesOnlyLegacy
234
+ ? ` (${legacyKind} back up any lines of your own first)`
235
+ : ` (${legacy.join(', ')}: ${legacyKind} — back up any lines of your own first)`;
236
+ if (appended.length > 0) {
237
+ clauses.push(`delete the totem block (from its start marker through its end marker) in ${appended.join(', ')} and re-run \`totem hook install\` to re-append it`);
238
+ }
239
+ if (attested.length > 0) {
240
+ clauses.push(`${clauses.length === 0 ? 'totem hook install' : '`totem hook install`'}${attested.length === files.length ? '' : ` for ${attested.join(', ')}`} (the managed block is rewritten in place; your attested extension after the end marker is carried through unchanged)`);
241
+ }
242
+ // The re-save clause sits BEFORE the --force clause and keeps its own tail
243
+ // inside its parenthetical, so a mixed set never renders two stacked em-dash
244
+ // tails about `--force` (re-armed leg F19).
245
+ if (nonUtf8.length > 0) {
246
+ clauses.push(`re-save ${nonUtf8.join(', ')} as UTF-8 and re-run \`totem hook install\` (the region above the extension — shebang line and managed block — does not decode as UTF-8, so a bare install reports it and leaves the file byte-identical), or take \`totem hook install --force\` for ${nonUtf8.length === 1 ? 'it' : 'them'} (rewrites the whole file and drops your extension)`);
247
+ }
248
+ if (forceable.length > 0) {
249
+ clauses.push(`${clauses.length === 0 ? 'totem hook install --force' : '`totem hook install --force`'}${forceable.length === files.length ? '' : ` for ${forceable.join(', ')}`}${legacyNote}`);
250
+ }
251
+ const remediation = clauses.join('; ') +
252
+ (appended.length > 0
253
+ ? ' — `--force` rewrites the whole file and would overwrite your own hook content'
254
+ : '');
255
+ // The headline names the cause. A `non-utf8` row's block may be byte-identical
256
+ // to the canonical (the bad byte on its shebang line), so "does not match …
257
+ // the managed block is stale" would be two false statements about it (final
258
+ // leg, F20): when every flagged hook is that shape the headline says so
259
+ // instead; a mixed set keeps the stale headline for the stale hooks and
260
+ // appends the encoding clause naming the others.
261
+ const encodingCause = 'the region above the extension (shebang line and managed block) does not decode as UTF-8';
262
+ const message = nonUtf8.length === files.length
263
+ ? `All ${markers.length} hooks installed, but ${files.join(', ')} cannot be kept current by this @mmnto/cli for totemDir '${sensed.totemDir}' — ${encodingCause}`
264
+ : `All ${markers.length} hooks installed, but ${files.join(', ')} ${files.length === 1 ? 'does' : 'do'} not match the canonical this @mmnto/cli renders for totemDir '${sensed.totemDir}' — the managed block is stale (a newer hook template, or a config change)${nonUtf8.length > 0 ? `; for ${nonUtf8.join(', ')} ${encodingCause}` : ''}`;
203
265
  return {
204
266
  name: 'Git Hooks',
205
267
  status: 'warn',
206
- message: `All ${markers.length} hooks installed, but ${files.join(', ')} ${files.length === 1 ? 'does' : 'do'} not match the canonical rendered for totemDir '${config?.totemDir}' — the hook reads a tree Totem does not write`,
268
+ message,
207
269
  remediation,
208
270
  };
209
271
  }
@@ -236,33 +298,40 @@ function totemOwnedBlock(text, marker, endMarker) {
236
298
  return text.slice(start, end + endMarker.length);
237
299
  }
238
300
  /**
239
- * The managed hooks whose totem-owned BLOCK differs from the canonical regenerated
240
- * at this repo's CONFIGURED `totemDir` (mmnto-ai/totem#2692 C6, amendment A4).
301
+ * The managed hooks whose totem-owned BLOCK differs from the canonical this
302
+ * `@mmnto/cli` regenerates at the repo's effective `totemDir`.
303
+ *
304
+ * Runs on EVERY install (mmnto-ai/totem#2753). It used to run only where the repo
305
+ * configured a NON-default `totemDir` (mmnto-ai/totem#2692 C6) — which made the
306
+ * default-`totemDir` majority a blind spot: a hook frozen at an older template read
307
+ * as healthy, because presence was all that was checked. The effective dir is the
308
+ * configured value, else `install-hooks`'s own `DEFAULT_TOTEM_DIR`, so `.totem` is
309
+ * compared like any other value instead of short-circuiting.
241
310
  *
242
- * Runs ONLY when the repo configures a non-default `totemDir` on the default
243
- * the row stays the marker-only, zero-IO sense it has always been. Compares the
244
- * region between the hook's start and end markers, the same bounded region
245
- * `installGitHook`'s drift-repair owns, so a user hook with an APPENDED totem
246
- * block is judged on the block alone and never on the user's own lines. Each row
247
- * says what kind of file it is, because the remedy differs.
311
+ * Compares the region between the hook's start and end markers, the same bounded
312
+ * region `installGitHook` owns, so a user hook with an APPENDED totem block is
313
+ * judged on the block alone and never on the user's own lines. Each row says what
314
+ * kind of file it is, because the remedy differs.
248
315
  *
249
- * The compare is on the `totemDir` axis ONLY (amendment A10): the canonical for
316
+ * The tier is NOT an axis of the compare (amendment A10): the canonical for
250
317
  * pre-commit / pre-push is rendered at the tier the INSTALLED hook itself
251
- * declares (`TOTEM_HOOK_TIER="…"`), so a hook installed with `--strict` on a
252
- * repo whose config says nothing is never reported as `totemDir` drift — and
318
+ * declares (`TOTEM_HOOK_TIER="…"`, read through the installer's own
319
+ * `declaredHookTier`: the totem-owned block only, and only a line-leading
320
+ * assignment — a comment quoting one does not count), so a hook installed with
321
+ * `--strict` on a repo whose config says nothing is never reported as drift — and
253
322
  * never handed a `--force` that would silently downgrade it to standard.
254
323
  */
255
- async function hooksRenderedForWrongTotemDir(gitRoot, hooksDir, config) {
256
- const totemDir = config?.totemDir;
257
- if (totemDir === undefined || totemDir === '.totem')
258
- return [];
324
+ async function hooksStaleAgainstCanonical(gitRoot, hooksDir, config) {
325
+ let totemDir = config?.totemDir;
259
326
  try {
260
327
  const hooks = await import('./install-hooks.js');
328
+ totemDir ??= hooks.DEFAULT_TOTEM_DIR;
261
329
  // A value the installer refuses (`.`, a `..` segment, …) never produced
262
330
  // installed hooks, so there is no render to compare against: the row stays
263
331
  // the marker-only sense — the same policy `doctor --parity` applies.
264
- if (hooks.hookTotemDirProblem(totemDir) !== null)
265
- return [];
332
+ if (hooks.hookTotemDirProblem(totemDir) !== null) {
333
+ return { kind: 'sensed', totemDir, stale: [] };
334
+ }
266
335
  const base = {
267
336
  tier: config?.hooks?.tier ?? 'standard',
268
337
  totemDir,
@@ -296,13 +365,44 @@ async function hooksRenderedForWrongTotemDir(gitRoot, hooksDir, config) {
296
365
  ];
297
366
  const stale = [];
298
367
  for (const { file, build, marker, endMarker } of canonical) {
299
- const existing = fs.readFileSync(path.join(hooksDir, file), 'utf-8');
368
+ // Raw bytes for the round-trip shape below; the decoded text for the compare.
369
+ const rawExisting = fs.readFileSync(path.join(hooksDir, file));
370
+ const existing = rawExisting.toString('utf-8');
300
371
  // Everything below reads the TOTEM-OWNED block, never the user's own lines:
301
372
  // a user line that happens to carry `TOTEM_HOOK_TIER="…"` or to quote an end
302
373
  // marker must not steer the compare or the remedy (pass-2 F3/F4).
303
374
  const existingBlock = totemOwnedBlock(existing, marker, endMarker);
304
- const installedTier = /TOTEM_HOOK_TIER="(strict|standard)"/.exec(existingBlock)?.[1];
375
+ // ONE predicate with the installer (`declaredHookTier`): block-scoped, and
376
+ // only a line-leading assignment counts, so a comment inside the block that
377
+ // quotes the assignment steers neither arm — a second regex here is how the
378
+ // two arms came to read the same bytes at different tiers (mmnto-ai/totem#2760
379
+ // round 1, leg F3).
380
+ const installedTier = hooks.declaredHookTier(existing, marker, endMarker);
305
381
  const content = build({ ...base, tier: installedTier ?? base.tier });
382
+ // An attested `totem:fork` extension after the end marker is the one trailing
383
+ // shape a BARE install can now cure, so it must be told apart from the general
384
+ // appended case BEFORE it — the remedies are opposites (mmnto-ai/totem#2753).
385
+ // The test is `isTotemOwnedWithAttestedTrailer`, the SAME predicate the install
386
+ // arm gates its cure on, not merely "bounded and the trailer is attested": that
387
+ // looser test would also catch a hook with the user's own lines BEFORE the totem
388
+ // block, which the installer declines because the marker does not OPEN the file
389
+ // — and the row would then prescribe a bare `totem hook install` that does
390
+ // nothing. A remedy that does not touch the surface it is prescribed for is an
391
+ // inert instruction (mmnto-ai/totem#2532); such a file stays `appended` and
392
+ // takes the delete-and-re-append line, which does work on it.
393
+ const attestedTrailer = hooks.isTotemOwnedWithAttestedTrailer(existing, marker, endMarker);
394
+ // The same byte-offset proof the installer's rewrite arm runs, BEFORE the
395
+ // staleness compare: the proof covers the region above the extension (shebang
396
+ // line and managed block) while the compare covers the block alone, so a bad
397
+ // byte on the shebang line leaves the block current yet makes the installer
398
+ // skip — the row must say so rather than "All hooks installed" (the
399
+ // one-predicate rule above, applied to the newest shape; mmnto-ai/totem#2760
400
+ // legs F13 and F16).
401
+ if (attestedTrailer &&
402
+ hooks.ownedTrailerByteStart(rawExisting, marker, endMarker) === undefined) {
403
+ stale.push({ file, kind: 'non-utf8' });
404
+ continue;
405
+ }
306
406
  if (existingBlock === totemOwnedBlock(content, marker, endMarker)) {
307
407
  continue;
308
408
  }
@@ -310,16 +410,24 @@ async function hooksRenderedForWrongTotemDir(gitRoot, hooksDir, config) {
310
410
  const boundedAfterStart = startIdx !== -1 && existing.indexOf(endMarker, startIdx + marker.length) !== -1;
311
411
  const kind = hooks.isTotemOwnedWholeFile(existing, marker, endMarker)
312
412
  ? 'owned-whole'
313
- : boundedAfterStart
314
- ? 'appended'
315
- : 'legacy';
413
+ : attestedTrailer
414
+ ? 'appended-attested'
415
+ : boundedAfterStart
416
+ ? 'appended'
417
+ : 'legacy';
316
418
  stale.push({ file, kind });
317
419
  }
318
- return stale;
420
+ return { kind: 'sensed', totemDir, stale };
319
421
  // totem-context: a sensor that cannot regenerate or read the canonical REPORTS that it could not — the caller renders a warn row — never a silent pass and never a crash of the diagnostic pipeline (Tenet 13; Greptile P1 on mmnto-ai/totem#2701).
320
422
  }
321
423
  catch (err) {
322
- return { failure: err instanceof Error ? err.message : String(err) };
424
+ return {
425
+ kind: 'unavailable',
426
+ // The default comes from the module that failed to load, so it may still be
427
+ // unresolved here — say so rather than printing `undefined`.
428
+ totemDir: totemDir ?? 'unresolved',
429
+ failure: err instanceof Error ? err.message : String(err),
430
+ };
323
431
  }
324
432
  }
325
433
  /**