Skip to content

Commit d89b94e

Browse files
authored
Merge pull request #681 from rajbos/feat/non-copilot-file-detection
feat: complete non-copilot instruction file detection (Missed Potential)
2 parents 95560c3 + 11d183c commit d89b94e

2 files changed

Lines changed: 164 additions & 6 deletions

File tree

vscode-extension/src/customizationPatterns.json

Lines changed: 90 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,17 @@
22
"description": "Defines Copilot customization file patterns to scan in workspaces",
33
"lastUpdated": "2026-02-11",
44
"stalenessThresholdDays": 90,
5-
"excludeDirs": ["node_modules", ".git", "dist", "build", "out", "__pycache__", ".venv", ".next", "coverage"],
5+
"excludeDirs": [
6+
"node_modules",
7+
".git",
8+
"dist",
9+
"build",
10+
"out",
11+
"__pycache__",
12+
".venv",
13+
".next",
14+
"coverage"
15+
],
616
"patterns": [
717
{
818
"id": "copilot-instructions",
@@ -83,11 +93,11 @@
8393
},
8494
{
8595
"id": "claude-code",
86-
"type": "non-copilot-instructions",
96+
"type": "non-copilot-config",
8797
"category": "non-copilot",
8898
"icon": "🎭",
89-
"label": "Claude Code",
90-
"path": "CLAUDE.md",
99+
"label": "Claude Code Settings",
100+
"path": ".claude/settings.json",
91101
"scanMode": "exact",
92102
"caseInsensitive": true
93103
},
@@ -216,6 +226,81 @@
216226
"path": ".aiassistant/rules/*.md",
217227
"scanMode": "recursive",
218228
"maxDepth": 2
229+
},
230+
{
231+
"id": "cursor-dotrules",
232+
"path": ".cursorrules",
233+
"scanMode": "exact",
234+
"type": "non-copilot-instructions",
235+
"icon": "🔷",
236+
"label": "Cursor Rules (.cursorrules)",
237+
"category": "non-copilot"
238+
},
239+
{
240+
"id": "windsurf-dotrules",
241+
"path": ".windsurfrules",
242+
"scanMode": "exact",
243+
"type": "non-copilot-instructions",
244+
"icon": "🌊",
245+
"label": "Windsurf Rules (.windsurfrules)",
246+
"category": "non-copilot"
247+
},
248+
{
249+
"id": "windsurf-skills",
250+
"path": ".windsurf/skills/*.md",
251+
"scanMode": "recursive",
252+
"maxDepth": 2,
253+
"type": "non-copilot-instructions",
254+
"icon": "🌊",
255+
"label": "Windsurf Skills",
256+
"category": "non-copilot"
257+
},
258+
{
259+
"id": "roo-code",
260+
"path": ".roo/rules/*.md",
261+
"scanMode": "recursive",
262+
"maxDepth": 2,
263+
"type": "non-copilot-instructions",
264+
"icon": "🦘",
265+
"label": "Roo Code Rules",
266+
"category": "non-copilot"
267+
},
268+
{
269+
"id": "opencode-config",
270+
"path": "opencode.json",
271+
"scanMode": "exact",
272+
"type": "non-copilot-config",
273+
"icon": "🔓",
274+
"label": "OpenCode Config",
275+
"category": "non-copilot"
276+
},
277+
{
278+
"id": "trae-rules",
279+
"path": ".trae/rules/*.md",
280+
"scanMode": "recursive",
281+
"maxDepth": 2,
282+
"type": "non-copilot-instructions",
283+
"icon": "🔧",
284+
"label": "Trae Rules",
285+
"category": "non-copilot"
286+
},
287+
{
288+
"id": "junie-guidelines",
289+
"path": ".junie/guidelines.md",
290+
"scanMode": "exact",
291+
"type": "non-copilot-instructions",
292+
"icon": "🧠",
293+
"label": "JetBrains Junie Guidelines",
294+
"category": "non-copilot"
295+
},
296+
{
297+
"id": "codex-md",
298+
"path": "CODEX.md",
299+
"scanMode": "exact",
300+
"type": "non-copilot-instructions",
301+
"icon": "🤖",
302+
"label": "OpenAI Codex Instructions",
303+
"category": "non-copilot"
219304
}
220305
]
221-
}
306+
}

vscode-extension/test/unit/workspaceHelpers.test.ts

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -487,4 +487,77 @@ test('extractMcpServerName: mcp__server__tool format extracts server name', () =
487487

488488
test('extractMcpServerName: mcp__server__multi__part__tool extracts only first server segment', () => {
489489
assert.equal(extractMcpServerName('mcp__my_server__tool__with__parts'), 'my_server');
490-
});
490+
});
491+
// ── scanWorkspaceCustomizationFiles — category detection ─────────────────
492+
493+
import * as fs from 'node:fs';
494+
import * as os from 'node:os';
495+
import * as nodePath from 'node:path';
496+
import { scanWorkspaceCustomizationFiles } from '../../src/workspaceHelpers';
497+
498+
test('scanWorkspaceCustomizationFiles: returns empty array for non-existent dir', () => {
499+
const result = scanWorkspaceCustomizationFiles('/does/not/exist/xyz123');
500+
assert.deepEqual(result, []);
501+
});
502+
503+
test('scanWorkspaceCustomizationFiles: detects copilot-instructions.md as copilot category', () => {
504+
const tmpDir = fs.mkdtempSync(nodePath.join(os.tmpdir(), 'wh-test-'));
505+
try {
506+
const githubDir = nodePath.join(tmpDir, '.github');
507+
fs.mkdirSync(githubDir);
508+
fs.writeFileSync(nodePath.join(githubDir, 'copilot-instructions.md'), '# Instructions');
509+
const result = scanWorkspaceCustomizationFiles(tmpDir);
510+
const copilotFile = result.find(f => f.type !== 'unknown' && f.path.includes('copilot-instructions.md'));
511+
assert.ok(copilotFile, 'should find copilot-instructions.md');
512+
assert.equal(copilotFile?.category, 'copilot');
513+
} finally {
514+
fs.rmSync(tmpDir, { recursive: true, force: true });
515+
}
516+
});
517+
518+
test('scanWorkspaceCustomizationFiles: detects .cursorrules as non-copilot category', () => {
519+
const tmpDir = fs.mkdtempSync(nodePath.join(os.tmpdir(), 'wh-test-'));
520+
try {
521+
fs.writeFileSync(nodePath.join(tmpDir, '.cursorrules'), '# Cursor rules');
522+
const result = scanWorkspaceCustomizationFiles(tmpDir);
523+
const cursorFile = result.find(f => f.path.includes('.cursorrules'));
524+
assert.ok(cursorFile, 'should find .cursorrules');
525+
assert.equal(cursorFile?.category, 'non-copilot');
526+
} finally {
527+
fs.rmSync(tmpDir, { recursive: true, force: true });
528+
}
529+
});
530+
531+
test('scanWorkspaceCustomizationFiles: detects .claude/settings.json as non-copilot (not CLAUDE.md)', () => {
532+
const tmpDir = fs.mkdtempSync(nodePath.join(os.tmpdir(), 'wh-test-'));
533+
try {
534+
// CLAUDE.md should NOT appear as non-copilot (it is Copilot-compatible)
535+
fs.writeFileSync(nodePath.join(tmpDir, 'CLAUDE.md'), '# Claude instructions');
536+
const result = scanWorkspaceCustomizationFiles(tmpDir);
537+
const claudeMd = result.find(f => f.path.includes('CLAUDE.md') && f.category === 'non-copilot');
538+
assert.equal(claudeMd, undefined, 'CLAUDE.md should not be flagged as non-copilot');
539+
540+
// .claude/settings.json SHOULD appear as non-copilot
541+
const claudeDir = nodePath.join(tmpDir, '.claude');
542+
fs.mkdirSync(claudeDir);
543+
fs.writeFileSync(nodePath.join(claudeDir, 'settings.json'), '{}');
544+
const result2 = scanWorkspaceCustomizationFiles(tmpDir);
545+
const claudeSettings = result2.find(f => f.path.includes('settings.json') && f.category === 'non-copilot');
546+
assert.ok(claudeSettings, 'should find .claude/settings.json as non-copilot');
547+
} finally {
548+
fs.rmSync(tmpDir, { recursive: true, force: true });
549+
}
550+
});
551+
552+
test('scanWorkspaceCustomizationFiles: detects opencode.json as non-copilot', () => {
553+
const tmpDir = fs.mkdtempSync(nodePath.join(os.tmpdir(), 'wh-test-'));
554+
try {
555+
fs.writeFileSync(nodePath.join(tmpDir, 'opencode.json'), '{}');
556+
const result = scanWorkspaceCustomizationFiles(tmpDir);
557+
const opencodeFile = result.find(f => f.path.includes('opencode.json'));
558+
assert.ok(opencodeFile, 'should find opencode.json');
559+
assert.equal(opencodeFile?.category, 'non-copilot');
560+
} finally {
561+
fs.rmSync(tmpDir, { recursive: true, force: true });
562+
}
563+
});

0 commit comments

Comments
 (0)