Checkwright v0.32.0
2026-10-23
Checkwright catches reproducible classes of unsupported claims in coding-agent delivery before merge, and this release places the git hooks in each clone instead of tracking them, makes the gates and the shell guard read the tree and the command the way git and bash do, and lets you write gates in PowerShell and run the guards under Antigravity.
In brief
| Section | Entries | Who acts |
|---|---|---|
| New and tightened gates | 74 | everyone: these may red on upgrade |
| Knob changes | 29 | if you set knobs |
| Gate-authoring changes | 8 | if you write or shadow gates |
| Platforms | 0 | if you install on a listed platform |
| Behavior changes | 212 | if you depend on a listed behavior |
- Your git hooks are no longer tracked files:
--install-hooksplaces the gate binary as each hook in the clone’s own git directory, so opting a clone in changes nothing you commit. - The gates read more of your tree, as git and kramdown see it: files with unusual names, fences opened in list items, and repositories git refuses are now read or refused rather than skipped, so a tree that was green can gain findings.
- The shell guard reads a command by the values bash gives its words, so a quoted, escaped or wrapped spelling no longer slips past a rule, and
--guard-verdictprints the decision for any command you give it. - You can write a gate or a KPI in PowerShell, and installing the plugin in Antigravity runs the shell and state-file guards.
- You have steps to take after upgrading. Re-run
--install-hooksin each clone, dropgates.listlines naming removed gates, and register each new gate or declare it unregistered. The allowed reds below name each.
New and tightened gates
check-crate-arms— declaresbash, and runs its clippy and test arms under theRUSTFLAGSgate_native_rustflagsprints, so its test arm no longer rewrites the placed binary unremapped. Nothing to do wherebashalready runs your build.check-gate-tamper— reads# exception-list:arrays through the shared grammar: an unspaced tag, an element on the opening=(line and a parenthesis inside a quoted element are now read, and an element’s words are split as bash splits them: a quote spanning lines, a backslash escape outside single quotes and a#inside a word are read, so the elements after such a line are no longer dropped and its value is the one bash assigns. A new exemption in any of those shapes matching a co-staged file now reds; land it in its own commit.check-gate-tamper— holds an executable# exec:target under the path dispatch starts, its host executable suffix appended, so on Windows a tracked<target>.exeis a gate file where it read as product code. A commit changing such a target beside product code now reds; split the gate change into its own commit.check-gate-exemption-tasks— disposition-checks an unspaced#exception-list:tag through that shared grammar, and the elements its bash word split no longer drops after a quote spanning lines or an escaped quote. Give each element of such an array its own line with a live# until:or a reasoned# permanent:; a quoted element spanning lines carries its disposition on the line it ends on.check-agent-tier-explicit— gains assertion B, armed only once you set the newDELEGATION_KIT_TIER_MODELbinding: each agent definition then carriestier: <class>with themodel:that class is bound to, or statesmodel: inheritwith notier:. Nothing to do while the binding stays empty; once you bind it, declare each definition’stier:and run--emit agent-tiers --write.check-rule-citation— leaves delegation-kit (Behavior changes), so agates.listline still naming it, which the kit README’s roster had you add, resolves nowhere and reds. Drop the line;initnever registered it.check-gate-substrate-parity— assertion I reds oncheck-pendency-contradiction, a new canon-kit gate the binary carries, until yourgates.listregisters it or declares# unregistered: check-pendency-contradiction — <reason>. Register it, or declare why you do not.check-prose-tells— gains assertion G, armed only once you set the newCANON_KIT_PROSE_TELL_ABSENCE_GLOBS(default empty, off): a section on those surfaces whose whole body is a placeholder or one sentence like “There are no known issues.” reds. Nothing to do while it stays empty; to arm it, bind hand prose only, never a checklist or a work queue.check-task-names— reds a placeholder token (None,n/a, a dash, from the newQUEUE_KIT_PLACEHOLDER_TOKENS, shipped set on by default) standing outside every entry of a task section, as a done slug or as an entry heading. Delete the line: an empty section is its heading alone. Set the knob empty to switch the arm off.check-pendency-contradiction— new, zero-config: a governed manifest file that calls one code span both pending (waits on,lands after,does not exist yet…) and landed (implemented by,landed,ships…) reds. Make the pending sentence past or drop it; a deliberate pair takes<!-- pendency-exempt: <reason> -->, and extending or replacing the phrase sets isCANON_KIT_PENDENCY_*in your canon-config.knobs.check-shellcheck— lints each kit root’ssmoke/as well, so a smoke script ShellCheck cannot parse or finds a warning in reds. Nothing to do in a vendored tree, which carries nosmoke/; a tree authoring a kit fixes each finding.check-shellcheck,check-psscriptanalyzer— lint each kit root’sgate-tests/and your tests dirGATE_SDK_TESTS_DIRas well, so a suite ShellCheck finds a warning in reds. Nothing to do unless you registered either gate: then fix a finding in your own suites, and drop anygate-testsdirectory you named inGATE_SDK_LINT_EXTRA_DIRS, which may now be linted twice.check-md-refs— an#anchormust now match the heading slug GitHub and kramdown render, each space a hyphen, so a link to a heading carrying an em dash (Content tiering — the star topology) needs the double hyphen (#content-tiering--the-star-topology); the single-hyphen form 404ed on the rendered page. Re-point each finding’s anchor.check-citation-link— arm B compares an anchor with that same slug. Re-point each finding’s anchor.check-surface-duplication— the glossary’s term-table section is named by the newCANON_KIT_GLOSSARY_TERM_SECTION(its default in canon-kit/SPEC.md) and opens at a heading of any level, closing at a heading of its level or above or at a---line; a table’s header row is the row its delimiter row follows, whatever its column is named. A term table under a heading level other than##now contributes its terms, so a surface restating one reds. Set the knob to your term section’s heading, or empty to read bold lead-ins alone.check-docs-link-convention— a kit page’s anchorless back-link to its canonical spec is read byCANON_KIT_SPEC_NAMErather than the literalSPEC.md. Nothing to do at the default; under another spec name, anchor each finding’s link.check-memory-off— withCONTEXT_KIT_MEMORY_DIRSempty, the derived memory dir is now the one the harness creates: the repository root under the harness’s own encoding, every character outside[A-Za-z0-9]folded to-rather than only/and., underCLAUDE_CONFIG_DIRwhen set, and underUSERPROFILEon Windows. A polluted memory dir the old fold missed, on a root carrying_, a space or another such character, now reds. Empty the dir it names.check-footprint-fresh— leaves context-kit (Behavior changes), so agates.listline still naming it, which the kit README’s roster had you add, resolves nowhere and reds. Drop the line;initnever registered it.check-kit-roots-dialect— leaves gate-sdk (Behavior changes), so agates.listline still naming it resolves nowhere and reds.updatedrops the lineinitwrote from agates.listyou never edited; otherwise drop it.check-packed-links— leaves gate-sdk (Behavior changes), so agates.listline still naming it resolves nowhere and reds.updatedrops the lineinitwrote from agates.listyou never edited; otherwise drop it.check-gate-binary-fresh— leaves gate-sdk (Behavior changes), so agates.listline still naming it resolves nowhere and reds. Drop the line;initnever registered it.check-crate-arms— leaves gate-sdk (Behavior changes), so agates.listline still naming it resolves nowhere and reds. Drop the line;initnever registered it.check-gate-substrate-parity— assertion I reds oncheck-disposed-findings, a newon-surfacelifecycle-kit gate the binary carries, until yourgates.listregisters it or declares# unregistered: check-disposed-findings — <reason>. It holds the grammar of the new disposed-findings record (Behavior changes) and reads clean while no record exists, so register it; declare a reason only if you keep no gap inbox.check-template-copy-parity— pairs each kit root’stemplates/*.sh(the rootsGATE_SDK_KIT_DIRSnames or the derivation finds) rather than<root>/*/templates/*.sh, so a subdirectory vendoring’s copies are now compared and a divergent one reds; a top-level directory with templates and no kit marker is no longer paired. Re-copy the template, or declare# copy-divergence: <reason>per finding.check-tree-terms— a file is self-exempt when it is a pattern file the two pattern knobs resolve to or the kit’s starter roster, rather than any name beginningmsg-patterns, so a trackedmsg-patternsfile no knob names is scanned and reds on a banned shape in it. Point the knob at each roster you keep, or remove the stray file.check-portability-floor— the same change overGATE_SDK_PORTABILITY_PATTERNSand theportability-patternsprefix. Nothing to do unless a stray file of that name sits in your corpus.check-md-refs,check-manifest-temporaland every other gate reading canon-kit’s manifest set — inside a git work tree a file now joins the set only where git’s index lists it, so an untrackedREADME.md,CLAUDE.mdorSPEC.md, a vendored library’s or a generated one, is no longer graded, nor is a submodule’s file; a member is still read from the working tree. A scan root a.gitentry marks but git refuses to answer for, on asafe.directoryownership refusal or broken repository metadata, exits 2 rather than grading the whole walk. Nothing to do; stage a new manifest file before the battery can grade it, and repair git’s access to a refused repository.check-spec-pointerandcheck-citation-link— a§with no path whose text opens with an ASCII digit (§3,§2.1) is a section-number mark, no longer a section citation, so a document numbering its sections stops redding. Nothing to do; cite a heading that opens with a digit by its path to keep it checked.check-enforcement-fresh— reds a committed enforcement page linking<kit>/index.mdfor a kit whose page your tree does not track, since the emitter now writes that kit as plain text. Regenerate with--emit enforcement-map.check-stage-evidence,check-memory-offandcheck-kit-roots-dialect— in a repository git refuses to answer for (asafe.directoryownership refusal, broken repository metadata, aGIT_DIRnaming nothing), each now exits 2 where it was inert, read clean or anchored on the working directory.check-install-disposition,check-kit-ref-liveness,check-fence-command-head,check-docs-cmd,check-fence-run,check-kit-enum,check-kit-registrationandcheck-template-copy-parityalready exited 2 there and now name git’s refusal rather thannot a git repository. The front end’s owncdalready refuses such a tree, so the change reaches a run through the binary door. Repair git’s access (git -C <dir> statusprints git’s reason).check-md-refs,check-manifest-temporaland every other gate reading canon-kit’s manifest set, with every gate and arm above — a scan root or directory reaching a repository git refuses through a symlink, a symlink followed by.., or a POSIX directory name carrying a\is now refused like one spelled directly, since the repository mark is sought along the physical path git discovers through, with no lexical step first, where it was missed and the tree graded unfiltered. Nothing to do; repair git’s access to the repository.check-md-refs,check-manifest-temporaland every other gate reading canon-kit’s manifest set, with every gate and arm above — a scan root or working directory beneath a nested.gitthat git cannot read (a garbageHEAD, an empty.gitdirectory, a.gitsymlink naming nothing) now exits 2, where git answered the enclosing repository at exit 0 and the nested tree’s files were silently dropped as untracked there. A run through the front end from such a checkout still changes to the enclosing toplevel first, so the refusal reaches a binary-door run or a scan root inside the tree. Repair the nested repository (git --git-dir=<mark> statusprints git’s reason) or remove its.git.check-md-refs,check-manifest-temporaland every other gate and arm refusing a nested repository git skips — a run under aGIT_WORK_TREEnaming a directory above the repository git discovers no longer exits 2 on that repository’s own.git, directory or gitfile, which git’s answer puts on the prefix; a.gitnearer the starting directory than it is still refused. Nothing to do.check-md-refs,check-manifest-temporaland every other gate and arm refusing a repository git refuses or skips, withbin/run-gates.shandbin/run-gates.ps1— a relativeGIT_CEILING_DIRECTORIESentry, which git ignores, no longer stops the search for a repository mark, so a refused repository above it is refused rather than read as none; and a skipped repository beneath a directory whose name holds a newline is now found. Nothing to do; spell a ceiling entry absolute for git to honour it.check-lesson-disposition,check-task-conservationandcheck-survey-record— in a repository git refuses, a nested one git skips included, each now exits 2 where it read clean as no repository;check-queue-slug-livenesslikewise, where its retired set read empty and passed every retired citation and status.check-amendment-retired-spelling,check-core-files,check-docs-cname-parity,check-docs-collapsible,check-docs-liquid-parse,check-docs-render-fidelity,check-exec-bit,check-hook-exec-bit,check-identity,check-md-refs,check-path-dialect,check-pipe-membership,check-root-tiering,check-spec-pointer,check-tracking-claim,check-tree-termsandcheck-workflow-tieringalready exited 2 there and now name git’s refusal rather thannot a git repository, and refuse a nested skipped repository they read as the enclosing one. Repair git’s access to the repository.check-battery-roster— a line in the battery-roster block whose command word is the gate binary’s door (./- or../-led, or rooted, a drive-rooted Windows path included) is now a roster line, where only a bare lowercase word was, so a stale door-led line naming no configured suite reds. Drop or correct each such line; document a derived fixture suite by its new door-led default (Knob changes).check-queue-sections— every section knob’s name (eachQUEUE_KIT_ACTIVE_SECTIONSmember,QUEUE_KIT_DEFERRED_SECTION,QUEUE_KIT_DONE_SECTIONand a configured icebox) now joins the required set whateverQUEUE_KIT_REQUIRED_SECTIONSholds, so a binding that left one of them out reds where the heading is missing from your queue. Restore the heading; a section renamed through its own knob needs no second edit.check-fence-paste-unit— new canon-kit gate, armed by the newCANON_KIT_FENCE_PASTE_PAGES(default empty, off): on a declared page, everybash,shorshellfence is one paste, each command list but the last a bare assignment, the rest joined with&&; and everypowershell,pwshorps1fence of two or more statement lines is wrapped as. { … }. Nothing to do while the knob stays empty; to arm it, register it, set the knob to your hand-authored pages, and join, split or wrap each fence it names.check-gate-substrate-parity— assertion I reds oncheck-fence-paste-unit, a new canon-kit gate the binary carries, until yourgates.listregisters it or declares# unregistered: check-fence-paste-unit — <reason>. Register it, or declare why you do not.check-fence-command-head,check-fence-paste-unitandcheck-fence-run— a fence opening on a list item’s marker (-sh`, `1.bash) is now read as a fence, as kramdown renders it, where its closing line was read as an opening and every later fence on the page toggled inverted. A list-item fence’s commands, and fences after it the inversion hid, are judged and can red. Fix each finding as for any fence.check-docs-page-length— new canon-kit gate, armed by the newCANON_KIT_PAGE_LENGTH_PAGES(default empty, off): no declared page, less those the newCANON_KIT_PAGE_LENGTH_EXCLUDEnames, runs past the newCANON_KIT_PAGE_LENGTH_MAX_WORDSwhitespace-separated words (off by default, nothing asserted), counted after the front matter with HTML comments skipped. Nothing to do while the pages knob stays empty; to arm it, register it, set the pages knob to your hand-authored pages, the exclude knob to your generated ones and the bound to your length, and shorten or split each page it names.check-gate-substrate-parity— assertion I reds oncheck-docs-page-length, a new canon-kit gate the binary carries, until yourgates.listregisters it or declares# unregistered: check-docs-page-length — <reason>. Register it, or declare why you do not.check-producer-liveness— a record naming the reading process’s own pid now reads free, where a pid recycled into the gate’s own process read held and refused a stage entry;--run-validate’s claim, the worktree liveness class and the shell guard’s live-record rule read the same predicate and loosen with it. The clean line names that case, and on Windows the red line also names the leg that answered and the reader’s pid. Nothing to do; a script matching the red line’s text to its end, or the clean line’snames dead pid, matches a prefix instead.check-spec-fence-balance— now reds a governed file that ends inside an open fence, read on one fence reader, where it counted three-backtick lines and red an odd total: a fence opened on four or more backticks and never closed by a run as long reds though its count is even, and a file whose one fence opens in a list item, behind its-marker, no longer reds. The finding names the unclosed opener’s line. Close each named fence with a backtick run at least as long as its opener’s.check-prose-tells,check-spec-pointer,check-citation-link,check-manifest-temporal,check-tag-lead-line,check-queue-wrap,check-task-names,check-stage-entry,check-scratch-citation,check-door-binding,check-graph,check-docs-collapsibleand every other gate on the shared fence reader — each now reads a fence through that one reader, whilecheck-md-unwrappedandcheck-docs-render-fidelitykeep fence scanners of their own: a list-item opener opens a fence, a fence closes only on a run at least as long as its opener’s, and where tildes are admitted only on its opener’s character. A doc carrying a list-item fence, or a shorter run inside a longer fence, had its fenced lines graded as prose and its prose after them skipped; both now read as rendered, so such a doc can gain a finding in prose that was skipped. Fix each finding as that gate’s own help line says.check-docs-page-length— a page opening on---that no later---closes is now counted whole, where it counted as zero words, so such a page over your bound reds. Close the front-matter block, or shorten the page.check-spec-embedded-source— a fenced block opened in a list item is now judged, and a block whose opener carries more than a bare language no longer shifts which lines the gate reads as fenced. A spec embedding a tracked file’s body in such a block reds; cite the path instead.check-hook-exec-bit— removed, no tracked hook being left to hold a mode on (Behavior changes). Agates.listline still naming it resolves nowhere and reds: drop the line.check-graph— loses assertion D, so it no longer holds a trackedpre-commitorcommit-msghook, and itscouples=drops the two hook paths. Nothing to do.check-stage-skill-coverage— gains a fourth direction, inert until you set the newLIFECYCLE_KIT_STAGE_EXECUTOR(Knob changes): every stage it binds must have an executed surface, the template its shim names or a copied skill itself, carrying the citationlifecycle-kit/SPEC.md §The host protocol. Of the shipped stage templates the build one alone lacks it, so bindingbuildreds. Its clean line gains the count of bound stages read. Nothing to do while the knob is empty; on a red, unbind the stage or carry the pointer line in your copied skill.check-portability-floor— a corpus member whose name carries a non-ASCII byte, a double quote or a backslash is now scanned, where the gate exited 2 naming a quoted path that does not exist. Such a member can gain a finding; fix it as the gate’s help line says.check-docs-collapsible,check-docs-render-fidelity,check-docs-liquid-parse,check-docs-cname-parity,check-docs-highlight-coverage,check-tree-terms,check-amendment-retired-spelling,check-door-binding,check-surface-ratchet,check-template-registry-parity,check-exec-bitand the gates reading the tracked shell tree (check-path-dialect,check-pipe-membership,check-gate-exemption-tasks) — a tracked file whose name carries a non-ASCII byte, a double quote or a backslash is now read, where it dropped out of the corpus unread and the verdict read clean. Such a file can gain a finding, and a clean line’s file count rises by the members now read; fix a finding as the gate’s help line says.check-root-tieringstops redding such a top-level name under its quoted spelling.check-reads-couples— a tracked file under a declared read root whose name carries a non-ASCII byte, a double quote or a backslash is now matched against the gate’s couples under its own name, where git’s quoted spelling of it was matched. The coverage verdict on such a file can change; widen the couple the finding names, as the gate’s help line says.check-portability-floor,check-prose-enum,check-comment-tier, every other gate reading canon-kit’s comment surface, and the gates of the bullet above — a tracked file whose name is not valid UTF-8, under a path the gate lists, is now an exit 2 naming the file, where the file was skipped or opened under a replacement-character spelling. Rename the file.check-docs-collapsible,check-citation-link,check-pendency-contradiction,check-docs-page-repeat,check-prose-tells(assertion G) andcheck-docs-render-fidelity— a page opening on---that no later---closes is now read whole, where each skipped it to the end of the file and passed it unread. Such a page can gain a finding. Close the front-matter block, or fix the finding as the gate’s help line says.check-action-gh-repo,check-action-permissions,check-action-step-order— three shapes of valid workflow YAML are now read as their plain spellings: a step sequence whose dashes sit at thesteps:key’s own column, where every such step was unread; arun:body whose block indicator is followed by a comment, where the body was unread; and, forcheck-action-step-order, a quoteduses:ref followed by a comment, where the job was refused. A job written in the first two can gain a finding. Fix it as the gate’s help line says.check-settings-paths— reads a hook registered in the harness’s exec form on the gate binary and gains one red: a hookcommandstill carrying@GATE_SDK_NATIVE_BIN@, a kit template merged without its placeholder step, which starts no program. Replace the placeholder as the finding’s help line says. An exec-form registration’s--hookmember is now checked like a front-end one’s. Two readings loosen: acommandnaming no extension resolves against the file carrying the host’s executable suffix, and an absolute hook path is skipped and counted where it used to red.check-gate-tamper,check-stage-evidenceandcheck-evidence-baseline— a staged file, or the baseline at the iteration start, whose name carries a non-ASCII byte, a double quote or a backslash is now read under its own name, where git’s quoted spelling of it was compared or opened: such a staged gate file’s added exemptions are now seen, such a file staged beside a stamp is now named, and such a baseline is now read for its flips. A staged name that is not valid UTF-8 is now an exit 2 naming it. Such a commit can gain a finding; fix it as the gate’s help line says, or rename the file.check-md-refs,check-spec-pointer,check-fence-command-head,check-docs-cmd,check-md-unwrapped,check-dispatch-entry,check-exec-bit,check-reads-couplesand thepre-commithook’s staged read — a tracked or staged file whose name is not valid UTF-8 is now an exit 2 naming it, where it was matched or opened under a replacement-character spelling and its verdict read clean.check-docs-cmdstill reads a deleted name as before. Rename the file; to delete one, rename it in one commit and delete it in the next.check-docs-collapsible,check-citation-link,check-pendency-contradiction,check-docs-page-repeat,check-prose-tells(assertion G),check-docs-page-lengthandcheck-docs-render-fidelity— a page whose front matter closes on a...line, as Jekyll allows, now has that block read as front matter, where its metadata was read as body. Such a page can lose a finding its metadata caused, andcheck-docs-page-lengthmeasures it shorter. Nothing to do.check-psscriptanalyzer— new,on-surface: lints your PowerShell gates and scripts with PSScriptAnalyzer at Warning and above onceGATE_SDK_POWERSHELLnames a host, and reads clean while it is empty. Nothing to do until you write PowerShell; once you set the host, install the PSScriptAnalyzer module for it.check-gate-substrate-parity— gains assertion K, which reds a misplaced or dangling# exec:field: one outside a descriptor directly under your gates directory, a second one, a rooted or..-bearing value, or a target that is untracked or not a.ps1. Nothing to do unless you write one; fix each finding as it names.check-gate-substrate-parity— assertion K admits a second target class, a program you build, whose name carries no.and which K never looks for, being a build output, and reds an# exec:value ending in a separator.check-gate-assertionscounts such a member out of reach on its clean line, and--emit port-blockersreports no row for it. Nothing to do; fix a finding as it names.check-gate-fixture-coverage— reds# no-fixture:on a descriptor carrying# exec:, since a PowerShell gate’s fixture pair is the only check of its output and fail-closed contracts. Give the gate agood/andbad/pair.check-gate-tamper— now runs atpre-commiton a staged.gatedescriptor, a.ps1exec target or any path yourDELEGATION_KIT_GATE_FILESnames, where only a staged.shtriggered it, so a descriptor edit staged beside a non-meta path reds at the hook rather than first in the full battery. Split the gate edit into its own commit.check-md-unwrappedandcheck-docs-liquid-parse— read a page’s front matter as canon-kit’s front-matter span, so a block closed on a...line is skipped as front matter, where its metadata was read as body and a later---line, a thematic break say, was taken for its closer. A page closed on dots can lose a finding its metadata caused, or gain one in the body above that later line. Fix a finding as the gate’s help line says.check-tightened-gates-grammar— reads a post as a release note only where itsrelease:key sits inside canon-kit’s front-matter span, so a post spelling the key in its body, below a...closer or under a block nothing closes, is no longer held to the note grammar. Nothing to do.check-gate-tamperandcheck-gate-exemption-tasks— keep a carriage return before a line’s newline where bash keeps it: inside a quoted element spanning lines, and after a backslash ending the line, which escapes it rather than continuing the word. Such an exemption’s value is now the one bash assigns. Nothing to do.check-delta-agreement— new canon-kit gate, zero-config, armed by the newCANON_KIT_DELTA_PATH(default empty, off): each delta file it names must agree with the base file its group names on which items exist, an item a present family names carried by the base, one an absent family names not, and one a base-required family names never on a delta with no base. Nothing to do while the knob stays empty; the OpenSpec recipe sets it, andcheck-gate-substrate-parityassertion I reds until yourgates.listregisters the gate or declares# unregistered: check-delta-agreement — <reason>.check-gap-inbox-neutrality— reds a dated bullet fused onto the gap inbox’s# contract:header line, which it skipped as the header, so a truncation that dropped the header’s line break no longer hides the next filed bullet from every line reader. Break the line before the bullet.check-manifest-count— gains arm B: a lead-in ending in:over a bullet or numbered list, whose last sentence opens with a cardinal (Three cases:,The two modes:), reds where the cardinal disagrees with the list’s item count. A file yourCANON_KIT_TEMPORAL_EXEMPT_PATHSnames is outside the arm. Make each finding’s cardinal match its list or drop it; a deliberate one takesmanifest-count-exempt: <reason>.check-docs-page-repeat— gains arm C, armed only once you set the newCANON_KIT_PAGE_REPEAT_CHROME(Knob changes): a declared page linking a target the chrome links literally, an absolute URL included, or stating a sentence ofCANON_KIT_PAGE_REPEAT_MIN_WORDSwords or more the chrome states, reds. Nothing to do while the knob stays empty; once you set it, drop each finding’s page copy, or keep one withpage-repeat-exempt: <reason>.check-comment-tier,check-spec-pointer,check-todo-task-liveness,check-deprecation-task— withCANON_KIT_COMMENT_SURFACEset, a scan root lying under a directory the prune set names (.tmp,target,node_modules, …) now governs its sources, where it governed none and read clean; the prune reads the path below the root. Nothing to do for a battery run at.; a run given such a root may now red on comments it skipped.check-manifest-count— arm B reads a lead-in’s list past any run of blank lines, where it stopped after one. A lead-in whose cardinal disagrees with a list two or more blank lines below it now reds. Fix the count, or tag the sitemanifest-count-exempt: <reason>.
Knob changes
GATE_SDK_RELEASE_SECTIONS— new: the release note’s section roster, one<role>: <heading>element per section in note order over the rolesbrief,gates,knobs,authoring,platformsandbehavior, defaulting to In brief, New and tightened gates, Knob changes, Gate-authoring changes, Platforms and Behavior changes. Nothing to do.GATE_SDK_RELEASE_SECTION_ALIASES— new: former headings a section is still read under, in the same grammar, defaulting togates: Tightened gatesandknobs: Renamed knobs. Nothing to do.EVIDENCE_KIT_RUN_<suite>— a derived fixture-suite member now runs the gate binaryGATE_SDK_NATIVE_BINnames,<door> --run-gate-tests <tests-dir> [<checks-dir>], where it ran the bash front end’s--run-gate-tests, and runs in the working directory--run-validateruns in rather than at the toplevel. If your runner doc’s battery-roster block lists a derived suite, respell its line to the new command, orcheck-battery-rosterreds; a member you set yourself is unchanged.QUEUE_KIT_REQUIRED_SECTIONS— now holds only the headings no section knob names, its default in queue-kit/SPEC.md; the section knobs’ names join the set by derivation (New and tightened gates). A binding restating the whole list keeps working. To drop the lesson channel, bindQUEUE_KIT_REQUIRED_SECTIONS[] = Iteration:alone; the knob may now be bound empty, which also drops the iteration header from the floor.QUEUE_KIT_ACTIVE_SECTIONS,QUEUE_KIT_DEFERRED_SECTION,QUEUE_KIT_ICEBOX_SECTIONandQUEUE_KIT_DONE_SECTION— a value ending in:is now malformed config, so every queue-kit gate and arm exits 2 naming the knob, wherecheck-queue-sectionsprefix-matched the name and passed while every section-scoped gate read no task under it. Drop the trailing:and name the heading exactly.DRIFT_KIT_MANUAL_OPS_LOG,DRIFT_KIT_MANUAL_OPS_IGNORE,DRIFT_KIT_MANUAL_OPS_TOP— new, read by the new--emit manual-ops(Behavior changes): its log (default${DRIFT_KIT_METRIC_DIR}/manual-ops-log.txt), the keys it drops as ordinary work (default empty) and how many it ranks (default10, a positive integer, else exit 2). Nothing to do; list your ordinary shapes in the ignore knob once a run shows them.LIFECYCLE_KIT_MIRROR_ROOT— new, default empty (off): the directory your generated mirrors of roster files sit under. Where it is set,check-stage-entry’s assertion C counts an amendment-body token resolving to<root>/<dir>as<dir>when<dir>holds a roster file, so an amendment naming its own component’s mirror no longer needs an audit stamp or a waiver; the refusal’s help line names the knob. Nothing to do; if you generate mirrors of your roster files, set it to their root.GATE_SDK_HOOKS_DIR— removed: the hooks are placed in the clone’s git directory and no knob places them (Behavior changes). A knob file still setting it is refused at its first read, naming the knob: delete the line.CONTEXT_KIT_BRIEF_COMPONENT_MARK,CONTEXT_KIT_BRIEF_NUDGES,CONTEXT_KIT_BRIEF_FOOTER_FILE,CONTEXT_KIT_BRIEF_COMMANDS— new, read by the newsession-contexthook member (Behavior changes): the directory name that marks a component (defaultsrc, empty turns the dirty-surface step off), a stage-keyed set of text files printed as nudges (default empty; the newtemplates/nudge-delegation.txtis an exemplar to copy), a text file replacing the built-in index footer (default empty) and your own brief lines, one command per element (default empty). Nothing to do unless you move off your copied script; then they hold what you edited into it.CONTEXT_KIT_DRIFT_REPORT,CONTEXT_KIT_STAGE_RULES,CONTEXT_KIT_SESSION_ROLE_FILE— now rows of context-kit’s table with their existing defaults, so a knob-file line sets each, where only an exported variable reached the copied script. The member spawns theCONTEXT_KIT_STAGE_RULEScommand with no shell: a value relying on quoting, a pipeline or an expansion is rewritten as one command, and a value naming an arm of the gate binary is written from its--flag on, such as--emit stage-rules.LIFECYCLE_KIT_STAGE_CONTRACT_FRAME— new, read by the new--emit stage-contract(Behavior changes): the repo-root-relative path of the frame printed ahead of a stage’s skill, defaultlifecycle-kit/templates/frames/stage-contract.md. Nothing to do; point it at your own file where a foreign vendor needs different wording, and set it where your kit is vendored under another directory.DELEGATION_KIT_FOREIGN_USAGE,DELEGATION_KIT_FOREIGN_USAGE_CMD,DELEGATION_KIT_FOREIGN_PAUSE_PCT,DELEGATION_KIT_FOREIGN_PAUSE_PCT_LONG— new, read by--foreign-runand--foreign-resume(Behavior changes): each foreign adapter’s usage snapshot as<adapter>=<path>, the argv that writes it as<adapter>=<word>elements with@USAGE_FILE@substituted (both default empty, which leaves an adapter unbudgeted), and the two pause thresholds, defaulting toDELEGATION_KIT_PAUSE_PCTandDELEGATION_KIT_PAUSE_PCT_7D. Nothing to do; to budget a foreign adapter, bind the first two in your gitignored.localoverlay.GATE_SDK_CRATE_TARGET_LINT_CMD— new, default empty (off): a command knob gate-sdk’s table now declares, read bycheck-crate-arms, a gate that does not ship, so nothing in a vendored tree reads it. Nothing to do.GATE_SDK_HARNESS_LITERALS,GATE_SDK_HARNESS_LITERAL_PATHS— new, both default empty (off): two rosters gate-sdk’s table now declares, read bycheck-harness-literal, a gate that does not ship, so nothing in a vendored tree reads them. Its site valveharness-binding: <reason>joinscheck-comment-tier’s built-in directive roster, so a full-line comment opening on that token is now a directive. Nothing to do.LIFECYCLE_KIT_STAGE_EXECUTOR— new, default empty (off): one<stage>=<adapter>element per stage whose work you send to a foreign adapter, the adapter a name yourDELEGATION_KIT_FOREIGN_ADAPTERSconfigures. A malformed element is refused at exit 2 by every lifecycle-kit gate and arm: no=, a stage outsideLIFECYCLE_KIT_STAGES, a stage bound twice, an empty adapter, or an adapter name outside[a-z0-9-]. Read bycheck-stage-skill-coverage(New and tightened gates) and by the lead template (Behavior changes). Nothing to do; to bind a stage, set it in your gitignoredlifecycle-config.local.knobsbeside the adapters it names.LIFECYCLE_KIT_CRITIQUE_ADAPTER— new, default empty (off): the one adapter name, from yourDELEGATION_KIT_FOREIGN_ADAPTERS, a consultation’s outside critique runs on (Behavior changes). A value outside[a-z0-9-]is refused at exit 2 by every lifecycle-kit gate and arm. Nothing to do; to have a consultation offer a critique, set it in your gitignoredlifecycle-config.local.knobsbeside the adapter it names, a resumable one where you want follow-up questions.LIFECYCLE_KIT_CONSULT_CLASS— new, its default in lifecycle-kit/SPEC.md: the tier class a consultation runs on. An empty value, or one outside[a-z0-9-], is refused at exit 2 by every lifecycle-kit gate and arm. Nothing to do; a consumer whose class roster names its own consultation class sets it, and moves its dispatched-consultation definition’stier:with it.<KIT>_LOCAL_KNOB_FILE— new for every kit, default unset, read from the environment only: the file a kit’s local overlay is read from, in place of<gates-dir>/<stem>-config.local.knobs. Set to a path that does not exist it is exit 2, as<KIT>_KNOB_FILEis. Each locator moves its own layer alone, so relocating the tracked file still leaves the overlay in the gates directory. A line naming it in a knob file is refused, in drift-kit’s open family too. Nothing to do unless you set it.GUARD_KIT_CONSUMER_VARIANT_LIMITS— new, default empty (off): a repository-relative path to your own limits table, inguard-tests/variant-limits.tsv’s grammar. Where it is set,--run-guard-testsderives the bash re-spellings of each row of yourGUARD_KIT_CONSUMER_CASEStable, feeds them to your rule command, and fails on one your rules read more permissively than the row unless the table declares its section and transform, and on a table row nothing needs. Set with no cases table, or naming a table it cannot read, the arm is exit 2. Nothing to do unless you set it; a consumer that sets it accepts that a later release adding a transform can red its lane until it adds a limit row.LIFECYCLE_KIT_STAGE_CONTRACT_SWEEP_FRAME— new, read by--emit stage-contract --mode sweep(Behavior changes): the repo-root-relative path of the frame printed ahead of a stage’s skill for a foreign agent that edits its clone, defaultlifecycle-kit/templates/frames/stage-contract-sweep.md. Nothing to do; point it at your own file where a foreign vendor needs different wording.GATE_SDK_POWERSHELL— new, default empty (off): the PowerShell host that starts a gate’s# exec:target and a PowerShell KPI plugin,pwshorpowershell, by name or path; any other value is refused at exit 2. Nothing to do; set it once you write PowerShell.GATE_SDK_PSSCRIPTANALYZER_EXCLUDE— new, default empty: the PSScriptAnalyzer rulescheck-psscriptanalyzerskips, for a rule your tree contradicts on every script. Nothing to do.CANON_KIT_DELTA_PATH,CANON_KIT_DELTA_BASE_DIR,CANON_KIT_DELTA_BASE_ITEM,CANON_KIT_DELTA_PRESENT,CANON_KIT_DELTA_ABSENT,CANON_KIT_DELTA_ITEM,CANON_KIT_DELTA_BASE_REQUIRED— new, all default empty, read bycheck-delta-agreement(New and tightened gates): the delta path and its base directory, the base’s item pattern, each family’s section heading and item pattern, and the families needing a base. While the path is set, a family in both or neither assertion knob, a pattern outside the one-group shape or a base-required member naming no family is refused at exit 2 by every canon-kit gate. Nothing to do unless you bind deltas.RESAMPLE_ATTEMPTS,RESAMPLE_PAUSE— new probe-step env in site-kit’stemplates/site-health.yml, shipped at3and30, the resample’s attempt count and its pause in whole seconds that were literals in the step’s body;RESAMPLE_ATTEMPTS: '1'turns the resample off, and a value outside its set reds the run as the knob’s own finding with no probe run. Nothing to do: a copy you made earlier keeps its literals and runs as before; to set the bounds, re-copy the probe step.ISSUE_TRACKING— new report-step env in site-kit’stemplates/site-health.yml, shippedoff:onhas a failure open or update thesite-healthissue and recovery close it, and needsissues: writein the permissions block; any other value reds the run naming the value (Behavior changes). Nothing to do unless you want the issue path.DRIFT_KIT_STATISTICS_FILE— new, read by the new--emit statistics(Behavior changes): the statistics view’s document, default${DRIFT_KIT_METRIC_DIR}/statistics.html. Nothing to do; pointing it at a tracked path publishes your spend.CANON_KIT_PAGE_REPEAT_CHROME— new, default empty (off): the site-chrome template files, an array of globs, thatcheck-docs-page-repeat’s new arm C reads as part of every declared page (New and tightened gates). Nothing to do; name the layout and includes every declared page renders through to arm it.CONTEXT_KIT_SWEEP_HORIZON_HOURS,CONTEXT_KIT_SWEEP_PRESERVE— new, read by thesession-contexthook member’s scratch sweep (Behavior changes): the whole hours a scratch path stays unmodified before the sweep takes it, a positive integer oroff, default24, the day the sweep always used; and the scratch-root names it spares whole at any age, default empty. Any other horizon value is refused at exit 2 by every context-kit reader. Nothing to do; set the horizonoffwhere your scratch dies at a boundary of its own, such as lifecycle-kit’s boundary wipe, or name on the keep-list what a long-lived session must still find past the horizon.DRIFT_KIT_SELECTION_AGENTS,DRIFT_KIT_SELECTION_PROMPTS,DRIFT_KIT_SELECTION_SUBJECT,DRIFT_KIT_SELECTION_TIMEOUT,DRIFT_KIT_SELECTION_RECORD,DRIFT_KIT_SELECTION_ANSWERS_DIR— new, read by the new--selection-probe(Behavior changes): the agents as<name>keys over argvs split on blanks, the prompts as<name>keys over their text, and the subject names (all three default empty, the probe off), the per-call bound (default300seconds, a positive integer, else exit 2), the record (default${DRIFT_KIT_METRIC_DIR}/agent-selection.log) and the answers directory (default${DRIFT_KIT_METRIC_DIR}/agent-selection). Nothing to do; bind the first three to run it.
Gate-authoring changes
lib/gate.sh,gate_skipped_mark <top> <prefix>— a new accessor: given the toplevel and prefix git answered from the working directory, it prints the nearest.gitentry along the prefix git skipped and returns 0, or returns 1 where git skipped none. Nothing to do; a script of yours that changes to git’s toplevel can ask it first.lib/test-hermetic.sh— now headsGIT_CEILING_DIRECTORIESwith the system temp dir, resolved physically, so a stray.gitin${TMPDIR:-/tmp}no longer marks a bespoke test’smktempsandbox as inside a repository. Nothing to do; a test that needs a repository above its sandbox sets the ceiling itself throughgate_env.- a gate in PowerShell — a
.gatedescriptor in your gates directory may carry# exec: <script>.ps1, and the battery, the hooks and the stage-entry pre-flight then start that script with the hostGATE_SDK_POWERSHELLnames. A member the binary does not implement, a shell gate included, now receivesGATE_SDK_NATIVE_BINnaming the running binary, so it reads a knob through--emit knob-values. Nothing to do; to write one, follow the install page’s PowerShell section. lib/gate.shgate_command,--emit gate-argv— for a.gatedeclarationgate_commandnow prints the binary’s--emit gate-argv <declaration>answer, whose first element for a compiled member is the binary’s own absolute path. Nothing to do, unless a test of yours names a stand-in binary inGATE_SDK_NATIVE_BIN: make it answer--emit gate-argvwith its argv, one element per line.- a gate you build — a
.gatedescriptor’s# exec:value whose last path component carries no.names a program you build, such as a gate written in Rust: the battery, the hooks, the stage-entry pre-flight andgate_commandstart the file at that path, with the host’s executable suffix on Windows, and never build it or compare it with its source; one not yet built fails as a gate that could not run, exit 2. An# exec:value ending in a separator is now refused as naming no file. Nothing to do; to write one, follow the install page’s Rust section. - a shell gate on native Windows — the battery, the hooks, the stage-entry pre-flight and
--run-gate-testsnow start a.shmember through Git for Windows’ bash, resolved onPATHoutside the Windows system directory, where they started the script by its path, which Windows cannot run; with no such bash the member fails as a gate that could not run, exit 2. Nothing to do on any other system; on native Windows put Git for Windows’ bash onPATH. - a descriptor’s exemptions — a
.gatedescriptor may carry# exception: <element> # until: <slug>or… # permanent: <reason>lines, held bycheck-gate-exemption-tasksandcheck-gate-tamperas a script’s# exception-list:array is, andcheck-comment-tierreads each such line as a machine directive. Nothing to do; a script’s array is unchanged. - a gate’s temporaries — a battery member, a fixture case and a bespoke suite now run with
TMPDIRunder a run scratch insideGATE_SDK_TMP_DIR, repo-local, and that run scratch headingGIT_CEILING_DIRECTORIES. Nothing to do for a gate that finds a repository through git or the ceiling; one that walks up from amktempdir by another means now meets your repository above it, so honor the ceiling or make that dir under an explicit system temp path.
Platforms
None.
Behavior changes
- gate-sdk
lib/gate.sh,bin/build-native.sh—gate_native_rustflagsowns the build’s flags and remapping. Read this function for cargo calls that can rewrite the placed binary. - gate-sdk
--run-gate-tests— clears inherited file-layer locators and scalars each case’s knob files set before applying runner pins. Pin a scalar in the case’s file to override an invoker export; unset case scalars still inherit. -
gate-sdk
lib/test-hermetic.sh— clears inherited kit-prefixed variables exceptGATE_SDK_NATIVE_BINbefore its own pins. Set a test’s deliberate overrides after sourcing the library. - guard-kit
--run-guard-tests, guard-kit/templates/guard-config.knobs — a new knob,GUARD_KIT_CONSUMER_CASES(default empty, off), names your own decision table incases.tsv’s grammar; where it is set,--run-guard-testsruns its rows against yourGUARD_KIT_CONSUMER_RULES_CMDfrom the repository root after the kit’s tables, and a mismatch reds the run. The template carries the commented example. Nothing to do; to prove your rules fire, set the knob and write a firing and a non-firing row for each. - guard-kit/templates/settings-hooks.json, plugin/hooks/hooks.json — each front-end hook command is now
bash "${CLAUDE_PROJECT_DIR}/gate-sdk/bin/run-gates.sh" --hook <member>, so a session that changes into a subdirectory keeps its guards, and the plugin’s hooks resolve the repository’s toplevel through git, so they also fire in a session launched from a subdirectory. Re-spell each front-end hook command you merged into.claude/settings.jsonfrom guard-kit, delegation-kit or lifecycle-kit the same way; a relative command stays silent once a session leaves the root. - guard-kit shell guard, rule
commit_only_paths— a new generic rule blocks a call that runsgit addand then agit committaking the whole index (no path, or-a/-i), steering to the only-paths formgit commit -o <paths>, since a pathless commit sweeps whatever a concurrent session staged. A lonegit commitand a commit naming its paths are untouched. Nothing to do. - delegation-kit/templates/agent-execution.md — the Serialize on shared files bullet names
git commit -o <paths>as the commit a shared-checkout agent makes; serialization or worktree isolation stays the answer to index-lock andHEADcontention. Nothing to do. - guard-kit shell guard, rules
sed_fileandro_pipeline, knobGUARD_KIT_RO_BINS— the default read-only roster gainssedandawkbehind a static program check, so asedorawkread whose program cannot write or execute (now/e, nosystem(), no print redirect, no pipe) is auto-allowed instead of falling to the harness’s decision, and rulesed_fileno longer steers anawkheading range on a markdown file to--emit md-section, which stays available. A piped-intosed -n A,BporawkNRrange is now blocked with the steer tohead -n B | tail -n +A. Nothing to do; a consumer whose explicitGUARD_KIT_RO_BINSomitssedandawkkeeps the prior disposition, and one that will not carry the check’s honest limit drops them. - guard-kit shell guard, rule
find_exec— a new generic rule blocks afindwhose one-exec … '{}' +or\;runs a read-only roster command, steering tofind … -print0 | xargs -0 <command>, which the read-only pipeline grant allows. Nothing to do. - guard-kit shell guard, rule
worktree_confinement— from a linked worktree the admitted read also takes a read-only arm of the gate binary (--emit md-section) over a main-checkout path, and the refusal names that arm and omitssedandawkfrom its roster. Nothing to do. - delegation-kit/templates/agent-execution.md — isolation cost (3) names a read-only arm of the gate binary beside the read-only pipeline as an isolated child’s search route. Nothing to do.
- every kit gate’s printed remedy — a gate whose red names a command to run (the graph, hook, roadmap, footprint and enforcement-map regenerators,
--install-lifecycle,--install-doctrine,--emit md-unwrap,--emit file-gap,--emit file-survey,--emit cite-survey,--emit entry-history,--emit queue-migrate,--emit always-loaded --ceiling,--enter-stageand the prose-bounds worklist among them) now prints it on the binaryGATE_SDK_NATIVE_BINnames,./-prefixed when relative, instead of the bash front end. Nothing to do. - guard-kit
check-door-binding, assertion C — aGUARD_KIT_DOOR_ROOTSmember whose name ends.rsis read as Rust source: up to its#[cfg(test)]mod tests {block, with//lines never a door site, a declaration only as// door-contributor: <reason>on the door’s line or the line above, and no span scope. Nothing to do unless your corpus lists Rust sources with a door a span declaration covered; give each a site declaration. - every kit’s smoke/install.sh and smoke/violation.sh — the entry-point guard’s hint now reads
run via --run-consumer-smoke on the gate binary. Nothing to do;check-smoke-entry-guardrecognizes the guard by its${SMOKE_KIT_ROOT:?prefix alone. - guard-kit shell guard, rule
pgrep_self_match— a redirect on thepgreporpkillsegment is no longer read as a second operand, sowhile pgrep -f <pattern> >/dev/null; do …; done, which self-matches like the unredirected form, is now blocked with the recorded-PID steer instead of passing. Nothing to do. - guard-kit shell guard, rule
sed_file— asedcarrying an option outside the read-only program check’s model (GNU’s-l N, say) no longer takes the Read-tool steer, since its argument cannot be sized; the call falls to the harness’s decision. Nothing to do. - lifecycle-kit/templates/stages/close.md — the roadmap-motion read also reads each
[roadmap:]entry’s recorded horizon condition and re-tags one the iteration’s range met, regenerating the projection; a horizon no recorded direction settles is asked of the operator. Nothing to do. doctrine-kit/DOCTRINE.mdProbe-before-assertion, lifecycle-kit/templates/lead.md — labelling a claim inferred no longer discharges the probe: inferred is for evidence out of reach, and a transcript, log or artifact that holds the answer is read first; the lead template’s tier rule says so, and the digest reads “a claim a cheap command or on-disk evidence settles is probed over the corpus it ranges over before it is asserted or relayed; inferred is for evidence out of reach”.check-doctrine-registrationreds your agent file’s bullet until it matches the new digest: re-run--install-doctrineafter re-vendoring.- delegation-kit tier binding,
--emit agent-tiers, agent-dispatch-guard rule D6, delegation-kit/templates/delegation-config.knobs — a new indexed knob,DELEGATION_KIT_TIER_MODEL(default empty, off), binds each tier class to a model alias, which follows the newest model, or an exact model id, which pins it, and optionally to an effort: one<class>=<model>[,<effort>]element per class. The classes are a second new indexed knob,DELEGATION_KIT_TIER_CLASSES, highest first, its default in delegation-kit/SPEC.md. Where the binding is set,--emit agent-tiers [--write]generates each agent definition’smodel:from itstier:field, and itseffort:where the class binds one, and the dispatch guard blocks a dispatch naming amodelthat matches no bound model. The template carries the commented examples. Nothing to do; to bind, follow the delegation-kit README’s tier step. - delegation-kit
--model-verdict, knobDELEGATION_KIT_SESSIONS_DIR— a new arm names the model a session runs on, read off its transcript, and with--expect <class>judges it against the tier binding: exit 0 READ or OK, 1 BELOW or UNBOUND, 2 UNKNOWN (never a refusal). Its line carrieseffort=, the effort the session runs at, and anOKnames one that is not the expected class’s bound effort. The new knob (default empty, derived likeDRIFT_KIT_SESSIONS_DIR) names the transcript directory it reads. Nothing to do; a stage session whose dispatch names its class can check it after its entry stamp. - delegation-kit/templates/agent-execution.md, lifecycle-kit/templates/lead.md, lifecycle-kit/templates/consult.md — the tier rules read the class roster and each class’s model and effort from the tier binding where one is set, a foreign reading passes
--class <class>(the host protocol, the lead’s executor read, the consult critique and the foreign-run bullet), and a mechanical batch on a pinned class is dispatched as a type whose definition declares that class, since a per-dispatchmodelis an alias only. Nothing to do. - delegation-kit
--foreign-run, knobsDELEGATION_KIT_FOREIGN_ADAPTERSandDELEGATION_KIT_FOREIGN_TIMEOUT, delegation-kit/templates/delegation-config.knobs, delegation-kit/templates/agent-execution.md — a new arm runs one read-only audit or mechanical sweep on another vendor’s coding agent in a scratch clone of committedHEAD: the report returns as a file, a sweep’s change as a patch you apply and commit, and the agent’s own commits are never taken (exit 0 OK, 1 REFUSED, 2 FAILED). The adapters knob (default empty, no adapter) holds each adapter’s argv as<adapter>=<word>elements, and the timeout knob bounds one run (default1800seconds). An adapter may take its model and effort from a tier class: name its harness inDELEGATION_KIT_FOREIGN_HARNESS(<adapter>=<harness>), bind that harness’s rows inDELEGATION_KIT_FOREIGN_TIER_MODEL, each a harness and class bound to a model and an optional effort, both new and default empty, write@MODEL@and@EFFORT@in its argv, and pass--class <class>, which such an adapter requires and any other ignores. The verdict line carriesclass=<class|->aftermode=. The knob template carries the commented example, and the protocol template gains the dispatcher’s bullet. Nothing to do; to route mechanical work to a vendor, configure an adapter that selects its vendor’s read-only or workspace-bounded sandbox mode, in your gitignored.localknob overlay. - lifecycle-kit/templates/consult.md, lifecycle-kit/templates/lead.md — a consultation now verifies its tier as its first step (
--model-verdict --expect <class>, the classLIFECYCLE_KIT_CONSULT_CLASSnames; skipped without delegation-kit; exit 1 stops it, exit 2 proceeds and says so), the lead dispatches a consultation on that class, and theruling-configslot description widens to name an optional hotfix type, with a new lead paragraph for an operator-ruled hotfix dispatch. No slot is added or renamed. Nothing to do; a consumer that names no hotfix type dispatches none. - delegation-kit/checks/check-rule-citation.gate —
check-rule-citationno longer ships with delegation-kit: its subject is the kit’s own SPEC, which the payload never carried, so in your tree it could only fail to find its input.initnever registered it. Nothing to do; if you registered it by hand, drop itsgates.listline. - canon-kit
check-docs-cmd, assertion B — on a line the history valves exempt (themanifest-temporal-exempt:marker,CANON_KIT_TEMPORAL_EXEMPT_SECTIONS,CANON_KIT_TEMPORAL_EXEMPT_PATHS), a kit-prefixed knob no kit code carries is admitted when your held history once carried it, so a release note or dated post naming a removed knob needs no whole-docCANON_KIT_MDREF_EXCLUDE. A misspelled knob still reds there. Nothing to do; a doc you excluded only for a removed knob can leave the exclude. - canon-kit
check-citation-link— a generated<!-- name:begin -->…<!-- name:end -->region on a declared page is skipped, ascheck-prose-tellsskips it, since its fix is the emitter’s. Nothing to do. - evidence-kit
--emit baseline-claims— a new arm prints each validate-baseline row as ameasured:oracle line,baseline-<suite>--<scenario>⇥<status>. Nothing to do; to hold prose that quotes a baseline row, name it inCANON_KIT_MEASURED_CLAIMS_CMD(or append its output to your emitter) and mark each quoting sentence. --emit docs-mirror— a mirrored SPEC’s and the doctrine’s section citations render as links to their sections. Regenerate your mirror with--emit docs-mirror --write; nothing else to do.doctrine-kit/DOCTRINE.mdDerivation-first — a member’s position inside a collection (the last line of a roster, a rule cited by number) is named beside its count as a derived fact prose restates; name the member. The digest is unchanged. Nothing to do.- canon-kit comment surface:
check-comment-tier,check-spec-pointer,check-todo-task-liveness,check-deprecation-task— withCANON_KIT_COMMENT_SURFACEunset, the derived surface is.shand.gatesources plus the workflow directory, and no longer.rssources: a tree’s Rust is its own code, not a kit gate file. Nothing to do; to govern a language, name it inCANON_KIT_COMMENT_SURFACEbeside**/*.shand**/*.gate. - canon-kit
check-spec-embedded-source— a file is held out of the source index byCANON_KIT_SPEC_NAMEandCANON_KIT_AMENDMENT_GLOBrather than by a.mdname carryingSPEC. Nothing to do at the defaults. - install disposition —
check-manifest-temporal,check-manifest-count,check-fence-command-head,check-comment-tier,check-spec-dod-singleton,check-spec-derivable-section,check-spec-embedded-source,check-amendment-queue,check-path-dialect,check-tree-terms,check-commit-msgandcheck-brevityare no longer registered at install, andcheck-knob-default-couplingnever is. The twelve hold your content to house rules rather than to defects, and the last can only skip in a vendored tree, whose kit SPECs it reads are withheld.updatedrops them from agates.listyou never edited; to keep one of the twelve, pass--with-gate <name>. CANON_KIT_SPEC_POINTER_TITLE_ONCE—check-spec-pointer’s one-title-per-file rule is off by default; its dangling-citation check is unchanged. Set the knobonincanon-config.knobsto keep the rule.- canon-kit
check-prose-tells, assertion G — its placeholder tokens and sentence openers are now the knobsCANON_KIT_PROSE_TELL_ABSENCE_TOKENSandCANON_KIT_PROSE_TELL_ABSENCE_OPENERS, each with an_EXTRA, defaulting to the lists G read before, so the verdict is unchanged. An opener ending in a space and…needs a word after it (the bundledno …), and an empty set turns its half of G off. Nothing to do; to add your own absence idiom (tbd, a non-English token), set the_EXTRAincanon-config.knobs. - unresolved registration — a
gates.listline naming a gate that resolves nowhere is reported once, by the runner, asFAIL: <name> (unresolved);check-gate-output,check-gate-fixture-coverage,check-gate-substrate-parity,check-graphandcheck-kit-enumskip it and count the skip. Nothing to do beyond dropping the stale line. # graph:rooted token, gate-sdk/checks/check-shellcheck.gate —knob:<NAME>/<glob>now admits a word-list knob, rooting the glob at each of its words (an empty list adds no trigger), andcheck-shellcheckcouplesknob:GATE_SDK_LINT_EXTRA_DIRS/*.sh, so a staged script under a directory that knob names fires the gate at commit rather than at the next full battery. Nothing to do; a descriptor of yours rooting a glob at a word-list knob, once refused bycheck-graph, is now admitted.--pack-installer, gate-sdkcheck-packed-links— the packer’s README rewrite also resolves a link to another packed kit’s SPEC,../<kit>/SPEC.mdwith or without a fragment, to the published location, so a kit README cites any kit’s SPEC relatively;check-packed-linksstops reddening that form, and its clean line countsSPEC link(s) resolved. Nothing to do.- context-kit
--emit md-indexand--emit pub-index—CONTEXT_KIT_PRUNE_DIRSis matched below each target, never on the target or a directory above it, so a checkout under abuild/,dist/,target/orworktrees/directory indexes whole where the arms printed their empty-case line before. Nothing to do. - installer
doctor, context-kit--emit env-probe— thebashaudience gains a fourth derivation arm: a member yourgates.listregisters that resolves to a.shin your gates dir owesbashthrough gate-sdk, sodoctorprobesbashfor a shell gate you wrote and refuses where it is absent or below its floor, where it read clean before. Nothing to do wherebashis installed; on native Windows, install Git for Windows before registering a shell gate. - new arm
--emit memory-dirs— prints the dirscheck-memory-offscans, one per line, aCONTEXT_KIT_MEMORY_DIRSset in your knob file included. Thesession-contexthook member (below) reads its memory backstop’s scan set from it. A script you copied from the removedsession-context.shtemplate still folds the root itself in step 5, only at/and., and misses the harness’s dir on any other character: move to the member, or in a copy you keep read step 5’s dirs from the arm’s output and drop itsREPO_ROOTline. - lifecycle-kit
--emit session-id, drift-kit and delegation-kit sessions dirs — the cwd slug is the harness’s encoder exactly: a character outside the Basic Multilingual Plane folds to two dashes, and a slug past 200 units is cut and hash-suffixed. Every ASCII root at or below 200 characters keeps its slug. Nothing to do. - context-kit/templates/settings-sessionstart.json, delegation-kit/README.md statusLine step — the statusLine command is now
bash "${CLAUDE_PROJECT_DIR}/gate-sdk/bin/run-gates.sh" --statuslineand the SessionStart command is rooted at${CLAUDE_PROJECT_DIR}too, so a session that changes into a subdirectory keeps its brief and its status bar. Re-spell the statusLine command you merged into.claude/settings.jsonthe same way, and point your SessionStart registration at thesession-contexthook member (below); a copied script you keep is registered asbash "${CLAUDE_PROJECT_DIR}/scripts/session-context.sh". A relative command exits 127 once a session leaves the root. doctrine-kit/DOCTRINE.mdScope-gated intake, lifecycle-kit/templates/lead.md — the rule names the closing stage’s drain as outside it, since the drain takes only debt: a mid-iteration gap, a found defect or an operator’s request converging on names your governed surfaces already carry, is fixed inline at the drain, and the digest reads “a mid-session initiative is filed as a costed Deferred entry by default, never started; work enters only through scope or an operator-ruled hotfix of an impacting failure, minimal and test-and-doc-complete in one commit; a debt-shaped gap is no initiative, fixed at close’s drain”. The lead template gains a mid-iteration intake section, a four-route test (a question, an impacting failure, debt, anything else) for an operator’s mid-iteration request; no slot is added or renamed.check-doctrine-registrationreds your agent file’s bullet until it matches the new digest: re-run--install-doctrineafter re-vendoring.- context-kit/checks/check-footprint-fresh.gate —
check-footprint-freshno longer ships with context-kit: its subject is the publisher’s footprint page, whose kit roster derives from SPEC files the payload never carried, so in your tree it had nothing to measure.--emit footprintis unchanged. Nothing to do; if you registered it by hand, drop itsgates.listline. - evidence-kit
check-evidence-baseline, knobEVIDENCE_KIT_DONE_SECTION— the queue section whose entries are finished is a new knob rather than a literal, so a queue naming its finished section otherwise is read right, and the stale-slug finding names that section. Nothing to do at the default; under another section name, set the knob inevidence-config.knobs. - lifecycle-kit
--emit session-id, drift-kit’s overhead meter, stage-economics and price-coverage arms, delegation-kit’s statusline, usage verdict and model verdict — on Windows the harness’s home is read fromUSERPROFILE, which the harness reads even withHOMEset, where these readers readHOME; elsewhere it staysHOME. A native-Windows session’s stage stamps, meters, usage snapshot and account config now resolve under the harness’s own home. Nothing to do. - lifecycle-kit/templates/consult.md, lifecycle-kit/templates/lead.md, lifecycle-kit/templates/stages/close.md, lifecycle-kit/templates/stages/scope.md — the consult inbox takes two classes, a ruling to reconsider and a request for advice. A direction question, an entry declined twice and a stage’s signal for the operator are asked live, through the lead or directly, and one the operator defers is filed to the gap inbox for the next intake: close’s drain and scope’s intake gain the asked disposition, and close’s roadmap-motion read asks the operator rather than filing a consult item. A consultation answers a request for advice, landing any change through the ordinary workflow marked
consult advice, <date>, oroperator direction, <date>, on consult adviceonce the operator accepts it, and a dispatched consultation answers advice alone and sends the recommendation to the lead. No slot is added or renamed. Nothing to do; an item in your consult inbox of neither class leaves by re-class at the next consultation. - queue-kit
--emit roadmap-lag, lifecycle-kit/templates/stages/close.md — a new arm prints each top-level entry outside the icebox whose[roadmap:]horizon is not the firstQUEUE_KIT_HORIZONSmember, or which carries no roadmap tag, and under which a split child sits in the done section:<slug>⇥<horizon or ->⇥<slices>, exit 0 with the report and 2 with no configured horizon. Close’s roadmap-motion read now runs it and reads its rows first for a met horizon condition, routing a row no condition settles with the read’s other findings. Nothing to do; with no horizon configured the arm exits 2 and close skips the read. - lifecycle-kit/templates/lead.md — an operator-ruled hotfix landing after the closing push now owns that push: its policy gains pushing and watching every run it triggers to green, within your close binding’s
push-budgethotfix allowance, and the lead’s no-push line names the exception. No slot is added or renamed. Where yourruling-confignames a hotfix type, add the push-and-watch rule to its definition, after your own pre-push account step. - lifecycle-kit/checks/check-skill-binding.gate —
check-skill-bindingcoupleskit:templates/*.mdin place of a by-name list of bound templates, so an edit to any kit template fires it at commit, a template a later shim binds included. Its verdict is unchanged. Nothing to do. - lifecycle-kit/templates/stages/spec.md, lifecycle-kit/templates/stages/align.md, lifecycle-kit/templates/stages/build.md — a delta changing what an adopter or a published-surface reader meets carries
{user-facing: <direction>}beside its work-class tag, naming the recorded direction that settles the choice; the authoring stage escalates one no direction settles, the audit stage reads every delta for the mark, and build’s triage stops only on user-facing semantics no mark settles. No slot is added or renamed. Nothing to do. - gate-sdk
check-kit-roots-dialect— its vendoring children, the git steps and the compared arms, run with git’s repository locators stripped, so a partial-path commit (git commit -o <paths>), whose hook git hands a relativeGIT_INDEX_FILE, no longer exits 2 on it. Nothing to do. - queue-kit
## Lessons Learned, lifecycle-kit/templates/stages/close.md, lifecycle-kit/templates/stages/validate.md — the lesson channel is consumer-optional and ships on: a queue that leaves the section out of its file and out ofQUEUE_KIT_REQUIRED_SECTIONSruns without it, close’s step 1 skips, the boundary entry passes, and validate files a method observation to the gap inbox instead. No slot is added or renamed. Nothing to do to keep the channel; to turn it off, drop the section and bindQUEUE_KIT_REQUIRED_SECTIONSwithout it (Knob changes). - gate-sdk/checks/check-gate-binary-fresh.gate, gate-sdk/checks/check-crate-arms.gate, gate-sdk/checks/check-kit-roots-dialect.gate, gate-sdk/checks/check-packed-links.gate — the four no longer ship with gate-sdk. The first two hold the publisher’s crate, which your tree never carried;
check-kit-roots-dialectre-checks the binary’s own derivation over a fixture, a red you could not repair;check-packed-linksreads the packer’s output, and only the publisher packs a payload. Nothing to do; if you registered one by hand, drop itsgates.listline. - gate-sdk
check-install-disposition— assertion C reads the installer recipe atinstaller/recipe.rsunderGATE_SDK_NATIVE_SRCrather than at a literal crate path. Nothing to do; your tree carries no recipe, which stays a skip on the clean line. - guard-kit shell guard, the bash reader’s compound and statement splits, rule
find_exec— a;after an odd run of backslashes no longer splits a call, as the harness’s matcher does not, so a command carrying an unquoted\;reaches every rule as one fewer segment: rulefind_execreads-exec … '{}' \;as one segment ending in its terminator, and a\;(a literal backslash, then a real break) carries no terminator and passes. Nothing to do. - every hook member’s and emitter’s printed remedy and usage line — the workflow-state and agent-dispatch hooks,
--diff-baseline,--install-hooksand the surface ceiling’s retired-unit refusal print their remedy on the binaryGATE_SDK_NATIVE_BINnames,./-prefixed when relative, instead of the bash front end, and a member’s own usage line names its arm and no program (usage: --model-verdict …,usage: --run-validate (it takes no argument)). Where either hook cannot resolve the knob, its block (the workflow-state rules and the agent-dispatch hook’s bound tier, D6) still blocks, naming the fault where the command would stand. Nothing to do; a script of yours matching the front end’s usage line on a member’s refusal matchesusage: --<arm>instead. - delegation-kit/templates/agent-execution.md, lifecycle-kit/templates/lead.md — the Background + notification, never poll bullet says a wait owes a producer that will make its condition true and that a question to your caller is a turn end, never a wait, and it bounds its notification claim: a notification wakes a supervisor when it arrives after the turn has ended. The lead template has a lead make one tool call before any turn it ends to await a completion notification. No slot is added or renamed. Propagate the bullet’s two new clauses into the operative copies in your always-loaded agent definitions.
- lifecycle-kit
--emit file-gap, knobLIFECYCLE_KIT_DISPOSED_FILE— a new knob (default${GATE_SDK_WORKFLOW_DIR:-.workflow}/disposed-findings.txt) names a committed record of the gap findings your drains discarded, one- <date> — `<term>` — <finding and ground>line each. The arm reads it after every filing, and where a record’s term occurs in the prose, case-insensitive and word-bounded, prints a stderr advisory naming that record; stdout stays the filed bullet. Nothing to do; the arm stays silent until a record exists. - lifecycle-kit/templates/stages/close.md, lifecycle-kit/templates/stages/scope.md — a →discard at close’s gap drain or scope’s carried-bullet intake appends its record to
LIFECYCLE_KIT_DISPOSED_FILE, or cites the record the bullet matched whose ground still holds, and close’s re-verification deletes a matched record whose ground fell. No slot is added or renamed. If you copied either template out, re-copy close’s step 2 and scope’s second step. - evidence-kit
--emit parse-smoke-log— the arm gains a log-only form: given the log alone, it reads the arm roster from the log’s ownsmoke-roster: <name>lines, which must precede the first header, the last naming the completion marker, and a log declaring fewer than two is exit 2. The driver form,--emit parse-smoke-log <driver> <log>, is unchanged. Nothing to do; a driver that prints its roster at the head of its log can drop the driver operand fromEVIDENCE_KIT_PARSER_<suite>. - lifecycle-kit
check-close-surfacesand--emit close-surfaces, every packed kit root — a kit whose spec declares close surfaces now ships those declarations asclose-surfaces.txtin its root, and the derivation reads that file beside each kit root’s spec. A kit-owned capture log you gitignore as guard-kit’s, drift-kit’s or delegation-kit’s README instructs is therefore declared in your tree, and assertion A no longer reds it. The gate’s help namesupdateas the remedy for an undeclared kit-owned log. Runupdateto vendor the file; a hand-declaration or a widenedLIFECYCLE_KIT_CLOSE_SURFACE_GLOBSyou added for those logs is no longer needed. - guard-kit shell guard, the bash reader’s compound, statement and pipe splits — a
|or a newline after an odd run of backslashes no longer splits a call, as the harness’s matcher does not, so a command carrying an unquoted\|or a backslash-continued line reaches every rule as one fewer segment, and a read-only call so spelled is granted where it fell through. An even run is a literal backslash and then a real break. Nothing to do. - guard-kit shell guard, new rule
bounded_write, knobGUARD_KIT_WRITE_BINS, guard-kit/templates/guard-config.knobs — a new generic rule grants a shell write whose every target lies strictly inside a relativeGUARD_KIT_SCRATCH_DIRSmember, is gitignored, holds no tracked path, reaches through no symbolic link and, for a removal, holds no live producer record: aGUARD_KIT_WRITE_BINSutility (defaultmkdir touch rm rmdir mv cp tee find, each read by a kit operand grammar) or a redirect from a read or an allowlisted command, alone or compounded with reads and allowlisted commands. Such a call was decided out of band, or blocked by ruleallowlist_chainor ruleemitter_write, and is now granted. The template carries the commented example. Nothing to do; an emptyGUARD_KIT_WRITE_BINSturns the utility grants off and keeps the bounded redirect. - guard-kit shell guard, rule
ro_pipeline— a compound of read-only statements joined by;,&&or||is granted as a newline-joined one already was, each statement read on its own and an allowlisted bare lead admitted at the head of any statement; only a backgrounding&still refuses. A batch of roster reads, or an allowlisted command chained with them, no longer reaches the harness’s decision. Nothing to do. - guard-kit shell guard, rule
rm_tracked— a barermof a directory holding a tracked file now steers togit rm -q -r <dir>, which git accepts and the recommendedgit rm -qgrant still matches, instead ofgit rm -q <dir>, which git refuses without-r. Nothing to do. - guard-kit shell guard, the bash reader’s dequoted view — a blank or tab after an odd run of backslashes stays inside its word, as the shell reads it, so a slot operand spelled
x\ yis one operand and rulegrant_path_slotno longer blocks it, rulebounded_writegrants a bounded target so spelled, rulecommit_only_pathsreadsgit commit -m fix\ itas a pathless commit, and rulesed_filesteers anawkline-range read of a file so spelled to the Read tool and reads an inline python program so spelled whole. Nothing to do. - gate-sdk
check-graph, assertion G — an inline span in aSPEC-*.mdamendment is read as a manifest only when its first word after the manifest header is a<key>=<value>pair with a value, so a sentence naming a bare key no longer reds for the four required keys. Nothing to do; an inline manifest meant to be held leads with a valued key. - gate-sdk
check-gate-substrate-parity, in a tree that carries no crate source — a missing default<gate-sdk root>/SPEC.mdno longer exits 2: disposition coverage and thereference-onlyallowance are not asserted, the clean line counts the members left undispositioned, and every other assertion now runs where the gate refused. A doc given as a positional still exits 2 when missing. Nothing to do. --emit enforcement-map— the kit column writes a kit’s name as plain text where the page’s directory tracks no<kit>/index.md. Regenerate the page; nothing else to reconcile.- gate-sdk knob resolution,
.gatedescriptors — a descriptor in your gates directory may declare a knob no kit owns on a# knob:line, one knob-file line naming aGATE_LOCAL_knob, which resolves from the environment and then the declared value. A# knob:line naming a kit’s knob, or a name two descriptors declare, is refused at exit 2. Nothing to do; no kit-shipped gate reads such a knob. --emit product-statement,--emit llms,--emit support-table,--emit value-rollup --write,--pack-installer— in a tree whose gates directory declares none of theirGATE_LOCAL_knobs, each still exits 2, naming the undeclared knob rather than the missing file; the bare--emit value-rollupis unchanged. Nothing to do; these arms serve the publishing repository’s own projections and packer.- canon-kit
check-comment-tier—# knob:joins the built-in machine directives, so a descriptor’s knob declaration lines need no directive window or exemption. Nothing to do. - delegation-kit
--foreign-run, new arm--foreign-resume, knobsDELEGATION_KIT_FOREIGN_RESUMEandDELEGATION_KIT_FOREIGN_SESSION_MARKER, delegation-kit/templates/delegation-config.knobs, delegation-kit/templates/agent-execution.md — a foreign adapter you give a resume form (default empty, every adapter one-shot as before) keeps its clone onOKas an open session, recorded insession.txtbeside the clone, and its verdict line ends— resumable: --foreign-resume <key> <prompt-file>.--foreign-resume <key> <prompt-file>runs the next turn in the same clone and vendor session (exit 0 OK, 1 REFUSED and the session ends, 2 FAILED and it stays open), and--foreign-resume <key> --closeremoves the clone and keeps every report. The marker knob names the literal your vendor prints its session id after, which a form resuming by@SESSION_ID@needs. A--foreign-rununder a key holding an open session now fails. The knob template carries the commented examples, and the protocol template’s foreign-run bullet tells you to ask the agent to end its turn with its open questions. Nothing to do; to make an adapter resumable, add its resume form, restating its sandbox, and its marker in your gitignored.localknob overlay, and leave its open form non-ephemeral. - delegation-kit
--usage-verdict,--emit usage-trend, knobDELEGATION_KIT_LOGIN_SETTLE— the verdict reads the live account identity (oauthAccount.accountUuidfromDELEGATION_KIT_ACCOUNT_CONFIG) and compares it with the snapshot’saccount. A snapshot naming another account is re-polled pastDELEGATION_KIT_REFRESH_MIN_AGEwhen a refresh command is set, and one still naming it routes a would-be OK toSTALE (snapshot predates an account switch; …). Under the same account the post-login reroute holds for the new knob’s settle floor (default90seconds) after the credentials file’s mtime instead of the wholeDELEGATION_KIT_LOGIN_WINDOW, which stays the fallback where either identity is missing. The roll witness reads the newest history sample for the snapshot’s own account. The trend report closes with a per-account weekly view and an at-or-over count where the log carries two or more accounts. A PAUSE is never suppressed. Nothing to do; a consumer whose snapshot carries noaccountreads as before. - delegation-kit/templates/agent-execution.md, delegation-kit
--usage-verdict’swidth=field — the Serialize on shared files bullet tells a dispatcher to count every read-only dispatch still in flight againstDELEGATION_KIT_FAN_WIDTH, those sent in one message included, and states that thewidth=field reports the bound while nothing refuses a dispatch past it. No guard rule, knob or field is added or changed. Nothing to do. - lifecycle-kit/SPEC.md §The audit roster, lifecycle-kit/templates/stages/close.md — close step 8 gains a third disposition beside perform and defer: a class whose sweep runs on an executor outside the session, such as a foreign vendor’s agent, is skipped with notice when that executor is unavailable or its spend unauthorized, its
last:unmoved so it stays due. No slot is added or renamed, andcheck-skill-bindingstays green, so a bound close shim needs nothing. If you copied close out, re-copy step 8. - delegation-kit
--emit usage-trend— a segment’s endpoints, token deltas, weekly headroom and combine line read its own first and last smoothed samples, where they read the first and last non-suspect ones, so a segment ending in a downward correction reports the corrected reading and an account at or over the weekly ceiling counts as such; the rate still leaves suspect samples out, and a segment with one trusted reading printsn/a (one trusted reading)for it. Nothing to do. - delegation-kit
--foreign-resume, delegation-kit/templates/agent-execution.md — a sweep session’s resume turn that endsFAILEDafter its spawn still regenerateschange.patch, so the patch a closed session keeps carries that turn’s change, and the line names it; the template’s foreign-run bullet adds that nothing else runs under a key while a turn runs, since the kit serializes no commands sharing a key. Nothing to do. - checkwright init, update — a kept
gates.listnow reports each gateinitno longer registers there as aretire:line naming its# install:disposition, a gate moved offzero-configamong them, read off the registryinitlast wrote. Remove the line, or keep the gate with--with-gate. - checkwright init, doctor,
--emit update-notice— an installed tree now checks for a newer Checkwright release: new knobsGATE_SDK_UPDATE_CHECK(off,dailyorweekly),GATE_SDK_UPDATE_UPSTREAM(written byinitfrom the package’s repository) andGATE_SDK_UPDATE_TIMEOUT, gate-sdk/SPEC.md owning their defaults. At most once per interval it runsgit ls-remote --tagsagainst the upstream, which shows that host your IP address, a failed attempt counting toward the interval; reads that find the check due together each run it, and every read does where the cache cannot be written; baredoctorgains alatestline andinitstates the check. Its cache,checkwright-update-checkin your clone’s git directory, is keyed on the upstream and replaced whole, anduninstalldeletes it, reporting a deletion that fails. Thesession-contexthook member (below) prints the notice in the session brief. Nothing to do, or setGATE_SDK_UPDATE_CHECKoff in your gates directory’sgate-sdk-config.knobs; a script you copied from the removedsession-context.shtemplate prints no notice until you move to the member or add a guarded--emit update-noticecall on the gate binary as its last step. - release notes,
--upgrade-smoke— a note now carries six sections, In brief, New and tightened gates, Knob changes, Gate-authoring changes, Platforms and Behavior changes, and In brief opens with a table linking each declaration-bearing section beside its entry count and who acts on it. New and tightened gates was Tightened gates and Knob changes was Renamed knobs; both former headings stay readable for good, so--upgrade-smokereads a gates section under either name at any FROM and TO, resolving the names in your tree. Nothing to do; a consumer reading notes by heading reads either spelling. - lifecycle-kit/templates/stages/build.md — the paragraph on declaring what a vendoring consumer will meet also triggers on a knob a unit adds and on a change to the gate-authoring contract (the gate library, the descriptor grammar, the skeleton or the runner’s member contract). No slot is added or renamed. Nothing to do.
- lifecycle-kit/templates/upgrade.md — step 2 names the New and tightened gates and Knob changes sections and adds reading Gate-authoring changes if you write or shadow gates. No slot is added or renamed. Nothing to do.
- docs/install.sh, docs/install.ps1 — the one-line install now checks the Release tarball’s build attestation, bound to that release’s tag, where
ghis installed and holds a credential, refuses one that fails it, and says so where it went unchecked. A check that cannot reach GitHub’s API refuses too, naming gh’s error: retry once it clears. Nothing to do. - lifecycle-kit
--emit ruling-staleness, drift-kit--price-coverage— an oracle or price-page command outliving its timeout is now killed with every process it started (its process group on unix, a job object on Windows where the host makes and joins one, else the command alone), not only the command itself, so a helper it spawned no longer runs on past the bound. Nothing to do. - queue-kit queue-history arm,
--queue demoteandthaw, the index and pub-index arms,--emit file-surveywithfile-gapandkfric,--emit ruling-staleness,--upgrade-smoke,--emit pack-installer, checkwright init, diff and uninstall — in a repository git refuses to answer for (asafe.directoryownership refusal, broken repository metadata), each now refuses at exit 2 naming the refusal rather than reading the tree as no repository: the history arm no longer prints the slug alone, the capture arms no longer write at the working directory, andinit,diffanduninstallname repair of git’s access in their help rather thangit init. The roadmap-lag arm still degrades, naming git’s refusal as its reason. Nothing to do; repair git’s access (git -C <dir> statusprints git’s reason). - the battery’s
--helptext, its gates-dir steer andcheckwright demo’s source-checkout help — the usage text names each form by its arm and no program (usage: --run [gates-dir],--only <name>…,--run -h | --help), every form valid on the gate binary and the front end alike; the steer for a gate name passed as a gates dir prints<door> --only <name>on the binaryGATE_SDK_NATIVE_BINnames, and is left off where that does not resolve; the demo’s help names the binary’s--run-demoarm. A script matchingusage: run-gates.shnow matchesusage: --run. - the gate binary’s directory walk — on Linux and macOS a directory whose name ends in a backslash is now descended at its own path, where the walk read the backslash as a separator and stated a path that does not exist, so every gate walking the tree now reads and grades a file under it. Nothing to do.
bin/run-gates.sh,bin/run-gates.ps1— in a directory a repository mark covers (a non-emptyGIT_DIR, or a.gitentry here or above, short ofGIT_CEILING_DIRECTORIES) where git answers no toplevel, the front end now printsrun-gates: git refuses the repository marked here (GIT_DIR, or a .git entry here or above); git status prints its reasonin place ofrun-gates: not inside a git repository, still at exit 2 for every arm but the fail-open pair (below). Nothing to do; repair git’s access to the repository.--enter-stage,--install-lifecycle,--emit queue-edges,--emit entry-history, the queue-history arm,--emit port-blockers --treeand--emit close-surfaces— in a repository git refuses, a nested one git skips included:--enter-stagewith the worktree check on refuses the entry at exit 2, writing nothing, where it skipped the check;--install-lifecyclestill skips the driver step at exit 0 but names git’s refusal as the cause; the queue arms exit 2 where they read an empty retired set; the other two name git’s refusal rather thannot a git repository. Nothing to do; repair git’s access to the repository.bin/run-gates.sh,bin/run-gates.ps1,lib/gate.sh— run from beneath a nested.gitgit skips (a garbageHEAD, an empty.gitdirectory, a.gitsymlink naming nothing), the front end now refuses withrun-gates: git skipped the repository a .git entry marks between here and the toplevel it answered, and answered the enclosing one; git --git-dir=<that .git> status prints its reasonwhere it ran the enclosing repository’s battery; the newgate_skipped_markaccessor holds the test. Every front-end refusal, a failed lookup included, now exits 0 for--hookand--statusline,--hookwriting asystemMessagesaying the tree’s guards are off, where a hook arm exited 2 and blocked the call; other arms keep exit 2. Thesession-contexthook member (below) prints no brief in such a tree, the front end declining first; a script you copied from the removedsession-context.shtemplate still changes to the enclosing toplevel and briefs that repository, until you move to the member or guard itscdongate_skipped_markreturning 1. Otherwise nothing to do; repair the nested repository or remove its.git.- drift-kit
--emit manual-ops, drift-kit/templates/economics.md — a new arm ranks the shell shapes and repository-relative paths an iteration’s sessions repeated by hand, over the transcripts the stage-economics meter attributes to it:<calls> <sessions> <iterations> <kind> <key> [<rows>], exit 0 always bar a usage error, logging only the printed keys under your metric dir and never command text. The/economicstemplate chains it as step 3 and gains a Repeated manual operations narrative bullet. No slot is added or renamed. Nothing to do; a bound/economicsshim picks it up. -
bin/run-gates.ps1,lib/gate.sh, and every gate and arm refusing a nested repository git skips — under aGIT_WORK_TREEabove the repository git selected, the PowerShell front end now compares that repository and git’s answer as physical paths, so a.gitsymlink or a gitfile naming a path through a symlink or junction no longer refuses where the bash front end and the binary ran on; and a gitfile whosegitdir:line ends in more than one carriage return is read as git reads it ingate_skipped_mark, while the binary no longer strips a trailing blank git refuses. Nothing to do. bin/run-gates.ps1— a gitfile whosegitdir:target takes a..past a symbolic link or junction is now resolved as the filesystem reads it, the link first and its parent second, where the PowerShell front end folded the..before following the link and could refuse the selected repository as one git skipped. Nothing to do.bin/run-gates.sh,lib/gate.sh— the bash front end now reads a git directory, a gitfile target and aGIT_CEILING_DIRECTORIESentry physically, so a..past a symbolic link means the link’s physical parent even where the lexically folded path is itself a directory; it could refuse the selected repository as one git skipped where git, the binary and the PowerShell front end ran on. Nothing to do.--diff-baseline,--run-validate— the baseline diff now reads a final line carrying no trailing newline, in the skip file and in a suite parser’s output alike, where it dropped that line: a self-skipped scenario recorded on it is demoted from pass, and an observedfailon it with no baseline row is a new failure. Nothing to do; a harness or parser ending its output in a newline sees no change.- lifecycle-kit
--emit journal,--enter-stage’s journal note, delegation-kit/templates/agent-execution.md — a new arm,--emit journal [--] "<text>", appends its operand, or standard input when none is given, to the resume journal of the stage the calling session’s own last stamp names, creating the file where absent and printingjournal: <path> <stage> <id>, the path resolved against the repository root; an empty text, a second operand, a caller no stamp names and a linked worktree are refused at exit 2. Where the id the arm derives carries no stamp, as while a child the session dispatched holds the newest transcript, it walks the derivation’s own transcript scan newest-first to the first id a stamp carries, and refuses naming the derived id when none does. Where--enter-stageopens a journal its note now names the arm, and prints the journal’s path resolved against the repository root where it printed the pattern’s relative spelling, and the template’s stage-session parenthesis cites the arm. Nothing to do; a stage session may append through the arm where it spelled a redirect onto the printed path, which stays legal and now lands from any working directory, and a script matching the note’s relative path matches the resolved one instead. - site-kit/templates/site-health.yml — the probe set is now resampled before it reports: up to three attempts thirty seconds apart, and only a set failing on the last attempt reaches the failure path, so one transient no longer reds the run. A copy you already made is unchanged; re-copy the template, carrying your step env and any arm of your own across, to take it.
- lifecycle-kit/templates/stages/close.md, queue-kit/SPEC.md §The queue format — a promoted entry’s
Filedline carries the date, the filer and at most one attribution, and close’s →promote clause says so: the inbox that captured the finding, the promoting stage and the dispositions tried first stay in the promoting commit’s message. No slot is added or renamed, and no gate reads the line beyond its date. Nothing to do; an entry already carrying its route stays valid. - lifecycle-kit
--enter-stage’s journal opener and predecessor assertion,--emit journal— a relativeLIFECYCLE_KIT_STAGE_JOURNAL_PATTERNis now resolved against the repository root by the opener and the predecessor assertion as the arm already resolved it, and against the working directory outside a repository, so the gate binary called directly from a subdirectory opens, asserts and appends one file where it named two. The arm refuses an absent state file as an unstamped caller, naming the id and--enter-stage <stage>, where it reported a read failure, and appends standard input byte for byte where it replaced a byte that is no UTF-8. Nothing to do; a call throughbin/run-gates.shorbin/run-gates.ps1already ran at the repository root. - lifecycle-kit
--emit journal --lead, templates/lead.md — a second form of the journal arm,--emit journal --lead [--] "<text>", appends its operand, or standard input when none is given, to the lead journal<scratch>/$LIFECYCLE_KIT_LEAD_JOURNAL_FILEand printsjournal: <path> lead, reading no stamp. It refuses at exit 2 an absent journal file and one whose last segment is disposed, each naming--enter-stage --open-lead-journal, beside the stage form’s refusals.--leadis read only as the first argument, so a stage-form text that begins with it is passed after--. The lead template now instructs the form; a copied-and-specialized lead skill may adopt it or keep its raw append, which stays legal. --install-hooks, the git hooks — the hooks are no longer tracked sh files under<gates-dir>/git-hooks/.--install-hooksnow places the gate binary under each hook’s name,pre-commitandcommit-msgwith the host’s executable suffix, ingate-hooksunder the clone’s git directory, and pointscore.hooksPaththere, so git starts a hook with no shell and opting in changes no tracked file. It exits 2, placing nothing, where the binaryGATE_SDK_NATIVE_BINnames is absent or outside a repository, and exits 2 ahead of its identity check and itsActive hooks:receipt where thecore.hooksPathwrite fails, naming the placed directory git is not pointed at; a failedblame.ignoreRevsFilewrite is still only reported. After upgrading, re-run--install-hooksonce per clone and then delete<gates-dir>/git-hooks/: until you do, the clone keeps running the tracked hooks it had, which still work. A placed hook readsGATE_SDK_NATIVE_BINby name and judges no other line of your gate-sdk knob files, so a hook older than the installed binary no longer refuses a commit over a knob the newer build added; the binary it starts still refuses every line the hook skipped. The new--install-hooks --refreshre-places both hook files in a clone already opted in and changes nothing else, printinginstall-hooks: refreshed <hooks-dir>, and is a silent exit 0 in a clone not opted in or off the work-tree top;gate-sdk/bin/build-native.shruns it after a host build. Nothing to do:updaterewrites the binary in place and a linked hook follows it; run--refreshafter replacing the binary’s file yourself, or where the hook was placed as a copy.--emit git-hooks,init,--git-hook commit-msg— the--emit git-hooksarm is removed and no hook file is generated, so a script or CI step of yours calling it drops the call.initwrites no hook, and its follow-up--install-hooksline is the one step that puts a hook in a clone.--git-hook commit-msgexits 0 printing nothing while notier=commit-msggate is registered, and a gate registered at that tier later is reached with no re-install.--git-hook,--for,--measure-commit— a registered gate whose declaration resolves and cannot be read (a mode that denies the read, say) now refuses at exit 2 naming the file,<hook>: cannot read the declaration <path> …from a hook andrun-gates: --for cannot read the declaration <path> …from the selector, where the gate dropped out of the selection and the commit or the run passed without it. Nothing to do; restore read access to the named file.uninstall,doctor—uninstallremovesgate-hooksfrom the clone’s git directory before it commits and prints thegit config --unset core.hooksPathline when the clone still names it;doctorprints ahooksline, not opted in, opted in with both hooks present, or opted in with one missing, and fails on none of them. Nothing to do.- guard-kit’s knob-prefix steer — the shell guard’s message for a kit knob assigned the value it already resolves to no longer adds the sentence on a respelled path reddening
check-graph. Nothing to do. - context-kit/templates/session-context.sh, new hook member
--hook session-context— the template is removed and the session brief is assembled by a member of the gate binary;templates/settings-sessionstart.jsonnow registersbash "${CLAUDE_PROJECT_DIR}/gate-sdk/bin/run-gates.sh" --hook session-context. Your copied script keeps working and is no longer updated. To move, set what you edited into it as theCONTEXT_KIT_BRIEF_*knobs (Knob changes), repoint your SessionStart registration at the member, then delete the copy. The member prints the brief your copy printed, except that outside a git repository the front end declines and no brief is printed. - drift-kit/templates/kpi-deprecated-surface.sh,
templates/kpis.list— the example plugin is removed andkpi-deprecated-surfaceis a bundled member the registry template names. It counts marker lines over the surfacecheck-deprecation-taskscans, so a count can step with no marker moving where yourCANON_KIT_COMMENT_SURFACEglobs reached a pruned directory. A copy of the plugin in your KPI dir still shadows the member; delete it to take the member. A registry copied from the template after this release shows onen/arow until you setCANON_KIT_DEPRECATION_MARKERSor prune the line. - new arm
--emit consult-count— printsConsult inbox: <n> item(s) owed to /consult, oldest <date>.when the consult inbox holds a bullet and nothing when it holds none, exit 0 both ways. Nothing to do; add it toCONTEXT_KIT_BRIEF_COMMANDSto carry the line in your session brief. - guard-kit shell guard, new rule
wait_no_producer— a backgrounded wait loop whose condition is shell tests alone ([ … ],[[ … ]],test) and whose body only sleeps is now blocked while no*.runrecord under aGUARD_KIT_SCRATCH_DIRSmember names a live PID, where rulebounded_waitgranted it. A session whose producer is real launches it first with its liveness record, in the spelling rulebackground_no_recordnames, and the wait then passes; akill -0 <pid>or a command condition, and a foreground wait, are unchanged. A banner or a read ahead of the loop is read past, soecho start; until [ -f <marker> ]; do sleep N; doneblocks as the bare wait does. The rule applies tobashalone and takes no knob. Nothing to do unless a script or prompt of yours backgrounds a marker wait before its producer is recorded: record the producer first, or wait on its pid. - delegation-kit
subagent-stop-livenesshook — the task-held refusal’s stderr now names a second exit beside awaiting the task’s completion notification: where the task is the session’s own wait and nothing will make its condition true, stop it. The decision is unchanged. Nothing to do. - delegation-kit/templates/agent-execution.md — the Background + notification, never poll bullet now says a backgrounded wait on a file condition is refused at its launch while no launch record names a live producer, and forbids launching a wait to hold a turn open. No slot is added or renamed. Propagate the two clauses into the operative copies in your always-loaded agent definitions.
- delegation-kit
--foreign-runand--foreign-resume, new form--foreign-run <adapter> --budget, delegation-kit/templates/delegation-config.knobs, delegation-kit/templates/agent-execution.md — a foreign adapter you give a usage snapshot (Knob changes; default none) is now budgeted: both arms read §usage-verdict’s rule over that snapshot before they spawn, pause axes ahead of the stale-age check, and a paused window clones and spawns nothing, printingFAILED (budget: …)at exit 2 with the session’s turn held. A stale or unreadable snapshot never blocks a run. Both verdict lines gain abudget=<OK|RESET-OK|STALE|OFF|PAUSE|->field, afterkey=on a run and afterturn=on a resume, so a script matchingkey=<key> exit=orturn=<n> exit=matches across the new field.--foreign-run <adapter> --budgetprints the adapter’sforeign-budget:line and spawns nothing: exit 0 OK or RESET-OK, 1 PAUSE, 2 STALE or OFF. The kit ships no producer. The template’s foreign-run bullet adds reading the budget before choosing an adapter and waiting out aFAILED (budget: …). With no snapshot configured every line readsbudget=OFFand nothing else changes. - delegation-kit
--foreign-run,--foreign-resume,--foreign-run <adapter> --budget— the keyed verdict now reads the long window ahead of RESET-OK: a snapshot whose short window has reset still pauses, exit 1 andFAILED (budget: …)with nothing spawned, where it carries a well-formed long pair at or overDELEGATION_KIT_FOREIGN_PAUSE_PCT_LONGwhose own reset has not passed; it readRESET-OKthere and ran. The line names the long window and says the short one rolled over. A snapshot with no long pair, an under-threshold one or a reset one reads as before, and--usage-verdict’s order is unchanged. Nothing to do; a producer that writes no long pair is not reached. - new arm
--emit stage-contract <stage>, new templatelifecycle-kit/templates/frames/stage-contract.md, lifecycle-kit/templates/stages/align.md, templates/lead.md, delegation-kit/templates/agent-execution.md — the arm prints the frame (Knob changes), a separator line and your<LIFECYCLE_KIT_SKILLS_DIR>/<stage>.mdverbatim, a stage’s contract for a foreign agent that loads no skill and writes nothing; exit 2 on a stage outsideLIFECYCLE_KIT_STAGES, an absent skill, an absent or empty frame, or a surplus operand. The align template gains a host protocol, read only by a session whose dispatch names a foreign adapter: the session stamps and holds the stage, runs the document as an audit-mode--foreign-run, and performs every proposed write itself. The lead template dispatches such a stage as an ordinary stage session with the adapter named in its prompt, and the agent-execution bullet says a stage’s contract runs foreign only as its stage template says, its lead sentence no longer calling every foreign run mechanical work. No slot is added or renamed. Nothing to do with no foreign adapter configured; propagate the paragraphs into a copied-and-specialized align or lead skill to use it. - delegation-kit/templates/agent-execution.md — the Background + notification, never poll bullet now gives a dispatched session the wait it owes an
Agentchild: with one still running and nothing else to run, it ends its turn without the hand-back call, and the child’s hand-back arrives as its next turn. The bullet bounds the claim to a harness that delivers a report through a hand-back call of its own. guard-kit rulewait_no_producer’s corrective names the same turn end. No slot is added or renamed. Propagate the clause into the operative copies in your always-loaded agent definitions. - new file
lifecycle-kit/templates/host-protocol.md, lifecycle-kit/templates/stages/align.md, scope.md, spec.md, validate.md and close.md — the host protocol leaves the align template for one file, worded for whichever stage its reader entered, and each of the five templates carries one line pointing at it, read only by a session whose dispatch names a foreign adapter. Two steps widen: the host appends to the prompt nothing read from a file your repository ignores, and it runs every command the report names as refused or as needing a built artifact, where it ran the consistency gate alone.--emit stage-contractis unchanged. No slot is added or renamed. Nothing to do with no foreign adapter configured; to send a stage foreign from a copied-and-specialized skill, carry the pointer line in it, and replace a copied align skill’s seven steps with it. - lifecycle-kit/templates/lead.md — the lead now reads
--emit knob-values LIFECYCLE_KIT_STAGE_EXECUTOR(Knob changes) before it dispatches a stage. Where an element binds the stage it reads that adapter’s--foreign-run <adapter> --budget, names the adapter in the dispatch prompt onOK,RESET-OK,STALEorOFF, and dispatches on its own harness onPAUSE, journalling the line. No slot is added or renamed. Nothing to do while the knob is empty; propagate the paragraph into a copied-and-specialized lead skill to use the binding. - lifecycle-kit/templates/consult.md, new template
lifecycle-kit/templates/frames/project-critique.md— a consultation gains an outside-critique step, run only when the operator asks for one in that session: it copies the new frame to a prompt file, appends the operator’s focus and nothing from an ignored file, runs it as an audit-mode--foreign-run, puts clarifying questions with--foreign-resume, and lands what survives its own checking as it lands any advice. It offers one only whereLIFECYCLE_KIT_CRITIQUE_ADAPTER(Knob changes) names an adapter, and a consultation a lead dispatched runs none. No slot is added or renamed. Nothing to do with the knob empty; propagate the paragraph and the dispatched-mode clause into a copied-and-specialized consult skill to use it. - delegation-kit/templates/agent-execution.md — the Background + notification, never poll bullet no longer grounds its rule in a dispatched agent’s turn end being its session end, which one harness build contradicts: it now says the report is all the caller holds, and that what becomes of the session after such a turn end differs by harness and is relied on by no one. The rule is unchanged: no turn ends on work still running. No slot is added or renamed. Propagate the wording into the operative copies in your always-loaded agent definitions, where one states the old premise.
- delegation-kit
--hook subagent-stop-liveness— a firing from a linked worktree now reads the main checkout’s record set: a relativeGATE_SDK_TMP_DIRresolves against the main checkout there, where it resolved against the worktree and so read an empty set beside a live producer the isolated session had itself recorded. An isolated child’s turn end is therefore refused while any*.runrecord in the main checkout names a live producer, as a child in the main checkout already is. An absolute scratch dir is read as before. Nothing to do on the default reader; aDELEGATION_KIT_LIVENESS_CMDoverride that cannot run inside a worktree now refuses ascorrupt, unbounded, while any record stands in the main checkout, so make it resolve there. - every subcommand and arm of the gate binary — a write the binary itself makes to stdout whose reader has gone is now dropped, with every later one, where the process died on a panic trace at exit 101: an arm or gate piped into a filter that exits early (
| head -1,| grep -q) runs to its end and exits its own status, 0, 1 or 2. A pipeline underpipefailthat read the 101 as a failure now reads the arm’s own status; a caller that needs the whole document read checks its reader’s status. Any other stdout failure, a full disk under a redirect among them, still fails. An arm that hands a child the binary’s own stdout (--scratch-run, a--run-validatepre-hook) returns that child’s status, which on Unix is 141 where the child writes after its reader has gone. - gate-sdk
--install-hooks,--install-hooks --refresh— a hook is now replaced by writing the new file under a sibling name and renaming it over the old one, where the old file was removed first: a re-placement whose link and copy both fail still exits 2, and now leaves the hook already placed in place, where it left the first hook absent and git ran no gate at commit. The sibling is named<hook>.new.<process id>.<a value drawn per run>and is created, never opened, so two placements at once keep to their own, in containers sharing one checkout included, and a placement that finds its name taken refuses at exit 2. A sibling the run cannot remove is now reported on stderr with its path. A run killed before its rename leaves one behind that serves no hook, and no later run removes it: delete it if the receipt lists one. Otherwise nothing to do. doctrine-kit/DOCTRINE.mdRehearse the published front door from a clean seat — a new engineering-craft rule: before an audience meets a release, install the artifact as published, by each route that audience takes, on a seat that never held the project, and file what it finds; which seats, platforms, routes and events is your release procedure’s to name. A re-vendored tree receives the rule and--emit stage-rules closegains its pointer; the digest is unchanged. Nothing to do.- gate-sdk
lib/test-hermetic.sh— the preamble now exports every kit’s<KIT>_LOCAL_KNOB_FILE(Knob changes) at its shared empty knob file beside<KIT>_KNOB_FILE, so a bespokegate-tests/*.test.shreads no local overlay of the tree it runs from, where a seat’s overlay row could refuse a suite running on kit defaults. gate-sdk’s is exported after the binary is resolved, so an overlay that places the binary still does. A suite of yours that writes an overlay and asserts its value un-pins the locator after the source line (unset <KIT>_LOCAL_KNOB_FILE, or anenv -uprefix); a suite that un-pinned a tracked file only to be read beside an overlay can drop that line. - guard-kit shell guard, rules
rm_trackedandfind_exec— rulerm_trackednow reads each operand as the shell passes it, so a quoted or escapedrmof a tracked path blocks (rm 'README.md',rm "docs",rm two\ words.md), a bare glob is tested as the shell expands it (*no longer crosses a/), and under PowerShell a quoted path is tested as a wildcard, a backtick escape outside quotes resolves to its character (Remove-Item trac`ked.mdblocks) and only a bare comma parts a word into paths (Remove-Item 'a,b'names one); a quoted operand carrying*,?or[, and a word mixing a quote with a bare glob character, pass. Both steers print the word as your command spelled it, so rulefind_exec’sxargsspelling carries an escaped blank or glob through unchanged;-exec cat '{}' '\;'is no longer steered and-exec \cat '{}' \;now is. Nothing to do; a consumer decision table row pinning one of those spellings is re-derived. - guard-kit shell guard, rule
git_stash— a new generic rule blocks agit stashthat rewrites the working tree or the index (no verb or an option,push,save,pop,apply,branch) where the session stands on the main checkout, since the stash takes a concurrent session’s uncommitted edits with it; the steer isgit worktree add <scratch-dir>/<name> <rev>to read or build another revision, or a commit naming your paths to set edits aside. Each word is read as the shell passes it, so a quoted or escaped spelling (git 'stash' push,git stash "pop") blocks as the plain one does.list,show,create,store,dropandclearpass, and so does every stash from a linked worktree. The rule takes no knob: a consumer whose sessions never share a checkout allows the command in its consumer rules, which run first. A consumer decision table row expectinggit stashto fall through is re-derived. - guard-kit shell guard, rule
background_no_record— the bash wait-loop exemption no longer covers a backgrounded loop that does work: it holds only where everydo … donebody issleepstatements, every statement after the first loop is another such loop or a read-only pipeline (a banner included), and no body or tail statement ends in a bare&. So a backgroundedwhile true; do make build; done, a wait followed by a build, anddo sleep 5 & make build; donenow take the block. Relaunch such a call in the recorded spelling the block prints, the loop standing as its<command>; an inline wait, alone or followed by a read, is unchanged, and a loop condition is held to the writes its text shows alone (the bullet on the lead and the condition, below). A consumer decision table row pinning a working loop as a fall-through is re-derived. - guard-kit/templates/settings-hooks.json, context-kit/templates/settings-sessionstart.json, the kit README wiring steps — every shipped hook registration is now the harness’s exec form:
commandnames the gate binary under the project-directory variable, spelled as the placeholder@GATE_SDK_NATIVE_BIN@, andargscarries--hookand the member, so the harness starts it with no shell and the wiring needs no bash. A shell-form wiring you already merged keeps working, the front end keeping the--hookarm. To take the new form, re-merge the template and replace the placeholder as its note says: theGATE_SDK_NATIVE_BINvalue as the binary prints it, minus any executable suffix, under the${CLAUDE_PROJECT_DIR}/prefix. It needs Claude Code 2.1.139 or later (docs/requirements.md); do not merge it on an older build. With the binary absent the harness reports the hook once and lets each call through, where the front end printed the build remedy on every call. --hook <member>on the gate binary — started directly, the arm now resolves the git toplevel of its working directory and runs the member there, so a member reads your knob files from the repository root whatever directory the session stands in. Outside a repository, or where git refuses or skips one, it exits 0 with asystemMessagesaying the guards are off, where a direct call used to run the member in place. A caller that spawned the binary as--hookoutside a repository now gets that decline.doctor,init, thebashrequirement — no kit ships bash-led hook wiring any more, so no profile owesbashthrough a kit:doctorrenders itnot probedandinitno longer refuses on a host without it, the profiles carrying guard-kit or context-kit included.bash4.3 or later is owed only once your docs mark a fence runnable or yourgates.listregisters a shell gate. Nothing to do.- every arm of the gate binary that starts or copies itself — a run whose binary file is replaced under it (a rebuild or an update landing while a battery, a hook or
--runis in flight) no longer refuses at exit 2 naming a path marked(deleted): on Linux it now starts or copies the build that is running, through its own image link. Nothing to do. - guard-kit shell guard, rule
background_no_record— the bash wait-loop exemption now refuses two shapes its body and tail clauses already excluded: a bodysleepredirecting to a named file (do sleep 1 > <file>; done), and a declared write form joined to the loop by a bare&(…; done & sort -o <out> <in>), which took the block only in its;spelling. An inert body redirect (2>/dev/null) and a read behind the&are unchanged. Relaunch such a call in the recorded spelling the block prints. A consumer decision table row pinning either shape as a fall-through is re-derived. - guard-kit shell guard, the shared loop walk (rules
bare_sleep,background_no_record,wait_no_producerandbounded_wait) — a shell keyword is now read in command position only, sodo,doneor a loop keyword spelled as an argument no longer unbalances the walk and makes those rules decline.sleep 5; echo donetakes the foreground-sleep block, a backgroundeduntil grep -q done <log>; do sleep 2; done; <build>takes the record block, a backgrounded file-test wait followed byecho donetakes rulewait_no_producer’s block, and a foreground wait whose condition or tail namesdonenow earns rulebounded_wait’s grant, as does the same wait spelled with no blank around its keywords (;do sleep 5;done). A wait piped into a reader with no blank around the pipe (done|cat) is granted since that change, as its spaced spelling (done | cat) was before it. Nothing to do unless a script or prompt of yours relied on the decline; a consumer decision table row pinning one is re-derived. - guard-kit shell guard, rule
background_no_record— the bash wait-loop exemption now reads a statement ahead of the first loop and each loop’s condition, where it read the body and the tail alone. A lead takes the tail’s test, so<build>; until <cond>; do sleep N; doneand a lead ending in a bare&take the block, and a banner or a read ahead of the wait is unchanged. A loop opens only at a keyword leading a statement, soecho for; <build>; until …hides no lead. A condition owes the record where its text shows a write: a redirect to a named file, a gitignored one included, a statement-ending&, or a read-only roster member’s declared write form (until sort -c -o <out> <in>; …). A condition led by a command outside the roster (a remote client, a lockmkdir, a build) is unchanged. Relaunch a blocked call in the recorded spelling the block prints; a wait that cached each poll through a redirect pipes it intogrep -qinstead. A consumer decision table row pinning such a call as a fall-through is re-derived. - guard-kit
--run-guard-tests, new fileguard-kit/guard-tests/variant-limits.tsv— the arm gains a third lane between the kit’s tables and yours: it derives re-spellings of the kit’s bash rows (a word quoted or escaped, a redirect glued or parted,commandahead of the row) and fails where the guard reads one more permissively than its row, unless a row of the kit’s limits table declares the pair. The summary line adds the derived-row and limit-row counts, and a run takes several times as many guard spawns as before. Your own table is not derived. Nothing to do where you run the arm with no operands; a call naming its own four tables now names a limits table as the fifth. - guard-kit shell guard, rules
git_mutation_under_producer,git_c_root,commit_only_paths,git_stash,rm_tracked,git_grepandgit_rewrite— every rule that reads a git command now reads its words by their value through one read, and reads the command word by its value, by its path and behind the wrappers the permission matcher strips. So a quoted or escaped subcommand or option (git 'commit' -m x,git comm\it,git \rm -q -f <path>), a quoted or path-spelledgit('git' stash,/usr/bin/git commit) and acommand-,nohup-,time- or assignment-led one take the block, steer or advise the plain spelling takes, where they fell through. Rulegit_grepalso steers behind a global option (git --no-pager grep foo) and declines on one that moves the repository (-C,--git-dir,--work-tree); rulegit_rewriteadvises behind a global option and reads one statement at a time, soecho --amend; git commit -m xno longer advises.env git …,sudo git …and a quoted wrapper are not read. Nothing to do; a call you relied on passing in one of these spellings runs through!<command>or your consumer rules. - guard-kit shell guard, the git rules’ redirect read — a quoted or escaped word glued ahead of a redirect is now read by its value, and a redirect operator standing apart from its target takes it when spelled
>&. Sogit 'stash'>/dev/null push,git stash 'pop'>/dev/nullandgit stash 2>& 1 popblock on the main checkout as the plain spellings do, where they fell through, and the same holds for every rule that reads a git command. Nothing to do. - guard-kit shell guard, rule
background_no_record— the bash wait-loop exemption now finds a loop’s own closing&past the loop’s inert redirects, sountil <cond>; do sleep N; done 2>/dev/null &is exempt as… done &is, where it took the record block. A redirect to a named file there (done > <file> &) and a producer behind that&still take it, and a file-test wait so spelled meets rulewait_no_producer. The shared loop walk also reads a keyword glued to a redirect, sowhile true; do <build>; done>/dev/nullin the background takes the record block andsleep>/dev/null 5the foreground-sleep block, where each made the walk decline. Nothing to do; a consumer decision table row pinning either verdict is re-derived. - guard-kit shell guard, the shared loop walk (rules
bare_sleep,background_no_record,wait_no_producerandbounded_wait) — aforloop’s variable and a redirect’s target behind>&are no longer read as command position, so a keyword spelled there no longer makes the walk decline: a backgroundedfor done in <list>; do <build>; donetakes the record block andsleep 5 >& donethe foreground-sleep block, where each fell through. The wait rules’ redirect test now reads>&onto a named file as a write, so a backgrounded wait whose body, condition, lead or tail carries>& <file>takes the record block, and a foreground one loses rulebounded_wait’s grant.>&onto an fd number or-is unchanged. Relaunch a blocked call in the recorded spelling the block prints; a consumer decision table row pinning one of these as a fall-through or a grant is re-derived. - guard-kit shell guard,
>&onto a named file (rulesro_pipeline,background_no_record,append_scratch,emitter_write,bounded_wait,scratch_redirect,truncate_scratchandworktree_confinement’s read exemption) —>& <file>and>&<file>are now read as the write> <file>is, where every redirect reader took&only ahead of an fd number. Sogrep -rn foo src >& outis no longer granted as a read-only pipeline, in the foreground or the background;<runner> >& op.logtakes the bare-name scratch block; and: >& <gitignored>and<emitter> >& <gitignored>are granted as their>spellings are.>&onto an fd number, with or without a closing-, or onto a bare-stays an fd-dup.scan-promptskeys such a call with its>suffix. Nothing to do; a consumer decision table row pinning one of these is re-derived. - guard-kit shell guard, rules
ro_pipelineandcat_file— an option spelled behind an escape or a quote is now read as the option it is.awk \-f prog.awk <file>and<reader> | awk \-f prog.awkare no longer granted as read-only, where the escaped-fwas read as the program and a program file ran with no permission decision; anysedorawkword the shell passes dash-led from behind an escape withholds the grant, while a quoted program text leading with a backslash and a dash (sed -n '\-x-p', an address delimited by-) is granted as the read-only program it is, where it fell through.cat \-n <file>,cat '-n' <file>andcat "-n" <file>take the Read-tool steer ascat -n <file>does, where each was granted.catreads--as ending its options:cat -- -n <file>stays the two-file concatenation it is, andcat -- -n, one file, takes the steer where it was granted. Nothing to do. - guard-kit shell guard, a second redirect glued to a scratch target (rules
truncate_scratchandbounded_write) — a redirect glued behind a gitignored target is now read as the write it is, where the glued word was tested as one ignored name and the call granted.: > .tmp/a>tracked.md,: >& .tmp/a>tracked.mdand<command> >> .tmp/a>tracked.md, with>,>>,>&or&>glued and with a digit ahead of the glued operator, are no longer granted: the:spellings fall through to the harness’s decision, an emitter’s takes ruleemitter_write’s block as the spaced spelling does, and any other command’s falls through. Nothing to do; a consumer decision table row pinning one of these as a grant is re-derived. - guard-kit
--run-guard-tests, the variant lane — the lane now derives three more families from the kit’s tables: a redirect onto the sandbox’s tracked file added to every bash row (added-write,added-write-glued,added-append-glued,added-dup-glued), which fails where the guard grants the result; the re-spellings of bash rows carrying a quote; and re-spellings of the PowerShell rows (ps-sq-arg,ps-dq-arg,ps-tick-lead,ps-tick-arg,ps-case-lead,ps-alias-lead).guard-tests/variant-limits.tsvgrows by the pairs the kit’s rules decline, and the ok line counts the four tables’ derived rows. Nothing to do where you run the arm with no operands andGUARD_KIT_CONSUMER_VARIANT_LIMITSempty. A call naming its own tables names limits for the pairs its rules decline, a section now being a section of any of the four tables, and a wrapper matching the ok line’sof the bash rowswording matchesof the four tables. - guard-kit shell guard, rule
sed_file— asedoption is now read by its word’s value, so a quoted or escaped option is the option it is.sed -'n' '1,60p' <file>andsed -"n" '1,60p' <file>take the Read-tool steer assed -n '1,60p' <file>does, where each was granted as a read-only pipeline, andsed -'i' 's/a/b/' <file>takes the--rewritesteer where it fell through. A quoted word that leads with a dash is read as the option the shell passes, never as the program, sosed -n '-x' <file>falls through where it took the Read-tool steer. Nothing to do. - guard-kit shell guard, the rewrite grant (rules
abs_scriptandbrace_glyph) — a rewritten command carrying a redirect that names a file is no longer granted, a gitignored target included.<root>/scripts/check-env.sh --list > out.md, with>,>>,>&,&>or a digit ahead of the operator, glued or spaced, takes the rule’s corrective block naming the repo-relative spelling, where the absolute spelling was rewritten and granted through an allowlist pattern’s trailing star. An fd-dup (2>&1) and/dev/null, quoted or not, are still granted. Nothing to do; a consumer decision table row pinning such a rewrite is re-derived. - guard-kit shell guard, the noclobber operator (rules
abs_script,brace_glyph,truncate_scratch,append_scratch,ro_pipeline,bounded_waitandbounded_write);--run-guard-tests, the variant lane — no grant attaches where>|stands unquoted in the call, spaced, glued or under a descriptor, since the reader parts it at its bar, reading the word it writes as a second segment:ls >| sortand<loop> done >| catwere auto-allowed and<root>/scripts/check-env.sh --list >| scripts/check-env.sh --listrewritten and granted, each writing or truncating that word. Such a call now falls to the harness’s decision, or takes the rewrite’s corrective block. The variant lane addsadded-noclobber, the row’s own cell appended behind>|, held to never being granted. Nothing to do; a decision table row of yours pinning such a grant is re-derived. - guard-kit shell guard, the redirect scan (rules
abs_script,brace_glyph,truncate_scratch,append_scratch,ro_pipeline,bounded_waitandbackground_no_record) — the scan reads a redirect’s blanks as bash does, a space or a tab, so a carriage return, vertical tab or form feed is a word character:>&2followed by a carriage return and a name,>&followed by a carriage return and a digit, and a>whose word is a lone carriage return each write a file, where they read as an fd-dup or as no redirect andgrep -rn foo src >&2<CR>tracked.mdwas auto-allowed. Such a call now falls to the harness’s decision, takes the rewrite’s corrective block, or takes rulebackground_no_record’s block when backgrounded. Nothing to do; a decision table row of yours pinning such a grant is re-derived. - guard-kit shell guard, the rewrite grant (rules
abs_scriptandbrace_glyph) — a rewritten command whose redirect word carries a second operator is no longer granted though both its targets are inert, so<root>/scripts/check-env.sh --list >/dev/null&>/dev/nulltakes the corrective block naming the repo-relative spelling. Nothing to do. - guard-kit
--run-guard-tests— a third substitution,@CR@, puts a carriage return in a command cell, applied after@ROOT@and@NL@in every lane, your consumer table included. Nothing to do unless a row of yours spells@CR@literally, which now reads as a carriage return. - guard-kit shell guard, rule
rm_tracked— under bash thermcommand word is read through the new command read, by its value, by its path and behind a wrapper the permission matcher strips, so'rm' <tracked>,\rm <tracked>,/bin/rm <tracked>,command rm <tracked>,time rm <tracked>and an assignment-ledrmtake thegit rm -qsteer the plain spelling takes, where they fell through; a redirect’s target is no longer read as an operand. Nothing to do; a decision table row of yours pinning such a spelling as a fall-through is re-derived. - guard-kit shell guard, rule
script_interpreter— under bash each pipe member’s words are read through the command read, so a quoted, escaped, path-spelled or wrapper-led interpreter ('bash' <scratch>/x.sh,command python3 …), a quoted or partly quoted body operand or<source (python3 '<scratch>/x.py',bash - < "<scratch>/x.sh"), a source glued to its<and a quoted pipe producer operand take the scratch-body block the plain spelling takes, where they fell through; rulebounded_wait’s arm (B) narrows with it. Nothing to do; a decision table row of yours pinning such a spelling as a fall-through is re-derived. - guard-kit shell guard, rule
script_interpreter— where the command read yields no words for the call, a quoted span carrying a newline say, a<source’s blanks are bash’s, a space or a tab, so a carriage return glued to the<stays in the source’s word:bash <followed by a carriage return and<scratch>/x.sh, beside a heredoc, falls through as it does without one, since bash reads a file named with the carriage return, where it took the scratch-body block. Nothing to do; a decision table row of yours pinning that block is re-derived. - guard-kit shell guard, the git read and rule
rm_tracked— a wrapper the permission matcher strips is now read past by its value, so a quoted or escaped one ('nohup' git stash,\time git commit -m x,'nohup' rm <tracked>) is the wrapped command’s, as bash runs it, where its segment was read as the wrapper’s and fell through every git rule. Nothing to do. - guard-kit shell guard, rule
grant_path_slot— a segment’s backslashes are removed before the harness view strips its wrappers, so an escaped wrapper (\timeout 5 rm -rf <scratch>/../<file>) is read past as the bare one is and the reach blocks, where it fell through; rulesabs_scriptandbrace_glyph’s rewrite grant narrows with it. Nothing to do. - guard-kit shell guard, rule
cd_compound— under bash the command word is read through the command read, so a quoted, escaped, path-spelled or wrapper-ledcdin a compound (ls && \cd <dir>,ls; FOO=1 cd <dir>) takes the block the plain spelling takes, where it fell through. Nothing to do. - guard-kit shell guard, rules
find_globandcat_file— under bash each command word of the call is read through the command read, a banner’s included, so a quoted, escaped, path-spelled or wrapper-ledfindlisting or single-filecat, and a batch whose banner is so spelled, take the Glob or Read steer the plain spelling takes, where they fell through. Nothing to do. - guard-kit shell guard, rule
shell_wrapper— under bash the command word and the-care read by value, the command word by its path too and past a leading keyword orcommand, so'bash' -c …,bash "-c" …,/bin/sh -c …andcommand bash -c …take the block the plain spelling takes, where they fell through; a wrapper behind another program (xargs bash -c,timeout 5 bash -c) is still not read. Nothing to do. - guard-kit shell guard, the declared-forms reader (rules
ro_pipeline,background_no_record,bounded_waitandbounded_write) — a roster member’s command word and the words after it are read by their values off the command read, so a quoted or escaped member in one of its declared write forms (until "sort" -c -o <file> <in>; do sleep 5; done), or one glued to a redirect (sort>/dev/null -o <file> <in>), is read as that write, where it was skipped: a backgrounded wait so spelled takes rulebackground_no_record’s block, and no grant widens. Nothing to do. - guard-kit shell guard, rule
scratch_redirect— under bash a redirect target is also read by its value, so a quoted, partly quoted or glued bare scratch name (2> "op.err",2>'op.err',> op.'log') and one carrying a carriage return take the block the plain name takes, where they fell through. Nothing to do. - guard-kit shell guard, rule
bare_sleep— under bash a segment’s command word is also read through the command read at that segment’s loop depth, so a quoted, escaped, path-spelled or wrapper-led foregroundsleep(echo start; \sleep 30,nohup /bin/sleep 30) takes the steer the plain spelling takes, where it fell through; a sleep in a loop body is untouched however spelled. Nothing to do. - guard-kit shell guard, rule
pgrep_self_match— the command word, the options and the pattern are now read by value too, so a quoted, escaped, path-spelled or wrapper-ledpgrep/pkilland a quoted-f('pgrep' -f <pat>,pgrep "-f" <pat>,command pkill -f <pat>) take the recorded-PID steer the plain spelling takes, where they fell through; a partly quoted pattern, which finds no copy of itself, still passes. Nothing to do. - guard-kit shell guard, rule
allowlist_chain— a lead the skeleton does not read as an allowlisted bare form is read by value too, past a wrapper the matcher strips, so a quoted, escaped or wrapper-led bare lead decorated with a chain or a redirect ("git" status && rm -rf tmp,command git status; make build) takes the run-it-bare steer, naming the bare form, where it fell through. Nothing to do. - guard-kit shell guard, rule
emitter_write— where a statement’s leading word as the skeleton spells it is noGUARD_KIT_APPEND_BINSmember, its lead is read by value, past a wrapper the matcher strips and cut at a glued redirect, so a quoted, escaped, glued or wrapper-led emitter write ('cat' >> <tracked>,cat>> <tracked>,command printf x >> <scratch>/j.md && make build) takes the arm’s steer, where it fell through; ruleappend_scratch’s grant still reads the skeleton’s lead and does not widen. Nothing to do. - guard-kit shell guard, rule
git_c_root— arms (a), (c) and (d) read words by value too: a quoted or escaped-Cbefore the repo root (git '-C' <root> status), a quoted, partly quoted orcommand-led absolute wrapper ('/usr/bin/time' -f %e make), and a quoted, escaped orcommand-ledexport/envsetting a knob to its resolved value each take the steer the plain spelling takes, where they fell through; a quoted knob value still falls through. Nothing to do. - guard-kit shell guard, rule
sed_file— under bash the tool word and its options are read through the command read, by value, by path and past a wrapper the matcher strips other thanxargs, in a pipe member as at a statement’s head, so a quoted, escaped or wrapper-ledsed,perlorawkand a quoted or escaped option ("perl" -pi -e … <file>,perl '-pi' …,… | sed \-n '5,10p',command awk 'NR<=5' <file>) take the steer the plain spelling takes, where they fell through. Nothing to do. - guard-kit
--guard-json— a new mode,words, prints the values of the payload command’s words, one line per segment with its words tab-separated, so a consumer rule can compare a quoted or escaped word by the value the shell passes. It prints nothing where the read does not answer; a PowerShell here-string prints as the wordHD, and a heredoc’s body prints as the lineHD, and a newline in a value prints as\n. Nothing to do; to have a rule see through a quoted word, readwordsand fall back toview sq dq hdwhere it prints nothing. - guard-kit wakeup-guard — on native Windows each attempt line’s stamp is now built in process from the zone bias the system reports, so a hook the harness starts with no Git for Windows userland on
PATHstill stamps its line, which opened with an empty stamp there before. Nothing to do. - delegation-kit
--foreign-run,--foreign-resume, knobDELEGATION_KIT_FOREIGN_USAGE_CMD, delegation-kit/templates/delegation-config.knobs — a producer word may carry@FAILED_RUN@: such a producer also runs after a spawned turn that endsFAILED, with that turn’s run directory in the word, and before a read with the word empty, so it can write a spend limit the vendor reported only as a failed turn’s error. Nothing to do; a producer without the token runs as before. - lifecycle-kit
--emit stage-contract, new templatelifecycle-kit/templates/frames/stage-contract-sweep.md, lifecycle-kit/templates/host-protocol.md, lifecycle-kit/templates/stages/build.md, lifecycle-kit/templates/lead.md — the arm takes--mode sweep, printing a contract for a foreign agent that makes the stage’s file changes in its clone. The host protocol gains that mode: the host applies the run’s patch, runs its oracle, returns a red with--foreign-resume, and commits what passes. The build template now carries the protocol’s pointer line namingsweep, soLIFECYCLE_KIT_STAGE_EXECUTORmay bind the build stage. Nothing to do; a stage you do not bind runs as before. - context-kit
--emit always-loaded --growthand thesession-contexthook member — a changed file whose name carries a non-ASCII byte, a double quote or a backslash is now read under its own name, where git’s quoted spelling of it was used: the growth worklist measures such a file rather than counting it as absent, and the brief’s dirty-surface step names the component holding it. A changed name that is not valid UTF-8 now stops--growthat exit 2 naming it; the hook still degrades silently. Nothing to do. - lifecycle-kit
--emit close-surfaces, the installer’suninstall, and gate-sdk’s--run-consumer-smokeand--projection-witness— a listed file whose name is not valid UTF-8 now stops the arm with a refusal naming it, where it was read under a replacement-character spelling. Rename the file. - lifecycle-kit
--enter-stage, the iteration boundary’s scratch wipe — a scratch member whose name is not valid UTF-8 is now removed, whole, and named in the wipe’s notes, where one at the scratch root left the whole scratch dir unwiped and unreported and one deeper left its directory behind at every boundary. Nothing to do; delete by hand no longer. - drift-kit
--emit stage-economicsand--price-coverage— a price-table row short of four numeric price cells, as a dropped tab leaves it, now prices nothing: stage-economics counts it in its skipped-rows line and--price-coveragereads its model unpriced, where its missing or unparsable cells priced at zero. Repair any row either names. - drift-kit KPI plugins — a KPI dir may hold
kpi-<name>.ps1, started by the PowerShell hostGATE_SDK_POWERSHELLnames, so a consumer KPI reports on native Windows. Nothing to do; a.shplugin is unchanged, and wins over a.ps1of the same name in one dir. - gate-sdk
--run-config-variant, gate-sdk/templates/adopt.md — a new arm that rehearses a config change: it runs the battery over a scratch copy of your tree as committed and again with the knob files in a directory you name, and prints each gate the change moves, exiting 1 when one goes red. Nothing to do;templates/adopt.mdnow rehearses each knob change with it. - drift-kit
kpi-gate-backlog— a proposed gate declared by a.gatedescriptor now counts as built, where only a.shdid, so the backlog row’s unbuilt count drops by each such gate. Nothing to do. --upgrade-smoke, site-kit/templates/site-health.yml — a tag’s release note is the post whoserelease:key sits inside canon-kit’s front-matter span, a block a...line closes included, so a key spelled in a post’s body, or under an opening---nothing closes, no longer selects that post as the tag’s declaration source or as the note the release-body arm checks. Nothing to do; re-copy the template where you vendored it.- lifecycle-kit
--enter-stage, lifecycle-kit/templates/stages/scope.md, lifecycle-kit/templates/stages/build.md, lifecycle-kit/templates/lead.md — the queue’s iteration header gains a set record, a block opening at an indented**Set record**line below the header and running to the next---rule or##heading. Scope lands every record ruling the whole promoted set there instead of on one entry, and the iteration boundary’s reset drops it with the closing iteration, leaving a blank line between a preamble above it and a closing---rule;--renamekeeps it. Scope also promotes an entry whose remaining work runs at the drain stage or later only tagged[drain-exempt:], else keeps it deferred with the obligation in the set record. Nothing to do on a queue carrying no such line; move a set-wide record living on an entry’s body into the block at your next scope. - lifecycle-kit/templates/consult.md — a consultation a lead dispatches may now run an outside critique an inbox item carries as an operator grant the lead relays, spent on the item’s own terms; it still runs none unasked. Nothing to do; re-vendor the template.
- drift-kit
--emit trajectory— the emission now opens with two prose lines ahead of its table, on every path including a degraded one: no controlled ungoverned baseline exists, and knowledge-friction counts are excluded. A committed projection of it is stale until you regenerate it with your own redirect; your freshness gate names the command. - drift-kit
--emit file-install,--emit install-evidence— the install-observation record gains a fourth kind,proof <id> <ttfp>, the minutes from the first install command to the first held activation proof, replacing on<id>; the Installs block gains two columns, how many installs reached a held proof and the min · median · max time to proof, and aproofwhose<id>has no install line counts nowhere. A record written before reads unchanged, and a committed projection of it is stale until you regenerate it with your own redirect; your freshness gate names the command. - lifecycle-kit/templates/consult.md, lifecycle-kit/templates/lead.md — a consultation spending a grant a consult-inbox item carries reads the item’s line for a
Grant spentmark first, and where the item stays, appendsGrant spent <YYYY-MM-DD>: <what it paid for>.to that line in the commit landing the spend’s result; the lead reads an inbox item’s grant as spent where its line carries the mark. The line stays one bullet, so the inbox count is unchanged. Nothing to do; re-vendor the templates. - site-kit/templates/site-health.yml — a failed probe now reds the run and files nothing: the report step, renamed
report the probe result, opens, updates and closes thesite-healthissue only with its newISSUE_TRACKINGset toon(Knob changes), and the permissions block dropsissues: write. A copy you already made is unchanged and keeps filing; to take the default, re-copy the template, carrying your step env and any arm of your own across. To keep the issue path in a new copy, setISSUE_TRACKING: 'on'and addissues: writeto its permissions block. - drift-kit
--emit statistics, drift-kit/templates/economics.md — a new arm charts the per-iteration figures over your closed iterations (spend and cache-read by stage band and by model from the stage-economics log; governance share from the overhead log; commit shape, amendments, validate, gate roster, queue flow, the always-loaded surface and the oldest deferred entry’s age from committed history) into one self-contained HTML file atDRIFT_KIT_STATISTICS_FILE(Knob changes), printing onechartedorn/a (<reason>)line per figure; exit 0 bar a usage error or a failed write. The file carries your spend and stays under your metric dir, with no freshness gate. The/economicstemplate runs it as step 4 and gains a The view narrative bullet. No slot is added or renamed. Nothing to do; a bound/economicsshim picks it up. - lifecycle-kit
--emit file-gap,--emit file-survey— an append to a capture file whose last line lacks its line break now writes the break first, so the filed bullet or block starts a line of its own rather than joining the last line. Nothing to do. - context-kit
session-contexthook member, scratch-sweep step — the scratch sweep now spares the session-role markerCONTEXT_KIT_SESSION_ROLE_FILEnames at any age, so a lead resumed past the horizon keeps its suppression, and names on its own line aCONTEXT_KIT_SWEEP_PRESERVEentry containing/, which spares nothing. Nothing to do. - guard-kit shell guard, rule
ro_pipeline— each pipe member’s command word is read by its value off the word read and compared whole, so a quoted or escaped roster binary ('grep' -rn foo src | head,\grep foo f) is granted where its forms clear, and a leftover quote elsewhere in the call no longer refuses on its own; a quoted path ('/bin/grep') is still no roster binary. Nothing to do; a prompt you saw on those spellings is gone. - guard-kit shell guard, PowerShell rules
cd_compound,rm_tracked,background_no_record,scratch_redirectandscript_interpreter— under PowerShell each rule reads its command words, redirect targets and body paths by value through the word read, so a backtick or a quote inside the word (c`d deploy; ls,r`m tracked.md,Start-Pro`cess,2> 'op.err',& '.tmp/x.ps1') no longer evades them. A quoted scratch path leading a statement is a string expression PowerShell prints, so'.tmp/x.ps1'is not read as a run. Nothing to do; a call spelled so is now steered or blocked as its bare spelling is. - guard-kit shell guard, rules
ro_pipeline,background_no_record,bounded_wait,append_scratchandemitter_write— a redirect target is read by its value where the word read answers, so a quoted/dev/null(2>"/dev/null") no longer withholds a grant or blocks, a quoted gitignored target (printf x >> ".tmp/j.md") is granted, andemitter_write’s steer names a quoted tracked target by its value rather than the placeholder’s letters. A quoted target spelling a descriptor (ls > '&1') is the file it names, never an fd-dup: rulesro_pipeline,append_scratch,bounded_waitandbounded_writewithhold there,background_no_recordblocks such a backgrounded call for its missing record (sort > '&1'), andemitter_writeblocks the write as one to a file git does not ignore. A heredoc-bearing call is read the same way, socat >> ".tmp/j.md" <<'EOF'is granted, and a quoted or escaped word beside a heredoc now reaches every rule that reads words by value:rm 'tracked.md' <<'EOF'blocks asrm 'tracked.md'does. An ANSI-C quoted word ($'…') is read by the value bash resolves, socat >> ".tmp/"$'\056\056/README.md'blocks as the write toREADME.mdit is, and a read-only call so spelled (grep $'a\tb' f) is granted. A span whose value hangs on a control byte or the locale is not read. Asedorawksegment, anxargssegment, or a call from a linked worktree carrying such a word is not granted (awk $'{print}' f), since a check guarding those grants reads the unresolved spelling. A locale-translated word ($"…") is not read either, since its catalog is out of the guard’s sight, socat >> ".tmp/"$"../README.md"blocks,sort $"-o" README.md xis not granted, andgrep $"a" freaches the harness’s decision. A decision-table row of yours pinning a fall-through there is re-derived. Nothing to do; a prompt or block you saw on those spellings is gone. - guard-kit shell guard, rule
background_no_record— the read-only-pipeline exemption reads each statement a bare&ends, as the wait-loop exemption does, so a writer behind a read’s&(grep -rn foo src & sort -o <out> <in> &,grep -rn foo src & make build &) takes the record block, where the call was exempt as one led by the read and fell through. A read behind a read’s&is unchanged. Relaunch such a call in the recorded spelling the block prints; a consumer decision table row pinning one as a fall-through is re-derived. - guard-kit shell guard, every rule reading words by value under PowerShell — the PowerShell word read now answers on a call carrying a here-string, which it reads as one word, so a quoted or escaped word beside one is read by its value:
git 'stash' push; @'…'@ | Out-Nulltakes rulegit_stash’s block andls 2> 'op.err'beside a here-string the bare-name scratch block, where each fell through.--guard-json wordsprints the here-string as the wordHD. Nothing to do; a consumer decision table row pinning such a fall-through is re-derived. - guard-kit shell guard, rules
find_glob,find_exec,cat_file,git_grep,expansion,sed_fileandemitter_write— a heredoc riding a call no longer makes these rules decline: each reads the call as its bare one, the heredoc’s opener and residue read past, socat <file> <<'EOF',find <dir> -type f <<'EOF',find … -exec grep … '{}' \; <<'EOF'andgit grep <pattern> <<'EOF'take their tool steers,NAME=value <<'EOF'the assignment block, an inlinepython3 -cliteral rewrite beside a heredoc the rewrite steer, and an escaped or wrapped emitter lead beside one (\cat>> <tracked> <<'EOF') the emitter block, where each fell through. A heredoc a word of the call reads (-,/dev/stdin) still composes, socat -- - <<'EOF'andgit grep -f - <<'EOF'are unchanged; rulegrant_path_slotand the rewrite grants still decline a heredoc-bearing call. Nothing to do; a consumer decision table row pinning such a fall-through is re-derived. - guard-kit shell guard, rule
background_no_recordand every grant reading its statement-ending&— a bare&glued to the next word ends its statement as a spaced one does, sols &touch xandgrep -rn foo src &sort -o <out> <in>take the record block where rulero_pipelinegranted them, and a read behind a read’s glued&falls through as its spaced spelling does;&&,&>,>&,<&,|&and an escaped\&end none, and a spaced fd-dup (<& 0) is no longer read as a backgrounding&. §scan-prompts marks such a logged call allowlist-unreachable. Relaunch such a call in the recorded spelling the block prints; a consumer decision table row pinning one as a grant is re-derived. - guard-kit shell guard, the bash reader’s skeleton — an ANSI-C quoted span (
$'…') now ends where bash ends it, at the first quote no backslash escapes, where it ended at an escaped quote and left the text up to bash’s next quote inert to every rule. Sogit status $'\'' ; git stash ; echo ''andls $'\'' ; rm <tracked> ; ls 'a'take the blocks their bare statements take, where they fell through, and a word carrying an escaped quote is read by its value:grep $'it\'s' <file>is granted as a read-only call. Nothing to do; a consumer decision table row pinning such a fall-through is re-derived. - guard-kit shell guard, rule
worktree_confinementand every grant taking its test — from a linked worktree a path word is read by its value, so an ANSI-C quoted main-checkout path (rm $'<main>/README.md', a..traversal spelled in hex escapes) is blocked as its plain spelling is, where it fell through. A call carrying an ANSI-C span the word read refuses ($'…\cA') or a locale-translated span ($"…") is no longer granted from a linked worktree, wherecat $"<main>/README.md" >> .tmp/j.mdwas granted by ruleappend_scratchwithGUARD_KIT_WORKTREE_READSoff; it falls to the harness’s decision. Nothing to do. - guard-kit shell guard, rule
background_no_recordand every grant reading its statement-ending&— a|,>,<or&behind an odd backslash run is a character of its word and forms no operator with a bare&after it, sowc -l f\|&touch fandwc -l f\<&touch ftake the record block where rulero_pipelinegranted them, andwc -l f\>&touch fblocks where it fell through. §scan-prompts marks such a logged call allowlist-unreachable. Nothing to do; a consumer decision table row pinning one as a grant is re-derived. - guard-kit shell guard, the bash reader’s skeleton and word read — a backslash-newline between a
$and its quote, or inside a run of$, is joined before the span is read, as bash removes it, so$, backslash, newline,'…'opens an ANSI-C span where it read as a plain single-quoted one; a call hiding a statement behind such an escaped quote, a scratch write or a read from a linked worktree, now takes the verdict its joined spelling takes, where it was granted. Nothing to do. - guard-kit shell guard, the bash reader’s dequoted view, rules
ro_pipeline,worktree_confinement,grant_path_slot,abs_script,brace_glyph,bounded_waitandbounded_write— the dequoted view holds an ANSI-C quoted span ($'…') by its value, its$dropped, as it holds a single-quoted span of that value, so thesed/awkprogram andxargsdiscriminators, ruleworktree_confinement’s admitted read and the$tests of the five rules read such a word as its single-quoted spelling:awk $'{print}' fandfind .tmp/old -name $'*.txt' -deleteare granted where they fell through. A value carrying a line feed is still held as spelled and withholds. A consumer rule readingview dequotedthrough--guard-jsonreads the value too. Nothing to do; a consumer decision table row pinning such a spelling as a fall-through is re-derived. - guard-kit shell guard, rule
ro_pipeline’s declared-forms reader and every rule applying it — a line feed inside a word’s value (an ANSI-C$'…\n…'span) is read as a character of that word, where the check read only the first line of the joined values and so missed every word after it:find . -name $'x\n' -deleteandsort $'a\n' -o <out> <in>were granted as read-only and now fall through. Nothing to do; a consumer decision table row pinning one as a grant is re-derived. - guard-kit
--guard-verdict— a new arm prints the decision the shell guard returns for a command you give it as an operand or on stdin: the decision word, then the hook’s own output. Its child’s friction log is a scratch file, so the probe is never filed as friction.--sandboxasks in the decision table’s sandbox rather than your tree, and--bin <binary>asks another build, such as an earlier release’s binary taken from its Release archive. Nothing to do. - guard-kit shell guard, rule
bounded_write— a write target is read by its value, a backslash its quoting keeps included, where the backslash was dropped before the bounds test.touch '\.tmp/a',touch "\.tmp/a",touch $'\\.tmp/a'andmkdir -p '\.tmp/x'were granted as scratch writes though bash writes under a directory named\.tmp, and now fall through;printf x > '\.tmp/a'now meets the redirect block. Unquotedtouch \.tmp/astays granted. Nothing to do; a consumer decision table row pinning one as a grant is re-derived. - lifecycle-kit
--enter-stage --open-lead-journal, lifecycle-kit/templates/lead.md — the opener now also writes the session-role marker,lead <id>to the fileCONTEXT_KIT_SESSION_ROLE_FILEnames, deriving the id as a stamp does, and skips it with a note where no id derives. The lead’s first step is that one command, where it was a shell line the shell guard refused for its expansions. Nothing to do; a lead shim of yours that copied the old line can drop it. - guard-kit shell guard, the grant rules — rules
truncate_scratch,append_scratch,ro_pipeline,bounded_wait,bounded_write,abs_scriptandbrace_glyphnow grant only a literal call: no quote, backslash,$, backtick, parenthesis, glob character,~-led word, heredoc or herestring opener or carriage return, rulebounded_wait’s recorded-launch record statement excepted, matched byte for byte. A quoted, escaped, globbed or heredoc-bearing call, a write through a scratch link and acpormvinto a directory (-t,--target-directoryandcp --parentsincluded) fall through to the harness’s own decision, or meet a rewrite’s corrective block; ruleemitter_write’s compounded-write steer fires only on a write the literal test passes. Nothing to do; a call that used to pass silently may now ask. - gate-sdk
--run, the git hooks and--run-gate-tests— each run’s scratch, with every member’s and every suite’s temporaries, now lies underGATE_SDK_TMP_DIRrather than the system temp dir, made with its self-ignoring.gitignorewhere absent and removed on the way out; each child also finds that run scratch heading itsGIT_CEILING_DIRECTORIES, so a directory it makes there still reads as outside any repository.check-crate-armsruns cargo’s lint and test arms withTMPDIRunset, so a crate’s unit tests keep the platform’s temp dir. Nothing to do; pointGATE_SDK_TMP_DIRelsewhere to move it. - gate-sdk, every compiled tree walk and glob
**descent but context-kit’s index arms’ — prunes the resolvedGATE_SDK_TMP_DIRby its path, whatever its name, so a tmp dir you placed inside the tree under a nameGATE_SDK_PRUNE_DIRSdoes not carry no longer has a walk descend into a sibling member’s live scratch and red at exit 2 when that scratch vanishes mid-walk. Nothing to do; a same-named directory elsewhere is still walked. - gate-sdk
--run, the git hooks,--run-gate-tests,--run-guard-tests,--guard-verdict --sandbox,--run-index-testsand--run-front-end-parity— the run’s own spawn captures, and each arm’s sandbox, now lie under its run scratch inGATE_SDK_TMP_DIRrather than the system temp dir, and each member and fixture child getsTMPbesideTMPDIR, so on Windows a compiled member’s temporaries land in its private dir too;check-crate-armsunsetsTMPwithTMPDIR. Nothing to do; a member of yours readingTMPnow gets its private dir. --emit release-table— a new gate-binary arm that prints, or with--writerewrites, the publishing repository’s releases-page table. In a tree whose gates directory leavesGATE_LOCAL_RELEASE_POSTS_DIRorGATE_LOCAL_RELEASES_PAGEundeclared, it exits 2, naming the undeclared knob. Nothing to do; the arm serves the publishing repository’s own releases page.workflow-state-guard— reads every path a write call targets: the written path, or each file header of a patch carried as the command, a relative one joined to the session’s working directory. A patch naming the lifecycle state file is refused, and a superseded stage session’s patch passes only when every path is under the scratch dir. Nothing to do.--hook <member> --binding antigravity— the gate binary’s hook arm reaches its before-tool-call members from Antigravity through an adapter, and the plugin package now carrieshooks.jsonat its root, so installing it in Antigravity runs the shell and state-file guards. They fall through, deciding nothing, on native Windows, on a payload spread over several lines and on a workspace path whose JSON escapes a quote or backslash. Nothing to do; to keep Antigravity unguarded, set"enabled": falseon thecheckwrighthook in the installed copy’shooks.json.--selection-probe— a new drift-kit arm asking the coding agents you bind, from an empty directory outside any repository, each prompt you bind, and recording whether an answer names your subject. It is off untilDRIFT_KIT_SELECTION_AGENTSis set. Nothing to do; each run spends on every bound agent’s account.
Upgrading
Replace the vendored kit directories wholesale at v0.32.0, or run checkwright update. Re-run --install-hooks once per clone, then delete <gates-dir>/git-hooks/. Regenerate your generated artifacts; each freshness gate that reds prints its own regen command. Then run the full battery.
The allowed reds. Every gate under New and tightened gates above may red, and its bullet names what clears it. Most fall into three groups:
- A
gates.listline naming a gate that left its kit or was removed resolves nowhere and reds. Drop the line. check-gate-substrate-parityreds once for each new gate your vendored kits carry and yourgates.listdoes not name. Register it, or declare it# unregistered:with a reason.- A gate that now reads files, fences or repositories it skipped can gain a finding there. Fix it as the gate’s help line says.
The behavior changes above are declared for reading, not a mechanical scan. If a gate reds that this note does not name, the upgrade smoke was supposed to catch it first — open an issue, because that is a defect in the release rather than work for you.