@pareto4/pp-core 1.22.0 → 1.23.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 (119) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/bin/install.js +205 -28
  3. package/commands/pp-help.md +2 -1
  4. package/gemini-extension.json +1 -1
  5. package/hooks/dist/pp-check-update-worker.js +1 -1
  6. package/hooks/dist/pp-context-monitor.js +3 -3
  7. package/hooks/dist/pp-statusline.js +88 -14
  8. package/hooks/dist/pp-update-banner.js +44 -4
  9. package/hooks/dist/pp-workflow-guard.js +3 -3
  10. package/hooks/pp-check-update-worker.js +1 -1
  11. package/hooks/pp-context-monitor.js +3 -3
  12. package/hooks/pp-statusline.js +88 -14
  13. package/hooks/pp-update-banner.js +44 -4
  14. package/hooks/pp-workflow-guard.js +3 -3
  15. package/package.json +1 -1
  16. package/pp-core/bin/lib/audience-command.cjs +271 -0
  17. package/pp-core/bin/lib/audience.cjs +293 -0
  18. package/pp-core/bin/lib/commands.cjs +26 -17
  19. package/pp-core/bin/lib/discovery-charter.cjs +13 -1
  20. package/pp-core/bin/lib/discovery-input-check.cjs +11 -11
  21. package/pp-core/bin/lib/discovery-session-check.cjs +7 -7
  22. package/pp-core/bin/lib/discovery-status.cjs +419 -0
  23. package/pp-core/bin/lib/explain-command.cjs +48 -0
  24. package/pp-core/bin/lib/gate-card.cjs +97 -0
  25. package/pp-core/bin/lib/hook-messages.cjs +132 -0
  26. package/pp-core/bin/lib/init-contract.cjs +116 -76
  27. package/pp-core/bin/lib/init-interactive.cjs +110 -49
  28. package/pp-core/bin/lib/init.cjs +44 -3
  29. package/pp-core/bin/lib/io.cjs +33 -0
  30. package/pp-core/bin/lib/message-labels.cjs +75 -0
  31. package/pp-core/bin/lib/messages/core.cjs +20 -0
  32. package/pp-core/bin/lib/messages/discovery.cjs +267 -0
  33. package/pp-core/bin/lib/messages/gate.cjs +257 -0
  34. package/pp-core/bin/lib/messages/hook.cjs +114 -0
  35. package/pp-core/bin/lib/messages/init.cjs +513 -0
  36. package/pp-core/bin/lib/messages/progress.cjs +110 -0
  37. package/pp-core/bin/lib/messages.cjs +208 -0
  38. package/pp-core/bin/lib/vault-command-router.cjs +16 -3
  39. package/pp-core/bin/lib/violation-render.cjs +78 -0
  40. package/pp-core/bin/pp-tools.cjs +68 -23
  41. package/pp-core/bin/shared/config-schema.manifest.json +1 -0
  42. package/pp-core/references/audience-moments.md +119 -0
  43. package/pp-core/references/audience.md +114 -0
  44. package/pp-core/references/discovery-charter.md +2 -2
  45. package/pp-core/templates/project-charter.md +3 -4
  46. package/pp-core/workflows/add-phase.md +2 -0
  47. package/pp-core/workflows/add-tests.md +2 -0
  48. package/pp-core/workflows/add-todo.md +2 -0
  49. package/pp-core/workflows/ai-integration-phase.md +2 -0
  50. package/pp-core/workflows/audit-milestone.md +2 -0
  51. package/pp-core/workflows/autonomous.md +2 -0
  52. package/pp-core/workflows/check-todos.md +2 -0
  53. package/pp-core/workflows/code-review-fix.md +2 -0
  54. package/pp-core/workflows/code-review.md +2 -0
  55. package/pp-core/workflows/complete-milestone.md +10 -1
  56. package/pp-core/workflows/discovery-sprint/charter.md +3 -2
  57. package/pp-core/workflows/discovery-sprint/prefill.md +3 -0
  58. package/pp-core/workflows/discovery-sprint/prototype.md +14 -0
  59. package/pp-core/workflows/discovery-sprint/sessions/01-kick-off.md +4 -1
  60. package/pp-core/workflows/discovery-sprint/sessions/13-wrap-up.md +1 -1
  61. package/pp-core/workflows/discovery-sprint/vault-context.md +15 -0
  62. package/pp-core/workflows/discovery-sprint.md +14 -0
  63. package/pp-core/workflows/discovery-to-build.md +8 -2
  64. package/pp-core/workflows/discuss-phase-assumptions.md +2 -0
  65. package/pp-core/workflows/discuss-phase.md +1 -1
  66. package/pp-core/workflows/edit-phase.md +2 -0
  67. package/pp-core/workflows/eval-review.md +2 -0
  68. package/pp-core/workflows/execute-phase/steps/check-query-gate.md +25 -1
  69. package/pp-core/workflows/execute-phase/steps/vault-fetch.md +1 -0
  70. package/pp-core/workflows/execute-phase/steps/vault-load-gate.md +2 -0
  71. package/pp-core/workflows/execute-phase.md +1 -1
  72. package/pp-core/workflows/execute-plan.md +2 -0
  73. package/pp-core/workflows/extract-learnings.md +2 -0
  74. package/pp-core/workflows/help/modes/business.md +56 -0
  75. package/pp-core/workflows/help.md +12 -2
  76. package/pp-core/workflows/import.md +10 -1
  77. package/pp-core/workflows/ingest-docs.md +2 -0
  78. package/pp-core/workflows/init.md +17 -1
  79. package/pp-core/workflows/insert-phase.md +2 -0
  80. package/pp-core/workflows/list-workspaces.md +2 -0
  81. package/pp-core/workflows/manager.md +2 -0
  82. package/pp-core/workflows/map-codebase.md +2 -0
  83. package/pp-core/workflows/milestone-summary.md +2 -0
  84. package/pp-core/workflows/new-milestone.md +10 -1
  85. package/pp-core/workflows/new-project.md +12 -2
  86. package/pp-core/workflows/new-workspace.md +2 -0
  87. package/pp-core/workflows/next.md +8 -0
  88. package/pp-core/workflows/plan-milestone-gaps.md +10 -1
  89. package/pp-core/workflows/plan-phase.md +2 -1
  90. package/pp-core/workflows/plan-review-convergence.md +1 -1
  91. package/pp-core/workflows/progress.md +10 -0
  92. package/pp-core/workflows/quick.md +11 -1
  93. package/pp-core/workflows/remove-phase.md +2 -0
  94. package/pp-core/workflows/remove-workspace.md +2 -0
  95. package/pp-core/workflows/resume-project.md +25 -0
  96. package/pp-core/workflows/review.md +2 -0
  97. package/pp-core/workflows/scan.md +2 -0
  98. package/pp-core/workflows/secure-phase.md +2 -0
  99. package/pp-core/workflows/settings.md +41 -0
  100. package/pp-core/workflows/ship.md +2 -0
  101. package/pp-core/workflows/spec-phase.md +1 -1
  102. package/pp-core/workflows/ui-phase.md +2 -0
  103. package/pp-core/workflows/ui-review.md +2 -0
  104. package/pp-core/workflows/ultraplan-phase.md +2 -0
  105. package/pp-core/workflows/validate-phase.md +2 -0
  106. package/pp-core/workflows/vault-review.md +2 -0
  107. package/pp-core/workflows/verify-phase.md +2 -0
  108. package/pp-core/workflows/verify-work.md +2 -0
  109. package/scripts/affected-tests-lib.cjs +26 -12
  110. package/scripts/ci-release-only.cjs +158 -0
  111. package/scripts/live-runs/driver.cjs +326 -0
  112. package/scripts/live-runs/run.cjs +906 -0
  113. package/scripts/live-runs/scenarios.cjs +326 -0
  114. package/scripts/live-runs/scratch-project.cjs +132 -0
  115. package/scripts/live-runs/scrub.cjs +291 -0
  116. package/scripts/live-runs/turn-guard.cjs +107 -0
  117. package/scripts/live-runs/walk-setup.cjs +198 -0
  118. package/scripts/run-tests.cjs +151 -135
  119. package/scripts/test-env.cjs +64 -0
@@ -8,6 +8,13 @@ Read all files referenced by the invoking prompt's execution_context before star
8
8
 
9
9
  <process>
10
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
+ pp_run query audience fields
14
+ ```
15
+
16
+ **Audience, before any question:** run the block above. If it prints `pp-tools.cjs not found`, show that error and stop. If it exits non-zero for any other reason, tell the person the audience setting could not be read (show the error) and continue; if it prints JSON, apply the **Audience** line below to it now.
17
+
11
18
  ## 1. Load Audit Results
12
19
 
13
20
  ```bash
@@ -65,7 +72,6 @@ Gap: Flow "View dashboard" broken at data fetch
65
72
 
66
73
  Find highest existing phase:
67
74
  ```bash
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
75
  [ -z "${ZSH_VERSION:-}" ] || emulate -R sh
70
76
  # Get sorted phase list, extract last one
71
77
  HIGHEST=$(pp_run query phases.list --pick directories[-1])
@@ -145,12 +151,15 @@ grep -c "Pending" .planning/REQUIREMENTS.md
145
151
  For each new phase (N, N+1, …), resolve the directory name via `init.phase-op` so the `project_code` prefix is honoured:
146
152
 
147
153
  ```bash
154
+ command -v pp_run >/dev/null 2>&1 || { echo "ERROR: pp_run is not on PATH; prepend the resolver line from the top block to this block and run it again" >&2; exit 1; }
148
155
  INIT=$(pp_run query init.phase-op "{NN}")
149
156
  if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
150
157
  expected_phase_dir=$(echo "$INIT" | node -e "process.stdout.write(JSON.parse(require('fs').readFileSync('/dev/stdin','utf8')).expected_phase_dir)")
151
158
  mkdir -p "${expected_phase_dir}"
152
159
  ```
153
160
 
161
+ **Audience:** if `audience` is set, Read and follow `{audience_ref}`. Reply in any `response_language` and pass it to subagents; code and paths stay as is.
162
+
154
163
  Repeat for each gap-closure phase number. This produces `{CODE}-{NN}-{slug}/` when `project_code` is set in `.planning/config.json`, and `{NN}-{slug}/` otherwise — consistent with all other phase-creation paths.
155
164
 
156
165
  ## 9. Commit Roadmap and Requirements Update
@@ -88,7 +88,7 @@ When `CONTEXT_WINDOW >= 500000`, the planner prompt includes the 3 most recent p
88
88
 
89
89
  Parse JSON for: `researcher_model`, `planner_model`, `checker_model`, `research_enabled`, `plan_checker_enabled`, `nyquist_validation_enabled`, `commit_docs`, `text_mode`, `phase_found`, `phase_dir`, `phase_number`, `phase_name`, `phase_slug`, `padded_phase`, `has_research`, `has_context`, `has_reviews`, `has_plans`, `plan_count`, `phase_status` (#3569), `planning_exists`, `roadmap_exists`, `phase_req_ids`, `response_language`, `granularity`.
90
90
 
91
- **If `response_language` is set:** Include `response_language: {value}` in all spawned subagent prompts so any user-facing output stays in the configured language.
91
+ **Audience:** if `audience` is set, Read and follow `{audience_ref}`. Reply in any `response_language` and pass it to subagents; code and paths stay as is.
92
92
 
93
93
  **File paths (for <files_to_read> blocks):** `state_path`, `roadmap_path`, `requirements_path`, `context_path`, `research_path`, `verification_path`, `uat_path`, `reviews_path`. These are null if files don't exist.
94
94
 
@@ -1404,6 +1404,7 @@ if [ "$GATE_CFG" != "false" ]; then
1404
1404
  # no trackable decisions). Verify-phase counterpart deliberately omits this exit-1 — that
1405
1405
  # gate is non-blocking by design (review finding F15).
1406
1406
  echo "$GATE_RESULT" | jq -e '(.passed // .data.passed) == true' >/dev/null || {
1407
+ pp_run gate-card decision-coverage --state blocked --details 2>/dev/null || true
1407
1408
  echo "$GATE_RESULT" | jq -r '(.message // .data.message // "Decision coverage gate failed.")'
1408
1409
  exit 1
1409
1410
  }
@@ -73,7 +73,7 @@ if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
73
73
 
74
74
  Parse JSON for: `phase_dir`, `phase_number`, `padded_phase`, `phase_name`, `has_plans`, `plan_count`, `commit_docs`, `text_mode`, `response_language`.
75
75
 
76
- **If `response_language` is set:** All user-facing output should be in `{response_language}`.
76
+ **Audience:** if `audience` is set, Read and follow `{audience_ref}`. Reply in any `response_language` and pass it to subagents; code and paths stay as is.
77
77
 
78
78
  Set `TEXT_MODE=true` if `--text` is present in $ARGUMENTS OR `text_mode` from init JSON is `true`. When `TEXT_MODE` is active, replace every `AskUserQuestion` call with a plain-text numbered list and ask the user to type their choice number.
79
79
 
@@ -21,8 +21,18 @@ INIT=$(pp_run query init.progress)
21
21
  if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
22
22
  ```
23
23
 
24
+ **Audience:** if `audience` is set, Read and follow `{audience_ref}`. Reply in any `response_language` and pass it to subagents; code and paths stay as is.
25
+
24
26
  Extract from init JSON: `project_exists`, `roadmap_exists`, `state_exists`, `phases`, `current_phase`, `next_phase`, `milestone_version`, `completed_count`, `phase_count`, `paused_at`, `state_path`, `roadmap_path`, `project_path`, `config_path`.
25
27
 
28
+ **Discovery Sprint only:** if `project_exists` and `roadmap_exists` are both false, run:
29
+
30
+ ```bash
31
+ pp_run query discovery.status --raw
32
+ ```
33
+
34
+ If it prints JSON with `"discovery_only": true`, run `pp_run query discovery.status` and show its output exactly as printed, with nothing before or after it. Then stop: skip the rest of this workflow (the report, the routing and the Route 0 scan) whatever the arguments; `--forensic` adds nothing here. If it prints `"discovery_only": false`, or the command fails, continue below exactly as before.
35
+
26
36
  ```bash
27
37
  DISCUSS_MODE=$(pp_run query config-get workflow.discuss_mode --raw 2>/dev/null || echo "discuss")
28
38
  ```
@@ -31,6 +31,14 @@ Valid pp-core subagent types (use exact names — do not fall back to 'general-p
31
31
  </available_agent_types>
32
32
 
33
33
  <process>
34
+
35
+ ```bash
36
+ _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
37
+ pp_run query audience fields
38
+ ```
39
+
40
+ **Audience, before any question:** run the block above. If it prints `pp-tools.cjs not found`, show that error and stop. If it exits non-zero for any other reason, tell the person the audience setting could not be read (show the error) and continue; if it prints JSON, apply the **Audience** line below to it now.
41
+
34
42
  **Step 1: Parse arguments and get task description**
35
43
 
36
44
  Parse `$ARGUMENTS` for:
@@ -130,7 +138,7 @@ If `$VALIDATE_MODE` only:
130
138
  **Step 2: Initialize**
131
139
 
132
140
  ```bash
133
- _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
141
+ command -v pp_run >/dev/null 2>&1 || { echo "ERROR: pp_run is not on PATH; prepend the resolver line from the top block to this block and run it again" >&2; exit 1; }
134
142
  INIT=$(pp_run query init.quick "$DESCRIPTION")
135
143
  if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
136
144
  AGENT_SKILLS_PLANNER=$(pp_run query agent-skills pp-planner)
@@ -139,6 +147,8 @@ AGENT_SKILLS_CHECKER=$(pp_run query agent-skills pp-plan-checker)
139
147
  AGENT_SKILLS_VERIFIER=$(pp_run query agent-skills pp-verifier)
140
148
  ```
141
149
 
150
+ **Audience:** if `audience` is set, Read and follow `{audience_ref}`. Reply in any `response_language` and pass it to subagents; code and paths stay as is.
151
+
142
152
  Parse JSON for: `planner_model`, `executor_model`, `checker_model`, `verifier_model`, `commit_docs`, `branch_name`, `quick_id`, `slug`, `date`, `timestamp`, `quick_dir`, `task_dir`, `roadmap_exists`, `planning_exists`.
143
153
 
144
154
  ```bash
@@ -34,6 +34,8 @@ INIT=$(pp_run query init.phase-op "${target}")
34
34
  if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
35
35
  ```
36
36
 
37
+ **Audience:** if `audience` is set, Read and follow `{audience_ref}`. Reply in any `response_language` and pass it to subagents; code and paths stay as is.
38
+
37
39
  Extract: `phase_found`, `phase_dir`, `phase_number`, `commit_docs`, `roadmap_exists`.
38
40
 
39
41
  Also read STATE.md and ROADMAP.md content for parsing current position.
@@ -18,6 +18,8 @@ INIT=$(pp_run query init.remove-workspace "$WORKSPACE_NAME")
18
18
  if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
19
19
  ```
20
20
 
21
+ **Audience:** if `audience` is set, Read and follow `{audience_ref}`. Reply in any `response_language` and pass it to subagents; code and paths stay as is.
22
+
21
23
  Parse JSON for: `workspace_name`, `workspace_path`, `has_manifest`, `strategy`, `repos`, `repo_count`, `dirty_repos`, `has_dirty_repos`.
22
24
 
23
25
  **If no workspace name provided:**
@@ -29,8 +29,33 @@ INIT=$(pp_run query init.resume)
29
29
  if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
30
30
  ```
31
31
 
32
+ **Audience:** if `audience` is set, Read and follow `{audience_ref}`. Reply in any `response_language` and pass it to subagents; code and paths stay as is.
33
+
32
34
  Parse JSON for: `state_exists`, `roadmap_exists`, `project_exists`, `planning_exists`, `has_interrupted_agent`, `interrupted_agent_id`, `commit_docs`.
33
35
 
36
+ **Discovery Sprint only (check this first):** if `project_exists` and `roadmap_exists` are both false, whatever `state_exists` says, run:
37
+
38
+ ```bash
39
+ pp_run query discovery.status --raw
40
+ ```
41
+
42
+ If it prints JSON with `"discovery_only": true`:
43
+
44
+ 1. Run `pp_run query discovery.status` and show its output exactly as printed; it already says where the sprint stopped.
45
+ 2. Run the block below (check_incomplete_work's scan); if it lists `HANDOFF.json` or a `.continue-here` file, add one line naming it as a note left by an earlier pause. The next step stays the sprint's. Leave the files in place: `HANDOFF.json` is not deleted here, it is left for a later build-mode resume.
46
+
47
+ ```bash
48
+ find .planning -maxdepth 1 -name HANDOFF.json -print 2>/dev/null || true
49
+ find .planning -maxdepth 3 -name '.continue-here*.md' -print 2>/dev/null || true
50
+ find . -maxdepth 1 -name '.continue-here*.md' -print 2>/dev/null || true
51
+ ```
52
+
53
+ 3. Offer three numbered choices in the person's wording: the next step (the JSON's `next.command`, with the reason the view gives); the whole sprint (`/pp-discovery`); Something else. When `next.kind` is `open_sprint`, offer only the last two. Wait for the person's choice.
54
+ 4. For the first two, show the chosen command directly, without `/clear` (route_to_workflow's rule). For Something else, ask what they need.
55
+ 5. End the workflow there: skip load_state, check_incomplete_work, present_status, determine_next_action, offer_options, route_to_workflow and update_session. On a discovery-only project never rebuild STATE.md and never run `state.record-session`.
56
+
57
+ If it prints `"discovery_only": false`, or the command fails, continue below exactly as before.
58
+
34
59
  **If `state_exists` is true:** Proceed to load_state
35
60
  **If `state_exists` is false but `roadmap_exists` or `project_exists` is true:** Offer to reconstruct STATE.md
36
61
  **If `planning_exists` is false:** This is a new project - route to /pp:init
@@ -116,6 +116,8 @@ INIT=$(pp_run query init.phase-op "${PHASE_ARG}")
116
116
  if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
117
117
  ```
118
118
 
119
+ **Audience:** if `audience` is set, Read and follow `{audience_ref}`. Reply in any `response_language` and pass it to subagents; code and paths stay as is.
120
+
119
121
  Read from init: `phase_dir`, `phase_number`, `padded_phase`.
120
122
 
121
123
  Then read:
@@ -44,6 +44,8 @@ INIT=$(pp_run query init.map-codebase 2>/dev/null || echo "{}")
44
44
  if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
45
45
  ```
46
46
 
47
+ **Audience:** if `audience` is set, Read and follow `{audience_ref}`. Reply in any `response_language` and pass it to subagents; code and paths stay as is.
48
+
47
49
  Look up which documents would be produced for the selected focus (from the mapping table above).
48
50
 
49
51
  For each target document, check if it already exists in `.planning/codebase/`:
@@ -22,6 +22,8 @@ if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
22
22
  AGENT_SKILLS_AUDITOR=$(pp_run query agent-skills pp-security-auditor)
23
23
  ```
24
24
 
25
+ **Audience:** if `audience` is set, Read and follow `{audience_ref}`. Reply in any `response_language` and pass it to subagents; code and paths stay as is.
26
+
25
27
  Parse: `phase_dir`, `phase_number`, `phase_name`, `phase_slug`, `padded_phase`.
26
28
 
27
29
  ```bash
@@ -535,6 +535,45 @@ Write `~/.gsd/defaults.json` with:
535
535
  ```
536
536
  </step>
537
537
 
538
+ <step name="audience">
539
+ This question is about **you**, not this project: it decides how pp-core talks to you in every project. It is saved in your own per-person file `~/.gsd/person.json` — never in this project's config file and never in `~/.gsd/defaults.json`.
540
+
541
+ Run `pp_run query audience status --raw` and read `audience`, `audience_source`, `language_source` and `language_name`.
542
+
543
+ If `language_source` is `person_file`, the person chose a language explicitly and every pick below keeps it. Show it before the question, so it is never silent:
544
+
545
+ ```
546
+ Language: {language_name} — your own choice, kept.
547
+ Clear: pp-tools audience set --clear-language
548
+ ```
549
+
550
+ The clear command names no audience on purpose: it removes only the language and never saves `PP_AUDIENCE` or a default as the person's audience (D-12).
551
+
552
+ ```
553
+ AskUserQuestion([
554
+ {
555
+ question: "Who are you? This is about you, not this project.",
556
+ header: "Audience",
557
+ multiSelect: false,
558
+ options: [
559
+ { label: "Business user", description: "Plain words, no technical terms." },
560
+ { label: "Business engineer", description: "Plain English; you know the technology." },
561
+ { label: "Developer", description: "As today." },
562
+ { label: "Keep current", description: "{audience}" }
563
+ ]
564
+ }
565
+ ])
566
+ ```
567
+
568
+ The labels are the first-run prompt's own (D-09; a test pins them). Mark the option matching the current value with `(current)`.
569
+
570
+ - `Keep current`: change nothing.
571
+ - Any other answer: run `pp_run query audience set business`, `pp_run query audience set engineer` or `pp_run query audience set developer` (the matching value) and show its output. An explicit pick is saved even when it equals the default, so the first-run question is never asked again.
572
+ - If `audience_source` is `environment`, tell the user that `PP_AUDIENCE` overrides the saved value in this terminal.
573
+ - A change affects only what pp-core shows from now on; files already written (discovery sessions, registers) are never rewritten.
574
+ - Never add `audience` or `language` to the project config write or to the defaults file.
575
+ </step>
576
+
538
577
  <step name="confirm">
539
578
  Display:
540
579
 
@@ -568,6 +607,7 @@ Display:
568
607
  | Skip Discuss | {On/Off} |
569
608
  | Context Warnings | {On/Off} |
570
609
  | Saved as Defaults | {Yes/No} |
610
+ | Audience (you, not this project) | {business/engineer/developer} |
571
611
 
572
612
  These settings apply to future /pp:plan-phase and /pp:execute-phase runs.
573
613
 
@@ -588,5 +628,6 @@ Quick commands:
588
628
  - [ ] User presented with 24 settings (profile + workflow toggles + features + git branching + git tagging + ctx warnings), grouped into six sections: Planning, Execution, Docs & Output, Features, Model & Pipeline, Misc. `code_review_depth` is conditional on `code_review=on`. Model profile uses a two-question split (Q1: Adaptive / Standard tier / Inherit; Q2: Quality / Balanced / Budget — only when Standard tier chosen) to stay within the 4-option AskUserQuestion cap while exposing all 5 valid profiles (#3784). Drift Guard (`plan_review.source_grounding`) is in the Planning section.
589
629
  - [ ] Config updated with model_profile, workflow, and git sections
590
630
  - [ ] User offered to save as global defaults (~/.gsd/defaults.json)
631
+ - [ ] Audience asked once and saved with pp_run query audience set — never into the project config or the defaults file
591
632
  - [ ] Changes confirmed to user
592
633
  </success_criteria>
@@ -33,6 +33,8 @@ INIT=$(pp_run query init.phase-op "${PHASE_ARG}")
33
33
  if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
34
34
  ```
35
35
 
36
+ **Audience:** if `audience` is set, Read and follow `{audience_ref}`. Reply in any `response_language` and pass it to subagents; code and paths stay as is.
37
+
36
38
  Parse from init JSON: `phase_found`, `phase_dir`, `phase_number`, `phase_name`, `padded_phase`, `commit_docs`.
37
39
 
38
40
  Also load config for branching strategy:
@@ -63,7 +63,7 @@ if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
63
63
 
64
64
  Parse JSON for: `phase_found`, `phase_dir`, `phase_number`, `phase_name`, `phase_slug`, `padded_phase`, `state_path`, `requirements_path`, `roadmap_path`, `planning_path`, `response_language`, `commit_docs`.
65
65
 
66
- **If `response_language` is set:** All user-facing text in this workflow MUST be in `{response_language}`. Technical terms, code, and file paths stay in English.
66
+ **Audience:** if `audience` is set, Read and follow `{audience_ref}`. Reply in any `response_language` and pass it to subagents; code and paths stay as is.
67
67
 
68
68
  **If `phase_found` is false:**
69
69
  ```
@@ -26,6 +26,8 @@ AGENT_SKILLS_UI=$(pp_run query agent-skills pp-ui-researcher)
26
26
  AGENT_SKILLS_UI_CHECKER=$(pp_run query agent-skills pp-ui-checker)
27
27
  ```
28
28
 
29
+ **Audience:** if `audience` is set, Read and follow `{audience_ref}`. Reply in any `response_language` and pass it to subagents; code and paths stay as is.
30
+
29
31
  Parse JSON for: `phase_dir`, `phase_number`, `phase_name`, `phase_slug`, `padded_phase`, `has_context`, `has_research`, `commit_docs`.
30
32
 
31
33
  **File paths:** `state_path`, `roadmap_path`, `requirements_path`, `context_path`, `research_path`.
@@ -22,6 +22,8 @@ if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
22
22
  AGENT_SKILLS_UI_REVIEWER=$(pp_run query agent-skills pp-ui-auditor)
23
23
  ```
24
24
 
25
+ **Audience:** if `audience` is set, Read and follow `{audience_ref}`. Reply in any `response_language` and pass it to subagents; code and paths stay as is.
26
+
25
27
  Parse: `phase_dir`, `phase_number`, `phase_name`, `phase_slug`, `padded_phase`, `commit_docs`.
26
28
 
27
29
  ```bash
@@ -72,6 +72,8 @@ INIT=$(pp_run query init.plan-phase "$PHASE")
72
72
  if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
73
73
  ```
74
74
 
75
+ **Audience:** if `audience` is set, Read and follow `{audience_ref}`. Reply in any `response_language` and pass it to subagents; code and paths stay as is.
76
+
75
77
  Parse JSON for: `phase_found`, `phase_number`, `phase_name`, `phase_slug`, `padded_phase`,
76
78
  `phase_dir`, `roadmap_path`, `requirements_path`, `research_path`, `planning_exists`.
77
79
 
@@ -26,6 +26,8 @@ if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
26
26
  AGENT_SKILLS_AUDITOR=$(pp_run query agent-skills pp-nyquist-auditor)
27
27
  ```
28
28
 
29
+ **Audience:** if `audience` is set, Read and follow `{audience_ref}`. Reply in any `response_language` and pass it to subagents; code and paths stay as is.
30
+
29
31
  Parse: `phase_dir`, `phase_number`, `phase_name`, `phase_slug`, `padded_phase`.
30
32
 
31
33
  ```bash
@@ -21,6 +21,8 @@ if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
21
21
  AGENT_SKILLS_VAULT_REVIEWER=$(pp_run query agent-skills pp-vault-reviewer)
22
22
  ```
23
23
 
24
+ **Audience:** if `audience` is set, Read and follow `{audience_ref}`. Reply in any `response_language` and pass it to subagents; code and paths stay as is.
25
+
24
26
  Parse: `phase_dir`, `phase_number`, `phase_name`, `padded_phase`, `commit_docs`.
25
27
 
26
28
  ### 0a. Derive first, and stop when there is nothing to review
@@ -34,6 +34,8 @@ INIT=$(pp_run query init.phase-op "${PHASE_ARG}")
34
34
  if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
35
35
  ```
36
36
 
37
+ **Audience:** if `audience` is set, Read and follow `{audience_ref}`. Reply in any `response_language` and pass it to subagents; code and paths stay as is.
38
+
37
39
  Extract from init JSON: `phase_dir`, `phase_number`, `phase_name`, `has_plans`, `plan_count`.
38
40
 
39
41
  Then load phase details and list plans/summaries:
@@ -49,6 +49,8 @@ AGENT_SKILLS_PLANNER=$(pp_run query agent-skills pp-planner)
49
49
  AGENT_SKILLS_CHECKER=$(pp_run query agent-skills pp-plan-checker)
50
50
  ```
51
51
 
52
+ **Audience:** if `audience` is set, Read and follow `{audience_ref}`. Reply in any `response_language` and pass it to subagents; code and paths stay as is.
53
+
52
54
  Parse JSON for: `planner_model`, `checker_model`, `commit_docs`, `phase_found`, `phase_dir`, `phase_number`, `phase_name`, `has_verification`, `uat_path`.
53
55
 
54
56
  ```bash
@@ -5,6 +5,7 @@ const { readdirSync, readFileSync, existsSync } = require('node:fs');
5
5
  const path = require('node:path');
6
6
 
7
7
  const { ExitError } = require('./lib/cli-exit.cjs');
8
+ const { prepareSuiteEnv } = require('./test-env.cjs');
8
9
  const { suiteOf } = require('./run-tests.cjs');
9
10
 
10
11
  const CRITICAL_PATHS = [
@@ -405,21 +406,33 @@ function runNodeTestFiles(repoRoot, files) {
405
406
  }
406
407
  if (current.length > 0) chunks.push(current);
407
408
 
409
+ // D-16 / WR-04: the same isolation `npm test` gets — session variables and
410
+ // PP_AUDIENCE stripped, GSD_HOME on a throwaway suite home, leak tripwire.
411
+ const suiteEnv = prepareSuiteEnv({ ...process.env });
408
412
  let firstFailure = 0;
409
- for (let i = 0; i < chunks.length; i++) {
410
- if (chunks.length > 1) {
411
- console.error(`affected-tests: chunk ${i + 1}/${chunks.length} (${chunks[i].length} files)`);
413
+ try {
414
+ for (let i = 0; i < chunks.length; i++) {
415
+ if (chunks.length > 1) {
416
+ console.error(`affected-tests: chunk ${i + 1}/${chunks.length} (${chunks[i].length} files)`);
417
+ }
418
+ try {
419
+ execFileSync(process.execPath, ['--test', concurrency, ...chunks[i]], {
420
+ cwd: repoRoot,
421
+ stdio: 'inherit',
422
+ env: suiteEnv.env,
423
+ });
424
+ } catch (error) {
425
+ const code = error.status || 1;
426
+ if (firstFailure === 0) firstFailure = code;
427
+ }
412
428
  }
413
- try {
414
- execFileSync(process.execPath, ['--test', concurrency, ...chunks[i]], {
415
- cwd: repoRoot,
416
- stdio: 'inherit',
417
- env: { ...process.env },
418
- });
419
- } catch (error) {
420
- const code = error.status || 1;
421
- if (firstFailure === 0) firstFailure = code;
429
+ const leak = suiteEnv.leakMessage();
430
+ if (leak) {
431
+ console.error(`affected-tests: ${leak}`);
432
+ if (firstFailure === 0) firstFailure = 1;
422
433
  }
434
+ } finally {
435
+ suiteEnv.cleanup();
423
436
  }
424
437
  if (firstFailure !== 0) throw new ExitError(firstFailure);
425
438
  }
@@ -548,6 +561,7 @@ module.exports = {
548
561
  resolveBaseRef,
549
562
  resolveRelativeDependency,
550
563
  resolveRunPlan,
564
+ runNodeTestFiles,
551
565
  shouldRunFullSuite,
552
566
  toPosixPath,
553
567
  runAffectedTests,
@@ -0,0 +1,158 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * ci-release-only.cjs — is this PR diff only a release (version bump + CHANGELOG
6
+ * render)? If so the CI `test` and `smoke` jobs run the release checks instead
7
+ * of the full suite: the code in a release PR is the code `main` already holds,
8
+ * and the v* tag run executes the full suite again before `pack` and `publish`,
9
+ * so a second full run on the release PR proves nothing new (2026-11-10: ~20
10
+ * billed minutes per release).
11
+ *
12
+ * Release-only means EVERY changed path is one of:
13
+ * - VERSION or CHANGELOG.md (modified);
14
+ * - a `.changeset/*.md` fragment that was DELETED (consumed by the render);
15
+ * - a versioned JSON manifest whose content is identical once its version
16
+ * field(s) are removed — so a dependency, script or override change in
17
+ * package.json / package-lock.json can never pass as a release.
18
+ * Anything else — a source file, an added or edited fragment, a rename, a new
19
+ * file — makes the diff a normal one and the full suite runs.
20
+ *
21
+ * Usage:
22
+ * node scripts/ci-release-only.cjs --base <rev> --head <rev>
23
+ * prints {"releaseOnly":bool,"reasons":[...]} and, under GitHub Actions,
24
+ * appends `release_only=true|false` to $GITHUB_OUTPUT. Exit 0 either way.
25
+ * node scripts/ci-release-only.cjs --list-tests
26
+ * prints the release-check test files, one per line.
27
+ */
28
+
29
+ const { execFileSync } = require('child_process');
30
+ const fs = require('fs');
31
+
32
+ const PLAIN_RELEASE_FILES = new Set(['VERSION', 'CHANGELOG.md']);
33
+
34
+ // Each versioned manifest and the key paths of the version fields a release
35
+ // bump rewrites (scripts/sync-manifest-versions.cjs + `npm version`).
36
+ const VERSIONED_JSON = new Map([
37
+ ['package.json', [['version']]],
38
+ ['package-lock.json', [['version'], ['packages', '', 'version']]],
39
+ ['.claude-plugin/plugin.json', [['version']]],
40
+ ['gemini-extension.json', [['version']]],
41
+ ]);
42
+
43
+ // The tests that read what a release PR changes: the fragments, the rendered
44
+ // CHANGELOG and the versioned manifests. tests/ci-release-only.test.cjs fails
45
+ // if one is missing, and if a test file that reads CHANGELOG.md or the
46
+ // .changeset directory is not listed here.
47
+ const RELEASE_CHECK_TESTS = [
48
+ 'tests/changeset-claims.test.cjs',
49
+ 'tests/changeset-cli.test.cjs',
50
+ 'tests/changeset-github-release-notes.test.cjs',
51
+ 'tests/changeset-lint.test.cjs',
52
+ 'tests/changeset-new.test.cjs',
53
+ 'tests/changeset-parse.test.cjs',
54
+ 'tests/changeset-render.test.cjs',
55
+ 'tests/changeset-serialize.test.cjs',
56
+ 'tests/docs-parity-live-registry.test.cjs',
57
+ 'tests/feat-71924-shipped-claims.test.cjs',
58
+ 'tests/feat-71924-tag-gate-steps.test.cjs',
59
+ 'tests/feat-71924-vault-required-gate.test.cjs',
60
+ 'tests/issue-766-plugin-manifest.test.cjs',
61
+ 'tests/issue-775-gemini-extension.test.cjs',
62
+ 'tests/issue-844-manifest-version-sync.test.cjs',
63
+ 'tests/package-manifest.test.cjs',
64
+ 'tests/product-name-purity.test.cjs',
65
+ ];
66
+
67
+ // Test files that mention CHANGELOG.md or .changeset without reading the live
68
+ // release content a release PR rewrites — each with the reason it is safe to
69
+ // leave out of the release checks. The ratchet in tests/ci-release-only.test.cjs
70
+ // makes every other mention a listed release check.
71
+ const RELEASE_CHECK_EXEMPT = new Map([
72
+ ['tests/bug-3258-no-stale-gsd-intel-references.test.cjs', 'excludes CHANGELOG.md from its scan'], // gsd-allow-legacy-name: names an existing test file
73
+ ['tests/bug-3810-no-gsd-sdk-runtime-refs.test.cjs', 'excludes .changeset/ from its scan'], // gsd-allow-legacy-name: names an existing test file
74
+ ['tests/cline-install.test.cjs', 'copies CHANGELOG.md as an install artifact, content unread'],
75
+ ['tests/copilot-install.test.cjs', 'checks CHANGELOG.md is installed, content unread'],
76
+ ['tests/install.test.cjs', 'excludes CHANGELOG.md from its tree scan'],
77
+ ['tests/issue-498-package-identity.test.cjs', 'a CHANGELOG.md URL in a fixture string'],
78
+ ['tests/issue-607-legacy-cleanup.test.cjs', 'writes its own CHANGELOG.md fixture in a temp dir'],
79
+ ['tests/lint-docs-required.test.cjs', 'synthetic .changeset paths, no repo read'],
80
+ ['tests/vault-seal.integration.test.cjs', 'synthetic .changeset paths in a temp repo'],
81
+ ['tests/vault-seal.test.cjs', 'synthetic CHANGELOG.md / .changeset paths'],
82
+ ['tests/vault-trace.integration.test.cjs', 'sweeps pp-core/commands/agents; deleting fragments cannot add a dispatcher'],
83
+ ]);
84
+
85
+ function stripVersions(text, keyPaths) {
86
+ const doc = JSON.parse(text);
87
+ for (const keyPath of keyPaths) {
88
+ let node = doc;
89
+ for (const key of keyPath.slice(0, -1)) {
90
+ node = node && typeof node === 'object' ? node[key] : undefined;
91
+ }
92
+ if (node && typeof node === 'object') delete node[keyPath[keyPath.length - 1]];
93
+ }
94
+ return JSON.stringify(doc);
95
+ }
96
+
97
+ /**
98
+ * @param {{status: string, path: string}[]} changes `git diff --name-status` rows
99
+ * @param {(rev: 'base'|'head', file: string) => string} read file content at a side
100
+ * @returns {{releaseOnly: boolean, reasons: string[]}}
101
+ */
102
+ function classify(changes, read) {
103
+ const reasons = [];
104
+ if (changes.length === 0) reasons.push('empty diff');
105
+ for (const { status, path: file } of changes) {
106
+ if (PLAIN_RELEASE_FILES.has(file) && status === 'M') continue;
107
+ if (/^\.changeset\/[^/]+\.md$/.test(file) && file !== '.changeset/README.md' && status === 'D') continue;
108
+ if (VERSIONED_JSON.has(file) && status === 'M') {
109
+ let same = false;
110
+ try {
111
+ same = stripVersions(read('base', file), VERSIONED_JSON.get(file)) ===
112
+ stripVersions(read('head', file), VERSIONED_JSON.get(file));
113
+ } catch (err) {
114
+ reasons.push(`${file}: unreadable (${err.message})`);
115
+ continue;
116
+ }
117
+ if (same) continue;
118
+ reasons.push(`${file}: changes beyond the version field`);
119
+ continue;
120
+ }
121
+ reasons.push(`${status} ${file}: not a release file`);
122
+ }
123
+ return { releaseOnly: reasons.length === 0, reasons };
124
+ }
125
+
126
+ function gitDiff(base, head, cwd) {
127
+ const out = execFileSync('git', ['diff', '--name-status', '--no-renames', base, head], { cwd, encoding: 'utf8' });
128
+ return out.split('\n').filter(Boolean).map(line => {
129
+ const [status, file] = line.split('\t');
130
+ return { status: status[0], path: file };
131
+ });
132
+ }
133
+
134
+ function main(argv) {
135
+ if (argv.includes('--list-tests')) {
136
+ process.stdout.write(RELEASE_CHECK_TESTS.join('\n') + '\n');
137
+ return 0;
138
+ }
139
+ const base = argv[argv.indexOf('--base') + 1];
140
+ const head = argv[argv.indexOf('--head') + 1];
141
+ if (!argv.includes('--base') || !argv.includes('--head') || !base || !head) {
142
+ process.stderr.write('usage: ci-release-only.cjs --base <rev> --head <rev> | --list-tests\n');
143
+ return 2;
144
+ }
145
+ const cwd = process.cwd();
146
+ const revs = { base, head };
147
+ const verdict = classify(gitDiff(base, head, cwd), (side, file) =>
148
+ execFileSync('git', ['show', `${revs[side]}:${file}`], { cwd, encoding: 'utf8' }));
149
+ process.stdout.write(JSON.stringify(verdict) + '\n');
150
+ if (process.env.GITHUB_OUTPUT) {
151
+ fs.appendFileSync(process.env.GITHUB_OUTPUT, `release_only=${verdict.releaseOnly}\n`);
152
+ }
153
+ return 0;
154
+ }
155
+
156
+ module.exports = { classify, stripVersions, RELEASE_CHECK_TESTS, RELEASE_CHECK_EXEMPT, VERSIONED_JSON };
157
+
158
+ if (require.main === module) process.exitCode = main(process.argv.slice(2));