* fix(#3351): reconcile state.patch report with persisted state.md * chore(#3351): add changeset fragment * chore(#3351): backfill pr number in changeset fragment --------- Co-authored-by: sim <sim@local>
This commit is contained in:
@@ -1365,6 +1365,87 @@ describe('cmdStatePatch and cmdStateUpdate (state patch, state update)', () => {
|
||||
assert.ok(output.failed.includes('Missing'), 'Missing should be in failed list');
|
||||
});
|
||||
|
||||
// #3351: state.patch's `updated`/`failed` report must reflect what actually
|
||||
// persisted to STATE.md after the write completes — not whether the internal
|
||||
// text-replace matched frontmatter text that the write pipeline then
|
||||
// re-derives away (syncStateFrontmatter re-derives current_phase /
|
||||
// current_phase_name from the body `Phase:` line on every write, and the
|
||||
// FIELD_CLASSIFICATION preservation rows restore the pre-write values when
|
||||
// the body source did not change).
|
||||
describe('#3351: state.patch report reconciled against persisted STATE.md', () => {
|
||||
const phaseStateMd = [
|
||||
'---',
|
||||
'gsd_state_version: 1.0',
|
||||
'current_phase: 1',
|
||||
'current_phase_name: alpha',
|
||||
'risk_level: low',
|
||||
'status: executing',
|
||||
'---',
|
||||
'',
|
||||
'# Project State',
|
||||
'',
|
||||
'## Current Position',
|
||||
'',
|
||||
'Phase: 1 (alpha)',
|
||||
'',
|
||||
].join('\n');
|
||||
|
||||
function readFm(dir) {
|
||||
return frontmatterLib.extractFrontmatter(
|
||||
fs.readFileSync(path.join(dir, '.planning', 'STATE.md'), 'utf-8'),
|
||||
);
|
||||
}
|
||||
|
||||
test('body-derived/curated frontmatter fields re-derived by the write are reported failed, not updated', () => {
|
||||
fs.writeFileSync(path.join(tmpDir, '.planning', 'STATE.md'), phaseStateMd);
|
||||
|
||||
const result = runGsdTools([
|
||||
'query',
|
||||
'state.patch',
|
||||
JSON.stringify({ current_phase: '7', current_phase_name: 'omega' }),
|
||||
], tmpDir);
|
||||
assert.ok(result.success, `state patch failed: ${result.error}`);
|
||||
|
||||
const report = JSON.parse(result.output);
|
||||
assert.deepEqual(report.updated, [], `phantom updates must not be reported: ${result.output}`);
|
||||
assert.deepEqual(report.failed.sort(), ['current_phase', 'current_phase_name'].sort());
|
||||
|
||||
// On-disk truth: neither requested value persisted.
|
||||
const fm = readFm(tmpDir);
|
||||
assert.notEqual(String(fm.current_phase), '7', 'current_phase was re-derived away by the write pipeline');
|
||||
assert.notEqual(String(fm.current_phase_name), 'omega', 'current_phase_name was restored by the curated preservation row');
|
||||
});
|
||||
|
||||
test('mixed patch reports an accurate updated/failed split (one lands, one is re-derived away)', () => {
|
||||
fs.writeFileSync(path.join(tmpDir, '.planning', 'STATE.md'), phaseStateMd);
|
||||
|
||||
const result = runGsdTools([
|
||||
'query',
|
||||
'state.patch',
|
||||
JSON.stringify({ risk_level: 'high', current_phase: '7' }),
|
||||
], tmpDir);
|
||||
assert.ok(result.success, `state patch failed: ${result.error}`);
|
||||
|
||||
const report = JSON.parse(result.output);
|
||||
assert.deepEqual(report.updated, ['risk_level']);
|
||||
assert.deepEqual(report.failed, ['current_phase']);
|
||||
|
||||
const fm = readFm(tmpDir);
|
||||
assert.equal(String(fm.risk_level), 'high', 'the custom frontmatter key must still land');
|
||||
assert.notEqual(String(fm.current_phase), '7');
|
||||
});
|
||||
|
||||
test('empty patch still reports both arrays empty', () => {
|
||||
fs.writeFileSync(path.join(tmpDir, '.planning', 'STATE.md'), phaseStateMd);
|
||||
|
||||
const result = runGsdTools(['query', 'state.patch', '{}'], tmpDir);
|
||||
assert.ok(result.success, `state patch failed: ${result.error}`);
|
||||
|
||||
const report = JSON.parse(result.output);
|
||||
assert.deepEqual(report, { updated: [], failed: [] });
|
||||
});
|
||||
});
|
||||
|
||||
test('state update changes a single field', () => {
|
||||
fs.writeFileSync(path.join(tmpDir, '.planning', 'STATE.md'), stateMd);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user