Skip to content

feat: ship the branch guard and formatter hooks in the arjs plugin - #5

Merged
kalwalt merged 11 commits into
devfrom
feat/plugin-hooks
Oct 10, 2026
Merged

kalwalt merged 11 commits into
devfrom
feat/plugin-hooks

Conversation

@kalwalt

@kalwalt kalwalt commented Oct 9, 2026 •

Copy link
Copy Markdown
Member

Refs #1. This is the plugin side. The three repositories drop their copies in follow-up PRs.

The two Claude Code hooks copied into arjs-plugin-artoolkit, AR.js-next and arjs-plugin-threejs now ship once, in the arjs plugin. They are wired by plugins/arjs/hooks/hooks.json through ${CLAUDE_PLUGIN_ROOT}.

Hooks

  • guard-protected-branches.mjs (PreToolUse, Bash|PowerShell) refuses git commit on main and pushes to main. It is imported from arjs-plugin-artoolkit@f8a6604 and changed as follows:
    • It acts only in AR-js-org repositories (scope.mjs): any remote.<name>.url under github.com[:/]AR-js-org/, so forks with an upstream remote count too. A plugin hook runs in every repository, so outside this scope the guard does nothing.
    • It checks the PowerShell tool. Before, it checked only Bash. It handles backtick continuations and backtick escapes, and Set-Location/sl/Push-Location with -Path/-LiteralPath.
    • It follows if ($?) { … } (the PowerShell 5.1 chaining idiom) and Bash then/do/else bodies.
    • It tracks git switch / git checkout [-c|-b] within one command, but only across &&. A switch to main holds across any separator. Without this, the release skill's dev re-sync was falsely blocked.
    • git push --tags is no longer blocked. It spawns git only when a verdict needs it, and it exits 0 silently on malformed stdin.
  • format-on-edit.mjs (PostToolUse, Edit|Write|MultiEdit) runs the repository's own prettier and eslint --fix on the edited file. Changes:
    • The repository is taken from the edited file, not the session cwd, so edits to a sibling repository are formatted too. Paths are compared via realpath, which handles Windows drive letters and 8.3 names.
    • It runs only in AR-js-org repositories.
    • It reports "not installed" only when package.json declares the tool. Repositories that don't use prettier get no noise.

Repository

  • package.json (Vitest, Node ≥ 22.12 for the tests; the hooks use only Node built-ins) and CI on ubuntu and windows.
  • Tests run with stub CLIs and git fixtures isolated from the runner's config: 48 tests.
  • AGENTS.md. The guard's message points to it, and this repository is protected by the guard too.
  • README: hooks table, "Use in a repository" (the extraKnownMarketplaces / enabledPlugins snippet), migration note, tests.
  • release skill: the step 3 re-sync pushes origin dev explicitly.
  • Plugin version 0.3.0.

Known limits

The guard is a guard-rail against mistakes. GitHub branch protection stays the enforcement. It lets through, by design: subshells, bash -c, git aliases, Pop-Location, -Path:"x", and a recorded main overwritten by a later failed switch. It blocks, fail-closed, a non-main switch followed by ; (use &&).

Migration

Project hooks and plugin hooks are not deduplicated. Each repository must drop .claude/hooks/ in the same PR that enables the plugin. Those PRs (artoolkit, AR.js-next, and threejs#6) follow this one.

Verification

  • npm test: 48/48 on Windows. CI runs ubuntu and windows.
  • claude plugin validate: passes.
  • Scripted smoke test of the hooks.json commands against an AR-js-org repository and a foreign repository.
  • Manual check with claude --plugin-dir ./plugins/arjs, in a throwaway clone of arjs-plugin-artoolkit on main with its own .claude/hooks removed:
    • /hooks lists [Plugin] Bash|PowerShell and [Plugin] Edit|Write|MultiEdit from arjs@inline, and no project hooks.
    • Bash git commit --allow-empty -m test on main is blocked with the plugin's message. git log shows no new commit.
    • PowerShell git add -A; if ($?) { git commit -m test } is blocked. git status shows nothing staged, because the whole call was refused.
    • git switch -c test/x && git commit --allow-empty -m test from main goes through, because the guard follows the switch.
    • Adding # Prova formatter to README.md ends up as # Prova formatter, with a blank line before it, from the repository's own prettier.
  • Still to check after merge: the enabledPlugins route (trusted folder, no --plugin-dir).

🤖 Generated with Claude Code

kalwalt and others added 11 commits October 9, 2026 16:28
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…commands

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rg repositories

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Wire guard-protected-branches (PreToolUse, Bash|PowerShell) and
format-on-edit (PostToolUse, Edit|Write|MultiEdit) through
plugins/arjs/hooks/hooks.json, document them with the repository
settings snippet, and release arjs 0.3.0. The engines field now matches
what the locked vitest requires (Node >= 22.12).

Refs #1

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…uard

- A `git switch` or `git checkout` earlier in the same command sets the
  branch that later commits and pushes in that directory run on, so the
  release skill's `git switch dev && ... && git push` is no longer taken
  for a push to main.
- Braces that open a block split segments, and shell keywords (`then`,
  `do`, `else`, `elif`, `!`, `if`, `while`, `until`) are skipped before
  git, so PowerShell's `if ($?) { git commit }` and Bash `then git commit`
  are checked. `HEAD@{0}` and quoted braces are left alone.
- `git push --tags` without refspecs pushes tags alone: no branch target.
- Git is spawned only when the verdict needs it: the branch for a commit,
  a bare push or a HEAD refspec; the scope check only before blocking.
  Both are cached per directory.
- Malformed stdin exits 0 quietly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lint

"<pkg> is not installed; run npm install" is reported only when the
repository's root package.json declares the tool in dependencies or
devDependencies. An undeclared, uninstalled tool is skipped silently; an
installed one still runs. Malformed stdin exits 0 quietly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ADME

- AGENTS.md: what the repository is, the Git flow, the plugin version
  rule and how to run the tests; the guard's message points to it.
- The release skill pushes `origin dev` explicitly when re-syncing dev.
- README: the guard refuses a push to main; it is a guard-rail against
  mistakes, not an access control.
- test.yml: read-only contents permission.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A branch recorded from `git switch`/`git checkout` now holds only while
every separator since the switch is `&&`. After `;`, `||`, `|`, a newline
or a brace, the switch may have failed and the next command still run, so
recorded branches other than `main` are dropped and the guard reads the
branch from git again. A recorded `main` is kept across any separator.
`git switch missing; git commit` on main is blocked again.

Braces inside a word (`HEAD@{0}`, `${VAR}`) are counted separately, so
their `}` no longer closes an enclosing block.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On Linux a backslash is a filename character, so `cd "<dir>\"` names a
missing directory and the guard rightly lets the commit through; the CI
ubuntu job failed on that. The path assertions now run on Windows only,
and a platform-neutral one checks the parse itself: in PowerShell the
quote after `C:\dir\` closes, so the push that follows is seen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kalwalt kalwalt self-assigned this Oct 10, 2026
@kalwalt kalwalt added enhancement New feature or request documentation Improvements or additions to documentation tests labels Oct 10, 2026
@kalwalt
kalwalt merged commit d010e1e into dev Oct 10, 2026
2 checks passed
@kalwalt
kalwalt deleted the feat/plugin-hooks branch October 10, 2026 12:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation enhancement New feature or request tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant