@pareto4/pp-core 1.20.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 (57) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/agents/pp-nyquist-auditor.md +19 -14
  3. package/agents/pp-plan-checker.md +59 -36
  4. package/agents/pp-planner.md +34 -53
  5. package/agents/pp-verifier.md +3 -52
  6. package/commands/pp-autonomous.md +2 -1
  7. package/commands/pp-plan-phase.md +2 -1
  8. package/commands/pp-plan-review-convergence.md +2 -1
  9. package/gemini-extension.json +1 -1
  10. package/package.json +1 -1
  11. package/pp-core/bin/lib/capability-activation.cjs +77 -0
  12. package/pp-core/bin/lib/capability-registry.cjs +14 -14
  13. package/pp-core/bin/lib/capability-state.cjs +8 -18
  14. package/pp-core/bin/lib/ceremony.cjs +1342 -0
  15. package/pp-core/bin/lib/check-command-router.cjs +62 -1
  16. package/pp-core/bin/lib/commands.cjs +29 -3
  17. package/pp-core/bin/lib/config.cjs +5 -0
  18. package/pp-core/bin/lib/init.cjs +50 -1
  19. package/pp-core/bin/lib/io.cjs +2 -0
  20. package/pp-core/bin/lib/loop-resolver.cjs +26 -12
  21. package/pp-core/bin/lib/phase.cjs +39 -3
  22. package/pp-core/bin/lib/plan-sizing.cjs +346 -0
  23. package/pp-core/bin/lib/test-substance.cjs +3190 -0
  24. package/pp-core/bin/lib/timeline.cjs +605 -0
  25. package/pp-core/bin/pp-tools.cjs +71 -4
  26. package/pp-core/bin/shared/ceremony-gates.json +17 -0
  27. package/pp-core/bin/shared/config-schema.manifest.json +1 -0
  28. package/pp-core/references/ceremony.md +81 -0
  29. package/pp-core/references/few-shot-examples/plan-checker.md +8 -8
  30. package/pp-core/references/phase-timeline.md +107 -0
  31. package/pp-core/references/planner-antipatterns.md +32 -0
  32. package/pp-core/references/planner-guidance.md +17 -14
  33. package/pp-core/references/planner-revision.md +1 -1
  34. package/pp-core/references/planning-config.md +3 -2
  35. package/pp-core/references/test-substance.md +324 -0
  36. package/pp-core/references/verifier-stub-patterns.md +51 -0
  37. package/pp-core/templates/VALIDATION.md +1 -0
  38. package/pp-core/templates/phase-prompt.md +19 -7
  39. package/pp-core/templates/roadmap.md +1 -1
  40. package/pp-core/templates/verification-report.md +1 -1
  41. package/pp-core/workflows/audit-milestone.md +4 -1
  42. package/pp-core/workflows/autonomous.md +11 -8
  43. package/pp-core/workflows/code-review.md +15 -6
  44. package/pp-core/workflows/execute-phase/steps/check-query-gate.md +7 -7
  45. package/pp-core/workflows/execute-phase/steps/codebase-drift-gate.md +3 -2
  46. package/pp-core/workflows/execute-phase.md +14 -3
  47. package/pp-core/workflows/plan-phase/steps/chunked-planning.md +7 -3
  48. package/pp-core/workflows/plan-phase/steps/prd-express.md +104 -0
  49. package/pp-core/workflows/plan-phase.md +47 -114
  50. package/pp-core/workflows/plan-review-convergence.md +6 -3
  51. package/pp-core/workflows/ship.md +2 -2
  52. package/pp-core/workflows/stats.md +19 -4
  53. package/pp-core/workflows/validate-phase.md +19 -4
  54. package/pp-core/workflows/verify-phase.md +1 -1
  55. package/pp-core/workflows/verify-work.md +1 -1
  56. package/scripts/gen-capability-registry.cjs +43 -45
  57. package/scripts/run-tests.cjs +54 -30
@@ -106,15 +106,19 @@ Default is active through the Capability Registry schema — only skip when the
106
106
  Determine review depth with priority order:
107
107
 
108
108
  1. DEPTH_OVERRIDE from --depth flag (highest priority)
109
- 2. Config value: `pp-tools.cjs query config-get workflow.code_review_depth 2>/dev/null`
110
- 3. Default: "standard"
109
+ 2. Phase verdict: `pp_run ceremony get "${PHASE_ARG}" --pick code_review_depth` (the configured depth when no verdict is recorded)
110
+ 3. Config value: `pp-tools.cjs query config-get workflow.code_review_depth 2>/dev/null`
111
+ 4. Default: "standard"
111
112
 
112
113
  ```bash
113
114
  if [ -n "$DEPTH_OVERRIDE" ]; then
114
115
  REVIEW_DEPTH="$DEPTH_OVERRIDE"
115
116
  else
116
- CONFIG_DEPTH=$(pp_run query config-get workflow.code_review_depth --raw 2>/dev/null || echo "")
117
- REVIEW_DEPTH="${CONFIG_DEPTH:-standard}"
117
+ REVIEW_DEPTH=$(pp_run ceremony get "${PHASE_ARG}" --pick code_review_depth 2>/dev/null || echo "")
118
+ if [ -z "$REVIEW_DEPTH" ]; then
119
+ CONFIG_DEPTH=$(pp_run query config-get workflow.code_review_depth --raw 2>/dev/null || echo "")
120
+ REVIEW_DEPTH="${CONFIG_DEPTH:-standard}"
121
+ fi
118
122
  fi
119
123
  ```
120
124
 
@@ -514,6 +518,8 @@ Spawn the pp-code-reviewer agent:
514
518
 
515
519
  Print: `◆ Spawning code reviewer... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)`
516
520
 
521
+ `pp_run timeline start code-reviewer "${PHASE_ARG}" --model inherit || true` — rules: `pp-core/references/phase-timeline.md`
522
+
517
523
  ```
518
524
  Agent(subagent_type="pp-code-reviewer", prompt="
519
525
  <files_to_read>
@@ -540,7 +546,7 @@ ${AGENT_SKILLS_REVIEWER}")
540
546
 
541
547
  **Agent failure handling:**
542
548
 
543
- If the Agent() call fails (agent error, timeout, or exception):
549
+ If the Agent() call fails (agent error, timeout, or exception): first run `pp_run timeline end code-reviewer "${PHASE_ARG}" failed || true`, then show:
544
550
  ```
545
551
  Error: Code review agent failed: ${error_message}
546
552
 
@@ -555,6 +561,7 @@ After agent completes successfully, verify REVIEW.md was created and has valid s
555
561
 
556
562
  ```bash
557
563
  [ -z "${ZSH_VERSION:-}" ] || emulate -R sh
564
+ TL_OUTCOME=failed
558
565
  if [ -f "${REVIEW_PATH}" ]; then
559
566
  # Validate REVIEW.md has valid YAML frontmatter with status field
560
567
  HAS_STATUS=$(REVIEW_PATH="${REVIEW_PATH}" node -e "
@@ -565,12 +572,13 @@ if [ -f "${REVIEW_PATH}" ]; then
565
572
  " 2>/dev/null)
566
573
 
567
574
  if [ "$HAS_STATUS" = "valid" ]; then
575
+ TL_OUTCOME=complete
568
576
  echo "REVIEW.md created at ${REVIEW_PATH}"
569
577
 
570
578
  if [ "$COMMIT_DOCS" = "true" ]; then
571
579
  pp_run query commit \
572
580
  "docs(planning): add code review report" --phase "${PADDED_PHASE}" \
573
- --files "${REVIEW_PATH}"
581
+ --files "${REVIEW_PATH}" "${PHASE_DIR}"/*-TIMELINE.jsonl
574
582
  fi
575
583
  else
576
584
  echo "Warning: REVIEW.md exists but has invalid or missing frontmatter (no status field)."
@@ -580,6 +588,7 @@ else
580
588
  echo "Warning: Agent completed but REVIEW.md not found at ${REVIEW_PATH}. This may indicate an agent issue."
581
589
  echo "No REVIEW.md to commit. Please retry with /pp:code-review ${PHASE_ARG}"
582
590
  fi
591
+ pp_run timeline end code-reviewer "${PHASE_ARG}" "${TL_OUTCOME}" || true
583
592
  ```
584
593
  </step>
585
594
 
@@ -54,13 +54,13 @@ old numbers, which is how 05-13 shifted three rows ~200 lines into a different f
54
54
 
55
55
  | Query | Handler branch / function | Emits boolean `block` on every path? | Emission site(s) |
56
56
  |-------|---------------------------|--------------------------------------|------------------|
57
- | `gap-analysis.plan-post` | `gap-analysis-plan-post` branch (`cmdGapAnalysisPlanPost`) | yes — always `false` (advisory by design) | `src/check-command-router.cts:1096` |
58
- | `ship.platform-drift` | `ship-platform-drift` branch (`cmdShipPlatformDrift`) | yes — the only producer that can emit `block: true` from a threshold comparison | `src/check-command-router.cts:1198,1208,1217,1234,1276,1285,1299` |
59
- | `tdd.review-checkpoint` | `tdd-review-checkpoint` branch | yes — `block = violations > 0`, advisory in the capability | `src/check-command-router.cts:977,1051` |
60
- | `ui.plan-gate` | `ui-plan-gate` branch (`computeUiPlanGate`) | yes — `block = frontend && !hasUiSpec` | `src/check-command-router.cts:703` (emitted at `:727`) |
61
- | `ui.safety-gate` | `ui-safety-gate` branch (`computeUiSafetyGate`) | yes — `block = frontend && hasUiFiles && !hasUiSpec` | `src/check-command-router.cts:830` (emitted at `:856`) |
62
- | `vault.status` | `vault-status` branch (`cmdVaultStatus`) | yes — COMPUTED, not constant: `block` is the `vault.required` policy applied to the shared tier-keyed verdict leaf (`computeVaultVerdict`, GATE-01 / D-VH-4). It is `false` with the policy off (STRICT-01's byte-identical promise), on the inert path (`.platform-bindings.json` absent — that branch runs FIRST by requirement, T-02-28, because `tier !== 'client'` is true of `undefined` and would otherwise block every non-Pareto project), and for a present, fresh, client-tier vault; it is `true` on a required miss, on a fallback-tier load, on a stale client vault whatever its source (D-08-01; the block skips staleness only when `vault.cache_ttl_hours` is 0, D-08-28), and on a client cache fetched from a ref the project no longer configures (D-08-23). The attributed override (`applyVaultOverride`, GATE-07) then gets the last word on every blocking path, and that overwrite is the last emission site cited; and on a passing required-on verdict the envelope now carries a `message` (the success line, D-08-20) — the enforcer's every-run passthrough prints it. | `src/check-command-router.cts:1655,1670,1709,1736,1797` (02-11: CHECKED by the same two-directional probe as the other rows — every cited line lies inside `cmdVaultStatus` and emits a block, and every emission site is cited. This was the one row the probe did NOT cover, so it is the row that rotted: wave 1 wrote the claim before 02-06 computed the block, and 05-13 shifted the citations arithmetically into the manifest-sanitiser, ~200 lines outside the handler) 14-06: + the envelope always carries `reason`, `status` may be `degraded` (index missing / unparseable / empty / unresolvable / unchecked, D-12), the flat index fields follow the override keys, and the healthy message is the one-line standards card (D-15); a degraded index blocks exactly when `vault.required` is on, through the same override (D-13), so it adds no emission site of its own. |
63
- | `vault.trace` | `vault-trace` branch (`cmdVaultTrace`) — declared by the `vault` capability (`when: vault.enabled`) at **four** points, `plan:post`, `execute:post`, `ship:pre` and `verify:post`, all `blocking: true` / `onError: halt`, and once more at `ship:pre` as a seal gate (`onRecordedArtifacts: true`) that renders with `vault.enabled` off over a phase holding vault records (16-08). It judges each PLAN's `<vault_standards>` block and each SUMMARY's `## Standards Consulted` section through `src/vault-standards.cts` and fails closed on the `standards_*` kinds (`standards_block_absent`, `standards_block_duplicate`, `standards_block_unterminated`, `standards_block_malformed`, `standards_reason_invalid`, `standards_document_duplicate`, `standards_degraded_mixed`, `standards_none_without_reason`, `standards_path_not_resolved`, `standards_pin_mismatch`, `standards_cache_unreadable`, `standards_degraded_untrue`, `standards_consulted_absent`, `standards_consulted_mismatch`, `standards_plan_id_mismatch`, `standards_pair_missing`, `standards_orphan_summary`, `standards_anchor_malformed`). Reads `--stage` (04-03) and `--unpaired` (04-07, D-10); `ship:pre` sets `GATE_UNPAIRED=strict` unconditionally, `verify:post` sets it only on a full-phase run and leaves it unset when **either** partial-execution mode is active — the host's `WAVE_FILTER` set, or `--gaps-only` (04-12, CR-04; 04-18, WR-01) — and the other two leave the tolerant default | yes — `false` before any policy is read (inert), `false` with status `inactive` when `vault.enabled` is off and the phase holds no vault records, `true` once a standards record fails to validate; an agreeing degraded record with `vault.required` off is `false` with a message (D-29), and a recorded override is `false` at the tolerant points and `true` at the strict ones; `true` with status `disabled_with_artifacts` when `vault.enabled` is off and the positional's phase (or plan file) holds engine-written vault records (16-09, SEAL-02, D-21) — the answer the `ship:pre` seal gate dispatches for | `src/check-command-router.cts:3011,3023,3026,3058,3089,3114,3144,3170,3445` (04-16: CHECKED — a test asserts every line cited here lies inside `cmdVaultTrace` and carries a `block` emission, because four plans in this pass moved these numbers and nothing noticed). 16-07 (SEAL-01): at the strict points only, `appendSealProblems` compares the phase's recorded end anchors with `HEAD` through bounded git and adds `uncovered_source_commit` (a source commit after the ends that no record's start-end range covers), `anchor_missing`, `anchor_start_missing`, `anchor_unknown`, `anchor_not_ancestor` and `seal_could_not_check` (all blocking) and the `anchor_backfilled` note; it pushes problems into `checked` and owns no emission site and no status. 16-09 (SEAL-02): + the `disabled_with_artifacts` emission inside the switch-off guard. Standards-only vault: re-derived with the probe's own `handlerRange`/`emitsBlock` after the second record family, the invalid-config refusal and the envelope's mode tag left the handler. |
57
+ | `gap-analysis.plan-post` | `gap-analysis-plan-post` branch (`cmdGapAnalysisPlanPost`) | yes — always `false` (advisory by design) | `src/check-command-router.cts:1151` |
58
+ | `ship.platform-drift` | `ship-platform-drift` branch (`cmdShipPlatformDrift`) | yes — the only producer that can emit `block: true` from a threshold comparison | `src/check-command-router.cts:1253,1263,1272,1289,1331,1340,1354` |
59
+ | `tdd.review-checkpoint` | `tdd-review-checkpoint` branch | yes — `block = violations > 0`, advisory in the capability | `src/check-command-router.cts:1032,1106` |
60
+ | `ui.plan-gate` | `ui-plan-gate` branch (`computeUiPlanGate`) | yes — `block = frontend && !hasUiSpec` | `src/check-command-router.cts:705` (emitted at `:729`) |
61
+ | `ui.safety-gate` | `ui-safety-gate` branch (`computeUiSafetyGate`) | yes — `block = frontend && hasUiFiles && !hasUiSpec` | `src/check-command-router.cts:885` (emitted at `:911`) |
62
+ | `vault.status` | `vault-status` branch (`cmdVaultStatus`) | yes — COMPUTED, not constant: `block` is the `vault.required` policy applied to the shared tier-keyed verdict leaf (`computeVaultVerdict`, GATE-01 / D-VH-4). It is `false` with the policy off (STRICT-01's byte-identical promise), on the inert path (`.platform-bindings.json` absent — that branch runs FIRST by requirement, T-02-28, because `tier !== 'client'` is true of `undefined` and would otherwise block every non-Pareto project), and for a present, fresh, client-tier vault; it is `true` on a required miss, on a fallback-tier load, on a stale client vault whatever its source (D-08-01; the block skips staleness only when `vault.cache_ttl_hours` is 0, D-08-28), and on a client cache fetched from a ref the project no longer configures (D-08-23). The attributed override (`applyVaultOverride`, GATE-07) then gets the last word on every blocking path, and that overwrite is the last emission site cited; and on a passing required-on verdict the envelope now carries a `message` (the success line, D-08-20) — the enforcer's every-run passthrough prints it. | `src/check-command-router.cts:1710,1725,1764,1791,1852` (02-11: CHECKED by the same two-directional probe as the other rows — every cited line lies inside `cmdVaultStatus` and emits a block, and every emission site is cited. This was the one row the probe did NOT cover, so it is the row that rotted: wave 1 wrote the claim before 02-06 computed the block, and 05-13 shifted the citations arithmetically into the manifest-sanitiser, ~200 lines outside the handler) 14-06: + the envelope always carries `reason`, `status` may be `degraded` (index missing / unparseable / empty / unresolvable / unchecked, D-12), the flat index fields follow the override keys, and the healthy message is the one-line standards card (D-15); a degraded index blocks exactly when `vault.required` is on, through the same override (D-13), so it adds no emission site of its own. |
63
+ | `vault.trace` | `vault-trace` branch (`cmdVaultTrace`) — declared by the `vault` capability (`when: vault.enabled`) at **four** points, `plan:post`, `execute:post`, `ship:pre` and `verify:post`, all `blocking: true` / `onError: halt`, and once more at `ship:pre` as a seal gate (`onRecordedArtifacts: true`) that renders with `vault.enabled` off over a phase holding vault records (16-08). It judges each PLAN's `<vault_standards>` block and each SUMMARY's `## Standards Consulted` section through `src/vault-standards.cts` and fails closed on the `standards_*` kinds (`standards_block_absent`, `standards_block_duplicate`, `standards_block_unterminated`, `standards_block_malformed`, `standards_reason_invalid`, `standards_document_duplicate`, `standards_degraded_mixed`, `standards_none_without_reason`, `standards_path_not_resolved`, `standards_pin_mismatch`, `standards_cache_unreadable`, `standards_degraded_untrue`, `standards_consulted_absent`, `standards_consulted_mismatch`, `standards_plan_id_mismatch`, `standards_pair_missing`, `standards_orphan_summary`, `standards_anchor_malformed`). Reads `--stage` (04-03) and `--unpaired` (04-07, D-10); `ship:pre` sets `GATE_UNPAIRED=strict` unconditionally, `verify:post` sets it only on a full-phase run and leaves it unset when **either** partial-execution mode is active — the host's `WAVE_FILTER` set, or `--gaps-only` (04-12, CR-04; 04-18, WR-01) — and the other two leave the tolerant default | yes — `false` before any policy is read (inert), `false` with status `inactive` when `vault.enabled` is off and the phase holds no vault records, `true` once a standards record fails to validate; an agreeing degraded record with `vault.required` off is `false` with a message (D-29), and a recorded override is `false` at the tolerant points and `true` at the strict ones; `true` with status `disabled_with_artifacts` when `vault.enabled` is off and the positional's phase (or plan file) holds engine-written vault records (16-09, SEAL-02, D-21) — the answer the `ship:pre` seal gate dispatches for | `src/check-command-router.cts:3066,3078,3081,3113,3144,3169,3199,3225,3500` (04-16: CHECKED — a test asserts every line cited here lies inside `cmdVaultTrace` and carries a `block` emission, because four plans in this pass moved these numbers and nothing noticed). 16-07 (SEAL-01): at the strict points only, `appendSealProblems` compares the phase's recorded end anchors with `HEAD` through bounded git and adds `uncovered_source_commit` (a source commit after the ends that no record's start-end range covers), `anchor_missing`, `anchor_start_missing`, `anchor_unknown`, `anchor_not_ancestor` and `seal_could_not_check` (all blocking) and the `anchor_backfilled` note; it pushes problems into `checked` and owns no emission site and no status. 16-09 (SEAL-02): + the `disabled_with_artifacts` emission inside the switch-off guard. Standards-only vault: re-derived with the probe's own `handlerRange`/`emitsBlock` after the second record family, the invalid-config refusal and the envelope's mode tag left the handler. |
64
64
  | `verify.codebase-drift` | `verify-codebase-drift` branch (`verify.cmdVerifyCodebaseDrift`) | yes — `block = action_required` | `src/verify.cts:2134,2149,2164,2186,2231,2246` |
65
65
  | `verify.schema-drift` | `verify-schema-drift` branch (`verify.cmdVerifySchemaDrift`) | yes — `block = !skipped && driftDetected` | `src/verify.cts:2051,2071,2108` |
66
66
 
@@ -60,6 +60,7 @@ from step `init_context` is for `pp-executor`, not the mapper):
60
60
  # with the single-canonical-preamble parity invariant (#619). This block only runs on the
61
61
  # `auto-remap` directive, which is always reached after the drift check above has run.
62
62
  AGENT_SKILLS_MAPPER=$(pp_run query agent-skills pp-codebase-mapper)
63
+ pp_run timeline start other "${PHASE_NUMBER}" --agent pp-codebase-mapper --source execute-phase --model inherit || true
63
64
  ```
64
65
 
65
66
  Then spawn `pp-codebase-mapper` agents with the `--paths` hint (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze):
@@ -80,11 +81,11 @@ ${AGENT_SKILLS_MAPPER}"
80
81
 
81
82
  > **ORCHESTRATOR RULE — CODEX RUNTIME**: After calling Agent() above, stop working on this task immediately. Do not read more files, edit code, or run tests related to this task while the subagent is active. Wait for the subagent to return its result. This prevents duplicate work, conflicting edits, and wasted context. Only resume when the subagent result is available.
82
83
 
83
- If the spawn fails or the agent reports an error: log `Codebase drift
84
+ If the spawn fails or the agent reports an error: run `pp_run timeline end other "${PHASE_NUMBER}" failed || true`, then log `Codebase drift
84
85
  auto-remap failed: {reason}` and continue to `verify_phase_goal`. The phase
85
86
  is NOT failed by a remap failure.
86
87
 
87
- If the remap succeeds: log `Codebase drift auto-remap completed for paths:
88
+ If the remap succeeds: run `pp_run timeline end other "${PHASE_NUMBER}" complete || true`, then log `Codebase drift auto-remap completed for paths:
88
89
  {affected_paths}` and continue to `verify_phase_goal`.
89
90
 
90
91
  The two relevant config keys (continue on error / failure if either is invalid):
@@ -87,7 +87,7 @@ if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
87
87
  AGENT_SKILLS=$(pp_run query agent-skills pp-executor)
88
88
  ```
89
89
 
90
- Parse JSON for: `executor_model`, `verifier_model`, `commit_docs`, `parallelization`, `branching_strategy`, `branch_name`, `phase_found`, `phase_dir`, `phase_number`, `phase_name`, `phase_slug`, `plans`, `incomplete_plans`, `plan_count`, `incomplete_count`, `state_exists`, `roadmap_exists`, `phase_req_ids`, `response_language`.
90
+ Parse JSON for: `executor_model`, `verifier_model`, `commit_docs`, `parallelization`, `branching_strategy`, `branch_name`, `phase_found`, `phase_dir`, `phase_number`, `phase_name`, `phase_slug`, `plans`, `incomplete_plans`, `plan_count`, `incomplete_count`, `state_exists`, `roadmap_exists`, `phase_req_ids`, `verifier_depth`, `response_language`.
91
91
 
92
92
  **Model resolution:** If `executor_model` is `"inherit"`, omit the `model=` parameter from all `Agent()` calls — do NOT pass `model="inherit"` to Agent. Omitting the `model=` parameter causes Claude Code to inherit the current orchestrator model automatically. Only set `model=` when `executor_model` is an explicit model name (e.g., `"claude-sonnet-4-6"`, `"claude-opus-4-7"`).
93
93
 
@@ -535,6 +535,8 @@ increases monotonically across waves. `{status}` is `complete` (success),
535
535
 
536
536
  `[checkpoint] phase {PHASE_NUMBER} wave {N}/{M} plan {plan_id} starting ({P}/{Q} plans done)`
537
537
 
538
+ Then `pp_run timeline start executor "${PHASE_NUMBER}" --plan {plan_id} --model "{executor_model}" || true` — rules: `pp-core/references/phase-timeline.md`
539
+
538
540
  Pass paths only — executors read files themselves with their fresh context window.
539
541
  For 200k models, this keeps orchestrator context lean (~10-15%).
540
542
  For 1M+ models (Opus 4.6, Sonnet 4.6), richer context can be passed directly.
@@ -699,6 +701,8 @@ increases monotonically across waves. `{status}` is `complete` (success),
699
701
  [checkpoint] phase {PHASE_NUMBER} wave {N}/{M} plan {plan_id} checkpoint ({P}/{Q} plans done)
700
702
  ```
701
703
 
704
+ Run `pp_run timeline end executor "${PHASE_NUMBER}" {outcome} --plan {plan_id} || true` per line (`{outcome}`: that word, or `fallback` if the spot-check confirmed it). Background worktree executors end here, not after `Agent()`.
705
+
702
706
  **Completion signal fallback (Copilot and runtimes where Agent() may not return):**
703
707
 
704
708
  If a spawned agent does not return a completion signal but appears to have finished
@@ -1070,7 +1074,7 @@ After all waves:
1070
1074
  **Security gate check:**
1071
1075
  ```bash
1072
1076
  [ -z "${ZSH_VERSION:-}" ] || emulate -R sh
1073
- VERIFY_POST_HOOKS_JSON=$(pp_run loop render-hooks verify:post --raw)
1077
+ VERIFY_POST_HOOKS_JSON=$(pp_run loop render-hooks verify:post --raw --phase "${PHASE_NUMBER}")
1074
1078
  printf '%s' "$VERIFY_POST_HOOKS_JSON" | node -e "let h=[];try{h=JSON.parse(require('fs').readFileSync(0,'utf-8')).activeHooks||[]}catch{}console.log('VERIFY_POST_HOOKS_JSON='+JSON.stringify({activeHooks:h}))"
1075
1079
  SECURITY_FILE=$(ls "${PHASE_DIR}"/*-SECURITY.md 2>/dev/null | head -1)
1076
1080
  ```
@@ -1298,6 +1302,7 @@ Verify phase achieved its GOAL, not just completed tasks.
1298
1302
 
1299
1303
  ```bash
1300
1304
  VERIFIER_SKILLS=$(pp_run query agent-skills pp-verifier)
1305
+ pp_run timeline start verifier "${PHASE_NUMBER}" || true
1301
1306
  ```
1302
1307
 
1303
1308
  ```
@@ -1307,6 +1312,7 @@ Agent(
1307
1312
  Phase directory: {phase_dir}
1308
1313
  Phase goal: {goal from ROADMAP.md}
1309
1314
  Phase requirement IDs: {phase_req_ids}
1315
+ Verifier depth: {verifier_depth}
1310
1316
  Check must_haves against actual codebase.
1311
1317
  Cross-reference requirement IDs from PLAN frontmatter against REQUIREMENTS.md — every ID MUST be accounted for.
1312
1318
  Create VERIFICATION.md.
@@ -1336,6 +1342,7 @@ VERIFICATION=$(pp_run query verification.status "$PHASE_DIR" 2>/dev/null)
1336
1342
  STATUS=$(printf '%s' "$VERIFICATION" | jq -r '.status' 2>/dev/null || echo "")
1337
1343
  NEXT_ACTION=$(printf '%s' "$VERIFICATION" | jq -r '.next_action' 2>/dev/null || echo "")
1338
1344
  NEXT_COMMAND=$(printf '%s' "$VERIFICATION" | jq -r '.next_command' 2>/dev/null || echo "")
1345
+ pp_run timeline end verifier "${PHASE_NUMBER}" "${STATUS:-failed}" || true
1339
1346
  ```
1340
1347
 
1341
1348
  Route on `$STATUS`: if `passed`, proceed to update_roadmap. Otherwise keep the phase pending — present `$NEXT_ACTION` to the user and, when `$NEXT_COMMAND` is non-empty, show it as the next command to run. The query covers all cases including missing files (`missing`) and unexpected values (`unknown`), so no per-status arm needs to be listed here.
@@ -1466,7 +1473,8 @@ These items are tracked and will appear in `/pp:progress` and `/pp:audit-uat`.
1466
1473
 
1467
1474
  ```bash
1468
1475
  [ -z "${ZSH_VERSION:-}" ] || emulate -R sh
1469
- pp_run query commit "docs(planning): complete phase execution" --phase "{X}" --files .planning/ROADMAP.md .planning/STATE.md .planning/REQUIREMENTS.md {phase_dir}/*-VERIFICATION.md
1476
+ pp_run query commit "docs(planning): complete phase execution" --phase "{X}" --files .planning/ROADMAP.md .planning/STATE.md .planning/REQUIREMENTS.md {phase_dir}/*-VERIFICATION.md {phase_dir}/*-TIMELINE.jsonl
1477
+ pp_run timeline summary "${PHASE_NUMBER}" --banner || true
1470
1478
  ```
1471
1479
  </step>
1472
1480
 
@@ -1567,6 +1575,7 @@ After verification passes and roadmap is updated, return completion status to pa
1567
1575
  Phase: ${PHASE_NUMBER} - ${PHASE_NAME}
1568
1576
  Plans: ${completed_count}/${total_count}
1569
1577
  Verification: {Passed | Gaps Found}
1578
+ {timeline banner, verbatim}
1570
1579
 
1571
1580
  [Include aggregate_results output]
1572
1581
  ```
@@ -1613,6 +1622,7 @@ If CONTEXT.md does **not** exist for the next phase, present:
1613
1622
 
1614
1623
  ```
1615
1624
  ## ✓ Phase {X}: {Name} Complete
1625
+ {timeline banner, verbatim}
1616
1626
 
1617
1627
  /pp:progress ${GSD_WS} — see updated roadmap
1618
1628
  /pp:discuss-phase {next} ${GSD_WS} — start here: discuss next phase before planning ← recommended
@@ -1624,6 +1634,7 @@ If CONTEXT.md **exists** for the next phase, present:
1624
1634
 
1625
1635
  ```
1626
1636
  ## ✓ Phase {X}: {Name} Complete
1637
+ {timeline banner, verbatim}
1627
1638
 
1628
1639
  /pp:progress ${GSD_WS} — see updated roadmap
1629
1640
  /pp:plan-phase {next} ${GSD_WS} — start here: plan next phase (CONTEXT.md already present) ← recommended
@@ -35,6 +35,7 @@ Display:
35
35
  Spawn the planner in **outline-only** mode — it must write only the outline manifest, not any
36
36
  PLAN.md files:
37
37
 
38
+ `pp_run timeline start planner "$PHASE" || true`
38
39
  ```javascript
39
40
  Agent(
40
41
  prompt="{same planning_context as step 8, plus:}
@@ -56,6 +57,7 @@ Agent(
56
57
  > **ORCHESTRATOR RULE — ALL RUNTIMES**: After calling Agent() above, stop working on this task immediately. Do not read more files, edit code, or run tests related to this task while the subagent is active. Wait for the subagent to return its result. This prevents duplicate work, conflicting edits, and wasted context. Only resume when the subagent result is available.
57
58
 
58
59
  Handle return:
60
+ `pp_run timeline end planner "$PHASE" {outcome} || true`, `complete` on `## OUTLINE COMPLETE`, otherwise `failed`.
59
61
  - **`## OUTLINE COMPLETE`:** Read `PLAN-OUTLINE.md`, extract plan list. Continue to 8.5.2.
60
62
  - **Any other return or empty:** Display error. Offer: 1) Retry outline, 2) Stop. (`--unattended`: U-P15 — retry once, then Stop.)
61
63
 
@@ -80,6 +82,7 @@ For each plan entry extracted from `PLAN-OUTLINE.md`:
80
82
  ```
81
83
 
82
84
  3. Spawn the planner in **single-plan** mode — it must write exactly one PLAN.md file:
85
+ `pp_run timeline start planner "$PHASE" || true`
83
86
  ```javascript
84
87
  Agent(
85
88
  prompt="{same planning_context as step 8, plus:}
@@ -99,7 +102,7 @@ For each plan entry extracted from `PLAN-OUTLINE.md`:
99
102
 
100
103
  > **ORCHESTRATOR RULE — ALL RUNTIMES**: After calling Agent() above, stop working on this task immediately. Do not read more files, edit code, or run tests related to this task while the subagent is active. Wait for the subagent to return its result. This prevents duplicate work, conflicting edits, and wasted context. Only resume when the subagent result is available.
101
104
 
102
- 4. **Verify disk:** Check `${PHASE_DIR}/{plan_id}-PLAN.md` exists. If missing: offer 1) Retry, 2) Stop.
105
+ 4. **Verify disk:** Check `${PHASE_DIR}/{plan_id}-PLAN.md` exists. Run `pp_run timeline end planner "$PHASE" {outcome} || true` with `complete` when it exists, otherwise `failed`. If missing: offer 1) Retry, 2) Stop.
103
106
 
104
107
  5. **Commit per-plan:**
105
108
  ```bash
@@ -107,5 +110,6 @@ _PP_SHIM_NAME="pp-tools.cjs"; _pp_root() { ( for _v in $(env | sed -n 's/^\(GIT_
107
110
  pp_run query commit "docs(planning): plan ${plan_id} (chunked)" --phase "${PADDED_PHASE}" --files "${PHASE_DIR}/${plan_id}-PLAN.md"
108
111
  ```
109
112
 
110
- After all N plans are written and committed, treat this as `## PLANNING COMPLETE` and continue
111
- to step 9.
113
+ After all N plans are written and committed, treat this as `## PLANNING COMPLETE` and continue to
114
+ step 9's return handling, skipping step 9's planner `end` line — every chunked planner run was
115
+ already closed in 8.5.1 (outline) and 8.5.2 step 4 (per plan), so a third `end` has nothing to close.
@@ -0,0 +1,104 @@
1
+ # Plan-phase step — PRD express path
2
+
3
+ > Read and executed from `workflows/plan-phase.md` §3.5 when `--prd` is given. Writes CONTEXT.md from the PRD, so step 4 is skipped; planning continues at step 4.6.
4
+
5
+ **If `--prd <filepath>` provided:**
6
+
7
+ 1. Read the PRD file:
8
+ ```bash
9
+ PRD_CONTENT=$(cat "$PRD_FILE" 2>/dev/null)
10
+ if [ -z "$PRD_CONTENT" ]; then
11
+ echo "Error: PRD file not found: $PRD_FILE"
12
+ exit 1
13
+ fi
14
+ ```
15
+
16
+ 2. Display banner:
17
+ ```
18
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
19
+ pp-core ► PRD EXPRESS PATH
20
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
21
+
22
+ Using PRD: {PRD_FILE}
23
+ Generating CONTEXT.md from requirements...
24
+ ```
25
+
26
+ 3. Parse the PRD content and generate CONTEXT.md. The orchestrator should:
27
+ - Extract all requirements, user stories, acceptance criteria, and constraints from the PRD
28
+ - Map each to a locked decision (everything in the PRD is treated as a locked decision)
29
+ - Identify any areas the PRD doesn't cover and mark as "Claude's Discretion"
30
+ - **Extract canonical refs** from ROADMAP.md for this phase, plus any specs/ADRs referenced in the PRD — expand to full file paths (MANDATORY)
31
+ - Create CONTEXT.md in the phase directory
32
+
33
+ 4. Write CONTEXT.md:
34
+ ```markdown
35
+ # Phase [X]: [Name] - Context
36
+
37
+ **Gathered:** [date]
38
+ **Status:** Ready for planning
39
+ **Source:** PRD Express Path ({PRD_FILE})
40
+
41
+ <domain>
42
+ ## Phase Boundary
43
+
44
+ [Extracted from PRD — what this phase delivers]
45
+
46
+ </domain>
47
+
48
+ <decisions>
49
+ ## Implementation Decisions
50
+
51
+ {For each requirement/story/criterion in the PRD:}
52
+ ### [Category derived from content]
53
+ - [Requirement as locked decision]
54
+
55
+ ### Claude's Discretion
56
+ [Areas not covered by PRD — implementation details, technical choices]
57
+
58
+ </decisions>
59
+
60
+ <canonical_refs>
61
+ ## Canonical References
62
+
63
+ **Downstream agents MUST read these before planning or implementing.**
64
+
65
+ [MANDATORY. Extract from ROADMAP.md and any docs referenced in the PRD.
66
+ Use full relative paths. Group by topic area.]
67
+
68
+ ### [Topic area]
69
+ - `path/to/spec-or-adr.md` — [What it decides/defines]
70
+
71
+ [If no external specs: "No external specs — requirements fully captured in decisions above"]
72
+
73
+ </canonical_refs>
74
+
75
+ <specifics>
76
+ ## Specific Ideas
77
+
78
+ [Any specific references, examples, or concrete requirements from PRD]
79
+
80
+ </specifics>
81
+
82
+ <deferred>
83
+ ## Deferred Ideas
84
+
85
+ [Items in PRD explicitly marked as future/v2/out-of-scope]
86
+ [If none: "None — PRD covers phase scope"]
87
+
88
+ </deferred>
89
+
90
+ ---
91
+
92
+ *Phase: XX-name*
93
+ *Context gathered: [date] via PRD Express Path*
94
+ ```
95
+
96
+ 5. Commit:
97
+ ```bash
98
+ _PP_SHIM_NAME="pp-tools.cjs"; _pp_root() { ( for _v in $(env | sed -n 's/^\(GIT_[A-Za-z0-9_]*\)=.*/\1/p'); do unset "$_v"; done; _c=$(git rev-parse --git-common-dir 2>/dev/null); if [ "${_c##*/}" = .git ] && cd "$_c/.." 2>/dev/null; then pwd -P; else git rev-parse --show-toplevel 2>/dev/null || pwd; fi ); }; pp_run() { case "$PP_TOOLS" in *.cjs) node "$PP_TOOLS" "$@";; *) "$PP_TOOLS" "$@";; esac; }; _PP_RUNTIME_ROOT="${RUNTIME_DIR:-$(_pp_root)}"; PP_TOOLS="${_PP_RUNTIME_ROOT}/pp-core/bin/${_PP_SHIM_NAME}"; if [ -f "$PP_TOOLS" ]; then :; elif [ -f "${_PP_RUNTIME_ROOT}/.claude/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${_PP_RUNTIME_ROOT}/.claude/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${_PP_RUNTIME_ROOT}/.codex/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${_PP_RUNTIME_ROOT}/.codex/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "$HOME/.claude/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="$HOME/.claude/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${HERMES_HOME:-$HOME/.hermes}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${CODEX_HOME:-$HOME/.codex}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${CODEX_HOME:-$HOME/.codex}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/pp-core/bin/${_PP_SHIM_NAME}"; elif command -v pp-tools >/dev/null 2>&1; then PP_TOOLS="$(command -v pp-tools)"; else echo "ERROR: pp-tools.cjs not found at $PP_TOOLS and pp-tools is not on PATH. Run: npx -y @pareto4/pp-core@latest --claude --local" >&2; exit 1; fi; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${PP_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${PP_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
99
+ pp_run query commit "docs(planning): generate context from PRD" --phase "${padded_phase}" --files "${phase_dir}/${padded_phase}-CONTEXT.md"
100
+ ```
101
+
102
+ 6. Set `context_content` to the generated CONTEXT.md content and continue to step 4.6 (Classify Ceremony).
103
+
104
+ **Effect:** This completely bypasses step 4 (Load CONTEXT.md) since we just created it. The rest of the workflow (research, planning, verification) proceeds normally with the PRD-derived context.