mirror of
https://github.com/glittercowboy/get-shit-done
synced 2026-04-25 17:25:23 +02:00
The Copilot content converter only replaced `~/.claude/` and `$HOME/.claude/` when followed by a literal `/`. Bare references (e.g. `configDir = ~/.claude` at end of line) slipped through and triggered the post-install "Found N unreplaced .claude path reference(s)" warning, since the leak scanner uses `(?:~|$HOME)/\.claude\b`. Switched both replacements to a `(\/|\b)` capture group so trailing-slash and bare forms are handled in a single pass — matching the pattern already used by Antigravity, OpenCode, Kilo, and Codex converters. Closes #2545
This commit is contained in:
@@ -877,14 +877,18 @@ function convertCopilotToolName(claudeTool) {
|
|||||||
*/
|
*/
|
||||||
function convertClaudeToCopilotContent(content, isGlobal = false) {
|
function convertClaudeToCopilotContent(content, isGlobal = false) {
|
||||||
let c = content;
|
let c = content;
|
||||||
// CONV-06: Path replacement — most specific first to avoid substring matches
|
// CONV-06: Path replacement — most specific first to avoid substring matches.
|
||||||
|
// Handle both `~/.claude/foo` (trailing slash) and bare `~/.claude` forms in
|
||||||
|
// one pass via a capture group, matching the approach used by Antigravity,
|
||||||
|
// OpenCode, Kilo, and Codex converters (issue #2545).
|
||||||
if (isGlobal) {
|
if (isGlobal) {
|
||||||
c = c.replace(/\$HOME\/\.claude\//g, '$HOME/.copilot/');
|
c = c.replace(/\$HOME\/\.claude(\/|\b)/g, '$HOME/.copilot$1');
|
||||||
c = c.replace(/~\/\.claude\//g, '~/.copilot/');
|
c = c.replace(/~\/\.claude(\/|\b)/g, '~/.copilot$1');
|
||||||
} else {
|
} else {
|
||||||
c = c.replace(/\$HOME\/\.claude\//g, '.github/');
|
c = c.replace(/\$HOME\/\.claude\//g, '.github/');
|
||||||
c = c.replace(/~\/\.claude\//g, '.github/');
|
c = c.replace(/~\/\.claude\//g, '.github/');
|
||||||
c = c.replace(/~\/\.claude\n/g, '.github/');
|
c = c.replace(/\$HOME\/\.claude\b/g, '.github');
|
||||||
|
c = c.replace(/~\/\.claude\b/g, '.github');
|
||||||
}
|
}
|
||||||
c = c.replace(/\.\/\.claude\//g, './.github/');
|
c = c.replace(/\.\/\.claude\//g, './.github/');
|
||||||
c = c.replace(/\.claude\//g, '.github/');
|
c = c.replace(/\.claude\//g, '.github/');
|
||||||
|
|||||||
64
tests/bug-2545-copilot-unreplaced-paths.test.cjs
Normal file
64
tests/bug-2545-copilot-unreplaced-paths.test.cjs
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
/**
|
||||||
|
* Regression test for issue #2545.
|
||||||
|
*
|
||||||
|
* The Copilot content converter's `~/.claude/` and `$HOME/.claude/` replacements
|
||||||
|
* only matched when a literal slash followed, so bare `~/.claude` references
|
||||||
|
* (end of line, quotes, punctuation) were left unreplaced. Those leaks then
|
||||||
|
* triggered the installer's "Found N unreplaced .claude path reference(s)"
|
||||||
|
* warning, which scans for `(?:~|$HOME)/\.claude\b`.
|
||||||
|
*
|
||||||
|
* Fix: replace with a word-boundary pattern so both forms are caught in a
|
||||||
|
* single pass, matching the approach already used by the Antigravity, OpenCode,
|
||||||
|
* Kilo, and Codex converters.
|
||||||
|
*/
|
||||||
|
|
||||||
|
process.env.GSD_TEST_MODE = '1';
|
||||||
|
|
||||||
|
const { test, describe } = require('node:test');
|
||||||
|
const assert = require('node:assert/strict');
|
||||||
|
|
||||||
|
const { convertClaudeToCopilotContent } = require('../bin/install.js');
|
||||||
|
|
||||||
|
describe('convertClaudeToCopilotContent — bare ~/.claude (issue #2545)', () => {
|
||||||
|
test('global install replaces bare ~/.claude at end of line', () => {
|
||||||
|
const input = 'configDir = ~/.claude\n';
|
||||||
|
const out = convertClaudeToCopilotContent(input, /* isGlobal */ true);
|
||||||
|
assert.ok(
|
||||||
|
!/(?:~|\$HOME)\/\.claude\b/.test(out),
|
||||||
|
`expected no leaked ~/.claude reference, got: ${JSON.stringify(out)}`,
|
||||||
|
);
|
||||||
|
assert.match(out, /~\/\.copilot\b/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('global install replaces bare $HOME/.claude at end of line', () => {
|
||||||
|
const input = 'configDir = $HOME/.claude\n';
|
||||||
|
const out = convertClaudeToCopilotContent(input, /* isGlobal */ true);
|
||||||
|
assert.ok(
|
||||||
|
!/(?:~|\$HOME)\/\.claude\b/.test(out),
|
||||||
|
`expected no leaked $HOME/.claude reference, got: ${JSON.stringify(out)}`,
|
||||||
|
);
|
||||||
|
assert.match(out, /\$HOME\/\.copilot\b/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('global install replaces bare ~/.claude before punctuation', () => {
|
||||||
|
const input = 'paths include `~/.claude`, `~/.copilot`';
|
||||||
|
const out = convertClaudeToCopilotContent(input, true);
|
||||||
|
assert.ok(!/(?:~|\$HOME)\/\.claude\b/.test(out));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('local install replaces bare ~/.claude', () => {
|
||||||
|
const input = 'configDir = ~/.claude\n';
|
||||||
|
const out = convertClaudeToCopilotContent(input, /* isGlobal */ false);
|
||||||
|
assert.ok(
|
||||||
|
!/(?:~|\$HOME)\/\.claude\b/.test(out),
|
||||||
|
`expected no leaked ~/.claude reference, got: ${JSON.stringify(out)}`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('does not double-replace trailing-slash form', () => {
|
||||||
|
const input = '@~/.claude/get-shit-done/foo.md\n';
|
||||||
|
const out = convertClaudeToCopilotContent(input, true);
|
||||||
|
assert.match(out, /~\/\.copilot\/get-shit-done\/foo\.md/);
|
||||||
|
assert.ok(!/\.copilot\/\.copilot/.test(out));
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user