Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
228 changes: 123 additions & 105 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -196,20 +196,20 @@
"build:webview": "cd webview && npm run build",
"build:dashboard": "cd dashboard && npm run build && rm -rf ../dashboard-dist && cp -r dist ../dashboard-dist",
"test": "npm run test:scanner",
"test:scanner": "tsc -p tsconfig.scanner-tests.json && node dist-test/test/scanner-patterns.test.js && node dist-test/test/workspace-scanner.test.js && node dist-test/test/workspace-file-access.test.js && node dist-test/test/endpoint-classification.test.js && node dist-test/test/local-waste-detector.test.js && node dist-test/test/chat-providers.test.js && node dist-test/test/fingerprint-registry.test.js && node dist-test/test/pricing-sync.test.js && node dist-test/test/ast-parser-loader.test.js && node dist-test/test/ast-call-visitor.test.js && node dist-test/test/ast-import-resolver.test.js && node dist-test/test/ast-scanner.test.js && node dist-test/test/ast-python.test.js && node dist-test/test/ast-frequency-analyzer.test.js && node dist-test/test/ast-cache-detector.test.js && node dist-test/test/ast-batch-detector.test.js && node dist-test/test/ast-concurrency-detector.test.js && node dist-test/test/ast-cross-file-resolver.test.js && node dist-test/intelligence/__tests__/builder.test.js && node dist-test/intelligence/__tests__/clusters.test.js && node dist-test/intelligence/__tests__/compression.test.js && node dist-test/intelligence/__tests__/export.test.js && node dist-test/test/api-client.test.js && node dist-test/test/key-management.test.js && node dist-test/test/ast-parser-loader-fallback.test.js && node dist-test/intelligence/__tests__/cost-utils.test.js && node dist-test/test/intelligence-compression-async.test.js && node dist-test/test/webview-provider-dispatch.test.js",
"test:scanner": "tsc -p tsconfig.scanner-tests.json && node dist-test/test/scanner-patterns.test.js && node dist-test/test/workspace-scanner.test.js && node dist-test/test/workspace-file-access.test.js && node dist-test/test/endpoint-classification.test.js && node dist-test/test/local-waste-detector.test.js && node dist-test/test/chat-providers.test.js && node dist-test/test/fingerprint-registry.test.js && node dist-test/test/pricing-sync.test.js && node dist-test/test/ast-parser-loader.test.js && node dist-test/test/ast-call-visitor.test.js && node dist-test/test/ast-import-resolver.test.js && node dist-test/test/ast-scanner.test.js && node dist-test/test/ast-python.test.js && node dist-test/test/ast-frequency-analyzer.test.js && node dist-test/test/ast-cache-detector.test.js && node dist-test/test/ast-batch-detector.test.js && node dist-test/test/ast-concurrency-detector.test.js && node dist-test/test/ast-cross-file-resolver.test.js && node dist-test/intelligence/__tests__/builder.test.js && node dist-test/intelligence/__tests__/clusters.test.js && node dist-test/intelligence/__tests__/compression.test.js && node dist-test/intelligence/__tests__/export.test.js && node dist-test/test/api-client.test.js && node dist-test/test/key-management.test.js && node dist-test/test/ast-parser-loader-fallback.test.js && node dist-test/intelligence/__tests__/cost-utils.test.js && node dist-test/test/intelligence-compression-async.test.js && node dist-test/test/webview-provider-dispatch.test.js && node dist-test/test/extension-activation.test.js",
"calibrate-detectors": "tsc -p tsconfig.scanner-tests.json && node dist-test/test/waste-calibration.js",
"watch:ext": "node esbuild.mjs --watch",
"watch:webview": "cd webview && npm run build -- --watch",
"package": "npm run build:webview && node esbuild.mjs --release && npx @vscode/vsce package --no-dependencies --allow-missing-repository"
},
"dependencies": {
"openai": "^4.73.0",
"openai": "^4.104.0",
"web-tree-sitter": "^0.26.7"
},
"devDependencies": {
"@types/node": "^20.0.0",
"@types/vscode": "^1.85.0",
"esbuild": "^0.24.0",
"esbuild": "^0.28.0",
"typescript": "^5.8.0"
}
}
4 changes: 2 additions & 2 deletions src/cli/scan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ async function runContextFormat(options: CliOptions, access: Awaited<ReturnType<
const snapshot = buildSnapshot({ apiCalls, findings, repoRoot, totalFilesScanned: access.files.length });
const scored = scoreSnapshot(snapshot);
const clusters = buildReviewClusters(scored);
const compressed = compressClusters(clusters, snapshot);
const compressed = await compressClusters(clusters, snapshot);
const exportContext = buildExportContext(compressed, snapshot, scored);
const markdown = formatAsMarkdown(exportContext);

Expand All @@ -176,7 +176,7 @@ async function runPackCommand(
const snapshot = buildSnapshot({ apiCalls, findings, repoRoot: dir, totalFilesScanned: access.files.length });
const scored = scoreSnapshot(snapshot);
const clusters = buildReviewClusters(scored);
const compressed = compressClusters(clusters, snapshot);
const compressed = await compressClusters(clusters, snapshot);
const context = buildExportContext(compressed, snapshot, scored);

const content = format === "json" ? formatAsJSON(context) : formatAsMarkdown(context);
Expand Down
2 changes: 1 addition & 1 deletion src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ export function activate(context: vscode.ExtensionContext) {
});
const scored = scoreSnapshot(snapshot);
const reviewClusters = buildReviewClusters(scored);
const compressedClusters = compressClusters(reviewClusters, snapshot);
const compressedClusters = await compressClusters(reviewClusters, snapshot);
const generatorVersion = String(context.extension.packageJSON.version ?? "");
const exportContext = buildExportContext(compressedClusters, snapshot, scored, {
generatorVersion: generatorVersion || undefined,
Expand Down
84 changes: 50 additions & 34 deletions src/intelligence/__tests__/compression.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,24 @@ import { buildReviewClusters } from "../clusters";
import { compressClusters } from "../compression";
import { scoreRepoIntelligence } from "../scorer";

function run(name: string, fn: () => void): void {
try {
fn();
console.log(`PASS ${name}`);
} catch (error) {
console.error(`FAIL ${name}`);
throw error;
}
const pendingTests: Array<() => Promise<void>> = [];

function run(name: string, fn: () => void | Promise<void>): void {
pendingTests.push(async () => {
try {
await fn();
console.log(`PASS ${name}`);
} catch (error) {
console.error(`FAIL ${name}`);
throw error;
}
});
}

function withTempWorkspace(files: Record<string, string>, fn: (workspaceDir: string) => void): void {
async function withTempWorkspace(
files: Record<string, string>,
fn: (workspaceDir: string) => void | Promise<void>
): Promise<void> {
const originalCwd = process.cwd();
const workspaceDir = fs.mkdtempSync(path.join(os.tmpdir(), "compression-test-"));

Expand All @@ -29,15 +36,15 @@ function withTempWorkspace(files: Record<string, string>, fn: (workspaceDir: str
fs.writeFileSync(absolutePath, content, "utf8");
}
process.chdir(workspaceDir);
fn(workspaceDir);
await fn(workspaceDir);
} finally {
process.chdir(originalCwd);
fs.rmSync(workspaceDir, { recursive: true, force: true });
}
}

run("compressClusters returns compact summaries, normalized findings, and bounded snippets", () => {
withTempWorkspace(
run("compressClusters returns compact summaries, normalized findings, and bounded snippets", async () => {
await withTempWorkspace(
{
"src/chat/loop.ts": [
"export async function loop(items) {",
Expand All @@ -63,7 +70,7 @@ run("compressClusters returns compact summaries, normalized findings, and bounde
"}, 1000);",
].join("\n"),
},
() => {
async () => {
const snapshot = buildSnapshot({
apiCalls: [
{
Expand Down Expand Up @@ -136,7 +143,7 @@ run("compressClusters returns compact summaries, normalized findings, and bounde
});

const clusters = buildReviewClusters(scoreRepoIntelligence(snapshot));
const compressed = compressClusters(clusters, snapshot);
const compressed = await compressClusters(clusters, snapshot);

assert.ok(compressed.length >= 1);
const loopCluster = compressed.find((cluster) => cluster.primarySummary.filePath === "src/chat/loop.ts");
Expand Down Expand Up @@ -179,8 +186,8 @@ run("compressClusters returns compact summaries, normalized findings, and bounde
);
});

run("compressClusters dedupes repeated same-file findings and collapses repeated titles in export output", () => {
withTempWorkspace(
run("compressClusters dedupes repeated same-file findings and collapses repeated titles in export output", async () => {
await withTempWorkspace(
{
"src/chat/cache.ts": [
"export async function loadModel() {",
Expand All @@ -189,7 +196,7 @@ run("compressClusters dedupes repeated same-file findings and collapses repeated
"}",
].join("\n"),
},
() => {
async () => {
const snapshot = buildSnapshot({
apiCalls: [
{
Expand Down Expand Up @@ -243,7 +250,7 @@ run("compressClusters dedupes repeated same-file findings and collapses repeated
],
});

const compressed = compressClusters(buildReviewClusters(scoreRepoIntelligence(snapshot)), snapshot);
const compressed = await compressClusters(buildReviewClusters(scoreRepoIntelligence(snapshot)), snapshot);
const cluster = compressed.find((entry) => entry.primarySummary.filePath === "src/chat/cache.ts");
assert.ok(cluster);
assert.equal(cluster?.findings.filter((finding) => finding.title === "Missing caching").length, 1);
Expand All @@ -252,16 +259,16 @@ run("compressClusters dedupes repeated same-file findings and collapses repeated
);
});

run("compressClusters uses softer evidence language for weak test-derived files", () => {
withTempWorkspace(
run("compressClusters uses softer evidence language for weak test-derived files", async () => {
await withTempWorkspace(
{
"src/test/providers.test.ts": [
"for (const provider of ALL_PROVIDERS) {",
" expect(provider).toBeDefined();",
"}",
].join("\n"),
},
() => {
async () => {
const snapshot = buildSnapshot({
apiCalls: [
{
Expand All @@ -276,7 +283,7 @@ run("compressClusters uses softer evidence language for weak test-derived files"
findings: [],
});

const compressed = compressClusters(buildReviewClusters(scoreRepoIntelligence(snapshot)), snapshot);
const compressed = await compressClusters(buildReviewClusters(scoreRepoIntelligence(snapshot)), snapshot);
const testCluster = compressed.find((cluster) => cluster.primarySummary.filePath === "src/test/providers.test.ts");
assert.ok(testCluster);
assert.ok(testCluster?.primarySummary.description.startsWith("This test file"));
Expand All @@ -289,16 +296,16 @@ run("compressClusters uses softer evidence language for weak test-derived files"
);
});

run("compressClusters uses neutral snippet labels for test helper cache-like code", () => {
withTempWorkspace(
run("compressClusters uses neutral snippet labels for test helper cache-like code", async () => {
await withTempWorkspace(
{
"src/test/providers.test.ts": [
"function findProvider(id) {",
" return ALL_PROVIDERS.find((x) => x.provider === id);",
"}",
].join("\n"),
},
() => {
async () => {
const snapshot = buildSnapshot({
apiCalls: [
{
Expand All @@ -324,7 +331,7 @@ run("compressClusters uses neutral snippet labels for test helper cache-like cod
],
});

const compressed = compressClusters(buildReviewClusters(scoreRepoIntelligence(snapshot)), snapshot);
const compressed = await compressClusters(buildReviewClusters(scoreRepoIntelligence(snapshot)), snapshot);
const testCluster = compressed.find((cluster) => cluster.primarySummary.filePath === "src/test/providers.test.ts");
assert.ok(testCluster);
assert.ok(testCluster?.snippets.some((snippet) => snippet.label === "Relevant test helper context"));
Expand All @@ -333,8 +340,8 @@ run("compressClusters uses neutral snippet labels for test helper cache-like cod
);
});

run("compressClusters handles files with only findings, null providers, and missing snippet files", () => {
withTempWorkspace(
run("compressClusters handles files with only findings, null providers, and missing snippet files", async () => {
await withTempWorkspace(
{
"src/shared/a.ts": [
"export async function a() {",
Expand All @@ -352,7 +359,7 @@ run("compressClusters handles files with only findings, null providers, and miss
"}",
].join("\n"),
},
() => {
async () => {
const snapshot = buildSnapshot({
apiCalls: [
{
Expand Down Expand Up @@ -404,7 +411,7 @@ run("compressClusters handles files with only findings, null providers, and miss
],
});

const compressed = compressClusters(buildReviewClusters(scoreRepoIntelligence(snapshot)), snapshot);
const compressed = await compressClusters(buildReviewClusters(scoreRepoIntelligence(snapshot)), snapshot);
assert.ok(compressed.length >= 1);

const sharedCluster = compressed.find((cluster) => cluster.primarySummary.filePath.startsWith("src/shared/"));
Expand All @@ -425,8 +432,8 @@ run("compressClusters handles files with only findings, null providers, and miss
);
});

run("compressClusters uses snapshot.repoRoot instead of process.cwd() for snippet reads", () => {
withTempWorkspace(
run("compressClusters uses snapshot.repoRoot instead of process.cwd() for snippet reads", async () => {
await withTempWorkspace(
{
"src/chat/loop.ts": [
"export async function loop(items) {",
Expand All @@ -436,7 +443,7 @@ run("compressClusters uses snapshot.repoRoot instead of process.cwd() for snippe
"}",
].join("\n"),
},
(workspaceDir) => {
async (workspaceDir) => {
const snapshot = buildSnapshot({
repoRoot: workspaceDir,
apiCalls: [
Expand All @@ -456,7 +463,7 @@ run("compressClusters uses snapshot.repoRoot instead of process.cwd() for snippe
process.chdir(os.tmpdir());

try {
const compressed = compressClusters(buildReviewClusters(scoreRepoIntelligence(snapshot)), snapshot);
const compressed = await compressClusters(buildReviewClusters(scoreRepoIntelligence(snapshot)), snapshot);
const loopCluster = compressed.find((cluster) => cluster.primarySummary.filePath === "src/chat/loop.ts");
assert.ok(loopCluster);
assert.ok((loopCluster?.snippets.length ?? 0) >= 1);
Expand All @@ -466,3 +473,12 @@ run("compressClusters uses snapshot.repoRoot instead of process.cwd() for snippe
}
);
});

(async () => {
for (const test of pendingTests) {
await test();
}
})().catch((error) => {
console.error(error);
process.exit(1);
});
48 changes: 34 additions & 14 deletions src/intelligence/__tests__/export.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,24 @@ import { buildExportContext, formatAsJSON, formatAsMarkdown } from "../export";
import { scoreRepoIntelligence } from "../scorer";
import type { CompressedCluster, ExportedContext } from "../types";

function run(name: string, fn: () => void): void {
try {
fn();
console.log(`PASS ${name}`);
} catch (error) {
console.error(`FAIL ${name}`);
throw error;
}
const pendingTests: Array<() => Promise<void>> = [];

function run(name: string, fn: () => void | Promise<void>): void {
pendingTests.push(async () => {
try {
await fn();
console.log(`PASS ${name}`);
} catch (error) {
console.error(`FAIL ${name}`);
throw error;
}
});
}

function withTempWorkspace(files: Record<string, string>, fn: (workspaceDir: string) => void): void {
async function withTempWorkspace(
files: Record<string, string>,
fn: (workspaceDir: string) => void | Promise<void>
): Promise<void> {
const originalCwd = process.cwd();
const workspaceDir = fs.mkdtempSync(path.join(os.tmpdir(), "export-test-"));

Expand All @@ -31,15 +38,15 @@ function withTempWorkspace(files: Record<string, string>, fn: (workspaceDir: str
fs.writeFileSync(absolutePath, content, "utf8");
}
process.chdir(workspaceDir);
fn(workspaceDir);
await fn(workspaceDir);
} finally {
process.chdir(originalCwd);
fs.rmSync(workspaceDir, { recursive: true, force: true });
}
}

run("buildExportContext assembles meta, top files, key risks, and passes clusters through unchanged", () => {
withTempWorkspace(
run("buildExportContext assembles meta, top files, key risks, and passes clusters through unchanged", async () => {
await withTempWorkspace(
{
"src/chat/loop.ts": [
"export async function loop(items) {",
Expand All @@ -59,7 +66,7 @@ run("buildExportContext assembles meta, top files, key risks, and passes cluster
"}, 1000);",
].join("\n"),
},
(workspaceDir) => {
async (workspaceDir) => {
const snapshot = buildSnapshot({
apiCalls: [
{
Expand Down Expand Up @@ -112,7 +119,7 @@ run("buildExportContext assembles meta, top files, key risks, and passes cluster
});

const scored = scoreRepoIntelligence(snapshot);
const clusters = compressClusters(buildReviewClusters(scored), snapshot);
const clusters = await compressClusters(buildReviewClusters(scored), snapshot);
const context = buildExportContext(clusters, snapshot, scored, { generatorVersion: "0.1.0" });

assert.equal(context.meta.projectName, path.basename(workspaceDir));
Expand Down Expand Up @@ -200,7 +207,9 @@ run("formatAsMarkdown and formatAsJSON render stable onboarding output", () => {
},
],
keyRisks: ["Unbounded loop API calls", "Rate-limit risk"],
costLeaks: [],
},
providerSummary: [],
clusters,
};

Expand Down Expand Up @@ -598,7 +607,9 @@ run("formatAsMarkdown clarifies cluster-vs-primary providers and softens heurist
},
],
keyRisks: ["Potential missing caching on hot path"],
costLeaks: [],
},
providerSummary: [],
clusters: [
{
id: "cluster:src/chat/providers/xai.ts",
Expand Down Expand Up @@ -838,3 +849,12 @@ run("buildExportContext prefers non-generated non-tooling top files when runtime
assert.ok(!context.summary.topFiles.some((file) => file.filePath === "dashboard-dist/assets/index-abc123.js"));
assert.ok(!context.summary.topFiles.some((file) => file.filePath === "src/scanner/patterns/provider-gemini.ts"));
});

(async () => {
for (const test of pendingTests) {
await test();
}
})().catch((error) => {
console.error(error);
process.exit(1);
});
Loading
Loading