* test(#2717): CommonJS marker for cursor/windsurf/codex staged .js hooks
Cursor/windsurf (skipSharedHooksInstall) and codex (!isCodex gate) stage .js
hook scripts via dedicated paths that bypass installSharedHooksBundle — the
only writer of the {"type":"commonjs"} marker. Under a config root declaring
{"type":"module"}, Node loaded those scripts as ESM and every require()
failed with 'require is not defined', silently disabling the runtime's hooks.
Adds regression tests (RED first, fix lands next commit):
- parametrized cursor/windsurf/codex install asserts hooks/package.json exists
with exactly GSD's marker content;
- end-to-end: a cursor require()-using hook loads under a planted ESM-typed
config root without the require-is-not-defined error;
- the ensureCommonJsMarker / removeCommonJsMarkerIfGsdOwned contract: GSD
markers are removed on uninstall, user-authored package.json is never touched.
* fix(#2717): write CommonJS marker for cursor/windsurf/codex staged .js hooks
The {"type":"commonjs"} marker lived only inside installSharedHooksBundle,
which cursor/windsurf (skipSharedHooksInstall) and codex (!isCodex gate) never
reach. Their .js hooks are staged by dedicated paths, so under a config root
declaring {"type":"module"} Node loaded them as ESM and every require()
failed with 'require is not defined', silently disabling those runtimes' hooks.
Decouple the marker write into a shared helper so any code path that stages
.js hooks can ensure it lands in the SAME directory as the scripts:
- src/runtime-hooks-surface.cts: add ensureCommonJsMarker(dir) +
removeCommonJsMarkerIfGsdOwned(dir) (byte-identical content to
installSharedHooksBundle's marker; preserves a user-authored package.json on
both write and uninstall). Call ensureCommonJsMarker(hooksDir) from
writeCursorHooksJson + writeWindsurfHooksJson; call
removeCommonJsMarkerIfGsdOwned on their matching remove paths. Export both.
- bin/install.js: call hooksSurface.ensureCommonJsMarker after the codex hook
copy; call hooksSurface.removeCommonJsMarkerIfGsdOwned in the generic
hooks-removal loop (safe no-op where no marker exists).
No change to which runtimes receive the shared bundle, the !isCodex gate,
skipSharedHooksInstall, or kimi/kimi-code/cline/copilot/trae/zcode (all
unchanged — audit in the diagnosis). RED @ dbb7d2bb (6 failures: 3 missing
markers + the ESM require error + missing helpers); GREEN pending.
* docs(#2717): changeset fragment (pr:0, backfilled post-PR)
* chore(#2717): regen codex/cursor/windsurf install-tree fixtures + attribution ack
The fix adds hooks/package.json to those three runtimes' install trees (the
new CommonJS marker), so the golden install-tree fixtures gain one path each
(regenerated via npm run gen:install-tree). emitted-attribution (ADR-2719)
flags the 3 emitted hooks/package.json paths under the hooks-built rule;
acknowledge them. Also drops 5 spent ack entries left by now-merged PRs
(#2694 code-review.md/code-review-fix.md, #2695 worker/registry, #2794
review.md) — they are stale on this branch (base already carries them).
* fix(#2717): codex ESM-root behavioral test + hooks-built provenance for package.json
Two review-driven follow-ups on the #2717 fix:
- Adversarial review noted the ESM-root behavioral test covered only cursor;
refactor it into a helper and add a codex case (the !isCodex-gated path most
likely to regress, whose marker write lives in bin/install.js). gsd-check-update.js
require()s at module load, so it surfaces the ESM failure immediately.
- emitted-provenance flagged hooks/package.json as 'attributed source does not
exist' — the marker is code-derived (a fixed literal emitted by
ensureCommonJsMarker at install time), not built from a tracked source. Route
the hooks-built rule's sources/transforms for package.json to the surface
source file, mirroring the existing .cmd-shim sub-family.
* chore(#2717): drop now-redundant hooks/package.json attribution ack
The hooks-built provenance routing (prior commit) now self-attributes the
emitted hooks/package.json to src/runtime-hooks-surface.cts, which IS in this
diff — so the attribution is self-explaining and the emitted-drift-ack entry
became stale. Delete the (now-empty) ack file per ADR-2719's empty-file rule.
* docs(changeset): backfill #2717 PR number to 2846