@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.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +31 -0
- package/agents/pp-code-fixer.md +1 -1
- package/agents/pp-debug-session-manager.md +1 -1
- package/agents/pp-debugger.md +1 -1
- package/agents/pp-executor.md +20 -10
- package/agents/pp-intel-updater.md +1 -1
- package/agents/pp-phase-researcher.md +1 -1
- package/agents/pp-plan-checker.md +5 -5
- package/agents/pp-planner.md +10 -24
- package/agents/pp-project-researcher.md +1 -1
- package/agents/pp-research-synthesizer.md +1 -1
- package/agents/pp-ui-researcher.md +1 -1
- package/agents/pp-verifier.md +8 -4
- package/bin/install.js +429 -82
- package/commands/pp-autonomous.md +8 -6
- package/commands/pp-discovery-to-build.md +1 -1
- package/commands/pp-discovery.md +3 -2
- package/commands/pp-discuss-phase.md +2 -2
- package/commands/pp-execute-phase.md +3 -1
- package/commands/pp-graphify.md +5 -5
- package/commands/pp-import.md +1 -1
- package/commands/pp-map-codebase.md +1 -1
- package/commands/pp-ns-project.md +2 -3
- package/commands/pp-ns-workflow.md +1 -1
- package/commands/pp-plan-phase.md +2 -1
- package/commands/pp-verify-work.md +2 -1
- package/gemini-extension.json +1 -1
- package/hooks/dist/pp-cursor-session-start.js +2 -2
- package/hooks/dist/pp-session-state.sh +1 -1
- package/hooks/dist/pp-statusline.js +21 -2
- package/hooks/pp-cursor-session-start.js +2 -2
- package/hooks/pp-session-state.sh +1 -1
- package/hooks/pp-statusline.js +21 -2
- package/package.json +18 -5
- package/pareto-skills/pp-project-setup/SKILL.md +5 -63
- package/pp-core/bin/detect-build-commands.sh +182 -0
- package/pp-core/bin/lib/capability-registry.cjs +65 -7
- package/pp-core/bin/lib/check-command-router.cjs +79 -38
- package/pp-core/bin/lib/clusters.cjs +2 -3
- package/pp-core/bin/lib/command-aliases.cjs +36 -0
- package/pp-core/bin/lib/command-arg-projection.cjs +99 -1
- package/pp-core/bin/lib/commands.cjs +21 -6
- package/pp-core/bin/lib/compose-bindings.cjs +70 -6
- package/pp-core/bin/lib/config-schema.cjs +16 -0
- package/pp-core/bin/lib/config.cjs +142 -27
- package/pp-core/bin/lib/core-utils.cjs +43 -7
- package/pp-core/bin/lib/discovery-input-check.cjs +218 -0
- package/pp-core/bin/lib/discovery-rebaseline.cjs +1372 -0
- package/pp-core/bin/lib/discovery-research-track.cjs +979 -0
- package/pp-core/bin/lib/discovery-rollup.cjs +476 -0
- package/pp-core/bin/lib/discovery-screens.cjs +1995 -0
- package/pp-core/bin/lib/discovery-sweep.cjs +390 -0
- package/pp-core/bin/lib/discovery-table.cjs +98 -0
- package/pp-core/bin/lib/dispatch-plan.cjs +472 -0
- package/pp-core/bin/lib/frontmatter.cjs +17 -2
- package/pp-core/bin/lib/init-command-router.cjs +8 -5
- package/pp-core/bin/lib/init-contract.cjs +93 -4
- package/pp-core/bin/lib/init.cjs +124 -134
- package/pp-core/bin/lib/install-profiles.cjs +5 -0
- package/pp-core/bin/lib/install-run.cjs +536 -0
- package/pp-core/bin/lib/io.cjs +15 -0
- package/pp-core/bin/lib/loop-resolver.cjs +24 -0
- package/pp-core/bin/lib/milestone.cjs +345 -48
- package/pp-core/bin/lib/phase-command-router.cjs +23 -1
- package/pp-core/bin/lib/phase-locator.cjs +15 -6
- package/pp-core/bin/lib/phase-scope.cjs +91 -0
- package/pp-core/bin/lib/phase-tier.cjs +467 -0
- package/pp-core/bin/lib/phase.cjs +247 -21
- package/pp-core/bin/lib/phases-command-router.cjs +2 -0
- package/pp-core/bin/lib/plan-graph.cjs +368 -0
- package/pp-core/bin/lib/planning-workspace.cjs +53 -0
- package/pp-core/bin/lib/platform-bindings.cjs +32 -2
- package/pp-core/bin/lib/profile-output.cjs +11 -3
- package/pp-core/bin/lib/prohibition-enforcement.cjs +112 -0
- package/pp-core/bin/lib/roadmap-parser.cjs +366 -99
- package/pp-core/bin/lib/roadmap.cjs +141 -39
- package/pp-core/bin/lib/runtime-hooks-surface.cjs +1 -1
- package/pp-core/bin/lib/shell-command-projection.cjs +24 -0
- package/pp-core/bin/lib/state-command-router.cjs +82 -13
- package/pp-core/bin/lib/state-document.cjs +124 -7
- package/pp-core/bin/lib/state.cjs +784 -257
- package/pp-core/bin/lib/template.cjs +22 -6
- package/pp-core/bin/lib/vault-command-router.cjs +57 -6
- package/pp-core/bin/lib/vault-practices.cjs +6 -36
- package/pp-core/bin/lib/vault-write-back.cjs +20 -4
- package/pp-core/bin/lib/vault.cjs +555 -23
- package/pp-core/bin/lib/verify.cjs +53 -31
- package/pp-core/bin/lib/workstream.cjs +1 -1
- package/pp-core/bin/lib/worktree-safety.cjs +26 -15
- package/pp-core/bin/portable-timeout.sh +89 -0
- package/pp-core/bin/pp-tools.cjs +149 -9
- package/pp-core/bin/shared/config-defaults.manifest.json +7 -0
- package/pp-core/bin/shared/config-schema.manifest.json +3 -0
- package/pp-core/references/autonomous-best-effort.md +461 -0
- package/pp-core/references/autonomous-lifecycle.md +146 -0
- package/pp-core/references/autonomous-smart-discuss.md +16 -12
- package/pp-core/references/checkpoints.md +1 -1
- package/pp-core/references/discovery-id-conventions.md +202 -0
- package/pp-core/references/discovery-rebaseline.md +254 -0
- package/pp-core/references/discovery-research-track.md +283 -0
- package/pp-core/references/discovery-traceability.md +400 -0
- package/pp-core/references/loop-hook-dispatch.md +7 -0
- package/pp-core/references/mandatory-initial-read.md +1 -1
- package/pp-core/references/planner-gap-closure.md +1 -0
- package/pp-core/references/planner-human-verify-mode.md +1 -1
- package/pp-core/references/planner-load-graph-context.md +1 -1
- package/pp-core/references/planner-plan-graph.md +66 -0
- package/pp-core/references/planner-tdd.md +1 -0
- package/pp-core/references/planning-config.md +8 -0
- package/pp-core/references/sketch-tooling.md +4 -0
- package/pp-core/references/tdd.md +1 -0
- package/pp-core/templates/claude-md.md +1 -1
- package/pp-core/templates/phase-prompt.md +6 -2
- package/pp-core/templates/prototype-review/review.js +303 -0
- package/pp-core/templates/state.md +6 -0
- package/pp-core/workflows/_runtime-launcher.snippet.sh +1 -1
- package/pp-core/workflows/add-backlog.md +1 -1
- package/pp-core/workflows/add-phase.md +1 -1
- package/pp-core/workflows/add-tests.md +1 -1
- package/pp-core/workflows/add-todo.md +1 -1
- package/pp-core/workflows/ai-integration-phase.md +2 -2
- package/pp-core/workflows/audit-fix.md +12 -16
- package/pp-core/workflows/audit-milestone.md +1 -1
- package/pp-core/workflows/audit-uat.md +1 -1
- package/pp-core/workflows/autonomous.md +68 -306
- package/pp-core/workflows/check-todos.md +1 -1
- package/pp-core/workflows/cleanup.md +1 -1
- package/pp-core/workflows/code-review-fix.md +1 -1
- package/pp-core/workflows/code-review.md +19 -7
- package/pp-core/workflows/complete-milestone.md +41 -60
- package/pp-core/workflows/debug.md +1 -1
- package/pp-core/workflows/diagnose-issues.md +1 -1
- package/pp-core/workflows/discovery-sprint/gap-analysis.md +140 -0
- package/pp-core/workflows/discovery-sprint/input-package.md +140 -0
- package/pp-core/workflows/discovery-sprint/prefill.md +92 -0
- package/pp-core/workflows/discovery-sprint/prototype.md +257 -0
- package/pp-core/workflows/discovery-sprint/rebaseline.md +276 -0
- package/pp-core/workflows/discovery-sprint/session-13-gates.md +24 -0
- package/pp-core/workflows/discovery-sprint/sessions/01-kick-off.md +92 -0
- package/pp-core/workflows/discovery-sprint/sessions/02-as-is.md +98 -0
- package/pp-core/workflows/discovery-sprint/sessions/03-to-be.md +215 -0
- package/pp-core/workflows/discovery-sprint/sessions/04-ux-design.md +146 -0
- package/pp-core/workflows/discovery-sprint/sessions/05-architecture.md +98 -0
- package/pp-core/workflows/discovery-sprint/sessions/06-security.md +107 -0
- package/pp-core/workflows/discovery-sprint/sessions/07-identity.md +98 -0
- package/pp-core/workflows/discovery-sprint/sessions/08-integration.md +85 -0
- package/pp-core/workflows/discovery-sprint/sessions/09-qa.md +109 -0
- package/pp-core/workflows/discovery-sprint/sessions/10-infrastructure.md +119 -0
- package/pp-core/workflows/discovery-sprint/sessions/11-roadmap.md +92 -0
- package/pp-core/workflows/discovery-sprint/sessions/12-delivery-model.md +98 -0
- package/pp-core/workflows/discovery-sprint/sessions/13-wrap-up.md +205 -0
- package/pp-core/workflows/discovery-sprint/templates/context-template.md +266 -0
- package/pp-core/workflows/discovery-sprint.md +79 -1714
- package/pp-core/workflows/discovery-to-build/steps/architecture-deliverables.md +5 -5
- package/pp-core/workflows/discovery-to-build/steps/screen-inventory-gate.md +70 -0
- package/pp-core/workflows/discovery-to-build/templates/discovery-references.md +54 -0
- package/pp-core/workflows/discovery-to-build/templates/pp-handoff.md +184 -0
- package/pp-core/workflows/discovery-to-build.md +21 -238
- package/pp-core/workflows/discuss-phase/modes/advisor.md +1 -1
- package/pp-core/workflows/discuss-phase/modes/auto.md +1 -1
- package/pp-core/workflows/discuss-phase/modes/chain.md +1 -1
- package/pp-core/workflows/discuss-phase-assumptions.md +1 -1
- package/pp-core/workflows/discuss-phase.md +9 -2
- package/pp-core/workflows/do.md +4 -4
- package/pp-core/workflows/docs-update.md +1 -1
- package/pp-core/workflows/edit-phase.md +1 -1
- package/pp-core/workflows/eval-review.md +1 -1
- package/pp-core/workflows/execute-phase/steps/checkpoint-handling.md +45 -0
- package/pp-core/workflows/execute-phase/steps/client-context-gate.md +1 -1
- package/pp-core/workflows/execute-phase/steps/codebase-drift-gate.md +1 -1
- package/pp-core/workflows/execute-phase/steps/cross-ai-delegation.md +90 -0
- package/pp-core/workflows/execute-phase/steps/install-run.md +29 -0
- package/pp-core/workflows/execute-phase/steps/phase-close-rebaseline.md +23 -0
- package/pp-core/workflows/execute-phase/steps/post-merge-gate.md +76 -77
- package/pp-core/workflows/execute-phase/steps/serial-group-dispatch.md +72 -0
- package/pp-core/workflows/execute-phase/steps/vault-fetch.md +5 -5
- package/pp-core/workflows/execute-phase/steps/vault-load-gate.md +19 -112
- package/pp-core/workflows/execute-phase.md +102 -216
- package/pp-core/workflows/execute-plan.md +6 -2
- package/pp-core/workflows/explore.md +1 -1
- package/pp-core/workflows/extract-learnings.md +1 -1
- package/pp-core/workflows/forensics.md +4 -4
- package/pp-core/workflows/graduation.md +1 -1
- package/pp-core/workflows/health.md +1 -1
- package/pp-core/workflows/help/modes/brief.md +1 -0
- package/pp-core/workflows/help/modes/default.md +2 -1
- package/pp-core/workflows/help/modes/full.md +12 -5
- package/pp-core/workflows/import.md +1 -1
- package/pp-core/workflows/ingest-docs.md +1 -1
- package/pp-core/workflows/init.md +18 -2
- package/pp-core/workflows/insert-phase.md +1 -1
- package/pp-core/workflows/list-workspaces.md +1 -1
- package/pp-core/workflows/manager.md +3 -3
- package/pp-core/workflows/map-codebase.md +2 -2
- package/pp-core/workflows/milestone-summary.md +4 -4
- package/pp-core/workflows/mvp-phase.md +1 -1
- package/pp-core/workflows/new-milestone.md +7 -8
- package/pp-core/workflows/new-project.md +6 -1
- package/pp-core/workflows/new-workspace.md +1 -1
- package/pp-core/workflows/next.md +2 -2
- package/pp-core/workflows/note.md +2 -2
- package/pp-core/workflows/pause-work.md +1 -1
- package/pp-core/workflows/plan-milestone-gaps.md +1 -1
- package/pp-core/workflows/plan-phase/steps/chunked-planning.md +111 -0
- package/pp-core/workflows/plan-phase/steps/phase-tier.md +34 -0
- package/pp-core/workflows/plan-phase.md +43 -167
- package/pp-core/workflows/plan-review-convergence.md +2 -2
- package/pp-core/workflows/plant-seed.md +1 -1
- package/pp-core/workflows/pr-branch.md +1 -1
- package/pp-core/workflows/profile-user.md +1 -1
- package/pp-core/workflows/progress.md +3 -3
- package/pp-core/workflows/quick.md +3 -1
- package/pp-core/workflows/remove-phase.md +1 -1
- package/pp-core/workflows/remove-workspace.md +1 -1
- package/pp-core/workflows/resume-project.md +6 -12
- package/pp-core/workflows/review.md +1 -1
- package/pp-core/workflows/scan.md +1 -1
- package/pp-core/workflows/secure-phase.md +1 -1
- package/pp-core/workflows/settings-advanced.md +1 -1
- package/pp-core/workflows/settings-integrations.md +1 -1
- package/pp-core/workflows/settings.md +1 -1
- package/pp-core/workflows/ship.md +25 -8
- package/pp-core/workflows/sketch-wrap-up.md +1 -1
- package/pp-core/workflows/sketch.md +2 -1
- package/pp-core/workflows/spec-phase.md +1 -1
- package/pp-core/workflows/spike-wrap-up.md +1 -1
- package/pp-core/workflows/spike.md +1 -1
- package/pp-core/workflows/stats.md +2 -2
- package/pp-core/workflows/thread.md +1 -1
- package/pp-core/workflows/transition.md +5 -9
- package/pp-core/workflows/ui-phase.md +23 -2
- package/pp-core/workflows/ui-review.md +1 -1
- package/pp-core/workflows/ultraplan-phase.md +2 -2
- package/pp-core/workflows/validate-phase.md +1 -1
- package/pp-core/workflows/vault-write-back.md +1 -1
- package/pp-core/workflows/verify-phase.md +29 -26
- package/pp-core/workflows/verify-work.md +14 -10
- package/scripts/base64-scan.sh +7 -41
- package/scripts/changeset/cli.cjs +25 -5
- package/scripts/dev-env.cjs +462 -0
- package/scripts/lib/allowlist-ratchet.cjs +103 -1
- package/scripts/lib/size-budget.cjs +311 -0
- package/scripts/lint-neutrality-names.cjs +694 -0
- package/scripts/lint-org-coordinates.allowlist.json +35 -0
- package/scripts/lint-org-coordinates.cjs +426 -0
- package/scripts/lint-skill-deps.cjs +48 -9
- package/scripts/lint-test-file-count.cjs +17 -2
- package/scripts/prompt-injection-scan.sh +41 -20
- package/scripts/sync-manifest-versions.cjs +41 -7
- package/scripts/sync-runtime-launcher.cjs +106 -1
- package/scripts/update-size-baseline.cjs +274 -30
- package/scripts/workflow-size.cjs +61 -9
- package/commands/pp-build-handoff.md +0 -46
- package/pp-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +0 -94
|
@@ -27,13 +27,145 @@ const phaseIdMod = require("./phase-id.cjs");
|
|
|
27
27
|
const { escapeRegex, normalizePhaseName, phaseTokenMatches } = phaseIdMod;
|
|
28
28
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
29
29
|
const roadmapParserMod = require("./roadmap-parser.cjs");
|
|
30
|
-
const { getMilestonePhaseFilter, extractCurrentMilestone } = roadmapParserMod;
|
|
30
|
+
const { getMilestonePhaseFilter, extractCurrentMilestone, getClosedMilestonePhaseDirOwners } = roadmapParserMod;
|
|
31
31
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
32
32
|
const coreUtilsMod = require("./core-utils.cjs");
|
|
33
|
-
const { extractOneLinerFromBody } = coreUtilsMod;
|
|
33
|
+
const { extractOneLinerFromBody, toPosixPath } = coreUtilsMod;
|
|
34
34
|
const { planningPaths } = planningWorkspace;
|
|
35
35
|
const { extractFrontmatter } = frontmatterMod;
|
|
36
|
-
const { writeStateMd, stateReplaceFieldWithFallback } = stateMod;
|
|
36
|
+
const { writeStateMd, stateReplaceField, stateReplaceFieldWithFallback, matchSessionSection } = stateMod;
|
|
37
|
+
/**
|
|
38
|
+
* Write `value` into the Session-scoped `Stopped at:` field — the exact scope the
|
|
39
|
+
* frontmatter `stopped_at` is derived from (matchSessionSection, as
|
|
40
|
+
* sessionStoppedAtField reads it) — so writeStateMd sees the caller changed the
|
|
41
|
+
* field and the frontmatter follows (261007-rly D-5, D-7). A Session section
|
|
42
|
+
* without the line gains it; a body without a Session section gains a
|
|
43
|
+
* `## Session` block holding the canonical `**Stopped at:**` label that
|
|
44
|
+
* `state record-session` writes.
|
|
45
|
+
*/
|
|
46
|
+
function setSessionStoppedAt(content, value) {
|
|
47
|
+
const m = matchSessionSection(content);
|
|
48
|
+
if (m && m.index !== undefined) {
|
|
49
|
+
const start = m.index + m[0].length - m[1].length;
|
|
50
|
+
const slice = m[1];
|
|
51
|
+
const replaced = stateReplaceField(slice, 'Stopped At', value) || stateReplaceField(slice, 'Stopped at', value);
|
|
52
|
+
const next = replaced ?? `${slice.replace(/\s+$/, '')}\n**Stopped at:** ${value}\n`;
|
|
53
|
+
return content.slice(0, start) + next + content.slice(start + slice.length);
|
|
54
|
+
}
|
|
55
|
+
return `${content.trimEnd()}\n\n## Session\n\n**Stopped at:** ${value}\n`;
|
|
56
|
+
}
|
|
57
|
+
// ─── The milestone record (261007-rly D-1, D-2, D-6) ─────────────────────────
|
|
58
|
+
/** The marker for an index cell the engine cannot state truthfully — never a guess. */
|
|
59
|
+
const NOT_RECORDED = '(not recorded)';
|
|
60
|
+
/**
|
|
61
|
+
* The milestone label: the version once, then the name only when it says
|
|
62
|
+
* something the version does not (261007-rly D-2). One helper for the two
|
|
63
|
+
* surfaces that used to read `v1.1 v1.1` when no --name was given: the
|
|
64
|
+
* MILESTONES.md heading and the archived REQUIREMENTS header.
|
|
65
|
+
*/
|
|
66
|
+
function milestoneLabel(version, name) {
|
|
67
|
+
const n = (name || '').trim();
|
|
68
|
+
return !n || n === version ? version : `${version} ${n}`;
|
|
69
|
+
}
|
|
70
|
+
function splitTableRow(line) {
|
|
71
|
+
const t = line.trim();
|
|
72
|
+
if (!t.startsWith('|'))
|
|
73
|
+
return null;
|
|
74
|
+
return t.replace(/^\|/, '').replace(/(?<!\\)\|$/, '').split(/(?<!\\)\|/).map((c) => c.trim());
|
|
75
|
+
}
|
|
76
|
+
/** One cell for one header, read from the header text at write time (D-1's column map). */
|
|
77
|
+
function indexCell(header, cells) {
|
|
78
|
+
let value;
|
|
79
|
+
switch (header.trim().toLowerCase()) {
|
|
80
|
+
case 'version':
|
|
81
|
+
value = cells.version;
|
|
82
|
+
break;
|
|
83
|
+
case 'shipped':
|
|
84
|
+
case 'date':
|
|
85
|
+
value = cells.shipped;
|
|
86
|
+
break;
|
|
87
|
+
case 'phases':
|
|
88
|
+
value = cells.phases === undefined ? undefined : String(cells.phases);
|
|
89
|
+
break;
|
|
90
|
+
case 'plans':
|
|
91
|
+
value = cells.plans === undefined ? undefined : String(cells.plans);
|
|
92
|
+
break;
|
|
93
|
+
case 'tasks':
|
|
94
|
+
value = cells.tasks === undefined ? undefined : String(cells.tasks);
|
|
95
|
+
break;
|
|
96
|
+
case 'name':
|
|
97
|
+
value = cells.name;
|
|
98
|
+
break;
|
|
99
|
+
case 'archive':
|
|
100
|
+
value = cells.archive;
|
|
101
|
+
break;
|
|
102
|
+
default: value = undefined; // Requirements, PRs, anything unforeseen
|
|
103
|
+
}
|
|
104
|
+
const text = (value ?? '').replace(/\s*\r?\n\s*/g, ' ').trim();
|
|
105
|
+
return text ? text.replace(/(?<!\\)\|/g, '\\|') : NOT_RECORDED;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* The ONE writer of a milestone record into MILESTONES.md, shared by
|
|
109
|
+
* `milestone complete` and `validate health`'s backfill (261007-rly D-1, D-6).
|
|
110
|
+
*
|
|
111
|
+
* (a) The index row. Before the first `## ` line, a pipe table whose header row
|
|
112
|
+
* has a `Version` cell is the file's index: a row for this version is added
|
|
113
|
+
* directly below the separator (newest first, the order the live table and the
|
|
114
|
+
* sections both use), unless a body row already carries the version. No such
|
|
115
|
+
* table → no row; a table is never invented.
|
|
116
|
+
* (b) The section. Inserted immediately before the first `## ` line — after the
|
|
117
|
+
* H1, any preamble and the index table — never between the H1 and the table,
|
|
118
|
+
* which is where the old header-regex prepend put it on the live shape. With no
|
|
119
|
+
* `## ` line it is appended; an empty or missing file gets `# Milestones` + entry.
|
|
120
|
+
*/
|
|
121
|
+
function insertMilestoneRecord(existing, record) {
|
|
122
|
+
if (existing === null || existing === undefined || !existing.trim()) {
|
|
123
|
+
return { content: `# Milestones\n\n${record.entry}`, index_row: 'no_index_table' };
|
|
124
|
+
}
|
|
125
|
+
const crlf = existing.includes('\r\n');
|
|
126
|
+
const bare = (l) => l.replace(/\r$/, '');
|
|
127
|
+
const lines = existing.split('\n');
|
|
128
|
+
const firstSection = () => lines.findIndex((l) => /^## /.test(bare(l)));
|
|
129
|
+
let indexRow = 'no_index_table';
|
|
130
|
+
const limit = firstSection() === -1 ? lines.length : firstSection();
|
|
131
|
+
for (let i = 0; i + 1 < limit; i++) {
|
|
132
|
+
const header = splitTableRow(bare(lines[i]));
|
|
133
|
+
const versionCol = header ? header.findIndex((c) => c.toLowerCase() === 'version') : -1;
|
|
134
|
+
if (!header || versionCol === -1)
|
|
135
|
+
continue;
|
|
136
|
+
const separator = splitTableRow(bare(lines[i + 1]));
|
|
137
|
+
if (!separator || !separator.every((c) => /^:?-+:?$/.test(c)))
|
|
138
|
+
continue;
|
|
139
|
+
let present = false;
|
|
140
|
+
for (let j = i + 2; j < lines.length; j++) {
|
|
141
|
+
const row = splitTableRow(bare(lines[j]));
|
|
142
|
+
if (!row)
|
|
143
|
+
break;
|
|
144
|
+
if ((row[versionCol] || '') === record.version)
|
|
145
|
+
present = true;
|
|
146
|
+
}
|
|
147
|
+
if (present) {
|
|
148
|
+
indexRow = 'already_present';
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
const row = `| ${header.map((h) => indexCell(h, record.cells)).join(' | ')} |`;
|
|
152
|
+
lines.splice(i + 2, 0, crlf ? `${row}\r` : row);
|
|
153
|
+
indexRow = 'added';
|
|
154
|
+
}
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
const entry = crlf ? record.entry.replace(/\n/g, '\r\n') : record.entry;
|
|
158
|
+
const at = firstSection();
|
|
159
|
+
let content;
|
|
160
|
+
if (at === -1) {
|
|
161
|
+
content = `${lines.join('\n').replace(/\s+$/, '')}${crlf ? '\r\n\r\n' : '\n\n'}${entry}`;
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
const head = at === 0 ? '' : `${lines.slice(0, at).join('\n')}\n`;
|
|
165
|
+
content = head + entry + lines.slice(at).join('\n');
|
|
166
|
+
}
|
|
167
|
+
return { content, index_row: indexRow };
|
|
168
|
+
}
|
|
37
169
|
function cmdRequirementsMarkComplete(cwd, reqIdsRaw, raw) {
|
|
38
170
|
if (!reqIdsRaw || reqIdsRaw.length === 0) {
|
|
39
171
|
error('requirement IDs required. Usage: requirements mark-complete REQ-01,REQ-02 or REQ-01 REQ-02');
|
|
@@ -103,6 +235,21 @@ function cmdRequirementsMarkComplete(cwd, reqIdsRaw, raw) {
|
|
|
103
235
|
total: reqIds.length,
|
|
104
236
|
}, raw, `${updated.length}/${reqIds.length} requirements marked complete`);
|
|
105
237
|
}
|
|
238
|
+
/**
|
|
239
|
+
* The ONE refusal for "the planning base in scope has no ROADMAP.md" — the root of a
|
|
240
|
+
* project partitioned into workstreams when no --ws, env or pointer selects one, or a
|
|
241
|
+
* workstream right after `workstream create`. The predicate is the milestone filter's
|
|
242
|
+
* own `roadmapMissing` signal (CARRY-04); `milestone complete` and `phases
|
|
243
|
+
* archive-closed` both call this, so the two verbs answer the same input the same way
|
|
244
|
+
* (261007-mmz follow-up: archive-closed used to return an empty success there, which
|
|
245
|
+
* let new-milestone §7.5's stop check stay silent on an unsubstituted ${GSD_WS}).
|
|
246
|
+
*/
|
|
247
|
+
function refuseWithoutRoadmap(roadmapMissing, roadmapPath, what) {
|
|
248
|
+
if (roadmapMissing) {
|
|
249
|
+
error(`ROADMAP.md not found at ${roadmapPath} — nothing to ${what}. ` +
|
|
250
|
+
`Write it, or pass --ws <name> if this project is partitioned into workstreams.`);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
106
253
|
function cmdMilestoneComplete(cwd, version, options, raw) {
|
|
107
254
|
if (!version) {
|
|
108
255
|
error('version required for milestone complete (e.g., v1.0)');
|
|
@@ -110,20 +257,55 @@ function cmdMilestoneComplete(cwd, version, options, raw) {
|
|
|
110
257
|
const roadmapPath = planningPaths(cwd).roadmap;
|
|
111
258
|
const reqPath = planningPaths(cwd).requirements;
|
|
112
259
|
const statePath = planningPaths(cwd).state;
|
|
113
|
-
|
|
114
|
-
|
|
260
|
+
// A milestone record belongs beside the ROADMAP it closes, so a project
|
|
261
|
+
// partitioned into workstreams archives into its OWN .planning/workstreams/
|
|
262
|
+
// <name>/milestones/ rather than the repo root — which is where all three of
|
|
263
|
+
// these used to land while roadmap/requirements/state/phases above already
|
|
264
|
+
// resolved per workstream. `--ws` needs no parsing here: pp-tools.cjs strips
|
|
265
|
+
// it centrally and applies it as GSD_WORKSTREAM, which planningDir() reads,
|
|
266
|
+
// and routing through planningPaths inherits that function's rejection of
|
|
267
|
+
// path separators and `..` in the name (phase 12, CARRY-04).
|
|
268
|
+
//
|
|
269
|
+
// CARRY-05: the location is no longer joined here — planningPaths owns it, so
|
|
270
|
+
// this writer and every reader resolve the same two paths from one place.
|
|
271
|
+
const milestonesPath = planningPaths(cwd).milestones;
|
|
272
|
+
const archiveDir = planningPaths(cwd).milestonesArchive;
|
|
115
273
|
const phasesDir = planningPaths(cwd).phases;
|
|
116
274
|
const today = new Date().toISOString().split('T')[0];
|
|
117
275
|
const milestoneName = options.name || version;
|
|
118
|
-
// Ensure archive directory exists
|
|
119
|
-
(0, shell_command_projection_cjs_1.platformEnsureDir)(archiveDir);
|
|
120
276
|
// Scope stats and accomplishments to only the phases belonging to the
|
|
121
277
|
// current milestone's ROADMAP. Uses the shared filter from roadmap-parser.cjs
|
|
122
278
|
// (same logic used by cmdPhasesList and other callers).
|
|
279
|
+
//
|
|
280
|
+
// All three refusals run BEFORE the archive directory is created below: a
|
|
281
|
+
// close that is going to be refused must leave nothing behind, not even an
|
|
282
|
+
// empty milestones/ directory. This is also the ONLY consumer of the filter's
|
|
283
|
+
// signal properties — the other nine call sites read none (phase 12).
|
|
123
284
|
const isDirInMilestone = getMilestonePhaseFilter(cwd, version);
|
|
285
|
+
refuseWithoutRoadmap(isDirInMilestone.roadmapMissing, roadmapPath, 'close');
|
|
124
286
|
if (isDirInMilestone.missingExplicitVersion) {
|
|
125
287
|
error(`no phases found for milestone ${version} in ROADMAP.md`);
|
|
126
288
|
}
|
|
289
|
+
// The third "could not scope this" arm (261007-mmz D-1, D-3). A ROADMAP that
|
|
290
|
+
// reads fine and declares the version but names no phase for it — or whose
|
|
291
|
+
// parse threw, which the filter swallows — yields the same pass-all callable
|
|
292
|
+
// with phaseCount 0. At this consumer that is indistinguishable from "this
|
|
293
|
+
// milestone legitimately contains every directory", so it used to count every
|
|
294
|
+
// directory under phases/ into MILESTONES.md, overwrite the archived
|
|
295
|
+
// <version>-ROADMAP.md snapshot, and under --archive-phases rename them all
|
|
296
|
+
// under this version's label, exit 0. The filter stays pass-all for its nine
|
|
297
|
+
// non-destructive callers (changing it changes ten call sites at once, the
|
|
298
|
+
// CARRY-04 argument); the refusal lives here, unconditionally, because the
|
|
299
|
+
// close without --archive-phases writes those false records too.
|
|
300
|
+
// See todos/done/milestone-complete-archives-every-directory-when-the-roadmap-lists-no-phases.md.
|
|
301
|
+
if (isDirInMilestone.phaseCount === 0) {
|
|
302
|
+
error(`milestone ${version}: ROADMAP.md at ${roadmapPath} names no phase for this milestone — ` +
|
|
303
|
+
`refusing to close; nothing was archived, moved or written. ` +
|
|
304
|
+
`List the milestone's phases as "### Phase N: Name" headings under its milestone, ` +
|
|
305
|
+
`or pass the version the ROADMAP declares.`);
|
|
306
|
+
}
|
|
307
|
+
// Ensure archive directory exists
|
|
308
|
+
(0, shell_command_projection_cjs_1.platformEnsureDir)(archiveDir);
|
|
127
309
|
// Guard: prevent marking complete when ROADMAP still lists phases that have
|
|
128
310
|
// no directory on disk (disk_status: no_directory). This catches the case
|
|
129
311
|
// where the active milestone was erroneously marked complete before phases
|
|
@@ -245,43 +427,54 @@ function cmdMilestoneComplete(cwd, version, options, raw) {
|
|
|
245
427
|
(0, shell_command_projection_cjs_1.platformWriteSync)(node_path_1.default.join(archiveDir, `${version}-ROADMAP.md`), roadmapContent);
|
|
246
428
|
}
|
|
247
429
|
// Archive REQUIREMENTS.md
|
|
430
|
+
// 261007-rly D-1: the Archive cell links only what THIS run wrote.
|
|
431
|
+
const archivedHere = [];
|
|
432
|
+
if (node_fs_1.default.existsSync(roadmapPath))
|
|
433
|
+
archivedHere.push(`${version}-ROADMAP.md`);
|
|
248
434
|
if (node_fs_1.default.existsSync(reqPath)) {
|
|
435
|
+
archivedHere.push(`${version}-REQUIREMENTS.md`);
|
|
249
436
|
const reqContent = node_fs_1.default.readFileSync(reqPath, 'utf-8');
|
|
250
|
-
|
|
437
|
+
// 261007-rly D-2: the version once when no --name was given.
|
|
438
|
+
const archiveHeader = `# Requirements Archive: ${milestoneLabel(version, options.name)}\n\n**Archived:** ${today}\n**Status:** SHIPPED\n\nFor current requirements, see \`.planning/REQUIREMENTS.md\`.\n\n---\n\n`;
|
|
251
439
|
(0, shell_command_projection_cjs_1.platformWriteSync)(node_path_1.default.join(archiveDir, `${version}-REQUIREMENTS.md`), archiveHeader + reqContent);
|
|
252
440
|
}
|
|
253
441
|
// Archive audit file if exists
|
|
254
|
-
const auditFile = node_path_1.default.join(cwd
|
|
442
|
+
const auditFile = node_path_1.default.join(planningPaths(cwd).planning, `${version}-MILESTONE-AUDIT.md`);
|
|
255
443
|
if (node_fs_1.default.existsSync(auditFile)) {
|
|
256
444
|
node_fs_1.default.renameSync(auditFile, node_path_1.default.join(archiveDir, `${version}-MILESTONE-AUDIT.md`));
|
|
445
|
+
archivedHere.push(`${version}-MILESTONE-AUDIT.md`);
|
|
257
446
|
}
|
|
258
|
-
//
|
|
447
|
+
// Under --archive-phases the directory is created unconditionally below and a
|
|
448
|
+
// failed move ends the verb naming the partial state, so the target exists
|
|
449
|
+
// whenever the verb returns (261007-rly D-1).
|
|
450
|
+
if (options.archivePhases)
|
|
451
|
+
archivedHere.push(`${version}-phases/`);
|
|
452
|
+
// Create/append MILESTONES.md entry — one row in the index table (when the
|
|
453
|
+
// file has one) and the section after it, through the shared record writer
|
|
454
|
+
// (261007-rly D-1, D-2, D-6).
|
|
259
455
|
const accomplishmentsList = accomplishments.map((a) => `- ${a}`).join('\n');
|
|
260
|
-
const milestoneEntry = `## ${version
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
else {
|
|
282
|
-
(0, shell_command_projection_cjs_1.platformWriteSync)(milestonesPath, `# Milestones\n\n${milestoneEntry}`);
|
|
283
|
-
}
|
|
456
|
+
const milestoneEntry = `## ${milestoneLabel(version, options.name)} (Shipped: ${today})\n\n**Phases completed:** ${phaseCount} phases, ${totalPlans} plans, ${totalTasks} tasks\n\n**Key accomplishments:**\n${accomplishmentsList || '- (none recorded)'}\n\n---\n\n`;
|
|
457
|
+
const linkPrefix = toPosixPath(node_path_1.default.relative(node_path_1.default.dirname(milestonesPath), archiveDir));
|
|
458
|
+
const archiveCell = archivedHere
|
|
459
|
+
.map((f) => `[${f}](${linkPrefix ? `${linkPrefix}/` : ''}${f})`)
|
|
460
|
+
.join(' · ');
|
|
461
|
+
const milestonesRecord = insertMilestoneRecord(node_fs_1.default.existsSync(milestonesPath) ? node_fs_1.default.readFileSync(milestonesPath, 'utf-8') : null, {
|
|
462
|
+
version,
|
|
463
|
+
entry: milestoneEntry,
|
|
464
|
+
cells: {
|
|
465
|
+
version,
|
|
466
|
+
shipped: today,
|
|
467
|
+
phases: phaseCount,
|
|
468
|
+
plans: totalPlans,
|
|
469
|
+
tasks: totalTasks,
|
|
470
|
+
name: options.name || null,
|
|
471
|
+
archive: archiveCell,
|
|
472
|
+
},
|
|
473
|
+
});
|
|
474
|
+
(0, shell_command_projection_cjs_1.platformWriteSync)(milestonesPath, milestonesRecord.content);
|
|
284
475
|
// Update STATE.md — keep frontmatter/body semantically aligned after closure
|
|
476
|
+
// 261007-ixt D-3: every frontmatter key the shared writer changed is named.
|
|
477
|
+
let stateFrontmatterUpdated = [];
|
|
285
478
|
if (node_fs_1.default.existsSync(statePath)) {
|
|
286
479
|
let stateContent = node_fs_1.default.readFileSync(statePath, 'utf-8');
|
|
287
480
|
stateContent = stateReplaceFieldWithFallback(stateContent, 'Status', null, `${version} milestone complete`);
|
|
@@ -308,28 +501,57 @@ function cmdMilestoneComplete(cwd, version, options, raw) {
|
|
|
308
501
|
else {
|
|
309
502
|
stateContent = `${stateContent.trimEnd()}\n\n## Operator Next Steps\n\n- Start the next milestone with ${(0, runtime_slash_cjs_1.formatGsdSlash)('new-milestone', (0, runtime_slash_cjs_1.resolveRuntime)(cwd))}\n`;
|
|
310
503
|
}
|
|
311
|
-
|
|
504
|
+
// 261007-rly D-5: the Session `Stopped at:` line — and through writeStateMd's
|
|
505
|
+
// delta the frontmatter stopped_at — moves only when the caller passes a new
|
|
506
|
+
// value. Without --stopped-at neither moves and the key is not reported.
|
|
507
|
+
// T-rly-03: operator text, written as ONE line — a newline in the value could
|
|
508
|
+
// otherwise open a new section of STATE.md below the Session block.
|
|
509
|
+
const stoppedAt = typeof options.stoppedAt === 'string' ? options.stoppedAt.replace(/\s+/g, ' ').trim() : '';
|
|
510
|
+
if (stoppedAt) {
|
|
511
|
+
stateContent = setSessionStoppedAt(stateContent, stoppedAt);
|
|
512
|
+
}
|
|
513
|
+
stateFrontmatterUpdated = writeStateMd(statePath, stateContent, cwd, undefined, { stoppedAtSetByCaller: !!stoppedAt }).frontmatter_updated;
|
|
312
514
|
}
|
|
313
515
|
// Archive phase directories if requested
|
|
314
516
|
let phasesArchived = false;
|
|
315
517
|
if (options.archivePhases) {
|
|
518
|
+
const phaseArchiveDir = node_path_1.default.join(archiveDir, `${version}-phases`);
|
|
519
|
+
// A missing phases/ directory is "nothing to archive" — archived.phases
|
|
520
|
+
// stays false, which is true. Anything else that goes wrong is reported:
|
|
521
|
+
// a blanket catch here used to swallow a failed rename, so the verb exited
|
|
522
|
+
// 0 reporting archived.phases: false while every directory sorted before
|
|
523
|
+
// the failure HAD moved and the rest stayed behind (measured, 261007-mmz).
|
|
524
|
+
let phaseDirNames = [];
|
|
316
525
|
try {
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
526
|
+
phaseDirNames = node_fs_1.default
|
|
527
|
+
.readdirSync(phasesDir, { withFileTypes: true })
|
|
528
|
+
.filter((e) => e.isDirectory())
|
|
529
|
+
.map((e) => e.name)
|
|
530
|
+
.filter((name) => isDirInMilestone(name))
|
|
531
|
+
.sort();
|
|
532
|
+
}
|
|
533
|
+
catch (e) {
|
|
534
|
+
if (e?.code !== 'ENOENT') {
|
|
535
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
536
|
+
error(`--archive-phases: could not read ${phasesDir}; the milestone record was already written. ${message}`);
|
|
327
537
|
}
|
|
328
|
-
|
|
538
|
+
phaseDirNames = [];
|
|
329
539
|
}
|
|
330
|
-
|
|
331
|
-
|
|
540
|
+
(0, shell_command_projection_cjs_1.platformEnsureDir)(phaseArchiveDir);
|
|
541
|
+
const moved = [];
|
|
542
|
+
for (const dir of phaseDirNames) {
|
|
543
|
+
try {
|
|
544
|
+
node_fs_1.default.renameSync(node_path_1.default.join(phasesDir, dir), node_path_1.default.join(phaseArchiveDir, dir));
|
|
545
|
+
}
|
|
546
|
+
catch (e) {
|
|
547
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
548
|
+
error(`--archive-phases: could not move ${dir} into ${phaseArchiveDir} after moving ` +
|
|
549
|
+
`${moved.length} of ${phaseDirNames.length} (${moved.length > 0 ? moved.join(', ') : 'none'}); ` +
|
|
550
|
+
`the milestone record was already written. ${message}`);
|
|
551
|
+
}
|
|
552
|
+
moved.push(dir);
|
|
332
553
|
}
|
|
554
|
+
phasesArchived = moved.length > 0;
|
|
333
555
|
}
|
|
334
556
|
const result = {
|
|
335
557
|
version,
|
|
@@ -346,7 +568,9 @@ function cmdMilestoneComplete(cwd, version, options, raw) {
|
|
|
346
568
|
phases: phasesArchived,
|
|
347
569
|
},
|
|
348
570
|
milestones_updated: true,
|
|
571
|
+
milestones_index_row: milestonesRecord.index_row,
|
|
349
572
|
state_updated: node_fs_1.default.existsSync(statePath),
|
|
573
|
+
state_frontmatter_updated: stateFrontmatterUpdated,
|
|
350
574
|
};
|
|
351
575
|
output(result, raw);
|
|
352
576
|
}
|
|
@@ -374,8 +598,81 @@ function cmdPhasesClear(cwd, raw, args) {
|
|
|
374
598
|
}
|
|
375
599
|
output({ cleared }, raw, `${cleared} phase director${cleared === 1 ? 'y' : 'ies'} cleared`);
|
|
376
600
|
}
|
|
601
|
+
/**
|
|
602
|
+
* `phases archive-closed [--dry-run]` (261007-mmz): move each directory under
|
|
603
|
+
* `phases/` that exactly one CLOSED milestone names into that milestone's
|
|
604
|
+
* `milestones/<version>-phases/`, and leave every other directory in place,
|
|
605
|
+
* reporting why. Membership comes from getClosedMilestonePhaseDirOwners — the
|
|
606
|
+
* archived `<version>-ROADMAP.md` snapshot lists the directory's number AND name,
|
|
607
|
+
* and the current milestone does not — the predicate the progress writer already
|
|
608
|
+
* reads (261007-ixt D-9). It replaces new-milestone.md's `--reset-phase-numbers`
|
|
609
|
+
* shell move, which renamed EVERY directory into the latest milestone's archive
|
|
610
|
+
* and so recorded a phase no milestone named as that milestone's (sweep R17a).
|
|
611
|
+
* Paths resolve through planningPaths, so `--ws` scopes it to one workstream.
|
|
612
|
+
*/
|
|
613
|
+
function cmdPhasesArchiveClosed(cwd, raw, args) {
|
|
614
|
+
const dryRun = Array.isArray(args) && args.includes('--dry-run');
|
|
615
|
+
const phasesDir = planningPaths(cwd).phases;
|
|
616
|
+
const archiveDir = planningPaths(cwd).milestonesArchive;
|
|
617
|
+
refuseWithoutRoadmap(getMilestonePhaseFilter(cwd).roadmapMissing, planningPaths(cwd).roadmap, 'archive');
|
|
618
|
+
const ownersOf = getClosedMilestonePhaseDirOwners(cwd);
|
|
619
|
+
let dirNames = [];
|
|
620
|
+
try {
|
|
621
|
+
dirNames = node_fs_1.default
|
|
622
|
+
.readdirSync(phasesDir, { withFileTypes: true })
|
|
623
|
+
.filter((e) => e.isDirectory())
|
|
624
|
+
.map((e) => e.name)
|
|
625
|
+
.sort();
|
|
626
|
+
}
|
|
627
|
+
catch (e) {
|
|
628
|
+
if (e?.code !== 'ENOENT') {
|
|
629
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
630
|
+
error(`phases archive-closed: could not read ${phasesDir}. ${message}`);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
const rel = (p) => node_path_1.default.relative(cwd, p).split(node_path_1.default.sep).join('/');
|
|
634
|
+
const planned = [];
|
|
635
|
+
const left = [];
|
|
636
|
+
for (const dir of dirNames) {
|
|
637
|
+
const owners = ownersOf(dir);
|
|
638
|
+
if (owners.length === 1) {
|
|
639
|
+
planned.push({ dir, milestone: owners[0], to: node_path_1.default.join(archiveDir, `${owners[0]}-phases`, dir) });
|
|
640
|
+
}
|
|
641
|
+
else if (owners.length > 1) {
|
|
642
|
+
left.push({ dir, reason: 'named_by_several_closed_milestones', milestones: owners });
|
|
643
|
+
}
|
|
644
|
+
else if (/^999(?:\.|-|$)/.test(dir)) {
|
|
645
|
+
left.push({ dir, reason: 'backlog' });
|
|
646
|
+
}
|
|
647
|
+
else {
|
|
648
|
+
left.push({ dir, reason: 'not_named_by_a_closed_milestone' });
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
const moved = [];
|
|
652
|
+
if (!dryRun) {
|
|
653
|
+
for (const item of planned) {
|
|
654
|
+
try {
|
|
655
|
+
if (node_fs_1.default.existsSync(item.to))
|
|
656
|
+
throw new Error(`${rel(item.to)} already exists`);
|
|
657
|
+
(0, shell_command_projection_cjs_1.platformEnsureDir)(node_path_1.default.dirname(item.to));
|
|
658
|
+
node_fs_1.default.renameSync(node_path_1.default.join(phasesDir, item.dir), item.to);
|
|
659
|
+
}
|
|
660
|
+
catch (e) {
|
|
661
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
662
|
+
error(`phases archive-closed: could not move ${item.dir} into ${rel(node_path_1.default.dirname(item.to))} after moving ` +
|
|
663
|
+
`${moved.length} of ${planned.length} (${moved.length > 0 ? moved.map((m) => m.dir).join(', ') : 'none'}). ${message}`);
|
|
664
|
+
}
|
|
665
|
+
moved.push(item);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
const shown = (dryRun ? planned : moved).map((m) => ({ dir: m.dir, milestone: m.milestone, to: rel(m.to) }));
|
|
669
|
+
output({ dry_run: dryRun, moved: shown, left }, raw, `${shown.length} phase director${shown.length === 1 ? 'y' : 'ies'} ${dryRun ? 'would be ' : ''}archived; ${left.length} left in place`);
|
|
670
|
+
}
|
|
377
671
|
module.exports = {
|
|
378
672
|
cmdRequirementsMarkComplete,
|
|
379
673
|
cmdMilestoneComplete,
|
|
674
|
+
insertMilestoneRecord,
|
|
675
|
+
milestoneLabel,
|
|
380
676
|
cmdPhasesClear,
|
|
677
|
+
cmdPhasesArchiveClosed,
|
|
381
678
|
};
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* Unsupported in this router:
|
|
7
7
|
* - scaffold: routed through top-level scaffold command.
|
|
8
8
|
*
|
|
9
|
-
* CJS-only subcommands: mvp-mode (dispatched directly, before hub).
|
|
9
|
+
* CJS-only subcommands: mvp-mode, dispatch-plan, install-run, tier, tier-propose (dispatched directly, before hub).
|
|
10
10
|
*
|
|
11
11
|
* #3788: dispatch is mediated by CommandRoutingHub. The public entry point
|
|
12
12
|
* and observable CLI behaviour are unchanged.
|
|
@@ -51,6 +51,28 @@ function routePhaseCommand({ phase, args, cwd, raw, error }) {
|
|
|
51
51
|
phase.cmdPhaseMvpMode(cwd, args.slice(2), raw);
|
|
52
52
|
return;
|
|
53
53
|
}
|
|
54
|
+
// `dispatch-plan` (pack 9): read-only; the executor spawns, suite runs and
|
|
55
|
+
// tracking commits execute-phase obeys (src/dispatch-plan.cts).
|
|
56
|
+
if (subcommand === 'dispatch-plan') {
|
|
57
|
+
phase.cmdPhaseDispatchPlan(cwd, args.slice(2), raw);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
// `install-run` (pack 9): heavy-tier verification of the installed product —
|
|
61
|
+
// pack, install, init, run the phase's install checks (src/install-run.cts).
|
|
62
|
+
if (subcommand === 'install-run') {
|
|
63
|
+
phase.cmdPhaseInstallRun(cwd, args.slice(2), raw);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
// `tier` / `tier-propose` (pack 9): the phase's ceremony tier and its
|
|
67
|
+
// proposal (src/phase-tier.cts); `tier-propose --record` writes ROADMAP.
|
|
68
|
+
if (subcommand === 'tier') {
|
|
69
|
+
phase.cmdPhaseTier(cwd, args.slice(2), raw);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (subcommand === 'tier-propose') {
|
|
73
|
+
phase.cmdPhaseTierPropose(cwd, args.slice(2), raw);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
54
76
|
// ── Build the CJS registry ──────────────────────────────────────────────────
|
|
55
77
|
// Each handler receives a ctx object from the hub and must return a HubResult.
|
|
56
78
|
const cjsRegistry = {
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* - node:fs / node:path (stdlib)
|
|
15
15
|
* - ./phase-id.cjs (normalizePhaseName, phaseTokenMatches, extractPhaseToken)
|
|
16
16
|
* - ./core-utils.cjs (readSubdirectories, getPhaseFileStats, extractCanonicalPlanId, toPosixPath)
|
|
17
|
-
* - ./planning-workspace.cjs (planningDir)
|
|
17
|
+
* - ./planning-workspace.cjs (planningDir, planningPaths)
|
|
18
18
|
*/
|
|
19
19
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
20
20
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
@@ -29,7 +29,7 @@ const coreUtilsModule = require("./core-utils.cjs");
|
|
|
29
29
|
const { readSubdirectories, getPhaseFileStats, extractCanonicalPlanId, toPosixPath } = coreUtilsModule;
|
|
30
30
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
31
31
|
const planningWorkspace = require("./planning-workspace.cjs");
|
|
32
|
-
const { planningDir } = planningWorkspace;
|
|
32
|
+
const { planningDir, planningPaths } = planningWorkspace;
|
|
33
33
|
// ─── Phase search helpers ─────────────────────────────────────────────────────
|
|
34
34
|
function searchPhaseInDir(baseDir, relBase, normalized) {
|
|
35
35
|
try {
|
|
@@ -83,9 +83,15 @@ function findPhaseInternal(cwd, phase) {
|
|
|
83
83
|
const current = searchPhaseInDir(phasesDir, relPhasesDir, normalized);
|
|
84
84
|
if (current)
|
|
85
85
|
return current;
|
|
86
|
-
|
|
86
|
+
// CARRY-05: the archive directory is planningPaths' to name, not ours. The
|
|
87
|
+
// hardcoded path.join(cwd, '.planning', 'milestones') that stood here
|
|
88
|
+
// bypassed the workspace helper entirely, so a phase archived inside a
|
|
89
|
+
// workstream was unreachable from every verb that resolves through
|
|
90
|
+
// findPhaseInternal.
|
|
91
|
+
const milestonesDir = planningPaths(cwd).milestonesArchive;
|
|
87
92
|
if (!node_fs_1.default.existsSync(milestonesDir))
|
|
88
93
|
return null;
|
|
94
|
+
const relMilestonesDir = node_path_1.default.relative(cwd, milestonesDir);
|
|
89
95
|
try {
|
|
90
96
|
const milestoneEntries = node_fs_1.default.readdirSync(milestonesDir, { withFileTypes: true });
|
|
91
97
|
const archiveDirs = milestoneEntries
|
|
@@ -97,7 +103,7 @@ function findPhaseInternal(cwd, phase) {
|
|
|
97
103
|
const versionMatch = archiveName.match(/^(v[\d.]+)-phases$/);
|
|
98
104
|
const version = versionMatch[1];
|
|
99
105
|
const archivePath = node_path_1.default.join(milestonesDir, archiveName);
|
|
100
|
-
const relBase =
|
|
106
|
+
const relBase = toPosixPath(node_path_1.default.join(relMilestonesDir, archiveName));
|
|
101
107
|
const result = searchPhaseInDir(archivePath, relBase, normalized);
|
|
102
108
|
if (result) {
|
|
103
109
|
result.archived = version;
|
|
@@ -109,7 +115,10 @@ function findPhaseInternal(cwd, phase) {
|
|
|
109
115
|
return null;
|
|
110
116
|
}
|
|
111
117
|
function getArchivedPhaseDirs(cwd) {
|
|
112
|
-
|
|
118
|
+
// CARRY-05: same owner as findPhaseInternal above and as the `milestone
|
|
119
|
+
// complete` writer — not a second hardcoded copy.
|
|
120
|
+
const milestonesDir = planningPaths(cwd).milestonesArchive;
|
|
121
|
+
const relMilestonesDir = node_path_1.default.relative(cwd, milestonesDir);
|
|
113
122
|
const results = [];
|
|
114
123
|
if (!node_fs_1.default.existsSync(milestonesDir))
|
|
115
124
|
return results;
|
|
@@ -129,7 +138,7 @@ function getArchivedPhaseDirs(cwd) {
|
|
|
129
138
|
results.push({
|
|
130
139
|
name: dir,
|
|
131
140
|
milestone: version,
|
|
132
|
-
basePath: node_path_1.default.join(
|
|
141
|
+
basePath: node_path_1.default.join(relMilestonesDir, archiveName),
|
|
133
142
|
fullPath: node_path_1.default.join(archivePath, dir),
|
|
134
143
|
});
|
|
135
144
|
}
|