@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.
- package/.claude-plugin/plugin.json +1 -1
- package/agents/pp-nyquist-auditor.md +19 -14
- package/agents/pp-phase-researcher.md +10 -0
- package/agents/pp-plan-checker.md +59 -36
- package/agents/pp-planner.md +49 -53
- package/agents/pp-vault-reviewer.md +2 -0
- package/agents/pp-verifier.md +15 -60
- package/commands/pp-autonomous.md +2 -1
- package/commands/pp-discovery.md +3 -2
- 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 +6 -3
- package/pareto-skills/_shared/pp-claude-md-protocol.md +1 -1
- package/pareto-skills/pp-project-setup/SKILL.md +18 -343
- package/pp-core/bin/lib/capability-activation.cjs +77 -0
- package/pp-core/bin/lib/capability-registry.cjs +15 -15
- 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/discovery-access.cjs +276 -0
- package/pp-core/bin/lib/discovery-charter.cjs +664 -0
- package/pp-core/bin/lib/discovery-deviations.cjs +264 -0
- package/pp-core/bin/lib/discovery-entities.cjs +323 -0
- package/pp-core/bin/lib/discovery-for-phase.cjs +628 -0
- package/pp-core/bin/lib/discovery-integrations.cjs +286 -0
- package/pp-core/bin/lib/discovery-migration.cjs +246 -0
- package/pp-core/bin/lib/discovery-phases.cjs +233 -0
- package/pp-core/bin/lib/discovery-processes.cjs +355 -0
- package/pp-core/bin/lib/discovery-research-track.cjs +5 -4
- package/pp-core/bin/lib/discovery-rollup.cjs +8 -7
- package/pp-core/bin/lib/discovery-rules.cjs +216 -0
- package/pp-core/bin/lib/discovery-screens.cjs +1 -1
- package/pp-core/bin/lib/discovery-session-check.cjs +264 -0
- package/pp-core/bin/lib/discovery-stories.cjs +273 -0
- package/pp-core/bin/lib/discovery-sweep.cjs +1 -1
- package/pp-core/bin/lib/discovery-table.cjs +55 -0
- package/pp-core/bin/lib/init.cjs +50 -1
- package/pp-core/bin/lib/io.cjs +24 -0
- package/pp-core/bin/lib/loop-resolver.cjs +26 -12
- package/pp-core/bin/lib/phase.cjs +39 -3
- package/pp-core/bin/lib/plan-sizing.cjs +346 -0
- package/pp-core/bin/lib/test-substance.cjs +3190 -0
- package/pp-core/bin/lib/timeline.cjs +605 -0
- package/pp-core/bin/lib/vault-standards-review.cjs +50 -2
- package/pp-core/bin/pp-tools.cjs +214 -5
- 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/discovery-access.md +112 -0
- package/pp-core/references/discovery-build-consumption.md +256 -0
- package/pp-core/references/discovery-charter.md +127 -0
- package/pp-core/references/discovery-data-model.md +145 -0
- package/pp-core/references/discovery-id-conventions.md +50 -14
- package/pp-core/references/discovery-integrations.md +151 -0
- package/pp-core/references/discovery-processes.md +190 -0
- package/pp-core/references/discovery-rebaseline.md +2 -2
- package/pp-core/references/discovery-research-track.md +5 -6
- package/pp-core/references/discovery-session-output.md +70 -0
- package/pp-core/references/discovery-standards.md +107 -0
- package/pp-core/references/discovery-traceability.md +2 -2
- 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-discovery-extract.md +35 -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-discovery-conformance.md +36 -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/project-charter.md +161 -100
- package/pp-core/templates/project.md +3 -3
- package/pp-core/templates/requirements.md +18 -0
- package/pp-core/templates/roadmap.md +1 -1
- package/pp-core/templates/verification-report.md +17 -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 +15 -6
- package/pp-core/workflows/discovery-sprint/charter.md +114 -0
- package/pp-core/workflows/discovery-sprint/prefill.md +1 -1
- package/pp-core/workflows/discovery-sprint/prototype.md +4 -4
- package/pp-core/workflows/discovery-sprint/session-13-gates.md +10 -0
- package/pp-core/workflows/discovery-sprint/sessions/01-kick-off.md +5 -2
- package/pp-core/workflows/discovery-sprint/sessions/02-as-is.md +27 -5
- package/pp-core/workflows/discovery-sprint/sessions/03-to-be.md +56 -31
- package/pp-core/workflows/discovery-sprint/sessions/04-ux-design.md +1 -1
- package/pp-core/workflows/discovery-sprint/sessions/05-architecture.md +42 -19
- package/pp-core/workflows/discovery-sprint/sessions/06-security.md +27 -12
- package/pp-core/workflows/discovery-sprint/sessions/07-identity.md +41 -23
- package/pp-core/workflows/discovery-sprint/sessions/08-integration.md +50 -23
- package/pp-core/workflows/discovery-sprint/sessions/09-qa.md +26 -11
- package/pp-core/workflows/discovery-sprint/sessions/10-infrastructure.md +28 -9
- package/pp-core/workflows/discovery-sprint/sessions/11-roadmap.md +21 -0
- package/pp-core/workflows/discovery-sprint/sessions/13-wrap-up.md +24 -9
- package/pp-core/workflows/discovery-sprint.md +12 -15
- package/pp-core/workflows/discovery-to-build/steps/architecture-deliverables.md +80 -29
- package/pp-core/workflows/discovery-to-build/steps/{screen-inventory-gate.md → handoff-gates.md} +111 -3
- package/pp-core/workflows/discovery-to-build/templates/discovery-references.md +12 -9
- package/pp-core/workflows/discovery-to-build/templates/pp-handoff.md +27 -26
- package/pp-core/workflows/discovery-to-build.md +70 -34
- package/pp-core/workflows/discuss-phase/discovery.md +83 -0
- package/pp-core/workflows/discuss-phase/templates/context.md +4 -0
- package/pp-core/workflows/discuss-phase.md +10 -7
- 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 +14 -3
- package/pp-core/workflows/new-project.md +3 -3
- package/pp-core/workflows/plan-phase/steps/chunked-planning.md +7 -3
- package/pp-core/workflows/plan-phase/steps/discovery-extract.md +41 -0
- package/pp-core/workflows/plan-phase/steps/prd-express.md +104 -0
- package/pp-core/workflows/plan-phase.md +62 -124
- package/pp-core/workflows/plan-review-convergence.md +6 -3
- package/pp-core/workflows/ship.md +2 -2
- package/pp-core/workflows/stats.md +19 -4
- package/pp-core/workflows/validate-phase.md +19 -4
- package/pp-core/workflows/vault-review.md +5 -0
- 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
- 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
|
-
-
|
|
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
|
|