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 +66 -0
- package/README.md +71 -211
- package/agents/conformance-reviewer.md +4 -4
- package/extensions/lib/phase-tracker-helpers.test.ts +120 -0
- package/extensions/lib/phase-tracker-helpers.ts +97 -0
- package/extensions/phase-tracker.ts +103 -16
- package/extensions/plan-tracker.ts +1 -1
- package/package.json +1 -1
- package/skills/brainstorming/SKILL.md +72 -25
- package/skills/brainstorming/gatherer.md +117 -0
- package/skills/finishing-a-development-branch/SKILL.md +46 -9
- package/skills/roasting-the-spec/SKILL.md +25 -16
- package/skills/subagent-driven-development/SKILL.md +4 -4
- package/skills/verification-before-completion/SKILL.md +1 -1
- package/skills/verification-before-completion/reference/conformance-check.md +131 -118
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
|
[](https://buymeacoffee.com/jjurasszek)
|
|
8
8
|
|
|
9
|
-
|
|
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
|
-
##
|
|
11
|
+
## The problem
|
|
12
12
|
|
|
13
|
-
|
|
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
|
-
|
|
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
|
-
|
|
17
|
+
## Why pi-gauntlet exists
|
|
18
18
|
|
|
19
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
23
|
+
## Part of the pi agent toolkit
|
|
24
24
|
|
|
25
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
61
|
+
## Architecture
|
|
40
62
|
|
|
41
|
-
|
|
63
|
+
pi-gauntlet ships three kinds of pieces, layered on top of pi-cohort's dispatch:
|
|
42
64
|
|
|
43
|
-
|
|
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
|
-
**
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
**
|
|
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
|
-
-
|
|
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
|
|
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
|
|
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
|
|
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`.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
138
|
+
## Configuring the gates
|
|
228
139
|
|
|
229
|
-
|
|
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
|
-
|
|
142
|
+
## Relationship to the other repos
|
|
232
143
|
|
|
233
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
-
|
|
126
|
-
|
|
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
|
|
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
|
+
});
|