fix(init): embed auto_advance/auto_chain_active/mode in init plan-phase output (#2228)

Prevents infinite config-get loops on Kimi K2.5 and other models that
re-execute bash tool calls when they encounter config-get subshell patterns.
Values are now bundled into the init plan-phase JSON so step 15 of
plan-phase.md can read them directly without separate shell calls.

Closes #2192

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Tom Boucher
2026-04-15 14:59:15 -04:00
committed by GitHub
parent 7b85d9e689
commit 875b257c18
4 changed files with 84 additions and 7 deletions

View File

@@ -1358,6 +1358,74 @@ describe('findProjectRoot integration via --cwd', () => {
});
});
// ─────────────────────────────────────────────────────────────────────────────
// #2192: init plan-phase must include auto_advance, auto_chain_active, and mode
// so workflows don't need separate config-get calls that loop on Kimi K2.5
// ─────────────────────────────────────────────────────────────────────────────
describe('#2192: init plan-phase includes auto-advance config to prevent separate config-get loops', () => {
let tmpDir;
beforeEach(() => {
tmpDir = createTempProject();
fs.mkdirSync(path.join(tmpDir, '.planning', 'phases', '01-auth'), { recursive: true });
fs.writeFileSync(
path.join(tmpDir, '.planning', 'ROADMAP.md'),
['# Roadmap', '', '## Milestone v1', '', '### Phase 1: Auth', '**Goal:** Auth'].join('\n')
);
});
afterEach(() => {
cleanup(tmpDir);
});
test('init plan-phase includes auto_advance field (defaults false)', () => {
const result = runGsdTools('init plan-phase 1', tmpDir);
assert.ok(result.success, `Command failed: ${result.error}`);
const output = JSON.parse(result.output);
assert.ok('auto_advance' in output, 'init plan-phase must include auto_advance field');
assert.strictEqual(output.auto_advance, false, 'auto_advance should default to false');
});
test('init plan-phase includes auto_chain_active field (defaults false)', () => {
const result = runGsdTools('init plan-phase 1', tmpDir);
assert.ok(result.success, `Command failed: ${result.error}`);
const output = JSON.parse(result.output);
assert.ok('auto_chain_active' in output, 'init plan-phase must include auto_chain_active field');
assert.strictEqual(output.auto_chain_active, false, 'auto_chain_active should default to false');
});
test('init plan-phase includes mode field (defaults to interactive)', () => {
const result = runGsdTools('init plan-phase 1', tmpDir);
assert.ok(result.success, `Command failed: ${result.error}`);
const output = JSON.parse(result.output);
assert.ok('mode' in output, 'init plan-phase must include mode field');
assert.strictEqual(output.mode, 'interactive', 'mode should default to interactive');
});
test('init plan-phase reflects auto_advance true when set in config', () => {
const configPath = path.join(tmpDir, '.planning', 'config.json');
const cfg = { workflow: { auto_advance: true } };
fs.writeFileSync(configPath, JSON.stringify(cfg, null, 2));
const result = runGsdTools('init plan-phase 1', tmpDir);
assert.ok(result.success, `Command failed: ${result.error}`);
const output = JSON.parse(result.output);
assert.strictEqual(output.auto_advance, true, 'auto_advance should reflect config value');
});
test('init plan-phase reflects auto_chain_active true when set in config', () => {
const configPath = path.join(tmpDir, '.planning', 'config.json');
const cfg = { workflow: { _auto_chain_active: true } };
fs.writeFileSync(configPath, JSON.stringify(cfg, null, 2));
const result = runGsdTools('init plan-phase 1', tmpDir);
assert.ok(result.success, `Command failed: ${result.error}`);
const output = JSON.parse(result.output);
assert.strictEqual(output.auto_chain_active, true, 'auto_chain_active should reflect config value');
});
});
// ─────────────────────────────────────────────────────────────────────────────
// roadmap analyze command
// ─────────────────────────────────────────────────────────────────────────────