@opengsd/gsd-core 1.19.0 → 1.20.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/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/commands/gsd/update.md +5 -2
- package/gsd-core/bin/gsd-tools.cjs +73 -23
- 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 +27 -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/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 +180 -67
- 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 +609 -35
- 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 +2990 -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 +730 -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/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 +39 -10
- package/gsd-core/bin/lib/state.cjs +498 -179
- 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 +26 -20
- 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-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/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/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-update/SKILL.md +4 -1
- 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 +6 -0
- package/scripts/lib/platform-conformance-tier.generated.cjs +11 -0
- package/scripts/lint-phase-enumeration-drift.cjs +8 -4
- package/scripts/lint-roadmap-progress-drift.cjs +421 -0
- package/scripts/mutation-matrix.cjs +23 -1
- package/scripts/sync-next-version.cjs +38 -6
- package/skills/gsd-update/SKILL.md +5 -2
- package/vscode/package.json +1 -1
|
@@ -15,7 +15,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
15
15
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
16
16
|
};
|
|
17
17
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
-
exports.PATH_ACTION_REASON = void 0;
|
|
18
|
+
exports.scrubGitLocationEnv = exports.GIT_LOCATION_ENV_VARS = exports.PATH_ACTION_REASON = void 0;
|
|
19
19
|
exports.toPosixPath = toPosixPath;
|
|
20
20
|
exports.toNativePath = toNativePath;
|
|
21
21
|
exports.posixNormalize = posixNormalize;
|
|
@@ -37,6 +37,7 @@ exports.projectCodexHookTomlCommand = projectCodexHookTomlCommand;
|
|
|
37
37
|
exports.escapePowerShellSingleQuoted = escapePowerShellSingleQuoted;
|
|
38
38
|
exports.escapePosixDoubleQuoted = escapePosixDoubleQuoted;
|
|
39
39
|
exports.escapeSingleQuotedShellLiteral = escapeSingleQuotedShellLiteral;
|
|
40
|
+
exports.shellSingleQuote = shellSingleQuote;
|
|
40
41
|
exports.projectPathExportLine = projectPathExportLine;
|
|
41
42
|
exports.renderShellActionLines = renderShellActionLines;
|
|
42
43
|
exports.projectPathActionProjection = projectPathActionProjection;
|
|
@@ -64,6 +65,7 @@ const node_fs_1 = __importDefault(require("node:fs"));
|
|
|
64
65
|
// at load time and become un-mockable.
|
|
65
66
|
const node_child_process_1 = __importDefault(require("node:child_process"));
|
|
66
67
|
const pattern_cjs_1 = require("./pattern.cjs");
|
|
68
|
+
const markdown_sectionizer_cjs_1 = require("./markdown-sectionizer.cjs");
|
|
67
69
|
const frontmatter_fence_cjs_1 = require("./frontmatter-fence.cjs");
|
|
68
70
|
const markdown_line_diff_cjs_1 = require("./markdown-line-diff.cjs");
|
|
69
71
|
const runtime_name_policy_cjs_1 = require("./runtime-name-policy.cjs");
|
|
@@ -458,6 +460,18 @@ function escapePosixDoubleQuoted(value) {
|
|
|
458
460
|
function escapeSingleQuotedShellLiteral(value) {
|
|
459
461
|
return String(value).replace(/'/g, "'\\''");
|
|
460
462
|
}
|
|
463
|
+
/**
|
|
464
|
+
* #1460 / #4039: POSIX single-quote an arbitrary string as ONE shell token. A path such as a
|
|
465
|
+
* hook script's absolute install location commonly sits under a home directory containing
|
|
466
|
+
* spaces or other metacharacters (e.g. "/Users/Bob Smith/.claude/..."); written unquoted it
|
|
467
|
+
* would word-split and, with a hostile prefix, could inject. Wrapping in single quotes — with
|
|
468
|
+
* each embedded `'` escaped as `'\''` by escapeSingleQuotedShellLiteral — makes the whole value a
|
|
469
|
+
* single token no metacharacter inside it can break. This is the one implementation of that rule;
|
|
470
|
+
* do not re-implement it in a caller.
|
|
471
|
+
*/
|
|
472
|
+
function shellSingleQuote(value) {
|
|
473
|
+
return "'" + escapeSingleQuotedShellLiteral(value) + "'";
|
|
474
|
+
}
|
|
461
475
|
/**
|
|
462
476
|
* The `export PATH="<dir>:$PATH"` line every persistence lane appends, plus the escaped directory
|
|
463
477
|
* token it embeds. One builder because three lanes emit this line: a lane that re-escapes it
|
|
@@ -615,6 +629,10 @@ function _spawnResult(result, program) {
|
|
|
615
629
|
timedOut: isSpawnTimeout({ error }),
|
|
616
630
|
};
|
|
617
631
|
}
|
|
632
|
+
// #5548: owned by read-only-git-env.cts (its default base is scrubbed); re-exported for callers.
|
|
633
|
+
var read_only_git_env_cjs_2 = require("./read-only-git-env.cjs");
|
|
634
|
+
Object.defineProperty(exports, "GIT_LOCATION_ENV_VARS", { enumerable: true, get: function () { return read_only_git_env_cjs_2.GIT_LOCATION_ENV_VARS; } });
|
|
635
|
+
Object.defineProperty(exports, "scrubGitLocationEnv", { enumerable: true, get: function () { return read_only_git_env_cjs_2.scrubGitLocationEnv; } });
|
|
618
636
|
function execGit(args, opts = {}) {
|
|
619
637
|
// Non-interactive defaults: a hung credential prompt or terminal-input
|
|
620
638
|
// probe must surface as a timeout, not block the tool forever. Callers
|
|
@@ -1055,7 +1073,6 @@ function probeTty(opts = {}) {
|
|
|
1055
1073
|
return null;
|
|
1056
1074
|
}
|
|
1057
1075
|
}
|
|
1058
|
-
// ─── Platform file I/O ────────────────────────────────────────────────────────
|
|
1059
1076
|
/**
|
|
1060
1077
|
* How many leading lines of LF-only `text` are its closed YAML frontmatter block (opening
|
|
1061
1078
|
* fence through closing fence), or 0 when there is none (an unterminated block is not
|
|
@@ -1075,7 +1092,7 @@ function leadingFrontmatterLineCount(text) {
|
|
|
1075
1092
|
function withoutBom(line) {
|
|
1076
1093
|
return line !== undefined && line.charCodeAt(0) === 0xFEFF ? line.slice(1) : (line ?? '');
|
|
1077
1094
|
}
|
|
1078
|
-
function _normalizeMd(content, prior) {
|
|
1095
|
+
function _normalizeMd(content, prior, preserveFencedStructure = false) {
|
|
1079
1096
|
if (!content || typeof content !== 'string')
|
|
1080
1097
|
return content;
|
|
1081
1098
|
let text = content.replace(/\r\n/g, '\n');
|
|
@@ -1107,6 +1124,7 @@ function _normalizeMd(content, prior) {
|
|
|
1107
1124
|
};
|
|
1108
1125
|
const fenceRegex = /^```/;
|
|
1109
1126
|
const insideFence = new Array(lines.length);
|
|
1127
|
+
const insideSharedFence = new Array(lines.length).fill(false);
|
|
1110
1128
|
let fenceOpen = false;
|
|
1111
1129
|
for (let i = 0; i < lines.length; i++) {
|
|
1112
1130
|
if (i < frontmatterLines) {
|
|
@@ -1127,6 +1145,13 @@ function _normalizeMd(content, prior) {
|
|
|
1127
1145
|
insideFence[i] = fenceOpen;
|
|
1128
1146
|
}
|
|
1129
1147
|
}
|
|
1148
|
+
if (preserveFencedStructure) {
|
|
1149
|
+
for (const block of (0, markdown_sectionizer_cjs_1.scanFencedBlocks)(lines)) {
|
|
1150
|
+
const end = block.closeLineIdx === -1 ? lines.length : block.closeLineIdx;
|
|
1151
|
+
for (let i = block.openLineIdx + 1; i < end; i++)
|
|
1152
|
+
insideSharedFence[i] = true;
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1130
1155
|
for (let i = 0; i < lines.length; i++) {
|
|
1131
1156
|
const line = lines[i];
|
|
1132
1157
|
if (i < frontmatterLines) {
|
|
@@ -1137,6 +1162,8 @@ function _normalizeMd(content, prior) {
|
|
|
1137
1162
|
const prevTrimmed = prev.trimEnd();
|
|
1138
1163
|
const trimmed = line.trimEnd();
|
|
1139
1164
|
const isFenceLine = fenceRegex.test(trimmed);
|
|
1165
|
+
// Bracket writers preserve literal headings and lists inside fenced examples.
|
|
1166
|
+
const normalizeStructuralLine = !preserveFencedStructure || !insideSharedFence[i];
|
|
1140
1167
|
// A heading or fence line the write authored brings its own separation, even
|
|
1141
1168
|
// where it replaced a prose line in place (#5165). One that replaced a heading
|
|
1142
1169
|
// or fence (a rename, a renumber) inherits the spacing around it instead.
|
|
@@ -1149,7 +1176,7 @@ function _normalizeMd(content, prior) {
|
|
|
1149
1176
|
// A blank-line run is collapsed only where the write made it (#5165).
|
|
1150
1177
|
if (scope !== null && line === '' && result.length > 0 && result[result.length - 1] === '' && !keptBefore)
|
|
1151
1178
|
continue;
|
|
1152
|
-
if (/^#{1,6}\s/.test(trimmed) && i > 0 && prevTrimmed !== '' && prevTrimmed !== '---' && !keptBefore)
|
|
1179
|
+
if (normalizeStructuralLine && /^#{1,6}\s/.test(trimmed) && i > 0 && prevTrimmed !== '' && prevTrimmed !== '---' && !keptBefore)
|
|
1153
1180
|
blank();
|
|
1154
1181
|
if (isFenceLine && i > 0 && prevTrimmed !== '' && !insideFence[i] && (i === 0 || !insideFence[i - 1] || isFenceLine) && !keptBefore) {
|
|
1155
1182
|
if (i === 0 || !insideFence[i - 1])
|
|
@@ -1165,11 +1192,11 @@ function _normalizeMd(content, prior) {
|
|
|
1165
1192
|
// from the after-heading rule below. The after-a-bullet rule at the end
|
|
1166
1193
|
// of this loop is a different transition (list→prose) and is unaffected.
|
|
1167
1194
|
result.push(line);
|
|
1168
|
-
if (/^#{1,6}\s/.test(trimmed) && i < lines.length - 1 && (lines[i + 1] ?? '').trimEnd() !== '' && !keptAfter)
|
|
1195
|
+
if (normalizeStructuralLine && /^#{1,6}\s/.test(trimmed) && i < lines.length - 1 && (lines[i + 1] ?? '').trimEnd() !== '' && !keptAfter)
|
|
1169
1196
|
blank();
|
|
1170
1197
|
if (/^```\s*$/.test(trimmed) && i > 0 && insideFence[i - 1] && i < lines.length - 1 && (lines[i + 1] ?? '').trimEnd() !== '' && !keptAfter)
|
|
1171
1198
|
blank();
|
|
1172
|
-
if (/^(\s*[-*+]\s|\s*\d+\.\s)/.test(line) && i < lines.length - 1 && !keptAfter) {
|
|
1199
|
+
if (normalizeStructuralLine && /^(\s*[-*+]\s|\s*\d+\.\s)/.test(line) && i < lines.length - 1 && !keptAfter) {
|
|
1173
1200
|
const next = lines[i + 1];
|
|
1174
1201
|
if (next !== undefined && next.trimEnd() !== '' && !/^(\s*[-*+]\s|\s*\d+\.\s)/.test(next) && !/^\s/.test(next))
|
|
1175
1202
|
blank();
|
|
@@ -1190,7 +1217,7 @@ function normalizeContent(filePath, content, opts = {}) {
|
|
|
1190
1217
|
const isMd = node_path_1.default.extname(filePath).toLowerCase() === '.md';
|
|
1191
1218
|
let normalized;
|
|
1192
1219
|
if (isMd) {
|
|
1193
|
-
normalized = _normalizeMd(content, opts.prior);
|
|
1220
|
+
normalized = _normalizeMd(content, opts.prior, opts.preserveFencedMarkdownStructure === true);
|
|
1194
1221
|
}
|
|
1195
1222
|
else {
|
|
1196
1223
|
normalized = (content ?? '').replace(/\r\n/g, '\n').replace(/\n*$/, '\n');
|
|
@@ -1211,8 +1238,8 @@ function normalizeContent(filePath, content, opts = {}) {
|
|
|
1211
1238
|
* post-write re-read of the actual on-disk bytes) instead of a raw `!==` —
|
|
1212
1239
|
* do not simplify this back to a direct string comparison.
|
|
1213
1240
|
*/
|
|
1214
|
-
function contentChangedAfterNormalize(filePath, before, after) {
|
|
1215
|
-
return normalizeContent(filePath, after, { prior: before }).content !== normalizeContent(filePath, before, { prior: before }).content;
|
|
1241
|
+
function contentChangedAfterNormalize(filePath, before, after, opts = {}) {
|
|
1242
|
+
return normalizeContent(filePath, after, { ...opts, prior: before }).content !== normalizeContent(filePath, before, { ...opts, prior: before }).content;
|
|
1216
1243
|
}
|
|
1217
1244
|
// Rename errnos that are transient on Windows: a concurrent reader (or an AV
|
|
1218
1245
|
// scanner / indexer) holding the target open makes renameSync fail briefly.
|
|
@@ -69,11 +69,14 @@ const stateDocument = require("./state-document.cjs");
|
|
|
69
69
|
const { stateFieldValue } = stateDocument;
|
|
70
70
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
71
71
|
const phaseId = require("./phase-id.cjs");
|
|
72
|
-
const { comparePhaseNum, extractPhaseToken,
|
|
72
|
+
const { comparePhaseNum, extractPhaseToken, customPhaseQuery, customDirectoryOwner, parsePhaseFromProse, stripProjectCodePrefix } = phaseId;
|
|
73
73
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
74
74
|
const stateMod = require("./state.cjs");
|
|
75
75
|
const { readStateHeadFreshness } = stateMod;
|
|
76
76
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
77
|
+
const phaseLocatorMod = require("./phase-locator.cjs");
|
|
78
|
+
const { phaseDirectoryOptions, resolvePhaseDirectoryLookup, matchPhaseDirsForLookup } = phaseLocatorMod;
|
|
79
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
77
80
|
const unusableInput = require("./unusable-input.cjs");
|
|
78
81
|
const { warnUnusableInput, UNUSABLE_REASON } = unusableInput;
|
|
79
82
|
// #5118: the verification-status owner's frontmatter-only report check.
|
|
@@ -120,8 +123,8 @@ function parseIntOrNull(s) {
|
|
|
120
123
|
const n = Number.parseInt(cleaned, 10);
|
|
121
124
|
return Number.isFinite(n) ? n : null;
|
|
122
125
|
}
|
|
123
|
-
function phaseTokenFromDirName(name) {
|
|
124
|
-
const token = extractPhaseToken(name);
|
|
126
|
+
function phaseTokenFromDirName(name, convention) {
|
|
127
|
+
const token = extractPhaseToken(name, convention);
|
|
125
128
|
// #2528: the shape probe runs on the PROJECT-CODE-STRIPPED token. A prefixed
|
|
126
129
|
// directory tokenizes to `MEM-05-80-20`, which does not start with a digit,
|
|
127
130
|
// so the unstripped probe rejected it and the entry was dropped before any
|
|
@@ -252,9 +255,9 @@ function readGitSignals(cwd) {
|
|
|
252
255
|
const unpushed = run(['log', '@{u}..HEAD', '--oneline']).trim().length > 0;
|
|
253
256
|
return { has_git: true, dirty, unpushed };
|
|
254
257
|
}
|
|
255
|
-
/**
|
|
256
|
-
function phaseTokenFromState(raw) {
|
|
257
|
-
return parsePhaseFromProse(raw).phase;
|
|
258
|
+
/** Phase identity from a STATE.md scalar or body `Phase:` value, honoring custom naming when a context is supplied. */
|
|
259
|
+
function phaseTokenFromState(raw, context) {
|
|
260
|
+
return parsePhaseFromProse(raw, undefined, context).phase;
|
|
258
261
|
}
|
|
259
262
|
/**
|
|
260
263
|
* Detect whether the current phase's verify report indicates failure. The
|
|
@@ -264,12 +267,20 @@ function phaseTokenFromState(raw) {
|
|
|
264
267
|
*/
|
|
265
268
|
function detectVerifyFailed(cwd, currentPhaseRaw) {
|
|
266
269
|
const phasesDir = node_path_1.default.join(planningPaths(cwd).phases);
|
|
270
|
+
// #4304: both the directory admission scan and the later selector must read
|
|
271
|
+
// the same current-checkout grammar. Bracket is the only opt-in widening.
|
|
272
|
+
const convention = resolvePhaseIdConvention(cwd) === 'bracket' ? 'bracket' : undefined;
|
|
273
|
+
const directoryContext = phaseDirectoryOptions(cwd);
|
|
267
274
|
let entries = [];
|
|
268
275
|
try {
|
|
269
276
|
entries = node_fs_1.default.readdirSync(phasesDir)
|
|
270
|
-
.map((name) =>
|
|
277
|
+
.map((name) => {
|
|
278
|
+
const custom = customPhaseQuery(name, directoryContext);
|
|
279
|
+
const parts = custom && customDirectoryOwner(name, custom);
|
|
280
|
+
return { name, phaseToken: parts ? parts.id : phaseTokenFromDirName(name, convention) };
|
|
281
|
+
})
|
|
271
282
|
.filter((entry) => entry.phaseToken !== null)
|
|
272
|
-
.sort((a, b) => comparePhaseNum(a.phaseToken, b.phaseToken) || a.name.localeCompare(b.name))
|
|
283
|
+
.sort((a, b) => comparePhaseNum(a.phaseToken, b.phaseToken, directoryContext) || a.name.localeCompare(b.name))
|
|
273
284
|
.map((entry) => entry.name);
|
|
274
285
|
}
|
|
275
286
|
catch {
|
|
@@ -277,10 +288,10 @@ function detectVerifyFailed(cwd, currentPhaseRaw) {
|
|
|
277
288
|
}
|
|
278
289
|
if (entries.length === 0)
|
|
279
290
|
return false;
|
|
280
|
-
const phaseToken = phaseTokenFromState(currentPhaseRaw);
|
|
291
|
+
const phaseToken = phaseTokenFromState(currentPhaseRaw, directoryContext);
|
|
281
292
|
let targetDir;
|
|
282
293
|
if (phaseToken) {
|
|
283
|
-
const
|
|
294
|
+
const lookup = resolvePhaseDirectoryLookup(cwd, phaseToken, undefined, directoryContext);
|
|
284
295
|
// #2528: the fourth directory-resolution site, and the one where a miss is
|
|
285
296
|
// silent — a phase whose directory cannot be found reports "not failed",
|
|
286
297
|
// which reads identically to a healthy phase. It must therefore apply the
|
|
@@ -289,7 +300,7 @@ function detectVerifyFailed(cwd, currentPhaseRaw) {
|
|
|
289
300
|
// surfaces its own failed verification. `entries` is already sorted, and
|
|
290
301
|
// `matchPhaseDirs` filters without reordering, so taking the first match
|
|
291
302
|
// preserves the previous `.find()` selection exactly.
|
|
292
|
-
const { matches } =
|
|
303
|
+
const { matches } = matchPhaseDirsForLookup(entries, lookup, undefined, directoryContext);
|
|
293
304
|
targetDir = matches[0];
|
|
294
305
|
if (!targetDir)
|
|
295
306
|
return false;
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
* already answers:
|
|
9
9
|
* - `planning-workspace.cjs`'s `planningPaths` for every `.planning/` path
|
|
10
10
|
* (workstream-aware — never re-derived here).
|
|
11
|
-
* - `
|
|
11
|
+
* - `roadmap-progress.cjs`'s `locateRoadmapProgress` (#5761; was
|
|
12
|
+
* `phase-lifecycle`'s `locateProgressTable`) for
|
|
12
13
|
* "where is the Progress table" — the SAME table
|
|
13
14
|
* `deriveProgressFromRoadmap` counts from, so `phases[]` and
|
|
14
15
|
* `deriveProgressFromRoadmap`'s `totalPhases` can never disagree about
|
|
@@ -79,6 +80,7 @@ const node_fs_1 = __importDefault(require("node:fs"));
|
|
|
79
80
|
// documented `state.cjs -> state-contract.cjs -> smart-entry.cjs -> state.cjs`
|
|
80
81
|
// require cycle the file header warns about.
|
|
81
82
|
const phase_status_cjs_1 = require("./phase-status.cjs");
|
|
83
|
+
const phase_entry_grammar_cjs_1 = require("./phase-entry-grammar.cjs");
|
|
82
84
|
// ─── Public wire vocabulary ────────────────────────────────────────────────
|
|
83
85
|
exports.STATE_CONTRACT_VERSION = '1.0.0';
|
|
84
86
|
exports.STATE_CONTRACT_FLAVOR = 'core';
|
|
@@ -114,7 +116,7 @@ function owners() {
|
|
|
114
116
|
/* eslint-disable @typescript-eslint/no-require-imports */
|
|
115
117
|
const planningWorkspaceMod = require('./planning-workspace.cjs');
|
|
116
118
|
const shellCommandProjectionMod = require('./shell-command-projection.cjs');
|
|
117
|
-
const
|
|
119
|
+
const roadmapProgressMod = require('./roadmap-progress.cjs');
|
|
118
120
|
const phaseIdMod = require('./phase-id.cjs');
|
|
119
121
|
const roadmapParserMod = require('./roadmap-parser.cjs');
|
|
120
122
|
const markdownSectionizerMod = require('./markdown-sectionizer.cjs');
|
|
@@ -126,7 +128,7 @@ function owners() {
|
|
|
126
128
|
planningPaths: planningWorkspaceMod.planningPaths,
|
|
127
129
|
platformReadSync: shellCommandProjectionMod.platformReadSync,
|
|
128
130
|
platformWriteSync: shellCommandProjectionMod.platformWriteSync,
|
|
129
|
-
|
|
131
|
+
locateRoadmapProgress: roadmapProgressMod.locateRoadmapProgress,
|
|
130
132
|
isSentinelPhaseId: phaseIdMod.isSentinelPhaseId,
|
|
131
133
|
parsePhaseFromProse: phaseIdMod.parsePhaseFromProse,
|
|
132
134
|
resolvePhaseIdConvention: planningWorkspaceMod.resolvePhaseIdConvention,
|
|
@@ -188,9 +190,11 @@ function phasesFromProgressTable(table, convention) {
|
|
|
188
190
|
}
|
|
189
191
|
// ─── phases: `## Phases` checkbox fallback ─────────────────────────────────
|
|
190
192
|
// `- [x] **Phase 1: Foundation**` / `- [ ] **Phase 2.1: Hardening**`.
|
|
191
|
-
|
|
193
|
+
// The entry prefix (marker, box, bold token) is the phase-entry grammar's (#5764): capture
|
|
194
|
+
// group 1 is the box character, 2 the number, 3 the name.
|
|
195
|
+
const PHASE_BULLET_RE = (0, phase_entry_grammar_cjs_1.phaseEntryRegExp)(`Phase[ \\t]+(\\d+(?:\\.\\d+)*)[ \\t]*:?[ \\t]*([^*\\n]*)${phase_entry_grammar_cjs_1.PHASE_ENTRY_BOLD_CLOSE_SRC}`, 'g', { state: 'captured', bold: 'required' });
|
|
192
196
|
// #5626: the same bullet under `milestone-prefixed`: the number capture takes one optional `-NN` segment.
|
|
193
|
-
const PHASE_BULLET_MILESTONE_RE =
|
|
197
|
+
const PHASE_BULLET_MILESTONE_RE = (0, phase_entry_grammar_cjs_1.phaseEntryRegExp)(`Phase[ \\t]+(\\d+(?:-\\d+)?(?:\\.\\d+)*)[ \\t]*:?[ \\t]*([^*\\n]*)${phase_entry_grammar_cjs_1.PHASE_ENTRY_BOLD_CLOSE_SRC}`, 'g', { state: 'captured', bold: 'required' });
|
|
194
198
|
/**
|
|
195
199
|
* STATE.md's current phase, via the same frontmatter-then-body chain
|
|
196
200
|
* `cmdStateCompletePhase` uses (`stateFieldValue` owns the #1760 fallback
|
|
@@ -249,7 +253,7 @@ function phasesFromChecklist(roadmapContent, cwd, statePath, convention) {
|
|
|
249
253
|
return phases;
|
|
250
254
|
}
|
|
251
255
|
function buildPhases(cwd, paths) {
|
|
252
|
-
const { platformReadSync,
|
|
256
|
+
const { platformReadSync, locateRoadmapProgress } = owners();
|
|
253
257
|
try {
|
|
254
258
|
let content = platformReadSync(paths.roadmap);
|
|
255
259
|
if (content === null)
|
|
@@ -257,7 +261,7 @@ function buildPhases(cwd, paths) {
|
|
|
257
261
|
if (content.charCodeAt(0) === 0xfeff)
|
|
258
262
|
content = content.slice(1); // strip leading BOM
|
|
259
263
|
const convention = phaseIdConventionOf(cwd);
|
|
260
|
-
const table =
|
|
264
|
+
const { table } = locateRoadmapProgress(content);
|
|
261
265
|
if (table)
|
|
262
266
|
return phasesFromProgressTable(table, convention);
|
|
263
267
|
return phasesFromChecklist(content, cwd, paths.state, convention);
|