@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
@@ -0,0 +1,368 @@
1
+ "use strict";
2
+ /**
3
+ * Plan graph — the single owner of how a phase's plans are ordered.
4
+ *
5
+ * Computes, from each PLAN.md's frontmatter (`depends_on`, `files_modified`,
6
+ * `serial_group`, `chain_reason`, task shapes):
7
+ *
8
+ * - the wave of every plan;
9
+ * - the serial groups (plans that share a wave but must run one after
10
+ * another because they touch the same non-source file);
11
+ * - the chain metrics (plan count, wave count, longest chain, and how many
12
+ * links of that chain exist only because two plans touch the same
13
+ * non-source file);
14
+ * - the chain check the plan checker enforces (a longest chain longer than
15
+ * half the plan count FAILS unless every link of it carries a recorded
16
+ * `chain_reason`);
17
+ * - same-shape merge candidates for the planner.
18
+ *
19
+ * Callers: `phase-plan-index` (src/phase.cts) adds this to the plan inventory.
20
+ * The planner and plan-checker agents read that output; they never compute a
21
+ * wave or a chain themselves.
22
+ *
23
+ * ## The wave rule
24
+ *
25
+ * A plan's wave is its longest-path level over two kinds of edges:
26
+ * 1. declared `depends_on` edges;
27
+ * 2. implicit edges between two plans that share a SOURCE file (see
28
+ * `classifyPlanFile`) and are not already ordered by (1). The implicit
29
+ * edge runs from the earlier plan (file order) to the later one.
30
+ * Two plans that share only NON-source files (a test file, a size baseline, a
31
+ * changeset, a doc, a planning file) get no edge: they stay in the same wave
32
+ * and are put in one serial group instead.
33
+ */
34
+ Object.defineProperty(exports, "__esModule", { value: true });
35
+ exports.PLAN_TASK_CAP = exports.PLAN_TITLE_MAX = void 0;
36
+ exports.classifyPlanFile = classifyPlanFile;
37
+ exports.isInstructionFile = isInstructionFile;
38
+ exports.isSourceFile = isSourceFile;
39
+ exports.planTitleProblem = planTitleProblem;
40
+ exports.readPlanTitle = readPlanTitle;
41
+ exports.computePlanGraph = computePlanGraph;
42
+ const SOURCE_DIRS = ['src/', 'bin/', 'pp-core/bin/', 'hooks/', 'scripts/'];
43
+ const DOC_EXTENSIONS = /\.(md|mdx|markdown|rst|txt|adoc)$/i;
44
+ const DOC_BASENAMES = /^(LICENSE|NOTICE|CHANGELOG|AUTHORS)(\.[a-z]+)?$/i;
45
+ function normalizePath(file) {
46
+ return String(file).trim().replace(/\\/g, '/').replace(/^\.\//, '');
47
+ }
48
+ /**
49
+ * Classify one `files_modified` entry. This is THE source/non-source predicate
50
+ * for plan ordering; nothing else decides it.
51
+ *
52
+ * Checked in this order (first match wins):
53
+ * planning — under `.planning/`
54
+ * changeset — under `.changeset/` or `.changesets/`
55
+ * baseline — a `*baseline*.json`/`*.baseline.*` file, `__snapshots__/`, `*.snap`
56
+ * test — under a `test/`, `tests/` or `__tests__/` directory or a
57
+ * `*.Tests/` project; or named `*.test.*`, `*.spec.*`,
58
+ * `*_test.go|py`, `test_*.py`
59
+ * source — under `src/`, `bin/`, `pp-core/bin/`, `hooks/` or `scripts/`
60
+ * (pp-core ships executable code from these)
61
+ * doc — a `.md/.mdx/.markdown/.rst/.txt/.adoc` file, anything under
62
+ * `docs/` or `doc/`, or LICENSE/NOTICE/CHANGELOG/AUTHORS. In
63
+ * pp-core this includes workflow, agent, reference and template
64
+ * markdown.
65
+ * source — anything else. An unrecognised file is treated as source, so
66
+ * it keeps the strict "shared file = later wave" rule.
67
+ */
68
+ function classifyPlanFile(file) {
69
+ const p = normalizePath(file);
70
+ const base = p.split('/').pop() ?? p;
71
+ if (p.startsWith('.planning/'))
72
+ return 'planning';
73
+ if (/^\.changesets?\//.test(p))
74
+ return 'changeset';
75
+ if (/baseline[^/]*\.json$/i.test(base) || /\.baseline\./i.test(base) || /(^|\/)__snapshots__\//.test(p) || /\.snap$/i.test(base)) {
76
+ return 'baseline';
77
+ }
78
+ if (/(^|\/)(tests?|__tests__)\//.test(p) ||
79
+ /(^|\/)[^/]+\.Tests?\//.test(p) ||
80
+ /\.(test|spec)\.[^/]+$/i.test(base) ||
81
+ /_test\.(go|py)$/i.test(base) ||
82
+ /^test_[^/]*\.py$/i.test(base)) {
83
+ return 'test';
84
+ }
85
+ if (SOURCE_DIRS.some((dir) => p.startsWith(dir)))
86
+ return 'source';
87
+ if (DOC_EXTENSIONS.test(base) || /^docs?\//.test(p) || DOC_BASENAMES.test(base))
88
+ return 'doc';
89
+ return 'source';
90
+ }
91
+ /** A workflow or agent markdown file (the unit same-shape plans are made of). */
92
+ function isInstructionFile(file) {
93
+ const p = normalizePath(file);
94
+ return /\.md$/i.test(p) && (/(^|\/)workflows\//.test(p) || /(^|\/)agents\//.test(p));
95
+ }
96
+ /** True when `file` is a source file for plan ordering (see `classifyPlanFile`). */
97
+ function isSourceFile(file) {
98
+ return classifyPlanFile(file) === 'source';
99
+ }
100
+ /** Longest a plan title may be before it is reported (still valid). */
101
+ exports.PLAN_TITLE_MAX = 100;
102
+ /**
103
+ * Problem with a plan's `title:` frontmatter value, or null when it is a valid
104
+ * one-line title. `undefined` means the key is absent.
105
+ */
106
+ function planTitleProblem(value) {
107
+ if (value === undefined)
108
+ return 'Missing required frontmatter field: title';
109
+ if (typeof value !== 'string' || value.trim() === '') {
110
+ return 'Frontmatter field title must be a non-empty one-line string';
111
+ }
112
+ if (/[\r\n]/.test(value))
113
+ return 'Frontmatter field title must be one line';
114
+ return null;
115
+ }
116
+ /** Normalised title, or null when absent/invalid. */
117
+ function readPlanTitle(value) {
118
+ return planTitleProblem(value) === null ? value.trim() : null;
119
+ }
120
+ /** Default task cap per plan (agents/pp-planner.md: "Each plan: 2-3 tasks maximum."). */
121
+ exports.PLAN_TASK_CAP = 3;
122
+ function slug(text) {
123
+ return text.toLowerCase().replace(/\.[a-z0-9]+$/i, '').replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '') || 'group';
124
+ }
125
+ function intersect(a, b) {
126
+ return [...new Set(a.filter((f) => b.has(f)))].sort();
127
+ }
128
+ /**
129
+ * Compute the plan graph. `nodes` must be in plan file order (that order
130
+ * breaks ties and orients implicit edges).
131
+ */
132
+ function computePlanGraph(nodes, opts = {}) {
133
+ const taskCap = opts.taskCap ?? exports.PLAN_TASK_CAP;
134
+ const ids = nodes.map((n) => n.id);
135
+ const index = new Map(ids.map((id, i) => [id, i]));
136
+ const files = nodes.map((n) => n.filesModified.map(normalizePath).filter((f) => f !== ''));
137
+ const fileSets = files.map((f) => new Set(f));
138
+ const warnings = [];
139
+ // Adjacency over indices: edges[i] = successors of i.
140
+ const succ = nodes.map(() => new Set());
141
+ nodes.forEach((n, j) => {
142
+ for (const dep of n.dependsOn) {
143
+ const i = index.get(dep);
144
+ if (i === undefined || i === j)
145
+ continue;
146
+ succ[i].add(j);
147
+ }
148
+ });
149
+ const levelsOf = () => {
150
+ const inDeg = nodes.map(() => 0);
151
+ succ.forEach((s) => s.forEach((j) => { inDeg[j]++; }));
152
+ const level = nodes.map(() => 0);
153
+ const queue = [];
154
+ inDeg.forEach((d, i) => { if (d === 0)
155
+ queue.push(i); });
156
+ let head = 0;
157
+ while (head < queue.length) {
158
+ const cur = queue[head++];
159
+ for (const nxt of [...succ[cur]].sort((a, b) => a - b)) {
160
+ level[nxt] = Math.max(level[nxt], level[cur] + 1);
161
+ if (--inDeg[nxt] === 0)
162
+ queue.push(nxt);
163
+ }
164
+ }
165
+ return { level, done: new Set(queue) };
166
+ };
167
+ // A declared cycle has no waves; nodes Kahn never dequeued are on or behind it.
168
+ const declaredLevels = levelsOf();
169
+ if (declaredLevels.done.size < nodes.length) {
170
+ return emptyGraph(nodes, ids.filter((_, i) => !declaredLevels.done.has(i)));
171
+ }
172
+ const reaches = (from, to) => {
173
+ const stack = [from];
174
+ const seen = new Set([from]);
175
+ while (stack.length > 0) {
176
+ const cur = stack.pop();
177
+ if (cur === to)
178
+ return true;
179
+ for (const nxt of succ[cur]) {
180
+ if (!seen.has(nxt)) {
181
+ seen.add(nxt);
182
+ stack.push(nxt);
183
+ }
184
+ }
185
+ }
186
+ return false;
187
+ };
188
+ // Implicit edges: a shared SOURCE file orders two otherwise-unordered plans.
189
+ const implicitEdges = [];
190
+ for (let i = 0; i < nodes.length; i++) {
191
+ for (let j = i + 1; j < nodes.length; j++) {
192
+ const sharedSource = intersect(files[j], fileSets[i]).filter(isSourceFile);
193
+ if (sharedSource.length === 0)
194
+ continue;
195
+ if (reaches(i, j) || reaches(j, i))
196
+ continue;
197
+ succ[i].add(j);
198
+ implicitEdges.push({ from: ids[i], to: ids[j], shared_source_files: sharedSource });
199
+ }
200
+ }
201
+ const { level } = levelsOf();
202
+ // Same-shape merge candidates: no source file, exactly one workflow/agent file
203
+ // (any other files are tests, baselines, changesets or planning), same task shape.
204
+ const shapeGroups = new Map();
205
+ nodes.forEach((n, i) => {
206
+ if (n.taskTypes.length === 0)
207
+ return;
208
+ const kinds = files[i].map(classifyPlanFile);
209
+ if (kinds.includes('source'))
210
+ return;
211
+ if (files[i].filter(isInstructionFile).length !== 1 || kinds.filter((k) => k === 'doc').length !== 1)
212
+ return;
213
+ const shape = n.taskTypes.join('+');
214
+ if (!shapeGroups.has(shape))
215
+ shapeGroups.set(shape, []);
216
+ shapeGroups.get(shape).push(i);
217
+ });
218
+ const mergeCandidates = [];
219
+ for (const [shape, members] of shapeGroups) {
220
+ if (members.length < 2)
221
+ continue;
222
+ const totalTasks = members.reduce((sum, i) => sum + nodes[i].taskTypes.length, 0);
223
+ mergeCandidates.push({
224
+ shape,
225
+ plans: members.map((i) => ids[i]),
226
+ total_tasks: totalTasks,
227
+ task_cap: taskCap,
228
+ action: totalTasks <= taskCap ? 'merge' : 'serial_group',
229
+ });
230
+ }
231
+ // Serial groups: union-find within each level.
232
+ const parent = nodes.map((_, i) => i);
233
+ const find = (x) => (parent[x] === x ? x : (parent[x] = find(parent[x])));
234
+ const union = (a, b) => {
235
+ const ra = find(a);
236
+ const rb = find(b);
237
+ if (ra !== rb)
238
+ parent[Math.max(ra, rb)] = Math.min(ra, rb);
239
+ };
240
+ for (let i = 0; i < nodes.length; i++) {
241
+ for (let j = i + 1; j < nodes.length; j++) {
242
+ if (level[i] !== level[j])
243
+ continue;
244
+ const shared = intersect(files[j], fileSets[i]);
245
+ const sameDeclared = nodes[i].serialGroup !== null && nodes[i].serialGroup === nodes[j].serialGroup;
246
+ if (shared.length > 0 || sameDeclared)
247
+ union(i, j);
248
+ }
249
+ }
250
+ for (const cand of mergeCandidates) {
251
+ if (cand.action !== 'serial_group')
252
+ continue;
253
+ const members = cand.plans.map((id) => index.get(id));
254
+ for (const a of members)
255
+ for (const b of members)
256
+ if (a < b && level[a] === level[b])
257
+ union(a, b);
258
+ }
259
+ const groupsByRoot = new Map();
260
+ nodes.forEach((_, i) => {
261
+ const r = find(i);
262
+ if (!groupsByRoot.has(r))
263
+ groupsByRoot.set(r, []);
264
+ groupsByRoot.get(r).push(i);
265
+ });
266
+ const serialGroupOf = Object.fromEntries(ids.map((id) => [id, null]));
267
+ const serialGroups = [];
268
+ const usedNames = new Set();
269
+ const roots = [...groupsByRoot.keys()].sort((a, b) => a - b);
270
+ for (const root of roots) {
271
+ const members = groupsByRoot.get(root).sort((a, b) => a - b);
272
+ if (members.length < 2)
273
+ continue;
274
+ const sharedFiles = new Set();
275
+ for (const a of members)
276
+ for (const b of members)
277
+ if (a < b)
278
+ intersect(files[b], fileSets[a]).forEach((f) => sharedFiles.add(f));
279
+ const shared = [...sharedFiles].sort();
280
+ const declaredName = members.map((i) => nodes[i].serialGroup).find((g) => !!g);
281
+ let name = declaredName ?? `w${level[root] + 1}-${shared.length > 0 ? slug(shared[0].split('/').pop()) : 'same-shape'}`;
282
+ if (usedNames.has(name)) {
283
+ let k = 2;
284
+ while (usedNames.has(`${name}-${k}`))
285
+ k++;
286
+ name = `${name}-${k}`;
287
+ }
288
+ usedNames.add(name);
289
+ for (const i of members)
290
+ serialGroupOf[ids[i]] = name;
291
+ serialGroups.push({ name, level: level[root], plans: members.map((i) => ids[i]), shared_files: shared });
292
+ }
293
+ nodes.forEach((n, i) => {
294
+ if (n.serialGroup && serialGroupOf[ids[i]] !== n.serialGroup) {
295
+ warnings.push(`Plan ${ids[i]}: declares serial_group: ${n.serialGroup} but the plan graph puts it in ` +
296
+ `${serialGroupOf[ids[i]] ?? 'no serial group'} (a serial group only holds plans of one wave).`);
297
+ }
298
+ });
299
+ // Longest chain (by plan count) over the full DAG.
300
+ const order = ids.map((_, i) => i).sort((a, b) => level[a] - level[b] || a - b);
301
+ const best = nodes.map(() => 1);
302
+ const prev = nodes.map(() => null);
303
+ const preds = nodes.map(() => []);
304
+ succ.forEach((s, i) => s.forEach((j) => preds[j].push(i)));
305
+ for (const j of order) {
306
+ for (const i of preds[j].sort((a, b) => a - b)) {
307
+ if (best[i] + 1 > best[j]) {
308
+ best[j] = best[i] + 1;
309
+ prev[j] = i;
310
+ }
311
+ }
312
+ }
313
+ let end = -1;
314
+ nodes.forEach((_, i) => { if (end === -1 || best[i] > best[end])
315
+ end = i; });
316
+ const chain = [];
317
+ for (let cur = end; cur !== null && cur !== -1; cur = prev[cur])
318
+ chain.unshift(cur);
319
+ const links = [];
320
+ for (let k = 1; k < chain.length; k++) {
321
+ const a = chain[k - 1];
322
+ const b = chain[k];
323
+ const shared = intersect(files[b], fileSets[a]);
324
+ const kind = shared.some(isSourceFile) ? 'source-file' : shared.length > 0 ? 'non-source-file' : 'declared';
325
+ links.push({ from: ids[a], to: ids[b], kind, shared_files: shared, reason: nodes[b].chainReason });
326
+ }
327
+ const nonSourceLinks = links.filter((l) => l.kind === 'non-source-file').length;
328
+ const limit = nodes.length / 2;
329
+ const unreasoned = links.filter((l) => !l.reason).map((l) => ({ from: l.from, to: l.to, kind: l.kind }));
330
+ const passed = chain.length <= limit || unreasoned.length === 0;
331
+ const waveCount = nodes.length === 0 ? 0 : Math.max(...level) + 1;
332
+ const message = passed
333
+ ? `longest chain ${chain.length} of ${nodes.length} plans (limit ${limit})` +
334
+ (chain.length > limit ? ' — every link has a chain_reason' : '')
335
+ : `longest chain is ${chain.length} plans, over half the plan count (${nodes.length} plans, limit ${limit}), ` +
336
+ `and ${unreasoned.length} link(s) have no chain_reason: ${unreasoned.map((l) => `${l.from}->${l.to} (${l.kind})`).join(', ')}. ` +
337
+ `Drop depends_on edges that exist only because two plans touch the same non-source file ` +
338
+ `(they share a wave in a serial group instead), merge same-shape plans, or record a ` +
339
+ `chain_reason on each later plan saying why it must wait.`;
340
+ return {
341
+ cycle: null,
342
+ level: Object.fromEntries(ids.map((id, i) => [id, level[i]])),
343
+ serialGroupOf,
344
+ serialGroups,
345
+ implicitEdges,
346
+ plan_count: nodes.length,
347
+ wave_count: waveCount,
348
+ longest_chain: { length: chain.length, plans: chain.map((i) => ids[i]), links, non_source_links: nonSourceLinks },
349
+ chain_check: { passed, limit, unreasoned_links: passed ? [] : unreasoned, message },
350
+ merge_candidates: mergeCandidates,
351
+ warnings,
352
+ };
353
+ }
354
+ function emptyGraph(nodes, cycle) {
355
+ return {
356
+ cycle,
357
+ level: {},
358
+ serialGroupOf: Object.fromEntries(nodes.map((n) => [n.id, null])),
359
+ serialGroups: [],
360
+ implicitEdges: [],
361
+ plan_count: nodes.length,
362
+ wave_count: 0,
363
+ longest_chain: { length: 0, plans: [], links: [], non_source_links: 0 },
364
+ chain_check: { passed: false, limit: nodes.length / 2, unreasoned_links: [], message: `depends_on cycle: ${cycle.join(', ')}` },
365
+ merge_candidates: [],
366
+ warnings: [],
367
+ };
368
+ }
@@ -72,6 +72,28 @@ function planningDir(cwd, ws, project) {
72
72
  function planningRoot(cwd) {
73
73
  return node_path_1.default.join(cwd, '.planning');
74
74
  }
75
+ /**
76
+ * THE owner of "where the milestone record lives", given an already-resolved
77
+ * planning base (`planningDir(cwd)` / `planningPaths(cwd).planning`).
78
+ *
79
+ * Phase 12 CARRY-04 moved the milestone-record WRITER into the workstream (a
80
+ * milestone record belongs beside the ROADMAP it closes) but left the readers
81
+ * joining their own copy of the location, some of them at the repo root — so a
82
+ * workstream-archived milestone was invisible to `init.new-milestone`,
83
+ * `history-digest`, `phases list --include-archived` and every verb that
84
+ * resolves a phase through `findPhaseInternal`. Callers that hold a cwd should
85
+ * read `planningPaths(cwd).milestones` / `.milestonesArchive`; callers that
86
+ * already hold a planning base (verify.cjs, phase.cjs) call this directly.
87
+ * Nobody re-derives the two names (phase 12, CARRY-05).
88
+ *
89
+ * @param planningBase - absolute path to the .planning workspace root
90
+ */
91
+ function milestonePathsFor(planningBase) {
92
+ return {
93
+ milestones: node_path_1.default.join(planningBase, 'MILESTONES.md'),
94
+ milestonesArchive: node_path_1.default.join(planningBase, 'milestones'),
95
+ };
96
+ }
75
97
  function planningPaths(cwd, ws) {
76
98
  const base = planningDir(cwd, ws);
77
99
  return {
@@ -82,8 +104,37 @@ function planningPaths(cwd, ws) {
82
104
  config: node_path_1.default.join(base, 'config.json'),
83
105
  phases: node_path_1.default.join(base, 'phases'),
84
106
  requirements: node_path_1.default.join(base, 'REQUIREMENTS.md'),
107
+ ...milestonePathsFor(base),
85
108
  };
86
109
  }
110
+ /**
111
+ * THE owner of "which PROJECT.md answers for this cwd" — for READS and
112
+ * existence checks (quick 261007-jkl, D1; todo "init project_exists is root
113
+ * hardcoded").
114
+ *
115
+ * PROJECT.md is a SHARED file by design: migrateToWorkstreams (workstream.cts)
116
+ * leaves it at the project level when it moves ROADMAP/STATE/phases into
117
+ * .planning/workstreams/<name>/. So under a workstream the answer is the
118
+ * workstream's OWN PROJECT.md when one exists, else the SHARED one:
119
+ * own = planningPaths(cwd).project — honours GSD_WORKSTREAM and GSD_PROJECT
120
+ * shared = planningDir(cwd, null)/PROJECT.md — workstream segment absent,
121
+ * GSD_PROJECT still honoured
122
+ * When neither exists the shared path is named, which is where /pp-new-project
123
+ * creates it. In flat mode own === shared. Both candidates come from
124
+ * planningDir, so a traversing GSD_WORKSTREAM / GSD_PROJECT throws before any
125
+ * path is named.
126
+ *
127
+ * planningPaths().project is deliberately left as it is: it is the write
128
+ * target for callers that create or rewrite the workstream's own file.
129
+ */
130
+ function resolveProjectFile(cwd) {
131
+ const own = planningPaths(cwd).project;
132
+ const shared = node_path_1.default.join(planningDir(cwd, null), 'PROJECT.md');
133
+ if (own !== shared && node_fs_1.default.existsSync(own)) {
134
+ return { path: own, exists: true, scope: 'workstream' };
135
+ }
136
+ return { path: shared, exists: node_fs_1.default.existsSync(shared), scope: 'shared' };
137
+ }
87
138
  /**
88
139
  * @param cwd
89
140
  * @param fn - callback to run while holding the lock
@@ -238,6 +289,8 @@ module.exports = {
238
289
  planningDir,
239
290
  planningRoot,
240
291
  planningPaths,
292
+ resolveProjectFile,
293
+ milestonePathsFor,
241
294
  withPlanningLock,
242
295
  getActiveWorkstream,
243
296
  setActiveWorkstream,
@@ -34,8 +34,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
34
34
  return (mod && mod.__esModule) ? mod : { "default": mod };
35
35
  };
36
36
  Object.defineProperty(exports, "__esModule", { value: true });
37
- exports.MACHINE_SPECIFIC_CLONE_TARGET_PATTERNS = exports.VAULT_REPO_PATTERN_SOURCE = exports.PLATFORM_REPO_PATTERN_SOURCE = exports.CONTRACT_SCHEMA_VERSION = void 0;
37
+ exports.BINDINGS_COORDINATE_FIELDS = exports.MACHINE_SPECIFIC_CLONE_TARGET_PATTERNS = exports.VAULT_REPO_PATTERN_SOURCE = exports.PLATFORM_REPO_PATTERN_SOURCE = exports.CONTRACT_SCHEMA_VERSION = void 0;
38
38
  exports.validateBindings = validateBindings;
39
+ exports.redactBindingsForEmission = redactBindingsForEmission;
39
40
  exports.cmdPlatformBindings = cmdPlatformBindings;
40
41
  exports.cmdValidateBindings = cmdValidateBindings;
41
42
  const node_fs_1 = __importDefault(require("node:fs"));
@@ -208,6 +209,33 @@ function validateBindings(o) {
208
209
  }
209
210
  return { valid: errors.length === 0, errors, warnings };
210
211
  }
212
+ /**
213
+ * Pack 6 D-07: the `.platform-bindings.json` fields that carry a remote repo
214
+ * coordinate (and so the fields a credential can ride in). Hosts are not
215
+ * listed: a host is a bare host by contract and is read for real use.
216
+ */
217
+ exports.BINDINGS_COORDINATE_FIELDS = Object.freeze(['platform.repo', 'client.vaultRepo']);
218
+ /**
219
+ * Emission-only: a copy of a bindings-shaped object with every coordinate field
220
+ * routed through the one owner (`redactCloneTarget`, src/vault.cts — lazily
221
+ * required so this module's load graph is unchanged). Never use the result for
222
+ * a write, a comparison, or a clone.
223
+ */
224
+ function redactBindingsForEmission(obj) {
225
+ if (!isObject(obj))
226
+ return obj;
227
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
228
+ const { redactCloneTarget } = require('./vault.cjs');
229
+ const copy = JSON.parse(JSON.stringify(obj));
230
+ for (const field of exports.BINDINGS_COORDINATE_FIELDS) {
231
+ const [ns, key] = field.split('.');
232
+ const parent = copy[ns];
233
+ if (isObject(parent) && typeof parent[key] === 'string') {
234
+ parent[key] = redactCloneTarget(parent[key]);
235
+ }
236
+ }
237
+ return copy;
238
+ }
211
239
  /**
212
240
  * Reader handler for `pp-tools query platform-bindings`.
213
241
  *
@@ -254,7 +282,9 @@ function cmdPlatformBindings(cwd, args, raw) {
254
282
  return;
255
283
  }
256
284
  // present + valid → echo the bindings under a present flag.
257
- output(Object.assign({ present: true }, parsed), raw);
285
+ // Pack 6 D-07: the remote coordinate fields are emitted display-safe; every
286
+ // other field is verbatim. The file on disk keeps the raw value.
287
+ output(Object.assign({ present: true }, redactBindingsForEmission(parsed)), raw);
258
288
  }
259
289
  // Anchored regex for `?ref=` values in module source lines (T-07-04: untrusted text,
260
290
  // no YAML dependency, no shell-eval).
@@ -28,7 +28,12 @@ const { loadConfig } = configLoader;
28
28
  const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
29
29
  const runtime_homes_cjs_1 = require("./runtime-homes.cjs");
30
30
  const runtime_slash_cjs_1 = require("./runtime-slash.cjs");
31
+ // The start-command fallback uses the pp-named alias (same function).
32
+ const runtime_slash_cjs_2 = require("./runtime-slash.cjs");
31
33
  const runtime_name_policy_cjs_1 = require("./runtime-name-policy.cjs");
34
+ // eslint-disable-next-line @typescript-eslint/no-require-imports -- planning-workspace.cjs is an export= CommonJS module
35
+ const planningWorkspace = require("./planning-workspace.cjs");
36
+ const { resolveProjectFile } = planningWorkspace;
32
37
  // ─── Constants ────────────────────────────────────────────────────────────────
33
38
  const DIMENSION_KEYS = [
34
39
  'communication_style', 'decision_speed', 'explanation_depth',
@@ -189,7 +194,7 @@ const CLAUDE_INSTRUCTIONS = {
189
194
  // depends on the runtime resolved from the project's config/env.
190
195
  function buildClaudeMdFallbacks(runtime) {
191
196
  return {
192
- project: `Project not yet initialized. Run ${String((0, runtime_slash_cjs_1.formatGsdSlash)('new-project', runtime))} to set up.`,
197
+ project: `Project not yet initialized. Run ${String((0, runtime_slash_cjs_2.formatPpSlash)('init', runtime))} to set up.`,
193
198
  stack: 'Technology stack not yet documented. Will populate after codebase mapping or first phase.',
194
199
  conventions: 'Conventions not yet established. Will populate as patterns emerge during development.',
195
200
  architecture: 'Architecture not yet mapped. Follow existing patterns found in the codebase.',
@@ -300,7 +305,9 @@ function extractMarkdownSection(content, sectionName) {
300
305
  // ─── CLAUDE.md Section Generators ─────────────────────────────────────────────
301
306
  function generateProjectSection(cwd) {
302
307
  const fallbacks = buildClaudeMdFallbacks((0, runtime_slash_cjs_1.resolveRuntime)(cwd));
303
- const projectPath = node_path_1.default.join(cwd, '.planning', 'PROJECT.md');
308
+ // D1 (261007-jkl): the PROJECT.md that answers for this cwd (workstream's own, else
309
+ // the shared one; GSD_PROJECT honoured) — this used to be a fixed repo-root join.
310
+ const projectPath = resolveProjectFile(cwd).path;
304
311
  const content = (0, shell_command_projection_cjs_1.platformReadSync)(projectPath);
305
312
  if (!content) {
306
313
  return { content: fallbacks['project'], source: 'PROJECT.md', linkPath: null, hasFallback: true };
@@ -330,7 +337,8 @@ function generateProjectSection(cwd) {
330
337
  if (parts.length === 0) {
331
338
  return { content: fallbacks['project'], source: 'PROJECT.md', linkPath: null, hasFallback: true };
332
339
  }
333
- return { content: parts.join('\n\n'), source: 'PROJECT.md', linkPath: '.planning/PROJECT.md', hasFallback: false };
340
+ const linkPath = node_path_1.default.relative(cwd, projectPath).split(node_path_1.default.sep).join('/');
341
+ return { content: parts.join('\n\n'), source: 'PROJECT.md', linkPath, hasFallback: false };
334
342
  }
335
343
  function generateStackSection(cwd) {
336
344
  const fallbacks = buildClaudeMdFallbacks((0, runtime_slash_cjs_1.resolveRuntime)(cwd));