diff --git a/.changeset/sturdy-jays-cheer.md b/.changeset/sturdy-jays-cheer.md new file mode 100644 index 000000000..262ebebe4 --- /dev/null +++ b/.changeset/sturdy-jays-cheer.md @@ -0,0 +1,5 @@ +--- +type: Fixed +pr: 4501 +--- +**`hooks.commit_types` and `hooks.community` are now settable via `config-set`** — both keys are consumed by shipped hooks (`hooks/gsd-validate-commit.sh`), and `hooks.commit_types` is documented in `docs/COMMANDS.md`, but neither was registered in `config-schema.manifest.json`'s `validKeys`, so `config-set` rejected them with "Unknown config key" — the only way to configure either was hand-editing `.planning/config.json`. (#4443) diff --git a/gsd-core/bin/shared/config-schema.manifest.json b/gsd-core/bin/shared/config-schema.manifest.json index 0212ba304..ad08ba1e7 100644 --- a/gsd-core/bin/shared/config-schema.manifest.json +++ b/gsd-core/bin/shared/config-schema.manifest.json @@ -77,6 +77,8 @@ "workflow.inline_plan_threshold", "hooks.context_warnings", "hooks.workflow_guard", + "hooks.commit_types", + "hooks.community", "workflow.context_coverage_gate", "statusline.show_last_command", "statusline.context_position", diff --git a/tests/config-schema.property.test.cjs b/tests/config-schema.property.test.cjs index 8d02ccdf1..f9081f76d 100644 --- a/tests/config-schema.property.test.cjs +++ b/tests/config-schema.property.test.cjs @@ -24,6 +24,7 @@ const os = require('node:os'); const path = require('node:path'); const fc = require('./helpers/fast-check-setup.cjs'); const { cleanup } = require('./helpers.cjs'); +const { PROBE_TIMEOUT_MS } = require('./helpers/timeouts.cjs'); const { isValidConfigKey, @@ -150,6 +151,22 @@ describe('config-schema: isValidConfigKey properties', () => { assert.equal(result, false, 'empty string must not be a valid config key'); }); + test('hooks.commit_types is a valid config key (#4443)', () => { + assert.equal( + isValidConfigKey('hooks.commit_types'), + true, + 'hooks.commit_types is a documented, hook-consumed key (see the CLI reference) and must be settable via config-set' + ); + }); + + test('hooks.community is a valid config key (sibling of #4443)', () => { + assert.equal( + isValidConfigKey('hooks.community'), + true, + 'hooks.community is the gsd-validate-commit.sh opt-in gate and must be settable via config-set' + ); + }); + // Boundary: null/undefined/number return false (not throw, not true) test('null, undefined, number inputs return false', () => { assert.equal(isValidConfigKey(null), false); @@ -1232,3 +1249,47 @@ describe('feat-3210: workflow and config contracts', () => { }); }); } + +describe('config-set: hooks.commit_types end-to-end (#4443)', () => { + const { spawnSync } = require('node:child_process'); + + test('config-set hooks.commit_types accepts a JSON array and does not report Unknown config key', (t) => { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-4443-')); + t.after(() => cleanup(dir)); + fs.mkdirSync(path.join(dir, '.planning'), { recursive: true }); + fs.writeFileSync(path.join(dir, '.planning', 'config.json'), '{}\n'); + + const gsdTools = path.join(__dirname, '..', 'gsd-core', 'bin', 'gsd-tools.cjs'); + const result = spawnSync( + process.execPath, + [gsdTools, 'config-set', 'hooks.commit_types', '["enhance"]'], + { cwd: dir, encoding: 'utf8', timeout: PROBE_TIMEOUT_MS }, + ); + + assert.equal(result.status, 0, `config-set failed: ${result.stderr || result.stdout}`); + assert.doesNotMatch(result.stdout + result.stderr, /Unknown config key/); + + const written = JSON.parse(fs.readFileSync(path.join(dir, '.planning', 'config.json'), 'utf8')); + assert.deepEqual(written.hooks.commit_types, ['enhance']); + }); + + test('config-set hooks.community accepts a boolean and does not report Unknown config key', (t) => { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-4443-community-')); + t.after(() => cleanup(dir)); + fs.mkdirSync(path.join(dir, '.planning'), { recursive: true }); + fs.writeFileSync(path.join(dir, '.planning', 'config.json'), '{}\n'); + + const gsdTools = path.join(__dirname, '..', 'gsd-core', 'bin', 'gsd-tools.cjs'); + const result = spawnSync( + process.execPath, + [gsdTools, 'config-set', 'hooks.community', 'true'], + { cwd: dir, encoding: 'utf8', timeout: PROBE_TIMEOUT_MS }, + ); + + assert.equal(result.status, 0, `config-set failed: ${result.stderr || result.stdout}`); + assert.doesNotMatch(result.stdout + result.stderr, /Unknown config key/); + + const written = JSON.parse(fs.readFileSync(path.join(dir, '.planning', 'config.json'), 'utf8')); + assert.equal(written.hooks.community, true); + }); +});