fix(3153): handle numeric 100 percent and block-list next_phases
This commit is contained in:
5
.changeset/fix-3153-statusline-percent-next-phases.md
Normal file
5
.changeset/fix-3153-statusline-percent-next-phases.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3153
|
||||
---
|
||||
**Statusline state rendering is now type-robust and YAML-list compatible** — milestone completion now renders for numeric and string `percent` values, and `next_phases` parsing supports both flow-array and block-list YAML forms.
|
||||
@@ -157,13 +157,22 @@ function parseStateMd(content) {
|
||||
// next_action: recommended command when idle (discuss-phase / plan-phase / execute-phase / verify-phase)
|
||||
if (key === 'next_action') state.nextAction = (v === 'null' || v === '') ? null : v;
|
||||
}
|
||||
// next_phases YAML flow array: ["4.5", "4.6"] — single-line flow only
|
||||
// Block sequences (- 4.5 / - 4.6 over multiple lines) are intentionally
|
||||
// not parsed here; statusline only needs the primary recommendation.
|
||||
const npMatch = fm.match(/^next_phases:\s*\[([^\]]*)\]/m);
|
||||
if (npMatch) {
|
||||
const items = npMatch[1].split(',').map(s => s.trim().replace(/^["']|["']$/g, '')).filter(Boolean);
|
||||
// next_phases supports both flow array and block-list YAML forms.
|
||||
const npFlowMatch = fm.match(/^next_phases:\s*\[([^\]]*)\]/m);
|
||||
if (npFlowMatch) {
|
||||
const items = npFlowMatch[1].split(',').map(s => s.trim().replace(/^["']|["']$/g, '')).filter(Boolean);
|
||||
state.nextPhases = items.length > 0 ? items : null;
|
||||
} else {
|
||||
const npBlockMatch = fm.match(/^next_phases:\s*\n((?:[ \t]*-[ \t]*[^\n]+\n?)*)/m);
|
||||
if (npBlockMatch) {
|
||||
const items = npBlockMatch[1]
|
||||
.split('\n')
|
||||
.map(line => line.match(/^[ \t]*-[ \t]*(.+)$/))
|
||||
.filter(Boolean)
|
||||
.map(m => m[1].trim().replace(/^["']|["']$/g, ''))
|
||||
.filter(Boolean);
|
||||
state.nextPhases = items.length > 0 ? items : null;
|
||||
}
|
||||
}
|
||||
// progress nested block: completed_phases / total_phases / percent (2-space indent)
|
||||
const progMatch = fm.match(/^progress:\s*\n((?:[ \t]+\w+:.+\n?)+)/m);
|
||||
@@ -256,7 +265,7 @@ function formatGsdState(s) {
|
||||
// Scene 2: idle + a recommended next command is visible to the user.
|
||||
// Surfaces "what to run next" without the user opening STATE.md.
|
||||
parts.push(`next ${s.nextAction} ${phasesStr}`);
|
||||
} else if (s.percent === '100' || (s.completedPhases && s.totalPhases && s.completedPhases === s.totalPhases)) {
|
||||
} else if (Number(s.percent) === 100 || (s.completedPhases && s.totalPhases && s.completedPhases === s.totalPhases)) {
|
||||
// Scene 3: milestone complete (every phase done).
|
||||
parts.push('milestone complete');
|
||||
} else {
|
||||
|
||||
@@ -125,6 +125,21 @@ describe('parseStateMd', () => {
|
||||
assert.equal(s.status, undefined);
|
||||
assert.equal(s.phaseNum, undefined);
|
||||
});
|
||||
|
||||
test('parses next_phases from YAML block-list form (#3153)', () => {
|
||||
const content = [
|
||||
'---',
|
||||
'next_action: execute',
|
||||
'next_phases:',
|
||||
' - 4.5',
|
||||
' - 4.6',
|
||||
'---',
|
||||
].join('\n');
|
||||
|
||||
const s = parseStateMd(content);
|
||||
assert.equal(s.nextAction, 'execute');
|
||||
assert.deepEqual(s.nextPhases, ['4.5', '4.6']);
|
||||
});
|
||||
});
|
||||
|
||||
// ─── formatGsdState ─────────────────────────────────────────────────────────
|
||||
@@ -185,6 +200,14 @@ describe('formatGsdState', () => {
|
||||
assert.equal(out, 'Foundations · planning');
|
||||
});
|
||||
|
||||
test('treats numeric 100 percent as milestone complete (#3153)', () => {
|
||||
const out = formatGsdState({
|
||||
milestone: 'v2.0',
|
||||
percent: 100,
|
||||
});
|
||||
assert.equal(out, 'v2.0 [██████████] 100% · milestone complete');
|
||||
});
|
||||
|
||||
test('returns empty string for empty state', () => {
|
||||
assert.equal(formatGsdState({}), '');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user