fix(settings): route /gsd-settings reads/writes through workstream-aware config path (#2285)

settings.md was reading and writing .planning/config.json directly while
gsd-tools config-get/config-set route to .planning/workstreams/<slug>/config.json
when GSD_WORKSTREAM is active, causing silent write-read drift (Closes #2282).

- config.cjs: add cmdConfigPath() — emits the planningDir-resolved config path as
  plain text (always raw, no JSON wrapping) so shell substitution works correctly
- gsd-tools.cjs: wire config-path subcommand
- settings.md: resolve GSD_CONFIG_PATH via config-path in ensure_and_load_config;
  replace hardcoded cat .planning/config.json and Write to .planning/config.json
  with $GSD_CONFIG_PATH throughout
- phase.cjs: fix renameDecimalPhases to preserve zero-padded prefix (06.3 → 06.2
  not 6.2) — pre-existing test failure on main
- tests/config.test.cjs: add config-path command tests (#2282)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Tom Boucher
2026-04-15 16:46:10 -04:00
committed by GitHub
parent 55877d372f
commit 5f521e0867
4 changed files with 60 additions and 3 deletions

View File

@@ -935,3 +935,41 @@ describe('config-set/config-get context', () => {
assert.ok(fs.existsSync(path.join(contextsDir, 'review.md')), 'review.md should exist');
});
});
// ─── config-path (#2282) ────────────────────────────────────────────────────
describe('config-path command (#2282)', () => {
let tmpDir;
beforeEach(() => {
tmpDir = createTempProject();
runGsdTools('config-ensure-section', tmpDir);
});
afterEach(() => {
cleanup(tmpDir);
});
test('returns root config path when no workstream is active', () => {
const result = runGsdTools('config-path', tmpDir);
assert.ok(result.success, `config-path failed: ${result.error}`);
assert.ok(result.output.trim().endsWith('.planning/config.json'), `expected root config path, got: ${result.output}`);
assert.ok(!result.output.includes('workstreams'), 'should not include workstreams in path');
});
test('returns workstream config path when GSD_WORKSTREAM is set', () => {
const result = runGsdTools('config-path', tmpDir, { GSD_WORKSTREAM: 'my-stream' });
assert.ok(result.success, `config-path failed: ${result.error}`);
assert.ok(result.output.trim().includes('workstreams/my-stream/config.json'), `expected workstream config path, got: ${result.output}`);
});
test('config-path and config-get agree on the active path', () => {
// Write a value via config-set (uses planningDir internally)
runGsdTools('config-set model_profile quality', tmpDir);
// config-path should point to a file containing that value
const pathResult = runGsdTools('config-path', tmpDir);
const configPath = pathResult.output.trim();
const configContent = JSON.parse(require('fs').readFileSync(configPath, 'utf-8'));
assert.strictEqual(configContent.model_profile, 'quality', 'config-path should point to the file config-set wrote');
});
});