fix: anticipate.sh never finds its own modules on a real install (L4 ships inert) - #114
Open
ObiWanKenobi wants to merge 1 commit into
Open
fix: anticipate.sh never finds its own modules on a real install (L4 ships inert)#114ObiWanKenobi wants to merge 1 commit into
ObiWanKenobi wants to merge 1 commit into
Conversation
…ped inert)
anticipate.sh derived its module directory by assertion:
CODE_ROOT=$SELF_DIR/../.. -> $CODE_ROOT/scripts/goal-match.mjs
That is correct only for the source layout <root>/plugin/scripts/. Both
layouts a user actually receives flatten the `plugin/` level -- the Claude
Code plugin directory and the Stable Spine's versions/<gen>/ tree both put
this script at <root>/scripts/ -- so `../..` overshoots by one directory,
goal-match.mjs is never found, and the
[ -f "$GOAL_MATCH" ] || exit 0
guard disables the whole L4 anticipatory surface. Measured on a real
install: identical inputs, the source layout speaks and the shipped layout
is silent.
The reason this survived a 678-line integration suite is that every run in
tests/integration/anticipate.test.mjs injects the path:
...(matcher ? { RUVNET_GOAL_MATCH: matcher } : {}),
so the default resolution had no coverage. Even "exits 0 and stays quiet
when the matcher module does not exist" passes an explicit path to a
missing file. The only symptom is silence, which is also the correct
behaviour on most prompts, so nothing ever looked wrong.
This is the fifth instance of the class documented in
tests/unit/installer-sibling-imports-packaged.test.mjs: "A capability that
silently does not exist on real installs is worse than one that was never
built, because the team believes it is running and stops looking."
Fix: resolve by probing the filesystem instead of asserting a shape -- try
each candidate directory, take the first that actually holds the matcher.
If none does, fall through to the historical path so the existing
diagnostics still name the expected location, and keep the no-node fast
path. Silence on doubt, never a guess.
Adds tests/integration/anticipate-module-resolution.test.mjs, which sets NO
module env vars and asserts resolution in both layouts. It fails on the
shipped-layout case without this change and passes with it, and re-asserts
the silence contract so a "make it speak" fix cannot become a nag.
NOTE: packaging is the other half and is NOT addressed here. goal-match.mjs,
capability-registry.mjs and advocacy-outcomes.mjs live only in the repo-root
scripts/, which the plugin directory does not carry, so on a real install
this resolver still finds nothing until they ship under plugin/. Raised in
the PR rather than guessed at here.
|
@ObiWanKenobi is attempting to deploy a commit to the stuart kerr's projects Team on Vercel. A member of the Team first needs to authorize it. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
plugin/scripts/anticipate.shderives its module directory by assertion:../..is correct only for the source layout<root>/plugin/scripts/. Both layouts a user actually receives flatten theplugin/level, so it overshoots by one directory:anticipate.shat../..resolves to<plugin-cache>/ruvnet-brain/<ver>/scripts/<plugin-cache>/ruvnet-brain~/.cache/ruvnet-brain/versions/<gen>/scripts/.../versions<src>/plugin/scripts/<src>[ -f "$GOAL_MATCH" ] || exit 0then fires and the entire L4 anticipatory surface is disabled. Measured on a real install — identical stdin and registry, only the directory differs:The script's own comment asserts that a dev checkout and the Spine "both keep
scripts/as a sibling ofplugin/". On a real install, neither does.Why it survived the test suite
tests/integration/anticipate.test.mjsis 678 lines and thorough, but every run injects the path:so the default resolution has no coverage. Even "exits 0 and stays quiet when the matcher module does not exist" passes an explicit path to a missing file. The only symptom is silence — which is also the correct behaviour on the overwhelming majority of prompts — so nothing ever looked wrong.
This is the fifth instance of the class already documented in
tests/unit/installer-sibling-imports-packaged.test.mjs:Same shape, different file. That gate derives its list from
bin/install.mjs's imports, so it does not coveranticipate.sh's.The fix
Resolve by probing the filesystem rather than asserting a shape: try each candidate directory, take the first that actually holds the matcher. If none does, fall through to the historical path so existing diagnostics still name the expected location, and keep the no-node fast path intact. Silence on doubt, never a guess.
Test
Adds
tests/integration/anticipate-module-resolution.test.mjs, which sets no module env vars — that omission is the point.Full
tests/unitshows 7 failures — all present on unmodifiedmain(verified on a clean tree), all insession-start/brain-off/star-ask/hook-registry-lint, none touching this path. One is self-labelled expected-red per ADR-055 appendix B.Not addressed here — packaging is the other half
This change alone does not make L4 speak on a real install.
goal-match.mjs,capability-registry.mjsandadvocacy-outcomes.mjslive only in the repo-rootscripts/, which the plugin directory does not carry, so the resolver still finds nothing until they ship underplugin/.I did not guess at that, because it interacts with how the plugin is assembled for the marketplace and with the root-
scripts/importers (console-engine.mjs, the test suites). Happy to follow up with whichever mechanism you prefer — moving them underplugin/scripts/, or a copy step during assembly.Worth noting separately:
goal-match.mjsitself is in good shape. Against an adversarial table of 20 negatives borrowing every goal's vocabulary it produced zero false alarms, and it recognised all 10 goals on corroborated prompts. The two-key INTENT+SUBJECT rule does what its header claims — this is purely a delivery problem, not a matcher problem.