@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
@@ -50,6 +50,7 @@ exports.verifyInitInvariants = verifyInitInvariants;
50
50
  exports.initSessionNext = initSessionNext;
51
51
  exports.initSessionAnswer = initSessionAnswer;
52
52
  exports.initSessionReset = initSessionReset;
53
+ exports.armVault = armVault;
53
54
  exports.cmdInitContract = cmdInitContract;
54
55
  const node_fs_1 = __importDefault(require("node:fs"));
55
56
  const node_path_1 = __importDefault(require("node:path"));
@@ -102,6 +103,7 @@ exports.INIT_CONTRACT_FLAGS = Object.freeze([
102
103
  '--session-answer',
103
104
  '--session-reset',
104
105
  '--offline',
106
+ '--arm-vault',
105
107
  ]);
106
108
  /** `.planning/.init-session.json` — the CLI-owned session state file
107
109
  * (Artifacts table, 34-08). Never hand-edited; malformed content is
@@ -165,6 +167,14 @@ function getDotted(obj, dottedPath) {
165
167
  * `optionalAnswer: true` — that flag describes the ANSWER (pressing Enter
166
168
  * is a complete, valid response), never whether the question is asked.
167
169
  *
170
+ * Question 6 (`org_profile`)'s `preselected` is resolved via
171
+ * `resolveDetectedOrgProfile` — set only when exactly one org-profile-shaped
172
+ * package is installed (INIT-PATCH-C); the prompt text is built from that
173
+ * detected value rather than kept as two hard-coded strings that can drift.
174
+ * `opts._globalRoot` is forwarded to it unchanged, the same test-seam
175
+ * convention `verifyInitInvariants`'s own `_globalRoot` already follows —
176
+ * production callers omit it.
177
+ *
168
178
  * Question 7 (`provider`) carries the fixed, complete option list: every
169
179
  * value in `PROVIDERS` (imported, never retyped — this is what keeps
170
180
  * `azure` from being silently dropped again, T-34-50) plus the three
@@ -173,7 +183,7 @@ function getDotted(obj, dottedPath) {
173
183
  * probe reads (pure config + org-profile consultation, no network) — so
174
184
  * calling it directly here is equivalent, not a second implementation.
175
185
  */
176
- function buildInitQuestions(cwd) {
186
+ function buildInitQuestions(cwd, opts = {}) {
177
187
  let preselected = git_provider_cjs_1.DEFAULT_PROVIDER;
178
188
  try {
179
189
  preselected = (0, git_provider_cjs_1.resolveProvider)(cwd).provider;
@@ -182,6 +192,29 @@ function buildInitQuestions(cwd) {
182
192
  preselected = git_provider_cjs_1.DEFAULT_PROVIDER;
183
193
  }
184
194
  const providerOptions = Object.freeze([...git_provider_cjs_1.PROVIDERS, ...IDENTITY_ONLY_PROVIDER_OPTIONS]);
195
+ const detectedOrgProfile = resolveDetectedOrgProfile(cwd, opts);
196
+ // The template example is written as an angle-bracket placeholder, not a
197
+ // literal package name. `tests/org-profile.test.cjs`'s client-confidentiality
198
+ // guard scans this file for any concrete scope-slash-org-prefix package
199
+ // identifier, so even a generic literal placeholder trips it — the template
200
+ // keeps the naming convention visible while staying clean, and the guard
201
+ // stays maximally strict, no allowlist entry was added for it. The detected
202
+ // value itself is never a literal in this file — it is a runtime string
203
+ // read from an installed package, interpolated into the prompt below.
204
+ const orgProfileQuestion = isUsableString(detectedOrgProfile)
205
+ ? {
206
+ id: 'org_profile',
207
+ prompt: `Org profile package (optional — a single installed org profile was detected: "${detectedOrgProfile}". ` +
208
+ 'Press Enter to use it, type "none" to skip it, or type a different package name.)',
209
+ optionalAnswer: true,
210
+ preselected: detectedOrgProfile,
211
+ preselectedLabel: `${detectedOrgProfile} (detected installed profile)`,
212
+ }
213
+ : {
214
+ id: 'org_profile',
215
+ prompt: 'Org profile package (optional — press Enter to skip, e.g. "@<your-scope>/org-<name>")',
216
+ optionalAnswer: true,
217
+ };
185
218
  return [
186
219
  { id: 'project_name', prompt: 'Project name (human-readable, e.g. "Acme Dashboard")', optionalAnswer: false },
187
220
  { id: 'project_slug', prompt: 'Project slug (kebab-case, e.g. "acme-dashboard")', optionalAnswer: false },
@@ -196,17 +229,7 @@ function buildInitQuestions(cwd) {
196
229
  optionalAnswer: false,
197
230
  },
198
231
  { id: 'description', prompt: 'One-line description (what this project does)', optionalAnswer: false },
199
- {
200
- id: 'org_profile',
201
- // The example is written as an angle-bracket TEMPLATE, not a literal package
202
- // name. `tests/org-profile.test.cjs`'s client-confidentiality guard scans this
203
- // file for any concrete scope-slash-org-prefix package identifier, so even a
204
- // generic literal placeholder trips it. The template keeps the naming
205
- // convention visible while staying clean, and the guard stays maximally
206
- // strict — no allowlist entry was added for it.
207
- prompt: 'Org profile package (optional — press Enter to skip, e.g. "@<your-scope>/org-<name>")',
208
- optionalAnswer: true,
209
- },
232
+ orgProfileQuestion,
210
233
  {
211
234
  id: 'provider',
212
235
  prompt: 'Provider (gitlab / azure / github / other / none)',
@@ -249,30 +272,47 @@ function resolveGlobalNodeModulesRoot() {
249
272
  return null;
250
273
  }
251
274
  }
275
+ /** Conservative npm-package-name shape check (bare `name` or scoped
276
+ * `@scope/name`; lowercase, digits, `.`/`_`/`-`, never leading `.`/`_` in
277
+ * either segment). A directory name that does not match is never offered as
278
+ * a detected default (T-QP-02): it cannot be a resolvable package pointer,
279
+ * so rejecting it here keeps a hostile directory name (control characters,
280
+ * terminal escapes) from ever reaching a rendered prompt. */
281
+ const NPM_PACKAGE_NAME_SHAPE_RE = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/;
252
282
  /**
253
- * True when `nodeModulesDir` contains at least one package directory (one
254
- * level, or one level inside an `@scope/` directory) carrying a file named
255
- * `ORG_PROFILE_DATA_FILE` at its root — the same fixed filename
256
- * `resolveOrgProfileDocument` reads, imported rather than retyped. Bounded
257
- * at `NODE_MODULES_SCAN_CAP` total directory entries visited (T-34-54: this
258
- * check must never hang on a pathological `node_modules` tree).
283
+ * Walk `nodeModulesDir` exactly like the boolean check below (one level, or
284
+ * one level inside an `@scope/` directory), collecting the name of every
285
+ * package directory carrying `ORG_PROFILE_DATA_FILE` at its root instead of
286
+ * stopping at the first hit. `opts.limit`, when set, stops the walk once
287
+ * that many names have been collected — passing `1` reproduces the boolean
288
+ * check's early-exit behaviour exactly. Bounded at `NODE_MODULES_SCAN_CAP`
289
+ * total directory entries visited (T-34-54: this check must never hang on a
290
+ * pathological `node_modules` tree).
259
291
  *
260
292
  * This is a heuristic, not a resolution: it answers "a package shaped like
261
293
  * an org profile is installed here", not "this exact profile would
262
- * resolve" — `verifyInitInvariants` never claims more than that.
294
+ * resolve" — callers never claim more than that.
263
295
  */
264
- function containsOrgProfileShapedPackage(nodeModulesDir) {
296
+ function collectOrgProfileShapedPackages(nodeModulesDir, opts = {}) {
297
+ const names = [];
265
298
  let visited = 0;
299
+ const limitReached = () => opts.limit !== undefined && names.length >= opts.limit;
300
+ const addIfShaped = (name) => {
301
+ if (NPM_PACKAGE_NAME_SHAPE_RE.test(name))
302
+ names.push(name);
303
+ };
266
304
  let entries;
267
305
  try {
268
306
  entries = node_fs_1.default.readdirSync(nodeModulesDir, { withFileTypes: true });
269
307
  }
270
308
  catch {
271
- return false;
309
+ return { names, capped: false };
272
310
  }
273
311
  for (const entry of entries) {
312
+ if (limitReached())
313
+ return { names, capped: false };
274
314
  if (visited >= NODE_MODULES_SCAN_CAP)
275
- return false;
315
+ return { names, capped: true };
276
316
  visited += 1;
277
317
  if (!entry.isDirectory())
278
318
  continue;
@@ -286,14 +326,17 @@ function containsOrgProfileShapedPackage(nodeModulesDir) {
286
326
  continue;
287
327
  }
288
328
  for (const scopedEntry of scoped) {
329
+ if (limitReached())
330
+ return { names, capped: false };
289
331
  if (visited >= NODE_MODULES_SCAN_CAP)
290
- return false;
332
+ return { names, capped: true };
291
333
  visited += 1;
292
334
  if (!scopedEntry.isDirectory())
293
335
  continue;
294
336
  try {
295
- if (node_fs_1.default.existsSync(node_path_1.default.join(scopeDir, scopedEntry.name, org_profile_cjs_1.ORG_PROFILE_DATA_FILE)))
296
- return true;
337
+ if (node_fs_1.default.existsSync(node_path_1.default.join(scopeDir, scopedEntry.name, org_profile_cjs_1.ORG_PROFILE_DATA_FILE))) {
338
+ addIfShaped(`${entry.name}/${scopedEntry.name}`);
339
+ }
297
340
  }
298
341
  catch {
299
342
  continue;
@@ -302,15 +345,27 @@ function containsOrgProfileShapedPackage(nodeModulesDir) {
302
345
  }
303
346
  else {
304
347
  try {
305
- if (node_fs_1.default.existsSync(node_path_1.default.join(nodeModulesDir, entry.name, org_profile_cjs_1.ORG_PROFILE_DATA_FILE)))
306
- return true;
348
+ if (node_fs_1.default.existsSync(node_path_1.default.join(nodeModulesDir, entry.name, org_profile_cjs_1.ORG_PROFILE_DATA_FILE))) {
349
+ addIfShaped(entry.name);
350
+ }
307
351
  }
308
352
  catch {
309
353
  continue;
310
354
  }
311
355
  }
312
356
  }
313
- return false;
357
+ return { names, capped: false };
358
+ }
359
+ /**
360
+ * True when `nodeModulesDir` contains at least one package directory (one
361
+ * level, or one level inside an `@scope/` directory) carrying a file named
362
+ * `ORG_PROFILE_DATA_FILE` at its root — the same fixed filename
363
+ * `resolveOrgProfileDocument` reads, imported rather than retyped. Reduced to
364
+ * the collecting walk above with `limit: 1`, preserving its early-exit
365
+ * behaviour exactly.
366
+ */
367
+ function containsOrgProfileShapedPackage(nodeModulesDir) {
368
+ return collectOrgProfileShapedPackages(nodeModulesDir, { limit: 1 }).names.length > 0;
314
369
  }
315
370
  function orgProfilePackageResolvable(cwd, opts) {
316
371
  try {
@@ -328,6 +383,44 @@ function orgProfilePackageResolvable(cwd, opts) {
328
383
  return false;
329
384
  }
330
385
  }
386
+ /**
387
+ * Detect a single, unambiguous installed org-profile-shaped package
388
+ * (INIT-PATCH-C): collects from the project's `node_modules` and — using the
389
+ * same `opts._globalRoot` override convention `orgProfilePackageResolvable`
390
+ * already follows — the global root, de-duplicates by package name across
391
+ * both roots, and returns that single name only when exactly one distinct
392
+ * name was found and NEITHER scan reported `capped`. A capped scan is
393
+ * inconclusive and must never yield a default (T-QP-03). Two or more
394
+ * distinct names, or none, returns null. Never throws.
395
+ */
396
+ function resolveDetectedOrgProfile(cwd, opts) {
397
+ try {
398
+ const names = new Set();
399
+ let anyCapped = false;
400
+ const projectNodeModules = node_path_1.default.join(cwd, 'node_modules');
401
+ if (node_fs_1.default.existsSync(projectNodeModules)) {
402
+ const projectResult = collectOrgProfileShapedPackages(projectNodeModules);
403
+ if (projectResult.capped)
404
+ anyCapped = true;
405
+ for (const name of projectResult.names)
406
+ names.add(name);
407
+ }
408
+ const globalRoot = opts._globalRoot !== undefined ? opts._globalRoot : resolveGlobalNodeModulesRoot();
409
+ if (globalRoot) {
410
+ const globalResult = collectOrgProfileShapedPackages(globalRoot);
411
+ if (globalResult.capped)
412
+ anyCapped = true;
413
+ for (const name of globalResult.names)
414
+ names.add(name);
415
+ }
416
+ if (anyCapped)
417
+ return null;
418
+ return names.size === 1 ? [...names][0] : null;
419
+ }
420
+ catch {
421
+ return null;
422
+ }
423
+ }
331
424
  /** Read `.planning/config.json` at `cwd`, distinguishing "file absent" from
332
425
  * "file present but empty/malformed" — `INIT_IDENTITY_MISSING` only fires
333
426
  * on the latter (see `verifyInitInvariants`'s own doc comment for why an
@@ -654,7 +747,7 @@ function computeCurrentStep(cwd, session, opts) {
654
747
  // the convention: any failure anywhere in this body degrades to a
655
748
  // `write_failed` verdict instead of propagating.
656
749
  try {
657
- const questions = buildInitQuestions(cwd);
750
+ const questions = buildInitQuestions(cwd, { _globalRoot: opts._globalRoot });
658
751
  for (const q of questions) {
659
752
  if (!Object.prototype.hasOwnProperty.call(session.answers, q.id)) {
660
753
  return { kind: 'question', question: q };
@@ -678,14 +771,18 @@ function computeCurrentStep(cwd, session, opts) {
678
771
  // end states and must record (and later report) distinct decisions,
679
772
  // never sharing the inert-path (INIT-02) message below.
680
773
  if (dryRun.composable === true && dryRun.action !== 'unchanged') {
774
+ // Pack 6 D-07: `info` is display-only (the CLI and the TTY renderer
775
+ // print it); approval re-composes with `write: true`, so the written
776
+ // file keeps the RAW coordinates.
777
+ const shown = (0, compose_bindings_cjs_1.redactComposeResultForEmission)(dryRun);
681
778
  return {
682
779
  kind: 'compose_confirm',
683
780
  info: {
684
- provenance: dryRun.provenance,
685
- drift: dryRun.drift,
781
+ provenance: shown.provenance,
782
+ drift: shown.drift,
686
783
  action: dryRun.action,
687
- composed: dryRun.composed,
688
- warnings: dryRun.warnings,
784
+ composed: shown.composed,
785
+ warnings: shown.warnings,
689
786
  },
690
787
  };
691
788
  }
@@ -927,6 +1024,81 @@ function initSessionReset(cwd) {
927
1024
  }
928
1025
  return { ok: true };
929
1026
  }
1027
+ /**
1028
+ * `init-contract --arm-vault`: turn the client vault on for a project whose
1029
+ * client vault is bound. Writes `vault.enabled: true` ONLY when the key is
1030
+ * absent — an explicit `false` (or any other explicit value) is the
1031
+ * operator's decision and is kept. With no client vault bound (no or
1032
+ * malformed `.platform-bindings.json`, or `client.vaultRepo` /
1033
+ * `client.vaultRef` missing — the same "bound" the gate's verdict uses) it
1034
+ * writes nothing and returns `ok: false` naming /pp-init. A present but
1035
+ * unparseable `.planning/config.json` is never overwritten. Never throws.
1036
+ * The reported coordinate is display-safe (D-07).
1037
+ */
1038
+ function armVault(cwd) {
1039
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
1040
+ const vaultMod = require('./vault.cjs');
1041
+ let binding = { bound: false, repo: null, ref: null };
1042
+ try {
1043
+ binding = vaultMod.readClientBinding(cwd);
1044
+ }
1045
+ catch {
1046
+ /* unbound */
1047
+ }
1048
+ const client = { repo: vaultMod.redactCloneTarget(binding.repo), ref: binding.ref };
1049
+ const configPath = node_path_1.default.join(cwd, '.planning', 'config.json');
1050
+ let cfg = {};
1051
+ let cfgExists = false;
1052
+ try {
1053
+ cfgExists = node_fs_1.default.existsSync(configPath);
1054
+ if (cfgExists) {
1055
+ const parsed = JSON.parse(node_fs_1.default.readFileSync(configPath, 'utf-8'));
1056
+ if (!isObject(parsed))
1057
+ throw new Error('not an object');
1058
+ cfg = parsed;
1059
+ }
1060
+ }
1061
+ catch {
1062
+ return {
1063
+ ok: false,
1064
+ armed: false,
1065
+ enabled: undefined,
1066
+ client,
1067
+ message: '.planning/config.json is not valid JSON — fix it (or re-run /pp-init), then arm the vault again. Nothing was written.',
1068
+ };
1069
+ }
1070
+ const vaultSection = isObject(cfg.vault) ? cfg.vault : null;
1071
+ const nested = vaultSection && Object.prototype.hasOwnProperty.call(vaultSection, 'enabled');
1072
+ const flat = Object.prototype.hasOwnProperty.call(cfg, 'vault.enabled');
1073
+ const current = nested && vaultSection ? vaultSection.enabled : flat ? cfg['vault.enabled'] : undefined;
1074
+ if (!binding.bound) {
1075
+ return {
1076
+ ok: false,
1077
+ armed: false,
1078
+ enabled: current,
1079
+ client,
1080
+ message: 'No client vault is bound — .platform-bindings.json needs client.vaultRepo and client.vaultRef. Run /pp-init to bind it; vault.enabled was not changed.',
1081
+ };
1082
+ }
1083
+ if (nested || flat) {
1084
+ return {
1085
+ ok: true,
1086
+ armed: false,
1087
+ enabled: current,
1088
+ client,
1089
+ message: current === true
1090
+ ? 'vault.enabled is already true — nothing to do.'
1091
+ : `vault.enabled is explicitly set to ${JSON.stringify(current)} — kept as the operator's choice.`,
1092
+ };
1093
+ }
1094
+ try {
1095
+ writeConfigMerge(cwd, { vault: { enabled: true } });
1096
+ }
1097
+ catch (err) {
1098
+ return { ok: false, armed: false, enabled: undefined, client, message: writeFailedMessage(err) };
1099
+ }
1100
+ return { ok: true, armed: true, enabled: true, client, message: 'vault.enabled set to true (it was absent).' };
1101
+ }
930
1102
  // ─── cmdInitContract ────────────────────────────────────────────────────────
931
1103
  /**
932
1104
  * CLI adapter for `pp-tools query init-contract`.
@@ -954,6 +1126,14 @@ function initSessionReset(cwd) {
954
1126
  * convention as `--verify`.
955
1127
  */
956
1128
  function cmdInitContract(cwd, options, raw) {
1129
+ if (options.armVault === true) {
1130
+ const result = armVault(cwd);
1131
+ output(result, raw);
1132
+ if (!result.ok) {
1133
+ process.exitCode = 1;
1134
+ }
1135
+ return;
1136
+ }
957
1137
  if (options.sessionReset === true) {
958
1138
  const result = initSessionReset(cwd);
959
1139
  output(result, raw);
@@ -168,7 +168,26 @@ async function runInitInteractive(cwd, prompter, opts = {}) {
168
168
  preselectedLabel: state.preselectedLabel,
169
169
  optionalAnswer: state.optionalAnswer,
170
170
  });
171
- const result = (0, init_contract_cjs_1.initSessionAnswer)(cwd, stepId, answer, sessionOpts);
171
+ // INIT-PATCH-C: Enter accepts a carried default, "none" clears it.
172
+ // Scoped to an OPTIONAL step carrying a usable `preselected` — today
173
+ // that is only `org_profile` when exactly one org-profile-shaped
174
+ // package was detected. `provider` also carries a `preselected` but is
175
+ // NOT `optionalAnswer`, so the `state.optionalAnswer === true` guard
176
+ // below leaves it untouched: a blank answer there still falls through
177
+ // to `initSessionAnswer` verbatim, which still rejects it and re-asks
178
+ // exactly as today.
179
+ const hasUsableDefault = state.optionalAnswer === true && typeof state.preselected === 'string' && state.preselected.trim() !== '';
180
+ let submittedAnswer = answer;
181
+ if (hasUsableDefault) {
182
+ const trimmedLower = answer.trim().toLowerCase();
183
+ if (answer.trim() === '') {
184
+ submittedAnswer = state.preselected;
185
+ }
186
+ else if (trimmedLower === 'none') {
187
+ submittedAnswer = '';
188
+ }
189
+ }
190
+ const result = (0, init_contract_cjs_1.initSessionAnswer)(cwd, stepId, submittedAnswer, sessionOpts);
172
191
  if (result.ok) {
173
192
  if (stepId === 'compose_confirm') {
174
193
  wroteBinding = readBindingsContent(cwd) !== preBindings;