Skip to content

feat(claude-code): Claude Code plugin adapter (S14) - #165

Merged
BrainerVirus merged 26 commits into
mainfrom
feature/claude-code-adapter
Oct 4, 2026
Merged

BrainerVirus merged 26 commits into
mainfrom
feature/claude-code-adapter

Conversation

@BrainerVirus

@BrainerVirus BrainerVirus commented Oct 3, 2026 •

Copy link
Copy Markdown
Owner

Summary

S14 (docs/workit-next/design.md §3, §5): a Claude Code plugin for workit, usable both as latest published and as a local pin to a checkout, wired into the same release, doctor and wizard machinery as the other hosts.

  • packages/workit-claude-code: .claude-plugin/plugin.json, hooks/hooks.json (SessionStart startup/resume/clear/compact/fork, UserPromptSubmit, PreToolUse for Bash and PowerShell git *, SubagentStart). Only hooks that change Claude's behavior are registered. All hooks go through one exec-form launcher (node bin/workit-hook.mjs, no shell) into the merged core/hooks claude-code adapter. Also: verifier/reviewer (read-only) and implementer (isolation: worktree) agents, bin/workit on the Bash tool PATH, and the fourteen skills generated at build time as /workit:<name> (gitignored, never committed). No .mcp.json, per the design.
  • Local pin: claude --plugin-dir <checkout>/packages/workit-claude-code (or CLAUDE_CODE_PLUGIN_DIRS). The launcher and bin/workit detect the monorepo and run the TS sources with bun. Only scripts/build.ts --skills-only is needed.
  • Latest: the repo root .claude-plugin/marketplace.json (name workit) has one entry with source: {source: "npm", package: "@brainervirus/workit-claude-code"} and no version. Install with claude plugin marketplace add BrainerVirus/workit and then claude plugin install workit@workit.
  • Release: a semantic-release npm bumper, RELEASE_PACKAGES, the workspace-dep rewrite (now also covering devDependencies), and manifest sync for package.json + .claude-plugin/plugin.json (including release.yml's list). The package is fully bundled and has zero runtime deps. This was verified: Claude's npm-source install extracts the tarball and has no node_modules.
  • Host wiring: wizard detection (claude on PATH, plus the plugin registry for "configured"), a native setup/upgrade plan, and a workit doctor claude_plugin check. The check warns when the install is behind the published package or out of step with the running CLI, and gives the native update command as the fix.
  • CI: the PR gate installs Claude Code 2.1.288 (the support-matrix pin) and runs claude plugin validate --strict on the plugin and the root marketplace. The packaging tier validates and installs the packed tarball through a local marketplace in an isolated config dir. The opt-in claude-eval.yml (nightly, eval label, or dispatch; needs ANTHROPIC_API_KEY; capped at $2) runs 3 eval cases. It is never a PR gate.

Evidence (local, Node 24.20.0)

  • lint, format:check, typecheck, knip (+ reachability), react-doctor, actionlint 1.7.12 and zizmor 1.30.1 all pass.
  • bun run test: 1257 pass / 0 fail. bun run test:packaging: 250 pass / 0 fail. verify:release-candidate packs 8 tarballs.
  • claude plugin validate --strict (2.1.288, isolated config) passes for the plugin, the root marketplace, and the packed tarball.
  • Live, --plugin-dir local pin (isolated CLAUDE_CONFIG_DIR, temp git repo): Claude loaded 3 agents and 14 skills. SessionStart:startup ran the hook and injected additionalContext (5670 chars, <workit-contract>…). The env file received export WORKIT_HOST=claude_code / WORKIT_SESSION_ID, and UserPromptSubmit ran. CLAUDE_CODE_PLUGIN_DIRS loads it the same way.
  • Live, installed mode (directory marketplace snapshot, claude plugin install workit@workit): the same hooks fired from the plugin cache using dist/ (5971 chars, including the /workit:<name> addendum). The bundled workit doctor --json reports claude_plugin pass, or warn with the update fix against a newer registry version.
  • Not done live: a model turn (and so a live PreToolUse deny of git checkout -b main). The isolated config dir has no credential, and the run stops at Not logged in. The deny is covered by the hook-fixture suite through the real launcher in both runtimes, by the packed-tarball test, and by the branch-policy-deny eval case.

Review follow-ups (after the first review)

  • PreToolUse denies are structured JSON: hookSpecificOutput.permissionDecision: "deny", exit 0, empty stderr. This is now asserted for both runtimes. Claude Code 2.1.288 converts a JSON deny into its internal blockingError, so its UI prints PreToolUse:Bash hook error: <reason> for a structured deny too. That is why the live test showed "hook error".
  • Removed the no-op hooks (Stop, SubagentStop, PostToolUse) and PreCompact. SessionStart now also matches fork. A plain turn now spawns 1 hook process instead of 2. Median latency per process, measured just now: about 110 ms installed (dist) and 160–170 ms on the local pin, for each of SessionStart, UserPromptSubmit, PreToolUse(git) and SubagentStart.
  • SubagentStart: on claude_code, (workit:)implementer gets worktree write guidance. Other agent types and other hosts keep the read-only text.
  • Release: BUNDLED_SOURCES republishes workit-claude-code on changes under packages/workit-core/ or packages/workit-cli/src/, and workit-pi on packages/workit-core/ changes. publish-changed-packages now attempts every changed package and throws one summary at the end. Doctor warns only when a newer plugin is published.
  • First publish: @brainervirus/workit-claude-code is a new npm name. The release NPMJS token must be allowed to create new packages in the @brainervirus scope. If it is not, that package fails and the others still publish; the release then fails with a summary naming it.
  • workit uninstall handles Claude Code with a reviewed native claude plugin uninstall workit@<market>.
  • Launcher: an unloadable dist/ fails open ({} plus one stderr line). The per-turn cache is seeded at SessionStart, so the first turn does not resend context, and caches untouched for 7 days are pruned.

Notes / deviations

  • hooks.json uses exec form with node bin/workit-hook.mjs rather than the design's #!/bin/sh hook shim, so hooks need no shell on Windows. bin/workit stays a sh shim; it runs only under the Bash tool.
  • The current CLI has no --version, so the "resolves to source" acceptance check uses WORKIT_SHIM_TRACE=1 bin/workit --help. After merging main (feat(cli): S9a deterministic CLI router, shared envelope, and core git/rev #163), bin/workit --version resolves workit-cli/src/main.ts on a local pin, and the build bundles main.ts.
  • claude plugin validate (2.1.288) does not check agent or skill frontmatter, so those are asserted in test/workit-claude-code/plugin.test.ts.
  • UserPromptSubmit dedup lives in the plugin entry ($CLAUDE_PLUGIN_DATA/ctx/<session>.json, cleared on SessionStart), so core/hooks stays unchanged.
  • A local directory marketplace installs a snapshot copy, not a live pin, so the docs recommend --plugin-dir / CLAUDE_CODE_PLUGIN_DIRS for pinning.
  • Hook process cost: about 150 ms per invocation for the installed bundle (node startup plus a 594 KB minified bundle) and about 230 ms from source. PreToolUse only spawns for git * commands via if.

🤖 Generated with Claude Code

BrainerVirus and others added 26 commits October 3, 2026 19:08
packages/workit-claude-code ships the Workit plugin for Claude Code:
- hooks/hooks.json registers SessionStart (startup|resume|clear|compact),
  UserPromptSubmit, PreToolUse (Bash/PowerShell `git *`), PostToolUse
  (Bash `workit *`), SubagentStart/Stop, PreCompact and Stop, all through
  one exec-form launcher (`node bin/workit-hook.mjs`, no shell).
- The launcher runs src/run.ts from source with bun when the plugin sits
  in the monorepo (local pin) and imports dist/workit-hook.js otherwise;
  any launcher failure fails open with a diagnostic.
- src/hook.ts maps events through core/hooks' claude-code adapter, adds a
  Claude addendum (workit-<name> skills are /workit:<name>), exports
  WORKIT_HOST/WORKIT_SESSION_ID via $CLAUDE_ENV_FILE on SessionStart, and
  re-injects per-turn context only when it changed (cache in
  $CLAUDE_PLUGIN_DATA).
- bin/workit puts the CLI on the Bash tool's PATH (source with bun in a
  checkout, bundled dist/workit.js when installed). TODO(#163): bundle
  workit-cli/src/main.ts once the router lands; both the shim and the
  build already prefer it when present.
- agents: read-only verifier and reviewer, implementer with
  isolation: worktree.
- scripts/build.ts bundles the hook and CLI, copies templates, and
  generates the fourteen skills from workit-core/skills, renamed to
  plugin-namespaced names (never committed).

Hook-fixture tests pipe every captured Claude payload through the real
launcher in both runtimes and validate the output against the hook output
schema transcribed from Claude Code 2.1.288.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The repository root becomes a Claude Code marketplace
(.claude-plugin/marketplace.json, name `workit`) whose single entry
installs the published @brainervirus/workit-claude-code npm package, so
`claude plugin marketplace add BrainerVirus/workit` +
`claude plugin install workit@workit` always gets the latest release.
The entry pins no version: plugin.json carries it.

Release wiring follows the other adapters: a semantic-release npm bumper,
RELEASE_PACKAGES (path-gated analysis and selective publish), the
workspace-dep rewrite (now also devDependencies, which this fully bundled
package uses), and manifest sync for package.json and
.claude-plugin/plugin.json, including the release workflow's sync list.

CI's PR gate installs the Claude Code CLI pinned in the support matrix
(2.1.288) and runs `claude plugin validate --strict` on the plugin and
the root marketplace; the packaging tier validates and installs the
packed tarball through a local marketplace in an isolated config dir.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Host detection: `claude` on PATH (or a version-manager/user bin dir)
  marks Claude Code detected; a Workit entry in Claude's plugin registry
  (<CLAUDE_CONFIG_DIR|~/.claude>/plugins/installed_plugins.json) whose
  install dir carries the canonical manifest marks it configured.
- The init wizard lists Claude Code; setup apply runs the native plan
  (node check, marketplace list, marketplace add BrainerVirus/workit when
  missing, plugin install workit@workit --scope user) and verifies the
  registry afterwards. Upgrade mode refreshes the marketplace and runs
  `claude plugin update workit@workit`.
- `workit doctor` adds `claude_plugin`: a warning when an installed
  plugin is behind the published package (stale_install) or differs from
  the running workit CLI, with the native update command as the fix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Three cases against a scaffolded git repo on a protected main:
session-context (SessionStart injects the workit contract),
branch-policy-deny (a Bash `git checkout -b main` is denied by the hook),
and skill-triggers (an independent-review request loads the review
skill). claude-eval.yml runs them nightly, on the `eval` label, or by
dispatch with the pinned CLI and ANTHROPIC_API_KEY, capped at $2; it is
never a PR gate (credential, cost, nondeterminism).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
README: latest published (git-hosted marketplace with the npm source,
native update) and the local pin (`claude --plugin-dir` or
CLAUDE_CODE_PLUGIN_DIRS, sources run with bun, only skills generated),
plus what the plugin ships and its host surface limits. AGENTS.md notes
the Claude Code adapter contract.

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

Each runDoctor call runs every check (runtime, identity and lock probes),
which exceeded bun's 5 s default on the Ubuntu runner.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…and per-turn cache

- hooks.json: SessionStart also matches fork; Stop, SubagentStop,
  PostToolUse and PreCompact are no longer registered (no-ops today, and
  SessionStart compact restores context), halving hook processes per turn.
- PreToolUse denies stay structured JSON (permissionDecision deny, exit 0,
  empty stderr), now asserted for both runtimes. Claude Code 2.1.288 folds
  a JSON deny into a blocking error internally, so its UI prints
  "PreToolUse:Bash hook error: <reason>" for it too.
- The launcher fails open on an unloadable dist/ ({} + one stderr line).
- SessionStart seeds the per-session turn cache with the context it just
  injected, so the next prompt does not resend it; caches untouched for
  7 days are pruned.
- bin/workit and the build use workit-cli/src/main.ts (S9a landed);
  bin/workit --version now resolves the source on a local pin.

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

SubagentStart told every Claude Code subagent it was read-only, including
the plugin's worktree-isolated implementer. On claude_code the text is now
keyed on agent_type: (workit:)implementer is told it may edit and commit in
its own worktree after switching to a policy-compliant branch; every other
agent type and every other host keeps the read-only text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- BUNDLED_SOURCES: workit-claude-code bundles core and the CLI sources,
  workit-pi bundles core, and neither declares them as runtime deps, so a
  change under packages/workit-core/ (or packages/workit-cli/src/ for
  claude-code) now marks them changed in release analysis and selective
  publish.
- publish-changed-packages attempts every changed package even after a
  failure (e.g. the first publish of a new npm name the token cannot
  create), then throws one summary listing failed and published packages.
- doctor's claude_plugin warns only when a newer plugin version is
  published; an older plugin than the CLI is normal when its payload did
  not change.

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

workit uninstall plans one reviewed `claude plugin uninstall workit@<market>`
per recorded Workit install and runs exactly that argv on apply (anything
else is refused; an already-removed install is skipped). The wizard lists
Claude Code.

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

Every adapter build is a no-external bun build, so a runtime dependency on
core does not reach the shipped bundle. Bundle metafiles show: mcp, cli,
opencode and pi inline core; cursor and codex inline core and the MCP
sources; claude-code inlines core and the CLI (src/ and package.json).
BUNDLED_SOURCES now lists all of them, and a metafile test fails when a
build entry inlines a workspace source missing from its package's payload
paths.

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

Bundles inline third-party code (zod, the MCP SDK, ink/react) at the
versions bun.lock resolves, and the CLI/MCP manifests that declare them.
BUNDLED_SOURCES now adds bun.lock to every adapter, the MCP package.json
to cursor and codex, and the CLI package.json to claude-code (core's
package.json is under its directory prefix). The metafile guard fails when
a bundle inlines node_modules code without bun.lock in its payload paths.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude namespaces plugin agents, so only the exact workit:implementer is
the Workit plugin's worktree agent; a bare or another plugin's implementer
keeps the read-only text.

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

The plugin registry records user, project and local installs (project and
local with their projectPath). Only installs Claude loads where workit
runs now count: user scope always, project/local only inside their
project; setup's already-installed check and post-install verification
look at user scope only. Doctor's update fix carries --scope and the
project. Uninstall plans `claude plugin uninstall <id> --scope <scope>`,
running project/local ones from their project, and refuses any other argv
shape or a scope/cwd mismatch.

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

Four full runDoctor calls exceeded 30 s on the Windows runner; run them
from the isolated home and allow 120 s.

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

Dev-tooling-only bun.lock bumps (e.g. oxlint) must not release. Instead of
treating the whole lockfile as payload, scripts/bundled-deps.json lists
the bun.lock keys of the third-party packages each dist/ inlines (from the
bundle metafiles; nested copies such as @opencode-ai/plugin/zod included).
Release analysis (per commit, against its first parent) and selective
publish (previous tag vs HEAD) mark a package changed only when one of its
inlined packages resolves to a different version. The bundled-sources
guard now requires the inventory to equal what the bundles inline.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…c, not the release

Comparing the plugin version with the current core version broke whenever
main released before this branch merged, because CI tests the merge with
main. The test now asserts plugin.json equals the plugin package.json, that
SYNC_MANIFEST_PATHS lists both files, and that syncManifests rewrites both
in a copied tree.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The packaging tests compared the packed plugin.json with the packed
package.json, but the release rewrite mirrors the tree's core version into
plugin.json only, so every release on main broke them. They now expect the
tree's core version (what the rewrite writes) for the packed manifest and
for Claude's recorded install. Plugin manifests aligned to 2.4.0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@BrainerVirus
BrainerVirus merged commit 78fd666 into main Oct 4, 2026
6 checks passed
@github-actions

github-actions Bot commented Oct 4, 2026

Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 2.6.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant