fix(sdk): state.prune current phase fallback handling (#3477)
* fix(sdk): derive state.prune phase from frontmatter fallbacks (#3471) * chore(changeset): add #3477 fragment * chore(sdk): clarify state.prune fallback guidance
This commit is contained in:
5
.changeset/steady-bears-purr.md
Normal file
5
.changeset/steady-bears-purr.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3477
|
||||
---
|
||||
**`gsd-sdk query state.prune` now derives current phase from frontmatter fallbacks** — the command no longer reports a false 'Only 0 phases' when STATE.md omits the body Current Phase field.
|
||||
@@ -1135,3 +1135,64 @@ Last activity: 2026-04-20 -- v1.0 shipped
|
||||
expect(data.error).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('statePrune current phase extraction (#3471)', () => {
|
||||
let tmpDir: string;
|
||||
|
||||
beforeEach(async () => {
|
||||
tmpDir = await mkdtemp(join(tmpdir(), 'gsd-sdk-stateprune-'));
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
if (tmpDir) await rm(tmpDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it('uses frontmatter progress.completed_phases when body Current Phase field is absent', async () => {
|
||||
const stateContent = `---
|
||||
gsd_state_version: 1.0
|
||||
milestone: v1.1
|
||||
status: executing
|
||||
progress:
|
||||
total_phases: 21
|
||||
completed_phases: 12
|
||||
total_plans: 79
|
||||
completed_plans: 75
|
||||
percent: 95
|
||||
---
|
||||
|
||||
# Session State
|
||||
|
||||
## Current Position
|
||||
|
||||
Phase 12 execution in progress.
|
||||
`;
|
||||
await setupTestProject(tmpDir, stateContent);
|
||||
const { statePrune } = await import('./state-mutation.js');
|
||||
const result = await statePrune(['--keep-recent', '3', '--dry-run'], tmpDir);
|
||||
const data = result.data as Record<string, unknown>;
|
||||
|
||||
expect(data.pruned).toBe(false);
|
||||
expect(data.dry_run).toBe(true);
|
||||
expect(data.cutoff_phase).toBe(9);
|
||||
expect(data.reason).toBeUndefined();
|
||||
});
|
||||
|
||||
it('returns a targeted reason when no current phase source can be parsed', async () => {
|
||||
const stateContent = `---
|
||||
gsd_state_version: 1.0
|
||||
milestone: v1.1
|
||||
status: executing
|
||||
---
|
||||
|
||||
# Session State
|
||||
`;
|
||||
await setupTestProject(tmpDir, stateContent);
|
||||
const { statePrune } = await import('./state-mutation.js');
|
||||
const result = await statePrune(['--keep-recent', '3', '--dry-run'], tmpDir);
|
||||
const data = result.data as Record<string, unknown>;
|
||||
|
||||
expect(data.pruned).toBe(false);
|
||||
expect(typeof data.reason).toBe('string');
|
||||
expect(String(data.reason)).toContain('Could not determine current phase');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1621,8 +1621,32 @@ export const statePrune: QueryHandler = async (args, projectDir, workstream) =>
|
||||
}
|
||||
|
||||
const fullContent = await readFile(statePath, 'utf-8');
|
||||
const currentPhaseRaw = stateExtractField(fullContent, 'Current Phase');
|
||||
const currentPhase = parseInt(String(currentPhaseRaw ?? ''), 10) || 0;
|
||||
const fm = extractFrontmatter(fullContent);
|
||||
const fmProgress = (typeof fm.progress === 'object' && fm.progress !== null)
|
||||
? fm.progress as Record<string, unknown>
|
||||
: null;
|
||||
const phaseCandidates: unknown[] = [
|
||||
fm.current_phase,
|
||||
stateExtractField(fullContent, 'Current Phase'),
|
||||
fmProgress?.completed_phases,
|
||||
fmProgress?.total_phases,
|
||||
];
|
||||
let currentPhase: number | null = null;
|
||||
for (const candidate of phaseCandidates) {
|
||||
const parsed = parseInt(String(candidate ?? '').trim(), 10);
|
||||
if (Number.isInteger(parsed) && parsed > 0) {
|
||||
currentPhase = parsed;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (currentPhase === null) {
|
||||
return {
|
||||
data: {
|
||||
pruned: false,
|
||||
reason: 'Could not determine current phase from STATE.md. Add **Current Phase:** N, frontmatter current_phase: N, progress.completed_phases, or progress.total_phases.',
|
||||
},
|
||||
};
|
||||
}
|
||||
const cutoff = currentPhase - keepRecent;
|
||||
|
||||
if (cutoff <= 0) {
|
||||
|
||||
Reference in New Issue
Block a user