@pareto4/pp-core 1.19.0 → 1.21.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.
Files changed (126) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/agents/pp-nyquist-auditor.md +19 -14
  3. package/agents/pp-phase-researcher.md +10 -0
  4. package/agents/pp-plan-checker.md +59 -36
  5. package/agents/pp-planner.md +49 -53
  6. package/agents/pp-vault-reviewer.md +2 -0
  7. package/agents/pp-verifier.md +15 -60
  8. package/commands/pp-autonomous.md +2 -1
  9. package/commands/pp-discovery.md +3 -2
  10. package/commands/pp-plan-phase.md +2 -1
  11. package/commands/pp-plan-review-convergence.md +2 -1
  12. package/gemini-extension.json +1 -1
  13. package/package.json +6 -3
  14. package/pareto-skills/_shared/pp-claude-md-protocol.md +1 -1
  15. package/pareto-skills/pp-project-setup/SKILL.md +18 -343
  16. package/pp-core/bin/lib/capability-activation.cjs +77 -0
  17. package/pp-core/bin/lib/capability-registry.cjs +15 -15
  18. package/pp-core/bin/lib/capability-state.cjs +8 -18
  19. package/pp-core/bin/lib/ceremony.cjs +1342 -0
  20. package/pp-core/bin/lib/check-command-router.cjs +62 -1
  21. package/pp-core/bin/lib/commands.cjs +29 -3
  22. package/pp-core/bin/lib/config.cjs +5 -0
  23. package/pp-core/bin/lib/discovery-access.cjs +276 -0
  24. package/pp-core/bin/lib/discovery-charter.cjs +664 -0
  25. package/pp-core/bin/lib/discovery-deviations.cjs +264 -0
  26. package/pp-core/bin/lib/discovery-entities.cjs +323 -0
  27. package/pp-core/bin/lib/discovery-for-phase.cjs +628 -0
  28. package/pp-core/bin/lib/discovery-integrations.cjs +286 -0
  29. package/pp-core/bin/lib/discovery-migration.cjs +246 -0
  30. package/pp-core/bin/lib/discovery-phases.cjs +233 -0
  31. package/pp-core/bin/lib/discovery-processes.cjs +355 -0
  32. package/pp-core/bin/lib/discovery-research-track.cjs +5 -4
  33. package/pp-core/bin/lib/discovery-rollup.cjs +8 -7
  34. package/pp-core/bin/lib/discovery-rules.cjs +216 -0
  35. package/pp-core/bin/lib/discovery-screens.cjs +1 -1
  36. package/pp-core/bin/lib/discovery-session-check.cjs +264 -0
  37. package/pp-core/bin/lib/discovery-stories.cjs +273 -0
  38. package/pp-core/bin/lib/discovery-sweep.cjs +1 -1
  39. package/pp-core/bin/lib/discovery-table.cjs +55 -0
  40. package/pp-core/bin/lib/init.cjs +50 -1
  41. package/pp-core/bin/lib/io.cjs +24 -0
  42. package/pp-core/bin/lib/loop-resolver.cjs +26 -12
  43. package/pp-core/bin/lib/phase.cjs +39 -3
  44. package/pp-core/bin/lib/plan-sizing.cjs +346 -0
  45. package/pp-core/bin/lib/test-substance.cjs +3190 -0
  46. package/pp-core/bin/lib/timeline.cjs +605 -0
  47. package/pp-core/bin/lib/vault-standards-review.cjs +50 -2
  48. package/pp-core/bin/pp-tools.cjs +214 -5
  49. package/pp-core/bin/shared/ceremony-gates.json +17 -0
  50. package/pp-core/bin/shared/config-schema.manifest.json +1 -0
  51. package/pp-core/references/ceremony.md +81 -0
  52. package/pp-core/references/discovery-access.md +112 -0
  53. package/pp-core/references/discovery-build-consumption.md +256 -0
  54. package/pp-core/references/discovery-charter.md +127 -0
  55. package/pp-core/references/discovery-data-model.md +145 -0
  56. package/pp-core/references/discovery-id-conventions.md +50 -14
  57. package/pp-core/references/discovery-integrations.md +151 -0
  58. package/pp-core/references/discovery-processes.md +190 -0
  59. package/pp-core/references/discovery-rebaseline.md +2 -2
  60. package/pp-core/references/discovery-research-track.md +5 -6
  61. package/pp-core/references/discovery-session-output.md +70 -0
  62. package/pp-core/references/discovery-standards.md +107 -0
  63. package/pp-core/references/discovery-traceability.md +2 -2
  64. package/pp-core/references/few-shot-examples/plan-checker.md +8 -8
  65. package/pp-core/references/phase-timeline.md +107 -0
  66. package/pp-core/references/planner-antipatterns.md +32 -0
  67. package/pp-core/references/planner-discovery-extract.md +35 -0
  68. package/pp-core/references/planner-guidance.md +17 -14
  69. package/pp-core/references/planner-revision.md +1 -1
  70. package/pp-core/references/planning-config.md +3 -2
  71. package/pp-core/references/test-substance.md +324 -0
  72. package/pp-core/references/verifier-discovery-conformance.md +36 -0
  73. package/pp-core/references/verifier-stub-patterns.md +51 -0
  74. package/pp-core/templates/VALIDATION.md +1 -0
  75. package/pp-core/templates/phase-prompt.md +19 -7
  76. package/pp-core/templates/project-charter.md +161 -100
  77. package/pp-core/templates/project.md +3 -3
  78. package/pp-core/templates/requirements.md +18 -0
  79. package/pp-core/templates/roadmap.md +1 -1
  80. package/pp-core/templates/verification-report.md +17 -1
  81. package/pp-core/workflows/audit-milestone.md +4 -1
  82. package/pp-core/workflows/autonomous.md +11 -8
  83. package/pp-core/workflows/code-review.md +15 -6
  84. package/pp-core/workflows/discovery-sprint/charter.md +114 -0
  85. package/pp-core/workflows/discovery-sprint/prefill.md +1 -1
  86. package/pp-core/workflows/discovery-sprint/prototype.md +4 -4
  87. package/pp-core/workflows/discovery-sprint/session-13-gates.md +10 -0
  88. package/pp-core/workflows/discovery-sprint/sessions/01-kick-off.md +5 -2
  89. package/pp-core/workflows/discovery-sprint/sessions/02-as-is.md +27 -5
  90. package/pp-core/workflows/discovery-sprint/sessions/03-to-be.md +56 -31
  91. package/pp-core/workflows/discovery-sprint/sessions/04-ux-design.md +1 -1
  92. package/pp-core/workflows/discovery-sprint/sessions/05-architecture.md +42 -19
  93. package/pp-core/workflows/discovery-sprint/sessions/06-security.md +27 -12
  94. package/pp-core/workflows/discovery-sprint/sessions/07-identity.md +41 -23
  95. package/pp-core/workflows/discovery-sprint/sessions/08-integration.md +50 -23
  96. package/pp-core/workflows/discovery-sprint/sessions/09-qa.md +26 -11
  97. package/pp-core/workflows/discovery-sprint/sessions/10-infrastructure.md +28 -9
  98. package/pp-core/workflows/discovery-sprint/sessions/11-roadmap.md +21 -0
  99. package/pp-core/workflows/discovery-sprint/sessions/13-wrap-up.md +24 -9
  100. package/pp-core/workflows/discovery-sprint.md +12 -15
  101. package/pp-core/workflows/discovery-to-build/steps/architecture-deliverables.md +80 -29
  102. package/pp-core/workflows/discovery-to-build/steps/{screen-inventory-gate.md → handoff-gates.md} +111 -3
  103. package/pp-core/workflows/discovery-to-build/templates/discovery-references.md +12 -9
  104. package/pp-core/workflows/discovery-to-build/templates/pp-handoff.md +27 -26
  105. package/pp-core/workflows/discovery-to-build.md +70 -34
  106. package/pp-core/workflows/discuss-phase/discovery.md +83 -0
  107. package/pp-core/workflows/discuss-phase/templates/context.md +4 -0
  108. package/pp-core/workflows/discuss-phase.md +10 -7
  109. package/pp-core/workflows/execute-phase/steps/check-query-gate.md +7 -7
  110. package/pp-core/workflows/execute-phase/steps/codebase-drift-gate.md +3 -2
  111. package/pp-core/workflows/execute-phase.md +14 -3
  112. package/pp-core/workflows/new-project.md +3 -3
  113. package/pp-core/workflows/plan-phase/steps/chunked-planning.md +7 -3
  114. package/pp-core/workflows/plan-phase/steps/discovery-extract.md +41 -0
  115. package/pp-core/workflows/plan-phase/steps/prd-express.md +104 -0
  116. package/pp-core/workflows/plan-phase.md +62 -124
  117. package/pp-core/workflows/plan-review-convergence.md +6 -3
  118. package/pp-core/workflows/ship.md +2 -2
  119. package/pp-core/workflows/stats.md +19 -4
  120. package/pp-core/workflows/validate-phase.md +19 -4
  121. package/pp-core/workflows/vault-review.md +5 -0
  122. package/pp-core/workflows/verify-phase.md +1 -1
  123. package/pp-core/workflows/verify-work.md +1 -1
  124. package/scripts/gen-capability-registry.cjs +43 -45
  125. package/scripts/run-tests.cjs +54 -30
  126. package/pp-core/workflows/discovery-sprint/templates/context-template.md +0 -272
@@ -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,36 @@
1
+ # Discovery conformance — the verifier contract (R3)
2
+
3
+ **Load this file from pp-verifier Step 6d on a project that ran the Discovery Sprint** (contract:
4
+ `references/discovery-build-consumption.md`). Planning read the Discovery registers; verification
5
+ is where conformance is enforced — "did we build what was signed" is answered per phase.
6
+
7
+ ```bash
8
+ DISCOVERY_CONFORMANCE=$(pp_run query discovery.for-phase "$PHASE_NUM" --conformance-template 2>/dev/null || true)
9
+ ```
10
+
11
+ Empty output means no `.planning/discovery/` — write no block and stop here. A non-zero exit is a
12
+ structural fault in a discovery register: record it as a gap naming the fault list.
13
+
14
+ Otherwise the output is the **Discovery conformance** block: one row per Discovery register row
15
+ that cites this phase's requirement IDs (`_requirements.md`, `_stories.md`, `_processes.md` steps,
16
+ `_entities.md`, `_rules.md`, `_screens.md`, roles, integrations), every row `missed`. Fill it:
17
+
18
+ 1. **The acceptance criteria ARE the UAT.** Each `_requirements.md` and `_stories.md` row's
19
+ acceptance criteria is a truth in Step 3 — add any the plans' `must_haves` omitted — verified
20
+ by the same evidence rules as every other truth.
21
+ 2. **One status per row**, exactly one of:
22
+ - `honoured` — evidence that the build realises the row (`file:line`, a test name) in `Evidence`;
23
+ - `amended (D-P<nn>-<n>)` — the register cell carries `→ amended by` that decision and the build
24
+ matches the amended cell; name the cell in `Evidence`;
25
+ - `missed` — no evidence, or the build contradicts the row; say which in `Evidence`.
26
+ 3. **Never add, remove or rename a row**; never open a `.planning/discovery/session-NN.md` — the
27
+ register row is the fact, and `--verify` refuses an unknown or missing row.
28
+ 4. **A `missed` row is a gap.** It fires rule 1 of Step 9 (`gaps_found`) and appears in the Gaps
29
+ Summary with its row key (`_rules.md BR-02`).
30
+ 5. **Paste the filled block into VERIFICATION.md** under `### Discovery conformance`, markers
31
+ included, after Requirements Coverage. Then run
32
+
33
+ `pp_run query discovery.for-phase "$PHASE_NUM" --verify "$REPORT_PATH"`
34
+
35
+ and repair the block until it exits 0 (markers once each, header, every row present once, legal
36
+ statuses). A `missed` count is never a fault of the block.
@@ -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
- - 2-3 tasks per plan
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
- - Complex phases: Multiple focused plans, not one large plan
255
+ - The task count follows the estimate, never a target
252
256
 
253
257
  **When to split:**
254
258
 
255
- - Different subsystems (auth vs API vs UI)
256
- - >3 tasks
257
- - Risk of context overflow
258
- - TDD candidates - separate plans
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
- - 2-3 tasks per plan, ~50% context max
521
+ - Up to 6 tasks per plan within ~50% of `context_window`; split only for parallelism or budget
510
522
 
511
523
  ---
512
524