@opengsd/gsd-core 1.19.0 → 1.20.1
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/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/agents/gsd-code-fixer.compact.md +5 -5
- package/agents/gsd-code-fixer.md +9 -4
- package/agents/gsd-code-reviewer.compact.md +49 -8
- package/agents/gsd-code-reviewer.md +53 -14
- package/agents/gsd-executor.md +8 -4
- package/bin/install.js +36 -40
- package/commands/gsd/new-milestone.md +2 -2
- package/commands/gsd/new-project.md +2 -2
- package/commands/gsd/update.md +5 -2
- package/gsd-core/bin/gsd-tools.cjs +177 -93
- package/gsd-core/bin/lib/capability-lifecycle.cjs +1 -12
- package/gsd-core/bin/lib/capability-registry.cjs +284 -93
- package/gsd-core/bin/lib/capability-validator.cjs +15 -0
- package/gsd-core/bin/lib/commands.cjs +46 -17
- package/gsd-core/bin/lib/config-loader.cjs +3 -3
- package/gsd-core/bin/lib/core-utils.cjs +11 -0
- package/gsd-core/bin/lib/frontmatter.cjs +21 -3
- package/gsd-core/bin/lib/gate-api-coverage-verify-pre.cjs +2 -2
- package/gsd-core/bin/lib/gate-context-drift.cjs +1 -1
- package/gsd-core/bin/lib/gate-phase-context.cjs +7 -8
- package/gsd-core/bin/lib/gate-schema-drift.cjs +1 -1
- package/gsd-core/bin/lib/git-info-exclude.cjs +1 -6
- package/gsd-core/bin/lib/gsd-tool-dir.cjs +134 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +13 -9
- package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +9 -7
- package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +3 -3
- package/gsd-core/bin/lib/init.cjs +181 -68
- package/gsd-core/bin/lib/install-engine.cjs +9 -9
- package/gsd-core/bin/lib/install-manifest-record.cjs +86 -0
- package/gsd-core/bin/lib/install-profiles.cjs +4 -2
- package/gsd-core/bin/lib/io.cjs +6 -0
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +34 -1
- package/gsd-core/bin/lib/markdown-table.cjs +23 -0
- package/gsd-core/bin/lib/mcp-server.cjs +8 -5
- package/gsd-core/bin/lib/milestone.cjs +18 -50
- package/gsd-core/bin/lib/phase-entry-grammar.cjs +119 -0
- package/gsd-core/bin/lib/phase-id-display.cjs +2 -0
- package/gsd-core/bin/lib/phase-id.cjs +627 -36
- package/gsd-core/bin/lib/phase-lifecycle.cjs +9 -74
- package/gsd-core/bin/lib/phase-locator.cjs +328 -19
- package/gsd-core/bin/lib/phase.cjs +3039 -666
- package/gsd-core/bin/lib/planning-document.cjs +26 -5
- package/gsd-core/bin/lib/planning-inspect.cjs +27 -35
- package/gsd-core/bin/lib/planning-snapshot.cjs +12 -7
- package/gsd-core/bin/lib/planning-workspace.cjs +3 -3
- package/gsd-core/bin/lib/read-only-git-env.cjs +23 -2
- package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +2 -2
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +22 -0
- package/gsd-core/bin/lib/review-lane-invocation.cjs +125 -18
- package/gsd-core/bin/lib/reviewer-step-dispatch.cjs +11 -9
- package/gsd-core/bin/lib/roadmap-parser.cjs +759 -144
- package/gsd-core/bin/lib/roadmap-progress.cjs +136 -0
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +8 -5
- package/gsd-core/bin/lib/roadmap.cjs +358 -106
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +310 -130
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +6 -0
- package/gsd-core/bin/lib/shell-command-projection.cjs +36 -9
- package/gsd-core/bin/lib/smart-entry.cjs +22 -11
- package/gsd-core/bin/lib/state-contract.cjs +11 -7
- package/gsd-core/bin/lib/state-document.cjs +332 -23
- package/gsd-core/bin/lib/state-transition.cjs +51 -15
- package/gsd-core/bin/lib/state.cjs +499 -180
- package/gsd-core/bin/lib/template.cjs +2 -2
- package/gsd-core/bin/lib/validate.cjs +3 -2
- package/gsd-core/bin/lib/vendor/re2js.cjs +863 -577
- package/gsd-core/bin/lib/vendor/re2js.d.cts +284 -219
- package/gsd-core/bin/lib/verify.cjs +2 -2
- package/gsd-core/bin/lib/workstream-inventory.cjs +27 -21
- package/gsd-core/bin/lib/worktree-base-ref.cjs +32 -2
- package/gsd-core/bin/verify-reapply-patches.cjs +411 -10
- package/gsd-core/references/cicd-review-patterns.md +45 -0
- package/gsd-core/references/necessity-review.md +48 -0
- package/gsd-core/workflows/code-review-fix.md +46 -5
- package/gsd-core/workflows/code-review.md +3 -0
- package/gsd-core/workflows/execute-phase.md +1 -1
- package/gsd-core/workflows/execute-plan.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +13 -9
- package/gsd-core/workflows/plan-phase.md +1 -1
- package/gsd-core/workflows/pr-branch.md +2 -0
- package/gsd-core/workflows/reapply-patches.md +104 -0
- package/gsd-core/workflows/review.md +54 -141
- package/gsd-core/workflows/secure-phase.md +1 -1
- package/gsd-core/workflows/settings-advanced.md +57 -9
- package/gsd-core/workflows/ship.md +12 -0
- package/hooks/dist/gsd-agent-isolation-guard.js +1 -1
- package/hooks/dist/gsd-cursor-subagent-start.js +1 -1
- package/hooks/dist/gsd-graphify-update.sh +6 -0
- package/hooks/dist/gsd-statusline.js +3 -2
- package/hooks/dist/gsd-windsurf-pre-write.js +4 -4
- package/hooks/dist/gsd-workflow-guard.js +2 -1
- package/hooks/dist/gsd-worktree-path-guard.js +4 -4
- package/hooks/dist/lib/git-probe.js +16 -0
- package/hooks/dist/lib/isolation-sentinel.js +77 -2
- package/hooks/gsd-agent-isolation-guard.js +1 -1
- package/hooks/gsd-cursor-subagent-start.js +1 -1
- package/hooks/gsd-graphify-update.sh +6 -0
- package/hooks/gsd-statusline.js +3 -2
- package/hooks/gsd-windsurf-pre-write.js +4 -4
- package/hooks/gsd-workflow-guard.js +2 -1
- package/hooks/gsd-worktree-path-guard.js +4 -4
- package/hooks/lib/git-probe.js +16 -0
- package/hooks/lib/isolation-sentinel.js +77 -2
- package/package.json +3 -3
- package/pi/package-agents/gsd-code-fixer.md +9 -4
- package/pi/package-agents/gsd-code-reviewer.md +53 -14
- package/pi/package-agents/gsd-executor.md +8 -4
- package/pi/package-agents/manifest.json +3 -3
- package/pi/package-skills/gsd-new-milestone/SKILL.md +1 -1
- package/pi/package-skills/gsd-new-project/SKILL.md +1 -1
- package/pi/package-skills/gsd-update/SKILL.md +4 -1
- package/scripts/changeset/lint.cjs +28 -3
- package/scripts/ci-next-health.cjs +246 -23
- package/scripts/docs-guard-registry.cjs +2 -0
- package/scripts/gen-features.cjs +14 -2
- package/scripts/lib/drift-scan.cjs +27 -0
- package/scripts/lib/macos-conformance-tier.generated.cjs +10 -0
- package/scripts/lib/platform-conformance-tier.generated.cjs +15 -0
- package/scripts/lint-phase-enumeration-drift.cjs +8 -4
- package/scripts/lint-roadmap-progress-drift.cjs +421 -0
- package/scripts/merge-commit-pr.cjs +186 -0
- package/scripts/mutation-matrix.cjs +23 -1
- package/scripts/release-ancestry.cjs +86 -0
- package/scripts/release-pipeline-pr.cjs +92 -0
- package/scripts/require-issue-link-policy.cjs +18 -9
- package/scripts/sync-next-version.cjs +48 -7
- package/skills/gsd-new-milestone/SKILL.md +1 -1
- package/skills/gsd-new-project/SKILL.md +1 -1
- package/skills/gsd-update/SKILL.md +5 -2
- package/vscode/package.json +1 -1
|
@@ -104,7 +104,7 @@ const planningScopeMod = require("../planning-scope.cjs");
|
|
|
104
104
|
const { SCOPE } = planningScopeMod;
|
|
105
105
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
106
106
|
const phaseIdMod = require("../phase-id.cjs");
|
|
107
|
-
const { matchPhaseDirs,
|
|
107
|
+
const { matchPhaseDirs, phaseLookupQuery, extractPhaseToken, isSentinelPhaseId, isSentinelPhaseDir } = phaseIdMod;
|
|
108
108
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
109
109
|
const validateMod = require("../validate.cjs");
|
|
110
110
|
const { phaseVariants } = validateMod;
|
|
@@ -116,7 +116,7 @@ const { phaseVariants } = validateMod;
|
|
|
116
116
|
* does the roadmap claim, for W007) call this — one matcher, reused, per the
|
|
117
117
|
* file-level comment.
|
|
118
118
|
*/
|
|
119
|
-
function dirsForPhase(dirs, phaseId, convention) {
|
|
119
|
+
function dirsForPhase(dirs, phaseId, convention, directoryContext) {
|
|
120
120
|
// #612: `convention` reaches BOTH operands of this pairing or it pairs
|
|
121
121
|
// nothing. The phases come from a bracket-aware heading scan and the dirs
|
|
122
122
|
// from a bracket-aware disk scan, so a convention-less selector here leaves
|
|
@@ -125,7 +125,7 @@ function dirsForPhase(dirs, phaseId, convention) {
|
|
|
125
125
|
// disk" AND every bracket directory reads as unclaimed. `matchPhaseDirs`
|
|
126
126
|
// forwards it to its primary `phaseTokenMatches`; handed nothing it is
|
|
127
127
|
// byte-identical, so no legacy repo's pairing changes.
|
|
128
|
-
const canonical = matchPhaseDirs(dirs,
|
|
128
|
+
const canonical = matchPhaseDirs(dirs, phaseLookupQuery(phaseId, directoryContext), convention, undefined, directoryContext).matches;
|
|
129
129
|
if (canonical.length > 0)
|
|
130
130
|
return canonical;
|
|
131
131
|
// Bug 2 (#3309 W006/W007 migration cluster, found while fixing the
|
|
@@ -187,6 +187,7 @@ function checkW006(snapshot) {
|
|
|
187
187
|
return [];
|
|
188
188
|
const dirs = snapshot.allPhaseDirNames.value;
|
|
189
189
|
const convention = snapshot.phaseIdConvention;
|
|
190
|
+
const directoryContext = snapshot.directoryContext ?? phaseIdMod.phaseDirectoryContext(snapshot.config.value ?? {}, '', snapshot.roadmapDeclaredPhases.value.map(({ phaseId }) => phaseId));
|
|
190
191
|
const archivedTokens = new Set(snapshot.archivedPhaseTokens.value);
|
|
191
192
|
// No separate scope guard: `roadmapSentinelPhaseTokens` and
|
|
192
193
|
// `roadmapDeclaredPhases` are produced by ONE builder call
|
|
@@ -219,7 +220,7 @@ function checkW006(snapshot) {
|
|
|
219
220
|
// not change its result.
|
|
220
221
|
if (sentinelTokens.has(phaseId) || isSentinelPhaseId(phaseId))
|
|
221
222
|
continue;
|
|
222
|
-
if (dirsForPhase(dirs, phaseId, convention).length > 0)
|
|
223
|
+
if (dirsForPhase(dirs, phaseId, convention, directoryContext).length > 0)
|
|
223
224
|
continue;
|
|
224
225
|
// Bug 1 (#3309 W006/W007 migration cluster): a phase whose ONLY
|
|
225
226
|
// directory lives under a milestone archive
|
|
@@ -247,10 +248,10 @@ function checkW006(snapshot) {
|
|
|
247
248
|
// ─── W007 — an on-disk phase directory has no matching ROADMAP entry ──────
|
|
248
249
|
// (verify.cts:2086-2101)
|
|
249
250
|
/** Every directory in `dirs` that ANY declared roadmap phase resolves to. */
|
|
250
|
-
function computeClaimedDirs(dirs, declaredPhases, convention) {
|
|
251
|
+
function computeClaimedDirs(dirs, declaredPhases, convention, directoryContext) {
|
|
251
252
|
const claimed = new Set();
|
|
252
253
|
for (const { phaseId } of declaredPhases) {
|
|
253
|
-
for (const dir of dirsForPhase(dirs, phaseId, convention))
|
|
254
|
+
for (const dir of dirsForPhase(dirs, phaseId, convention, directoryContext))
|
|
254
255
|
claimed.add(dir);
|
|
255
256
|
}
|
|
256
257
|
return claimed;
|
|
@@ -261,7 +262,8 @@ function checkW007(snapshot) {
|
|
|
261
262
|
return [];
|
|
262
263
|
const dirs = snapshot.allPhaseDirNames.value;
|
|
263
264
|
const convention = snapshot.phaseIdConvention;
|
|
264
|
-
const
|
|
265
|
+
const directoryContext = snapshot.directoryContext ?? phaseIdMod.phaseDirectoryContext(snapshot.config.value ?? {}, '', snapshot.roadmapDeclaredPhases.value.map(({ phaseId }) => phaseId));
|
|
266
|
+
const claimedDirs = computeClaimedDirs(dirs, snapshot.roadmapDeclaredPhases.value, convention, directoryContext);
|
|
265
267
|
const diagnostics = [];
|
|
266
268
|
for (const dirName of dirs) {
|
|
267
269
|
// `extractPhaseToken` is the phase-id.cts owner `PHASE_TOKEN_FROM_DIR_RE`
|
|
@@ -46,7 +46,7 @@ const healthDiagnosticMod = require("../health-diagnostic-types.cjs");
|
|
|
46
46
|
const { SEVERITY, adviseRemedy } = healthDiagnosticMod;
|
|
47
47
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
48
48
|
const phaseIdMod = require("../phase-id.cjs");
|
|
49
|
-
const { getMilestoneFromPhaseId, matchPhaseDirs,
|
|
49
|
+
const { getMilestoneFromPhaseId, matchPhaseDirs, phaseLookupQuery, extractPhaseToken, PHASE_NUMBER_TOKEN_SOURCE } = phaseIdMod;
|
|
50
50
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- the single owner of the persisted status vocabulary
|
|
51
51
|
const stateDocumentMod = require("../state-document.cjs");
|
|
52
52
|
const { normalizeStateStatus } = stateDocumentMod;
|
|
@@ -332,8 +332,8 @@ const RULE_W026 = {
|
|
|
332
332
|
// field's own doc comment on `PlanningSnapshot` for why
|
|
333
333
|
// `roadmapDeclaredPhases`'s `milestone` attribution is the wrong fit).
|
|
334
334
|
const unstarted = [];
|
|
335
|
+
const directoryContext = snapshot.directoryContext ?? phaseIdMod.phaseDirectoryContext(snapshot.config.value ?? {}, '', snapshot.currentMilestoneRoadmapPhaseIds.value);
|
|
335
336
|
for (const phaseId of snapshot.currentMilestoneRoadmapPhaseIds.value) {
|
|
336
|
-
const normalized = normalizePhaseName(phaseId);
|
|
337
337
|
// `allPhaseDirNames` — every directory under `phases/`, UNWINDOWED by
|
|
338
338
|
// ROADMAP-declaration membership — mirrors the original's own
|
|
339
339
|
// unwindowed `phaseDirNames2` (`verify.cts:2372-2382`, a direct
|
|
@@ -346,7 +346,7 @@ const RULE_W026 = {
|
|
|
346
346
|
// moved the read onto the shared selector; the convention rides along,
|
|
347
347
|
// since the selector's primary match IS the `phaseTokenMatches` call this
|
|
348
348
|
// line used to make directly.
|
|
349
|
-
const hasDirectory = matchPhaseDirs(snapshot.allPhaseDirNames.value,
|
|
349
|
+
const hasDirectory = matchPhaseDirs(snapshot.allPhaseDirNames.value, phaseLookupQuery(phaseId, directoryContext), snapshot.phaseIdConvention, undefined, directoryContext).matches.length > 0;
|
|
350
350
|
if (!hasDirectory)
|
|
351
351
|
unstarted.push(phaseId);
|
|
352
352
|
}
|
|
@@ -15,6 +15,7 @@ const node_os_1 = __importDefault(require("node:os"));
|
|
|
15
15
|
const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
|
|
16
16
|
const clock_cjs_1 = require("./clock.cjs");
|
|
17
17
|
const pattern_cjs_1 = require("./pattern.cjs");
|
|
18
|
+
const phase_entry_grammar_cjs_1 = require("./phase-entry-grammar.cjs");
|
|
18
19
|
const markdown_sectionizer_cjs_1 = require("./markdown-sectionizer.cjs");
|
|
19
20
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- io.cjs is an export= CommonJS module
|
|
20
21
|
const io = require("./io.cjs");
|
|
@@ -33,6 +34,8 @@ const roadmapParser = require("./roadmap-parser.cjs");
|
|
|
33
34
|
const coreUtils = require("./core-utils.cjs");
|
|
34
35
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- phase-id.cjs is an export= CommonJS module
|
|
35
36
|
const phaseId = require("./phase-id.cjs");
|
|
37
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports -- phase-id-display.cjs is an export= CommonJS module
|
|
38
|
+
const phaseIdDisplayMod = require("./phase-id-display.cjs");
|
|
36
39
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- worktree-safety.cjs is an export= CommonJS module
|
|
37
40
|
const worktreeSafety = require("./worktree-safety.cjs");
|
|
38
41
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- planning-workspace.cjs is an export= CommonJS module
|
|
@@ -93,10 +96,11 @@ const { harvestPriorVerifyCommands } = verifyCommandGrounding;
|
|
|
93
96
|
const { output, error, ERROR_REASON, formatDiagnosticToken } = io;
|
|
94
97
|
const { loadConfig, loadConfigResolved } = configLoader;
|
|
95
98
|
const { resolveModelInternal, resolveGranularityInternal, assertValidGranularityOverride } = modelResolver;
|
|
96
|
-
const { findPhaseInternal, listMilestonePhaseDirs, listAllPhaseDirs } = phaseLocator;
|
|
99
|
+
const { phaseDirectoryOptions, findPhaseInternal, listMilestonePhaseDirs, listAllPhaseDirs, } = phaseLocator;
|
|
97
100
|
const { getRoadmapPhaseInternal, getMilestoneInfo, stripShippedMilestones, extractCurrentMilestone, } = roadmapParser;
|
|
98
101
|
const { pathExistsInternal, generateSlugInternal, toPosixPath } = coreUtils;
|
|
99
|
-
const { comparePhaseNum, normalizePhaseName, stripProjectCodePrefix, PHASE_NUMBER_TOKEN_SOURCE, PHASE_DEP_REF_SOURCE, isForeignPrefixedPhaseQuery, isSentinelPhaseId, extractPhaseToken, scopeToPhase, renderPhaseBranchName, parsePhaseId, renderPhaseId, phaseHeadingPrefixSrcFor, PHASE_HEADING_BASELINE, buildPhaseHeadingScanRegex, } = phaseId;
|
|
102
|
+
const { comparePhaseNum, phaseDirectoryName, customPhaseQuery, customDirectoryOwner, phaseScanTokenSource, phaseNumberTokenSource, phaseKeyFromToken, phaseDirectoryContext, normalizePhaseName, stripProjectCodePrefix, PHASE_NUMBER_TOKEN_SOURCE, PHASE_DEP_REF_SOURCE, extractPhaseDependencyTokens, isForeignPrefixedPhaseQuery, isSentinelPhaseId, extractPhaseToken, scopeToPhase, renderPhaseBranchName, parsePhaseId, renderPhaseId, bracketQualifiedKey, phaseHeadingPrefixSrcFor, parsePhaseChecklistLine, PHASE_HEADING_BASELINE, buildPhaseHeadingScanRegex, } = phaseId;
|
|
103
|
+
const { phaseToken } = phaseIdDisplayMod;
|
|
100
104
|
const { pruneOrphanedWorktrees } = worktreeSafety;
|
|
101
105
|
const { planningPaths, planningDir, planningRoot, todosDir, listAvailableWorkstreams, peekActiveWorkstream, resolveEnvWorkstream, diagnoseUnresolvedActiveWorkstream, describeUnresolvedWorkstreamReason, findContextMdIn, findPhaseSpecIn, resolvePhaseIdConvention, worktreesOptedOut, } = planningWorkspace;
|
|
102
106
|
const { extractFrontmatter, frontmatterBlock } = frontmatterMod;
|
|
@@ -134,8 +138,8 @@ function roadmapPhaseMatchesExactPrefix(roadmapPhase, phase) {
|
|
|
134
138
|
// #2104: shared helpers that wrap findPhaseInternal / getRoadmapPhaseInternal
|
|
135
139
|
// with the #2056 foreign-prefix guard, so every init command gets the same
|
|
136
140
|
// protection without duplicating the guard logic at each call site.
|
|
137
|
-
function guardedFindPhase(cwd, phase, projectCode) {
|
|
138
|
-
let phaseInfo = findPhaseInternal(cwd, phase);
|
|
141
|
+
function guardedFindPhase(cwd, phase, projectCode, phaseNaming) {
|
|
142
|
+
let phaseInfo = findPhaseInternal(cwd, phase, undefined, phaseDirectoryOptions(cwd, { phase_naming: phaseNaming, project_code: projectCode }));
|
|
139
143
|
if (isForeignPrefixedPhaseQuery(phase, projectCode) && !phaseInfoMatchesExactPrefix(phaseInfo, phase)) {
|
|
140
144
|
phaseInfo = null;
|
|
141
145
|
}
|
|
@@ -902,7 +906,7 @@ function cmdInitExecutePhase(cwd, phase, raw, options = {}) {
|
|
|
902
906
|
error('phase required for init execute-phase');
|
|
903
907
|
}
|
|
904
908
|
const config = loadConfig(cwd);
|
|
905
|
-
let phaseInfo = guardedFindPhase(cwd, phase, config.project_code);
|
|
909
|
+
let phaseInfo = guardedFindPhase(cwd, phase, config.project_code, config.phase_naming);
|
|
906
910
|
// #3216: getMilestoneInfo now returns a ScopedResult — `.value` carries the
|
|
907
911
|
// MilestoneInfo (or null on any non-COMPLETE scope). NOT display-only: when
|
|
908
912
|
// `branching_strategy === 'milestone'`, `milestone['version']`/`['name']`
|
|
@@ -1077,7 +1081,7 @@ function cmdInitPlanPhase(cwd, phase, raw, options = {}) {
|
|
|
1077
1081
|
}
|
|
1078
1082
|
const config = loadConfig(cwd);
|
|
1079
1083
|
// #2056/#2104: foreign-prefixed queries must not collapse to numeric phases.
|
|
1080
|
-
let phaseInfo = guardedFindPhase(cwd, phase, config.project_code);
|
|
1084
|
+
let phaseInfo = guardedFindPhase(cwd, phase, config.project_code, config.phase_naming);
|
|
1081
1085
|
const roadmapPhase = guardedGetRoadmapPhase(cwd, phase, config.project_code);
|
|
1082
1086
|
phaseInfo = applyRoadmapFallback(phaseInfo, roadmapPhase, (rp) => {
|
|
1083
1087
|
const phaseName = rp['phase_name'];
|
|
@@ -1111,12 +1115,14 @@ function cmdInitPlanPhase(cwd, phase, raw, options = {}) {
|
|
|
1111
1115
|
const phaseNamePlan = resolvePhaseDisplayName(phaseInfo, roadmapPhase);
|
|
1112
1116
|
const rawProjectCodePlan = config.project_code || '';
|
|
1113
1117
|
let expectedPhaseDirPlan = null;
|
|
1114
|
-
|
|
1118
|
+
const namingContextPlan = phaseDirectoryContext(config);
|
|
1119
|
+
const customNamingPlan = customPhaseQuery(phase, namingContextPlan) !== null;
|
|
1120
|
+
if ((!phaseDirPlan || customNamingPlan) && phaseNumberPlan && phaseNamePlan) {
|
|
1115
1121
|
const paddedNum = normalizePhaseName(phaseNumberPlan);
|
|
1116
|
-
const
|
|
1122
|
+
const expectedName = roadmapPhase?.['phase_name'] || phaseNamePlan;
|
|
1123
|
+
const slug = (generateSlugInternal(expectedName) || '').substring(0, 60);
|
|
1117
1124
|
if (slug) {
|
|
1118
|
-
const
|
|
1119
|
-
const dirName = `${prefix}${paddedNum}-${slug}`;
|
|
1125
|
+
const dirName = phaseDirectoryName(customPhaseQuery(phaseNumberPlan, namingContextPlan) ? phaseNumberPlan : paddedNum, slug, rawProjectCodePlan);
|
|
1120
1126
|
// #2376: absolute — see comment on phase_dir below.
|
|
1121
1127
|
expectedPhaseDirPlan = toPosixPath(node_path_1.default.join(planningPaths(cwd).phases, dirName));
|
|
1122
1128
|
}
|
|
@@ -1707,7 +1713,7 @@ function cmdInitVerifyWork(cwd, phase, raw) {
|
|
|
1707
1713
|
}
|
|
1708
1714
|
const config = loadConfig(cwd);
|
|
1709
1715
|
const _slashRuntime = (0, runtime_slash_cjs_1.resolveRuntime)(cwd);
|
|
1710
|
-
let phaseInfo = guardedFindPhase(cwd, phase, config.project_code);
|
|
1716
|
+
let phaseInfo = guardedFindPhase(cwd, phase, config.project_code, config.phase_naming);
|
|
1711
1717
|
const roadmapPhase = guardedGetRoadmapPhase(cwd, phase, config.project_code);
|
|
1712
1718
|
phaseInfo = applyRoadmapFallback(phaseInfo, roadmapPhase, (rp) => {
|
|
1713
1719
|
const phaseName = rp['phase_name'];
|
|
@@ -1814,7 +1820,7 @@ function cmdInitVerifyWork(cwd, phase, raw) {
|
|
|
1814
1820
|
*/
|
|
1815
1821
|
function cmdInitCodeReview(cwd, phase, raw, options = {}) {
|
|
1816
1822
|
const config = loadConfig(cwd);
|
|
1817
|
-
let phaseInfo = guardedFindPhase(cwd, phase, config.project_code);
|
|
1823
|
+
let phaseInfo = guardedFindPhase(cwd, phase, config.project_code, config.phase_naming);
|
|
1818
1824
|
const roadmapPhase = guardedGetRoadmapPhase(cwd, phase, config.project_code);
|
|
1819
1825
|
phaseInfo = applyRoadmapFallback(phaseInfo, roadmapPhase, (rp) => {
|
|
1820
1826
|
const rpName = rp['phase_name'];
|
|
@@ -1880,7 +1886,7 @@ function cmdInitCodeReview(cwd, phase, raw, options = {}) {
|
|
|
1880
1886
|
*/
|
|
1881
1887
|
function cmdInitReview(cwd, phase, raw, options = {}) {
|
|
1882
1888
|
const config = loadConfig(cwd);
|
|
1883
|
-
let phaseInfo = guardedFindPhase(cwd, phase, config.project_code);
|
|
1889
|
+
let phaseInfo = guardedFindPhase(cwd, phase, config.project_code, config.phase_naming);
|
|
1884
1890
|
const roadmapPhase = guardedGetRoadmapPhase(cwd, phase, config.project_code);
|
|
1885
1891
|
phaseInfo = applyRoadmapFallback(phaseInfo, roadmapPhase, (rp) => ({
|
|
1886
1892
|
found: true,
|
|
@@ -1939,7 +1945,7 @@ function cmdInitReview(cwd, phase, raw, options = {}) {
|
|
|
1939
1945
|
*/
|
|
1940
1946
|
function cmdInitDiscussPhaseAssumptions(cwd, phase, raw, options = {}) {
|
|
1941
1947
|
const config = loadConfig(cwd);
|
|
1942
|
-
let phaseInfo = guardedFindPhase(cwd, phase, config.project_code);
|
|
1948
|
+
let phaseInfo = guardedFindPhase(cwd, phase, config.project_code, config.phase_naming);
|
|
1943
1949
|
const roadmapPhase = guardedGetRoadmapPhase(cwd, phase, config.project_code);
|
|
1944
1950
|
phaseInfo = applyRoadmapFallback(phaseInfo, roadmapPhase, (rp) => {
|
|
1945
1951
|
const rpName = rp['phase_name'];
|
|
@@ -1995,7 +2001,7 @@ function cmdInitDiscussPhaseAssumptions(cwd, phase, raw, options = {}) {
|
|
|
1995
2001
|
}
|
|
1996
2002
|
function cmdInitPhaseOp(cwd, phase, raw) {
|
|
1997
2003
|
const config = loadConfig(cwd);
|
|
1998
|
-
let phaseInfo = guardedFindPhase(cwd, phase, config.project_code);
|
|
2004
|
+
let phaseInfo = guardedFindPhase(cwd, phase, config.project_code, config.phase_naming);
|
|
1999
2005
|
// #2237: surface ambiguous phase-directory collisions instead of silently
|
|
2000
2006
|
// taking the first match when unrelated projects share a .planning/phases/ tree.
|
|
2001
2007
|
if (phaseInfo?.['ambiguous_matches']) {
|
|
@@ -2598,37 +2604,37 @@ function cmdInitManager(cwd, raw) {
|
|
|
2598
2604
|
}
|
|
2599
2605
|
const rawContent = node_fs_1.default.readFileSync(paths.roadmap, 'utf-8');
|
|
2600
2606
|
const content = extractCurrentMilestone(rawContent, cwd);
|
|
2607
|
+
const directoryContext = phaseDirectoryOptions(cwd, config, rawContent);
|
|
2601
2608
|
// #3185 (ADR-3180 Decision 1): "which phase directories belong to the
|
|
2602
2609
|
// CURRENT milestone" is the scoped question listMilestonePhaseDirs owns —
|
|
2603
2610
|
// routed through it instead of a hand-rolled readdirSync + a separate
|
|
2604
2611
|
// getMilestonePhaseFilter window check (which also never excluded
|
|
2605
2612
|
// sentinels, unlike the owner).
|
|
2606
2613
|
const _checkboxStates = new Map();
|
|
2607
|
-
// #4982:
|
|
2608
|
-
// after the checkbox (tolerating only an optional `**`
|
|
2609
|
-
//
|
|
2610
|
-
//
|
|
2611
|
-
// own phase. First-match-wins (`if (!_checkboxStates.has(...))` below)
|
|
2614
|
+
// #4982: parsePhaseChecklistLine is line-anchored, so the phase label must
|
|
2615
|
+
// be the FIRST thing after the checkbox (tolerating only an optional `**`
|
|
2616
|
+
// bold marker), and a later "Phase N" in the line's prose cannot bind the
|
|
2617
|
+
// checkbox. First-match-wins (`if (!_checkboxStates.has(...))` below)
|
|
2612
2618
|
// additionally guards against a later anchored line (e.g. a per-plan
|
|
2613
2619
|
// sub-entry self-titled "Phase N ...") overwriting an already-recorded
|
|
2614
|
-
// phase's own (first) checkbox
|
|
2615
|
-
//
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
const _cbKey = _cbMatch[phaseGroup];
|
|
2621
|
-
if (!_checkboxStates.has(_cbKey)) {
|
|
2622
|
-
_checkboxStates.set(_cbKey, _cbMatch[1].toLowerCase() === 'x');
|
|
2620
|
+
// phase's own (first) checkbox; anchoring alone does not fully close that
|
|
2621
|
+
// "last-match-wins" composition defect.
|
|
2622
|
+
for (const line of content.split(/\r?\n/)) {
|
|
2623
|
+
const checkbox = parsePhaseChecklistLine(line, phaseIdConvention, directoryContext);
|
|
2624
|
+
if (checkbox && !_checkboxStates.has(checkbox.phaseToken)) {
|
|
2625
|
+
_checkboxStates.set(checkbox.phaseToken, checkbox.checked);
|
|
2623
2626
|
}
|
|
2624
2627
|
}
|
|
2625
2628
|
// #1729: `(?:\s*\([^)\n]{0,200}\))?` tolerates a pre-colon ( ) tag (literal mirror of OPTIONAL_PHASE_TAG_SOURCE).
|
|
2626
|
-
const phasePattern = new RegExp(`#{2,4}\\s*${phaseHeadingPrefix}(${
|
|
2629
|
+
const phasePattern = new RegExp(`#{2,4}\\s*${phaseHeadingPrefix}(${phaseScanTokenSource(directoryContext, phaseIdConvention)})(?:\\s*\\([^)\\n]{0,200}\\))?\\s*:\\s*([^\\n]+)`, 'gi');
|
|
2627
2630
|
const phases = [];
|
|
2631
|
+
const bracketIdsByPhaseNumber = new Map();
|
|
2628
2632
|
let match;
|
|
2629
2633
|
while ((match = phasePattern.exec(content)) !== null) {
|
|
2630
2634
|
const bracketId = capturesBracketId ? match[1] : undefined;
|
|
2631
2635
|
const phaseNum = capturesBracketId ? match[2] : match[1];
|
|
2636
|
+
if (bracketId)
|
|
2637
|
+
bracketIdsByPhaseNumber.set(phaseNum, bracketId);
|
|
2632
2638
|
const phaseName = (capturesBracketId ? match[3] : match[2])
|
|
2633
2639
|
.replace(/\(INSERTED\)/i, '')
|
|
2634
2640
|
.trim();
|
|
@@ -2644,7 +2650,7 @@ function cmdInitManager(cwd, raw) {
|
|
|
2644
2650
|
}
|
|
2645
2651
|
const sectionStart = match.index;
|
|
2646
2652
|
const restOfContent = content.slice(sectionStart);
|
|
2647
|
-
const nextHeader = restOfContent.match(new RegExp(`\\n#{2,4}\\s+${phaseHeadingPrefixNoCapture}
|
|
2653
|
+
const nextHeader = restOfContent.match(new RegExp(`\\n#{2,4}\\s+${phaseHeadingPrefixNoCapture}${phaseScanTokenSource(directoryContext, phaseIdConvention)}`, 'i'));
|
|
2648
2654
|
const sectionEnd = nextHeader
|
|
2649
2655
|
? sectionStart + nextHeader.index
|
|
2650
2656
|
: content.length;
|
|
@@ -2674,7 +2680,7 @@ function cmdInitManager(cwd, raw) {
|
|
|
2674
2680
|
// of reporting no_directory/phase_complete:false. Five other init
|
|
2675
2681
|
// commands already route through this same primitive; this was the
|
|
2676
2682
|
// holdout private copy (#4793-family declared-owner drift).
|
|
2677
|
-
const located = findPhaseInternal(cwd, phaseNum, phaseIdConvention);
|
|
2683
|
+
const located = findPhaseInternal(cwd, phaseNum, phaseIdConvention, directoryContext);
|
|
2678
2684
|
const dirMatch = located && located['found']
|
|
2679
2685
|
? node_path_1.default.posix.basename(String(located['directory']))
|
|
2680
2686
|
: null;
|
|
@@ -2785,15 +2791,83 @@ function cmdInitManager(cwd, raw) {
|
|
|
2785
2791
|
.filter((p) => p['phase_complete'] === true)
|
|
2786
2792
|
.map((p) => normalizePhaseNumber(p['number'])));
|
|
2787
2793
|
const phaseMap = new Map(phases.map((p) => [normalizePhaseNumber(p['number']), p]));
|
|
2788
|
-
const
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2794
|
+
const bracketIdentityKey = (value) => {
|
|
2795
|
+
try {
|
|
2796
|
+
const id = parsePhaseId(value);
|
|
2797
|
+
const subphase = id.subphase ? `.${id.subphase}` : '';
|
|
2798
|
+
return bracketQualifiedKey(`${id.project}.${id.milestone}-${id.phase}${subphase}`, 'bracket');
|
|
2799
|
+
}
|
|
2800
|
+
catch {
|
|
2801
|
+
return null;
|
|
2802
|
+
}
|
|
2803
|
+
};
|
|
2804
|
+
const phaseByBracketIdentity = new Map();
|
|
2805
|
+
const bracketIdentityByPhaseNumber = new Map();
|
|
2806
|
+
const completedBracketIdentities = new Set();
|
|
2807
|
+
if (phaseIdConvention === 'bracket') {
|
|
2808
|
+
for (const phase of phases) {
|
|
2809
|
+
const phaseNumber = phase['number'];
|
|
2810
|
+
const bracketId = bracketIdsByPhaseNumber.get(phaseNumber);
|
|
2811
|
+
const canonicalPhaseToken = phaseToken(phaseNumber);
|
|
2812
|
+
// #4304: key each heading on the reader's canonical
|
|
2813
|
+
// token, not on a strict parse of its rendered display. An accepted
|
|
2814
|
+
// unpadded heading (`### [CK.02] 1:`) gets no display_id, because
|
|
2815
|
+
// parsePhaseId rejects `CK.02-1`, so it had no identity at all and a
|
|
2816
|
+
// checked `[CK.02] 01` row completed it while phase_complete was false.
|
|
2817
|
+
// phaseToken pads the number to `01`, and `CK.02-01` is the form
|
|
2818
|
+
// parsePhaseId accepts; canonical headings key exactly as before.
|
|
2819
|
+
const key = bracketId && canonicalPhaseToken
|
|
2820
|
+
? bracketIdentityKey(`${bracketId}-${canonicalPhaseToken}`)
|
|
2821
|
+
: null;
|
|
2822
|
+
if (!key)
|
|
2823
|
+
continue;
|
|
2824
|
+
bracketIdentityByPhaseNumber.set(normalizePhaseNumber(phaseNumber), key);
|
|
2825
|
+
phaseByBracketIdentity.set(key, phase);
|
|
2826
|
+
if (phase['phase_complete'] === true)
|
|
2827
|
+
completedBracketIdentities.add(key);
|
|
2828
|
+
}
|
|
2829
|
+
}
|
|
2830
|
+
if (phaseIdConvention === 'bracket') {
|
|
2831
|
+
// A checked historical row satisfies only its own qualified identity. In
|
|
2832
|
+
// particular, `[CK.01] 01` must not complete bare/current `[CK.02] 01`.
|
|
2833
|
+
for (const line of rawContent.split(/\r?\n/)) {
|
|
2834
|
+
const checkbox = parsePhaseChecklistLine(line, phaseIdConvention);
|
|
2835
|
+
if (!checkbox?.checked || !checkbox.bracketId)
|
|
2836
|
+
continue;
|
|
2837
|
+
const identity = `[${checkbox.bracketId}] ${checkbox.phaseToken}`;
|
|
2838
|
+
const key = bracketIdentityKey(identity);
|
|
2839
|
+
if (!key)
|
|
2840
|
+
continue;
|
|
2841
|
+
const current = phaseMap.get(normalizePhaseNumber(checkbox.phaseToken));
|
|
2842
|
+
const currentKey = current
|
|
2843
|
+
? bracketIdentityByPhaseNumber.get(normalizePhaseNumber(current['number'])) ?? null
|
|
2844
|
+
: null;
|
|
2845
|
+
if (!current || currentKey !== key || current['phase_complete'] === true) {
|
|
2846
|
+
completedBracketIdentities.add(key);
|
|
2847
|
+
}
|
|
2795
2848
|
}
|
|
2796
2849
|
}
|
|
2850
|
+
else {
|
|
2851
|
+
// Preserve the historical greedy non-bracket fallback byte-for-byte.
|
|
2852
|
+
const _allCompletedPattern = (0, phase_entry_grammar_cjs_1.phaseEntryRegExp)(`.*${phaseHeadingPrefixNoCapture}(${phaseNumberTokenSource(phaseIdConvention)})[:\\s]`, 'gi');
|
|
2853
|
+
let _allMatch;
|
|
2854
|
+
while ((_allMatch = _allCompletedPattern.exec(rawContent)) !== null) {
|
|
2855
|
+
if (!(0, phase_entry_grammar_cjs_1.isPhaseEntryChecked)(_allMatch[0]))
|
|
2856
|
+
continue;
|
|
2857
|
+
const phaseNum = normalizePhaseNumber(_allMatch[1]);
|
|
2858
|
+
const phase = phaseMap.get(phaseNum);
|
|
2859
|
+
if (!phase || phase['phase_complete'] === true) {
|
|
2860
|
+
completedNums.add(phaseNum);
|
|
2861
|
+
}
|
|
2862
|
+
}
|
|
2863
|
+
}
|
|
2864
|
+
const currentDependencyNumber = (dependency) => {
|
|
2865
|
+
const identity = bracketIdentityKey(dependency);
|
|
2866
|
+
if (!identity)
|
|
2867
|
+
return normalizePhaseNumber(dependency);
|
|
2868
|
+
const current = phaseByBracketIdentity.get(identity);
|
|
2869
|
+
return current ? normalizePhaseNumber(current['number']) : null;
|
|
2870
|
+
};
|
|
2797
2871
|
function reaches(from, to, visited = new Set()) {
|
|
2798
2872
|
const normalizedFrom = normalizePhaseNumber(from);
|
|
2799
2873
|
const normalizedTo = normalizePhaseNumber(to);
|
|
@@ -2803,15 +2877,18 @@ function cmdInitManager(cwd, raw) {
|
|
|
2803
2877
|
const p = phaseMap.get(normalizedFrom);
|
|
2804
2878
|
if (!p || !p['dep_phases'] || p['dep_phases'].length === 0)
|
|
2805
2879
|
return false;
|
|
2806
|
-
if (p['dep_phases'].some((dep) =>
|
|
2880
|
+
if (p['dep_phases'].some((dep) => currentDependencyNumber(dep) === normalizedTo)) {
|
|
2807
2881
|
return true;
|
|
2808
2882
|
}
|
|
2809
|
-
return p['dep_phases'].some((dep) =>
|
|
2883
|
+
return p['dep_phases'].some((dep) => {
|
|
2884
|
+
const dependencyNumber = currentDependencyNumber(dep);
|
|
2885
|
+
return dependencyNumber === null ? false : reaches(dependencyNumber, to, visited);
|
|
2886
|
+
});
|
|
2810
2887
|
}
|
|
2811
2888
|
function hasDepRelationship(numA, numB) {
|
|
2812
2889
|
return reaches(numA, numB) || reaches(numB, numA);
|
|
2813
2890
|
}
|
|
2814
|
-
// #4764: a phase reference in depends_on prose is a PHASE-SHAPED token in
|
|
2891
|
+
// #4764/#4304: a phase reference in depends_on prose is a PHASE-SHAPED token in
|
|
2815
2892
|
// context — directly following "Phase"/"Phases" — never a bare digit run.
|
|
2816
2893
|
// The previous whole-field scrape matched the token grammar against every
|
|
2817
2894
|
// digit run, so calendar dates ("2026-09-14" → 2026, 09, 14), git shas
|
|
@@ -2824,9 +2901,27 @@ function cmdInitManager(cwd, raw) {
|
|
|
2824
2901
|
// "Phase 1-3") — silently dropping a REAL dependency would clear
|
|
2825
2902
|
// deps_satisfied prematurely, the dangerous direction. Negation prose
|
|
2826
2903
|
// ("dropped the dependency on Phase 654") is NOT detected: the issue's own
|
|
2827
|
-
// minimum keeps such tokens.
|
|
2828
|
-
|
|
2829
|
-
|
|
2904
|
+
// minimum keeps such tokens. Bracket repositories route through the same
|
|
2905
|
+
// phase-id owner so `[CK.02] 01` retains its complete qualified identity;
|
|
2906
|
+
// bare `01` still resolves against the active milestone. This prevents a
|
|
2907
|
+
// checked historical `[CK.01] 01` from satisfying incomplete `[CK.02] 01`.
|
|
2908
|
+
// Non-bracket extraction remains this manager surface's exact #4764 code
|
|
2909
|
+
// path below, including its case-insensitive token regex. Planning-inspect's
|
|
2910
|
+
// old token regex was case-sensitive, so forcing both through one widened
|
|
2911
|
+
// helper changed its `Phase 1a` bytes from `1` to `1a`.
|
|
2912
|
+
const legacyManagerDependencyTokens = (prose) => {
|
|
2913
|
+
const depPhaseRefRe = new RegExp(`${PHASE_DEP_REF_SOURCE}`, 'gi');
|
|
2914
|
+
const depTokenRe = new RegExp(`${PHASE_NUMBER_TOKEN_SOURCE}`, 'gi');
|
|
2915
|
+
const tokens = [];
|
|
2916
|
+
let refMatch;
|
|
2917
|
+
while ((refMatch = depPhaseRefRe.exec(prose)) !== null) {
|
|
2918
|
+
let tokenMatch;
|
|
2919
|
+
depTokenRe.lastIndex = 0;
|
|
2920
|
+
while ((tokenMatch = depTokenRe.exec(refMatch[1])) !== null)
|
|
2921
|
+
tokens.push(tokenMatch[0]);
|
|
2922
|
+
}
|
|
2923
|
+
return tokens;
|
|
2924
|
+
};
|
|
2830
2925
|
for (const phase of phases) {
|
|
2831
2926
|
if (!phase['depends_on'] ||
|
|
2832
2927
|
/^none$/i.test(phase['depends_on'].trim())) {
|
|
@@ -2835,24 +2930,39 @@ function cmdInitManager(cwd, raw) {
|
|
|
2835
2930
|
else {
|
|
2836
2931
|
const prose = phase['depends_on'];
|
|
2837
2932
|
const ownNumber = normalizePhaseNumber(phase['number']);
|
|
2933
|
+
const ownIdentity = typeof phase['display_id'] === 'string'
|
|
2934
|
+
? bracketIdentityKey(phase['display_id'])
|
|
2935
|
+
: null;
|
|
2838
2936
|
const depNums = [];
|
|
2839
2937
|
const seen = new Set();
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
if (seen.has(normalized))
|
|
2938
|
+
const dependencyTokens = phaseIdConvention === 'bracket'
|
|
2939
|
+
? extractPhaseDependencyTokens(prose, phaseIdConvention)
|
|
2940
|
+
: legacyManagerDependencyTokens(prose);
|
|
2941
|
+
for (const token of dependencyTokens) {
|
|
2942
|
+
const identity = bracketIdentityKey(token);
|
|
2943
|
+
if (identity) {
|
|
2944
|
+
if (identity === ownIdentity)
|
|
2945
|
+
continue;
|
|
2946
|
+
if (seen.has(identity))
|
|
2850
2947
|
continue;
|
|
2851
|
-
seen.add(
|
|
2852
|
-
depNums.push(
|
|
2948
|
+
seen.add(identity);
|
|
2949
|
+
depNums.push(token);
|
|
2950
|
+
continue;
|
|
2853
2951
|
}
|
|
2952
|
+
const normalized = normalizePhaseNumber(token);
|
|
2953
|
+
if (normalized === ownNumber)
|
|
2954
|
+
continue; // #4764: never the row's own phase
|
|
2955
|
+
if (seen.has(normalized))
|
|
2956
|
+
continue;
|
|
2957
|
+
seen.add(normalized);
|
|
2958
|
+
depNums.push(token);
|
|
2854
2959
|
}
|
|
2855
|
-
phase['deps_satisfied'] = depNums.every((
|
|
2960
|
+
phase['deps_satisfied'] = depNums.every((dependency) => {
|
|
2961
|
+
const identity = bracketIdentityKey(dependency);
|
|
2962
|
+
return identity
|
|
2963
|
+
? completedBracketIdentities.has(identity)
|
|
2964
|
+
: completedNums.has(normalizePhaseNumber(dependency));
|
|
2965
|
+
});
|
|
2856
2966
|
phase['dep_phases'] = depNums;
|
|
2857
2967
|
}
|
|
2858
2968
|
}
|
|
@@ -3316,19 +3426,20 @@ function cmdInitProgress(cwd, raw, options = {}) {
|
|
|
3316
3426
|
`Available workstreams: ${_availableWorkstreams.join(', ')}`, ERROR_REASON.WORKSTREAM_MODE_NONE_ACTIVE);
|
|
3317
3427
|
}
|
|
3318
3428
|
const phasesDir = node_path_1.default.join(planningDir(cwd), 'phases');
|
|
3429
|
+
const directoryContext = phaseDirectoryOptions(cwd, config);
|
|
3319
3430
|
const phases = [];
|
|
3320
3431
|
let currentPhase = null;
|
|
3321
3432
|
let nextPhase = null;
|
|
3322
3433
|
const roadmapPhaseNums = new Set();
|
|
3323
3434
|
const roadmapPhaseNames = new Map();
|
|
3324
3435
|
const roadmapCheckboxStates = new Map();
|
|
3436
|
+
const progressConvention = resolvePhaseIdConvention(cwd);
|
|
3325
3437
|
try {
|
|
3326
3438
|
const roadmapContent = extractCurrentMilestone(node_fs_1.default.readFileSync(node_path_1.default.join(planningDir(cwd), 'ROADMAP.md'), 'utf-8'), cwd);
|
|
3327
|
-
const progressConvention = resolvePhaseIdConvention(cwd);
|
|
3328
3439
|
// #1729: `(?:\s*\([^)\n]{0,200}\))?` tolerates a pre-colon ( ) tag, owned by
|
|
3329
3440
|
// buildPhaseHeadingScanRegex (phase-id.cts) so this scan also recognizes
|
|
3330
3441
|
// bracket-convention headings instead of hand-rolling a literal `Phase\s+`.
|
|
3331
|
-
const { regex: headingPattern, phaseNumGroup: hNumGroup, phaseNameGroup: hNameGroup } = buildPhaseHeadingScanRegex(PHASE_HEADING_BASELINE.ANY_BRACKET, progressConvention);
|
|
3442
|
+
const { regex: headingPattern, phaseNumGroup: hNumGroup, phaseNameGroup: hNameGroup } = buildPhaseHeadingScanRegex(PHASE_HEADING_BASELINE.ANY_BRACKET, progressConvention, directoryContext);
|
|
3332
3443
|
let hm;
|
|
3333
3444
|
while ((hm = headingPattern.exec(roadmapContent)) !== null) {
|
|
3334
3445
|
roadmapPhaseNums.add(hm[hNumGroup]);
|
|
@@ -3361,12 +3472,12 @@ function cmdInitProgress(cwd, raw, options = {}) {
|
|
|
3361
3472
|
// matched (by absorbing the bracket text). This does NOT migrate the site
|
|
3362
3473
|
// onto phaseHeadingPrefixSrcFor / the bracket convention — that stays
|
|
3363
3474
|
// out of scope per the comment above (#4984 revert, pinned census).
|
|
3364
|
-
const cbPattern =
|
|
3475
|
+
const cbPattern = (0, phase_entry_grammar_cjs_1.phaseEntryRegExp)(`(?:\\[[^\\]\\n]{0,200}\\]\\s*)?Phase\\s+(${phaseScanTokenSource(directoryContext, progressConvention)})[:\\s]`, 'gim', { state: 'captured' });
|
|
3365
3476
|
let cbm;
|
|
3366
3477
|
while ((cbm = cbPattern.exec(roadmapContent)) !== null) {
|
|
3367
3478
|
const cbKey = cbm[2];
|
|
3368
3479
|
if (!roadmapCheckboxStates.has(cbKey)) {
|
|
3369
|
-
roadmapCheckboxStates.set(cbKey, cbm[1]
|
|
3480
|
+
roadmapCheckboxStates.set(cbKey, (0, phase_entry_grammar_cjs_1.isBoxCharChecked)(cbm[1]));
|
|
3370
3481
|
}
|
|
3371
3482
|
}
|
|
3372
3483
|
}
|
|
@@ -3383,10 +3494,12 @@ function cmdInitProgress(cwd, raw, options = {}) {
|
|
|
3383
3494
|
// behavior-preserving).
|
|
3384
3495
|
const dirs = listMilestonePhaseDirs(phasesDir, { cwd }).value;
|
|
3385
3496
|
for (const dir of dirs) {
|
|
3386
|
-
const dirMatch = dir.match(new RegExp(`^(${
|
|
3387
|
-
const
|
|
3388
|
-
const
|
|
3389
|
-
|
|
3497
|
+
const dirMatch = dir.match(new RegExp(`^(${phaseScanTokenSource(directoryContext, progressConvention)})-?(.*)`, 'i'));
|
|
3498
|
+
const query = customPhaseQuery(dir, directoryContext);
|
|
3499
|
+
const owner = query && customDirectoryOwner(dir, query);
|
|
3500
|
+
const phaseNumber = owner ? owner.id : dirMatch ? dirMatch[1] : dir;
|
|
3501
|
+
const phaseName = owner ? owner.slug : dirMatch && dirMatch[2] ? dirMatch[2] : null;
|
|
3502
|
+
seenPhaseNums.add((phaseKeyFromToken(phaseNumber, directoryContext) ?? phaseNumber).toLowerCase());
|
|
3390
3503
|
const phasePath = node_path_1.default.join(phasesDir, dir);
|
|
3391
3504
|
const phaseFiles = node_fs_1.default.readdirSync(phasePath);
|
|
3392
3505
|
// #5060: Phase Status Module — the single owner of "what state is
|
|
@@ -3431,7 +3544,7 @@ function cmdInitProgress(cwd, raw, options = {}) {
|
|
|
3431
3544
|
/* otherwise intentionally empty */
|
|
3432
3545
|
}
|
|
3433
3546
|
for (const [num, name] of roadmapPhaseNames) {
|
|
3434
|
-
const stripped = num
|
|
3547
|
+
const stripped = (phaseKeyFromToken(num, directoryContext) ?? num).toLowerCase();
|
|
3435
3548
|
if (!seenPhaseNums.has(stripped)) {
|
|
3436
3549
|
const checkboxComplete = roadmapCheckboxStates.get(num) === true ||
|
|
3437
3550
|
roadmapCheckboxStates.get(stripped) === true;
|
|
@@ -3461,7 +3574,7 @@ function cmdInitProgress(cwd, raw, options = {}) {
|
|
|
3461
3574
|
}
|
|
3462
3575
|
}
|
|
3463
3576
|
}
|
|
3464
|
-
phases.sort((a, b) => comparePhaseNum(a['number'], b['number']));
|
|
3577
|
+
phases.sort((a, b) => comparePhaseNum(a['number'], b['number'], directoryContext));
|
|
3465
3578
|
// #3581: the frontier is ROADMAP ORDER, not artifact presence. The disk loop
|
|
3466
3579
|
// above could claim nextPhase from a stray out-of-order artifact directory
|
|
3467
3580
|
// (a phase-9 UAT evidence dir while roadmap phase 8 was pending and
|
|
@@ -229,11 +229,8 @@ const USER_OWNED_ARTIFACTS = ['USER-PROFILE.md'];
|
|
|
229
229
|
* with the computed pathPrefix for the install.
|
|
230
230
|
*/
|
|
231
231
|
function applyOpencodeFamilyPathPrefix(content, runtime, pathPrefix) {
|
|
232
|
-
|
|
233
|
-
content =
|
|
234
|
-
content = content.replace(/\.\/\.claude\//g, `./${getDirName(runtime)}/`);
|
|
235
|
-
content = content.replace(/~\/\.opencode\//g, pathPrefix);
|
|
236
|
-
content = content.replace(/~\/\.kilo\//g, pathPrefix);
|
|
232
|
+
// #5715: one pass with literal replacement (see rewriteOpencodeFamilyConfigDirForms).
|
|
233
|
+
content = runtimeArtifactConversion._rewriteOpencodeFamilyConfigDirForms(content, pathPrefix, getDirName(runtime));
|
|
237
234
|
// #5637: the typed-text capture heredoc ends at a terminator the typed text cannot know.
|
|
238
235
|
return (0, args_terminator_cjs_1.stampInstalledContent)(content);
|
|
239
236
|
}
|
|
@@ -1653,7 +1650,8 @@ function installOpencodeFamilySkills(runtime, targetDir, rawCommandsDir, pathPre
|
|
|
1653
1650
|
let content = installFs().readFileSync(node_path_1.default.join(rawDir, entry.name), 'utf8');
|
|
1654
1651
|
content = applyOpencodeFamilyPathPrefix(content, runtime, pathPrefix);
|
|
1655
1652
|
content = processAttribution(content, resolveAttribution(runtime));
|
|
1656
|
-
|
|
1653
|
+
// #5715: the converter runs after the prefix pass; shield the emitted prefix from it.
|
|
1654
|
+
content = runtimeArtifactConversion._shieldEmittedPrefix(content, pathPrefix, (c) => converter(c, skillName));
|
|
1657
1655
|
const skillDir = node_path_1.default.join(dest, skillName);
|
|
1658
1656
|
installFs().mkdirSync(skillDir, { recursive: true });
|
|
1659
1657
|
installFs().writeFileSync(node_path_1.default.join(skillDir, 'SKILL.md'), content);
|
|
@@ -1881,9 +1879,11 @@ function installOpencodeFamilyCommands(runtime, destDir, srcDir, pathPrefix, res
|
|
|
1881
1879
|
// already satisfies the fold-to-descriptor requirement. Only the SKILLS
|
|
1882
1880
|
// converter site above (installOpencodeFamilySkills) has a real
|
|
1883
1881
|
// `converter` string to key on via SKILLS_CONVERTER_REGISTRY.
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1882
|
+
// #5715: the converter runs after the prefix pass; shield the emitted prefix from it.
|
|
1883
|
+
const frontmatterConverter = hostBehaviorsFor(runtime).frontmatterDialect === 'kilo'
|
|
1884
|
+
? (c) => runtimeArtifactConversion.convertClaudeToKiloFrontmatter(c)
|
|
1885
|
+
: (c) => runtimeArtifactConversion.convertClaudeToOpencodeFrontmatter(c);
|
|
1886
|
+
content = runtimeArtifactConversion._shieldEmittedPrefix(content, pathPrefix, frontmatterConverter);
|
|
1887
1887
|
installFs().writeFileSync(node_path_1.default.join(destDir, destName), content);
|
|
1888
1888
|
}
|
|
1889
1889
|
}
|