@pareto4/pp-core 1.11.0 → 1.12.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 (256) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +31 -0
  3. package/agents/pp-code-fixer.md +1 -1
  4. package/agents/pp-debug-session-manager.md +1 -1
  5. package/agents/pp-debugger.md +1 -1
  6. package/agents/pp-executor.md +20 -10
  7. package/agents/pp-intel-updater.md +1 -1
  8. package/agents/pp-phase-researcher.md +1 -1
  9. package/agents/pp-plan-checker.md +5 -5
  10. package/agents/pp-planner.md +10 -24
  11. package/agents/pp-project-researcher.md +1 -1
  12. package/agents/pp-research-synthesizer.md +1 -1
  13. package/agents/pp-ui-researcher.md +1 -1
  14. package/agents/pp-verifier.md +8 -4
  15. package/bin/install.js +429 -82
  16. package/commands/pp-autonomous.md +8 -6
  17. package/commands/pp-discovery-to-build.md +1 -1
  18. package/commands/pp-discovery.md +3 -2
  19. package/commands/pp-discuss-phase.md +2 -2
  20. package/commands/pp-execute-phase.md +3 -1
  21. package/commands/pp-graphify.md +5 -5
  22. package/commands/pp-import.md +1 -1
  23. package/commands/pp-map-codebase.md +1 -1
  24. package/commands/pp-ns-project.md +2 -3
  25. package/commands/pp-ns-workflow.md +1 -1
  26. package/commands/pp-plan-phase.md +2 -1
  27. package/commands/pp-verify-work.md +2 -1
  28. package/gemini-extension.json +1 -1
  29. package/hooks/dist/pp-cursor-session-start.js +2 -2
  30. package/hooks/dist/pp-session-state.sh +1 -1
  31. package/hooks/dist/pp-statusline.js +21 -2
  32. package/hooks/pp-cursor-session-start.js +2 -2
  33. package/hooks/pp-session-state.sh +1 -1
  34. package/hooks/pp-statusline.js +21 -2
  35. package/package.json +18 -5
  36. package/pareto-skills/pp-project-setup/SKILL.md +5 -63
  37. package/pp-core/bin/detect-build-commands.sh +182 -0
  38. package/pp-core/bin/lib/capability-registry.cjs +65 -7
  39. package/pp-core/bin/lib/check-command-router.cjs +79 -38
  40. package/pp-core/bin/lib/clusters.cjs +2 -3
  41. package/pp-core/bin/lib/command-aliases.cjs +36 -0
  42. package/pp-core/bin/lib/command-arg-projection.cjs +99 -1
  43. package/pp-core/bin/lib/commands.cjs +21 -6
  44. package/pp-core/bin/lib/compose-bindings.cjs +128 -15
  45. package/pp-core/bin/lib/config-schema.cjs +16 -0
  46. package/pp-core/bin/lib/config.cjs +142 -27
  47. package/pp-core/bin/lib/core-utils.cjs +43 -7
  48. package/pp-core/bin/lib/discovery-input-check.cjs +218 -0
  49. package/pp-core/bin/lib/discovery-rebaseline.cjs +1372 -0
  50. package/pp-core/bin/lib/discovery-research-track.cjs +979 -0
  51. package/pp-core/bin/lib/discovery-rollup.cjs +476 -0
  52. package/pp-core/bin/lib/discovery-screens.cjs +1995 -0
  53. package/pp-core/bin/lib/discovery-sweep.cjs +390 -0
  54. package/pp-core/bin/lib/discovery-table.cjs +98 -0
  55. package/pp-core/bin/lib/dispatch-plan.cjs +472 -0
  56. package/pp-core/bin/lib/frontmatter.cjs +17 -2
  57. package/pp-core/bin/lib/init-command-router.cjs +8 -5
  58. package/pp-core/bin/lib/init-contract.cjs +213 -33
  59. package/pp-core/bin/lib/init-interactive.cjs +20 -1
  60. package/pp-core/bin/lib/init.cjs +124 -134
  61. package/pp-core/bin/lib/install-profiles.cjs +5 -0
  62. package/pp-core/bin/lib/install-run.cjs +536 -0
  63. package/pp-core/bin/lib/io.cjs +15 -0
  64. package/pp-core/bin/lib/loop-resolver.cjs +24 -0
  65. package/pp-core/bin/lib/milestone.cjs +345 -48
  66. package/pp-core/bin/lib/phase-command-router.cjs +23 -1
  67. package/pp-core/bin/lib/phase-locator.cjs +15 -6
  68. package/pp-core/bin/lib/phase-scope.cjs +91 -0
  69. package/pp-core/bin/lib/phase-tier.cjs +467 -0
  70. package/pp-core/bin/lib/phase.cjs +247 -21
  71. package/pp-core/bin/lib/phases-command-router.cjs +2 -0
  72. package/pp-core/bin/lib/plan-graph.cjs +368 -0
  73. package/pp-core/bin/lib/planning-workspace.cjs +53 -0
  74. package/pp-core/bin/lib/platform-bindings.cjs +32 -2
  75. package/pp-core/bin/lib/profile-output.cjs +11 -3
  76. package/pp-core/bin/lib/prohibition-enforcement.cjs +112 -0
  77. package/pp-core/bin/lib/roadmap-parser.cjs +366 -99
  78. package/pp-core/bin/lib/roadmap.cjs +141 -39
  79. package/pp-core/bin/lib/runtime-hooks-surface.cjs +1 -1
  80. package/pp-core/bin/lib/shell-command-projection.cjs +24 -0
  81. package/pp-core/bin/lib/state-command-router.cjs +82 -13
  82. package/pp-core/bin/lib/state-document.cjs +124 -7
  83. package/pp-core/bin/lib/state.cjs +784 -257
  84. package/pp-core/bin/lib/template.cjs +22 -6
  85. package/pp-core/bin/lib/vault-command-router.cjs +57 -6
  86. package/pp-core/bin/lib/vault-practices.cjs +6 -36
  87. package/pp-core/bin/lib/vault-write-back.cjs +20 -4
  88. package/pp-core/bin/lib/vault.cjs +555 -23
  89. package/pp-core/bin/lib/verify.cjs +53 -31
  90. package/pp-core/bin/lib/workstream.cjs +1 -1
  91. package/pp-core/bin/lib/worktree-safety.cjs +26 -15
  92. package/pp-core/bin/portable-timeout.sh +89 -0
  93. package/pp-core/bin/pp-tools.cjs +158 -8
  94. package/pp-core/bin/shared/config-defaults.manifest.json +7 -0
  95. package/pp-core/bin/shared/config-schema.manifest.json +3 -0
  96. package/pp-core/references/autonomous-best-effort.md +461 -0
  97. package/pp-core/references/autonomous-lifecycle.md +146 -0
  98. package/pp-core/references/autonomous-smart-discuss.md +16 -12
  99. package/pp-core/references/checkpoints.md +1 -1
  100. package/pp-core/references/discovery-id-conventions.md +202 -0
  101. package/pp-core/references/discovery-rebaseline.md +254 -0
  102. package/pp-core/references/discovery-research-track.md +283 -0
  103. package/pp-core/references/discovery-traceability.md +400 -0
  104. package/pp-core/references/loop-hook-dispatch.md +7 -0
  105. package/pp-core/references/mandatory-initial-read.md +1 -1
  106. package/pp-core/references/planner-gap-closure.md +1 -0
  107. package/pp-core/references/planner-human-verify-mode.md +1 -1
  108. package/pp-core/references/planner-load-graph-context.md +1 -1
  109. package/pp-core/references/planner-plan-graph.md +66 -0
  110. package/pp-core/references/planner-tdd.md +1 -0
  111. package/pp-core/references/planning-config.md +8 -0
  112. package/pp-core/references/sketch-tooling.md +4 -0
  113. package/pp-core/references/tdd.md +1 -0
  114. package/pp-core/templates/claude-md.md +1 -1
  115. package/pp-core/templates/phase-prompt.md +6 -2
  116. package/pp-core/templates/prototype-review/review.js +303 -0
  117. package/pp-core/templates/state.md +6 -0
  118. package/pp-core/workflows/_runtime-launcher.snippet.sh +1 -1
  119. package/pp-core/workflows/add-backlog.md +1 -1
  120. package/pp-core/workflows/add-phase.md +1 -1
  121. package/pp-core/workflows/add-tests.md +1 -1
  122. package/pp-core/workflows/add-todo.md +1 -1
  123. package/pp-core/workflows/ai-integration-phase.md +2 -2
  124. package/pp-core/workflows/audit-fix.md +12 -16
  125. package/pp-core/workflows/audit-milestone.md +1 -1
  126. package/pp-core/workflows/audit-uat.md +1 -1
  127. package/pp-core/workflows/autonomous.md +68 -306
  128. package/pp-core/workflows/check-todos.md +1 -1
  129. package/pp-core/workflows/cleanup.md +1 -1
  130. package/pp-core/workflows/code-review-fix.md +1 -1
  131. package/pp-core/workflows/code-review.md +19 -7
  132. package/pp-core/workflows/complete-milestone.md +41 -60
  133. package/pp-core/workflows/debug.md +1 -1
  134. package/pp-core/workflows/diagnose-issues.md +1 -1
  135. package/pp-core/workflows/discovery-sprint/gap-analysis.md +140 -0
  136. package/pp-core/workflows/discovery-sprint/input-package.md +140 -0
  137. package/pp-core/workflows/discovery-sprint/prefill.md +92 -0
  138. package/pp-core/workflows/discovery-sprint/prototype.md +257 -0
  139. package/pp-core/workflows/discovery-sprint/rebaseline.md +276 -0
  140. package/pp-core/workflows/discovery-sprint/session-13-gates.md +24 -0
  141. package/pp-core/workflows/discovery-sprint/sessions/01-kick-off.md +92 -0
  142. package/pp-core/workflows/discovery-sprint/sessions/02-as-is.md +98 -0
  143. package/pp-core/workflows/discovery-sprint/sessions/03-to-be.md +215 -0
  144. package/pp-core/workflows/discovery-sprint/sessions/04-ux-design.md +146 -0
  145. package/pp-core/workflows/discovery-sprint/sessions/05-architecture.md +98 -0
  146. package/pp-core/workflows/discovery-sprint/sessions/06-security.md +107 -0
  147. package/pp-core/workflows/discovery-sprint/sessions/07-identity.md +98 -0
  148. package/pp-core/workflows/discovery-sprint/sessions/08-integration.md +85 -0
  149. package/pp-core/workflows/discovery-sprint/sessions/09-qa.md +109 -0
  150. package/pp-core/workflows/discovery-sprint/sessions/10-infrastructure.md +119 -0
  151. package/pp-core/workflows/discovery-sprint/sessions/11-roadmap.md +92 -0
  152. package/pp-core/workflows/discovery-sprint/sessions/12-delivery-model.md +98 -0
  153. package/pp-core/workflows/discovery-sprint/sessions/13-wrap-up.md +205 -0
  154. package/pp-core/workflows/discovery-sprint/templates/context-template.md +266 -0
  155. package/pp-core/workflows/discovery-sprint.md +79 -1714
  156. package/pp-core/workflows/discovery-to-build/steps/architecture-deliverables.md +5 -5
  157. package/pp-core/workflows/discovery-to-build/steps/screen-inventory-gate.md +70 -0
  158. package/pp-core/workflows/discovery-to-build/templates/discovery-references.md +54 -0
  159. package/pp-core/workflows/discovery-to-build/templates/pp-handoff.md +184 -0
  160. package/pp-core/workflows/discovery-to-build.md +21 -238
  161. package/pp-core/workflows/discuss-phase/modes/advisor.md +1 -1
  162. package/pp-core/workflows/discuss-phase/modes/auto.md +1 -1
  163. package/pp-core/workflows/discuss-phase/modes/chain.md +1 -1
  164. package/pp-core/workflows/discuss-phase-assumptions.md +1 -1
  165. package/pp-core/workflows/discuss-phase.md +9 -2
  166. package/pp-core/workflows/do.md +4 -4
  167. package/pp-core/workflows/docs-update.md +1 -1
  168. package/pp-core/workflows/edit-phase.md +1 -1
  169. package/pp-core/workflows/eval-review.md +1 -1
  170. package/pp-core/workflows/execute-phase/steps/checkpoint-handling.md +45 -0
  171. package/pp-core/workflows/execute-phase/steps/client-context-gate.md +1 -1
  172. package/pp-core/workflows/execute-phase/steps/codebase-drift-gate.md +1 -1
  173. package/pp-core/workflows/execute-phase/steps/cross-ai-delegation.md +90 -0
  174. package/pp-core/workflows/execute-phase/steps/install-run.md +29 -0
  175. package/pp-core/workflows/execute-phase/steps/phase-close-rebaseline.md +23 -0
  176. package/pp-core/workflows/execute-phase/steps/post-merge-gate.md +76 -77
  177. package/pp-core/workflows/execute-phase/steps/serial-group-dispatch.md +72 -0
  178. package/pp-core/workflows/execute-phase/steps/vault-fetch.md +5 -5
  179. package/pp-core/workflows/execute-phase/steps/vault-load-gate.md +19 -112
  180. package/pp-core/workflows/execute-phase.md +102 -216
  181. package/pp-core/workflows/execute-plan.md +6 -2
  182. package/pp-core/workflows/explore.md +1 -1
  183. package/pp-core/workflows/extract-learnings.md +1 -1
  184. package/pp-core/workflows/forensics.md +4 -4
  185. package/pp-core/workflows/graduation.md +1 -1
  186. package/pp-core/workflows/health.md +1 -1
  187. package/pp-core/workflows/help/modes/brief.md +1 -0
  188. package/pp-core/workflows/help/modes/default.md +2 -1
  189. package/pp-core/workflows/help/modes/full.md +12 -5
  190. package/pp-core/workflows/import.md +1 -1
  191. package/pp-core/workflows/ingest-docs.md +1 -1
  192. package/pp-core/workflows/init.md +18 -2
  193. package/pp-core/workflows/insert-phase.md +1 -1
  194. package/pp-core/workflows/list-workspaces.md +1 -1
  195. package/pp-core/workflows/manager.md +3 -3
  196. package/pp-core/workflows/map-codebase.md +2 -2
  197. package/pp-core/workflows/milestone-summary.md +4 -4
  198. package/pp-core/workflows/mvp-phase.md +1 -1
  199. package/pp-core/workflows/new-milestone.md +7 -8
  200. package/pp-core/workflows/new-project.md +6 -1
  201. package/pp-core/workflows/new-workspace.md +1 -1
  202. package/pp-core/workflows/next.md +2 -2
  203. package/pp-core/workflows/note.md +2 -2
  204. package/pp-core/workflows/pause-work.md +1 -1
  205. package/pp-core/workflows/plan-milestone-gaps.md +1 -1
  206. package/pp-core/workflows/plan-phase/steps/chunked-planning.md +111 -0
  207. package/pp-core/workflows/plan-phase/steps/phase-tier.md +34 -0
  208. package/pp-core/workflows/plan-phase.md +43 -167
  209. package/pp-core/workflows/plan-review-convergence.md +2 -2
  210. package/pp-core/workflows/plant-seed.md +1 -1
  211. package/pp-core/workflows/pr-branch.md +1 -1
  212. package/pp-core/workflows/profile-user.md +1 -1
  213. package/pp-core/workflows/progress.md +3 -3
  214. package/pp-core/workflows/quick.md +3 -1
  215. package/pp-core/workflows/remove-phase.md +1 -1
  216. package/pp-core/workflows/remove-workspace.md +1 -1
  217. package/pp-core/workflows/resume-project.md +6 -12
  218. package/pp-core/workflows/review.md +1 -1
  219. package/pp-core/workflows/scan.md +1 -1
  220. package/pp-core/workflows/secure-phase.md +1 -1
  221. package/pp-core/workflows/settings-advanced.md +1 -1
  222. package/pp-core/workflows/settings-integrations.md +1 -1
  223. package/pp-core/workflows/settings.md +1 -1
  224. package/pp-core/workflows/ship.md +25 -8
  225. package/pp-core/workflows/sketch-wrap-up.md +1 -1
  226. package/pp-core/workflows/sketch.md +2 -1
  227. package/pp-core/workflows/spec-phase.md +1 -1
  228. package/pp-core/workflows/spike-wrap-up.md +1 -1
  229. package/pp-core/workflows/spike.md +1 -1
  230. package/pp-core/workflows/stats.md +2 -2
  231. package/pp-core/workflows/thread.md +1 -1
  232. package/pp-core/workflows/transition.md +5 -9
  233. package/pp-core/workflows/ui-phase.md +23 -2
  234. package/pp-core/workflows/ui-review.md +1 -1
  235. package/pp-core/workflows/ultraplan-phase.md +2 -2
  236. package/pp-core/workflows/validate-phase.md +1 -1
  237. package/pp-core/workflows/vault-write-back.md +1 -1
  238. package/pp-core/workflows/verify-phase.md +29 -26
  239. package/pp-core/workflows/verify-work.md +14 -10
  240. package/scripts/base64-scan.sh +7 -41
  241. package/scripts/changeset/cli.cjs +25 -5
  242. package/scripts/dev-env.cjs +462 -0
  243. package/scripts/lib/allowlist-ratchet.cjs +103 -1
  244. package/scripts/lib/size-budget.cjs +311 -0
  245. package/scripts/lint-neutrality-names.cjs +694 -0
  246. package/scripts/lint-org-coordinates.allowlist.json +35 -0
  247. package/scripts/lint-org-coordinates.cjs +426 -0
  248. package/scripts/lint-skill-deps.cjs +48 -9
  249. package/scripts/lint-test-file-count.cjs +17 -2
  250. package/scripts/prompt-injection-scan.sh +41 -20
  251. package/scripts/sync-manifest-versions.cjs +41 -7
  252. package/scripts/sync-runtime-launcher.cjs +106 -1
  253. package/scripts/update-size-baseline.cjs +274 -30
  254. package/scripts/workflow-size.cjs +61 -9
  255. package/commands/pp-build-handoff.md +0 -46
  256. package/pp-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +0 -94
@@ -0,0 +1,536 @@
1
+ "use strict";
2
+ /**
3
+ * Install run — the single owner of heavy-tier "measure the installed
4
+ * product" verification (pack 9, item 5).
5
+ *
6
+ * For a pp-core package (a package.json whose `bin` has both `pp-core` and
7
+ * `pp-tools`), `phase.install-run <phase>`:
8
+ *
9
+ * 1. builds (`npm run build`, unless `--no-build`) and packs the package
10
+ * (`npm pack`, whose `prepack` compiles the shipped `.cjs`) — or takes a
11
+ * tarball given with `--tarball`;
12
+ * 2. installs the tarball into a temp npm prefix and runs the installer the
13
+ * user runs (`pp-core --claude --local`) in a temp fixture project, under
14
+ * a temp HOME — reusing the release smoke harness
15
+ * (scripts/release-tarball-smoke.cjs: `runSmoke`, `driveInstaller`);
16
+ * 3. runs init non-interactively through the INSTALLED pp-tools
17
+ * (`query init-contract --session-next/--session-answer`, the documented
18
+ * non-interactive form of `pp-tools init`);
19
+ * 4. runs the phase's install-check commands against the INSTALLED
20
+ * `.claude/pp-core/bin/pp-tools.cjs`, never the repository's;
21
+ * 5. writes `<phase_dir>/<NN>-INSTALL-RUN.json`: tarball name and version,
22
+ * every command with its exit code and truncated stdout/stderr, and a
23
+ * verdict (`pass` / `fail` / `not_applicable`).
24
+ *
25
+ * The install-check commands have ONE owner: the phase's ROADMAP entry, as an
26
+ * `**Install check:**` line of backticked `pp-tools …` commands (the same
27
+ * owner as the `**Tier:**` line). Without the line, `DEFAULT_INSTALL_CHECKS`
28
+ * run.
29
+ *
30
+ * npm runs offline first (`npm_config_offline`, from the caller's npm cache);
31
+ * on a cache miss it retries with `prefer-offline`, which may fetch the
32
+ * package's dependencies — the same network the existing install tests use.
33
+ * The registry is whatever the caller's npm configuration says; nothing here
34
+ * names one.
35
+ */
36
+ var __importDefault = (this && this.__importDefault) || function (mod) {
37
+ return (mod && mod.__esModule) ? mod : { "default": mod };
38
+ };
39
+ Object.defineProperty(exports, "__esModule", { value: true });
40
+ exports.OUTPUT_LIMIT = exports.INIT_ANSWERS = exports.DEFAULT_INSTALL_CHECKS = exports.INSTALL_RUN_SUFFIX = exports.INSTALL_RUN_SCHEMA = void 0;
41
+ exports.parseInstallCheckField = parseInstallCheckField;
42
+ exports.splitCommandLine = splitCommandLine;
43
+ exports.parseCheckCommand = parseCheckCommand;
44
+ exports.resolveInstallChecks = resolveInstallChecks;
45
+ exports.truncateOutput = truncateOutput;
46
+ exports.computeInstallVerdict = computeInstallVerdict;
47
+ exports.readTarballPackageJson = readTarballPackageJson;
48
+ exports.isPpCorePackage = isPpCorePackage;
49
+ exports.parseInstallRunArgs = parseInstallRunArgs;
50
+ exports.findSmokeHarness = findSmokeHarness;
51
+ exports.buildRunEnv = buildRunEnv;
52
+ exports.runInstallRun = runInstallRun;
53
+ const node_fs_1 = __importDefault(require("node:fs"));
54
+ const node_os_1 = __importDefault(require("node:os"));
55
+ const node_path_1 = __importDefault(require("node:path"));
56
+ const node_crypto_1 = __importDefault(require("node:crypto"));
57
+ const node_zlib_1 = __importDefault(require("node:zlib"));
58
+ const node_child_process_1 = require("node:child_process");
59
+ // eslint-disable-next-line @typescript-eslint/no-require-imports -- roadmap-parser.cjs is an export= CommonJS module
60
+ const roadmapParser = require("./roadmap-parser.cjs");
61
+ // eslint-disable-next-line @typescript-eslint/no-require-imports -- phase-locator.cjs is an export= CommonJS module
62
+ const phaseLocator = require("./phase-locator.cjs");
63
+ const { getRoadmapPhaseInternal } = roadmapParser;
64
+ const { findPhaseInternal } = phaseLocator;
65
+ exports.INSTALL_RUN_SCHEMA = 'pp-install-run/1';
66
+ exports.INSTALL_RUN_SUFFIX = 'INSTALL-RUN.json';
67
+ /** Run when the phase's ROADMAP entry has no `**Install check:**` line: the smoke job's surface. */
68
+ exports.DEFAULT_INSTALL_CHECKS = Object.freeze([
69
+ 'pp-tools --help',
70
+ 'pp-tools query config-get project.slug',
71
+ 'pp-tools init phase-op 1',
72
+ 'pp-tools progress',
73
+ ]);
74
+ /** Fixed answers for the non-interactive init session in the fixture project. */
75
+ exports.INIT_ANSWERS = Object.freeze({
76
+ project_name: 'Install Run Fixture',
77
+ project_slug: 'install-run-fixture',
78
+ client_name: 'internal',
79
+ client_slug: 'internal',
80
+ description: 'Fixture project for the heavy-tier install run',
81
+ org_profile: 'none',
82
+ provider: 'none',
83
+ visibility: 'private',
84
+ });
85
+ exports.OUTPUT_LIMIT = 2000;
86
+ const INIT_MAX_STEPS = 30;
87
+ const COMMAND_TIMEOUT_MS = 120_000;
88
+ const BUILD_TIMEOUT_MS = 600_000;
89
+ const TOOL_NAMES = new Set(['pp-tools', 'pp_run']);
90
+ // ─── Pure parts ──────────────────────────────────────────────────────────────
91
+ const INSTALL_CHECK_RE = /^\*\*Install check(?::\*\*|\*\*:)[^\S\n]*([^\n]*)$/im;
92
+ /** The `**Install check:**` line of a ROADMAP phase section: its backticked commands, or null when absent. */
93
+ function parseInstallCheckField(section) {
94
+ if (!section)
95
+ return null;
96
+ const m = INSTALL_CHECK_RE.exec(section);
97
+ if (!m)
98
+ return null;
99
+ const commands = [...m[1].matchAll(/`([^`]+)`/g)].map((c) => c[1].trim()).filter(Boolean);
100
+ return { line: m[0].trim(), commands };
101
+ }
102
+ /** Split one command line into argv (single and double quotes, backslash escapes); no shell is involved. */
103
+ function splitCommandLine(text) {
104
+ const argv = [];
105
+ let cur = '';
106
+ let inToken = false;
107
+ let quote = null;
108
+ for (let i = 0; i < text.length; i++) {
109
+ const ch = text[i];
110
+ if (quote) {
111
+ if (ch === quote)
112
+ quote = null;
113
+ else if (ch === '\\' && quote === '"' && i + 1 < text.length)
114
+ cur += text[++i];
115
+ else
116
+ cur += ch;
117
+ }
118
+ else if (ch === '"' || ch === "'") {
119
+ quote = ch;
120
+ inToken = true;
121
+ }
122
+ else if (ch === '\\' && i + 1 < text.length) {
123
+ cur += text[++i];
124
+ inToken = true;
125
+ }
126
+ else if (/\s/.test(ch)) {
127
+ if (inToken)
128
+ argv.push(cur);
129
+ cur = '';
130
+ inToken = false;
131
+ }
132
+ else {
133
+ cur += ch;
134
+ inToken = true;
135
+ }
136
+ }
137
+ if (quote)
138
+ return { error: `unterminated ${quote} quote` };
139
+ if (inToken)
140
+ argv.push(cur);
141
+ return argv;
142
+ }
143
+ /** A listed command must start with `pp-tools` (or `pp_run`); the rest is its argv. */
144
+ function parseCheckCommand(text) {
145
+ const split = splitCommandLine(text);
146
+ if (!Array.isArray(split))
147
+ return { text, error: split.error };
148
+ if (split.length === 0 || !TOOL_NAMES.has(split[0])) {
149
+ return { text, error: 'an install check must be a `pp-tools …` command (run against the installed copy)' };
150
+ }
151
+ return { text, argv: split.slice(1) };
152
+ }
153
+ /** The phase's install checks from its ROADMAP section, or the default smoke set. */
154
+ function resolveInstallChecks(section) {
155
+ const field = parseInstallCheckField(section);
156
+ const texts = field ? field.commands : [...exports.DEFAULT_INSTALL_CHECKS];
157
+ const commands = [];
158
+ const invalid = [];
159
+ for (const text of texts) {
160
+ const parsed = parseCheckCommand(text);
161
+ if ('error' in parsed)
162
+ invalid.push(parsed);
163
+ else
164
+ commands.push(parsed);
165
+ }
166
+ if (field && field.commands.length === 0) {
167
+ invalid.push({ text: field.line, error: 'the **Install check:** line lists no backticked commands' });
168
+ }
169
+ return { source: field ? 'roadmap' : 'default', line: field ? field.line : null, commands, invalid };
170
+ }
171
+ /** Keep the head and tail of long output. */
172
+ function truncateOutput(text, limit = exports.OUTPUT_LIMIT) {
173
+ const s = String(text ?? '');
174
+ if (s.length <= limit)
175
+ return s;
176
+ const half = Math.floor(limit / 2);
177
+ return `${s.slice(0, half)}\n… [${s.length - limit} chars truncated] …\n${s.slice(s.length - half)}`;
178
+ }
179
+ /**
180
+ * The verdict: `not_applicable` when nothing installable was found; `fail`
181
+ * when any recorded command exited non-zero (or did not run), a listed check
182
+ * was invalid, or init did not finish; `pass` otherwise.
183
+ */
184
+ function computeInstallVerdict(input) {
185
+ if (!input.applicable) {
186
+ return { verdict: 'not_applicable', reason: input.notApplicableReason || 'nothing installable', failures: [] };
187
+ }
188
+ const failures = input.commands
189
+ .filter((c) => c.exit_code !== 0)
190
+ .map((c) => ({ stage: c.stage, command: c.command, exit_code: c.exit_code, ...(c.error ? { error: c.error } : {}) }));
191
+ for (const bad of input.invalid) {
192
+ failures.push({ stage: 'check', command: bad.text, exit_code: null, error: bad.error });
193
+ }
194
+ if (!input.initDone && !failures.some((f) => f.stage !== 'check')) {
195
+ failures.push({ stage: 'init', command: 'pp-tools init', exit_code: null, error: 'the init session did not finish' });
196
+ }
197
+ if (!input.checksRun && failures.length === 0) {
198
+ failures.push({ stage: 'check', command: '(install checks)', exit_code: null, error: 'the install checks did not run' });
199
+ }
200
+ if (failures.length === 0)
201
+ return { verdict: 'pass', reason: 'every command exited 0 against the installed copy', failures };
202
+ const f = failures[0];
203
+ const how = f.exit_code === null ? (f.error || 'did not run') : `exited ${f.exit_code}`;
204
+ return { verdict: 'fail', reason: `${f.stage}: \`${f.command}\` ${how}`, failures };
205
+ }
206
+ /** `package/package.json` from an npm tarball (gzip + ustar), without a `tar` binary. */
207
+ function readTarballPackageJson(tarballPath) {
208
+ let buf;
209
+ try {
210
+ buf = node_zlib_1.default.gunzipSync(node_fs_1.default.readFileSync(tarballPath));
211
+ }
212
+ catch {
213
+ return null;
214
+ }
215
+ let off = 0;
216
+ let longName = null;
217
+ while (off + 512 <= buf.length) {
218
+ const header = buf.subarray(off, off + 512);
219
+ if (header.every((b) => b === 0))
220
+ break;
221
+ const field = (start, len) => header.subarray(start, start + len).toString('utf8').replace(/\0.*$/s, '');
222
+ const size = parseInt(field(124, 12).trim() || '0', 8) || 0;
223
+ const type = field(156, 1);
224
+ const prefix = field(345, 155);
225
+ const name = longName ?? (prefix ? `${prefix}/${field(0, 100)}` : field(0, 100));
226
+ longName = null;
227
+ const body = buf.subarray(off + 512, off + 512 + size);
228
+ if (type === 'L')
229
+ longName = body.toString('utf8').replace(/\0.*$/s, '');
230
+ else if (type === 'x') {
231
+ const m = /\d+ path=([^\n]*)\n/.exec(body.toString('utf8'));
232
+ if (m)
233
+ longName = m[1];
234
+ }
235
+ else if ((type === '0' || type === '') && name.replace(/^[^/]+\//, '') === 'package.json' && name.split('/').length === 2) {
236
+ try {
237
+ return JSON.parse(body.toString('utf8'));
238
+ }
239
+ catch {
240
+ return null;
241
+ }
242
+ }
243
+ off += 512 + Math.ceil(size / 512) * 512;
244
+ }
245
+ return null;
246
+ }
247
+ /** A pp-core package: its package.json `bin` names both `pp-core` (installer) and `pp-tools`. */
248
+ function isPpCorePackage(pkg) {
249
+ const bin = pkg && typeof pkg['bin'] === 'object' && pkg['bin'] !== null ? pkg['bin'] : null;
250
+ return !!bin && typeof bin['pp-core'] === 'string' && typeof bin['pp-tools'] === 'string';
251
+ }
252
+ function parseInstallRunArgs(args) {
253
+ const usage = 'Usage: phase install-run <phase> [--tarball <path.tgz>] [--package-dir <dir>] [--no-build]';
254
+ let phase = null;
255
+ let tarball = null;
256
+ let packageDir = null;
257
+ let build = true;
258
+ for (let i = 0; i < args.length; i++) {
259
+ const a = args[i];
260
+ if (a === '--raw')
261
+ continue;
262
+ if (a === '--no-build')
263
+ build = false;
264
+ else if (a === '--tarball' || a === '--package-dir') {
265
+ const v = args[i + 1];
266
+ if (!v || v.startsWith('--'))
267
+ return { error: `${a} requires a value. ${usage}` };
268
+ if (a === '--tarball')
269
+ tarball = v;
270
+ else
271
+ packageDir = v;
272
+ i++;
273
+ }
274
+ else if (a.startsWith('--'))
275
+ return { error: `phase install-run does not support ${a}. ${usage}` };
276
+ else if (phase === null)
277
+ phase = a;
278
+ else
279
+ return { error: `phase install-run takes one phase. ${usage}` };
280
+ }
281
+ if (phase === null)
282
+ return { error: `phase required for phase install-run. ${usage}` };
283
+ if (tarball && packageDir)
284
+ return { error: `--tarball and --package-dir are exclusive. ${usage}` };
285
+ return { phase, tarball, packageDir, build };
286
+ }
287
+ const HARNESS_REL = node_path_1.default.join('scripts', 'release-tarball-smoke.cjs');
288
+ /** The release smoke harness: the package's own first, then the running copy's. */
289
+ function findSmokeHarness(packageDir) {
290
+ const own = node_path_1.default.resolve(__dirname, '..', '..', '..');
291
+ for (const root of [packageDir, own]) {
292
+ if (!root)
293
+ continue;
294
+ const p = node_path_1.default.join(root, HARNESS_REL);
295
+ if (node_fs_1.default.existsSync(p))
296
+ return p;
297
+ }
298
+ return null;
299
+ }
300
+ const npmCmd = () => (process.platform === 'win32' ? 'npm.cmd' : 'npm');
301
+ function npmConfigGet(key, env) {
302
+ const r = (0, node_child_process_1.spawnSync)(npmCmd(), ['config', 'get', key], { encoding: 'utf-8', env, shell: process.platform === 'win32', timeout: 30_000, windowsHide: true });
303
+ const v = r.status === 0 ? String(r.stdout || '').trim() : '';
304
+ return v && v !== 'undefined' && v !== 'null' ? v : null;
305
+ }
306
+ /**
307
+ * The env every child of the run sees: a temp HOME (and XDG config home), so
308
+ * nothing reads or writes the caller's runtime config dirs; the caller's npm
309
+ * cache and npm user config kept, so npm can install offline and reach the
310
+ * caller's configured registry.
311
+ */
312
+ function buildRunEnv(base, home) {
313
+ const env = { ...base };
314
+ for (const k of Object.keys(env)) {
315
+ if (k.startsWith('GIT_') || k === 'GSD_TEST_MODE' || k === 'CLAUDE_CONFIG_DIR' || k === 'GSD_WORKSTREAM')
316
+ delete env[k];
317
+ }
318
+ const cache = base.npm_config_cache || npmConfigGet('cache', base);
319
+ if (cache)
320
+ env.npm_config_cache = cache;
321
+ env.npm_config_userconfig = base.npm_config_userconfig || node_path_1.default.join(node_os_1.default.homedir(), '.npmrc');
322
+ env.HOME = home;
323
+ env.USERPROFILE = home;
324
+ env.XDG_CONFIG_HOME = node_path_1.default.join(home, '.config');
325
+ env.npm_config_update_notifier = 'false';
326
+ env.npm_config_audit = 'false';
327
+ env.npm_config_fund = 'false';
328
+ env.npm_config_loglevel = 'error';
329
+ env.NO_UPDATE_NOTIFIER = '1';
330
+ return env;
331
+ }
332
+ const ANSI_RE = new RegExp('\\u001b\\[[0-9;]*[A-Za-z]', 'g');
333
+ /** Drop ANSI colour codes and replace the temp root with `<install-run>`. */
334
+ function scrub(text, root) {
335
+ const s = String(text ?? '').replace(ANSI_RE, '');
336
+ return root ? s.split(root).join('<install-run>') : s;
337
+ }
338
+ function record(out, root, stage, command, res, started) {
339
+ const entry = {
340
+ stage,
341
+ command,
342
+ exit_code: res.status,
343
+ stdout: truncateOutput(scrub(res.stdout ?? '', root)),
344
+ stderr: truncateOutput(scrub(res.stderr ?? '', root)),
345
+ duration_ms: Date.now() - started,
346
+ ...(res.error ? { error: scrub(res.error, root) } : {}),
347
+ };
348
+ out.push(entry);
349
+ return entry;
350
+ }
351
+ function spawnRecorded(out, root, stage, command, file, args, opts) {
352
+ const started = Date.now();
353
+ const r = (0, node_child_process_1.spawnSync)(file, args, {
354
+ cwd: opts.cwd, env: opts.env, encoding: 'utf-8', timeout: opts.timeout ?? COMMAND_TIMEOUT_MS,
355
+ stdio: ['pipe', 'pipe', 'pipe'], shell: opts.shell ?? false, windowsHide: true,
356
+ });
357
+ const error = r.error ? String(r.error.message || r.error) : (r.signal ? `terminated by ${r.signal}` : undefined);
358
+ return record(out, root, stage, command, { status: r.status, stdout: r.stdout, stderr: r.stderr, error }, started);
359
+ }
360
+ /** Run the install run for `phase` and write its results file. Throws only on a missing phase. */
361
+ function runInstallRun(opts) {
362
+ const found = findPhaseInternal(opts.cwd, opts.phase);
363
+ if (!found)
364
+ throw new Error(`Phase ${opts.phase} not found`);
365
+ const phaseDirAbs = node_path_1.default.resolve(opts.cwd, found.directory);
366
+ const rp = getRoadmapPhaseInternal(opts.cwd, opts.phase);
367
+ const checks = resolveInstallChecks(rp?.section ?? null);
368
+ const commands = [];
369
+ const result = {
370
+ schema: exports.INSTALL_RUN_SCHEMA,
371
+ phase: found.phase_number,
372
+ phase_dir: found.directory.split(node_path_1.default.sep).join('/'),
373
+ generated_at: new Date().toISOString(),
374
+ verdict: 'fail',
375
+ reason: '',
376
+ package: { name: null, version: null, tarball: null, source: null },
377
+ install: { runtime: 'claude', scope: 'local', npm_mode: null, tools: null },
378
+ checks: { source: checks.source, line: checks.line, invalid: checks.invalid },
379
+ commands,
380
+ failures: [],
381
+ };
382
+ const root = node_path_1.default.join(node_os_1.default.tmpdir(), `pp-install-run-${node_crypto_1.default.randomUUID()}`);
383
+ let applicable = true;
384
+ let notApplicableReason = '';
385
+ let initDone = false;
386
+ let checksRun = false;
387
+ try {
388
+ node_fs_1.default.mkdirSync(root, { recursive: true });
389
+ const home = node_path_1.default.join(root, 'home');
390
+ const prefix = node_path_1.default.join(root, 'prefix');
391
+ const fixture = node_path_1.default.join(root, 'fixture');
392
+ for (const d of [home, prefix, fixture, node_path_1.default.join(root, 'pack')])
393
+ node_fs_1.default.mkdirSync(d, { recursive: true });
394
+ const env = buildRunEnv(opts.env ?? process.env, home);
395
+ const winShell = process.platform === 'win32';
396
+ // 1. The tarball: given, or built and packed from the package dir.
397
+ let tarballPath = null;
398
+ const packageDir = opts.tarball ? null : node_path_1.default.resolve(opts.cwd, opts.packageDir || '.');
399
+ if (opts.tarball) {
400
+ tarballPath = node_path_1.default.resolve(opts.cwd, opts.tarball);
401
+ result.package.source = 'tarball';
402
+ if (!node_fs_1.default.existsSync(tarballPath)) {
403
+ record(commands, root, 'pack', `(tarball) ${opts.tarball}`, { status: null, error: 'tarball not found' }, Date.now());
404
+ tarballPath = null;
405
+ }
406
+ }
407
+ else if (packageDir) {
408
+ let pkg = null;
409
+ try {
410
+ pkg = JSON.parse(node_fs_1.default.readFileSync(node_path_1.default.join(packageDir, 'package.json'), 'utf-8'));
411
+ }
412
+ catch { /* no package.json */ }
413
+ if (!isPpCorePackage(pkg)) {
414
+ applicable = false;
415
+ notApplicableReason = 'the package dir is not a pp-core package (package.json bin lacks pp-core and pp-tools); nothing to install';
416
+ }
417
+ else {
418
+ result.package.source = 'pack';
419
+ const built = opts.build === false ? null
420
+ : spawnRecorded(commands, root, 'build', 'npm run build', npmCmd(), ['run', 'build'], { cwd: packageDir, env, timeout: BUILD_TIMEOUT_MS, shell: winShell });
421
+ if (!built || built.exit_code === 0) {
422
+ const packed = spawnRecorded(commands, root, 'pack', 'npm pack', npmCmd(), ['pack', '--pack-destination', node_path_1.default.join(root, 'pack')], { cwd: packageDir, env, timeout: BUILD_TIMEOUT_MS, shell: winShell });
423
+ const tgz = node_fs_1.default.readdirSync(node_path_1.default.join(root, 'pack')).find((f) => f.endsWith('.tgz'));
424
+ if (packed.exit_code === 0 && tgz)
425
+ tarballPath = node_path_1.default.join(root, 'pack', tgz);
426
+ else if (packed.exit_code === 0) {
427
+ packed.exit_code = 1;
428
+ packed.error = 'npm pack produced no .tgz';
429
+ }
430
+ }
431
+ }
432
+ }
433
+ const harnessPath = applicable ? findSmokeHarness(packageDir) : null;
434
+ if (applicable && !harnessPath) {
435
+ applicable = false;
436
+ notApplicableReason = `no install harness (${HARNESS_REL.split(node_path_1.default.sep).join('/')}) in the package or the running pp-core`;
437
+ }
438
+ if (applicable && tarballPath) {
439
+ const meta = readTarballPackageJson(tarballPath);
440
+ result.package.tarball = node_path_1.default.basename(tarballPath);
441
+ result.package.name = meta && typeof meta['name'] === 'string' ? meta['name'] : null;
442
+ result.package.version = meta && typeof meta['version'] === 'string' ? meta['version'] : null;
443
+ // eslint-disable-next-line @typescript-eslint/no-require-imports -- the harness path is resolved at run time
444
+ const harness = require(harnessPath);
445
+ // 2. npm install into a temp prefix: offline first, then prefer-offline on a cache miss.
446
+ const installStarted = Date.now();
447
+ const smoke = (mode) => harness.runSmoke({
448
+ tarballPath, installPrefix: prefix, expectedVersion: result.package.version ?? '', lifecycleCommands: [],
449
+ npmEnv: { ...env, ...(mode === 'offline' ? { npm_config_offline: 'true' } : { npm_config_prefer_offline: 'true' }) },
450
+ });
451
+ let mode = 'offline';
452
+ let s = smoke(mode);
453
+ const detail = (key) => { const v = s.details[key]; return typeof v === 'string' ? v : ''; };
454
+ if (s.code === harness.SMOKE.INSTALL_FAILED && /ENOTCACHED/.test(detail('stderr'))) {
455
+ mode = 'prefer-offline';
456
+ s = smoke(mode);
457
+ }
458
+ result.install.npm_mode = mode;
459
+ const installCmd = `npm install -g --prefix <install-run>/prefix ${result.package.tarball} --${mode}`;
460
+ const installed = record(commands, root, 'install', installCmd, {
461
+ status: s.code === harness.SMOKE.OK ? 0 : 1,
462
+ stdout: detail('stdout'),
463
+ stderr: detail('stderr'),
464
+ error: s.code === harness.SMOKE.OK ? undefined : `harness code ${s.code}`,
465
+ }, installStarted);
466
+ // 3. The installer the user runs, in a fresh git project. The global npm
467
+ // prefix is the temp prefix: the installed product sees only itself
468
+ // installed globally, as on a fresh machine.
469
+ const toolEnv = { ...env, npm_config_prefix: prefix };
470
+ delete toolEnv.NPM_CONFIG_PREFIX;
471
+ if (installed.exit_code === 0) {
472
+ (0, node_child_process_1.spawnSync)('git', ['init', '-q'], { cwd: fixture, env, encoding: 'utf-8', timeout: COMMAND_TIMEOUT_MS, windowsHide: true });
473
+ const instStarted = Date.now();
474
+ const inst = harness.driveInstaller({
475
+ installPrefix: prefix, flags: ['--claude', '--local'], cwd: fixture, configRoot: node_path_1.default.join(fixture, '.claude'), env: toolEnv,
476
+ });
477
+ const tools = node_path_1.default.join(fixture, '.claude', 'pp-core', 'bin', 'pp-tools.cjs');
478
+ const toolsMissing = inst.ok && !node_fs_1.default.existsSync(tools);
479
+ record(commands, root, 'install', 'pp-core --claude --local', {
480
+ status: toolsMissing ? 1 : inst.status, stdout: inst.stdout, stderr: inst.stderr,
481
+ error: inst.reason || (toolsMissing ? 'the installer exited 0 but .claude/pp-core/bin/pp-tools.cjs is missing' : undefined),
482
+ }, instStarted);
483
+ if (inst.ok && !toolsMissing) {
484
+ result.install.tools = '.claude/pp-core/bin/pp-tools.cjs';
485
+ const runTool = (stage, label, argv) => spawnRecorded(commands, root, stage, label, process.execPath, [tools, ...argv], { cwd: fixture, env: toolEnv });
486
+ // 4. init, non-interactively, through the installed pp-tools.
487
+ for (let i = 0; i < INIT_MAX_STEPS && !initDone; i++) {
488
+ const next = runTool('init', 'pp-tools query init-contract --session-next --offline', ['query', 'init-contract', '--session-next', '--offline']);
489
+ if (next.exit_code !== 0)
490
+ break;
491
+ let state;
492
+ try {
493
+ state = JSON.parse(next.stdout);
494
+ }
495
+ catch {
496
+ next.exit_code = 1;
497
+ next.error = 'unparseable init session state';
498
+ break;
499
+ }
500
+ if (state.done) {
501
+ initDone = true;
502
+ break;
503
+ }
504
+ const step = String(state.step ?? '');
505
+ const answer = exports.INIT_ANSWERS[step] ?? state.preselected ?? (state.optionalAnswer ? '' : undefined);
506
+ if (answer === undefined) {
507
+ next.exit_code = 1;
508
+ next.error = `no fixture answer for init step "${step}"`;
509
+ break;
510
+ }
511
+ const ans = runTool('init', `pp-tools query init-contract --session-answer ${step}=… --offline`, ['query', 'init-contract', '--session-answer', `${step}=${answer}`, '--offline']);
512
+ if (ans.exit_code !== 0)
513
+ break;
514
+ }
515
+ // 5. The phase's install checks against the installed copy.
516
+ for (const check of checks.commands)
517
+ runTool('check', check.text, check.argv);
518
+ checksRun = true;
519
+ }
520
+ }
521
+ }
522
+ }
523
+ finally {
524
+ try {
525
+ node_fs_1.default.rmSync(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 });
526
+ }
527
+ catch { /* best-effort */ }
528
+ }
529
+ const v = computeInstallVerdict({ applicable, notApplicableReason, commands, invalid: checks.invalid, initDone, checksRun });
530
+ result.verdict = v.verdict;
531
+ result.reason = v.reason;
532
+ result.failures = v.failures;
533
+ const resultsFile = node_path_1.default.join(phaseDirAbs, `${found.phase_number}-${exports.INSTALL_RUN_SUFFIX}`);
534
+ node_fs_1.default.writeFileSync(resultsFile, JSON.stringify(result, null, 2) + '\n');
535
+ return { ...result, results_file: node_path_1.default.relative(opts.cwd, resultsFile).split(node_path_1.default.sep).join('/') };
536
+ }
@@ -161,6 +161,7 @@ const ERROR_REASON = Object.freeze({
161
161
  CONFIG_NO_FILE: 'config_no_file',
162
162
  CONFIG_PARSE_FAILED: 'config_parse_failed',
163
163
  CONFIG_INVALID_KEY: 'config_invalid_key',
164
+ CONFIG_INVALID_VALUE: 'config_invalid_value',
164
165
  // SDK / pp-tools dispatch
165
166
  SDK_FAIL_FAST: 'sdk_fail_fast',
166
167
  SDK_UNKNOWN_COMMAND: 'sdk_unknown_command',
@@ -186,6 +187,20 @@ const ERROR_REASON = Object.freeze({
186
187
  VAULT_PROMOTE_TARGET_REFUSED: 'vault_promote_target_refused',
187
188
  VAULT_PROMOTE_CONCURRENT_EDIT: 'vault_promote_concurrent_edit',
188
189
  VAULT_PROMOTE_TRANSPORT_FAILED: 'vault_promote_transport_failed',
190
+ // discovery rollup (discovery-v2 Phase 1: D-07/D-08 gate)
191
+ DISCOVERY_ROLLUP_PARSE_FAILED: 'discovery_rollup_parse_failed',
192
+ DISCOVERY_ROLLUP_MISMATCH: 'discovery_rollup_mismatch',
193
+ // discovery input package (discovery-v2 Phase 2: D-19/D-22 gate)
194
+ DISCOVERY_INPUT_CHECK_FAILED: 'discovery_input_check_failed',
195
+ // discovery research track (discovery-v2 Phase 3: D-42/D-43 gate)
196
+ DISCOVERY_RESEARCH_CHECK_FAILED: 'discovery_research_check_failed',
197
+ // discovery screen inventory (discovery-v2 Phase 4: D-52/D-53 gate)
198
+ DISCOVERY_SCREEN_CHECK_FAILED: 'discovery_screen_check_failed',
199
+ DISCOVERY_FEEDBACK_OPEN: 'discovery_feedback_open',
200
+ // discovery prototype element sweep
201
+ DISCOVERY_ELEMENT_SWEEP_FAILED: 'discovery_element_sweep_failed',
202
+ // discovery scope re-baselining (discovery-v2 Phase 5: D-72/D-73 gate)
203
+ DISCOVERY_REBASELINE_CHECK_FAILED: 'discovery_rebaseline_check_failed',
189
204
  // generic
190
205
  USAGE: 'usage',
191
206
  UNKNOWN: 'unknown',
@@ -41,6 +41,8 @@ const { resolveCapabilityRuntimeState } = capabilityStateModule;
41
41
  // eslint-disable-next-line @typescript-eslint/no-require-imports
42
42
  const capabilityActivationModule = require("./capability-activation.cjs");
43
43
  const { _getNestedConfigValue, _readRawConfigKey, _resolveActivationValue, resolveConfigKey } = capabilityActivationModule;
44
+ // ─── Phase ceremony tier (pack 9): `--phase` / `--tier` overlay ───────────────
45
+ const phase_tier_cjs_1 = require("./phase-tier.cjs");
44
46
  // ─── Canonical points (derived from LOOP_HOST_CONTRACT — authoritative 12) ───
45
47
  // FIX 2: Derive the authoritative canonical set from LOOP_HOST_CONTRACT so it
46
48
  // cannot drift from the host contract. CANONICAL_POINTS_FALLBACK is kept as an
@@ -400,6 +402,14 @@ function renderLoopHooks(resolved) {
400
402
  * No JSON envelope is emitted — output is clean for shell $(…) capture.
401
403
  * Missing <capId> value → coreError + non-zero exit.
402
404
  * Unknown/inactive capId → `false` (not an error).
405
+ *
406
+ * --phase <N> [--tier light|standard|heavy] (pack 9): resolve the phase's
407
+ * ceremony tier (src/phase-tier.cts) and overlay its gate booleans
408
+ * (`workflow.research`, `workflow.pattern_mapper`, …) on the config snapshot
409
+ * BEFORE capability state and hook activation read it, so every `when` /
410
+ * activation key of those gates answers the tier-resolved value. Without
411
+ * `--phase` the project's own booleans are read as before (an explicitly
412
+ * invoked gate command such as /pp-secure-phase is not governed by a tier).
403
413
  */
404
414
  function cmdLoopRenderHooks(cwd, point, raw, options = {}) {
405
415
  if (!point) {
@@ -429,6 +439,17 @@ function cmdLoopRenderHooks(cwd, point, raw, options = {}) {
429
439
  catch {
430
440
  config = {};
431
441
  }
442
+ const tierPhase = typeof options['phase'] === 'string' && options['phase'] !== '' ? options['phase'] : null;
443
+ const tierCli = typeof options['tier'] === 'string' && options['tier'] !== '' ? options['tier'] : null;
444
+ let phaseTier = null;
445
+ if (tierPhase !== null || tierCli !== null) {
446
+ if (tierCli !== null && !['light', 'standard', 'heavy'].includes(tierCli)) {
447
+ coreError('--tier needs one of light, standard, heavy');
448
+ return;
449
+ }
450
+ phaseTier = (0, phase_tier_cjs_1.resolvePhaseTierForProject)(cwd, tierPhase, tierCli);
451
+ config = (0, phase_tier_cjs_1.overlayTierGates)(config, phaseTier);
452
+ }
432
453
  const state = resolveCapabilityRuntimeState(cwd, runtimeConfigDir, config);
433
454
  // Registry is the static generated module — same object capability-state uses internally.
434
455
  // eslint-disable-next-line @typescript-eslint/no-require-imports
@@ -458,6 +479,9 @@ function cmdLoopRenderHooks(cwd, point, raw, options = {}) {
458
479
  activeHooks: resolved.activeHooks,
459
480
  rendered,
460
481
  };
482
+ if (phaseTier) {
483
+ envelope.phase_tier = { phase: tierPhase, tier: phaseTier.tier, source: phaseTier.source, suppressed: phaseTier.suppressed };
484
+ }
461
485
  if (state.warnings && state.warnings.length > 0) {
462
486
  envelope.warnings = state.warnings;
463
487
  }