* test(#4023): reproduce decimal phase-id coercions * fix(#4023): preserve decimal phase ids in progress signals * test(#4023): align phase token contract expectations * chore(#4023): point the changeset at PR #4110 Co-Authored-By: Claude Opus 5 (1M context) <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:
@@ -92,7 +92,7 @@ const {
|
||||
extractCurrentMilestone,
|
||||
} = roadmapParser;
|
||||
const { pathExistsInternal, generateSlugInternal, toPosixPath } = coreUtils;
|
||||
const { normalizePhaseName, matchPhaseDirs, stripProjectCodePrefix, PHASE_NUMBER_TOKEN_SOURCE, isForeignPrefixedPhaseQuery, isSentinelPhaseId, extractPhaseToken, scopeToPhase } = phaseId;
|
||||
const { comparePhaseNum, normalizePhaseName, matchPhaseDirs, stripProjectCodePrefix, PHASE_NUMBER_TOKEN_SOURCE, isForeignPrefixedPhaseQuery, isSentinelPhaseId, extractPhaseToken, scopeToPhase } = phaseId;
|
||||
const { pruneOrphanedWorktrees } = worktreeSafety;
|
||||
|
||||
const {
|
||||
@@ -3175,9 +3175,7 @@ function cmdInitProgress(cwd: string, raw: boolean, options: Record<string, unkn
|
||||
}
|
||||
}
|
||||
|
||||
phases.sort(
|
||||
(a, b) => parseInt(a['number'] as string, 10) - parseInt(b['number'] as string, 10),
|
||||
);
|
||||
phases.sort((a, b) => comparePhaseNum(a['number'], b['number']));
|
||||
|
||||
// #3581: the frontier is ROADMAP ORDER, not artifact presence. The disk loop
|
||||
// above could claim nextPhase from a stray out-of-order artifact directory
|
||||
|
||||
@@ -54,7 +54,7 @@ import stateDocument = require('./state-document.cjs');
|
||||
const { stateFieldValue } = stateDocument;
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
import phaseId = require('./phase-id.cjs');
|
||||
const { comparePhaseNum, extractPhaseToken, matchPhaseDirs, normalizePhaseName, stripProjectCodePrefix } = phaseId;
|
||||
const { comparePhaseNum, extractPhaseToken, matchPhaseDirs, normalizePhaseName, parsePhaseFromProse, stripProjectCodePrefix } = phaseId;
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
import stateMod = require('./state.cjs');
|
||||
const { readStateHeadFreshness } = stateMod;
|
||||
@@ -87,7 +87,7 @@ export interface SmartEntryAction {
|
||||
}
|
||||
|
||||
export interface SmartEntrySignals {
|
||||
current_phase: number | null;
|
||||
current_phase: string | null;
|
||||
total_phases: number | null;
|
||||
status: string;
|
||||
progress: number | null;
|
||||
@@ -306,9 +306,7 @@ function readGitSignals(cwd: string): GitSignals {
|
||||
|
||||
/** Leading numeric phase token from a STATE.md scalar or body `Phase:` value. */
|
||||
function phaseTokenFromState(raw: string | null): string | null {
|
||||
if (!raw?.trim()) return null;
|
||||
const match = raw.trim().match(/^(\d+(?:[A-Z])?(?:\.\d+)*)/i);
|
||||
return match ? match[1] : null;
|
||||
return parsePhaseFromProse(raw).phase;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -513,7 +511,7 @@ export function detectSignals(cwd: string, now: () => number = Date.now): SmartE
|
||||
const freshness = readStateHeadFreshness(cwd, stateHeadRaw);
|
||||
|
||||
return {
|
||||
current_phase: parseIntOrNull(currentPhaseRaw),
|
||||
current_phase: phaseTokenFromState(currentPhaseRaw),
|
||||
total_phases: parseIntOrNull(totalPhasesRaw),
|
||||
status: (statusRaw || '').toLowerCase(),
|
||||
progress: parseIntOrNull(progressRaw),
|
||||
@@ -555,10 +553,11 @@ function isComplete(s: SmartEntrySignals): boolean {
|
||||
if (s.roadmap_total_phases === 0) return false;
|
||||
if (s.roadmap_completed_phases < s.roadmap_total_phases) return false;
|
||||
} else {
|
||||
// Legacy path: STATE.md comparison. Still subject to the two-scale bug,
|
||||
// but only fires when ROADMAP.md is absent or has no Progress table.
|
||||
// Legacy path: STATE.md comparison. It only fires when ROADMAP.md is
|
||||
// absent or has no Progress table, and uses canonical phase-id ordering
|
||||
// so dotted phase ids are never coerced to JavaScript numbers.
|
||||
if (s.total_phases === null || s.current_phase === null) return false;
|
||||
if (s.current_phase < s.total_phases) return false;
|
||||
if (comparePhaseNum(s.current_phase, s.total_phases) < 0) return false;
|
||||
}
|
||||
// Status regex: require milestone-level completion language. The pre-fix
|
||||
// regex matched any "shipped" / "done" substring (per-phase language).
|
||||
|
||||
Reference in New Issue
Block a user