pi-gauntlet 4.9.0 → 4.11.0
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 +14 -0
- package/README.md +85 -3
- package/agents/conformance-reviewer.md +1 -1
- package/agents/spec-council-member.md +1 -1
- package/agents/spec-council-synthesizer.md +1 -1
- package/extensions/plan-tracker.test.ts +130 -5
- package/extensions/plan-tracker.ts +21 -9
- package/package.json +1 -1
- package/skills/brainstorming/SKILL.md +3 -3
- package/skills/brainstorming/gatherer.md +1 -1
- package/skills/brainstorming/reference/documentation-impact.md +3 -2
- package/skills/dispatching-parallel-agents/SKILL.md +1 -1
- package/skills/finishing-a-development-branch/SKILL.md +1 -1
- package/skills/finishing-a-development-branch/reference/disposition-protocol.md +1 -1
- package/skills/gatekeep-pr/SKILL.md +299 -0
- package/skills/gatekeep-pr/review-baseline.md +43 -0
- package/skills/gatekeep-pr/verification-brief.md +187 -0
- package/skills/receiving-code-review/SKILL.md +1 -1
- package/skills/requesting-code-review/SKILL.md +1 -1
- package/skills/roasting-the-spec/SKILL.md +1 -1
- package/skills/shape-ticket/SKILL.md +267 -0
- package/skills/subagent-driven-development/SKILL.md +2 -2
- package/skills/systematic-debugging/SKILL.md +1 -1
- package/skills/test-driven-development/SKILL.md +1 -1
- package/skills/using-git-worktrees/SKILL.md +2 -2
- package/skills/verification-before-completion/SKILL.md +1 -1
- package/skills/verification-before-completion/reference/conformance-check.md +1 -1
- package/skills/writing-plans/SKILL.md +2 -2
- package/skills/writing-skills/SKILL.md +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v4.11.0 - 2026-08-18
|
|
4
|
+
|
|
5
|
+
- New skill: `gatekeep-pr` - consent-gated pre-merge PR verification: read-only three-role brief, `REVIEW.md` rubric convention with a shipped baseline, deterministic authorship-aware consent menu, thin-wrapper contract.
|
|
6
|
+
- `plan_tracker`: additive `failed` task status (terminal-negative `✗`), excluded from the complete count.
|
|
7
|
+
|
|
8
|
+
## v4.10.0 - 2026-08-18
|
|
9
|
+
|
|
10
|
+
New skill: `shape-ticket` - the tracker-facing entry stage ahead of brainstorming.
|
|
11
|
+
|
|
12
|
+
- `shape-ticket` (user-invoked only, `disable-model-invocation: true`): creates or repairs exactly one tracker issue per run - Context/Problem/Idea/AC template, AC integrity gate (wishful/tautological hard stops, unspecified/external-input parking, deploy-window relocation, discovery-ticket conversion), vertical-only split rule, metadata audit, evidence gate, cheap council roast (`:low` model-suffix over the xhigh pin; worker fallback; failure never blocks), and a single human-gated batched write with post-approval re-fetch. Zero-config on `gh`/`linearis`; other trackers via the overrides `## Issue tracker` section.
|
|
13
|
+
- Overrides discovery ladder: the gauntlet overrides file is now found at `.pi/gauntlet-overrides.md` -> `<repo root>/gauntlet-overrides.md` -> `<repo root>/doc/gauntlet-overrides.md` (first found wins) - migrated across all skills' "Project overrides" blocks and inline references (Claude Code prep for #11).
|
|
14
|
+
- Shared AGENTS core v2: the "Ticket convention" section now points at `/skill:shape-ticket` (roast applied to the ticket body pre-write; no roast comments); propagated to pi-quiver, pi-cohort, pi-condense.
|
|
15
|
+
- `spec-council-member`/`spec-council-synthesizer` gain shape-ticket as a second sanctioned dispatcher (docs).
|
|
16
|
+
|
|
3
17
|
## v4.9.0 - 2026-08-14
|
|
4
18
|
|
|
5
19
|
Review fix rounds parallelize when the reviewer certifies disjoint findings.
|
package/README.md
CHANGED
|
@@ -35,17 +35,20 @@ pi-gauntlet's only hard dependency is pi-cohort - every gate that dispatches a r
|
|
|
35
35
|
|
|
36
36
|
Concretely, one change through the gauntlet:
|
|
37
37
|
|
|
38
|
+
0. *(Optional)* Before there's even a spec, `/skill:shape-ticket` can create or repair a single tracker issue - shaping a raw ask into a Context/Problem/Idea/Acceptance Criteria ticket, gated by an AC integrity check, a cheap council roast, and one human-confirmed write. It's a tool, not a phase: no worktree, no plan/phase tracker, runs from any repo state. It never activates on its own (`disable-model-invocation: true`) - invoke it explicitly.
|
|
38
39
|
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. 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`; event-driven only — gauntlet never sweeps historical specs). **You read and approve the spec - human gate 1.** No implementation code exists yet.
|
|
39
40
|
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
41
|
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
42
|
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. 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 dense list - one line per decision, plain-language, with its recommended choice inline. Reply `1` to take every recommendation, or `2:` with per-item overrides; a current `CONFORMS` / no-concerns result goes straight to the branch options with no extra conformance sign-off.
|
|
42
|
-
5. **`finishing-a-development-branch`**: squash, PR, keep, or discard. **Human gate 2** - the only other decision you make.
|
|
43
|
+
5. **`finishing-a-development-branch`**: squash, PR, keep, or discard. Once a PR exists, run `/skill:gatekeep-pr <pr>` to verify it against its issue before merging. **Human gate 2** - the only other decision you make.
|
|
43
44
|
|
|
44
45
|
Only the machine-owned `plan -> implement` and `verify -> ship` handoffs receive a branch-local one-shot nudge after an unexpected settled stop; it is fire-and-forget, does not bypass either human gate, and older Pi hosts without `agent_settled` retain existing behavior.
|
|
45
46
|
|
|
46
47
|
```mermaid
|
|
47
48
|
flowchart LR
|
|
49
|
+
T["shape-ticket<br/>(optional, explicit)"]
|
|
48
50
|
R([request]) --> B[brainstorm<br/>+ spec]
|
|
51
|
+
T -.-> R
|
|
49
52
|
B --> G1{{human gate 1:<br/>approve spec}}
|
|
50
53
|
G1 --> P[plan]
|
|
51
54
|
P --> I[implement<br/>waves + reviews]
|
|
@@ -64,7 +67,7 @@ Everything between gate 1 and gate 2 - task breakdown, implementation, both revi
|
|
|
64
67
|
|
|
65
68
|
pi-gauntlet ships three kinds of pieces, layered on top of pi-cohort's dispatch:
|
|
66
69
|
|
|
67
|
-
- **
|
|
70
|
+
- **15 skills** - the workflow logic. Thirteen 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`. The fourteenth, `shape-ticket`, is explicit-invocation-only (`disable-model-invocation: true`): create or repair one tracker issue per run against a Context/Problem/Idea/Acceptance-Criteria template, gated by an AC integrity check, a cheap council roast, and a single human-confirmed write. Run it with `/skill:shape-ticket`. The fifteenth, `gatekeep-pr`, is also explicit-invocation-only: consent-gated pre-merge verification of a PR against its issue - read-only gathering, running the project's verification command, a rubric-based review, then a deterministic authorship-aware menu; nothing mutates (fixes, pushes, reviews, merges) until you pick a row. Run it with `/skill:gatekeep-pr <pr>`.
|
|
68
71
|
- **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.
|
|
69
72
|
- **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 push or open a PR without a passing test run since your last edit; a phase-tracker flow guard reminds on implement-phase commits missing spec/code review. See [doc/configuration.md](./doc/configuration.md) for the settings each one reads.
|
|
70
73
|
|
|
@@ -89,7 +92,7 @@ pi-gauntlet is **opinionated**: every non-trivial change is *meant* to ride this
|
|
|
89
92
|
## Requirements
|
|
90
93
|
|
|
91
94
|
- [pi-coding-agent](https://github.com/earendil-works/pi) ≥ 0.1.0
|
|
92
|
-
- [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.
|
|
95
|
+
- [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`, `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.
|
|
93
96
|
|
|
94
97
|
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.
|
|
95
98
|
|
|
@@ -137,6 +140,85 @@ database and copies `.env.local`. Never call `git worktree add` directly.
|
|
|
137
140
|
|
|
138
141
|
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.
|
|
139
142
|
|
|
143
|
+
**Discovery ladder:** skills check three locations, in order, and use the first one found - never merged: `.pi/gauntlet-overrides.md`, then `<repo root>/gauntlet-overrides.md`, then `<repo root>/doc/gauntlet-overrides.md` (`<repo root>` = `git rev-parse --show-toplevel`, or the current directory outside a repo). Pick one location per repo.
|
|
144
|
+
|
|
145
|
+
**`## Issue tracker` section:** `shape-ticket` resolves tracker access through a capability ladder, and this is its first rung - it overrides the zero-config `gh` (GitHub) / `linearis` (Linear) defaults for any other tracker. Name the CLI's read, search, create, and update commands explicitly. For a Jira CLI, for example:
|
|
146
|
+
|
|
147
|
+
```markdown
|
|
148
|
+
## Issue tracker
|
|
149
|
+
|
|
150
|
+
Use the `jira` CLI (authenticated via `jira login`), not `gh` or `linearis`.
|
|
151
|
+
|
|
152
|
+
- read (full, incl. comments): `jira issue view ABC-123 --comments`
|
|
153
|
+
- search (dup/reversal check): `jira issue search --jql "project = ABC AND text ~ '<query>'"`
|
|
154
|
+
- create: `jira issue create --project ABC --type Task --summary "<title>" --description "<body>"`
|
|
155
|
+
- update: `jira issue edit ABC-123 --summary "<title>" --description "<body>"`
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## REVIEW.md convention
|
|
159
|
+
|
|
160
|
+
`/skill:gatekeep-pr` (the pre-merge gate) reads an optional root-level `REVIEW.md` -
|
|
161
|
+
discovered at the repo root only, read from the PR's base (never the PR's own head,
|
|
162
|
+
so a PR can't weaken the rubric that gates it). It's a plain data file, not agent
|
|
163
|
+
instructions: a rubric other tooling can read too. The skill is fully functional with
|
|
164
|
+
no `REVIEW.md` present - it falls back to the shipped baseline rubric
|
|
165
|
+
(`skills/gatekeep-pr/review-baseline.md`).
|
|
166
|
+
|
|
167
|
+
**Overlay precedence**, first match wins on any conflict:
|
|
168
|
+
|
|
169
|
+
1. Repo root `REVIEW.md` - always wins over everything below it.
|
|
170
|
+
2. Shipped `skills/gatekeep-pr/review-baseline.md` - the generic default rubric.
|
|
171
|
+
3. Reviewer-persona defaults.
|
|
172
|
+
|
|
173
|
+
A `REVIEW.md` entry that names a baseline concern (e.g. a severity mapping) replaces
|
|
174
|
+
it; everything it doesn't name stays baseline. Severities it introduces but doesn't
|
|
175
|
+
map to blocking/non-blocking are treated as **blocking** (fail-safe), noted in the
|
|
176
|
+
gate's output.
|
|
177
|
+
|
|
178
|
+
`REVIEW.md` is a diff over the baseline, not a full rewrite. Starter template:
|
|
179
|
+
|
|
180
|
+
```markdown
|
|
181
|
+
# REVIEW.md
|
|
182
|
+
|
|
183
|
+
Severity mapping: Critical and Moderate findings block merge; Minor is a
|
|
184
|
+
non-blocking follow-up. Migration-safety findings also block merge.
|
|
185
|
+
|
|
186
|
+
Project checks (in addition to the baseline):
|
|
187
|
+
- Schema migrations are additive and reversible - no destructive column drops
|
|
188
|
+
without a documented backfill/rollback plan.
|
|
189
|
+
- New background jobs declare an explicit retry/backoff policy - unbounded
|
|
190
|
+
retries block merge.
|
|
191
|
+
|
|
192
|
+
Everything else follows the shipped baseline rubric.
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Thin-wrapper contract
|
|
196
|
+
|
|
197
|
+
A consumer repo that wants its own trigger phrases for the pre-merge gate (e.g. "gate
|
|
198
|
+
this PR", "ready to merge?") adds a wrapper skill that carries **zero data** - only a
|
|
199
|
+
name, its trigger phrases, and an instruction to follow `/skill:gatekeep-pr`. All
|
|
200
|
+
customization lives in two places, never in the wrapper itself:
|
|
201
|
+
|
|
202
|
+
- **`REVIEW.md`** - the review rubric (see above).
|
|
203
|
+
- **The gauntlet overrides file, `## PR gate` section** - everything operational:
|
|
204
|
+
|
|
205
|
+
```markdown
|
|
206
|
+
## PR gate
|
|
207
|
+
- verification command: <command> # required unless documented elsewhere
|
|
208
|
+
- timeout minutes: 15 # optional; default 15
|
|
209
|
+
- requires credentials: false # optional; true => skill reports "not run" as missing evidence
|
|
210
|
+
- worktree wrapper: <command> # optional
|
|
211
|
+
- issue fetch: <command with <ref> placeholder> # optional, replaces gh issue view
|
|
212
|
+
- merge policy: squash | merge-commit # optional
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
An existing `## verification-before-completion` overrides section is an accepted
|
|
216
|
+
equivalent source for the verification command only; all other PR-gate keys
|
|
217
|
+
still live under `## PR gate`.
|
|
218
|
+
|
|
219
|
+
Anything a wrapper skill contains beyond trigger phrases is misplaced - move it to
|
|
220
|
+
`REVIEW.md` or the overrides file instead.
|
|
221
|
+
|
|
140
222
|
## Configuring the gates
|
|
141
223
|
|
|
142
224
|
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.
|
|
@@ -26,7 +26,7 @@ Work flows `origin (prompt + spec) → plan → code/doc`. Every hop is lossy: a
|
|
|
26
26
|
|---|---|---|
|
|
27
27
|
| 1 | The written spec (`doc/specs/…`) | Canonical. Brainstorm already fetched the ticket, reconciled its ACs, and recorded deviations here. |
|
|
28
28
|
| 2 | Original prompt (verbatim) | Catches inline requirements never folded into the spec. |
|
|
29
|
-
| 3 | Re-fetch the ticket | **Fallback only**, when no spec exists. Skip when a spec exists — the live ticket may have drifted. The project's issue-tracker skill (for this fallback) is named in
|
|
29
|
+
| 3 | Re-fetch the ticket | **Fallback only**, when no spec exists. Skip when a spec exists — the live ticket may have drifted. The project's issue-tracker skill (for this fallback) is named in the gauntlet overrides file (see Project overrides in the dispatching skill). |
|
|
30
30
|
|
|
31
31
|
## Process
|
|
32
32
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: spec-council-member
|
|
3
|
-
description: Adversarial single-model spec critic dispatched by the roasting-the-spec
|
|
3
|
+
description: Adversarial single-model spec critic dispatched by the roasting-the-spec or shape-ticket skills; assesses whether a spec is sound, complete, and actionable. Not for direct dispatch.
|
|
4
4
|
tools: read, grep, find, ls, bash
|
|
5
5
|
thinking: xhigh
|
|
6
6
|
defaultContext: fresh
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: spec-council-synthesizer
|
|
3
|
-
description: Neutral chair that consolidates and adjudicates spec-council member critiques into a single ranked, conflict-resolved report. Dispatched by the roasting-the-spec
|
|
3
|
+
description: Neutral chair that consolidates and adjudicates spec-council member critiques into a single ranked, conflict-resolved report. Dispatched by the roasting-the-spec or shape-ticket skills; not for direct dispatch.
|
|
4
4
|
tools: read, grep, find, ls, bash
|
|
5
5
|
thinking: xhigh
|
|
6
6
|
defaultContext: fresh
|
|
@@ -8,18 +8,38 @@ type ToolResult = {
|
|
|
8
8
|
};
|
|
9
9
|
|
|
10
10
|
function harness(branch: unknown[] = []) {
|
|
11
|
-
const tools: {
|
|
11
|
+
const tools: {
|
|
12
|
+
name: string;
|
|
13
|
+
execute: (...args: any[]) => unknown;
|
|
14
|
+
renderResult: (result: unknown, options: unknown, theme: unknown) => { text?: string };
|
|
15
|
+
}[] = [];
|
|
16
|
+
const handlers: { event: string; handler: (event: unknown, ctx: unknown) => Promise<void> }[] = [];
|
|
12
17
|
const pi = {
|
|
13
|
-
on(
|
|
14
|
-
|
|
18
|
+
on(event: string, handler: (event: unknown, ctx: unknown) => Promise<void>) {
|
|
19
|
+
handlers.push({ event, handler });
|
|
20
|
+
},
|
|
21
|
+
registerTool(tool: any) {
|
|
15
22
|
tools.push(tool);
|
|
16
23
|
},
|
|
17
24
|
};
|
|
18
25
|
registerPlanTracker(pi as any);
|
|
19
|
-
|
|
26
|
+
let widgetText: string | undefined;
|
|
27
|
+
const theme = { fg: (_c: string, s: string) => s, bold: (s: string) => s };
|
|
28
|
+
const ctx = {
|
|
29
|
+
hasUI: true,
|
|
30
|
+
ui: {
|
|
31
|
+
setWidget(_id: string, cb?: (tui: unknown, theme: unknown) => { text: string }) {
|
|
32
|
+
widgetText = cb ? cb(undefined, theme).text : undefined;
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
sessionManager: { getBranch: () => branch },
|
|
36
|
+
};
|
|
20
37
|
const call = async (params: Record<string, unknown>): Promise<ToolResult> =>
|
|
21
38
|
(await tools[0].execute("id", params, undefined, undefined, ctx)) as ToolResult;
|
|
22
|
-
|
|
39
|
+
const fire = async (event: string) => {
|
|
40
|
+
for (const h of handlers) if (h.event === event) await h.handler({}, ctx);
|
|
41
|
+
};
|
|
42
|
+
return { call, fire, tool: () => tools[0], theme, widget: () => widgetText };
|
|
23
43
|
}
|
|
24
44
|
|
|
25
45
|
test("add appends pending tasks and preserves existing statuses", async () => {
|
|
@@ -64,3 +84,108 @@ test("add with empty/missing tasks errors and preserves state", async () => {
|
|
|
64
84
|
const res2 = await call({ action: "add" });
|
|
65
85
|
assert.equal(res2.details.error, "tasks required");
|
|
66
86
|
});
|
|
87
|
+
|
|
88
|
+
test("update to failed round-trips and is excluded from complete count", async () => {
|
|
89
|
+
const { call } = harness();
|
|
90
|
+
await call({ action: "init", tasks: ["a", "b", "c"] });
|
|
91
|
+
await call({ action: "update", index: 0, status: "complete" });
|
|
92
|
+
const res = await call({ action: "update", index: 1, status: "failed" });
|
|
93
|
+
assert.equal(res.details.error, undefined);
|
|
94
|
+
assert.deepEqual(
|
|
95
|
+
res.details.tasks.map((t) => [t.name, t.status]),
|
|
96
|
+
[["a", "complete"], ["b", "failed"], ["c", "pending"]],
|
|
97
|
+
);
|
|
98
|
+
assert.match(res.content[0].text, /1\/3 complete/);
|
|
99
|
+
assert.match(res.content[0].text, /1 failed/);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("reconstruction preserves failed status from serialized details", async () => {
|
|
103
|
+
const branch = [
|
|
104
|
+
{
|
|
105
|
+
type: "message",
|
|
106
|
+
message: {
|
|
107
|
+
role: "toolResult",
|
|
108
|
+
toolName: "plan_tracker",
|
|
109
|
+
details: {
|
|
110
|
+
action: "update",
|
|
111
|
+
tasks: [
|
|
112
|
+
{ name: "a", status: "complete" },
|
|
113
|
+
{ name: "b", status: "failed" },
|
|
114
|
+
],
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
];
|
|
119
|
+
const { call, fire } = harness(branch);
|
|
120
|
+
await fire("session_start");
|
|
121
|
+
const res = await call({ action: "status" });
|
|
122
|
+
assert.deepEqual(
|
|
123
|
+
res.details.tasks.map((t) => [t.name, t.status]),
|
|
124
|
+
[["a", "complete"], ["b", "failed"]],
|
|
125
|
+
);
|
|
126
|
+
assert.match(res.content[0].text, /\u2717 \[1\] b/);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test("widget renders failed as \u2717, keeps failed out of complete count and current", async () => {
|
|
130
|
+
const { call, widget } = harness();
|
|
131
|
+
await call({ action: "init", tasks: ["a", "b"] });
|
|
132
|
+
await call({ action: "update", index: 0, status: "failed" });
|
|
133
|
+
const w = widget();
|
|
134
|
+
assert.ok(w);
|
|
135
|
+
assert.match(w!, /\u2717/);
|
|
136
|
+
assert.match(w!, /\(0\/2\)/);
|
|
137
|
+
assert.match(w!, /b$/); // current = first pending, never the failed task
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test("renderResult status path shows \u2717 for failed and excludes it from complete", async () => {
|
|
141
|
+
const { call, tool, theme } = harness();
|
|
142
|
+
await call({ action: "init", tasks: ["a", "b"] });
|
|
143
|
+
await call({ action: "update", index: 1, status: "failed" });
|
|
144
|
+
const res = await call({ action: "status" });
|
|
145
|
+
const rendered = tool().renderResult(res as any, {}, theme as any);
|
|
146
|
+
const text = (rendered as any).text as string;
|
|
147
|
+
assert.match(text, /0\/2 complete/);
|
|
148
|
+
assert.match(text, /\u2717/);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test("renderResult status header appends failed count when a task has failed", async () => {
|
|
152
|
+
const { call, tool, theme } = harness();
|
|
153
|
+
await call({ action: "init", tasks: ["a", "b", "c"] });
|
|
154
|
+
await call({ action: "update", index: 0, status: "complete" });
|
|
155
|
+
await call({ action: "update", index: 1, status: "failed" });
|
|
156
|
+
const res = await call({ action: "status" });
|
|
157
|
+
const rendered = tool().renderResult(res as any, {}, theme as any);
|
|
158
|
+
const text = (rendered as any).text as string;
|
|
159
|
+
assert.match(text, /1\/3 complete, 1 failed/);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test("renderResult status header omits failed count when no task has failed", async () => {
|
|
163
|
+
const { call, tool, theme } = harness();
|
|
164
|
+
await call({ action: "init", tasks: ["a", "b"] });
|
|
165
|
+
await call({ action: "update", index: 0, status: "complete" });
|
|
166
|
+
const res = await call({ action: "status" });
|
|
167
|
+
const rendered = tool().renderResult(res as any, {}, theme as any);
|
|
168
|
+
const text = (rendered as any).text as string;
|
|
169
|
+
assert.match(text, /^1\/2 complete\n/);
|
|
170
|
+
assert.doesNotMatch(text, /failed/);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
test("renderResult update case appends failed count when a task has failed", async () => {
|
|
174
|
+
const { call, tool, theme } = harness();
|
|
175
|
+
await call({ action: "init", tasks: ["a", "b", "c"] });
|
|
176
|
+
await call({ action: "update", index: 0, status: "complete" });
|
|
177
|
+
const res = await call({ action: "update", index: 1, status: "failed" });
|
|
178
|
+
const rendered = tool().renderResult(res as any, {}, theme as any);
|
|
179
|
+
const text = (rendered as any).text as string;
|
|
180
|
+
assert.match(text, /^\u2713 Updated \(1\/3 complete, 1 failed\)$/);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
test("renderResult update case omits failed count when no task has failed", async () => {
|
|
184
|
+
const { call, tool, theme } = harness();
|
|
185
|
+
await call({ action: "init", tasks: ["a", "b"] });
|
|
186
|
+
const res = await call({ action: "update", index: 0, status: "complete" });
|
|
187
|
+
const rendered = tool().renderResult(res as any, {}, theme as any);
|
|
188
|
+
const text = (rendered as any).text as string;
|
|
189
|
+
assert.match(text, /^\u2713 Updated \(1\/2 complete\)$/);
|
|
190
|
+
assert.doesNotMatch(text, /failed/);
|
|
191
|
+
});
|
|
@@ -11,7 +11,7 @@ import type { ExtensionAPI, ExtensionContext, Theme } from "@earendil-works/pi-c
|
|
|
11
11
|
import { Text } from "@earendil-works/pi-tui";
|
|
12
12
|
import { type Static, Type } from "@sinclair/typebox";
|
|
13
13
|
|
|
14
|
-
type TaskStatus = "pending" | "in_progress" | "complete";
|
|
14
|
+
type TaskStatus = "pending" | "in_progress" | "complete" | "failed";
|
|
15
15
|
|
|
16
16
|
interface Task {
|
|
17
17
|
name: string;
|
|
@@ -40,8 +40,8 @@ const PlanTrackerParams = Type.Object({
|
|
|
40
40
|
}),
|
|
41
41
|
),
|
|
42
42
|
status: Type.Optional(
|
|
43
|
-
StringEnum(["pending", "in_progress", "complete"] as const, {
|
|
44
|
-
description: "New status (for update)",
|
|
43
|
+
StringEnum(["pending", "in_progress", "complete", "failed"] as const, {
|
|
44
|
+
description: "New status (for update); failed is terminal-negative (ran and did not pass)",
|
|
45
45
|
}),
|
|
46
46
|
),
|
|
47
47
|
});
|
|
@@ -59,6 +59,8 @@ function formatWidget(tasks: Task[], theme: Theme): string {
|
|
|
59
59
|
return theme.fg("success", "✓");
|
|
60
60
|
case "in_progress":
|
|
61
61
|
return theme.fg("warning", "→");
|
|
62
|
+
case "failed":
|
|
63
|
+
return theme.fg("error", "✗");
|
|
62
64
|
default:
|
|
63
65
|
return theme.fg("dim", "○");
|
|
64
66
|
}
|
|
@@ -78,13 +80,16 @@ function formatStatus(tasks: Task[]): string {
|
|
|
78
80
|
const complete = tasks.filter((t) => t.status === "complete").length;
|
|
79
81
|
const inProgress = tasks.filter((t) => t.status === "in_progress").length;
|
|
80
82
|
const pending = tasks.filter((t) => t.status === "pending").length;
|
|
83
|
+
const failed = tasks.filter((t) => t.status === "failed").length;
|
|
81
84
|
|
|
82
85
|
const lines: string[] = [];
|
|
83
|
-
lines.push(
|
|
86
|
+
lines.push(
|
|
87
|
+
`Plan: ${complete}/${tasks.length} complete (${inProgress} in progress, ${pending} pending, ${failed} failed)`,
|
|
88
|
+
);
|
|
84
89
|
lines.push("");
|
|
85
90
|
for (let i = 0; i < tasks.length; i++) {
|
|
86
91
|
const t = tasks[i];
|
|
87
|
-
const icon = t.status === "complete" ? "✓" : t.status === "in_progress" ? "→" : "○";
|
|
92
|
+
const icon = t.status === "complete" ? "✓" : t.status === "in_progress" ? "→" : t.status === "failed" ? "✗" : "○";
|
|
88
93
|
lines.push(` ${icon} [${i}] ${t.name}`);
|
|
89
94
|
}
|
|
90
95
|
return lines.join("\n");
|
|
@@ -129,7 +134,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
129
134
|
name: "plan_tracker",
|
|
130
135
|
label: "Plan Tracker",
|
|
131
136
|
description:
|
|
132
|
-
"Track progress while EXECUTING an implementation plan (the implement phase)
|
|
137
|
+
"Track progress while EXECUTING an implementation plan (the implement phase), a verify-phase conformance fix wave, or another bounded gate checklist (e.g. pre-merge PR verification). Statuses: pending, in_progress, complete, failed (terminal-negative: the task ran and did not pass; never counted complete). Actions: init (set task list), add (append tasks as pending; existing statuses preserved), update (change task status), status (show current state), clear (remove plan). Do NOT use for brainstorming, research, or planning checklists: those phases are open-ended and a bounded task list misrepresents them as a fixed N-step process.",
|
|
133
138
|
parameters: PlanTrackerParams,
|
|
134
139
|
|
|
135
140
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
@@ -305,8 +310,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
305
310
|
);
|
|
306
311
|
case "update": {
|
|
307
312
|
const complete = taskList.filter((t) => t.status === "complete").length;
|
|
313
|
+
const failed = taskList.filter((t) => t.status === "failed").length;
|
|
314
|
+
const suffix = failed > 0 ? `, ${failed} failed` : "";
|
|
308
315
|
return new Text(
|
|
309
|
-
theme.fg("success", "✓ ") +
|
|
316
|
+
theme.fg("success", "✓ ") +
|
|
317
|
+
theme.fg("muted", `Updated (${complete}/${taskList.length} complete${suffix})`),
|
|
310
318
|
0,
|
|
311
319
|
0,
|
|
312
320
|
);
|
|
@@ -316,14 +324,18 @@ export default function (pi: ExtensionAPI) {
|
|
|
316
324
|
return new Text(theme.fg("dim", "No plan active"), 0, 0);
|
|
317
325
|
}
|
|
318
326
|
const complete = taskList.filter((t) => t.status === "complete").length;
|
|
319
|
-
|
|
327
|
+
const failed = taskList.filter((t) => t.status === "failed").length;
|
|
328
|
+
const suffix = failed > 0 ? `, ${failed} failed` : "";
|
|
329
|
+
let text = theme.fg("muted", `${complete}/${taskList.length} complete${suffix}`);
|
|
320
330
|
for (const t of taskList) {
|
|
321
331
|
const icon =
|
|
322
332
|
t.status === "complete"
|
|
323
333
|
? theme.fg("success", "✓")
|
|
324
334
|
: t.status === "in_progress"
|
|
325
335
|
? theme.fg("warning", "→")
|
|
326
|
-
:
|
|
336
|
+
: t.status === "failed"
|
|
337
|
+
? theme.fg("error", "✗")
|
|
338
|
+
: theme.fg("dim", "○");
|
|
327
339
|
text += `\n${icon} ${theme.fg("muted", t.name)}`;
|
|
328
340
|
}
|
|
329
341
|
return new Text(text, 0, 0);
|
package/package.json
CHANGED
|
@@ -176,7 +176,7 @@ Cover at minimum:
|
|
|
176
176
|
- Derived / memory docs invalidated: <routers / AGENTS.md sections / topic guides / indexes, or "none">
|
|
177
177
|
```
|
|
178
178
|
|
|
179
|
-
Each entry answers with a doc name, "none", or "deferred: <trigger>". A new standalone `.md` appears only where no existing doc already owns the topic. Project-specific doc taxonomy goes in a `## documentation` block in
|
|
179
|
+
Each entry answers with a doc name, "none", or "deferred: <trigger>". A new standalone `.md` appears only where no existing doc already owns the topic. Project-specific doc taxonomy goes in a `## documentation` block in the gauntlet overrides file (see Project overrides) (no new settings key; guidance only). Doc updates ship in the same commit and are verified against the spec by the conformance gate.
|
|
180
180
|
|
|
181
181
|
Be ready to go back and clarify when something doesn't make sense.
|
|
182
182
|
|
|
@@ -240,7 +240,7 @@ When the new spec replaces a prior spec — fully or in part — and you **alrea
|
|
|
240
240
|
- **Mark, never delete.** Delete/archive policy is consumer territory via overrides.
|
|
241
241
|
- **Coverage limits**: unmarked does NOT mean current (code drift, abandoned designs, and partial ships produce no successor spec); marked does NOT mean dead (partial supersession leaves live sections).
|
|
242
242
|
- Predecessor in a **different service's spec directory**: out of scope — record it in the new spec's Open Questions instead of editing outside the write grant.
|
|
243
|
-
- **Override contract**:
|
|
243
|
+
- **Override contract**: the gauntlet overrides file (see Project overrides) may replace the banner *syntax*; placement, append-only, no-transitive-rewrite, and mark-never-delete stay fixed. A syntax override entry must itself state the scout-citation guidance for its format (the shipped `gatherer.md` guidance names only the default banner).
|
|
244
244
|
|
|
245
245
|
## Spec Self-Review (Before User Review Gate)
|
|
246
246
|
|
|
@@ -384,4 +384,4 @@ phase_tracker({ action: "complete", phase: "brainstorm" })
|
|
|
384
384
|
|
|
385
385
|
## Project overrides
|
|
386
386
|
|
|
387
|
-
If `.pi/gauntlet-overrides.md
|
|
387
|
+
If a gauntlet overrides file exists - checked in order: `.pi/gauntlet-overrides.md`, `<repo root>/gauntlet-overrides.md`, `<repo root>/doc/gauntlet-overrides.md`; first found wins - read it. Any sections relevant to this skill — by name match, by topic (routing, verification, worktrees, etc.), or by workflow convention — override or extend the instructions above. Project-local `AGENTS.md` is already in context — check it for project-specific routing tables, service paths, and verification commands.
|
|
@@ -68,7 +68,7 @@ contains any of:
|
|
|
68
68
|
|
|
69
69
|
- an `http(s)://` URL;
|
|
70
70
|
- a tracker-style ID matching `[A-Z][A-Z0-9]+-\d+` (Linear/Jira form) **when a fetch
|
|
71
|
-
path exists** (a tracker tool/MCP, or a URL pattern in
|
|
71
|
+
path exists** (a tracker tool/MCP, or a URL pattern in the gauntlet overrides file, see Project overrides);
|
|
72
72
|
- a GitHub-style ref `owner/repo#N`, or a bare `#N` when the repo's tracker is
|
|
73
73
|
GitHub Issues.
|
|
74
74
|
|
|
@@ -130,5 +130,6 @@ Keep this list in sync with the skills that cite this doc:
|
|
|
130
130
|
## Project-specific taxonomy
|
|
131
131
|
|
|
132
132
|
This doc is generic. Project-specific doc taxonomy (which docs a given
|
|
133
|
-
project treats as canonical for which topic) lives in
|
|
134
|
-
|
|
133
|
+
project treats as canonical for which topic) lives in the gauntlet
|
|
134
|
+
overrides file (see Project overrides in `brainstorming/SKILL.md`), in a
|
|
135
|
+
`## documentation` section - not here.
|
|
@@ -203,4 +203,4 @@ After agents return:
|
|
|
203
203
|
|
|
204
204
|
## Project overrides
|
|
205
205
|
|
|
206
|
-
If `.pi/gauntlet-overrides.md
|
|
206
|
+
If a gauntlet overrides file exists - checked in order: `.pi/gauntlet-overrides.md`, `<repo root>/gauntlet-overrides.md`, `<repo root>/doc/gauntlet-overrides.md`; first found wins - read it. Any sections relevant to this skill — by name match, by topic (routing, verification, worktrees, etc.), or by workflow convention — override or extend the instructions above. Project-local `AGENTS.md` is already in context — check it for project-specific routing tables, service paths, and verification commands.
|
|
@@ -358,4 +358,4 @@ phase_tracker({ action: "complete", phase: "ship" })
|
|
|
358
358
|
|
|
359
359
|
## Project overrides
|
|
360
360
|
|
|
361
|
-
If `.pi/gauntlet-overrides.md
|
|
361
|
+
If a gauntlet overrides file exists - checked in order: `.pi/gauntlet-overrides.md`, `<repo root>/gauntlet-overrides.md`, `<repo root>/doc/gauntlet-overrides.md`; first found wins - read it. Any sections relevant to this skill — by name match, by topic (routing, verification, worktrees, etc.), or by workflow convention — override or extend the instructions above. Project-local `AGENTS.md` is already in context — check it for project-specific routing tables, service paths, and verification commands.
|
|
@@ -38,7 +38,7 @@ Take **no** disposition action before the reply. Then, once, in order:
|
|
|
38
38
|
3. **Re-audit if step 2 changed the spec**; regenerate the inventory and re-render if it changed. Project `fix-now` only from the refreshed inventory.
|
|
39
39
|
4. **fix-now + code-changing custom:** project the selected concerns per gap into the reference's concern-scoped fix contract (excluding accepted/rescoped/followed-up siblings); run the reference "Fix loop" (unchanged - do not re-describe it). A code-changing `custom` runs the project's tests + `code-reviewer` on its delta before proceeding. Re-run Step 1's canonical tests.
|
|
40
40
|
5. **Re-audit after all state-changing work;** obtain fresh decisions **only if** the refreshed inventory differs from the approved one, else proceed.
|
|
41
|
-
6. **follow-up** from the current inventory: create the item via the project's issue-tracker convention (
|
|
41
|
+
6. **follow-up** from the current inventory: create the item via the project's issue-tracker convention (the gauntlet overrides file, see Project overrides in `finishing-a-development-branch/SKILL.md`), record the ticket ID/URL; on failure keep the concern open.
|
|
42
42
|
7. **Non-state-changing custom:** execute and record the result.
|
|
43
43
|
8. **revert** (`revert conformance fix Gn`): light-revert the indexed commit, re-run Step 1's canonical tests; on failure stop; on pass re-audit and regenerate.
|
|
44
44
|
9. Re-enter Step 3.5 with the re-audited block if any concern remains open.
|