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:
BeeHiggs
2026-09-15 02:47:47 -06:00
committed by GitHub
parent 2f0e99f9e0
commit 0967358b8b
40 changed files with 993 additions and 50 deletions

View File

@@ -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]);