Bug: RebuildArchSummary never skips — checks a filename nothing ever writes, and resolves the generator with the wrong case
Summary
hooks/handlers/RebuildArchSummary.ts contains two independent path defects in a single function. Together they mean the handler either regenerates the architecture summary every time it runs, or never runs at all, depending on your filesystem — but never does the thing it was written to do, which is regenerate only when a tracked system file actually changed.
The entire mtime-comparison body of the function is unreachable on every platform.
Defect 1 — the existence check targets a filename that is never produced
LifeOS/install/hooks/handlers/RebuildArchSummary.ts:27
const output = join(paiDir, "DOCUMENTATION", "LIFEOS_ARCHITECTURE_SUMMARY.md");
But the generator writes a different name — LifeOS/install/LIFEOS/TOOLS/ArchitectureSummaryGenerator.ts:39:
const SUMMARY_OUTPUT = path.join(LIFEOS_DIR, "DOCUMENTATION", "ARCHITECTURE_SUMMARY.md");
LIFEOS_ARCHITECTURE_SUMMARY.md does not exist anywhere in the repository and no code path writes it. The only file present is LifeOS/install/LIFEOS/DOCUMENTATION/ARCHITECTURE_SUMMARY.md.
So existsSync(output) is unconditionally false, and the handler always takes the early-return branch at lines 33–38:
const outputStat = existsSync(output) ? statSync(output) : null;
if (!outputStat) {
console.error("[RebuildArchSummary] Architecture summary missing - regenerating");
await rebuild(generator, paiDir);
return; // <-- always taken
}
Everything after that return — the trackedDirs walk, the mtime comparison, and the "... is current" skip path — is dead code that has never executed.
Effect: every invocation spawns bun ArchitectureSummaryGenerator.ts generate as a subprocess and rewrites ARCHITECTURE_SUMMARY.md, whether or not anything changed. The file is left perpetually dirty in git.
Defect 2 — the generator path uses the wrong case (breaks on case-sensitive filesystems)
LifeOS/install/hooks/handlers/RebuildArchSummary.ts:28
const generator = join(paiDir, "Tools/ArchitectureSummaryGenerator.ts");
The canonical path in the repo is LIFEOS/TOOLS/ArchitectureSummaryGenerator.ts — uppercase TOOLS. Every other reference in the tree uses that casing (LIFEOS/TOOLS/DocCheck.ts:58, LIFEOS/TOOLS/ReferenceCheck.ts:319).
Two lines later:
if (!existsSync(generator)) return;
- On macOS/APFS (case-insensitive),
Tools/ resolves to TOOLS/, the check passes, and Defect 1 fires — constant regeneration.
- On Linux (case-sensitive),
existsSync returns false and the handler returns immediately. The architecture summary is never regenerated, silently.
Defect 2 masks Defect 1 on Linux, which is likely why the constant-regeneration symptom hasn't been widely reported.
This is the same class as the case-duplicate path cleanup in bc0a20a (issues #1556 / #1557), just in a hook handler rather than a template.
Documentation is inconsistent too, in the same direction
The doc strings disagree with each other about the filename, which looks like a rename that only partially landed:
LifeOS/install/settings.enhancements.json:761 — "...regenerates LIFEOS_ARCHITECTURE_SUMMARY.md on Stop."
LifeOS/install/settings.enhancements.json:791 — "...regenerates ARCHITECTURE_SUMMARY.md when LifeosSystemArchitecture.md changes."
ArchitectureSummaryGenerator.ts:10 and :321 say LIFEOS_ARCHITECTURE_SUMMARY.md in comments/help text, while the code at :39 writes ARCHITECTURE_SUMMARY.md.
RebuildArchSummary.ts:3 and :8 say DOCUMENTATION/ARCHITECTURE_SUMMARY.md — contradicting line 27 of the same file.
Affected versions
Present in every release where both files exist: v6.0.2, v6.0.3, v6.0.5, v7.0.0, v7.1.1, and current main (origin/HEAD). The handler line has been byte-identical since v6.0.0.
Verified with:
git show v7.1.1:LifeOS/install/hooks/handlers/RebuildArchSummary.ts | sed -n '27,28p'
git show origin/HEAD:LifeOS/install/LIFEOS/TOOLS/ArchitectureSummaryGenerator.ts | sed -n '39p'
git ls-tree -r --name-only origin/HEAD | grep -i ARCHITECTURE_SUMMARY
Reproduction (macOS)
- Install LifeOS v7.1.1 and let
DocIntegrity.hook.ts fire (it calls handleRebuildArchSummary()).
- Observe stderr:
[RebuildArchSummary] Architecture summary missing - regenerating — even though LIFEOS/DOCUMENTATION/ARCHITECTURE_SUMMARY.md is present and current.
git status shows ARCHITECTURE_SUMMARY.md modified after every run.
- The
"[RebuildArchSummary] DOCUMENTATION/ARCHITECTURE_SUMMARY.md is current" message never appears in any log, because that branch is unreachable.
Suggested fix
- const output = join(paiDir, "DOCUMENTATION", "LIFEOS_ARCHITECTURE_SUMMARY.md");
- const generator = join(paiDir, "Tools/ArchitectureSummaryGenerator.ts");
+ const output = join(paiDir, "DOCUMENTATION", "ARCHITECTURE_SUMMARY.md");
+ const generator = join(paiDir, "TOOLS/ArchitectureSummaryGenerator.ts");
Ideally both constants would be imported from a shared module rather than duplicated as string literals in two files — that duplication is what allowed them to drift apart. The stale LIFEOS_ARCHITECTURE_SUMMARY.md mentions in ArchitectureSummaryGenerator.ts:10, :321 and settings.enhancements.json:761 are worth correcting in the same pass.
Note on impact
With the shipped SessionEnd registration this costs one unnecessary subprocess and one needless file rewrite per session end — modest, but it also means the handler's actual purpose (skip when nothing changed) has never worked, and on Linux it never regenerates at all.
Worth flagging for anyone who has customised the registration: if DocIntegrity.hook.ts is moved to Stop, this fires on every assistant turn. On a machine with several concurrent sessions that was measurable CPU load — which is how I found it. That amplification is a local misconfiguration, not a defect in the shipped config, but the underlying dead-code path is the shipped behaviour.
Bug:
RebuildArchSummarynever skips — checks a filename nothing ever writes, and resolves the generator with the wrong caseSummary
hooks/handlers/RebuildArchSummary.tscontains two independent path defects in a single function. Together they mean the handler either regenerates the architecture summary every time it runs, or never runs at all, depending on your filesystem — but never does the thing it was written to do, which is regenerate only when a tracked system file actually changed.The entire mtime-comparison body of the function is unreachable on every platform.
Defect 1 — the existence check targets a filename that is never produced
LifeOS/install/hooks/handlers/RebuildArchSummary.ts:27But the generator writes a different name —
LifeOS/install/LIFEOS/TOOLS/ArchitectureSummaryGenerator.ts:39:LIFEOS_ARCHITECTURE_SUMMARY.mddoes not exist anywhere in the repository and no code path writes it. The only file present isLifeOS/install/LIFEOS/DOCUMENTATION/ARCHITECTURE_SUMMARY.md.So
existsSync(output)is unconditionallyfalse, and the handler always takes the early-return branch at lines 33–38:Everything after that
return— thetrackedDirswalk, the mtime comparison, and the"... is current"skip path — is dead code that has never executed.Effect: every invocation spawns
bun ArchitectureSummaryGenerator.ts generateas a subprocess and rewritesARCHITECTURE_SUMMARY.md, whether or not anything changed. The file is left perpetually dirty in git.Defect 2 — the generator path uses the wrong case (breaks on case-sensitive filesystems)
LifeOS/install/hooks/handlers/RebuildArchSummary.ts:28The canonical path in the repo is
LIFEOS/TOOLS/ArchitectureSummaryGenerator.ts— uppercaseTOOLS. Every other reference in the tree uses that casing (LIFEOS/TOOLS/DocCheck.ts:58,LIFEOS/TOOLS/ReferenceCheck.ts:319).Two lines later:
Tools/resolves toTOOLS/, the check passes, and Defect 1 fires — constant regeneration.existsSyncreturnsfalseand the handler returns immediately. The architecture summary is never regenerated, silently.Defect 2 masks Defect 1 on Linux, which is likely why the constant-regeneration symptom hasn't been widely reported.
This is the same class as the case-duplicate path cleanup in bc0a20a (issues #1556 / #1557), just in a hook handler rather than a template.
Documentation is inconsistent too, in the same direction
The doc strings disagree with each other about the filename, which looks like a rename that only partially landed:
LifeOS/install/settings.enhancements.json:761— "...regenerates LIFEOS_ARCHITECTURE_SUMMARY.md on Stop."LifeOS/install/settings.enhancements.json:791— "...regenerates ARCHITECTURE_SUMMARY.md when LifeosSystemArchitecture.md changes."ArchitectureSummaryGenerator.ts:10and:321sayLIFEOS_ARCHITECTURE_SUMMARY.mdin comments/help text, while the code at:39writesARCHITECTURE_SUMMARY.md.RebuildArchSummary.ts:3and:8sayDOCUMENTATION/ARCHITECTURE_SUMMARY.md— contradicting line 27 of the same file.Affected versions
Present in every release where both files exist: v6.0.2, v6.0.3, v6.0.5, v7.0.0, v7.1.1, and current
main(origin/HEAD). The handler line has been byte-identical since v6.0.0.Verified with:
Reproduction (macOS)
DocIntegrity.hook.tsfire (it callshandleRebuildArchSummary()).[RebuildArchSummary] Architecture summary missing - regenerating— even thoughLIFEOS/DOCUMENTATION/ARCHITECTURE_SUMMARY.mdis present and current.git statusshowsARCHITECTURE_SUMMARY.mdmodified after every run."[RebuildArchSummary] DOCUMENTATION/ARCHITECTURE_SUMMARY.md is current"message never appears in any log, because that branch is unreachable.Suggested fix
Ideally both constants would be imported from a shared module rather than duplicated as string literals in two files — that duplication is what allowed them to drift apart. The stale
LIFEOS_ARCHITECTURE_SUMMARY.mdmentions inArchitectureSummaryGenerator.ts:10,:321andsettings.enhancements.json:761are worth correcting in the same pass.Note on impact
With the shipped
SessionEndregistration this costs one unnecessary subprocess and one needless file rewrite per session end — modest, but it also means the handler's actual purpose (skip when nothing changed) has never worked, and on Linux it never regenerates at all.Worth flagging for anyone who has customised the registration: if
DocIntegrity.hook.tsis moved toStop, this fires on every assistant turn. On a machine with several concurrent sessions that was measurable CPU load — which is how I found it. That amplification is a local misconfiguration, not a defect in the shipped config, but the underlying dead-code path is the shipped behaviour.