@pareto4/pp-core 1.20.0 → 1.21.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/.claude-plugin/plugin.json +1 -1
- package/agents/pp-nyquist-auditor.md +19 -14
- package/agents/pp-plan-checker.md +59 -36
- package/agents/pp-planner.md +34 -53
- package/agents/pp-verifier.md +3 -52
- package/commands/pp-autonomous.md +2 -1
- package/commands/pp-plan-phase.md +2 -1
- package/commands/pp-plan-review-convergence.md +2 -1
- package/gemini-extension.json +1 -1
- package/package.json +1 -1
- package/pp-core/bin/lib/capability-activation.cjs +77 -0
- package/pp-core/bin/lib/capability-registry.cjs +14 -14
- package/pp-core/bin/lib/capability-state.cjs +8 -18
- package/pp-core/bin/lib/ceremony.cjs +1342 -0
- package/pp-core/bin/lib/check-command-router.cjs +62 -1
- package/pp-core/bin/lib/commands.cjs +29 -3
- package/pp-core/bin/lib/config.cjs +5 -0
- package/pp-core/bin/lib/init.cjs +50 -1
- package/pp-core/bin/lib/io.cjs +2 -0
- package/pp-core/bin/lib/loop-resolver.cjs +26 -12
- package/pp-core/bin/lib/phase.cjs +50 -8
- package/pp-core/bin/lib/plan-sizing.cjs +346 -0
- package/pp-core/bin/lib/state.cjs +1 -0
- package/pp-core/bin/lib/test-substance.cjs +3190 -0
- package/pp-core/bin/lib/timeline.cjs +605 -0
- package/pp-core/bin/pp-tools.cjs +71 -4
- package/pp-core/bin/shared/ceremony-gates.json +17 -0
- package/pp-core/bin/shared/config-schema.manifest.json +1 -0
- package/pp-core/references/ceremony.md +81 -0
- package/pp-core/references/few-shot-examples/plan-checker.md +8 -8
- package/pp-core/references/phase-timeline.md +107 -0
- package/pp-core/references/planner-antipatterns.md +32 -0
- package/pp-core/references/planner-guidance.md +17 -14
- package/pp-core/references/planner-revision.md +1 -1
- package/pp-core/references/planning-config.md +3 -2
- package/pp-core/references/test-substance.md +324 -0
- package/pp-core/references/verifier-stub-patterns.md +51 -0
- package/pp-core/templates/VALIDATION.md +1 -0
- package/pp-core/templates/phase-prompt.md +19 -7
- package/pp-core/templates/roadmap.md +1 -1
- package/pp-core/templates/verification-report.md +1 -1
- package/pp-core/workflows/audit-milestone.md +4 -1
- package/pp-core/workflows/autonomous.md +11 -8
- package/pp-core/workflows/code-review.md +21 -9
- package/pp-core/workflows/execute-phase/steps/check-query-gate.md +7 -7
- package/pp-core/workflows/execute-phase/steps/codebase-drift-gate.md +3 -2
- package/pp-core/workflows/execute-phase.md +15 -4
- package/pp-core/workflows/init/steps/client-context-capture.md +1 -1
- package/pp-core/workflows/plan-phase/steps/chunked-planning.md +7 -3
- package/pp-core/workflows/plan-phase/steps/prd-express.md +104 -0
- package/pp-core/workflows/plan-phase.md +47 -114
- package/pp-core/workflows/plan-review-convergence.md +6 -3
- package/pp-core/workflows/profile-user.md +2 -2
- package/pp-core/workflows/quick.md +1 -1
- package/pp-core/workflows/ship.md +3 -3
- package/pp-core/workflows/spec-phase.md +2 -2
- package/pp-core/workflows/stats.md +19 -4
- package/pp-core/workflows/validate-phase.md +19 -4
- package/pp-core/workflows/verify-phase.md +1 -1
- package/pp-core/workflows/verify-work.md +1 -1
- package/scripts/gen-capability-registry.cjs +43 -45
- package/scripts/run-tests.cjs +54 -30
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
# Test substance
|
|
2
|
+
|
|
3
|
+
Extracted from `agents/pp-planner.md` to keep that file inside its size budget.
|
|
4
|
+
**Read by** the planner (task anatomy and `validate_plan`), the plan checker
|
|
5
|
+
(Dimension 8) and the Nyquist auditor (`asserts_nothing` gaps). The rules are
|
|
6
|
+
enforced by `pp_run check test-substance <phase>` (plans) and
|
|
7
|
+
`pp_run check test-substance --scan <file...>` (test files), both in
|
|
8
|
+
`src/test-substance.cts`. A parity test ties every rule constant named below to
|
|
9
|
+
that module, so a rule added in code must be added here.
|
|
10
|
+
|
|
11
|
+
A test that passes proves nothing unless it ran an assertion of the behaviour the
|
|
12
|
+
task is about. Node's test runner exits 0 on an empty test body, on a
|
|
13
|
+
`--test-name-pattern` that matches no test, and on a file with zero tests
|
|
14
|
+
(verified on Node 25.9.0), so a green `<automated>` command alone is not
|
|
15
|
+
evidence. The rules below make the plan say what is asserted and let code reject
|
|
16
|
+
the shapes that cannot assert.
|
|
17
|
+
|
|
18
|
+
## Behaviour statement
|
|
19
|
+
|
|
20
|
+
Every non-checkpoint task has a `<behavior>` element (D-01).
|
|
21
|
+
|
|
22
|
+
- An ordinary task: one line naming what its `<automated>` command asserts.
|
|
23
|
+
- A documentation or configuration task: the state its check reads.
|
|
24
|
+
- A `tdd="true"` task: its list of test cases.
|
|
25
|
+
|
|
26
|
+
A behaviour is an observable outcome the command fails without (D-02). Write it
|
|
27
|
+
as input → expected output, or as condition → result.
|
|
28
|
+
|
|
29
|
+
Good:
|
|
30
|
+
- `ceremony classify` on an empty repository returns tier light
|
|
31
|
+
- `slugify('Hello World')` returns `hello-world`; an empty string returns an empty string
|
|
32
|
+
|
|
33
|
+
Bad:
|
|
34
|
+
- tests pass
|
|
35
|
+
- the login route works
|
|
36
|
+
|
|
37
|
+
Deterministic floor: a statement shorter than `20` characters or with fewer than
|
|
38
|
+
`4` words is `behavior_generic`. A statement that equals, after normalisation, any
|
|
39
|
+
of these phrases is also `behavior_generic` (whole-statement matches, never
|
|
40
|
+
substrings): `tests pass`, `test passes`, `the tests pass`, `all tests pass`,
|
|
41
|
+
`all the tests pass`, `it works`, `works`, `works as expected`,
|
|
42
|
+
`it works as expected`, `everything works`, `file exists`, `the file exists`,
|
|
43
|
+
`files exist`, `is implemented`, `it is implemented`, `implemented`, `done`,
|
|
44
|
+
`passes`, `complete`, `build passes`, `the build passes`.
|
|
45
|
+
Filler words (`all`, `of`, `the`, `now`, `correctly`, ...) are dropped before the
|
|
46
|
+
compare, and up to two other words may lead the phrase, so "All of the tests
|
|
47
|
+
pass now." and "the feature is implemented correctly" match. The list is
|
|
48
|
+
deliberately narrow. A statement that still holds a template placeholder such as
|
|
49
|
+
`[expected behavior]` is `behavior_generic` too.
|
|
50
|
+
|
|
51
|
+
The floor catches filler. It cannot tell whether a statement names what the
|
|
52
|
+
command really asserts; the plan checker judges that.
|
|
53
|
+
|
|
54
|
+
## Scaffold shape
|
|
55
|
+
|
|
56
|
+
When no test exists yet, a scaffold carries the task's real assertion (D-03). It
|
|
57
|
+
is created in one of two places:
|
|
58
|
+
|
|
59
|
+
- by the task itself, only when it is `tdd="true"`: its RED step writes and runs
|
|
60
|
+
the test;
|
|
61
|
+
- by an earlier task of the same plan, which must be test infrastructure or a
|
|
62
|
+
`tdd="true"` RED task. Any other earlier task's own verify would fail on the
|
|
63
|
+
still-failing assertion. The verb accepts any earlier task; the checker judges
|
|
64
|
+
this one.
|
|
65
|
+
|
|
66
|
+
Either way the file exists when the task's verify runs, so `<automated>` is that
|
|
67
|
+
file's real test command. The marker `MISSING — Wave 0 must create {test_file} first`
|
|
68
|
+
is never a final verify: the executor has no MISSING handling and would run it
|
|
69
|
+
as a command. Every marker is a `scaffold_uncreated` finding, in any case and
|
|
70
|
+
whatever wraps it (backticks, quotes, a list dash, `(`, `>`) or follows it
|
|
71
|
+
(`MISSING.`, `MISSING-…`, `|| true` included); its detail names the fix. `marker_not_replaced`: the file is
|
|
72
|
+
created by this task or an earlier one, so write the real command. `later_task`,
|
|
73
|
+
`other_plan:<id>` or `no_creator`: move the scaffold before the task. `no_path`:
|
|
74
|
+
the marker names no file.
|
|
75
|
+
|
|
76
|
+
The same order check covers every real `<automated>` command. The verb reads the
|
|
77
|
+
test files a command runs: a token with a directory and a test-file name, an
|
|
78
|
+
option value such as `--files=…`, or a pytest `path::case`. After a leading
|
|
79
|
+
`cd <dir> &&` the paths are read under `<dir>`. A file already on disk is never a
|
|
80
|
+
finding. A file not on disk must be listed by the task itself, by an earlier task
|
|
81
|
+
of its plan, or by a plan of an earlier wave. Otherwise it is `scaffold_uncreated`:
|
|
82
|
+
|
|
83
|
+
- `later_task`: only a later task of the plan lists it. Move the creating task
|
|
84
|
+
first, or list the file in this task under `tdd="true"`.
|
|
85
|
+
- `other_plan:<id>`: only a plan in the same or a later wave lists it. Create the
|
|
86
|
+
file in this plan, or give its creator an earlier wave if it is test
|
|
87
|
+
infrastructure.
|
|
88
|
+
- `no_creator`: no plan lists it. List the file in the `<files>` of the task that
|
|
89
|
+
writes it.
|
|
90
|
+
|
|
91
|
+
At most `20` test paths per command are read (`MAX_ORDER_PATHS`). The verb skips,
|
|
92
|
+
and the checker reads for:
|
|
93
|
+
|
|
94
|
+
- the whole command for any other `cd` or `pushd`, and these working-directory
|
|
95
|
+
options: `--prefix`, `--cwd`, `--root`, `--dir`, `--chdir`, `--workspace`,
|
|
96
|
+
`-C`, `--rootDir`, `--project`, `--projects`, `--testFile`;
|
|
97
|
+
- the step alone (later steps at the root are still read) for a workspace runner
|
|
98
|
+
or container, also behind `npx`, `pnpm exec` or `yarn dlx`: `nx`, `turbo`,
|
|
99
|
+
`lerna`, `docker`, `docker-compose`, `podman`; for `yarn workspace`, `npm -w`,
|
|
100
|
+
`pnpm -F` and `pnpm --filter`; and for a child shell's `-c` script
|
|
101
|
+
(`bash -c "cd app && ..."`), whose quoted text is never read as steps;
|
|
102
|
+
- a step whose verb names a path without running it, or writes one: `grep`, `rg`,
|
|
103
|
+
`echo`, `printf`, `test`, `[`, `ls`, `cat`, `head`, `tail`, `wc`, `git`, `cmp`,
|
|
104
|
+
`diff`, `stat`, `find`, `tee`, `cp`, `mv`, `touch`;
|
|
105
|
+
- a file the command writes: the word after `>`, `>>` or `2>`, and the value of an
|
|
106
|
+
option whose name holds `destination`, `output` or `junit`
|
|
107
|
+
(`--test-reporter-destination`, `--outputFile`);
|
|
108
|
+
- a glob, a `$` or `~` path, a token with `:`, a bare file name, and a path
|
|
109
|
+
outside the project.
|
|
110
|
+
|
|
111
|
+
A real assertion fails until the code exists, and no wave may merge a failing test
|
|
112
|
+
into the post-merge gate. So the test that carries a task's own assertion is
|
|
113
|
+
created in that task's plan. Cross-wave Wave 0 work is reserved for test
|
|
114
|
+
infrastructure (framework install, config, shared fixtures) that carries no
|
|
115
|
+
behaviour assertion. An earlier-wave plan may also create a test that passes with
|
|
116
|
+
its own code in that wave, which a later task runs. The verb accepts every
|
|
117
|
+
earlier-wave creator, and the plan checker judges which kind it is (Dimension 8,
|
|
118
|
+
check 8d).
|
|
119
|
+
|
|
120
|
+
A scaffold is never vacuous. A scaffold action that describes one of these is
|
|
121
|
+
`scaffold_empty`: `empty_body`, `placeholder`, `todo`, `skip` or `tautology`. A
|
|
122
|
+
tautology is one of these literals (matched after collapsing whitespace):
|
|
123
|
+
`assert.ok(true)`, `assert(true)`, `expect(true).toBe(true)`,
|
|
124
|
+
`expect(true).toBeTruthy()`, `assert.equal(1, 1)`, `assert.strictEqual(1, 1)`,
|
|
125
|
+
`assert True`, `self.assertTrue(True)`, `assert.True(t, true)`.
|
|
126
|
+
The action is read sentence by sentence: a prose form needs a writing verb
|
|
127
|
+
(write, create, add, leave, ...) within four words of it (eight for
|
|
128
|
+
"placeholder test"), so "counts skipped tests" does not block. A placeholder
|
|
129
|
+
phrase governed by a removing verb, with only determiners between ("replaces
|
|
130
|
+
the placeholder test", "in place of the placeholder test", "removing every
|
|
131
|
+
placeholder test"), is the remedy and does not block; "replaces it with a
|
|
132
|
+
placeholder test" still does. A match is skipped when a negation (`never`, `not`,
|
|
133
|
+
`no`, `don't`, `dont`, `without`, `avoid`, `instead of`; not a `no-` compound)
|
|
134
|
+
sits earlier in its own clause or in the three words right before the phrase, so
|
|
135
|
+
"never a placeholder test" and "write no placeholder tests" do not block. A
|
|
136
|
+
new clause starts at "then" or "but", so "does not crash, then add a placeholder
|
|
137
|
+
test" still blocks, while "never a placeholder test, `test.skip` or
|
|
138
|
+
`assert.ok(true)`" negates the whole list. A negation earlier in the same clause
|
|
139
|
+
("does not crash and add a placeholder test") hides the match: a residual the
|
|
140
|
+
checker reads for.
|
|
141
|
+
|
|
142
|
+
## Commands that pass without asserting
|
|
143
|
+
|
|
144
|
+
An `<automated>` command can exit 0 without running an assertion (D-06). The
|
|
145
|
+
verb flags a command whose every segment is one of these (`verify_vacuous`):
|
|
146
|
+
|
|
147
|
+
| Kind | Command shape |
|
|
148
|
+
|------|---------------|
|
|
149
|
+
| `existence_only` | `test -f`, `[ -f ... ]`, `[[ -f ... ]]`, `ls`: the file exists, nothing ran |
|
|
150
|
+
| `grep_test_file` | a `grep` over a test file: the text is there, the test never ran |
|
|
151
|
+
| `or_true` | `... \|\| true`, or an `\|\|` fallback that checks nothing (below): the exit status is discarded |
|
|
152
|
+
| `semicolon_true` | `...; true`, or a last `;`/newline segment that checks nothing: the exit status is discarded |
|
|
153
|
+
| `exit_zero` | `exit 0`, also `exit "0"`, `exit '0'` or `exit 00` |
|
|
154
|
+
| `pass_with_no_tests` | `--passWithNoTests` or `--pass-with-no-tests` |
|
|
155
|
+
| `echo_only` | `echo ...` alone, or `env` / `printenv` with nothing to run |
|
|
156
|
+
| `no_command` | an `<automated>` that is empty or holds only `cd`, `export`, `set`, a `VAR=val` or a comment: nothing runs |
|
|
157
|
+
|
|
158
|
+
Segment rule: a shell comment (an unquoted `#` that starts a word) is dropped to
|
|
159
|
+
the end of its line, then the command is split on `&&`, `;`, `||` and newline; a
|
|
160
|
+
pipeline stays one segment. An existence check or an echo flags the command only when
|
|
161
|
+
every segment is vacuous, so `test -f a.test.cjs && node --test a.test.cjs` is
|
|
162
|
+
not flagged, and an `echo` or `printf` piped into another command is not an
|
|
163
|
+
echo. The discarding forms (`or_true`, `semicolon_true`) flag on their own. A
|
|
164
|
+
segment that checks nothing is an echo, `true`, `:`, `/bin/true`, `! false`, an
|
|
165
|
+
existence check, a grep over a test file, or `cat`, `head`, `tail`, `wc`,
|
|
166
|
+
`pwd`, `date`, `sleep`, `env` with nothing to run, `printenv`, `whoami`, `sort`,
|
|
167
|
+
`uniq`, `tee`, an `awk` without `exit`, a `sed` without a `q` exit code, or a
|
|
168
|
+
`git` `status`, `log`, `show`, `branch`, `add` or `diff` (without `--exit-code`,
|
|
169
|
+
`--quiet` or `--check`). A bare `VAR=val` discards the status as the last step,
|
|
170
|
+
not as an `||` fallback, where `|| rc=1` records the failure. A step is judged
|
|
171
|
+
by the command it runs: a leading `env [VAR=val ...]` or `VAR=val` prefix is
|
|
172
|
+
dropped, and a pipeline checks nothing only when its first
|
|
173
|
+
and last stages both check nothing (a `|` inside quotes or escaped is not a
|
|
174
|
+
stage), so `cat out.txt | grep -q ok` keeps the status. A real
|
|
175
|
+
last step (`node --test b.test.cjs`, `exit $?`) keeps the status. So does a guard
|
|
176
|
+
right after the last real step that exits non-zero when it fails, followed only
|
|
177
|
+
by steps that check nothing: `|| exit 1`, `|| exit $?`, `|| { echo FAIL; exit 1; }`
|
|
178
|
+
or `if [ $? -ne 0 ]; then exit 1; fi`. An `exit` inside a `( ... )` subshell
|
|
179
|
+
leaves only the subshell and does not count. So does a
|
|
180
|
+
`set -e` (or `set -o errexit`) before the first real step, for `;` only, and
|
|
181
|
+
only where errexit stops the script: an `&&` or `||` list, a pipe without
|
|
182
|
+
`pipefail`, a `!`, `if`, `elif`, `while` or `until` condition, a background `&`,
|
|
183
|
+
an `export X=$(...)`, a `set +e`, a `<(...)` process substitution or a child
|
|
184
|
+
shell (`bash -c`, `sh -c`) whose quoted script holds a `;` or newline anywhere in
|
|
185
|
+
the command withdraws it.
|
|
186
|
+
|
|
187
|
+
Named residuals the verb does not detect; the checker reads for them:
|
|
188
|
+
|
|
189
|
+
- a `--test-name-pattern` that matches nothing;
|
|
190
|
+
- a pipe to `tail`, `head`, `wc` or `tee` without `pipefail`, which hides a red;
|
|
191
|
+
- `node --test` on a file with zero tests;
|
|
192
|
+
- a `;true` or `|| true` quoted inside a `node -e "..."` string is flagged
|
|
193
|
+
(the scan is textual), a false positive to rephrase.
|
|
194
|
+
|
|
195
|
+
## The check
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
SUBSTANCE=$(pp_run check test-substance "$PHASE" --raw)
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Report keys: `phase_found`, `enforced`, `block`, `finding_count`, `plans[]` with
|
|
202
|
+
`executed`, `findings` and `tasks[].findings[]` (each with `code` and `detail`),
|
|
203
|
+
and `tdd_candidates_without_tdd`.
|
|
204
|
+
|
|
205
|
+
Finding codes, all blockers (D-07), so they survive the light tier's
|
|
206
|
+
blockers-only round:
|
|
207
|
+
|
|
208
|
+
- `behavior_missing`: a non-checkpoint task has no `<behavior>` or an empty one,
|
|
209
|
+
or the plan has no task element at all (`no_xml_tasks`).
|
|
210
|
+
- `behavior_generic`: the statement fails the floor or matches a denylist phrase.
|
|
211
|
+
- `verify_vacuous`: the `<automated>` command is one of the kinds above.
|
|
212
|
+
- `scaffold_uncreated`: a MISSING marker left as the verify, or a test file a real
|
|
213
|
+
command runs that is not on disk and has no earlier creator (see Scaffold shape).
|
|
214
|
+
- `scaffold_empty`: the scaffold action describes a vacuous test.
|
|
215
|
+
- `over_cap`: a plan or command is over the size cap, so it was not analysed; a
|
|
216
|
+
PLAN.md file over `MAX_PLAN_BYTES` bytes is not read at all.
|
|
217
|
+
- `unreadable`: a plan file that is not a regular file (a symlink, FIFO or directory,
|
|
218
|
+
never read) or could not be read, or a phase directory that could not be read
|
|
219
|
+
(`read_error`), or the phase has no PLAN.md (`no_plans`); never clean.
|
|
220
|
+
|
|
221
|
+
`block` follows `workflow.nyquist_validation` (D-08). When it is `false` the verb
|
|
222
|
+
reports `enforced: false`, lists the findings and sets `block: false`; the
|
|
223
|
+
planner still writes behaviour statements. A plan that already has a SUMMARY is
|
|
224
|
+
reported but not counted. An unknown phase blocks (`phase_found: false`).
|
|
225
|
+
|
|
226
|
+
`tdd_candidates_without_tdd` lists tasks that touch non-test source files but lack
|
|
227
|
+
`tdd="true"`. It is information that never blocks by itself (D-12): the planner
|
|
228
|
+
and the checker judge each against the heuristic in the next section.
|
|
229
|
+
|
|
230
|
+
## TDD by default
|
|
231
|
+
|
|
232
|
+
With `workflow.tdd_mode` off (the default), a task that passes the heuristic gets
|
|
233
|
+
`tdd="true"` and a test-case `<behavior>` inside an ordinary plan (D-10).
|
|
234
|
+
|
|
235
|
+
Heuristic: can you write `expect(fn(input)).toBe(output)` before writing `fn`?
|
|
236
|
+
Candidates are business logic with defined I/O, API endpoints with
|
|
237
|
+
request/response contracts, data transformations, validation rules, algorithms
|
|
238
|
+
and state machines.
|
|
239
|
+
|
|
240
|
+
Dedicated `type: tdd` plans stay `tdd_mode`'s: their ~40% context budget would undo
|
|
241
|
+
the plan-count gains on small phases.
|
|
242
|
+
|
|
243
|
+
Exclusions (D-11): `checkpoint:*` tasks, configuration-only files, documentation,
|
|
244
|
+
migration scripts, glue code wiring already-tested components, styling-only
|
|
245
|
+
changes and test-only tasks.
|
|
246
|
+
|
|
247
|
+
The executor's RED → GREEN → optional REFACTOR flow applies to a default
|
|
248
|
+
`tdd="true"` task unchanged (D-13). The extras stay tied to `tdd_mode`: the
|
|
249
|
+
execute:post `tdd.review-checkpoint` gate, the MVP+TDD runtime gate and the
|
|
250
|
+
plan-level gate sequence. The cost is one extra commit per business-logic task.
|
|
251
|
+
|
|
252
|
+
Task-level example:
|
|
253
|
+
|
|
254
|
+
```xml
|
|
255
|
+
<task type="auto" tdd="true">
|
|
256
|
+
<name>Task: [name]</name>
|
|
257
|
+
<files>src/feature.ts, src/feature.test.ts</files>
|
|
258
|
+
<behavior>
|
|
259
|
+
- Test 1: [expected behavior]
|
|
260
|
+
- Test 2: [edge case]
|
|
261
|
+
</behavior>
|
|
262
|
+
<action>[Implementation after tests pass]</action>
|
|
263
|
+
<verify>
|
|
264
|
+
<automated>npm test -- --filter=feature</automated>
|
|
265
|
+
</verify>
|
|
266
|
+
<done>[Criteria]</done>
|
|
267
|
+
</task>
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
## Assertion-free tests at validation
|
|
271
|
+
|
|
272
|
+
`validate-phase` runs the scanner over the test files it mapped to requirements
|
|
273
|
+
(D-14):
|
|
274
|
+
|
|
275
|
+
```bash
|
|
276
|
+
pp_run check test-substance --scan <file...> --raw
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
Report: `mode`, `files[]` and `totals`. Each case has a state in `asserts`,
|
|
280
|
+
`asserts_nothing`, `todo` or `skip`; each file has a state in `ok`, `no_cases` or
|
|
281
|
+
`unknown`. The scan covers at most 200 files, each under 1 MB, inside the project.
|
|
282
|
+
|
|
283
|
+
Recognised assertion forms (D-16):
|
|
284
|
+
|
|
285
|
+
- JS/TS: `assert(`, `assert.*`, `t.assert`, `expect(`, `.should`, `assert*()`,
|
|
286
|
+
`expect*()`
|
|
287
|
+
- Python: `assert`, `self.assert*`, `pytest.raises`, `assert*()`, `expect*()`
|
|
288
|
+
- Go: `t.Error*`, `t.Fatal*`, `t.Fail*`, `assert.*`, `require.*`, `assert*()`,
|
|
289
|
+
`expect*()`
|
|
290
|
+
|
|
291
|
+
A literal tautology counts as asserting nothing. Any other language reports
|
|
292
|
+
`unknown` and the auditor judges.
|
|
293
|
+
|
|
294
|
+
Named residuals; the auditor reads the case before acting:
|
|
295
|
+
|
|
296
|
+
- a helper whose name does not start with `assert` or `expect` hides its assertion
|
|
297
|
+
(`expected*` and `expectation*` are fixture builders and do not count; a JS
|
|
298
|
+
`expect(x)` counts only with a `.matcher`);
|
|
299
|
+
- forms outside the lists above read as nothing (tape and ava `t.is`, Python
|
|
300
|
+
`self.fail`, ESLint `RuleTester.run`);
|
|
301
|
+
- `test.each` / `test.concurrent` / `test.skip.each` tables, `describe`-only
|
|
302
|
+
files, `test(name, fnReference)`, a named call with a callback on another
|
|
303
|
+
object or member (`Deno.test`, `tap.test`, `test.fixme`), mocha
|
|
304
|
+
`specify` / `context`, testify
|
|
305
|
+
suite methods and a Python `class Test*` with no `def test*` report the file
|
|
306
|
+
`unknown`, even when other cases in it parse (those stay listed). Hooks,
|
|
307
|
+
steps and configuration on `test` (`test.beforeEach`, `test.step`,
|
|
308
|
+
`test.use`), a regex `.test('x')` and a variable named `it` are not cases;
|
|
309
|
+
- a nested subtest credits its container;
|
|
310
|
+
- a conditional `t.Skip` on an asserting Go test, or `t.skip()` / `t.todo()` on
|
|
311
|
+
a JS case's callback parameter, is listed under `skipped` (a node:test
|
|
312
|
+
`{ skip }` / `{ todo }` option counts only with a literal truthy value).
|
|
313
|
+
|
|
314
|
+
Gap handling (D-14 to D-17):
|
|
315
|
+
|
|
316
|
+
- A case with zero assertions leaves its requirement PARTIAL with gap type
|
|
317
|
+
`asserts_nothing`.
|
|
318
|
+
- The auditor reads each case first: an assertion made inside a helper clears it.
|
|
319
|
+
- It fills the gap by adding an assertion of the requirement's behaviour to the
|
|
320
|
+
existing test. It never deletes or weakens the test.
|
|
321
|
+
- An assertion that still fails after 3 debug iterations is an implementation bug
|
|
322
|
+
and is escalated.
|
|
323
|
+
- An assertion-free test is no coverage. `nyquist_compliant: true` requires zero
|
|
324
|
+
`asserts_nothing` gaps; the alternative is manual-only with a reason.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Verifier stub patterns
|
|
2
|
+
|
|
3
|
+
The verifier reads this at Step 7 (anti-pattern scan) to recognise stub red flags.
|
|
4
|
+
|
|
5
|
+
## React Component Stubs
|
|
6
|
+
|
|
7
|
+
```javascript
|
|
8
|
+
// RED FLAGS:
|
|
9
|
+
return <div>Component</div>
|
|
10
|
+
return <div>Placeholder</div>
|
|
11
|
+
return <div>{/* TODO */}</div>
|
|
12
|
+
return null
|
|
13
|
+
return <></>
|
|
14
|
+
|
|
15
|
+
// Empty handlers:
|
|
16
|
+
onClick={() => {}}
|
|
17
|
+
onChange={() => console.log('clicked')}
|
|
18
|
+
onSubmit={(e) => e.preventDefault()} // Only prevents default
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## API Route Stubs
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
// RED FLAGS:
|
|
25
|
+
export async function POST() {
|
|
26
|
+
return Response.json({ message: "Not implemented" });
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function GET() {
|
|
30
|
+
return Response.json([]); // Empty array with no DB query
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Wiring Red Flags
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
// Fetch exists but response ignored:
|
|
38
|
+
fetch('/api/messages') // No await, no .then, no assignment
|
|
39
|
+
|
|
40
|
+
// Query exists but result not returned:
|
|
41
|
+
await prisma.message.findMany()
|
|
42
|
+
return Response.json({ ok: true }) // Returns static, not query result
|
|
43
|
+
|
|
44
|
+
// Handler only prevents default:
|
|
45
|
+
onSubmit={(e) => e.preventDefault()}
|
|
46
|
+
|
|
47
|
+
// State exists but not rendered:
|
|
48
|
+
const [messages, setMessages] = useState([])
|
|
49
|
+
return <div>No messages</div> // Always shows "no messages"
|
|
50
|
+
```
|
|
51
|
+
|
|
@@ -71,6 +71,7 @@ created: {date}
|
|
|
71
71
|
- [ ] Wave 0 covers all MISSING references
|
|
72
72
|
- [ ] No watch-mode flags
|
|
73
73
|
- [ ] Feedback latency < {N}s
|
|
74
|
+
- [ ] No test that asserts nothing (`asserts_nothing` gaps: 0)
|
|
74
75
|
- [ ] `nyquist_compliant: true` set in frontmatter
|
|
75
76
|
|
|
76
77
|
**Approval:** {pending / approved YYYY-MM-DD}
|
|
@@ -22,6 +22,7 @@ files_modified: [] # Files this plan modifies.
|
|
|
22
22
|
autonomous: true # false if plan has checkpoints requiring user interaction
|
|
23
23
|
requirements: [] # REQUIRED — Requirement IDs from ROADMAP this plan addresses. MUST NOT be empty.
|
|
24
24
|
user_setup: [] # Human-required setup Claude cannot automate (see below)
|
|
25
|
+
context_estimate: N # REQUIRED — sum of task estimates in tokens: 90000 or 90k, no ~/units
|
|
25
26
|
|
|
26
27
|
# Goal-backward verification (derived during planning, verified after execution)
|
|
27
28
|
must_haves:
|
|
@@ -63,6 +64,7 @@ Output: [What artifacts will be created]
|
|
|
63
64
|
<task type="auto">
|
|
64
65
|
<name>Task 1: [Action-oriented name]</name>
|
|
65
66
|
<files>path/to/file.ext, another/file.ext</files>
|
|
67
|
+
<behavior>[One line: input → expected output that the verify command asserts. For a tdd="true" task, list its test cases.]</behavior>
|
|
66
68
|
<read_first>path/to/reference.ext, path/to/source-of-truth.ext</read_first>
|
|
67
69
|
<action>[Specific implementation - what to do, how to do it, what to avoid and WHY. Include CONCRETE values: exact identifiers, parameters, expected outputs, file paths, command arguments. Never say "align X with Y" without specifying the exact target state.]</action>
|
|
68
70
|
<verify>[Command or check to prove it worked]</verify>
|
|
@@ -76,6 +78,7 @@ Output: [What artifacts will be created]
|
|
|
76
78
|
<task type="auto">
|
|
77
79
|
<name>Task 2: [Action-oriented name]</name>
|
|
78
80
|
<files>path/to/file.ext</files>
|
|
81
|
+
<behavior>[One line: input → expected output that the verify command asserts]</behavior>
|
|
79
82
|
<read_first>path/to/reference.ext</read_first>
|
|
80
83
|
<action>[Specific implementation with concrete values]</action>
|
|
81
84
|
<verify>[Command or check]</verify>
|
|
@@ -140,6 +143,7 @@ After completion, create `.planning/phases/XX-name/{phase}-{plan}-SUMMARY.md`
|
|
|
140
143
|
| `autonomous` | Yes | `true` if no checkpoints, `false` if has checkpoints |
|
|
141
144
|
| `requirements` | Yes | **MUST** list requirement IDs from ROADMAP. Every roadmap requirement MUST appear in at least one plan. |
|
|
142
145
|
| `user_setup` | No | Array of human-required setup items (external services) |
|
|
146
|
+
| `context_estimate` | No (schema) / always written by the planner | Estimated context cost of the plan in tokens (a bare integer or `90k`) — the sum of its task estimates; must fit the per-plan budget (50% of `context_window`) |
|
|
143
147
|
| `must_haves` | Yes | Goal-backward verification criteria (see below) |
|
|
144
148
|
|
|
145
149
|
**Wave is pre-computed:** Wave numbers are assigned during `/pp:plan-phase`. Execute-phase reads `wave` directly from frontmatter and groups plans by wave number. No runtime dependency analysis needed.
|
|
@@ -246,16 +250,16 @@ Wave 3 runs after Waves 1 and 2. Pauses at checkpoint, orchestrator presents to
|
|
|
246
250
|
|
|
247
251
|
**Plan sizing:**
|
|
248
252
|
|
|
249
|
-
-
|
|
253
|
+
- Size each plan from the estimated context cost of its tasks: up to 6 tasks while the summed estimate (`context_estimate`) stays within ~50% of `context_window`
|
|
250
254
|
- ~50% context usage maximum
|
|
251
|
-
-
|
|
255
|
+
- The task count follows the estimate, never a target
|
|
252
256
|
|
|
253
257
|
**When to split:**
|
|
254
258
|
|
|
255
|
-
-
|
|
256
|
-
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
+
- Parallelism: independent subsystems with disjoint `files_modified` that run in the same wave
|
|
260
|
+
- Budget: merged, the work would exceed ~50% of `context_window` or 6 tasks (a dedicated TDD plan is a budget case)
|
|
261
|
+
|
|
262
|
+
Never split serial single-concern work to fit a task count.
|
|
259
263
|
|
|
260
264
|
**Vertical slices preferred:**
|
|
261
265
|
|
|
@@ -332,6 +336,7 @@ Output: User model, API endpoints, and UI components.
|
|
|
332
336
|
<task type="auto">
|
|
333
337
|
<name>Task 1: Create User model</name>
|
|
334
338
|
<files>src/features/user/model.ts</files>
|
|
339
|
+
<behavior>tsc --noEmit fails when User lacks id, email, name or createdAt, and passes once the interface exports all four</behavior>
|
|
335
340
|
<action>Define User type with id, email, name, createdAt. Export TypeScript interface.</action>
|
|
336
341
|
<verify>tsc --noEmit passes</verify>
|
|
337
342
|
<done>User type exported and usable</done>
|
|
@@ -340,6 +345,7 @@ Output: User model, API endpoints, and UI components.
|
|
|
340
345
|
<task type="auto">
|
|
341
346
|
<name>Task 2: Create User API endpoints</name>
|
|
342
347
|
<files>src/features/user/api.ts</files>
|
|
348
|
+
<behavior>GET /users returns 200 with the seeded users; GET /users/:id returns 404 for an unknown id; POST /users returns 201 and the created user</behavior>
|
|
343
349
|
<action>GET /users (list), GET /users/:id (single), POST /users (create). Use User type from model.</action>
|
|
344
350
|
<verify>fetch tests pass for all endpoints</verify>
|
|
345
351
|
<done>All CRUD operations work</done>
|
|
@@ -398,6 +404,7 @@ Output: Working dashboard component.
|
|
|
398
404
|
<task type="auto">
|
|
399
405
|
<name>Task 1: Build Dashboard layout</name>
|
|
400
406
|
<files>src/components/Dashboard.tsx</files>
|
|
407
|
+
<behavior>npm run build exits non-zero if Dashboard imports a missing component, and exits 0 once UserList and ProductList resolve</behavior>
|
|
401
408
|
<action>Create responsive grid with UserList and ProductList components. Use Tailwind for styling.</action>
|
|
402
409
|
<verify>npm run build succeeds</verify>
|
|
403
410
|
<done>Dashboard renders without errors</done>
|
|
@@ -406,6 +413,7 @@ Output: Working dashboard component.
|
|
|
406
413
|
<!-- Checkpoint pattern: Claude starts server, user visits URL. See checkpoints.md for full patterns. -->
|
|
407
414
|
<task type="auto">
|
|
408
415
|
<name>Start dev server</name>
|
|
416
|
+
<behavior>GET http://localhost:3000 returns 200 once the dev server is ready</behavior>
|
|
409
417
|
<action>Run `npm run dev` in background, wait for ready</action>
|
|
410
418
|
<verify>fetch http://localhost:3000 returns 200</verify>
|
|
411
419
|
</task>
|
|
@@ -460,6 +468,8 @@ files_modified: [...]
|
|
|
460
468
|
```xml
|
|
461
469
|
<task type="auto">
|
|
462
470
|
<name>Set up authentication</name>
|
|
471
|
+
<behavior>auth works</behavior>
|
|
472
|
+
<!-- Generic behavior: no input, no expected output -->
|
|
463
473
|
<action>Add auth to the app</action>
|
|
464
474
|
</task>
|
|
465
475
|
```
|
|
@@ -469,6 +479,7 @@ files_modified: [...]
|
|
|
469
479
|
<task type="auto">
|
|
470
480
|
<name>Update database config</name>
|
|
471
481
|
<files>src/config/database.ts</files>
|
|
482
|
+
<behavior>pool config is read from the environment</behavior>
|
|
472
483
|
<!-- No read_first! Executor doesn't know current state or conventions -->
|
|
473
484
|
<action>Update the database config to match production settings</action>
|
|
474
485
|
</task>
|
|
@@ -487,6 +498,7 @@ files_modified: [...]
|
|
|
487
498
|
<task type="auto">
|
|
488
499
|
<name>Update database config for connection pooling</name>
|
|
489
500
|
<files>src/config/database.ts</files>
|
|
501
|
+
<behavior>importing database.ts with NODE_ENV=production yields a pool with max 20 and ssl.rejectUnauthorized true</behavior>
|
|
490
502
|
<read_first>src/config/database.ts, .env.example, docker-compose.yml</read_first>
|
|
491
503
|
<action>Add pool configuration: min=2, max=20, idleTimeoutMs=30000. Add SSL config: rejectUnauthorized=true when NODE_ENV=production. Add .env.example entry: DATABASE_POOL_MAX=20.</action>
|
|
492
504
|
<acceptance_criteria>
|
|
@@ -506,7 +518,7 @@ files_modified: [...]
|
|
|
506
518
|
- Prefer vertical slices over horizontal layers
|
|
507
519
|
- Only reference prior SUMMARYs when genuinely needed
|
|
508
520
|
- Group checkpoints with related auto tasks in same plan
|
|
509
|
-
-
|
|
521
|
+
- Up to 6 tasks per plan within ~50% of `context_window`; split only for parallelism or budget
|
|
510
522
|
|
|
511
523
|
---
|
|
512
524
|
|
|
@@ -107,7 +107,7 @@ Phases execute in numeric order: 2 → 2.1 → 2.2 → 3 → 3.1 → 4
|
|
|
107
107
|
**Initial planning (v1.0):**
|
|
108
108
|
- Phase count depends on granularity setting (coarse: 3-5, standard: 5-8, fine: 8-12)
|
|
109
109
|
- Each phase delivers something coherent
|
|
110
|
-
- Phases can have 1+ plans (split
|
|
110
|
+
- Phases can have 1+ plans (split only for parallelism across independent subsystems or when the work exceeds the per-plan context budget; up to 6 tasks per plan)
|
|
111
111
|
- Plans use naming: {phase}-{plan}-PLAN.md (e.g., 01-02-PLAN.md)
|
|
112
112
|
- No time estimates (this isn't enterprise PM)
|
|
113
113
|
- Progress table updated by execute workflow
|
|
@@ -209,7 +209,7 @@ None — all verifiable items checked programmatically.
|
|
|
209
209
|
**Fix plan generation:**
|
|
210
210
|
- Only generate if gaps_found
|
|
211
211
|
- Group related fixes into single plans
|
|
212
|
-
-
|
|
212
|
+
- Size each fix plan by estimated context cost (up to 6 tasks per plan)
|
|
213
213
|
- Include verification task in each plan
|
|
214
214
|
|
|
215
215
|
---
|
|
@@ -163,8 +163,11 @@ Classify per phase:
|
|
|
163
163
|
| COMPLIANT | `nyquist_compliant: true` and all tasks green |
|
|
164
164
|
| PARTIAL | VALIDATION.md exists, `nyquist_compliant: false` or red/pending |
|
|
165
165
|
| MISSING | No VALIDATION.md |
|
|
166
|
+
| SKIPPED | No VALIDATION.md and the phase verdict skipped the Nyquist audit — report `skipped (ceremony: <reason>)`, never MISSING |
|
|
166
167
|
|
|
167
|
-
|
|
168
|
+
Before marking a phase MISSING, read `pp_run ceremony get "<phase number>" --pick gates.nyquist.report`; when it starts with `skipped (ceremony:` the status is SKIPPED. A failed read leaves the phase MISSING. A SKIPPED phase does not lower `overall`.
|
|
169
|
+
|
|
170
|
+
Add to audit YAML: `nyquist: { compliant_phases, partial_phases, missing_phases, skipped_phases, overall }`
|
|
168
171
|
|
|
169
172
|
Discovery only — never auto-calls `/pp:validate-phase`.
|
|
170
173
|
|
|
@@ -16,7 +16,7 @@ Read all files referenced by the invoking prompt's execution_context before star
|
|
|
16
16
|
|
|
17
17
|
## 1. Initialize
|
|
18
18
|
|
|
19
|
-
Parse `$ARGUMENTS` for `--from N`, `--to N`, `--only N`, `--interactive`, `--supervised`, `--converge`/`--cross-ai`, reviewer selector flags, and `--max-cycles N`:
|
|
19
|
+
Parse `$ARGUMENTS` for `--from N`, `--to N`, `--only N`, `--interactive`, `--supervised`, `--converge`/`--cross-ai`, `--full`, reviewer selector flags, and `--max-cycles N`:
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
22
|
FROM_PHASE=""
|
|
@@ -44,6 +44,8 @@ SUPERVISED=""
|
|
|
44
44
|
if echo "$ARGUMENTS" | grep -q '\-\-supervised'; then SUPERVISED="true"; fi
|
|
45
45
|
UNATTENDED_ARG=""
|
|
46
46
|
if [ -z "$SUPERVISED" ]; then UNATTENDED_ARG=" --unattended"; fi
|
|
47
|
+
FULL_ARG=""
|
|
48
|
+
if echo "$ARGUMENTS" | grep -qE '(^|[[:space:]])--full([[:space:]]|$)'; then FULL_ARG=" --full"; fi
|
|
47
49
|
|
|
48
50
|
PLAN_STRATEGY="local"
|
|
49
51
|
if echo "$ARGUMENTS" | grep -qE '(^|[[:space:]])\-\-(converge|cross-ai)([[:space:]]|$)'; then
|
|
@@ -62,11 +64,12 @@ if echo "$ARGUMENTS" | grep -qE '\-\-max-cycles\s+[0-9]+'; then
|
|
|
62
64
|
MAX_CYCLES_ARG=$(echo "$ARGUMENTS" | grep -oE '\-\-max-cycles\s+[0-9]+' | awk '{print $2}')
|
|
63
65
|
CONVERGENCE_ARGS="${CONVERGENCE_ARGS} --max-cycles ${MAX_CYCLES_ARG}"
|
|
64
66
|
fi
|
|
67
|
+
CONVERGENCE_ARGS="${CONVERGENCE_ARGS}${FULL_ARG}"
|
|
65
68
|
```
|
|
66
69
|
|
|
67
70
|
When `--interactive` is set, discuss runs inline with questions (not auto-answered). On runtimes where a backgrounded agent can spawn subagents, plan and execute are dispatched as background agents — keeping the main context lean (only discuss conversations accumulate) and enabling overlap. On Claude Code, where a backgrounded agent cannot nest subagents, plan and execute run inline to preserve worktree isolation and independent verification, so they run sequentially and their work accumulates in the main context. Either way, user input is preserved on all design decisions.
|
|
68
71
|
|
|
69
|
-
When `PLAN_STRATEGY=converge`, the planning step MUST invoke the plan-review convergence workflow instead of `pp-plan-phase`. `--cross-ai` is an alias for `--converge`. Forward `CONVERGENCE_ARGS` exactly as parsed so reviewer flags and `--max-cycles N` retain the same meaning as they have on `/pp:plan-review-convergence`.
|
|
72
|
+
When `PLAN_STRATEGY=converge`, the planning step MUST invoke the plan-review convergence workflow instead of `pp-plan-phase`. `--cross-ai` is an alias for `--converge`. Forward `CONVERGENCE_ARGS` exactly as parsed so reviewer flags and `--max-cycles N` retain the same meaning as they have on `/pp:plan-review-convergence`. `--full` is forwarded the same way and reaches every plan-phase call the convergence loop makes; it is never passed to execute-phase.
|
|
70
73
|
|
|
71
74
|
With `--supervised` every prompt appears as before; otherwise each decision is best-effort and recorded (reference § Rules). `--interactive` only moves discuss inline.
|
|
72
75
|
|
|
@@ -286,7 +289,7 @@ Resolve active `plan:pre` hooks:
|
|
|
286
289
|
```bash
|
|
287
290
|
[ -z "${ZSH_VERSION:-}" ] || emulate -R sh
|
|
288
291
|
UI_SPEC_FILE=$(ls "${PHASE_DIR}"/*-UI-SPEC.md 2>/dev/null | head -1)
|
|
289
|
-
HOOKS_JSON=$(pp_run loop render-hooks plan:pre --raw)
|
|
292
|
+
HOOKS_JSON=$(pp_run loop render-hooks plan:pre --raw --phase "${PHASE_NUM}")
|
|
290
293
|
printf '%s' "$HOOKS_JSON" | node -e "let h=[];try{h=JSON.parse(require('fs').readFileSync(0,'utf-8')).activeHooks||[]}catch{}console.log('HOOKS_JSON='+JSON.stringify({activeHooks:h}))"
|
|
291
294
|
```
|
|
292
295
|
|
|
@@ -404,7 +407,7 @@ RUNTIME=$(pp_run query config-get runtime --default claude --raw 2>/dev/null ||
|
|
|
404
407
|
- Otherwise (local planning):
|
|
405
408
|
|
|
406
409
|
```
|
|
407
|
-
Skill(skill="pp-plan-phase", args="${PHASE_NUM}${UNATTENDED_ARG}")
|
|
410
|
+
Skill(skill="pp-plan-phase", args="${PHASE_NUM}${UNATTENDED_ARG}${FULL_ARG}")
|
|
408
411
|
```
|
|
409
412
|
|
|
410
413
|
- **On other runtimes:** Dispatch plan as a background agent to keep the main context lean. While plan runs, the workflow can immediately start discussing the next phase (see step 4).
|
|
@@ -425,7 +428,7 @@ RUNTIME=$(pp_run query config-get runtime --default claude --raw 2>/dev/null ||
|
|
|
425
428
|
Agent(
|
|
426
429
|
description="Plan phase ${PHASE_NUM}: ${PHASE_NAME}",
|
|
427
430
|
run_in_background=true,
|
|
428
|
-
prompt="Run plan-phase for phase ${PHASE_NUM}: Skill(skill=\"pp-plan-phase\", args=\"${PHASE_NUM}${UNATTENDED_ARG}\")"
|
|
431
|
+
prompt="Run plan-phase for phase ${PHASE_NUM}: Skill(skill=\"pp-plan-phase\", args=\"${PHASE_NUM}${UNATTENDED_ARG}${FULL_ARG}\")"
|
|
429
432
|
)
|
|
430
433
|
```
|
|
431
434
|
|
|
@@ -442,7 +445,7 @@ Skill(skill="pp-plan-review-convergence", args="${PHASE_NUM} ${CONVERGENCE_ARGS}
|
|
|
442
445
|
If `PLAN_STRATEGY=local`, run the regular planner:
|
|
443
446
|
|
|
444
447
|
```
|
|
445
|
-
Skill(skill="pp-plan-phase", args="${PHASE_NUM}${UNATTENDED_ARG}")
|
|
448
|
+
Skill(skill="pp-plan-phase", args="${PHASE_NUM}${UNATTENDED_ARG}${FULL_ARG}")
|
|
446
449
|
```
|
|
447
450
|
|
|
448
451
|
Verify plan produced output — re-run `init phase-op` and check `has_plans`. If false → go to handle_blocker: "Plan phase ${PHASE_NUM} did not produce any plans."
|
|
@@ -535,7 +538,7 @@ Phase ${PHASE_NUM} ✅ ${PHASE_NAME} — Verification passed
|
|
|
535
538
|
**If `gaps_found`:** rule A-3 — one closure attempt:
|
|
536
539
|
|
|
537
540
|
```
|
|
538
|
-
Skill(skill="pp-plan-phase", args="${PHASE_NUM} --gaps${UNATTENDED_ARG}")
|
|
541
|
+
Skill(skill="pp-plan-phase", args="${PHASE_NUM} --gaps${UNATTENDED_ARG}${FULL_ARG}")
|
|
539
542
|
```
|
|
540
543
|
|
|
541
544
|
Verify gap plans were created — re-run `init phase-op ${PHASE_NUM}` and check `has_plans`. If no new gap plans → go to handle_blocker: "Gap closure planning for phase ${PHASE_NUM} did not produce plans."
|
|
@@ -561,7 +564,7 @@ Resolve the active post-verification hooks and the UI-SPEC gate:
|
|
|
561
564
|
```bash
|
|
562
565
|
[ -z "${ZSH_VERSION:-}" ] || emulate -R sh
|
|
563
566
|
UI_SPEC_FILE=$(ls "${PHASE_DIR}"/*-UI-SPEC.md 2>/dev/null | head -1)
|
|
564
|
-
HOOKS_JSON=$(pp_run loop render-hooks verify:post --raw)
|
|
567
|
+
HOOKS_JSON=$(pp_run loop render-hooks verify:post --raw --phase "${PHASE_NUM}")
|
|
565
568
|
```
|
|
566
569
|
|
|
567
570
|
Read the `activeHooks` array directly from the `HOOKS_JSON` value already in context (do not invoke a shell `jq` pipeline — parse as the JSON object it is). **If `activeHooks` is empty or absent:** skip silently to the iterate step.
|