mirror of
https://github.com/glittercowboy/get-shit-done
synced 2026-04-25 17:25:23 +02:00
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:
@@ -639,6 +639,11 @@ async function runCommand(command, args, cwd, raw, defaultValue) {
|
||||
break;
|
||||
}
|
||||
|
||||
case 'config-path': {
|
||||
config.cmdConfigPath(cwd, raw);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'agent-skills': {
|
||||
init.cmdAgentSkills(cwd, args[1], raw);
|
||||
break;
|
||||
|
||||
@@ -495,6 +495,17 @@ function getCmdConfigSetModelProfileResultMessage(
|
||||
return paragraphs.join('\n\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* Print the resolved config.json path (workstream-aware). Used by settings.md
|
||||
* so the workflow writes/reads the correct file when a workstream is active (#2282).
|
||||
*/
|
||||
function cmdConfigPath(cwd) {
|
||||
// Always emit as plain text — a file path is used via shell substitution,
|
||||
// never consumed as JSON. Passing raw=true forces plain-text output.
|
||||
const configPath = path.join(planningDir(cwd), 'config.json');
|
||||
output(configPath, true, configPath);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
VALID_CONFIG_KEYS,
|
||||
cmdConfigEnsureSection,
|
||||
@@ -502,4 +513,5 @@ module.exports = {
|
||||
cmdConfigGet,
|
||||
cmdConfigSetModelProfile,
|
||||
cmdConfigNewProject,
|
||||
cmdConfigPath,
|
||||
};
|
||||
|
||||
@@ -13,16 +13,18 @@ Ensure config exists and load current state:
|
||||
|
||||
```bash
|
||||
node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" config-ensure-section
|
||||
GSD_CONFIG_PATH=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" config-path)
|
||||
INIT=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" state load)
|
||||
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
|
||||
```
|
||||
|
||||
Creates `.planning/config.json` with defaults if missing and loads current config values.
|
||||
Creates config.json (at the workstream-aware path) with defaults if missing and loads current config values.
|
||||
Store `$GSD_CONFIG_PATH` — all subsequent reads and writes use this path, not the hardcoded `.planning/config.json`, so active-workstream installs write to the correct location (#2282).
|
||||
</step>
|
||||
|
||||
<step name="read_current">
|
||||
```bash
|
||||
cat .planning/config.json
|
||||
cat "$GSD_CONFIG_PATH"
|
||||
```
|
||||
|
||||
Parse current values (default to `true` if not present):
|
||||
@@ -213,7 +215,7 @@ Merge new settings into existing config.json:
|
||||
}
|
||||
```
|
||||
|
||||
Write updated config to `.planning/config.json`.
|
||||
Write updated config to `$GSD_CONFIG_PATH` (the workstream-aware path resolved in `ensure_and_load_config`). Never hardcode `.planning/config.json` — workstream installs route to `.planning/workstreams/<slug>/config.json`.
|
||||
</step>
|
||||
|
||||
<step name="save_as_defaults">
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user