pi-gauntlet 8.4.9 → 8.4.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -1
- package/README.md +26 -8
- package/agents/loop-implementer.md +5 -6
- package/extensions/phase-tracker.test.ts +78 -2
- package/extensions/phase-tracker.ts +11 -4
- package/extensions/verify-before-ship.test.ts +47 -22
- package/extensions/verify-before-ship.ts +3 -3
- package/package.json +2 -2
- package/skills/brainstorming/SKILL.md +1 -1
- package/skills/brainstorming/reference/amendment-surface.md +1 -1
- package/skills/brainstorming/reference/legs.md +57 -10
- package/skills/brainstorming/reference/spec-pr.md +41 -9
- package/skills/finishing-a-development-branch/SKILL.md +5 -5
- package/skills/finishing-a-development-branch/reference/legs.md +14 -4
- package/skills/gauntlet-resume/SKILL.md +1 -1
- package/skills/gauntlet-resume/reference/brief-contract.md +1 -1
- package/skills/gauntlet-resume/reference/reconstruction.md +10 -10
- package/skills/loop-implementation/SKILL.md +17 -17
- package/skills/loop-implementation/reference/coordinated-legs.md +3 -3
- package/skills/loop-implementation/reference/interrupted-recovery.md +7 -6
- package/skills/loop-implementation/reference/unreachable-returns.md +2 -3
- package/skills/using-git-worktrees/reference/pr-head.md +33 -7
- package/skills/verification-before-completion/reference/conformance-check.md +1 -1
- package/skills/writing-plans/SKILL.md +1 -1
- package/skills/writing-plans/reference/plan-contract.md +1 -1
- package/skills/loop-implementation/reference/action-spend.md +0 -10
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v8.4.11 - 2026-11-12
|
|
4
|
+
|
|
5
|
+
- Loop interrupted returns recover unconditionally as one failed attempt under either approval option: job identity checks fence signals, not reset or repair. Pending companion and resumed returns share the same recovery; only a dirty tree with no pending dispatch asks for discard. Git recovery failures consume the existing allowance rather than opening a new stop.
|
|
6
|
+
- Spec approval authorizes every named paid or external action for the whole flow without count-based stops or spend accounting. Workers reuse action output for local rechecks; missing prerequisites and unnamed actions still report `unreachable:`. Option 5 also continues rows reopened during verify within their remaining allowance, without a second authorization; option 4 keeps its authorize-or-stop ask.
|
|
7
|
+
- pi-cohort peer floor raised to 8.0.0.
|
|
8
|
+
|
|
9
|
+
## v8.4.10 - 2026-11-12
|
|
10
|
+
|
|
11
|
+
- Draft-PR handoff cleanup checks live remote equality before removing owned trees and conditionally deleting local branches at the pinned commit. Guarded wrappers can delete branches; host-owned, mismatched and failed cleanups report the state read back.
|
|
12
|
+
- Coordinated brainstorms record portable script/native creation and destruction commands without inventing a `home:` path. Keyed and built-in creators retain `home:`, pre-existing host-owned trees retain ownership, and companion lookup uses the recorded branch with checkout identity validation.
|
|
13
|
+
- pi-cohort peer floor raised to 7.7.5.
|
|
14
|
+
|
|
3
15
|
## v8.4.9 - 2026-11-11
|
|
4
16
|
|
|
5
17
|
- Gatekeep-pr uses action, final and post menus with the judged recommendation on row 1. Its plain-language report describes the change and scope, explains standing and problems, then recommends a choice; detailed evidence stays behind `show evidence`.
|
|
@@ -52,7 +64,7 @@
|
|
|
52
64
|
|
|
53
65
|
## v8.3.0 - 2026-11-06
|
|
54
66
|
|
|
55
|
-
- Loop execution uses the ledger allowance for interrupted child and seed recovery, exit-verification repairs and grant-funded conformance rounds. Recovery requires checkout ownership and confirmed job exit; stagnation supplies a row-local hint rather than stopping early. Unreachable target failures become audited scope cuts under amend permission, while exhausted named-action bounds still stop. Loop workers bound blocking calls and track longer jobs in isolated POSIX sessions; design approval authorizes named paid or external actions only within their flow-wide bounds.
|
|
67
|
+
- Loop execution uses the ledger allowance for interrupted child and seed recovery, exit-verification repairs and grant-funded conformance rounds. Recovery requires checkout ownership and confirmed job exit; stagnation supplies a row-local hint rather than stopping early. Unreachable target failures become audited scope cuts under amend permission, while exhausted named-action bounds still stop. Loop workers bound blocking calls and track longer jobs in isolated POSIX sessions; design approval authorizes named paid or external actions only within their flow-wide bounds. The Unreleased changes remove this recovery proof and these named-action bounds.
|
|
56
68
|
|
|
57
69
|
## v8.2.1 - 2026-11-06
|
|
58
70
|
|
package/README.md
CHANGED
|
@@ -36,10 +36,28 @@ pi-gauntlet's only hard dependency is pi-cohort - every gate that dispatches a r
|
|
|
36
36
|
Concretely, one change through the gauntlet:
|
|
37
37
|
|
|
38
38
|
0. *(Optional)* Before there's even a spec, `/skill:shape-ticket` can create one tracker issue or repair a comma/whitespace list of issue refs - shaping each into a Context/Problem/Idea/Acceptance Criteria ticket, grounded by a repository audit, an AC integrity check, and a cheap council roast. Repair finishes preparation for all tickets before presenting exact-diff confirmations sequentially in ref order; each approval writes that ticket before the next gate, and may include one optional gated comment for a Reporter note or overlap evidence. The audit checks factual claims, Idea fit, AC mechanisms against cited repo guidance, and already-stated observable legs in configured sibling checkouts. It reports in-motion work and attempts a best-effort scenario through authorized current-system reads, without promising reproduction or a fix; missing access or nothing to observe is reported, not a new approval gate. Implementer detail moves into the gated Reporter note; brainstorming reads that note with the ticket so the spec retains the detail. A failed roast is retried once, then surfaced inline at the gate if it fails again. It's a tool, not a phase: no worktree, no plan/phase tracker, runs from any repo state. The agent may start it on its own when the ask is "file a ticket" or another skill routes there; the write still waits for your yes on the presented diff. Similarly, `/skill:chase-bug` triages a raw bug report into an evidenced verdict - and can hand off to shape-ticket, brainstorming, or a bounded hotfix - before any spec exists.
|
|
39
|
-
1. Opt in with `/skill:brainstorming` and describe the change. The command is the reliable discovery-and-entry path; explicit prose requesting brainstorming/the gauntlet also supplies consent when the skill is already loaded or an available instruction or route identifies it. An ordinary implementation request or an agent recommendation is not consent. A leading run of GitHub references (`#N`, `owner/repo#N`, or a `/pull/N` or `/issues/N` URL), separated by whitespace or commas, resolves each reference as a PR first: related open, same-repository, non-fork PRs on one base form one rework subject, including drafts. Admission runs in listed order and relatedness is checked before provisioning; the first retained PR is home, whose existing branch rebases onto the fetched PR base by default, then member commits replay onto it in listed order. The first intent word `merge-forward` opts out and keeps the merge path. An unresolved conflict after its scoped helper aborts that step and continues to the spec with the repair as Task 1. A single non-PR reference follows the ticket path. The first non-reference token starts intent text; mid-sentence references and other ticket/free-text subjects keep their existing paths. The combined code, PR-carried specs, claims, and review threads are the brainstorm input, with each PR's provenance retained; only home's bound ticket supplies acceptance criteria, while member tickets supply context. A single PR retains the same rework path without member replays or a close offer. The combined state is judged against constraints from the fetched current base HEAD, not the fork point or the PRs' proposed policy edits; see the [PR-subject contract](skills/brainstorming/reference/pr-subject.md) and [PR-head worktree owner](skills/using-git-worktrees/reference/pr-head.md). **`brainstorming`** sets up an isolated worktree (on the existing branch for PR rework), explores the codebase, and turns the subject into a written spec - when its recon finds a cited case that the ask is the wrong change (misdiagnosed, wrong-sized, already solved, a band-aid, or cutting across a sound pattern - including a repo convention that is itself debt) and can name the alternative, question one is a framing question (`as framed`, `pivot: <alternative>`, or `smaller or none`); otherwise it opens with one sentence naming what it checked, and every approach it proposes ends with a `Pattern:` line naming what it follows or cuts across; when the run starts from a ticket, the spec carries the ticket's acceptance criteria verbatim, each with a disposition (`in-scope`, `deviates:`, `deferred:`, `venue:`, `elsewhere:` for a row another repo's part delivers), scope cuts come only from your answer to a cut question and are listed at the gate, the conformance gate checks the in-scope ones, and `/skill:check-delivery` verifies `venue:` rows before the prod deploy. A multi-model critique runs on it automatically. If the spec replaces a known prior spec, brainstorming marks the predecessor with a `> **Superseded by:**` banner under its title (default format, syntax overridable via `.pi/gauntlet-overrides.md`; candidates come from brainstorming's scout recon, never a mechanical sweep). **You read and approve the spec - human gate 1.** Options 1/2/4/5 approve and pick an engine; option 6 approves the content only: it writes `**Engine:** deferred` under `**Amend-grant:** none`, pushes the branch, opens a draft PR carrying just the spec, prints the URL, and ends the flow - no plan, no loop, no handoff brief. For a coordinated spec, it first pushes every companion branch to origin, then opens one home draft PR; it creates no companion PRs. A later `brainstorming #N` reconstructs the recorded companions from their origin branches before re-presenting the implementation gate; the [publication and re-gate owner](skills/brainstorming/reference/spec-pr.md) defines all-member preflight, recovery, and cleanup.
|
|
40
|
-
2. By default, **`writing-plans`** decomposes the approved spec into atomic, independently-verifiable tasks, grouped into parallel waves where they don't touch the same files. Experimental approval options 4/5 instead select **`loop-implementation`**: the plan phase is skipped and a stamped acceptance-criteria ledger replaces the task plan. Option 4 retains amendment review; option 5 auto-applies amends, including unreachable-row cuts, and funds conformance rounds up to the allowance when the cap blocks. Redraws
|
|
39
|
+
1. Opt in with `/skill:brainstorming` and describe the change. The command is the reliable discovery-and-entry path; explicit prose requesting brainstorming/the gauntlet also supplies consent when the skill is already loaded or an available instruction or route identifies it. An ordinary implementation request or an agent recommendation is not consent. A leading run of GitHub references (`#N`, `owner/repo#N`, or a `/pull/N` or `/issues/N` URL), separated by whitespace or commas, resolves each reference as a PR first: related open, same-repository, non-fork PRs on one base form one rework subject, including drafts. Admission runs in listed order and relatedness is checked before provisioning; the first retained PR is home, whose existing branch rebases onto the fetched PR base by default, then member commits replay onto it in listed order. The first intent word `merge-forward` opts out and keeps the merge path. An unresolved conflict after its scoped helper aborts that step and continues to the spec with the repair as Task 1. A single non-PR reference follows the ticket path. The first non-reference token starts intent text; mid-sentence references and other ticket/free-text subjects keep their existing paths. The combined code, PR-carried specs, claims, and review threads are the brainstorm input, with each PR's provenance retained; only home's bound ticket supplies acceptance criteria, while member tickets supply context. A single PR retains the same rework path without member replays or a close offer. The combined state is judged against constraints from the fetched current base HEAD, not the fork point or the PRs' proposed policy edits; see the [PR-subject contract](skills/brainstorming/reference/pr-subject.md) and [PR-head worktree owner](skills/using-git-worktrees/reference/pr-head.md). **`brainstorming`** sets up an isolated worktree (on the existing branch for PR rework), explores the codebase, and turns the subject into a written spec - when its recon finds a cited case that the ask is the wrong change (misdiagnosed, wrong-sized, already solved, a band-aid, or cutting across a sound pattern - including a repo convention that is itself debt) and can name the alternative, question one is a framing question (`as framed`, `pivot: <alternative>`, or `smaller or none`); otherwise it opens with one sentence naming what it checked, and every approach it proposes ends with a `Pattern:` line naming what it follows or cuts across; when the run starts from a ticket, the spec carries the ticket's acceptance criteria verbatim, each with a disposition (`in-scope`, `deviates:`, `deferred:`, `venue:`, `elsewhere:` for a row another repo's part delivers), scope cuts come only from your answer to a cut question and are listed at the gate, the conformance gate checks the in-scope ones, and `/skill:check-delivery` verifies `venue:` rows before the prod deploy. A multi-model critique runs on it automatically. If the spec replaces a known prior spec, brainstorming marks the predecessor with a `> **Superseded by:**` banner under its title (default format, syntax overridable via `.pi/gauntlet-overrides.md`; candidates come from brainstorming's scout recon, never a mechanical sweep). **You read and approve the spec - human gate 1.** Options 1/2/4/5 approve and pick an engine; option 6 approves the content only: it writes `**Engine:** deferred` under `**Amend-grant:** none`, pushes the branch, opens a draft PR carrying just the spec, prints the URL, and ends the flow - no plan, no loop, no handoff brief. For a coordinated spec, it first pushes every companion branch to origin, then opens one home draft PR; it creates no companion PRs. A later `brainstorming #N` reconstructs the recorded companions from their origin branches before re-presenting the implementation gate; the [publication and re-gate owner](skills/brainstorming/reference/spec-pr.md) defines all-member preflight, recovery, and cleanup. After confirming the live remote and local branch match the worktree commit, cleanup removes an owned tree and conditionally deletes the local branch at that pinned commit. A host-owned checkout or failed remote check keeps tree and branch, with a per-repo reason. Option 6 is withheld on a PR subject (`Withheld: draft PR - #N already exists`) and when the branch's diff against the default branch is not spec-only. A later `brainstorming #N` on that PR classifies it from the diff alone: one added spec carrying the marker plus banner-only predecessor edits re-presents the gate with 1/2/4/5 (3 only when the spec commit carries a council audit) and skips gather, questionary, council, and scout; a spec-only diff without the marker prints `spec-only PR without the gate marker - running lint and council` and takes the normal brainstorm path with the carried spec as the draft; any other diff takes the rework path. Approval at that re-gate writes `**Engine:**`, `**Amend-grant:**`, and `**PR:** <owner/repo#N>` as a new commit on the PR branch and chains into planning or the loop as usual. Before approval, PR rework's sole write/run exception outside the spec directories permits only the base sync (rebase or merge), each member replay or merge, their conflict helpers' edits to the conflicted files, and their scoped conflict tests; other subjects have no implementation code yet.
|
|
40
|
+
2. By default, **`writing-plans`** decomposes the approved spec into atomic, independently-verifiable tasks, grouped into parallel waves where they don't touch the same files. Experimental approval options 4/5 instead select **`loop-implementation`**: the plan phase is skipped and a stamped acceptance-criteria ledger replaces the task plan. Option 4 retains amendment review; option 5 auto-applies amends, including unreachable-row cuts, and funds conformance rounds up to the allowance when the cap blocks. Redraws and exhausted attempt or repair allowances still stop; an interrupted return recovers without a human turn under either option. See [Loop engine operations](doc/configuration.md#loop-engine-operations).
|
|
41
41
|
3. **`subagent-driven-development`** executes the plan in waves, with task implementers followed by one integrated `wave-reviewer` after the scoped test gate passes. The reviewer checks anchored spec clauses and code quality together; repairs stay on the current tree and pass the same gate before re-review. TDD-locked: red, green, refactor. Independent implementation, review, and council batches remain parallel, but their dispatches are foreground: the orchestrator waits for terminal results before accepting work or advancing a phase.
|
|
42
|
-
The loop alternative to step 3 runs one fresh `loop-implementer` per seed or row iteration, without implement-phase reviewers. Each row starts with the shared allowance (default 4); approval replies such as `4; allowance 6` override it for one run. Under either loop approval option, attempt `escalateAfter + 1` (default 4), clamped to the last allowed attempt, escalates once per row on `escalationLoop.implModel` (the main-loop model when unset); later attempts use the ordinary worker model. The allowance also bounds seed and exit-verification repair counts; verification repairs use the same escalation model and count home repair commits plus an unmatched `pending: verify` reservation. Stagnation supplies a row-local hint, not an early stop.
|
|
42
|
+
The loop alternative to step 3 runs one fresh `loop-implementer` per seed or row iteration, without implement-phase reviewers. Each row starts with the shared allowance (default 4); approval replies such as `4; allowance 6` override it for one run. Under either loop approval option, attempt `escalateAfter + 1` (default 4), clamped to the last allowed attempt, escalates once per row on `escalationLoop.implModel` (the main-loop model when unset); later attempts use the ordinary worker model. The allowance also bounds seed and exit-verification repair counts; verification repairs use the same escalation model and count home repair commits plus an unmatched `pending: verify` reservation. Stagnation supplies a row-local hint, not an early stop. An interrupted return is one failed attempt: recovery is unconditional under either option, before checking allowance. The driver signals only identity-matching jobs, skips absent or mismatched jobs, deletes the jobs file, resets to the saved pre-dispatch HEAD and runs `clean -fd`, then restores the saved ledger and commits the failed attempt. Continue recovery even with a surviving job or remaining dirt. Wrong HEAD or an unwritten repair repeats reset and repair, counting each pass against the same allowance until the repair lands on the saved HEAD or the existing exhaustion stop fires. With no pending dispatch, a dirty entry or restored tree instead lists paths and asks for typed `discard` or Stop only; discard resets to HEAD, cleans and resumes the suspended entry or restore sequence before dispatch or verification. An admitted `unreachable:` target failure becomes an audited scope cut under amend permission, never a passing row. At a row stop, `retry <n> | <hint>` adds one attempt to that row, while `rows +N` raises every row's allowance. Attempts never reset and there is no total iteration cap. Ledger refresh completes implement when every row is closed; verify step 1 runs the gate set: deduplicated (owning checkout, command) pairs from done rows' recorded checks, with ownership from the ledger. Gate output goes to logs; the driver keeps exit codes and paths, not test output. CI on push owns the full run.
|
|
43
|
+
|
|
44
|
+
Approval authorizes every paid or external action named in an AC or Design, with its runner and inputs, for the whole flow. A count or wording deferring it to later approval does not withhold authorization. The worker runs it whenever an attempt needs it without asking and reuses its output for local rechecks; evidence commands name local replay or fixtures. Missing prerequisites and unnamed actions still fail as `unreachable:`.
|
|
45
|
+
|
|
46
|
+
These are the loop stops this recovery and authorization contract keeps or touches, not the driver's whole stop set. Missing capabilities or references, malformed grants, unresolved verification entrypoints, repeated-evidence amendment handling and failed refresh guards retain their own text. The driver renders only stops named in its skill and references; another failed dispatch consumes an attempt, not an invented menu or approval.
|
|
47
|
+
|
|
48
|
+
| Stop | Option 4 | Option 5 |
|
|
49
|
+
|---|---|---|
|
|
50
|
+
| Row attempts reach effective allowance; seed repairs reach allowance; escalation model unavailable for a row's escalated round | Retry, `rows +N`, amend, stop | Same |
|
|
51
|
+
| Verification repairs reach allowance; escalation model unavailable for a verification repair | Amend, stop | Same |
|
|
52
|
+
| Conformance fix-round cap | Stops at cap | Funds up to allowance, then stops |
|
|
53
|
+
| Spec amendment, including an `unreachable:` cut | Reviewer applies evidence-backed factual corrections; other amendments stop for review | Applies with no human turn |
|
|
54
|
+
| Row reopened by an applied amendment during verify | Authorize the row's return, or stop | Runs within remaining row allowance, with verify active, then reruns step 1 |
|
|
55
|
+
| A redraw | Stops | Stops |
|
|
56
|
+
| Run cannot start: missing `checks:`, neither `allowance:` nor legacy `cap:`, unavailable settings tool, or seed `plan_check` findings | Stops | Stops |
|
|
57
|
+
| Dirty tree with no dispatch pending; on resume, admission question or missing-history stops (unrecoverable pre-dispatch ledger/tuple, or unreadable child attribution with no tuple) | Stops | Stops |
|
|
58
|
+
| Dispatch returns an async handle despite `async: false` | Stops | Stops |
|
|
59
|
+
|
|
60
|
+
A budget reply does not supply a missing escalation model. A reopened row already at allowance takes the row stop under either option; option 5 keeps verify active without a phase reset. Resume's admission question lists commits and dirt and explains that answering `interrupted` discards them through reset to the pre-dispatch HEAD and `clean -fd`. See [loop stop boundaries](doc/configuration.md#loop-stop-boundaries) for the reply and continuation details.
|
|
43
61
|
|
|
44
62
|
4. **verify**: the parent first runs the plan's full verification command set for waves, or the ledger gate set for loop; only a passing result permits whole-diff code review, then the **conformance gate**. A review fix invalidates that result, so the parent reruns the same set before the next review or conformance gate. Loop conformance repairs and finish also use the gate set, never the ledger's full `verification:` command. When the overrides file declares a happy path for the touched paths (see [Project-specific overrides](#project-specific-overrides)), the parent runs it once here, between code review and the conformance gate, and the reviewer reads its transcript as runtime evidence - a hang or a failed request shows up in the audit with the transcript quoted - as a gap when an origin clause ties to it, otherwise as a `happy-path: failed - unattributable` line - never as a silent green. The conformance subagent reads the finished code and docs against your *original words* from step 1, not the plan, and reports per-requirement: delivered, partial, missing, drifted, or unauthorized. Unauthorized rows - shipped surface no human input asked for, whether it crept in or was laundered through the spec - follow their recommendation like every other row: a contained removal auto-runs, anything another requirement leans on is deferred with a plain-language "I'd cut it / I'd keep it" recommendation. Inside a brainstorming-entered flow this gate is machine-blocked from being skipped. Compatible executable recommendations auto-run through an isolated fix-and-re-audit loop with no prompt; anything still open surfaces as a numbered decision brief - one item per decision, the problem in plain words first, lettered options each with a one-line consequence, and `Recommended: <letter> - <reason>` or `No recommendation - pick one`. Reply with one line per item (`1. B`), or bare `1` to take every recommendation (no-recommendation items are re-asked by number); a current `CONFORMS` / no-concerns result goes straight to the branch options with no extra conformance sign-off.
|
|
45
63
|
5. **`finishing-a-development-branch`**: PR, draft PR, squash, keep, or discard; PR rework's row 1 offers a lease push of the rebased home branch to the existing PR (`Base:` records in `merge:` form push non-force). A spec whose header carries `**PR:**` (approved at a spec-PR re-gate) and no `## Rework record` replaces the two create rows with `Push to PR #N and mark it ready for review` and `Push to PR #N, keep it a draft` (`Push to PR #N (already ready for review)` when the PR is already ready): both push without force after strip-and-seal, the first then runs `gh pr ready`, neither rewrites the PR body, and both are withheld when origin's branch head is no longer an ancestor of local HEAD. For coordinated specs, these [header-linked rows 1/2](skills/finishing-a-development-branch/reference/spec-pr.md#push-rows-12) deliver companions in recorded order before one home seal carrying every companion PR URL, then push home and apply its readiness choice. Row 1 makes companion drafts ready too; row 2 creates drafts but never converts an already-ready PR back to draft. Companion URLs appear in the shipped summary, companions first and home last, not in the unchanged home PR body; keep that summary for cross-repo review because successful home delivery removes receipts. A spec still carrying `**Engine:** deferred` stops finishing (and `gauntlet-resume`) with `spec approved for handoff only - run brainstorming #N` before any phase change, verification, or push. The PR-rework push appends a `## Rework` block naming absorbed PRs while retaining the original description bytes; the header-linked `**PR:**` push leaves the body untouched. Only after a successful rework push and home-description edit does one gated confirmation show the exact close comments and eligible member targets, linking home; declining or any other finish option leaves members open. Closing never deletes member branches, and finish posts no review-thread replies. Its `Before:` handle includes the full portable pre-rework SHA and the local `refs/gauntlet/pre-rework/pr-<N>` ref in the rework checkout. The [finishing owner](skills/finishing-a-development-branch/SKILL.md#pr-subject-row-1-push-to-the-pr-branch) defines the landing and body-update contract. Once a PR exists, run `/skill:gatekeep-pr <pr>` to verify it before merging - it judges the diff against the spec at the PR's head (the acceptance-criteria dispositions and Design you approved; the PR description when no spec applies) - rows the spec defers or deviates are settled, every other row needs its mechanism (code, tests, docs) - and against the rubric; whatever can be seen only after merge is left to step 6. **Human gate 2** - the only other decision you make.
|
|
@@ -71,17 +89,17 @@ In create mode, `shape-ticket` runs one bounded overlap check after clarificatio
|
|
|
71
89
|
|
|
72
90
|
Use `/skill:shape-ticket --skip-overlap <ask>` to bypass discovery explicitly. Repair mode never discovers candidates. Bypass still retains source reads, known conflicts, integrity checks, roast, approval, and source revalidation. Incomplete search coverage is disclosed, not treated as proof of no overlap; independent sessions can still race, so this is not a uniqueness guarantee.
|
|
73
91
|
|
|
74
|
-
Between gate 1 and gate 2, both engines take named authorized recommendations under the spec's amend grant and run one bounded escalated execution round before eligible stops;
|
|
92
|
+
Between gate 1 and gate 2, both engines take named authorized recommendations under the spec's amend grant and run one bounded escalated execution round before eligible stops; exhausted allowances, cap and admission failures, waves stagnation and redraws still stop, and budget-only grants supply no amend permission. Planning takes concrete baseline `narrow` or `amend` recommendations under that permission; `fix-first` enters ticket shaping's own gate. An implementer-evidenced text correction to a drafted AC is an amendment, not a redraw; ticket rows, row add/remove and problem or component boundary changes still require redraw approval. See [execution continuation](doc/implement-engines.md#stops-models-and-telemetry). Once gate 1 picks an engine, task breakdown, implementation and reviews proceed within those limits; option 6 parks the approved spec in a draft PR and planning starts only when someone runs `brainstorming #N` on it and approves at the re-gate. Changing an approved spec later goes through brainstorming's `Amending an approved spec`: a fresh-context reviewer clears evidence-backed factual corrections on its own, escalations reach you as one readable batch, and only a redraw is a full stop - not a third numbered gate. That's the mechanism. What follows is the machinery behind it.
|
|
75
93
|
|
|
76
94
|
### Multi-repo runs
|
|
77
95
|
|
|
78
|
-
A run has one home repository and one leg per selected repository. The home's `## shape-ticket` `- sibling:` declarations define eligible companions, not automatic membership: the ask must select a declared companion by repo identifier, the declared clone URL's `NAME`, or the URL itself, or the ticket must select it through a matching repo-group heading. Unselected siblings are not resolved. Members are ordered home first, then selected siblings in declaration order. Every member gets its own worktree with the same full branch and
|
|
96
|
+
A run has one home repository and one leg per selected repository. The home's `## shape-ticket` `- sibling:` declarations define eligible companions, not automatic membership: the ask must select a declared companion by repo identifier, the declared clone URL's `NAME`, or the URL itself, or the ticket must select it through a matching repo-group heading. Unselected siblings are not resolved. Members are ordered home first, then selected siblings in declaration order. Every member gets its own worktree with the same full branch and shared creation NAME (branch with `/` replaced by `-`); a script or native tool can choose a different folder name.
|
|
79
97
|
|
|
80
|
-
The approved home spec records `## Legs`, including each leg's scope, rules and resolved `contract:`. Each companion commits a leg file with the same filename in its own spec directory, carrying the home pointer, Problem, binding decisions and its own leg block. Plan and ledger stay home-owned; task `Leg:` selects the checkout for implementation and review. Committed artifacts use portable repo identifiers, not machine-local paths. See the [membership and artifact contract](skills/brainstorming/reference/legs.md).
|
|
98
|
+
The approved home spec records `## Legs`, including each leg's scope, rules and resolved `contract:`. Keyed and built-in creators retain `home:`; flow-created script/native trees record portable `create:` and `destroy:` without `home:`, and a pre-existing host-owned tree without a keyed contract records neither `home:` nor `destroy:`. Script creation passes the shared NAME and explicit branch, while destruction uses the located folder's basename. Companion lookup selects the unique linked tree on the recorded branch and validates its checkout identity, rather than computing its location from `home:`. Each companion commits a leg file with the same filename in its own spec directory, carrying the home pointer, Problem, binding decisions and its own leg block. Plan and ledger stay home-owned; task `Leg:` selects the checkout for implementation and review. Committed artifacts use portable repo identifiers, not machine-local paths. See the [membership and artifact contract](skills/brainstorming/reference/legs.md).
|
|
81
99
|
|
|
82
100
|
Home overrides and settings govern flow policy. Each leg's own overrides and settings govern checkout operations, directories and verification; absent configuration uses defaults. Recorded `contract:` lines are authoritative for every spec with `## Legs`, including one-member runs: amend the home block and affected companion file before using a changed contract. Finish does not replace them by rereading overrides. Legacy specs without `## Legs` are home-only and retain the finish-time reread.
|
|
83
101
|
|
|
84
|
-
Option 6's draft-PR handle is one home draft PR plus companion branches on origin, with no brief, companion PRs or receipts. `brainstorming #N` preflights every recorded companion before provisioning them from origin and re-gating under the [publication and re-gate contract](skills/brainstorming/reference/spec-pr.md). This
|
|
102
|
+
Option 6's draft-PR handle is one home draft PR plus companion branches on origin, with no brief, companion PRs or receipts. `brainstorming #N` preflights every recorded companion before provisioning them from origin and re-gating under the [publication and re-gate contract](skills/brainstorming/reference/spec-pr.md). Cleanup runs companions first, then home, removing owned trees and deleting local branches only after a live remote equality check and confirmed removal; each leg reports its read-back state even when another cleanup fails. This supersedes the kept-local-branch cleanup in the [spec-only draft-PR design](doc/specs/2026-10-28-spec-only-draft-pr.md); a remote branch deleted after handoff now needs restoration from a clone that still has it, not the cleaned-up machine. PR lists still take the rework path. The separate `gauntlet-handoff` / `gauntlet-resume` route carries every member in the brief's `legs:` block. Resume starts only from the home primary, validates companion repository, branch and home pointer, and announces per-leg HEAD and dirty drift. On that route a missing companion worktree requires a live-validated completion receipt; draft-PR re-entry instead uses the published origin branch. The [brief contract](skills/gauntlet-resume/reference/brief-contract.md#coordinated-brief-grammar) owns the brief grammar and admission rules.
|
|
85
103
|
|
|
86
104
|
One finish choice spans all legs. PR, draft PR and squash deliver companions in declaration order, then seal and deliver home; keep preserves all legs, and discard lists all legs before one confirmation. Verification and conformance run per leg; home's passing tests do not credit companions. Delivery failure stops without rollback and reports shipped and unshipped legs. A retry from the brief validates home-owned receipts and skips only proven deliveries; stale PR heads are updated, and failed post-squash verification reruns without another squash. See the [delivery and recovery contract](skills/finishing-a-development-branch/reference/legs.md).
|
|
87
105
|
|
|
@@ -148,7 +166,7 @@ Run by a human against pi-cohort >= 7.1.0, before a pi-gauntlet release claims t
|
|
|
148
166
|
## Requirements
|
|
149
167
|
|
|
150
168
|
- [pi-coding-agent](https://github.com/earendil-works/pi) >= 1.5.0 - supports model-only tracker exposure and sequential tracker execution, so updates persist as transcript tool results rather than nested calls.
|
|
151
|
-
- [pi-cohort](https://github.com/jjuraszek/pi-cohort) >=
|
|
169
|
+
- [pi-cohort](https://github.com/jjuraszek/pi-cohort) >= 8.0.0 - required peer package, including binary-safe worktree patches; it also lets a persona with `subagent` in `tools:` start with inherited extensions ([pi-cohort #26](https://github.com/jjuraszek/pi-cohort/issues/26)), which multi-PR `gatekeep-pr` needs. Skills that dispatch agents (`requesting-code-review`, `subagent-driven-development`, `dispatching-parallel-agents`, `writing-plans`, `forge-skill`, `shape-ticket`, `roasting-the-spec`) call `subagent({})`, which pi-cohort provides. pi-gauntlet does not vendor the dispatch tool; without pi-cohort those skills have nothing to call. Gauntlet execution dispatches explicitly use `async: false`; do not enable pi-cohort's incompatible `forceTopLevelAsync` setting. See pi-cohort's [dispatch configuration](https://github.com/jjuraszek/pi-cohort/blob/main/doc/configuration.md) for that setting's owner and semantics. A wave of N >= 1 tasks dispatches its implementers and first spec reviews as one chain call. `subagent-driven-development` stops before dispatch on unsupported installs; there is no older-runtime fallback.
|
|
152
170
|
|
|
153
171
|
Both packages must be listed in your `.pi/settings.json#packages` array (pi adds them automatically when you `pi install`). pi-gauntlet and pi-cohort are versioned independently but release together whenever dispatch semantics change - pin compatible versions of both. At each pi-gauntlet release, set the pi-cohort peer minimum and README floor together to the newest released pi-cohort version; `/skill:release` checks the registry before proceeding.
|
|
154
172
|
|
|
@@ -17,7 +17,7 @@ Append one decision line per changed row to the ledger's `## Decisions` section
|
|
|
17
17
|
|
|
18
18
|
`row <n> <done|failed> | <one sentence: what was done> | <one sentence: reusable artifacts for done, retry guidance for failed>`
|
|
19
19
|
|
|
20
|
-
For `done`, name what later rows can build on (a file, helper, interface or fixture) and how to reuse it; write `nothing new to build on` when the row added no reusable artifact, including an extra closed by a check without further edits. For `failed`, state what the next iteration should not redo or should try differently. Limit each sentence to 200 characters.
|
|
20
|
+
For `done`, name what later rows can build on (a file, helper, interface or fixture) and how to reuse it; write `nothing new to build on` when the row added no reusable artifact, including an extra closed by a check without further edits. For `failed`, state what the next iteration should not redo or should try differently. Limit each sentence to 200 characters. Use the seed contract below for `target: seed`.
|
|
21
21
|
|
|
22
22
|
## Inputs
|
|
23
23
|
|
|
@@ -31,13 +31,12 @@ Read additive inputs `Leg:`, `Rules:`, mapped original AC and, for companions, e
|
|
|
31
31
|
2. Close the `target` row first. Read every `row <n> ...` line for the target row from `## Decisions`, oldest first, before choosing an approach. Treat them as ordered guidance: avoid what a `failed` line's third slot says not to redo, follow what it says to try differently, and never repeat an approach a prior line of this row rules out. Exclude other rows' lines from guidance for this target. Read `retry` and `escalated` lines in the same pass: use the retry hint as guidance; treat the escalation marker as historical evidence that escalation is spent, and never infer the current attempt's model from it. Keep `previous decision` as the latest admitted target line and the tie-break for most recent. Use `previous decision` and `hint` to avoid repeating work and to try a different approach; keep the target unchanged.
|
|
32
32
|
3. Run `git diff --stat <base>`. Open full diff hunks only for files the row being worked on touches, using `git diff <base> -- <row files>`; read the relevant code before editing it.
|
|
33
33
|
4. Implement the target row using Three-scenario TDD and Test economy below. Apply step 5's action authorization before running a check. Treat empty evidence on an unreached row as a normal state after partial seeding, not a seed failure; create its first check before implementing it, then close or fail the row with command-first evidence under step 5. Run only its targeted check under Evidence command, never the owning checkout's full verification command or the full suite.
|
|
34
|
-
5.
|
|
34
|
+
5. Update the target row's mutable cells: set `status` to `done` on a passing check or `failed` on a failing check, increment `attempts` by 1, and set `evidence` to one physical Markdown table cell opening with the command you ran under Evidence command, up to the first unescaped semicolon, plus at most three result lines separated by semicolons. Escape pipes as `\|` and use plain text without angle-bracket markup. On failure, record the concrete cause in plain words: the spec-vs-code contradiction, failing assertion, or missing credential, tool, or fixture. Keep reasoning and transcripts out of evidence. Run ordinary local checks as today. Apply the action rules below before any paid or external action.
|
|
35
35
|
|
|
36
36
|
| Observed action state | Result |
|
|
37
37
|
|---|---|
|
|
38
|
-
| Named, prerequisites present
|
|
38
|
+
| Named, prerequisites present | Treat an action that an acceptance row or Design names as authorized for the whole flow by the spec's approval; run it without asking on every attempt that needs it. Treat a count, or wording that defers it to a later approval, written beside the action as text that does not withhold it. Reuse its output for local rechecks rather than spending it again. |
|
|
39
39
|
| Concrete prerequisite no permitted attempt in this checkout can supply: credential, environment or data not present, or action the spec does not name | Record `unreachable: <what is missing> - <where it can run>` as the first result line after the check name; set the target `failed` and make no further attempt in this session. Name the missing prerequisite and runnable venue; do not ask the driver for permission. |
|
|
40
|
-
| Named action's count exhausted | Record `bound spent: <action> <k> of <k>` as the first result line and set the target `failed`; do not run the action or mark it unreachable. |
|
|
41
40
|
|
|
42
41
|
6. Append the target's decision line defined in Output contract. Only after the target is `done`, consider further open rows of your leg whose work sits in the files or helper just changed: a check already passing, or a small step on the same code. Take extras one at a time; write or name each row's selective check before implementing it, apply Three-scenario TDD, make it pass, record command-first evidence bounded as in step 5, and append its decision line. Set each extra `done` with attempts unchanged. Do not look elsewhere to pad the return or start a row you cannot finish in this session. Revert all code, test, cell and decision changes for an unfinished extra before committing; preserve the completed rows' changes.
|
|
43
42
|
7. Remove the `pending:` line from the ledger in your commit. Stage your row changes and the ledger explicitly. Commit exactly once with subject `loop: row <n> <status>`, naming the target only. Check `git status --porcelain` and leave the working tree clean.
|
|
@@ -77,9 +76,9 @@ Require the recorded command to exit nonzero when it selects no test. Before rec
|
|
|
77
76
|
|
|
78
77
|
## Target seed
|
|
79
78
|
|
|
80
|
-
Use this contract for `target: seed`, normally iteration 0. After a
|
|
79
|
+
Use this contract for `target: seed`, normally iteration 0. After a recovery discards an interrupted seed, repeat seeding at the consumed iteration count; keep the ledger's `iterations` unchanged.
|
|
81
80
|
|
|
82
|
-
Replace steps 2-6 with evidence seeding for at least one non-cut row; seed a subset or all open rows. Apply step 5's action authorization
|
|
81
|
+
Replace steps 2-6 with evidence seeding for at least one non-cut row; seed a subset or all open rows. Apply step 5's action authorization before every seed check or action. Keep the spec-first read order and scope diff hunks to the seeded rows' files. Leave unreached rows unchanged.
|
|
83
82
|
|
|
84
83
|
For each selected `open` row, write or identify its failing test (use an asserting command for a trivial or assertion-only row) and record the command you ran in `evidence` under Evidence command. Close a row already satisfied by the codebase as `done` only with passing baseline evidence; record the command of the passing check for every baseline closure. Keep other open rows `open`. Keep every row's `attempts` at 0; never set `failed`. Preserve `cut` rows and their disposition evidence. Bound evidence as in step 5.
|
|
85
84
|
|
|
@@ -2718,8 +2718,8 @@ for (const pattern of [".worktrees/<name>", "../companion-<name>"]) test(`plan_c
|
|
|
2718
2718
|
const repo = `team/${name}`;
|
|
2719
2719
|
const cloneUrl = `git@example.test:${repo}.git`;
|
|
2720
2720
|
execFileSync("git", ["config", "remote.origin.url", `https://example.test/${repo}.git`], { cwd: companion });
|
|
2721
|
-
const branch = "
|
|
2722
|
-
|
|
2721
|
+
const branch = "user/slug";
|
|
2722
|
+
let companionRoot = join(companion, pattern.replace("<name>", "user-slug"));
|
|
2723
2723
|
execFileSync("git", ["worktree", "add", "-b", branch, companionRoot], { cwd: companion, stdio: "pipe" });
|
|
2724
2724
|
if (pattern.startsWith("..")) tempDirs.push(companionRoot);
|
|
2725
2725
|
mkdirSync(join(primary, ".pi"), { recursive: true });
|
|
@@ -2801,6 +2801,22 @@ for (const pattern of [".worktrees/<name>", "../companion-<name>"]) test(`plan_c
|
|
|
2801
2801
|
for (const spy of spies) spy.mock.restore();
|
|
2802
2802
|
syncBuiltinESMExports();
|
|
2803
2803
|
}
|
|
2804
|
+
const specPath = join(primary, "doc/specs/spec.md");
|
|
2805
|
+
const originalSpec = readFileSync(specPath, "utf8");
|
|
2806
|
+
const scriptHome = join(companion, "..", `${name}-worktrees`);
|
|
2807
|
+
tempDirs.push(scriptHome);
|
|
2808
|
+
mkdirSync(scriptHome);
|
|
2809
|
+
const scriptRoot = join(scriptHome, "slug");
|
|
2810
|
+
execFileSync("git", ["worktree", "move", companionRoot, scriptRoot], { cwd: companion, stdio: "pipe" });
|
|
2811
|
+
companionRoot = scriptRoot;
|
|
2812
|
+
for (const home of [undefined, `../${name}-worktrees/slug`, ".worktrees/<name>"]) {
|
|
2813
|
+
await t.test(`script-placed companion with home ${home ?? "absent"}`, async () => {
|
|
2814
|
+
writeFileSync(specPath, originalSpec.replace(`contract: home: ${pattern}`, home ? `contract:\n- home: ${home}` : "contract:"));
|
|
2815
|
+
const result = await tool.execute("script-placed", { planPath }, undefined, undefined, h.ctx) as { details: { status: string; findings?: unknown } };
|
|
2816
|
+
assert.equal(result.details.status, "pass", JSON.stringify(result.details.findings));
|
|
2817
|
+
});
|
|
2818
|
+
}
|
|
2819
|
+
writeFileSync(specPath, originalSpec);
|
|
2804
2820
|
const originalPlan = readFileSync(planPath, "utf8");
|
|
2805
2821
|
const foreignSpec = join(companionRoot, "foreign-spec.md");
|
|
2806
2822
|
writeFileSync(foreignSpec, readFileSync(join(primary, "doc/specs/spec.md")));
|
|
@@ -2817,6 +2833,66 @@ for (const pattern of [".worktrees/<name>", "../companion-<name>"]) test(`plan_c
|
|
|
2817
2833
|
assert.ok(missing.details.findings.some(f => f.check === "input"));
|
|
2818
2834
|
});
|
|
2819
2835
|
|
|
2836
|
+
for (const state of ["unlinked", "primary", "detached", "not a checkout", "foreign linked checkout", "duplicate", "missing branch", "lookup failed"] as const) {
|
|
2837
|
+
test(`plan_check rejects companion branch lookup: ${state}`, async () => {
|
|
2838
|
+
const primary = gitPrimary().primary;
|
|
2839
|
+
const companion = gitPrimary().primary;
|
|
2840
|
+
const name = companion.split("/").at(-1)!;
|
|
2841
|
+
const repo = `team/${name}`;
|
|
2842
|
+
const branch = "user/slug";
|
|
2843
|
+
const companionRoot = join(companion, ".worktrees", "slug");
|
|
2844
|
+
const git = (...args: string[]) => execFileSync("git", ["-C", companion, ...args], { stdio: "pipe" });
|
|
2845
|
+
git("config", "remote.origin.url", `https://example.test/${repo}.git`);
|
|
2846
|
+
git("branch", branch);
|
|
2847
|
+
if (state === "primary") git("checkout", branch);
|
|
2848
|
+
if (state === "detached") git("worktree", "add", "--detach", companionRoot, branch);
|
|
2849
|
+
if (state === "not a checkout" || state === "foreign linked checkout" || state === "duplicate") {
|
|
2850
|
+
git("worktree", "add", companionRoot, branch);
|
|
2851
|
+
if (state === "duplicate") git("worktree", "add", "--force", `${companionRoot}-other`, branch);
|
|
2852
|
+
else {
|
|
2853
|
+
rmSync(companionRoot, { recursive: true });
|
|
2854
|
+
if (state === "foreign linked checkout") {
|
|
2855
|
+
execFileSync("git", ["-C", primary, "worktree", "add", "--detach", companionRoot], { stdio: "pipe" });
|
|
2856
|
+
} else mkdirSync(companionRoot);
|
|
2857
|
+
}
|
|
2858
|
+
}
|
|
2859
|
+
mkdirSync(join(primary, ".pi"), { recursive: true });
|
|
2860
|
+
writeFileSync(join(primary, ".pi/gauntlet-overrides.md"), `## shape-ticket\n- sibling: git@example.test:${repo}.git\n`);
|
|
2861
|
+
const { planPath } = writePlanFixture(primary, {
|
|
2862
|
+
mutateSpec: s => s + `\n## Legs\n### Leg: team/repo-a\nscope: none\nbranch: ${branch}\n### Leg: ${repo}\nscope: none\n${state === "missing branch" ? "" : `branch: ${branch}\n`}contract:\n`,
|
|
2863
|
+
mutatePlan: s => s.replace("---", `Baseline team/repo-a: abc1234\nBaseline ${repo}: def5678\n---`).replace("### Task 2: Implement naming", `## Wave 2 - Companion\n\n### Task 2: Implement naming\nLeg: ${repo}`).replace("abc1234 skipped - lib/task2", "def5678 skipped - lib/task2"),
|
|
2864
|
+
});
|
|
2865
|
+
const h = harness({ cwd: primary });
|
|
2866
|
+
await h.emit("session_start");
|
|
2867
|
+
const tool = h.tools.find(t => t.name === "plan_check")!;
|
|
2868
|
+
const original = childProcess.execFileSync;
|
|
2869
|
+
const spy = state === "lookup failed" ? mock.method(childProcess, "execFileSync", (...args: any[]) => {
|
|
2870
|
+
if (args[0] === "git" && args[2]?.cwd === companion && JSON.stringify(args[1]) === JSON.stringify(["worktree", "list", "--porcelain", "-z"])) {
|
|
2871
|
+
throw Object.assign(new Error("worktree listing failed"), { status: 128 });
|
|
2872
|
+
}
|
|
2873
|
+
return (original as Function)(...args);
|
|
2874
|
+
}) : undefined;
|
|
2875
|
+
if (spy) syncBuiltinESMExports();
|
|
2876
|
+
let result: { details: { status: string; findings: { check: string; reason: string }[] } };
|
|
2877
|
+
try {
|
|
2878
|
+
result = await tool.execute(state, { planPath }, undefined, undefined, h.ctx) as typeof result;
|
|
2879
|
+
} finally {
|
|
2880
|
+
if (spy) {
|
|
2881
|
+
spy.mock.restore();
|
|
2882
|
+
syncBuiltinESMExports();
|
|
2883
|
+
}
|
|
2884
|
+
}
|
|
2885
|
+
const reason = state === "duplicate" ? `2 worktrees hold ${branch}`
|
|
2886
|
+
: state === "not a checkout" || state === "foreign linked checkout" ? `${companionRoot} is not a checkout`
|
|
2887
|
+
: state === "lookup failed" ? `worktree lookup failed for ${branch}`
|
|
2888
|
+
: `no worktree holds ${branch}`;
|
|
2889
|
+
assert.equal(result.details.status, "fail");
|
|
2890
|
+
assert.deepEqual(result.details.findings.map(({ check, reason }) => ({ check, reason })), [
|
|
2891
|
+
{ check: "input", reason: state === "missing branch" ? `invalid recorded branch for ${repo.toLowerCase()}` : `cannot resolve worktree for ${repo.toLowerCase()}: ${reason}` },
|
|
2892
|
+
]);
|
|
2893
|
+
});
|
|
2894
|
+
}
|
|
2895
|
+
|
|
2820
2896
|
for (const length of [32767, 32768, 32769]) {
|
|
2821
2897
|
test(`plan_check complete manifest JS length ${length} bounds stamping`, async () => {
|
|
2822
2898
|
const dir = gitPrimary().primary;
|
|
@@ -1026,13 +1026,20 @@ export default function (pi: ExtensionAPI) {
|
|
|
1026
1026
|
}
|
|
1027
1027
|
if (candidates.length !== 1) return inputFailure(`cannot resolve selected sibling ${leg.repo}`);
|
|
1028
1028
|
const branch = /^branch:\s*(\S+)\s*$/m.exec(leg.body)?.[1];
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
const
|
|
1029
|
+
if (!branch) return inputFailure(`invalid recorded branch for ${leg.repo}`);
|
|
1030
|
+
const unresolved = (reason: string) => inputFailure(`cannot resolve worktree for ${leg.repo}: ${reason}`);
|
|
1031
|
+
const listing = gitSync(["worktree", "list", "--porcelain", "-z"], candidates[0]);
|
|
1032
|
+
if (listing.code !== 0) return unresolved(`worktree lookup failed for ${branch}`);
|
|
1033
|
+
const matches = listing.stdout.split("\0\0").slice(1)
|
|
1034
|
+
.map(block => block.split("\0"))
|
|
1035
|
+
.filter(fields => fields.includes(`branch refs/heads/${branch}`));
|
|
1036
|
+
if (!matches.length) return unresolved(`no worktree holds ${branch}`);
|
|
1037
|
+
if (matches.length !== 1) return unresolved(`${matches.length} worktrees hold ${branch}`);
|
|
1038
|
+
const legRoot = matches[0].find(field => field.startsWith("worktree "))!.slice("worktree ".length);
|
|
1032
1039
|
const checkout = existsSync(legRoot) ? await checkoutOf(legRoot, gitSync) : undefined;
|
|
1033
1040
|
const expected = gitSync(["rev-parse", "--path-format=absolute", "--git-common-dir"], candidates[0]);
|
|
1034
1041
|
const actual = checkout ? gitSync(["rev-parse", "--path-format=absolute", "--git-common-dir"], legRoot) : undefined;
|
|
1035
|
-
if (!checkout || checkout.toplevel !== legRoot || expected.code !== 0 || actual?.code !== 0 || actual.stdout.trim() !== expected.stdout.trim()) return
|
|
1042
|
+
if (!checkout || checkout.isPrimary || checkout.toplevel !== realpathSync(legRoot) || expected.code !== 0 || actual?.code !== 0 || realpathSync(actual.stdout.trim()) !== realpathSync(expected.stdout.trim())) return unresolved(`${legRoot} is not a checkout`);
|
|
1036
1043
|
roots.set(leg.repo, legRoot);
|
|
1037
1044
|
}
|
|
1038
1045
|
}
|
|
@@ -313,28 +313,53 @@ test("settings errors appear once per chained call", async () => {
|
|
|
313
313
|
assert.equal(warning.split("gauntlet settings load error").length - 1, 1, warning);
|
|
314
314
|
});
|
|
315
315
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
if (
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
316
|
+
for (const { name, home, state } of [
|
|
317
|
+
{ name: "no home", home: "", state: "member" },
|
|
318
|
+
{ name: "literal home", home: "home: ../sib-worktrees/slug\n", state: "member" },
|
|
319
|
+
{ name: "mismatching home pattern", home: "home: linked-<name>\n", state: "member" },
|
|
320
|
+
{ name: "another branch at the recorded home", home: "home: ../sib-worktrees/<name>\n", state: "other" },
|
|
321
|
+
{ name: "detached HEAD at the recorded home", home: "home: ../sib-worktrees/<name>\n", state: "detached" },
|
|
322
|
+
{ name: "duplicate branch checkouts", home: "", state: "duplicate" },
|
|
323
|
+
]) {
|
|
324
|
+
test(`coordinated linked worktree policy follows branch membership: ${name}`, async () => {
|
|
325
|
+
const h = harness();
|
|
326
|
+
const primary = join(h.dir, "sib");
|
|
327
|
+
mkdirSync(primary);
|
|
328
|
+
const git = (...args: string[]) => execFileSync("git", ["-C", primary, ...args], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
|
|
329
|
+
git("init", "-b", "main");
|
|
330
|
+
git("-c", "user.name=Test", "-c", "user.email=test@example.invalid", "commit", "--allow-empty", "-m", "base");
|
|
331
|
+
const branch = "user/slug";
|
|
332
|
+
const worktree = join(h.dir, "sib-worktrees", state === "other" || state === "detached" ? "user-slug" : "slug");
|
|
333
|
+
git("worktree", "add", "-b", state === "other" ? "other" : branch, worktree);
|
|
334
|
+
if (state === "detached") git("-C", worktree, "checkout", "--detach");
|
|
335
|
+
const targets = [worktree];
|
|
336
|
+
if (state === "duplicate") {
|
|
337
|
+
targets.push(join(h.dir, "sib-worktrees", "duplicate"));
|
|
338
|
+
git("worktree", "add", "--force", targets[1], branch);
|
|
339
|
+
}
|
|
340
|
+
for (const [root, command] of [[primary, "primary-check"], ...targets.map(target => [target, "local-check"])]) {
|
|
341
|
+
mkdirSync(join(root, ".pi"));
|
|
342
|
+
writeFileSync(join(root, ".pi/settings.json"), JSON.stringify({ piGauntlet: { verifyBeforeShip: { testCommands: [command] } } }));
|
|
343
|
+
}
|
|
344
|
+
git("remote", "add", "origin", "https://example.invalid/team/sib.git");
|
|
345
|
+
const spec = join(h.dir, "spec.md");
|
|
346
|
+
writeFileSync(spec, `## Legs\n### Leg: team/home\n### Leg: team/sib\nbranch: ${branch}\n${home}`);
|
|
347
|
+
await h.emitEvent("tool_result", { toolName: "plan_check", isError: false, details: { status: "pass", specPath: spec } });
|
|
348
|
+
const member = state === "member" || state === "duplicate";
|
|
349
|
+
const accepted = member ? "primary-check" : "local-check";
|
|
350
|
+
const rejected = member ? "local-check" : "primary-check";
|
|
351
|
+
for (const target of targets) {
|
|
352
|
+
for (const [command, warns] of [[rejected, true], [accepted, false]] as const) {
|
|
353
|
+
await h.emitEvent("tool_call", bashCall("test", `(cd ${target} && ${command})`));
|
|
354
|
+
await h.emitEvent("tool_result", bashResult("test"));
|
|
355
|
+
await h.emitEvent("tool_call", bashCall("ship", `git -C ${target} push`));
|
|
356
|
+
const warning = warningOf(await h.emitEvent("tool_result", bashResult("ship")));
|
|
357
|
+
if (warns) assert.ok(warning?.includes(accepted), warning);
|
|
358
|
+
else assert.equal(warning, undefined);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
});
|
|
362
|
+
}
|
|
338
363
|
|
|
339
364
|
test("real linked worktree evidence stays separate from primary post-squash verification", async () => {
|
|
340
365
|
const h = harness();
|
|
@@ -209,9 +209,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
209
209
|
}
|
|
210
210
|
return legs.slice(1).some(leg => {
|
|
211
211
|
const branch = /^branch:\s*(\S+)\s*$/m.exec(leg.body)?.[1];
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
212
|
+
if (leg.repo !== repo || !branch) return false;
|
|
213
|
+
const current = gitSync(["branch", "--show-current"], target);
|
|
214
|
+
return current.code === 0 && current.stdout.trim() === branch;
|
|
215
215
|
});
|
|
216
216
|
};
|
|
217
217
|
const pendingTests = new Map<string, Set<string>>();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-gauntlet",
|
|
3
|
-
"version": "8.4.
|
|
3
|
+
"version": "8.4.11",
|
|
4
4
|
"description": "Opinionated, gated workflow skills, subagent personas, and runtime extensions for the pi coding agent.",
|
|
5
5
|
"author": "Jacek Juraszek",
|
|
6
6
|
"type": "module",
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
"test": "node scripts/ci.mjs"
|
|
63
63
|
},
|
|
64
64
|
"peerDependencies": {
|
|
65
|
-
"pi-cohort": ">=
|
|
65
|
+
"pi-cohort": ">=8.0.0"
|
|
66
66
|
},
|
|
67
67
|
"dependencies": {
|
|
68
68
|
"yaml": "^2.9.0"
|
|
@@ -136,7 +136,7 @@ Start the message with `gauntlet: question` alone at column zero on the first ph
|
|
|
136
136
|
|
|
137
137
|
Use two rounds, targeting 300-500 words each, with one approval each; revisions remain within that approval point. Ask once per round; round-1 approval without correction confirms the predecessor. Round 1 covers architecture, responsibilities, data flow, and `supersedes <path>, <scope>` when applicable. Round 2 covers errors, edges, tests, and `## Documentation impact`. In round 2, read and apply [Documentation impact](reference/documentation-impact.md), including its verbatim template; cite that portable relative path in the spec; do not restate its categories. Ask the user when doc impact is unclear. Unreadable -> stop the step with a blocking error. Cite the draft's `Docs touched:` entries as candidates for "Materially amended existing docs"; admit or drop each by that owner's materiality bar, never list them automatically. In round 1, carry chosen `Removes:`/`Adds:` into Design as exhaustive `Remove:`/`Add:` imperatives naming every moving part; route unlisted parts through `reference/amendment-surface.md`. In round 2, write `## Tests`: one `<observable> - <test name or file>` per acceptance row and Design edge/error clause; `<observable> - none: <why>` for untestable.
|
|
138
138
|
|
|
139
|
-
In round 2, name every paid or external action needed for implementation or verification in Design with its runner
|
|
139
|
+
In round 2, name every paid or external action needed for implementation or verification in Design with its runner and inputs. Propose no flow-wide count for a named action. State that approving the spec authorizes every named action for the whole flow across seed, rows, repairs and finish, without per-step permission prompts; treat any stated count as advisory. Never block a named action for a missing or exhausted count, and never write a hold that makes a named action wait for a later approval. Treat a needed action the spec does not name as unreachable, and require separate explicit authorization to add it to Design.
|
|
140
140
|
|
|
141
141
|
## Ticket Handling
|
|
142
142
|
|
|
@@ -169,7 +169,7 @@ reread rule; amend the block before using a changed contract. Legacy specs
|
|
|
169
169
|
without that section retain finish-time rereading. A one-member run has no
|
|
170
170
|
companion artifact or companion aftermath.
|
|
171
171
|
|
|
172
|
-
Aftermath, once per batch, only when at least one item applied (nothing applied -> skip to the commit below). Ledger exists (line 1 `# Ledger - loop engine` in the resolved plan dir) -> skip `plan-writer` and `plan_check`. Reconcile the ledger against the approved spec dispositions: `deviates:`, `deferred:`, or `elsewhere:` -> `cut`, evidence = the disposition; factual or requirement-preserving amendment -> preserve rows; re-bounded `done` row -> `open`, evidence = `reopened by amendment`. Preserve every row's attempts and the ledger's iterations and allowance. Apply evidenced drafted-row text corrections as amend items while preserving row identity; retain the cut/reopen rules above. Ticket text and row add/remove changes remain redraws. Commit spec + ledger together with subject `loop: cut row <n>` for a batch containing only one cut, otherwise `loop: amend` (including mixed batches), carrying the amendment audit body below (including each `granted` record and granting sentence); after the commit, use the driver's next ledger-path refresh to project the new statuses and continue without task-plan aftermath. Keep task-plan `add`/`update`/`init` out of this route. Project `done -> cut` as `complete -> skipped` and a re-bound `done -> open` as `complete -> pending`; count neither as a reopen. During verify, stop on a re-bound row with `Stopped: row <n> reopened by amendment during verify`; keep verify active and offer authorization to return that row to ledger implementation or stop.
|
|
172
|
+
Aftermath, once per batch, only when at least one item applied (nothing applied -> skip to the commit below). Ledger exists (line 1 `# Ledger - loop engine` in the resolved plan dir) -> skip `plan-writer` and `plan_check`. Reconcile the ledger against the approved spec dispositions: `deviates:`, `deferred:`, or `elsewhere:` -> `cut`, evidence = the disposition; factual or requirement-preserving amendment -> preserve rows; re-bounded `done` row -> `open`, evidence = `reopened by amendment`. Preserve every row's attempts and the ledger's iterations and allowance. Apply evidenced drafted-row text corrections as amend items while preserving row identity; retain the cut/reopen rules above. Ticket text and row add/remove changes remain redraws. Commit spec + ledger together with subject `loop: cut row <n>` for a batch containing only one cut, otherwise `loop: amend` (including mixed batches), carrying the amendment audit body below (including each `granted` record and granting sentence); after the commit, use the driver's next ledger-path refresh to project the new statuses and continue without task-plan aftermath. Keep task-plan `add`/`update`/`init` out of this route. Project `done -> cut` as `complete -> skipped` and a re-bound `done -> open` as `complete -> pending`; count neither as a reopen. During verify without amend permission, stop on a re-bound row with `Stopped: row <n> reopened by amendment during verify`; keep verify active and offer authorization to return that row to ledger implementation or stop. Under amend permission, continue without a second authorization. For the granted continuation or the option-4 authorization, apply the driver's `## Before each dispatch` boundaries, including exhausted-row rule 2, then apply `## Dispatch` through the driver's `## After the loop` step 3 one-task `tasks` continuation and `## Validate the return` (tuple, refresh, admission, refresh), keeping verify active with no phase reset; close the row on disk, then rerun the step 1 gate before verify continues. Never close it with `plan_tracker update`. No plan yet -> commit the spec; continue. Plan exists -> read `git -C "<abs worktree path>" status --porcelain` (plain jj: `jj -R "<abs worktree path>" diff --summary`); collect the parent's in-flight workflow edits as worktree-relative paths, including the amended spec and staged in-place wave-repair deliverables. Dispatch `plan-writer` per `writing-plans` `## Author the plan` step 3 with an `Amend:` block naming the amended spec sections, an `In-flight:` list of those paths (the parent supplies the set; the child never infers it), and the rule "preserve every task title and wave position the amendment does not touch; edit only affected anchors and tasks", then run that section's steps 5-6 (the parent `plan_check` and its branches).
|
|
173
173
|
|
|
174
174
|
At that return point, classify human-facing outcomes with this table; let writing-plans' first `Findings:` repair run as written.
|
|
175
175
|
|
|
@@ -51,16 +51,32 @@ Keep flow policy (council, grants, tracker, response channels, finish choice and
|
|
|
51
51
|
home flow guards) under home `.pi/settings.json#piGauntlet` and home overrides.
|
|
52
52
|
Never use `gauntlet_setting` for companions or add a `piGauntlet.*` key.
|
|
53
53
|
|
|
54
|
-
Record portable templates
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
54
|
+
### Record portable templates
|
|
55
|
+
|
|
56
|
+
Record only the contract fields authorized by the creator; never record a produced path:
|
|
57
|
+
|
|
58
|
+
| Creator | Recorded fields |
|
|
59
|
+
|---|---|
|
|
60
|
+
| Keyed `## using-git-worktrees` contract | Retain `home:`, `create:`, `destroy:`, including the keyed `.worktrees/<name>` default for omitted `home:`. |
|
|
61
|
+
| Owned built-in | Retain `.worktrees/<name>` as `home:`, with built-in `create:` and `destroy:`. |
|
|
62
|
+
| Native tool or discovered script, tree created by this flow | Record `create:` and `destroy:`; omit `home:`. |
|
|
63
|
+
| Tree the session was already inside, no keyed contract | Omit `home:` and `destroy:`; retain host ownership. |
|
|
64
|
+
|
|
65
|
+
- Create: retain the keyed contract, native tool, discovered wrapper, or owned
|
|
66
|
+
built-in command. Render discovered-script commands from the primary with the
|
|
67
|
+
script's repo-relative path, `<name>` for shared NAME (branch with `/` replaced
|
|
68
|
+
by `-`), and an explicit `<branch>` argument. Use that same NAME at continuation.
|
|
69
|
+
Resolve other wrapper tokens from that leg's touched top-level directory; zero
|
|
70
|
+
or several candidates ask once for that leg. Keep portable values, not machine
|
|
71
|
+
paths, user names, or expanded folders. Require Step 4's branch read-back before
|
|
72
|
+
recording a block; a script unable to take the branch gets no block on mismatch.
|
|
73
|
+
- Destroy: for a flow-created or keyed tree prefer the `## cleanup` worktree line,
|
|
74
|
+
then `destroy:`, then owned built-in removal matching `home:`. Render native and
|
|
75
|
+
script commands from the primary with portable tokens and the script's
|
|
76
|
+
repo-relative path; set `<name>` at execution to the located tree's folder
|
|
77
|
+
basename, not shared NAME. Include configured `subagents.worktreeTeardownHook`
|
|
78
|
+
with pi-cohort's existing semantics. Never bypass a wrapper with `git worktree
|
|
79
|
+
remove`; keep unmatched homes host-owned.
|
|
64
80
|
- Directories: resolve `flowGuards.specDirs` with the existing default
|
|
65
81
|
`doc/specs`, `docs/specs`; derive plan directories with `planDirsFor`.
|
|
66
82
|
Never read `planDirs` as settings input. Choose the leg's spec directory by the
|
|
@@ -76,6 +92,34 @@ Carry every `Worktree ready at <path>` result, home
|
|
|
76
92
|
first. On any failure stop naming repo and branch and list already-created legs;
|
|
77
93
|
leave them preserved, never auto-remove them.
|
|
78
94
|
|
|
95
|
+
## Locate a leg tree
|
|
96
|
+
|
|
97
|
+
Keep the home leg at the session's home worktree. For a companion, read
|
|
98
|
+
`git -C <leg primary> worktree list --porcelain` and select the linked block
|
|
99
|
+
carrying `branch refs/heads/<recorded branch>`; exclude the first (primary) block.
|
|
100
|
+
Locate by branch regardless of a recorded `home:`; never expand `home:` or shared
|
|
101
|
+
NAME into a lookup path or record the returned path in the spec.
|
|
102
|
+
|
|
103
|
+
Validate the selected path: require it to exist, its checkout toplevel to equal
|
|
104
|
+
its realpath, the checkout to differ from the primary, and its canonical
|
|
105
|
+
`--git-common-dir` to equal the primary's. Treat a listed block as retained
|
|
106
|
+
administration, not proof of a live checkout.
|
|
107
|
+
|
|
108
|
+
| Result | Action |
|
|
109
|
+
|---|---|
|
|
110
|
+
| Exactly one matching block, identity passes | Use its `worktree` path for all later admission, plan paths, and provisioning reuse. |
|
|
111
|
+
| No matching block | Stop: `cannot resolve worktree for <repo>: no worktree holds <branch>`. Only continuation provisioning may proceed by creating a tree. |
|
|
112
|
+
| Several matching blocks | Stop: `cannot resolve worktree for <repo>: <n> worktrees hold <branch>`. |
|
|
113
|
+
| Selected path fails identity | Stop: `cannot resolve worktree for <repo>: <path> is not a checkout`. |
|
|
114
|
+
|
|
115
|
+
For cleanup lookup failures, run no destroy command and delete no branch for
|
|
116
|
+
that leg; report the repo, branch and reason without an invented path, then
|
|
117
|
+
continue with remaining legs. Apply this rule to plan writing, finishing,
|
|
118
|
+
draft-PR cleanup and continuation, and resume's absent companion locations.
|
|
119
|
+
Use it also when loop-implementation seed or authorized restore and bare-worktree
|
|
120
|
+
gauntlet-resume capture companion paths without a brief `legs:` location.
|
|
121
|
+
Keep receipt admission of missing delivered trees unchanged.
|
|
122
|
+
|
|
79
123
|
## Gather rules
|
|
80
124
|
|
|
81
125
|
Dispatch one scout per leg with its worktree as `cwd`. Collect root `AGENTS.md`
|
|
@@ -117,6 +161,9 @@ scope: AC 1, AC 3
|
|
|
117
161
|
design: <owned paths, observable outcomes, and clauses binding this leg>
|
|
118
162
|
```
|
|
119
163
|
|
|
164
|
+
For a no-`home:` variant, apply the creator-specific omissions in
|
|
165
|
+
[Record portable templates](#record-portable-templates) to this template.
|
|
166
|
+
|
|
120
167
|
### Scope grammar and validation
|
|
121
168
|
|
|
122
169
|
Write exactly one `scope:` line in each block: `scope: none` or a comma-separated
|