mirror of
https://github.com/glittercowboy/get-shit-done
synced 2026-04-25 17:25:23 +02:00
The sliding-window pattern serialized discuss to one phase at a time even when phases had no dependency relationship. Replaced it with a simple predicate: every undiscussed phase whose dependencies are satisfied is marked is_next_to_discuss, letting the user pick any of them from the manager's recommended_actions list. Closes #2268 Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
113 lines
4.0 KiB
JavaScript
113 lines
4.0 KiB
JavaScript
/**
|
|
* Regression test for bug #2268
|
|
*
|
|
* cmdInitProgress used a sliding-window pattern that set is_next_to_discuss
|
|
* only on the FIRST undiscussed phase. Multiple independent undiscussed phases
|
|
* could not be discussed in parallel — the manager only ever recommended one
|
|
* discuss action at a time.
|
|
*
|
|
* Fix: mark ALL undiscussed phases as is_next_to_discuss = true so the user
|
|
* can pick any of them.
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
const { test, describe, beforeEach, afterEach } = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const { runGsdTools, createTempProject, cleanup } = require('./helpers.cjs');
|
|
|
|
function writeRoadmap(tmpDir, phases) {
|
|
const sections = phases.map(p => {
|
|
let section = `### Phase ${p.number}: ${p.name}\n\n**Goal:** Do the thing\n`;
|
|
return section;
|
|
}).join('\n');
|
|
const checklist = phases.map(p => {
|
|
const mark = p.complete ? 'x' : ' ';
|
|
return `- [${mark}] **Phase ${p.number}: ${p.name}**`;
|
|
}).join('\n');
|
|
fs.writeFileSync(
|
|
path.join(tmpDir, '.planning', 'ROADMAP.md'),
|
|
`# Roadmap\n\n## Progress\n\n${checklist}\n\n${sections}`
|
|
);
|
|
}
|
|
|
|
function writeState(tmpDir) {
|
|
fs.writeFileSync(path.join(tmpDir, '.planning', 'STATE.md'), '---\nstatus: active\n---\n# State\n');
|
|
}
|
|
|
|
let tmpDir;
|
|
|
|
describe('bug #2268: parallel discuss — all undiscussed phases marked is_next_to_discuss', () => {
|
|
beforeEach(() => { tmpDir = createTempProject(); });
|
|
afterEach(() => { cleanup(tmpDir); });
|
|
|
|
test('two undiscussed phases: both marked is_next_to_discuss', () => {
|
|
writeState(tmpDir);
|
|
writeRoadmap(tmpDir, [
|
|
{ number: '1', name: 'Foundation' },
|
|
{ number: '2', name: 'Cloud Deployment' },
|
|
]);
|
|
|
|
const result = runGsdTools('init manager', tmpDir);
|
|
const output = JSON.parse(result.output);
|
|
|
|
assert.strictEqual(output.phases[0].is_next_to_discuss, true, 'phase 1 should be discussable');
|
|
assert.strictEqual(output.phases[1].is_next_to_discuss, true, 'phase 2 should also be discussable');
|
|
});
|
|
|
|
test('two undiscussed phases: both get discuss recommendations', () => {
|
|
writeState(tmpDir);
|
|
writeRoadmap(tmpDir, [
|
|
{ number: '1', name: 'Foundation' },
|
|
{ number: '2', name: 'Cloud Deployment' },
|
|
]);
|
|
|
|
const result = runGsdTools('init manager', tmpDir);
|
|
const output = JSON.parse(result.output);
|
|
|
|
const discussActions = output.recommended_actions.filter(a => a.action === 'discuss');
|
|
assert.strictEqual(discussActions.length, 2, 'should recommend discuss for both undiscussed phases');
|
|
|
|
const phases = discussActions.map(a => a.phase).sort();
|
|
assert.deepStrictEqual(phases, ['1', '2']);
|
|
});
|
|
|
|
test('five undiscussed phases: all five marked is_next_to_discuss', () => {
|
|
writeState(tmpDir);
|
|
writeRoadmap(tmpDir, [
|
|
{ number: '1', name: 'Alpha' },
|
|
{ number: '2', name: 'Beta' },
|
|
{ number: '3', name: 'Gamma' },
|
|
{ number: '4', name: 'Delta' },
|
|
{ number: '5', name: 'Epsilon' },
|
|
]);
|
|
|
|
const result = runGsdTools('init manager', tmpDir);
|
|
const output = JSON.parse(result.output);
|
|
|
|
for (const phase of output.phases) {
|
|
assert.strictEqual(phase.is_next_to_discuss, true, `phase ${phase.number} should be discussable`);
|
|
}
|
|
});
|
|
|
|
test('discussed phase stays false; undiscussed sibling is true', () => {
|
|
writeState(tmpDir);
|
|
writeRoadmap(tmpDir, [
|
|
{ number: '1', name: 'Foundation' },
|
|
{ number: '2', name: 'API Layer' },
|
|
]);
|
|
// scaffold CONTEXT.md to mark phase 1 as discussed
|
|
const dir = path.join(tmpDir, '.planning', 'phases', '01-foundation');
|
|
fs.mkdirSync(dir, { recursive: true });
|
|
fs.writeFileSync(path.join(dir, '01-CONTEXT.md'), '# Context');
|
|
|
|
const result = runGsdTools('init manager', tmpDir);
|
|
const output = JSON.parse(result.output);
|
|
|
|
assert.strictEqual(output.phases[0].is_next_to_discuss, false, 'discussed phase must not be is_next_to_discuss');
|
|
assert.strictEqual(output.phases[1].is_next_to_discuss, true, 'undiscussed sibling must be is_next_to_discuss');
|
|
});
|
|
});
|