You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix: close five more defects the delta review found
The bare-`yarn` branch was the worst of these. It lived inside the generic
VERBS pattern, whose prefix any space satisfies, so it matched the token
ANYWHERE and blocked `which yarn`, `rm -rf /tmp/yarn` and `git switch -c
feat/yarn`. That fires on ordinary commands in a linked worktree, which is the
mandated working state here, and a gate that cries wolf gets turned off. It is
now its own anchored pattern requiring command position.
The hyphenated npm verbs needed spelling out. The trailing word boundary
excludes `-`, so listing `install` never reached `install-test`,
`install-ci-test` or `clean-install-test`, and the short aliases do not cover
them. The remove verbs are in the table too: `npm rm` in a linked worktree
deletes from the checkout that owns the tree.
`--check` under WEBJS_NO_WORKTREE_REPAIR=1 exited 0 while inspecting nothing,
because `touched` kept its initializer when the repair block was skipped. The
hatch suppresses the repair WRITE, and `--check` never writes, so it now always
inspects. My previous test asserted the exit 0 and locked the defect in.
The `entry_name` rename gets the source guard the last commit claimed for it.
Copy file name to clipboardExpand all lines: AGENTS.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -66,7 +66,7 @@ The script discovers the `node_modules` set from the primary checkout rather tha
66
66
67
67
**Know what this does NOT give you.** The worktree then runs the PRIMARY checkout's framework source through every bare `@webjsdev/*` specifier, because `<primary>/node_modules/@webjsdev/core` is a relative symlink into `<primary>/packages/core` and resolving through the linked root lands there. Relative imports (`../../../src/x.js`) and the browser suite, which web-test-runner serves from the worktree, do use the worktree's own files. So linking makes the suite RUNNABLE, not self-testing: if you are editing `packages/core/src` or `packages/server/src` and need a bare-specifier consumer to exercise YOUR copy, delete the `node_modules` SYMLINK first (`rm node_modules`, it is only a link and nothing else is lost) and then install, or repoint the individual `@webjsdev/<pkg>` entries at it. CI always builds from the branch, so it is unaffected either way.
68
68
69
-
**NEVER install while the `node_modules` symlink is standing (#1442).** This is the trap the two paragraphs above used to walk you into, and the damage lands on a checkout you are not working in, so the failure surfaces in someone else's session with nothing naming the cause. Measured on npm 11.19.0 and bun 1.3.14: `npm ci` DELETES the primary's whole `node_modules` through the link before any lifecycle script runs, `bun install` writes packages and `.bin` entries straight into the primary through it, and `npm install` silently replaces the link with a real tree, detaching the worktree from the shared source. No `preinstall` script can prevent any of it, because npm removes the symlink before `preinstall` runs, `npm ci` has already emptied the primary by then, and Bun runs it in time but ignores a non-zero exit. So the layers are: Claude Code BLOCKS the command through `.claude/hooks/block-install-in-linked-worktree.sh` (escape hatch `WEBJS_NO_WORKTREE_INSTALL_GATE=1`), the root `preinstall` REPORTS it for every other tool without ever blocking, `npm run worktree:link` REPAIRS an already-damaged primary, and `npm run check:worktree-links` reports what it would repair without changing anything (escape hatch `WEBJS_NO_WORKTREE_REPAIR=1`). Tests: `test/hooks/block-install-in-linked-worktree.test.mjs`, `test/repo-health/warn-worktree-install.test.mjs`, `test/repo-health/link-worktree-deps.test.mjs`.
69
+
**NEVER install while the `node_modules` symlink is standing (#1442).** This is the trap the two paragraphs above used to walk you into, and the damage lands on a checkout you are not working in, so the failure surfaces in someone else's session with nothing naming the cause. Measured on npm 11.19.0 and bun 1.3.14: `npm ci` DELETES the primary's whole `node_modules` through the link before any lifecycle script runs, `bun install` writes packages and `.bin` entries straight into the primary through it, and `npm install` silently replaces the link with a real tree, detaching the worktree from the shared source. No `preinstall` script can prevent any of it, because npm removes the symlink before `preinstall` runs, `npm ci` has already emptied the primary by then, and Bun runs it in time but ignores a non-zero exit. So the layers are: Claude Code BLOCKS the command through `.claude/hooks/block-install-in-linked-worktree.sh` (escape hatch `WEBJS_NO_WORKTREE_INSTALL_GATE=1`), the root `preinstall` REPORTS it for every other tool without ever blocking, `npm run worktree:link` REPAIRS an already-damaged primary, and `npm run check:worktree-links` reports what it would repair without changing anything, exiting non-zero when there is work. `WEBJS_NO_WORKTREE_REPAIR=1` suppresses the repair WRITE, so it has no effect on `--check`, which never writes and always inspects. Tests: `test/hooks/block-install-in-linked-worktree.test.mjs`, `test/repo-health/warn-worktree-install.test.mjs`, `test/repo-health/link-worktree-deps.test.mjs`.
70
70
71
71
Note the `webjs doctor` / `webjs dev` remedy message suggests the root-only symlink. That advice is correct for a scaffolded APP worktree, which has no nested trees and no built `dist/`, and wrong only for this monorepo. It stays app-generic on purpose, because it ships in the published CLI and `webjs dev` prints it verbatim to someone whose app has none of this repo's scripts; it names `npm run worktree:link` only when it finds a package.json actually declaring that script, so in this repo you get the monorepo path and in a scaffolded app you do not.
Copy file name to clipboardExpand all lines: framework-dev.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -165,7 +165,7 @@ So prevention lives one layer up, and the rest is repair:
165
165
-**Block.**`.claude/hooks/block-install-in-linked-worktree.sh` is a `PreToolUse` (Bash) hook, the only layer that sees the state before the package manager starts. It refuses an install verb whose target directory has a symlinked `node_modules`, and stays narrow: `npm test`, `npm run <script>`, and `npx ...` all pass. Escape hatch `WEBJS_NO_WORKTREE_INSTALL_GATE=1`.
166
166
-**Report.** The root `preinstall` runs `scripts/warn-worktree-install.mjs`, which ALWAYS exits 0 and returns immediately unless `.git` is a FILE, so a normal clone and CI never see it. It names whichever of the three states it landed in and prints the repair.
167
167
-**Repoint on teardown.**`.claude/hooks/cleanup-merged-worktree.sh` repoints any `<primary>/node_modules/@webjsdev/*` link targeting a worktree it is about to remove, before removing it.
168
-
-**Repair on demand.**`npm run worktree:link` repairs the primary's `@webjsdev/*` scope: a dangling link, a link into a live foreign checkout, and an absolute in-primary link all become the relative form, and a DANGLING `.name-HASH` npm staging entry is dropped. A LIVE staging entry is left strictly alone. `npm run check:worktree-links` reports without writing and exits non-zero when there is work. Escape hatch `WEBJS_NO_WORKTREE_REPAIR=1`, which the `defaultPrimary()` test needs for the same reason it needs `WEBJS_NO_WORKTREE_SEED=1`: the repair pass sits ABOVE the primary-checkout guard by design, so it runs in both positions and would otherwise rewrite the real checkout during `npm test`.
168
+
-**Repair on demand.**`npm run worktree:link` repairs the primary's `@webjsdev/*` scope: a dangling link, a link into a live foreign checkout, and an absolute in-primary link all become the relative form, and a DANGLING `.name-HASH` npm staging entry is dropped. A LIVE staging entry is left strictly alone. `npm run check:worktree-links` reports without writing and exits non-zero when there is work. Escape hatch `WEBJS_NO_WORKTREE_REPAIR=1` suppresses the repair WRITE only, so `--check` ignores it and still inspects; the `defaultPrimary()` test needs the hatch for the same reason it needs `WEBJS_NO_WORKTREE_SEED=1`: the repair pass sits ABOVE the primary-checkout guard by design, so it runs in both positions and would otherwise rewrite the real checkout during `npm test`.
169
169
-**Detect.**`webjs doctor`'s `framework-links` check warns on a dangling or foreign `@webjsdev/core` link. `FRAMEWORK_RESOLVE` cannot see the foreign case, because a link into a live checkout resolves perfectly.
test('the broadened alias table does not swallow non-install commands',()=>{
100
141
const{ root, worktree }=makeLinkedPair();
101
142
try{
102
143
// `npm init` must not match `in` or `i`, and a bare `yarn test` must not
103
144
// match the bare-yarn install branch.
104
-
for(constcmdof['npm init','npm init -y','yarn test','yarn run build','bun run dev','bun test','pnpm run build']){
145
+
for(constcmdof['npm init','npm init -y','yarn test','yarn run build','bun run dev','bun test','pnpm run build','git rm x','rm -rf node_modules','npm run rm']){
105
146
assert.equal(runHook(cmd,worktree).status,0,`expected allow for \`${cmd}\``);
0 commit comments