diff --git a/bin/gstack-config b/bin/gstack-config index 0cec75b6a5..2a6e9ff688 100755 --- a/bin/gstack-config +++ b/bin/gstack-config @@ -100,6 +100,7 @@ lookup_default() { skill_prefix) echo "false" ;; checkpoint_mode) echo "explicit" ;; checkpoint_push) echo "false" ;; + explain_level) echo "default" ;; codex_reviews) echo "enabled" ;; gstack_contributor) echo "false" ;; skip_eng_review) echo "false" ;; @@ -169,8 +170,8 @@ case "${1:-}" in echo "" echo "# ─── Active values (including defaults for unset keys) ───" for KEY in proactive routing_declined telemetry auto_upgrade update_check \ - skill_prefix checkpoint_mode checkpoint_push codex_reviews \ - gstack_contributor skip_eng_review workspace_root \ + skill_prefix checkpoint_mode checkpoint_push explain_level \ + codex_reviews gstack_contributor skip_eng_review workspace_root \ artifacts_sync_mode artifacts_sync_mode_prompted; do VALUE=$(grep -E "^${KEY}:" "$CONFIG_FILE" 2>/dev/null | tail -1 | awk '{print $2}' | tr -d '[:space:]' || true) SOURCE="default" @@ -185,8 +186,8 @@ case "${1:-}" in defaults) echo "# gstack-config defaults" for KEY in proactive routing_declined telemetry auto_upgrade update_check \ - skill_prefix checkpoint_mode checkpoint_push codex_reviews \ - gstack_contributor skip_eng_review workspace_root \ + skill_prefix checkpoint_mode checkpoint_push explain_level \ + codex_reviews gstack_contributor skip_eng_review workspace_root \ artifacts_sync_mode artifacts_sync_mode_prompted; do printf ' %-24s %s\n' "$KEY:" "$(lookup_default "$KEY")" done diff --git a/test/docs-config-keys.test.ts b/test/docs-config-keys.test.ts index 9fcfc787be..a80d8c882e 100644 --- a/test/docs-config-keys.test.ts +++ b/test/docs-config-keys.test.ts @@ -46,6 +46,14 @@ function scanDocsForConfigKeys(): { docPath: string; key: string; line: number } return hits; } +function runConfig(args: string[], tmpHome: string) { + return spawnSync(CONFIG_BIN, args, { + encoding: 'utf-8', + env: { ...process.env, HOME: tmpHome, GSTACK_HOME: tmpHome }, + timeout: 5000, + }); +} + describe('docs ↔ gstack-config key drift guard', () => { test('docs/ references at least one config key (smoke)', () => { const hits = scanDocsForConfigKeys(); @@ -65,15 +73,32 @@ describe('docs ↔ gstack-config key drift guard', () => { // without a Git Bash interpreter shim. Skip on Windows — the deprecated-key // denylist test above already pins the v1.27.0.0 rename behavior at the // doc layer, which is the actual invariant this wave defends. + test.skipIf(process.platform === 'win32')('`explain_level` is exposed as a documented default', () => { + const tmpHome = fs.mkdtempSync(path.join(require('os').tmpdir(), 'gstack-cfg-')); + try { + const get = runConfig(['get', 'explain_level'], tmpHome); + expect(get.status).toBe(0); + expect(get.stdout.trim()).toBe('default'); + + const defaults = runConfig(['defaults'], tmpHome); + expect(defaults.status).toBe(0); + expect(defaults.stdout).toContain('explain_level:'); + expect(defaults.stdout).toContain('default'); + + const list = runConfig(['list'], tmpHome); + expect(list.status).toBe(0); + expect(list.stdout).toContain('explain_level:'); + expect(list.stdout).toContain('default'); + } finally { + fs.rmSync(tmpHome, { recursive: true, force: true }); + } + }); + test.skipIf(process.platform === 'win32')('`gstack-config get artifacts_sync_mode` returns a value (the rename landed)', () => { // Run from a clean HOME so the user's local config doesn't pollute. const tmpHome = fs.mkdtempSync(path.join(require('os').tmpdir(), 'gstack-cfg-')); try { - const result = spawnSync(CONFIG_BIN, ['get', 'artifacts_sync_mode'], { - encoding: 'utf-8', - env: { ...process.env, HOME: tmpHome, GSTACK_HOME: tmpHome }, - timeout: 5000, - }); + const result = runConfig(['get', 'artifacts_sync_mode'], tmpHome); expect(result.status).toBe(0); // A known key returns its default value, not the "unknown key" error string. expect(result.stderr).not.toContain('not recognized');