feat(installer): add Kiro CLI/IDE target (#385) (#473)

`codegraph install` now detects and configures Kiro alongside the
existing seven agents. Writes `mcpServers.codegraph` to
`~/.kiro/settings/mcp.json` (global) or `./.kiro/settings/mcp.json`
(local), plus a dedicated `~/.kiro/steering/codegraph.md` /
`./.kiro/steering/codegraph.md` instruction file — Kiro's steering
system loads every `*.md` file in `steering/` as agent context, so a
dedicated file is the natural surface (no marker-based merging needed).

Sibling MCP servers in `mcp.json` and unrelated steering files
(`product.md`, `tech.md`, etc.) are preserved across install and
uninstall. Validated end-to-end on macOS, Linux (Docker node:22-bookworm
arm64), and Windows 11 (Parallels VM, Node 24): full installer-targets
suite passes (132 tests) on all three platforms, and live install /
idempotent re-run / uninstall round-trip works as expected.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Colby Mchenry
2026-05-26 18:09:51 -05:00
committed by GitHub
co-authored by Claude Opus 4.7
parent 8c69001289
commit 6558b585ed
6 changed files with 262 additions and 3 deletions
+79
View File
@@ -390,6 +390,84 @@ describe('Installer targets — partial-state idempotency', () => {
expect(body).not.toContain('codegraph_callers');
});
it('kiro: install writes settings/mcp.json (mcpServers.codegraph) and steering/codegraph.md', () => {
const kiro = getTarget('kiro')!;
const result = kiro.install('global', { autoAllow: true });
const mcp = path.join(tmpHome, '.kiro', 'settings', 'mcp.json');
const steering = path.join(tmpHome, '.kiro', 'steering', 'codegraph.md');
expect(result.files.some((f) => f.path === mcp)).toBe(true);
expect(result.files.some((f) => f.path === steering)).toBe(true);
const cfg = JSON.parse(fs.readFileSync(mcp, 'utf-8'));
expect(cfg.mcpServers.codegraph).toEqual({ type: 'stdio', command: 'codegraph', args: ['serve', '--mcp'] });
const md = fs.readFileSync(steering, 'utf-8');
expect(md).toContain('codegraph_callers');
expect(md).toContain('CodeGraph MCP server');
});
it('kiro: install preserves a pre-existing sibling MCP server in mcp.json', () => {
const kiro = getTarget('kiro')!;
const mcp = path.join(tmpHome, '.kiro', 'settings', 'mcp.json');
fs.mkdirSync(path.dirname(mcp), { recursive: true });
fs.writeFileSync(mcp, JSON.stringify({
mcpServers: { other: { command: 'uvx', args: ['other-server'] } },
}, null, 2) + '\n');
kiro.install('global', { autoAllow: true });
const after = JSON.parse(fs.readFileSync(mcp, 'utf-8'));
expect(after.mcpServers.other).toBeDefined();
expect(after.mcpServers.codegraph).toBeDefined();
});
it('kiro: uninstall strips codegraph but leaves sibling MCP servers intact', () => {
const kiro = getTarget('kiro')!;
const mcp = path.join(tmpHome, '.kiro', 'settings', 'mcp.json');
fs.mkdirSync(path.dirname(mcp), { recursive: true });
fs.writeFileSync(mcp, JSON.stringify({
mcpServers: { other: { command: 'uvx', args: ['other-server'] } },
}, null, 2) + '\n');
kiro.install('global', { autoAllow: true });
kiro.uninstall('global');
const after = JSON.parse(fs.readFileSync(mcp, 'utf-8'));
expect(after.mcpServers.other).toBeDefined();
expect(after.mcpServers.codegraph).toBeUndefined();
});
it('kiro: uninstall removes the steering codegraph.md file outright', () => {
const kiro = getTarget('kiro')!;
kiro.install('global', { autoAllow: true });
const steering = path.join(tmpHome, '.kiro', 'steering', 'codegraph.md');
expect(fs.existsSync(steering)).toBe(true);
kiro.uninstall('global');
expect(fs.existsSync(steering)).toBe(false);
});
it('kiro: uninstall leaves a sibling steering file (product.md) untouched', () => {
const kiro = getTarget('kiro')!;
const sibling = path.join(tmpHome, '.kiro', 'steering', 'product.md');
fs.mkdirSync(path.dirname(sibling), { recursive: true });
fs.writeFileSync(sibling, '# Product\n\nMy team practices.\n');
kiro.install('global', { autoAllow: true });
kiro.uninstall('global');
expect(fs.existsSync(sibling)).toBe(true);
expect(fs.readFileSync(sibling, 'utf-8')).toContain('My team practices.');
});
it('kiro: local install writes ./.kiro/settings/mcp.json and ./.kiro/steering/codegraph.md', () => {
const kiro = getTarget('kiro')!;
const result = kiro.install('local', { autoAllow: true });
const paths = result.files.map((f) => f.path.replace(/\\/g, '/'));
expect(paths.some((p) => p.endsWith('/.kiro/settings/mcp.json'))).toBe(true);
expect(paths.some((p) => p.endsWith('/.kiro/steering/codegraph.md'))).toBe(true);
});
it('antigravity: install writes to LEGACY ~/.gemini/antigravity/mcp_config.json when no migration marker', () => {
const antigravity = getTarget('antigravity')!;
antigravity.install('global', { autoAllow: true });
@@ -970,6 +1048,7 @@ describe('Installer targets — registry', () => {
expect(getTarget('hermes')?.id).toBe('hermes');
expect(getTarget('gemini')?.id).toBe('gemini');
expect(getTarget('antigravity')?.id).toBe('antigravity');
expect(getTarget('kiro')?.id).toBe('kiro');
expect(getTarget('not-a-real-target')).toBeUndefined();
});