Skip to content

Enforce Bun parity as a blocking gate (hook + skill + docs) #661

@vivek7405

Description

@vivek7405

Problem

Bun cross-runtime parity is supposed to be part of the definition of done (webjs runs on Node 24+ AND Bun, #508), but the guardrail is too weak, so it slips to an afterthought. Concretely, during #659 (the Origin/Sec-Fetch-Site CSRF change, which sits squarely on the request path) NOTHING prompted Bun verification: the CSRF check, SSR response building, and action dispatch are runtime-sensitive (the Bun.serve shell must preserve the headers the check reads), yet the change shipped, was reviewed, and was marked ready before Bun was run or the Bun tests updated. The user had to ask for it explicitly.

Two gaps in .claude/hooks/require-tests-with-src.sh:

  1. It only emits a SOFT, non-blocking additionalContext reminder for runtime-sensitive source; nothing blocks a commit that changes a runtime-sensitive surface without touching a test/bun/* script.
  2. Its runtime-sensitive regex is serialize|file-storage|listener-core|listener-bun|ts-strip|action-stream|render-server|websocket|node-version, which MISSES the request-handling path: csrf, actions, ssr, the dev handler, auth, session, cors. So a request-path change does not even trigger the soft reminder.

Design / approach

Make Bun parity a first-class, blocking gate, matching the existing require-tests-with-src.sh / require-docs-with-src.sh model (block + named env escape hatch).

  • Add a new PreToolUse Bash hook .claude/hooks/require-bun-parity-with-runtime-src.sh: on a git commit, if the staged diff changes framework source on a runtime-sensitive surface AND stages no test/bun/** file, BLOCK (exit 2). The message tells the author to add/update a test/bun/<feature>.mjs cross-runtime assertion, OR re-run with WEBJS_BUN_VERIFIED=1 to acknowledge an existing test/bun script already covers it AND was run under Bun (node scripts/run-bun-tests.js + the relevant test/bun/*.mjs).
  • Broaden the runtime-sensitive surface list (shared shape with the existing hook's regex) to add: csrf, actions, ssr, dev (the request handler), request, auth, session, cors alongside the existing serializer / listener / stream / crypto / ts-strip / websocket / node-version entries.
  • Keep require-tests-with-src.sh's reminder but widen its regex to match, so the soft nudge and the hard gate agree.
  • Elevate Bun parity in the webjs-start-work skill: today it is buried inside the test-layer bullet (SKILL.md ~L114). Add a distinct, prominent Definition-of-done line ("Bun parity is required for any runtime-sensitive change: run the Bun matrix and add/update test/bun") and mention the new hook.
  • Update root AGENTS.md (the "Code workflow (mandatory)" / testing area) and agent-docs/testing.md so running the Bun matrix and adding/updating test/bun is stated as part of the definition of done for a runtime-sensitive change, not an optional extra.

Alternatives considered: extending the existing hook to block instead of adding a new one. Rejected to keep the repo's one-hook-per-concern shape (block-prose, block-raw-htmlelement, require-tests, require-docs) and so the Bun gate has its own escape hatch and its own test.

Implementation notes (for the implementing agent)

  • Where to edit:
    • New hook: .claude/hooks/require-bun-parity-with-runtime-src.sh (model it on .claude/hooks/require-tests-with-src.sh: read the git commit Bash payload via jq -r '.tool_input.command', word-match git commit, inspect git diff --cached --name-only, gate packages/([^/]+/src|editors/[^/]+/src|cli/lib)/). Make executable (chmod +x).
    • Wire it: .claude/settings.json PreToolUse matcher: "Bash" array (alongside require-tests-with-src.sh / require-docs-with-src.sh).
    • Widen the regex in .claude/hooks/require-tests-with-src.sh runtime_sensitive=... to include the request-path tokens.
    • Hooks test: add test/hooks/require-bun-parity.test.mjs (the repo already has test/hooks/route-skills.test.mjs; follow its harness shape, invoking the hook script with a crafted payload + a temp git index, asserting exit 2 when runtime-sensitive src is staged with no test/bun, exit 0 when a test/bun file is staged or WEBJS_BUN_VERIFIED=1).
    • Skill: .claude/skills/webjs-start-work/SKILL.md (the Definition-of-done section).
    • Docs: root AGENTS.md ("Code workflow (mandatory)") + agent-docs/testing.md.
  • Landmines:
    • The hook fires only on git commit Bash calls; --no-verify and the env escape hatch must both be honored (mirror require-tests-with-src.sh's wording).
    • Do NOT block a pure docs / release / non-src commit (the packages/*/src gate handles that). A test/bun change alone, or a non-runtime-sensitive src change, must pass.
    • Bun must NOT be required to RUN for the hook itself (the hook is static file analysis); the hook only checks that a test/bun file is staged or the ack flag is set. Running Bun is the author's job, prompted by the message.
    • Invariant release: bump core/server/cli versions, honest engines fields #11 (no em-dash / pause-punctuation) applies to the hook's heredoc message text and all new prose.
  • Invariants to respect: AGENTS.md "Code workflow (mandatory)"; the cross-runtime rule (Support both Bun and Node runtimes (first-class create + run) #508); the one-hook-per-concern + escape-hatch pattern of the existing hooks.

Acceptance criteria

  • A git commit that stages a runtime-sensitive packages/*/src change with no test/bun/** file is BLOCKED (exit 2) with an actionable message naming the Bun matrix + WEBJS_BUN_VERIFIED=1 escape hatch.
  • The same commit passes when a test/bun/** file is staged, or with WEBJS_BUN_VERIFIED=1.
  • A non-runtime-sensitive src change, and a docs/test-only commit, are NOT blocked.
  • The runtime-sensitive surface list now matches the request path (csrf / actions / ssr / dev / auth / session / cors) so a CSRF-style change trips the gate.
  • test/hooks/require-bun-parity.test.mjs covers block + both pass paths (a counterfactual that the gate fires).
  • webjs-start-work skill + root AGENTS.md + agent-docs/testing.md state Bun parity as part of the definition of done.
  • The hook is wired in .claude/settings.json and is executable.

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type
No fields configured for issues without a type.

Projects

Status
Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions