Follow-up to #425, which fixed the population (the user bindirs now join PATH before detection) but not the structure.
The residual bug
zsh/00-tools.zsh computes all 43 HAVE_* flags at ~line 125, against the PATH as it stands at load-order band 00. PATH is not final there and never will be:
| contributor |
when |
can it move before detection? |
mise activate zsh (00-tools.zsh:378) |
registers a runtime chpwd/precmd hook |
no — it rewrites PATH per directory, forever |
80-os.zsh |
band 80 |
no — bands ≥70 load after Core by contract |
85-*.zsh role |
band 85 |
no |
99-local.zsh |
band 99 |
no — that is the user's escape hatch |
So a tool contributed by any of those still gets no flag, no alias and no shell init, while core-doctor — which probes live via _core_have (zsh/05-ui.zsh:53) against the finished PATH — reports it ✓. The disagreement is not the bug; the bug is that it is silent, and that a ✓ reads as "Core wired this" when it only ever meant "this is on PATH right now".
zsh/00-tools.zsh already carries the standing note: "#425 is the reminder that a HAVE_* flag and the doctor disagreeing about the same box is itself a bug."
Options considered
Re-probe late and re-wire — rejected. There is no "after PATH is final" (mise's chpwd hook moves the target on every cd), and the wiring is not idempotent: re-sourcing atuin's init double-registers _atuin_precmd, 40-bindings.zsh has already bound Ctrl+E, and re-sourcing 20-aliases.zsh would silently clobber deliberate overrides from an 85-* role fragment. It also needs _have, which is unfunction'd at the end of 00-tools.zsh.
Snapshot-only warning — too coarse alone. PATH differs after band 00 on essentially every box (Homebrew, mise shims, 99-local.zsh), so a bare "PATH changed" warning fires on 100% of healthy shells and names no tool.
Doctor reports the divergence — recommended. Keep the live probe (deliberate: it is honest even if 00-tools.zsh never ran), and add a second axis: for each row, does the HAVE_* flag Core would have set actually hold a value? Present-but-unset → "installed, but Core did not wire it". The mirror case is also worth having: flag set but binary now gone → a live alias that will fail.
Sketch
00-tools.zsh records typeset -g _CORE_PROBE_PATH=$PATH after the flag block — one assignment, no fork, and it doubles as the sentinel for "detection ran in this shell" so the unit harness and scripts are not told all 41 tools are unwired.
core-doctor derives the tool→flag map by parsing 00-tools.zsh's own probe lines (^_have <tool> && HAVE_<X>=1) rather than keeping a table. That is what gets the irregulars right without maintenance — ast-grep→HAVE_ASTGREP but git-absorb→HAVE_GIT_ABSORB, two opposite conventions three lines apart. 38 of the 41 inventory entries match; fd/bat are supplied where _core_doctor_bin already special-cases them, and op correctly maps to nothing (it self-gates in 50-op.zsh). scripts/test-core.sh already parses this file with the same ^-anchor, so the convention is a de-facto pinned interface.
- Render as a
⚠ marker on the row plus a not wired block — using ⚠, not ✓/✗, keeps the render⇄JSON set-equality test blind to it by construction. The hint names the directory that joined late (set difference of live PATH against _CORE_PROBE_PATH), which is what makes it actionable.
--json gains "wiring":{"probed":bool,"unwired":[],"stale":[]}.
This also lets scripts/test-core.sh's probed ⊆ reported guard be closed in the reverse direction — reported ⊆ mapped ∪ {op} — which its comment currently has to decline.
Cost: one parameter assignment at startup. All the work lands in core-doctor, a one-off command that already forks for git --exec-path.
Split out of #425; the PATH half is fixed in #544.
Follow-up to #425, which fixed the population (the user bindirs now join PATH before detection) but not the structure.
The residual bug
zsh/00-tools.zshcomputes all 43HAVE_*flags at ~line 125, against the PATH as it stands at load-order band 00. PATH is not final there and never will be:mise activate zsh(00-tools.zsh:378)chpwd/precmdhook80-os.zsh85-*.zshrole99-local.zshSo a tool contributed by any of those still gets no flag, no alias and no shell init, while
core-doctor— which probes live via_core_have(zsh/05-ui.zsh:53) against the finished PATH — reports it✓. The disagreement is not the bug; the bug is that it is silent, and that a✓reads as "Core wired this" when it only ever meant "this is on PATH right now".zsh/00-tools.zshalready carries the standing note: "#425 is the reminder that aHAVE_*flag and the doctor disagreeing about the same box is itself a bug."Options considered
Re-probe late and re-wire — rejected. There is no "after PATH is final" (mise's
chpwdhook moves the target on everycd), and the wiring is not idempotent: re-sourcing atuin's init double-registers_atuin_precmd,40-bindings.zshhas already bound Ctrl+E, and re-sourcing20-aliases.zshwould silently clobber deliberate overrides from an85-*role fragment. It also needs_have, which isunfunction'd at the end of00-tools.zsh.Snapshot-only warning — too coarse alone. PATH differs after band 00 on essentially every box (Homebrew, mise shims,
99-local.zsh), so a bare "PATH changed" warning fires on 100% of healthy shells and names no tool.Doctor reports the divergence — recommended. Keep the live probe (deliberate: it is honest even if
00-tools.zshnever ran), and add a second axis: for each row, does theHAVE_*flag Core would have set actually hold a value? Present-but-unset → "installed, but Core did not wire it". The mirror case is also worth having: flag set but binary now gone → a live alias that will fail.Sketch
00-tools.zshrecordstypeset -g _CORE_PROBE_PATH=$PATHafter the flag block — one assignment, no fork, and it doubles as the sentinel for "detection ran in this shell" so the unit harness and scripts are not told all 41 tools are unwired.core-doctorderives the tool→flag map by parsing00-tools.zsh's own probe lines (^_have <tool> && HAVE_<X>=1) rather than keeping a table. That is what gets the irregulars right without maintenance —ast-grep→HAVE_ASTGREPbutgit-absorb→HAVE_GIT_ABSORB, two opposite conventions three lines apart. 38 of the 41 inventory entries match;fd/batare supplied where_core_doctor_binalready special-cases them, andopcorrectly maps to nothing (it self-gates in50-op.zsh).scripts/test-core.shalready parses this file with the same^-anchor, so the convention is a de-facto pinned interface.⚠marker on the row plus anot wiredblock — using⚠, not✓/✗, keeps the render⇄JSON set-equality test blind to it by construction. The hint names the directory that joined late (set difference of live PATH against_CORE_PROBE_PATH), which is what makes it actionable.--jsongains"wiring":{"probed":bool,"unwired":[],"stale":[]}.This also lets
scripts/test-core.sh'sprobed ⊆ reportedguard be closed in the reverse direction —reported ⊆ mapped ∪ {op}— which its comment currently has to decline.Cost: one parameter assignment at startup. All the work lands in
core-doctor, a one-off command that already forks forgit --exec-path.Split out of #425; the PATH half is fixed in #544.