pi-gauntlet 5.8.0 → 5.8.1
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
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v5.8.1 - 2026-09-17
|
|
4
|
+
|
|
5
|
+
- chase-bug hotfix: the implementer proves its worktree binding first, addresses every mutating git command with `git -C`, runs with a fresh context, and the parent aborts non-destructively on any primary-checkout drift after each implementer return; `ci.mjs` asserts the guard text ([#36](https://github.com/jjuraszek/pi-gauntlet/issues/36))
|
|
6
|
+
- `gauntlet-resume`: same-repository worktrees resume from a pi session launched in the primary checkout; cross-repository targets still stop, and reconstruction addresses the resolved worktree by path.
|
|
7
|
+
|
|
3
8
|
## v5.8.0 - 2026-09-17
|
|
4
9
|
|
|
5
10
|
- New extension `telemetry`: records one committed YAML record per gauntlet run at `.pi/gauntlet/telemetry/<spec path>.yaml` (phase timing, model/thinking snapshots, per-persona dispatches and tokens, reviewer findings, gate and fix-round counters, plan totals, last test result, diff buckets and modified files at ship), keyed by spec path and continued across sessions; pathspec-commits the record at checkpoints; reconciles a failed ship command; freezes after squash/PR/discard. During brainstorm a `write` into a spec whose record is shipped is blocked (`edit` passes). Settings `piGauntlet.telemetry.{enabled,dir,buckets}`. (#33)
|
package/package.json
CHANGED
|
@@ -37,6 +37,7 @@ succeeds (it creates both). Every destructive command below is gated on its flag
|
|
|
37
37
|
DEFAULT=$(git symbolic-ref --short refs/remotes/origin/HEAD) && DEFAULT=${DEFAULT#origin/}
|
|
38
38
|
BASE_SHA=$(git rev-parse "$DEFAULT")
|
|
39
39
|
ORIG_BRANCH=$(git branch --show-current)
|
|
40
|
+
ORIG_HEAD=$(git rev-parse HEAD)
|
|
40
41
|
git status --porcelain > "$TMPDIR/hotfix-<slug>.baseline"
|
|
41
42
|
```
|
|
42
43
|
|
|
@@ -49,14 +50,42 @@ succeeds (it creates both). Every destructive command below is gated on its flag
|
|
|
49
50
|
reuse, never force); `git worktree add` itself failing (creation failure).
|
|
50
51
|
|
|
51
52
|
```bash
|
|
52
|
-
git worktree add "
|
|
53
|
+
git worktree add "$PRIMARY_ROOT/.worktrees/hotfix/<slug>" -b "hotfix/<slug>" "$DEFAULT"
|
|
54
|
+
WORKTREE=$(git -C "$PRIMARY_ROOT/.worktrees/hotfix/<slug>" rev-parse --show-toplevel)
|
|
53
55
|
```
|
|
54
56
|
|
|
55
|
-
Success sets both flags
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
Success sets both flags; `WORKTREE` is set only after `git worktree add`
|
|
58
|
+
succeeds - empty `WORKTREE` -> abort (pre-land, both flags set so cleanup runs)
|
|
59
|
+
- and is the literal the child is compared against - both sides come from `git
|
|
60
|
+
rev-parse --show-toplevel`, so a symlinked `.worktrees/` cannot make them
|
|
61
|
+
disagree. Run the project's dependency install inside the worktree. Every
|
|
62
|
+
dispatch below: `cwd` = the worktree path, the record path in the task text,
|
|
63
|
+
`output:` (when used) an absolute `$TMPDIR` path. Every implementer round - step
|
|
64
|
+
4, the step 5 retry, the step 6 gap fix, the step 7 FIX_FIRST round - is
|
|
65
|
+
`context: "fresh"` and reuses the full step 4 task text (binding block, record
|
|
66
|
+
path and inlined evidence pack, `SCOPED_TEST_COMMANDS`, commit-before-reporting,
|
|
67
|
+
SDD status line) with the round's failing output appended verbatim: the red test
|
|
68
|
+
output (step 5), the conformance gaps (step 6), the review findings (step 7).
|
|
69
|
+
Each round is followed by `binding_check` (see Abort, **Binding**) before
|
|
70
|
+
anything else. Under a fresh context a retry child sees only its task text; a
|
|
71
|
+
fix child not told to commit leaves its fix where step 7's `<base-sha>..HEAD`
|
|
72
|
+
review and step 8's `merge --squash` never see it.
|
|
73
|
+
4. **Implement.** One `implementer`, `context: "fresh"` - passed explicitly,
|
|
74
|
+
because the persona's frontmatter defaults to fork, and a forked parent
|
|
75
|
+
transcript carries the parent's `cd <primary>` commands and primes the child to
|
|
76
|
+
leave the worktree; the record path and inlined evidence pack already make the
|
|
77
|
+
task self-contained. `<WORKTREE>` and `<PRIMARY_ROOT>` below are the literal
|
|
78
|
+
step 3 values, never a `$VAR` for the child to resolve:
|
|
79
|
+
|
|
80
|
+
> Worktree binding: your checkout is <WORKTREE>. Your first command, before any
|
|
81
|
+
> other, is
|
|
82
|
+
> actual=$(git rev-parse --show-toplevel); [ "$actual" = "<WORKTREE>" ] || echo "MISMATCH: $actual != <WORKTREE>"
|
|
83
|
+
> On MISMATCH run nothing else; your final message opens with the line
|
|
84
|
+
> `BLOCKED: toplevel <actual> != <WORKTREE>` and ends with `STATUS: BLOCKED`.
|
|
85
|
+
> Every mutating git command (add, commit, checkout, reset, stash) runs as
|
|
86
|
+
> `git -C <WORKTREE> ...`. Never `cd` out of <WORKTREE>; never run a command
|
|
87
|
+
> against <PRIMARY_ROOT>.
|
|
88
|
+
>
|
|
60
89
|
> Read `$TMPDIR/hotfix-<slug>.md`; the evidence pack is also inlined here:
|
|
61
90
|
> <evidence pack>. The evidence pack replaces plan and spec; do
|
|
62
91
|
> not report BLOCKED for a missing plan. TDD: write the regression test, run it,
|
|
@@ -64,6 +93,14 @@ succeeds (it creates both). Every destructive command below is gated on its flag
|
|
|
64
93
|
> re-run is the regression evidence). SCOPED_TEST_COMMANDS: <commands>. Commit on
|
|
65
94
|
> `hotfix/<slug>` before reporting. End with the SDD status line verbatim.
|
|
66
95
|
|
|
96
|
+
The `BLOCKED:` opening line is load-bearing: pi-cohort turns a final message
|
|
97
|
+
whose first non-empty line starts with `BLOCKED:` into a dispatch error that
|
|
98
|
+
carries the full output, ahead of the implementer's completion guard that would
|
|
99
|
+
otherwise replace an edit-free report with a generic no-edits error.
|
|
100
|
+
|
|
101
|
+
On return, `binding_check` runs before anything else - before the status line
|
|
102
|
+
is read and before a dispatch error is handled; a failed check -> `binding_abort`,
|
|
103
|
+
the status branch is skipped. A dispatch error is routed as `BLOCKED`. Then:
|
|
67
104
|
`DONE` -> step 5. `DONE_WITH_CONCERNS` -> step 5 unless a concern names a safety
|
|
68
105
|
invariant -> abort. `NEEDS_CONTEXT` or `BLOCKED` -> abort. A regression command
|
|
69
106
|
named in the report joins `SCOPED_TEST_COMMANDS` only if it uses the resolved
|
|
@@ -126,6 +163,8 @@ succeeds (it creates both). Every destructive command below is gated on its flag
|
|
|
126
163
|
without the hotfix entry; `git branch --list hotfix/<slug>` empty; porcelain
|
|
127
164
|
delta vs baseline none. PR exit and land-stage aborts: both present, plus
|
|
128
165
|
`git worktree remove --force .worktrees/hotfix/<slug> && git branch -D hotfix/<slug>`.
|
|
166
|
+
Binding aborts: worktree and `hotfix/<slug>` both present; no removal command
|
|
167
|
+
is run.
|
|
129
168
|
10. **Response.** chase-bug step 5 rules apply unchanged: addressable -> draft
|
|
130
169
|
citing `fixed in <SHA>` or the PR link -> `send it`; unaddressable -> summary.
|
|
131
170
|
Abort never reaches this step - it returns to the menu.
|
|
@@ -151,7 +190,7 @@ Judgment predicates (`[recommended]` only; Moderate review items):
|
|
|
151
190
|
|
|
152
191
|
## Abort
|
|
153
192
|
|
|
154
|
-
|
|
193
|
+
All three classes end with the baseline re-check, then chase-bug step 4 re-renders.
|
|
155
194
|
|
|
156
195
|
**Pre-land** (steps 2-7): unresolvable commands or task text; setup precondition
|
|
157
196
|
unmet; `NEEDS_CONTEXT`/`BLOCKED` or a concern naming an invariant; red after retry;
|
|
@@ -175,21 +214,73 @@ exactly that); on base moved `<default>` is never touched and both SHAs are
|
|
|
175
214
|
reported. Restore `<orig-branch>`. Preserve worktree and branch (reviewed work).
|
|
176
215
|
Report path, tip SHA, closing line. The menu re-renders without the hotfix row.
|
|
177
216
|
|
|
178
|
-
**
|
|
179
|
-
|
|
180
|
-
|
|
217
|
+
**Binding** (after any implementer return, steps 4-7): the parent runs
|
|
218
|
+
`binding_check` from `PRIMARY_ROOT` before reading the child's status line or
|
|
219
|
+
handling a dispatch error - a `BLOCKED` or errored child may have mutated the
|
|
220
|
+
primary before stopping, and the pre-land cleanup would delete the evidence.
|
|
221
|
+
|
|
222
|
+
```bash
|
|
223
|
+
binding_check() {
|
|
224
|
+
local head branch dirt default
|
|
225
|
+
default=$(git -C "$PRIMARY_ROOT" rev-parse "$DEFAULT") || binding_abort read "rev-parse $DEFAULT failed"
|
|
226
|
+
head=$(git -C "$PRIMARY_ROOT" rev-parse HEAD) || binding_abort read "rev-parse HEAD failed"
|
|
227
|
+
branch=$(git -C "$PRIMARY_ROOT" branch --show-current) || binding_abort read "branch --show-current failed"
|
|
228
|
+
dirt=$(git -C "$PRIMARY_ROOT" status --porcelain --untracked-files=no) || binding_abort read "status failed"
|
|
229
|
+
local moved=""
|
|
230
|
+
[ "$default" = "$BASE_SHA" ] || moved="$moved <default>:$BASE_SHA..$default"
|
|
231
|
+
[ "$head" = "$ORIG_HEAD" ] || moved="$moved HEAD:$ORIG_HEAD..$head"
|
|
232
|
+
[ "$branch" = "$ORIG_BRANCH" ] || moved="$moved branch:$ORIG_BRANCH->$branch"
|
|
233
|
+
[ -z "$moved" ] && [ -z "$dirt" ] && return 0
|
|
234
|
+
binding_abort drift "$moved" "$dirt"
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Every read must succeed before any comparison: a failed read with empty stdout
|
|
239
|
+
would otherwise pass `-z "$dirt"` or be misread as HEAD drift. All observations
|
|
240
|
+
are collected first, so a stray commit plus tracked dirt is one abort. Three refs,
|
|
241
|
+
not one: a drifted child commits on whatever the primary has checked out, which is
|
|
242
|
+
`<orig-branch>`, and `<orig-branch>` may differ from `<default>`. The tracked-only
|
|
243
|
+
target is the empty string - the step 3 precondition already guarantees it.
|
|
244
|
+
|
|
245
|
+
`binding_abort` never touches the primary checkout: no `git reset`, no
|
|
246
|
+
`git checkout`, no stash - this run moved nothing, so the land-stage reset rule
|
|
247
|
+
already forbids it. The worktree and `hotfix/<slug>` are **preserved** as
|
|
248
|
+
evidence of what the child did where (a deliberate exception to pre-land
|
|
249
|
+
cleanup). Report, by kind: `read` - the failing command and its stderr, the
|
|
250
|
+
worktree path and `hotfix/<slug>` tip SHA, no drift claim; `drift` - for each
|
|
251
|
+
moved ref `<ref> moved from <old> to <new>` plus
|
|
252
|
+
`git --no-pager log --oneline <old>..<new>`
|
|
253
|
+
(branch switch: `checked-out branch changed from <orig-branch> to <branch>`),
|
|
254
|
+
for tracked dirt the path fields only (no `XY` codes), the full
|
|
255
|
+
`git status --porcelain` delta against the step 3 baseline file
|
|
256
|
+
(`$TMPDIR/hotfix-<slug>.baseline`, report-only as today), the worktree path and
|
|
257
|
+
`hotfix/<slug>` tip SHA. The non-executed repair line
|
|
258
|
+
`git checkout <ref> && git reset --hard <old-sha>` is printed only when tracked
|
|
259
|
+
dirt is empty -
|
|
260
|
+
with dirt present the parent cannot tell whose edits a reset would destroy. The
|
|
261
|
+
menu re-renders without the hotfix row.
|
|
262
|
+
|
|
263
|
+
**Baseline re-check**: `git status --porcelain --untracked-files=no` is empty (the
|
|
264
|
+
step 3 precondition; the tracked-only baseline is empty by construction); full
|
|
265
|
+
porcelain delta reported; `<default>` == `<base-sha>` asserted only when this run
|
|
266
|
+
touched `<default>`. Pre-existing dirt is never touched.
|
|
181
267
|
|
|
182
268
|
## Harness fallback
|
|
183
269
|
|
|
184
270
|
No `subagent` tool and no personas (the Claude Code marketplace ships `agents: []`):
|
|
185
271
|
run the duties inline, same order. Write the failing regression test, confirm red,
|
|
186
|
-
minimal fix, confirm green, commit on `hotfix/<slug>`.
|
|
187
|
-
|
|
188
|
-
|
|
272
|
+
minimal fix, confirm green, commit on `hotfix/<slug>`. With no child the binding
|
|
273
|
+
block is moot, but `binding_check` still runs after the initial inline commit and
|
|
274
|
+
after each inline retry, conformance fix, or review fix, before tests continue.
|
|
275
|
+
Self-review the diff against the record, invariants 1-3, predicates 4-6. Run
|
|
276
|
+
`SCOPED_TEST_COMMANDS`. Apply the abort classes as written. Finish and report per
|
|
277
|
+
steps 8-9.
|
|
189
278
|
|
|
190
279
|
## Red Flags - STOP
|
|
191
280
|
|
|
192
281
|
- Any mutation before every step 3 precondition passes
|
|
282
|
+
- Reading an implementer's status line before `binding_check`
|
|
283
|
+
- Resetting or checking out any primary ref from `binding_abort`
|
|
193
284
|
- Reusing or force-replacing an existing `hotfix/<slug>` branch or path
|
|
194
285
|
- `git reset --hard` after *proven*, on `<orig-branch>`, or inside the worktree
|
|
195
286
|
- Re-running tests in the primary checkout
|
|
@@ -59,14 +59,18 @@ In order. All before any tracker mutation; entry check 1 is read-only.
|
|
|
59
59
|
`not a git repo` and no override was given - except a `worktree: no` brief **without**
|
|
60
60
|
process state, which is the brainstorming route in Dispatch, not a stop. Other
|
|
61
61
|
`unavailable` fields inside `## Repo state` are legal.
|
|
62
|
-
3. **
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
62
|
+
3. **Same-repository binding.** Settings and flow guards come from the repository in
|
|
63
|
+
the extension's session cwd. Compare
|
|
64
|
+
`realpath "$(git rev-parse --path-format=absolute --git-common-dir)"` in the session
|
|
65
|
+
cwd with
|
|
66
|
+
`realpath "$(git -C <worktree> rev-parse --path-format=absolute --git-common-dir)"`.
|
|
67
|
+
If they differ, stop, name both paths, and say the resolved worktree belongs to a
|
|
68
|
+
different repository; restart pi in that repository's primary checkout and re-run.
|
|
69
|
+
Same-repository worktrees proceed by path: use `git -C <worktree>` for
|
|
70
|
+
worktree Git commands and absolute artifact paths for `Read` and `plan_check`.
|
|
67
71
|
4. **Drift notice.** Compare the brief's `HEAD` and `dirty` fields in `## Repo state`
|
|
68
|
-
with the live worktree (`git rev-parse HEAD`, `git
|
|
69
|
-
differences. Informational, never a stop.
|
|
72
|
+
with the live worktree (`git -C <worktree> rev-parse HEAD`, `git -C <worktree>
|
|
73
|
+
status --porcelain`). Announce differences. Informational, never a stop.
|
|
70
74
|
5. **Skills loaded.** For each name in `## Skills loaded` (none for
|
|
71
75
|
`## Skills loaded: none`): match against the frontmatter `name` of every
|
|
72
76
|
`skills/*/SKILL.md` in this package; `Read` each match's complete file into the
|
|
@@ -97,7 +97,8 @@ Per-stage call table, keyed by the brief's active phase (`→`). `R` is the reas
|
|
|
97
97
|
(`reconstruction.md`, "Candidates", including its `flowGuards.specDirs` resolution): the
|
|
98
98
|
single spec/plan pair added after base in the worktree, paired by identical basename
|
|
99
99
|
(`<specDir>/<name>.md` <-> `<sibling plans dir>/<name>.md`, the writing-plans contract);
|
|
100
|
-
|
|
100
|
+
resolve the selected plan to an absolute path under the worktree before `plan_check`.
|
|
101
|
+
Zero pairs -> stop; more than one -> human picks.
|
|
101
102
|
|
|
102
103
|
"Exact" restoration binds: the active phase identity and substep, and the plan task list
|
|
103
104
|
(names, order, statuses) verbatim. Prior phases show `⊘ (resume: ...)` regardless of the
|
|
@@ -13,12 +13,12 @@ available" - and never invent Intent or Decisions.
|
|
|
13
13
|
## Base
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
|
-
git merge-base HEAD origin/HEAD 2>/dev/null \
|
|
17
|
-
|| git merge-base HEAD main 2>/dev/null \
|
|
18
|
-
|| git merge-base HEAD master 2>/dev/null
|
|
16
|
+
git -C <worktree> merge-base HEAD origin/HEAD 2>/dev/null \
|
|
17
|
+
|| git -C <worktree> merge-base HEAD main 2>/dev/null \
|
|
18
|
+
|| git -C <worktree> merge-base HEAD master 2>/dev/null
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
Base = `git merge-base HEAD origin/HEAD`, else `main`/`master`. Empty -> ask the human
|
|
21
|
+
Base = `git -C <worktree> merge-base HEAD origin/HEAD`, else `main`/`master`. Empty -> ask the human
|
|
22
22
|
for a base ref before reading any artifact.
|
|
23
23
|
|
|
24
24
|
## Candidates
|
|
@@ -29,15 +29,18 @@ else the active pi profile's `settings.json`, else the default `["doc/specs"]` (
|
|
|
29
29
|
space-separated - never the literal defaults when a setting is present.
|
|
30
30
|
|
|
31
31
|
```bash
|
|
32
|
-
git diff --diff-filter=A --name-only <base>..HEAD -- <dirs>
|
|
33
|
-
git ls-files --others --exclude-standard -- <dirs>
|
|
32
|
+
git -C <worktree> diff --diff-filter=A --name-only <base>..HEAD -- <dirs>
|
|
33
|
+
git -C <worktree> ls-files --others --exclude-standard -- <dirs>
|
|
34
34
|
```
|
|
35
35
|
|
|
36
36
|
Candidates are files under those directories added after base, plus untracked files
|
|
37
|
-
there.
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
there. Paths returned by these commands are relative to `<worktree>`; resolve them to
|
|
38
|
+
absolute paths under `<worktree>` before reading artifacts, showing paths in prompts,
|
|
39
|
+
or calling `plan_check`. A spec and a plan pair by identical basename
|
|
40
|
+
(`<specDir>/<name>.md` <-> `<sibling plans dir>/<name>.md`). The plan commit is the first
|
|
41
|
+
post-base commit that added the plan file: `git -C <worktree> log --diff-filter=A
|
|
42
|
+
--format=%H --reverse <base>..HEAD -- <plan>`, first line. An uncommitted plan has no
|
|
43
|
+
plan commit; treat every task as `pending`.
|
|
41
44
|
|
|
42
45
|
| Candidates | Route |
|
|
43
46
|
|---|---|
|
|
@@ -63,11 +66,12 @@ Show, and ask the human to confirm or edit both in one reply:
|
|
|
63
66
|
|
|
64
67
|
1. Per task, in plan order: the commits after the plan commit that touch any path in
|
|
65
68
|
the task's declared `Files:` block. Strip a trailing `:digits[-digits]` range from
|
|
66
|
-
each `Modify:` path before matching `git log -- <path>`:
|
|
67
|
-
`git log --format=%h --oneline <plan-commit>..HEAD -- <path>`.
|
|
69
|
+
each `Modify:` path before matching `git -C <worktree> log -- <path>`:
|
|
70
|
+
`git -C <worktree> log --format=%h --oneline <plan-commit>..HEAD -- <path>`.
|
|
71
|
+
Uncommitted plan (no
|
|
68
72
|
plan commit): skip this query entirely - there is no range to search - and show
|
|
69
73
|
"plan uncommitted; no task evidence" in its place; every task is proposed `pending`.
|
|
70
|
-
2. Uncommitted files: `git status --porcelain`.
|
|
74
|
+
2. Uncommitted files: `git -C <worktree> status --porcelain`.
|
|
71
75
|
3. Proposed task statuses: `complete` iff at least one matching commit, else `pending`.
|
|
72
76
|
4. Proposed stage: `implement` if any task is `pending`, else `verify`.
|
|
73
77
|
|
|
@@ -83,7 +87,7 @@ edits override proposals; never rewrite confirmed state silently.
|
|
|
83
87
|
After confirmation:
|
|
84
88
|
|
|
85
89
|
1. `start brainstorm`; `skip brainstorm resume: <spec path>`; `start plan`.
|
|
86
|
-
2. `plan_check` with `planPath` = the plan. FAIL -> print the findings, stop with plan
|
|
90
|
+
2. `plan_check` with `planPath` = the plan's absolute path. FAIL -> print the findings, stop with plan
|
|
87
91
|
in_progress, no `init`.
|
|
88
92
|
3. PASS -> `skip plan` with the same `resume:` reason; for stage verify also
|
|
89
93
|
`skip implement`; `start <stage>`.
|