enhance(#3638): render bracket phase IDs on progress, stats, manager and statusline surfaces (epic #612 PR-5) (#4111)
* enhance(#3638): render bracket IDs on display surfaces Gate progress, stats, manager, and statusline projections on the bracket convention; validate phase_id_convention and single-source the convention card. Forward note: the uat.cts bracket co-change remains deliberately deferred to its owning slice. * chore(#3638): point the changeset at PR #4111 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(#3638): close bracket display review gaps * docs(#3638): register phase display modules * chore(#3638): re-trigger CI after macOS shard SIGTERM `full test (macos-latest, 24, shard 3/3)` failed on 20ce98cd1 in `tests/lint-compiled-artifact-sync.test.cjs` — the spawned `scripts/lint-compiled-artifact-sync.cjs` was killed at 60024ms (`exited null (signal SIGTERM)`, stdout and stderr both empty), 24ms past the test's own `TSC_COMPILE_TIMEOUT_MS`. That is the failure mode the constant's comment already documents ("under CI shard load that compile can exceed the budget, dying to a SIGTERM with empty piped stdout"). No content change; this empty commit exists only to re-run the matrix, since re-running a job needs write access on the upstream repository. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: Tom Boucher <trekkie@nomorestars.com>
This commit is contained in:
80
src/init.cts
80
src/init.cts
@@ -97,7 +97,22 @@ const {
|
||||
extractCurrentMilestone,
|
||||
} = roadmapParser;
|
||||
const { pathExistsInternal, generateSlugInternal, toPosixPath } = coreUtils;
|
||||
const { comparePhaseNum, normalizePhaseName, matchPhaseDirs, stripProjectCodePrefix, PHASE_NUMBER_TOKEN_SOURCE, isForeignPrefixedPhaseQuery, isSentinelPhaseId, extractPhaseToken, scopeToPhase, renderPhaseBranchName } = phaseId;
|
||||
const {
|
||||
comparePhaseNum,
|
||||
normalizePhaseName,
|
||||
matchPhaseDirs,
|
||||
stripProjectCodePrefix,
|
||||
PHASE_NUMBER_TOKEN_SOURCE,
|
||||
isForeignPrefixedPhaseQuery,
|
||||
isSentinelPhaseId,
|
||||
extractPhaseToken,
|
||||
scopeToPhase,
|
||||
renderPhaseBranchName,
|
||||
parsePhaseId,
|
||||
renderPhaseId,
|
||||
phaseHeadingPrefixSrcFor,
|
||||
PHASE_HEADING_BASELINE,
|
||||
} = phaseId;
|
||||
const { pruneOrphanedWorktrees } = worktreeSafety;
|
||||
|
||||
const {
|
||||
@@ -111,6 +126,7 @@ const {
|
||||
diagnoseUnresolvedActiveWorkstream,
|
||||
describeUnresolvedWorkstreamReason,
|
||||
findContextMdIn,
|
||||
resolvePhaseIdConvention,
|
||||
} = planningWorkspace;
|
||||
|
||||
const { determinePhaseStatus } = commandsMod;
|
||||
@@ -2745,6 +2761,17 @@ function cmdInitManager(cwd: string, raw: boolean): void {
|
||||
const config = loadConfig(cwd);
|
||||
const milestone = milestoneRecord(cwd);
|
||||
const _slashRuntime = resolveRuntime(cwd);
|
||||
const phaseIdConvention = resolvePhaseIdConvention(cwd);
|
||||
const capturesBracketId = phaseIdConvention === 'bracket';
|
||||
const phaseHeadingPrefix = phaseHeadingPrefixSrcFor(
|
||||
PHASE_HEADING_BASELINE.LABEL_ONLY,
|
||||
phaseIdConvention,
|
||||
capturesBracketId,
|
||||
);
|
||||
const phaseHeadingPrefixNoCapture = phaseHeadingPrefixSrcFor(
|
||||
PHASE_HEADING_BASELINE.LABEL_ONLY,
|
||||
phaseIdConvention,
|
||||
);
|
||||
|
||||
const paths = planningPaths(cwd);
|
||||
|
||||
@@ -2763,27 +2790,52 @@ function cmdInitManager(cwd: string, raw: boolean): void {
|
||||
// routed through it instead of a hand-rolled readdirSync + a separate
|
||||
// getMilestonePhaseFilter window check (which also never excluded
|
||||
// sentinels, unlike the owner).
|
||||
const _phaseDirEntries = listMilestonePhaseDirs(phasesDir, { cwd }).value;
|
||||
const _phaseDirEntries = listMilestonePhaseDirs(phasesDir, {
|
||||
cwd,
|
||||
phaseIdConvention,
|
||||
}).value;
|
||||
|
||||
const _checkboxStates = new Map<string, boolean>();
|
||||
const _cbPattern = new RegExp(`-\\s*\\[(x| )\\]\\s*.*Phase\\s+(${PHASE_NUMBER_TOKEN_SOURCE})[:\\s]`, 'gi');
|
||||
const _cbPattern = new RegExp(
|
||||
`-\\s*\\[(x| )\\]\\s*.*${phaseHeadingPrefix}(${PHASE_NUMBER_TOKEN_SOURCE})[:\\s]`,
|
||||
'gi',
|
||||
);
|
||||
let _cbMatch: RegExpExecArray | null;
|
||||
while ((_cbMatch = _cbPattern.exec(content)) !== null) {
|
||||
_checkboxStates.set(_cbMatch[2], _cbMatch[1].toLowerCase() === 'x');
|
||||
const phaseGroup = capturesBracketId ? 3 : 2;
|
||||
_checkboxStates.set(_cbMatch[phaseGroup], _cbMatch[1].toLowerCase() === 'x');
|
||||
}
|
||||
|
||||
// #1729: `(?:\s*\([^)\n]{0,200}\))?` tolerates a pre-colon ( ) tag (literal mirror of OPTIONAL_PHASE_TAG_SOURCE).
|
||||
const phasePattern = new RegExp(`#{2,4}\\s*Phase\\s+(${PHASE_NUMBER_TOKEN_SOURCE})(?:\\s*\\([^)\\n]{0,200}\\))?\\s*:\\s*([^\\n]+)`, 'gi');
|
||||
const phasePattern = new RegExp(
|
||||
`#{2,4}\\s*${phaseHeadingPrefix}(${PHASE_NUMBER_TOKEN_SOURCE})(?:\\s*\\([^)\\n]{0,200}\\))?\\s*:\\s*([^\\n]+)`,
|
||||
'gi',
|
||||
);
|
||||
const phases: Record<string, unknown>[] = [];
|
||||
let match: RegExpExecArray | null;
|
||||
|
||||
while ((match = phasePattern.exec(content)) !== null) {
|
||||
const phaseNum = match[1];
|
||||
const phaseName = match[2].replace(/\(INSERTED\)/i, '').trim();
|
||||
const bracketId = capturesBracketId ? match[1] : undefined;
|
||||
const phaseNum = capturesBracketId ? match[2] : match[1];
|
||||
const phaseName = (capturesBracketId ? match[3] : match[2])
|
||||
.replace(/\(INSERTED\)/i, '')
|
||||
.trim();
|
||||
let displayId: string | undefined;
|
||||
if (bracketId) {
|
||||
try {
|
||||
displayId = renderPhaseId(parsePhaseId(`${bracketId}-${phaseNum}`));
|
||||
} catch {
|
||||
// The bracket selector is deliberately read-tolerant. If a heading is
|
||||
// non-canonical, retain the manager row without fabricating display_id.
|
||||
}
|
||||
}
|
||||
|
||||
const sectionStart = match.index;
|
||||
const restOfContent = content.slice(sectionStart);
|
||||
const nextHeader = restOfContent.match(/\n#{2,4}\s+Phase\s+\d[\d.]*/i);
|
||||
const nextHeader = restOfContent.match(new RegExp(
|
||||
`\\n#{2,4}\\s+${phaseHeadingPrefixNoCapture}\\d[\\d.]*`,
|
||||
'i',
|
||||
));
|
||||
const sectionEnd = nextHeader
|
||||
? sectionStart + (nextHeader.index as number)
|
||||
: content.length;
|
||||
@@ -2820,7 +2872,11 @@ function cmdInitManager(cwd: string, raw: boolean): void {
|
||||
// milestone-scoped set and onto the physical one; that scope choice is
|
||||
// kept. Only the matcher is this PR's: matchPhaseDirs resolves
|
||||
// digit-leading directory names the token predicate cannot (#2528).
|
||||
const dirMatch = matchPhaseDirs(_phaseDirEntries, normalized).matches[0];
|
||||
const dirMatch = matchPhaseDirs(
|
||||
_phaseDirEntries,
|
||||
normalized,
|
||||
phaseIdConvention,
|
||||
).matches[0];
|
||||
|
||||
if (dirMatch) {
|
||||
const fullDir = path.join(phasesDir, dirMatch);
|
||||
@@ -2901,6 +2957,7 @@ function cmdInitManager(cwd: string, raw: boolean): void {
|
||||
|
||||
phases.push({
|
||||
number: phaseNum,
|
||||
...(displayId ? { display_id: displayId } : {}),
|
||||
name: phaseName,
|
||||
goal,
|
||||
depends_on,
|
||||
@@ -2945,7 +3002,10 @@ function cmdInitManager(cwd: string, raw: boolean): void {
|
||||
);
|
||||
const phaseMap = new Map(phases.map((p) => [normalizePhaseNumber(p['number'] as string), p]));
|
||||
|
||||
const _allCompletedPattern = new RegExp(`-\\s*\\[x\\]\\s*.*Phase\\s+(${PHASE_NUMBER_TOKEN_SOURCE})[:\\s]`, 'gi');
|
||||
const _allCompletedPattern = new RegExp(
|
||||
`-\\s*\\[x\\]\\s*.*${phaseHeadingPrefixNoCapture}(${PHASE_NUMBER_TOKEN_SOURCE})[:\\s]`,
|
||||
'gi',
|
||||
);
|
||||
let _allMatch: RegExpExecArray | null;
|
||||
while ((_allMatch = _allCompletedPattern.exec(rawContent)) !== null) {
|
||||
const phaseNum = normalizePhaseNumber(_allMatch[1]);
|
||||
|
||||
Reference in New Issue
Block a user