@mmnto/cli 1.124.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.
- package/dist/commands/doctor.d.ts +16 -10
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +162 -54
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/doctor.test.js +444 -28
- package/dist/commands/doctor.test.js.map +1 -1
- package/dist/commands/hook-totemdir-render.test.js +55 -1
- package/dist/commands/hook-totemdir-render.test.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +39 -0
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/init.test.js +32 -0
- package/dist/commands/init.test.js.map +1 -1
- package/dist/commands/install-hooks-exit-contract.test.js +32 -1
- package/dist/commands/install-hooks-exit-contract.test.js.map +1 -1
- package/dist/commands/install-hooks.d.ts +143 -10
- package/dist/commands/install-hooks.d.ts.map +1 -1
- package/dist/commands/install-hooks.js +441 -69
- package/dist/commands/install-hooks.js.map +1 -1
- package/dist/commands/install-hooks.test.js +1075 -7
- package/dist/commands/install-hooks.test.js.map +1 -1
- package/dist/commands/spec-templates.d.ts +17 -4
- package/dist/commands/spec-templates.d.ts.map +1 -1
- package/dist/commands/spec-templates.js +23 -3
- package/dist/commands/spec-templates.js.map +1 -1
- package/dist/commands/spec.d.ts +57 -8
- package/dist/commands/spec.d.ts.map +1 -1
- package/dist/commands/spec.js +124 -28
- package/dist/commands/spec.js.map +1 -1
- package/dist/commands/spec.test.js +277 -44
- package/dist/commands/spec.test.js.map +1 -1
- package/dist/utils.d.ts +5 -3
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js.map +1 -1
- package/dist/utils.test.js +8 -3
- package/dist/utils.test.js.map +1 -1
- 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
|
-
*
|
|
56
|
-
*
|
|
57
|
-
* `totemDir`
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
* `
|
|
62
|
-
*
|
|
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":"AAMA,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAOhE,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;AAE5D,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,WAAW,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,IAAI,CAAC;CACnB;AAID;;;;;;;;;GASG;AACH,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,MAAM,CAAC;AAEzC,eAAO,MAAM,YAAY,EAAE,SAAS,UAAU,EAAqB,CAAC;AAEpE;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,GACnC,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CAUjC;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,SAAS,gBAAgB,EAAE,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAIhG;AAyBD,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,gBAAgB,CAiBzD;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,SAAW,GAAG,gBAAgB,CAiCrF;AAED
|
|
1
|
+
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"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"}
|
package/dist/commands/doctor.js
CHANGED
|
@@ -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
|
-
*
|
|
115
|
-
*
|
|
116
|
-
* `totemDir`
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
* `
|
|
121
|
-
*
|
|
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
|
|
175
|
-
if (
|
|
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 '${
|
|
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
|
|
186
|
-
if (
|
|
187
|
-
const files =
|
|
188
|
-
//
|
|
189
|
-
//
|
|
190
|
-
//
|
|
191
|
-
//
|
|
192
|
-
//
|
|
193
|
-
|
|
194
|
-
|
|
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
|
-
:
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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
|
|
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
|
|
240
|
-
* at
|
|
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
|
-
*
|
|
243
|
-
*
|
|
244
|
-
*
|
|
245
|
-
*
|
|
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
|
|
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="…"
|
|
252
|
-
*
|
|
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
|
|
256
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
:
|
|
314
|
-
? 'appended'
|
|
315
|
-
:
|
|
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 {
|
|
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
|
/**
|