fix(#4482): strip Copilot notes from OpenCode artifacts (#4532)

* fix(#4482): strip Copilot notes from OpenCode artifacts

* chore: add changeset for #4532

* fix(#4482): filter runtime notes across emitted surfaces

* fix(#4482): make runtime note filtering runtime-neutral

* test(#4482): keep ack fixture outside registered provenance

---------

Co-authored-by: Tom Boucher <trekkie@nomorestars.com>
This commit is contained in:
Michel Moreira
2026-09-10 16:29:08 -03:00
committed by GitHub
parent 1e47560e34
commit 93ba63aeff
7 changed files with 184 additions and 26 deletions

View File

@@ -11,6 +11,8 @@
const { test, describe } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
process.env.GSD_TEST_MODE = '1';
const {
@@ -73,6 +75,70 @@ const flatRuntimeSuites = [
},
];
describe('#4482: OpenCode conversion strips Copilot-only runtime notes', () => {
const convert = (body) => liveConversion.convertClaudeToOpencodeFrontmatter(
['---', 'name: gsd-test', 'description: test', '---', body].join('\n'),
);
test('removes a Copilot-only note without leaving an empty wrapper', () => {
const out = convert([
'<runtime_note>',
'**Copilot (VS Code):** Use `vscode_askquestions` wherever this workflow calls `AskUserQuestion`.',
'</runtime_note>',
'',
'<objective>Keep me.</objective>',
].join('\n'));
assert.ok(!out.includes('vscode_askquestions'));
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',
];
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.strictEqual(liveConversion.filterRuntimeNotesForTarget(note, 'copilot'), note);
});
test('a real OpenCode install filters mvp-phase workflow assets too', (t) => {
const { runMinimalInstall } = require('./helpers/install-shared.cjs');
const { cleanup } = require('./helpers.cjs');
const { configDir, root } = runMinimalInstall({ runtime: 'opencode', scope: 'global' });
t.after(() => cleanup(root));
const installed = fs.readFileSync(path.join(configDir, 'gsd-core', 'workflows', 'mvp-phase.md'), 'utf8');
assert.ok(!installed.includes('vscode_askquestions'));
assert.ok(installed.includes('TEXT_MODE fallback'));
assert.ok(installed.includes('<runtime_note>'));
});
});
for (const { label, convert, configDir } of flatRuntimeSuites) {
describe(`${label} agent conversion (isAgent: true)`, () => {
test('keeps name: field for agents', () => {
@@ -273,6 +339,19 @@ describe('convertClaudeToKiloFrontmatter output parity: bin/install.js vs runtim
});
});
describe('convertClaudeToOpencodeFrontmatter output parity: bin/install.js vs runtime-artifact-conversion.cjs (#4482)', () => {
const { convertClaudeToOpencodeFrontmatter: convertViaConversionModule } =
require('../gsd-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>`;
test('published and module converters filter runtime notes identically', () => {
assert.equal(
convertClaudeToOpencodeFrontmatter(input),
convertViaConversionModule(input),
);
});
});
// ─────────────────────────────────────────────────────────────────────────────
// DEFECT.GENERATIVE-FIX output-parity guard: convertClaudeCommandToTraeSkill is
// defined TWICE — once in bin/install.js (dead for the live skills-install