Repository navigation
fix(vibe): ship the 1.0.76 fleet lessons release - #337
Merged
Merged
Conversation
- Questions carry a default and a deadline (session-questions.mjs --default, --deadline-minutes and the If no answer by line); the recovery pass resumes an idle Codex writer on its default once the deadline passes, never for a flag, copy, destructive, credential or scope question. watch.sh exits with HEARTBEAT every 30 quiet minutes and board.mjs status fails with WATCHER-MISSING. Writers' questions once sat unread for seven hours overnight. - fleet.sh refuses a prompt with Done: claims and no Re-verify: line; the takeover prompt makes the new writer re-verify each claim. - migration-publish.mjs refuses a push that adds a migration with no recorded test, or per-row work with no measured scale test; the environment worker runs it before every push and handoff runs the same check. - Handoff's spec-change rule blocks an edit to an existing spec with no dated human source; rulings-check.mjs --criteria-before blocks a ticket version that rewords or drops a criterion no ruling amends. - executed-check.mjs --kind ticket refuses DONE with unchecked criteria; the ticket worker, cl-execute and cl-sweep run it and status-audit.mjs reads each merged or DONE child's body. - shepherd.mjs stops on a review thread the writer has not answered last; it shares pr-threads.mjs with board.mjs. - vibe 1.0.76 and its changelog. Testing: node --test on each changed suite and pytest on the vibe contracts; each new check shown failing with its fix removed. Full suites run on a second machine. Risks: vibe questions other than flag, copy, destructive, credential or scope ones now need a default and a deadline; status-audit needs the body of every merged or DONE child.
- A flag approval asks about a "feature switch", so session-questions treats switch and toggle questions as waiting for the person, with no default or deadline; flag-keys.test.mjs caught it. - Follow-up: accepts any tracker slug, and the fleet test fixture uses a generic one, as generic.test.mjs requires. - An existing test that applies every migration counts as the migration test, since a vibe session writes no tests unless asked for a pull request; the inventory case records one. Testing: flag-keys, session-questions, generic, status-audit, executed-check and the inventory case pass locally. Risks: None identified.
- The 1.0.76 entry says a feature switch question waits for the person and an existing test that applies every migration counts. Testing: none needed for changelog text. Risks: None identified.
- snapshot.sh marks STALE-PR when an open PR with threads, a conflict, a draft flag or a failed check has had no push for 45 minutes. - classify.sh marks QUESTION-UNANSWERED on an idle writer's question after 15 minutes; board.mjs tracks Claude writers' questions the same way. - session-questions.mjs caps a default's deadline at 30 minutes and refuses a question that holds a merge, a push or the PR. - Writers push every 45 minutes (push-cadence.mjs), never held for optional evidence; the 1.0.74 contract follows the ruling. Testing: snapshot, liveness, board, session-questions, flag-keys and push-cadence suites. Risks: questions that say they hold a PR are refused.
- shepherd.mjs splits unresolved threads at the round-one fix push (--round-one-fixed-at): round-one threads stop it as work (threads, exit 13), later ones stop it for triage (later-threads, exit 14). - review-followup.mjs plans the pull request's one follow-up ticket: create once, then later rounds append criteria to the same ticket; recording a second ticket is refused. - review-threads.mjs resolves a later-round thread answered with the follow-up link or a reason, and refuses to treat one as fixed here. Testing: shepherd, review-followup and review-threads suites. Risks: None identified.
- hooks/kill-guard.mjs, registered for Bash in hooks/hooks.json, denies pkill, killall, xargs kill, and kill of a pattern, a variable, a command substitution, a pid of 1 or lower or a pid this session did not record; kill-guard.mjs own <pid> records one, tied to its directory and process start time. A writer ran pkill -f on a shared laptop after a written rule forbade it. Testing: kill-guard.test.mjs asks the guard for decisions only and sends no signal. Risks: Claude sessions with vibe installed must record a pid before kill <pid>.
- rollout-gate.mjs (handoff) fails each flag, rollback or rollout requirement in the ticket criteria or the PRD's rollout section that records neither its code at HEAD (a flag requirement's line reads a flag) nor a human ruling naming the ticket or PRD. - rulings-check.mjs --ruling --scope, executed-check --kind ticket --slug and status-audit (UNSCOPED) accept a ruling only when it names the ticket or its PRD. - qa-plan-check.mjs --criteria makes every rollout requirement a cited checkpoint. Testing: rollout-gate, rulings-check, executed-check, status-audit and qa-plan-check suites with PRD-shaped fixtures. Risks: executed-check --kind ticket now needs --slug.
- board.mjs status prints MISSING-DUTY for each REQUIRED_DUTIES entry without an owner (green-watch by default while PRs are watched). - fleet.sh slot heavy runs load-check.sh first and refuses a machine over MAX_LOAD_PER_CORE, naming the other machine. - fleet-orchestrator SKILL gains Program practices (green watch, placement, repo knowledge, close-out, deadline mode, runtime choice) with their config keys in setup.md. Testing: board suite locally; fleet.test.mjs on the second machine. Risks: a heavy slot on a loaded machine now refuses unless FLEET_FORCE_HERE=1.
- The kill guard and its test name the by-name killers without the literal shapes the repository's signal and by-name scans read; the guardrail keeps its never-by-name sentence. - A ledger cannot make a pid of 1 or lower killable; the test pins it. - Changelog entries for the added items. Testing: pytest on the vibe contracts and the kill-guard suite. Risks: None identified.
- Keeps both changelog entries: vibe v1.0.76 above closedloop-core v1.5.2. Testing: no code conflict; changelog only. Risks: None identified.
- board.mjs writer --ready records a writer's push-ready head and push-check refuses any other head; the fleet rule pushes only that. - green-watch.mjs re-lists main's runs on every call and reports every lane of every run containing a program PR, with NO-RUN for an unbatched head. - claude-usage.mjs reads claude -p /usage into a claude-usage line on the Codex bands; the fleet skill moves Claude writers to Codex near the limit. - The requirements worker's reference mode observes the live product through the signed-in browser or returns NEEDS_PERSON; a contract test refuses help center guidance. Testing: board, green-watch, claude-usage, snapshot suites; the contract test shown failing with the old worker text. Risks: snapshot runs claude -p /usage every 10 minutes unless CLAUDE_USAGE=0.
- status-audit.mjs, the program's status sweep, prints PLAN-OPEN for a DONE ticket whose plan is not EXECUTED and ABSORBED-OPEN for a child left open after the umbrella that absorbed it closed. - status-sweep joins green-watch as a required board duty; the fleet skill runs the sweep at every merge, assignment and status report and closes absorbed children with their umbrella. Testing: status-audit, board and generic suites; vibe contracts. Risks: a program with watched PRs now needs a status-sweep owner.
- The sweep bullet keeps "At every writer assignment and every merge". Testing: vibe contracts. Risks: None identified.
- Ready heads, green watch, Claude usage, the status sweep and live reference research. Testing: none needed for changelog text. Risks: None identified.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Purpose
This PR ships the fourteen open vibe lessons ledger entries, the operator rulings and two consistency fixes from the 2026-10-22 fleet run as one pull request and one vibe 1.0.76 release. Each lesson gets a mechanical check where one fits, and each check extends the 1.0.75 scripts rather than adding a parallel one.
Release metadata
plugins/vibe/tools/python/test_vibe_1076_lessons_contract.py.plugins/vibegrows by about 1,550 words (2,564 added, 1,016 removed).test_vibe_1074_lessons_contract.pyandpush-cadence.test.mjsmove the push cadence from 90 to 45 minutes (operator ruling, 2026-10-22).status-audit.test.mjsfixtures now carry ticket bodies and scoped out-of-scope reasons.handoff-inventory.test.mjsrecords a migration test for its migration case.Affected files
scripts/green-watch.mjs, codex-fleet-monitorscripts/claude-usage.mjs, andagents/vibe-requirements-worker.mdchanges its reference mode.plugins/vibe/hooks/hooks.jsonwithkill-guard.mjsand its test.scripts/recover.mjs,scripts/watch.sh,scripts/snapshot.sh,scripts/classify.sh, their tests,SKILL.md.scripts/board.mjs,scripts/codex-session.mjs,scripts/status-audit.mjs,scripts/qa-plan-check.mjs,scripts/fleet.sh, newscripts/load-check.sh, their tests,SKILL.md,references/templates.md,references/setup.md.scripts/executed-check.mjs,scripts/criteria-audit.mjs,scripts/rulings-check.mjs,scripts/diff-rules.mjs,scripts/review-threads.mjs, newscripts/spec-rules.mjsandscripts/rollout-gate.mjs, their tests,SKILL.md.scripts/session-questions.mjs,scripts/shepherd.mjs,scripts/push-cadence.mjs,scripts/commit-worktree.mjs, newscripts/migration-publish.mjs,scripts/pr-threads.mjsandscripts/review-followup.mjs, their tests,SKILL.md,references/guardrails.md.vibe-ticket-worker.md,vibe-environment-worker.md,vibe-guardrails-reviewer.md.references/monitoring-merge-completion.md, cl-sweepreferences/open-pr-lifecycle.md.Prevention closeouts
The orchestrator processed no teammate messages for about 7 hours overnight
Prevention
codex-fleet-monitor/scripts/watch.sh(with no change it ran up to 200 ticks, about 6.7 hours, before itsTIMEOUTexit),fleet-orchestrator/scripts/board.mjs,vibe/scripts/session-questions.mjs,codex-fleet-monitor/scripts/recover.mjs, fleetSKILL.mdand the worker preamble.rg -n "seq 1 200|TIMEOUT|watcher.mjs|NEEDS-REPLY|session-questions" plugins/vibe/skills plugins/vibe/agentssession-questions.mjs ask. It refuses a question with no--defaultand--deadline-minutes, except flag, copy, destructive, credential and scope questions, and returns theIf no answer by <time>: <default>line. Rung 3 via the recovery pass: once the deadline passes it resumes an idle Codex writer on its default (proceeded-on-default). Also rung 3:watch.shexits withHEARTBEATeveryHEARTBEAT_SECONDS(30 minutes), andboard.mjs statusfails withWATCHER-MISSING. Rung 4 covers the recurring schedule while the operator is away.session-questions.test.mjs.proceedOnDefaultsfailsrecover.test.mjs. The watcher check failsboard.test.mjs. The heartbeat exit failswatcher.test.mjs(run on the second machine, because it starts processes).Takeover writers found handoff claims of "done" that weren't
Prevention
fleet-orchestrator/scripts/codex-session.mjsandreferences/templates.md(the takeover line and the resume prompt). Thread resolution already needs a verified verdict (handoff/scripts/review-threads.mjs). The per-row cost is the migration check below.rg -n "Done:|Takeover|resume" plugins/vibe/skills/fleet-orchestratorcodex-session.mjs(everyfleet.shturn). It refuses a prompt with aDone:line and noRe-verify:line. Rung 4 covers the Claude takeover preamble.codex-session.test.mjs.A pushed migration became shared state through the preview CI
Prevention
vibe/scripts/migration-publish.mjs(new),agents/vibe-environment-worker.md(run before every push),handoff/scripts/diff-rules.mjs(same check at handoff),vibe/references/guardrails.md. The guardrail's old sentence said the build applied a migration only to the session's own data. That was false for preview CI, and it is corrected.rg -n -i "migration" plugins/vibe/agents/vibe-environment-worker.md plugins/vibe/skills/vibe/references plugins/vibe/skills/handoff/scripts/diff-rules.mjsmigration-publish.mjs. Each migration added since the last push needsMigration test: <id>: <test file>, with the test file present at HEAD. A trigger, function orFOR EACH ROWalso needsScale test: <id>: <test file>: <n> rows, <time>.migration-publish.test.mjsand the diff-rules migration case.An agent rewrote a human-authored spec to match its code
Prevention
project-hierarchy-spec.ts, "reject moves beyond four levels") so it agreed with the code. A written rule already forbade that.spec-rules.mjs),rulings-check.mjs --criteria-beforefor ticket acceptance criteria,agents/vibe-ticket-worker.md(runs it before each body write),agents/vibe-guardrails-reviewer.md.git ls-files | rg -- "-spec\.(ts|tsx|md)$"in the product repo;rg -n "Spec change|criteria-before" plugins/vibe*-spec.*file, or to Markdown underprd/,prds/,spec/orspecs/, blocks unless the change log recordsSpec change: <path>: <human name or operator ruling>, <YYYY-MM-DD>, <where>. A ticket version that rewords or drops a criterion needs a ticket ruling amending that criterion's id.checkSpecChangesfromevaluateDifffails the diff-rules spec case, and the case shows a plan citation is refused. EmptyingcriteriaChangeProblemsfailsrulings-check.test.mjs.Tickets were bulk-set DONE with acceptance criteria unchecked
Prevention
handoff/scripts/executed-check.mjs(--kind ticket),fleet-orchestrator/scripts/status-audit.mjs(it used to report a merged child as expected DONE on the merge alone),agents/vibe-ticket-worker.mdmergeCheck, cl-execute completion, cl-sweep terminal reconciliation.rg -n -i -e 'to \?DONE' -e 'mark[^.]{0,20}done' -e 'merged pull request means DONE' plugins/vibe`executed-check.mjs --kind ticket, the check every DONE path runs. It refuses while a criterion is unchecked, unless that criterion namesFollow-up: <slug>orExcluded: <human name or operator ruling>, <YYYY-MM-DD>. Rung 3 viastatus-audit.mjs, which needs each merged or DONE child's body and printsUNREADandUNCHECKED.ticketProblemsfailsexecuted-check.test.mjs. Skipping the criteria finding failsstatus-audit.test.mjs.Approved PRs blocked by unresolved threads (writer side), as amended by the single review round ruling
Prevention
vibe/scripts/shepherd.mjs, newvibe/scripts/review-followup.mjs,handoff/scripts/review-threads.mjs, the vibe, handoff and fleetSKILL.md. The monitor side shipped in 1.0.75.board.mjsand the shepherd sharevibe/scripts/pr-threads.mjs.rg -n "reviewThreads|isResolved|resolveReviewThread" plugins/vibe/skillsshepherd.mjs --round-one-fixed-at.threads(exit 13) on a round-one thread the writer has not answered last.later-threads(exit 14) on a thread created after the round-one fix push.review-followup.mjs record: it refuses a second follow-up ticket for the same PR, so a later round appends to the recorded one.review-threads.mjsresolves a later thread only with a follow-up link or a reason, and refuses to treat it as fixed in the PR.shepherd.test.mjs.review-followup.test.mjs("a second later round reuses the recorded ticket").review-threads.test.mjs.A writer killed processes by pattern on the shared laptop
Prevention
plugins/vibe/hooks/kill-guard.mjsregistered for Bash inhooks/hooks.json,vibe/references/guardrails.md, fleetSKILL.md. Codex has no plugin hook, so Codex writers keep the written rule.rg -n "pkill|killall|xargs kill|kill -- -" pluginsand the existingtest_vibe_session_friction_contract.pyandtest_vibe_signal_safety_contract.pyscans.xargs kill;killwith a variable or a command substitution;kill-guard.mjs own <pid>. A recorded pid is tied to its directory and the process start time.kill-guard.test.mjs, and so does disabling the 1-or-lower refusal, even when a ledger claims the pid. The test only asks the guard for decisions and sends no signal.Open PRs sat for hours with no push while their writers looked busy
Prevention
codex-fleet-monitor/scripts/snapshot.sh,vibe/scripts/push-cadence.mjs(now 45 minutes),vibe/SKILL.md, fleetSKILL.mdand templates, monitorSKILL.md.rg -n "headRefOid|committedDate|isDraft|90 minutes" plugins/vibe/skillsSTALE-PRwhen an open PR with unresolved threads, a conflict, a draft flag or a failed check has had no push forSTALE_PR_SECONDS(45 minutes), whatever its writer reports.pushDueafter 45 minutes unpushed.snapshot.test.mjsSTALE-PR case. Restoring 90 minutes failspush-cadence.test.mjs.A writer held its PR for six hours on an unanswered question
Prevention
vibe/scripts/session-questions.mjs,codex-fleet-monitor/scripts/classify.sh(viasnapshot.sh),fleet-orchestrator/scripts/board.mjs, the vibe and fleetSKILL.md, templates.rg -n "NEEDS-REPLY|deadline|hold" plugins/vibe/skills/vibe/scripts/session-questions.mjs plugins/vibe/skills/codex-fleet-monitor/scriptssession-questions.mjs ask. It refuses a question that says it holds a merge, a push or the PR, and caps a default's deadline at 30 minutes. Rung 3 covers the age flags:QUESTION-UNANSWERED(15m+)on an idle Codex writer's NEEDS-REPLY message;board.mjs statusQUESTION-UNANSWEREDfor questions Claude writers record withboard.mjs question.session-questions.test.mjs. The classify branch fails theliveness.test.mjscase. The board filter failsboard.test.mjs.Orchestrator practices still not in fleet-orchestrator
Prevention
SKILL.md(new Program practices section),references/setup.md(config keysGREEN_REF,GREEN_WATCH_AGENT,REPO_MEMORY_CMD,REQUIRED_DUTIES,MAX_LOAD_PER_CORE),scripts/board.mjs,scripts/fleet.sh, newscripts/load-check.sh. Repo names stay in config.rg -n "green|duty|HEAVY_SLOTS|load" plugins/vibe/skills/fleet-orchestratorboard.mjs statusprintsMISSING-DUTY green-watchwhile PRs are watched and nobody owns it.fleet.sh slot heavyrefuses a machine overMAX_LOAD_PER_CORE(exit 4) and names the other machine.executed-check.mjs --kind prd(flags section first) andstatus-audit.mjs, both already shipped.board.test.mjs. Removing the load comparison fails thefleet.test.mjsload case, run on the second machine.A PRD's rollout gate was skipped and nobody caught it
Prevention
scripts/rollout-gate.mjs(new),scripts/rulings-check.mjs(--ruling --scope),scripts/executed-check.mjs(--kind ticket --slug), fleetscripts/status-audit.mjs(UNSCOPED),scripts/qa-plan-check.mjs(--criteria), handoff and fleetSKILL.md.rg -n -i "rollback|cohort|closed-by-default|out of scope|Excluded:" plugins/vibe/skillsRollout: <id>: <file>:<line>at HEAD, or a dated human ruling that names the ticket or PRD. A flag requirement's line must read a flag.rollout-gate.test.mjs;qa-plan-check.test.mjs;UNSCOPEDfailsstatus-audit.test.mjs;executed-check.test.mjs.The orchestrator pushed a writer's partial commit
Prevention
scripts/board.mjs,SKILL.md,references/templates.md.rg -n "headRefOid|--head|push" plugins/vibe/skills/fleet-orchestrator/SKILL.md plugins/vibe/skills/fleet-orchestrator/scripts/board.mjswriter <name> --ready <sha>).push-check <writer> --head <sha>exits 1 unless the head is that one. A prefix shorter than 7 characters proves nothing.board.test.mjs.git pushcannot be intercepted from the board.Green watch missed a batch that started later (detail of the practices entry)
Prevention
scripts/green-watch.mjs, and the green watch practice inSKILL.md.rg -n "run list|green" plugins/vibe/skills/fleet-orchestratorRED,PENDING);NO-RUNwhen no run exists for the branch head.green-watch.test.mjs. Keeping only the first lane, or droppingNO-RUN, fails it. The command test adds a later run between two calls.Requirements worker said public help centers; the quality loop says live Asana
Prevention
agents/vibe-requirements-worker.md. Reference mode now observes the live product through whoever has the operator's signed-in browser, or returnsNEEDS_PERSON. Precedent mode already matched.rg -n -i "help cent|public documentation|release notes|web search" plugins/vibetest_vibe_1076_lessons_contract.py: any sentence in the worker that sends it to help centers, public documentation, release notes or web search without "never" or "not" fails.Claude usage was called unreadable
Prevention
scripts/claude-usage.mjs, plussnapshot.sh, monitor and fleetSKILL.md.rg -n -i "claude usage|cannot read Claude" plugins/vibe/skillsclaude-usage local session=<band> week=<band>fromclaude -p "/usage", on the Codex bands, read at most every 10 minutes.CLAUDE_USAGE=0turns it off.HIGHband carries its reset time. The fleet skill tells the operator, and moves Claude writers to Codex when the operator is away.claude-usage.test.mjs.Ticket and plan statuses drifted
Prevention
scripts/status-audit.mjs(now the status sweep),scripts/board.mjs(astatus-sweepduty),SKILL.md. DONE with open criteria isUNCHECKED(executed-check, above).rg -n "EXECUTED|absorb|IN_PROGRESS" plugins/vibe/skills/fleet-orchestrator plugins/vibe/agents/vibe-ticket-worker.mdPLAN-OPENfor a DONE ticket whose plan is not EXECUTED.ABSORBED-OPENfor a child still open after the umbrella that absorbed it closed.MISSING-DUTY status-sweepappears until someone owns the sweep.status-audit.test.mjs, and removing the duty failsboard.test.mjs.Commands run
All suites ran on the second machine, in this branch's own worktree:
uv run ruff check .,uv run pyrightanduv run pytest plugins/all passed.node --testover every plugin test file: 701 of 701 passed.npm ci,typecheck,testandbuildfor eachtools/*package passed, and the dist tree was clean after the build.The first run's
vibe-workersvitest (three cases) and one cl-sweep black-box case timed out. The machine's load was about 34 on 18 cores, and this PR changes no TypeScript. Rerun alone, both passed.HEARTBEATcase inwatcher.test.mjsfails, and with the loop as shipped it passes.