mirror of
https://github.com/glittercowboy/get-shit-done
synced 2026-04-25 17:25:23 +02:00
* fix: remove deprecated Codex config keys causing UI instability (closes #1037) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: update codex config tests to match simplified config structure Tests asserted the old config structure ([features] section, multi_agent, default_mode_request_user_input, [agents] table with max_threads/max_depth) that was deliberately removed. Tests now verify the new behavior: config block contains only the GSD marker and per-agent [agents.gsd-*] sections. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
495 lines
22 KiB
JavaScript
495 lines
22 KiB
JavaScript
/**
|
|
* GSD Tools Tests - codex-config.cjs
|
|
*
|
|
* Tests for Codex adapter header, agent conversion, config.toml generation/merge,
|
|
* per-agent .toml generation, and uninstall cleanup.
|
|
*/
|
|
|
|
// Enable test exports from install.js (skips main CLI logic)
|
|
process.env.GSD_TEST_MODE = '1';
|
|
|
|
const { test, describe, beforeEach, afterEach } = require('node:test');
|
|
const assert = require('node:assert');
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const os = require('os');
|
|
|
|
const {
|
|
getCodexSkillAdapterHeader,
|
|
convertClaudeAgentToCodexAgent,
|
|
generateCodexAgentToml,
|
|
generateCodexConfigBlock,
|
|
stripGsdFromCodexConfig,
|
|
mergeCodexConfig,
|
|
GSD_CODEX_MARKER,
|
|
CODEX_AGENT_SANDBOX,
|
|
} = require('../bin/install.js');
|
|
|
|
// ─── getCodexSkillAdapterHeader ─────────────────────────────────────────────────
|
|
|
|
describe('getCodexSkillAdapterHeader', () => {
|
|
test('contains all three sections', () => {
|
|
const result = getCodexSkillAdapterHeader('gsd-execute-phase');
|
|
assert.ok(result.includes('<codex_skill_adapter>'), 'has opening tag');
|
|
assert.ok(result.includes('</codex_skill_adapter>'), 'has closing tag');
|
|
assert.ok(result.includes('## A. Skill Invocation'), 'has section A');
|
|
assert.ok(result.includes('## B. AskUserQuestion'), 'has section B');
|
|
assert.ok(result.includes('## C. Task() → spawn_agent'), 'has section C');
|
|
});
|
|
|
|
test('includes correct invocation syntax', () => {
|
|
const result = getCodexSkillAdapterHeader('gsd-plan-phase');
|
|
assert.ok(result.includes('`$gsd-plan-phase`'), 'has $skillName invocation');
|
|
assert.ok(result.includes('{{GSD_ARGS}}'), 'has GSD_ARGS variable');
|
|
});
|
|
|
|
test('section B maps AskUserQuestion parameters', () => {
|
|
const result = getCodexSkillAdapterHeader('gsd-discuss-phase');
|
|
assert.ok(result.includes('request_user_input'), 'maps to request_user_input');
|
|
assert.ok(result.includes('header'), 'maps header parameter');
|
|
assert.ok(result.includes('question'), 'maps question parameter');
|
|
assert.ok(result.includes('label'), 'maps options label');
|
|
assert.ok(result.includes('description'), 'maps options description');
|
|
assert.ok(result.includes('multiSelect'), 'documents multiSelect workaround');
|
|
assert.ok(result.includes('Execute mode'), 'documents Execute mode fallback');
|
|
});
|
|
|
|
test('section C maps Task to spawn_agent', () => {
|
|
const result = getCodexSkillAdapterHeader('gsd-execute-phase');
|
|
assert.ok(result.includes('spawn_agent'), 'maps to spawn_agent');
|
|
assert.ok(result.includes('agent_type'), 'maps subagent_type to agent_type');
|
|
assert.ok(result.includes('fork_context'), 'documents fork_context default');
|
|
assert.ok(result.includes('wait(ids)'), 'documents parallel wait pattern');
|
|
assert.ok(result.includes('close_agent'), 'documents close_agent cleanup');
|
|
assert.ok(result.includes('CHECKPOINT'), 'documents result markers');
|
|
});
|
|
});
|
|
|
|
// ─── convertClaudeAgentToCodexAgent ─────────────────────────────────────────────
|
|
|
|
describe('convertClaudeAgentToCodexAgent', () => {
|
|
test('adds codex_agent_role header and cleans frontmatter', () => {
|
|
const input = `---
|
|
name: gsd-executor
|
|
description: Executes GSD plans with atomic commits
|
|
tools: Read, Write, Edit, Bash, Grep, Glob
|
|
color: yellow
|
|
---
|
|
|
|
<role>
|
|
You are a GSD plan executor.
|
|
</role>`;
|
|
|
|
const result = convertClaudeAgentToCodexAgent(input);
|
|
|
|
// Frontmatter rebuilt with only name and description
|
|
assert.ok(result.startsWith('---\n'), 'starts with frontmatter');
|
|
assert.ok(result.includes('"gsd-executor"'), 'has quoted name');
|
|
assert.ok(result.includes('"Executes GSD plans with atomic commits"'), 'has quoted description');
|
|
assert.ok(!result.includes('color: yellow'), 'drops color field');
|
|
// Tools should be in <codex_agent_role> but NOT in frontmatter
|
|
const fmEnd = result.indexOf('---', 4);
|
|
const frontmatterSection = result.substring(0, fmEnd);
|
|
assert.ok(!frontmatterSection.includes('tools:'), 'drops tools from frontmatter');
|
|
|
|
// Has codex_agent_role block
|
|
assert.ok(result.includes('<codex_agent_role>'), 'has role header');
|
|
assert.ok(result.includes('role: gsd-executor'), 'role matches agent name');
|
|
assert.ok(result.includes('tools: Read, Write, Edit, Bash, Grep, Glob'), 'tools in role block');
|
|
assert.ok(result.includes('purpose: Executes GSD plans with atomic commits'), 'purpose from description');
|
|
assert.ok(result.includes('</codex_agent_role>'), 'has closing tag');
|
|
|
|
// Body preserved
|
|
assert.ok(result.includes('<role>'), 'body content preserved');
|
|
});
|
|
|
|
test('converts slash commands in body', () => {
|
|
const input = `---
|
|
name: gsd-test
|
|
description: Test agent
|
|
tools: Read
|
|
---
|
|
|
|
Run /gsd:execute-phase to proceed.`;
|
|
|
|
const result = convertClaudeAgentToCodexAgent(input);
|
|
assert.ok(result.includes('$gsd-execute-phase'), 'converts slash commands');
|
|
assert.ok(!result.includes('/gsd:execute-phase'), 'original slash command removed');
|
|
});
|
|
|
|
test('handles content without frontmatter', () => {
|
|
const input = 'Just some content without frontmatter.';
|
|
const result = convertClaudeAgentToCodexAgent(input);
|
|
assert.strictEqual(result, input, 'returns input unchanged');
|
|
});
|
|
});
|
|
|
|
// ─── generateCodexAgentToml ─────────────────────────────────────────────────────
|
|
|
|
describe('generateCodexAgentToml', () => {
|
|
const sampleAgent = `---
|
|
name: gsd-executor
|
|
description: Executes plans
|
|
tools: Read, Write, Edit
|
|
color: yellow
|
|
---
|
|
|
|
<role>You are an executor.</role>`;
|
|
|
|
test('sets workspace-write for executor', () => {
|
|
const result = generateCodexAgentToml('gsd-executor', sampleAgent);
|
|
assert.ok(result.includes('sandbox_mode = "workspace-write"'), 'has workspace-write');
|
|
});
|
|
|
|
test('sets read-only for plan-checker', () => {
|
|
const checker = `---
|
|
name: gsd-plan-checker
|
|
description: Checks plans
|
|
tools: Read, Grep, Glob
|
|
---
|
|
|
|
<role>You check plans.</role>`;
|
|
const result = generateCodexAgentToml('gsd-plan-checker', checker);
|
|
assert.ok(result.includes('sandbox_mode = "read-only"'), 'has read-only');
|
|
});
|
|
|
|
test('includes developer_instructions from body', () => {
|
|
const result = generateCodexAgentToml('gsd-executor', sampleAgent);
|
|
assert.ok(result.includes("developer_instructions = '''"), 'has literal triple-quoted instructions');
|
|
assert.ok(result.includes('<role>You are an executor.</role>'), 'body content in instructions');
|
|
assert.ok(result.includes("'''"), 'has closing literal triple quotes');
|
|
});
|
|
|
|
test('defaults unknown agents to read-only', () => {
|
|
const result = generateCodexAgentToml('gsd-unknown', sampleAgent);
|
|
assert.ok(result.includes('sandbox_mode = "read-only"'), 'defaults to read-only');
|
|
});
|
|
});
|
|
|
|
// ─── CODEX_AGENT_SANDBOX mapping ────────────────────────────────────────────────
|
|
|
|
describe('CODEX_AGENT_SANDBOX', () => {
|
|
test('has all 11 agents mapped', () => {
|
|
const agentNames = Object.keys(CODEX_AGENT_SANDBOX);
|
|
assert.strictEqual(agentNames.length, 11, 'has 11 agents');
|
|
});
|
|
|
|
test('workspace-write agents have write tools', () => {
|
|
const writeAgents = [
|
|
'gsd-executor', 'gsd-planner', 'gsd-phase-researcher',
|
|
'gsd-project-researcher', 'gsd-research-synthesizer', 'gsd-verifier',
|
|
'gsd-codebase-mapper', 'gsd-roadmapper', 'gsd-debugger',
|
|
];
|
|
for (const name of writeAgents) {
|
|
assert.strictEqual(CODEX_AGENT_SANDBOX[name], 'workspace-write', `${name} is workspace-write`);
|
|
}
|
|
});
|
|
|
|
test('read-only agents have no write tools', () => {
|
|
const readOnlyAgents = ['gsd-plan-checker', 'gsd-integration-checker'];
|
|
for (const name of readOnlyAgents) {
|
|
assert.strictEqual(CODEX_AGENT_SANDBOX[name], 'read-only', `${name} is read-only`);
|
|
}
|
|
});
|
|
});
|
|
|
|
// ─── generateCodexConfigBlock ───────────────────────────────────────────────────
|
|
|
|
describe('generateCodexConfigBlock', () => {
|
|
const agents = [
|
|
{ name: 'gsd-executor', description: 'Executes plans' },
|
|
{ name: 'gsd-planner', description: 'Creates plans' },
|
|
];
|
|
|
|
test('starts with GSD marker', () => {
|
|
const result = generateCodexConfigBlock(agents);
|
|
assert.ok(result.startsWith(GSD_CODEX_MARKER), 'starts with marker');
|
|
});
|
|
|
|
test('does not include feature flags or agents table header', () => {
|
|
const result = generateCodexConfigBlock(agents);
|
|
assert.ok(!result.includes('[features]'), 'no features table');
|
|
assert.ok(!result.includes('multi_agent'), 'no multi_agent');
|
|
assert.ok(!result.includes('default_mode_request_user_input'), 'no request_user_input');
|
|
// Should not have bare [agents] table header (only [agents.gsd-*] sections)
|
|
assert.ok(!result.match(/^\[agents\]\s*$/m), 'no bare [agents] table');
|
|
assert.ok(!result.includes('max_threads'), 'no max_threads');
|
|
assert.ok(!result.includes('max_depth'), 'no max_depth');
|
|
});
|
|
|
|
test('includes per-agent sections', () => {
|
|
const result = generateCodexConfigBlock(agents);
|
|
assert.ok(result.includes('[agents.gsd-executor]'), 'has executor section');
|
|
assert.ok(result.includes('[agents.gsd-planner]'), 'has planner section');
|
|
assert.ok(result.includes('config_file = "agents/gsd-executor.toml"'), 'has executor config_file');
|
|
assert.ok(result.includes('"Executes plans"'), 'has executor description');
|
|
});
|
|
});
|
|
|
|
// ─── stripGsdFromCodexConfig ────────────────────────────────────────────────────
|
|
|
|
describe('stripGsdFromCodexConfig', () => {
|
|
test('returns null for GSD-only config', () => {
|
|
const content = `${GSD_CODEX_MARKER}\n[features]\nmulti_agent = true\n`;
|
|
const result = stripGsdFromCodexConfig(content);
|
|
assert.strictEqual(result, null, 'returns null when GSD-only');
|
|
});
|
|
|
|
test('preserves user content before marker', () => {
|
|
const content = `[model]\nname = "o3"\n\n${GSD_CODEX_MARKER}\n[features]\nmulti_agent = true\n`;
|
|
const result = stripGsdFromCodexConfig(content);
|
|
assert.ok(result.includes('[model]'), 'preserves user section');
|
|
assert.ok(result.includes('name = "o3"'), 'preserves user values');
|
|
assert.ok(!result.includes('multi_agent'), 'removes GSD content');
|
|
assert.ok(!result.includes(GSD_CODEX_MARKER), 'removes marker');
|
|
});
|
|
|
|
test('strips injected feature keys without marker', () => {
|
|
const content = `[features]\nmulti_agent = true\ndefault_mode_request_user_input = true\nother_feature = false\n`;
|
|
const result = stripGsdFromCodexConfig(content);
|
|
assert.ok(!result.includes('multi_agent'), 'removes multi_agent');
|
|
assert.ok(!result.includes('default_mode_request_user_input'), 'removes request_user_input');
|
|
assert.ok(result.includes('other_feature = false'), 'preserves user features');
|
|
});
|
|
|
|
test('removes empty [features] section', () => {
|
|
const content = `[features]\nmulti_agent = true\n[model]\nname = "o3"\n`;
|
|
const result = stripGsdFromCodexConfig(content);
|
|
assert.ok(!result.includes('[features]'), 'removes empty features section');
|
|
assert.ok(result.includes('[model]'), 'preserves other sections');
|
|
});
|
|
|
|
test('strips injected keys above marker on uninstall', () => {
|
|
// Case 3 install injects keys into [features] AND appends marker block
|
|
const content = `[model]\nname = "o3"\n\n[features]\nmulti_agent = true\ndefault_mode_request_user_input = true\nsome_custom_flag = true\n\n${GSD_CODEX_MARKER}\n[agents]\nmax_threads = 4\n`;
|
|
const result = stripGsdFromCodexConfig(content);
|
|
assert.ok(result.includes('[model]'), 'preserves user model section');
|
|
assert.ok(result.includes('some_custom_flag = true'), 'preserves user feature');
|
|
assert.ok(!result.includes('multi_agent'), 'strips injected multi_agent');
|
|
assert.ok(!result.includes('default_mode_request_user_input'), 'strips injected request_user_input');
|
|
assert.ok(!result.includes(GSD_CODEX_MARKER), 'strips marker');
|
|
});
|
|
|
|
test('removes [agents.gsd-*] sections', () => {
|
|
const content = `[agents.gsd-executor]\ndescription = "test"\nconfig_file = "agents/gsd-executor.toml"\n\n[agents.custom-agent]\ndescription = "user agent"\n`;
|
|
const result = stripGsdFromCodexConfig(content);
|
|
assert.ok(!result.includes('[agents.gsd-executor]'), 'removes GSD agent section');
|
|
assert.ok(result.includes('[agents.custom-agent]'), 'preserves user agent section');
|
|
});
|
|
});
|
|
|
|
// ─── mergeCodexConfig ───────────────────────────────────────────────────────────
|
|
|
|
describe('mergeCodexConfig', () => {
|
|
let tmpDir;
|
|
|
|
beforeEach(() => {
|
|
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-codex-merge-'));
|
|
});
|
|
|
|
afterEach(() => {
|
|
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
});
|
|
|
|
const sampleBlock = generateCodexConfigBlock([
|
|
{ name: 'gsd-executor', description: 'Executes plans' },
|
|
]);
|
|
|
|
test('case 1: creates new config.toml', () => {
|
|
const configPath = path.join(tmpDir, 'config.toml');
|
|
mergeCodexConfig(configPath, sampleBlock);
|
|
|
|
assert.ok(fs.existsSync(configPath), 'file created');
|
|
const content = fs.readFileSync(configPath, 'utf8');
|
|
assert.ok(content.includes(GSD_CODEX_MARKER), 'has marker');
|
|
assert.ok(content.includes('[agents.gsd-executor]'), 'has agent');
|
|
assert.ok(!content.includes('[features]'), 'no features section');
|
|
assert.ok(!content.includes('multi_agent'), 'no multi_agent');
|
|
});
|
|
|
|
test('case 2: replaces existing GSD block', () => {
|
|
const configPath = path.join(tmpDir, 'config.toml');
|
|
const userContent = '[model]\nname = "o3"\n';
|
|
fs.writeFileSync(configPath, userContent + '\n' + sampleBlock + '\n');
|
|
|
|
// Re-merge with updated block
|
|
const newBlock = generateCodexConfigBlock([
|
|
{ name: 'gsd-executor', description: 'Updated description' },
|
|
{ name: 'gsd-planner', description: 'New agent' },
|
|
]);
|
|
mergeCodexConfig(configPath, newBlock);
|
|
|
|
const content = fs.readFileSync(configPath, 'utf8');
|
|
assert.ok(content.includes('[model]'), 'preserves user content');
|
|
assert.ok(content.includes('Updated description'), 'has new description');
|
|
assert.ok(content.includes('[agents.gsd-planner]'), 'has new agent');
|
|
// Verify no duplicate markers
|
|
const markerCount = (content.match(new RegExp(GSD_CODEX_MARKER.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g')) || []).length;
|
|
assert.strictEqual(markerCount, 1, 'exactly one marker');
|
|
});
|
|
|
|
test('case 3: appends to config without GSD marker', () => {
|
|
const configPath = path.join(tmpDir, 'config.toml');
|
|
fs.writeFileSync(configPath, '[model]\nname = "o3"\n');
|
|
|
|
mergeCodexConfig(configPath, sampleBlock);
|
|
|
|
const content = fs.readFileSync(configPath, 'utf8');
|
|
assert.ok(content.includes('[model]'), 'preserves user content');
|
|
assert.ok(content.includes(GSD_CODEX_MARKER), 'adds marker');
|
|
assert.ok(content.includes('[agents.gsd-executor]'), 'has agent');
|
|
});
|
|
|
|
test('case 3 with existing [features]: preserves user features, does not inject GSD keys', () => {
|
|
const configPath = path.join(tmpDir, 'config.toml');
|
|
fs.writeFileSync(configPath, '[features]\nother_feature = true\n\n[model]\nname = "o3"\n');
|
|
|
|
mergeCodexConfig(configPath, sampleBlock);
|
|
|
|
const content = fs.readFileSync(configPath, 'utf8');
|
|
assert.ok(content.includes('other_feature = true'), 'preserves existing feature');
|
|
assert.ok(!content.includes('multi_agent'), 'does not inject multi_agent');
|
|
assert.ok(!content.includes('default_mode_request_user_input'), 'does not inject request_user_input');
|
|
assert.ok(content.includes(GSD_CODEX_MARKER), 'adds marker for agents block');
|
|
assert.ok(content.includes('[agents.gsd-executor]'), 'has agent');
|
|
});
|
|
|
|
test('idempotent: re-merge produces same result', () => {
|
|
const configPath = path.join(tmpDir, 'config.toml');
|
|
mergeCodexConfig(configPath, sampleBlock);
|
|
const first = fs.readFileSync(configPath, 'utf8');
|
|
|
|
mergeCodexConfig(configPath, sampleBlock);
|
|
const second = fs.readFileSync(configPath, 'utf8');
|
|
|
|
assert.strictEqual(first, second, 'idempotent merge');
|
|
});
|
|
|
|
test('case 2 after case 3 with existing [features]: no duplicate sections', () => {
|
|
const configPath = path.join(tmpDir, 'config.toml');
|
|
fs.writeFileSync(configPath, '[features]\nother_feature = true\n\n[model]\nname = "o3"\n');
|
|
mergeCodexConfig(configPath, sampleBlock);
|
|
|
|
mergeCodexConfig(configPath, sampleBlock);
|
|
|
|
const content = fs.readFileSync(configPath, 'utf8');
|
|
const featuresCount = (content.match(/^\[features\]\s*$/gm) || []).length;
|
|
assert.strictEqual(featuresCount, 1, 'exactly one [features] section');
|
|
assert.ok(content.includes('other_feature = true'), 'preserves user feature keys');
|
|
assert.ok(content.includes('[agents.gsd-executor]'), 'has agent');
|
|
// Verify no duplicate markers
|
|
const markerCount = (content.match(new RegExp(GSD_CODEX_MARKER.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g')) || []).length;
|
|
assert.strictEqual(markerCount, 1, 'exactly one marker');
|
|
});
|
|
|
|
test('case 2 does not inject feature keys', () => {
|
|
const configPath = path.join(tmpDir, 'config.toml');
|
|
const manualContent = '[features]\nother_feature = true\n\n' + GSD_CODEX_MARKER + '\n[agents.gsd-old]\ndescription = "old"\n';
|
|
fs.writeFileSync(configPath, manualContent);
|
|
|
|
mergeCodexConfig(configPath, sampleBlock);
|
|
|
|
const content = fs.readFileSync(configPath, 'utf8');
|
|
assert.ok(!content.includes('multi_agent'), 'does not inject multi_agent');
|
|
assert.ok(!content.includes('default_mode_request_user_input'), 'does not inject request_user_input');
|
|
assert.ok(content.includes('other_feature = true'), 'preserves user feature');
|
|
assert.ok(content.includes('[agents.gsd-executor]'), 'has agent from fresh block');
|
|
});
|
|
|
|
test('case 2 strips leaked [agents] and [agents.gsd-*] from before content', () => {
|
|
const configPath = path.join(tmpDir, 'config.toml');
|
|
const brokenContent = [
|
|
'[features]',
|
|
'child_agents_md = false',
|
|
'',
|
|
'[agents]',
|
|
'max_threads = 4',
|
|
'max_depth = 2',
|
|
'',
|
|
'[agents.gsd-executor]',
|
|
'description = "old"',
|
|
'config_file = "agents/gsd-executor.toml"',
|
|
'',
|
|
GSD_CODEX_MARKER,
|
|
'',
|
|
'[agents.gsd-executor]',
|
|
'description = "Executes plans"',
|
|
'config_file = "agents/gsd-executor.toml"',
|
|
'',
|
|
].join('\n');
|
|
fs.writeFileSync(configPath, brokenContent);
|
|
|
|
mergeCodexConfig(configPath, sampleBlock);
|
|
|
|
const content = fs.readFileSync(configPath, 'utf8');
|
|
assert.ok(content.includes('child_agents_md = false'), 'preserves user feature keys');
|
|
assert.ok(content.includes('[agents.gsd-executor]'), 'has agent from fresh block');
|
|
// Verify the leaked [agents] table header above marker was stripped
|
|
const markerIndex = content.indexOf(GSD_CODEX_MARKER);
|
|
const beforeMarker = content.substring(0, markerIndex);
|
|
assert.ok(!beforeMarker.match(/^\[agents\]\s*$/m), 'no leaked [agents] above marker');
|
|
assert.ok(!beforeMarker.includes('[agents.gsd-'), 'no leaked [agents.gsd-*] above marker');
|
|
});
|
|
|
|
test('case 2 idempotent after case 3 with existing [features]', () => {
|
|
const configPath = path.join(tmpDir, 'config.toml');
|
|
fs.writeFileSync(configPath, '[features]\nother_feature = true\n');
|
|
mergeCodexConfig(configPath, sampleBlock);
|
|
const first = fs.readFileSync(configPath, 'utf8');
|
|
|
|
mergeCodexConfig(configPath, sampleBlock);
|
|
const second = fs.readFileSync(configPath, 'utf8');
|
|
|
|
mergeCodexConfig(configPath, sampleBlock);
|
|
const third = fs.readFileSync(configPath, 'utf8');
|
|
|
|
assert.strictEqual(first, second, 'idempotent after 2nd merge');
|
|
assert.strictEqual(second, third, 'idempotent after 3rd merge');
|
|
});
|
|
});
|
|
|
|
// ─── Integration: installCodexConfig ────────────────────────────────────────────
|
|
|
|
describe('installCodexConfig (integration)', () => {
|
|
let tmpTarget;
|
|
const agentsSrc = path.join(__dirname, '..', 'agents');
|
|
|
|
beforeEach(() => {
|
|
tmpTarget = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-codex-install-'));
|
|
});
|
|
|
|
afterEach(() => {
|
|
fs.rmSync(tmpTarget, { recursive: true, force: true });
|
|
});
|
|
|
|
// Only run if agents/ directory exists (not in CI without full checkout)
|
|
const hasAgents = fs.existsSync(agentsSrc);
|
|
|
|
(hasAgents ? test : test.skip)('generates config.toml and agent .toml files', () => {
|
|
const { installCodexConfig } = require('../bin/install.js');
|
|
const count = installCodexConfig(tmpTarget, agentsSrc);
|
|
|
|
assert.ok(count >= 11, `installed ${count} agents (expected >= 11)`);
|
|
|
|
// Verify config.toml
|
|
const configPath = path.join(tmpTarget, 'config.toml');
|
|
assert.ok(fs.existsSync(configPath), 'config.toml exists');
|
|
const config = fs.readFileSync(configPath, 'utf8');
|
|
assert.ok(config.includes(GSD_CODEX_MARKER), 'has GSD marker');
|
|
assert.ok(config.includes('[agents.gsd-executor]'), 'has executor agent');
|
|
assert.ok(!config.includes('multi_agent'), 'no feature flags');
|
|
|
|
// Verify per-agent .toml files
|
|
const agentsDir = path.join(tmpTarget, 'agents');
|
|
assert.ok(fs.existsSync(path.join(agentsDir, 'gsd-executor.toml')), 'executor .toml exists');
|
|
assert.ok(fs.existsSync(path.join(agentsDir, 'gsd-plan-checker.toml')), 'plan-checker .toml exists');
|
|
|
|
const executorToml = fs.readFileSync(path.join(agentsDir, 'gsd-executor.toml'), 'utf8');
|
|
assert.ok(executorToml.includes('sandbox_mode = "workspace-write"'), 'executor is workspace-write');
|
|
assert.ok(executorToml.includes('developer_instructions'), 'has developer_instructions');
|
|
|
|
const checkerToml = fs.readFileSync(path.join(agentsDir, 'gsd-plan-checker.toml'), 'utf8');
|
|
assert.ok(checkerToml.includes('sandbox_mode = "read-only"'), 'plan-checker is read-only');
|
|
});
|
|
});
|