@pareto4/pp-core 1.13.0 → 1.17.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (201) hide show
  1. package/.claude-plugin/plugin.json +3 -3
  2. package/README.md +26 -9
  3. package/agents/pp-code-reviewer.md +2 -1
  4. package/agents/pp-debug-session-manager.md +9 -6
  5. package/agents/pp-executor.md +10 -13
  6. package/agents/pp-plan-checker.md +4 -4
  7. package/agents/pp-planner.md +23 -9
  8. package/agents/pp-vault-reviewer.md +244 -21
  9. package/agents/pp-verifier.md +7 -3
  10. package/bin/install.js +10 -3
  11. package/commands/pp-execute-phase.md +1 -3
  12. package/commands/pp-ns-review.md +1 -1
  13. package/commands/pp-plan-phase.md +1 -2
  14. package/commands/pp-vault-review.md +5 -3
  15. package/commands/pp-verify-work.md +1 -2
  16. package/gemini-extension.json +1 -1
  17. package/hooks/dist/pp-check-update.js +0 -0
  18. package/hooks/dist/pp-cursor-session-start.js +1 -1
  19. package/hooks/dist/pp-statusline.js +1 -1
  20. package/hooks/dist/pp-update-banner.js +0 -0
  21. package/hooks/pp-check-update.js +0 -0
  22. package/hooks/pp-cursor-session-start.js +1 -1
  23. package/hooks/pp-statusline.js +1 -1
  24. package/hooks/pp-update-banner.js +0 -0
  25. package/package.json +14 -19
  26. package/pareto-skills/_shared/create-project-board.sh +0 -0
  27. package/pareto-skills/pp-project-setup/SKILL.md +1 -1
  28. package/pp-core/bin/check-latest-version.cjs +0 -0
  29. package/pp-core/bin/detect-build-commands.sh +2 -2
  30. package/pp-core/bin/lib/capability-activation.cjs +83 -9
  31. package/pp-core/bin/lib/capability-registry.cjs +396 -82
  32. package/pp-core/bin/lib/capability-state.cjs +160 -4
  33. package/pp-core/bin/lib/capability-writer.cjs +19 -5
  34. package/pp-core/bin/lib/check-command-router.cjs +3334 -946
  35. package/pp-core/bin/lib/clusters.cjs +3 -0
  36. package/pp-core/bin/lib/command-aliases.cjs +0 -35
  37. package/pp-core/bin/lib/commands.cjs +9 -4
  38. package/pp-core/bin/lib/compose-bindings.cjs +251 -88
  39. package/pp-core/bin/lib/config-loader.cjs +7 -103
  40. package/pp-core/bin/lib/config-schema.cjs +1 -1
  41. package/pp-core/bin/lib/config.cjs +154 -62
  42. package/pp-core/bin/lib/decisions.cjs +4 -4
  43. package/pp-core/bin/lib/discovery-vault-check.cjs +246 -0
  44. package/pp-core/bin/lib/frontmatter.cjs +1 -38
  45. package/pp-core/bin/lib/git-base-branch.cjs +13 -11
  46. package/pp-core/bin/lib/git-provider.cjs +4 -4
  47. package/pp-core/bin/lib/init-command-router.cjs +5 -8
  48. package/pp-core/bin/lib/init-contract.cjs +648 -738
  49. package/pp-core/bin/lib/init-interactive.cjs +76 -4
  50. package/pp-core/bin/lib/init.cjs +24 -51
  51. package/pp-core/bin/lib/install-profiles.cjs +1 -1
  52. package/pp-core/bin/lib/io.cjs +8 -0
  53. package/pp-core/bin/lib/loop-resolver.cjs +189 -129
  54. package/pp-core/bin/lib/org-profile.cjs +469 -152
  55. package/pp-core/bin/lib/package-identity.cjs +3 -3
  56. package/pp-core/bin/lib/phase-command-router.cjs +1 -29
  57. package/pp-core/bin/lib/phase-scope.cjs +1 -1
  58. package/pp-core/bin/lib/phase.cjs +11 -253
  59. package/pp-core/bin/lib/platform-bindings.cjs +40 -19
  60. package/pp-core/bin/lib/practices-review-check.cjs +15 -9
  61. package/pp-core/bin/lib/profile-output.cjs +1 -1
  62. package/pp-core/bin/lib/shell-command-projection.cjs +104 -1
  63. package/pp-core/bin/lib/state.cjs +11 -85
  64. package/pp-core/bin/lib/template.cjs +1 -1
  65. package/pp-core/bin/lib/vault-audit.cjs +650 -126
  66. package/pp-core/bin/lib/vault-command-router.cjs +2604 -367
  67. package/pp-core/bin/lib/vault-coordinates.cjs +13 -4
  68. package/pp-core/bin/lib/vault-index.cjs +699 -0
  69. package/pp-core/bin/lib/vault-mode.cjs +235 -0
  70. package/pp-core/bin/lib/vault-practices.cjs +139 -72
  71. package/pp-core/bin/lib/vault-review.cjs +194 -451
  72. package/pp-core/bin/lib/vault-seal.cjs +1936 -0
  73. package/pp-core/bin/lib/vault-standards.cjs +1505 -0
  74. package/pp-core/bin/lib/vault-trace.cjs +607 -254
  75. package/pp-core/bin/lib/vault-update.cjs +9 -33
  76. package/pp-core/bin/lib/vault-write-back.cjs +70 -42
  77. package/pp-core/bin/lib/vault.cjs +2734 -920
  78. package/pp-core/bin/lib/verify.cjs +0 -11
  79. package/pp-core/bin/portable-timeout.sh +1 -1
  80. package/pp-core/bin/pp-tools.cjs +184 -54
  81. package/pp-core/bin/shared/config-defaults.manifest.json +0 -7
  82. package/pp-core/bin/shared/config-schema.manifest.json +0 -3
  83. package/pp-core/bin/shared/model-catalog.json +2 -2
  84. package/pp-core/bin/verify-reapply-patches.cjs +0 -0
  85. package/pp-core/org-profile.json +26 -0
  86. package/pp-core/references/autonomous-best-effort.md +8 -7
  87. package/pp-core/references/loop-hook-dispatch.md +0 -25
  88. package/pp-core/references/planner-gap-closure.md +0 -1
  89. package/pp-core/references/planner-tdd.md +0 -1
  90. package/pp-core/references/planner-vault-practices.md +17 -34
  91. package/pp-core/references/planner-vault-standards.md +320 -0
  92. package/pp-core/references/planning-config.md +0 -8
  93. package/pp-core/references/practice-arithmetic-contract.md +121 -0
  94. package/pp-core/references/tdd.md +0 -1
  95. package/pp-core/references/verifier-practice-verdicts.md +263 -0
  96. package/pp-core/templates/README.md +1 -0
  97. package/pp-core/templates/VAULT-REVIEW.md +202 -0
  98. package/pp-core/templates/phase-prompt.md +2 -6
  99. package/pp-core/templates/practices/README.md +20 -23
  100. package/pp-core/workflows/_runtime-launcher.snippet.sh +0 -0
  101. package/pp-core/workflows/add-tests.md +55 -4
  102. package/pp-core/workflows/audit-fix.md +63 -2
  103. package/pp-core/workflows/audit-milestone.md +1 -1
  104. package/pp-core/workflows/autonomous.md +85 -11
  105. package/pp-core/workflows/check-todos.md +13 -2
  106. package/pp-core/workflows/code-review-fix.md +59 -3
  107. package/pp-core/workflows/code-review.md +10 -1
  108. package/pp-core/workflows/complete-milestone.md +1 -1
  109. package/pp-core/workflows/debug.md +54 -4
  110. package/pp-core/workflows/discovery-sprint/prefill.md +19 -7
  111. package/pp-core/workflows/discovery-sprint/prototype.md +46 -49
  112. package/pp-core/workflows/discovery-sprint/session-13-gates.md +2 -0
  113. package/pp-core/workflows/discovery-sprint/sessions/01-kick-off.md +1 -3
  114. package/pp-core/workflows/discovery-sprint/sessions/02-as-is.md +1 -3
  115. package/pp-core/workflows/discovery-sprint/sessions/03-to-be.md +1 -3
  116. package/pp-core/workflows/discovery-sprint/sessions/04-ux-design.md +20 -24
  117. package/pp-core/workflows/discovery-sprint/sessions/05-architecture.md +1 -3
  118. package/pp-core/workflows/discovery-sprint/sessions/06-security.md +1 -3
  119. package/pp-core/workflows/discovery-sprint/sessions/07-identity.md +1 -3
  120. package/pp-core/workflows/discovery-sprint/sessions/08-integration.md +1 -3
  121. package/pp-core/workflows/discovery-sprint/sessions/09-qa.md +1 -3
  122. package/pp-core/workflows/discovery-sprint/sessions/10-infrastructure.md +1 -6
  123. package/pp-core/workflows/discovery-sprint/sessions/11-roadmap.md +1 -3
  124. package/pp-core/workflows/discovery-sprint/sessions/12-delivery-model.md +1 -3
  125. package/pp-core/workflows/discovery-sprint/sessions/13-wrap-up.md +1 -3
  126. package/pp-core/workflows/discovery-sprint/templates/context-template.md +1 -1
  127. package/pp-core/workflows/discovery-sprint/vault-context.md +213 -0
  128. package/pp-core/workflows/discovery-sprint.md +7 -3
  129. package/pp-core/workflows/discovery-to-build/steps/architecture-deliverables.md +4 -4
  130. package/pp-core/workflows/discovery-to-build/templates/pp-handoff.md +43 -1
  131. package/pp-core/workflows/discovery-to-build.md +10 -2
  132. package/pp-core/workflows/discuss-phase.md +3 -10
  133. package/pp-core/workflows/execute-phase/steps/check-query-gate.md +329 -0
  134. package/pp-core/workflows/execute-phase/steps/client-context-gate.md +3 -2
  135. package/pp-core/workflows/execute-phase/steps/cross-ai-delegation.md +1 -1
  136. package/pp-core/workflows/execute-phase/steps/execute-pre-dispatch.md +98 -0
  137. package/pp-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +94 -0
  138. package/pp-core/workflows/execute-phase/steps/post-merge-gate.md +6 -8
  139. package/pp-core/workflows/execute-phase/steps/vault-fetch.md +115 -14
  140. package/pp-core/workflows/execute-phase/steps/vault-load-gate.md +322 -20
  141. package/pp-core/workflows/execute-phase/steps/verifier-practice-payload.md +165 -0
  142. package/pp-core/workflows/execute-phase/steps/verify-post-gate.md +163 -0
  143. package/pp-core/workflows/execute-phase.md +155 -128
  144. package/pp-core/workflows/execute-plan.md +56 -6
  145. package/pp-core/workflows/fast.md +53 -4
  146. package/pp-core/workflows/help/modes/full.md +3 -3
  147. package/pp-core/workflows/init/steps/client-context-capture.md +101 -0
  148. package/pp-core/workflows/init/steps/gitlab-provisioning.md +130 -0
  149. package/pp-core/workflows/init/steps/project-md-deferral-narration.md +66 -0
  150. package/pp-core/workflows/init/steps/reachability-outcome.md +70 -0
  151. package/pp-core/workflows/init.md +232 -272
  152. package/pp-core/workflows/new-project.md +31 -23
  153. package/pp-core/workflows/plan-phase.md +98 -45
  154. package/pp-core/workflows/quick.md +117 -3
  155. package/pp-core/workflows/secure-phase.md +1 -1
  156. package/pp-core/workflows/ship.md +57 -28
  157. package/pp-core/workflows/undo.md +1 -1
  158. package/pp-core/workflows/update.md +3 -3
  159. package/pp-core/workflows/validate-phase.md +58 -3
  160. package/pp-core/workflows/vault-review.md +282 -22
  161. package/pp-core/workflows/verify-work.md +12 -14
  162. package/scripts/changeset/cli.cjs +6 -5
  163. package/scripts/changeset/lint.cjs +0 -0
  164. package/scripts/changeset/new.cjs +33 -50
  165. package/scripts/dev-env.cjs +25 -85
  166. package/scripts/gen-capability-registry.cjs +199 -11
  167. package/scripts/gen-practice-arithmetic-contract.cjs +412 -0
  168. package/scripts/lib/allowlist-ratchet.cjs +1 -103
  169. package/scripts/lint-allow-test-rule-refs.allowlist.json +0 -2
  170. package/scripts/lint-docs-required.cjs +0 -0
  171. package/scripts/lint-pr-check-project-dir.cjs +1 -3
  172. package/scripts/lint-test-file-count.allowlist.json +12 -10
  173. package/scripts/practice-arithmetic-contract.allow.json +35 -0
  174. package/scripts/prompt-injection-scan.sh +22 -17
  175. package/scripts/record-vault-mode-golden.cjs +89 -0
  176. package/scripts/release-channel.cjs +0 -0
  177. package/scripts/sync-manifest-versions.cjs +5 -4
  178. package/scripts/sync-rulesets.sh +1 -1
  179. package/scripts/update-size-baseline.cjs +30 -274
  180. package/scripts/vault-calibrate.cjs +996 -0
  181. package/scripts/verify-opengsd-sweep.cjs +3 -5
  182. package/scripts/workflow-size.cjs +9 -61
  183. package/pp-core/bin/lib/dispatch-plan.cjs +0 -483
  184. package/pp-core/bin/lib/install-run.cjs +0 -536
  185. package/pp-core/bin/lib/loop-gates.cjs +0 -213
  186. package/pp-core/bin/lib/phase-manifest.cjs +0 -527
  187. package/pp-core/bin/lib/phase-tier.cjs +0 -480
  188. package/pp-core/bin/lib/plan-graph.cjs +0 -368
  189. package/pp-core/references/executor-vault-trace.md +0 -47
  190. package/pp-core/references/planner-plan-graph.md +0 -66
  191. package/pp-core/workflows/discovery-sprint/vault-fetch.md +0 -37
  192. package/pp-core/workflows/execute-phase/steps/checkpoint-handling.md +0 -45
  193. package/pp-core/workflows/execute-phase/steps/install-run.md +0 -29
  194. package/pp-core/workflows/execute-phase/steps/phase-close-rebaseline.md +0 -23
  195. package/pp-core/workflows/execute-phase/steps/serial-group-dispatch.md +0 -73
  196. package/pp-core/workflows/execute-phase/steps/vault-door-gate.md +0 -19
  197. package/pp-core/workflows/plan-phase/steps/phase-tier.md +0 -43
  198. package/scripts/lib/size-budget.cjs +0 -311
  199. package/scripts/lint-neutrality-names.cjs +0 -694
  200. package/scripts/lint-org-coordinates.allowlist.json +0 -35
  201. package/scripts/lint-org-coordinates.cjs +0 -426
@@ -70,21 +70,9 @@ _PP_SHIM_NAME="pp-tools.cjs"; _pp_root() { ( for _v in $(env | sed -n 's/^\(GIT_
70
70
  # Gate on presence via reader — absent file → {present:false} exit 0 (inert, step skipped, no jq)
71
71
  if [ "$(pp_run query platform-bindings --pick present 2>/dev/null)" = "true" ]; then
72
72
  PINNED=$(pp_run query platform-bindings --pick platform.tag)
73
- REPO=$(pp_run query platform-bindings --pick platform.repo)
74
- HOST_FROM_BINDINGS=$(pp_run query platform-bindings --pick platform.host 2>/dev/null)
75
- PLATFORM_HOST="${HOST_FROM_BINDINGS:-${HOST:-gitlab.com}}"
76
- PLATFORM_REPO_URL="https://${PLATFORM_HOST}/${REPO}.git"
77
- # Resolve latest STABLE tag from remote — never hard-block if offline (empty LATEST → skip drift check)
78
- # Keep --refs to strip ^{} annotated-tag rows; --sort=-v:refname sorts server-side
79
- # Stable filter fixes prerelease mis-ordering: git ranks v1.4.0-rc.1 after v1.4.0 (wrong by semver)
80
- # semver-compare.cjs is the single blessed source for comparison — do NOT reimplement
81
- REMOTE_TAGS=$(git ls-remote --tags --refs --sort=-v:refname "$PLATFORM_REPO_URL" 'v*' 2>/dev/null | sed 's#.*/##')
82
- LATEST=$(printf '%s\n' "$REMOTE_TAGS" | node -e "
83
- const sc = require('${PP_TOOLS%/*}/lib/semver-compare.cjs');
84
- const lines = require('fs').readFileSync(0, 'utf-8').split('\n').map(s=>s.trim()).filter(Boolean);
85
- const stable = lines.filter(sc.isStableTripletSemver).sort(sc.compareSemverCore);
86
- process.stdout.write((stable[stable.length-1] || '') + '\n');
87
- " 2>/dev/null)
73
+ # The owner is compose-bindings resolveLatestPlatformTag — the same lookup pp-tools init and check ship.platform-drift use — so the URL is never composed in shell.
74
+ # Tagless, offline and unreachable all print as empty: latest=<unresolved> and the drift comparison is skipped (never hard-block).
75
+ LATEST=$(pp_run query platform-latest-tag --pick latest 2>/dev/null)
88
76
  echo "platform preflight: pinned=${PINNED:-<none>} latest=${LATEST:-<unresolved>}"
89
77
  if [ -n "$LATEST" ] && [ -n "$PINNED" ] && [ "$PINNED" != "$LATEST" ]; then
90
78
  echo "PP_PLATFORM_DRIFT=1"
@@ -102,15 +90,10 @@ choose:
102
90
  - **Proceed on the older pin** — continue knowingly; record the deferral in
103
91
  `PROJECT.md` so it's a conscious decision, not silent drift.
104
92
 
105
- If `LATEST` was unresolved (offline / no network), note it and proceed — never
106
- hard-block initiation on an unreachable remote. For non-Pareto repos the block is
93
+ If `LATEST` was unresolved (no stable release tag, offline, or an unreachable
94
+ remote), note it and proceed — never hard-block initiation on an unresolved tag. For non-Pareto repos the block is
107
95
  a no-op and this step is skipped entirely.
108
96
 
109
- When the bindings file is present, also run `pp_run query init-contract --arm-vault`
110
- (sets `vault.enabled` and `vault.required` to true, each only where absent; `ok:false` = no client vault bound — run
111
- `/pp-init`) and `pp_run vault probe` (one reachability line; empty when the vault is
112
- disabled). Show both outputs verbatim; neither blocks.
113
-
114
97
  ## 1. Setup
115
98
 
116
99
  **MANDATORY FIRST STEP — Execute these checks before ANY user interaction:**
@@ -149,7 +132,7 @@ with "agent type not found" if `required_agents_installed` is false. Run the ins
149
132
 
150
133
  Proceeding without research subagents — roadmap will be generated inline.
151
134
  ```
152
- Skip Steps 6–7 (parallel research and synthesis) and proceed directly to roadmap creation in Step 8.
135
+ Continue through Steps 2–5.5 as normal. When you reach Step 6, skip Steps 6–7 (parallel research and synthesis) and go straight to roadmap creation in Step 8.
153
136
 
154
137
  **Detect runtime and set instruction file name:**
155
138
 
@@ -453,6 +436,11 @@ Loop until "Create PROJECT.md" selected.
453
436
 
454
437
  Synthesize all context into `.planning/PROJECT.md` using the template from `templates/project.md`.
455
438
 
439
+ **Init deferral (D-07-03, OD-2).** Before writing the file, Read
440
+ `pp-core/workflows/init/steps/project-md-deferral-narration.md` and follow it — the single
441
+ rendering of the reachability-proof deferral `/pp-init` recorded, silent when none is on
442
+ file. Do not restate its logic here.
443
+
456
444
  **For greenfield projects:**
457
445
 
458
446
  Initialize requirements as hypotheses:
@@ -887,6 +875,26 @@ Config changes are saved locally — no commit needed since `commit_docs` is `fa
887
875
 
888
876
  Use models from init: `researcher_model`, `synthesizer_model`, `roadmapper_model`.
889
877
 
878
+ **Arm the client vault (both modes, every route).**
879
+ It sits here, not in Step 6, because Step 1's agents-not-installed note skips Steps 6–7, and this step must run on that route too.
880
+
881
+ ```bash
882
+ ARM=$(pp_run query init-contract --arm-vault); ARM_EXIT=$?
883
+ echo "$ARM"
884
+ ARMED=$(printf '%s' "$ARM" | node -e 'let s="";process.stdin.on("data",d=>{s+=d}).on("end",()=>{try{process.stdout.write(String(JSON.parse(s).armed===true))}catch{process.stdout.write("false")}})')
885
+ if [ "$ARM_EXIT" -eq 0 ] && [ "$ARMED" = "true" ]; then
886
+ if [ -f .platform-bindings.json ]; then
887
+ pp_run query commit "chore: arm client vault from org profile" --files .planning/config.json .platform-bindings.json
888
+ else
889
+ pp_run query commit "chore: arm client vault from org profile" --files .planning/config.json
890
+ fi
891
+ fi
892
+ ```
893
+
894
+ - `ok: false` (or `ARM_EXIT` is non-zero): **STOP.** Print the engine's `message` verbatim and tell the user to run `/pp-init` before continuing. Do not continue with the vault silently off.
895
+ - `armed: true` (the block above already committed it): tell the user, in one or two short lines, that a client-vault binding was composed from the org profile `<profile.packageName>@<profile.version>` without a prompt, and that setting `vault.enabled: false` in `.planning/config.json` opts out and is honoured by every later arming run.
896
+ - `armed: false`: nothing was written or committed and the vault is NOT armed — do not say it was. If `reason` is `vault_disabled`, tell the user the project's `vault.enabled` setting keeps the vault off. Continue.
897
+
890
898
  ## 6. Research Decision
891
899
 
892
900
  **If auto mode:** Default to "Research first" without asking.
@@ -72,8 +72,7 @@ Load all context in one call (paths only to minimize orchestrator context):
72
72
  ```bash
73
73
  _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
74
74
  GRAN_PARAM=""; if [[ "$ARGUMENTS" =~ (^|[[:space:]])--granularity[[:space:]]+([^[:space:]-][^[:space:]]*) ]]; then GRAN_PARAM="--granularity ${BASH_REMATCH[2]}"; fi
75
- TIER_FLAG=$(printf '%s\n' "$ARGUMENTS" | grep -oE -- '--tier[[:space:]=]+(light|standard|heavy)' | head -1 | sed -E 's/^--tier[[:space:]=]+/--tier=/')
76
- INIT=$(pp_run query init.plan-phase "$PHASE" $GRAN_PARAM $TIER_FLAG)
75
+ INIT=$(pp_run query init.plan-phase "$PHASE" $GRAN_PARAM)
77
76
  if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
78
77
  AGENT_SKILLS_RESEARCHER=$(pp_run query agent-skills pp-phase-researcher)
79
78
  AGENT_SKILLS_PLANNER=$(pp_run query agent-skills pp-planner)
@@ -90,8 +89,6 @@ Parse JSON for: `researcher_model`, `planner_model`, `checker_model`, `research_
90
89
 
91
90
  **If `response_language` is set:** Include `response_language: {value}` in all spawned subagent prompts so any user-facing output stays in the configured language.
92
91
 
93
- **Ceremony tier:** Read and follow `pp-core/workflows/plan-phase/steps/phase-tier.md` now; every `render-hooks` call below passes `--phase "$PHASE" $TIER_FLAG`.
94
-
95
92
  **File paths (for <files_to_read> blocks):** `state_path`, `roadmap_path`, `requirements_path`, `context_path`, `research_path`, `verification_path`, `uat_path`, `reviews_path`. These are null if files don't exist.
96
93
 
97
94
  **If `planning_exists` is false:** Error — run `/pp:init` first.
@@ -141,7 +138,7 @@ The gate fires only on `Complete`. `Executed` and `Needs Review` are not gated
141
138
 
142
139
  ## 2. Parse and Normalize Arguments
143
140
 
144
- Extract from $ARGUMENTS: phase number (integer or decimal like `2.1`), flags (`--research`, `--skip-research`, `--research-phase <N>`, `--gaps`, `--skip-verify`, `--skip-ui`, `--prd <filepath>`, `--ingest <path-or-glob>`, `--ingest-format <auto|nygard|madr|narrative>`, `--reviews`, `--text`, `--bounce`, `--skip-bounce`, `--chunked`, `--mvp`, `--tdd`, `--granularity <coarse|standard|fine>`, `--tier <light|standard|heavy>`, `--force` (override closed-phase gate, see §1.5), `--unattended`).
141
+ Extract from $ARGUMENTS: phase number (integer or decimal like `2.1`), flags (`--research`, `--skip-research`, `--research-phase <N>`, `--gaps`, `--skip-verify`, `--skip-ui`, `--prd <filepath>`, `--ingest <path-or-glob>`, `--ingest-format <auto|nygard|madr|narrative>`, `--reviews`, `--text`, `--bounce`, `--skip-bounce`, `--chunked`, `--mvp`, `--tdd`, `--granularity <coarse|standard|fine>`, `--force` (override closed-phase gate, see §1.5), `--unattended`).
145
142
 
146
143
  **`--research-phase <N>` — research-only mode (#3042 + #3044).** When this flag is present, parse `<N>` as the phase number (overrides any positional phase argument), set `RESEARCH_ONLY=true`, and treat the rest of this workflow as a research-dispatch only — the planner spawn (step 8), plan-checker, verification, gaps, bounce, and post-planning-gaps blocks all skip on `RESEARCH_ONLY`. Use this for cross-phase research, doc review before committing to a planning approach, and correction-without-replanning loops. Replaces the deleted `/pp-research-phase` command.
147
144
 
@@ -430,10 +427,6 @@ If `AI_SPEC_FILE` is non-empty, pass `AI_SPEC_PATH` and `FRAMEWORK_LINE` to the
430
427
 
431
428
  ## 5. Handle Research
432
429
 
433
- ### 5.00. plan:pre vault load — ALWAYS, before the skip rule below
434
-
435
- Every path runs this before any agent: `PLAN_PRE_HOOKS_JSON=${PLAN_PRE_HOOKS_JSON:-$(pp_run loop render-hooks plan:pre --raw --phase "$PHASE" $TIER_FLAG)} || exit 1`. Then Read and execute `pp-core/workflows/execute-phase/steps/vault-fetch.md` (active only for a `capId == "vault"` step hook; writes `.planning/vault/SUMMARY.md`; `onError:skip`). Then, if an active plan:pre gate has `check.query == "vault.status"`, set `VAULT_GATE_POINT=plan` and Read and execute `pp-core/workflows/execute-phase/steps/vault-load-gate.md` — it relays the CLI's vault card and acts on its `action` only (`stop` halts planning in every mode; `confirm` asks, or logs under --auto/--chain). Skip silently when neither hook is active.
436
-
437
430
  **Skip if:** `--gaps` flag or `--skip-research` flag or `--reviews` flag.
438
431
 
439
432
  ### 5.0. Research-Only Modifiers (`--view`, `--research`)
@@ -501,10 +494,32 @@ fi
501
494
  ```bash
502
495
  PHASE_DESC=$(pp_run query roadmap.get-phase "${PHASE}" --pick section)
503
496
  if [ -z "${PLAN_PRE_HOOKS_JSON:-}" ]; then
504
- PLAN_PRE_HOOKS_JSON=$(pp_run loop render-hooks plan:pre --raw --phase "$PHASE" $TIER_FLAG) || exit 1
497
+ PLAN_PRE_HOOKS_JSON=$(pp_run loop render-hooks plan:pre --raw)
505
498
  fi
506
499
  ```
507
500
 
501
+ ### plan:pre vault fetch + summary
502
+
503
+ Reached on **every** path through §5.1 — including the exits that reuse an existing RESEARCH.md or skip research. Those skip the researcher spawn, not this section: the vault load is not conditional on research. Scope: paths through §5.1 only — §5.0's research-only exits leave the workflow before §5.1 runs and are not build paths.
504
+
505
+ **Admission first (D-28, D-05):** a config the gate cannot read renders no vault hook at all, so this runs before the render; without bindings it is inert.
506
+
507
+ ```bash
508
+ VAULT_ADMIT_PRESENT=$(pp_run query platform-bindings --pick present 2>/dev/null || echo true)
509
+ VAULT_ADMIT=inert
510
+ if [ "$VAULT_ADMIT_PRESENT" = true ]; then VAULT_ADMIT=$(pp_run vault admission 2>/dev/null || echo unrecognised); fi
511
+ case "$VAULT_ADMIT" in on) echo "VAULT_ADMIT=on" ;; off|inert) : ;; *) pp_run vault admission --explain; echo "PARETO_GATE_BLOCKED:vault.admission"; exit 1 ;; esac
512
+ ```
513
+
514
+ ```bash
515
+ PLAN_PRE_HOOKS_JSON=${PLAN_PRE_HOOKS_JSON:-$(pp_run loop render-hooks plan:pre --raw)}
516
+ printf '%s' "$PLAN_PRE_HOOKS_JSON" | node -e "let h=[];try{h=JSON.parse(require('fs').readFileSync(0,'utf-8')).activeHooks||[]}catch{}console.log('PLAN_PRE_HOOKS_JSON='+JSON.stringify({activeHooks:h}))"
517
+ ```
518
+
519
+ Set `VAULT_FETCH_HOOKS_JSON=$PLAN_PRE_HOOKS_JSON`.
520
+
521
+ Read and execute `pp-core/workflows/execute-phase/steps/vault-fetch.md` — the bespoke plan:pre vault fetch + summary dispatch (active only for a `capId == "vault"` step hook; produces `.planning/vault/SUMMARY.md` for the researcher/planner read paths; `onError:skip`). Skip silently when none.
522
+
508
523
  ### into:researcher contributions
509
524
 
510
525
  In `PLAN_PRE_HOOKS_JSON`, read active plan:pre entries where `kind == "contribution"` and `into == "researcher"` (in array order). If none, inject nothing. If present, append each entry's `fragment.inline` verbatim to `filled_research_hook_fragment` (or add it as a `<files_to_read>`/context line in the researcher prompt) — typically a pointer to `.planning/vault/SUMMARY.md` (the bounded vault index). Inject verbatim — do not paraphrase.
@@ -584,7 +599,7 @@ test -f "${PHASE_DIR}/${PADDED_PHASE}-VALIDATION.md" && echo "VALIDATION_CREATED
584
599
  > Capability-driven dispatch. Resolves active `plan:pre` hooks via the capability registry; the security hook's `when` condition is evaluated by the registry.
585
600
 
586
601
  ```bash
587
- PLAN_PRE_HOOKS_JSON=$(pp_run loop render-hooks plan:pre --raw --phase "$PHASE" $TIER_FLAG) || exit 1
602
+ PLAN_PRE_HOOKS_JSON=$(pp_run loop render-hooks plan:pre --raw)
588
603
  ```
589
604
 
590
605
  Resolve active contribution hooks from `PLAN_PRE_HOOKS_JSON` where `kind == "contribution"` and `capId == "security"`.
@@ -614,11 +629,12 @@ Continue to step 5.6. Security config is passed to the planner in step 8.
614
629
  > **Config semantics (cutover fix):** `workflow.ui_phase` gates UI-SPEC *generation* (step); `workflow.ui_safety_gate` gates the *planning block* (gate). Both-on = identical to OLD §5.6. Intended change: `{ui_phase:true, ui_safety_gate:false}` now auto-generates in pipelines but does NOT block manual planning (each key controls exactly what its description says).
615
630
 
616
631
  ```bash
617
- PLAN_PRE_HOOKS_JSON=${PLAN_PRE_HOOKS_JSON:-$(pp_run loop render-hooks plan:pre --raw --phase "$PHASE" $TIER_FLAG)} || exit 1
632
+ PLAN_PRE_HOOKS_JSON=${PLAN_PRE_HOOKS_JSON:-$(pp_run loop render-hooks plan:pre --raw)}
633
+ printf '%s' "$PLAN_PRE_HOOKS_JSON" | node -e "let h=[];try{h=JSON.parse(require('fs').readFileSync(0,'utf-8')).activeHooks||[]}catch{}console.log('PLAN_PRE_HOOKS_JSON='+JSON.stringify({activeHooks:h}))"
618
634
  HOOKS_JSON="$PLAN_PRE_HOOKS_JSON"
619
635
  ```
620
636
 
621
- Read the `activeHooks` array directly from `PLAN_PRE_HOOKS_JSON` / `HOOKS_JSON` (in-context — do NOT invoke a shell pipeline).
637
+ Read the `activeHooks` array directly from the printed `PLAN_PRE_HOOKS_JSON=` line (in-context — do NOT invoke a shell pipeline).
622
638
 
623
639
  **Branch 1 — all plan:pre hooks inactive (`activeHooks` is empty or absent):** Skip to step 6.
624
640
 
@@ -686,11 +702,30 @@ Output this markdown directly (not as a code block):
686
702
  ───────────────────────────────────────────────
687
703
  Also available:
688
704
  - `/pp:plan-phase {N} --skip-ui ${GSD_WS}` — plan without UI-SPEC (not recommended for frontend phases)
689
- - Not a UI phase? Matched: {GATE.tokens}. Add `**UI:** none` on its own line in the ROADMAP phase section.
690
705
  ```
691
706
 
692
707
  **Exit the plan-phase workflow. Do not continue.**
693
708
 
709
+ ### plan:pre vault gate — enforce, then render
710
+
711
+ Reached on **every** path through §5.6. Branches 1-5 skip the UI work, not this section: their "skip/continue to step 6" passes THROUGH this section and the render sentence below it on the way to `## 6.`. Only Branch 6's explicit **EXIT the plan-phase workflow** stops before it. A §5.1-originating "skip to step 6" (use existing, or Skip research) likewise passes THROUGH the fetch section above and this section on its way to `## 6.`.
712
+ A researcher-return "continue to step 6" (the `## RESEARCH COMPLETE` arm) and a §5.5-originating "continue to step 6" (the research-disabled warning arm and the Nyquist-not-applicable arm) likewise pass THROUGH the fetch section above and this section on their way to `## 6.`.
713
+
714
+ **Admission again (D-28):** `--gaps`, `--skip-research` and `--reviews` skip §5, fetch section included, so the same check runs here before the gate lookup.
715
+
716
+ ```bash
717
+ VAULT_ADMIT_PRESENT=$(pp_run query platform-bindings --pick present 2>/dev/null || echo true)
718
+ VAULT_ADMIT=inert
719
+ if [ "$VAULT_ADMIT_PRESENT" = true ]; then VAULT_ADMIT=$(pp_run vault admission 2>/dev/null || echo unrecognised); fi
720
+ case "$VAULT_ADMIT" in on) echo "VAULT_ADMIT=on" ;; off|inert) : ;; *) pp_run vault admission --explain; echo "PARETO_GATE_BLOCKED:vault.admission"; exit 1 ;; esac
721
+ ```
722
+
723
+ In `PLAN_PRE_HOOKS_JSON` (already loaded), find active plan:pre entries where `kind == "gate"` and `check.query == "vault.status"`. If none, skip silently to step 6.
724
+
725
+ **Enforce first.** §5.6 already ran `ui.plan-gate` with its own positional and owns its outcome (Branch 6 blocks in manual mode; Branch 5 continues), so skip that entry — the enforcer never sees it. Set `GATE_POINT=plan:pre`, `GATE_HOOKS_JSON=$PLAN_PRE_HOOKS_JSON`, leave `GATE_POSITIONAL_ARG` empty deliberately (`vault.status` reads project state, not a phase; every plan:pre gate needing one is caller-handled — D-VH-7), then Read `pp-core/workflows/execute-phase/steps/check-query-gate.md` — the single generic enforcer (GATE-02); it owns the halt.
726
+
727
+ **Then render.** If still running, set `VAULT_GATE_POINT=plan` and Read `pp-core/workflows/execute-phase/steps/vault-load-gate.md` — it surfaces the §8 load-state message and decides nothing.
728
+
694
729
  ## 6. Check Existing Plans
695
730
 
696
731
  ```bash
@@ -816,7 +851,7 @@ Continue to step 8.
816
851
  Run this Bash command to emit the branded Plan stage banner (renders on stderr, auto-degrades on non-TTY — do NOT print an inline ASCII banner, that would double-print):
817
852
  ```bash
818
853
  pp_run banner plan "PLANNING PHASE {X}"
819
- touch "${PHASE_DIR}/.planner-run-mark" # the threat-model gate examines only newer plans
854
+ touch "${PHASE_DIR}/.planner-run-mark" # step 9d examines only plans newer than this
820
855
  ```
821
856
 
822
857
  Then announce the spawn:
@@ -970,7 +1005,7 @@ Read and execute `pp-core/workflows/plan-phase/steps/chunked-planning.md` — th
970
1005
 
971
1006
  ## 9. Handle Planner Return
972
1007
 
973
- - **`## PLANNING COMPLETE`:** Display plan count, then run "After planning" in `plan-phase/steps/phase-tier.md`. If `--skip-verify` or `plan_checker_enabled` is false (from init): skip to step 13. Otherwise: step 10.
1008
+ - **`## PLANNING COMPLETE`:** Run the step 9d gate first. Then display plan count. If `--skip-verify` or `plan_checker_enabled` is false (from init): skip to step 13. Otherwise: step 10.
974
1009
  - **`## PHASE SPLIT RECOMMENDED`:** The planner determined the phase exceeds the context budget for full-fidelity implementation of all source items. Handle in step 9b.
975
1010
  - **`## ⚠ Source Audit: Unplanned Items Found`:** The planner's multi-source coverage audit found items from REQUIREMENTS.md, RESEARCH.md, ROADMAP goal, or CONTEXT.md decisions that are not covered by any plan. Handle in step 9c.
976
1011
  - **`## CHECKPOINT REACHED`:** Present to user, get response, spawn continuation (step 12) (U-P4 — hard stop)
@@ -1065,6 +1100,33 @@ Use AskUserQuestion for each gap (or batch if multiple gaps) (U-P7).
1065
1100
  **If "Split":** Use `/pp:phase --insert` for overflow items, then replan.
1066
1101
  **If "Defer":** Record in CONTEXT.md `## Deferred Ideas` with developer's confirmation. Proceed to step 10.
1067
1102
 
1103
+ ## 9d. Threat Model Presence Gate
1104
+
1105
+ > Mechanical post-planner check. Active only when the security capability's `plan:pre` contribution is active — the same hook step 5.55 banners on; otherwise it prints `false` and changes nothing. Runs on every `## PLANNING COMPLETE` (including plans accepted through step 9a) before step 10 or step 13.
1106
+
1107
+ ```bash
1108
+ PLAN_PRE_HOOKS_JSON=${PLAN_PRE_HOOKS_JSON:-$(pp_run loop render-hooks plan:pre --raw)}
1109
+ SEC_ON=$(printf '%s' "$PLAN_PRE_HOOKS_JSON" | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{const h=JSON.parse(s||"{}").activeHooks||[];process.stdout.write(h.some(x=>x.kind==="contribution"&&x.capId==="security")?"true":"false")})')
1110
+ MARK="${PHASE_DIR}/.planner-run-mark"
1111
+ [ -f "$MARK" ] || touch -t 197001010000 "$MARK"
1112
+ NO_TM=""
1113
+ if [ "$SEC_ON" = "true" ]; then
1114
+ for p in $(find "${PHASE_DIR}" -maxdepth 1 -name '*-PLAN.md' -newer "$MARK" | sort); do
1115
+ grep -q '<threat_model>' "$p" || NO_TM="${NO_TM} ${p##*/}"
1116
+ done
1117
+ fi
1118
+ echo "THREAT_MODEL_GATE=${SEC_ON}${NO_TM:+ MISSING:${NO_TM}}"
1119
+ ```
1120
+
1121
+ **If the output carries `MISSING:`:** STOP. Do not proceed to step 10 or step 13. Display:
1122
+
1123
+ ```text
1124
+ ✗ Security enforcement is active, but these plans carry no <threat_model> block: {NO_TM}
1125
+ Remedy: re-spawn the planner (step 8) with the block required for each plan named above, then re-run this gate.
1126
+ ```
1127
+
1128
+ **Otherwise:** continue with the step 9 branch (`false` means the security contribution is inactive and this gate examined nothing).
1129
+
1068
1130
  ## 10. Spawn pp-plan-checker Agent
1069
1131
 
1070
1132
  Display banner:
@@ -1172,17 +1234,17 @@ Offer 3 options (U-P9):
1172
1234
 
1173
1235
  **If `DISK_PLANS` is 0:** No plans on disk — something is seriously wrong. Display error and stop.
1174
1236
 
1175
- ## 12. Revision Loop (Max `plan_check_passes` Iterations, from init)
1237
+ ## 12. Revision Loop (Max 3 Iterations)
1176
1238
 
1177
1239
  Track `iteration_count` (starts at 1 after initial plan + check).
1178
1240
  Track `prev_issue_count` (initialized to `Infinity` before the loop begins).
1179
1241
  Track `stall_reentry_count` (starts at 0; incremented each time "Adjust approach" re-enters step 8).
1180
1242
 
1181
- **If iteration_count < plan_check_passes:**
1243
+ **If iteration_count < 3:**
1182
1244
 
1183
1245
  Parse issue count from checker return: count BLOCKER + WARNING entries in the YAML issues block (structured output from pp-plan-checker). If the checker's return contains no YAML issues block (i.e., the plan was approved with no issues), treat `issue_count` as 0 and skip the stall check — the plan passed. Proceed to step 13.
1184
1246
 
1185
- Display: `Revision iteration {N}/{plan_check_passes} -- {blocker_count} blockers, {warning_count} warnings`
1247
+ Display: `Revision iteration {N}/3 -- {blocker_count} blockers, {warning_count} warnings`
1186
1248
 
1187
1249
  **Stall detection (U-P16):** If `issue_count >= prev_issue_count`:
1188
1250
  Display: `Revision loop stalled — issue count not decreasing ({issue_count} issues remain after {N} iterations)`
@@ -1241,7 +1303,7 @@ Agent(
1241
1303
 
1242
1304
  After planner returns -> spawn checker again (step 10), increment iteration_count.
1243
1305
 
1244
- **If iteration_count >= plan_check_passes:**
1306
+ **If iteration_count >= 3:**
1245
1307
 
1246
1308
  Display: `Max iterations reached. {N} issues remain:` + issue list
1247
1309
 
@@ -1251,7 +1313,7 @@ Offer: 1) Force proceed, 2) Provide guidance and retry, 3) Abandon (U-P10)
1251
1313
 
1252
1314
  **Skip if:** `--skip-bounce` flag, `--gaps` flag, or bounce is not activated.
1253
1315
 
1254
- **Activation:** Bounce runs when `--bounce` flag is present OR `plan_bounce_enabled` (init, tier-resolved) is `true`. The `--skip-bounce` flag always wins (disables bounce even if config enables it). The `--gaps` flag also disables bounce (gap-closure mode should not modify plans externally).
1316
+ **Activation:** Bounce runs when `--bounce` flag is present OR `workflow.plan_bounce` config is `true`. The `--skip-bounce` flag always wins (disables bounce even if config enables it). The `--gaps` flag also disables bounce (gap-closure mode should not modify plans externally).
1255
1317
 
1256
1318
  **Prerequisites:** `workflow.plan_bounce_script` must be set to a valid script path. If bounce is activated but no script is configured, display warning and skip:
1257
1319
  ```
@@ -1477,31 +1539,22 @@ Proactive, non-blocking coverage report gated on `workflow.post_planning_gaps`
1477
1539
  `<decisions>` and cross-references each REQ-ID / D-ID against `${PHASE_DIR}/*-PLAN.md`.
1478
1540
 
1479
1541
  ```bash
1480
- PLAN_POST_HOOKS_JSON=$(pp_run loop render-hooks plan:post --raw --phase "$PHASE" $TIER_FLAG) || exit 1
1481
- TRACE_GATE_JSON=$(pp_run loop run-gates plan:post --phase "$PHASE" $TIER_FLAG --query vault.trace) || exit 1
1542
+ PLAN_POST_HOOKS_JSON=$(pp_run loop render-hooks plan:post --raw)
1543
+ printf '%s' "$PLAN_POST_HOOKS_JSON" | node -e "let h=[];try{h=JSON.parse(require('fs').readFileSync(0,'utf-8')).activeHooks||[]}catch{}console.log('PLAN_POST_HOOKS_JSON='+JSON.stringify({activeHooks:h}))"
1482
1544
  PHASE_REQ_IDS=$(pp_run query init.plan-phase "$PHASE" --pick phase_req_ids 2>/dev/null || echo TBD)
1483
- ```
1484
-
1485
- The `run-gates` line runs the blocking `vault.trace` gate (vault practices on) and owns its halt: exit 1 (a `⛔` line) stops planning here — relay it verbatim and do not present the plans as ready. Read the `activeHooks` array from `PLAN_POST_HOOKS_JSON` in-context. If the
1486
- `gap-analysis` gate hook is absent (capability inactive), skip the rest of this step.
1487
-
1488
- **For each active entry where `kind == "gate"`** except `check.query == "vault.trace"` (run above), in array order:
1489
-
1490
- ```bash
1491
- GATE_RESULT=$(pp_run check ${hook.check.query} "${PHASE_DIR}" "${PHASE_REQ_IDS}" --raw)
1492
- CHECK_EXIT=$?
1493
- ```
1494
-
1495
- **Step 1 — did the CHECK COMMAND itself succeed?**
1496
- If the check command failed (non-zero `CHECK_EXIT`, empty output, or unparseable JSON):
1497
- - `onError == "halt"` → halt and surface command error.
1498
- - `onError == "skip"` → log a warning and continue to the next hook.
1499
-
1500
- **Step 2 — read `GATE_RESULT.block` (boolean).** Only reached when command succeeded.
1501
-
1502
- - If `hook.blocking == true` and `GATE_RESULT.block == true`: halt. (gap-analysis is always `blocking: false` so this branch is informational only.)
1503
- - If `hook.blocking == false` (advisory): if `GATE_RESULT.block == true` or non-empty `table`/`summary`, output the gap table and continue. Advisory gates never block phase completion.
1504
- - If `hook.blocking == true` and `GATE_RESULT.block == false`: continue silently.
1545
+ echo "PHASE_REQ_IDS=$PHASE_REQ_IDS"
1546
+ ```
1547
+
1548
+ Set `GATE_POINT=plan:post`, `GATE_HOOKS_JSON=$PLAN_POST_HOOKS_JSON`,
1549
+ `GATE_POSITIONAL_ARG="${PHASE_DIR}"`, `GATE_POSITIONAL_ARG_2="${PHASE_REQ_IDS}"`, `GATE_STAGE=plan`
1550
+ — this point validates plan artifacts, and without the scope the trace gate would demand an
1551
+ execute-stage trace from every historical SUMMARY.md in the phase directory, hard-halting any re-plan
1552
+ of an executed phase — and `GATE_WS` to the
1553
+ active workstream name (empty in flat mode), then Read and
1554
+ follow `pp-core/workflows/execute-phase/steps/check-query-gate.md` — the single generic `check.query`
1555
+ gate enforcer already used at `plan:pre`, `execute:pre` and `execute:post` (GATE-02, D-VH-7). It
1556
+ dispatches every active gate entry at this point, not only gap-analysis, and owns the two-step
1557
+ contract, the halt and the message passthrough. Do not restate its loop here.
1505
1558
 
1506
1559
  ## 14. Present Final Status
1507
1560
 
@@ -38,6 +38,7 @@ Parse `$ARGUMENTS` for:
38
38
  - `--validate` flag → store `$VALIDATE_MODE=true`
39
39
  - `--discuss` flag → store `$DISCUSS_MODE=true`
40
40
  - `--research` flag → store `$RESEARCH_MODE=true`
41
+ - `--from-debug <slug>` → store `$FROM_DEBUG_SLUG` (D-08: `debug.md` hands a confirmed root cause to a quick task). Sanitise it exactly as `debug.md` does: strip whitespace, reject unless it matches `^[a-z0-9][a-z0-9-]*$`, max 30 chars, reject any `..`, `/` or `\`; if invalid, print `Invalid --from-debug slug` and stop. If `.planning/debug/${FROM_DEBUG_SLUG}.md` does not exist, print `No debug session found with slug: ${FROM_DEBUG_SLUG}` and stop. Otherwise Steps 5 and 6 add that file to the planner's and the executor's reading lists; nothing else changes.
41
42
  - Remaining text → use as `$DESCRIPTION` if non-empty
42
43
 
43
44
  After parsing, normalize: if `$DISCUSS_MODE` and `$RESEARCH_MODE` and `$VALIDATE_MODE` are all true, set `$FULL_MODE=true`. This ensures `--discuss --research --validate` is treated identically to `--full`.
@@ -176,7 +177,49 @@ Quick tasks can run mid-phase - validation only checks ROADMAP.md exists, not ph
176
177
 
177
178
  ---
178
179
 
179
- **Step 2.4: Vault door gate** — Step 2.5 is the first write. Read and execute `pp-core/workflows/execute-phase/steps/vault-door-gate.md` now; a stop ends the quick task before anything is written.
180
+ **Step 2.4: Vault admission and load gate**
181
+
182
+ Runs here because Step 2.5 is the first step that writes anything — it creates the quick-task branch
183
+ — and the gate has to decide before code can be written, not after.
184
+
185
+ Quick renders the loop points' own hooks (`plan:pre` here, `plan:post` in Step 5.55, `execute:pre`
186
+ for the executor prompt, `execute:post` in Step 6.1) and keeps only the `vault` and
187
+ `vault-practices` hooks, mechanically in each fence (D-30): no other capability's gate, step or
188
+ fragment is run or injected by quick. Mode filtering, the practices switch and the seal gates
189
+ therefore reach quick with no second copy of any rule (D-01). The renders pass no `--ws`: every
190
+ `pp_run` call here resolves the same workstream ladder as the admission verb, and each trace gate's
191
+ positional is the quick directory, which carries its own partition (D-73).
192
+
193
+ Admission first (D-28): bindings first, then every config file the gate reads is parsed. With
194
+ bindings present, a config the gate cannot read halts here, naming the file and the repair (D-05).
195
+
196
+ ```bash
197
+ VAULT_ADMIT_PRESENT=$(pp_run query platform-bindings --pick present 2>/dev/null || echo true)
198
+ VAULT_ADMIT=inert
199
+ if [ "$VAULT_ADMIT_PRESENT" = true ]; then VAULT_ADMIT=$(pp_run vault admission 2>/dev/null || echo unrecognised); fi
200
+ case "$VAULT_ADMIT" in on) echo "VAULT_ADMIT=on" ;; off|inert) : ;; *) pp_run vault admission --explain; echo "PARETO_GATE_BLOCKED:vault.admission"; exit 1 ;; esac
201
+ ```
202
+
203
+ **If the fence printed no `VAULT_ADMIT=on` line** (`off` and `inert` print nothing): skip the rest of this step and the vault sub-steps below
204
+ (the planner fragments, Step 5.55, the anchor and executor fragments in Step 6, Step 6.1) silently
205
+ and continue to Step 2.5. Print nothing: a non-Pareto or `vault.enabled=false` project runs this
206
+ workflow byte-identically to how it ran before this gate existed (GATE-04).
207
+
208
+ **Otherwise, fetch and load-gate at `plan:pre`:**
209
+
210
+ ```bash
211
+ QUICK_PLAN_PRE_HOOKS_JSON=$(pp_run loop render-hooks plan:pre --raw | node -e "const d=require('fs').readFileSync(0,'utf-8');try{const j=JSON.parse(d);j.activeHooks=(j.activeHooks||[]).filter((x)=>x&&(x.capId==='vault'||x.capId==='vault-practices'));process.stdout.write(JSON.stringify(j));}catch{process.stdout.write('');}")
212
+ printf '%s' "$QUICK_PLAN_PRE_HOOKS_JSON" | node -e "let h=[];try{h=JSON.parse(require('fs').readFileSync(0,'utf-8')).activeHooks||[]}catch{}console.log('QUICK_PLAN_PRE_HOOKS_JSON='+JSON.stringify({activeHooks:h}))"
213
+ ```
214
+
215
+ Set `VAULT_FETCH_HOOKS_JSON=$QUICK_PLAN_PRE_HOOKS_JSON`, then Read and execute
216
+ `pp-core/workflows/execute-phase/steps/vault-fetch.md` (hook mode). Then, in the
217
+ `QUICK_PLAN_PRE_HOOKS_JSON=` line that fence printed, find active entries where `kind == "gate"` and `check.query ==
218
+ "vault.status"`; if none, skip silently. Otherwise set `GATE_POINT=plan:pre` and
219
+ `GATE_HOOKS_JSON=$QUICK_PLAN_PRE_HOOKS_JSON`, leave `GATE_POSITIONAL_ARG`, `GATE_WS` and
220
+ `GATE_STAGE` empty (`vault.status` reads project state), then Read and follow
221
+ `pp-core/workflows/execute-phase/steps/check-query-gate.md`. That single enforcer (GATE-02) owns the
222
+ two-step contract, the halt and the message passthrough; none of them are restated here.
180
223
 
181
224
  ---
182
225
 
@@ -464,6 +507,16 @@ If research file not found, warn but continue: "Research agent did not produce o
464
507
 
465
508
  **If NOT `$VALIDATE_MODE`:** Use standard `quick` mode.
466
509
 
510
+ Vault fragments for the planner: the `plan:pre` contributions `into: planner`, re-rendered and
511
+ filtered in this fence (a shell variable from Step 2.4 does not survive into this call). Skip unless
512
+ Step 2.4 printed `VAULT_ADMIT=on`. The fence prints the fragments; put that printed text verbatim where
513
+ the prompt says `${QUICK_PLANNER_HOOK_FRAGMENTS}` (nothing when it printed nothing).
514
+
515
+ ```bash
516
+ QUICK_PLANNER_HOOK_FRAGMENTS=$(pp_run loop render-hooks plan:pre --raw | node -e "const d=require('fs').readFileSync(0,'utf-8');try{const j=JSON.parse(d);j.activeHooks=(j.activeHooks||[]).filter((x)=>x&&(x.capId==='vault'||x.capId==='vault-practices'));process.stdout.write(JSON.stringify(j));}catch{process.stdout.write('');}" | node -e "const d=require('fs').readFileSync(0,'utf-8');try{const h=(JSON.parse(d).activeHooks||[]).filter((x)=>x&&x.kind==='contribution'&&x.into==='planner'&&x.fragment&&typeof x.fragment.inline==='string');process.stdout.write(h.map((x)=>x.fragment.inline).join('\n\n'));}catch{process.stdout.write('');}")
517
+ if [ -n "$QUICK_PLANNER_HOOK_FRAGMENTS" ]; then printf '%s\n' "$QUICK_PLANNER_HOOK_FRAGMENTS"; fi
518
+ ```
519
+
467
520
  Display: `◆ Spawning planner... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)`
468
521
 
469
522
  ```
@@ -480,10 +533,13 @@ Agent(
480
533
  - ./CLAUDE.md or ./.claude/CLAUDE.md (if exists — follow project-specific guidelines)
481
534
  ${DISCUSS_MODE ? '- ' + QUICK_DIR + '/' + quick_id + '-CONTEXT.md (User decisions — locked, do not revisit)' : ''}
482
535
  ${RESEARCH_MODE ? '- ' + QUICK_DIR + '/' + quick_id + '-RESEARCH.md (Research findings — use to inform implementation choices)' : ''}
536
+ ${FROM_DEBUG_SLUG ? '- .planning/debug/' + FROM_DEBUG_SLUG + '.md (Debug session from --from-debug — the confirmed root cause this task fixes)' : ''}
483
537
  </files_to_read>
484
538
 
485
539
  ${AGENT_SKILLS_PLANNER}
486
540
 
541
+ ${QUICK_PLANNER_HOOK_FRAGMENTS}
542
+
487
543
  **Project skills:** Check .claude/skills/ or .agents/skills/ directory (if either exists) — read SKILL.md files, plans should account for project skill rules
488
544
 
489
545
  </planning_context>
@@ -633,6 +689,26 @@ Offer: 1) Force proceed, 2) Abort
633
689
 
634
690
  ---
635
691
 
692
+ **Step 5.55: plan:post trace gate (runs before Step 5.6)**
693
+
694
+ Skip silently unless Step 2.4 printed `VAULT_ADMIT=on`. Runs after the planner (and the checker under
695
+ `$VALIDATE_MODE`) and BEFORE Step 5.6, so a malformed or absent record halts the task with nothing
696
+ committed and no executor spawned (D-02, D-34).
697
+
698
+ ```bash
699
+ QUICK_PLAN_POST_HOOKS_JSON=$(pp_run loop render-hooks plan:post --raw | node -e "const d=require('fs').readFileSync(0,'utf-8');try{const j=JSON.parse(d);j.activeHooks=(j.activeHooks||[]).filter((x)=>x&&(x.capId==='vault'||x.capId==='vault-practices'));process.stdout.write(JSON.stringify(j));}catch{process.stdout.write('');}")
700
+ printf '%s' "$QUICK_PLAN_POST_HOOKS_JSON" | node -e "let h=[];try{h=JSON.parse(require('fs').readFileSync(0,'utf-8')).activeHooks||[]}catch{}console.log('QUICK_PLAN_POST_HOOKS_JSON='+JSON.stringify({activeHooks:h}))"
701
+ ```
702
+
703
+ In the `QUICK_PLAN_POST_HOOKS_JSON=` line that fence printed, find active entries where `kind == "gate"` and `check.query ==
704
+ "vault.trace"`; if none, skip silently. Otherwise set `GATE_POINT=plan:post`,
705
+ `GATE_HOOKS_JSON=$QUICK_PLAN_POST_HOOKS_JSON`, `GATE_POSITIONAL_ARG="${QUICK_DIR}"` and
706
+ `GATE_STAGE=plan` (the plan stage, tolerant: nothing has run yet, D-03), leave `GATE_WS` empty (a
707
+ quick directory carries its own partition), then Read and follow
708
+ `pp-core/workflows/execute-phase/steps/check-query-gate.md`; it owns the halt.
709
+
710
+ ---
711
+
636
712
  **Step 5.6: Pre-dispatch plan commit (worktree mode only)**
637
713
 
638
714
  When `USE_WORKTREES !== "false"`, commit PLAN.md to the current branch **before** spawning the executor. This ensures the worktree inherits PLAN.md at its branch HEAD so the executor can read it via a worktree-rooted path — avoiding the main-repo path priming that triggers CC #36182 path-resolution drift.
@@ -674,9 +750,10 @@ fi
674
750
 
675
751
  **Step 6: Spawn executor**
676
752
 
677
- Capture current HEAD before spawning (used for worktree branch check):
753
+ Capture current HEAD before spawning (used for worktree branch check, and as the vault START anchor):
678
754
  ```bash
679
755
  EXPECTED_BASE=$(git rev-parse HEAD)
756
+ pp_run vault anchor start "${QUICK_DIR}" --raw
680
757
  if [ "${USE_WORKTREES:-true}" != "false" ]; then
681
758
  QUICK_WORKTREE_MANIFEST=$(mktemp "${TMPDIR:-/tmp}/pp-quick-worktree-XXXXXX.json")
682
759
  printf '{"worktrees":[]}\n' > "$QUICK_WORKTREE_MANIFEST"
@@ -684,6 +761,20 @@ if [ "${USE_WORKTREES:-true}" != "false" ]; then
684
761
  fi
685
762
  ```
686
763
 
764
+ `vault anchor start` records HEAD (this `EXPECTED_BASE`) as the quick directory's START anchor
765
+ (D-31); the executor's record verb reads it, so the SUMMARY carries start and end. The verb prints
766
+ nothing and writes nothing unless `vault admission` answers `on`, so it needs no guard.
767
+
768
+ Vault fragments for the executor: the `execute:pre` contributions `into: executor`, from a filtered
769
+ render (never the unfiltered `--into` form of the render, which no capability filter narrows). Skip unless
770
+ Step 2.4 printed `VAULT_ADMIT=on`. The fence prints the fragments; put that printed text verbatim where the
771
+ prompt says `${QUICK_EXECUTOR_HOOK_FRAGMENTS}` (nothing when it printed nothing).
772
+
773
+ ```bash
774
+ QUICK_EXECUTOR_HOOK_FRAGMENTS=$(pp_run loop render-hooks execute:pre --raw | node -e "const d=require('fs').readFileSync(0,'utf-8');try{const j=JSON.parse(d);j.activeHooks=(j.activeHooks||[]).filter((x)=>x&&(x.capId==='vault'||x.capId==='vault-practices'));process.stdout.write(JSON.stringify(j));}catch{process.stdout.write('');}" | node -e "const d=require('fs').readFileSync(0,'utf-8');try{const h=(JSON.parse(d).activeHooks||[]).filter((x)=>x&&x.kind==='contribution'&&x.into==='executor'&&x.fragment&&typeof x.fragment.inline==='string');process.stdout.write(h.map((x)=>x.fragment.inline).join('\n\n'));}catch{process.stdout.write('');}")
775
+ if [ -n "$QUICK_EXECUTOR_HOOK_FRAGMENTS" ]; then printf '%s\n' "$QUICK_EXECUTOR_HOOK_FRAGMENTS"; fi
776
+ ```
777
+
687
778
  Spawn pp-executor with plan reference:
688
779
 
689
780
  ```
@@ -717,10 +808,13 @@ MANDATORY: load every file listed here with the Read tool before you start the w
717
808
  - .planning/STATE.md (Project state)
718
809
  - ./CLAUDE.md or ./.claude/CLAUDE.md (Project instructions, if exists)
719
810
  - .claude/skills/ or .agents/skills/ (Project skills, if either exists — list skills, read SKILL.md for each, follow relevant rules during implementation)
811
+ ${FROM_DEBUG_SLUG ? '- .planning/debug/' + FROM_DEBUG_SLUG + '.md (Debug session from --from-debug — the confirmed root cause this task fixes)' : ''}
720
812
  </files_to_read>
721
813
 
722
814
  ${AGENT_SKILLS_EXECUTOR}
723
815
 
816
+ ${QUICK_EXECUTOR_HOOK_FRAGMENTS}
817
+
724
818
  <submodule_commit_guard>
725
819
  SUBMODULE_PATHS for this project: ${SUBMODULE_PATHS}
726
820
 
@@ -806,13 +900,33 @@ Note: For quick tasks producing multiple plans (rare), spawn executors in parall
806
900
 
807
901
  ---
808
902
 
903
+ **Step 6.1: execute:post trace gate**
904
+
905
+ Skip silently unless Step 2.4 printed `VAULT_ADMIT=on`. Strict, because a quick task is complete here: one plan,
906
+ one SUMMARY, nothing pending (D-03). Runs before code review, verification and the STATE.md update,
907
+ so a blocked task never gets a STATE.md row and never reaches the final commit (D-34); task commits
908
+ the executor already made stay in history, and the SEAL-01 check names them (D-02).
909
+
910
+ ```bash
911
+ QUICK_EXECUTE_POST_HOOKS_JSON=$(pp_run loop render-hooks execute:post --raw | node -e "const d=require('fs').readFileSync(0,'utf-8');try{const j=JSON.parse(d);j.activeHooks=(j.activeHooks||[]).filter((x)=>x&&(x.capId==='vault'||x.capId==='vault-practices'));process.stdout.write(JSON.stringify(j));}catch{process.stdout.write('');}")
912
+ printf '%s' "$QUICK_EXECUTE_POST_HOOKS_JSON" | node -e "let h=[];try{h=JSON.parse(require('fs').readFileSync(0,'utf-8')).activeHooks||[]}catch{}console.log('QUICK_EXECUTE_POST_HOOKS_JSON='+JSON.stringify({activeHooks:h}))"
913
+ ```
914
+
915
+ In the `QUICK_EXECUTE_POST_HOOKS_JSON=` line that fence printed, find active entries where `kind == "gate"` and `check.query ==
916
+ "vault.trace"`; if none, skip silently. Otherwise set `GATE_POINT=execute:post`,
917
+ `GATE_HOOKS_JSON=$QUICK_EXECUTE_POST_HOOKS_JSON`, `GATE_POSITIONAL_ARG="${QUICK_DIR}"` and
918
+ `GATE_UNPAIRED=strict`, leave `GATE_STAGE` and `GATE_WS` empty, then Read and follow
919
+ `pp-core/workflows/execute-phase/steps/check-query-gate.md`; it owns the halt.
920
+
921
+ ---
922
+
809
923
  **Step 6.25: Code review (auto)**
810
924
 
811
925
  Skip this step entirely if `$FULL_MODE` is false.
812
926
 
813
927
  **Capability gate:**
814
928
  ```bash
815
- EXECUTE_POST_HOOKS_JSON=$(pp_run loop render-hooks execute:post --raw) || exit 1
929
+ EXECUTE_POST_HOOKS_JSON=$(pp_run loop render-hooks execute:post --raw)
816
930
  ```
817
931
 
818
932
  Resolve active step hooks from `EXECUTE_POST_HOOKS_JSON` where `kind == "step"` and `ref.skill == "code-review"`.
@@ -26,7 +26,7 @@ Parse: `phase_dir`, `phase_number`, `phase_name`, `phase_slug`, `padded_phase`.
26
26
 
27
27
  ```bash
28
28
  AUDITOR_MODEL=$(pp_run query resolve-model pp-security-auditor --raw)
29
- VERIFY_POST_HOOKS_JSON=$(pp_run loop render-hooks verify:post --raw) || exit 1
29
+ VERIFY_POST_HOOKS_JSON=$(pp_run loop render-hooks verify:post --raw)
30
30
  ```
31
31
 
32
32
  Resolve active step hooks from `VERIFY_POST_HOOKS_JSON` where `kind == "step"` and `ref.skill == "secure-phase"`.