Skip to content

Commit 159b683

Browse files
committed
Address review feedback for PR #244
1 parent 298e5b2 commit 159b683

File tree

8 files changed

+134
-21
lines changed

8 files changed

+134
-21
lines changed

extensions/vscode/src/server/pack-installer.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,7 @@ export class PackInstaller extends DisposableObject {
247247
): Promise<void> {
248248
const qlRoot = this.getQlpackRoot();
249249
let successCount = 0;
250+
let skippedCount = 0;
250251

251252
for (const lang of languages) {
252253
const packDir = join(qlRoot, 'ql', lang, 'tools', 'src');
@@ -257,6 +258,7 @@ export class PackInstaller extends DisposableObject {
257258
await access(packDir, constants.R_OK);
258259
} catch {
259260
this.logger.debug(`Pack directory not found, skipping: ${packDir}`);
261+
skippedCount++;
260262
continue;
261263
}
262264

@@ -271,8 +273,10 @@ export class PackInstaller extends DisposableObject {
271273
);
272274
}
273275
}
276+
const attemptCount = languages.length - skippedCount;
277+
const skippedSuffix = skippedCount > 0 ? `, ${skippedCount} skipped` : '';
274278
this.logger.info(
275-
`Bundled pack install complete: ${successCount}/${languages.length} languages succeeded.`,
279+
`Bundled pack install complete: ${successCount}/${attemptCount} languages succeeded${skippedSuffix}.`,
276280
);
277281
}
278282

extensions/vscode/test/server/pack-installer.test.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,48 @@ describe('PackInstaller', () => {
456456
);
457457
});
458458

459+
it('should report attemptCount (not languages.length) when packs are skipped', async () => {
460+
cliResolver.getCliVersion.mockReturnValue('2.25.1');
461+
serverManager.getExtensionVersion.mockReturnValue('2.25.1');
462+
463+
// Only javascript pack dir exists; python and go are missing
464+
vi.mocked(access).mockImplementation((path: any) => {
465+
if (String(path).includes('/javascript/')) return Promise.resolve(undefined as any);
466+
return Promise.reject(new Error('ENOENT'));
467+
});
468+
469+
await installer.installAll({ languages: ['javascript', 'python', 'go'] });
470+
471+
// Summary should say 1/1 (succeeded/attempted), not 1/3
472+
const summaryCall = logger.info.mock.calls.find(
473+
(call: any[]) => typeof call[0] === 'string' && call[0].includes('Bundled pack install complete'),
474+
);
475+
expect(summaryCall).toBeDefined();
476+
const summaryMsg = summaryCall![0] as string;
477+
expect(summaryMsg).toContain('1/1');
478+
expect(summaryMsg).not.toContain('1/3');
479+
});
480+
481+
it('should mention skipped count in summary when packs are missing', async () => {
482+
cliResolver.getCliVersion.mockReturnValue('2.25.1');
483+
serverManager.getExtensionVersion.mockReturnValue('2.25.1');
484+
485+
// Only javascript pack dir exists; python and go are missing
486+
vi.mocked(access).mockImplementation((path: any) => {
487+
if (String(path).includes('/javascript/')) return Promise.resolve(undefined as any);
488+
return Promise.reject(new Error('ENOENT'));
489+
});
490+
491+
await installer.installAll({ languages: ['javascript', 'python', 'go'] });
492+
493+
const summaryCall = logger.info.mock.calls.find(
494+
(call: any[]) => typeof call[0] === 'string' && call[0].includes('Bundled pack install complete'),
495+
);
496+
expect(summaryCall).toBeDefined();
497+
const summaryMsg = summaryCall![0] as string;
498+
expect(summaryMsg).toMatch(/2 skipped/);
499+
});
500+
459501
it('should use download terminology in pack download logs', async () => {
460502
cliResolver.getCliVersion.mockReturnValue('2.24.1');
461503
serverManager.getExtensionVersion.mockReturnValue('2.25.1-next.1');

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

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -196876,10 +196876,13 @@ var MAX_FILE_SIZE_BYTES = 5 * 1024 * 1024;
196876196876
var MAX_FILES_TRAVERSED = 1e4;
196877196877
var MAX_CONTEXT_LINES = 50;
196878196878
var MAX_MAX_RESULTS = 1e4;
196879-
var SKIP_DIRS2 = getScanExcludeDirs();
196879+
function getSkipDirs() {
196880+
return getScanExcludeDirs();
196881+
}
196880196882
function collectFiles(paths, extensions, fileCount) {
196881196883
const files = [];
196882196884
const visitedDirs = /* @__PURE__ */ new Set();
196885+
const skipDirs = getSkipDirs();
196883196886
function walk(p) {
196884196887
if (fileCount.value >= MAX_FILES_TRAVERSED) return;
196885196888
let stat;
@@ -196895,7 +196898,7 @@ function collectFiles(paths, extensions, fileCount) {
196895196898
}
196896196899
fileCount.value++;
196897196900
} else if (stat.isDirectory()) {
196898-
if (SKIP_DIRS2.has(basename9(p))) return;
196901+
if (skipDirs.has(basename9(p))) return;
196899196902
let realPath;
196900196903
try {
196901196904
realPath = realpathSync2(p);
@@ -198322,7 +198325,9 @@ init_package_paths();
198322198325
var MAX_FILE_COMPLETIONS = 50;
198323198326
var MAX_SCAN_DEPTH = 8;
198324198327
var CACHE_TTL_MS = 5e3;
198325-
var SKIP_DIRS3 = getScanExcludeDirs();
198328+
function getSkipDirs2() {
198329+
return getScanExcludeDirs();
198330+
}
198326198331
var scanCache = /* @__PURE__ */ new Map();
198327198332
function getCachedResults(cacheKey2) {
198328198333
const entry = scanCache.get(cacheKey2);
@@ -198338,8 +198343,9 @@ function completeLanguage(value) {
198338198343
const lower = (value || "").toLowerCase();
198339198344
return [...SUPPORTED_LANGUAGES].filter((lang) => lang.startsWith(lower));
198340198345
}
198341-
async function findFilesByExtension(dir, baseDir, extensions, maxDepth, results) {
198346+
async function findFilesByExtension(dir, baseDir, extensions, maxDepth, results, skipDirs) {
198342198347
if (maxDepth <= 0 || results.length >= MAX_FILE_COMPLETIONS) return;
198348+
const excluded = skipDirs ?? getSkipDirs2();
198343198349
let entries;
198344198350
try {
198345198351
entries = await readdir(dir, { withFileTypes: true });
@@ -198350,10 +198356,10 @@ async function findFilesByExtension(dir, baseDir, extensions, maxDepth, results)
198350198356
if (results.length >= MAX_FILE_COMPLETIONS) break;
198351198357
const fullPath = join22(dir, entry.name);
198352198358
if (entry.isDirectory()) {
198353-
if (SKIP_DIRS3.has(entry.name)) {
198359+
if (excluded.has(entry.name)) {
198354198360
continue;
198355198361
}
198356-
await findFilesByExtension(fullPath, baseDir, extensions, maxDepth - 1, results);
198362+
await findFilesByExtension(fullPath, baseDir, extensions, maxDepth - 1, results, excluded);
198357198363
} else if (entry.isFile()) {
198358198364
const lower = entry.name.toLowerCase();
198359198365
if (extensions.some((ext) => lower.endsWith(ext))) {
@@ -198463,7 +198469,7 @@ async function findDatabaseDirs(dir, _baseDir, maxDepth, results) {
198463198469
}
198464198470
for (const entry of entries) {
198465198471
if (results.length >= MAX_FILE_COMPLETIONS) break;
198466-
if (entry.isDirectory() && !SKIP_DIRS3.has(entry.name)) {
198472+
if (entry.isDirectory() && !getSkipDirs2().has(entry.name)) {
198467198473
await findDatabaseDirs(join22(dir, entry.name), _baseDir, maxDepth - 1, results);
198468198474
}
198469198475
}
@@ -198490,7 +198496,7 @@ async function completePackRoot(value) {
198490198496
}
198491198497
for (const entry of entries) {
198492198498
if (results.length >= MAX_FILE_COMPLETIONS) break;
198493-
if (entry.isDirectory() && !SKIP_DIRS3.has(entry.name)) {
198499+
if (entry.isDirectory() && !getSkipDirs2().has(entry.name)) {
198494198500
await scan(join22(dir, entry.name), depth - 1);
198495198501
}
198496198502
}

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/prompts/prompt-completions.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,11 @@ const CACHE_TTL_MS = 5_000;
3838
/**
3939
* Directories to skip during recursive workspace scans.
4040
* Uses the shared, configurable exclusion list from scan-exclude.ts.
41-
* Computed once per module load; the env var is read at that time.
41+
* Resolved at traversal time so that env var changes take effect.
4242
*/
43-
const SKIP_DIRS: Set<string> = getScanExcludeDirs();
43+
function getSkipDirs(): Set<string> {
44+
return getScanExcludeDirs();
45+
}
4446

4547
/** Cached scan results keyed by a workspace+type identifier. */
4648
interface CacheEntry {
@@ -96,9 +98,12 @@ async function findFilesByExtension(
9698
extensions: string[],
9799
maxDepth: number,
98100
results: string[],
101+
skipDirs?: Set<string>,
99102
): Promise<void> {
100103
if (maxDepth <= 0 || results.length >= MAX_FILE_COMPLETIONS) return;
101104

105+
const excluded = skipDirs ?? getSkipDirs();
106+
102107
let entries;
103108
try {
104109
entries = await readdir(dir, { withFileTypes: true });
@@ -113,10 +118,10 @@ async function findFilesByExtension(
113118

114119
if (entry.isDirectory()) {
115120
// Skip common non-CodeQL directories
116-
if (SKIP_DIRS.has(entry.name)) {
121+
if (excluded.has(entry.name)) {
117122
continue;
118123
}
119-
await findFilesByExtension(fullPath, baseDir, extensions, maxDepth - 1, results);
124+
await findFilesByExtension(fullPath, baseDir, extensions, maxDepth - 1, results, excluded);
120125
} else if (entry.isFile()) {
121126
const lower = entry.name.toLowerCase();
122127
if (extensions.some(ext => lower.endsWith(ext))) {
@@ -284,7 +289,7 @@ async function findDatabaseDirs(
284289

285290
for (const entry of entries) {
286291
if (results.length >= MAX_FILE_COMPLETIONS) break;
287-
if (entry.isDirectory() && !SKIP_DIRS.has(entry.name)) {
292+
if (entry.isDirectory() && !getSkipDirs().has(entry.name)) {
288293
await findDatabaseDirs(join(dir, entry.name), _baseDir, maxDepth - 1, results);
289294
}
290295
}
@@ -322,7 +327,7 @@ export async function completePackRoot(value: string): Promise<string[]> {
322327

323328
for (const entry of entries) {
324329
if (results.length >= MAX_FILE_COMPLETIONS) break;
325-
if (entry.isDirectory() && !SKIP_DIRS.has(entry.name)) {
330+
if (entry.isDirectory() && !getSkipDirs().has(entry.name)) {
326331
await scan(join(dir, entry.name), depth - 1);
327332
}
328333
}

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,11 @@ const MAX_MAX_RESULTS = 10_000;
3434
/**
3535
* Directory names to skip during traversal.
3636
* Uses the shared, configurable exclusion list from scan-exclude.ts.
37+
* Resolved at traversal time so that env var changes take effect.
3738
*/
38-
const SKIP_DIRS: Set<string> = getScanExcludeDirs();
39+
function getSkipDirs(): Set<string> {
40+
return getScanExcludeDirs();
41+
}
3942

4043
// ---------------------------------------------------------------------------
4144
// Types
@@ -73,6 +76,7 @@ function collectFiles(
7376
): string[] {
7477
const files: string[] = [];
7578
const visitedDirs = new Set<string>();
79+
const skipDirs = getSkipDirs();
7680

7781
function walk(p: string): void {
7882
if (fileCount.value >= MAX_FILES_TRAVERSED) return;
@@ -95,7 +99,7 @@ function collectFiles(
9599
fileCount.value++;
96100
} else if (stat.isDirectory()) {
97101
// Skip well-known directories that mirror source or contain deps
98-
if (SKIP_DIRS.has(basename(p))) return;
102+
if (skipDirs.has(basename(p))) return;
99103

100104
// Track visited directories by real path to prevent cycles
101105
let realPath: string;

server/test/src/prompts/prompt-completions.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -631,6 +631,27 @@ describe('completeQueryPath — directory filtering', () => {
631631
const result = await completeQueryPath('');
632632
expect(result).toEqual([]);
633633
});
634+
635+
it('should respect CODEQL_MCP_SCAN_EXCLUDE_DIRS changes after module load', async () => {
636+
// Custom directory that is NOT in the default exclusion list
637+
const customDir = join(tmpDir, 'custom-build');
638+
mkdirSync(customDir, { recursive: true });
639+
writeFileSync(join(customDir, 'Query.ql'), '');
640+
writeFileSync(join(tmpDir, 'TopLevel.ql'), '');
641+
642+
// Without exclusion: custom-build should be found
643+
vi.stubEnv('CODEQL_MCP_SCAN_EXCLUDE_DIRS', '');
644+
clearCompletionCache();
645+
const before = await completeQueryPath('');
646+
expect(before).toContain(join('custom-build', 'Query.ql'));
647+
648+
// Now exclude custom-build via env var — should be respected
649+
vi.stubEnv('CODEQL_MCP_SCAN_EXCLUDE_DIRS', 'custom-build');
650+
clearCompletionCache();
651+
const after = await completeQueryPath('');
652+
expect(after).not.toContain(join('custom-build', 'Query.ql'));
653+
expect(after).toContain('TopLevel.ql');
654+
});
634655
});
635656

636657
// ─────────────────────────────────────────────────────────────────────────────

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

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* Tests for search-ql-code tool
33
*/
44

5-
import { afterEach, describe, expect, it } from 'vitest';
5+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
66
import { mkdirSync, writeFileSync } from 'fs';
77
import { join } from 'path';
88
import { searchQlCode } from '../../../../src/tools/codeql/search-ql-code';
@@ -337,4 +337,35 @@ describe('searchQlCode', () => {
337337
expect(result.results[0].filePath).toContain('lib');
338338
});
339339
});
340+
341+
describe('dynamic scan exclude dirs', () => {
342+
beforeEach(() => {
343+
vi.stubEnv('CODEQL_MCP_SCAN_EXCLUDE_DIRS', '');
344+
});
345+
346+
afterEach(() => {
347+
vi.unstubAllEnvs();
348+
});
349+
350+
it('should respect CODEQL_MCP_SCAN_EXCLUDE_DIRS changes after module load', async () => {
351+
tempDir = createTestTempDir('search-ql-code');
352+
const srcDir = join(tempDir, 'src');
353+
const customDir = join(tempDir, 'custom-build');
354+
mkdirSync(srcDir, { recursive: true });
355+
mkdirSync(customDir, { recursive: true });
356+
writeFileSync(join(srcDir, 'Query.qll'), 'findme\n');
357+
writeFileSync(join(customDir, 'Query.qll'), 'findme\n');
358+
359+
// Without exclusion: custom-build should be found
360+
vi.stubEnv('CODEQL_MCP_SCAN_EXCLUDE_DIRS', '');
361+
const before = await searchQlCode({ pattern: 'findme', paths: [tempDir] });
362+
expect(before.totalMatches).toBe(2);
363+
364+
// Now exclude custom-build via env var — should be respected
365+
vi.stubEnv('CODEQL_MCP_SCAN_EXCLUDE_DIRS', 'custom-build');
366+
const after = await searchQlCode({ pattern: 'findme', paths: [tempDir] });
367+
expect(after.totalMatches).toBe(1);
368+
expect(after.results[0].filePath).toContain('src');
369+
});
370+
});
340371
});

0 commit comments

Comments
 (0)