Releases: CSalcedoDataBI/agentic-board
Release list
v0.36.0
Say the same thing everywhere, and stop pointing at a CLI that died. Two halves. The fleet had
been quietly planning work around Gemini CLI since Google closed it to individual accounts on
2026-06-18 — the probe failed, the fallback swallowed it, and nothing ever looked broken; it now
runs on Antigravity (#615). And the four-block closing summary that v0.35.0 shipped as a renderer
nobody consumed is now attached to all seven typed commands, generated from that renderer so the
two cannot drift, with the docs-freshness gate finally checking more than the README (#493).
Fixed
-
The fleet was routing work to a Gemini CLI that can no longer authenticate (#615). Google
shut the "Sign in with Google" path for individual accounts on 2026-06-18:gemini -pnow
answersIneligibleTierError: UNSUPPORTED_CLIENTand points at Antigravity.Get-CliProbeStatus
read that asauthandResolve-LaunchClidegraded the issue to claude, so nothing crashed —
which is exactly why it went unnoticed. MeanwhileFleet-Plan.ps1advertisedgeminias the
FIRST choice for Docs and the second for Chore / size S–XS work: the two most common buckets on
the board were being planned around a CLI that could never run.The
geminiadapter is replaced by anantigravityone (agy), Google's official successor,
which authenticates with the same personal account — no API key, no billing. Its probe was
measured at ~7s, well inside the 30sInvoke-CliProbetimeout. Unlike the other adapters it is
pointed at the briefing file instead of receiving its content as an argument, because
PowerShell drops embedded quotes when passing an argument to a native.exeand a briefing is
full of backticks and quotes;--dangerously-skip-permissionsis mandatory, since without it
agysoft-denies its own file read in headless mode and the session would start blind.Two things the rename alone would have missed.
Find-FleetOrphansqueriedpwsh.exeand
node.exeonly — true while every CLI ran under node, butagy.exeis a Go binary, so an
escaped Antigravity session would have been invisible to the sweep and reported clean while it
kept running; the filter now includes it (no false-positive risk: the pure core only ever
considers a process whose command line names a fleet issue artifact). Andagydrops an
.antigravitycli/working directory into whatever repo it runs in, including a fleet worktree,
so that path is now gitignored.Each new test was verified by reintroducing the defect and watching it go red: dropping
agy.exefrom the filter, dropping the headless flag, and puttinggeminiback in the routing
table each fail their assertion. -
The docs-freshness CI gate only ever looked at the README (#493). It regenerated the derived
regions in place and then rangit diff --exit-code -- README.md. Scoped to that one file, the
step rewrote a driftedcommands/*.mdand then never compared it — so command-file drift passed
green. It now runsUpdate-Docs.ps1 -Check, which writes nothing and names whichever region
moved. Measured on a one-word drift in a command file: the new step exits 1, the old one exited 0.Found while reviewing this PR, against a changelog line of its own claiming the gate already
covered it. It did not.
Added
-
Every typed command now carries the four-block closing summary (#493). v0.35.0 shipped the
renderer (#492) and nothing consumed it: not one command file or script referenced it, so in
practice each flow still ended however its author felt like ending it. The contract is now
attached to all seven command surfaces (/board,/docs,/expert,/knowledge,/scan,
/skills,/tools) as a generated region, and the generator reads the four headings — and each
block's when-empty sentence — from the renderer itself, so the text an agent is told to write and
the text a script prints cannot drift apart.Hand-copying the same paragraph into seven prompts would have re-created the drift #200/#202
already fixed for the README, so this reuses that machinery instead:Update-Docs.ps1owns a
third derived region,<!-- BEGIN:closing-summary -->, and the docs-freshness CI gate now fails
when a command file drifts from the renderer (see Fixed below — it did not, until this PR). A
command file missing the region is a failure rather than a skip, and one whose region is
malformed says so specifically instead of reporting an absent marker — otherwise a newly added
command would ship with no closing contract and nothing would notice.Asserted rather than eyeballed, per the epic's Definition of Done:
CommandSurface.Tests.ps1
checks every command file for the region, all four headings and all four when-empty sentences,
reading the expected values from the renderer so the test can never become a stale second copy,
andGet-CommandRegionPlanunit-tests the generator's own decision (missing, malformed, stale,
idempotent, per-file newline). Verified by breaking it three ways — deleting one file's region
fails 3 tests and the gate; renaming one label in the renderer fails 7 tests and marks all seven
files stale; dropping the missing-file bookkeeping lets a region-less command pass silently and
turns 3 tests red.
v0.35.0
The trust release: what a run says now has to be what happened. Two days of dogfooding — the
expert fleet clearing its own board — converged on one theme: closing every gap between what the
tool reports and what it did. Deliverable claims now pass a verification gate (#479), a braked run
proves post-run that it never touched main (#440), role selection stops guessing from one keyword
(#474), triage can no longer write to the wrong issue on multi-repo boards (#506), the 85
field-measured silent failures get real messages (#485), and every flow can close with the same
four-block summary a non-programmer reads at a glance (#492).
Fixed
-
/expert autotwo remaining gaps from #440: no self-inspection and two launch-time footguns.
After PR #468 armed the irreversible brake in the launch briefing, three problems were still
open:-
No post-run compliance report (problem 2). When the run ended — cleanly, in violation, or
over budget — nothing checked whether the brake had held and nothing told the human. The brief
now embeds the default-branch SHA recorded at launch and instructs the session to compare the
current SHA against it before recording Fleet-Findings.Assert-BrakeCompliance(pure,
testable) decides compliant / violated;Format-ComplianceReportwrites the verdict as an
[abios-evidence]comment that surfaces to the human immediately. -
Token scope footgun (problem 3a).
Expert-Auto.ps1unconditionally overwrote
GH_TOKENwith the registry PAT even when the ambientghlogin was better-scoped. If the
PAT lackedprojectscope while the ambient session had it, every board operation inside the
launched run failed withINSUFFICIENT_SCOPES. Now:Test-GhScope(pure, injectable
$StatusText) checks the ambient login first; the registry PAT is used only as a fallback
when the ambient login lacksprojectscope; if neither has it, a warning with a recovery
command (gh auth refresh -s project) is emitted before launch. -
Stale HEAD footgun (problem 3b). Before launching,
git fetch originruns explicitly
and the local default-branch SHA is compared to the remote. If they differ, a targeted
warning tells the human their local view is stale (the worktree is still cut from
origin/<default>— the base is safe; only the local clone is behind).
24 new test cases covering the three new pure functions and the updated
Format-AutoBrief
signature. -
Fixed
- Board-Triage now works correctly on multi-repo boards — cross-repo items visible, number collisions refused (#506).
Two silent defects on boards that hold issues from several repositories: (1)-Pendingsorted and
displayed items by bare#numberwith no repo context, making cross-repo items indistinguishable
and colliding numbers invisible; (2) a bare-Issue <n>matched against the first item with that
number — which was always the linked repo's — so triage writes landed on the wrong issue with a
success message naming a completely different title. Three new pure functions fix this:Resolve-IssueRef
parses-Issueas a bare number OR a qualifiedowner/repo#n;Find-TriageItemsmatches by
repo+number when qualified, or returns ALL same-number candidates when bare;Format-ItemRef
renders the canonicalowner/repo#numberreference. The-Pendingdisplay now groups items by
repo and shows the full reference for every item. When a bare-Issue <n>would write to more
than one board item, the script refuses and lists the candidates — silent writes to the wrong issue
are structurally impossible. A new-Repoparameter lets callers qualify a bare number without
rewriting existing scripts that already pass plain integers. 11 new Pester tests cover
Resolve-IssueRef(bare, qualified, -Repo, conflict detection),Find-TriageItems(single-repo,
multi-repo, collision, not-found), andFormat-ItemRef(with and without repo).
Fixed
/board expert autorole selection is now score-based, not first-keyword-wins (#474).
Get-DomainFromPlanpreviously returned on the first keyword hit, scanning roles in catalog order.
powerbi-reportis the first factory role and ownsvisual,chart,dashboard,report—
four ordinary English words — so one generic word anywhere in a plan text handed the entire task
to the Power BI toolset. A VS Code debugging task that mentioned the word "visual" drew 40 Power
BI authoring skills and zero debugging skills; the mislabel was silent because a large hook count
looks healthy from the outside. Fix: all roles are now scored (sum of matched keyword lengths;
longer, more specific keywords outweigh short ones), and the highest-scoring role wins. Ties
preserve catalog order (local-before-factory). A vscode+extension+plugin+CLI plan now correctly
resolves toextensioneven when it also mentionsdashboardorchart. The four regression
sentences from the issue all resolve correctly, and a genuine Deneb visual plan still resolves
topowerbi-report. Second defect fixed in the same pass:Get-HookedSkillsused substring
matching for skill patterns, so the bareskillpattern inextension.skillshooked
marketplaces:example-skill(suffix match — wrong). Skill patterns now use prefix matching
(the leaf must start with the pattern), and theextensionrole's pattern was tightened from
"skill"to"skills"so it hooksskills-auditand similar but notexample-skillor
skill-development.roles whynow reports runner-up roles and their scores alongside the
winner, making a wrong pick visible without reading the catalog. 11 new Pester tests cover all
four acceptance sentences, the regression guard, suffix prevention, and runner-up output.
Added
-
Claims verification gate for expert deliverables — the evidence contract now covers external facts, not just tests (#479).
An autonomous/expert autorun could produce fully green DoD evidence while every factual claim
in its deliverable was invented: five fabrications (package name, auth mechanism, CLI flag, two
API tools) reachedmainoncsalcedodatabi.combecause the evidence block recorded that tests
passed, not whether the claimed facts existed.Expert-ClaimsGate.ps1closes that gap: when a
deliverable asserts external facts, each claim is registered as verified (looked up; records how
and what was found), unverified (explicitly acknowledged as unchecked — visible, not hidden), or
not-applicable (the deliverable makes no external claims).Test-ClaimsGatefails the gate when
a verified claim does not resolve (correct=$false);Format-ClaimsSectionrenders three distinct
status labels (PASS / FAIL / UNVERIFIED) that never collapse. The expert brief now includes the
claims requirement: a document is a deliverable, and a deliverable needs a gate. The common case
(a code-only run, no external-claim deliverable) incurs no added friction — the gate is
not-applicable and costs nothing. 22 Pester tests cover all five issue tests including a
regression fixture from the five fabrications above: each one is caught. -
A closing summary every flow can end with — four blocks, always the same four (#492, epic #491).
Reported first-hand by the product owner, a BI professional and not a programmer: every command
ends however its author felt like ending it, so there is no fixed place to look for the only four
things a user wants to know — what I found · what I did · what is left · what I need from you.
Board-Summary.ps1renders exactly those, in that order, whatever order the caller passes them
in. Two rules make it readable rather than merely present: an empty block says so out loud
(a blank "what I need from you" is indistinguishable from a summary that was cut off, so it
renders "Nada — esto quedo listo."), and the order never changes, because the point of a
fixed shape is that the eye stops reading and starts scanning. Two renderings from one source:
plain text for the terminal, and markdown carrying an[abios-summary]marker for PR bodies and
issue comments. 14 tests pin the contract; threading it through the command surfaces is #493.
v0.34.1
Security patch: the fleet's merge brake, generalized. Ships the #598 fix alone so it reaches
installs immediately: no session launched by the fleet can merge its own PR unless the human
explicitly opts in.
Fixed
- Fleet sessions launched with
-Parallel -Launchnow stop at a reviewed PR by default (#598).
Previously, a session launched without an expert contract could merge its own PR with no review:
it was running with--permission-mode bypassPermissions, was briefed to callBoard-Merge.ps1,
and nothing in the tool layer refused it. On 2026-08-03, two such sessions merged their own PRs
withreviews: []and no[abios-review]record; post-merge review found that one PR had not
built the central item of its issue's DoD. Fix: all-Launchand-Fleetsessions now arm the
brake by default —Resolve-LaunchBrakereturns$true(stop at PR) unless-AllowMergeis
explicitly passed or the caller explicitly passes-StopAtPR:$false(an expert contract that
allows merging). This aligns the plain fleet path with the expert path, where the brake was
already mechanical. The-Relaunchpath receives the same default. Three new Pester tests
cover: default brakes,-AllowMergeopt-out, explicit-StopAtPR:$falsehonored.
v0.34.0
The pending queue, cleared by the tool's own expert mode. All seven triaged issues on the
board (#413–#419) were worked in one day by autonomous /board expert auto sessions — one
isolated worktree and one persona-briefed agent per issue, launched in dependency-safe waves,
each finishing at a reviewed PR with recorded evidence and the merge held for the supervising
session. The batch fixes the fleet's two operational leaks (teardown handles, watch re-polling),
hardens /board plan with a prior-art gate, keeps large prose off the command line, and pivots
documentation from generated wiki pages to DeepWiki routing plus its MCP integration.
Fixed
-Sessions -Watchno longer re-polls every registered session on every 30-second cycle (#414).
Three related defects: (1) sessions already reportedLISTOin a prior cycle were re-polled on
every subsequent cycle — 31 zombie entries from 2026-07-13 drove ~33 API calls per cycle,
exhausting the 5 000/hr GraphQL quota within four watch runs. Fix: a$doneSettracks issues
already known to be done; at the start of each cycleReadSessionsoutput is filtered through
it, so a done session is never polled again. (2) zombie sessions (dead PID +workPathgone from
disk) were never pruned and kept accumulating insessions.json. Fix: injectable$IsStale
scriptblock detects these; when stale, the session is removed from the registry via
Remove-SessionRegistryEntry -Outcome stale-prune— zero API calls, noGetStatusprobe at all.
(3) whenghfailed due to a rate-limit error,Get-SessionLiveStatussilently fell back to
process-liveness and printedLISTO: proceso terminado— indistinguishable in the output from a
verifiedLISTO: PR merged. Fix:Get-SessionLiveStatuscaptures stderr with2>&1, checks
$LASTEXITCODE, and returns{ done=$false; reason='UNKNOWN (rate limit)'; rateLimited=$true }
on any rate-limit hit — never degrading to a PID signal that looks verified. The watch loop
prints rate-limited sessions inDarkYellowso the degraded state is visible. Four new Pester
tests cover: no re-poll after LISTO, stale-prune skips GetStatus, stale-prune writes
stale-pruneoutcome, rate-limited session never reported done.- Large inline text payloads no longer abort Board-Plan / Board-Handoff (#419). The tool-layer
path guard pattern-matches the full command string: prose containing slash-commands (/board,
/scan,/knowledge,/expert) passed as inline parameters was misread as a filesystem path
and caused the whole command to abort before the script ran — accounting for 65 field failures
across 945 sessions (Board-Plan.ps1 at 30.3% failure rate, Board-Handoff.ps1 at 22%). The fix
is to keep large payloads off the command line entirely.Board-Plan.ps1now accepts
-DescriptionFile,-ResearchFile, and-PriorArtFile(each a path to a plain-text file);
Board-Handoff.ps1now accepts-BodyFile(path to a JSON file withNextStep,Done,
OpenThreads,Traps,KeyFileskeys). Inline parameters remain supported unchanged for short
text. The newResolve-TextParamandRead-HandoffBodyFilepure helpers are covered by 14 new
Pester tests (file reads content, slashes treated as data, destructive text never executed,
missing-file and directory-path throw, file wins over inline — a field present in the JSON
overrides even when explicitly empty, while an absent field falls back to the inline default). -AutoCleanteardown now kills the Windows Terminal tab shell and deletes the worktree folder (#413).Board-Work.ps1 -Sessions -Watch -AutoCleanpreviously left thepwsh -NoExittab shell alive (its handle blockedgit worktree removefrom deleting the directory) and printed a manual-cleanup note. The fix: before runninggit worktree remove, teardown finds the tab's shell by its launch-script command line (launch-<n>.ps1— exact match on the issue number, so sibling sessions are never touched), kills it viaStop-ProcessTree, waits 500 ms for the OS to release the handle, then runsRemove-Itemaftergitde-registers the worktree. When no shell is found (session already closed, or non-wtlaunch), teardown falls back to the same attempt. Two helper additions:Find-WtTabShellCore(pure, testable) +Find-WtTabShell(thin CIM wrapper, mockable) inBoardWork.Processes.ps1; 6 new Pester tests including the "do not kill a different issue's shell" guard.Publish-DocsWiki.ps1no longer generatesDocs-Command-*wiki pages (#418).
commands/*.mdfiles are agent instruction files (system prompts addressed to an AI),
not documentation. Publishing them verbatim produced a 31 KBDocs-Command-Boardpage
full of internal recipes, script paths and conditional branches — instructions readable
only by the agent that would execute them. The_Sidebarno longer links to command
pages.Docs-Homeis now purely the README with HTML stripped. StaleDocs-Command-*
pages left in the wiki are automatically removed on the next publish (the clone-write-
git add -Acycle stages their deletion). The CI freshness step is annotated to explain
why "it generates" was never evidence that "it reads" — a check that cannot ask that
question will report green on garbage forever. For code and architecture reference, route
to DeepWiki instead of generating. 15 Pester tests updated; theDocs-Command-<X>
describe block (7 tests) was removed; 2 new tests (does NOT generate any Docs-Command-* pages,does NOT link to any Docs-Command-* page) enforce the invariant.
Added
/docsis now a router, not a generator (#417).commands/docs.mddocuments the split
between what is generated and what is routed:/docs wikigenerates pages from repo content
(README + knowledge registry) and pushes them to the GitHub Wiki;/docs deepwikiresolves the
repo, checks visibility, and reports (or navigates to) the AI-authored wiki on DeepWiki without
generating anything.commands/*.mdagent instruction files are explicitly excluded from
generation — publishing them verbatim produces AI system prompts dressed as documentation.
Three new Pester tests enforce the contract:commands/docs.mdmust not reference
Docs-Command-*pages (regression guard for #418), must document both subcommands, and must
declare the public-repos-only limit for DeepWiki at the point of use.- DeepWiki MCP integration (#416). Three connected pieces: (1)
mcp.jsontoolkit catalog
entry registers the DeepWiki MCP server (cognitionai/deepwiki, three tools:
read_wiki_structure/read_wiki_contents/ask_question) so/toolscan install it via
claude mcp addlike any other referenced tool. (2)/docs deepwikiverb — runs
Get-DeepWikiStatus.ps1to resolve the current repo fromorigin, check GitHub visibility,
probedeepwiki.com, and reportindexed,not-indexed,private, orunknown. The
public-repos-only limit (private repos require paid Devin) is documented at the point of use,
never buried. (3)tools-catalogskill'sresearch <id>action now callsask_questionas
the first probe when the MCP is configured, before any clone. Machinery: newmcptoolkit
kind (next toskill-cloneandplugin), detected viaclaude mcp listthrough new
Get-InstalledMcpServers.ps1;Get-ToolsCatalogandInstall-ToolFromCatalogextended to
handle the new kind; 17 new Pester tests across Get-DeepWikiStatus, Get-ToolsCatalog (mcp
kind), and Install-ToolFromCatalog (mcp kind). /board planprior-art gate (#415).Board-Plan.ps1now refuses to create an epic
unless the caller provides either-PriorArt "<block>"(queries run, candidates found with
stars/license/adoption, and the explicit build-vs-reference-vs-extend decision) or-NoPriorArt
(for genuinely novel work). In both cases the outcome lands in a## Prior-art gatesection of
the epic body, making the decision auditable later. A silent skip — the failure mode that cost
five PRs on the wiki epic (#401) — is no longer possible: the script throws before creating
anything. Same enforcement shape as the-Rationalerequirement in/board triage. Verified by
9 new Pester tests covering both paths (gate and skip).
v0.33.0
The performance & autonomy overhaul — the tool stops feeling slow and stalled. A cold
engineering review (three parallel deep-dives over the work loop, the plan→expert flow and the
architecture) found the stalls were structural: an unbounded CI wait, a 6-minute reviewer poll
paid on every PR, a dead-code run budget, no automatic epic progression, zero visible signal
when a run stopped, ~50–140 uncached GitHub calls per issue, a ~1.3 s interpreter spawn on every
tool call, ~72 KB of always-loaded instructions, and a telemetry subsystem blind to time. Epic
#561 fixed all fifteen findings: #562–#576, PRs #577–#591, every behavioral change with tests
verified by breaking the code, and every PR through the review gate (Codex external review in
rounds while quota lasted — 40+ findings fixed across the batch — then the sanctioned
self-review fallback, stated on each record).
Added
- The product agenda policy is written down (#576, closing epic #561's list).
CONTRIBUTING.md
now states the rebalance rule the architecture review argued for: releases should carry
user-facing value (the last measured week ran 8:1 self-directed); before building a new
self-control mechanism, ask what measurement would make it unnecessary; and field evidence
(/board telemetry, now with durations) outranks introspection when picking the next piece
of work. A policy, not a gate — deliberately unenforced.
Changed
Board-Work.ps1sheds its inline job scheduler and process killer (#575, part of epic
#561; first slice of the monolith split). The 3,176-line dispatcher — 18.7% of all script
code — contained a machine-capacity governor and a process-tree supervisor a board tool has
no business holding inline. Both clusters moved verbatim into part files
(BoardWork.Capacity.ps1: capacity snapshot, dispatch plan, fleet slot pacing, dispatch
governor;BoardWork.Processes.ps1: pid→parent map, descendant walk, guarded tree kill,
orphan detection and reaping), dot-sourced before the test guard so every caller and test
sees the exact same surface. Behavior-preserving by construction and by evidence: 371 tests
across the Board-Work, Board-Doctor and lint suites stay green with zero changes to them.
Further slices (session registry, worktree launch) can now follow the same pattern.
Added
.agentic-board/finally has a garbage collector (#574, part of epic #561). The state dir
had accumulated 48 files across the project's entire life — briefings and launch scripts for
issues #123→#532, signal markers, compaction snapshots — because nothing ever cleaned it.
Clear-AbiosState.ps1reaps only regenerable per-run debris (briefings, launch scripts,
expert briefs, per-issue logs, signal markers, snapshots) under three pinned rules: a live
session protects its files at any age, files younger than-MaxAgeDays(14) stay, and durable
records (sessions history, run-ledger marker, contract, denial log, fleet files) plus anything
unrecognized are never touched — unknown is somebody's state, not deletable. Plan-only by
default,-Forceexecutes, every reap entry carries its reason. This is the GC half of the
"one run, one record" direction; the record half was seeded by #568'ssessions-history.jsonl
archive (started/ended/outcome). Verified by 9 Pester tests.
Changed
- The always-loaded instruction payload shrank ~60% (#573, part of epic #561). Every
/boardinvocation loaded ~72 KB of instructions (≈18k tokens, ~9% of a 200k window) before
reading a single line of the actual issue —commands/board.mddescribed all 22 verbs in full
andprojects-admin/SKILL.mdduplicated the three biggest. The ten heavyweight verbs (work,
plan, fill, field, changelog, handoff, doctor, cerrar-ciclo, telemetry, triage) now live in
per-verb reference files loaded on demand when that verb runs; the command keeps the menu,
one-line routing and the safety rules, and the skill keeps identity/anchoring/conventions and
the routing table. board.md: 34.7→9.7 KB; SKILL.md: 31.6→12.7 KB. No content was deleted —
every recipe moved verbatim into its reference. Contract intact: command-surface suite green
and README regions unchanged (docs-freshness gate). This weight was causing the very
auto-compactions the run-ledger machinery exists to survive — paying the disease and the cure. - The brake hook stops taxing every tool call in every session (#572, part of epic #561).
The PreToolUse hook fires on every Bash/Edit/Write in every session and paid a full pwsh
interpreter spawn (measured ~1.3 s on the reference machine) just to discover that no
brake marker exists — the case for every session except a launched autonomous run. At 100
tool calls per session that was ~2 minutes of pure interpreter startup, paid by every user in
every repo, and the tool had no way to see it (#568's blindness, costing #572's tax).
Brake-PreCheck.cmdnow answers the common case in ~0.25 s (5× cheaper): no marker in
the working directory or three ancestors → exit 0 without ever starting pwsh. Fail direction
unchanged: an armed worktree always reaches the real PowerShell guard with its fail-closed
behavior intact, and stdin passes through untouched. Verified by 4 new tests, including an
end-to-end deny through the shim over real stdin and a no-pwsh timing bound. - GitHub reads gain a cache, and the fail-closed wrapper gains a lint that stops the
bleeding (#571, part of epic #561). Every gh invocation is a fresh process + TLS round trip
and nothing was cached — a clean work cycle made ~50–65 calls, many re-reading facts that had
not changed.Invoke-GhCachedis a small cross-process file cache (in-memory caches die
with each script's pwsh process) for read-only queries with an explicit TTL; failures are
never cached,-Forcebusts, TTL 0 always fetches. The board's Status-schema read — the
exact anti-pattern quoted in the wrapper's own header, still live — now goes through it
(5-min TTL), and four more of the worst raw sites migrated toInvoke-Gh(issue blockers,
claims, linked PRs, commit search: a 401 no longer reads as "no blockers/no claim/no PRs").
A new lint test freezes the per-file raw-gh baseline (59 sites): counts may only go down,
a new raw call names its file and fails, and stale allowances are themselves flagged.
Verified by 7 new Pester tests (cache semantics + lint). - Evidence is written once, linked twice (#570, part of epic #561). The same evidence block
used to be copied to three destinations (PR body, issue comment,evidence/<issue>.md) —
three copies of one content, drifting independently, and the "INCOMPLETE → record → re-run"
loop mostly existed to keep them in sync. Nowevidence/<issue>.mdis the single source of
truth and the PR body and issue comment carry a link stub
(Format-EvidenceLinkStub: marker + pass/fail summary + link to the file). The completion
check tightened accordingly: a surface satisfies its requirement with the marker plus
substance (the file reference or a full block — pre-#570 runs stay valid); a bare marker is
a stamp anyone can leave, and now reads as missing. Verified by 7 new Pester tests.
Added
- The definition of done scales to the diff (#569, part of epic #561). The contract's six
gates all defaulted totrueand the brief listed them flat, so a 10-line docs fix owed the
same verify pass as a model migration.Get-ApplicableDodGatesderives the owed gates from
the changed paths —bpa/tmdlBreakingonly when a semantic-model file changed,
build/lint/testsonly when something executable changed,cialways — and the
work-class CLI prints which gates the current diff owes and which it does not. Fail direction:
an unreadable diff owes every enabled gate, the derivation can only disable gates the
contract enabled (never re-enable one it turned off), and an unrecognized custom gate is
always owed. The autonomous brief now points the run at this instead of the flat list.
Verified by 6 new Pester tests. - The tool can finally measure its own time (#568, part of epic #561). The telemetry
subsystem measured everything except time: the transcript parser discarded the timestamp
every line carries, the correlation window was "6 events" (4 seconds or 40 minutes,
unknowable), the ledger's columns were events and bytes,startedhad minute granularity, and
completed session rows were deleted — every run's wall-clock cost vanished at the moment it
finished. Now: events keep their ISO timestamp; episodes carryts+durationMs(unknown
staysnull, never a fabricated zero); per-session records and the field ledger gain
durationMin;startedis stamped to the second (the supervisor reads both formats); and a
completed session row is archived tosessions-history.jsonlwithendedand its outcome
(pr-merged / cleaned / close-loop) before removal. Verified by 8 new Pester tests. - The visual work-class gate scopes to what the owner actually judges by looking (#567, part
of epic #561). In a web app (a FabricApp) every change touches html/css/assets, so the
classifier marked the whole project "visual" and the classification stopped carrying
information — when the end-to-end gate reopens (#541), that would have routed 100% of web work
to the human. The policy gainsworkClass.codeExceptions— globs subtracted from
visualPatterns, declared per project in the contract (e.g.src/components/**/*.cssfor
utility styles nobody screenshots), never guessed: the default list is empty and the
fail-visual direction is unchanged out of the box. And the verdict now carries
visualGroups— visual paths grouped by top-level section — so the owner approves "the
pages section" once instead of 47 file rows. Verified by 6 new Pester tests. - **T...
v0.32.0
Expert mode carries its method. Until now the launched session received a capability list —
what agentic-board can do — while the seven-phase loop and the research-before-deciding protocol
lived in a reference document it was never handed. This release folds the method into the brief
itself, makes the protocol the response to an error rather than a footnote, lets a run escalate to
an epic when the work outgrows its issue, and adds the first check that can tell whether a run
actually recorded its evidence instead of merely saying so. Plan #526, tasks #527 · #528 · #531 ·
#532.
Measured while shipping it, and worth stating plainly: all four autonomous runs in this plan
produced work that CI approved and external review rejected. Three silently deleted an instruction
while rewriting a sentence inFormat-AutoBrief; one added a component wired to nothing. All four
reported their work done while owing evidence they had never recorded. The cause is structural —
that function's text is not documentation about the run, it is the whole of what the run is
told, so editing prose there is editing behaviour and no syntax check can see it. Autonomous work
in this repo is not mergeable without external review; a green gate measures syntax, not judgement.
Added
-
Expert-RunVerify.ps1— a completion check that proves a run used the tool instead of
asserting it did (#532, part of #526). A run is considered COMPLETE only when all three
evidence artifacts exist and carry the[abios-evidence]marker: the versioned
evidence/<issue>.mdfile, the PR body block, and an issue comment. A run that skipped any
of them is reported INCOMPLETE, and the check names which artifact is missing — an
actionable gap, not a bare verdict.The check fails closed: null, empty, or whitespace content is treated as missing, never as
present. A PR body of"Closes #532"(the exact body the first autonomous runs left, per
evidence/527.mdandevidence/528.md) has no marker and is therefore missing. Ordinary PR
chatter in the comment list does not satisfy the comment requirement.Verified by 16 Pester tests. Each guard was confirmed by reintroducing its exact defect and
watching the matching tests go red, then restoring and returning to 16/16:Defect reintroduced Tests that went red Evidence-file check removed 4 — "names the evidence file", "null is missing", "whitespace is missing", "lists all three" PR-body check removed 3 — "names the PR body block", "non-empty body without marker is missing", "lists all three" Issue-comment check removed 4 — "names the issue comment", "chatter is not evidence", "null-propagated array is no comments", "lists all three" — restored — 16 passed, 0 failed The check is wired, and the wiring is the point.
/board expert verify <issue> <pr>is a verb
on the command surface, and phase 7 of the autonomous brief now requires the run to execute it and
quote its verdict in the final report. It is the one claim in that report the run does not get
to make about itself.Caught by external review before merging — two P1 findings, and the first is the founding defect
again. The first cut wired the check to nothing: it was referenced only by its own file and its
own test, so a run could still report "evidence recorded" with no mechanical check ever running.
The gap #532 exists to close was left open by the change closing it — the same shape the 0.31.0
notes record for the identity resolver. Fixed by the command verb and the phase-7 instruction
above, both asserted on the rendered brief.The second: the evidence file was resolved by walking three directories above
$PSScriptRoot.
That holds only in this checkout — installed, the script lives in the plugin cache, so
evidence/<issue>.mdwas looked up inside the cache and never found. A check that always answers
INCOMPLETE is as useless as one that always answers COMPLETE. The root now comes from
git rev-parse --show-toplevel, falling back to the working directory, with tests forbidding the
script-relative walk.Proven on real runs, not only on fixtures. Against #527 / PR #553, whose three artifacts were
written by hand:COMPLETE, exit 0. Against #531 / PR #558, where the autonomous run wrote
nothing:INCOMPLETE, exit 1, naming all three missing artifacts. Three consecutive runs in this
plan reported their work done having recorded no evidence at all; this is the check that makes
that state visible instead of invisible.
Changed
-
The autonomous brief now carries the seven-phase loop and the decision protocol, not a
capability list (#527, part of #526).Format-AutoBriefused to emit a bullet list of
agentic-board capabilities; the 7-phase method lived inauto-loop.md, a file the launched
session was never handed. The session had no phases, no sequence, and no instruction to
research before deciding — only "an in-scope problem: fix it in the loop and continue" as its
sole heuristic.The brief now includes the full phase sequence (Ingest → Become the expert → Execute →
Verify + evidence → Self-heal → Loop until done → Report) and an explicit decision protocol:
when you hit an error, an unexpected state, or a fork in the path, research before deciding —
do NOT act first. Steps in order: Research (check prior-art, register via/knowledge),
Register (log findings — read-and-forget is not research), Decide (then choose the path).
The capability map is retained as a lookup table inside the same section, subordinate to the
phases rather than their replacement.Verified by five tests that assert on the rendered brief text, not on the source documents: the
seven numbered phases are present, "research before deciding" appears explicitly, the
Research-Register-Decide ordering holds inside the protocol section, "read-and-forget" is called
out, and the no-improvise guard survives. Each was confirmed by reintroducing the defect and
watching the matching test go red.Caught by external review before merging, and worth recording. The rewrite replaced the old
headingtotal self-use of agentic-board (do NOT improvise your own tooling)and never restated
it. Since this function composes the only text the launched session ever receives, deleting that
line deleted the instruction — a capability map lists options, it does not forbid inventing one.
The plan's own founding principle, dropped by the change meant to strengthen it. The guard is back
in the section heading and is now asserted.Review also found the ordering test measuring the first occurrence of Research / Register /
Decide across the whole brief. "Research" already appears in phase 2, so the test could stay green
with the protocol steps scrambled — a test that reported a guarantee it did not hold. It is now
scoped to the decision-protocol section, and the phase test matches the numbered markers rather
than bare words ("verify" and "report" also occur in the capability map, so the loose match
survived deleting the phases). -
Autonomous brief now instructs the run to escalate to an epic + sub-issues when the work
outgrows its issue (#531, part of #526). A run that discovered six tasks had one move: drop six
loosediscoveredissues on the board with no parent and no order.Board-Plan.ps1already
created epics + native sub-issues for humans; it was unreachable from inside an autonomous run.Format-AutoBriefnow includes a Self-planning — escalating to an epic section that:- tells the run to use
/board planrather than filing loose issues, - caps the sub-issues of the created epic by the existing
boardSelfDrive.cap(same limit as
discoveredissues, so self-planning stays bounded), and - requires linking the new epic back to the originating issue for traceability.
Verified by six tests that assert on the rendered brief text, each confirmed by reintroducing its
defect and watching the matching test go red.Caught by external review before merging — two defects, both known shapes. The first cut told
the run to useBoard-Plan.ps1and pinned that with a test asserting the script name. The brief
travels to runs working in any repository, where a plugin script name resolves to nothing — the
defect #480 and #494 are already open on, reintroduced and then locked in by its own test. It now
names/board plan, and the test asserts the command and forbids the script name.The cap was read as
if ($Contract.boardSelfDrive.cap). In PowerShell0is falsy, so a contract
settingcap = 0— create nothing — was read as absent and the run was told it could create
ten. Presence is now tested, not truthiness, with a test constructing exactly thecap = 0
contract. The same fail-open shape this project keeps finding: the safe reading of a missing
value is not the permissive one. - tells the run to use
-
Phase 5 self-heal now explicitly invokes the decision protocol, replacing fix-it-and-continue
(#528, part of #526). After #527 added the decision protocol to the brief, Phase 5 still said
"fix it (after researching first)" — a parenthetical that reads as act-first with a research note,
not as research-first with an act-later decision. Phase 5 now reads: "when you hit an error, a
fork, or an unexpected state — apply the decision protocol (research → register → decide); do
NOT act first." The protocol section below it (Research → Register → Decide) is now the named
authority that self-heal defers to, rather than a separate section that contradicts the phase's
framing. Verified by five tests scoped to Phase 5 text, each confirmed by reintroducing its defect
and watching the matching test go red.Caught by external review before merging — the same defect two PRs running. The first cut
reframed th...
v0.31.0
Added
-
A braked run can now authenticate as a machine account instead of as the owner (#550, part of
#541). The autonomy brake could never be complete as a text classifier — eleven of the nineteen
defects found on 2026-07-31 were that same defect in different clothes. The capability side is
different in kind, and it is now wired.mainrequires a pull request, but the ruleset exempts the repository admin role, and a PAT
authenticates as its owner — so any token of his walks straight through. Weaker permissions do
not help: GitHub cannot distinguish "the human typed this" from "an agent used the human's token",
because they are the same principal. Only a different identity gets a different answer.Measured, not argued, against the new machine account:
Action Result write to mainRepository rule violations found — Changes must be made through a pull request (422)create an ordinary branch 200 OK Refused exactly where it matters, still able to do the work. New: one resolver answering which
identity applies here, wired intoNew-BoardPR— the script a braked run actually pushes
through — withBoard-MergeandPublish-DocsWikinow sharing its owner→variable map instead of
each carrying a copy.gh-accountteaches the same rule inline, because a run's owngit push
never goes through a plugin script; that snippet stays path-independent by design and does not
call the resolver.Caught by review before merging, and worth recording: the first cut added the resolver, claimed
the consolidation, and wired it to nothing — referenced only by its own test while four scripts
kept their duplicate maps. A fifth copy of the rule, shipped as a fix for having copies. That is
this tool's founding defect (reporting intent as fact) committed inside the change meant to cure
it. Four tests now assert the claim rather than assert it in prose: the resolver is referenced by
real scripts, the push path uses it, no duplicated owner map survives, and every consumer loads it
behind its dot-source guard.Inside an armed run there is no route back to the owner identity — including
-TokenVar.
Review round 2 found the first cut branching on an explicit-TokenVarand skipping the armed
check on that branch, under the comment "explicit override wins": a bypass written as a feature,
and one flag was enough to undo the whole change. The branch is gone rather than guarded — an
override is now something the resolver judges, not something that routes around it — with a
structural test forbidding the push script from branching on-TokenVaragain. Outside an armed
run an override behaves exactly as before; that is how cross-account work happens.A missing agent token inside an armed run FAILS; it does not fall back to the owner's PAT. A
silent fallback would hand the run precisely the capability the brake exists to remove while every
message still read "brake armed" — the defect this repo has now found in the brake (#440), the
review gate (#510) and the evidence blocks (#479). Mutation-verified: introducing that fallback
turns 4 tests red.Stated limit, deliberately not dressed up. This removes the default capability, not the
possibility. A run that ignores the skill and readsGITHUB_TOKEN_PERSONALfrom the registry
itself is not stopped — the token is ambient in the Windows user environment and cannot be taken
away from a process running as that user. The deliberate case is what #517 and the review gate are
for. Treating a backstop as a sandbox is what produced most of yesterday's defects.
v0.30.0
Fixed
-
A braked run could push straight to the default branch (#542). The brake watched
gh pr merge,
the REST merge endpoints andBoard-Merge.ps1— and missed the simplest route of all:
git push origin HEAD:mainputs work on main with one command and matched nothing. Present in
every version of the brake, including shipped 0.29.0, and found only when an external review was
pointed at whether the door was really shut rather than at the change in front of it.Not quite an oversight, which is the interesting part: the delete pattern carried a comment
callingHEAD:main"an ordinary push refspec", written while guarding against over-matching.
A test pinned that reading in place. Both were correct about the delete pattern and wrong about
the vocabulary — this guard defines merge as "putting work on the default branch", which is
exactly what that refspec does. The obsolete assertion was corrected, not worked around.Now refused: refspecs onto
main/master(HEAD:main,branch:main,+HEAD:main,
HEAD:refs/heads/main) and pushing the default branch by name.How the branch name must end took three tries, each one a real defect — and the CI reviewer
caught both mistakes, in the first two reviews it managed to publish after #543 raised its turn
cap.\brefusedHEAD:main-cleanupandHEAD:master.bak: legitimate branches, blocked, on the
run's most common command. Replacing it with(\s|$)fixed that and reopened a different
hole — that only accepts a space or end-of-segment, and the segment splitter does not treat a
lone&or a redirection as a separator, sogit push origin HEAD:main&(background the push,
same effect) matched nothing, a case the original\bhad caught. It now ends at a lookahead
over the shell's own terminator set, which covers both directions.A third review round found the anchor itself was wrong — and had always been. Every git
rule in this file began\bgit\s+push\b, which demands thatpushfollowgit
immediately. One ordinary global flag shook all of them off at once:git -C . push origin HEAD:main,git -c k=v push …,git --no-pager push …. That was never about the new patterns —
the pre-existing branch-delete rules had the same hole, andgit -C . push origin --delete f
went through untouched. All four git rules now share one prefix that tolerates flag-shaped tokens
between the program and the subcommand, so fixing it once fixed the older rules too. Only
flag-shaped tokens are allowed in that gap, sogit commit -m "push to main"is still not read
as a push, and the repetition is bounded rather than*— this runs on every tool call, and an
unbounded nested quantifier over adversarial input is a stall waiting to happen (measured: 1 ms
against a command carrying 200 flag-like tokens).Round four found two more in that fix — and measuring the repair found a third nobody had
seen. The flag allowance was capped at five, which was itself a bypass: six-cflags and the
rule stopped matching, and chaining-cis ordinary scripting. And[:/]treated/as a
refspec separator, which it is not —release/master,team/mainandHEAD:team/mainwere all
refused for merely ending in the default branch's name.Removing the cap came with an argument, made by the reviewer and accepted by me, that the
repetition could not blow up because its character classes are disjoint. The argument was
wrong and only the stopwatch said so:--flagrepeated 1000 times ran the matcher past three
minutes, because-{1,2}and[^\s;|&]+can both consume the second dash — two readings per
token, 2^1000 for a thousand of them. This code runs on every tool call, so that is not a slow
path, it is a wedged session, and a wedged guard is a removed guard. The form now allows exactly
one reading per token: measured at 3 ms with no match and 334 ms in the absurd worst case, with
three timing tests to catch a return — the suite was green with the hang present.Recorded in the source, because this file keeps teaching it: "the classes are disjoint so it
cannot blow up" is an argument, not a measurement. Time it.Round five found no bypass at all — the first round on this change that did not. Its one
finding was about honesty rather than permission:git push origin :maindeletes the remote
default branch, but the merge rule matched it first (its source side allowed zero characters),
so the refusal announced "merge is marked irreversible" for a command that removes main. Not a
bypass — the contract filter runs per pattern, so a delete-braking contract already refused it —
but a control is worth exactly as much as the account it gives of itself, which is this tool's
founding defect. The refspec source now requires at least one character, so an empty source falls
through to the delete rule where it belongs.Recorded as a decision rather than left to chance: a contract that brakes merges but not
deletes no longer stops that command. It is a delete, and the guard follows the contract instead
of inventing policy — the rule this file opens with. There is a test saying so.Round six, also no bypass, and the last one taken: the prefix before the refspec excluded
only;, so the matcher could step past a background&and read a latersomething:mainin
the same segment as the push target —git push origin fine & echo notes:mainwas refused for
text belonging to a different command. A false positive, never a bypass (a looser prefix can only
add matches), fixed because over-blocking on the run's most common command is the argument this
whole entry rests on. The prefix now stops at the same operators the branch-name lookahead uses.
The review's second example,> log:main.txt, did not reproduce — checked rather than assumed.Stopped here deliberately. Rounds one to four found real defects — a bypass, a false
positive, an anchor broken for months, and a matcher that could hang the session. Rounds five and
six found no bypass at all: a mislabelled verb and this over-block. The curve flattened, so the
loop was ended on judgement rather than run until it produced nothing.Known and accepted:
git push mainis read as a push to the default branch even whenmain
is the name of a remote. A false positive, not a bypass, on a rare spelling — and this pattern
deliberately errs toward refusing.The original over-block, for the record: the tests missed it because the case they checked,
maintenance, continues witht(a word character) and so passed for the wrong reason —
proving nothing about-or.. A green test that passes by accident is the same failure this
whole entry is about. The reviewer also noted
thatHEAD:refs/heads/mainwas claimed in the description and the code comment but asserted
nowhere; it has a test now. Deliberately still allowed, since
this pattern sits on the run's most common command and over-blocking is how a control gets
switched off:git push -u origin <branch>, a baregit push,--force-with-leaseon its own
branch, and any branch whose name merely containsmain(issue-9-domain-model,
feature/maintenance,HEAD:my-domain). Stated limit: the pure core cannot ask the repo what
its default branch is, so a project whose default is neithermainnormasteris not covered. -
The reviewer's turn cap sat one turn above what a real review needs (#543). Measured across
recent runs: reviews that actually published consumed 4 / 13 / 17 / 18 / 19 turns against a
cap of 20, and three consecutive runs on a large PR died at 21. A run that hits the cap leaves
no review, so the verification added in #510 fails the check — correctly — and the review gate
ends up blocking legitimate PRs on an infrastructure limit. That is how a control gets switched
off: not by argument, but by being wrong often enough. Raised to 40, a little over twice the
observed maximum; time was never the constraint (those runs finish in 2-4 minutes against a
20-minute job timeout). -
-EndToEndshipped inert in 0.29.0, and after five review findings it is staying inert — on
purpose (#536, #541). Field-testing the autonomy boundary found the ordered end-to-end close
could refuse but never allow. Three separate blockers were fixed; then three rounds of external
review found five live false-permission paths in the mechanism that would have opened it, two of
them complete bypasses. The boundary held throughout — nothing merged that should not — and
every failure failed safe. The order is now recorded, explained, and acted on nowhere.What was genuinely broken and is now fixed:
- The tests condition could never be satisfied.
Invoke-BrakeMergeCheckdot-sources
Board-ReviewGate.ps1, whoseparam()block runs in the caller's scope and declares
[int]$PR = 0.$PRsilently became0immediately beforegh pr checks $PR, which exits 1
with empty output, so the CI condition was always false. Proven against a real PR whose four
checks all passed: the gate refused for "missing test evidence"; the same inputs without the
clobber were permitted. Stated precisely: the bug is gone because the code that had it was
withdrawn with the allowance, not because a line was patched around it. What remains is the
trap, recorded where the gate will be rebuilt and guarded by a test that proves the review
gate really does clobber a caller's$PR— so #541 cannot walk into it again. - The tests requirement ignored the contract, being hardcoded
$true; and reading it cast
the string"false"to$true, the trap already closed for the marker'sendToEnd. - The brief never mentioned the order, so a session carrying one was briefed exactly li...
- The tests condition could never be satisfied.
v0.29.0
Added
-
/board expert auto -EndToEnd— an autonomous run can now finish what it started, under four
conditions (#530, part of #526). The brake stops being all-or-nothing: a run the owner ordered
to finish may close code work that carries a real review and recorded tests for the current
commit. Everything else still stops for him.The four conditions, all established at merge time — the first moment the facts exist, since
the brake marker is written before a line of code is:Condition Why it cannot be dropped The owner ordered it The permission travels with the instruction, not with a setting written weeks ago The change is code-class (#529) What he judges by looking at it stays his, even when he ordered the finish A real review exists for this commit (#510) A green check is not a review Automated tests ran and left evidence "Testable and untested" is not finished work Why four rather than a priority order. Each covers a failure the others cannot see: a
reviewed, tested change that is a dashboard is still his to approve; an ordered, reviewed,
code-class change nobody ran is still unverified. Collapsing them into one flag is exactly how a
control ends up meaning less than it says — the defect already found in the brake (#440), the
review gate (#510) and the evidence blocks (#479). A refusal names every unmet condition, not
the first: one reason at a time forces a round trip per condition and misrepresents how far the
work actually is.The tests requirement reads from the contract's
dod.tests, never from the run's own opinion
that its change was untestable — a per-run "no se podía probar" is precisely the self-issued
excuse this removes. A project with genuinely no automated tests says so once, in writing.Failing to establish the facts is not a yes: if the class, the review or the evidence cannot be
read, the merge is refused with that as the stated reason.Found by its own tests while building: the decision function dot-sourced the classifier without
its guard, so every call also ran agit diffand printed a banner — a decision function with
a side effect per call, running git while deciding whether a merge is allowed.19 decision tests + 5 marker tests, mutation-verified: removing the order condition turns 3 red,
the work-class condition 6, the review condition 4, and the tests condition 5. -
The autonomy boundary can finally express the owner's actual rule (#529, part of #526).
Until nowautonomy.irreversiblewas a flat list of actions — the same for every kind of work —
so "code the agent closes by itself; anything I can judge by looking at it waits for me" had
nowhere to live. The boundary is now about what the change produces, not which action performs
it: a newworkClasspolicy in the contract plus a pure classifier that reads the touched paths.Why this framing rather than a stricter action list: asking someone who does not read code to
approve a diff is not caution, it hands them a decision they have no way to make. Asking them to
approve a dashboard is the opposite — they are the only one who can look at it and say whether
it reads right. So reports, pages, themes and images route to the human; scripts, tests, workflows
and prose stay with the agent.Three decisions worth stating, because each is load-bearing:
- Timing. The brake marker is written at launch, before a line exists, so the class cannot
be decided there. The contract carries the policy; the classification runs later against the
paths the run actually touched. Facts first, decision second. - One visual file makes the whole change visual. The failures are wildly asymmetric — the
owner glancing at something routine costs a minute; an agent shipping a report he wanted to see
costs trust. - "I could not tell what changed" is
unknown, nevercode, andunknowngoes to the human.
Conflating "I looked and it was all code" with "I could not look" is precisely the defect this
repo keeps finding in its own surfaces.
Globs are compiled to regex rather than handed to
-like, which has no**and whose*crosses
/— under-like,src/*.csswould swallowsrc/deep/nested/main.cssand the classification
would widen with every subdirectory. A project can declare what is visual for it (a website
where the posts are the product), replacing the defaults rather than silently merging with them.This alone changes no behaviour yet — it adds the vocabulary and the classifier; nothing
consults them. Said plainly because the alternative is a release note that implies a capability
the code does not have.mergeremains in every default contract's irreversible list, so no run
can merge regardless of class. Wiring it into the merge decision — against the actual changed
paths, at merge time, where the facts exist — is #530, and its review requirement is recorded on
that issue rather than left as a hope.36 tests, mutation-verified: classifying nothing as visual turns 8 red, letting
unknownpass as
approved 1, letting a single*cross a directory boundary 1, and restoring the
TrimStart('./')bug 1 — that last one found by external review:TrimStarttakes a character
set, so it ate the leading dot of.reports/x.md, and a project pattern like.reports/**
would silently stop matching. A visual change reclassified as code is the one direction that
costs trust. - Timing. The brake marker is written at launch, before a line exists, so the class cannot
Fixed
-
A reviewer that never reviewed no longer reads as approval (#510). On PR #508 the
claude-reviewcheck reported success while the PR ended with zero reviews and zero
comments — twice, on two runs. The gate then printed the sameGATE PASSEDit prints for a
genuinely clean review, with the self-review reminder as a footnote nobody had to act on. This
landed in the worst possible window: Copilot has been quota-blocked for weeks, so that workflow
was the only automated reviewer on the repo, and it was passing without reviewing.Fixed on both sides:
- The workflow now fails when it produced no review. Publishing the review is part of the
task, not a side effect — the job was running, exiting clean and leaving nothing because it had
neither the instruction nor the tool to comment. It now gets both, and a verification step
turns the check red when no review and no[abios-review]comment landed. - The gate distinguishes "reviewed, found nothing" from "nobody looked." A new exit code
2 —GATE SIN REVISAR— reports the second case. It is deliberately not 0: any caller
testing-eq 0now fails closed, while still telling an unreviewed PR apart from a genuinely
blocked one (1). Nothing in the repo branched on the gate's exit code programmatically, so this
changes no existing behaviour silently. - A reviewer with no GitHub identity can now be counted.
second-opinion(Codex) is the
reviewer that actually shows up here, and it submits no review object — so to the gate it was
indistinguishable from nobody.-RecordReview -Reviewer <who> -Summary <what>writes the
evidence onto the PR itself, where it survives the session. -AllowUnreviewedis the deliberate exception for changes where a review buys nothing (a typo,
a regenerated file). It prints that nobody read the code rather than implying someone did.
All evidence is bound to the PR's head commit, and external review found that this was the
whole ballgame. The first cut counted any review ever left on the PR, which reproduced the
original defect one level up: approve, push three more commits, and the gate would authorise a
diff nobody had read on the strength of a review of different code. Now a GitHub review counts
only for the commit it was performed on,-RecordReviewstamps the head SHA into the record, and
the workflow's verification demands its own marker for this SHA — so a run that publishes
nothing can no longer coast on an earlier run's comment. When evidence exists but belongs to an
older commit the gate says so explicitly ("empujaste cambios después de que se revisó") instead of
reporting a bare zero. The cost is deliberate: a new push invalidates the evidence and someone has
to look again, which is the correct reading — those commits genuinely have not been reviewed.-RecordReviewnow requires-Summary. Without it, it was a one-flag way to stamp "reviewed"
on a PR nobody read — the same empty assurance as the original bug, with a different author.
Having to state what the review found is the cheapest available proof that one happened.The root cause, confirmed live while this very PR was being gated (run 30578175712):
claude-code-actionskips itself and exitsoutcome=successon any PR that edits its own
workflow file — a GitHub security measure, so a PR cannot rewrite the reviewer that reviews it.
Correct as a measure; the problem is that the signal it emits is indistinguishable from "reviewed,
found nothing". This repo edits that file whenever it tunes the review engine, so it is not an
edge case. The verification step now names this cause in its error output, and the gate stops the
deadlock it would otherwise create: a failing REVIEWER check asks "was this reviewed?", not
"does the code work?" — so once a real review is on record for the commit, the reviewer's own red
is no longer a blocker. Narrow on purpose: one non-reviewer failure and it blocks as before, and
the failing-check list is read from structured data (gh pr checks --json) rather than scraped
from the printed table. That distinction is load-bearing — the first cut parsed the human-readable
output, where any failure printed in an unexpected shape would drop out of the list, leave a
... - The workflow now fails when it produced no review. Publishing the review is part of the
v0.28.1
Changed
- CI run volume cut ~60 %, and Board Sync no longer triggers itself (#504; PR #512). The sync
workflow listened forassignedwhileboard-sync.shassigns unassigned issues itself, so its own
write re-triggered it — every new issue cost at least two runs, the second recomputing a state the
first had already reconciled.assignedis gone, and a burst of issue events now collapses onto one
run viaconcurrency: cancel-in-progress(safe precisely because the sync is a full-state
reconciler, not an incremental writer; the telemetry snapshot deliberately does NOT cancel, since a
half-written run there loses data no later run can rebuild).
ci.ymlgates on the pull request only — a push tomainis always a merge of something those
same jobs already passed, so re-running them after the merge doubled the cost of every change
without ever catching anything new.pr-reviewandissue-languageare grouped per PR / per issue
and cancel superseded runs: addressing gate feedback with three quick commits used to start three
reviews of three diffs, two already obsolete — which protects the Max review quota, not just
Actions minutes. Every job now carries an explicittimeout-minutes(the default is 360, so one
hung job could eat 12 % of the monthly quota), and test-log artifacts drop from 90-day to 7-day
retention.
Fixed
- A mature board no longer reports itself empty (#484).
Board-Work.ps1read board items with
gh project item-list --limit 200. That call returns exit 0 and exactly 200 items on a bigger
board — oldest-first, so on a mature board the cap fills with Done work and the Backlog falls
off the end. Against the tool's own 291-item board,/board workprinted
Sin pendientes. Todo el board esta en progreso o terminado.over 37 open Backlog items.
Not a truncation warning — a confident false all-clear, landing precisely on the mature boards
where the stakes are highest, and it silently under-counted the-ListBoardsboard picker the
same way.
Board reads now go throughGet-BoardItems.ps1, which returns{ Items; Read; Limit; Truncated }
and treats a read that reached its cap as possibly short. No caller may state an absence off
one. Every board reader was audited, not just the two in the bug report — the caps ranged from 200
to 1000 and six surfaces were asserting things a short read cannot support:/board worksays how many items it actually saw instead of "sin pendientes"; the board picker
renders capped counts asN+with an explicitTRUNCADOline./board completefails closed — aPASSis exactly the absence a short read cannot
support, and CI would read it as ground truth./board triageno longer prints "(no hay items pendientes)" over an untriaged board./board field apply --merge-conflictsno longer deletes an option on an unproven verification.
The worst of the six: it moves items off a legacy option, checks that none remain, then deletes
it — and its own comment names the stake ("an item silently losing its Status is data loss").
The check read with a baregh ... --limit 800and no cap test, so "0 left" could be an
artifact of the cap. A truncated verification now aborts on the same grounds as a found item.Backup-Boardrefuses to write a partial snapshot: it is the safety net taken before a
destructive operation, so a partial one is worse than none — it would license the delete it
exists to make reversible.Export-BoardSnapshotrefuses to publish a truncatedN of M, and/board updatepublishes
floors (N+) rather than stating a count and retracting it in a footnote.Set-BoardFieldwarns before its sweep that the pass is partial, instead of printing a
set=Nsummary that reads like a complete one.
The shared ceiling is 2000 and costs nothing:ghpages the underlying GraphQL 100 at a time, so
request count tracks the items that exist, not the cap — the old 200 bought no savings and cost
the truth. Regression tests assert that no script hardcodes its ownitem-listcap and that every
board reader pulls in the shared one.
This is the same defect classInvoke-Gh.ps1was written for (#303): a read consumed as fact.
Invoke-Ghmade a failed read loud; this makes a short one loud. Neither substitutes for
the other — a truncated read succeeds.