refactor: drop 12 runtimes, keep Claude, Codex, OpenCode, Cursor, ZCode, Antigravity
Removes kilo, kimi, kimi-code, copilot, windsurf, augment, trae, qwen, hermes, cline, codebuddy and pi end to end: capability descriptors, installer branches and converters (bin/install.js 14.9k -> 11.2k lines), TypeScript converters, hook surfaces and runtime homes, review lanes qwen/kimi-code, the two pi migrations, Kimi payload normalization in the hook guards, dead hostBehaviors vocabulary, launcher home probes, fixtures, runtime-specific tests and the prose that presented them as supported. Installer output for the six kept runtimes is byte-identical to before the prune. The Kimi tool-vocabulary tests in workflow-guard, read-guard and read-injection-scanner are left in place pending a decision.
This commit is contained in:
@@ -1,12 +1,12 @@
|
||||
/**
|
||||
* Runtime Converter Tests — OpenCode + Kilo
|
||||
* Runtime Converter Tests — OpenCode
|
||||
*
|
||||
* Tests for small runtime-specific conversion functions from install.js.
|
||||
* Larger runtime test suites (Copilot, Codex, Antigravity) have their own files.
|
||||
* Larger runtime test suites (Codex, Antigravity) have their own files.
|
||||
*
|
||||
* OpenCode/Kilo: flat-runtime frontmatter converters (agent + command modes)
|
||||
* OpenCode: flat-runtime frontmatter converter (agent + command modes)
|
||||
* model: inherit is NOT added (runtime uses its configured default model)
|
||||
* but mode: subagent IS added (required by both runtimes' agents).
|
||||
* but mode: subagent IS added (required by the runtime's agents).
|
||||
*/
|
||||
|
||||
const { test, describe } = require('node:test');
|
||||
@@ -17,17 +17,12 @@ const path = require('node:path');
|
||||
process.env.MSD_TEST_MODE = '1';
|
||||
const {
|
||||
convertClaudeToOpencodeFrontmatter,
|
||||
convertClaudeToKiloFrontmatter,
|
||||
convertClaudeAgentToAntigravityAgent,
|
||||
convertClaudeCommandToTraeSkill,
|
||||
convertClaudeCommandToKimiSkill,
|
||||
buildKimiAgentArtifacts,
|
||||
neutralizeAgentReferences,
|
||||
} = require('../bin/install.js');
|
||||
|
||||
const {
|
||||
convertClaudeCommandToOpencodeSkill,
|
||||
convertClaudeCommandToKiloSkill,
|
||||
} = require('../msd-core/bin/lib/install-engine.cjs');
|
||||
|
||||
// Sample Claude agent frontmatter (matches actual MSD agent format)
|
||||
@@ -68,63 +63,40 @@ const flatRuntimeSuites = [
|
||||
convert: convertClaudeToOpencodeFrontmatter,
|
||||
configDir: '.config/opencode',
|
||||
},
|
||||
{
|
||||
label: 'Kilo',
|
||||
convert: convertClaudeToKiloFrontmatter,
|
||||
configDir: '.config/kilo',
|
||||
},
|
||||
];
|
||||
|
||||
describe('#4482: OpenCode conversion strips Copilot-only runtime notes', () => {
|
||||
describe('#4482: OpenCode conversion strips runtime notes declared for another runtime', () => {
|
||||
const convert = (body) => liveConversion.convertClaudeToOpencodeFrontmatter(
|
||||
['---', 'name: msd-test', 'description: test', '---', body].join('\n'),
|
||||
);
|
||||
|
||||
test('removes a Copilot-only note without leaving an empty wrapper', () => {
|
||||
test('removes a note declared for another runtime without leaving an empty wrapper', () => {
|
||||
const out = convert([
|
||||
'<runtime_note>',
|
||||
'**Copilot (VS Code):** Use `vscode_askquestions` wherever this workflow calls `AskUserQuestion`.',
|
||||
'<runtime_note runtime="claude">',
|
||||
'**Claude Code:** Use `AskUserQuestion` for interactive prompts.',
|
||||
'</runtime_note>',
|
||||
'',
|
||||
'<objective>Keep me.</objective>',
|
||||
].join('\n'));
|
||||
|
||||
assert.ok(!out.includes('vscode_askquestions'));
|
||||
assert.ok(!out.includes('<runtime_note>'));
|
||||
assert.ok(!out.includes('interactive prompts'));
|
||||
assert.ok(!out.includes('<runtime_note'));
|
||||
assert.ok(out.includes('<objective>Keep me.</objective>'));
|
||||
});
|
||||
|
||||
test('preserves runtime-neutral fallback text in a mixed note', () => {
|
||||
const out = convert([
|
||||
'<runtime_note>',
|
||||
'**Copilot (VS Code):** Use `vscode_askquestions` instead of `AskUserQuestion`.',
|
||||
'',
|
||||
'**TEXT_MODE fallback:** Present a numbered list when interactive tools are unavailable.',
|
||||
'</runtime_note>',
|
||||
].join('\n'));
|
||||
|
||||
assert.ok(!out.includes('vscode_askquestions'));
|
||||
assert.match(out, /<runtime_note>\n\*\*TEXT_MODE fallback:\*\*/);
|
||||
assert.ok(out.includes('</runtime_note>'));
|
||||
});
|
||||
|
||||
test('leaves unrelated runtime notes intact', () => {
|
||||
const note = '<runtime_note>\n**OpenCode:** Keep this runtime-specific guidance.\n</runtime_note>';
|
||||
assert.ok(convert(note).includes(note));
|
||||
});
|
||||
|
||||
test('the shared audience filter covers every non-Copilot runtime and preserves Copilot', () => {
|
||||
const note = '<runtime_note>\n**Copilot (VS Code):** Use `vscode_askquestions`.\n\n**TEXT_MODE fallback:** Keep me.\n</runtime_note>';
|
||||
const runtimes = [
|
||||
'antigravity', 'augment', 'claude', 'cline', 'codebuddy', 'codex', 'cursor', 'hermes',
|
||||
'kilo', 'kimi', 'kimi-code', 'opencode', 'pi', 'qwen', 'trae', 'windsurf', 'zcode',
|
||||
];
|
||||
test('the shared audience filter drops a declared-audience note for every other runtime and preserves it for its audience', () => {
|
||||
const note = '<runtime_note runtime="claude">\n**Claude Code:** Only Claude sees this.\n</runtime_note>';
|
||||
const runtimes = ['antigravity', 'codex', 'cursor', 'opencode', 'zcode'];
|
||||
for (const runtime of runtimes) {
|
||||
const out = liveConversion.filterRuntimeNotesForTarget(note, runtime);
|
||||
assert.ok(!out.includes('vscode_askquestions'), `${runtime} must not receive the Copilot note`);
|
||||
assert.ok(out.includes('TEXT_MODE fallback'), `${runtime} must retain neutral fallback guidance`);
|
||||
assert.ok(!out.includes('Only Claude sees this'), `${runtime} must not receive the Claude-declared note`);
|
||||
}
|
||||
assert.strictEqual(liveConversion.filterRuntimeNotesForTarget(note, 'copilot'), note);
|
||||
assert.ok(liveConversion.filterRuntimeNotesForTarget(note, 'claude').includes('Only Claude sees this'));
|
||||
});
|
||||
|
||||
test('a real OpenCode install filters mvp-phase workflow assets too', (t) => {
|
||||
@@ -164,18 +136,7 @@ for (const { label, convert, configDir } of flatRuntimeSuites) {
|
||||
const frontmatter = result.split('---')[1];
|
||||
assert.ok(!frontmatter.includes('tools:'), 'tools: should be stripped for agents');
|
||||
assert.ok(!frontmatter.includes('read: true'), 'tools object should not be generated');
|
||||
|
||||
if (label === 'Kilo') {
|
||||
assert.ok(frontmatter.includes('permission:'), 'Kilo agents should emit permission block');
|
||||
assert.ok(frontmatter.includes('read: allow'), 'Read should map to read: allow');
|
||||
assert.ok(frontmatter.includes('edit: allow'), 'Write/Edit should map to edit: allow');
|
||||
assert.ok(frontmatter.includes('bash: allow'), 'Bash should map to bash: allow');
|
||||
assert.ok(frontmatter.includes('grep: allow'), 'Grep should map to grep: allow');
|
||||
assert.ok(frontmatter.includes('glob: allow'), 'Glob should map to glob: allow');
|
||||
assert.ok(frontmatter.includes('task: deny'), 'unspecified permissions should be denied');
|
||||
} else {
|
||||
assert.ok(!frontmatter.includes('permission:'), 'OpenCode agents should not emit permission block');
|
||||
}
|
||||
assert.ok(!frontmatter.includes('permission:'), 'OpenCode agents should not emit permission block');
|
||||
});
|
||||
|
||||
test('strips skills: array', () => {
|
||||
@@ -226,14 +187,6 @@ Fallback skills live in .agents/skills/.`;
|
||||
const result = convert(agentWithClaudePaths, { isAgent: true });
|
||||
assert.ok(result.includes(`~/${configDir}/agent-memory/`), '~/.claude should be replaced');
|
||||
assert.ok(result.includes(`$HOME/${configDir}/skills/`), '$HOME/.claude should be replaced');
|
||||
|
||||
if (label === 'Kilo') {
|
||||
assert.ok(result.includes('.kilo/skills/'), '.claude/skills should be replaced for Kilo');
|
||||
assert.ok(result.includes('.kilo/agents/'), '.claude/agents should be replaced for Kilo');
|
||||
assert.ok(result.includes('./.kilo/hooks/'), './.claude should be replaced for Kilo');
|
||||
assert.ok(result.includes('Fallback skills live in .kilo/skills/.'), '.agents/skills should be rewritten to Kilo skills dir');
|
||||
assert.ok(!result.includes('.kilo/skill/'), 'singular Kilo skill dir should not be emitted');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -258,9 +211,7 @@ Fallback skills live in .agents/skills/.`;
|
||||
});
|
||||
});
|
||||
|
||||
// ─── #2256: model_overrides support for OpenCode/Kilo agents ────────────────
|
||||
// Only test OpenCode — Kilo uses the same converter but model override injection
|
||||
// is wired only for OpenCode at the call site in install().
|
||||
// ─── #2256: model_overrides support for OpenCode agents ────────────────────
|
||||
if (label === 'OpenCode') {
|
||||
describe('OpenCode agent model override (modelOverride option) (#2256)', () => {
|
||||
test('adds model: field when modelOverride is provided', () => {
|
||||
@@ -301,48 +252,10 @@ Fallback skills live in .agents/skills/.`;
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// DEFECT.GENERATIVE-FIX output-parity guard: convertClaudeToKiloFrontmatter is
|
||||
// defined TWICE — once in bin/install.js (the function bound at the top of this
|
||||
// file, used by bin/install.js's own legacy install path) and once in
|
||||
// src/runtime-artifact-conversion.cts, compiled to
|
||||
// msd-core/bin/lib/runtime-artifact-conversion.cjs (used by
|
||||
// src/install-engine.cts's newer TS install path, see install-engine.cts ~L871).
|
||||
// Both copies are LIVE — neither re-exports the other — so #2093's modelOverride
|
||||
// edit had to be applied twice by hand. Source-text identity can't be asserted
|
||||
// (they live in different module systems: plain CJS vs a tsc-compiled .cts
|
||||
// output with different surrounding comments), so this instead proves the two
|
||||
// implementations still produce IDENTICAL output for representative agent and
|
||||
// command input. If a future edit changes one copy's behavior without mirroring
|
||||
// it into the other, this test is the guard that catches the divergence.
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
describe('convertClaudeToKiloFrontmatter output parity: bin/install.js vs runtime-artifact-conversion.cjs (#2093)', () => {
|
||||
const { convertClaudeToKiloFrontmatter: convertViaConversionModule } =
|
||||
require('../msd-core/bin/lib/runtime-artifact-conversion.cjs');
|
||||
|
||||
test('identical output for an agent with a model override', () => {
|
||||
const viaInstall = convertClaudeToKiloFrontmatter(SAMPLE_AGENT, { isAgent: true, modelOverride: 'anthropic/claude-sonnet-5' });
|
||||
const viaModule = convertViaConversionModule(SAMPLE_AGENT, { isAgent: true, modelOverride: 'anthropic/claude-sonnet-5' });
|
||||
assert.equal(viaInstall, viaModule, 'bin/install.js and runtime-artifact-conversion.cjs must emit identical agent output');
|
||||
});
|
||||
|
||||
test('identical output for an agent with no model override', () => {
|
||||
const viaInstall = convertClaudeToKiloFrontmatter(SAMPLE_AGENT, { isAgent: true, modelOverride: null });
|
||||
const viaModule = convertViaConversionModule(SAMPLE_AGENT, { isAgent: true, modelOverride: null });
|
||||
assert.equal(viaInstall, viaModule, 'bin/install.js and runtime-artifact-conversion.cjs must emit identical agent output with no override');
|
||||
});
|
||||
|
||||
test('identical output for a command (model override never applies)', () => {
|
||||
const viaInstall = convertClaudeToKiloFrontmatter(SAMPLE_COMMAND, { isAgent: false, modelOverride: 'x' });
|
||||
const viaModule = convertViaConversionModule(SAMPLE_COMMAND, { isAgent: false, modelOverride: 'x' });
|
||||
assert.equal(viaInstall, viaModule, 'bin/install.js and runtime-artifact-conversion.cjs must emit identical command output');
|
||||
});
|
||||
});
|
||||
|
||||
describe('convertClaudeToOpencodeFrontmatter output parity: bin/install.js vs runtime-artifact-conversion.cjs (#4482)', () => {
|
||||
const { convertClaudeToOpencodeFrontmatter: convertViaConversionModule } =
|
||||
require('../msd-core/bin/lib/runtime-artifact-conversion.cjs');
|
||||
const input = `${SAMPLE_COMMAND}\n\n<runtime_note>\n**Copilot (VS Code):** Use vscode_askquestions.\n</runtime_note>`;
|
||||
const input = `${SAMPLE_COMMAND}\n\n<runtime_note runtime="claude">\n**Claude Code:** Claude-only guidance.\n</runtime_note>`;
|
||||
|
||||
test('published and module converters filter runtime notes identically', () => {
|
||||
assert.equal(
|
||||
@@ -352,100 +265,6 @@ describe('convertClaudeToOpencodeFrontmatter output parity: bin/install.js vs ru
|
||||
});
|
||||
});
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// DEFECT.GENERATIVE-FIX output-parity guard: convertClaudeCommandToTraeSkill is
|
||||
// defined TWICE — once in bin/install.js (dead for the live skills-install
|
||||
// path; kept for this file's own module-level export/test surface) and once in
|
||||
// src/runtime-artifact-conversion.cts, compiled to
|
||||
// msd-core/bin/lib/runtime-artifact-conversion.cjs (used by
|
||||
// src/install-engine.cts's skills-install path via SKILLS_CONVERTER_REGISTRY,
|
||||
// see install-engine.cts ~L754). Both copies are LIVE call surfaces — neither
|
||||
// re-exports the other — so #2094's `stage:` emission had to be applied to
|
||||
// bin/install.js's copy by hand to keep parity. Source-text identity can't be
|
||||
// asserted (they live in different module systems: plain CJS vs a
|
||||
// tsc-compiled .cts output with different surrounding comments), so this
|
||||
// instead proves the two implementations still produce IDENTICAL output for
|
||||
// representative command input. If a future edit changes one copy's behavior
|
||||
// without mirroring it into the other, this test is the guard that catches
|
||||
// the divergence.
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
describe('convertClaudeCommandToTraeSkill output parity: bin/install.js vs runtime-artifact-conversion.cjs (#2094)', () => {
|
||||
const { convertClaudeCommandToTraeSkill: convertViaConversionModule } =
|
||||
require('../msd-core/bin/lib/runtime-artifact-conversion.cjs');
|
||||
|
||||
test('identical output for a representative command, including the #2094 stage: field', () => {
|
||||
const viaInstall = convertClaudeCommandToTraeSkill(SAMPLE_COMMAND, 'msd-execute-phase');
|
||||
const viaModule = convertViaConversionModule(SAMPLE_COMMAND, 'msd-execute-phase');
|
||||
assert.equal(viaInstall, viaModule, 'bin/install.js and runtime-artifact-conversion.cjs must emit identical command output');
|
||||
assert.match(viaInstall, /\nstage: workflow\n/, 'both copies must emit the #2094 stage: field');
|
||||
});
|
||||
|
||||
test('identical output when the source has no description (falls back to generic description)', () => {
|
||||
const noDescriptionCommand = `---
|
||||
name: msd-noop
|
||||
allowed-tools:
|
||||
- Read
|
||||
---
|
||||
|
||||
Do nothing.`;
|
||||
const viaInstall = convertClaudeCommandToTraeSkill(noDescriptionCommand, 'msd-noop');
|
||||
const viaModule = convertViaConversionModule(noDescriptionCommand, 'msd-noop');
|
||||
assert.equal(viaInstall, viaModule, 'bin/install.js and runtime-artifact-conversion.cjs must emit identical output when description is absent');
|
||||
});
|
||||
});
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// DEFECT.GENERATIVE-FIX output-parity guard: convertClaudeCommandToKimiSkill
|
||||
// and buildKimiAgentArtifacts are each defined TWICE — once in bin/install.js
|
||||
// (dead for the live install path; kept for this file's own module-level
|
||||
// export/test surface) and once in src/runtime-artifact-conversion.cts,
|
||||
// compiled to msd-core/bin/lib/runtime-artifact-conversion.cjs (used by
|
||||
// src/install-engine.cts's skills-install path via SKILLS_CONVERTER_REGISTRY
|
||||
// for skills, and by src/runtime-artifact-layout.cts's kimiAgentsKind for
|
||||
// agent YAML — see runtime-artifact-layout.cts ~L268/L289). Both copies are
|
||||
// LIVE call surfaces — neither re-exports the other, so any future
|
||||
// kimi-specific fix (e.g. the #2095 code-review fixes) must be applied to
|
||||
// both by hand. Source-text identity can't be asserted (different module
|
||||
// systems: plain CJS vs a tsc-compiled .cts output with different
|
||||
// surrounding comments), so this instead proves the two implementations
|
||||
// still produce IDENTICAL output for representative command and agent
|
||||
// input. If a future edit changes one copy's behavior without mirroring it
|
||||
// into the other, this test is the guard that catches the divergence.
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
describe('convertClaudeCommandToKimiSkill / buildKimiAgentArtifacts output parity: bin/install.js vs runtime-artifact-conversion.cjs (#2095)', () => {
|
||||
const {
|
||||
convertClaudeCommandToKimiSkill: convertViaConversionModule,
|
||||
buildKimiAgentArtifacts: buildViaConversionModule,
|
||||
} = require('../msd-core/bin/lib/runtime-artifact-conversion.cjs');
|
||||
|
||||
test('identical skill output for a representative command', () => {
|
||||
const viaInstall = convertClaudeCommandToKimiSkill(SAMPLE_COMMAND, 'msd-execute-phase');
|
||||
const viaModule = convertViaConversionModule(SAMPLE_COMMAND, 'msd-execute-phase');
|
||||
assert.equal(viaInstall, viaModule, 'bin/install.js and runtime-artifact-conversion.cjs must emit identical Kimi skill output');
|
||||
});
|
||||
|
||||
test('identical skill output when the source has no description (falls back to generic description)', () => {
|
||||
const noDescriptionCommand = `---
|
||||
name: msd-noop
|
||||
allowed-tools:
|
||||
- Read
|
||||
---
|
||||
|
||||
Do nothing.`;
|
||||
const viaInstall = convertClaudeCommandToKimiSkill(noDescriptionCommand, 'msd-noop');
|
||||
const viaModule = convertViaConversionModule(noDescriptionCommand, 'msd-noop');
|
||||
assert.equal(viaInstall, viaModule, 'bin/install.js and runtime-artifact-conversion.cjs must emit identical output when description is absent');
|
||||
});
|
||||
|
||||
test('identical agent-artifact output for a representative agent with a subagent, including the Agent tool grant', () => {
|
||||
const viaInstall = buildKimiAgentArtifacts({ rootAgent: SAMPLE_AGENT, subagents: [SAMPLE_AGENT] });
|
||||
const viaModule = buildViaConversionModule({ rootAgent: SAMPLE_AGENT, subagents: [SAMPLE_AGENT] });
|
||||
assert.deepEqual(viaInstall, viaModule, 'bin/install.js and runtime-artifact-conversion.cjs must emit an identical artifact bundle');
|
||||
assert.match(viaInstall.root.yaml, /kimi_cli\.tools\.agent:Agent/,
|
||||
'both copies must grant the Agent tool when a subagent is present (#2095 Upgrade 2)');
|
||||
});
|
||||
});
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Antigravity agent conversion — shared Gemini-backend tool mapping (#1394 / #1928)
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
@@ -540,7 +359,7 @@ describe('neutralizeAgentReferences', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// ─── OpenCode-family skill converters (SKILL.md) — #784 ──────────────────────
|
||||
// ─── OpenCode skill converter (SKILL.md) — #784 ──────────────────────────────
|
||||
|
||||
const SKILL_SAMPLE_COMMAND = `---
|
||||
description: Show available MSD commands and usage guide
|
||||
@@ -560,10 +379,9 @@ description: "[BETA] Offload plan phase to the cloud and import back."
|
||||
Body for /msd:ultraplan-phase.
|
||||
`;
|
||||
|
||||
describe('convertClaudeCommandToOpencodeSkill / convertClaudeCommandToKiloSkill (#784)', () => {
|
||||
describe('convertClaudeCommandToOpencodeSkill (#784)', () => {
|
||||
const cases = [
|
||||
{ label: 'opencode', convert: convertClaudeCommandToOpencodeSkill },
|
||||
{ label: 'kilo', convert: convertClaudeCommandToKiloSkill },
|
||||
];
|
||||
|
||||
for (const { label, convert } of cases) {
|
||||
@@ -655,9 +473,8 @@ const { cleanup } = require('./helpers.cjs');
|
||||
|
||||
/**
|
||||
* Minimal Claude agent source with comma-separated tools (Claude format).
|
||||
* Copilot conversion turns tools into a JSON array (CONV-04/05).
|
||||
* Codex conversion adds <codex_agent_role> block.
|
||||
* Cursor/Windsurf/Augment/Trae/Codebuddy/Cline conversion strips color field.
|
||||
* Cursor conversion strips color field.
|
||||
*/
|
||||
const CLAUDE_AGENT_SOURCE = `---
|
||||
name: msd-planner
|
||||
@@ -795,39 +612,6 @@ describe('feat-1173: stageAgentsForRuntimeWithConverter', () => {
|
||||
// ─── dispatchKindEntry wiring tests ──────────────────────────────────────────
|
||||
|
||||
describe('feat-1173: dispatchKindEntry agents converter wiring', () => {
|
||||
test('agents kind with convertClaudeAgentToCopilotAgent converter applies copilot conversion', (t) => {
|
||||
const fixtureRoot = makeFixtureRoot([{ name: 'msd-planner.md', content: CLAUDE_AGENT_SOURCE }]);
|
||||
t.after(() => {
|
||||
cleanup(fixtureRoot);
|
||||
cleanupStagedSkills();
|
||||
});
|
||||
|
||||
const registry = makeSyntheticRegistry('convertClaudeAgentToCopilotAgent');
|
||||
const layout = resolveRuntimeArtifactLayoutFromRegistry(
|
||||
registry, 'testruntime', fixtureRoot, 'global',
|
||||
);
|
||||
|
||||
assert.strictEqual(layout.kinds.length, 1);
|
||||
const agentKind = layout.kinds[0];
|
||||
assert.strictEqual(agentKind.kind, 'agents');
|
||||
|
||||
const resolvedProfile = { name: 'full', skills: '*', agents: new Set() };
|
||||
const stagedDir = agentKind.stage(resolvedProfile);
|
||||
|
||||
const stagedFile = path.join(stagedDir, 'msd-planner.md');
|
||||
assert.ok(fs.existsSync(stagedFile), `staged file must exist: ${stagedFile}`);
|
||||
|
||||
const stagedContent = fs.readFileSync(stagedFile, 'utf8');
|
||||
|
||||
// Copilot CONV-04/05: tools converted from "Bash, Read, Write" to JSON array "['bash', 'read', 'write']"
|
||||
// Raw copy would keep the original comma-separated "tools: Bash, Read, Write" line.
|
||||
assert.notStrictEqual(stagedContent, CLAUDE_AGENT_SOURCE, 'converter must have transformed the content');
|
||||
assert.ok(
|
||||
stagedContent.includes("tools: ['") || stagedContent.includes('tools: ['),
|
||||
`Copilot conversion must produce JSON array tools. Got:\n${stagedContent.slice(0, 300)}`,
|
||||
);
|
||||
});
|
||||
|
||||
test('agents kind with convertClaudeAgentToCodexAgent converter applies codex conversion', (t) => {
|
||||
const fixtureRoot = makeFixtureRoot([{ name: 'msd-planner.md', content: CLAUDE_AGENT_SOURCE }]);
|
||||
t.after(() => {
|
||||
@@ -905,7 +689,7 @@ describe('feat-1173: dispatchKindEntry agents converter wiring', () => {
|
||||
test('scope threads isGlobal to a scope-aware converter (global vs local differ)', (t) => {
|
||||
// The plumbing kept by #1173 (option a): convertedAgentsKind / dispatchKindEntry
|
||||
// pass the install scope to the converter as isGlobal. A scope-aware converter
|
||||
// (copilot) must therefore produce different output for global vs local. This
|
||||
// (antigravity) must therefore produce different output for global vs local. This
|
||||
// proves the thread is live via a synthetic descriptor — no real runtime
|
||||
// declares a converted agents kind yet (declarations deferred to the ADR-1235
|
||||
// §0 parity follow-up).
|
||||
@@ -921,7 +705,7 @@ describe('feat-1173: dispatchKindEntry agents converter wiring', () => {
|
||||
prefix: 'msd-',
|
||||
nesting: 'flat',
|
||||
recursive: false,
|
||||
converter: 'convertClaudeAgentToCopilotAgent',
|
||||
converter: 'convertClaudeAgentToAntigravityAgent',
|
||||
};
|
||||
const registry = {
|
||||
runtimes: { testruntime: { runtime: { artifactLayout: { global: [agentsEntry], local: [agentsEntry] } } } },
|
||||
@@ -1202,8 +986,8 @@ test('parity: every non-Claude runtime stamps its own runtime default, and use_w
|
||||
|
||||
// #2652: the use_worktrees=false stamp is scoped to the runtimes whose
|
||||
// negotiated dispatch.isolation really is `none`. A host that declares
|
||||
// harness-worktree (cursor) or orchestrator-worktree (codex, opencode,
|
||||
// kimi, kimi-code) must keep the unstamped `true` default, or the stamp
|
||||
// harness-worktree (cursor) or orchestrator-worktree (codex, opencode)
|
||||
// must keep the unstamped `true` default, or the stamp
|
||||
// resolves USE_WORKTREES=false before dispatch-isolation is consulted and
|
||||
// the runtime is judged by its name after all.
|
||||
if (isolation === 'none') {
|
||||
@@ -1677,9 +1461,9 @@ test('manager.md and autonomous.md no longer contain old "not claude" background
|
||||
|
||||
describe('#2486 regression: settings/health worktrees isolation branch', () => {
|
||||
// Review round 2 (#2584 Phase 3): isolation is a DECLARED CAPABILITY, not a
|
||||
// runtime name. cursor declares harness-worktree and codex/opencode/kimi/
|
||||
// kimi-code declare orchestrator-worktree, so a `RUNTIME != claude` gate
|
||||
// would block a supported configuration on five runtimes and false-warn in
|
||||
// runtime name. cursor declares harness-worktree and codex/opencode
|
||||
// declare orchestrator-worktree, so a `RUNTIME != claude` gate
|
||||
// would block a supported configuration on several runtimes and false-warn in
|
||||
// health. Both surfaces branch on `dispatch-isolation` instead.
|
||||
test('settings.md and health.md contain NO runtime-name gate for the worktrees branch', () => {
|
||||
// allow-test-rule: source-text-is-the-product (#2486)
|
||||
@@ -1923,7 +1707,7 @@ test('manager.md and autonomous.md no longer contain old "not claude" background
|
||||
return res.stdout.includes('W025:');
|
||||
};
|
||||
|
||||
// The reviewer's exact repro: rt=qwen, source (un-stamped) emit, cfg={}.
|
||||
// The reviewer's exact repro: an isolation=none runtime, source (un-stamped) emit, cfg={}.
|
||||
// The key is absent, so `config-get` prints nothing. This FIRED before.
|
||||
assert.equal(
|
||||
fire('none', ''),
|
||||
@@ -2111,7 +1895,7 @@ test('manager.md and autonomous.md no longer contain old "not claude" background
|
||||
*
|
||||
* YAML 1.2 treats a leading `[` as the start of a flow sequence, so any
|
||||
* downstream parser (gh-copilot, JetBrains' kit, etc.) fails with
|
||||
* "Unexpected scalar at node end". The Copilot/Antigravity/Trae/Codebuddy
|
||||
* "Unexpected scalar at node end". The Antigravity
|
||||
* skill+agent converters in `bin/install.js` re-emit the description
|
||||
* unquoted; the Claude variant `yamlQuote(...)`s it. Bring the others
|
||||
* in line so any value is round-trip-safe regardless of leading char.
|
||||
@@ -2237,14 +2021,10 @@ function assertDescriptionRoundTrips(emitted, expected, label) {
|
||||
}
|
||||
|
||||
const COMMAND_CONVERTERS = [
|
||||
{ label: 'convertClaudeCommandToCopilotSkill', fn: (src) => install.convertClaudeCommandToCopilotSkill(src, 'msd-ultraplan-phase') },
|
||||
{ label: 'convertClaudeCommandToAntigravitySkill', fn: (src) => install.convertClaudeCommandToAntigravitySkill(src, 'msd-ultraplan-phase') },
|
||||
{ label: 'convertClaudeCommandToTraeSkill', fn: (src) => install.convertClaudeCommandToTraeSkill(src, 'msd-ultraplan-phase') },
|
||||
{ label: 'convertClaudeCommandToCodebuddySkill', fn: (src) => install.convertClaudeCommandToCodebuddySkill(src, 'msd-ultraplan-phase') },
|
||||
];
|
||||
|
||||
const AGENT_CONVERTERS = [
|
||||
{ label: 'convertClaudeAgentToCopilotAgent', fn: (src) => install.convertClaudeAgentToCopilotAgent(src) },
|
||||
{ label: 'convertClaudeAgentToAntigravityAgent', fn: (src) => install.convertClaudeAgentToAntigravityAgent(src) },
|
||||
];
|
||||
|
||||
@@ -2253,11 +2033,8 @@ const AGENT_CONVERTERS = [
|
||||
// rest defend against neighbouring drift.
|
||||
const FLOW_HOSTILE_PREFIXES = ['[', '{', '*', '&', '!', '|', '>', '%', '@', '`'];
|
||||
|
||||
// Some converters (Trae, CodeBuddy) deliberately rewrite "Claude Code"
|
||||
// in body content to their target runtime name, and the rewrite cuts
|
||||
// across the description too. That's correct behavior — out of scope for
|
||||
// the YAML-quoting fix — so for the reporter case we assert only the
|
||||
// quoting requirement, not byte-equality of the round-tripped value.
|
||||
// For the reporter case we assert only the quoting requirement, not
|
||||
// byte-equality of the round-tripped value.
|
||||
function assertDescriptionIsQuoted(emitted, label) {
|
||||
const fmLines = extractFrontmatter(emitted);
|
||||
const descLine = findDescriptionLine(fmLines);
|
||||
@@ -2311,7 +2088,6 @@ describe('bug-2876: skill+agent converters emit YAML-quoted description', () =>
|
||||
const liveConversion = require('../msd-core/bin/lib/runtime-artifact-conversion.cjs');
|
||||
|
||||
const OPENCODE_CONVERTERS = [['src (live bake)', liveConversion.convertClaudeToOpencodeFrontmatter]];
|
||||
const KILO_CONVERTERS = [['src (live bake)', liveConversion.convertClaudeToKiloFrontmatter]];
|
||||
|
||||
describe('#3706: OpenCode agent variant (resolved effort)', () => {
|
||||
const AGENT = ['---', 'name: msd-executor', 'description: x', 'model: sonnet', '---', '', 'Body.'].join('\n');
|
||||
@@ -2353,17 +2129,6 @@ describe('#3706: OpenCode agent variant (resolved effort)', () => {
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
for (const [label, convert] of KILO_CONVERTERS) {
|
||||
test(`[${label}] Kilo does NOT emit variant even when one is passed`, () => {
|
||||
// The asymmetry, pinned. EFFORT_ARGV declares surfaces for claude/opencode/
|
||||
// codex and has no kilo entry — unlike the model side, where #2794 J8
|
||||
// requires the two runtimes to resolve identically.
|
||||
const out = convert(AGENT, { isAgent: true, modelOverride: 'M', variant: 'high' });
|
||||
assert.ok(!hasKey(out, 'variant'), `kilo has no declared effort surface; got:\n${out}`);
|
||||
assert.match(out, /^model: M$/m, 'but the model override still applies');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// ─── #3706: config-supplied values cannot inject frontmatter keys ────────────
|
||||
@@ -2392,13 +2157,6 @@ describe('#3706: frontmatter values are escaped, not interpolated raw', () => {
|
||||
assert.match(out, /^variant: "/m);
|
||||
});
|
||||
}
|
||||
|
||||
for (const [label, convert] of KILO_CONVERTERS) {
|
||||
test(`[${label}] Kilo model values are escaped too — the same sink exists there`, () => {
|
||||
const out = convert(AGENT, { isAgent: true, modelOverride: 'sonnet\ntools: ["*"]' });
|
||||
assert.ok(!/^tools:/m.test(out), `injected a tools key:\n${out}`);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// ─── #3706: values that look plain but do not round-trip ────────────────────
|
||||
|
||||
Reference in New Issue
Block a user