fix(#4462): normalize whitespace workstream environment values (#4509)

* test(#4462): expose whitespace workstream scope

* fix(#4462): normalize the workstream environment scope

* docs(#4462): add changeset for #4509

* fix(#4462): normalize sibling planning scope readers

* test(#4462): cover workstream normalization properties

* fix(#4462): share normalized workstream resolution

---------

Co-authored-by: Tom Boucher <trekkie@nomorestars.com>
This commit is contained in:
Michel Moreira
2026-09-15 01:40:15 -03:00
committed by GitHub
parent e5c4941f2a
commit 26e650f312
10 changed files with 145 additions and 12 deletions

View File

@@ -2948,6 +2948,28 @@ describe('phase add --ws workstream-scoped allocation vs sibling git worktrees (
);
});
test('whitespace-only GSD_WORKSTREAM uses the root sibling horizon (#4462)', () => {
const repoDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-4462-blank-ws-'));
activeDirs.push(repoDir);
initWsRepo(repoDir, 2, 'ws-alpha', 39);
addSiblingAtHead(repoDir);
const result = runGsdTools(
['query', 'phase.add', 'Root next'],
repoDir,
{ GSD_WORKSTREAM: ' ' },
);
assert.ok(result.success, `Command failed: ${result.error}`);
const output = JSON.parse(result.output);
assert.strictEqual(output.phase_number, 3);
assert.strictEqual(output.directory, '.planning/phases/03-root-next');
assert.ok(
!fs.existsSync(path.join(repoDir, '.planning', 'workstreams', ' ')),
'an effectively-empty env value must not mint a whitespace-named workstream',
);
});
test('phase next-decimal --ws is unaffected (planningDir-scoped already)', () => {
const repoDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-4225-dec-'));
activeDirs.push(repoDir);
@@ -6203,6 +6225,22 @@ describe('#2028 — phase complete milestone-end + workstream guard', () => {
assert.match(result.error || '', /workstream|--ws/i, 'error should name the workstream requirement');
});
test('refuses to write root when GSD_WORKSTREAM is whitespace only', (t) => {
fs.mkdirSync(path.join(tmpDir, '.planning', 'workstreams', 'alpha'), { recursive: true });
fs.writeFileSync(path.join(tmpDir, '.planning', 'STATE.md'), '# State\n');
fs.writeFileSync(path.join(tmpDir, '.planning', 'ROADMAP.md'), '# Roadmap\n\n### Phase 1: A\n**Goal:** x\n');
const previous = process.env.GSD_WORKSTREAM;
t.after(() => {
if (previous === undefined) delete process.env.GSD_WORKSTREAM;
else process.env.GSD_WORKSTREAM = previous;
});
process.env.GSD_WORKSTREAM = ' \t ';
const result = runGsdTools('phase complete 1', tmpDir);
assert.equal(result.success, false, 'a whitespace workstream must not write the shared root');
assert.match(result.error || '', /workstream|--ws/i);
});
// An explicit --ws satisfies the guard (it sets GSD_WORKSTREAM upstream) AND
// targets that workstream — the write must land in the workstream's own
// STATE.md/ROADMAP.md, leaving root untouched.