Skip to content

Commit 2eaea9e

Browse files
committed
feat: shared configurable scan-exclude dirs
Extract SKIP_DIRS from prompt-completions.ts and search-ql-code.ts into a shared scan-exclude module with a comprehensive default list of 20 directories. The exclusion set is configurable via the env var CODEQL_MCP_SCAN_EXCLUDE_DIRS (comma-separated). Entries prefixed with ! remove a default (negation). Add codeql-mcp.scanExcludeDirs VS Code setting that the extension passes to the server as the env var. New files: - server/src/lib/scan-exclude.ts - server/test/src/lib/scan-exclude.test.ts Modified: - server/src/prompts/prompt-completions.ts - server/src/tools/codeql/search-ql-code.ts - extensions/vscode/package.json - extensions/vscode/src/bridge/environment-builder.ts - extensions/vscode/test/bridge/environment-builder.test.ts - server/test/src/prompts/prompt-completions.test.ts
1 parent 63247cc commit 2eaea9e

File tree

10 files changed

+447
-25
lines changed

10 files changed

+447
-25
lines changed

extensions/vscode/package.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,14 @@
123123
"default": ".codeql/ql-mcp",
124124
"markdownDescription": "Workspace-relative path for the ql-mcp scratch directory used for temporary files (query logs, external predicates, etc). The `.codeql/` parent is shared with other CodeQL CLI commands like `codeql pack bundle`. Set to an absolute path to override workspace-relative resolution."
125125
},
126+
"codeql-mcp.scanExcludeDirs": {
127+
"type": "array",
128+
"items": {
129+
"type": "string"
130+
},
131+
"default": [],
132+
"markdownDescription": "Additional directory names to exclude from workspace scanning (prompt completions, QL code search). Entries are merged with the built-in defaults (`.git`, `node_modules`, `dist`, etc.). Prefix an entry with `!` to remove a default (e.g., `!build` re-includes the `build` directory). Passed to the server as `CODEQL_MCP_SCAN_EXCLUDE_DIRS`."
133+
},
126134
"codeql-mcp.watchCodeqlExtension": {
127135
"type": "boolean",
128136
"default": true,

extensions/vscode/src/bridge/environment-builder.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,14 @@ export class EnvironmentBuilder extends DisposableObject {
166166
env.MONITORING_STORAGE_LOCATION = env.CODEQL_MCP_SCRATCH_DIR;
167167
}
168168

169+
// Scan exclusion directories for prompt completions and QL code search.
170+
// The server reads CODEQL_MCP_SCAN_EXCLUDE_DIRS to merge with built-in
171+
// defaults. The setting accepts additions and `!`-prefixed negations.
172+
const scanExcludeDirs = config.get<string[]>('scanExcludeDirs', []);
173+
if (scanExcludeDirs.length > 0) {
174+
env.CODEQL_MCP_SCAN_EXCLUDE_DIRS = scanExcludeDirs.join(',');
175+
}
176+
169177
// User-configured additional environment variables (overrides above defaults)
170178
const additionalEnv = config.get<Record<string, string>>('additionalEnv', {});
171179
for (const [key, value] of Object.entries(additionalEnv)) {

extensions/vscode/test/bridge/environment-builder.test.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,4 +392,35 @@ describe('EnvironmentBuilder', () => {
392392
}
393393
}
394394
});
395+
396+
it('should set CODEQL_MCP_SCAN_EXCLUDE_DIRS when scanExcludeDirs setting is non-empty', async () => {
397+
const vscode = await import('vscode');
398+
const originalGetConfig = vscode.workspace.getConfiguration;
399+
400+
try {
401+
vscode.workspace.getConfiguration = () => ({
402+
get: (_key: string, defaultVal?: any) => {
403+
if (_key === 'scanExcludeDirs') return ['custom-build', '!dist'];
404+
if (_key === 'additionalDatabaseDirs') return [];
405+
if (_key === 'additionalQueryRunResultsDirs') return [];
406+
if (_key === 'additionalMrvaRunResultsDirs') return [];
407+
return defaultVal;
408+
},
409+
has: () => false,
410+
inspect: () => undefined as any,
411+
update: () => Promise.resolve(),
412+
}) as any;
413+
414+
builder.invalidate();
415+
const env = await builder.build();
416+
expect(env.CODEQL_MCP_SCAN_EXCLUDE_DIRS).toBe('custom-build,!dist');
417+
} finally {
418+
vscode.workspace.getConfiguration = originalGetConfig;
419+
}
420+
});
421+
422+
it('should not set CODEQL_MCP_SCAN_EXCLUDE_DIRS when scanExcludeDirs is empty', async () => {
423+
const env = await builder.build();
424+
expect(env.CODEQL_MCP_SCAN_EXCLUDE_DIRS).toBeUndefined();
425+
});
395426
});

server/dist/codeql-development-mcp-server.js

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -196679,12 +196679,53 @@ var codeqlResolveTestsTool = {
196679196679
import { closeSync as closeSync2, createReadStream as createReadStream3, fstatSync as fstatSync2, lstatSync, openSync as openSync2, readdirSync as readdirSync8, readFileSync as readFileSync12, realpathSync as realpathSync2 } from "fs";
196680196680
import { basename as basename8, extname as extname2, join as join19, resolve as resolve9 } from "path";
196681196681
import { createInterface as createInterface3 } from "readline";
196682+
196683+
// src/lib/scan-exclude.ts
196684+
var DEFAULT_SCAN_EXCLUDE_DIRS = [
196685+
".cache",
196686+
".codeql",
196687+
".git",
196688+
".github",
196689+
".idea",
196690+
".mypy_cache",
196691+
".pytest_cache",
196692+
".tmp",
196693+
".tox",
196694+
".vscode",
196695+
".yarn",
196696+
"__pycache__",
196697+
"build",
196698+
"coverage",
196699+
"dist",
196700+
"node_modules",
196701+
"out",
196702+
"target",
196703+
"vendor"
196704+
];
196705+
function getScanExcludeDirs() {
196706+
const result = new Set(DEFAULT_SCAN_EXCLUDE_DIRS);
196707+
const envValue = process.env.CODEQL_MCP_SCAN_EXCLUDE_DIRS;
196708+
if (!envValue) {
196709+
return result;
196710+
}
196711+
const entries = envValue.split(",").map((e) => e.trim()).filter((e) => e.length > 0);
196712+
for (const entry of entries) {
196713+
if (entry.startsWith("!")) {
196714+
result.delete(entry.slice(1).trim());
196715+
} else {
196716+
result.add(entry);
196717+
}
196718+
}
196719+
return result;
196720+
}
196721+
196722+
// src/tools/codeql/search-ql-code.ts
196682196723
init_logger();
196683196724
var MAX_FILE_SIZE_BYTES = 5 * 1024 * 1024;
196684196725
var MAX_FILES_TRAVERSED = 1e4;
196685196726
var MAX_CONTEXT_LINES = 50;
196686196727
var MAX_MAX_RESULTS = 1e4;
196687-
var SKIP_DIRS2 = /* @__PURE__ */ new Set([".codeql", "node_modules", ".git"]);
196728+
var SKIP_DIRS2 = getScanExcludeDirs();
196688196729
function collectFiles(paths, extensions, fileCount) {
196689196730
const files = [];
196690196731
const visitedDirs = /* @__PURE__ */ new Set();
@@ -198130,15 +198171,7 @@ init_package_paths();
198130198171
var MAX_FILE_COMPLETIONS = 50;
198131198172
var MAX_SCAN_DEPTH = 8;
198132198173
var CACHE_TTL_MS = 5e3;
198133-
var SKIP_DIRS3 = /* @__PURE__ */ new Set([
198134-
".git",
198135-
".github",
198136-
".tmp",
198137-
"build",
198138-
"coverage",
198139-
"dist",
198140-
"node_modules"
198141-
]);
198174+
var SKIP_DIRS3 = getScanExcludeDirs();
198142198175
var scanCache = /* @__PURE__ */ new Map();
198143198176
function getCachedResults(cacheKey2) {
198144198177
const entry = scanCache.get(cacheKey2);

server/dist/codeql-development-mcp-server.js.map

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

server/src/lib/scan-exclude.ts

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/**
2+
* Shared directory exclusion list for workspace scanning operations.
3+
*
4+
* Provides a comprehensive default set of directory names to skip during
5+
* recursive file discovery (prompt completions, QL code search, etc.)
6+
* and makes the set configurable via the `CODEQL_MCP_SCAN_EXCLUDE_DIRS`
7+
* environment variable.
8+
*
9+
* The VS Code extension surfaces this as the `codeql-mcp.scanExcludeDirs`
10+
* setting, which is passed through to the server as an env var.
11+
*
12+
* ## Configuration
13+
*
14+
* The env var accepts a comma-separated list of directory names.
15+
* Entries prefixed with `!` remove a default from the set (negation).
16+
* All other entries are added to the default set.
17+
*
18+
* Examples:
19+
* - `CODEQL_MCP_SCAN_EXCLUDE_DIRS="custom-build,tmp-output"` — adds
20+
* - `CODEQL_MCP_SCAN_EXCLUDE_DIRS="!build,!dist"` — removes `build` and `dist` from defaults
21+
* - `CODEQL_MCP_SCAN_EXCLUDE_DIRS="custom-dir,!coverage"` — adds `custom-dir`, removes `coverage`
22+
*/
23+
24+
/**
25+
* Comprehensive default set of directory names to skip during workspace
26+
* scanning. Sorted alphabetically.
27+
*
28+
* This list is the union of directories previously skipped in
29+
* `prompt-completions.ts` and `search-ql-code.ts`, plus additional
30+
* well-known directories that never contain CodeQL source files and
31+
* would slow down scanning.
32+
*/
33+
export const DEFAULT_SCAN_EXCLUDE_DIRS: readonly string[] = [
34+
'.cache',
35+
'.codeql',
36+
'.git',
37+
'.github',
38+
'.idea',
39+
'.mypy_cache',
40+
'.pytest_cache',
41+
'.tmp',
42+
'.tox',
43+
'.vscode',
44+
'.yarn',
45+
'__pycache__',
46+
'build',
47+
'coverage',
48+
'dist',
49+
'node_modules',
50+
'out',
51+
'target',
52+
'vendor',
53+
];
54+
55+
/**
56+
* Parse the `CODEQL_MCP_SCAN_EXCLUDE_DIRS` environment variable and
57+
* merge with defaults.
58+
*
59+
* @returns A `Set` of directory names to exclude from scanning.
60+
*/
61+
export function getScanExcludeDirs(): Set<string> {
62+
const result = new Set(DEFAULT_SCAN_EXCLUDE_DIRS);
63+
64+
const envValue = process.env.CODEQL_MCP_SCAN_EXCLUDE_DIRS;
65+
if (!envValue) {
66+
return result;
67+
}
68+
69+
const entries = envValue.split(',').map(e => e.trim()).filter(e => e.length > 0);
70+
71+
for (const entry of entries) {
72+
if (entry.startsWith('!')) {
73+
// Negation: remove from defaults
74+
result.delete(entry.slice(1).trim());
75+
} else {
76+
// Addition: add to the set
77+
result.add(entry);
78+
}
79+
}
80+
81+
return result;
82+
}
83+
84+
/**
85+
* Check whether a directory name should be excluded from scanning.
86+
*
87+
* @param dirName - The directory basename to check.
88+
* @param excludeSet - Optional pre-computed exclusion set. When omitted,
89+
* `getScanExcludeDirs()` is called (reads the env var).
90+
* @returns `true` if the directory should be skipped.
91+
*/
92+
export function isScanExcluded(
93+
dirName: string,
94+
excludeSet?: Set<string>,
95+
): boolean {
96+
const set = excludeSet ?? getScanExcludeDirs();
97+
return set.has(dirName);
98+
}

server/src/prompts/prompt-completions.ts

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { dirname, join, relative, sep } from 'path';
1717
import { completable } from '@modelcontextprotocol/sdk/server/completable.js';
1818
import { z } from 'zod';
1919
import { getDatabaseBaseDirs } from '../lib/discovery-config';
20+
import { getScanExcludeDirs } from '../lib/scan-exclude';
2021
import { logger } from '../utils/logger';
2122
import { getUserWorkspaceDir } from '../utils/package-paths';
2223
import { SUPPORTED_LANGUAGES } from './constants';
@@ -36,17 +37,10 @@ const CACHE_TTL_MS = 5_000;
3637

3738
/**
3839
* Directories to skip during recursive workspace scans.
39-
* Centralised so that all completion providers use the same list.
40+
* Uses the shared, configurable exclusion list from scan-exclude.ts.
41+
* Computed once per module load; the env var is read at that time.
4042
*/
41-
const SKIP_DIRS = new Set([
42-
'.git',
43-
'.github',
44-
'.tmp',
45-
'build',
46-
'coverage',
47-
'dist',
48-
'node_modules',
49-
]);
43+
const SKIP_DIRS: Set<string> = getScanExcludeDirs();
5044

5145
/** Cached scan results keyed by a workspace+type identifier. */
5246
interface CacheEntry {

server/src/tools/codeql/search-ql-code.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { closeSync, createReadStream, fstatSync, lstatSync, openSync, readdirSyn
1212
import { basename, extname, join, resolve } from 'path';
1313
import { createInterface } from 'readline';
1414
import { z } from 'zod';
15+
import { getScanExcludeDirs } from '../../lib/scan-exclude';
1516
import { logger } from '../../utils/logger';
1617

1718
// ---------------------------------------------------------------------------
@@ -30,8 +31,11 @@ const MAX_CONTEXT_LINES = 50;
3031
/** Maximum allowed value for `maxResults`. */
3132
const MAX_MAX_RESULTS = 10_000;
3233

33-
/** Directory names to skip during traversal (compiled pack caches, deps). */
34-
const SKIP_DIRS = new Set(['.codeql', 'node_modules', '.git']);
34+
/**
35+
* Directory names to skip during traversal.
36+
* Uses the shared, configurable exclusion list from scan-exclude.ts.
37+
*/
38+
const SKIP_DIRS: Set<string> = getScanExcludeDirs();
3539

3640
// ---------------------------------------------------------------------------
3741
// Types

0 commit comments

Comments
 (0)