feat(claude-code): Claude Code plugin adapter (S14) - #165
Merged
Merged
Conversation
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>
Contributor
|
🎉 This PR is included in version 2.6.0 🎉 The release is available on:
Your semantic-release bot 📦🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 PowerShellgit *, 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 mergedcore/hooksclaude-code adapter. Also:verifier/reviewer(read-only) andimplementer(isolation: worktree) agents,bin/workiton the Bash tool PATH, and the fourteen skills generated at build time as/workit:<name>(gitignored, never committed). No.mcp.json, per the design.claude --plugin-dir <checkout>/packages/workit-claude-code(orCLAUDE_CODE_PLUGIN_DIRS). The launcher andbin/workitdetect the monorepo and run the TS sources with bun. Onlyscripts/build.ts --skills-onlyis needed..claude-plugin/marketplace.json(nameworkit) has one entry withsource: {source: "npm", package: "@brainervirus/workit-claude-code"}and no version. Install withclaude plugin marketplace add BrainerVirus/workitand thenclaude plugin install workit@workit.RELEASE_PACKAGES, the workspace-dep rewrite (now also covering devDependencies), and manifest sync forpackage.json+.claude-plugin/plugin.json(includingrelease.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 nonode_modules.claudeon PATH, plus the plugin registry for "configured"), a native setup/upgrade plan, and aworkit doctorclaude_plugincheck. 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.claude plugin validate --stricton 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-inclaude-eval.yml(nightly,evallabel, or dispatch; needsANTHROPIC_API_KEY; capped at $2) runs 3 eval cases. It is never a PR gate.Evidence (local, Node 24.20.0)
bun run test: 1257 pass / 0 fail.bun run test:packaging: 250 pass / 0 fail.verify:release-candidatepacks 8 tarballs.claude plugin validate --strict(2.1.288, isolated config) passes for the plugin, the root marketplace, and the packed tarball.--plugin-dirlocal pin (isolatedCLAUDE_CONFIG_DIR, temp git repo): Claude loaded 3 agents and 14 skills.SessionStart:startupran the hook and injectedadditionalContext(5670 chars,<workit-contract>…). The env file receivedexport WORKIT_HOST=claude_code/WORKIT_SESSION_ID, andUserPromptSubmitran.CLAUDE_CODE_PLUGIN_DIRSloads it the same way.claude plugin install workit@workit): the same hooks fired from the plugin cache usingdist/(5971 chars, including the/workit:<name>addendum). The bundledworkit doctor --jsonreportsclaude_pluginpass, or warn with the update fix against a newer registry version.git checkout -b main). The isolated config dir has no credential, and the run stops atNot 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 thebranch-policy-denyeval case.Review follow-ups (after the first review)
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 internalblockingError, so its UI printsPreToolUse:Bash hook error: <reason>for a structured deny too. That is why the live test showed "hook error".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.(workit:)implementergets worktree write guidance. Other agent types and other hosts keep the read-only text.BUNDLED_SOURCESrepublishesworkit-claude-codeon changes underpackages/workit-core/orpackages/workit-cli/src/, andworkit-pionpackages/workit-core/changes.publish-changed-packagesnow attempts every changed package and throws one summary at the end. Doctor warns only when a newer plugin is published.@brainervirus/workit-claude-codeis a new npm name. The releaseNPMJStoken must be allowed to create new packages in the@brainervirusscope. If it is not, that package fails and the others still publish; the release then fails with a summary naming it.workit uninstallhandles Claude Code with a reviewed nativeclaude plugin uninstall workit@<market>.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.jsonuses exec form withnode bin/workit-hook.mjsrather than the design's#!/bin/shhook shim, so hooks need no shell on Windows.bin/workitstays a sh shim; it runs only under the Bash tool.--version, so the "resolves to source" acceptance check usesWORKIT_SHIM_TRACE=1 bin/workit --help. After merging main (feat(cli): S9a deterministic CLI router, shared envelope, and core git/rev #163),bin/workit --versionresolvesworkit-cli/src/main.tson a local pin, and the build bundlesmain.ts.claude plugin validate(2.1.288) does not check agent or skill frontmatter, so those are asserted intest/workit-claude-code/plugin.test.ts.UserPromptSubmitdedup lives in the plugin entry ($CLAUDE_PLUGIN_DATA/ctx/<session>.json, cleared on SessionStart), socore/hooksstays unchanged.--plugin-dir/CLAUDE_CODE_PLUGIN_DIRSfor pinning.git *commands viaif.🤖 Generated with Claude Code