Skip to content
AI CraftsmanSUPERPOWERS
Blog
  • Craftsmanship
  • DevEx

What changed in AI Craftsman Superpowers 4.12

Version 4.12.0 of the plugin, for Claude Code, Codex, Grok and Hermes: 54 recorded changes across 2 releases, taken from the CHANGELOG rather than rewritten.

Alexandre Mallet16 min read

Version 4.12.0 is out. It carries 54 recorded changes across 2 releases, and the plugin now ships 22 skills, 12 agents and 7 packs.

Every entry below is copied from the CHANGELOG in the repository rather than rewritten for this page. A release note that says something the CHANGELOG does not is a release note nobody can check.

4.12.0 (2026-09-27)

Native install on the three hosts that load plugins (Claude Code, Codex, Grok), a Grok write gate that actually runs, and per-host session isolation. Hermes keeps its own plugin manifest (plugin.yaml).

Upgrading

  • Grok: run bin/craftsman-grok-install again (or

craftsman-ci export --target grok-hooks --into ~/.grok/hooks). The gate that runs is now the global ~/.grok/hooks/craftsman.json; a project .grok/hooks file from 4.11.0 still needs grok --trust to run.

  • Codex: install from the native entry point

(codex plugin marketplace add BULDEE/ai-craftsman-superpowers, then codex plugin add craftsman@ai-craftsman-superpowers) and trust the handlers in /hooks. Installing never grants trust.

  • Skills and scripts that read the metrics database from a shell: resolve

it with bash "$(craftsman-path bin/craftsman-runtime)" metrics. The ~/.claude/craftsman-metrics-db-path bridge is a Claude Code fallback only; native Codex and Grok sessions never read it.

  • /craftsman:healthcheck names the export command when a gate is missing,

behind this install, or written by another install.

Added

  • Grok native install (.grok-plugin/plugin.json,

.grok-plugin/marketplace.json) and bin/craftsman-grok-install, which installs the plugin and writes the global gate. Grok lists a plugin's hooks/hooks.json as hookType: file and runs none of it on a fresh process (measured on 1.0.34 and 1.0.40), so the gate is exported from the same manifest instead of typed twice. It judges write and search_replace before disk and keeps pre-push-verify.sh. When no other review CLI is installed, the semantic review falls back to grok -p.

  • Codex native entry point (.codex-plugin/plugin.json, catalogue in

.agents/plugins/marketplace.json). The native loader reads the 22 skills and 14 handlers without the Claude manifest. Codex 0.155.1 omits hooks from the portable root plugin.json format, so the package ships no competing root manifest.

  • All 12 agent missions ship as ordinary files before the first session.

scripts/native-manifests.py generates the Codex and Grok manifests and --check fails on drift between the six pack agent copies and their sources; scripts/bump-version.sh runs it.

  • bin/craftsman-runtime: one resolver for the current host and session

store (metrics), used by skills and one-off queries.

  • Grok slash names: /name, except /plan, /loop and /workflow, which

Grok already owns and which stay /craftsman:name.

  • The shared hooks/hooks.json matchers also name Grok's write tools, and

the healthcheck gained a write-gate row: ok when the gate's owner marker matches this install, the export command otherwise.

Changed

  • Every session store is bound to its host and session. A validated binding

from the hook payload wins over inherited environment variables, Grok bindings are recovered from all supported aliases after a hook reload, and an unbound native session reports missing state instead of reading another host's data. Metrics and correction persistence never bypass validation when the store is unavailable: the write gate still judges the code.

  • The session context is resolved in one Python process instead of several

helper processes per hook.

  • Host facts live in hooks/host-capabilities.json only. The gate exporter

(ci/host_hooks.py) reads each host's gate_file and gate_export row (timeout, native environment names, tool aliases, conditional handlers, data directory) and derives the events a host does not fire from its events_loaded. A test fails when a host name, host environment name or host tool name is typed in the exporter again.

  • craftsman-ci export --target codex-hooks is refused and writes nothing.

Codex runs the plugin's hooks/hooks.json natively once trusted and reads user or project hooks only from hooks.json or config.toml next to a config layer (Codex hooks), never from .codex/hooks/*.json: the file this target wrote was read by nothing.

  • bin/craftsman-grok-install reads grok plugin list --json to know

whether this checkout is already installed, instead of matching the wording of an install error, and prints the checkout's origin and commit before trusting it.

Fixed

  • Grok's default hook timeout is 5 seconds and fail-open. The Grok export

alone sets timeout: 15 on every handler. hooks/hooks.json stays without a timeout, so Claude Code and Codex keep their 600 second default. The export sets each host's own root and data variables (GROK_PLUGIN_* or PLUGIN_*) and still sets CLAUDE_PLUGIN_*, which the engine scripts read on every host. A Codex gate does not carry GROK_*. The gate file is replaced atomically (mkstemp in the same directory, then os.replace), keeping the existing mode.

  • Each generated gate names the install that wrote it. The healthcheck is

ok when the marker matches, and names craftsman-ci export --target <host>-hooks when the same install is behind. A 4.11.0 gate with no marker is a warning, and SessionStart adopts it, when its CLAUDE_PLUGIN_ROOT is this install. Any other root is an error that names both paths and the export command, and the file is left untouched. The export, the healthcheck and the refresh share one relative path, .<host>/hooks/craftsman.json. craftsman-grok-install still exports the gate when the plugin is already installed, and prints the installer's own output on success.

  • Session lifecycle stores are isolated per native session: start, write,

verify, failure, compact and end read and write the same store, and a parent session's sentinel is left intact (#103).

  • Pre-write caches are scoped to the bound session. A cache filled for one

store could answer for another after the store changed between two pack_loader_init calls.

  • Grok config protection covers the host's own hook configuration, so the

model cannot relax the gate through an edit.

  • Hermes exported skills expand the native helper paths.
  • Hook libraries import their siblings under the Python 3.9 floor.
  • The Linux latency ceilings in tests/perf/test-hook-latency.sh were inside

the spread of one commit on ubuntu-latest. They are now 1.35x the quiet reading and at least 1.15x the fast one, still under twice the quiet reading, so a 50 percent regression still fails.

  • /craftsman:healthcheck no longer warns about the write gate on every

Claude Code session. The row warns only where the host's capability row records a trust step (Codex: review the handlers in /hooks), and is ok where plugin hooks run on install.

Security

  • Test evidence is granted from shell grammar, not from text separators.

hooks/lib/command_grammar.py lexes the command with shlex, so a quoted or escaped runner (printf '%s\n' 'hello; ./run-tests.sh --quick') no longer sets verified, which gates a push. Runners after &&, by path, and through python -m or npx stay qualified.

  • Redirections are part of the command, not separators. Before this release,

pytest -q 2>&1 lexed as 2>, &, 1, so a failing run kept the evidence green; &>, >/dev/null 2>&1 had the same hole, and |& read as one word hid a pipe. < and > are now lexer punctuation, |& is a pipe, and a command sent to the background (pytest -q &) grants and revokes nothing, since its status is the shell's.

  • A runner fed by a pipe (echo n | ./run-tests.sh) no longer grants the

evidence: it ran on input the model chose. A failure there still revokes.

  • The Grok semantic review runs with the read-only allowlist of Grok's

headless guide (--tools read_file,grep,list_dir). The previous denylist named the shell tool run_terminal_cmd while hook payloads name it run_terminal_command; an allowlist with a wrong name fails closed.

  • Config protection judges the file a write REACHES, whatever its spelling.

A bare relative path, //, /./, an inner .., a symlinked parent directory and a case change (.Claude/Settings.json, PHPStan.neon, an upper-cased plugin path) all reached a protected file with exit 0 on Claude Code and Grok. Each path is now joined to the payload's cwd, resolved physically, and classified case-insensitively.

  • Under bypassPermissions the gate's own configuration (.craft-config.yml,

.craft-rules.yml, .craftsman-baseline.json) is denied instead of asked: in that mode an ask proceeds without the user seeing it, and one write of the global config could set hooks.disabled for every later session. In the default mode it is still put to the user. If you run /craftsman:setup in bypass mode, edit the file yourself or approve it outside bypass mode.

  • The plugin's own data is protected like its code: registry caches, session

state, session bindings and the ~/.claude/craftsman-* bridges. A forged registry cache had turned a denied phpstan.neon into an allowed one. A shell command can still write there (see SECURITY.md).

  • A registry that cannot be built refuses the write instead of judging it

against an empty list of protected configs; an unwritable cache directory with no earlier cache compiles a private copy first.

  • A skill's set-verified is refused after a failing test run, until a

passing run clears it: the evidence a failure revoked can no longer be restored by asking.

  • Session bindings (~/.<host>/craftsman/sessions/*.json) are swept after 7

days with the other session files; nothing removed them before.

  • A session started from another host's shell carries both hosts' ids;

skills now resolve the innermost one (the most recent binding) instead of whichever host a fixed order named first.

  • A gate is never written through a symlink: a symlinked gate file or gate

directory is refused and its target left untouched. A first write honours the umask (0644 under 022) instead of the 0600 of a temporary file.

  • SECURITY.md gains "Host limits that weaken the gate": Grok's fail-open

hook timeout, Codex's trust step, and shell-written files.

Fixed (test suite)

  • A test whose cd fails inside the plugin repository now stops, and no test

may run a git write there (tests/lib/test-helpers.sh). With /tmp unwritable, tests/core/test-hostile-repo.sh fell back to the repository and committed its fixtures on the current branch. Its work directory now comes from mktemp under $TMPDIR.

Documentation

  • README and README.fr rewritten for adoption: one install block per host

(Claude Code, Codex, Grok, Hermes), a "first ten minutes" path that provokes a refusal on purpose, and a host support table read from hooks/host-capabilities.json. The Codex and Grok badges read their qualified version from that matrix, so they cannot drift from it.

  • New docs/guides/codex-quickstart.md and docs/guides/grok-quickstart.md

(install, verify, limits, uninstall), on the model of the Hermes one.

  • Every command now has a worked example: agent-design, ci, loop,

mlops, rag, scaffold and spec had none. COMMANDS-QUICK-REF.md says who starts each command and links its example.

  • tests/core/test-command-docs.sh fails when a skill has no example, when a

document names a command that does not exist, when the quick reference misses a command or its example, when the README's invocation split disagrees with the skills' frontmatter, and when a host that runs no plugin hook lacks its gate command in either README.

4.11.0 (2026-09-20)

Changed

  • The verification evidence is revoked whenever a failing command ENDS on a

test runner, not only when the runner is the whole command. A session that ran echo 1 > pytest.rc && ./bin/pytest -q, saw it exit 1 on a red suite, and kept verified true was measured on 2026-09-20; verified gates a push, so doubt revokes it. The session is still woken with "REGRESSED" only when the failure can be attributed to the runner alone; a compound failure revokes quietly and says how to grant the evidence again. If your habit is to chain a command before your test runner, expect to re-run the runner on its own to restore the evidence.

Added

  • Host adapters, one core (ADR-0029, campaign of 2026-09-15). The write gates

read a Codex apply_patch (V4A, multi-file, moves, @@ anchors, End of File) before it lands and refuse the gate's own configuration with deny where the host cannot ask; test runs are decoded per host from the shapes the hosts actually send (hooks/lib/tool_result.py), and a host whose shell output carries no exit code grants and revokes nothing; every hook names its session from its payload (session_files_bind); the doctrine export owns one delimited block of AGENTS.md; pack skills ship as regular files and learned skills may land in .agents/skills; agents export as Codex roles (craftsman-ci export --target codex-agents); the challenge skill collects its context with craftsman-context review; the semantic review has a backend port (claude-cli, codex-cli, none) and each run row names its backend; a per-host event matrix (hooks/host-capabilities.json) and a host row in the healthcheck say what each host loads and observes; a Copilot adapter honours the documented contract, surfaces still to qualify; a fresh install from the built archive is tested; every handler carries a statusMessage. Fixtures under tests/fixtures/hosts/ are captured from the real CLIs with their provenance (tests/fixtures/hosts/PROVENANCE.md), which also records what each host was seen NOT to do.

  • Codex qualified by Codex (2026-09-20): a Codex session installed this

branch natively (codex plugin marketplace add, which reads the Claude marketplace manifest, then codex plugin add) and reported what it found. Four defects fixed, each with a witness seen red first. model without prompt_id names Codex and a string transcript path no longer names Claude Code: run through its app server, Codex 0.154.0 sends a REAL transcript path where a project hook of the same version sent null, so that session called itself claude-code and wrote Claude Code's bridge. A shell with CODEX_SESSION_ID or CODEX_THREAD_ID names Codex, so craftsman-healthcheck no longer reports "unknown host" about its own host, and the session-bridge row is read per host instead of judging every host by ~/.claude. The rules store honours TMPDIR and the plugin data directory before /tmp, which a sandboxed Codex could not write, turning mktemp: mkdtemp failed into the refusal the model read. The Codex row of the capability matrix records twelve event kinds (Interrupt included), that installing and enabling a plugin does not trust its hooks, and that a background hook cannot force a continuation there. Two limits are now stated rather than implied (docs/reference/hooks.md, README): the gate judges the host's write TOOLS, so a file written by a shell command the model runs is caught by CI and the pre-push gate, not before disk; and async delivery is the host's, which Codex does not offer.

  • A host's hooks file is generated from the manifest, never typed twice:

craftsman-ci export --target grok-hooks (and codex-hooks) writes the handlers with ${CLAUDE_PLUGIN_ROOT} expanded and carried in each handler's environment, the events that host does not fire dropped, and the conditional handlers that are Claude Code's dropped. Grok needs it because it discovers a plugin's hooks/hooks.json and runs none of it; the generated file was replayed on the real CLI and gives the same refusals. /craftsman:healthcheck gained an agent-roles row: on Codex it counts the craftsman roles in that host's home and names the export command when there are none, since installing the plugin does not write them and, written, all twelve are offered to spawn_agent. README documents the native install on both hosts, including the steps installing does not perform (trusting the hooks, exporting the roles) and what each host does not offer.

  • Grok 1.0.30 as a host, measured (audit of 2026-09-15): the write gates

judge its write and search_replace tools (they exited 0 in silence on the captured envelope), host_detect names grok from the envelope (workspaceRoot and a lowercase hookEventName; it was read as Copilot because both carry a timestamp) and from GROK_SESSION_ID, the capability matrix has a grok row and the healthcheck describes any host from that row instead of a case per host, and run_terminal_command results carry exit_code, so the verification loop is live there. The mirror now holds one table of host tool names to a kind of write (WRITE_TOOL_KINDS), and the pre-write gate no longer branches on a tool name. What Grok does NOT do is recorded too: it discovers a plugin's hooks/hooks.json and loads none of its handlers (headless, project plugin enabled; tests/fixtures/hosts/PROVENANCE.md), so on Grok the engine runs only from a project or global hooks file, and "compatible Grok" is not claimed.

  • Hermes: a git push between two conclusions waits for the conclusion

gate. adapters/hermes/pre-tool-call.sh now reads terminal and refuses a push (and a commit under strict) unless pre-verify.sh's last verdict is a pass on the tree that would be published; the verdict is recorded with that tree (terminal_gate.py, <git-dir>/craftsman-verdict), so a pass on one tree does not authorise another, and with no verdict at all the push waits. The matcher becomes ^(terminal|write_file|patch)$. tests/adapters/test-hermes-terminal-gate.sh covers no verdict, a refused turn, a pass, a different tree, a pipeline, git -C, and the two strictness levels; every refusal was seen red before the branch existed.

Fixed

  • The engine holds no list of languages, as CLAUDE.md has said since the

language registry: the FileChanged matcher carried a glob the event does not support (*.php|*.ts|*.tsx, so the hook never fired; it now watches the common source roots and the registry decides the language), the session banner found only composer.json and package.json (it now reads every pack's entry_markers and compares pack names), the codemap and the write mirror kept their own extension and marker lists (they read the registry), the metrics extractor took a language name in place of the declared metrics_dialect (it takes a dialect only, and the symfony pack declares php-like, which is what it always received), the engine kept the one list of rule ids it says it must not hold (relaxed_in_tests: true is declared on the rule, compiled as the registry's seventh column, and asked through rule_relaxed_in_tests), and the PHP namespace reader and the Python AST checker lived in hooks/lib (each is in its pack). tests/core/test-no-language-literals.sh scans hooks/ and ci/ and fails on any language name or extension outside a comment: fourteen hits before, zero after, two of them kept on an explicit allowlist with a backlog line each.

  • A baseline row had two owners with two cadences: the structural mark

tightened on every green pass, the rule counts were written once by craftsman-ci baseline and never moved. Debt that was paid kept its count, so the same debt coming back was reported as inherited on the very write the ratchet refused as a regression. A green pass now tightens the rule counts to what it observed (rule_baseline.py tighten), and a git mv keeps the file's mark instead of photographing the renamed path at whatever state the first write left it in.

  • Five session-state keys were read and written by nothing: design_used

(the design-pass exemption in the bias detector never fired; a /craftsman:design prompt now sets it), writes_count (the TaskCompleted evidence gate saw zero writes; it counts the session-writes file), and agent_invocations, team_type, completed_tasks (sessions.agents_spawned held [] on every row; it carries the agent types the subagent gate records). The README rows saying the plugin blocks a design decision now say it warns, which is what it does.

  • ci/craftsman-ci.sh exited 0 on a checkout missing rules-engine.sh,

pack-loader.sh or precedence.sh: it scanned nothing and reported clean. The four libraries the gate is made of now load through one function that exits 2 naming the missing file; the standalone severity table and config parser, reachable only in that state, are gone.

  • Five published claims the code contradicted: the Level 2/3 budget

(15s per file, 30s per project, not <2s), pre-push-verify.sh warns and does not gate, Levels 2 and 3 are off until trust_project_tools, all eighteen CRAFTSMAN_* switches are named in SECURITY.md, and the rules table in docs/ci-integration.md (13 of 66) is replaced by the export command that prints the live list. tests/core/test-doc-claims.sh reads the code first and the document second.

  • plugin.yaml, the Hermes manifest at the repository root, is now the fifth

file scripts/bump-version.sh tracks, so --check and release-guard.sh refuse a release that leaves it behind. It was off the list, and 4.9.0 and 4.10.x shipped with hermes plugins reporting 4.8.1. The manifest is at 4.10.3 again and tests/meta/test-release.sh carries the drift fixture.

Upgrading

The same engine runs on every host; update it the way that host installs plugins:

# Claude Code
claude plugin update craftsman@ai-craftsman-superpowers
# Codex (then review the handlers in /hooks)
codex plugin marketplace upgrade
# Grok (re-exports the gate as well)
bash <your clone>/bin/craftsman-grok-install
# Hermes
git -C ~/.hermes/plugins/craftsman pull

The full history, including every patch summarised here, is in CHANGELOG.md.