* 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:
@@ -4744,6 +4744,70 @@ describe('#3581: init.progress next_phase prefers the roadmap frontier', () => {
|
||||
assert.equal(eight.directory, null, 'Phase 8 has no directory (corroborating the stray-only-disk shape)');
|
||||
});
|
||||
|
||||
test('#4023: init.progress sorts decimal phase ids before choosing the roadmap frontier', (t) => {
|
||||
const tmpDir = createTempProject('gsd-4023-init-');
|
||||
t.after(() => cleanup(tmpDir));
|
||||
fs.writeFileSync(path.join(tmpDir, '.planning', 'ROADMAP.md'), [
|
||||
'# Roadmap',
|
||||
'',
|
||||
'## Milestone v1.1.0',
|
||||
'',
|
||||
'### Phase 12: Parent',
|
||||
'**Goal:** g',
|
||||
'',
|
||||
'### Phase 12.1: Inserted fix',
|
||||
'**Goal:** g',
|
||||
'',
|
||||
'### Phase 12.2: Second insert',
|
||||
'**Goal:** g',
|
||||
'',
|
||||
'### Phase 12.10: Tenth insert',
|
||||
'**Goal:** g',
|
||||
'',
|
||||
'### Phase 13: Follow-up',
|
||||
'**Goal:** g',
|
||||
'',
|
||||
].join('\n'));
|
||||
fs.writeFileSync(path.join(tmpDir, '.planning', 'STATE.md'), [
|
||||
'---',
|
||||
'gsd_state_version: 1.0',
|
||||
'milestone: v1.1.0',
|
||||
'milestone_name: Active',
|
||||
'status: executing',
|
||||
'current_phase: 12.1',
|
||||
'progress:',
|
||||
' total_phases: 13',
|
||||
' completed_phases: 11',
|
||||
' percent: 85',
|
||||
'---',
|
||||
'',
|
||||
'# Project State',
|
||||
'',
|
||||
'## Current Position',
|
||||
'',
|
||||
'Phase: 12.1',
|
||||
'Status: Executing',
|
||||
'',
|
||||
].join('\n'));
|
||||
for (const dir of ['12.1-inserted-fix', '12.10-tenth-insert']) {
|
||||
fs.mkdirSync(path.join(tmpDir, '.planning', 'phases', dir), { recursive: true });
|
||||
}
|
||||
|
||||
const result = runGsdTools(['init', 'progress', '--raw'], tmpDir);
|
||||
assert.ok(result.success, `init progress failed: ${result.error}`);
|
||||
const out = JSON.parse(result.output);
|
||||
assert.deepEqual(
|
||||
out.phases.map((phase) => String(phase.number).replace(/^0+(?=\d)/, '')),
|
||||
['12', '12.1', '12.2', '12.10', '13'],
|
||||
'the disk/roadmap union follows component-wise phase-id order (12.2 before 12.10)',
|
||||
);
|
||||
assert.equal(
|
||||
String(out.next_phase.number).replace(/^0+(?=\d)/, ''),
|
||||
'12',
|
||||
'the pending parent remains the frontier when an inserted decimal directory exists first',
|
||||
);
|
||||
});
|
||||
|
||||
test('#3581 (control): a pending roadmap-only phase outranks a later pending directory', (t) => {
|
||||
writeProgressFixture(t, { strayNine: false });
|
||||
// pure ordering property, no stray artifacts: roadmap-only pending 8 vs a
|
||||
@@ -4941,4 +5005,3 @@ describe('init — GSD_PROJECT scoping (#3964)', () => {
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -327,7 +327,7 @@ describe('smart-entry: real STATE.md schema (nested progress YAML + body Phase f
|
||||
roadmap: true,
|
||||
}));
|
||||
const signals = detectSignals(dir);
|
||||
assert.equal(signals.current_phase, 3, 'current_phase from body Phase: field');
|
||||
assert.equal(signals.current_phase, '3', 'current_phase from body Phase: field');
|
||||
assert.equal(signals.total_phases, 5, 'total_phases from nested progress.total_phases');
|
||||
assert.equal(signals.progress, 40, 'percent from nested progress.percent');
|
||||
assert.equal(signals.status, 'verifying');
|
||||
@@ -457,6 +457,19 @@ describe('smart-entry: JSON shape invariants', () => {
|
||||
describe('smart-entry: CLI dispatch (gsd-tools smart-entry)', () => {
|
||||
afterEach(removeAll);
|
||||
|
||||
test('#4023: --json preserves decimal current_phase as a phase-id string', () => {
|
||||
for (const phase of ['12.1', '12.10']) {
|
||||
const dir = track(makeProject({
|
||||
state: state({ status: 'executing', total_phases: 13, current_phase: phase }),
|
||||
roadmap: true,
|
||||
}));
|
||||
const r = runNode([TOOLS, 'smart-entry', '--json', '--cwd', dir], { timeoutMs: PROBE_TIMEOUT_MS });
|
||||
throwIfFailed(r, 'gsd-tools smart-entry --json');
|
||||
const out = JSON.parse(r.stdout);
|
||||
assert.equal(out.signals.current_phase, phase);
|
||||
}
|
||||
});
|
||||
|
||||
test('--json in an empty dir returns no-project machine JSON', () => {
|
||||
// A bare tmpdir with no .planning is a true no-project.
|
||||
const bare = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-se-bare-'));
|
||||
|
||||
@@ -6157,7 +6157,7 @@ describe('#3187 chain-owner identity — every consumer agrees with stateFieldVa
|
||||
stateDocument.stateFieldValue(fm, body, null, 'Phase').value;
|
||||
|
||||
const output = JSON.parse(runGsdTools('smart-entry --json', tmpDir).output);
|
||||
assert.strictEqual(output.signals.current_phase, parseInt(ownerPhaseRaw, 10));
|
||||
assert.strictEqual(output.signals.current_phase, ownerPhaseRaw);
|
||||
});
|
||||
|
||||
test('C5: workstream projection matches the owner', () => {
|
||||
@@ -6258,7 +6258,7 @@ describe('#3187 chain-owner identity — every consumer agrees with stateFieldVa
|
||||
|
||||
// C4: smart-entry
|
||||
const smartEntry = JSON.parse(runGsdTools('smart-entry --json', tmpDir).output);
|
||||
assert.strictEqual(smartEntry.signals.current_phase, Number(ownerPhase));
|
||||
assert.strictEqual(smartEntry.signals.current_phase, ownerPhase);
|
||||
|
||||
// C6: complete-phase idempotency guard (no frontmatter in this fixture,
|
||||
// so this row does not exercise the frontmatter tier — see C6's own test
|
||||
|
||||
Reference in New Issue
Block a user