@opengsd/gsd-core 1.20.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/bin/install.js +36 -40
- package/commands/gsd/new-milestone.md +2 -2
- package/commands/gsd/new-project.md +2 -2
- package/gsd-core/bin/gsd-tools.cjs +104 -70
- package/gsd-core/bin/lib/capability-registry.cjs +64 -64
- package/gsd-core/bin/lib/commands.cjs +19 -0
- package/gsd-core/bin/lib/gsd-tool-dir.cjs +134 -0
- package/gsd-core/bin/lib/init.cjs +7 -7
- 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/phase-id.cjs +26 -9
- package/gsd-core/bin/lib/phase.cjs +64 -15
- package/gsd-core/bin/lib/planning-snapshot.cjs +2 -2
- package/gsd-core/bin/lib/roadmap-parser.cjs +29 -0
- 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/state-transition.cjs +12 -5
- package/gsd-core/bin/lib/state.cjs +2 -2
- package/gsd-core/bin/lib/workstream-inventory.cjs +4 -4
- package/gsd-core/bin/lib/worktree-base-ref.cjs +32 -2
- package/hooks/dist/gsd-agent-isolation-guard.js +1 -1
- package/hooks/dist/gsd-cursor-subagent-start.js +1 -1
- 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/lib/isolation-sentinel.js +77 -2
- package/package.json +1 -1
- package/pi/package-skills/gsd-new-milestone/SKILL.md +1 -1
- package/pi/package-skills/gsd-new-project/SKILL.md +1 -1
- package/scripts/changeset/lint.cjs +28 -3
- package/scripts/lib/macos-conformance-tier.generated.cjs +4 -0
- package/scripts/lib/platform-conformance-tier.generated.cjs +4 -0
- package/scripts/merge-commit-pr.cjs +186 -0
- 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 +11 -2
- package/skills/gsd-new-milestone/SKILL.md +1 -1
- package/skills/gsd-new-project/SKILL.md +1 -1
- package/vscode/package.json +1 -1
|
@@ -42,7 +42,7 @@ const coreUtilsMod = require("./core-utils.cjs");
|
|
|
42
42
|
const { toPosixPath, generateSlugInternal, readSubdirectories, extractCanonicalPlanId, findUnsummarizedPlans, } = coreUtilsMod;
|
|
43
43
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- phase-id.cjs is an export= CommonJS module
|
|
44
44
|
const phaseIdMod = require("./phase-id.cjs");
|
|
45
|
-
const { normalizePhaseName, phaseMarkdownRegexSource, comparePhaseNum, matchPhaseDirs, phaseLookupQuery, phaseNumberForMatch, phaseKeyFromDir, customPhaseDirectoryParts, customDirectoryOwner, phaseScanTokenSource, CUSTOM_PHASE_ID_FILE, isCustomPhaseId, isSentinelPhaseId, scopeToPhase, parsePhaseId, renderPhaseId, renderMilestoneId, toDir, phaseHeadingPrefixSrcFor, parsePhaseChecklistLine, PHASE_HEADING_BASELINE, OPTIONAL_PROJECT_CODE_PREFIX_SOURCE, OPTIONAL_PHASE_TAG_SOURCE, PHASE_NUMBER_TOKEN_SOURCE, buildPhaseDeclarationScans, BRACKET_DIR_PREFIX_SRC, tokenizePhaseDependencyReferences, foldBracketId, } = phaseIdMod;
|
|
45
|
+
const { normalizePhaseName, phaseMarkdownRegexSource, comparePhaseNum, matchPhaseDirs, phaseLookupQuery, phaseNumberForMatch, phaseKeyFromDir, customPhaseDirectoryParts, customDirectoryOwner, phaseScanTokenSource, phaseNumberTokenSource, CUSTOM_PHASE_ID_FILE, isCustomPhaseId, isSentinelPhaseId, scopeToPhase, parsePhaseId, renderPhaseId, renderMilestoneId, toDir, phaseHeadingPrefixSrcFor, parsePhaseChecklistLine, PHASE_HEADING_BASELINE, OPTIONAL_PROJECT_CODE_PREFIX_SOURCE, OPTIONAL_PHASE_TAG_SOURCE, PHASE_NUMBER_TOKEN_SOURCE, buildPhaseDeclarationScans, BRACKET_DIR_PREFIX_SRC, tokenizePhaseDependencyReferences, foldBracketId, } = phaseIdMod;
|
|
46
46
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
47
47
|
const phaseIdDisplayMod = require("./phase-id-display.cjs");
|
|
48
48
|
// #4304: reuse the Phase Id Display Module's milestone/phase
|
|
@@ -61,7 +61,10 @@ const planningScopeMod = require("./planning-scope.cjs");
|
|
|
61
61
|
const { SCOPE } = planningScopeMod;
|
|
62
62
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- roadmap-parser.cjs is an export= CommonJS module
|
|
63
63
|
const roadmapParserMod = require("./roadmap-parser.cjs");
|
|
64
|
-
const { stripShippedMilestones, extractCurrentMilestone, currentMilestoneRawRanges, withPhaseSection, editCurrentMilestone, findMilestoneScopeHeadingLines, getMilestoneInfo,
|
|
64
|
+
const { stripShippedMilestones, extractCurrentMilestone, currentMilestoneRawRanges, withPhaseSection, editCurrentMilestone, findMilestoneScopeHeadingLines, getMilestoneInfo,
|
|
65
|
+
// #5679: the STATE.md milestone read getMilestoneInfo itself uses, and the
|
|
66
|
+
// archive-aware owner of a ROADMAP offset.
|
|
67
|
+
readStateMilestoneScalar, classifyMilestoneScalar, milestoneVersionAtOffset, blankCodeAndComments, scanMilestonePhaseIds,
|
|
65
68
|
// #4304: every recognized milestone heading in the document,
|
|
66
69
|
// not just the active one — see bracketProgressSectionOwnedByOtherMilestone.
|
|
67
70
|
listMilestoneHeadings, selectMilestoneHeading,
|
|
@@ -496,6 +499,9 @@ function cmdFindPhase(cwd, phase, raw) {
|
|
|
496
499
|
const directoryContext = phaseDirectoryOptions(cwd);
|
|
497
500
|
const lookup = resolvePhaseDirectoryLookup(cwd, phase, undefined, directoryContext);
|
|
498
501
|
const { normalized, convention } = lookup;
|
|
502
|
+
// #5676: `lookup.convention` is only ever 'bracket'; the directory token grammar
|
|
503
|
+
// (`03-02-beta` -> `03-02` + `beta`) needs the repo's own convention.
|
|
504
|
+
const tokenConvention = resolvePhaseIdConvention(cwd);
|
|
499
505
|
const notFound = {
|
|
500
506
|
found: false,
|
|
501
507
|
directory: null,
|
|
@@ -561,7 +567,7 @@ function cmdFindPhase(cwd, phase, raw) {
|
|
|
561
567
|
&& BRACKET_DIRECTORY_PREFIX_RE.test(foldBracketId(match));
|
|
562
568
|
const dirMatch = isBracketDirectory
|
|
563
569
|
? null
|
|
564
|
-
: match.match(new RegExp(`^${OPTIONAL_PROJECT_CODE_PREFIX_SOURCE}(${
|
|
570
|
+
: match.match(new RegExp(`^${OPTIONAL_PROJECT_CODE_PREFIX_SOURCE}(${phaseNumberTokenSource(tokenConvention)})-?(.*)`, 'i')) || match.match(new RegExp(`^(${phaseNumberTokenSource(tokenConvention)})-?(.*)`, 'i'));
|
|
565
571
|
const customParts = custom ? customPhaseDirectoryParts(match, custom) : null;
|
|
566
572
|
const phaseNumber = isBracketDirectory
|
|
567
573
|
? phaseNumberForMatch(match, usedBareFallback, convention)
|
|
@@ -2187,6 +2193,34 @@ function isPhaseTokenWithin(token, ancestor) {
|
|
|
2187
2193
|
const canon = (segment) => segment.replace(/^0+(?=\d)/, '').toLowerCase();
|
|
2188
2194
|
return a.every((segment, i) => canon(segment) === canon(t[i]));
|
|
2189
2195
|
}
|
|
2196
|
+
/**
|
|
2197
|
+
* #5679: the `phase insert` error for a phase that exists in the ROADMAP but
|
|
2198
|
+
* outside the current milestone. Names the milestone that holds it (archive
|
|
2199
|
+
* aware, never the current one) and the current milestone, saying "STATE.md's"
|
|
2200
|
+
* only when STATE.md's `milestone:` is where that version came from.
|
|
2201
|
+
*/
|
|
2202
|
+
function otherMilestonePhaseMessage(rawContent, foundAt, cwd, afterPhase) {
|
|
2203
|
+
let stateVersion = null;
|
|
2204
|
+
try {
|
|
2205
|
+
const stateRaw = (0, shell_command_projection_cjs_1.platformReadSync)(node_path_1.default.join(planningDir(cwd), 'STATE.md'));
|
|
2206
|
+
const scalar = stateRaw === null ? null : readStateMilestoneScalar(stateRaw);
|
|
2207
|
+
if (scalar !== null)
|
|
2208
|
+
stateVersion = classifyMilestoneScalar(scalar).version;
|
|
2209
|
+
}
|
|
2210
|
+
catch { /* unreadable STATE.md: getMilestoneInfo's own fallback below decides */ }
|
|
2211
|
+
const derivedVersion = getMilestoneInfo(cwd).value?.version ?? null;
|
|
2212
|
+
const current = stateVersion ?? derivedVersion;
|
|
2213
|
+
const owner = milestoneVersionAtOffset(rawContent, foundAt);
|
|
2214
|
+
const ownerClause = owner && owner.toLowerCase() !== current?.toLowerCase()
|
|
2215
|
+
? `is in milestone ${owner}`
|
|
2216
|
+
: 'is outside the current milestone';
|
|
2217
|
+
let currentClause = 'the current milestone does not include it';
|
|
2218
|
+
if (stateVersion)
|
|
2219
|
+
currentClause = `STATE.md's current milestone is ${stateVersion}`;
|
|
2220
|
+
else if (derivedVersion)
|
|
2221
|
+
currentClause = `the current milestone is ${derivedVersion}`;
|
|
2222
|
+
return `Phase ${afterPhase} ${ownerClause}, but ${currentClause}. phase insert only targets phases in the current milestone.`;
|
|
2223
|
+
}
|
|
2190
2224
|
function cmdPhaseInsert(cwd, afterPhase, description, raw, allocation = 'nested') {
|
|
2191
2225
|
if (!afterPhase || !description) {
|
|
2192
2226
|
error('after-phase and description required for phase insert');
|
|
@@ -2257,7 +2291,8 @@ function cmdPhaseInsert(cwd, afterPhase, description, raw, allocation = 'nested'
|
|
|
2257
2291
|
const anyHeadingPattern = new RegExp(`#{2,4}\\s*${phaseHeadingPrefixSrcFor(PHASE_HEADING_BASELINE.LABEL_ONLY, insertConvention)}\\d`, 'i');
|
|
2258
2292
|
const roadmapHasHeadingPhases = anyHeadingPattern.test(content);
|
|
2259
2293
|
// #4304: bracket identities live in headings only, so a bracket ROADMAP never takes the legacy bullet-insertion branch — a bullet-only bracket ROADMAP falls through to the checklist-refusal path below instead.
|
|
2260
|
-
const
|
|
2294
|
+
const liveCurrent = blankCodeAndComments(content);
|
|
2295
|
+
const isBulletStyle = !bracketContext && !headingMatch && bulletPattern.test(liveCurrent) && !roadmapHasHeadingPhases;
|
|
2261
2296
|
if (bracketContext && !bracketHeading) {
|
|
2262
2297
|
const searchedRanges = bracketSectionRanges?.details
|
|
2263
2298
|
? 'primary and Phase Details ranges'
|
|
@@ -2266,9 +2301,19 @@ function cmdPhaseInsert(cwd, afterPhase, description, raw, allocation = 'nested'
|
|
|
2266
2301
|
}
|
|
2267
2302
|
if (!headingMatch && !isBulletStyle) {
|
|
2268
2303
|
const checklistPattern = (0, phase_entry_grammar_cjs_1.phaseEntryRegExp)(`${headingIntro}${afterPhaseEscaped}${OPTIONAL_PHASE_TAG_SOURCE}[:\\s]`, 'i');
|
|
2269
|
-
if (checklistPattern.test(
|
|
2304
|
+
if (checklistPattern.test(liveCurrent)) {
|
|
2270
2305
|
error(`Phase ${afterPhase} exists in roadmap summary but is missing a detail section (### Phase ${afterPhase}: ...).`);
|
|
2271
2306
|
}
|
|
2307
|
+
// #5679: this whole-file probe only chooses the error message; insert
|
|
2308
|
+
// stays current-milestone-only and never writes to another milestone.
|
|
2309
|
+
// Fenced blocks and HTML comments are masked (same length, offsets stay aligned) so example text never counts as a phase.
|
|
2310
|
+
const liveContent = blankCodeAndComments(rawContent);
|
|
2311
|
+
const headingOffset = (0, markdown_sectionizer_cjs_1.tokenizeHeadings)(liveContent).find((h) => targetPattern.test(h.text))?.offset;
|
|
2312
|
+
const checklistOffset = checklistPattern.exec(liveContent)?.index;
|
|
2313
|
+
const foundOffsets = [headingOffset, checklistOffset].filter((o) => o !== undefined);
|
|
2314
|
+
if (foundOffsets.length > 0) {
|
|
2315
|
+
error(otherMilestonePhaseMessage(rawContent, Math.min(...foundOffsets), cwd, afterPhase));
|
|
2316
|
+
}
|
|
2272
2317
|
error(`Phase ${afterPhase} not found in ROADMAP.md`);
|
|
2273
2318
|
}
|
|
2274
2319
|
const phasesDir = node_path_1.default.join(planningDir(cwd), 'phases');
|
|
@@ -2333,7 +2378,7 @@ function cmdPhaseInsert(cwd, afterPhase, description, raw, allocation = 'nested'
|
|
|
2333
2378
|
let roadmapEntry;
|
|
2334
2379
|
if (isBulletStyle) {
|
|
2335
2380
|
const boldBulletPattern = (0, phase_entry_grammar_cjs_1.phaseEntryRegExp)(`Phase\\s+${afterPhaseEscaped}${OPTIONAL_PHASE_TAG_SOURCE}:`, 'i', { bold: 'required' });
|
|
2336
|
-
const useBold = boldBulletPattern.test(
|
|
2381
|
+
const useBold = boldBulletPattern.test(liveCurrent);
|
|
2337
2382
|
const phaseLabel = useBold
|
|
2338
2383
|
? `**Phase ${_decimalPhase}: ${description}**`
|
|
2339
2384
|
: `Phase ${_decimalPhase}: ${description}`;
|
|
@@ -2356,11 +2401,12 @@ function cmdPhaseInsert(cwd, afterPhase, description, raw, allocation = 'nested'
|
|
|
2356
2401
|
// true line-content boundary so bulletLineEnd lands cleanly before the
|
|
2357
2402
|
// terminator.
|
|
2358
2403
|
const targetBulletPattern = (0, phase_entry_grammar_cjs_1.phaseEntryRegExp)(`Phase\\s+${afterPhaseEscaped}${OPTIONAL_PHASE_TAG_SOURCE}[:\\s][^\\r\\n]*`, 'i');
|
|
2359
|
-
|
|
2404
|
+
// Match against the masked text (same length, offsets aligned) so a fenced or commented example bullet is never the splice anchor; splice into the original text at the masked match index.
|
|
2405
|
+
const bulletMatchResult = blankCodeAndComments(rawContent).match(targetBulletPattern);
|
|
2360
2406
|
if (!bulletMatchResult) {
|
|
2361
2407
|
error(`Could not find Phase ${afterPhase} bullet line`);
|
|
2362
2408
|
}
|
|
2363
|
-
const bulletLineEnd =
|
|
2409
|
+
const bulletLineEnd = bulletMatchResult.index + bulletMatchResult[0].length;
|
|
2364
2410
|
const afterBullet = rawContent.slice(bulletLineEnd);
|
|
2365
2411
|
// #5599: land after the last bullet inside the allocation family's
|
|
2366
2412
|
// subtree, not before the first following bullet of any number (which is
|
|
@@ -6424,6 +6470,9 @@ function cmdPhaseComplete(cwd, phaseNum, raw) {
|
|
|
6424
6470
|
// where the roadmap would give `2` and a slugified title. Promoting the
|
|
6425
6471
|
// roadmap without this would silently change the reported value on
|
|
6426
6472
|
// every aligned project, which is the majority case.
|
|
6473
|
+
// #5676: the numeric phase-token grammar follows the convention, so a
|
|
6474
|
+
// milestone-prefixed `03-02-beta` is phase `3-02`, not phase `3`.
|
|
6475
|
+
const phaseConvention = resolvePhaseIdConvention(cwd);
|
|
6427
6476
|
let diskNextNum = null;
|
|
6428
6477
|
let diskNextName = null;
|
|
6429
6478
|
let roadmapNextNum = null;
|
|
@@ -6442,7 +6491,7 @@ function cmdPhaseComplete(cwd, phaseNum, raw) {
|
|
|
6442
6491
|
if (roadmapContent !== null) {
|
|
6443
6492
|
try {
|
|
6444
6493
|
const milestoneForComplete = extractCurrentMilestone(roadmapContent, cwd);
|
|
6445
|
-
const completePattern = (0, phase_entry_grammar_cjs_1.phaseEntryRegExp)(`Phase\\s+(${phaseScanTokenSource(directoryContext)})`, 'gi');
|
|
6494
|
+
const completePattern = (0, phase_entry_grammar_cjs_1.phaseEntryRegExp)(`Phase\\s+(${phaseScanTokenSource(directoryContext, phaseConvention)})`, 'gi');
|
|
6446
6495
|
let cm;
|
|
6447
6496
|
while ((cm = completePattern.exec(milestoneForComplete)) !== null) {
|
|
6448
6497
|
if (!(0, phase_entry_grammar_cjs_1.isPhaseEntryChecked)(cm[0]))
|
|
@@ -6472,7 +6521,7 @@ function cmdPhaseComplete(cwd, phaseNum, raw) {
|
|
|
6472
6521
|
for (const dir of dirs) {
|
|
6473
6522
|
const custom = phaseIdMod.customPhaseQuery(dir, directoryContext);
|
|
6474
6523
|
const owner = custom && customDirectoryOwner(dir, custom);
|
|
6475
|
-
const dm = owner ? [dir, owner.id, owner.slug] : dir.match(new RegExp(`^(${
|
|
6524
|
+
const dm = owner ? [dir, owner.id, owner.slug] : dir.match(new RegExp(`^(${phaseNumberTokenSource(phaseConvention)})-?(.*)`, 'i'));
|
|
6476
6525
|
if (dm) {
|
|
6477
6526
|
// #3185: canonical sentinel predicate (SENTINEL_RANGES [0,999]) — this was a local 999-only literal that admitted Phase 0.
|
|
6478
6527
|
if (isSentinelPhaseId(dm[1]))
|
|
@@ -6535,8 +6584,8 @@ function cmdPhaseComplete(cwd, phaseNum, raw) {
|
|
|
6535
6584
|
// checkbox branch widens, and only to the separators #2199 already
|
|
6536
6585
|
// accepts. The two branches keep separate capture groups, normalized
|
|
6537
6586
|
// just below the loop.
|
|
6538
|
-
const phasePattern = new RegExp(`(?:#{2,4}\\s*(?:\\*\\*|__)?\\s*Phase\\s+(${phaseScanTokenSource(directoryContext)})(?:\\s*\\([^)\\n]{0,200}\\))?\\s*:\\s*([^\\n*]+)` +
|
|
6539
|
-
`|${(0, phase_entry_grammar_cjs_1.phaseEntryPrefixSrc)()}Phase\\s+(${phaseScanTokenSource(directoryContext)})(?:\\s*\\([^)\\n]{0,200}\\))?\\s*[—–:\\-]\\s*([^\\n*]+))`, 'gim');
|
|
6587
|
+
const phasePattern = new RegExp(`(?:#{2,4}\\s*(?:\\*\\*|__)?\\s*Phase\\s+(${phaseScanTokenSource(directoryContext, phaseConvention)})(?:\\s*\\([^)\\n]{0,200}\\))?\\s*:\\s*([^\\n*]+)` +
|
|
6588
|
+
`|${(0, phase_entry_grammar_cjs_1.phaseEntryPrefixSrc)()}Phase\\s+(${phaseScanTokenSource(directoryContext, phaseConvention)})(?:\\s*\\([^)\\n]{0,200}\\))?\\s*[—–:\\-]\\s*([^\\n*]+))`, 'gim');
|
|
6540
6589
|
let pm;
|
|
6541
6590
|
while ((pm = phasePattern.exec(roadmapForPhases)) !== null) {
|
|
6542
6591
|
// #4078: normalize the two alternation branches' captures (heading
|
|
@@ -6636,7 +6685,7 @@ function cmdPhaseComplete(cwd, phaseNum, raw) {
|
|
|
6636
6685
|
// Without it, this lowest-outstanding override was blind to dash-grammar
|
|
6637
6686
|
// rows and could not correct an out-of-order completion on the same
|
|
6638
6687
|
// mixed-grammar roadmaps that broke stage 2.
|
|
6639
|
-
const cbPattern = (0, phase_entry_grammar_cjs_1.phaseEntryRegExp)(`Phase\\s+(${phaseScanTokenSource(directoryContext)})(?:\\s*\\([^)\\n]{0,200}\\))?\\s*[—–:\\-]\\s*([^\\n*]+)`, 'gi', { state: 'captured' });
|
|
6688
|
+
const cbPattern = (0, phase_entry_grammar_cjs_1.phaseEntryRegExp)(`Phase\\s+(${phaseScanTokenSource(directoryContext, phaseConvention)})(?:\\s*\\([^)\\n]{0,200}\\))?\\s*[—–:\\-]\\s*([^\\n*]+)`, 'gi', { state: 'captured' });
|
|
6640
6689
|
let cbm;
|
|
6641
6690
|
let lowestOutstanding = null;
|
|
6642
6691
|
while ((cbm = cbPattern.exec(milestoneScope)) !== null) {
|
|
@@ -6685,7 +6734,7 @@ function cmdPhaseComplete(cwd, phaseNum, raw) {
|
|
|
6685
6734
|
// pin would all rewind it. A first-time completion (box unchecked, or
|
|
6686
6735
|
// STATE not past N, #2028 out-of-order) still transitions.
|
|
6687
6736
|
const stateAlreadyPast = phaseWasAlreadyComplete
|
|
6688
|
-
&& (0, state_transition_cjs_1.isStatePastPhase)(originalStateContent, phaseNum, statePath);
|
|
6737
|
+
&& (0, state_transition_cjs_1.isStatePastPhase)(originalStateContent, phaseNum, statePath, phaseConvention);
|
|
6689
6738
|
let stateContent = originalStateContent;
|
|
6690
6739
|
// ADR-1769 Phase 3: the STATE.md field-update policy (Current Phase
|
|
6691
6740
|
// shape/name, Status, Current Plan, Last Activity + Description, and
|
|
@@ -6715,7 +6764,7 @@ function cmdPhaseComplete(cwd, phaseNum, raw) {
|
|
|
6715
6764
|
const nextInfo = findPhaseInternal(cwd, nextPhaseNum, undefined, directoryContext);
|
|
6716
6765
|
if (nextInfo && nextInfo.found && nextInfo.directory) {
|
|
6717
6766
|
const { value } = (0, phase_status_cjs_1.phaseStatus)(node_path_1.default.join(cwd, nextInfo.directory), {
|
|
6718
|
-
convention:
|
|
6767
|
+
convention: phaseConvention,
|
|
6719
6768
|
});
|
|
6720
6769
|
nextPhaseDisk = { status: value.status, planCount: value.planCount, summaryCount: value.summaryCount };
|
|
6721
6770
|
}
|
|
@@ -608,7 +608,7 @@ function buildRoadmapPhaseCheckboxesField(roadmapPath, convention, directoryCont
|
|
|
608
608
|
// group 2 and the legacy repo compiles a byte-identical source.
|
|
609
609
|
// The entry prefix (marker, box, bold token) is the phase-entry grammar's (#5764),
|
|
610
610
|
// the same one `buildNotStartedPhaseVariants` composes; group 1 is the box.
|
|
611
|
-
const checkboxRe = (0, phase_entry_grammar_cjs_1.phaseEntryRegExp)(`${phaseHeadingPrefixSrcFor(PHASE_HEADING_BASELINE.LABEL_ONLY, convention)}0*(${convention === 'bracket' ? PHASE_NUMBER_TOKEN_SOURCE : phaseIdMod.phaseScanTokenSource(directoryContext)})${OPTIONAL_PHASE_TAG_SOURCE}[:\\s]`, 'gi', { state: 'captured' });
|
|
611
|
+
const checkboxRe = (0, phase_entry_grammar_cjs_1.phaseEntryRegExp)(`${phaseHeadingPrefixSrcFor(PHASE_HEADING_BASELINE.LABEL_ONLY, convention)}0*(${convention === 'bracket' ? PHASE_NUMBER_TOKEN_SOURCE : phaseIdMod.phaseScanTokenSource(directoryContext, convention)})${OPTIONAL_PHASE_TAG_SOURCE}[:\\s]`, 'gi', { state: 'captured' });
|
|
612
612
|
const value = {};
|
|
613
613
|
let m;
|
|
614
614
|
while ((m = checkboxRe.exec(content)) !== null) {
|
|
@@ -905,7 +905,7 @@ function buildCurrentMilestoneRoadmapPhaseIdsField(cwd, roadmapPath, convention,
|
|
|
905
905
|
// pinned to this site by name) would catch that narrowing, since the census
|
|
906
906
|
// only pins `phaseHeadingPrefixSrcFor`'s own output, not this regex's tail.
|
|
907
907
|
// Kept as its own composition to preserve this exact byte-for-behavior.
|
|
908
|
-
const phasePattern = new RegExp(`#{2,4}\\s*${phaseHeadingPrefixSrcFor(PHASE_HEADING_BASELINE.LABEL_ONLY, convention, Boolean(bracketGroup))}(${convention !== 'bracket' ? phaseIdMod.phaseScanTokenSource(directoryContext) : PHASE_NUMBER_TOKEN_SOURCE})(?:\\s*\\([^)\\n]{0,200}\\))?\\s*:`, 'gi');
|
|
908
|
+
const phasePattern = new RegExp(`#{2,4}\\s*${phaseHeadingPrefixSrcFor(PHASE_HEADING_BASELINE.LABEL_ONLY, convention, Boolean(bracketGroup))}(${convention !== 'bracket' ? phaseIdMod.phaseScanTokenSource(directoryContext, convention) : PHASE_NUMBER_TOKEN_SOURCE})(?:\\s*\\([^)\\n]{0,200}\\))?\\s*:`, 'gi');
|
|
909
909
|
const value = [];
|
|
910
910
|
for (const m of scoped.matchAll(phasePattern)) {
|
|
911
911
|
const bracketId = bracketGroup ? m[1] : undefined;
|
|
@@ -1736,6 +1736,31 @@ function scanArchiveSpans(content, active) {
|
|
|
1736
1736
|
spans.push(...plainClosedSections(content, overlapsActive, activeVersion));
|
|
1737
1737
|
return { spans, inActive, ambiguous };
|
|
1738
1738
|
}
|
|
1739
|
+
/**
|
|
1740
|
+
* #5679: the milestone version that owns `offset`. An offset inside a `<details>`
|
|
1741
|
+
* archive (or a plain closed-milestone section) belongs to that block's
|
|
1742
|
+
* `<summary>` (or heading) version, wherever the block sits relative to the
|
|
1743
|
+
* active heading; any other offset belongs to the last milestone heading before
|
|
1744
|
+
* it. Blocks come from `scanArchiveSpans` (fence/comment-aware, depth-paired),
|
|
1745
|
+
* innermost first, so a notes block inside an archive does not hide the archive.
|
|
1746
|
+
* Null when neither names a version.
|
|
1747
|
+
*/
|
|
1748
|
+
function milestoneVersionAtOffset(content, offset) {
|
|
1749
|
+
const containing = scanArchiveSpans(content, []).spans
|
|
1750
|
+
.filter((span) => span.start <= offset && offset < span.end)
|
|
1751
|
+
.sort((a, b) => b.start - a.start);
|
|
1752
|
+
for (const span of containing) {
|
|
1753
|
+
const head = content.slice(span.start, span.start + 1200);
|
|
1754
|
+
// Only a <details> span owns its <summary>; a plain closed-milestone section must not borrow a later block's.
|
|
1755
|
+
const summary = /^<details\b/i.test(head) ? /<summary[^>]*>([\s\S]{0,1000}?)<\/summary>/i.exec(head) : null;
|
|
1756
|
+
const text = summary ? summary[1].replace(/<[^>]{0,200}>/g, '') : head.split('\n', 1)[0];
|
|
1757
|
+
const version = text.match(VERSION_TOKEN)?.[0];
|
|
1758
|
+
if (version)
|
|
1759
|
+
return version;
|
|
1760
|
+
}
|
|
1761
|
+
const preceding = listMilestoneHeadings(content.slice(0, offset));
|
|
1762
|
+
return preceding.length > 0 ? preceding[preceding.length - 1].version : null;
|
|
1763
|
+
}
|
|
1739
1764
|
/**
|
|
1740
1765
|
* #5553: hide every archived `<details>` block, and every closed milestone
|
|
1741
1766
|
* written as a plain heading section, behind a placeholder; `restore` puts them
|
|
@@ -2915,6 +2940,10 @@ module.exports = {
|
|
|
2915
2940
|
// #4304: destructive ROADMAP writers classify details blocks through
|
|
2916
2941
|
// the same closed-summary rule as stripClosedMilestoneDetails.
|
|
2917
2942
|
isClosedMilestoneDetails,
|
|
2943
|
+
// #5679: the owning milestone version of a ROADMAP offset, archive-aware.
|
|
2944
|
+
milestoneVersionAtOffset,
|
|
2945
|
+
// #5679: same-length copy with fenced blocks, code spans and HTML comments blanked.
|
|
2946
|
+
blankCodeAndComments,
|
|
2918
2947
|
// #4144 round 5 Blocker 1: the readers' own phase-heading grammar, so the
|
|
2919
2948
|
// bracket roadmap migrator's "phase-like but unparsed" refusal
|
|
2920
2949
|
// (src/roadmap-upgrade.cts) can gate on it instead of a private, looser
|