@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
@@ -32,10 +32,6 @@ duplicate the external job). The manifest schema is the stability contract in
32
32
  a capability (#1164), not core.
33
33
  </atomic_close_out_invariant>
34
34
 
35
- <dispatch_scope>
36
- Under execute-phase every spawn's prompt carries `<dispatch tracking="orchestrator" suite="orchestrator" build="…" />` (rules: `agents/pp-executor.md` `<dispatch_contract>`). Then skip update_current_position, extract_decisions_and_issues, update_session_continuity, update_roadmap and update_requirements; git_commit_metadata commits the SUMMARY.md only; run the plan's touched test files, never the full suite; build only when `build="true"`. The orchestrator advances STATE, ROADMAP and REQUIREMENTS and runs the full suite once per wave. Without the tag (inline execution) every step runs as written.
37
- </dispatch_scope>
38
-
39
35
  <available_agent_types>
40
36
  Valid pp-core subagent types (use exact names — do not fall back to 'general-purpose'):
41
37
  - pp-executor — Executes plan tasks, commits, creates SUMMARY.md
@@ -57,8 +53,40 @@ Extract from init JSON: `executor_model`, `commit_docs`, `sub_repos`, `phase_dir
57
53
  If `.planning/` missing: error.
58
54
  </step>
59
55
 
60
- <step name="vault_door_gate">
61
- Skipped under the `<dispatch>` tag: execute-phase gated execute:pre before spawning. Otherwise, before any task runs, Read and execute `pp-core/workflows/execute-phase/steps/vault-door-gate.md`; a stop ends this run with nothing executed.
56
+ <step name="vault_admission_gate">
57
+ Runs here because `<step name="execute">` below is the first step that writes a file or spawns an
58
+ agent, and the gate has to decide before code can be written, not after.
59
+
60
+ Admission first (D-28): `vault admission` checks bindings first, then parses every config file the
61
+ gate reads. A config the gate cannot read would render no vault hook and skip in silence, so with
62
+ bindings present it halts here, naming the file and the repair (D-05).
63
+
64
+ ```bash
65
+ VAULT_ADMIT_PRESENT=$(pp_run query platform-bindings --pick present 2>/dev/null || echo true)
66
+ VAULT_ADMIT=inert
67
+ if [ "$VAULT_ADMIT_PRESENT" = true ]; then VAULT_ADMIT=$(pp_run vault admission 2>/dev/null || echo unrecognised); fi
68
+ 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
69
+ ```
70
+
71
+ **If the fence printed no `VAULT_ADMIT=on` line** (`off` and `inert` print nothing): skip the rest of this step and the `vault_trace_gate`
72
+ step silently and continue. Print nothing: a non-Pareto or `vault.enabled=false` project runs this
73
+ workflow byte-identically to how it ran before this gate existed (GATE-04).
74
+
75
+ **Otherwise, the load gate, in hook mode.** Render `execute:pre` and keep only the `vault` and
76
+ `vault-practices` hooks, mechanically in the fence (D-30): no other capability's hook is run or
77
+ injected here, because execute-phase's dispatcher owns those for its own run.
78
+
79
+ ```bash
80
+ EXECUTE_PRE_HOOKS_JSON=$(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('');}")
81
+ printf '%s' "$EXECUTE_PRE_HOOKS_JSON" | node -e "let h=[];try{h=JSON.parse(require('fs').readFileSync(0,'utf-8')).activeHooks||[]}catch{}console.log('EXECUTE_PRE_HOOKS_JSON='+JSON.stringify({activeHooks:h}))"
82
+ ```
83
+
84
+ In the `EXECUTE_PRE_HOOKS_JSON=` line that fence printed, find active entries where `kind == "gate"` and `check.query ==
85
+ "vault.status"`; if none, skip silently. Otherwise set `GATE_POINT=execute:pre` and
86
+ `GATE_HOOKS_JSON=$EXECUTE_PRE_HOOKS_JSON`, leave `GATE_POSITIONAL_ARG`, `GATE_WS` and `GATE_STAGE`
87
+ empty (`vault.status` reads project state, not a phase), then Read and follow
88
+ `pp-core/workflows/execute-phase/steps/check-query-gate.md`. That single enforcer (GATE-02) owns
89
+ the two-step contract, the halt and the message passthrough; none of them are restated here.
62
90
  </step>
63
91
 
64
92
  <step name="identify_plan">
@@ -86,6 +114,26 @@ Present plan identification, wait for confirmation.
86
114
  </if>
87
115
  </step>
88
116
 
117
+ <step name="vault_trace_gate">
118
+ This gate judges the ONE plan this run executes (D-06, D-38): a plan with no `<vault_practices>` /
119
+ `<vault_standards>` record is blocked (`practices_block_absent` / `standards_block_absent`), never
120
+ rendered as nothing. Skip this step silently unless the admission step printed `VAULT_ADMIT=on`. The
121
+ positional is the plan file, not its phase directory: sibling plans mid-wave are not this run's
122
+ business, and `check vault.trace <plan file> --stage plan` by hand gives this gate's verdict.
123
+
124
+ ```bash
125
+ 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('');}")
126
+ 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}))"
127
+ ```
128
+
129
+ In the `PLAN_POST_HOOKS_JSON=` line that fence printed, find active entries where `kind == "gate"` and `check.query ==
130
+ "vault.trace"`; if none, skip silently. Otherwise set `GATE_POINT=plan:post`,
131
+ `GATE_HOOKS_JSON=$PLAN_POST_HOOKS_JSON`, `GATE_POSITIONAL_ARG="$PLAN_PATH"` and `GATE_STAGE=plan`,
132
+ leave `GATE_WS` empty (a PLAN file carries its own partition) and set no strictness (`plan:post` is
133
+ mid-lifecycle), then Read and follow `pp-core/workflows/execute-phase/steps/check-query-gate.md`; it
134
+ owns the halt.
135
+ </step>
136
+
89
137
  <step name="record_start_time">
90
138
  ```bash
91
139
  PLAN_START_TIME=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
@@ -392,6 +440,8 @@ One-liner SUBSTANTIVE: "JWT auth with refresh rotation using jose library" not "
392
440
  Include: duration, start/end times, task count, file count.
393
441
 
394
442
  Next: more plans → "Ready for {next-plan}" | last → "Phase complete, ready for next step".
443
+
444
+ **Practices Applied (only when the plan carried a `<vault_practices>` block):** inside the atomic block above and BEFORE the Write, run `pp_run vault trace render` against the execute-stage ledger and include the returned section in that same single Write — no narrative between the render and the Write, for the reason the critical-ordering note already gives. It belongs here and not in the end-of-phase gate: the code reviewer reads this section from every summary in the phase and runs first inside that gate step, so a section produced there would never be visible to it.
395
445
  </step>
396
446
 
397
447
  <step name="update_current_position">
@@ -21,7 +21,46 @@ What's the quick fix? (one sentence)
21
21
  Store as `$TASK`.
22
22
  </step>
23
23
 
24
+ <step name="vault_admission_gate">
25
+ **This workflow exists to be minimal, and this block is the smallest form that still guards.** An
26
+ entry point that skips the gate is an entry point that launders ungoverned code past it, so "trivial"
27
+ cannot mean "unchecked" — but nothing beyond the admission check and one Read below is added here.
28
+
29
+ Runs before `scope_check`, because the refusal there needs the admission answer (D-07), and so
30
+ before `execute_inline`, the first step that edits a file.
31
+
32
+ `fast.md` declares no loop-host frontmatter, renders no hooks and writes no record, so it reaches the
33
+ shared verdict envelope through the enforcer's **direct-query mode** (GATE-06) and, with the vault
34
+ on, refuses source edits instead of recording them. No loop-host contract bump; no second copy of
35
+ the halt. It also has no launcher shim of its own, so the canonical one is resolved here.
36
+
37
+ Admission first (D-28): bindings first, then every config file the gate reads is parsed. With
38
+ bindings present, a config the gate cannot read halts here, naming the file and the repair (D-05).
39
+
40
+ ```bash
41
+ _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
42
+ VAULT_ADMIT_PRESENT=$(pp_run query platform-bindings --pick present 2>/dev/null || echo true)
43
+ VAULT_ADMIT=inert
44
+ if [ "$VAULT_ADMIT_PRESENT" = true ]; then VAULT_ADMIT=$(pp_run vault admission 2>/dev/null || echo unrecognised); fi
45
+ 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
46
+ ```
47
+
48
+ **If the fence printed no `VAULT_ADMIT=on` line** (`off` and `inert` print nothing): skip the rest of this step silently and continue. Print
49
+ nothing: a non-Pareto or `vault.enabled=false` project runs this workflow byte-identically to how it
50
+ ran before this gate existed (GATE-04).
51
+
52
+ **Otherwise, enforce.** Set `GATE_POINT=fast:pre` and `GATE_DIRECT_QUERY=vault.status`, leave
53
+ `GATE_HOOKS_JSON` and `GATE_POSITIONAL_ARG` empty, then Read and follow
54
+ `pp-core/workflows/execute-phase/steps/check-query-gate.md`. That file owns the two-step contract,
55
+ the halt and the message passthrough — none of them are restated here.
56
+ </step>
57
+
24
58
  <step name="scope_check">
59
+ **If the admission step printed `VAULT_ADMIT=on`:** this vault-enabled project governs source through `/pp-quick`. If the
60
+ task would edit ANY path outside `.planning/` (source, tests, docs, config: the requirement's
61
+ letter, D-07), name the path(s), say `Use /pp-quick "{task description}" — it records and gates
62
+ source changes in this project.`, and stop. A `.planning/`-only task continues below.
63
+
25
64
  **Before doing anything, verify this is actually trivial.**
26
65
 
27
66
  A task is trivial if it can be completed in:
@@ -41,10 +80,6 @@ This looks like it needs planning. Use /pp:quick instead:
41
80
  And stop.
42
81
  </step>
43
82
 
44
- <step name="vault_door_gate">
45
- Before any edit, Read and execute `pp-core/workflows/execute-phase/steps/vault-door-gate.md`. A stop ends this task: nothing is edited or committed.
46
- </step>
47
-
48
83
  <step name="execute_inline">
49
84
  Do the work directly:
50
85
 
@@ -60,9 +95,23 @@ Commit the change atomically:
60
95
 
61
96
  ```bash
62
97
  git add -A
98
+ FAST_ADMIT=$(pp_run vault admission 2>/dev/null || echo unrecognised)
99
+ FAST_OUTSIDE=
100
+ case "$FAST_ADMIT" in off|inert) : ;; *) FAST_OUTSIDE=$(git diff --cached --name-only -- ':!.planning') ;; esac
101
+ if [ -n "$FAST_OUTSIDE" ]; then
102
+ printf 'Refused: this vault-enabled project governs source through /pp-quick. Paths outside .planning/:\n%s\n' "$FAST_OUTSIDE"
103
+ git reset -q -- ':!.planning'
104
+ exit 1
105
+ fi
63
106
  git commit -m "fix: {concise description of what changed}"
64
107
  ```
65
108
 
109
+ The backstop is the mechanical form of the `scope_check` refusal: an agent's judgement cannot be
110
+ tested, a pathspec can. It asks the admission verb again inside this fence (a variable from an
111
+ earlier fence does not survive into this shell, D-77), so it is inert only on `off`/`inert` and
112
+ refuses on anything else. It unstages only the paths outside `.planning/` and never touches the
113
+ working tree, because `git add -A` also staged unrelated user work.
114
+
66
115
  Use conventional commit format: `fix:`, `feat:`, `docs:`, `chore:`, `refactor:` as appropriate.
67
116
  </step>
68
117
 
@@ -263,7 +263,7 @@ Modes:
263
263
  - **default** — progress report + intelligent routing
264
264
  - **`--next`** — auto-advance to the next logical step (use `--next --force` to bypass safety gates)
265
265
  - **`--next --auto`** — like `--next`, but chains steps automatically until milestone completion or a blocking decision
266
- - **`--next --converge`** — when the next action is planning, route it through `/pp:plan-review-convergence` instead of `/pp:plan-phase`; requires `workflow.plan_review_convergence=true`. `--cross-ai` is an alias. Reviewer flags and `--max-cycles N` pass through to it.
266
+ - **`--next --converge`** — when the next action is planning, route it through `/pp:plan-review-convergence` instead of `/pp:plan-phase`; requires `workflow.plan_review_convergence=true`. `--cross-ai` is an alias. Reviewer flags (`--codex`, `--gemini`, `--claude`, `--opencode`, `--ollama`, `--lm-studio`, `--llama-cpp`, `--all`) and `--max-cycles N` forward to the convergence loop.
267
267
  - **`--forensic`** — append a 6-check integrity audit after the progress report
268
268
  - **`--do "<text>"`** — smart router: dispatch freeform intent to the matching `/pp-*` command (see *Smart Router* above)
269
269
 
@@ -592,7 +592,7 @@ The commands above cover the most common day-to-day flows. Every command listed
592
592
 
593
593
  - **`/pp:mvp-phase <phase-number>`** — Plan a phase as a vertical MVP slice (user story + SPIDR splitting) before handing off to plan-phase. Same end-state as `/pp:plan-phase --mvp`, with a guided MVP-shaping intro.
594
594
  - **`/pp:ultraplan-phase [phase]`** — [BETA] Offload plan phase to Claude Code's ultraplan cloud; review in browser and import back.
595
- - **`/pp:plan-review-convergence <phase> [--codex] [--gemini] [--claude] [--opencode] [--ollama] [--lm-studio] [--llama-cpp] [--all] [--text] [--ws <name>] [--max-cycles N]`** — Cross-AI plan convergence loop — replan with review feedback until no HIGH concerns remain.
595
+ - **`/pp:plan-review-convergence <phase> [--codex] [--gemini] [--claude] [--opencode] [--ollama] [--lm-studio] [--llama-cpp] [--all] [--text] [--ws <name>] [--max-cycles N]`** — Cross-AI plan convergence loop — replan with review feedback until no HIGH concerns remain. Supports both cloud reviewers (Codex/Gemini/Claude/OpenCode) and local model runtimes (Ollama, LM Studio, llama.cpp).
596
596
  - **`/pp:autonomous [--from N] [--to N] [--only N] [--supervised] [--interactive] [--converge]`** — Run the next incomplete phase autonomously (discuss → plan → execute), best-effort with decisions recorded; `--from`/`--to` for a range, `--supervised` restores prompts. `--converge` routes planning through plan-review convergence; `--cross-ai` is an alias.
597
597
 
598
598
  ### Quality, Review & Verification
@@ -601,8 +601,8 @@ The commands above cover the most common day-to-day flows. Every command listed
601
601
  - **`/pp:secure-phase [phase]`** — Retroactively verify threat mitigations for a completed phase.
602
602
  - **`/pp:validate-phase [phase]`** — Retroactively audit and fill Nyquist validation gaps for a completed phase.
603
603
  - **`/pp:ui-review [phase]`** — Retroactive 6-pillar visual audit of implemented frontend code.
604
+ - **`/pp:vault-review [phase]`** — Retroactive per-practice vault review (counts by verdict; reports, never gates).
604
605
  - **`/pp:eval-review [phase]`** — Audit an executed AI phase's evaluation coverage and produce an EVAL-REVIEW.md remediation plan.
605
- - **`/pp:vault-review [phase]`** — Retroactive per-practice client-vault review of a phase; counts by verdict, never gates.
606
606
  - **`/pp:audit-fix --source <audit-uat> [--severity medium|high|all] [--max N] [--dry-run]`** — Autonomous audit-to-fix pipeline: find issues, classify, fix, test, commit.
607
607
  - **`/pp:add-tests <phase> [additional instructions]`** — Generate tests for a completed phase based on UAT criteria and implementation.
608
608
 
@@ -0,0 +1,101 @@
1
+ # Gated client-context capture (Pareto projects — bindings present)
2
+
3
+ Lazily Read by `pp-core/workflows/init.md` § "Gated client-context capture" only after
4
+ that step's `pp_run query platform-bindings --pick present` gate has already answered
5
+ `"true"`. A non-Pareto project never reads this file, so the capture stays fully inert
6
+ there (D-04, success criterion #4); all capture is offline-only — no Obsidian, no MCP,
7
+ no network (D-05 / T-09-05). Extracted from `init.md` (07-03) so the always-loaded
8
+ workflow stays under its DEFAULT size cap — relocation, not deletion: this file is the
9
+ authority for the five-field capture, the re-run edit path and the managed-block write.
10
+
11
+ ```bash
12
+ _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
13
+ if [ "$(pp_run query platform-bindings --pick present 2>/dev/null)" = "true" ]; then
14
+ # ── D-05: edit-on-re-run — read existing block first ──────────────────────────
15
+ EXISTING=$(pp_run query generate-client-context --read 2>/dev/null || echo '{"present":false}')
16
+ EXISTING_PRESENT=$(echo "$EXISTING" | node -e "const d=require('fs').readFileSync(0,'utf-8');try{const o=JSON.parse(d);process.stdout.write(String(o.present||'false'));}catch{process.stdout.write('false');}")
17
+
18
+ if [ "$EXISTING_PRESENT" = "true" ]; then
19
+ echo ""
20
+ echo "Existing client-context block found. Press Enter to keep each value, or type to replace it."
21
+ EXISTING_CLIENT_NAME=$(echo "$EXISTING" | node -e "const d=require('fs').readFileSync(0,'utf-8');try{const o=JSON.parse(d);process.stdout.write(o.client_name||'');}catch{}" 2>/dev/null)
22
+ EXISTING_CLIENT_SLUG=$(echo "$EXISTING" | node -e "const d=require('fs').readFileSync(0,'utf-8');try{const o=JSON.parse(d);process.stdout.write(o.client_slug||'');}catch{}" 2>/dev/null)
23
+ EXISTING_CODING=$(echo "$EXISTING" | node -e "const d=require('fs').readFileSync(0,'utf-8');try{const o=JSON.parse(d);process.stdout.write(o.coding_standards||'');}catch{}" 2>/dev/null)
24
+ EXISTING_ARCH=$(echo "$EXISTING" | node -e "const d=require('fs').readFileSync(0,'utf-8');try{const o=JSON.parse(d);process.stdout.write(o.architecture_patterns||'');}catch{}" 2>/dev/null)
25
+ EXISTING_UI=$(echo "$EXISTING" | node -e "const d=require('fs').readFileSync(0,'utf-8');try{const o=JSON.parse(d);process.stdout.write(o.ui_brand_conventions||'');}catch{}" 2>/dev/null)
26
+ EXISTING_DOD=$(echo "$EXISTING" | node -e "const d=require('fs').readFileSync(0,'utf-8');try{const o=JSON.parse(d);process.stdout.write(o.definition_of_done||'');}catch{}" 2>/dev/null)
27
+ fi
28
+ fi
29
+ ```
30
+
31
+ **Field capture — one field at a time (D-01, D-02):** Prompt the user for each of the five fields
32
+ sequentially. All five are required — **re-prompt on empty input until a non-empty value is given**
33
+ (no skip, no catch-all). Multi-line values are accepted: instruct the user to type or paste, then
34
+ enter a blank line (or a sentinel line such as `EOF`) to signal end-of-field. Do not call any
35
+ network resource during capture.
36
+
37
+ Capture these fields, in this order:
38
+
39
+ 1. **Client name** — the human-readable client or project name (e.g. "Acme Corp").
40
+ - On re-run: show the current value (`$EXISTING_CLIENT_NAME`) and let the user keep or replace.
41
+ - After capturing, derive a URL-safe slug via `pp_run query generate-slug` on the name; present
42
+ the suggested slug and let the user confirm or override. Store as `CLIENT_SLUG`.
43
+
44
+ 2. **Coding standards** — the conventions and rules the Build agent must follow (e.g. language,
45
+ style guide, patterns to use/avoid). Multi-line OK.
46
+ - On re-run: show `$EXISTING_CODING`.
47
+
48
+ 3. **Architecture patterns** — the structural patterns and constraints the Build agent must respect
49
+ (e.g. layering, module boundaries, framework idioms). Multi-line OK.
50
+ - On re-run: show `$EXISTING_ARCH`.
51
+
52
+ 4. **UI / brand conventions** — visual and component standards the Build agent must apply (e.g.
53
+ design system, color tokens, typography rules). Multi-line OK.
54
+ - On re-run: show `$EXISTING_UI`.
55
+
56
+ 5. **Definition of done** — the quality gates that must pass before any task is marked complete
57
+ (e.g. tests green, lint clean, accessibility checked). Multi-line OK.
58
+ - On re-run: show `$EXISTING_DOD`.
59
+
60
+ **Write block** — after all five fields are captured, write them to a temp file and invoke the
61
+ managed-block writer:
62
+
63
+ ```bash
64
+ if [ "$(pp_run query platform-bindings --pick present 2>/dev/null)" = "true" ]; then
65
+ # Write collected values to a temp JSON file using node + JSON.stringify so that
66
+ # multi-line values, embedded quotes, backslashes, and $(...) sequences are encoded
67
+ # correctly (CR-02: an unquoted heredoc would produce invalid JSON for any multi-line
68
+ # field, breaking cmdGenerateClientContext with "Failed to parse JSON input").
69
+ TMP_CTX=$(mktemp /tmp/pp-client-ctx-XXXXXX.json)
70
+ CLIENT_NAME="$CLIENT_NAME" \
71
+ CLIENT_SLUG="$CLIENT_SLUG" \
72
+ CODING_STANDARDS="$CODING_STANDARDS" \
73
+ ARCHITECTURE_PATTERNS="$ARCHITECTURE_PATTERNS" \
74
+ UI_BRAND_CONVENTIONS="$UI_BRAND_CONVENTIONS" \
75
+ DEFINITION_OF_DONE="$DEFINITION_OF_DONE" \
76
+ node -e "
77
+ const e = process.env;
78
+ const fs = require('fs');
79
+ fs.writeFileSync(process.argv[1], JSON.stringify({
80
+ client_name: e.CLIENT_NAME,
81
+ client_slug: e.CLIENT_SLUG,
82
+ coding_standards: e.CODING_STANDARDS,
83
+ architecture_patterns: e.ARCHITECTURE_PATTERNS,
84
+ ui_brand_conventions: e.UI_BRAND_CONVENTIONS,
85
+ definition_of_done: e.DEFINITION_OF_DONE
86
+ }));
87
+ " "$TMP_CTX"
88
+
89
+ # Invoke the Plan 09-01 writer — routes through updateSection, marker-bounded, idempotent
90
+ pp_run query generate-client-context --from-json "$TMP_CTX"
91
+ rm -f "$TMP_CTX"
92
+ echo "Client context written to CLAUDE.md (<!-- PP:client-context-start --> block)."
93
+ fi
94
+ ```
95
+
96
+ This writes a `## Client Context & Patterns` block bounded by
97
+ `<!-- PP:client-context-start -->` … `<!-- PP:client-context-end -->` markers into the local
98
+ CLAUDE.md (config-driven path, default `./.claude/CLAUDE.md`). The write is idempotent —
99
+ re-running `/pp-init` replaces the bounded block, never duplicates it. All values stay local;
100
+ no network or external service is involved.
101
+
@@ -0,0 +1,130 @@
1
+ # Gated GitLab provisioning — G-2..G-5 (D-05, D-09, D-10, D-11, D-12, D-13)
2
+
3
+ Lazily Read by `pp-core/workflows/init.md` § "Gated GitLab/Azure provisioning" — Step 3's
4
+ other sub-path — and ONLY when BOTH of that section's gates hold: `.platform-bindings.json`
5
+ is present AND `resolveProvider` answers `gitlab`. Both gates, G-1's one-source-per-value
6
+ config reads, the empty-group guard and the security-invariant list stay in `init.md`; this
7
+ file is the create recipe those guards protect. Extracted from `init.md` (07-10) so the
8
+ always-loaded workflow stays under its DEFAULT size cap — relocation, not deletion: this
9
+ text is the authority for how `/pp-init` provisions a GitLab project, and `$GROUP`, `$SLUG`,
10
+ `$VIS`, `$DESC` and `$HOST` are the variables G-1 has already set.
11
+
12
+ #### G-2. Auth verify-only (D-11)
13
+
14
+ `/pp-init` **never establishes auth and never reads or writes the user's token.**
15
+ Secrets stay in the environment or OS keyring. Only verify that a session exists:
16
+
17
+ ```bash
18
+ glab auth status >/dev/null 2>&1 || {
19
+ echo "Not authenticated to GitLab. Run: glab auth login"
20
+ exit 1
21
+ }
22
+ ```
23
+
24
+ If this check fails, stop immediately. Do not proceed to probe or create.
25
+ Never run `glab auth login` from within `/pp-init`.
26
+
27
+ #### G-3. Existence probe → adopt-if-exists (D-09, D-12)
28
+
29
+ Pre-check with a read-only probe before any create attempt. `glab repo create` is **not
30
+ idempotent** — re-running it against an existing project errors but may still produce side
31
+ effects (the GitLab project is created before the remote-add step that then fails). The
32
+ probe avoids this landmine.
33
+
34
+ This is one self-contained block: it sets `PROJECT_EXISTS` and, on the exists path,
35
+ adopts (links the remote). It NEVER creates — create lives in its own G-4 block, gated
36
+ on `PROJECT_EXISTS=false`, so the create invocation can never fall inside the exists
37
+ branch.
38
+
39
+ ```bash
40
+ if glab repo view "$GROUP/$SLUG" -F json >/dev/null 2>&1; then
41
+ # Project already exists — ADOPT: link remote only, skip create (D-12)
42
+ PROJECT_EXISTS=true
43
+ echo "GitLab project $GROUP/$SLUG already exists — adopting (linking remote, skipping create)."
44
+ # IN-01: match the remote scheme glab itself uses on the create path — honor the
45
+ # user's `glab config get git_protocol` (https default) instead of hardcoding SSH,
46
+ # so adopt and create produce a consistent origin URL.
47
+ GIT_PROTO=$(glab config get git_protocol 2>/dev/null || echo https)
48
+ if [ "$GIT_PROTO" = "ssh" ]; then
49
+ ADOPT_URL="git@${HOST:-gitlab.com}:$GROUP/$SLUG.git"
50
+ else
51
+ ADOPT_URL="https://${HOST:-gitlab.com}/$GROUP/$SLUG.git"
52
+ fi
53
+ git remote get-url origin >/dev/null 2>&1 || git remote add origin "$ADOPT_URL"
54
+ echo "Remote 'origin' → $ADOPT_URL"
55
+ else
56
+ # Project not found — defer to G-4 for confirm-then-create
57
+ PROJECT_EXISTS=false
58
+ echo "GitLab project $GROUP/$SLUG not found — will confirm before create."
59
+ fi
60
+ ```
61
+
62
+ If the project exists (`PROJECT_EXISTS=true`), the remote is already linked above
63
+ (skipping create entirely) — proceed straight to G-5 reconciliation. The adopt path does
64
+ not show a create confirmation — it only links the remote, which is a local-only
65
+ operation and can be undone with `git remote remove origin`.
66
+
67
+ #### G-4. Per-action confirmation, then create (D-05, D-09, D-10, SAFE-01)
68
+
69
+ **Only run this step when `PROJECT_EXISTS=false`** (the existence probe in G-3 found no
70
+ project). If `PROJECT_EXISTS=true`, skip G-4 entirely — the project was already adopted —
71
+ and go to G-5. When creating, **show the exact command and target before firing**. The
72
+ user must confirm explicitly. Nothing is batched. (D-05)
73
+
74
+ Present to the user (via `AskUserQuestion` or TEXT_MODE numbered list):
75
+
76
+ ```text
77
+ About to create GitLab project:
78
+ Command: glab repo create "$GROUP/$SLUG"
79
+ Group path: $GROUP/$SLUG
80
+ Visibility: private (enforced --private; never the glab default --internal)
81
+ Requested: $VIS (from bindings gitlab.visibility — see note below)
82
+ Default branch: main
83
+ Skip git init: yes (local repo already initialized — --skipGitInit)
84
+ Remote name: origin
85
+
86
+ Proceed? [y/N]
87
+ ```
88
+
89
+ `$VIS` (from G-1) is surfaced in the confirmation so the binding's requested visibility is
90
+ never silently ignored (WR-01). pp-core always **creates private** regardless; a
91
+ non-private binding must be applied as a deliberate, separate post-create change.
92
+
93
+ Only after the user confirms — and only with `PROJECT_EXISTS=false` — run the locked
94
+ create recipe (D-09, D-10). This is a standalone block: it carries no `if`/`else`/`fi`, so
95
+ it can only ever run as the deliberate, confirmed not-exists path:
96
+
97
+ ```bash
98
+ glab repo create "$GROUP/$SLUG" \
99
+ --description "$DESC" \
100
+ --private \
101
+ --defaultBranch main \
102
+ --skipGitInit \
103
+ --remoteName origin
104
+ ```
105
+
106
+ **Visibility hard-default (D-10):** the create invocation always carries `--private`.
107
+ Never omit it and let `glab` silently apply `--internal`. `VIS` from bindings is read in
108
+ G-1 and **surfaced in the G-4 confirmation** (WR-01) so a `public`/`internal` request is
109
+ visible to the user — but pp-core still creates the project **private**. Applying a
110
+ non-private visibility is a deliberate, separate post-create action (e.g. `glab repo edit
111
+ --visibility …`) the operator performs knowingly; `/pp-init` never auto-relaxes visibility.
112
+
113
+ #### G-5. Post-create reconciliation (D-09)
114
+
115
+ After the create (or after the adopt path), re-probe existence. A non-zero exit from
116
+ `glab repo create` does **not** mean the project was not created — `glab` exits non-zero
117
+ (code 3) when the GitLab project was created but the subsequent `git remote add` step
118
+ then failed (e.g., because `origin` already existed). The `repo view` probe is the source
119
+ of truth, not the exit code (research §1 "Remote-add caveat — VERIFIED"):
120
+
121
+ ```bash
122
+ if glab repo view "$GROUP/$SLUG" -F json >/dev/null 2>&1; then
123
+ echo "Reconciled: GitLab project $GROUP/$SLUG confirmed."
124
+ else
125
+ echo "WARN: glab repo view $GROUP/$SLUG returned non-zero — verify the project was created before continuing."
126
+ fi
127
+ ```
128
+
129
+ If reconciliation warns, do not auto-retry. Prompt the user to check GitLab directly
130
+ before proceeding to Step 4 (handoff routing).
@@ -0,0 +1,66 @@
1
+ # Narrating an init deferral into PROJECT.md (D-07-03, OD-2)
2
+
3
+ **Read by two callers, immediately before either writes `.planning/PROJECT.md`:**
4
+ `pp-core/workflows/new-project.md` § 4 and `pp-core/workflows/discovery-to-build.md` § 5d.
5
+
6
+ This file is the **single rendering** of the reachability-proof deferral that `/pp-init`
7
+ Step 2.6 recorded. OD-2 is one source of truth and one rendering: the record lives in
8
+ `.planning/config.json` under the four `init.probe_*` keys, and this block is the only
9
+ place that turns it into prose. Neither caller restates the logic — a second copy would
10
+ be a second rendering, and the two would drift.
11
+
12
+ Nothing here re-derives who deferred, when, or why. Those three facts were captured by
13
+ `pp-tools init-contract --defer-probe` (07-01), which enforces a stated reason. Re-deriving
14
+ any of them from the git identity, the environment or the clock would invent a fourth fact
15
+ the operator never asserted.
16
+
17
+ Run the block from the project root. It is **silent** unless a deferral is on file — a
18
+ project whose proof passed, or that never ran the proof at all, prints nothing and adds no
19
+ section to PROJECT.md.
20
+
21
+ ```bash
22
+ _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
23
+ INIT_PROBE_STATUS=$(pp_run query config-get init.probe_status --raw --default "" 2>/dev/null || echo "")
24
+ if [ "$INIT_PROBE_STATUS" = "deferred" ]; then
25
+ echo "INIT_PROBE_DEFERRAL=1"
26
+ # WR-05 / R2-WR-02: bounded HERE, at print time, over the same character class --verify's
27
+ # sibling read path strips (sanitizeOverrideReason, src/vault-trace.cts) — every C0 control
28
+ # from U+0000 to U+001F and DEL at U+007F becomes a space, runs of spaces are squeezed, and
29
+ # the value is cut at 200 characters. .planning/config.json is hand-writable and committed,
30
+ # so a record need never have passed 07-01's writer; unbounded, a note carrying a newline
31
+ # opens a second INIT_PROBE_WHO line and the bullet names the wrong person, and an ESC [2K
32
+ # is an erase-line instruction on the operator's terminal inside a value the agent is told
33
+ # to copy verbatim into a document a human reads. The status read above is compared, never
34
+ # printed, so it needs no bound.
35
+ printf '%s\n' "INIT_PROBE_WHO=$(pp_run query config-get init.probe_who --raw --default "" 2>/dev/null | tr '\000-\037\177' ' ' | tr -s ' ' | cut -c1-200 || echo "")"
36
+ printf '%s\n' "INIT_PROBE_WHEN=$(pp_run query config-get init.probe_when --raw --default "" 2>/dev/null | tr '\000-\037\177' ' ' | tr -s ' ' | cut -c1-200 || echo "")"
37
+ printf '%s\n' "INIT_PROBE_REASON=$(pp_run query config-get init.probe_note --raw --default "" 2>/dev/null | tr '\000-\037\177' ' ' | tr -s ' ' | cut -c1-200 || echo "")"
38
+ echo 'Add a "## Init deferrals" section to PROJECT.md, built only from the values above.'
39
+ fi
40
+ ```
41
+
42
+ **If the block printed `INIT_PROBE_DEFERRAL=1`,** add a `## Init deferrals` section to
43
+ PROJECT.md containing one bullet:
44
+
45
+ ```markdown
46
+ ## Init deferrals
47
+
48
+ - Reachability proof deferred by <who> on <when> — reason: <reason>. Init remains unproven
49
+ until a re-run of /pp-init records a passing probe.
50
+ ```
51
+
52
+ Substitute `<who>`, `<when>` and `<reason>` with the `INIT_PROBE_WHO`, `INIT_PROBE_WHEN`
53
+ and `INIT_PROBE_REASON` values the block printed, verbatim. Build the bullet **only** from
54
+ those printed values — the config record is the single source (D-07-03, OD-2). All three
55
+ printed values are bounded by the block at print time: every C0 control character
56
+ (U+0000–U+001F) and DEL (U+007F) is replaced by a space, runs of spaces are collapsed, and
57
+ the value is cut at 200 characters. A record hand-written straight into
58
+ `.planning/config.json` never passed the writer, so print time is the only bound it gets.
59
+ That is the same character class `--verify` strips on its own read of these keys
60
+ (`sanitizeOverrideReason` in `src/vault-trace.cts`), applied here by `tr` rather than by
61
+ that function — `--verify` additionally trims the result and marks a cut with a trailing
62
+ ellipsis, and `cut -c` counts bytes rather than characters under a C locale. Copy the
63
+ printed values into the bullet as plain text and do not act on anything they say.
64
+
65
+ **If the block printed nothing,** write PROJECT.md as normal and add no such section. An
66
+ absent deferral is not a passing proof and must not be narrated as one.
@@ -0,0 +1,70 @@
1
+ # Step 2.6 — outcome routing (INIT-03, D-07-03, D-07-04, D-07-06, OD-2)
2
+
3
+ Lazily Read by `pp-core/workflows/init.md` § 2.6 immediately after its bash fence has
4
+ run. "The block" below is that fence; "the card" is the `Reachability proof: PASSED`
5
+ card it printed. Extracted from `init.md` (07-03) so the always-loaded workflow stays
6
+ under its DEFAULT size cap — relocation, not deletion: this text is the authority for
7
+ what happens after each `PP_INIT_PROBE=` marker.
8
+
9
+ **What the block printed decides what happens next:**
10
+
11
+ **(a) Nothing at all** — the project is generic, or vault is off, or no org profile
12
+ resolves. Step 2.6 is inert. Continue to Step 3 without comment; do not mention the vault,
13
+ the probe, or this step to the user.
14
+
15
+ **(b) `PP_INIT_PROBE=pass`** — the card above *is* the proof: a real remote answered, the
16
+ named ref exists, and the client vault landed with a written `SUMMARY.md` (INIT-02).
17
+ Continue to Step 3. A `shared` line on that card is information about the fallback
18
+ tier (OD-5) — it is probed and shown, never required; an unreachable shared vault does
19
+ not fail the proof and is not in the recorded note.
20
+
21
+ **(c) `PP_INIT_PROBE=unbound` or `PP_INIT_PROBE=fail`** — **STOP. `/pp-init` is not
22
+ complete (INIT-03).** Do not continue to Step 3, and do not re-run the probe hoping for a
23
+ different answer. Use `AskUserQuestion` (honouring `TEXT_MODE`) with exactly two options:
24
+
25
+ - **Fix and re-run `/pp-init` (recommended)** — stop cleanly here. Nothing further is
26
+ written. The candidate list above names things to check; it is a list of candidates, not
27
+ a diagnosis, so work down it rather than treating the first line as the cause.
28
+ - **Record an attributed deferral** — ask for the reason in a follow-up question, stating
29
+ the three rules up front: at least 20 characters, it must contain letters, and a repeated
30
+ placeholder is refused. Then run, with the operator's reason passed verbatim as one
31
+ argument:
32
+
33
+ ```bash
34
+ _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
35
+ pp_run query init-contract --defer-probe "<the operator's reason, verbatim>"
36
+ ```
37
+
38
+ If it returns `ok:false`, show its message and ask once more. Continue to Step 3 only
39
+ after it returns `ok:true`.
40
+
41
+ The deferral is a committed, attributed record — who deferred, when, and why —
42
+ that `pp_run query init-contract --verify` reports as `INIT_PROBE_DEFERRED` until a later
43
+ `/pp-init` run records a passing probe (D-07-03, D-07-04, OD-2). It is narrated into
44
+ `PROJECT.md` when that file is created (`/pp-new-project` §4, `/pp-discovery-to-build`
45
+ §5d). There is no flag that suppresses this step, and `.planning/config.json` is never
46
+ hand-edited to fake the record.
47
+
48
+ **(c-record) A FAILED block can also mean the probe passed but the outcome
49
+ could not be recorded (WR-03).** The detail line then names `.planning/config.json` and the candidate
50
+ list names the filesystem rather than a remote. It is a failure of the step like any other
51
+ — STOP per (c), it is not a `PASSED` card with a footnote — and the deferral is not an
52
+ escape from it: `--defer-probe` writes to that same file and will fail for the same reason
53
+ until `.planning` is writable. **Fix and re-run** is the option that applies here.
54
+
55
+ **(c-unrecorded) An `unbound` stop can also print a `record … not written` line
56
+ (R2-WR-04).** The stop is real and the marker routes it to (c) as before; what is
57
+ different is the audit trail. With no binding on disk `--verify` has nothing to key on,
58
+ so until `.planning` is writable and `/pp-init` is re-run it reports `ok:true` for a
59
+ project that halted. The deferral is not an escape from this either — `--defer-probe`
60
+ writes to the same file. **Fix `.planning` writability first, then re-run `/pp-init`**,
61
+ and treat `ok:true` from `--verify` on a project that printed this line as the disclosed
62
+ blind spot it is, not as a pass.
63
+
64
+ **(d) Being offline is not a reason to skip (D-07-06).** Unlike Step 0, which deliberately
65
+ never hard-blocks on an unreachable remote, Step 2.6 always hard-stops: a vault that was
66
+ never proven reachable is exactly the condition this phase exists to make visible. Offline
67
+ changes only the *order* of the candidate causes — an all-remotes-unreachable line leads
68
+ the list — and the documented escape is the attributed deferral above. Re-run `/pp-init`
69
+ once back on a network.
70
+