Changelog
What changed in each release of Agent Triforge, newest first. The newest release is open. Select an older one to read its notes.
- Current version
- 4.2.0
- Released
- Release notes
- Twenty-one
- Oldest note
v4.2.0: Today's CLIs read correctly, a Coverage line in every review lane, fail-closed verdicts, a 2.1.314 worker floor
What's new
This release brings Triforge's detection, notices, docs and probe up to date with Claude Code 2.1.318, Codex 0.166.0, agy 1.5.1 and Cursor 2026.11.05, and closes gaps in how the review cycle decides it is done.
- Claude Code's
instructionFilessetting is read. Whether aCLAUDE.mdhidesAGENTS.mddepends on that user-tier setting. Setup and session start now read it and report what Claude Code loads under its mode, instead of assuming the default. - Codex's content-bound hook approval is read. Codex now ties a plugin hook's approval to the plugin's contents. Detection, setup and the probe read the new shape and the older one. Any change to the plugin's contents, this update included, asks for approval again, and until then a Codex lead runs without the plugin's hooks.
- An agy run that stopped early no longer passes as finished. Any
finished_reasonmarks the run incomplete, a print-timeout expiry is a timeout that is not retried, and every nonzero exit writes the status file the promotion guards read. Partial output or an error message no longer lands as a review or an architecture report, and a cut-off lease never goes to review. - The default review no longer stops at agy's analyst. On agy 1.5.1 a tool call that needs a permission prompt ends a headless turn with an empty response, so one attempted write, command or read outside the workspace lost the analyst and stopped the whole review. 4.1.1 fails the same way. The Antigravity analyst now returns its review instead of trying to write it, and a refused tool call no longer loses the run.
- Every review lane ends with a Coverage line. It reads
Coverage: complete,Coverage: partial: <reason>orCoverage: none: <reason>. A lane that reports less than complete, or omits the line, is a gap the cycle cannot converge on. Coverage is self-reported: the snapshot-bound review receipt is the first 4.3 item. - Reviewers judge every
Accept:. An unmet one is at least P2, and one they cannot run is judged by reading and listednot run. They read callers, contracts and surrounding code before reporting or clearing a finding, and never quote a secret value. Convergence needs a report on the final changed snapshot: a cycle that applied a P1 or P2 fix re-reviews the changed files, and that review counts against the three-cycle cap. - Plan and verify verdicts fail closed. A new helper,
persona_verdict, readsplan-checkerandintegration-verifierreports. It passes only an explicit APPROVED with no blocking issue, or an explicit PASS with a matching recommendation. Anything else, a malformed report included, stops the plan or the wave. coordinate.shexits 75 when the sprint did not converge. It now exits 75 instead of 0 when its sessions run out withoutops/.sprint-complete, so exit 0 means the sprint completed or a dry run printed. A wrapper that treats any nonzero exit as a crash needs to handle 75 as "run again to continue".- A claude worker needs Claude Code 2.1.314. That build carries the sandbox isolation fixes. Below it a worker refuses to start and session start prints a
worker-floorline. The lead's own floor stays 2.1.277. - Codex workers no longer load your installed Codex plugins. Foreground, lease and persona workers run with
--disable plugins. A Codex lead keeps its own trusted plugin hooks, and skills under.agents/skills/still load. - Cursor's default moved to Grok 4.7. The shipped pin is
grok-4.7-xhigh, and it answered live on Cursor 2026.11.05. A roster that pins the 4.6 id keeps working. - The probe reports what it proved. Rows that depend on a host setting pin it and report the host's value. On an OpenCode V2 host every OpenCode row reads
UNAVAILABLEand says why, and a deny row needs a positive control before a missing marker counts. A local row installs the exported tree under both leads, and a new row proves the Codex worker flag.
Details
Instruction files
The setting is pluginConfigs."cc-plugin-agents-md@builtin".options.instructionFiles in your user-tier settings.json. A live run of each mode on Claude Code 2.1.318 showed:
| Mode | No CLAUDE.md |
A plain CLAUDE.md |
A CLAUDE.md with @AGENTS.md |
|---|---|---|---|
claude-md-or-agents-md (default) |
loaded | hidden | loaded |
claude-md-and-agents-md |
loaded | loaded | loaded |
claude-md |
hidden | hidden | loaded |
managed-only |
hidden | hidden | hidden |
Triforge reads the user tier only, and never writes it. Managed settings and an active --settings are not read, and the verdict's reason says so. A value that is unset, unknown or unreadable gets the default's verdict, with the reason named. claude-md and managed-only print one warning naming the setting, with no import offered. An import that already loads under claude-md reads as visible. With an import under claude-md-and-agents-md, input tokens matched the default mode in each of three runs, so the file is read once.
Codex hook approval
hooks/list adds no new field. A plugin hook's currentHash is one string, sha256:<declaration>/sha256:<content>, and a project hook's has no slash. In hooks.state, a plugin hook's trusted_hash holds the declaration half and trusted_content_hash the content half. A project hook keeps trusted_hash equal to currentHash. Detection accepts that shape and a carried-over approval with only the declaration half, which Codex 0.166.0 also reports as trusted and runs. For a carried-over approval the content binding is Codex's own: its first hooks/list records a baseline in $CODEX_HOME/.hook-trust/, so a first listing after an update pins the updated contents. Your config.toml stays byte-identical. Upstream calls the value opaque, so if the format changes, detection reads the hooks as absent and the probe row fails.
agy
The analyst lane was found failing live on agy 1.5.1, 5 runs of 5, and on 4.1.1, 2 of 2. invoke_antigravity now passes session-only rules through --settings where agy has that flag (no write, no command, reads everywhere except the credential paths; nothing saved, your own rules kept), and all four Antigravity agents are read-only and return their answer. The analyst, research and documentation agents no longer run shell commands. After the fix the lane promoted a review in 12 runs of 12 on 1.5.1, native and injected, and two runs with a seeded defect both reported it. Not run live: --settings beside real user-tier rules, and an agy older than 1.5.1.
Observed live on agy 1.5.0, before the host updated itself to 1.5.1: a normal finish is exit 0 with no finished_reason; an expiry is exit 3 with finished_reason: print_timeout; an interrupt is exit 1 with interrupted; a bad model is exit 1 with error. With --print-timeout equal to Triforge's outer window the outer timeout usually wins, so the usual route is exit 124. Both are class timeout and neither retries. A lease that expires follows the existing orphan path: requeued once, then escalated, never reviewed.
These stay fixture-only, because they could not be provoked on 1.5.0: a finished_reason at exit 0 (the shape comes from agy 1.4.11's release note), an AGY_ERROR with no stderr line or with an empty body, and retryable: true on an expiry. The lease lane's expiry ran on stubs; only invoke_antigravity ran on real agy.
Review
The Coverage line must be the last non-empty line, outside any code fence, so a line quoted from a diff cannot speak for the lane. A model that wraps its whole report in a fence therefore reads as missing, and a re-run clears it. Under Codex's verdict schema the field is coverage, and ops/REVIEW_CODEX.md now holds the JSON answer plus the folded line, not the session's text. Agent files already deployed in a project are not rewritten; the prompts carry the rule, so those lanes still report it.
Three limits stay. Coverage is self-reported: a lane can claim complete falsely, and only the quoting and contradiction paths are closed. at-build's team mode and team-lead run their own reviews, and no gate reads the Coverage line there. The re-review rule is prose, not machinery: a lead that proceeds on an earlier cycle's report is not caught by anything. The snapshot-bound receipt in 4.3 is what would close these. For the same reason, lease_merge's wording changed: the pin names the reviewer and records that the review step was reached, and does not prove the final snapshot was reviewed.
persona_verdict is enforced by at-plan's wait block, which exits on it. at-ship, at-coordinate and at-build tell the lead to read the verdict through it. findings-synthesizer treats the dispatched lane list as authoritative, and dispositions say Cycle N everywhere.
Codex workers
--disable plugins is proven for hooks and skills, not for plugin MCP servers, which were not tested. The flag needs no higher floor: it exists and works on 0.153.0. -c plugins._default.enabled=false does not stop a plugin, so it isn't used. Project hooks and skills provisioned under .agents/skills/ still load in a worker.
Which CLIs were tested
Claude Code 2.1.318, Codex 0.166.0, agy 1.5.1, Cursor 2026.11.05, Devin 3000.11.234 and Grok Build 1.0.70 ran on the probe host. OpenCode there is V2 (2.1.4), which the V1 lane refuses, so the new OpenCode deny-row control is proven by a stub only and has not run live on V1. Kimi 2.5.1 is installed but not tested, because the host's plan refuses Kimi Code. The agy builder lane did not run on 1.5.1, because a throwaway home cannot log in and the lane needs a user-tier rule. The last full run: 172 rows, 139 PASS, 9 FAIL. Eight of the FAILs record a capability the CLI does not have and Triforge does not rely on (AGY-06, AGY-07, AGY-09, AGY-10, CUR-06, CUR-07, CC-03, CC-05). The ninth, SELF-06f, asks a Haiku worker to list its skills, and judges the answer: it failed 2 of 6 runs, and both times the skill was in the listing the worker received and the worker left it out of its answer, so discovery worked and the row is noisy. Of the 2 AUTH-FAILs one is Kimi's plan, and Cursor's CUR-13 passed in the two earlier full runs and in 3 reruns alone, cause not established. AGY-12, AGY-13, AGY-16 and AGY-19 passed on the shipped pack.
Devin's read_config_from.cloud key stays unset. It is new in 3000.11.205 and on by default, so MCP servers and personal plugins from your Devin Cloud account can load into Triforge's Devin runs, reviews and analyses included. Whether the key holds in the --config copy the lane passes has not been proven, so it is not shipped. at-setup says so before you enroll Devin.
/goal is an assist: probe CC-03 went 0 of 3 on Claude Code 2.1.318, and ops/.sprint-complete plus coordinate.sh decide completion. Gemini 4 Argon answered READY on agy 1.5.1 and stays a one-line opt-in, because the vendor's models page does not offer it on Free or Google AI Plus. Gemini 3.1 Pro is Enterprise only on that page.
Smaller fixes
best-practices-researcherandframework-docs-researcherget 40 turns (was 12), which makes their default timeout 8000 s. A persona that spends its turn budget now returns rc 81 with a line naming the fix.- The key scrubber no longer redacts words like
Task-completion. - A blank line inside an
[R]task no longer ends the task in the review package. at-resolve-prpaginates every comment fetch. A live pull request with 42 review comments returned 30 without the flag.learnings-researcheris skipped when the project's solutions, decisions,MEMORYandCHANGELOGhold nothing beyond the skeleton.plan-checkerno longer offers an "approve with caveats" exit or a "costly" reversibility value.locate-triforge.sh --helpprints usage and exits 0 before resolving anything.validate-versions.shgains a local link and anchor check.- Kimi's install hints use the global
code.kimi.aiaddress.
Upgrading: refresh the marketplace, then update (claude plugin marketplace update agent-triforge, then claude plugin update agent-triforge; the Codex lines are in the README's FAQ), then check these.
- Run
claude --version. Below 2.1.314 a claude worker refuses to start, and the shipped builder is a claude worker. Session start printsworker-flooruntil you runclaude update.TRIFORGE_CLAUDE_SANDBOX=offstill runs the lane without the sandbox. - Under a Codex lead, approve the hooks again. Any change to the plugin's contents, this update included, asks for approval again, and until then a Codex lead runs without the plugin's hooks. Pick "Review hooks" when Codex asks, or open
/hooksin the session. - A user-tier
command(...)allow rule no longer reaches the read-only agy lanes: their session rules deny commands, and a deny wins. - If a script wraps
coordinate.sh, handle exit 75. - If a script reads
lease_merge's no-pin refusal orops/REVIEW_CODEX.md, re-check it. The refusal's wording changed, with the same exit code and state. - If your roster pins
cursor-grok-4.6-xhighit keeps working. Change it to move to Grok 4.7.
v4.2.0 ships with a regenerated capability probe record, kept in the maintainer's own records: 172 rows, 139 PASS, 9 FAIL, 2 AUTH-FAIL, 12 UNAVAILABLE, 2 SKIPPED, 1 PENDING-U15, 4 PENDING-AUTH, 3 INFO.
v4.1.1: The README in agent-blueprint's order, light diagrams, release notes per version
What's new
This release changes the documentation and the release tooling. Nothing changes in how you run Triforge.
- The README follows agent-blueprint's order. It starts with what Triforge is, then covers install for either lead, getting started, unattended runs, upgrading from 3.x and updating, and ends with the reference sections. It shows only the latest What's new; older releases are in the collapsed Release history.
- The diagrams are light. The 13 dark diagrams became 15 light ones that read on GitHub's light and dark themes, and on a phone a tap opens each one full size. Two are new: the install routes and the unattended-run loop. Checking every label against the code corrected a few stale facts: there are five quality gates, at-ship reviews in five lanes, the kill grace is 10 s, and test fix cycles stop at 3.
- Each release has its own notes file. A version's notes live in
docs/releases/v<version>.md, and the release workflow publishes that file as the GitHub release. - The maintainer gates are stricter.
validate-versions.shfails a version bump that has no notes file and rejects Liquid delimiters underdocs/, which break the GitHub Pages build. It also fails when a diagram source changed without a re-render, or when a diagram names a stale pin, a wrong count or a Codex invocation without itsagent-triforge:prefix.
Details
The README work kept every heading that other files cite, so their links still resolve, and the #whats-new and #recent-changes anchors still land where they did. The ten dark SVGs that nothing embedded any more are gone. The website keeps its own four SVGs until its redesign. The README also drops its comparison with Agent Blueprint, and the maintainer-only watch commands are no longer in the published repository.
The diagrams come from docs/images/render-diagrams.html, plus three Excalidraw sources in docs/images/excalidraw/ that the page embeds. node docs/images/render-diagrams.js renders each one at 2x and writes docs/images/render-diagrams.sha256. It refuses to render when Inter or JetBrains Mono did not load, so no PNG ships in a fallback font.
scripts/release-notes.sh takes the release title and body from the notes file and turns its ../../ links into links at the release tag. It exits 1 when the file is missing, has no v<version>: heading, or has nothing after the heading. In scripts/validate-versions.sh, check 1 now requires the notes file and every line of the README's What's new block in it. Check 12 covers Liquid delimiters, and check 13 the render stamp. The README checks skip only the ## Release history section, so FAQ, Contributing and License are checked again.
Upgrading: nothing to do beyond the usual update (claude plugin update agent-triforge, or the Codex lines under Update in the README).
v4.1.1 ships without a regenerated probe record, as v4.1.0 did: no adapter, model pin or runtime path changed. The newest record is still ops/research/2026-09-probe-record.md.
v4.1.0: The 4.0 review's residuals fixed: a small reader, bounded reads, process-group time bounds, watched promotion targets, CLAUDE_CONFIG_DIR
What's new
This release fixes what the 4.0 review left open, and what the review of those fixes found. Nothing changes in how you run Triforge.
- The import reader stays small. On a CLAUDE.md built to stress it, the check that asks whether a file imports
AGENTS.mdused up to 2.4 GB; it now stays under 100 MiB on every hostile 4 MiB shape in the tests, with the same answers (4.5 million generated documents compared against 4.0's reader and 6.6 million more after the review's fixes, 1.4 million token by token against marked). Setup and session start read each such file once. - No instruction file is read whole.
AGENTS.md,AGENTS.override.md, Codex'sconfig.tomland the pointer template are read to 4 MiB at most, and their sizes come from the file itself. ACLAUDE.mdpast a reading bound is named as such (past-boundin setup's file list, its own line at session start) instead of being offered an import that would then be refused. - A time bound stops everything a command started. When setup or session start runs a command under a time bound, nothing the command started outlives the call. At the bound its whole process group is stopped, and so is anything it left running when it returns early, with or without the
timeoutbinary. The watchdog's temp directory never stays behind. Session start's warning names the real reason a check did not finish. - Promotion targets are watched. A promotion into a branch other than the default one records that branch when you approve it, and a move of it stops the next lease call (rc 44); an approval refuses to drop or replace a watched branch that moved. A default branch that appears where none was on record (a
git checkout mainthat creates it, say) also stops the next lease call untillease_rebaselineaccepts it. A merge whose integration branch moved during it says so, and the recovery steps it prints keep your uncommitted edits (git reset --keepor--merge, never--hard). CLAUDE_CONFIG_DIRis honored. When you move Claude Code's config home withCLAUDE_CONFIG_DIR(an absolute path outside the project), setup reads your user-levelCLAUDE.mdfrom there, never writes it, keeps a claude worker on the same config home, and denies its.credentials.jsonto the read-only lanes. Unset,~/.claudeas before. Set to anything else (empty, relative or inside the project), setup lists thatCLAUDE.mdas a project file but never writes it or any file beside it.- The maintainer tools ignore planted modules, like the rest of the helpers since 4.0: the validators, the release notes and the probe harness.
- Five diagrams redrawn so no arrow crosses a label.
Details
The import reader (md_scan) reads its long-span patterns in bounded chunks, and no bounded repeat holds another: peak memory on a crafted 4 MiB CLAUDE.md drops from up to 2.4 GB to under 100 MiB, with 0 answer differences from 4.0's reader on 4,500,000 generated documents, 0 from the pre-review 4.1 reader on 6,640,000 more, and 0 token differences from marked on 1,400,000. instruction_files_detect and visibility read each CLAUDE.md-family file once. AGENTS.md, AGENTS.override.md, Codex's config.toml and the pointer template are read to 4 MiB + 1 byte at most, sized by fstat, and the budget sums true sizes; past that, visibility and the merge fail closed. A CLAUDE.md read only up to a bound gets - and the state past-bound in instruction_files_detect, and visibility names the bound instead of offering an import (SELF-16 b-cap-, b-cap-two, b-cap-sum-, b-mem-, b-rep-).
Both watchdogs, and both timeout paths, stop the command's process group at the bound and sweep what is left of it when the command returns first (SELF-08 forkwatch, forkexit, boundfork, boundexit); _instr_py owns the watchdog's temp dir (watchkill); session start's warning names its cause (detect69) and gives a past-bound file its own notice (pastbound). lease_approve records an explicit promotion target in the integrity baseline (rc 44 on a move) and refuses to clear or replace one that moved; a default branch that appears where the baseline recorded none is reported (rc 44); lease_merge holds its scope to the integration head it read, and its recovery steps say git reset --keep or --merge (SELF-14 target, targetsym, targetmoved, targetgone, targetswitch, defappear, defrace, trunkmain, racemergehead). CLAUDE_CONFIG_DIR is modeled where Triforge reads or protects Claude Code's user tier: an absolute value outside the project moves the user-level CLAUDE.md (detect and visibility), the writers refuse the CLAUDE.md in any set value (an empty or relative one taken from the working directory, in the project too), the claude lane's and the codex read persona's credential deny rules gain its .credentials.json (each path once, as given and with its symlinks resolved), the persona guard exempts that CLAUDE.md in place of ~/.claude's, and _adapter_env hands an absolute value to claude workers (SELF-08, SELF-12, SELF-16 b-ccd-*, SELF-20 ccd). The maintainer tools run their Python after the prelude (SELF-27 maint). Five diagrams no longer have arrows over text.
The probe record was not regenerated for this release (user decision); the newest is ops/research/2026-09-probe-record.md (2026-09-27).
v4.0.0: Lead choice (Claude Code or Codex), at- lead workflows, personas, AGENTS.md, Grok Build and Devin
What's new
Codex can lead a Triforge project now. You pick the lead in at-setup, which records it in a [lead] table in ops/roster.toml; a project without that table keeps Claude Code. The scripts, skills and templates read the lead from that table and the CLI registry; outside the registry and the roster loader, no script branches on the lead's name (KTD1). The work followed docs/plans/2026-09-28-1946-feat-lead-choice-v4-plan.md, Phases 0 to 6.
- Codex can lead.
triforge_bootstrapsets up a project from either lead, before you have trusted the plugin's hooks.scripts/coordinate.shruns the lead's launch line from the registry with the[lead]model and effort, and the context and tool-failure monitors read the lead's own tool names. In a Codex prompt the lead workflows are$agent-triforge:at-<name>. SELF-17 runs the same two-task fixture sprint under each lead, and the two ledgers differ only in the lead CLI and the reviewer class. - The slash commands are lead workflows. Each 3.x command is now an
at-skill (/at-shipunder Claude Code,$agent-triforge:at-shipin a Codex prompt), andcommands/ships empty. The 17 lead workflows share oneskills/tree with the 10 portable skills. A plugin-root resolver and a skill locator find the plugin from either lead, andscripts/validate-skills.shchecks every skill in strict mode. The command map lists each 3.x command and its 4.0 name. AGENTS.mdreplaces theCLAUDE.mdfiles. The plugin ships noCLAUDE.md, anddocs/rule-inventory.mdshows where each retired rule went. The Claude Code floor rises to 2.1.277, the first build that reads a rootAGENTS.md. Session start warns when Claude Code is older than that, or when a project still carries a 3.xCLAUDE.md.- One CLI registry.
scripts/lib/registry.shholds one entry per CLI with its tier, binary, model, install hint, environment allowlist, lane, egress and lead fields. Every other script reads it, andvalidate-versions.shfails when one keeps a copy. - The 19 agents are personas. Each is a prompt file in
personas/, andpersonas/manifest.tomlsets its tools, model tier and turn budget.dispatch_personaruns them the same way under either lead, andagents/ships empty. - Builders outlive the lead's turn. Lease builders run detached.
lease_waitwaits on them within a time budget, and on resumelease_heartbeat_checkcollects the builders that finished and adopts the ones still running. A worker marker keeps workers out of the lead-only helpers (rc 45). Theclaude -pworker lane runs Bash in Claude Code's sandbox and needs 2.1.285 or later. Each ledger row records the lead CLI, the reviewer class and the approvals given for it. - Grok Build and Devin join the optional tier. Grok Build builds, reviews or analyzes. Devin reviews and analyzes once you consent to Cognition seeing your code, and builds only after a separate opt-in.
at-reviewchecks the change's integrity first and then gives every lane the same review package. at-setupasks who leads. It also lists the instruction files in the project and the directories above it, and changes one only after you say yes. Before it writes the lead, it shows which providers will receive your code, and it lets you pick Devin's model.- Lead-side Python ignores the working directory. Inline Python in the helpers no longer imports modules from the directory it runs in. A builder could have planted a
json.pyin its worktree, and the lead's output parser would have run it outside the builder's sandbox. - Two lease-integrity fixes. A lease left open by 3.3.x now merges after the upgrade; 3.3.3 read it as a deleted baseline and escalated it on the first lease call. A ledger whose digest and saved copy were both deleted stops the next lease call with rc 44, so a worker can't delete them and then forge an approval into the ledger.
- Watch-cycle carry-ins. The watch cycle's research workers run through the persona lane in the
read-webclass. Antigravity routing defaults toauto(D-042), and theAGY_ERRORline agy prints on exit 3 now feeds the failure class (D-043). The Cursor probe rows compose Grok 4.7 model ids with the same helper dispatch uses.
Upgrading: read Upgrading from 3.x first. It maps each 3.x command to its workflow, lists what changes in a project, and explains how to go back to 3.3.3. v4.0.0 ships without a regenerated probe record. The newest one is still ops/research/2026-09-probe-record.md from 2026-09-27. The new rows for the persona lane, the Codex lead, Devin and Grok Build ran live during development with --only, and Compatibility lists them.
Details
Triforge's lead is now a choice between Claude Code and Codex, recorded in the [lead] table of ops/roster.toml; a roster without the table keeps Claude Code. Scripts branch on registry fields, never on a CLI's name (KTD1). SELF-17 runs one two-task fixture sprint under each lead, one task under scripts/lib/, and the two ledgers differ only in the lead CLI and the reviewer class (U20).
Phases 0 and 1 replaced the CLAUDE.md files with a root AGENTS.md and a rule inventory, docs/rule-inventory.md (U3, U4). They raised the Claude Code floor to 2.1.277, with session-start notices for an older build and for a stale 3.x CLAUDE.md (U22). They added a plugin-root resolver and skill locator (U5), one CLI registry in scripts/lib/registry.sh (U26) and a strict skill validator (U6), then turned the 17 commands into at- lead workflows beside the 10 portable skills; commands/ ships empty (U7, U23, U24).
Phase 2 added the lead capability and builder survival probe rows (U29), detached leases with lease_wait and a reconcile when the lead exits (U13), the worker marker (U11), the Devin and Pi skill manifests (U18), the [lead] table (U9) and the sandboxed claude -p lane (U12). Ledger rows record the lead CLI, the reviewer class and each approval (U10). Personas run through one lane, dispatch_persona, and the 19 personas live in personas/, so agents/ ships empty (U25, U8).
Phase 3 lets Codex lead. triforge_bootstrap sets up a project from either lead, scripts/coordinate.sh runs either lead's launch line, the monitors read the lead's own tool names, and Codex invokes the workflows as $agent-triforge:at-<name> (U14).
Phase 4 enrolled Grok Build and Devin as optional members, Devin only with recorded consent (U16, U17), and at-review now gives every lane one review package after an integrity check. Phase 5 rebuilt at-setup around the lead step, instruction-file detection with writers that ask first, the egress disclosure and Devin's model choice (U15, SELF-16).
Phase 6 stopped inline Python from importing modules out of the working directory, where a builder could plant one (S1, SELF-27). The watch cycle's research workers run through the persona lane (U19, SELF-28). agy routing defaults to auto, its AGY_ERROR line sets a failed run's reason and whether it is retried (D-042, D-043), and the Cursor probe rows compose Grok 4.7 ids (SELF-29). A ledger whose digest and copy were both deleted now stops the next lease call with rc 44 instead of being adopted as found (S2, SELF-29).
The probe record was not regenerated for this release. The newest is ops/research/2026-09-probe-record.md (2026-09-27); DVN-01 to DVN-07, CC-21 to CC-25, CDX-20 to CDX-23, GRK-02 to GRK-12 and SELF-06g ran live during development under --only. README "Upgrading from 3.x" has the command map and the steps back to 3.3.3.
v3.3.3: Protected-path registry, digest-stamped skills, lead git integrity
What's new
A safety hotfix, shipped before the lead-choice redesign (plan docs/plans/2026-09-28-1946-feat-lead-choice-v4-plan.md, Phase H):
- Promotion catches every control-plane change.
lease_promote's protected list missedscripts/lib/,scripts/lease-git-hooks/andscripts/probe-self-tests.sh, saw only the new name of a renamed file, and read a scan error as "nothing protected". The lists now live inscripts/lib/registry.sh: a framework list that applies only in the Triforge checkout, and a project list that applies everywhere (ops/roster.toml,.claude/,.codex/,.agents/and the other CLI config trees, and everyAGENTS.md,AGENTS.override.md,CLAUDE.md,CLAUDE.local.mdand.mcp.jsonat any depth). The scan is case-folded, sees both sides of a rename, and blocks promotion (rc 42) when it can't run. - Your own skills survive the refresh. Session start used to delete any
.agents/skills/directory whose name matched a shipped skill. The stamp now records a content digest for each directory Triforge wrote, and a directory is replaced or retired only while it still matches. An edited copy, or your own directory under a shipped name, is kept with a notice. A 3.3.0–3.3.2 stamp is migrated against the digests of every released copy (scripts/lib/skill-digests.txt). Lease worktrees follow the same rule and no longer write through a committed.agents/skillssymlink. - A builder can't steer the lead's git. A worktree limits where a builder starts, not where it writes. Every git call in the lease lifecycle now goes through
_lead_git: system config off, a trusted global config captured on first use, and hooks, fsmonitor, the global attributes file, signing and pager turned off. Before the lease functions act, they compare.git/config,.git/config.worktree,.git/hooks/,.git/info/, the global git config, the ledger, the default branch and each lease's worktree pointer with the lead's recorded baseline. A change the lead didn't make is restored where possible, the open leases are escalated, and the call returns 44.lease_rebaselineaccepts a change you made yourself. This is detection, not prevention. - Only the lead's snapshot merges.
lease_collectnow takes the snapshot (one lead-made commit on the recorded base) that the review and the merge bind to.lease_mergesquashes that SHA and refuses, by name, a builder's own commit, an edit toops/, or a worktree changed after collect. It also refuses when the integration branch moved since the lead's last merge. - The SELF rows are a gate.
bash scripts/probe-capabilities.sh --self-onlyruns only the static SELF rows, writes its record to a scratch path, and exits 3 on any SELF FAIL..github/workflows/gates.ymlruns it with both validators on macOS for every PR tomainandrelease/4.0. New rows: SELF-10 (protected paths) and SELF-18 (lead git integrity); SELF-08b covers the digest refresh.
Upgrading: open 3.3.x leases keep working. A lease collected by 3.3.2 has no collect snapshot, so it is snapshotted at merge under the new checks; review its worktree as it stands at merge time. Behavior changes to know about:
lease_merge's commit no longer runs repository hooks and is never GPG-signed (_lead_gitturns hooks, fsmonitor and signing off), so a branch-protection rule that requires signed commits needs the squash signed by hand.- If you commit on the integration branch between merges, switch the lead's checkout to another branch, or promote by hand after a blocked
lease_promote, the next lease call refuses with rc 44 until you runlease_rebaseline. A.git/configor hook change you made yourself (git remote add,pre-commit install) is also reverted at the next lease call and saved as<lease root>/lead/<name>.changed-<UTC time>: copy it back, then runlease_rebaseline. Each acceptance is recorded inops/leases.toml[baseline]. - More promotions are gated in user projects.
lease_promotenow blocks (rc 42, lead or user cross-review required) on any change under.claude/,.codex/,.agents/,.antigravity/,.gemini/,.opencode/,.kimi-code/or.cursor/, on the rootopencode.json,opencode.jsonc,.cursorrulesand.gitmodules, onops/roster.toml, and on anyAGENTS.md,AGENTS.override.md,CLAUDE.md,CLAUDE.local.mdor.mcp.jsonat any depth — paths 3.3.2 promoted without a gate. A submodule entry recorded at one of those paths is listed even when.gitmodulessaysignore = all. - Deleting an integrity anchor is a change, not a reset: a missing ledger digest, a ledger whose
[baseline]table is gone, or a deletedops/leases.tomlwhile<lease root>/lead/still holds the lead's copies refuses with rc 44 and names the recovery (lease_rebaseline, or removing the lead state dir when you removed the ledger on purpose).
Details
A safety hotfix ahead of the lead-choice redesign (Phase H of docs/plans/2026-09-28-1946-feat-lead-choice-v4-plan.md). lease_promote classifies the integration diff against two lists in scripts/lib/registry.sh: framework paths in the Triforge checkout only, and project paths everywhere. The scan is case-folded, includes instruction files at any depth and both sides of a rename, and fails closed with rc 42 (U1, SELF-10). The .agents/skills/ refresh and lease provisioning replace only directories whose content digest matches the stamp, and a 3.3.0–3.3.2 stamp is migrated against the released digests (U2, SELF-08b). Every lease git call goes through _lead_git. A planted config, hook, filter, ref, pointer redirect or ledger edit is restored where possible and escalated with rc 44 (lease_rebaseline accepts your own change). lease_merge squashes only the snapshot lease_collect recorded, refusing builder commits, ops/ edits and post-collect changes (U27, SELF-18). probe-capabilities.sh --self-only is the SELF gate (exit 3 on a SELF FAIL), and .github/workflows/gates.yml runs it with both validators on macOS for PRs to main and release/4.0 (U28). lease_merge's commit no longer runs repository hooks. Ladder hash unchanged (24a7ee2039c8c2e4907472e4cf82fdfd); the probe record is not regenerated for this hotfix, and the SELF gate passes (17 SELF rows, none FAIL).
v3.3.2: Release gate green, OpenCode V2 guard, Cursor Grok 4.7 ids
What's new
A hotfix from the 2026-09-27 watch cycle (D-039, D-049, D-050, S28), shipped before the lead-choice redesign:
- The release gate is green again. Claude Code 2.1.281 started warning on an unquoted
${CLAUDE_PLUGIN_ROOT}in shell-form hooks, and--strictturns warnings into errors. All four commands inhooks/hooks.jsonnow readbash "${CLAUDE_PLUGIN_ROOT}/hooks/handlers/<name>.sh". Probe CC-06 reported PASS while the gate was red, becausevalidate --strict <repo>checks only the marketplace manifest. It now validatesplugin.jsonandmarketplace.jsonseparately and passes only when both do. - OpenCode V2 is refused. V2 (npm
@opencode/cli, sameopencodebinary) does not readOPENCODE_PERMISSION, so Triforge's deny set would be silently dropped.invoke_opencodeand the leaseopencode)arm refuse whenopencode --versionis 2.x or later, as a deterministic failure that names the V1 pin (npm i -g opencode-ai@1). The check fails closed: an OpenCode whose version can't be read is refused the same way. Every role's fallback chain skips a refused OpenCode and moves to the next CLI, and/setupreports it as unsupported instead of offering enrollment. Porting to V2 comes later. - Cursor model ids follow the Grok family. Grok 4.7 ids carry no
cursor-prefix (grok-4.7-xhigh), while 4.6 ids do (cursor-grok-4.6-xhigh). Every composer used to emitcursor-<family>-<effort>. All four composers (dispatch,resolve_role, the/setuprole table, androster_write_role) now take the prefix from one shared family table. The shipped pin stayscursor-grok-4.6-xhigh. /setuphas no positional tokens. Claude Code substitutes$0,$1, … in a command body before the model reads it, code blocks included./setup's ROLES helper now takes the CLI name as a named variable.
Details
A hotfix from the 2026-09-27 watch cycle. The four hook commands quote "${CLAUDE_PLUGIN_ROOT}", so claude plugin validate --strict passes on both manifests on Claude Code 2.1.283. Probe CC-06 validates plugin.json and marketplace.json separately (D-039). invoke_opencode, the lease opencode) arm, and /setup refuse OpenCode V2, which ignores the deny set, or an OpenCode whose version can't be read, and name the V1 pin npm i -g opencode-ai@1; resolve_role skips a refused OpenCode so the fallback chain moves on (D-049). The Cursor id composers take the cursor- prefix from the Grok family through one shared helper: grok-4.7 → grok-4.7-xhigh, while grok-4.6 still gives cursor-grok-4.6-xhigh, which stays the shipped pin (D-050). /setup's ROLES helper drops a positional $1 that Claude Code would have substituted (S28).
v3.3.1: Mechanical no-push backstop, quota failure class, split control-plane files
What's new
Follow-ups from the v3.3.0 code review (PR #8 "Unapplied review findings"), shipped 2026-09-12:
- A lease builder cannot push. The dispatch contract's "git stays local" is now mechanical:
_adapter_envbindscore.hooksPathto the shippedscripts/lease-git-hooks/pre-push(which refuses) and rewrites every push URL scheme to an unresolvableno-push://address viaurl.*.pushInsteadOf— per-processGIT_CONFIG_*, sogit pushfails whichever remote or path the builder names while status/log/diff keep working. New static row SELF-09 proves it against a bare remote. - Quota exhaustion fails fast. A provider usage-limit / quota error (Kimi's
403 … monthly usage limit, or any lane's equivalent) is classified deterministic with "wait for the refresh or buy usage" guidance instead of a wasted retry; the probe record has aQUOTA-FAILoutcome and gates the dependent Kimi rows on the quota, not on a login. - Effort-only roster overrides work.
[roles.analyst] effort = "low"on the shipped agy default now dispatchesGemini 3.8 Flash (Low)(andcursor-grok-4.6-lowfor Cursor):resolve_rolerecomposes the effort suffix when the role's model is the shipped default; an explicit roster model still wins. - Smaller control-plane files.
scripts/invoke-external.shis a loader overscripts/lib/{common,antigravity,codex,opencode,kimi,cursor,roster,lease}.sh(4.0 addsregistry,lease-wait,devinandgrok; same 68 functions, same contracts — commands keep sourcing the one file;validate-versions.shparseslib/roster.sh), and the probe harness's SELF-* rows live inscripts/probe-self-tests.sh. - Smaller fixes. The OpenCode deny set enumerates the common bypass spellings (
rm -fr/-Rf/-r,command sudo,doas,git -c/-C … push) in both copies (still defense-in-depth);invoke_antigravityleaves its diagnostic in the output file when the retry also returns an empty envelope; session-start writes.claude/roster-detected.local.mdtmp+mv.
Details
The v3.3.0 review residuals, applied: a lease builder's git push is refused by shipped git config (pre-push hook + no-push:// URL rewrite; SELF-09); provider quota exhaustion is a deterministic failure class with a QUOTA-FAIL probe outcome; effort-only roster overrides recompose the agy/Cursor effort suffix; scripts/invoke-external.sh became a loader over scripts/lib/*.sh and the probe harness's SELF-* rows moved to scripts/probe-self-tests.sh (behavior unchanged); the OpenCode deny set enumerates the common bypass spellings.
v3.3.0: September 2026 models, trustworthy completion signals, native agent formats
What's new
Install route fixed. The plugin now installs on current Claude Code: the repository ships a single-plugin marketplace manifest (.claude-plugin/marketplace.json) so claude plugin marketplace add https://github.com/Ninety2UA/agent-triforge + claude plugin install agent-triforge@agent-triforge works (the old claude plugin add <url> form is no longer a Claude Code command), and plugin.json no longer names hooks/hooks.json — Claude Code auto-loads that path and rejected the duplicate, which made the installed plugin fail to load. Verified from an installed project: all twelve skills discoverable headless, /<skill> expands, the session-start hook fires.
Every lane runs on the September 2026 generation, and every headless run reports a completion signal the lead can trust. The shipped pins moved in lockstep — Claude Fable 5.1 → Opus 5 → Sonnet 5 ladder, Codex gpt-6-astra at xhigh, Antigravity Gemini 3.8 Flash (High) (the new policy: the newest Gemini at its highest thinking level, Pro or Flash; Gemini 3.1 Pro (High) stays the one-line opt-in), OpenCode openrouter/z-ai/glm-5.3, Kimi kimi-code/k3, Cursor cursor-grok-4.6-xhigh (effort now rides in Cursor's model-id suffix, like agy's (High)). invoke_antigravity reads agy's JSON envelope (status, response, denied_actions) instead of the exit code, and every lease carries one dispatch contract with a typed Status: report that lease_collect parses — a clean exit without one is "report missing", never review-ready. The Antigravity agent pack ships in agy's Markdown-agent format (native --agent behind TRIFORGE_AGY_MODE; injection remains the shipped mode), Kimi roles load natively through --agent-file, the Codex declarations deploy as .codex/triforge-agents.toml, and .agents/skills/ refreshes on every plugin version change so all six CLIs see the current skills in their own invocation form (/name, $name, the skill tool, /skill:name). Two new release gates — scripts/validate-skills.sh and scripts/validate-versions.sh — and the Tier-1 process patterns from the September repo mining (a counterfactual bar for compounding, Accept:/Fails when: task fields, rulings-not-stalls, review dispositions, evidence-gated verification) round it out. Details in Recent changes; floors and tested versions in Compatibility.
Also recent (history)
v2.4.3 sequential downgrade ladder; v2.4.0–v2.4.2 framework self-audits; v2.2.0 Opus-max-effort + reliability patterns; v2.0.0 plugin conversion — full detail in Recent changes.
Details
Install route fixed. The plugin installs on current Claude Code again: the repository ships a single-plugin marketplace manifest (.claude-plugin/marketplace.json) so claude plugin marketplace add https://github.com/Ninety2UA/agent-triforge + claude plugin install agent-triforge@agent-triforge works (the old claude plugin add <url> form is no longer a Claude Code command), and plugin.json no longer names hooks/hooks.json — Claude Code auto-loads that path and rejected the duplicate, which made the installed plugin fail to load. Verified from an installed project: all twelve skills discoverable headless, /<skill> expands, the session-start hook fires.
Model pins (D-020..D-025, user-directed). Ladder fable+max → opus (Opus 5) + xhigh → opus+high → sonnet (Sonnet 5) + high (the fable/opus aliases resolve to Fable 5.1 / Opus 5 from Claude Code 2.1.257 / 2.1.219; floor raised to ≥ 2.1.267 so effort: frontmatter is honored). Codex gpt-6-astra at xhigh on all three roles, with model_reasoning_effort now replayed on every codex exec (roster effort reaches Codex). Antigravity Gemini 3.8 Flash (High) under the new policy "newest Gemini at its highest thinking level, Pro or Flash" — Gemini 3.1 Pro (High) is the documented opt-in and the roster writer normalizes effort into the (Low)/(Medium)/(High) suffix (3.1 Pro has no Medium). OpenCode openrouter/z-ai/glm-5.3; Kimi kimi-code/k3 (the OAuth-managed alias — the old kimi-k3 fails on OAuth hosts); Cursor cursor-grok-4.6-xhigh, effort composed into the model-id suffix from a bare grok-4.6 (the documented bracket form is rejected headless), binary cursor-agent first with an agent fallback accepted only when its --version matches Cursor's build-id format.
Completion signals (D-032, KTD2/KTD11). invoke_antigravity runs --output-format json: an empty response with denied_actions is a deterministic failure naming the user-tier permissions.allow rule (research lanes need read_url(*) since agy 1.1.28), sidecars .status/.denied/.mode ride beside the output file, and the six commands promote only non-empty SUCCESS output. Every lease carries one CLI-neutral dispatch contract (no sub-dispatch, git stays local, typed final report); lease_collect routes DONE/DONE_WITH_CONCERNS to review (copying "Discoveries for later tasks" into ops/MEMORY.md), BLOCKED/NEEDS_CONTEXT to escalation, and a clean exit with no Status: line to "report missing" (rc 80, stays building). Foreground helpers scrub the lead's harness markers and set NO_COLOR=1.
Native agent formats (D-024, D-026, D-027). The four antigravity-agents/agents/*.md use agy's Markdown-agent frontmatter (mainAgent, subagent: false, commandExecutionPolicy, model: inherit) and agy tool names; TRIFORGE_AGY_MODE=injection|native|auto governs native --agent selection (default injection until AGY-12 and the new AGY-16 negative pass for a full cycle); deny intent is written in agy action syntax (command(rm -rf) …); session start reinstalls the pack on version change. Kimi builder/reviewer are --agent-file definitions extending Kimi's prompt (${base_prompt}, ${skills}, ${agents_md}, subagents: [], a read-only tools allowlist on the reviewer), --skills-dir is gone (Kimi discovers .agents/skills/ natively), and the project config.toml is documented as not read. Codex declarations deploy as .codex/triforge-agents.toml (Codex ≥ 0.147 sweeps .codex/agents/*.toml as role files), include_plan_tool (never a Codex key) is gone, --full-auto is documented as removed, and /setup detects — never writes — the user-tier project trust entry.
Skills everywhere (D-029). .agents/skills/ is refreshed under a version stamp (.agents/skills/.triforge-plugin-version; shipped-name directories are Triforge-owned, customizations go in differently named directories; names and symlinks validated before any replace). The six-harness discovery matrix — which path each CLI reads and its invocation form — is documented in .claude/CLAUDE.md "Portable skills"; agent definitions are never deployed into .agents/agents/.
Probe harness and record (D-028). Date-stamped record path/title, AGY-08 rewritten to agy's documented .agents/hooks.json named-hook shape (hooks fired on agy 1.2.0 — the July finding was a probe-shape error — but FAIL again on 1.2.1, the version the shipped record was captured on: an open watch, not a settled pass), Astra rows, cursor-grok-4.6-xhigh, kimi-code/k3, OC-06 with OPENCODE_PERMISSION, and new rows for headless skill expansion per CLI (AGY-14, CDX-09, OC-07, CUR-09), the agy denied-actions envelope (AGY-15), the native-mode negative (AGY-16), Codex project trust (CDX-10/11), lifecycle and idempotence self-checks (SELF-05..08). Fresh record: ops/research/2026-09-probe-record.md; latest_probe_record in scripts/invoke-external.sh resolves "the current record" everywhere.
Release gates and process patterns. scripts/validate-skills.sh (portable frontmatter, sequential steps, ## Output) and scripts/validate-versions.sh (manifest/README lockstep, ladder md5 across the four files, DEFAULTS drift between the duplicated constant blocks, the scoped stale-pin sweep, surface counts) join claude plugin validate --strict .; .github/PULL_REQUEST_TEMPLATE.md lands. Skills gained portable metadata: frontmatter and "Use when" triggers, an evidence-gated verification rewrite with Excuse | Reality tables, writing-good-tests.md, the counterfactual compounding bar, rulings-not-stalls with the four hard stops, Accept:/Fails when:/Precondition:/Reversibility: task fields, verification_baseline in STATE.md, review dispositions; commands and agents gained ceremony classification, reviewer trust rules, the gated learnings-researcher, outbound-endpoint hygiene, Codex spawn hygiene; all four hook handlers declare ON_CRASH: ALLOW and an exit-code note. settings.json sets CLAUDE_CODE_ENABLE_TODO_TOOLS=1. Compatibility floors: Claude Code ≥ 2.1.267, agy ≥ 1.1.27, Codex ≥ 0.153.0, OpenCode ≥ 1.18.20, Kimi ≥ 0.33.0, Cursor date-versioned.
Deferred (recorded, not dropped). Kimi live rows stay PENDING-AUTH until kimi login; the TRIFORGE_AGY_MODE default flip; the mechanical no-push backstop in lease worktrees; docs/images/roster.svg regeneration (its labels still read gpt-5.6-sol / "Antigravity · Pro"); OC-06 (deny vs --auto) as an open watch; the Tier-2 repo-mining items.
v3.2.0: Watch commands become repo-local maintainer tooling
What's new
The plugin got leaner: the watch cycle is now repo-local maintainer tooling. /cli-watch and /repo-watch — the monthly cycles that keep Triforge itself current against its six CLIs and four reference repos — never belonged in the shipped command set. They now live in this checkout's .claude/commands/, with the watch-cycle skill in .claude/skills/ and the watch registry tracked at ops/watch-registry.toml. At this release the plugin shipped 17 commands and 12 skills (previously 19 and 13), and session-start.sh no longer copies a watch registry into your ops/. Run the watches from a clone of this repo (see Keeping the framework current); nothing changes for /setup, /ship, or any other command.
Details
/cli-watch + /repo-watch moved out of the plugin. Both commands, the watch-cycle skill, and the watch registry are framework self-maintenance, not features for your projects. They now live in this checkout only: .claude/commands/ (Claude Code discovers them when run from the repo root), .claude/skills/watch-cycle/, and the tracked registry ops/watch-registry.toml. The commands read only that registry — the fallback through plugin templates is gone.
Plugin surface. 17 shipped commands (was 19) and 12 portable skills (was 13). session-start.sh no longer bootstraps ops/watch-registry.toml into user projects and no longer lists the two commands in its orientation line. If you were running /cli-watch from an installed plugin, run it from a clone of this repo instead.
Docs. README, .claude/CLAUDE.md, templates/CLAUDE.md, docs/agent-triforge.md, and the landing page updated to the new counts and paths; README's watch section renamed "Keeping the framework current".
v3.1.0: Role customization in guided onboarding
What's new
The roster is now yours to shape from onboarding. /setup gained a role-assignment step: see the current role table (role → CLI · model · effort · fallbacks), then keep it, customize any role, or restore the shipped defaults — every write validated so the roster always still loads. Backed by new single-writer helpers (roster_role_entry, roster_write_role), roster model overrides that now reach every external-CLI dispatch lane (CODEX_MODEL joined its siblings), agy effort→(High)/(Low) suffix normalization, and a content-level roster guard that makes a broken ops/roster.toml loud instead of quietly rendering clean-looking tables. Hardened across three multi-agent review iterations (local roster + independent cross-model adversarial passes) — details in Recent changes.
Also new at the time (history — superseded by v3.3.0's D-022 policy, which pins the newest Gemini at its highest thinking level): the 2026-07-21 Gemini 3.6 Flash release was recorded in the model watch; the v3.1.0 pins stayed on Gemini 3.1 Pro, with 3.6 Flash as the explicit per-role opt-in.
Details
/setup now covers who does what. After the core-trio gate and optional-member enrollment, a new role step shows the current assignment table (role → CLI · model · effort · fallbacks) and asks one question: proceed with the shipped defaults (recommended) or customize. Customizing walks CLI, model, and effort per chosen role; /setup roles jumps straight to that step on re-runs. The closing status table now derives each CLI's roles from the live roster instead of hardcoding the shipped posture.
New single-writer helpers. roster_role_entry <role> prints a role's merged configuration (shipped defaults overlaid per-field by ops/roster.toml, no liveness walk; the model column shows what dispatch would actually run, even for a hand-edited cli-only override); roster_write_role <role> <cli> <model> <effort> [fallbacks-csv] is the validated single writer for [roles.*] — it enforces a strict superset of resolve_role's load rules (known role/CLI and core-trio chain terminus, plus writer-only checks: the effort enum and agy effort→(High)/(Low) suffix normalization), derives a valid fallback chain when none is given (the displaced primary becomes the first fallback), and round-trip-verifies before an atomic replace, preserving comments elsewhere in the file.
Roster model overrides now reach every external-CLI dispatch lane. invoke_codex gained a CODEX_MODEL override (same pattern as AGY_MODEL/OPENCODE_MODEL/KIMI_MODEL/CURSOR_MODEL), and dispatch_role's codex lane passes the resolved roster model through it — a customized reviewer/tester model actually reaches codex exec -m instead of silently deferring to the agents.toml pin. The shipped default matches the pin, so behavior is unchanged until a user customizes. (The claude lane stays ladder-governed by design: work resolved to claude runs as a native Agent-tool subagent, whose model comes from the Fable/downgrade ladder, not the roster.)
Review-hardened across three multi-agent iterations (full local roster + independent cross-model adversarial pass per round, every fix re-verified by execution in the next round): the roster guard runs resolve_role's full load validation, so TOML-valid-but-content-invalid rosters (typo'd CLI, chain with no core-trio terminus, non-table shapes) are LOUD instead of rendering clean-looking tables; a missing TOML parser (rc 3) is distinguished from roster errors with its own fix; the closing verdict warns when any role chain contains an auth-failed member (dispatch never skips them — as a primary it breaks the role outright, as a fallback it blocks recovery exactly when the primary degrades); the role ask is truthful on re-runs (keep-current / customize / restore-shipped-defaults); agy writes normalize the effort→(High)/(Low) suffix and auto-fill an empty model with the effort-matched Pro pin. Deliberately-accepted residuals are recorded in docs/residual-review-findings/.
Model watch — Gemini 3.6 Flash (released 2026-07-21). Recorded in ops/research/2026-07-22-gemini-3.6-flash.md and verified against the live agy models catalog: no new Pro shipped, so the Gemini 3.1 Pro (High)/(Low) pins remain the latest Pro and all defaults hold; "Gemini 3.6 Flash (High|Medium|Low)" is now the named per-role Flash opt-in example. Next Pro release (Gemini 4 teased) moves the pins under the latest-Pro policy.
v3.0.1: Landing-page copy + release hygiene
Messaging fixes. The "Three AI models" tagline (page title, hero, footer, README, plugin manifest) now reads "a core trio of AI coding CLIs" — the old count clashed with the six-CLI builder pool introduced in v3.0.0. The Antigravity model wording on the landing page softened from "never Flash" to "Gemini 3.1 Pro by default, Flash opt-in": the shipped defaults still pin Pro on every agy path, but a [roles.*] model entry in ops/roster.toml (or AGY_MODEL) can select a Flash variant when a user explicitly wants one.
Rolls up post-v3.0.0 fixes. v3.0.0 review hardening (#2) and the 2026-07-20 capability-record refresh (#3), which landed after the v3.0.0 tag.
v3.0.0: CLI modernization + six-CLI builder pool
What's new
The biggest release since the plugin conversion. Triforge moved from a fixed Claude + Gemini + Codex trio to a six-CLI builder pool coordinated by a roster, per-task leases, and mandatory cross-review — and swapped the retired Gemini CLI lane for Antigravity. The coordination model and the CLI surface both changed, so this was a major version.
Headline changes
- Builder pool (default). All six supported CLIs — the core trio (Claude, Antigravity, Codex) plus enrolled optional members (OpenCode, Kimi, Cursor) — are eligible builders.
ops/roster.tomlassigns each role; every build runs under a per-task lease in an isolated worktree and merges only after cross-review by a pinned non-author reviewer. The single-writer rule is retired. - Gemini → Antigravity. Google shut down the hosted Gemini CLI service for consumer tiers on 2026-06-18; the analyst / reviewer / documenter lane is now Antigravity (
agy) — running Gemini 3.1 Pro (High) at the time (history: v3.3.0 moved the shipped pin to Gemini 3.8 Flash (High), D-022), invoked viainvoke_antigravity. - Guided onboarding (
/setup). One idempotent command takes a fresh install to a working roster: gate the core trio, enroll or decline each optional CLI, then accept the shipped role defaults or customize who does what (CLI · model · effort per role). - Self-maintenance (
/cli-watch,/repo-watch). Scheduled or manual watch cycles keep the framework current against primary sources. - Native-first runtime. Completion gating moved from the retired
ship-loop.shpromise gate to Claude Code's native/goal+ anops/.sprint-completesentinel; Codex rangpt-5.6-sol(history:gpt-6-astraatxhighsince v3.3.0) with structured--output-schemaverdicts and hooks undercodex exec.
Migrating from v2.4.x
- (a) Builder-pool default. External CLIs are now eligible builders, not review-only. To restore the old reviewer-only posture, edit
ops/roster.tomland take the external CLIs off thebuilderrole (leave them onreviewer/tester/analyst/documenter, or set[members.<cli>] enabled = false). The core trio can't be disabled, and every fallback chain must still terminate at a core member. - (b) Gemini → Antigravity. The Gemini lane is gone. Former Gemini-API-key users: install Antigravity and run
/setupto authenticate theagylane (the oldGEMINI_API_KEYis no longer used). Users who must stay on legacy Gemini: pin pluginv2.4.3— the last release with the Gemini lane. - (c) Raised floors (KTD-13). New enforcement floors at v3.0.0 (history — v3.3.0 re-baselined them; the current matrix is in Compatibility): Claude Code ≥ 2.1.212, Codex ≥ 0.144.0, Antigravity
agy≥ 1.1.3; optional tier OpenCode ≥ 1.18, Kimi Code ≥ 0.15 (re-baselined to the tested version — the plan's aspirational ≥ 0.27 predated the probe host, and Kimi's near-daily cadence makes the tested floor the honest one), Cursor (date-versioned). Full matrix with tested-against versions + READY probes in Compatibility. - (d) Retired mechanisms.
ship-loop.shpromise gate (<promise>DONE</promise>) →/goal+ops/.sprint-completesentinel; single-writer rule → lease + cross-review. The historical changelog entries below keep the retired names on purpose — they record what past releases shipped, and are not rewritten.
Details
The largest release since the plugin conversion — the coordination model and the CLI surface both changed.
Six-CLI builder pool. The fixed single-writer trio is retired. All six supported CLIs — core trio (Claude, Antigravity, Codex) plus enrolled optional members (OpenCode, Kimi, Cursor) — are eligible builders assigned from ops/roster.toml (resolve_role). Every implementation task runs under a per-task lease (ops/leases.toml + isolated worktree) and merges only after cross-review by a pinned non-author reviewer; approved work lands one squash commit per task on a sprint integration branch, and the lead promotes to main honoring a [promotion] gate. Safety is leases + worktree isolation + cross-review, not write-restriction.
Gemini → Antigravity. Google shut down the hosted Gemini CLI service for consumer tiers (2026-06-18). The analyst / reviewer / documenter lane migrated to Antigravity (agy) running Gemini 3.1 Pro (High): invoke_gemini → invoke_antigravity, gemini-agents/ → antigravity-agents/ (a valid agy plugin), ops/REVIEW_GEMINI.md → ops/REVIEW_ANTIGRAVITY.md. Legacy Gemini users pin v2.4.3.
Native-first runtime. Completion gating moved from the retired ship-loop.sh Stop hook and its <promise>DONE</promise> convention to Claude Code's native /goal gate + an ops/.sprint-complete sentinel (probe CC-03). Wave orchestration delegates 5+-task waves to dynamic workflows. Claude runs a Fable-5-topped downgrade ladder (fable/max → opus/xhigh → opus/high → sonnet/high) with a spawn-time Fable override for the lead and the never-downgrade trio.
Codex modernization. gpt-5.6-sol at xhigh (was gpt-5.4), structured --output-schema review verdicts, and hooks under codex exec (D-004 reversed — probe CDX-04; see ops/decisions/2026-07-18-codex-hooks-under-exec.md).
Onboarding + self-maintenance. New /setup guides a fresh install to a live roster via first-detection enrollment. New /cli-watch + /repo-watch run watch cycles over ops/watch-registry.toml using the watch-cycle skill (since relocated to repo-local maintainer tooling — see Keeping the framework current).
Raised floors (KTD-13). Claude Code ≥ 2.1.212, Codex ≥ 0.144.0, Antigravity agy ≥ 1.1.3; optional OpenCode ≥ 1.18, Kimi Code ≥ 0.15, Cursor (date-versioned). See Compatibility.
Docs. Full refresh across README, .claude/CLAUDE.md, templates/CLAUDE.md, docs/agent-triforge.md, docs/index.html; single-writer statements reframed to the lease/cross-review contract; drift fixes (Codex --full-auto is deprecated-not-removed; max effort is not Opus-only). Known cosmetic gap: docs/images/*.svg still render "Gemini" labels (image assets, deferred).
v2.4.3: Sequential downgrade ladder for narrow runtime tasks
Historical note (2026-07): entries below predate the Gemini → Antigravity migration and intentionally keep retired names (
Gemini CLI,invoke_gemini,gemini-agents/,ops/REVIEW_GEMINI.md,ops/RESEARCH_GEMINI.md) as an accurate record of what those releases shipped. The live lane is Antigravity (agy,invoke_antigravity,antigravity-agents/,ops/REVIEW_ANTIGRAVITY.md,ops/RESEARCH_ANTIGRAVITY.md); legacy Gemini users pin plugin v2.4.3. Some file links in these historical entries point at paths that have since moved or been removed by v3.0.0 (CLAUDE.md→.claude/CLAUDE.md;gemini-agents/→antigravity-agents/;hooks/handlers/ship-loop.shretired) — they document where the file lived at that release, not the current tree.
Tiered downgrade ladder for the team-lead and lead agent. The previous single-step downgrade (opus/max → sonnet/high) was a big leap that skipped past two cheaper same-family reductions. New ladder lets the orchestrator step down one notch at a time: opus/max → opus/xhigh → opus/high → sonnet/high. Locked agents (security-sentinel, plan-checker, findings-synthesizer) remain hardwired to opus/max. Documentation-only change — runtime behavior is unchanged; the team-lead's allowed move set widens. Also fixed a doc-drift bug in CLAUDE.md: the documented effort values were missing xhigh (a valid Claude Code value per the v2.2.0 audit at README.md:654).
Files changed (6 files)
| File | Change |
|---|---|
CLAUDE.md |
Architecture line references new ladder; effort field values corrected to include xhigh |
templates/CLAUDE.md |
Architecture line references new ladder |
agents/team-lead.md |
"Model routing discretion" block rewritten as a 3-tier ladder table |
skills/wave-orchestration/SKILL.md |
Mirrors the team-lead.md rewrite |
README.md |
v2.2.0 changelog reference refreshed; this entry; "What's new" + version refs bumped |
.claude-plugin/plugin.json |
Bumped to 2.4.3 |
docs/index.html |
Hero badge + terminal version bumped to v2.4.3 |
v2.4.2: Third audit pass — both prior passes missed siblings of the same anti-pattern
A third adversarial audit on top of v2.4.0 and v2.4.1 — three parallel Explore agents covered shell safety + hooks + scripts, agent definitions + security model, and docs + commands + skills + templates. Surfaced 1 MEDIUM + 3 doc-drift fixes plus one bonus bug uncovered during verification. The audit agents collectively reported four BLOCKER/HIGH findings; every one was verified false against the actual code. The framework is converging.
[MEDIUM] Two surviving || echo "0" siblings in session-start.sh:138, 174 — v2.4.0 fixed three grep -c || echo "0" instances; v2.4.1 fixed a fourth grep -c instance both the team-lead pass and its consolidation step missed; this pass found that both prior audits only ever checked grep -c and never the find … | wc -l siblings. SOLUTION_COUNT (line 138) and GEMINI_AGENT_COUNT (line 174) both used the same broken || echo "0" tail. Triggered whenever ops/solutions/ or .gemini/agents/ is missing (e.g., user manually deletes the directory after bootstrap, or bootstrap is skipped because ops/ already exists) — wc -l already emits 0, then echo "0" appends another 0, and the downstream [ "$VAR" -gt "0" ] comparison errors with integer expression expected under set -euo pipefail. The handler dies before printing the orientation banner. Fix: || echo "0" → || true on both lines.
[LOW, bonus] Codex agent count off-by-3 in session-start.sh:193 — Caught when the verification reproducer for the previous fix ran in an empty test project and the orientation banner reported "External agent definitions loaded: 4 Gemini + 6 Codex" when there are only 3 Codex agents. The Python fast path did len(data.get('agents', {})), but the [agents] table in codex-agents/agents.toml holds three scalar runtime caps (max_depth, max_threads, job_max_runtime_seconds) alongside the three agent subtables (logic_reviewer, test_writer, debugger) — so len() returned 6. The fallback grep -c '^\[agents\.' would have been correct (it matches only the dotted subtable headers), but the Python path wins whenever tomllib is available. Fix: filter to dict values only — sum(1 for v in data.get('agents', {}).values() if isinstance(v, dict)) — mirroring the _list_codex_agents helper which already had the correct pattern. Re-verified: "4 Gemini + 3 Codex".
[LOW] CLAUDE.md Context-management section omitted 2 of 5 hook handlers — The Hook-safety subsection said "All 5 hook handlers use set -euo pipefail" but the Context-management subsection only named 3 (ship-loop.sh, coordinate.sh, context-monitor.sh). The two missing handlers were pre-compact.sh (auto-snapshots ops/STATE.md before context compaction, both already wired in hooks/hooks.json) and tool-failure-monitor.sh (tracks consecutive + total tool failures, warns at 5 consecutive / 10 total per session). Fix: both handlers now listed with one-line descriptions of what they do and when they fire.
[LOW] codex-agents/agents.toml top-comment explained max_threads only — Sets three caps (max_depth = 2, max_threads = 4, job_max_runtime_seconds = 1800) but the inline comment only described the max_depth fallback issue and max_threads's fan-out role. max_depth = 2's actual semantics — raising the cap by exactly one level so a single spawn round is allowed but recursive spawn-of-spawn is blocked (runaway protection) — was invisible to anyone reading the TOML in isolation. Fix: comment now explains both max_depth and max_threads properly.
[NIT] CLAUDE.md agent-frontmatter-fields list was Claude-only without saying so — Section listed name/description/model/effort/tools/maxTurns/skills/mcpServers/hooks/memory/background/isolation/color/permissionMode — all Claude conventions. Gemini definitions use max_turns/timeout_mins plus lowercase tool names; Codex uses model_reasoning_effort/sandbox_mode/approval_policy/include_plan_tool. Fix: one trailing sentence cross-references both CLIs' conventions so readers know where to look.
False alarms verified and rejected — For the record so they're not re-raised next pass: (1) "BLOCKER path injection in ship-loop.sh:131" — SHIP_STATE_FILE is the hardcoded constant .claude/ship-loop.local.md declared at line 22, never user-controlled; (2) "BLOCKER JSON/command injection in coordinate.sh:69,72 notify()" — only ever called from lines 122 and 134 with hardcoded title="Agent Triforge" and integer-templated bodies ("converged in $ITERATION iterations"); no user-controlled text flows through; (3) "HIGH approval_policy injection in invoke-external.sh:157-161" — values come from project-controlled agents.toml, parsed via tomllib, serialized via json.dumps; the auditor walked the claim back mid-analysis; (4) "HIGH NEXT_ITERATION arithmetic needs validation" — ITERATION is already validated as ^[0-9]+$ at ship-loop.sh:60 before the $((ITERATION + 1)) at line 124. Severity inflation is a real failure mode; manual cross-check against the actual code is non-negotiable.
Verified — grep -rn '|| echo "0"' hooks/ scripts/ returns only the cautionary comment at pre-compact.sh:18 (which references the anti-pattern by name); bash -n hooks/handlers/session-start.sh clean; reproducer in /tmp/empty/ops/ (where ops/solutions/ and .gemini/agents/ are missing) ran cleanly under the fixed handler with no integer expression expected errors and correct "4 Gemini + 3 Codex" count.
Files changed (6 files)
| File | Change |
|---|---|
hooks/handlers/session-start.sh |
Two || echo "0" → || true fixes (lines 138, 174); Codex agent count filtered to dict values only (line 193) |
CLAUDE.md |
Context-management section now names all 5 hook handlers; frontmatter-fields section cross-references Gemini/Codex conventions |
codex-agents/agents.toml |
Top-comment extended to explain max_depth = 2 recursion cap |
.claude-plugin/plugin.json |
Bumped to 2.4.2 |
README.md |
This changelog entry; What's new bumped to v2.4.2 |
docs/index.html |
Hero badge + terminal version bumped to v2.4.2 |
v2.4.1: Second audit pass — surviving blocker + 27 findings actioned
A second team-lead-orchestrated audit on top of v2.4.0 — 6 parallel specialist reviewers (hooks/scripts, commands, skills, agents, plugin manifest, docs/cross-refs) plus Team Lead consolidation with spot-checks — surfaced 28 findings: 1 BLOCKER, 1 HIGH, 13 MEDIUM, 13 LOW. The Team Lead's cross-check caught one reviewer mis-attributing a line number (proving the consolidation step earns its keep) and downgraded one reviewer-rated HIGH to MEDIUM pending independent verification.
[BLOCKER] Surviving grep -c || echo "0" in session-start.sh:194 — v2.4.0 fixed three earlier instances of the bug pattern (pre-compact.sh:19-22 + session-start.sh:121-123) but missed one. The Codex-agent-count fallback chain (python tomllib || grep -c '^\[agents\.' || echo "0") produced "0\n0" when both Python toml libs were unavailable and grep matched nothing. Line 195's [ "$CODEX_AGENT_COUNT" -gt "0" ] then errored with integer expression expected under set -euo pipefail — the handler died before printing the orientation banner. Fix: || echo "0" → || true.
[HIGH] Broken ADR reference in CLAUDE.md:269 — Cited ops/decisions/0001-cli-deprecation-watch.md; the actual file is 2026-05-12-cli-deprecation-watch.md. The link is the only documented justification for why Triforge does not auto-enable Codex hooks under codex exec. Fix: path updated to the ISO-date naming the repo already uses for its other ADR.
[MEDIUM] effort: max field verified, kept as-is — The audit raised a concern that Claude Code's agent-frontmatter schema might not recognize effort: and that the 19 agents' "Opus max effort" claim was unenforced. Spot-checked against the current Claude Code subagents spec: effort is documented (values low/medium/high/xhigh/max; overrides session effort while the subagent is active). All 19 declarations are honored. No change. [Corrected in v3.0.0: max is not Opus-only — it is documented on Fable 5, Sonnet 5, and Opus 4.8/4.7.]
[MEDIUM] All 16 commands now declare allowed-tools — Defense-in-depth narrowing; /quick was previously inheriting the full Claude Code tool surface despite being scoped to <3 file changes. Allowlists scoped per command's actual needs: /status, /resume, /pause, /compound are read-mostly; /build, /plan, /ship, /coordinate, /wrap, /quick, /debug, /resolve-pr get the full set; /analyze is read-only per its own constraint.
[MEDIUM] 7 inline bash blocks gained set -euo pipefail — commands/{build,plan,ship,coordinate,deep-research,review,test}.md each ran multi-line source ${CLAUDE_PLUGIN_ROOT}/scripts/invoke-external.sh + parallel invoke_gemini/invoke_codex blocks without the safety flags, so a source failure or non-zero wait could fall through silently.
[MEDIUM] 4 commands gained $ARGUMENTS boundary guards — commands/{plan,ship,coordinate,deep-research,debug,resolve-pr,compound,quick}.md (the ones that pipe user input directly into subagent prompts) now have an explicit > **Note:** block instructing downstream agents to treat the argument block as data, not as instructions overriding the command's own logic.
[MEDIUM] 3 commands gained "When to use" decision aids — commands/{build,plan,ship}.md were missing the comparison block that /quick already had. New users now have a decision tree for picking between /plan → /build (manual phasing) vs /ship (autonomous end-to-end) vs /coordinate (autonomous, simpler) vs /quick (lightweight).
[MEDIUM] All 19 agents now carry a color — Wave orchestration and team builds routinely fan out to 5+ agents at once; the parallel-swarm UI was monochrome. Categorized: planners/coordinators blue (plan-checker, architecture-strategist, team-lead); code reviewers green (code-simplicity-reviewer, convention-enforcer, continuous-reviewer); researchers + verifiers cyan (learnings-researcher, framework-docs-researcher, best-practices-researcher, git-history-analyzer, integration-verifier, test-gap-analyzer, deployment-verifier); synthesizers + resolver magenta (findings-synthesizer, research-synthesizer, pr-comment-resolver); security/perf/repro red/yellow (security-sentinel, performance-oracle, bug-reproduction-validator).
[MEDIUM] 2 skill descriptions rewritten for trigger precision — skills/scope-cutting and skills/verification-before-completion lacked the "Primary consumer" + concrete trigger scenarios that the other 10 skills had. Both now name the consumer and the specific situations that should fire the skill.
[MEDIUM] templates/CLAUDE.md self-contradiction repaired — Line 19 prose said the helper lived at scripts/invoke-external.sh; line 133 code block correctly used ${CLAUDE_PLUGIN_ROOT}/scripts/invoke-external.sh. New users copying the template were being told to look for a file that doesn't exist in their own project. Prose now uses the ${CLAUDE_PLUGIN_ROOT}/ prefix. Same edit added a "this is the simplified per-project copy" preamble pointing back to the plugin's canonical CLAUDE.md.
[MEDIUM] ops/research/ added to the shared-file protocol — Both CLAUDE.md's shared-file table and templates/ops/ skeleton now include research/; the v2.4.0 work introduced ops/research/cli-updates-2026-05.md but the protocol table was never updated.
[MEDIUM] .gitignore *.local.md anchored to .claude/*.local.md — Unanchored pattern was silently swallowing any .local.md at any depth, hiding user notes in places like docs/notes.local.md. Anchored; the two redundant explicit lines removed.
[MEDIUM] Root-level .gemini/settings.json artifact cleaned — Running session-start.sh inside the plugin repo itself created a byte-identical duplicate of templates/.gemini/settings.json at the plugin root. Deleted the artifact and added .gemini/ to .gitignore so future runs don't recreate it as an untracked file.
[LOW] Cleanup batch — tool-failure-monitor.sh unicode ⚠ → ASCII WARN: (non-UTF8 portability); continuous-reviewer description picked up the Use: keyword to match the other 18 agents; git-history-analyzer and bug-reproduction-validator gained WebFetch/WebSearch for upstream lookups (CVE feeds, issue trackers, vendor notes); codex-agents/agents.toml lost the undocumented nickname_candidates fields; status.md description rewritten in prescriptive style; README TOC gained a Recent changes link.
Deferred — One MEDIUM (<example> blocks in each agent's description: field, recommended by Anthropic's agent guidance for routing precision) deferred because it requires custom examples per agent's role; three LOW intentionally skipped (context-monitor.sh tool classifier drifts as Claude Code adds tools — kept conservative default; knowledge-compounding / test-driven-development skills don't need numbered "Step 1:" wrappers since they're already organized into cycles and templates; templates/.codex/ directory will remain config-only until Codex hooks become viable under codex exec).
Verified — Shell syntax (bash -n on all 5 hook handlers + 2 scripts), TOML (codex-agents/agents.toml still parses with all 3 agents + nesting caps intact), YAML (47 frontmatter files: 19 agents + 16 commands + 12 skills), git diff (45 files changed, +149/-20).
Files changed (45 files, +149/-20)
| File | Change |
|---|---|
hooks/handlers/session-start.sh |
|| true fix at line 194 (the v2.4.0 audit miss) |
hooks/handlers/tool-failure-monitor.sh |
Unicode ⚠ → ASCII WARN: for non-UTF8 portability |
CLAUDE.md |
ADR reference path fixed; research/ added to shared-file table |
README.md |
Recent changes TOC link added; this changelog; What's new bumped to v2.4.1 |
.gitignore |
*.local.md anchored to .claude/*.local.md; .gemini/ added |
templates/CLAUDE.md |
${CLAUDE_PLUGIN_ROOT}/ prefix on helper reference; "simplified copy" preamble |
templates/ops/research/.gitkeep |
New — skeleton for ops/research/ |
codex-agents/agents.toml |
nickname_candidates fields removed (undocumented in Codex 0.130.0 schema) |
commands/{build,plan,ship,coordinate,deep-research,review,test}.md |
set -euo pipefail prepended to inline bash blocks |
commands/*.md (all 16) |
allowed-tools declared in frontmatter, scoped per command |
commands/{build,plan,ship}.md |
"When to use" decision-aid section added |
commands/{plan,ship,coordinate,deep-research,debug,resolve-pr,compound,quick}.md |
$ARGUMENTS boundary guard added |
commands/status.md |
Description rewritten in prescriptive style |
agents/*.md (all 19) |
color: declared in frontmatter |
agents/continuous-reviewer.md |
Use: keyword added to description for consistency |
agents/{git-history-analyzer,bug-reproduction-validator}.md |
WebFetch + WebSearch added to tools: |
skills/scope-cutting/SKILL.md |
Description rewritten with Primary consumer: + trigger scenarios |
skills/verification-before-completion/SKILL.md |
Description rewritten with Primary consumer: + trigger scenarios |
.claude-plugin/plugin.json |
Bumped to 2.4.1 |
docs/index.html |
Hero badge + terminal version bumped to v2.4.1 |
v2.4.0: Framework self-audit — two blockers + four HIGH fixes
A team-lead-orchestrated audit of the entire framework (hooks, commands, skills, agents, scripts, manifests, docs) surfaced 18 findings — including 2 blockers that silently defeated core reliability invariants. Every finding was either fixed or verified as a false alarm against upstream Gemini/Codex/Claude Code specs. No functionality was added; existing behavior was corrected to match what the docs already promised.
[BLOCKER] Ship-loop guard never fired — commands/ship.md and commands/coordinate.md wrote session_id: "<current-branch-name>" into the loop state file, but ship-loop.sh compared that against Claude Code's runtime session UUID from hook-input JSON. Branch name ≠ UUID → the handler took the "different session — don't interfere" exit on every call. The inner loop guard (max iterations, <promise>DONE</promise> check, prompt re-feed) never actually blocked anything during autonomous /ship or /coordinate runs. Fix: removed session_id logic entirely — presence of the state file with active: true now indicates the loop. Cleaned the state template in both commands.
[BLOCKER] PostToolUseFailure hook event doesn't exist — hooks/hooks.json registered tool-failure-monitor.sh under an invented event name (PostToolUseFailure) that Claude Code's hook loader silently ignores. The advertised "warn at 5 consecutive or 10 total failures" feature was dead code; .claude/tool-failures.local.md was never written. Fix: merged the handler into the existing PostToolUse hook, with in-handler filtering on tool_response.is_error / tool_response.error so only actual failures increment the counters. Smoke-tested with both success and failure payloads.
[HIGH] Gemini -y (YOLO) flag silently nullified policies.toml — scripts/invoke-external.sh passed -y on every gemini -p invocation. The plugin's own gemini-agents/policies.toml explicitly warns at the top of the file that YOLO installs a max-priority allow rule that overrides every deny — including the rm -rf / git push / sudo denylists documented in the "Security model" section of CLAUDE.md. Fix: -y is now gated on an explicit GEMINI_YOLO=1 env var (off by default). Subagent isolation comes from the per-agent tools allowlist (always respected by Gemini) plus the restored policy rules.
[HIGH] team-lead bypassed invoke-external.sh — agents/team-lead.md shelled out to gemini -p "$(cat SKILL.md) ..." / codex exec "$(cat SKILL.md) ..." directly, skipping policy loading, timeout enforcement, retry, and native-agent routing. Team-mode builds silently lost every piece of reliability infrastructure the rest of the framework relies on. Fix: migrated to invoke_gemini / invoke_codex with per-PID exit-code capture and fail-fast, matching the pattern in /review and /build.
[HIGH] Template and README docs drifted to legacy invocation — templates/CLAUDE.md (the file session-start.sh offers new users as a starting template) and README.md both presented gemini -p "$(cat ...)" as the current invocation pattern, not the invoke_gemini helper. New adopters were being handed outdated guidance at first touch. Fix: both now document the helper as the primary path; legacy injection is labeled as the automatic fallback the helper applies when a CLI lacks native-agent support.
[MEDIUM] grep -c … || echo "0" produced "0\n0" on zero matches — grep -c already prints 0 to stdout on zero matches and then exits with status 1; pairing it with || echo "0" ran the fallback and appended a second 0, yielding a multiline "0\n0" value that garbled session-start's task banner and pre-compact's STATE.md checkpoint. The CLAUDE.md "Hook safety" section even documented this broken pattern as the correct one. Fix: switched to || true in session-start.sh and pre-compact.sh; rewrote the CLAUDE.md guidance to explain why.
[MEDIUM] pre-compact.sh CURRENT_PHASE default clobbered by empty sed output — sed -n with no match exits 0, so || echo "unknown" never fired when ops/STATE.md existed but lacked a ## Current phase: line. The heredoc then wrote a blank phase into STATE.md, breaking /resume heuristics. Fix: captures sed output into a temporary variable and falls back explicitly when empty.
[MEDIUM] Session-start commands banner stale + sed injection risk in state writes + silent timeout fallback — the banner listed 13 of 16 commands (/analyze, /coordinate, /resolve-pr missing); ship-loop.sh and tool-failure-monitor.sh injected user-influenced values into sed patterns without escaping; _run_with_timeout silently ran Gemini/Codex with no timeout when neither timeout nor gtimeout was on PATH. Fix: banner now lists all 16 commands; state-file writes switched from sed to python3; _run_with_timeout emits a one-shot stderr warning the first time it falls through.
[LOW] Shared-file table + coordinate.sh preflight + plugin.json cleanup — targeted-researcher writes to ops/RESEARCH_GEMINI.md but the shared-file table never mentioned it (added to both CLAUDE.md and templates/CLAUDE.md). scripts/coordinate.sh invoked claude --print via || true, silently producing empty output if the CLI wasn't on PATH (now fails fast with an install hint). .claude-plugin/plugin.json declared agents/skills/commands path fields that are redundant under the current Claude Code plugin spec (auto-discovery handles them) — removed for clarity.
False alarms verified upstream, no change needed — the Gemini CLI subagents spec explicitly uses snake_case max_turns / timeout_mins in its frontmatter schema, so gemini-agents/*.md is already correct. The Codex agent-roles source (codex-rs/core/src/config/agent_roles.rs) shows nickname_candidates is a real, documented field — not dead config. Audit flagged both for verification; both were clean.
Files changed (16 files)
| File | Change |
|---|---|
hooks/hooks.json |
Removed non-existent PostToolUseFailure event; tool-failure-monitor merged into PostToolUse |
hooks/handlers/tool-failure-monitor.sh |
Filters on tool_response.is_error / tool_response.error; resets consecutive counter on success; python3 state writes |
hooks/handlers/ship-loop.sh |
Removed broken session_id comparison; switched state update from sed to python3 |
hooks/handlers/session-start.sh |
grep -c … || true fix; commands banner now lists all 16 |
hooks/handlers/pre-compact.sh |
grep -c … || true fix; CURRENT_PHASE fallback no longer clobbered by empty sed output |
commands/ship.md |
Removed session_id from state template |
commands/coordinate.md |
Removed session_id from state template |
scripts/invoke-external.sh |
-y gated on GEMINI_YOLO=1 env var; one-shot stderr warning when timeout fallback engages |
scripts/coordinate.sh |
Preflight check for claude CLI availability |
agents/team-lead.md |
Migrated from legacy gemini -p/codex exec to invoke_gemini/invoke_codex |
templates/CLAUDE.md |
Invocation section rewritten around the helper; RESEARCH_GEMINI.md added to shared-file table |
templates/ops/AGENTS.md |
Documents invoke_gemini / invoke_codex for new projects |
CLAUDE.md |
Fixed grep -c guidance; RESEARCH_GEMINI.md added to shared-file table |
README.md |
Invocation docs updated to helper-primary; this changelog |
.claude-plugin/plugin.json |
Removed redundant agents/skills/commands path fields; bumped to 2.4.0 |
docs/index.html |
Hero badge + terminal version bumped to v2.4.0 |
v2.3.0: Subagent hardening pass (Gemini + Codex docs alignment)
Re-analyzed the Gemini CLI subagents spec and Codex CLI subagents spec against our current implementation and closed the concrete gaps. Focus: security hardening, invocation-layer parity, and observability — no new features, just making the existing integrations robust against real failure modes.
Codex per-agent tools allowlist — logic_reviewer now has tools = ["read", "grep", "glob"] (no shell, no write); test_writer and debugger get ["read", "grep", "glob", "write", "bash"]. Defense-in-depth pairs with the existing per-agent sandbox_mode — sandbox isolates the filesystem, the allowlist narrows the tool surface.
Codex nesting caps — New top-level [agents] block in codex-agents/agents.toml: max_depth = 2, max_threads = 4, job_max_runtime_seconds = 1800. logic_reviewer and test_writer explicitly spawn sub-agents for 5+ file scopes — without explicit caps, nesting depth would default to 1 and silently block those spawn paths.
Codex retry parity — invoke_codex now retries once with a raw prompt on failure, mirroring invoke_gemini. Prior asymmetry meant a transient Codex failure killed the pipeline while Gemini kept going.
Parallel-wait exit-code capture — /review and /build now capture per-PID exit codes (GEMINI_RC, CODEX_RC) and fail-fast when either reviewer died. Previously a silent failure would leave ops/REVIEW_GEMINI.md or ops/REVIEW_CODEX.md empty — which the findings-synthesizer treated as "no findings."
Loud failure paths — _extract_codex_agent_config now exits 1 and emits a clear stderr warning instead of silently returning an empty config when Python's TOML parser isn't available. Both invoke_gemini and invoke_codex now log a warning listing available agents when the requested agent name doesn't resolve (instead of silently falling through to raw mode / session defaults).
Collision-safe tmp paths — All output paths switched from /tmp/gemini_review.txt to ${TMPDIR:-/tmp}/gemini_review_$$_$(date +%s).txt. Running /review in two tabs no longer clobbers cross-session output. Applied across all seven commands, scripts/invoke-external.sh defaults, CLAUDE.md, templates/CLAUDE.md, and docs/agent-triforge.md examples.
include_plan_tool = false — All three Codex agents disable the Agents SDK plan tool. logic_reviewer / test_writer / debugger are focused executors; the plan tool adds cognitive overhead without value for single-purpose roles. [Corrected in v3.3.0: include_plan_tool was never a Codex key. The per-agent tools list and the [agents] caps are Triforge-internal declarations carried in the developer instructions (Codex never reads the file; sandbox_mode + approval_policy are the enforced isolation); Codex's own switch is tools.update_plan.enabled (default false since 0.152.0), and the file is deployed as .codex/triforge-agents.toml.]
macOS coreutils hint — session-start.sh emits a one-time warning when neither timeout nor gtimeout is on PATH; CLAUDE.md prerequisites now recommend brew install coreutils for macOS. Without one of those, invoke-external.sh silently ran without timeout enforcement.
Security model documentation — New "Security model" section in CLAUDE.md explains the approval_policy = "never" trust model, the defense-in-depth between sandbox_mode and the tools allowlist, and how gemini-agents/policies.toml layers shell-command denylists on top.
Files changed (13 files)
| File | Change |
|---|---|
codex-agents/agents.toml |
[agents] top-level block (max_depth, max_threads, job_max_runtime_seconds) + per-agent tools allowlist + include_plan_tool = false [corrected in v3.3.0 — Triforge-internal declarations; see the note above] |
scripts/invoke-external.sh |
Codex retry, loud TOML parser failure, TMPDIR-scoped default output paths, _list_gemini_agents + _list_codex_agents helpers, unknown-agent warnings |
commands/review.md |
Per-PID wait with fail-fast + TMPDIR paths |
commands/build.md |
Per-PID wait with fail-fast + TMPDIR paths |
commands/plan.md |
TMPDIR paths |
commands/test.md |
TMPDIR paths |
commands/deep-research.md |
TMPDIR paths |
commands/ship.md |
TMPDIR paths |
commands/coordinate.md |
TMPDIR paths |
hooks/handlers/session-start.sh |
One-time gtimeout / timeout missing warning |
CLAUDE.md |
Security model section + brew install coreutils hint + updated parallel-invocation example |
templates/CLAUDE.md |
TMPDIR paths in the legacy gemini -p / codex exec example |
docs/agent-triforge.md |
TMPDIR paths across all illustrative examples |
.claude-plugin/plugin.json |
Version bump 2.2.0 → 2.3.0 |
v2.2.0: Opus max effort, reliability patterns, and framework audit
All agents upgraded to Opus max effort — All 19 agents now run at model: opus + effort: max for maximum reasoning quality. The team-lead and lead agent have runtime discretion to downgrade narrow, rubric-following tasks via model + effort override when spawning. (See v2.4.3 for the tiered ladder: opus/xhigh → opus/high → sonnet/high.)
Forced reflection on retry — Before any retry, agents must answer: "What specifically failed? What concrete change will fix it? Am I repeating the same broken approach?" Applied to the ship-loop Stop hook (systemMessage) and wave-orchestration skill. Prevents agents from looping on the same broken approach.
Same-error kill criteria — Error fingerprinting tracks recurring failures per executor. If the same error appears 3+ times across retries, the executor is killed and the task reassigned to a fresh agent with anti-pattern context ("Previous executor failed on X — do NOT repeat the same approach"). Added to wave-orchestration skill and team-lead agent.
Dedicated continuous-reviewer teammate — New continuous-reviewer agent (19th agent, Opus max, read-only) auto-reviews every completed task during team builds for test/lint/security compliance. Spawned by team-lead at 1:3-4 ratio with builders. The lead only sees green-reviewed code — like a permanent CI gate built into the team.
Per-task reflection (conditional) — After task completion, if the task took >3 retries, produced test failures, or modified >5 files, a structured reflection is appended to ops/MEMORY.md (surprise, pattern proposal, improvement suggestion). Captures non-obvious learnings while they're fresh.
Provenance-enhanced institutional memory — Solution and decision documents now include sprint_id, task_id, agent, evidence_files, and related_decisions fields. The learnings-researcher agent now filters by tags, status, and follows cross-references between related documents.
Full framework audit (5th pass) — 5 parallel reviewer agents + team lead consolidation reviewed all 52 framework components (5 hooks, 16 commands, 12 skills, 19 agents). Found and fixed:
| Category | Issues found | Key fixes |
|---|---|---|
| Hooks | 1 critical, 3 warnings | Promise tag false-positive in ship-loop.sh, set -euo pipefail added to all handlers, atomic state writes |
| Commands | 1 error, 2 warnings | Phase 1.1 missing from /coordinate, reviewer list cross-references, /quick cross-ref in /review |
| Skills | Clean (0 issues) | — |
| Agents | Clean (0 issues) | — |
| Infrastructure | 7 errors | Agent count 18→19 in 6 files + SVG, GEMINI.md/CODEX.md phantom file references clarified, plugin.json arrays added |
Files changed
| File | Change |
|---|---|
agents/*.md (all 19) |
model: opus + effort: max |
agents/team-lead.md |
Model routing discretion, continuous-reviewer spawning, reflection+kill criteria in failure protocol |
agents/continuous-reviewer.md |
New — dedicated per-task reviewer for team builds |
agents/learnings-researcher.md |
Enhanced search: tags, status, cross-references |
skills/wave-orchestration/SKILL.md |
Reflection on retry, same-error kill criteria, per-task reflection, model routing discretion |
skills/knowledge-compounding/SKILL.md |
Provenance fields in solution + decision templates |
hooks/handlers/ship-loop.sh |
Promise tag false-positive fix, reflection in systemMessage |
hooks/handlers/context-monitor.sh |
set -euo pipefail, #!/usr/bin/env bash, atomic state writes |
hooks/handlers/session-start.sh |
set -euo pipefail, #!/usr/bin/env bash, quoted variable, pipeline fallback |
hooks/handlers/tool-failure-monitor.sh |
set -euo pipefail, #!/usr/bin/env bash |
hooks/handlers/pre-compact.sh |
set -euo pipefail, #!/usr/bin/env bash |
.claude-plugin/plugin.json |
Agent count 19, version 2.2.0, agents/skills/commands path declarations |
commands/coordinate.md |
Phase 1.1 added |
commands/ship.md |
Phase ordering fixed (1.1 before 1.5) |
commands/review.md |
/quick cross-reference note |
docs/agent-triforge.md |
Agent count 19, GEMINI.md/CODEX.md reference fix, hook count fix |
docs/index.html |
Agent count 19 (3 locations) |
docs/images/hero-banner.svg |
Agent count 19 |
CLAUDE.md |
Agent count 19, Opus max effort, phase numbering (1a/1b/1.1) |
templates/CLAUDE.md |
Agent count 19, Opus max effort, phase numbering, scope-cutting reference |
README.md |
Agent count 19 (3 locations), audit heading clarified, this changelog |
Unversioned: Reliability hardening and tactical enhancements
Informed by a deep comparative analysis against the official Codex plugin and oh-my-claudecode, this update addresses operational reliability gaps and adds targeted capabilities identified through triaged review (P1/P2 findings only, no architectural changes).
Subprocess timeouts — All 7 wait $PID calls for Gemini/Codex now use a watchdog pattern: a background timer sends SIGTERM after timeout (600s for review/build/plan, 900s for TDD test writing), with SIGKILL fallback after 5s grace. Normal runs are completely undisturbed — the watchdog is silently killed when the process finishes. Previously, a hung codex exec or gemini -p call would block the entire pipeline indefinitely.
Circuit breaker for debugging — The systematic-debugging skill now enforces a 3-attempt ceiling per issue. If the same failing test or error recurs after 3 consecutive fix attempts, the agent stops and produces a structured escalation report (error signature, attempts summary, assumption ledger, hypothesis, suggested next step) instead of looping. The ship-loop has a session-level ceiling, but this catches per-issue repetition within a session.
Git trailer conventions — /wrap and the CLAUDE.md template now define structured commit trailers (Constraint, Rejected, Confidence, Scope-risk, Not-tested) to embed decision context directly in git history. Previously, decision rationale lived only in ops/decisions/ files, separated from the code.
Pre-plan ambiguity resolution (Phase 1.1) — /plan and /ship now surface the 3 most critical unverified assumptions about the goal before building, asking the user to confirm or correct. Skipped for unambiguous goals. Previously, ambiguous specs were caught by the plan-checker after a full planning pass — this catches them earlier.
PreCompact hook — New pre-compact.sh handler auto-checkpoints ops/STATE.md with task status counts and current phase before Claude Code compacts the context window. Previously, if compaction hit mid-sprint without a /wrap, the outer loop could restart from stale state.
PostToolUse failure tracker — New tool-failure-monitor.sh handler tracks tool failures in .claude/tool-failures.local.md and warns at 5 consecutive or 10 total failures per session. Registered under PostToolUse and filters on tool_response.is_error. Previously, tool failures during autonomous runs were silent.
Completion notifications — scripts/coordinate.sh now sends macOS (osascript), Linux (notify-send), or webhook notifications on sprint completion and max-iteration exit. Gated on NOTIFY_WEBHOOK_URL env var for webhook delivery. Previously, long autonomous runs required manual polling.
Files changed (14 files, 250 insertions)
| File | Change |
|---|---|
commands/review.md |
Watchdog timeout (600s) on Gemini + Codex wait |
commands/test.md |
Watchdog timeout (900s) on Codex wait |
commands/build.md |
Watchdog timeout (600s) on Gemini + Codex wait |
commands/coordinate.md |
Watchdog timeout (600s) on Gemini wait |
commands/ship.md |
Watchdog timeout (600s) on Gemini wait + Phase 1.1 ambiguity check |
commands/plan.md |
Watchdog timeout (600s) on Gemini wait + Phase 1.1 ambiguity check |
commands/deep-research.md |
Watchdog timeout (600s) on Gemini wait |
commands/wrap.md |
Step 7: git trailer conventions |
skills/systematic-debugging/SKILL.md |
Circuit breaker (3-attempt ceiling) |
templates/CLAUDE.md |
Git trailer conventions section |
hooks/hooks.json |
PostToolUse failure-tracker + PreCompact hook entries |
hooks/handlers/tool-failure-monitor.sh |
New — failure tracking handler |
hooks/handlers/pre-compact.sh |
New — STATE.md auto-checkpoint handler |
scripts/coordinate.sh |
notify() function + completion/failure notification calls |
v2.0.0: Claude Code plugin conversion
The framework was converted from a git clone + manual copy installation to a Claude Code plugin. This is a breaking change in how you install and update the framework.
- Install:
claude plugin marketplace add https://github.com/Ninety2UA/agent-triforge, thenclaude plugin install agent-triforge@agent-triforge(the pre-3.3.0claude plugin add <url>form is not a Claude Code command any more — plugins install from a marketplace, and the repository now ships its own single-plugin marketplace manifest) - Update:
claude plugin update agent-triforge@agent-triforge - All components moved to root level (
agents/,skills/,commands/,hooks/) - Plugin manifest at
.claude-plugin/plugin.json(v2.0.0) - Hook registration via
hooks/hooks.jsonwith${CLAUDE_PLUGIN_ROOT}paths - Root
settings.jsonfor env vars (CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS) - Session-start hook bootstraps
ops/directory + copies skeleton templates on first run - CLAUDE.md template provided at
templates/CLAUDE.mdfor user projects - All skill injection paths use
${CLAUDE_PLUGIN_ROOT}/skills/(8 files updated) docs/agent-triforge.mdupdated: plugin layout, hooks.json config, removed stale settings block- Solution docs updated for plugin hook system
Unversioned: Four-pass audit and Blueprint alignment
Four full audit passes (20 parallel agents + manual 11-point verification) reviewed all 49 framework components (5 hooks, 16 commands, 12 skills, 19 agents). Each pass caught issues the previous missed — converging to zero remaining defects.
| Pass | Found | Fixed |
|---|---|---|
| 1st (5 agents) | 4 critical, 10 high, ~20 medium | Hooks stdin parsing, README config, coordinate.md guard, all high+medium |
| 2nd (5 agents) | 1 critical, 8 high, 4 medium | JSON injection, PID captures, path consistency, reviewer coverage |
| 3rd (5 agents) | 3 critical, 7 high, 6 medium | mkdir .claude in hooks, stale docs, deep-research PID, ops refs |
| 4th (5 agents + manual) | 1 high | ship-loop.sh mkdir guard (last hook missing it) |
Critical fixes
- Hooks were completely non-functional — both
ship-loop.shandcontext-monitor.shread environment variables ($CLAUDE_STOP_ASSISTANT_MESSAGE,$CLAUDE_TOOL_NAME) that Claude Code does not set. Hook data arrives via stdin JSON. Both hooks now parse stdin correctly. This means completion detection and analysis paralysis detection were silently broken since the framework's creation. - README shipped broken hook config — the Getting Started section used the flat
{ "command", "timeout" }format that Claude Code rejects. Migrated to the correct{ "matcher", "hooks": [{ "type": "command", "command" }] }format. /coordinateran full sprints unguarded — the command executed Phase 0–6 without activating the ship-loop Stop hook, so context exhaustion could silently kill mid-sprint. Now creates the state file and emits<promise>DONE</promise>on completion.- Ship-loop JSON injection (found in second pass) — the heredoc embedded raw
$PROMPT_TEXTinto JSON output. Any goal containing double quotes or newlines produced malformed JSON, silently breaking the Stop hook. Now usespython3 json.dumps()for safe encoding.
Ship-loop rewrite (Blueprint alignment)
The Stop hook was rewritten to match the Claude Code Blueprint's architecture:
| Before | After |
|---|---|
| Plain text output | JSON {decision, reason, systemMessage} |
| No session isolation | Session-isolated via session_id matching |
| Basic stdin grep for promise | Transcript-based JSONL promise detection |
sed -i.bak state updates |
Atomic temp file + mv |
| No input validation | Integer validation + set -euo pipefail |
| Simple 2-field state file | Rich frontmatter: active, session_id, iteration, max_iterations, completion_promise + prompt body |
| Raw shell variable in JSON | python3 json.dumps() for safe JSON encoding |
High-severity fixes
session-start.shnow cleans stalecontext-monitor.local.mdon startup (was documented but never implemented)build.mdreplaced hardcodedsrc/auth/paths with<scope>placeholders in agent team examplesdeep-research.mdGemini invocation now injectscodebase-mappingskill (was the only Gemini call without it)review.mdaddedwait $GEMINI_PID $CODEX_PIDbefore synthesis (was racing on incomplete review files)test.mdCodex invocation now uses proper> /tmp/... 2>&1 &redirect + PID capture patternsecurity-sentinel.mdremoved unnecessary Bash tool (least-privilege for static analysis)team-lead.mdadded structured output format (was the only agent without one)wave-orchestrationskill flagged team mode as Claude-specific + experimentalreview.md --fullnow includesarchitecture-strategist(was designed for Phase 3 but never wired in)build.mdagent team bash block now captures PIDs and waits (was fire-and-forget)ship.md,plan.md,coordinate.mdPhase 0 Gemini invocations now captureGEMINI_PIDandwait(were backgrounded with no wait)coordinate.mdPhase 3 now includes all 5 review agents (was missingconvention-enforcerandarchitecture-strategist)pr-comment-resolver.mdnow checksgh auth statusbefore fetching PR comments (was a hard failure ifghCLI unavailable)- All 3 hook scripts now have
mkdir -p .claudeguards for project-local state files (.claude/dir no longer exists after plugin migration) deep-research.mdaddedGEMINI_PIDcapture +waitfor background Gemini processdocs/agent-triforge.mdrewrote repo tree + settings section for plugin layout (was still showing old structure)ops/solutions/settings-jsonupdated for pluginhooks/hooks.jsonapproach- Stale comment cleanup in hook script headers (referenced old
.claude/settings.json)
Medium-severity fixes
- Fixed missing
ops/path prefixes acrossship.md,plan.md,quick.md,coordinate.md plan-checker.mdnow accepts "Claude subagent" as a valid assignment categorygit-history-analyzer.mdreplaced interactivegit bisectwith non-interactivegit log -Salternativedeployment-verifier.mdadded explicit "never execute rollbacks" safety rulesession-start.shreplacedecho -ewith POSIX-portableprintf '%b\n'- Fixed bare
ops/path prefixes indebug.md,review.md,coordinate.md status.mdnow lists all 16 commands (was missing/coordinate,/analyze,/resolve-pr)coordinate.mdPhase 5 now specifies max 3 fix cycles (was open-ended)- Standardized archive path wording to
[today's date]acrossship.mdandcoordinate.md
Documented solutions: ops/solutions/2026-03-31-hooks-stdin-json-parsing.md
Verification
The framework has been validated through:
- 20 parallel audit agents across 4 passes (hooks, commands, skills, agents, infrastructure)
- Manual 11-point verification: bash syntax, JSON validity, stale path scans, file counts, skill injection paths, executable permissions, mkdir guards, stdin parsing, agent cross-references, plugin hook paths, state file template compatibility
- All checks pass. Runtime behavior (plugin installation, hook firing,
${CLAUDE_PLUGIN_ROOT}expansion) requires live testing viaclaude --plugin-dir .