@pareto4/pp-core 1.11.1 → 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 (255) 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 +70 -6
  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 +93 -4
  59. package/pp-core/bin/lib/init.cjs +124 -134
  60. package/pp-core/bin/lib/install-profiles.cjs +5 -0
  61. package/pp-core/bin/lib/install-run.cjs +536 -0
  62. package/pp-core/bin/lib/io.cjs +15 -0
  63. package/pp-core/bin/lib/loop-resolver.cjs +24 -0
  64. package/pp-core/bin/lib/milestone.cjs +345 -48
  65. package/pp-core/bin/lib/phase-command-router.cjs +23 -1
  66. package/pp-core/bin/lib/phase-locator.cjs +15 -6
  67. package/pp-core/bin/lib/phase-scope.cjs +91 -0
  68. package/pp-core/bin/lib/phase-tier.cjs +467 -0
  69. package/pp-core/bin/lib/phase.cjs +247 -21
  70. package/pp-core/bin/lib/phases-command-router.cjs +2 -0
  71. package/pp-core/bin/lib/plan-graph.cjs +368 -0
  72. package/pp-core/bin/lib/planning-workspace.cjs +53 -0
  73. package/pp-core/bin/lib/platform-bindings.cjs +32 -2
  74. package/pp-core/bin/lib/profile-output.cjs +11 -3
  75. package/pp-core/bin/lib/prohibition-enforcement.cjs +112 -0
  76. package/pp-core/bin/lib/roadmap-parser.cjs +366 -99
  77. package/pp-core/bin/lib/roadmap.cjs +141 -39
  78. package/pp-core/bin/lib/runtime-hooks-surface.cjs +1 -1
  79. package/pp-core/bin/lib/shell-command-projection.cjs +24 -0
  80. package/pp-core/bin/lib/state-command-router.cjs +82 -13
  81. package/pp-core/bin/lib/state-document.cjs +124 -7
  82. package/pp-core/bin/lib/state.cjs +784 -257
  83. package/pp-core/bin/lib/template.cjs +22 -6
  84. package/pp-core/bin/lib/vault-command-router.cjs +57 -6
  85. package/pp-core/bin/lib/vault-practices.cjs +6 -36
  86. package/pp-core/bin/lib/vault-write-back.cjs +20 -4
  87. package/pp-core/bin/lib/vault.cjs +555 -23
  88. package/pp-core/bin/lib/verify.cjs +53 -31
  89. package/pp-core/bin/lib/workstream.cjs +1 -1
  90. package/pp-core/bin/lib/worktree-safety.cjs +26 -15
  91. package/pp-core/bin/portable-timeout.sh +89 -0
  92. package/pp-core/bin/pp-tools.cjs +149 -9
  93. package/pp-core/bin/shared/config-defaults.manifest.json +7 -0
  94. package/pp-core/bin/shared/config-schema.manifest.json +3 -0
  95. package/pp-core/references/autonomous-best-effort.md +461 -0
  96. package/pp-core/references/autonomous-lifecycle.md +146 -0
  97. package/pp-core/references/autonomous-smart-discuss.md +16 -12
  98. package/pp-core/references/checkpoints.md +1 -1
  99. package/pp-core/references/discovery-id-conventions.md +202 -0
  100. package/pp-core/references/discovery-rebaseline.md +254 -0
  101. package/pp-core/references/discovery-research-track.md +283 -0
  102. package/pp-core/references/discovery-traceability.md +400 -0
  103. package/pp-core/references/loop-hook-dispatch.md +7 -0
  104. package/pp-core/references/mandatory-initial-read.md +1 -1
  105. package/pp-core/references/planner-gap-closure.md +1 -0
  106. package/pp-core/references/planner-human-verify-mode.md +1 -1
  107. package/pp-core/references/planner-load-graph-context.md +1 -1
  108. package/pp-core/references/planner-plan-graph.md +66 -0
  109. package/pp-core/references/planner-tdd.md +1 -0
  110. package/pp-core/references/planning-config.md +8 -0
  111. package/pp-core/references/sketch-tooling.md +4 -0
  112. package/pp-core/references/tdd.md +1 -0
  113. package/pp-core/templates/claude-md.md +1 -1
  114. package/pp-core/templates/phase-prompt.md +6 -2
  115. package/pp-core/templates/prototype-review/review.js +303 -0
  116. package/pp-core/templates/state.md +6 -0
  117. package/pp-core/workflows/_runtime-launcher.snippet.sh +1 -1
  118. package/pp-core/workflows/add-backlog.md +1 -1
  119. package/pp-core/workflows/add-phase.md +1 -1
  120. package/pp-core/workflows/add-tests.md +1 -1
  121. package/pp-core/workflows/add-todo.md +1 -1
  122. package/pp-core/workflows/ai-integration-phase.md +2 -2
  123. package/pp-core/workflows/audit-fix.md +12 -16
  124. package/pp-core/workflows/audit-milestone.md +1 -1
  125. package/pp-core/workflows/audit-uat.md +1 -1
  126. package/pp-core/workflows/autonomous.md +68 -306
  127. package/pp-core/workflows/check-todos.md +1 -1
  128. package/pp-core/workflows/cleanup.md +1 -1
  129. package/pp-core/workflows/code-review-fix.md +1 -1
  130. package/pp-core/workflows/code-review.md +19 -7
  131. package/pp-core/workflows/complete-milestone.md +41 -60
  132. package/pp-core/workflows/debug.md +1 -1
  133. package/pp-core/workflows/diagnose-issues.md +1 -1
  134. package/pp-core/workflows/discovery-sprint/gap-analysis.md +140 -0
  135. package/pp-core/workflows/discovery-sprint/input-package.md +140 -0
  136. package/pp-core/workflows/discovery-sprint/prefill.md +92 -0
  137. package/pp-core/workflows/discovery-sprint/prototype.md +257 -0
  138. package/pp-core/workflows/discovery-sprint/rebaseline.md +276 -0
  139. package/pp-core/workflows/discovery-sprint/session-13-gates.md +24 -0
  140. package/pp-core/workflows/discovery-sprint/sessions/01-kick-off.md +92 -0
  141. package/pp-core/workflows/discovery-sprint/sessions/02-as-is.md +98 -0
  142. package/pp-core/workflows/discovery-sprint/sessions/03-to-be.md +215 -0
  143. package/pp-core/workflows/discovery-sprint/sessions/04-ux-design.md +146 -0
  144. package/pp-core/workflows/discovery-sprint/sessions/05-architecture.md +98 -0
  145. package/pp-core/workflows/discovery-sprint/sessions/06-security.md +107 -0
  146. package/pp-core/workflows/discovery-sprint/sessions/07-identity.md +98 -0
  147. package/pp-core/workflows/discovery-sprint/sessions/08-integration.md +85 -0
  148. package/pp-core/workflows/discovery-sprint/sessions/09-qa.md +109 -0
  149. package/pp-core/workflows/discovery-sprint/sessions/10-infrastructure.md +119 -0
  150. package/pp-core/workflows/discovery-sprint/sessions/11-roadmap.md +92 -0
  151. package/pp-core/workflows/discovery-sprint/sessions/12-delivery-model.md +98 -0
  152. package/pp-core/workflows/discovery-sprint/sessions/13-wrap-up.md +205 -0
  153. package/pp-core/workflows/discovery-sprint/templates/context-template.md +266 -0
  154. package/pp-core/workflows/discovery-sprint.md +79 -1714
  155. package/pp-core/workflows/discovery-to-build/steps/architecture-deliverables.md +5 -5
  156. package/pp-core/workflows/discovery-to-build/steps/screen-inventory-gate.md +70 -0
  157. package/pp-core/workflows/discovery-to-build/templates/discovery-references.md +54 -0
  158. package/pp-core/workflows/discovery-to-build/templates/pp-handoff.md +184 -0
  159. package/pp-core/workflows/discovery-to-build.md +21 -238
  160. package/pp-core/workflows/discuss-phase/modes/advisor.md +1 -1
  161. package/pp-core/workflows/discuss-phase/modes/auto.md +1 -1
  162. package/pp-core/workflows/discuss-phase/modes/chain.md +1 -1
  163. package/pp-core/workflows/discuss-phase-assumptions.md +1 -1
  164. package/pp-core/workflows/discuss-phase.md +9 -2
  165. package/pp-core/workflows/do.md +4 -4
  166. package/pp-core/workflows/docs-update.md +1 -1
  167. package/pp-core/workflows/edit-phase.md +1 -1
  168. package/pp-core/workflows/eval-review.md +1 -1
  169. package/pp-core/workflows/execute-phase/steps/checkpoint-handling.md +45 -0
  170. package/pp-core/workflows/execute-phase/steps/client-context-gate.md +1 -1
  171. package/pp-core/workflows/execute-phase/steps/codebase-drift-gate.md +1 -1
  172. package/pp-core/workflows/execute-phase/steps/cross-ai-delegation.md +90 -0
  173. package/pp-core/workflows/execute-phase/steps/install-run.md +29 -0
  174. package/pp-core/workflows/execute-phase/steps/phase-close-rebaseline.md +23 -0
  175. package/pp-core/workflows/execute-phase/steps/post-merge-gate.md +76 -77
  176. package/pp-core/workflows/execute-phase/steps/serial-group-dispatch.md +72 -0
  177. package/pp-core/workflows/execute-phase/steps/vault-fetch.md +5 -5
  178. package/pp-core/workflows/execute-phase/steps/vault-load-gate.md +19 -112
  179. package/pp-core/workflows/execute-phase.md +102 -216
  180. package/pp-core/workflows/execute-plan.md +6 -2
  181. package/pp-core/workflows/explore.md +1 -1
  182. package/pp-core/workflows/extract-learnings.md +1 -1
  183. package/pp-core/workflows/forensics.md +4 -4
  184. package/pp-core/workflows/graduation.md +1 -1
  185. package/pp-core/workflows/health.md +1 -1
  186. package/pp-core/workflows/help/modes/brief.md +1 -0
  187. package/pp-core/workflows/help/modes/default.md +2 -1
  188. package/pp-core/workflows/help/modes/full.md +12 -5
  189. package/pp-core/workflows/import.md +1 -1
  190. package/pp-core/workflows/ingest-docs.md +1 -1
  191. package/pp-core/workflows/init.md +18 -2
  192. package/pp-core/workflows/insert-phase.md +1 -1
  193. package/pp-core/workflows/list-workspaces.md +1 -1
  194. package/pp-core/workflows/manager.md +3 -3
  195. package/pp-core/workflows/map-codebase.md +2 -2
  196. package/pp-core/workflows/milestone-summary.md +4 -4
  197. package/pp-core/workflows/mvp-phase.md +1 -1
  198. package/pp-core/workflows/new-milestone.md +7 -8
  199. package/pp-core/workflows/new-project.md +6 -1
  200. package/pp-core/workflows/new-workspace.md +1 -1
  201. package/pp-core/workflows/next.md +2 -2
  202. package/pp-core/workflows/note.md +2 -2
  203. package/pp-core/workflows/pause-work.md +1 -1
  204. package/pp-core/workflows/plan-milestone-gaps.md +1 -1
  205. package/pp-core/workflows/plan-phase/steps/chunked-planning.md +111 -0
  206. package/pp-core/workflows/plan-phase/steps/phase-tier.md +34 -0
  207. package/pp-core/workflows/plan-phase.md +43 -167
  208. package/pp-core/workflows/plan-review-convergence.md +2 -2
  209. package/pp-core/workflows/plant-seed.md +1 -1
  210. package/pp-core/workflows/pr-branch.md +1 -1
  211. package/pp-core/workflows/profile-user.md +1 -1
  212. package/pp-core/workflows/progress.md +3 -3
  213. package/pp-core/workflows/quick.md +3 -1
  214. package/pp-core/workflows/remove-phase.md +1 -1
  215. package/pp-core/workflows/remove-workspace.md +1 -1
  216. package/pp-core/workflows/resume-project.md +6 -12
  217. package/pp-core/workflows/review.md +1 -1
  218. package/pp-core/workflows/scan.md +1 -1
  219. package/pp-core/workflows/secure-phase.md +1 -1
  220. package/pp-core/workflows/settings-advanced.md +1 -1
  221. package/pp-core/workflows/settings-integrations.md +1 -1
  222. package/pp-core/workflows/settings.md +1 -1
  223. package/pp-core/workflows/ship.md +25 -8
  224. package/pp-core/workflows/sketch-wrap-up.md +1 -1
  225. package/pp-core/workflows/sketch.md +2 -1
  226. package/pp-core/workflows/spec-phase.md +1 -1
  227. package/pp-core/workflows/spike-wrap-up.md +1 -1
  228. package/pp-core/workflows/spike.md +1 -1
  229. package/pp-core/workflows/stats.md +2 -2
  230. package/pp-core/workflows/thread.md +1 -1
  231. package/pp-core/workflows/transition.md +5 -9
  232. package/pp-core/workflows/ui-phase.md +23 -2
  233. package/pp-core/workflows/ui-review.md +1 -1
  234. package/pp-core/workflows/ultraplan-phase.md +2 -2
  235. package/pp-core/workflows/validate-phase.md +1 -1
  236. package/pp-core/workflows/vault-write-back.md +1 -1
  237. package/pp-core/workflows/verify-phase.md +29 -26
  238. package/pp-core/workflows/verify-work.md +14 -10
  239. package/scripts/base64-scan.sh +7 -41
  240. package/scripts/changeset/cli.cjs +25 -5
  241. package/scripts/dev-env.cjs +462 -0
  242. package/scripts/lib/allowlist-ratchet.cjs +103 -1
  243. package/scripts/lib/size-budget.cjs +311 -0
  244. package/scripts/lint-neutrality-names.cjs +694 -0
  245. package/scripts/lint-org-coordinates.allowlist.json +35 -0
  246. package/scripts/lint-org-coordinates.cjs +426 -0
  247. package/scripts/lint-skill-deps.cjs +48 -9
  248. package/scripts/lint-test-file-count.cjs +17 -2
  249. package/scripts/prompt-injection-scan.sh +41 -20
  250. package/scripts/sync-manifest-versions.cjs +41 -7
  251. package/scripts/sync-runtime-launcher.cjs +106 -1
  252. package/scripts/update-size-baseline.cjs +274 -30
  253. package/scripts/workflow-size.cjs +61 -9
  254. package/commands/pp-build-handoff.md +0 -46
  255. package/pp-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +0 -94
@@ -31,6 +31,8 @@ const schema_detect_cjs_1 = require("./schema-detect.cjs");
31
31
  const artifacts_cjs_1 = require("./artifacts.cjs");
32
32
  // eslint-disable-next-line @typescript-eslint/no-require-imports -- agent-install-check.cjs is an export= CommonJS module
33
33
  const agentInstallCheck = require("./agent-install-check.cjs");
34
+ // eslint-disable-next-line @typescript-eslint/no-require-imports -- milestone.cjs is an export= CommonJS module
35
+ const milestoneMod = require("./milestone.cjs");
34
36
  const { checkAgentsInstalled } = agentInstallCheck;
35
37
  // eslint-disable-next-line @typescript-eslint/no-require-imports
36
38
  const ioMod = require("./io.cjs");
@@ -42,15 +44,16 @@ const { loadConfig, CONFIG_DEFAULTS } = configLoaderMod;
42
44
  const phaseIdMod = require("./phase-id.cjs");
43
45
  const { normalizePhaseName, phaseTokenMatches, escapeRegex, getMilestoneFromPhaseId } = phaseIdMod;
44
46
  // eslint-disable-next-line @typescript-eslint/no-require-imports
45
- const phaseLocatorMod = require("./phase-locator.cjs");
46
- const { findPhaseInternal } = phaseLocatorMod;
47
+ const phaseScopeMod = require("./phase-scope.cjs");
48
+ const { resolvePhaseForRead } = phaseScopeMod;
47
49
  // eslint-disable-next-line @typescript-eslint/no-require-imports
48
50
  const roadmapParserMod = require("./roadmap-parser.cjs");
49
- const { getMilestoneInfo, stripShippedMilestones, extractCurrentMilestone } = roadmapParserMod;
51
+ const { getDeclaredMilestoneInfo, stripShippedMilestones, extractCurrentMilestone } = roadmapParserMod;
50
52
  // eslint-disable-next-line @typescript-eslint/no-require-imports
51
53
  const worktreeSafetyMod = require("./worktree-safety.cjs");
52
54
  const { inspectWorktreeHealth } = worktreeSafetyMod;
53
- const { planningDir } = planningWorkspace;
55
+ const plan_graph_cjs_1 = require("./plan-graph.cjs");
56
+ const { planningDir, milestonePathsFor, resolveProjectFile } = planningWorkspace;
54
57
  const { extractFrontmatter, parseMustHavesBlock } = frontmatterMod;
55
58
  const { writeStateMd } = stateMod;
56
59
  const { MODEL_PROFILES } = modelProfilesMod;
@@ -538,6 +541,16 @@ function cmdVerifyPlanStructure(cwd, filePath, raw) {
538
541
  if (fm[field] === undefined)
539
542
  errors.push(`Missing required frontmatter field: ${field}`);
540
543
  }
544
+ // `title:` is required on every plan not yet executed. A plan that already has a
545
+ // SUMMARY predates the rule (or ran without it): its missing title only warns.
546
+ const titleProblem = (0, plan_graph_cjs_1.planTitleProblem)(fm['title']);
547
+ if (titleProblem) {
548
+ const summaryPath = fullPath.replace(/PLAN\.md$/, 'SUMMARY.md');
549
+ if (summaryPath !== fullPath && node_fs_1.default.existsSync(summaryPath))
550
+ warnings.push(`${titleProblem} (executed plan: warning only)`);
551
+ else
552
+ errors.push(titleProblem);
553
+ }
541
554
  const taskPattern = /<task[^>]*>([\s\S]*?)<\/task>/g;
542
555
  const tasks = [];
543
556
  let taskMatch;
@@ -592,7 +605,7 @@ function cmdVerifyPhaseCompleteness(cwd, phase, raw) {
592
605
  if (!phase) {
593
606
  error('phase required');
594
607
  }
595
- const phaseInfoRaw = findPhaseInternal(cwd, phase);
608
+ const phaseInfoRaw = resolvePhaseForRead(cwd, phase);
596
609
  if (!phaseInfoRaw || !phaseInfoRaw['found']) {
597
610
  output({ error: 'Phase not found', phase }, raw);
598
611
  return;
@@ -898,7 +911,10 @@ function cmdVerifyKeyLinks(cwd, planFilePath, raw) {
898
911
  }, raw, allVerified ? 'valid' : 'invalid');
899
912
  }
900
913
  function listMilestoneArchiveDirs(planBase) {
901
- const milestonesDir = node_path_1.default.join(planBase, 'milestones');
914
+ // CARRY-05: one owner for the archive-directory location, shared with the
915
+ // `milestone complete` writer. planBase is planningDir(cwd) at every call
916
+ // site, so this already followed --ws; routing it here keeps it that way.
917
+ const milestonesDir = milestonePathsFor(planBase).milestonesArchive;
902
918
  try {
903
919
  return node_fs_1.default
904
920
  .readdirSync(milestonesDir, { withFileTypes: true })
@@ -938,7 +954,7 @@ function getActiveMilestoneArchiveDir(planBase) {
938
954
  const m = state.match(/^\s*(?:\*\*)?milestone(?:\*\*)?:\s*\*{0,2}\s*([^\s*\r\n#][^\s\r\n#]*)/mi);
939
955
  if (m && m[1]) {
940
956
  const milestone = m[1].trim();
941
- const candidate = node_path_1.default.join(planBase, 'milestones', `${milestone}-phases`);
957
+ const candidate = node_path_1.default.join(milestonePathsFor(planBase).milestonesArchive, `${milestone}-phases`);
942
958
  return archiveDirs.includes(candidate) ? candidate : null;
943
959
  }
944
960
  }
@@ -1113,7 +1129,9 @@ function cmdValidateHealth(cwd, options, raw) {
1113
1129
  return;
1114
1130
  }
1115
1131
  const planBase = planningDir(cwd);
1116
- const projectPath = node_path_1.default.join(planBase, 'PROJECT.md');
1132
+ // D1 (261007-jkl): E002/W001 judge the PROJECT.md that answers for this cwd —
1133
+ // the workstream's own, else the shared one — not a workstream path nothing creates.
1134
+ const projectPath = resolveProjectFile(cwd).path;
1117
1135
  const roadmapPath = node_path_1.default.join(planBase, 'ROADMAP.md');
1118
1136
  const statePath = node_path_1.default.join(planBase, 'STATE.md');
1119
1137
  const configPath = node_path_1.default.join(planBase, 'config.json');
@@ -1134,7 +1152,7 @@ function cmdValidateHealth(cwd, options, raw) {
1134
1152
  info.push(issue);
1135
1153
  };
1136
1154
  if (!node_fs_1.default.existsSync(planBase)) {
1137
- addIssue('error', 'E001', '.planning/ directory not found', `Run ${slash('new-project')} to initialize`);
1155
+ addIssue('error', 'E001', '.planning/ directory not found', `Run ${slash('init')} to initialize`);
1138
1156
  output({ status: 'broken', errors, warnings, info, repairable_count: 0 }, raw);
1139
1157
  return;
1140
1158
  }
@@ -1440,8 +1458,8 @@ function cmdValidateHealth(cwd, options, raw) {
1440
1458
  catch {
1441
1459
  /* W021 check is advisory — skip on error */
1442
1460
  }
1443
- const milestonesPath = node_path_1.default.join(planBase, 'MILESTONES.md');
1444
- const milestonesArchiveDir = node_path_1.default.join(planBase, 'milestones');
1461
+ // CARRY-05: W018's two paths come from the owner, not a local join.
1462
+ const { milestones: milestonesPath, milestonesArchive: milestonesArchiveDir } = milestonePathsFor(planBase);
1445
1463
  const missingFromRegistry = [];
1446
1464
  try {
1447
1465
  if (node_fs_1.default.existsSync(milestonesArchiveDir)) {
@@ -1530,8 +1548,13 @@ function cmdValidateHealth(cwd, options, raw) {
1530
1548
  }
1531
1549
  // ─── Perform repairs if requested ─────────────────────────────────────────
1532
1550
  const repairActions = [];
1533
- if (options['repair'] && repairs.length > 0) {
1551
+ // 261007-rly D-9: `--backfill` alone runs the backfill W018 names as its
1552
+ // remedy (health.md documents it as "triggered by `--backfill`"); every other
1553
+ // repair still needs `--repair`.
1554
+ if ((options['repair'] || options['backfill']) && repairs.length > 0) {
1534
1555
  for (const repair of repairs) {
1556
+ if (!options['repair'] && repair !== 'backfillMilestones')
1557
+ continue;
1535
1558
  try {
1536
1559
  switch (repair) {
1537
1560
  case 'createConfig':
@@ -1564,22 +1587,24 @@ function cmdValidateHealth(cwd, options, raw) {
1564
1587
  node_fs_1.default.copyFileSync(statePath, backupPath);
1565
1588
  repairActions.push({ action: 'backupState', success: true, path: backupPath });
1566
1589
  }
1567
- const milestone = getMilestoneInfo(cwd);
1590
+ const milestone = getDeclaredMilestoneInfo(cwd); // 261007-ixt W4: never a guessed milestone
1591
+ // D1 (261007-jkl): reference the PROJECT.md that answers for this cwd.
1568
1592
  const projectRef = node_path_1.default
1569
- .relative(cwd, node_path_1.default.join(planningDir(cwd), 'PROJECT.md'))
1593
+ .relative(cwd, resolveProjectFile(cwd).path)
1570
1594
  .split(node_path_1.default.sep)
1571
1595
  .join('/');
1572
1596
  let stateContent = `# Session State\n\n`;
1573
1597
  stateContent += `## Project Reference\n\n`;
1574
1598
  stateContent += `See: ${projectRef}\n\n`;
1575
1599
  stateContent += `## Position\n\n`;
1576
- stateContent += `**Milestone:** ${milestone.version} ${milestone.name}\n`;
1600
+ stateContent += `**Milestone:** ${milestone ? `${milestone.version} ${milestone.name}` : 'none declared in ROADMAP.md'}\n`;
1577
1601
  stateContent += `**Current phase:** (determining...)\n`;
1578
1602
  stateContent += `**Status:** Resuming\n\n`;
1579
1603
  stateContent += `## Session Log\n\n`;
1580
1604
  stateContent += `- ${new Date().toISOString().split('T')[0]}: STATE.md regenerated by ${slash('health')} --repair\n`;
1581
- writeStateMd(statePath, stateContent, cwd);
1582
- repairActions.push({ action: repair, success: true, path: 'STATE.md' });
1605
+ // 261007-ixt D-3: the repair names every frontmatter key it wrote.
1606
+ const stateReport = writeStateMd(statePath, stateContent, cwd);
1607
+ repairActions.push({ action: repair, success: true, path: 'STATE.md', frontmatter_updated: stateReport.frontmatter_updated });
1583
1608
  break;
1584
1609
  }
1585
1610
  case 'addNyquistKey': {
@@ -1647,20 +1672,17 @@ function cmdValidateHealth(cwd, options, raw) {
1647
1672
  const milestonesContent = node_fs_1.default.existsSync(milestonesPath)
1648
1673
  ? node_fs_1.default.readFileSync(milestonesPath, 'utf-8')
1649
1674
  : '';
1650
- if (!milestonesContent.trim()) {
1651
- (0, shell_command_projection_cjs_1.platformWriteSync)(milestonesPath, `# Milestones\n\n${entry}`);
1652
- }
1653
- else {
1654
- const headerMatch = milestonesContent.match(/^(#{1,3}\s+[^\n]*\n\n?)/);
1655
- if (headerMatch) {
1656
- const header = headerMatch[1];
1657
- const rest = milestonesContent.slice(header.length);
1658
- (0, shell_command_projection_cjs_1.platformWriteSync)(milestonesPath, header + entry + rest);
1659
- }
1660
- else {
1661
- (0, shell_command_projection_cjs_1.platformWriteSync)(milestonesPath, entry + milestonesContent);
1662
- }
1663
- }
1675
+ // 261007-rly D-6: the same record writer `milestone complete` uses —
1676
+ // one index row (the version and the snapshot read are all a backfill
1677
+ // knows; every other cell is the unknown marker) and the section after
1678
+ // the table, never between the H1 and the table.
1679
+ const snapshotLink = node_path_1.default.relative(node_path_1.default.dirname(milestonesPath), snapshotPath).split(node_path_1.default.sep).join('/');
1680
+ const record = milestoneMod.insertMilestoneRecord(milestonesContent, {
1681
+ version: ver,
1682
+ entry,
1683
+ cells: { version: ver, archive: `[${ver}-ROADMAP.md](${snapshotLink})` },
1684
+ });
1685
+ (0, shell_command_projection_cjs_1.platformWriteSync)(milestonesPath, record.content);
1664
1686
  backfilled++;
1665
1687
  }
1666
1688
  catch {
@@ -102,7 +102,7 @@ function cmdWorkstreamCreate(cwd, name, options, raw) {
102
102
  }
103
103
  const baseDir = planningRoot(cwd);
104
104
  if (!node_fs_1.default.existsSync(baseDir)) {
105
- error(`.planning/ directory not found — run ${(0, runtime_slash_cjs_1.formatGsdSlash)('new-project', (0, runtime_slash_cjs_1.resolveRuntime)(cwd))} first`);
105
+ error(`.planning/ directory not found — run ${(0, runtime_slash_cjs_1.formatPpSlash)('init', (0, runtime_slash_cjs_1.resolveRuntime)(cwd))} first`);
106
106
  }
107
107
  const wsRoot = node_path_1.default.join(baseDir, 'workstreams');
108
108
  const wsDir = node_path_1.default.join(wsRoot, slug);
@@ -409,9 +409,10 @@ function defaultFindSummaryFiles(worktreePath) {
409
409
  * For each *SUMMARY.md found under <worktreePath>/.planning/:
410
410
  * - compute relative path from worktree root → .planning/<id>-SUMMARY.md
411
411
  * - if the file is ALREADY COMMITTED on the worktree branch
412
- * (`git cat-file -e HEAD:<relPath>` returns exit 0), skip the copy entirely:
413
- * the merge will carry it naturally and copying it as an untracked file would
414
- * cause a "untracked working tree files would be overwritten by merge" collision.
412
+ * (`git ls-tree -z --full-tree --name-only HEAD -- <relPath>` prints the path),
413
+ * skip the copy entirely: the merge will carry it naturally and copying it as
414
+ * an untracked file would cause a "untracked working tree files would be
415
+ * overwritten by merge" collision. Empty ls-tree output means not committed.
415
416
  * On timeout or fatal exit (128) the rescue is also skipped (fail-closed).
416
417
  * (#706 — execute-phase committed-SUMMARY contract)
417
418
  * - destination = <repoRoot>/<relPath>
@@ -442,27 +443,37 @@ function rescueSummaryArtifacts(worktreePath, repoRoot, deps) {
442
443
  // output works on Windows too (git always emits forward slashes in porcelain output).
443
444
  const relPath = absPath.slice(worktreePath.length).replace(/^[/\\]/, '').replace(/\\/g, '/');
444
445
  // #706: skip rescue when the SUMMARY is already committed on the branch.
445
- // Use `git cat-file -e HEAD:<relPath>` (not `ls-files --error-unmatch`) so
446
- // the check is against the committed tree, not the index. ls-files also
447
- // matches staged-but-uncommitted files, which would skip rescue when the
448
- // file is staged but not yet committed — the merge wouldn't carry it, and
449
- // the executor's content could be lost. cat-file -e HEAD:<path> returns
450
- // exit 0 only when the object exists in the committed HEAD tree.
446
+ // Ask the committed HEAD tree directly with `git ls-tree -z --name-only
447
+ // HEAD -- <relPath>` (not `ls-files --error-unmatch`, which also matches
448
+ // staged-but-uncommitted files the merge would NOT carry). ls-tree exits 0
449
+ // in both answers: the path on stdout when it is in HEAD, empty stdout when
450
+ // it is not. That is the property the previous form lacked: `cat-file -e
451
+ // HEAD:<relPath>` resolves the object NAME first and dies (exit 128, "path
452
+ // '…' does not exist in 'HEAD'") for any path not in HEAD — on every git
453
+ // from 1.7 through 2.55 — while its exit 1 is reserved for a raw OID missing
454
+ // from the store. So the fail-closed arm below swallowed every uncommitted
455
+ // SUMMARY and cleanup-wave blocked with worktree_dirty on each /pp-quick
456
+ // worktree run (261002 vault-gate probe). `-z` keeps the comparison byte-
457
+ // exact: without it core.quotePath would C-quote a non-ASCII path and a
458
+ // committed file would read as absent, re-creating the #706 collision.
451
459
  //
452
460
  // Fail-closed on timeout/fatal git errors: if we cannot determine whether
453
461
  // the file is committed, do NOT rescue it (rescuing an actually-committed
454
462
  // file would re-create the untracked collision; the merge will surface the
455
463
  // issue). The cleanup will be blocked by merge_failed in the worst case,
456
464
  // which is the observable behaviour before this fix and is recoverable.
457
- const catFileResult = execGit(['-C', worktreePath, 'cat-file', '-e', `HEAD:${relPath}`], { cwd: repoRoot });
458
- if (catFileResult.exitCode !== 1) {
459
- // Rescue only when cat-file definitively reports the object is absent (exit 1).
460
- // exit 0 → object exists (committed on HEAD) — merge will carry it, skip.
465
+ const treeCheck = execGit(['-C', worktreePath, 'ls-tree', '-z', '--full-tree', '--name-only', 'HEAD', '--', relPath], { cwd: repoRoot });
466
+ if (!gitResultOk(treeCheck)) {
461
467
  // exit 128 → fatal git error (corrupt store, unborn HEAD, etc.) — uncertain,
462
468
  // fail-closed: do NOT rescue to avoid recreating the #706 collision.
463
469
  // timedOut / null / other → unreliable result — same fail-closed policy.
464
- // In all non-1 cases the merge will either succeed naturally (0) or surface
465
- // the problem safely (128/timeout), which is the recoverable pre-fix behaviour.
470
+ // The merge then surfaces the problem safely, which is the recoverable
471
+ // pre-fix behaviour.
472
+ continue;
473
+ }
474
+ const committedOnHead = (treeCheck.stdout || '').split('\0').some((line) => line === relPath);
475
+ if (committedOnHead) {
476
+ // Object exists in the committed HEAD tree — the merge will carry it, skip.
466
477
  continue;
467
478
  }
468
479
  const dest = node_path_1.default.join(repoRoot, relPath);
@@ -0,0 +1,89 @@
1
+ #!/usr/bin/env bash
2
+ # portable-timeout.sh — THE portable timeout wrapper for this repository (D-09-03).
3
+ #
4
+ # SOURCED, never executed. It defines three functions and nothing else, and it
5
+ # deliberately sets no `set -e` / `set -u`: it is sourced into shells that may
6
+ # or may not use them.
7
+ #
8
+ # Consumers:
9
+ # - scripts/base64-scan.sh (CI, executed)
10
+ # - the gate blocks in pp-core/workflows/ (runtime; wired by plan 09-02)
11
+ #
12
+ # Why it lives in pp-core/bin/ rather than scripts/:
13
+ # `scripts/` shell files are not installed — pp-file-manifest.json carries no
14
+ # `scripts/*.sh` entry, so an installed workflow can neither find nor source
15
+ # scripts/base64-scan.sh (which also ends in an unguarded `main "$@"`).
16
+ # `pp-core/` is copied wholesale at install time — bin/install.js's
17
+ # copyWithPathReplacement() falls through to fs.copyFileSync for any non-.md
18
+ # file, so a .sh arrives byte-identical — it is in package.json "files", and
19
+ # it already hosts the tracked shell launcher `pp_run`. `pp-core/bin/lib/` was
20
+ # deliberately NOT used: several .cjs files there are gitignored build output
21
+ # and the directory reads as compiler output.
22
+ #
23
+ # MEASURED exit-code matrix (macOS 26.5, bash 3.2.57, no timeout and no
24
+ # gtimeout on PATH, perl at /usr/bin/perl; re-measured 2026-09-19 and pinned by
25
+ # tests/gate-shell-owners.test.cjs):
26
+ #
27
+ # run_with_timeout 1 sleep 5 -> 142 (perl) / 124 (GNU)
28
+ # run_with_timeout 5 bash -c '<missing command>' -> 127 under every backend
29
+ # run_with_timeout 5 <missing binary> -> 2 under perl, 127 GNU
30
+ # run_with_timeout 5 true -> 0
31
+ # run_with_timeout 5 bash -c 'exit 7' -> 7
32
+ #
33
+ # CAVEAT, and the reason that third row is pinned by a test: under the perl
34
+ # backend a DIRECT exec of a missing binary exits 2 — perl's own
35
+ # `die "exec: $!"` — not 127. A caller that wants a portable "never ran"
36
+ # signal must wrap its command in `bash -c "$CMD"`. Every gate block does,
37
+ # except the two that exec a binary directly (code-review.md's Fallow binary
38
+ # and ship.md's ${REVIEW_CMD}); those see 2, not 127, when the binary is absent.
39
+ #
40
+ # Backend selection is injectable for tests by assigning _TIMEOUT_CMD AFTER
41
+ # sourcing (`. portable-timeout.sh; _TIMEOUT_CMD=perl_alarm`) — _init_timeout_cmd
42
+ # returns early when it is already set, so no edit to the moved block was needed
43
+ # to make the backend selectable.
44
+ #
45
+ # Everything below this line is moved BYTE-VERBATIM from
46
+ # scripts/base64-scan.sh:42-83, which now sources this file instead.
47
+
48
+ # -- Portable timeout wrapper (#116) ------------------------------------------
49
+ # macOS does not ship GNU coreutils timeout. We probe for it (or gtimeout
50
+ # from homebrew coreutils), falling back to perl alarm(N)+exec.
51
+ # Exit codes: GNU timeout uses 124 on timeout; perl SIGALRM produces 142.
52
+ # Both are treated as timeout exits by is_timeout_exit() below.
53
+ # Usage: run_with_timeout <seconds> <command> [args...]
54
+ _TIMEOUT_CMD=""
55
+ # shellcheck disable=SC2329 # intentionally defined for use by callers; not called in main loop
56
+ _init_timeout_cmd() {
57
+ if [[ -n "$_TIMEOUT_CMD" ]]; then return; fi
58
+ if command -v timeout >/dev/null 2>&1; then
59
+ _TIMEOUT_CMD="timeout"
60
+ elif command -v gtimeout >/dev/null 2>&1; then
61
+ _TIMEOUT_CMD="gtimeout"
62
+ else
63
+ _TIMEOUT_CMD="perl_alarm"
64
+ fi
65
+ }
66
+
67
+ # shellcheck disable=SC2329 # intentionally defined for use by callers; not called in main loop
68
+ run_with_timeout() {
69
+ local secs="$1"; shift
70
+ _init_timeout_cmd
71
+ case "$_TIMEOUT_CMD" in
72
+ timeout|gtimeout)
73
+ "$_TIMEOUT_CMD" "$secs" "$@"
74
+ ;;
75
+ perl_alarm)
76
+ # perl sets SIGALRM after N seconds, then exec()s the command.
77
+ # Exit 142 (SIGALRM) when timed out.
78
+ perl -e '
79
+ my $secs = shift @ARGV;
80
+ alarm($secs);
81
+ exec(@ARGV) or die "exec: $!\n";
82
+ ' -- "$secs" "$@"
83
+ ;;
84
+ esac
85
+ }
86
+
87
+ # is_timeout_exit: returns 0 (true) if rc indicates a timeout kill.
88
+ # shellcheck disable=SC2329 # intentionally defined for use by callers; not called in main loop
89
+ is_timeout_exit() { [[ "$1" -eq 124 || "$1" -eq 142 ]]; }
@@ -57,6 +57,7 @@
57
57
  * milestone complete <version> Archive milestone, create MILESTONES.md
58
58
  * [--name <name>]
59
59
  * [--archive-phases] Move phase dirs to milestones/vX.Y-phases/
60
+ * [--stopped-at <text>] New Session "Stopped at:" (else stopped_at is kept)
60
61
  *
61
62
  * User Story Validation:
62
63
  * user-story validate --story "..." Validate "As a / I want to / so that" format
@@ -525,7 +526,7 @@ async function main() {
525
526
  const TOP_LEVEL_USAGE = 'Usage: pp-tools <command> [args] [--raw] [--pick <field>] [--cwd <path>] [--ws <name>] [--json-errors]\n' +
526
527
  'Commands: agent, agent-skills, audit-open, audit-uat, check, check-commit, commit, commit-to-subrepo, ' +
527
528
  'compose-bindings, config-ensure-section, config-get, config-new-project, config-path, config-set, migrate-config, ' +
528
- 'current-timestamp, detect-custom-files, docs-init, drift-guard, effort, extract-messages, find-phase, ' +
529
+ 'current-timestamp, detect-custom-files, discovery, docs-init, drift-guard, effort, extract-messages, find-phase, ' +
529
530
  'from-gsd2, frontmatter, gap-analysis, generate-claude-md, generate-claude-profile, ' +
530
531
  'generate-client-context, generate-dev-preferences, generate-slug, graphify, history-digest, init, init-contract, intel, ' +
531
532
  'capability, classify-confidence, git, learnings, list-todos, loop, milestone, package-legitimacy, phase, phase-plan-index, phases, platform-bindings, profile-questionnaire, ' +
@@ -713,8 +714,8 @@ function extractField(obj, fieldPath) {
713
714
  * (Phase 34-09's own design hazard) so the interactive flow stays testable
714
715
  * with a scripted fake and no pty.
715
716
  */
716
- function createReadlinePrompter() {
717
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
717
+ function createReadlinePrompter({ input = process.stdin, output = process.stdout } = {}) {
718
+ const rl = readline.createInterface({ input, output });
718
719
  return {
719
720
  ask(question) {
720
721
  let rendered = question.prompt;
@@ -734,15 +735,20 @@ function createReadlinePrompter() {
734
735
  if (question.optionalAnswer) {
735
736
  // The unconditional "Enter skips" hint would be a lie once a
736
737
  // default is carried — Enter now ACCEPTS the default above, so the
737
- // skip hint must point at "none" instead.
738
- rendered += question.preselected ? '\n (type "none" to skip)' : '\n (press Enter to skip)';
738
+ // skip hint must point at "none" instead. The question text may
739
+ // already carry that hint (both org profile prompts do), so it is
740
+ // added only for an optional question whose text lacks it (I-1).
741
+ const skipHint = question.preselected ? 'type "none" to skip' : 'press Enter to skip';
742
+ if (!String(question.prompt).toLowerCase().includes(skipHint.toLowerCase())) {
743
+ rendered += `\n (${skipHint})`;
744
+ }
739
745
  }
740
746
  return new Promise((resolve) => {
741
747
  rl.question(`${rendered}\n> `, (answer) => resolve(answer));
742
748
  });
743
749
  },
744
750
  print(text) {
745
- process.stdout.write(`${text}\n`);
751
+ output.write(`${text}\n`);
746
752
  },
747
753
  close() {
748
754
  rl.close();
@@ -819,6 +825,7 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
819
825
  case 'init-contract': {
820
826
  // init-contract [--questions] [--verify] [--session-next]
821
827
  // [--session-answer <stepId>=<value>] [--session-reset] [--offline]
828
+ // [--arm-vault]
822
829
  // --questions (also the no-flag default, D-05): read-only, always
823
830
  // exits 0, prints the canonical /pp-init question set as data.
824
831
  // --verify: runs the end-state invariant check (Phase 34-07 gap
@@ -852,7 +859,7 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
852
859
  );
853
860
  if (unrecognizedIcFlag !== undefined) {
854
861
  error(
855
- `Unrecognized flag "${unrecognizedIcFlag}" for init-contract. Supported: --questions, --verify, --session-next, --session-answer, --session-reset, --offline.`,
862
+ `Unrecognized flag "${unrecognizedIcFlag}" for init-contract. Supported: --questions, --verify, --session-next, --session-answer, --session-reset, --offline, --arm-vault.`,
856
863
  ERROR_REASON.SDK_FAIL_FAST,
857
864
  );
858
865
  break;
@@ -870,6 +877,9 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
870
877
  icOptions.sessionReset = true;
871
878
  } else if (a === '--offline') {
872
879
  icOptions.offline = true;
880
+ } else if (a === '--arm-vault') {
881
+ // Pack 6 D-09: set vault.enabled true only where absent.
882
+ icOptions.armVault = true;
873
883
  }
874
884
  }
875
885
  if (sessionAnswerRaw !== undefined) {
@@ -1410,9 +1420,10 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
1410
1420
  const subcommand = args[1];
1411
1421
  if (subcommand === 'complete') {
1412
1422
  const milestoneName = parseMultiwordArg(args, 'name');
1423
+ const stoppedAt = parseMultiwordArg(args, 'stopped-at');
1413
1424
  const archivePhases = args.includes('--archive-phases');
1414
1425
  const force = args.includes('--force');
1415
- milestone.cmdMilestoneComplete(cwd, args[2], { name: milestoneName, archivePhases, force }, raw);
1426
+ milestone.cmdMilestoneComplete(cwd, args[2], { name: milestoneName, archivePhases, force, stoppedAt }, raw);
1416
1427
  } else {
1417
1428
  error('Unknown milestone subcommand. Available: complete', ERROR_REASON.SDK_UNKNOWN_COMMAND);
1418
1429
  }
@@ -1573,9 +1584,24 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
1573
1584
  }
1574
1585
  loopActiveCap = value;
1575
1586
  }
1587
+ // --phase <N> / --tier <light|standard|heavy>: resolve the phase's
1588
+ // ceremony tier and read the gate booleans from it (pack 9).
1589
+ const loopValue = (flag) => {
1590
+ const eq = args.find(arg => arg.startsWith(`--${flag}=`));
1591
+ if (eq) return eq.slice(flag.length + 3).trim();
1592
+ const idx = args.indexOf(`--${flag}`);
1593
+ if (idx === -1) return undefined;
1594
+ const value = args[idx + 1];
1595
+ if (!value || value.startsWith('--')) {
1596
+ error(`Missing value for --${flag}`, ERROR_REASON ? ERROR_REASON.USAGE : undefined);
1597
+ }
1598
+ return value;
1599
+ };
1576
1600
  loopResolver.cmdLoopRenderHooks(cwd, args[2], raw, {
1577
1601
  configDir: loopConfigDir ? path.resolve(loopConfigDir) : undefined,
1578
1602
  activeCap: loopActiveCap,
1603
+ phase: loopValue('phase'),
1604
+ tier: loopValue('tier'),
1579
1605
  });
1580
1606
  } else {
1581
1607
  error(
@@ -2309,6 +2335,120 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
2309
2335
  break;
2310
2336
  }
2311
2337
 
2338
+ // ─── Discovery ─────────────────────────────────────────────────────────
2339
+ //
2340
+ // discovery rollup <path> [--check]
2341
+ //
2342
+ // Read-only rollup of a discovery _requirements.md artifact (discovery-v2
2343
+ // Phase 1, REQM-04). Plain mode always exits 0 with JSON + rendered totals
2344
+ // table; --check exits non-zero on drift, marker problems, or parse
2345
+ // failure (D-07/D-08). The dotted form `query discovery.rollup` reaches
2346
+ // here via the dispatcher dot-split above.
2347
+ //
2348
+ // discovery input-check
2349
+ //
2350
+ // Input-package enforcement (discovery-v2 Phase 2, INPUT-02/INPUT-03).
2351
+ // No positional path — fixed convention paths resolve against cwd. Exits
2352
+ // non-zero (discovery_input_check_failed) when a session is Completed
2353
+ // with [TO CONFIRM] markers remaining, a completeness_score counts
2354
+ // unconfirmed topics (D-18/D-19), or git tracks a file under
2355
+ // .planning/discovery-input/confidential/ (D-22). Non-git projects,
2356
+ // missing input packages, and marker-free engagements exit 0. The dotted
2357
+ // form `query discovery.input-check` reaches here the same way.
2358
+ //
2359
+ // discovery research-check
2360
+ //
2361
+ // Research-track enforcement (discovery-v2 Phase 3, RTRK-01/02/03 +
2362
+ // TRACE-05). No positional path — fixed convention paths resolve against
2363
+ // cwd. Exits non-zero (discovery_research_check_failed) on any STRUCTURAL
2364
+ // fault in .planning/discovery/_research.md, _risks.md or _sparring.md, or
2365
+ // in a ⚠️ CONDITIONAL marker that cites them (D-42). Never-blocks contract
2366
+ // (D-43): unfinished research is NOT a fault — an Open Gates-MVP item exits
2367
+ // 0 and is reported under open_by_priority, so a still-running research
2368
+ // item can never block Session 13 sign-off. A project with no research
2369
+ // artifacts, a register absent while a sibling is present, and an absent
2370
+ // _requirements.md all exit 0. The dotted form
2371
+ // `query discovery.research-check` reaches here the same way.
2372
+ //
2373
+ // discovery screen-check
2374
+ //
2375
+ // Screen-inventory and feedback-loop enforcement (discovery-v2 Phase 4,
2376
+ // TRACE-02/TRACE-03/TRACE-04 + GUARD-01). No positional path — fixed
2377
+ // convention paths resolve against cwd. Exits non-zero
2378
+ // (discovery_screen_check_failed) on any STRUCTURAL fault in
2379
+ // .planning/discovery/_screens.md or _feedback.md, in the D-51 computed
2380
+ // coverage block, or in the D-54 build-tree scan that asserts discovery
2381
+ // instrumentation never shipped. Never-blocks contract (D-43): an
2382
+ // unprocessed feedback row is NOT a fault — a round whose every Processing
2383
+ // cell is Open exits 0 and is reported under open_feedback, and the
2384
+ // outstanding-work payload is emitted on the failure path too. Graceful
2385
+ // arms: a project with no discovery package, an absent _screens.md, an
2386
+ // absent _requirements.md, and an absent _risks.md/_research.md all exit 0,
2387
+ // and the build-tree scan does not run at all unless a discovery artifact
2388
+ // was found. The dotted form `query discovery.screen-check` reaches here
2389
+ // the same way.
2390
+ //
2391
+ // discovery element-sweep [--check]
2392
+ //
2393
+ // Lists every functional element of the prototype under
2394
+ // .planning/discovery/prototype/ so each is decided once in _sweep.md;
2395
+ // --check fails (discovery_element_sweep_failed) while one is undecided.
2396
+
2397
+ case 'discovery': {
2398
+ const subcommand = args[1];
2399
+ if (subcommand === 'rollup') {
2400
+ const rollup = require('./lib/discovery-rollup.cjs');
2401
+ try {
2402
+ rollup.cmdDiscoveryRollup(cwd, args, raw);
2403
+ } catch (rollupErr) {
2404
+ error(`discovery rollup: ${rollupErr && rollupErr.message ? rollupErr.message : String(rollupErr)}`, ERROR_REASON.UNKNOWN);
2405
+ }
2406
+ } else if (subcommand === 'input-check') {
2407
+ const inputCheck = require('./lib/discovery-input-check.cjs');
2408
+ try {
2409
+ inputCheck.cmdDiscoveryInputCheck(cwd, args, raw);
2410
+ } catch (inputCheckErr) {
2411
+ error(`discovery input-check: ${inputCheckErr && inputCheckErr.message ? inputCheckErr.message : String(inputCheckErr)}`, ERROR_REASON.UNKNOWN);
2412
+ }
2413
+ } else if (subcommand === 'research-check') {
2414
+ const researchTrack = require('./lib/discovery-research-track.cjs');
2415
+ try {
2416
+ researchTrack.cmdDiscoveryResearchCheck(cwd, args, raw);
2417
+ } catch (researchCheckErr) {
2418
+ error(`discovery research-check: ${researchCheckErr && researchCheckErr.message ? researchCheckErr.message : String(researchCheckErr)}`, ERROR_REASON.UNKNOWN);
2419
+ }
2420
+ } else if (subcommand === 'screen-check') {
2421
+ const screens = require('./lib/discovery-screens.cjs');
2422
+ try {
2423
+ screens.cmdDiscoveryScreenCheck(cwd, args, raw);
2424
+ } catch (screenCheckErr) {
2425
+ error(`discovery screen-check: ${screenCheckErr && screenCheckErr.message ? screenCheckErr.message : String(screenCheckErr)}`, ERROR_REASON.UNKNOWN);
2426
+ }
2427
+ } else if (subcommand === 'element-sweep') {
2428
+ const sweep = require('./lib/discovery-sweep.cjs');
2429
+ try {
2430
+ sweep.cmdDiscoveryElementSweep(cwd, args, raw);
2431
+ } catch (sweepErr) {
2432
+ error(`discovery element-sweep: ${sweepErr && sweepErr.message ? sweepErr.message : String(sweepErr)}`, ERROR_REASON.UNKNOWN);
2433
+ }
2434
+ } else if (subcommand === 'rebaseline-check') {
2435
+ const rebaseline = require('./lib/discovery-rebaseline.cjs');
2436
+ try {
2437
+ rebaseline.cmdDiscoveryRebaselineCheck(cwd, args, raw);
2438
+ } catch (rebaselineCheckErr) {
2439
+ error(`discovery rebaseline-check: ${rebaselineCheckErr && rebaselineCheckErr.message ? rebaselineCheckErr.message : String(rebaselineCheckErr)}`, ERROR_REASON.UNKNOWN);
2440
+ }
2441
+ } else {
2442
+ // WIRING, NOT HELP TEXT: tests/discovery-artifacts.test.cjs derives each
2443
+ // subcommand spelling by regexing `<namespace>_<subcommand>_failed` out of
2444
+ // the engine's exported exit identity, splits this string on commas and
2445
+ // asserts membership. A spelling missing here is a command the engine
2446
+ // claims to be and the router does not dispatch.
2447
+ error('Unknown discovery subcommand. Available: rollup, input-check, research-check, screen-check, element-sweep, rebaseline-check', ERROR_REASON.SDK_UNKNOWN_COMMAND);
2448
+ }
2449
+ break;
2450
+ }
2451
+
2312
2452
  case 'effort': {
2313
2453
  const subcommand = args[1];
2314
2454
  if (subcommand === 'sync') {
@@ -2529,4 +2669,4 @@ if (require.main === module) {
2529
2669
  // ─── Exports (for tests) ──────────────────────────────────────────────────────
2530
2670
  // ADR-959: export dispatchCapabilityCommand so tests can exercise it with
2531
2671
  // synthetic registry + requireModule injections.
2532
- module.exports = { dispatchCapabilityCommand };
2672
+ module.exports = { dispatchCapabilityCommand, createReadlinePrompter };
@@ -93,5 +93,12 @@
93
93
  "plan_review": {
94
94
  "source_grounding": true,
95
95
  "source_grounding_authority": "grep"
96
+ },
97
+ "size_budget": {
98
+ "per_file_bytes": 1024,
99
+ "per_commit_bytes": 4096
100
+ },
101
+ "phase": {
102
+ "tier": "standard"
96
103
  }
97
104
  }
@@ -31,6 +31,9 @@
31
31
  "workflow.plan_bounce",
32
32
  "workflow.plan_bounce_script",
33
33
  "workflow.plan_bounce_passes",
34
+ "size_budget.per_file_bytes",
35
+ "size_budget.per_commit_bytes",
36
+ "phase.tier",
34
37
  "workflow.plan_chunked",
35
38
  "workflow.plan_review_convergence",
36
39
  "code_quality.fallow.enabled",