pi-gauntlet 4.3.1 → 4.4.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,71 @@
1
1
  # Changelog
2
2
 
3
+ ## v4.4.1 - 2026-07-13
4
+
5
+ Gates ratify, not ceremony: two gauntlet gates now auto-apply mechanical/drafting
6
+ changes and stop the human only for a load-bearing decision, not ritual confirmation.
7
+
8
+ - **Conformance gate (`verification-before-completion/reference/conformance-check.md`,
9
+ `subagent-driven-development`, `verification-before-completion`):** GAPS handling is
10
+ a deterministic verdict-then-`recommended` partition, no menu. `fix` gaps auto-close
11
+ through an isolated SDD-mirror fix loop (per-gap `implementer` + `spec-reviewer` on a
12
+ gap-block contract, `plan_tracker` fix-wave progress, per-round `code-reviewer`, delta
13
+ re-audit with regression guard, `maxFixRounds` cap -> escalate). `accept`/`rescope`/
14
+ `UNAUTHORIZED` gaps carry OPEN to the finishing gate. New `## Closure / conformance`
15
+ handoff schema + `conformance fix Gn` commit convention.
16
+ - **Spec council (`roasting-the-spec`, `brainstorming`):** the main-loop model applies
17
+ the chair's apply-set before the single brainstorming review gate; a gate-only
18
+ Applied/Deferred/Rejected audit is recorded in the spec commit body. The verbatim
19
+ summary now reflects the final post-apply spec on both worker and council paths;
20
+ checklist item 10 reduced to a re-scan; revert valve at the gate.
21
+ - **`finishing-a-development-branch` Step 3.5:** enforced per-gap disposition gate
22
+ (accept-into-spec / rescope-into-spec / fix-now / custom disposition / revert) with
23
+ three-tier revert semantics.
24
+ - **`agents/conformance-reviewer.md`:** deterministic `UNAUTHORIZED` recommendation
25
+ (harmless -> accept, else fix).
26
+ - **Extensions (`phase-tracker.ts`, `plan-tracker.ts`):** `SHIP_ADVISORY` and the
27
+ `plan_tracker` tool description reworded to allow carried-open deferred gaps and
28
+ verify-phase fix waves (description strings only, no logic change).
29
+ - **`README.md`:** conformance gate auto-fixes requirement-restoring gaps, defers
30
+ spec-rewriting decisions to the finishing gate.
31
+
32
+ ## v4.4.0 - 2026-07-12
33
+
34
+ Add an unconditional parallel context-gather step to `/skill:brainstorming`. Step 3's
35
+ inline exploration (raw whole-file reads polluting the main session, pruned by
36
+ pi-condense right before spec-writing, serialized for no reason) is replaced by a
37
+ recon fan-out whose output persists on disk as a context draft at the spec path,
38
+ fully overwritten by the real spec at spec-writing.
39
+
40
+ - **`brainstorming/gatherer.md` (new):** the gather procedure - one parallel-tasks
41
+ `subagent` call: `scout` always, `context-builder` only when the request carries
42
+ external refs (URL, tracker ID with a fetch path, GitHub issue ref). Outputs land
43
+ in a temp dir; the main loop assembles them into a draft headed by a line-1
44
+ marker (`# CONTEXT DRAFT - NOT A SPEC - fully replaced at spec-writing`) with
45
+ `## Codebase recon` / `## External context` / `## Appended during questionary`
46
+ sections. Builder failure degrades to a noted thin draft, never blocks, never
47
+ surfaces at gather time. Foreground, no user interaction - the first thing the
48
+ operator sees after gather is questionary question one.
49
+ - **`brainstorming/SKILL.md`:** checklist gains the gather step; step 3 rewritten
50
+ around the draft (unconditional `Read` before question one; helper-not-fence -
51
+ load-bearing claims still verified against real code; append bar for citable
52
+ findings); spec-writing gains a 3-step overwrite discipline (re-read draft
53
+ immediately before, `write`-tool full replacement, line-1 marker-gone check
54
+ before any downstream dispatch); slug minted once at gather, rename-with-delete
55
+ at spec-writing; 4 new Red Flags.
56
+ - **`phase-tracker.ts`:** new `substep` action - `phase_tracker({ action: "substep",
57
+ phase, substep })` labels an `in_progress` phase in the widget
58
+ (`brainstorm(gather)`), `null`/omitted clears, any transition drops it. New
59
+ marker commit guard between the branch-op block and the mutation warning: during
60
+ brainstorm, `git commit` (incl. `git -C` / `cd X &&` forms) is blocked while any
61
+ file under `flowGuards.specDirs` still begins with the marker line; skipped
62
+ entirely when `piGauntlet.flowGuards.enforce` is `false`.
63
+ - **`extensions/lib/phase-tracker-helpers.ts` (new):** pure, injected-fs logic for
64
+ the above (commit-form parsing, repo-dir resolution, line-1 marker scan, substep
65
+ transition contract) with 17 `node --test` cases registered in `scripts/ci.mjs`.
66
+ - **`doc/configuration.md`:** documents the `substep` action and the third flow
67
+ guard.
68
+
3
69
  ## v4.3.1 - 2026-07-06
4
70
 
5
71
  Fix spec-summary pruning at the brainstorming user-review gate. The gate's
package/README.md CHANGED
@@ -6,77 +6,94 @@
6
6
 
7
7
  [![Buy Me A Coffee](https://img.shields.io/badge/Buy%20Me%20A%20Coffee-donate-yellow?logo=buymeacoffee&logoColor=black)](https://buymeacoffee.com/jjurasszek)
8
8
 
9
- A workflow library for the [pi coding agent](https://github.com/earendil-works/pi): opinionated skills, ready-to-use subagent personas, and three runtime extensions that turn "run the agent in a loop" into a gated pipeline from idea to merge.
9
+ The gated workflow for the [pi coding agent](https://github.com/earendil-works/pi): brainstorm, plan, implement, verify, ship - each stage a gate the next can't open until it closes.
10
10
 
11
- ## Why
11
+ ## The problem
12
12
 
13
- The agentic loop everyone's excited about - point an agent at a problem, let it iterate until done - is the easy part. A bare loop has nothing to aim at, nothing to stop it shipping the wrong thing, and no check that the output matches the ask. It holds up on narrow tasks and falls apart on everything open-ended.
13
+ Point an agent at a task and let it loop until done - that's the easy 5%. A bare loop has nothing to aim at, nothing to stop it shipping the wrong thing, and no check that the final output matches what you actually asked for. It holds up on a narrow, well-specified task and drifts on anything open-ended: the agent reinterprets the ask as it goes, nobody catches it until review, and by then the diff is large enough that review is theater too.
14
14
 
15
- pi-gauntlet is the scaffolding that makes the loop hold: **brainstorm plan implement verify ship**, each stage a gate the next one can't open until it closes.
15
+ That's not a model problem. Cursor, Claude Code, Codex, Devin all run some version of the same loop, and all of them drift the same way on long tasks - because nothing in the loop confronts the output against the *original* intent.
16
16
 
17
- **A gate here is an automated check, not a signature to collect.** Most are machine-enforced - a multi-model spec critique, an adversarial code review, a conformance check that confronts the finished diff against your original ask, and an extension that blocks a commit or push until verification has actually passed. The agent can't wave itself through, and you're not rubber-stamping each step. Human judgment is spent on the two decisions that need it - *what to build* up front and *how to land it* at the end - while the middle (plan → execution, task → task) runs without pausing for you.
17
+ ## Why pi-gauntlet exists
18
18
 
19
- The spec and docs it produces get committed to the repo, so the next change starts from ground truth, not a blank slate. The loop is the easy 5%; the gated system around it is the whole job - that's what this is.
19
+ pi-gauntlet is the scaffolding that makes the loop hold: **brainstorm plan implement verify ship**. Gates between phases are automated checks, not signatures to collect - a multi-model spec critique, an adversarial code review, and a closing conformance check that confronts the finished diff **and docs** against your **original verbatim prompt**, not the plan that got derived from it. The agent can't wave itself through a gate, and you're not rubber-stamping each step by hand.
20
20
 
21
- Rebuilt for pi with enforced gates, a spec council, conformance review, and parallel execution waves. Inspired by [obra/superpowers](https://github.com/obra/superpowers) (Claude Code), by way of [coctostan/pi-superpowers-plus](https://github.com/coctostan/pi-superpowers-plus); see [Lineage](#lineage).
21
+ Human judgment is spent on the two decisions that need it - *what to build*, up front, and *how to land it*, at the end. The middle runs without pausing for you. The spec and docs get committed to the repo, so the next change starts from ground truth, not a blank slate.
22
22
 
23
- ## The workflow
23
+ ## Part of the pi agent toolkit
24
24
 
25
- pi-gauntlet is **opinionated**: every non-trivial change rides one pipeline, idea to merge. There is no separate "just edit a file and commit" path — the skills gate each other, so the next phase can't open until the current one closes.
25
+ Four independent extensions for the [pi coding agent](https://github.com/earendil-works/pi), each owning one concern of running agents seriously:
26
26
 
27
- ```
28
- brainstorm plan implement verify ship
27
+ - [pi-quiver](https://github.com/jjuraszek/pi-quiver) - capabilities (ground-truth ingestion: fetch, doc conversion, session tools)
28
+ - [pi-cohort](https://github.com/jjuraszek/pi-cohort) - coordination (delegate to focused child agents)
29
+ - [pi-condense](https://github.com/jjuraszek/pi-condense) - context economy (prune context, keep it recoverable)
30
+ - **pi-gauntlet - process (this repo: the gated brainstorm→ship workflow)**
31
+
32
+ pi-gauntlet's only hard dependency is pi-cohort - every gate that dispatches a reviewer or an implementer does it through pi-cohort's `subagent()`. pi-condense is not required, but a long gated run generates a lot of tool output; pruning it as you go is what keeps that run affordable.
33
+
34
+ ## What a run looks like
35
+
36
+ Concretely, one change through the gauntlet:
37
+
38
+ 1. You describe the change. **`brainstorming`** sets up an isolated worktree, explores the codebase, and turns your description into a written spec. A multi-model critique runs on it automatically. **You read and approve the spec - human gate 1.** No implementation code exists yet.
39
+ 2. **`writing-plans`** decomposes the approved spec into atomic, independently-verifiable tasks, grouped into parallel waves where they don't touch the same files.
40
+ 3. **`subagent-driven-development`** executes the plan one task at a time, each in a fresh subagent, behind spec-compliance review then code-quality review. TDD-locked: red, green, refactor.
41
+ 4. **verify**: a whole-diff code review, then the **conformance gate** - a 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. This gate is machine-blocked from being skipped. Requirement-restoring gaps (code drifted from the approved spec) auto-close through an isolated fix-and-re-audit loop with no prompt; only decisions that would *rewrite* your approved spec - accept, rescope, or removing unrequested code - are deferred to the finishing gate for your call.
42
+ 5. **`finishing-a-development-branch`**: squash, PR, keep, or discard. **Human gate 2** - the only other decision you make.
43
+
44
+ ```mermaid
45
+ flowchart LR
46
+ R([request]) --> B[brainstorm<br/>+ spec]
47
+ B --> G1{{human gate 1:<br/>approve spec}}
48
+ G1 --> P[plan]
49
+ P --> I[implement<br/>waves + reviews]
50
+ I --> V[verify]
51
+ V --> M{{machine gate:<br/>conformance vs<br/>original words}}
52
+ M --> S[ship]
53
+ S --> G2{{human gate 2:<br/>merge / PR / discard}}
54
+ G2 --> D([done])
29
55
  ```
30
56
 
31
- 1. **`brainstorming`** every change starts here. Sets up an isolated worktree, explores the codebase, and turns the idea into a written spec under `doc/specs/`. A multi-model critique runs automatically before you read it (`roasting-the-spec` when a council is configured, else one fresh `worker`). **Hard gate:** no implementation code is written until you approve the spec.
32
- 2. **`writing-plans`** — derives an implementation plan from the approved spec, decomposed into atomic, independently-verifiable tasks (grouped into parallel waves when they're file- and resource-disjoint). Auto-chains into execution.
33
- 3. **`subagent-driven-development`** — executes the plan one atomic task at a time, each in a **fresh subagent**, behind a **two-stage review**: spec compliance first (`spec-reviewer`), then code quality (`code-reviewer`). The `implementer` persona is TDD-locked (RED→GREEN→REFACTOR). You orchestrate; you never hand-write the code.
34
- 4. **verify** — after the last task: a whole-diff review (`requesting-code-review`), then the `conformance-reviewer` closing-loop gate that confronts the delivered code **and** docs against the *origin* (spec + your verbatim original prompt), not the plan. The phase-tracker **blocks `complete verify`** until a conformance dispatch has run, and on a successful `complete verify` (ship still pending) injects an advisory to invoke `finishing-a-development-branch` immediately without a redundant "ready to finish?" prompt - or to reopen verify if a requirement decision is still open.
35
- 5. **`finishing-a-development-branch`** — squash / PR / keep / discard. This menu is the single human decision gate at the end, mirroring spec approval at the start.
57
+ <!-- TODO GIF: a real gauntlet run end to end -->
36
58
 
37
- Spec, plan, and implementation are all developed in the **same worktree**. The squash commit ships spec and implementation; the plan is ephemeral and is stripped before any landing path.
59
+ Everything between gate 1 and gate 2 - task breakdown, implementation, both review passes - runs without you in the loop. That's the mechanism. What follows is the machinery behind it.
38
60
 
39
- **Supporting skills** slot in as the pipeline needs them: `using-git-worktrees` (isolation, before the spec), `test-driven-development` (inside every implementer), `dispatching-parallel-agents` (wave fan-out), `systematic-debugging` (when something breaks), `receiving-code-review` (when you get feedback), `writing-skills` (authoring more of these).
61
+ ## Architecture
40
62
 
41
- **The gates are enforced, not suggested.** Brainstorming refuses to write code before spec approval; the phase-tracker refuses to close verify before the conformance gate runs; `verify-before-ship` warns on any commit/push/PR without a passing test run since your last edit. Reach for a shortcut and a gate stops you — that is the design, not a side effect.
63
+ pi-gauntlet ships three kinds of pieces, layered on top of pi-cohort's dispatch:
42
64
 
43
- ## What you get
65
+ - **13 skills** - the workflow logic. They activate automatically when pi sees the matching kind of task, and each one gates the next: `brainstorming`, `writing-plans`, `roasting-the-spec`, `test-driven-development`, `subagent-driven-development`, `dispatching-parallel-agents`, `verification-before-completion`, `systematic-debugging`, `requesting-code-review`, `receiving-code-review`, `using-git-worktrees`, `finishing-a-development-branch`, `writing-skills`.
66
+ - **7 subagent personas** - the specialized child agents the skills dispatch via pi-cohort: `implementer`, `code-reviewer`, `spec-reviewer`, `conformance-reviewer`, `spec-summarizer`, `spec-council-member`, `spec-council-synthesizer`. See [doc/personas.md](./doc/personas.md) for what each one does and why its permissions are scoped the way they are.
67
+ - **3 runtime extensions** - the enforcement layer. `plan-tracker` and `phase-tracker` are tools skills call to track progress (with a TUI widget); `verify-before-ship` is a hook that warns if you commit/push without a passing test run since your last edit. See [doc/configuration.md](./doc/configuration.md) for the settings each one reads.
44
68
 
45
- **13 skills** that activate automatically when pi sees the right kind of task:
69
+ pi-gauntlet is **opinionated**: every non-trivial change rides this one pipeline. There's no separate "just edit a file and commit" path - the skills gate each other, so the phase-tracker extension mechanically blocks a phase from closing before its gate runs. Reach for a shortcut and a gate stops you; that's the design, not friction.
46
70
 
47
- - **Design & planning** — `brainstorming`, `writing-plans`, `roasting-the-spec`
48
- - **Implementation** — `test-driven-development`, `subagent-driven-development`, `dispatching-parallel-agents`
49
- - **Verification** — `verification-before-completion`, `systematic-debugging`
50
- - **Review** — `requesting-code-review`, `receiving-code-review`
51
- - **Worktree lifecycle** — `using-git-worktrees`, `finishing-a-development-branch`
52
- - **Meta** — `writing-skills`
71
+ ## Key concepts
53
72
 
54
- **7 subagent personas** dispatchable via [pi-cohort](https://github.com/jjuraszek/pi-cohort):
73
+ | Term | Meaning |
74
+ | --- | --- |
75
+ | Gate | A machine-enforced checkpoint between phases (e.g. `complete verify` is blocked until conformance review has run). Not a suggestion. |
76
+ | Spec council | Multi-model critique of the spec before you see it (`roasting-the-spec`); falls back to a single-model critique if no council is configured. |
77
+ | Conformance gate | The closing check: does the delivered code + docs match your *original prompt*, not the derived plan? Per-requirement verdict; auto-fixes requirement-restoring gaps, defers spec-rewriting decisions (accept/rescope/unauthorized) to the finishing gate. |
78
+ | Wave | A batch of plan tasks that don't touch the same files, dispatched to implementers in parallel. |
79
+ | Overrides file | `.pi/gauntlet-overrides.md` - where you put project-specific detail the generic skills don't know (CI command, worktree wrapper, routing rules). |
55
80
 
56
- - `implementer` strict RED→GREEN→REFACTOR TDD, completion-guarded.
57
- - `code-reviewer` — read-only review, Critical/Moderate/Minor severity.
58
- - `spec-reviewer` — verifies an implementation against its plan/spec, per-requirement table.
59
- - `conformance-reviewer` — closing-loop intent gate; confronts the delivered code+docs against the *origin* (spec + verbatim prompt), skipping the plan, and emits a per-requirement coverage verdict. Read-only; proposes remediation, never fixes or decides. Ships model-free — pin its model per preset (see [Conformance gate](#conformance-gate-model)).
60
- - `spec-summarizer` - produces a tight, spec-only human summary for the brainstorming user review gate. Fresh context, read-only (`tools: read`), reads only the spec it is given; output is ephemeral (rendered at the gate, never committed). Dispatched only by `brainstorming`; not for direct dispatch. Ships model-free - set `subagents.agentOverrides.spec-summarizer.model` per preset to override (unset -> inherits the main loop).
61
- - `spec-council-member` — adversarial single-model spec critic; one per configured council model. Dispatched only by `roasting-the-spec`.
62
- - `spec-council-synthesizer` — neutral chair that consolidates and adjudicates member critiques. Dispatched only by `roasting-the-spec`.
81
+ ## When to use / when NOT to use
63
82
 
64
- **3 runtime extensions**:
83
+ **Use it** for any change with more than one moving part: a feature, a refactor across files, anything where "what did we actually agree to build" matters by the time it's done.
65
84
 
66
- - `plan-tracker` persistent task list with a TUI widget. Use the `plan_tracker` tool from skills.
67
- - `phase-tracker` — tracks workflow phase (brainstorm → plan → implement → verify → ship) with a TUI widget. Use the `phase_tracker` tool from skills. Distinct from `plan-tracker` which tracks per-task progress within the implement phase.
68
- - `verify-before-ship` — advisory warning if you run `git commit` / `git push` / `gh pr create` without passing tests since your last source edit.
85
+ **Don't use it** for a one-line fix, a typo, or a throwaway spike you're going to discard. The gates have real overhead - a spec, a plan, a conformance check - and that overhead isn't worth paying for a change trivial enough to just make.
69
86
 
70
87
  ## Requirements
71
88
 
72
89
  - [pi-coding-agent](https://github.com/earendil-works/pi) ≥ 0.1.0
73
- - [pi-cohort](https://github.com/jjuraszek/pi-cohort) ≥ 1.4.5 required peer package. Skills that dispatch agents (`requesting-code-review`, `subagent-driven-development`, `dispatching-parallel-agents`, `writing-plans`, `writing-skills`) call `subagent({})`, which is provided by pi-cohort. pi-gauntlet does not vendor the dispatch tool; without pi-cohort those skills have nothing to call.
90
+ - [pi-cohort](https://github.com/jjuraszek/pi-cohort) ≥ 1.4.5 - required peer package. Skills that dispatch agents (`requesting-code-review`, `subagent-driven-development`, `dispatching-parallel-agents`, `writing-plans`, `writing-skills`) call `subagent({})`, which pi-cohort provides. pi-gauntlet does not vendor the dispatch tool; without pi-cohort those skills have nothing to call.
74
91
 
75
- 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.
92
+ 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.
76
93
 
77
94
  ## Install
78
95
 
79
- **Project scope** (recommended committable via the repo's `.pi/settings.json`; `-l` writes to project settings):
96
+ **Project scope** (recommended - committable via the repo's `.pi/settings.json`; `-l` writes to project settings):
80
97
 
81
98
  ```bash
82
99
  pi install -l npm:pi-cohort
@@ -90,45 +107,19 @@ pi install npm:pi-cohort
90
107
  pi install npm:pi-gauntlet
91
108
  ```
92
109
 
93
- Pin an exact release with `npm:pi-gauntlet@X.Y.Z`. Pi clones the package, runs `npm install --omit=dev`, which triggers the `postinstall` script. Where personas land depends on the install location:
94
-
95
- - **User install** (package under `<home>/.pi/<profile>/...`): symlinks the seven agent files into `getAgentDir()/agents` — i.e. `$PI_CODING_AGENT_DIR/agents`, defaulting to `~/.pi/agent/agents`. This is pi-cohort's profile-scoped user dir, so each pi profile (`agent`, `agent.anthropic`, …) gets its own personas instead of sharing the machine-global `~/.agents/`. Earlier releases installed into the machine-global `~/.agents/`; on upgrade the postinstall removes stale `~/.agents/<name>.md` symlinks that point into a pi-gauntlet package (which would otherwise shadow the profile-scoped copy) and leaves your own files there alone.
96
- - **Project install** (package under `<repo>/.pi/...`): copies the seven agent files into `<repo>/.pi/agents/` (the project-scope discovery path). Copy, not symlink, so the files stay valid if you commit them; gitignore `.pi/agents/` if you'd rather keep them install-managed. Project scope wins over user scope on name collisions, so each repo's personas are independent of the user dir and of other repos.
97
-
98
- ## Upgrading from v3.x
110
+ Pin an exact release with `npm:pi-gauntlet@X.Y.Z`. See [doc/install-internals.md](./doc/install-internals.md) for what the postinstall step actually does (symlink vs copy, `PI_GAUNTLET_AGENT_DIR`, upgrading from the pre-rename package).
99
111
 
100
- v4.0.0 is the first public release and is a **breaking rename** (no behavior change). If you ran the package under its old identity:
101
-
102
- - Reinstall under the new name: `pi install -l npm:pi-gauntlet` (was `@jjuraszek/pi-superpowers`).
103
- - Rename settings namespace `piSuperpowers.*` -> `piGauntlet.*` in every preset's `settings.json` (a preset still on the old key silently gets defaults).
104
- - Rename your override file `.pi/superpowers-overrides.md` -> `.pi/gauntlet-overrides.md`.
105
- - Rename the env override `PI_SUPERPOWERS_AGENT_DIR` -> `PI_GAUNTLET_AGENT_DIR` if you set it.
106
-
107
- See [CHANGELOG.md](CHANGELOG.md) for the full v4.0.0 entry.
108
-
109
- ## Install (local development)
112
+ For local development against a checkout instead of npm:
110
113
 
111
114
  ```bash
112
115
  git clone git@github.com:jjuraszek/pi-gauntlet.git ~/repos/pi-gauntlet
113
- cd ~/path/to/your/repo
114
- pi install -l ~/repos/pi-gauntlet
115
- # Local-path installs skip `npm install`; run the symlink step manually:
116
- cd ~/repos/pi-gauntlet && npm run link-agents
116
+ cd ~/path/to/your/repo && pi install -l ~/repos/pi-gauntlet
117
+ cd ~/repos/pi-gauntlet && npm run link-agents # local-path installs skip npm install; run this once
117
118
  ```
118
119
 
119
- After that, edits in `~/repos/pi-gauntlet/` are picked up on next pi launch.
120
-
121
120
  ## Project-specific overrides
122
121
 
123
- The skills shipped here are generic on purpose they describe *how* to TDD, brainstorm, debug, request review, etc., without naming your services, your CI command, or your worktree wrapper. When you need that level of detail, drop a file at:
124
-
125
- ```
126
- .pi/gauntlet-overrides.md
127
- ```
128
-
129
- …in your repo. The skills read it at runtime and merge sections that match the skill's name or topic.
130
-
131
- ### Example `.pi/gauntlet-overrides.md`
122
+ The skills shipped here are generic on purpose - they describe *how* to TDD, brainstorm, debug, request review, etc., without naming your services, your CI command, or your worktree wrapper. When you need that level of detail, drop a file at `.pi/gauntlet-overrides.md` in your repo. The skills read it at runtime and merge sections that match the skill's name or topic:
132
123
 
133
124
  ```markdown
134
125
  ## verification-before-completion
@@ -140,152 +131,21 @@ the gate — it skips integration tests.
140
131
 
141
132
  Use the project's wrapper: `script/worktree create <name>`. It provisions an isolated
142
133
  database and copies `.env.local`. Never call `git worktree add` directly.
143
-
144
- ## brainstorming
145
-
146
- Project routing: dashboard work → `dashboard/AGENTS.md`. Compliance work → `compliance/AGENTS.md`.
147
- Spec docs land in `doc/specs/`, plans in `doc/plans/`, both sibling to each other.
148
- ```
149
-
150
- Two notes:
151
-
152
- - The override file is read by the **skill instructions** at runtime — not by the pi runtime itself. So adding a section here doesn't load anything; the skill that's currently active reads the file and pulls in the matching section.
153
- - Section headers should match skill names (`## verification-before-completion`) or skill topics (`## worktrees`, `## routing`). Skills look for both.
154
-
155
- ## Subagent personas
156
-
157
- On a user install the seven personas in `agents/` are symlinked into `getAgentDir()/agents` (profile-scoped user dir — `$PI_CODING_AGENT_DIR/agents`, default `~/.pi/agent/agents`). On a project install they are copied into `<repo>/.pi/agents/` (project scope, isolated per repo). Override precedence is `project > user > builtin`, so a project install always shadows the user personas for that repo, and you can hand-edit or drop your own `.pi/agents/<name>.md` to shadow them further.
158
-
159
- Target dir override: set `PI_GAUNTLET_AGENT_DIR` to force symlinking into a specific dir (leading `~` expanded; always symlink mode).
160
-
161
- ### Thinking budgets
162
-
163
- `implementer`, `code-reviewer`, and `spec-reviewer` ship without `thinking:` in their frontmatter — pi-cohort `agentOverrides` only fill frontmatter-unset fields, so leaving it unset makes the budget a per-preset config knob. Set it in each preset's `settings.json` (use `false` on non-thinking models → provider default):
164
-
165
- ```json
166
- {
167
- "subagents": {
168
- "agentOverrides": {
169
- "implementer": { "thinking": "medium" },
170
- "code-reviewer": { "thinking": "high" },
171
- "spec-reviewer": { "thinking": "medium" }
172
- }
173
- }
174
- }
175
- ```
176
-
177
- Unset → provider default thinking for that model. `conformance-reviewer` and the two `spec-council-*` personas stay frontmatter-pinned at `xhigh` and are not configurable — the gate and the council must run at max budget even when they inherit the session's model.
178
-
179
- ### Conformance gate model
180
-
181
- `conformance-reviewer` ships without a `model:` in its frontmatter — like the spec-council personas, its model is supplied per preset so each profile points the last correctness gate at the strongest reasoning model its providers can reach. The verify-step skills resolve `piGauntlet.closureReview.model` **repo-local first** (a repo's `.pi/settings.json` overrides the preset whole-object - defining `closureReview` there replaces the preset's entire block, so set every leaf you need together) and inject it **call-site** on the conformance dispatch (the same mechanism the spec-council chair uses). Add it to each preset's `settings.json` (or a repo's `.pi/settings.json` to override per repo):
182
-
183
- ```json
184
- {
185
- "piGauntlet": {
186
- "closureReview": { "model": "<provider/model>", "enforce": true, "maxFixRounds": 2 }
187
- }
188
- }
189
134
  ```
190
135
 
191
- Frontmatter pins `thinking: xhigh` and `defaultContext: fresh` (the gate always runs cold, with max reasoning) and `thinking` is not call-site overridable, so the config supplies only `model`. If `closureReview.model` is unset the dispatch omits `model:` and the gate inherits the parent's model; if the configured model is unreachable it retries once inherited.
192
-
193
- When `closureReview.model` **is** set, the phase-tracker match-checks call-site injection: a `subagent` dispatch of `conformance-reviewer` that omits `model:` is **blocked at tool-call time** (before it runs) so the gate can never silently degrade to the parent's builder model, and a dispatch whose `model:` **differs** from the configured value gets a non-blocking **warning** appended to the result (drift is surfaced, not blocked). The documented one-retry fallback still works - pass an explicit model and it runs (with a warning if it differs). Disabling `closureReview.enforce` disables this guard too.
194
-
195
- `closureReview.enforce` (default `true`) controls the phase-tracker gate that
196
- blocks `complete verify` until the conformance-reviewer has run; set `false` to
197
- disable enforcement for a preset.
198
-
199
- `closureReview.maxFixRounds` (default `2`) caps the conformance **remediation loop**: when a `GAPS` verdict's gaps are dispositioned `fix`, the orchestrator dispatches isolated fix waves and re-audits the delta, up to this many rounds before escalating to the user with the per-gap history. Missing or non-integer -> `2`; `< 0` clamps to `0`; `0` disables fix dispatch (the gap menu offers accept / rescope only). Enforced by the protocol prose in `verification-before-completion/reference/conformance-check.md`, not by the phase-tracker extension.
200
-
201
- If you want to know what's in each persona before using it, see [`agents/`](./agents/). The frontmatter (tools, thinking level, context mode) is documented in [`AGENTS.md`](./AGENTS.md#agents).
202
-
203
- ## Spec council
204
-
205
- `/skill:roasting-the-spec` runs a multi-model critique of a spec before the brainstorming user-review gate. It is the **critique half** of brainstorming's self-review: when a council is configured in the active preset's `settings.json`, brainstorming **auto-dispatches** it (no prompt); when none is configured, brainstorming runs a single fresh-`worker` critique instead. Each member runs on a different model (divergent critiques), a neutral chair consolidates and adjudicates, and you approve what gets applied.
206
-
207
- ```json
208
- {
209
- "piGauntlet": {
210
- "specCouncil": {
211
- "members": ["<provider/model>", "<provider/model>", "<provider/model>"],
212
- "chair": "<provider/model>"
213
- }
214
- }
215
- }
216
- ```
217
-
218
- - `members` (required) — roster of `provider/model` strings; council size = array length, one critique per model. Empty or absent → the council never runs; brainstorming falls back to a single fresh-`worker` critique (scope + ambiguity, auto-applied).
219
- - `chair` (optional) — model for the consolidating synthesizer; defaults to the inherited model when omitted.
220
-
221
- Rosters resolve **repo-local first**: a repo's `.pi/settings.json` overrides the preset (whole-object — the first file that defines `specCouncil` wins), otherwise each pi profile (`agent`, `agent.anthropic`, `agent.bedrock`, …) reads its own `settings.json`. List only models the resolving config's providers can reach. The two personas it dispatches — `spec-council-member` and `spec-council-synthesizer` — are model-free; their model is injected per task from this config.
222
-
223
- ## Extensions
224
-
225
- ### `plan-tracker`
136
+ Section headers should match skill names (`## verification-before-completion`) or skill topics (`## worktrees`, `## routing`). The override file is read by the skill instructions at runtime, not by the pi runtime itself, so adding a section only matters once the matching skill is active.
226
137
 
227
- A tool, not a hook. Skills call `plan_tracker({ action: "init" | "update" | "status" | "clear", ... })` to manage a task list; a TUI widget above the editor shows progress (✓/→/○). State branches with the session, no config needed.
138
+ ## Configuring the gates
228
139
 
229
- ### `phase-tracker`
140
+ The conformance gate's model, the spec council's roster, and the phase-tracker's flow guards are all configured per pi preset (or per repo, via `.pi/settings.json`). See [doc/configuration.md](./doc/configuration.md) for every setting, its default, and how repo-local config overrides a preset.
230
141
 
231
- A tool, not a hook. Skills call `phase_tracker({ action: "start" | "complete" | "skip" | "status" | "reset", phase?, reason? })` to track workflow phase progress. A TUI widget shows the five-phase pipeline: `○ brainstorm → ○ plan → ○ implement → ○ verify → ○ ship`. State branches with the session, no config needed. Phases are entered **explicitly** by the phase-owning skills, so outside a gauntlet flow the widget stays dormant. The `brainstorming` skill resets both trackers on entry (new flow, clean slate); `implement` auto-completes from `plan-tracker` once a skill has started it.
142
+ ## Relationship to the other repos
232
143
 
233
- Distinct from `plan-tracker`: `phase-tracker` answers "what stage of the workflow am I in?"; `plan-tracker` answers "which task within the current stage am I on?"
144
+ pi-gauntlet is the process layer: it enforces the workflow, but every reviewer and implementer it dispatches runs through [pi-cohort](https://github.com/jjuraszek/pi-cohort)'s `subagent()` - that's a hard dependency, not an integration you can skip. [pi-condense](https://github.com/jjuraszek/pi-condense) is optional but keeps a long gated run's context (and cost) from growing unbounded across all those dispatches. [pi-quiver](https://github.com/jjuraszek/pi-quiver) is complementary - if a brainstorm or implementation step needs to pull in a real doc or web page, that's what ingests it safely.
234
145
 
235
- **`gauntlet_setting` tool.** `phase-tracker` also registers `gauntlet_setting({ key: "specCouncil" | "closureReview" })`, a gauntlet-internal tool through which skills resolve merged `piGauntlet.*` settings (repo `.pi/settings.json` over the agent preset, via pi's own `SettingsManager`). It returns the resolved value as a JSON block in the tool result — `specCouncil` yields the council-vs-worker verdict, `closureReview` yields the conformance-gate `model`/`enforce`/`maxFixRounds`. It introduces no new settings key. Every `piGauntlet.*` read — the skills via this tool, both extensions directly — routes through one shared helper (`extensions/lib/gauntlet-settings*.ts`); no code reads `pi.settings` by hand.
236
-
237
- **Closure-review gate.** `complete verify` is rejected unless a successful
238
- `conformance-reviewer` dispatch (a `subagent` result whose `results[]` contains
239
- `agent: "conformance-reviewer"` with `exitCode: 0`) has been observed since the
240
- last `reset`. Management calls (`action: "list"` etc.) and async dispatches never
241
- qualify. A user waiver is recorded via `skip` with a reason — there is no `force`
242
- bypass on `complete`. Disable per preset with
243
- `settings.json#piGauntlet.closureReview.enforce: false` (default: enforced).
244
-
245
- **Flow guards.** Two guards, on by default, disabled per
246
- preset with `settings.json#piGauntlet.flowGuards.enforce: false`:
247
-
248
- - **Worktree discipline (blocks).** During `brainstorm`/`plan`/`implement`, an in-place
249
- `git switch` / `git checkout -b`/`-B` is blocked — the bash call does not run.
250
- Active **only when pi was launched in the primary checkout** (not a linked
251
- worktree); `git worktree ...` and plain `git checkout <file>` never trip it.
252
- Override via `piGauntlet.flowGuards.enforce: false`.
253
- - **Spec-phase confinement (advisory).** During `brainstorm`, a `write`/`edit` (or a bash
254
- mutation: `>`/`>>`/`tee`/`sed -i`/`git apply`) outside the spec dir warns that
255
- brainstorming may only touch the spec. Spec dirs come from
256
- `flowGuards.specDirs` (default `["doc/specs"]`). Redirects to scratch paths
257
- (`/tmp`, `/var/folders`, `/dev`) are exempt. Warns once per brainstorm.
258
-
259
- ### `verify-before-ship`
260
-
261
- A hook on `git commit` / `git push` / `gh pr create`. If you haven't run a passing test command since your last source-file write in this session, an advisory warning is injected into the tool result. The warning clears automatically after a passing test run.
262
-
263
- Default test-command regex matches: `make ci`, `make test`, `npm test`, `pnpm test`, `yarn test`, `pytest`, `rspec`, `cargo test`, `go test`.
264
-
265
- Override in `.pi/settings.json`:
266
-
267
- ```json
268
- {
269
- "piGauntlet": {
270
- "verifyBeforeShip": {
271
- "testCommands": ["make ci", "bundle exec rspec"],
272
- "warningReference": "doc/testing.md"
273
- }
274
- }
275
- }
276
- ```
277
-
278
- `testCommands` entries are regex fragments (anchored with `\b` automatically). `warningReference` is a doc path appended to the warning text — useful for pointing engineers at your testing conventions.
279
-
280
- ## Versioning
281
-
282
- Bump explicitly:
283
-
284
- ```bash
285
- pi install -l npm:pi-gauntlet@X.Y.Z
286
- ```
146
+ ## Roadmap
287
147
 
288
- See [`CHANGELOG.md`](./CHANGELOG.md) for what changed in each release. Semver: minor for new skill/agent/extension, major for renames or breaking config changes. pi-gauntlet and its dispatch peer [pi-cohort](https://github.com/jjuraszek/pi-cohort) version independently but ship together whenever dispatch semantics change; pin compatible versions of both.
148
+ Nothing committed beyond what's shipped. Changes land via [CHANGELOG.md](./CHANGELOG.md).
289
149
 
290
150
  ## Lineage
291
151
 
@@ -64,7 +64,7 @@ DELIVERED rows keep an `Rn` id; every non-DELIVERED row gets a durable `Gn` id r
64
64
  ## Structured gap blocks
65
65
 
66
66
  After the coverage table, emit one fenced block per non-DELIVERED row so the
67
- orchestrator can drive the disposition menu mechanically. In a re-audit round
67
+ orchestrator can partition and dispose of them mechanically. In a re-audit round
68
68
  (see conformance-check.md "When the check finds gaps"), also emit a `DELIVERED`
69
69
  block for any gap that closed, reusing its original `Gn` id.
70
70
 
@@ -122,15 +122,15 @@ disjointness rule does. When you cannot confidently certify a pair disjoint, mar
122
122
  `recommended` is a proposal; you never decide, edit, dispatch, or re-audit.
123
123
 
124
124
  - Default `fix` for every `PARTIAL` / `MISSING` / `DRIFTED` row.
125
- - `accept` only for an `UNAUTHORIZED` row whose behavior is harmless, with a one-line
126
- rationale in `remediation`.
125
+ - For every `UNAUTHORIZED` row: harmless `accept` with a one-line rationale in
126
+ `remediation`; otherwise `fix` (= remove the unrequested code).
127
127
  - `rescope` only when the `origin` requirement is impractical to satisfy in this branch
128
128
  (`rescope` is inapplicable to `UNAUTHORIZED` — there is no requirement to defer).
129
129
 
130
130
  ## Rules
131
131
 
132
132
  - **Read-only. Never edit.** You audit; you do not fix.
133
- - **Propose, do not dispose.** For each gap you may suggest a one-line remediation *direction*, but you do **not** decide whether to apply it, defer it, or accept it that is the user's call, surfaced by the orchestrator. Never present a fix as a decision made.
133
+ - **Propose, do not dispose.** For each gap you may suggest a one-line remediation *direction*, but you do **not** decide the disposition - the orchestrator auto-applies `fix` gaps and defers `accept`/`rescope`/`UNAUTHORIZED` to the user at the finish gate. Never present a fix as a decision you made.
134
134
  - **Evidence or it didn't happen.** Cite a real `file:line` for every DELIVERED/PARTIAL. If you cannot, downgrade the row to MISSING.
135
135
  - **Spec is canonical; the prompt catches what the spec dropped; the ticket is fallback only** when no spec exists.
136
136
  - **Do not absorb origin drift silently** — flag every spec↔prompt/ticket disagreement.
@@ -0,0 +1,120 @@
1
+ import { test } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import {
4
+ CONTEXT_DRAFT_MARKER,
5
+ checkSubstep,
6
+ phaseLabel,
7
+ parseGitCommit,
8
+ resolveRepoDir,
9
+ findMarkerFile,
10
+ markerBlockReason,
11
+ transitionPhaseState,
12
+ markerGuardApplies,
13
+ } from "./phase-tracker-helpers.ts";
14
+
15
+ test("checkSubstep: in_progress -> ok", () => {
16
+ assert.deepEqual(checkSubstep("in_progress"), { ok: true });
17
+ });
18
+
19
+ test("checkSubstep: non-in_progress statuses -> error naming actual status", () => {
20
+ for (const s of ["pending", "complete", "skipped"]) {
21
+ const r = checkSubstep(s);
22
+ assert.equal(r.ok, false);
23
+ if (!r.ok) assert.match(r.error, new RegExp(s));
24
+ }
25
+ });
26
+
27
+ test("phaseLabel: with and without substep", () => {
28
+ assert.equal(phaseLabel("brainstorm", "gather"), "brainstorm(gather)");
29
+ assert.equal(phaseLabel("brainstorm", undefined), "brainstorm");
30
+ });
31
+
32
+ test("parseGitCommit: plain, -am, chained after &&", () => {
33
+ assert.ok(parseGitCommit('git commit -m "x"'));
34
+ assert.ok(parseGitCommit("git commit -am 'x'"));
35
+ assert.ok(parseGitCommit('git add -A && git commit -m "x"'));
36
+ });
37
+
38
+ test("parseGitCommit: -C path and cd prefix are captured", () => {
39
+ assert.deepEqual(parseGitCommit('git -C /wt commit -m "x"'), { cPath: "/wt", cdPath: undefined });
40
+ assert.deepEqual(parseGitCommit('cd /wt && git commit -m "x"'), { cPath: undefined, cdPath: "/wt" });
41
+ });
42
+
43
+ test("parseGitCommit: non-commit git and non-git -> undefined", () => {
44
+ assert.equal(parseGitCommit("git log --oneline"), undefined);
45
+ assert.equal(parseGitCommit("git commitish"), undefined);
46
+ assert.equal(parseGitCommit('echo "git commit"'), undefined); // statement-start anchor: a quote is not a statement boundary
47
+ assert.equal(parseGitCommit("npm test"), undefined);
48
+ });
49
+
50
+ test("parseGitCommit: -c config flags between git and commit don't bypass the guard", () => {
51
+ assert.deepEqual(parseGitCommit('git -c user.email=x commit -m "y"'), { cPath: undefined, cdPath: undefined });
52
+ assert.deepEqual(parseGitCommit("git -C /wt -c user.email=x commit"), { cPath: "/wt", cdPath: undefined });
53
+ });
54
+
55
+ test("parseGitCommit: commit-graph / commit-tree are not commit", () => {
56
+ assert.equal(parseGitCommit("git commit-graph write"), undefined);
57
+ assert.equal(parseGitCommit("git commit-tree HEAD^{tree}"), undefined);
58
+ });
59
+
60
+ test("parseGitCommit: cd path in subshell (cd /a) captures /a without trailing paren", () => {
61
+ assert.deepEqual(parseGitCommit("(cd /a) && git commit -m x"), { cPath: undefined, cdPath: "/a" });
62
+ });
63
+
64
+ test("parseGitCommit: last cd before the commit wins over an earlier one", () => {
65
+ assert.deepEqual(parseGitCommit("cd /a && cd /b && git commit -m x"), { cPath: undefined, cdPath: "/b" });
66
+ });
67
+
68
+ test("parseGitCommit: cd recognized before non-&& statement separators", () => {
69
+ assert.deepEqual(parseGitCommit("cd /wt; git commit -m x"), { cPath: undefined, cdPath: "/wt" });
70
+ assert.deepEqual(parseGitCommit("cd /wt\ngit commit -m x"), { cPath: undefined, cdPath: "/wt" });
71
+ assert.deepEqual(parseGitCommit("cd /wt || git commit -m x"), { cPath: undefined, cdPath: "/wt" });
72
+ });
73
+
74
+ test("resolveRepoDir: -C wins over cd, cd wins over session cwd, relative -C resolves against cd", () => {
75
+ assert.equal(resolveRepoDir({ cPath: "/b", cdPath: "/a" }, "/s"), "/b");
76
+ assert.equal(resolveRepoDir({ cPath: undefined, cdPath: "/a" }, "/s"), "/a");
77
+ assert.equal(resolveRepoDir({ cPath: undefined, cdPath: undefined }, "/s"), "/s");
78
+ assert.equal(resolveRepoDir({ cPath: "wt", cdPath: "/a" }, "/s"), "/a/wt");
79
+ });
80
+
81
+ test("findMarkerFile: line-1 hit found, quoted-in-body miss, missing dir -> undefined", () => {
82
+ const files: Record<string, string> = {
83
+ "/r/doc/specs/a.md": CONTEXT_DRAFT_MARKER + "\n\nbody",
84
+ "/r/doc/specs/b.md": "# Real spec\n\n`" + CONTEXT_DRAFT_MARKER + "` quoted in body",
85
+ };
86
+ const listFiles = (dir: string) =>
87
+ dir === "/r/doc/specs" ? Object.keys(files) : [];
88
+ const readFirstLine = (f: string) => files[f]?.split("\n", 1)[0];
89
+ assert.equal(findMarkerFile("/r", ["doc/specs"], listFiles, readFirstLine), "/r/doc/specs/a.md");
90
+ delete files["/r/doc/specs/a.md"];
91
+ assert.equal(
92
+ findMarkerFile("/r", ["doc/specs"], (d) => (d === "/r/doc/specs" ? Object.keys(files) : []), readFirstLine),
93
+ undefined,
94
+ );
95
+ assert.equal(findMarkerFile("/r", ["nope"], () => [], readFirstLine), undefined);
96
+ });
97
+
98
+ test("markerBlockReason names the file and the enforce escape hatch", () => {
99
+ const r = markerBlockReason("/r/doc/specs/a.md");
100
+ assert.match(r, /\/r\/doc\/specs\/a\.md/);
101
+ assert.match(r, /flowGuards\.enforce/);
102
+ });
103
+
104
+ test("transitionPhaseState: complete/skipped/pending drop any prior substep", () => {
105
+ for (const status of ["complete", "skipped", "pending"]) {
106
+ const r = transitionPhaseState(status);
107
+ assert.equal(r.status, status);
108
+ assert.ok(!("substep" in r));
109
+ }
110
+ });
111
+
112
+ test("transitionPhaseState: reason propagates when provided", () => {
113
+ assert.deepEqual(transitionPhaseState("skipped", "why"), { status: "skipped", reason: "why" });
114
+ });
115
+
116
+ test("markerGuardApplies: gated by flowGuards.enforce and brainstorm in_progress", () => {
117
+ assert.equal(markerGuardApplies(false, "in_progress"), false);
118
+ assert.equal(markerGuardApplies(true, "in_progress"), true);
119
+ assert.equal(markerGuardApplies(true, "pending"), false);
120
+ });