feat(#2093): migrate Kilo onto EoS imperative adapter + hook-bus/model/MCP/dispatch upgrades (ADR-1239)

Fold remaining isKilo logic branches into descriptor-driven reads:
finishPermissionWriter (uninstall cleanup), skipSharedHooksInstall (hooks
copy), and a skills converter-name registry (the artifactLayout.converter
field is now load-bearing, not decorative). frontmatterDialect stays the
documented dispatch key for frontmatter (no descriptor field for it). Dead
isKilo destructure bindings removed. Byte-identical golden parity for all 16
runtimes (opencode, which shares kilo's combined-family path, verified clean).

UPGRADE 1 (hook bus): install .kilo/plugins/gsd-core.js native plugin +
extensionEvents:"kilo" + EXTENSION_EVENT_SURFACES.kilo (OpenCode-fork bus).
UPGRADE 2 (active model): populate runtimeTierDefaults.kilo + thread
modelOverride through convertClaudeToKiloFrontmatter — model no longer stripped
from agents. UPGRADE 3 (MCP): document the gsd-core MCP companion under kilo's
mcp config key. UPGRADE 4 (named dispatch): agents/*.md mode:subagent roster is
the Task-tool dispatch surface (tested); subagentToolkit stays 'undocumented'
per AC so dispatch degrades to 'degraded' by design.

Model-catalog single-source edit ripples the shared model-catalog.json hash
into all 16 golden fixtures (expected). Inline defect fixes (no-defer): stale-
bake-guard resolveAgentDir 'agent'->'agents' (was a silent no-op for opencode/
codex), hardcoded 'Removed OpenCode plugin' uninstall log -> generic, and the
connect-gsd-mcp-server.md OpenCode mcpServers->mcp doc error.

Tests: kilo-imperative-reference (adapter/axes/fail-closed/degradation/
hostBehaviors + widened isKilo source-grep across 4 modules) + kilo-upgrades
(plugin parity+load, model-override converter, agents dispatch surface, MCP
doc). Matrix + how-to + config docs updated; changeset added.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Tom Boucher
2026-07-10 17:40:50 -04:00
parent 61d04daacf
commit f014ec83bd
41 changed files with 1666 additions and 117 deletions

View File

@@ -702,6 +702,36 @@ describe('issue #2612: runtime "opencode" — OpenCode tier resolution', () => {
});
});
// ─── Issue #2093: kilo runtime tier resolution ───────────────────────────────
// Kilo is an OpenCode fork and shares the IDENTICAL built-in tier IDs (UPGRADE 2
// / ADR-1239). Kilo moved from Group B (no built-in defaults) to Group A here.
describe('issue #2093: runtime "kilo" — Kilo tier resolution', () => {
let tmpDir;
beforeEach(() => { isolateHome(); tmpDir = createTempProject(); resetRuntimeWarningCaches(); });
afterEach(() => { cleanup(tmpDir); restoreHome(); });
test('opus tier -> anthropic/claude-opus-4-8', () => {
writeConfig(tmpDir, { runtime: 'kilo', model_profile: 'quality' });
assert.strictEqual(resolveModelInternal(tmpDir, 'gsd-planner'), 'anthropic/claude-opus-4-8');
});
test('sonnet tier -> anthropic/claude-sonnet-5', () => {
writeConfig(tmpDir, { runtime: 'kilo', model_profile: 'balanced' });
assert.strictEqual(resolveModelInternal(tmpDir, 'gsd-roadmapper'), 'anthropic/claude-sonnet-5');
});
test('haiku tier -> anthropic/claude-haiku-4-5', () => {
writeConfig(tmpDir, { runtime: 'kilo', model_profile: 'budget' });
assert.strictEqual(resolveModelInternal(tmpDir, 'gsd-codebase-mapper'), 'anthropic/claude-haiku-4-5');
});
test('kilo: effort resolves universally but render param is null (no wire param)', () => {
writeConfig(tmpDir, { runtime: 'kilo', model_profile: 'quality' });
const eff = resolveEffortInternal(tmpDir, 'gsd-planner');
assert.strictEqual(renderEffortForRuntime('kilo', eff).param, null);
});
});
// ─── Issue #2612: copilot runtime tier resolution ────────────────────────────
describe('issue #2612: runtime "copilot" — Copilot tier resolution', () => {
let tmpDir;
@@ -736,10 +766,6 @@ describe('issue #2612: Group B runtimes — no built-in map, use unknown-runtime
assert.strictEqual(RUNTIME_PROFILE_MAP.cursor, undefined);
});
test('kilo is not in RUNTIME_PROFILE_MAP', () => {
assert.strictEqual(RUNTIME_PROFILE_MAP.kilo, undefined);
});
test('windsurf is not in RUNTIME_PROFILE_MAP', () => {
assert.strictEqual(RUNTIME_PROFILE_MAP.windsurf, undefined);
});
@@ -830,4 +856,21 @@ describe('issue #2612: partial override merge for new Group A runtimes', () => {
// gsd-planner budget -> sonnet -> built-in default
assert.strictEqual(resolveModelInternal(tmpDir, 'gsd-planner'), 'claude-sonnet-5');
});
// #2093: kilo just moved into Group A — same partial-merge coverage as opencode.
test('kilo.sonnet override wins; opus and haiku still use built-in defaults', () => {
writeConfig(tmpDir, {
runtime: 'kilo',
model_profile: 'balanced',
model_profile_overrides: {
kilo: { sonnet: 'anthropic/claude-sonnet-4-7' },
},
});
// gsd-planner balanced -> opus -> built-in default
assert.strictEqual(resolveModelInternal(tmpDir, 'gsd-planner'), 'anthropic/claude-opus-4-8');
// gsd-roadmapper balanced -> sonnet -> overridden
assert.strictEqual(resolveModelInternal(tmpDir, 'gsd-roadmapper'), 'anthropic/claude-sonnet-4-7');
// gsd-codebase-mapper balanced -> haiku -> built-in default (haiku not overridden)
assert.strictEqual(resolveModelInternal(tmpDir, 'gsd-codebase-mapper'), 'anthropic/claude-haiku-4-5');
});
});