The installer ships 39 hook scripts and registers 28; nothing compares the two inventories (follow-up to #1508)
Structured for machine parsing: claims are atomic, and each carries the command that verifies it. Every command runs against a fresh clone of this repo — none depend on my machine, my install, or my configuration. Reproduction is at the bottom if you'd rather run it than read it.
Summary
InstallHooks.ts performs two operations and verifies each independently:
cpSync the entire payload hooks/ tree onto disk — verified by a file count (hookFiles vs hookFilesCopied).
- Merge
hooks/hooks.json into settings.json — verified by reporting added, skipped, events.
Both succeed. Neither is wrong. Nothing compares the set of files copied against the set of hooks registered. 39 hook scripts land on disk; 28 are named by the manifest merged beside them. The install reports success accurately, because "validated" here means "both operations completed," never "the things I copied are the things I registered."
Of the 11-hook difference, 8 are legitimately loaded via dispatcher imports. Three are unreachable: DriftReminder, PostToolObserver, and LoopDetector (whose only importer is PostToolObserver).
Doctor.ts --reconcile — which you shipped in v7.1.1 for exactly this, and which the same install run copies onto disk — reports all three correctly in under a second. The installer does not call it.
Claims and verification
Run from the root of a fresh clone.
C1 — the payload ships 39 hook scripts.
ls LifeOS/install/hooks/*.hook.ts LifeOS/install/hooks/*.hook.sh | wc -l
# 39
C2 — the manifest registers 28 distinct hooks.
grep -oE '[A-Za-z]+\.hook\.(ts|sh)' LifeOS/install/hooks/hooks.json | sort -u | wc -l
# 28
C3 — 11 shipped hooks are absent from the manifest.
comm -23 \
<(ls LifeOS/install/hooks/*.hook.ts LifeOS/install/hooks/*.hook.sh | xargs -n1 basename | sort) \
<(grep -oE '[A-Za-z]+\.hook\.(ts|sh)' LifeOS/install/hooks/hooks.json | sort -u)
# 11 lines
C4 — of those 11, two have no importer anywhere; they are unreachable by any path.
comm -23 \
<(ls LifeOS/install/hooks/*.hook.ts LifeOS/install/hooks/*.hook.sh | xargs -n1 basename | sort) \
<(grep -oE '[A-Za-z]+\.hook\.(ts|sh)' LifeOS/install/hooks/hooks.json | sort -u) \
| while read -r f; do b="${f%.hook.*}"
grep -qlE "from \"\./$b\.hook\"" LifeOS/install/hooks/*.hook.ts 2>/dev/null || echo "UNWIRED: $f"
done
# UNWIRED: DriftReminder.hook.ts
# UNWIRED: PostToolObserver.hook.ts
The other 8 resolve to PreToolGuard, StopGates, or MemoryTurnStart and are correctly dispatcher-loaded.
C5 — LoopDetector is dead transitively: its sole importer is PostToolObserver, which is itself unwired. C4's check misses this because it only tests one level; your reconciler does the transitive closure and catches it.
C6 — your own tool reports all three. Against an installed tree:
bun LIFEOS/TOOLS/Doctor.ts --reconcile
# { "unwired": ["LoopDetector.hook.ts", "DriftReminder.hook.ts", "PostToolObserver.hook.ts"], ... }
Output from a clean v7.1.1 install, today. This matches C4+C5 exactly.
C7 — Doctor.ts ships in the payload and is copied by the same install run.
ls LifeOS/install/LIFEOS/TOOLS/Doctor.ts
C8 — InstallHooks.ts never cross-checks the two inventories.
LifeOS/Tools/InstallHooks.ts — hookFiles is set at line 71 from the payload dir, hookFilesCopied at line 102 from the destination; added/skipped/events come from mergeHooks at line 80. The two are reported in the same JSON object (line 104) and never compared. No call to the reconciler appears in the file.
C9 — the shipped docs disagree with the shipped manifest. LifeOS/install/LIFEOS/DOCUMENTATION/Hooks/HookSystem.md:1562 states:
"the UserPromptSubmit chain is 6 hooks (PromptProcessing, SatisfactionCapture, ReminderRouter, DriftReminder, MemoryTurnStart, AlgorithmNudge)"
jq -r '.hooks.UserPromptSubmit[].hooks[].command' LifeOS/install/hooks/hooks.json
# 4 entries; DriftReminder and AlgorithmNudge absent
Same for HookSystem.md:322 (PostToolObserver on PostToolUse) and :22 (census of 30 registered; actual 28).
Why this wasn't caught
Not an oversight in the reconciler — it works. The gap is that --reconcile has exactly one automatic caller, LIFEOS/PULSE/modules/doctor.ts, which serves it to the Pulse System→Hooks page. Pulse is an à-la-carte component. On a Core install it never runs, so the reconciler's correct answer is never requested.
Impact
DriftReminder is named twice in LIFEOS_SYSTEM_PROMPT.md as the enforcement for voice/format drift, and HookSystem.md documents it as registered. It never executes. PostToolObserver is documented as the sync catch-all; loop/oscillation detection never executes.
Because these hooks never fire, they also produce no logs — so on a stock install their absence is silent. There is no error, no degraded mode, and no telemetry indicating anything is missing.
Suggested fix
One line in InstallHooks.ts: after --apply, shell Doctor.ts --reconcile and print unwired alongside the existing success report. The tool is already on disk at that point in the install — it was copied by the same run.
This would have surfaced these three hooks at install time, on every install, for every user, in the release that introduced the reconciler.
Happy to open the PR.
Reproduction
git clone https://github.com/danielmiessler/LifeOS && cd LifeOS
ls LifeOS/install/hooks/*.hook.ts LifeOS/install/hooks/*.hook.sh | wc -l # 39
grep -oE '[A-Za-z]+\.hook\.(ts|sh)' LifeOS/install/hooks/hooks.json | sort -u | wc -l # 28
Two numbers, one repo, no install required.
Scope
Verified against v7.1.1 on a clean install (second vanilla install; the first was archived to eliminate a legacy-content variable). C1–C5 and C7–C9 are properties of the repo tree and hold for any clone. C6 was run on my install; it depends on settings.json content and will differ on an install whose settings carry additional registrations.
The installer ships 39 hook scripts and registers 28; nothing compares the two inventories (follow-up to #1508)
Summary
InstallHooks.tsperforms two operations and verifies each independently:cpSyncthe entire payloadhooks/tree onto disk — verified by a file count (hookFilesvshookFilesCopied).hooks/hooks.jsonintosettings.json— verified by reportingadded,skipped,events.Both succeed. Neither is wrong. Nothing compares the set of files copied against the set of hooks registered. 39 hook scripts land on disk; 28 are named by the manifest merged beside them. The install reports success accurately, because "validated" here means "both operations completed," never "the things I copied are the things I registered."
Of the 11-hook difference, 8 are legitimately loaded via dispatcher imports. Three are unreachable:
DriftReminder,PostToolObserver, andLoopDetector(whose only importer isPostToolObserver).Doctor.ts --reconcile— which you shipped in v7.1.1 for exactly this, and which the same install run copies onto disk — reports all three correctly in under a second. The installer does not call it.Claims and verification
Run from the root of a fresh clone.
C1 — the payload ships 39 hook scripts.
C2 — the manifest registers 28 distinct hooks.
C3 — 11 shipped hooks are absent from the manifest.
C4 — of those 11, two have no importer anywhere; they are unreachable by any path.
The other 8 resolve to
PreToolGuard,StopGates, orMemoryTurnStartand are correctly dispatcher-loaded.C5 —
LoopDetectoris dead transitively: its sole importer isPostToolObserver, which is itself unwired. C4's check misses this because it only tests one level; your reconciler does the transitive closure and catches it.C6 — your own tool reports all three. Against an installed tree:
bun LIFEOS/TOOLS/Doctor.ts --reconcile # { "unwired": ["LoopDetector.hook.ts", "DriftReminder.hook.ts", "PostToolObserver.hook.ts"], ... }Output from a clean v7.1.1 install, today. This matches C4+C5 exactly.
C7 —
Doctor.tsships in the payload and is copied by the same install run.C8 —
InstallHooks.tsnever cross-checks the two inventories.LifeOS/Tools/InstallHooks.ts—hookFilesis set at line 71 from the payload dir,hookFilesCopiedat line 102 from the destination;added/skipped/eventscome frommergeHooksat line 80. The two are reported in the same JSON object (line 104) and never compared. No call to the reconciler appears in the file.C9 — the shipped docs disagree with the shipped manifest.
LifeOS/install/LIFEOS/DOCUMENTATION/Hooks/HookSystem.md:1562states:Same for
HookSystem.md:322(PostToolObserveron PostToolUse) and:22(census of 30 registered; actual 28).Why this wasn't caught
Not an oversight in the reconciler — it works. The gap is that
--reconcilehas exactly one automatic caller,LIFEOS/PULSE/modules/doctor.ts, which serves it to the Pulse System→Hooks page. Pulse is an à-la-carte component. On a Core install it never runs, so the reconciler's correct answer is never requested.Impact
DriftReminderis named twice inLIFEOS_SYSTEM_PROMPT.mdas the enforcement for voice/format drift, andHookSystem.mddocuments it as registered. It never executes.PostToolObserveris documented as the sync catch-all; loop/oscillation detection never executes.Because these hooks never fire, they also produce no logs — so on a stock install their absence is silent. There is no error, no degraded mode, and no telemetry indicating anything is missing.
Suggested fix
One line in
InstallHooks.ts: after--apply, shellDoctor.ts --reconcileand printunwiredalongside the existing success report. The tool is already on disk at that point in the install — it was copied by the same run.This would have surfaced these three hooks at install time, on every install, for every user, in the release that introduced the reconciler.
Happy to open the PR.
Reproduction
Two numbers, one repo, no install required.
Scope
Verified against v7.1.1 on a clean install (second vanilla install; the first was archived to eliminate a legacy-content variable). C1–C5 and C7–C9 are properties of the repo tree and hold for any clone. C6 was run on my install; it depends on
settings.jsoncontent and will differ on an install whose settings carry additional registrations.