@pareto4/pp-core 1.18.0 → 1.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (194) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/agents/pp-code-fixer.md +9 -5
  3. package/agents/pp-code-reviewer.md +1 -0
  4. package/agents/pp-codebase-mapper.md +3 -0
  5. package/agents/pp-debugger.md +3 -2
  6. package/agents/pp-executor.md +16 -7
  7. package/agents/pp-integration-checker.md +1 -0
  8. package/agents/pp-phase-researcher.md +13 -1
  9. package/agents/pp-plan-checker.md +3 -0
  10. package/agents/pp-planner.md +20 -1
  11. package/agents/pp-research-synthesizer.md +1 -1
  12. package/agents/pp-ui-auditor.md +1 -0
  13. package/agents/pp-ui-researcher.md +3 -1
  14. package/agents/pp-vault-reviewer.md +2 -0
  15. package/agents/pp-verifier.md +22 -10
  16. package/commands/pp-add-tests.md +1 -1
  17. package/commands/pp-debug.md +1 -0
  18. package/commands/pp-discovery.md +3 -2
  19. package/commands/pp-init.md +4 -3
  20. package/commands/pp-quick.md +3 -0
  21. package/commands/pp-review-backlog.md +2 -1
  22. package/gemini-extension.json +1 -1
  23. package/package.json +6 -3
  24. package/pareto-skills/_shared/pp-claude-md-protocol.md +1 -1
  25. package/pareto-skills/pp-project-setup/SKILL.md +18 -343
  26. package/pp-core/bin/lib/capability-registry.cjs +3 -3
  27. package/pp-core/bin/lib/check-command-router.cjs +60 -20
  28. package/pp-core/bin/lib/commands.cjs +79 -4
  29. package/pp-core/bin/lib/compose-bindings.cjs +97 -14
  30. package/pp-core/bin/lib/discovery-access.cjs +276 -0
  31. package/pp-core/bin/lib/discovery-charter.cjs +664 -0
  32. package/pp-core/bin/lib/discovery-deviations.cjs +264 -0
  33. package/pp-core/bin/lib/discovery-entities.cjs +323 -0
  34. package/pp-core/bin/lib/discovery-for-phase.cjs +628 -0
  35. package/pp-core/bin/lib/discovery-integrations.cjs +286 -0
  36. package/pp-core/bin/lib/discovery-migration.cjs +246 -0
  37. package/pp-core/bin/lib/discovery-phases.cjs +233 -0
  38. package/pp-core/bin/lib/discovery-processes.cjs +355 -0
  39. package/pp-core/bin/lib/discovery-research-track.cjs +5 -4
  40. package/pp-core/bin/lib/discovery-rollup.cjs +12 -8
  41. package/pp-core/bin/lib/discovery-rules.cjs +216 -0
  42. package/pp-core/bin/lib/discovery-screens.cjs +21 -3
  43. package/pp-core/bin/lib/discovery-session-check.cjs +264 -0
  44. package/pp-core/bin/lib/discovery-stories.cjs +273 -0
  45. package/pp-core/bin/lib/discovery-sweep.cjs +13 -4
  46. package/pp-core/bin/lib/discovery-table.cjs +55 -0
  47. package/pp-core/bin/lib/discovery-vault-check.cjs +37 -3
  48. package/pp-core/bin/lib/init-contract.cjs +48 -7
  49. package/pp-core/bin/lib/io.cjs +22 -0
  50. package/pp-core/bin/lib/vault-coordinates.cjs +1 -1
  51. package/pp-core/bin/lib/vault-index.cjs +18 -5
  52. package/pp-core/bin/lib/vault-standards-review.cjs +50 -2
  53. package/pp-core/bin/lib/vault-update.cjs +4 -0
  54. package/pp-core/bin/lib/vault.cjs +2 -2
  55. package/pp-core/bin/pp-tools.cjs +153 -5
  56. package/pp-core/org-profile.json +1 -1
  57. package/pp-core/references/autonomous-best-effort.md +3 -2
  58. package/pp-core/references/autonomous-lifecycle.md +1 -0
  59. package/pp-core/references/autonomous-smart-discuss.md +5 -1
  60. package/pp-core/references/discovery-access.md +112 -0
  61. package/pp-core/references/discovery-build-consumption.md +256 -0
  62. package/pp-core/references/discovery-charter.md +127 -0
  63. package/pp-core/references/discovery-data-model.md +145 -0
  64. package/pp-core/references/discovery-id-conventions.md +77 -8
  65. package/pp-core/references/discovery-integrations.md +151 -0
  66. package/pp-core/references/discovery-processes.md +190 -0
  67. package/pp-core/references/discovery-rebaseline.md +2 -2
  68. package/pp-core/references/discovery-research-track.md +5 -6
  69. package/pp-core/references/discovery-session-output.md +70 -0
  70. package/pp-core/references/discovery-standards.md +107 -0
  71. package/pp-core/references/discovery-traceability.md +2 -2
  72. package/pp-core/references/execute-mvp-tdd.md +3 -3
  73. package/pp-core/references/git-integration.md +57 -33
  74. package/pp-core/references/git-planning-commit.md +31 -9
  75. package/pp-core/references/phase-argument-parsing.md +1 -0
  76. package/pp-core/references/planner-discovery-extract.md +35 -0
  77. package/pp-core/references/planner-gap-closure.md +1 -0
  78. package/pp-core/references/planner-revision.md +3 -1
  79. package/pp-core/references/planner-tdd.md +4 -4
  80. package/pp-core/references/planning-config.md +3 -3
  81. package/pp-core/references/shell-portability.md +28 -0
  82. package/pp-core/references/tdd.md +20 -16
  83. package/pp-core/references/verifier-discovery-conformance.md +36 -0
  84. package/pp-core/references/worktree-path-safety.md +1 -0
  85. package/pp-core/templates/milestone.md +5 -5
  86. package/pp-core/templates/project-charter.md +161 -100
  87. package/pp-core/templates/project.md +3 -3
  88. package/pp-core/templates/requirements.md +18 -0
  89. package/pp-core/templates/verification-report.md +16 -0
  90. package/pp-core/workflows/add-backlog.md +1 -1
  91. package/pp-core/workflows/add-tests.md +5 -2
  92. package/pp-core/workflows/add-todo.md +3 -1
  93. package/pp-core/workflows/ai-integration-phase.md +2 -1
  94. package/pp-core/workflows/audit-fix.md +4 -3
  95. package/pp-core/workflows/audit-milestone.md +1 -0
  96. package/pp-core/workflows/autonomous.md +6 -0
  97. package/pp-core/workflows/check-todos.md +2 -1
  98. package/pp-core/workflows/cleanup.md +3 -1
  99. package/pp-core/workflows/code-review-fix.md +12 -5
  100. package/pp-core/workflows/code-review.md +15 -1
  101. package/pp-core/workflows/complete-milestone.md +13 -3
  102. package/pp-core/workflows/debug.md +2 -0
  103. package/pp-core/workflows/diagnose-issues.md +1 -1
  104. package/pp-core/workflows/discovery-sprint/charter.md +114 -0
  105. package/pp-core/workflows/discovery-sprint/prefill.md +1 -1
  106. package/pp-core/workflows/discovery-sprint/prototype.md +4 -4
  107. package/pp-core/workflows/discovery-sprint/session-13-gates.md +11 -1
  108. package/pp-core/workflows/discovery-sprint/sessions/01-kick-off.md +54 -7
  109. package/pp-core/workflows/discovery-sprint/sessions/02-as-is.md +31 -7
  110. package/pp-core/workflows/discovery-sprint/sessions/03-to-be.md +64 -37
  111. package/pp-core/workflows/discovery-sprint/sessions/04-ux-design.md +12 -3
  112. package/pp-core/workflows/discovery-sprint/sessions/05-architecture.md +45 -22
  113. package/pp-core/workflows/discovery-sprint/sessions/06-security.md +30 -15
  114. package/pp-core/workflows/discovery-sprint/sessions/07-identity.md +43 -25
  115. package/pp-core/workflows/discovery-sprint/sessions/08-integration.md +63 -24
  116. package/pp-core/workflows/discovery-sprint/sessions/09-qa.md +28 -13
  117. package/pp-core/workflows/discovery-sprint/sessions/10-infrastructure.md +35 -16
  118. package/pp-core/workflows/discovery-sprint/sessions/11-roadmap.md +32 -8
  119. package/pp-core/workflows/discovery-sprint/sessions/12-delivery-model.md +21 -18
  120. package/pp-core/workflows/discovery-sprint/sessions/13-wrap-up.md +29 -12
  121. package/pp-core/workflows/discovery-sprint/vault-context.md +14 -3
  122. package/pp-core/workflows/discovery-sprint.md +23 -23
  123. package/pp-core/workflows/discovery-to-build/steps/architecture-deliverables.md +81 -29
  124. package/pp-core/workflows/discovery-to-build/steps/{screen-inventory-gate.md → handoff-gates.md} +111 -3
  125. package/pp-core/workflows/discovery-to-build/templates/discovery-references.md +12 -9
  126. package/pp-core/workflows/discovery-to-build/templates/pp-handoff.md +27 -26
  127. package/pp-core/workflows/discovery-to-build.md +70 -34
  128. package/pp-core/workflows/discuss-phase/discovery.md +83 -0
  129. package/pp-core/workflows/discuss-phase/templates/context.md +4 -0
  130. package/pp-core/workflows/discuss-phase-assumptions.md +5 -3
  131. package/pp-core/workflows/discuss-phase.md +18 -10
  132. package/pp-core/workflows/docs-update.md +1 -0
  133. package/pp-core/workflows/eval-review.md +2 -1
  134. package/pp-core/workflows/execute-phase/steps/check-query-gate.md +7 -6
  135. package/pp-core/workflows/execute-phase/steps/cross-ai-delegation.md +1 -0
  136. package/pp-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +1 -0
  137. package/pp-core/workflows/execute-phase/steps/vault-fetch.md +12 -0
  138. package/pp-core/workflows/execute-phase.md +19 -7
  139. package/pp-core/workflows/execute-plan.md +18 -7
  140. package/pp-core/workflows/explore.md +1 -1
  141. package/pp-core/workflows/forensics.md +4 -2
  142. package/pp-core/workflows/health.md +1 -0
  143. package/pp-core/workflows/import.md +2 -1
  144. package/pp-core/workflows/ingest-docs.md +1 -0
  145. package/pp-core/workflows/init/steps/client-context-capture.md +1 -0
  146. package/pp-core/workflows/init/steps/github-provisioning.md +145 -0
  147. package/pp-core/workflows/init/steps/gitlab-provisioning.md +18 -4
  148. package/pp-core/workflows/init/steps/reachability-outcome.md +3 -1
  149. package/pp-core/workflows/init.md +56 -66
  150. package/pp-core/workflows/map-codebase.md +5 -1
  151. package/pp-core/workflows/milestone-summary.md +2 -1
  152. package/pp-core/workflows/mvp-phase.md +1 -0
  153. package/pp-core/workflows/new-milestone.md +9 -5
  154. package/pp-core/workflows/new-project.md +12 -11
  155. package/pp-core/workflows/new-workspace.md +1 -0
  156. package/pp-core/workflows/next.md +2 -1
  157. package/pp-core/workflows/pause-work.md +6 -4
  158. package/pp-core/workflows/plan-milestone-gaps.md +3 -1
  159. package/pp-core/workflows/plan-phase/steps/chunked-planning.md +1 -1
  160. package/pp-core/workflows/plan-phase/steps/discovery-extract.md +41 -0
  161. package/pp-core/workflows/plan-phase.md +36 -16
  162. package/pp-core/workflows/plan-review-convergence.md +2 -0
  163. package/pp-core/workflows/plant-seed.md +4 -2
  164. package/pp-core/workflows/pr-branch.md +2 -0
  165. package/pp-core/workflows/progress.md +5 -0
  166. package/pp-core/workflows/quick.md +11 -4
  167. package/pp-core/workflows/reapply-patches.md +3 -0
  168. package/pp-core/workflows/remove-phase.md +1 -1
  169. package/pp-core/workflows/resume-project.md +3 -0
  170. package/pp-core/workflows/review.md +7 -1
  171. package/pp-core/workflows/secure-phase.md +2 -1
  172. package/pp-core/workflows/session-report.md +1 -0
  173. package/pp-core/workflows/ship.md +8 -1
  174. package/pp-core/workflows/sketch-wrap-up.md +1 -1
  175. package/pp-core/workflows/sketch.md +3 -2
  176. package/pp-core/workflows/spec-phase.md +2 -1
  177. package/pp-core/workflows/spike-wrap-up.md +1 -1
  178. package/pp-core/workflows/spike.md +4 -3
  179. package/pp-core/workflows/sync-skills.md +5 -2
  180. package/pp-core/workflows/thread.md +3 -2
  181. package/pp-core/workflows/transition.md +6 -0
  182. package/pp-core/workflows/ui-phase.md +3 -1
  183. package/pp-core/workflows/ui-review.md +2 -1
  184. package/pp-core/workflows/ultraplan-phase.md +1 -0
  185. package/pp-core/workflows/undo.md +14 -10
  186. package/pp-core/workflows/update.md +5 -0
  187. package/pp-core/workflows/validate-phase.md +6 -3
  188. package/pp-core/workflows/vault-review.md +7 -1
  189. package/pp-core/workflows/vault-write-back.md +5 -0
  190. package/pp-core/workflows/verify-phase.md +5 -0
  191. package/pp-core/workflows/verify-work.md +8 -1
  192. package/scripts/prompt-injection-scan.sh +1 -1
  193. package/scripts/sync-runtime-launcher.cjs +115 -5
  194. package/pp-core/workflows/discovery-sprint/templates/context-template.md +0 -266
@@ -11,8 +11,8 @@ D-13 (gate on .platform-bindings.json via the Phase 7 reader — Step 2 now comp
11
11
  writes this file too, not only reads it, Phase 34 INIT-01).
12
12
 
13
13
  Provisioning is provider-aware (Phase 34 D-11/D-13): `resolveProvider` is the sole
14
- authority for which provisioning path runs. The gated GitLab `glab` recipe (G-2…G-5) runs
15
- only when it resolves to `gitlab`; under any other provider (`azure` today) it is
14
+ authority for which provisioning path runs. The gated create recipe runs only when it
15
+ resolves to `gitlab` (`glab`, G-2…G-5) or `github` (`gh`, H-2…H-5); under any other provider (`azure` today) it is
16
16
  **skipped, not failed**, and the run falls through to this same generic remote-wiring
17
17
  path so the user still leaves with a wired remote (Phase 34 INIT-03).
18
18
 
@@ -31,13 +31,6 @@ Read all files referenced by the invoking prompt's execution_context before star
31
31
 
32
32
  <process>
33
33
 
34
- ## Shim hoist
35
-
36
- Resolve `pp_run` before any step — required for the platform-bindings preflight (D-13).
37
-
38
- ```bash
39
- ```
40
-
41
34
  ## Text mode (resolve before any prompt — WR-02)
42
35
 
43
36
  Resolve `TEXT_MODE` here, **before Step 0**, because the Step 0 drift prompt is the first
@@ -91,13 +84,13 @@ step is skipped entirely.
91
84
 
92
85
  ## 1. Worktree-aware git init
93
86
 
94
- Parse the init signals by running the query below. `init.execute-phase` is reused here
95
- deliberately (IN-02): it is the shared verb that emits the generic git-state signals
96
- (`has_git`, `in_nested_subdir`, `git_worktree_root`) this step needs — no phase work is
97
- triggered when called with an empty phase argument.
87
+ Parse the init signals by running the query below. `init.ingest-docs` takes no argument
88
+ and emits the git-state signals this step needs (`has_git`, `in_nested_subdir`,
89
+ `git_worktree_root`) from the same #3491 reader `init.new-project` uses. Never use
90
+ `init.execute-phase ""`: it refuses an empty phase, and the `{}` fallback would disable the guard.
98
91
 
99
92
  ```bash
100
- INIT=$(pp_run query init.execute-phase "" 2>/dev/null || echo '{}')
93
+ INIT=$(pp_run query init.ingest-docs 2>/dev/null || echo '{}')
101
94
  if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
102
95
  ```
103
96
 
@@ -177,7 +170,12 @@ its booleans, never in `$?`, and its `error` prose is never echoed (D-07-07). Th
177
170
  runs only *after* a clean probe, so a project that cannot reach its vault never ends up
178
171
  with a half-populated cache (D-07-05).
179
172
 
173
+ The verdict is the client vault's. The platform repo is probed and shown, but a client
174
+ project may have no access to it, so an unreachable platform repo reads `(not required)`
175
+ and does not fail the proof.
176
+
180
177
  ```bash
178
+ [ -z "${ZSH_VERSION:-}" ] || emulate -R sh
181
179
  # D-07-01 + OD-1 + INIT-01: three signals, all read through the resolvers every other path
182
180
  # uses. Anything but the literal "true" is a fail-inert default.
183
181
  PP_INIT_PROBE=""
@@ -219,26 +217,25 @@ if [ "$VAULT_ON" = "true" ] && [ "$PROFILE_RESOLVED" = "true" ]; then
219
217
  PROBE=$(pp_run query vault probe --raw 2>/dev/null || echo '{"targets":[]}')
220
218
  # R3-CR-02 + R3-WR-02: ONE evaluation of the target set, read by every arm
221
219
  # below. FOUR hand-copied de-dup pipelines stood here and had already
222
- # diverged; the one that mattered inverted the proof. 07-18 excluded the
223
- # fallback tier AFTER the de-dup, and targets arrive in the fixed order
224
- # client, shared, platform — so a platform sharing a coordinate with the
225
- # fallback survived under the FALLBACK'S NAME, the exclusion deleted the
226
- # only representative of a REQUIRED tier, and Step 2.6 printed and durably
227
- # recorded a platform reachable line for a target measured unreachable. The
228
- # exclusion now runs BEFORE the de-dup; the DISPLAY de-dup keeps the REQUIRED
229
- # tier's identity when two tiers name one coordinate (a fallback identical to
230
- # the client is still one remote, not two). OD-5 Option A unchanged: the
231
- # fallback tier stays out of the verdict and out of the note's sick list and
232
- # keeps its "(fallback tier, not required)" line when its coordinate is its
233
- # own. The all-down flag counts EVERY distinct remote, fallback included, ON
234
- # PURPOSE: it drives the offline candidate below, and a fallback that answered
235
- # is evidence the network is up. CR-02: a REQUIRED target with no coordinate
236
- # gets its own "missing" line — without it the D-07-02 case printed a card
237
- # whose every line read healthy under a fail verdict; measured, not a
238
- # diagnosis (D-07-07). EMITS: line 1 verdict, 2 all-down, 3 note, 4+ card; a
239
- # card line can never carry a newline of its own (redactCloneTarget collapses
240
- # the control class at the source, R2-WR-03), so the split below is safe.
241
- PROBE_EVAL=$(printf '%s' "$PROBE" | node -e "const d=require('fs').readFileSync(0,'utf-8');try{const t=(JSON.parse(d).targets||[]).filter(x=>x&&x.configured===true);const key=x=>[x.repo,x.ref,x.host].join('|');const names=new Set(t.map(x=>x.name));const seen=new Set();const req=t.filter(x=>x.name!=='shared'&&!seen.has(key(x))&&seen.add(key(x)));const pick=new Map();for(const x of t){const k=key(x);const p=pick.get(k);if(!p||(p.name==='shared'&&x.name!=='shared'))pick.set(k,x);}const shown=t.filter(x=>pick.get(key(x))===x);const cfg={client:'client.vaultRepo',platform:'platform.repo'};const gone=Object.keys(cfg).filter(n=>!names.has(n));const ok=names.has('client')&&names.has('platform')&&req.length>0&&req.every(x=>x.reachable===true&&x.refFound===true);const sick=req.filter(x=>!(x.reachable===true&&x.refFound===true)).map(x=>String(x.name)+' '+String(x.repoSafe)+' @ '+String(x.ref)+' '+(x.reachable===true?'ref not found':'unreachable'));const all=gone.map(n=>n+' not configured').concat(sick);const lines=shown.map(x=>' '+String(x.name).padEnd(9)+String(x.repoSafe)+' @ '+((x.name==='platform'&&x.ref==='HEAD')?'(unpinned)':String(x.ref))+' '+(x.reachable===true?(x.refFound===true?'reachable, ref found':'ref not found'):'unreachable')+(x.name==='shared'?' (fallback tier, not required)':'')).concat(gone.map(n=>' '+n.padEnd(9)+'(not configured) missing — '+cfg[n]+' is absent from .platform-bindings.json'));process.stdout.write([ok?'pass':'fail',String(shown.length>0&&shown.every(x=>x.reachable!==true)),'failed='+(all.length?all.join('; '):'probe payload unreadable')].concat(lines).join('\n'));}catch{process.stdout.write('fail\nfalse\nfailed=probe payload unreadable');}")
220
+ # diverged, so every arm reads this one line. Only the CLIENT vault decides
221
+ # the verdict: it is what the fetch below lands. The platform repo is shown
222
+ # and recorded but not required (a client project may have no access to
223
+ # it), so an unreachable platform line carries "(not required)", like the
224
+ # fallback tier (OD-5 Option A: shared is probed and shown, out of the
225
+ # verdict and out of the note's sick list). A binding with no platform.repo
226
+ # at all still fails as "not configured". The DISPLAY de-dup keeps a
227
+ # non-shared tier's identity when two tiers name one coordinate, so a
228
+ # platform colliding with the fallback is still shown as platform. The
229
+ # all-down flag counts EVERY distinct remote, fallback included, ON
230
+ # PURPOSE: it drives the offline candidate below, and a remote that
231
+ # answered is evidence the network is up. CR-02: a target with no
232
+ # coordinate gets its own "missing" line — without it the D-07-02 case
233
+ # printed a card whose every line read healthy under a fail verdict;
234
+ # measured, not a diagnosis (D-07-07). EMITS: line 1 verdict, 2 all-down,
235
+ # 3 note, 4+ card; a card line can never carry a newline of its own
236
+ # (redactCloneTarget collapses the control class at the source, R2-WR-03),
237
+ # so the split below is safe.
238
+ PROBE_EVAL=$(printf '%s' "$PROBE" | node -e "const d=require('fs').readFileSync(0,'utf-8');try{const t=(JSON.parse(d).targets||[]).filter(x=>x&&x.configured===true);const key=x=>[x.repo,x.ref,x.host].join('|');const names=new Set(t.map(x=>x.name));const healthy=x=>x.reachable===true&&x.refFound===true;const req=t.filter(x=>x.name==='client');const pick=new Map();for(const x of t){const k=key(x);const p=pick.get(k);if(!p||(p.name==='shared'&&x.name!=='shared'))pick.set(k,x);}const shown=t.filter(x=>pick.get(key(x))===x);const cfg={client:'client.vaultRepo',platform:'platform.repo'};const gone=Object.keys(cfg).filter(n=>!names.has(n));const ok=names.has('client')&&names.has('platform')&&req.length>0&&req.every(healthy);const sick=req.filter(x=>!healthy(x)).map(x=>String(x.name)+' '+String(x.repoSafe)+' @ '+String(x.ref)+' '+(x.reachable===true?'ref not found':'unreachable'));const all=gone.map(n=>n+' not configured').concat(sick);const lines=shown.map(x=>' '+String(x.name).padEnd(9)+String(x.repoSafe)+' @ '+((x.name==='platform'&&x.ref==='HEAD')?'(unpinned)':String(x.ref))+' '+(x.reachable===true?(x.refFound===true?'reachable, ref found':'ref not found'):'unreachable')+(x.name==='shared'?' (fallback tier, not required)':(x.name==='platform'&&!healthy(x)?' (not required)':''))).concat(gone.map(n=>' '+n.padEnd(9)+'(not configured) missing — '+cfg[n]+' is absent from .platform-bindings.json'));process.stdout.write([ok?'pass':'fail',String(shown.length>0&&shown.every(x=>x.reachable!==true)),'failed='+(all.length?all.join('; '):'probe payload unreadable')].concat(lines).join('\n'));}catch{process.stdout.write('fail\nfalse\nfailed=probe payload unreadable');}")
242
239
  PROBE_VERDICT=$(printf '%s' "$PROBE_EVAL" | sed -n '1p')
243
240
  PROBE_ALL_DOWN=$(printf '%s' "$PROBE_EVAL" | sed -n '2p')
244
241
  PROBE_NOTE=$(printf '%s' "$PROBE_EVAL" | sed -n '3p')
@@ -257,6 +254,9 @@ if [ "$VAULT_ON" = "true" ] && [ "$PROFILE_RESOLVED" = "true" ]; then
257
254
  FETCH_COUNT=$(printf '%s' "$FETCH" | node -e "const d=require('fs').readFileSync(0,'utf-8');try{process.stdout.write(String((JSON.parse(d).files||[]).length));}catch{process.stdout.write('0');}")
258
255
  FETCH_SUMMARY=$(printf '%s' "$FETCH" | node -e "const d=require('fs').readFileSync(0,'utf-8');try{process.stdout.write(String(JSON.parse(d).summaryWritten===true));}catch{process.stdout.write('false');}")
259
256
  PLATFORM_REPO=$(printf '%s' "$PROBE" | node -e "const d=require('fs').readFileSync(0,'utf-8');try{const x=(JSON.parse(d).targets||[]).find(y=>y&&y.name==='platform');process.stdout.write(String((x&&x.repoSafe)||''));}catch{process.stdout.write('');}")
257
+ # The platform repo is not required (a client may have no access to it): its measured
258
+ # state goes on the card and into the record, it never decides the verdict.
259
+ PLATFORM_STATE=$(printf '%s' "$PROBE" | node -e "const d=require('fs').readFileSync(0,'utf-8');try{const x=(JSON.parse(d).targets||[]).find(y=>y&&y.name==='platform');process.stdout.write(x&&x.reachable===true?(x.refFound===true?'reachable':'ref not found (not required)'):'unreachable (not required)');}catch{process.stdout.write('unreachable (not required)');}")
260
260
  # Never the probe's internal HEAD placeholder — an unpinned platform says so.
261
261
  PLATFORM_TAG=$(pp_run query platform-bindings --pick platform.tag 2>/dev/null)
262
262
  if [ "$FETCH_TIER" = "client" ] && [ "$FETCH_SUMMARY" = "true" ] && { [ "$FETCH_SOURCE" = "fetched" ] || [ "$FETCH_SOURCE" = "cache" ]; }; then
@@ -264,13 +264,13 @@ if [ "$VAULT_ON" = "true" ] && [ "$PROFILE_RESOLVED" = "true" ]; then
264
264
  # card prints. A refused write used to be a warning under a PASSED card, and PASSED
265
265
  # routes to "continue to Step 3" — so "unrecorded" was defined to continue. It is a
266
266
  # failure of the step, kind FAIL_KIND=record. Markers unchanged: pass/unbound/fail.
267
- if pp_run query init-contract --record-probe "passed=platform ${PLATFORM_REPO} @ ${PLATFORM_TAG:-(unpinned)}; vault ${FETCH_REPO} @ ${FETCH_REF} tier ${FETCH_TIER} ${FETCH_COUNT} files" >/dev/null 2>&1; then
267
+ if pp_run query init-contract --record-probe "passed=platform ${PLATFORM_REPO} @ ${PLATFORM_TAG:-(unpinned)} ${PLATFORM_STATE}; vault ${FETCH_REPO} @ ${FETCH_REF} tier ${FETCH_TIER} ${FETCH_COUNT} files" >/dev/null 2>&1; then
268
268
  PP_INIT_PROBE=pass
269
269
  echo "Reachability proof: PASSED"
270
270
  # OD-3 + CR-01: the DISPLAY-SAFE coordinate (repoSafe), ref, tier and booleans
271
271
  # only. Credentials are stripped at the source, so a PAT in the binding reaches
272
272
  # neither this card nor the record. Never url, never error.
273
- printf '%s\n' " platform ${PLATFORM_REPO} @ ${PLATFORM_TAG:-(unpinned)} reachable"
273
+ printf '%s\n' " platform ${PLATFORM_REPO} @ ${PLATFORM_TAG:-(unpinned)} ${PLATFORM_STATE}"
274
274
  printf '%s\n' " vault ${FETCH_REPO} @ ${FETCH_REF} reachable, ref found — tier ${FETCH_TIER}, sha ${FETCH_SHA}, ${FETCH_COUNT} files, SUMMARY.md written (${FETCH_SOURCE})"
275
275
  printf '%s\n' "$PROBE_LINES" | grep '^ shared ' || true
276
276
  echo "PP_INIT_PROBE=pass"
@@ -353,7 +353,7 @@ outright. Take THIS generic sub-path when
353
353
  bindings ARE present, take the gated GitLab/Azure sub-path below instead — that section is
354
354
  still part of Step 3, not a separate optional step. **The provider answer never decides
355
355
  whether Step 3 runs** — inside the gated sub-path it only decides whether the
356
- GitLab-specific `glab` recipe fires or falls through to this same generic code below.
356
+ `glab` or `gh` create recipe fires or falls through to this same generic code below.
357
357
 
358
358
  On the generic path:
359
359
 
@@ -393,14 +393,14 @@ gets its own per-action confirmation immediately before it fires — nothing bat
393
393
  step makes **ZERO `gh`/`glab`/provider-API calls**. The push target is the user-supplied
394
394
  URL only.
395
395
 
396
- ### Gated GitLab/Azure provisioning (Step 3's other sub-path — bindings present)
396
+ ### Gated GitLab/GitHub provisioning (Step 3's other sub-path — bindings present)
397
397
 
398
398
  This is Step 3's other sub-path, not a separate step. It runs **instead of** the generic
399
399
  remote-wiring path above when
400
400
  `pp_run query platform-bindings --pick present 2>/dev/null` equals `"true"`. Within it,
401
- G-1 always runs first to source coordinates and apply the two guards below; the
402
- GitLab-specific create recipe (G-2…G-5) runs **only when the resolved provider is
403
- `gitlab`** — under any other provider (`azure` today) it is skipped, not failed, and the
401
+ G-1 always runs first to source coordinates and apply the two guards below; a create
402
+ recipe runs **only when the resolved provider is `gitlab` or `github`** — under any
403
+ other provider (`azure` today) it is skipped, not failed, and the
404
404
  run falls through to Step 3's generic remote-wiring path above instead (D-11, D-13).
405
405
  Implements D-05 (per-action confirmation), D-09 (existence probe → adopt-if-exists), D-10
406
406
  (private by default), D-11 (auth verify-only; gated on provider), D-12 (adopt-if-exists
@@ -442,8 +442,9 @@ comes from, skip G-2 through G-5 entirely, and fall through to Step 3's generic
442
442
  wiring:
443
443
 
444
444
  ```bash
445
+ [ -z "${ZSH_VERSION:-}" ] || emulate -R sh
445
446
  if [ -z "$GROUP" ] || [ -z "$SLUG" ]; then
446
- echo "Skipping GitLab provisioning: $( [ -z "$GROUP" ] && echo "GROUP (org profile seed provisioning group)" || echo "SLUG (project.slug)" ) is empty — falling through to Step 3's generic remote wiring."
447
+ echo "Skipping repository provisioning: $( [ -z "$GROUP" ] && echo "GROUP (org profile seed provisioning group)" || echo "SLUG (project.slug)" ) is empty — falling through to Step 3's generic remote wiring."
447
448
  fi
448
449
  ```
449
450
 
@@ -452,35 +453,24 @@ fi
452
453
 
453
454
  ```bash
454
455
  PROVIDER=$(pp_run query compose-bindings --offline --pick provider 2>/dev/null)
455
- if [ "$PROVIDER" != "gitlab" ]; then
456
- echo "Skipping GitLab-specific provisioning for provider \"$PROVIDER\" — falling through to Step 3's generic remote wiring instead."
456
+ if [ "$PROVIDER" != "gitlab" ] && [ "$PROVIDER" != "github" ]; then
457
+ echo "Skipping repository provisioning for provider \"$PROVIDER\" — falling through to Step 3's generic remote wiring instead."
457
458
  fi
458
459
  ```
459
460
 
460
- Continue to G-2 only when the empty-group guard above did not skip **and** `PROVIDER`
461
- equals `gitlab`. Otherwise stop here and go to Step 3's generic remote wiring (`:283-312`)
461
+ Continue only when the empty-group guard above did not skip **and** `PROVIDER`
462
+ is `gitlab` or `github`. Otherwise stop here and go to Step 3's generic remote wiring (`:283-312`)
462
463
  instead — do not fail, do not error, do not prompt for a missing CLI. "Skipped, not
463
464
  failed" is satisfied *and* the user still leaves with a wired remote, which a bare skip
464
465
  would not give them; pp-core makes zero provider-API calls it has no client for.
465
466
 
466
- When both conditions hold, **Read `pp-core/workflows/init/steps/gitlab-provisioning.md` and
467
- follow it.** That file owns G-2 (auth verify-only), G-3 (existence probe →
468
- adopt-if-exists), G-4 (per-action confirmation, then the locked `--private` create) and G-5
469
- (post-create reconciliation). Under any other provider do not read it at all — fall through
470
- to Step 3's generic remote wiring above.
471
-
472
- **Security invariants — landmine guards:**
473
- - Always use the group-qualified `$GROUP/$SLUG` path — never a bare slug (T-08-04, D-09).
474
- A bare slug creates under the user's personal namespace, not the client group. G-1's
475
- empty-group guard above is what actually enforces this before any create attempt.
476
- - Always pass `--private` explicitly — never inherit `glab`'s default `--internal`
477
- visibility (T-08-05, D-10). The `--internal` flag is what makes this dangerous; the
478
- locked recipe must carry `--private` and must NOT emit `--internal`.
479
- - Always confirm immediately before each outward action — nothing batched (T-08-06, D-05).
480
- - Verify-only auth via `glab auth status` — never run `glab auth login`, never read
481
- or write the user's token (T-08-08, D-11). Bindings hold only non-secret routing data.
482
- - Always reconcile create with a `glab repo view` probe — a non-zero create exit does
483
- NOT mean nothing happened (T-08-09, D-09).
467
+ When both conditions hold, **Read `pp-core/workflows/init/steps/gitlab-provisioning.md`
468
+ (provider `gitlab`) or `pp-core/workflows/init/steps/github-provisioning.md` (provider
469
+ `github`) and follow it.** Each owns, for its
470
+ CLI, auth verify-only, the existence probe → adopt-if-exists, per-action confirmation then
471
+ the locked `--private` create, post-create reconciliation, and its security invariants
472
+ (landmine guards: group-qualified path, explicit `--private`, verify-only auth). Under any
473
+ other provider read neither — fall through to Step 3's generic remote wiring above.
484
474
 
485
475
  ### Gated client-context capture (Pareto projects — bindings present)
486
476
 
@@ -555,9 +545,9 @@ legitimately not exist — the session declines or composes nothing on the inert
555
545
 
556
546
  ```bash
557
547
  if [ -f .platform-bindings.json ]; then
558
- pp_run query commit "docs: initialize project" --files .planning/config.json .platform-bindings.json
548
+ pp_run query commit "docs(planning): initialize project" --files .planning/config.json .platform-bindings.json
559
549
  else
560
- pp_run query commit "docs: initialize project" --files .planning/config.json
550
+ pp_run query commit "docs(planning): initialize project" --files .planning/config.json
561
551
  fi
562
552
  ```
563
553
 
@@ -327,6 +327,7 @@ Continue to verify_output.
327
327
  Verify all documents created successfully:
328
328
 
329
329
  ```bash
330
+ [ -z "${ZSH_VERSION:-}" ] || emulate -R sh
330
331
  ls -la .planning/codebase/
331
332
  wc -l .planning/codebase/*.md
332
333
  ```
@@ -346,6 +347,7 @@ Continue to scan_for_secrets.
346
347
  Run secret pattern detection:
347
348
 
348
349
  ```bash
350
+ [ -z "${ZSH_VERSION:-}" ] || emulate -R sh
349
351
  # Check for common API key patterns in generated docs
350
352
  grep -E '(sk-[a-zA-Z0-9]{20,}|sk_live_[a-zA-Z0-9]+|sk_test_[a-zA-Z0-9]+|ghp_[a-zA-Z0-9]{36}|gho_[a-zA-Z0-9]{36}|glpat-[a-zA-Z0-9_-]+|AKIA[A-Z0-9]{16}|xox[baprs]-[a-zA-Z0-9-]+|-----BEGIN.*PRIVATE KEY|eyJ[a-zA-Z0-9_-]+\.eyJ[a-zA-Z0-9_-]+\.)' .planning/codebase/*.md 2>/dev/null && SECRETS_FOUND=true || SECRETS_FOUND=false
351
353
  ```
@@ -379,7 +381,8 @@ Continue to commit_codebase_map.
379
381
  Commit the codebase map:
380
382
 
381
383
  ```bash
382
- pp_run query commit "docs: map existing codebase" --files .planning/codebase/*.md
384
+ [ -z "${ZSH_VERSION:-}" ] || emulate -R sh
385
+ pp_run query commit "docs(planning): map existing codebase" --files .planning/codebase/*.md
383
386
  ```
384
387
 
385
388
  Continue to offer_next.
@@ -390,6 +393,7 @@ Present completion summary and next steps.
390
393
 
391
394
  **Get line counts:**
392
395
  ```bash
396
+ [ -z "${ZSH_VERSION:-}" ] || emulate -R sh
393
397
  wc -l .planning/codebase/*.md
394
398
  ```
395
399
 
@@ -78,6 +78,7 @@ git tag -l "v${VERSION}" | head -1
78
78
  ```
79
79
  If the tag exists:
80
80
  ```bash
81
+ [ -z "${ZSH_VERSION:-}" ] || emulate -R sh
81
82
  git log v${VERSION} --oneline | wc -l
82
83
  git diff --stat $(git log --format=%H --reverse v${VERSION} | head -1)..v${VERSION}
83
84
  ```
@@ -190,7 +191,7 @@ mkdir -p .planning/reports
190
191
 
191
192
  Write the summary, then commit:
192
193
  ```bash
193
- pp_run query commit "docs(v${VERSION}): generate milestone summary for onboarding" --files \
194
+ pp_run query commit "docs(planning): generate v${VERSION} milestone summary for onboarding" --files \
194
195
  ".planning/reports/MILESTONE_SUMMARY-v${VERSION}.md"
195
196
  ```
196
197
 
@@ -99,6 +99,7 @@ If any of the three answers is empty or whitespace-only, error and re-prompt tha
99
99
  **Validate via the centralized User Story validator.** The verb owns the canonical regex `/^As a .+, I want to .+, so that .+\.$/` and surfaces per-error guidance:
100
100
 
101
101
  ```bash
102
+ [ -z "${ZSH_VERSION:-}" ] || emulate -R sh
102
103
  USER_STORY_RESULT=$(pp_run query user-story.validate --story "$USER_STORY")
103
104
  if [ "$(echo "$USER_STORY_RESULT" | jq -r '.valid')" != "true" ]; then
104
105
  echo "$USER_STORY_RESULT" | jq -r '.errors[]' >&2
@@ -55,6 +55,7 @@ If the flag is absent, keep the current behavior of continuing phase numbering f
55
55
  Check `.planning/seeds/` for seed files that match the milestone goals gathered in step 2.
56
56
 
57
57
  ```bash
58
+ [ -z "${ZSH_VERSION:-}" ] || emulate -R sh
58
59
  ls .planning/seeds/SEED-*.md 2>/dev/null
59
60
  ```
60
61
 
@@ -217,7 +218,7 @@ pp_run query phases.clear --confirm
217
218
  ```
218
219
 
219
220
  ```bash
220
- pp_run query commit "docs: start milestone v[X.Y] [Name]" --files .planning/PROJECT.md .planning/STATE.md
221
+ pp_run query commit "docs(planning): start milestone v[X.Y] [Name]" --files .planning/PROJECT.md .planning/STATE.md
221
222
  ```
222
223
 
223
224
  ## 7. Load Context and Resolve Models
@@ -256,6 +257,7 @@ If `--reset-phase-numbers` is active:
256
257
  If `phase_dir_count > 0`, let the engine archive what the closed milestones name — never move phase directories by hand:
257
258
 
258
259
  ```bash
260
+ [ -z "${ZSH_VERSION:-}" ] || emulate -R sh
259
261
  ARCHIVED=$(pp_run query phases.archive-closed ${GSD_WS})
260
262
  ```
261
263
 
@@ -368,7 +370,7 @@ Commit after writing.
368
370
  **Synthesizer output self-heal (#222) — verify SUMMARY.md materialized:** The synthesizer's canonical output is `.planning/research/SUMMARY.md` on disk; its brief structured return (`## SYNTHESIS COMPLETE` plus a few `###` confirmation lines) is NOT the file content. A known LLM false-refusal (issue #222) sometimes makes the agent return the full SUMMARY.md document inline — fabricating a write restriction (e.g. "the runtime is blocking file writes") — instead of writing the file. Prompt hardening alone does not fully eliminate it, so the orchestrator MUST absorb the failure deterministically before spawning `pp-roadmapper`:
369
371
 
370
372
  1. Verify `.planning/research/SUMMARY.md` exists AND is substantive — non-empty, and free of any leftover `<!-- gsd:write-continue -->` continuation sentinel (which marks a truncated/incomplete write). You may validate with `pp-tools verify-summary .planning/research/SUMMARY.md` — it exits 0 regardless, so check its JSON `passed` field (`"passed": false` means missing or invalid), not the process exit code. If it passes, continue normally.
371
- 2. If it is MISSING or invalid AND the synthesizer's return message contains the FULL SUMMARY.md document — recognizable by the template's top-level markers `# Project Research Summary`, `## Key Findings`, `## Implications for Roadmap`, and `## Sources`, not merely the brief `## SYNTHESIS COMPLETE` confirmation — the false-refusal fired: write that returned document to `.planning/research/SUMMARY.md` with the Write tool, then commit ALL research artifacts the synthesizer owns (it commits on behalf of the four researchers) with `pp-tools query commit "docs: complete project research" --files .planning/research/` unless they are already committed. Log `⚠ #222 self-heal: synthesizer returned SUMMARY.md inline without writing it; orchestrator persisted the file.`
373
+ 2. If it is MISSING or invalid AND the synthesizer's return message contains the FULL SUMMARY.md document — recognizable by the template's top-level markers `# Project Research Summary`, `## Key Findings`, `## Implications for Roadmap`, and `## Sources`, not merely the brief `## SYNTHESIS COMPLETE` confirmation — the false-refusal fired: write that returned document to `.planning/research/SUMMARY.md` with the Write tool, then commit ALL research artifacts the synthesizer owns (it commits on behalf of the four researchers) with `pp_run query commit "docs(planning): complete project research" --files .planning/research/` unless they are already committed. Log `⚠ #222 self-heal: synthesizer returned SUMMARY.md inline without writing it; orchestrator persisted the file.`
372
374
  3. If it is MISSING or invalid AND the return is only a brief confirmation (no full SUMMARY document to recover), the synthesizer genuinely failed — surface the error and stop; do NOT spawn `pp-roadmapper` against a missing or incomplete SUMMARY.md.
373
375
 
374
376
  This guarantees `pp-roadmapper` (which lists SUMMARY.md as required reading) never runs against a missing or truncated SUMMARY.md.
@@ -456,7 +458,7 @@ If "adjust": Return to scoping.
456
458
 
457
459
  **Commit requirements:**
458
460
  ```bash
459
- pp_run query commit "docs: define milestone v[X.Y] requirements" --files .planning/REQUIREMENTS.md
461
+ pp_run query commit "docs(planning): define milestone v[X.Y] requirements" --files .planning/REQUIREMENTS.md
460
462
  ```
461
463
 
462
464
  ## 10. Create Roadmap
@@ -542,7 +544,7 @@ Success criteria:
542
544
 
543
545
  **Commit roadmap** (after approval):
544
546
  ```bash
545
- pp_run query commit "docs: create milestone v[X.Y] roadmap ([N] phases)" --files .planning/ROADMAP.md .planning/STATE.md .planning/REQUIREMENTS.md
547
+ pp_run query commit "docs(planning): create milestone v[X.Y] roadmap ([N] phases)" --files .planning/ROADMAP.md .planning/STATE.md .planning/REQUIREMENTS.md
546
548
  ```
547
549
 
548
550
  ## 10.5. Link Pending Todos to Roadmap Phases
@@ -551,6 +553,7 @@ After roadmap approval, scan pending todos against the newly approved phases. Fo
551
553
 
552
554
  **Check for pending todos:**
553
555
  ```bash
556
+ [ -z "${ZSH_VERSION:-}" ] || emulate -R sh
554
557
  PENDING_TODOS=$(ls .planning/todos/pending/*.md 2>/dev/null | head -50)
555
558
  ```
556
559
 
@@ -585,7 +588,8 @@ files: [existing]
585
588
 
586
589
  **If any todos were linked:**
587
590
  ```bash
588
- pp_run query commit "docs: tag [count] pending todos with resolves_phase after milestone v[X.Y] roadmap" --files .planning/todos/pending/*.md
591
+ [ -z "${ZSH_VERSION:-}" ] || emulate -R sh
592
+ pp_run query commit "docs(planning): tag [count] pending todos with resolves_phase after milestone v[X.Y] roadmap" --files .planning/todos/pending/*.md
589
593
  ```
590
594
 
591
595
  Print a summary:
@@ -323,7 +323,7 @@ pp_run query config-new-project '{"mode":"yolo","granularity":"[selected]","para
323
323
 
324
324
  ```bash
325
325
  mkdir -p .planning
326
- pp_run query commit "chore: add project config" --files .planning/config.json
326
+ pp_run query commit "chore(planning): add project config" --files .planning/config.json
327
327
  ```
328
328
 
329
329
  **Persist auto-advance chain flag to config (survives context compaction):**
@@ -339,6 +339,7 @@ Proceed to Step 4 (skip Steps 3 and 5).
339
339
  Check for existing spike and sketch work that should inform project setup:
340
340
 
341
341
  ```bash
342
+ [ -z "${ZSH_VERSION:-}" ] || emulate -R sh
342
343
  # Check for spike findings skill (project-local)
343
344
  SPIKE_SKILL=$(ls ./.claude/skills/spike-findings-*/SKILL.md 2>/dev/null | head -1 || true)
344
345
 
@@ -500,9 +501,9 @@ Initialize with any decisions made during questioning:
500
501
  ```markdown
501
502
  ## Key Decisions
502
503
 
503
- | Decision | Rationale | Outcome |
504
- |----------|-----------|---------|
505
- | [Choice from questioning] | [Why] | — Pending |
504
+ | ID | Decision | Rationale | Outcome |
505
+ |----|----------|-----------|---------|
506
+ | — | [Choice from questioning] | [Why] | — Pending |
506
507
  ```
507
508
 
508
509
  **Last updated footer:**
@@ -539,7 +540,7 @@ Do not compress. Capture everything gathered.
539
540
 
540
541
  ```bash
541
542
  mkdir -p .planning
542
- pp_run query commit "docs: initialize project" --files .planning/PROJECT.md
543
+ pp_run query commit "docs(planning): initialize project" --files .planning/PROJECT.md
543
544
  ```
544
545
 
545
546
  ## 5. Workflow Preferences
@@ -836,7 +837,7 @@ pp_run query config-new-project '{"mode":"[yolo|interactive]","granularity":"[se
836
837
  **Commit config.json:**
837
838
 
838
839
  ```bash
839
- pp_run query commit "chore: add project config" --files .planning/config.json
840
+ pp_run query commit "chore(planning): add project config" --files .planning/config.json
840
841
  ```
841
842
 
842
843
  ## 5.1. Sub-Repo Detection
@@ -884,9 +885,9 @@ echo "$ARM"
884
885
  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
886
  if [ "$ARM_EXIT" -eq 0 ] && [ "$ARMED" = "true" ]; then
886
887
  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
+ pp_run query commit "chore(planning): arm client vault from org profile" --files .planning/config.json .platform-bindings.json
888
889
  else
889
- pp_run query commit "chore: arm client vault from org profile" --files .planning/config.json
890
+ pp_run query commit "chore(planning): arm client vault from org profile" --files .planning/config.json
890
891
  fi
891
892
  fi
892
893
  ```
@@ -1138,7 +1139,7 @@ Commit after writing.
1138
1139
  **Synthesizer output self-heal (#222) — verify SUMMARY.md materialized:** The synthesizer's canonical output is `.planning/research/SUMMARY.md` on disk; its brief structured return (`## SYNTHESIS COMPLETE` plus a few `###` confirmation lines) is NOT the file content. A known LLM false-refusal (issue #222) sometimes makes the agent return the full SUMMARY.md document inline — fabricating a write restriction (e.g. "the runtime is blocking file writes") — instead of writing the file. Prompt hardening alone does not fully eliminate it, so the orchestrator MUST absorb the failure deterministically before spawning `pp-roadmapper`:
1139
1140
 
1140
1141
  1. Verify `.planning/research/SUMMARY.md` exists AND is substantive — non-empty, and free of any leftover `<!-- gsd:write-continue -->` continuation sentinel (which marks a truncated/incomplete write). You may validate with `pp-tools verify-summary .planning/research/SUMMARY.md` — it exits 0 regardless, so check its JSON `passed` field (`"passed": false` means missing or invalid), not the process exit code. If it passes, continue normally.
1141
- 2. If it is MISSING or invalid AND the synthesizer's return message contains the FULL SUMMARY.md document — recognizable by the template's top-level markers `# Project Research Summary`, `## Key Findings`, `## Implications for Roadmap`, and `## Sources`, not merely the brief `## SYNTHESIS COMPLETE` confirmation — the false-refusal fired: write that returned document to `.planning/research/SUMMARY.md` with the Write tool, then commit ALL research artifacts the synthesizer owns (it commits on behalf of the four researchers) with `pp-tools query commit "docs: complete project research" --files .planning/research/` unless they are already committed. Log `⚠ #222 self-heal: synthesizer returned SUMMARY.md inline without writing it; orchestrator persisted the file.`
1142
+ 2. If it is MISSING or invalid AND the synthesizer's return message contains the FULL SUMMARY.md document — recognizable by the template's top-level markers `# Project Research Summary`, `## Key Findings`, `## Implications for Roadmap`, and `## Sources`, not merely the brief `## SYNTHESIS COMPLETE` confirmation — the false-refusal fired: write that returned document to `.planning/research/SUMMARY.md` with the Write tool, then commit ALL research artifacts the synthesizer owns (it commits on behalf of the four researchers) with `pp_run query commit "docs(planning): complete project research" --files .planning/research/` unless they are already committed. Log `⚠ #222 self-heal: synthesizer returned SUMMARY.md inline without writing it; orchestrator persisted the file.`
1142
1143
  3. If it is MISSING or invalid AND the return is only a brief confirmation (no full SUMMARY document to recover), the synthesizer genuinely failed — surface the error and stop; do NOT spawn `pp-roadmapper` against a missing or incomplete SUMMARY.md.
1143
1144
 
1144
1145
  This guarantees `pp-roadmapper` (which lists SUMMARY.md as required reading) never runs against a missing or truncated SUMMARY.md.
@@ -1312,7 +1313,7 @@ If "adjust": Return to scoping.
1312
1313
  **Commit requirements:**
1313
1314
 
1314
1315
  ```bash
1315
- pp_run query commit "docs: define v1 requirements" --files .planning/REQUIREMENTS.md
1316
+ pp_run query commit "docs(planning): define v1 requirements" --files .planning/REQUIREMENTS.md
1316
1317
  ```
1317
1318
 
1318
1319
  ## 7.5. Project Structure Mode
@@ -1496,7 +1497,7 @@ This ensures new projects get the default pp-core workflow-enforcement guidance
1496
1497
  **Commit roadmap (after approval or auto mode):**
1497
1498
 
1498
1499
  ```bash
1499
- pp_run query commit "docs: create roadmap ([N] phases)" --files .planning/ROADMAP.md .planning/STATE.md .planning/REQUIREMENTS.md "$INSTRUCTION_FILE"
1500
+ pp_run query commit "docs(planning): create roadmap ([N] phases)" --files .planning/ROADMAP.md .planning/STATE.md .planning/REQUIREMENTS.md "$INSTRUCTION_FILE"
1500
1501
  ```
1501
1502
 
1502
1503
  ## 9. Done
@@ -110,6 +110,7 @@ Before creating anything, validate:
110
110
 
111
111
  1. **Target path** — must not exist or must be empty:
112
112
  ```bash
113
+ [ -z "${ZSH_VERSION:-}" ] || emulate -R sh
113
114
  if [ -d "$TARGET_PATH" ] && [ "$(ls -A "$TARGET_PATH" 2>/dev/null)" ]; then
114
115
  echo "Error: Target path already exists and is not empty: $TARGET_PATH"
115
116
  echo "Choose a different --name or --path."
@@ -187,7 +187,7 @@ Choice [S]:
187
187
  ```
188
188
  2. Commit the deferral record:
189
189
  ```bash
190
- pp_run query commit "docs: defer incomplete Phase {src} items to backlog"
190
+ pp_run query commit "docs(planning): defer incomplete Phase {src} items to backlog"
191
191
  ```
192
192
  3. Continue routing to `determine_next_action` immediately — no second prompt.
193
193
 
@@ -198,6 +198,7 @@ pp_run query commit "docs: defer incomplete Phase {src} items to backlog"
198
198
  Check for pending spike/sketch work and surface a notice (does not change routing):
199
199
 
200
200
  ```bash
201
+ [ -z "${ZSH_VERSION:-}" ] || emulate -R sh
201
202
  # Check for pending spikes (verdict: PENDING in any README)
202
203
  PENDING_SPIKES=$(grep -rl 'verdict: PENDING' .planning/spikes/*/README.md 2>/dev/null | wc -l | tr -d ' ')
203
204
 
@@ -14,14 +14,15 @@ Read all files referenced by the invoking prompt's execution_context before star
14
14
  Determine what kind of work is being paused and set the handoff destination accordingly:
15
15
 
16
16
  ```bash
17
+ [ -z "${ZSH_VERSION:-}" ] || emulate -R sh
17
18
  # Check for active phase
18
- phase=$(( ls -lt .planning/phases/*/PLAN.md 2>/dev/null || true ) | head -1 | grep -oP 'phases/\K[^/]+' || true)
19
+ phase=$(( ls -lt .planning/phases/*/PLAN.md 2>/dev/null || true ) | head -1 | sed -n 's|.*phases/\([^/]*\)/.*|\1|p')
19
20
 
20
21
  # Check for active spike
21
- spike=$(( ls -lt .planning/spikes/*/SPIKE.md .planning/spikes/*/DESIGN.md .planning/spikes/*/README.md 2>/dev/null || true ) | head -1 | grep -oP 'spikes/\K[^/]+' || true)
22
+ spike=$(( ls -lt .planning/spikes/*/SPIKE.md .planning/spikes/*/DESIGN.md .planning/spikes/*/README.md 2>/dev/null || true ) | head -1 | sed -n 's|.*spikes/\([^/]*\)/.*|\1|p')
22
23
 
23
24
  # Check for active sketch
24
- sketch=$(( ls -lt .planning/sketches/*/README.md .planning/sketches/*/index.html 2>/dev/null || true ) | head -1 | grep -oP 'sketches/\K[^/]+' || true)
25
+ sketch=$(( ls -lt .planning/sketches/*/README.md .planning/sketches/*/index.html 2>/dev/null || true ) | head -1 | sed -n 's|.*sketches/\([^/]*\)/.*|\1|p')
25
26
 
26
27
  # Check for active deliberation
27
28
  deliberation=$(ls .planning/deliberations/*.md 2>/dev/null | head -1 || true)
@@ -57,6 +58,7 @@ Ask user for clarifications if needed via conversational questions.
57
58
 
58
59
  **Also inspect SUMMARY.md files for false completions:**
59
60
  ```bash
61
+ [ -z "${ZSH_VERSION:-}" ] || emulate -R sh
60
62
  # Check for placeholder content in existing summaries
61
63
  grep -l "To be filled\|placeholder\|TBD" .planning/phases/*/*.md 2>/dev/null || true
62
64
  ```
@@ -214,7 +216,7 @@ timestamp=$(pp_run query current-timestamp full --raw)
214
216
 
215
217
  <step name="commit">
216
218
  ```bash
217
- pp_run query commit "wip: [context-name] paused at [X]/[Y]" --files [handoff-path] .planning/HANDOFF.json
219
+ pp_run query commit "docs(planning): pause [context-name] at [X]/[Y]" --files [handoff-path] .planning/HANDOFF.json
218
220
  ```
219
221
  </step>
220
222
 
@@ -11,6 +11,7 @@ Read all files referenced by the invoking prompt's execution_context before star
11
11
  ## 1. Load Audit Results
12
12
 
13
13
  ```bash
14
+ [ -z "${ZSH_VERSION:-}" ] || emulate -R sh
14
15
  # Find the most recent audit file
15
16
  (ls -t .planning/v*-MILESTONE-AUDIT.md 2>/dev/null || true) | head -1
16
17
  ```
@@ -65,6 +66,7 @@ Gap: Flow "View dashboard" broken at data fetch
65
66
  Find highest existing phase:
66
67
  ```bash
67
68
  _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
69
+ [ -z "${ZSH_VERSION:-}" ] || emulate -R sh
68
70
  # Get sorted phase list, extract last one
69
71
  HIGHEST=$(pp_run query phases.list --pick directories[-1])
70
72
  ```
@@ -154,7 +156,7 @@ Repeat for each gap-closure phase number. This produces `{CODE}-{NN}-{slug}/` wh
154
156
  ## 9. Commit Roadmap and Requirements Update
155
157
 
156
158
  ```bash
157
- pp_run query commit "docs(roadmap): add gap closure phases {N}-{M}" --files .planning/ROADMAP.md .planning/REQUIREMENTS.md
159
+ pp_run query commit "docs(planning): add gap closure phases {N}-{M}" --files .planning/ROADMAP.md .planning/REQUIREMENTS.md
158
160
  ```
159
161
 
160
162
  ## 10. Offer Next Steps
@@ -104,7 +104,7 @@ For each plan entry extracted from `PLAN-OUTLINE.md`:
104
104
  5. **Commit per-plan:**
105
105
  ```bash
106
106
  _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
107
- pp_run query commit "docs(${PADDED_PHASE}): plan ${plan_id} (chunked)" --files "${PHASE_DIR}/${plan_id}-PLAN.md"
107
+ pp_run query commit "docs(planning): plan ${plan_id} (chunked)" --phase "${PADDED_PHASE}" --files "${PHASE_DIR}/${plan_id}-PLAN.md"
108
108
  ```
109
109
 
110
110
  After all N plans are written and committed, treat this as `## PLANNING COMPLETE` and continue