[Unreleased]
Added
Changed
Fixed
.gitattributesnow pinseol=lfon its* text=autorule. The plugin directory scanner refuses a repository whose line-ending rule leaveseolunset, so every commit since80926d7failed to fetch
[3.2.0] - 2026-10-16
Adds the experimental rewrite plugin (Elixir Rewrite Architect 0.1.0) and fixes the hook
scripts that still blocked v3.1.3 in the plugin directory.
Added
test_hook_scripts_run_only_paths_the_directory_can_follow(contributor tooling). Rejects inline programs,eval,source,bash/commandinvocations and$0/BASH_SOURCEpaths in hook scripts.- New
rewriteplugin (Elixir Rewrite Architect, 0.1.0, experimental). Plans a rewrite of an app from any language into idiomatic Elixir. Five commands (/rewrite:start,analyze,architect,roadmap,verify), one reference skill (elixir-way, with stack packs for Rails, Django, Laravel/PHP, Node, Go and JVM/.NET plus a generic method), seven agents (six sonnet,elixir-way-architecton opus) and four stdlib scripts (inventory.py,ledger.py,fingerprint.pyfor the content identity of both trees, andevidence.py, which writes every verify record). Slices reach phx as aninterview.mdseed for/phx:plan; phx itself is unchanged. The dependency onelixir-phoenixlives in the marketplace entry, notplugin.json, so the plugin still loads beside--plugin-dir ./plugins/elixir-phoenix(phx@inline). Versions independently. Seeplugins/rewrite/README.md. - The rewrite inventory grades the legacy app's oracles.
inventory.py(inventory version 3) adds anoraclessection: tests by framework with approximate case counts, black-box API suites (Hurl requests and asserts, Postman, Bruno, REST Client, Tavern, Dredd), E2E suites, benchmark and load harnesses, API contracts and coverage config (paths only). A deterministic grade (correctnessstrong | moderate | weak | none, performanceharness | none) states the counts and the threshold each one crossed.--oracle DIRadds a suite outside the source, such as RealWorld'sspecs/api, with the same symlink, special-file and stat-fence safety and outside the source fingerprint; a directory inside the source is not scanned twice.--comparestill accepts version 2 inventories. The contract-surveyor names a legacy witness per contract and confirms or downgrades the grade. Roadmap puts characterization tests first on a weak grade, names each slice's black-box files and command in a new seed## Oraclesection, and writes a port-only load script when no harness exists. Verify requires a fresh passing run of those files, against the port only. It also re-runs every other command an acceptance criterion names, such as a release smoke, since a/phx:worklog is a claim, and the parity auditor proposesimplementedfor each slice row whose code exists, so passing rows stop counting as unverified. A record missing its identity values is re-run before the audits, not after them. Verify writes its run list (commands.md) before running anything: the default commands, every command an acceptance criterion names exactly as named (a seed,--force, the full suite) and every## Oraclecommand. The parity auditor checks the list against the criteria and the records, and a missing list is a finding: verify writes it before auditing, where two trial runs had skipped it. Each listed command keeps the arguments its criterion names, and a command is left out only when it cannot run: a trial verify had swapped a named seed for a random one and skipped whole-file diagnostics as already run by/phx:work. Black-box files run against the release, not a dev server, whosedebug_errorspage answered HTML where the release answers the JSON envelope. Building the release inline needs the phx gate from #151, which allowsMIX_ENV=prod mix release. A load smoke that errors goes to/phx:investigatewith the error from the server log, never to a bigger pool or a lighter load: the first release-based trial smoke failed 25% of requests, and the cause was the password hasher. The run list includes static checks a criterion names (rg,git diff), and the report keeps the parity auditor's states: a slice 04 trial verify had recorded three greps after the audit and reported them as passing. The release starts withRELEASE_DISTRIBUTION=none, so noepmddaemon outlives the run, and each Hurl command gets its ownuid, where two runs in the same second had collided on registration. The parity auditor splits each criterion into one row per case it names: the slice 04 denominator had grown from 22 to 32 over three runs as the cases surfaced one at a time. Splitting alone did not hold it: two slice 07 audits counted 52 and 68, one row per Hurl file where the other had one row in total. The auditor now gets the slice's previousparity.mdasPREVIOUS, starts from its rows (never its states), and names every added, split, merged or dropped row with the old and new count. Verify writes each run's script from the skill, never from a copy of another slice's or an earlier run's: the fourth slice 04 verify reran its old script and the slice 05 verify copied slice 04's, so both started the release withoutRELEASE_DISTRIBUTION=noneand the copy lacked the static checks its run list named. Catch-up runs before the audits go through the wrapper, where slice 05 ran those checks bare and left them with no record. The release start and stop are now a code block in the skill, since the next run wrote a fresh script that still followed the old one's start line and leftepmdbehind again. The elixir-way references now name:crypto.pbkdf2_hmac/5(a dirty NIF) for Django PBKDF2 hashes instead ofpbkdf2_elixir, whose Elixir loop runs all 1,000,000 rounds on the normal schedulers: with 42 hashes in flight a trivial task took up to 412 ms, against 33 µs through:crypto, so concurrent registrations starved the DB pool. The Django stack reference probes django-ninja's per-status response models:return 409, Noneunderresponse={409: Any}renders JSONnull, which a trial analysis had recorded as an empty body, and a status the route'sresponse=omits raisesConfigError, a 500 unlessDEBUG: the legacy favorite route returns 409 without declaring it. A 422 for a missing or wrong-type field carries pydantic's own text, which three trial slices had replaced without recording it. It proposes Evidencepassfor a REPLACE or MAP row whose Target passed fresh, where it had proposednot-runbecause an ADR changed the legacy behavior, which left those rows unverified. Verify keeps the PID of the port it starts and stops only that process, where a trial run had stopped every BEAM process on the machine withpkill -f beam, and it drops only a database it created itself.--no-runstops before the audits when the target has moved since its newest record, because they could only score itstale. Roadmap assigns each ledger row to the slice that builds all of its Target, so a foundation gate no longer fails on a row whose replacement a later slice writes. Each roadmap run refreshes every slice'sBlocked byfirst, so--nextno longer skips a slice whose blockers the user has since resolved. A black-box file's leading entries that an earlier slice can pass in full (register and login ahead of the user routes) gate that slice with Hurl--to-entry. - Structural eval scores more than one plugin (contributor tooling).
lab/eval/plugins.pymaps a file to its plugin, somake evalandmake eval-allscorerewriteskills and agents alongsidephx. A namespaced agent reference (phx:x,rewrite:x) resolves only in its own plugin, and an unknown--pluginname fails instead of scoring nothing. - Verify records come from
evidence.py, not from a script verify writes. About 11 of the 19 fixes from the django-ninja trial were mechanics that each verify session worked out again: the wrapper, record fields, identity captures, the release start line, kill by PID, and copying an earlier run's script.ev initstarts a run and moves old records tosuperseded/.ev run SLICE NAME -- CMDcaptures target and source identity, runs the command without a shell, captures both again and writes the record: exit and expected exit, times, the four identities, app env names (secret-like values redacted), ExUnit counts and the log.{uid}gives each Hurl run its own value.ev port start|stopruns the release withRELEASE_DISTRIBUTION=nonein its own process group and stops only that group.ev statuscheckscommands.mdagainst the records (missing, incomplete, stale, outdated, unexpected exit), andev currentgives the auditorsCURRENT_IDENTITY. Each black-box file now gets its own record, where the trial's verify had bundled 13 Hurl files, curl and k6 into one. A check that must find nothing records--expect-exit 1, and the parity auditor compares each exit with its record's expected exit. Smoke-tested on the trial's prod release: the releaseexecs intobeam.smpunder the same PID and leads its own group, and noepmdwas left after stop.evpasses the shell's PATH asEV_PATH, because a mise shim onpython3rewrites PATH for its child from the directory's tool config. In the ASP.NET trial, the port'smixthen reached an unpinnederlshim and failed, and verify blamed the script for stripping the environment. The skill's absence-check example now skips comment lines ('^\s*([^#\s].*)?Repo\.'). In slices 00a and 00b, verify's firstrgabsence check matched a comment and had to be re-run. In ASP.NET slice 02, verify credited the Hurl files from an ExUnit wrapper on the test endpoint. The parity auditor now counts a black-box file only from its own record against the release started withev port start. The load script runs with achecksthreshold: k6 exits 0 when checks fail, which both trials hit. The verify report quotes each row's obligation fromparity.mdinstead of paraphrasing it (a trial report mislabeled TK-08), and verify never schedules a wakeup. That rule now sits where verify waits for its auditors: ASP.NET slice 04's round 2 set a 20-minute fallback wakeup there, which re-entered the finished run. Verify names thePREVIOUSfile exactly: this slice'sparity.md, renamed toparity-previous.md. Told only to move "an earlierparity.md" aside, ASP.NET slice 03's first round ranmv ../02/parity.md /dev/null; the move failed and the file survived.CLAIMSnow includes the plan'sscratchpad.md, and the parity auditor reads the source for every behavior the work phase recorded as assumed, adding it as an omission when no case covers it. Slice 03's work logged three "ASSUMED parity, no witness" entries there that the auditor passed over. One was wrong: the port answered an emptylimit=with 422, where the legacy binds it toint?and serves the default page. A behavior the user adds after a round now gets a new case ID in the seed. The trial operator's additions went into the plan only: slice 00b's auditor counted two as omissions, but slice 03's counted none of four. The preflight now also readsconfig/prod.exs, the release's compile-time config, and records the endpoint'sip:as read. In ASP.NET slice 04, preflight.md said "loopback only" while the release served the throwaway database on0.0.0.0:8080, copying the legacy container's bind. Verify now writes the ledger's Impl and Evidence cells withledger.py set --ids … --impl … --evidence …, which keeps every other byte and writes nothing on an unknown ID. Each verify had edited those cells withsed. In ASP.NET slice 03, macOSsed -i -Etook-Eas the backup suffix, and the slice commit carried a staleledger.md-Einto the port. The parity auditor now treats a behavior that the seed's Scope or Dead Ends gives to another slice as out of scope, naming that slice's case. Slice 06's first round counted the concurrent article-slug 409 as an omission. The seed had put it in slice 03, where AC-4o already passes. On Linux,ev port stoprefused to stop a release whose path was long. It matches the release root againstps -o command=, and procps cuts piped output at 80 columns. It now passes-ww. - A slice seed's acceptance cases fix the verify denominator. Two
auditors had counted 52 and 68 obligations from the same slice 07
criteria, and slice 04 went 22 → 23 → 32 over three audits. The seed
template now gives each case its own ID (
AC-2a,AC-2b: a token shape, route, fixture or black-box file) citing the ledger IDs it witnesses.ledger.py obligations --slice interview.mdlists the cases and fails on an item without an ID, a duplicate, a criterion listed both bare and lettered, or a mechanism or contract row that no case cites. The roadmap fixes the seed until it passes, and verify passes the list to the parity auditor asOBLIGATION_IDS. With a clean list the auditor scores those cases as given and counts anything else as a labelled omission. For an older seed it falls back to splitting criteria, seeded fromPREVIOUS. On the trial seeds it lists 22 cases for slice 04, the first audit's count. For slice 07 it flags M-10, C-14 and C-24, which the tables witness but the cases cite only through decision IDs.ledger.py stats --slicenow counts only the IDs the cases cite and the tables list. The ASP.NET trial's first 00a seed named other slices' rows in its prose, and the verify gate line read 5 of 12 rows instead of 5 of 5. That scoping exposed a gate rule that failed every slice owning no ledger rows. The trial's oracle-fixtures slice 01 selects 0 rows by design, so verify now exempts a slice whoseroadmap.mdrow lists none. - contract-surveyor records framework-rendered responses. The four
django-ninja surprises were all answers the framework renders, not a
handler: the 409
nullbody, a 500 for an undeclared status, pydantic's 422 text, and the malformed-JSON 400. They surfaced in slices 05–07, and two of them reached back into slices 02a–04. The surveyor now records, from the pinned framework version's source, the status and body for: a body that does not parse, a missing or wrong-type field, a path parameter that does not convert, an unknown route or wrong method, an undeclared status or reshaped return value, and an unhandled exception. When that source is not on disk, the row isUNKNOWNwith a question naming the function, never a body inferred from handler code. In the ASP.NET trial the pass found the 404, 405, 415 and binder answers at analysis time. It missed two classes, which the 00b seed raised instead: the exactContent-Type(the legacy 401 carries no charset) and an unsatisfiableAccept, which MVC ignores but Phoenix'splug :acceptsanswers with 406. The pass now records both. Slice 06 then found that every legacy error body is exactlyapplication/json(the error middleware and validation filter set it), where the port's 403, 404, 409 and 422 carried a charset. The Hurl spec asserts noContent-Type, so nothing had caught it. The surveyor's wire pass now records the type for success and for each error class. - Tests for the rewrite scripts (contributor tooling).
scripts/tests/test_rewrite_inventory.py,scripts/tests/test_rewrite_fingerprint.pyandscripts/tests/test_rewrite_ledger.pycover stack detection (PEP 621, Poetry and Pipfile manifests), coverage counts, the snapshot compare, the refusal to write inside the source, that git never runs a program the source checkout configures (fsmonitor, filters, hooks, signature checks), that a file written mid-read or a FIFO never yields a silent inventory, and the ledger check: only row IDs and aliases account for a mechanism, rows must match their header's width, anAR-NNdeparture needs a complete record (owner, scope listing the row, rationale, review, approval), each ledger table needs its own state columns, an ID-shaped token that breaks theM-/R-/C-+ digits scheme (R-D4) fails the check instead of vanishing from every count,statsfails on a malformed ledger, and an empty or unreadable slice never falls back to the whole ledger. Fingerprinting fails closed when a tracked file is flaggedassume-unchangedorskip-worktree, since git hides its edits.scripts/tests/test_rewrite_evidence.pyrunsevidence.pyin throwaway repos: fresh and stale identity, the command's own exit status, redaction,{uid},initmoving old records aside, everystatusstate, and a fake release thatport stopends while a neighbouring process survives.obligationshas its own cases in the ledger tests.scripts/tests/test_rewrite_plugin.pychecks the plugin's own rules: skill-dir paths resolve, namespaced references exist, every KB file an agent names exists, the ledger vocabulary matchesledger.py, and no stale vocabulary remains. - Trigger eval covers the rewrite plugin (contributor tooling).
trigger_scorer.pyroutes over the phx and rewrite skill pools together (phx keys stay bare, others arerewrite:verify), withtriggers/rewrite-*.jsonfixtures and hard phx near-miss negatives. All six rewrite skills score 100% on the Haiku gate. The behavioral dimension readsrewrite-verify.jsonforrewrite:verifyinstead of phx'sverify.json. make budgettracks the rewrite plugin (~1,144 always-on tokens).- Rewrite eval evidence (contributor tooling).
lab/rewrite_eval/holds seven negative controls with a grading rubric, their blind-replay results, and the artifacts of three--autoend-to-end runs (Campfire, Node and Django RealWorld) plus a fresh-session/phx:planhandoff from a Django seed. scripts/tests/test_block_dangerous_ops.py(contributor tooling). Pytest cases for the Bash gate's pattern-kill,MIX_ENV=prodand script checks. It also runshooks/tests/block-dangerous-ops_test.sh, which no CI job ran before, somake testnow covers the shell suite too.
Changed
The rewrite README states what the trials showed. Its Limitations now record the django-ninja and ASP.NET Core trials, what stays untested, and that
rewriteis Claude Code only: it relies on specialist subagents and a parity auditor in a fresh context, so the Codex, Pi, OpenCode, Amp and dsh ports do not include it.Directory listing metadata.
plugin.jsonsetsdisplayName(Elixir/Phoenix, so the plugin directory no longer lists it as "Phx") andsupportUrl(GitHub issues). The plugin README now says which data leaves the machine: dependency names tohex.pm, PR reads and replies to GitHub throughgh, and, only with--codex, the diff under review to OpenAI.Listing metadata for the
ectoandlvcompatibility plugins. Each gets adisplayName(Elixir/Phoenix: Ecto,Elixir/Phoenix: LiveView), keywords,supportUrl, an icon matching the main plugin's, and a README saying it is installed with the Elixir/Phoenix plugin and only hands its commands off to/phx:skills.catchupis ready for the plugin directory./catchupand/ketchupno longer pre-approve any tool (gh/gitcalls ask once like any other command). The plugin gets adisplayName,supportUrl, an icon, and a README section listing what it reads (GitHub viagh,git fetchfromorigin, your own Linear/Calendar connectors, session-file timestamps) and that the brief stays in your project.make venv(contributor tooling). Creates.venvfromrequirements.txt. When it exists,makeand the pre-commit hook put it first onPATH, so apython3upgrade without pytest or PyYAML (Homebrew movedpython3to 3.15) no longer breaksmake testormake ci.
Fixed
- Oban cron reference uses the 2.24 form.
oban/references/queue-config.mdshowed{Oban.Plugins.Cron, ...}underplugins:; Oban 2.24 moved cron to a top-levelcron:key (Oban.Cron, per-entrytimezone:). The old name is deprecated and still delegates, which the reference now says. --codexno longer dies when OpenAI retires the configured model.codex exec reviewexits 1 withThe 'gpt-5.5' model is not supported when using Codex with a ChatGPT accountonce themodelline in~/.codex/config.tomlis rotated out for the account (observed 2026-10-15; every gpt-5.x slug failed, codex-cli 0.162.1). The codex-reviewer agent and/phx:codex-loopnow resolve the account's current default fromcodex debug models(local cache, instant) and retry once with-mplus-c review_model(a pinnedreview_modelbeats-malone — codex's own review of the fix caught that), report the fallback and the config fix, and never hardcode a model name.codex-cli.mdgotcha 10 records the error shape and the one-liner; gotcha 5 drops the deadgpt-5-codex-maxexample.- The Bash gate now denies pattern kills of BEAM processes. A headless
session stopped a dev server it had started with
pkill -f phx.server, thenpkill -f beam, and/phx:workranpkill -f "mix phx.server". A pattern kill stops every Erlang VM on the machine, including the user's own servers, IEx shells and ElixirLS. In a project withmix.exs,pkill/killallnamingbeam,beam.smp,erl,erlang,epmd,elixir,iex,mixorphx.server(whole words, any case, any flags) is denied, as arekill $(pgrep …)andpgrep … | xargs killfor the same names. The message says to stop only the process you started, by the PID recorded from$!.kill PID,kill -0 PIDandkill %1still run. MIX_ENV=prodrule and deny message now agree. The gate denied everyMIX_ENV=prod … mix …command while its message said a release build was expected. Build steps now run:mix release,mix compile,mix deps.compile,mix assets.deploy,mix phx.digest,mix deps.get --only prod, andmix dochains of only those. Every other mix task andiex -S mixstay denied, and the message lists both sides.- A script no longer hides a denied step. A session ran
MIX_ENV=prod mix releasefromscripts/release_smoke.sh, which the gate never read, soMIX_ENV=prod mix ecto.migratethere would have run too. When a command runs a local file (bash/sh/zsh/source/.FILE,./FILE,DIR/FILE.sh), the gate greps it for theMIX_ENV=prodand pattern-kill denials and names the script and line. A missing or unreadable file is skipped (fail open). Known gaps (export MIX_ENV=prod,bash -c "…", quoted script paths) are listed inhooks/docs/safety-gates.md. - The plugin directory still blocked v3.1.3 with "Command path can't be
followed". The validator follows each hook script, not only the
hooks.jsoncommand. It rejects inline programs and paths from variables other than${CLAUDE_PLUGIN_ROOT}.deps-audit-gate.sh,check-scratchpad.shandcompact-rules.shusedawkandsed, now replaced by plain bash. A differential test against the old scripts gave identical output on bash 5.3 and on macOS's bash 3.2. - Hook message text no longer uses ASCII quotes. v3.1.3 plus the fix above
still failed at
., next to the Bash gate's deny messages. Those messages held apostrophes (user's) and'…'-quoted commands inside double-quoted strings, which a reader that loses track of double quotes misparses. Every hook script now uses’/‘…’in prose andNAMEfor placeholders, andtest_hook_script_prose_has_no_ascii_apostrophesguards it. - Hook scripts no longer run other programs by path. Still blocked at
.after the fixes above. The Bash gate re-parsed itself withbash -non its own path and looked upjqwithcommand -v;deps-audit-gate.shscannedmix.lockfor bidi characters with an inlineperl -neprogram, which the validator names outright. The self-check is removed (CI'sbash -npass and conflict-marker check still keep a corrupt copy from shipping, but a copy corrupted on disk after install is no longer caught),jq --versionreplacescommand -v jq, and the bidi scan is agrepbyte pattern underLC_ALL=Cthat matched the perl version on every test input with BSD grep, GNU grep and ugrep. - Hook scripts no longer use heredocs or strings that span lines. Still
blocked at
.after the fixes above. Read with heredoc bodies as code,deps-audit-gate.sh'sBLOCKED (:new_only). Bidi …message is a subshell followed by a.command; read line by line, the Bash gate's multi-line deny messages give the same). Do not retry.shape. Eleven scripts now print withprintf '%s\n'and one quoted argument per line. A 43-case before/after run on bash 5.3 and macOS's bash 3.2 gave byte-identical output except the/phx:deps-vethint, which now readsPKG VERinstead of<pkg> <ver>.test_hook_scripts_keep_each_string_on_one_lineguards it. - Fewer credential review holds. The
/phx:ash-frameworkcode-interface example registered a user withpassword, in the same file as ahexdocs.pmlink, and the directory read that as a credential sent to a remote host. It now uses[:email, :name], and the prose around it says "giveactor:" rather than "passactor:". - No agent sets
permissionModeany more. The plugin directory's security scan failedcatchupas "Weakens permission or sandbox controls" overpermissionMode: bypassPermissionsoncatchup-runner, and all 26phxagents carried the same line. Claude Code ignorespermissionModeon plugin agents, so they already ran under your session's permission mode; nothing changes at runtime. The agent eval now fails an agent that sets it, andtest_plugin_agents_do_not_set_permission_modechecks every plugin. - No skill pre-approves tools any more. The directory reviewer rejected
phxbecause "a hook or setting auto-approves its own tool calls". Hooks only ever deny, butallowed-toolson/phx:deps-audit,/phx:boundaries,/phx:assigns-audit,/phx:n1-check,/catchupand/ketchupapproved tools without a prompt. They listed onlyRead,GrepandGlob, which never prompt inside the project, and/phx:deps-audit'sWebFetchforhex.pm, which it does not use (it fetches throughpython3).test_plugin_skills_do_not_preapprove_toolsguards it. - Dev dependency advisories (contributor tooling).
brace-expansion,js-yamlandmarkdown-itpatch updates. Anoverridesentry lifts markdownlint-cli'sjs-yaml ~5.2.1pin to^5.4.1(Dependabot #18, GHSA-r3ph-w7gj-g6xm).
[3.1.3] - 2026-09-29
Ready for the Anthropic plugin directory. Every hook command is a single script
path, four skills no longer pre-approve Bash, the plugin has an icon and its own
README, and Amp-only code no longer ships to Claude Code. The branch-freshness
hook no longer runs git fetch with your credentials, and it only runs in
Elixir projects.
Added
- Plugin icon and README.
.claude-plugin/icon.svg(the phxagents.dev mark) andplugins/elixir-phoenix/README.md, which lists what the plugin runs on your machine: hooks, network calls (the Tidewave probe onlocalhost:4000,hex.pm, your ownghandcodex) and the files it writes. test_hook_scripts_parse(contributor tooling). Every hook script must passbash -nand contain no merge-conflict markers.
Changed
- The Bash safety gate's hook command is now one quoted script path. The
plugin directory rejects hook commands with shell operators, and
block-dangerous-ops.shwas the only one with|| exit 0. That suffix had kept a corrupted script (bash exits 2 on a syntax error, and exit 2 blocks the call) from blocking every Bash command. The script now checks its own syntax first (bash -n "$0" || exit 0), andtest_hook_scripts_parsechecks every hook script in CI.test_hook_commands_are_one_literal_scriptreplaces the quoting test and also rejects operators, arguments and missing scripts. The Codex target keeps|| exit 0. - Narrower
allowed-tools./lv:assigns,/phx:boundariesand/ecto:n1-checkno longer pre-approve Bash, so theirmixcommands ask for permission like any other./phx:deps-auditpre-approvesWebFetchonly forhex.pmanddiff.hex.pm, not every domain, and no longer pre-approves Bash. - The Amp-only watch-pr sources moved out of the Claude Code plugin.
plugins/elixir-phoenix/amp/(thephx-watch-pr.tsAmp plugin, which keeps a webhook credential under~/.config/amp/, and its skill overlay) is nowscripts/port_lib/amp_overlay/. Claude Code installs no longer receive it. The generatedtargets/ampoutput is byte-identical.
Fixed
check-branch-freshnessno longer runsgit fetch, and it only runs in Elixir projects. At every session start on a non-main branch, the hook fetched from your remotes with your SSH key or credential helper, in any git repository, Elixir or not. It now needs amix.exsand compares againstorigin/mainas of your last fetch./releaseskill (contributor tooling):git describematchesv*tags only. Afterclaude plugin tagit returnedlv--vX.Y.Zas the last release.
[3.1.2] - 2026-09-29
Claude Code 2.1.284 and the 5.5 models. Hooks now work when the plugin's install path contains a space. Workflow rules are re-injected after compaction. Agents run on Sonnet 5.5 at medium effort, and the prompts are reworded for the new models. Six skills that rarely fired on Sonnet 5.5 have new descriptions.
Added
make eval-quality, an output-quality eval (contributor tooling). Sixclaude plugin evalcases run each workflow with and without the plugin (--ablation with-without) and grade the resulting code, plan or findings. The cases are money stored as a float, queries inmounton a 50k-row list, an idempotent Oban worker, a plan with checkbox tasks, an investigation that can reuse a prior solution doc, and a review with nine seeded defects. On Sonnet 5.5, five runs per arm, the plugin's clearest gains are writing code against a bad local convention (money field +0.20, LiveView list +0.20) and reusing.claude/solutions/(+0.16). Review is a tie: Claude without the plugin found all nine defects in 5 of 8 runs. Paid and not part ofmake ci(about $2 per run of all six cases).
Changed
- Aligned with the Claude 5.5 models (Opus 5.5 default since CC 2.1.280,
Sonnet 5.5 since CC 2.1.284).
/phx:auditpins its twogeneral-purposetracks tomodel: "sonnet": Opus is now the default model on every plan, including Pro and Team Standard, so an unpinned track ran on Opus for everyone. The eval tooling follows the new models —trigger_scorer'ssonnet/opusaliases resolve toclaude-sonnet-5-5/claude-opus-5-5,make eval-pluginpins Sonnet 5.5, with a new baseline of 164/269 positive recall and 233/233 clean negatives (Sonnet 5: 176/269), and/session-scanknows the 1M context window of Sonnet 5+, Opus 4.8+ and Fable. Contributor docs record thateffort: highis now a step above the Opus 5.5/Sonnet 5.5 default ofmedium, and that Haiku 4.5 ignoreseffort. make validatenow runsclaude plugin validate --strict, so a validator warning fails the gate instead of printing "passed with warnings". A pytest guard checks hook quoting in GitHub CI, which has noclaudebinary.- The five former Haiku agents run on Sonnet 5.5:
context-supervisor,web-researcher,xref-analyzer,codex-reviewerandverification-runner. Haiku 4.5 is a generation behind, has a 200K window, ignoreseffort, and its retirement window opens on October 15, 2026; Sonnet 5.5 costs twice as much. Every agent now runs ateffort: medium, the CC default for the 5.5 models — three of the four Opus agents drop fromhigh, since Opus 5.5 atmediummatches Opus 5 athigh. - Prompts reworded for the 5.5 models. Anthropic's prompting guide for
this generation warns that emphatic CRITICAL/MUST/NEVER wording
over-triggers. About 100 edits across skills, agents, hook messages and
CLAUDE.mdreplace unexplained caps, duplicated prohibitions, session-ID history notes and narration suppressors with the instruction and its reason. Iron Laws, headings, and text the port builders anchor on are unchanged. /phx:fullIron Law 7 now says when to write text, instead of banning it. It was an older-model rule: a list of banned phrases and "if you catch yourself narrating, delete the text". Opus 5.5 follows such rules literally, which risks a silent autonomous run. The law now asks for a one-line status at each phase transition, a reason for each non-obvious decision, and a report of every error and blocker.- Amp specialists follow the canonical agent models instead of Haiku 4.5.
The security specialist runs on
anthropic/claude-opus-5-5; the other reviewers and the four investigation tracks run onanthropic/claude-sonnet-5, because Amp does not offer Sonnet 5.5 to plugin agents. Amp accepts any model ID at load and fails each child run at inference, so the build now fails on a canonical model alias it has no Amp mapping for.ELIXIR_PHOENIX_AMP_SPECIALIST_MODELaccepts anyprovider/modelID Amp lists (it used to reject providers such asgoogle-vertex). - Six skill descriptions rewritten after they almost never fired on Sonnet 5.5:
document,ecto-patterns,oban,perf,helpandtrace. Sonnet 5.5 does Ecto, Oban and performance work itself unless the description asks it to load the skill first, so each now opens with the user's task and, where it helped, says to load the skill before acting. Measured on held-out prompts the rewrites never saw (3 runs each):perf0/12 → 9/12,oban3/12 → 7/12,ecto-patterns0/12 → 6/12,document5/12 → 9/12;helpandtracegained on one or two prompts. No skill lost precision on negative prompts.quickkept its description; neither rewrite helped it. make eval-pluginhas a held-out split, and the description tournament requires it (contributor tooling). Trigger files takeshould_trigger_test/should_not_trigger_testprompts, taggedsplit-val(TAG=split-trainreproduces the old 502 cases).description_tournament.pyrefuses a skill with no held-out prompts unless--allow-unvalidatedis passed, and its prompts no longer ask for keyword-stuffed descriptions. Half the zero-recall prompts named code the eval fixture doesn't have, so Claude asked a question instead of loading a skill; they now target the fixture's real files./phx:triageno longer auto-approves Iron Law violations and BLOCKERs. It lists them first as recommended fixes and lets the user decide, as its own Iron Law 6 requires./phx:investigatechecks migration status (mix ecto.migrations) instead of runningmix ecto.migrateduring a diagnosis.- Contributor tooling adopts the new
claude plugincommands.make budgetfails when a plugin's always-on token cost (claude plugin details) grows more than 5% pastscripts/plugin_budget.json.make prompt-audit TARGET=...runs a report-only/doctor prompt-audit.make validatealso covers the contributor.claude/skills and agents./docs-checkfetches the plugin CLI, marketplace, dependency, measurement and plugin-eval reference pages.- The plugin manifests declare
"license": "MIT". - Each release now also gets
phx--vX.Y.Z,ecto--vX.Y.Zandlv--vX.Y.Ztags fromclaude plugin tag, so other plugins can depend on these with a version range.
Fixed
- Every hook failed when the plugin was installed under a path with a
space (for example
/Users/John Doe/.claude/...). All 30 commands inhooks.jsonleft${CLAUDE_PLUGIN_ROOT}unquoted, so the shell split the path and the script never ran. On the always-on Bash gate,|| exit 0then turned that into an allow: the force-push and destructive-mixblocks were silently off. Commands are now quoted;claude plugin validatehas flagged this since CC 2.1.281. /phx:work,/phx:full,/phx:plan,/phx:auditand/phx:reviewtold Claude to call task tools that current models don't have. Since CC 2.1.233 (narrowed further in 2.1.268),TaskCreate/TaskUpdate/TodoWriteexist only on older models unlessCLAUDE_CODE_ENABLE_TODO_TOOLS=1is set. Those steps are now conditional on the tool being present, and/phx:auditand/phx:reviewwait on agent completion notifications instead of task status. Plan checkboxes andprogress.mdwere already the real state./phx:watch-prasked Monitor for a 60–120 minute watch and apersistentoption that no longer exist. Since CC 2.1.271 a Monitor watch ends within 30 minutes. The watcher now runs in segments (WATCH_SEGMENT): before the deadline it emits a non-terminalrearmevent, and a restart withWATCH_RESUME=1restores the baseline, the already-reported reviews and comments, the check state and the codex flags from its delta file, so nothing is reported twice or missed.WATCH_MAX_DURATIONstill bounds the whole watch. Each segment polls at least once before it re-arms, and review and comment events now carry anid.- Workflow rules were never re-injected after compaction. The PreCompact
hook sent them as
systemMessageand the PostCompact hook as exit 2 + stderr, and CC shows both to the user only. Both now run onSessionStartwith thecompactmatcher, whose stdout reaches Claude, ascompact-rules.shandcompact-verify.sh. - The debugging-loop critic escalated a session's first
mixfailure.error-critic.shkept its failure counts in a global/tmpdirectory and never cleared them. After a command had failed twice in any earlier session, every later failure of it went straight to the "debugging loop" analysis. The count is now per session and resets when the command succeeds (the script is also registered onPostToolUseformixcommands). - The session-start scratchpad banner reported dead ends that didn't exist. It counted every bullet in the file, including the template's Handoff bullets. It now counts only the Dead Ends section.
- The Iron Laws injected into subagents skipped Law 18 (check changeset errors before UI debugging). All 26 are now injected.
- The Ecto failure hint told Claude to run
mix ecto.reset, which the dangerous-ops hook blocks. It now tells Claude to ask the user. - Stale facts in skills and agents:
pr-reviewcited Iron Laws 6 + 9 (it has 8).- The compound-docs schema limited
iron_law_numberto 1–13. /phx:deps-vetlisted unvetted-dependency blocking as future work; thedeps-audit-gate.shhook already enforces it.- The help catalog kept orphan bullets from the removed
/phx:autoresearch. context-supervisorignored caller-supplied globs such as/phx:deps-audit'striage/*.json.parallel-reviewertoldverification-runnerit had 10 turns (it has 15).- The
/phx:fullreferences documented nonexistent--resumeand--checkpoint-afterflags, and a commit per task. - "Fresh 200k context" claims predated 1M windows.
- The example plans used retired
--detailvalues.
- The monthly "Regenerate hex_vet seed" workflow failed on every run since
June (contributor CI). Its audit step is a placeholder and the
compose_seed.pyscript it calls was never committed. The workflow is now manual-only until both exist.
[3.1.1] - 2026-09-29
Two fixes that reach every runtime — deps-audit no longer installs its test
harness, and /phx:watch-pr now reports CI from commit-status integrations —
plus six skill descriptions that route more reliably in real sessions.
Added
- Real-session trigger eval with
claude plugin eval(CC 2.1.269+) —make eval-pluginturns everylab/eval/triggers/*.jsonprompt into a plugin-eval case and runs it in an isolated Claude Code session with phx, ecto and lv loaded, against a scaffolded Phoenix app (plus plan, review and Ash fixture flavors for skills whose prompts reference those artifacts).lab/plugin_eval/analyze.pyreports what the pass/fail table cannot: which skills actually fired, stray fires on other skills' prompts, plugin load and hook errors, and per-skill recall (MIN_RECALL=gates on it). First full baseline (502 cases, Sonnet 5): positive recall 176/269 (65%), negatives 233/233 clean — while the Haiku description-only gate scored several of the weakest skills at 100%. 173 of 176 hits invoked the skill as the first tool call, so cases cap at 3 turns (~$0.09/run). Suite lives outside the plugin: phx'sdependenciesare unsatisfiable whenclaude plugin evalloads it alone, which silently disables the plugin while still scoring every run. Unit tests run inmake testand CI.
Changed
- Six skill descriptions rewritten to lead with the user's task, each
validated by a real-session A/B (15 samples per skill per arm):
security8→14/15,investigate7→11,ecto-patterns3→6,review1→4,perf4→7,oban11→13 (pooled 34/90 → 55/90, Fisher p=0.001). All six still clear the 75% Haiku gate. The same rewrite madeelixir-idiomsandphoenix-contextsworse, so those two were reverted. Generated targets re-synced. - The dsh runtime smoke has now been executed against an installed
dsh— it shipped in v3.1.0 written against the rc.2 wire contract read from source but never run, anddocs/dsh.mdcarried a caveat saying so. Run againstdsh 0.1.1-rc.2:session.createreturns{ agentPreset, sessionId }andskill.listreturns all 51 generated skills as{ name, description, modelInvocable }, all model-invocable — the contract matches, no code change needed. The same session confirmed the one-level discovery rule empirically: the identical 51 skills installed one directory deeper are discovered zero times, which is the behaviour the builder's nested-SKILL.mdrejection and the install guide's warning both rest on. The caveat is replaced with the observed results.
Fixed
/phx:watch-prnever reported CI on repos with commit-status checks, and reported cancelled runs as green (#144, reported by @aquental) — the watcher read everystatusCheckRollupentry as a GitHub Actions CheckRun, but StatusContext entries (CodeRabbit, CircleCI and other commit-status integrations) carry onlystate. A passing one counted as pending forever, so thecheckevent never fired and the watcher ran silently to its watchdog. CheckRuns concludingCANCELLED,TIMED_OUT,STARTUP_FAILURE,ACTION_REQUIREDorSTALEcounted as success. Entries are now classified by__typename; a regression test runs the real script against 19 rollups.trigger_scorer --model sonnet|opusevaluated retired models — the aliases still resolved toclaude-sonnet-4-6andclaude-opus-4-7; they now resolve toclaude-sonnet-5andclaude-opus-5.deps-auditshipped its test harness to every install — the Claude Code plugin and all five generated targets (Amp, Codex, dsh, OpenCode, Pi) installed 53 dev-only files: thesmoke-test/fixture suite, the recorded hex.pm cassettes intest-assets/, andpriv/cassettes/capture.sh. They now live in the repo atlab/deps-audit/, and a test fails if a skill folder holds anything besidesSKILL.md,references/,scripts/andpriv/. The Hex tarball loader was the one runtime piece in that suite: it moved todeps-audit/scripts/fetch_tarball.sh, and/phx:deps-vetnow calls it through${CLAUDE_SKILL_DIR}instead of a repo-relative path that did not exist inside a user's project.
[3.1.0] - 2026-08-27
A fifth runtime target (DeepSeek Harness), the first user-facing hook
documentation, and a native Amp phx-watch-pr lifecycle.
Added
DeepSeek Harness (dsh) target (#139) — a fifth generated runtime target at
targets/dsh/, projecting all 51 canonical skills with their complete resource subtrees. dsh'sdsh-skill-filesystemprovider scans.agents/skillsand friends for single-level<name>/SKILL.mdbundles, and its pre-step boundary injects the full body for a whitespace-bounded/phx-*token, so the plugin's command surface survives intact. dsh also readsCLAUDE.mdnatively, so/phx:initoutput needs no porting. Addsscripts/port_lib/dsh.py,scripts/build_dsh_skills.py, twelve tests inscripts/tests/test_dsh.py,make dsh-skills{,-sync,-validate}wired intogenerated-skills-syncandci, an optionalmake dsh-runtime-smokethat verifies discovery over dsh's loopback/apiRPC bridge (session.createthenskill.list, no API key needed), a golden snapshot entry,docs/dsh.md, and adshcolumn indocs/runtime-support.md. Two dsh-specific build gates: descriptions are capped at the 500-charcatalogDescriptionMaxLength(dsh truncates silently past it) and nestedSKILL.mdfiles are rejected (dsh discovery is deliberately one level deep). Skills-only by design — dsh has no markdown agent registry, and its Claude Code hook bridge covers 7 of 30 events while droppingif:gating, so neither the 26 agents nor the 30 hooks ship. Tested against dsh0.1.1-rc.2, a developer preview.Hook documentation — the 23 hooks across 10 lifecycle events had no user-facing docs; the reasoning lived only in script header comments and a terse contributor checklist. Adds
HOOKS.mdat the repo root (what fires when, a lifecycle diagram, the full hook table, escape hatches),plugins/elixir-phoenix/hooks/README.md(per-event output-channel table and the shared conventions:mix.exsgating, the fail-open contract,ifconditions, blame-aware scanning), and six per-group deep dives underplugins/elixir-phoenix/hooks/docs/— safety gates, code quality, failure recovery, context injection, session lifecycle, workflow state. Each records the incident or measurement the hook came from (issue #55 cross-project bleed, issue #61--force-with-lease, issue #38 progress.md cross-writes, the ~0% CLAUDE.md routing finding).CLAUDE.mdnow requires these docs to be updated in the same commit as any hook change.Amp-native
phx-watch-prlifecycle — the deterministic Amp target now includes a focused Plugin API runtime that acquires an Orb keep-alive lease, persists watch state across reloads, filters deployment-like checks out of readiness, detects unresolved review threads and required CI failures, and serializes--fixevents back into the same worker thread. Defaults are a 60-second poll, 15-minute activity-based quiet period, and 2-hour hard active-watch cap; head pushes, required-check transitions, reviews, and comments restart quiet so delayed reviews cannot be hidden by an early green snapshot without waking inference for routine progress. Deployment-only transitions remain status-visible but silent. Every terminal path releases the lease, and the plugin never blindly reruns shared CI, merges, or deploys. An optional durable Amp webhook can reactivate a completed watch after the Orb pauses only when the event identifies the exact watched PR or head SHA; repository webhook configuration remains an explicit external administrator action.README links phxagents.dev above the fold — a docs badge, a one-line Docs block pointing at
/install/,/compatibility/,/iron-laws/and/catalog/, and a per-runtime install-guide link in each of the four "Using Amp/Codex/Pi/OpenCode?" callouts. The README had zerophxagents.devmentions across 1,056 lines while being the project's higher-traffic discovery surface: over 08/02-08/15, Google sent 56 unique visitors to the repo versus 14 from the docs site, so everyone arriving from search read the whole README and never learned the docs site exists. The repo's About → Website field was already set.Deterministic Amp workflow plugin — the generated Amp target now includes 40 workflow palette commands, five read-only domain specialists, bounded parallel review and investigation, a persistent classified edit lock, and a bounded
phx-fullverification gate. The standalone distribution promotes a validatedstablebranch for direct GitHub installation.
Changed
route-intent.shtimeout raised 10s → 30s — theUserPromptSubmitintent-routing hook had the tightest budget of any hook in the plugin despite sitting on the latency path of every prompt. It normally finishes in well under 100ms (it truncates the prompt to 4000 chars before scanning), but a timeout drops the routing hint silently, so the extra headroom costs nothing on the common path and avoids losing suggestions on a slow first run. The hook already exits 0 on every path — aUserPromptSubmithook must never exit 2, which would erase the user's prompt.Release bodies now carry a docs-site footer — the
releasecontributor skill gained an Iron Law and aprintfstep that appends<https://phxagents.dev>to the extracted CHANGELOG release notes. A release body is read at the moment someone decides whether to install, and releases are this project's one promotion lever with a measured effect: v3.0.1 drove unique cloners from 51 to 120 in a single day (2.4x), decaying to baseline over about four days.Amp installation profiles and scope names now match current Amp — docs distinguish hosted-native from paired-full behavior, use native
skill: invokefor deterministic hosted skill loading, and separate machine-local--globalinstalls from personal/workspace hosted repositories. The public standalone repository, generated target, gatedstablebranch, paired local install, and GitHub/curl fallback remain supported.deep-bug-investigatorsynthesizes on opus, its four tracks run on sonnet (thanks @BugsBunny338, #133) — the agent coordinates four parallel investigation tracks and then reconciles their often-conflicting evidence. Cross-track synthesis is where the stronger model earns its cost, and hard-to-reproduce bugs are where a weak synthesis is most expensive. The frontmatter bump alone would have been the wrong fix: the tracks spawn as baregeneral-purposesubagents, which default tomodel: inheritand so resolve to the spawning parent's model — opus on the orchestrator would have silently promoted the whole fan-out to five opus contexts. The tracks are now pinned tomodel: "sonnet"explicitly, keeping fan-out cost where it was: sonnet gathers evidence, opus synthesizes.effort: mediumis unchanged; the eval allowlists opus for orchestrators, so no symmetry fix is owed.workflow-orchestratorpins its COMPOUNDING spawn to sonnet — same inheritance leak as above, found while reviewing #133. The orchestrator runs on opus and spawned an unpinnedgeneral-purposesubagent to write solution docs, so that mechanical phase was silently billed at opus. Namedphx:*subagents were never affected — their own frontmatter wins — soparallel-reviewerandplanning-orchestratorneeded no change.The
releasecontributor skill hoists upgrade warnings to the top of the release body — a new Iron Law and template: when a release needs anything beyond/plugin update, the body opens with a> [!WARNING]block carrying the exact commands, and states the blast radius in what the user loses rather than in mechanism. v3.0.0 documented its staged upgrade correctly but placed it at roughly line 145 of a long changelog dump, phrased as "a temporary missing-dependency state" — and users upgraded into a broken install anyway (#135). A correct instruction nobody reaches is indistinguishable from a missing one.CLAUDE.md model-tier rules describe the actual split — the guidance said "opus for primary workflow orchestrators, sonnet for secondary orchestrators (investigation, tracing)", which stopped matching the plugin once
planning-orchestratormoved to sonnet anddeep-bug-investigatormoved to opus. The rule is now stated in terms of what earns opus (synthesis across parallel workers) and names the four agents on each side, plus thegeneral-purposepinning requirement that follows from it.
Fixed
Canonical skills told every runtime to run Claude Code's
/mcp(#141) —tidewave-integration/SKILL.md(Iron Law 3) andexamples/SKILL.mdboth used/mcpto check MCP availability, and shipped byte-identical to all five generated targets, where that command does not exist. Iron Law 3 now reads "Call Tidewave only when matchingmcp__tidewave__*tools are present" — a check that works on every runtime — and theexamplesfence drops the probe for a# Requires connected mcp__tidewave__* toolsnote, switching from abashfence to atextone since its contents are tool invocations rather than shell. The one remaining mention stays, already qualified with "in Claude Code". Found while reviewing #140; fixing it in one target would have broken the test asserting the two Agent Skills projections differ only by the runtime name.The
phx-watch-prharness test was one second from red — it ran with a 60-second subprocess timeout and takes ~59 seconds of wall time for ~3 seconds of CPU: the harness drives a fake clock with real 80ms flushes, andadvance()performs up to 21 of them across 23 calls. Observed locally at 58.9s, 60.4s, and 73.9s on consecutive runs, so it passes or fails on runner load alone — and it is a required check, meaning it would have started blocking unrelated PRs. Raised to 240s; the harness itself is unchanged, since shortening the flush would trade a timeout flake for a race.The
phx-watch-prinstall command could never have worked — README anddocs/amp.mdtold users to runamp plugins addagainst araw.githubusercontent.comURL. Amp0.0.1787045288rejects that withFor now, only https://ampcode.com/@amp/plugins/*.ts ... URLs are allowedand writes nothing. The samedocs/amp.mdalready said, 390 lines further down, that "Amp currently restrictsamp plugins add... to Amp-hosted plugins" — the file contradicted itself, and the install instruction was the wrong half. Both call sites now use the atomiccurl+mvpattern already documented forelixir-phoenix.ts, pointing at the standalone repository'sstablebranch. The update section covers both plugins instead of only the workflow one.Upgrading from v2.x no longer lands users in a dead install (reported by @barquesurlocean, #135) — v3.0.0 renamed the plugin manifest to
phx(which is what makes/phx:*correct; pre-v3 versions namespaced their commands as/elixir-phoenix:*while/phx:initwrote/phx:*intoCLAUDE.md) and in the same commit introduced theectoandlvcompatibility plugins as manifestdependencies. Adding a dependency turns out to be a breaking change for already-installed users:claude plugin updatedoes not install dependencies a new version newly declares, and a missing dependency is a hard load failure, so the obvious/plugin updateleaves the plugin at✘ failed to loadwith all 36/phx:*commands gone — taken down by the 3 compatibility commands. Verified on Claude Code 2.1.234, so this is not the 2.1.76–2.1.109 version band recorded during #130; it affects every v2 user on every Claude Code version. Nothing self-heals either, because auto-update is off by default for non-Anthropic marketplaces. The README's staged upgrade block was already correct but described the failure as "a missing-dependency state"; it now leads with a warning, states the blast radius in commands lost, declares the 2.1.110 version floor, adds a recovery path for anyone who already updated in the wrong order, and explains the/phx:vs/elixir-phoenix:prefix history. Note during recovery thatclaude plugin install elixir-phoenix@oliver-kriskaresolves only one missing dependency per invocation.make validatecovers every plugin manifest — the target validatedplugins/elixir-phoenixand the marketplace only, soplugins/ecto,plugins/lv, andplugins/catchupcould ship a schema violation that CI never saw. This is exactly howdisplayNamereached users: it was present in all four Claude-facing manifests while the gate looked at one of them. All five manifests are validated now.Amp wrapper skill resolution follows supported local precedence — the generated plugin now checks machine-local skill roots before workspace roots and searches
.agents/skillsand.claude/skillsthrough the parent directories exposed fromworkspaceRoot. Docs explicitly call out sources the Plugin API cannot resolve, including hosted repositories, built-ins, plugin caches, customamp.skills.path, and nested invocation roots that are not exposed to plugins.displayNameremoved from all Claude Code manifests (reported by @ndrean, #130) — the field was introduced alongside the v3 plugin split and reported as an install blocker one day later. It is undocumented: it appears nowhere in the plugin manifest schema, whose metadata fields areversion,description,author,homepage,repository,license, andkeywords. It is also inert —claude plugin listrenders the pluginname, neverdisplayName— and Anthropic's own marketplace uses it in 3 of 285 entries, always inmarketplace.jsonand never in aplugin.json, which is exactly where this repo had it.catchuphad already been shipping without it. A clean install from GitHub could not be reproduced as broken on 2.1.227, so the original failure was likely specific to the Claude Code range current in late July; the field is gone regardless, since nothing depended on it. The Codex manifests (targets/codex/.codex-plugin/plugin.json,.agents/plugins/marketplace.json) keep theirs — different runtime, different schema, where it is nested underinterface.
[3.0.1] - 2026-08-10
Maintenance release: closes a bypass in the destructive-command hook, retires injected CLAUDE.md prose that session analysis measured as inert, and brings the marketplace manifest under CI validation.
Changed
/phx:initwrites a routing table instead of a seven-step procedure — the template injected into yourCLAUDE.mdcarried an "EXECUTE BEFORE EVERY RESPONSE" block: classify the request, total a complexity score from an eight-row point table, route off the number, then consult a file-pattern auto-load table. Session analysis across 400 sessions measured that style of CLAUDE.md prose firing ~0% of the time, which is why plugin routing moved into theUserPromptSubmithook and Iron Laws into theSubagentStarthook. The injected template had never been updated to match and still shipped the pre-measurement design. It is now a compact routing table plus the rules that carry real content — every routing decision is preserved, including that security work does not skip planning. Iron Laws, verification, and the stack-conditional sections are unchanged. Re-run/phx:init --updateto pick it up.Marketplace manifest is now validated in CI —
make validateran onlyclaude plugin validate plugins/elixir-phoenix, so the marketplace manifest and itsecto/lv/catchupentries were never checked. CC 2.1.221 added name warnings for entries that Claude Desktop's managed marketplace sync would reject;make cinow catches them.Trimmed dated prompt scaffolding from four agents — removed fixed word ceilings from the
deep-bug-investigatorandcall-tracersubagent prompts (deep-bug-investigatoralready compresses every track throughcontext-supervisor, so the cap was degrading input to its own compression layer), and dropped duplicated "you do not have Bash access" restatements iniron-law-judgeandsecurity-analyzerwhere the frontmatter tool list already enforces it.
Fixed
- Closed a leading-whitespace and subshell bypass in the destructive-command
hook — all three deny patterns anchored on
^with no leading whitespace permitted, so a leading tab beforemix ecto.drop, leading spaces beforegit push --force, and a subshell-wrapped(MIX_ENV=prod mix release)each executed while evading the block. The anchor is now^[[:space:](]*. Claude Code fixed the same padding-hides-the-command class of hole in its own permission prompts in 2.1.223. The quoted-mention protection from issue #61 is preserved —echo "(git push --force)"still does not trigger — and the regression suite covers both directions.
[3.0.0] - 2026-07-25
Major release: keep Claude Code as the full canonical plugin while adding generated, runtime-native skills distributions for Amp, Codex, Pi, and OpenCode. Each target is explicit about supported workflows and deferred capabilities rather than claiming cross-runtime feature parity.
Added
LiveView client-boundary guidance — documents that
phx-value-*, form, and hook payloads are user-controlled; clarifies that exposed IDs are not an authorization flaw by themselves and must be checked against server-side state in every event.Generated multi-runtime skills distributions — all 51 canonical skills, complete resource subtrees, non-Markdown bytes, and executable modes now ship as deterministic targets for Amp, Codex, Pi, and OpenCode. Target generators normalize names and invocation syntax, validate references and manifests, reject collisions and escaping resources, build through staging directories, preserve prior output on failure, and provide read-only drift checks. Claude Code remains the canonical source and retains its full native hooks, agents, MCP, permissions, and instructions.
Canonical multi-runtime support matrix — documents native invocation, distribution, supported and deferred capabilities, generated-target acceptance requirements, and isolated Amp, Codex, Pi, and OpenCode smoke-test contracts.
All-runtime generated skills sync command — contributors can run
make generated-skills-syncto regenerate and validate Amp, Codex, Pi, and OpenCode in sequence, then validate the reviewed golden snapshots. Target-specific sync and validation commands remain the authoritative implementation and keep failures attributable.Codex native destructive-command safeguard — the generated Codex plugin now includes one trust-gated, synchronous
PreToolUsehook that reuses the canonical audited blocker for destructive Ecto operations, unguarded force pushes, and accidental production Mix commands. The broad Claude hook set, async hooks, custom agents, and Tidewave MCP remain deferred.Optional runtime smoke harness —
make amp-runtime-smoke,make codex-runtime-smoke,make pi-runtime-smoke, andmake opencode-runtime-smokevalidate locally generated targets, native installation or discovery, all 51 installed skills, retained resources and modes, and fresh-process removal in isolated temporary homes without requiring credentials or making model calls.Generated-target golden snapshots — CI now pins aggregate path, byte, and executable-mode digests for Amp, Codex, Pi, and OpenCode before shared generator refactoring. Target changes require an explicit reviewed snapshot update instead of silently redefining the baseline.
Package-specific
/phx:learn-from-fixrouting — verified fixes and explicit user-taught rules can now be saved as native background skills with--library <package> --scope personal|project. The workflow reads locked versions, checks both scopes for shadowing, merges safely, and never writes to cached plugin files. General cross-project rules route to personal~/.claude/CLAUDE.md. Description-based activation remains model-selected.Amp target drift pre-commit guard — canonical changes under
plugins/elixir-phoenix/skills/now triggermake amp-skills-validateand require regeneratedtargets/amp/skills/changes to be staged. CI retains the same drift check as the authoritative fallback. Contributors can runmake amp-skills-syncto regenerate and verify the complete target in one command.
Changed
Optional release-service configuration — deployment guidance now gates optional S3, Redis, and similar credentials behind their feature switch so unrelated release commands such as
bin/migratecan start without them.Portable Amp investigation and review workflows — generated Amp
phx-investigateandphx-reviewnow preserve evidence-first investigation, read-only review, optional native workers, and complete same-session sequential fallbacks without Claude task APIs, named agents, hooks, or MCP identifiers.Portable PR-review/full workflow overlays — generated Amp, Codex, Pi, and OpenCode
phx-pr-reviewnow use available GitHub connectors or authenticatedgh, preserve read-only triage and explicit mutation approval, and never infer replies or resolution. Generatedphx-fullnow preserves user phase gates, bounded retries/cycles, explicit verification, read-only review, and compound completion through portable skill invocation or sequential same-session execution. Canonical Claude output remains unchanged; other workflows are not claimed portable.Portable plan/work workflow overlays — generated Amp, Codex, Pi, and OpenCode
phx-planandphx-workskills now use scratchpad research checklists, plan checkboxes, andprogress.mdinstead of Claude-only named agents, task APIs, question tools, hooks, or MCP identifiers. Optional native subagents and Tidewave retain complete same-session sequential fallbacks. Canonical Claude skills are unchanged; this does not claim parity for other generated workflows.Codex skill descriptions now preserve routing signal within a compact budget — generated descriptions are capped at 120 characters while retaining key capability and trigger cues. Route-sensitive skills keep explicit negative routing rules to avoid collisions. This reduces pressure on Codex's shared skills context without changing canonical Claude descriptions, explicit skill bodies, or other generated runtimes.
Amp installation no longer requires cloning this repository — the primary project-local and global instructions now install all 51 generated skills directly from the GitHub
targets/amp/skillstree. Update instructions also clarify that Amp copies skills and requires an explicit--overwriteinstall.
Fixed
Examples skill routing — clarify that requests for sample code, proper implementations, walkthroughs, and expected workflow output should load the examples skill alongside the relevant domain skill.
Accurate Tidewave MCP setup boundary — update the maintained Phoenix dependency requirement and repository link, distinguish exposing Tidewave's HTTP server from registering it with a client, and document external registration for Claude Code, Codex, and OpenCode.
Claude Code 2.1.217–2.1.220 nested-agent compatibility — flagship workflows account for the depth-1 default in 2.1.217–2.1.218 and the restored depth-3 default in 2.1.219+. Explicit lower-depth configurations keep orchestration in the main conversation while spawning leaf specialists directly, preserving the plan/work/verify/review contracts. Nested investigation tracks apply tracing directly to avoid a depth-4 chain.
Claude Code 2.1.212 Agent invocation compatibility — removed the ignored, deprecated Task/Agent
modeparameter from workflow instructions. Subagents inherit the parent session's permission mode.Fork-session continuity on Claude Code 2.1.214+ — resume, scratchpad, and branch-freshness SessionStart checks now also run for source
fork.Claude Code 2.1.218 agent validation compatibility — declared agent names remain unqualified while concrete runtime invocations use the
phx:plugin namespace. Regression coverage rejects colons in declared agent names and unrecognized agent tool names.Claude Code slash-command compatibility remains stable in v3 — the marketplace package keeps its
elixir-phoenixinstall identity while the plugin namespace now explicitly registers/phx:*; auto-installedectoandlvcompatibility plugins preserve/ecto:*and/lv:*. The call tracing skill directory now matches/phx:trace, and regression coverage checks effective namespace-plus-frontmatter command names. Canonical frontmatter names contain only the final command segment because Claude Code 2.1.216+ retains the plugin prefix when a skill declaresname.v2 → v3 upgrade path preserves newly added compatibility dependencies — install the new
ectoandlvcompatibility plugins from the updated marketplace before updatingelixir-phoenix. This avoids a temporary missing-dependency state on existing installations. Restart Claude Code before relying on the updated hooks and agents.Generated freeze is now honest about its safety boundary — Amp, Codex, Pi, and OpenCode receive an advisory current-session edit scope instead of a sentinel that falsely claimed enforcement by an uninstalled Claude hook.
Generated nested resource links now resolve from their containing file — Amp, Codex, Pi, and OpenCode projections compute skill-relative paths from each Markdown resource directory instead of the skill root, fixing broken cross-skill and sibling links under nested
references/directories.Pre-release test and docs-site consistency —
npm testnow includes thescripts/tests/port suite, matchingmake testand CI, and the docs-site rebuild workflow triggers on all five runtime guides instead of onlydocs/amp.md.Pi Git-package metadata now matches the generated distribution — the repository-root manifest used by the documented Pi installation reports the same
pi-elixir-phoenixname, 3.0.0 version, and Pi-focused description astargets/pi/package.json.Amp generation now rejects source symlinks and preserves Markdown modes — generated resources can no longer dereference links outside the canonical skill tree, and transformed Markdown permissions no longer depend on the invoking process's umask.
Runtime support and security claims now match operational boundaries — the flagship portability contract is scoped to adapted Codex, Pi, and OpenCode workflows; Codex documents and surfaces its Bash/
jqhook prerequisites; and security documentation distinguishes absent telemetry or covert egress from visible, user-authorized external service calls.Codex dependency-vetting routing metadata stays complete — the generated
phx-deps-vetdescription now preserves both its post-audit trigger and its distinction from dependency scanning instead of ending mid-clause.Reliable behavioral trigger gates and routing boundaries — deterministic structural evals no longer depend on ignored local result caches, while
make eval-fullruns a fresh Claude Haiku 4.5 gate requiring every skill to reach 75% accuracy. The judge now receives complete descriptions, validates the exact skill/fixture set, retries infrastructure failures, rejects malformed output, and writes results atomically. Generalized routing fixes brought all 51 skills above the threshold in the final full run (95.74% aggregate).Generated-runtime install lifecycle guidance — Amp now documents native, target-scoped removal and exact-sync boundaries; Pi distinguishes configured Git refs from unpinned dependencies and includes both project and user clean reinstall flows; OpenCode uses its documented plural skills path and treats slash invocation as a tested 1.17.2 convenience rather than the portable API.
Codex plugin skill references now use their required runtime namespace — explicit invocations and generated sibling references use
$elixir-phoenix:phx-investigaterather than the non-resolving unqualified$phx-investigateform. README and Codex installation guidance now document the exact plugin-qualified syntax inserted by/skills.Portable command rewriting now requires complete invocation tokens — Amp, Codex, Pi, and OpenCode projections leave filesystem paths, URLs, uppercase names, underscored suffixes, and malformed namespace wildcards unchanged. Pi now emits native
/skill:*syntax directly instead of passing through a path-like intermediate form.Learning destination and skill-loading documentation — project-keyed auto-memory is no longer described as applying to every Elixir project, and the intro now documents native
paths:frontmatter as a file-path gate.