Skip to content

Commit 92428ab

Browse files
Fixes
1 parent ee2e572 commit 92428ab

10 files changed

Lines changed: 331 additions & 185 deletions

.vscode-test.mjs

Lines changed: 42 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,50 @@
1-
import { defineConfig } from '@vscode/test-cli';
2-
import { cpSync, mkdtempSync } from 'fs';
3-
import { tmpdir } from 'os';
4-
import { join, resolve } from 'path';
5-
import { fileURLToPath } from 'url';
6-
import { dirname } from 'path';
1+
import { defineConfig } from "@vscode/test-cli";
2+
import { cpSync, mkdtempSync } from "fs";
3+
import { tmpdir } from "os";
4+
import { join, resolve } from "path";
5+
import { fileURLToPath } from "url";
6+
import { dirname } from "path";
77

88
const __dirname = dirname(fileURLToPath(import.meta.url));
99

1010
// Copy fixtures to a temp directory so tests run in full isolation
11-
const testWorkspace = mkdtempSync(join(tmpdir(), 'commandtree-test-'));
12-
cpSync('./src/test/fixtures/workspace', testWorkspace, { recursive: true });
11+
const testWorkspace = mkdtempSync(join(tmpdir(), "commandtree-test-"));
12+
cpSync("./src/test/fixtures/workspace", testWorkspace, { recursive: true });
1313

14-
const userDataDir = resolve(__dirname, '.vscode-test/user-data');
14+
const userDataDir = resolve(__dirname, ".vscode-test/user-data");
1515

1616
export default defineConfig({
17-
tests: [{
18-
files: ['out/test/e2e/**/*.test.js', 'out/test/providers/**/*.test.js', 'out/test/unit/**/*.test.js'],
19-
version: 'stable',
20-
workspaceFolder: testWorkspace,
21-
extensionDevelopmentPath: './',
22-
srcDir: __dirname,
23-
mocha: {
24-
ui: 'tdd',
25-
timeout: 60000,
26-
color: true,
27-
slow: 10000
28-
},
29-
launchArgs: [
30-
'--user-data-dir', userDataDir
31-
]
32-
}],
33-
coverage: {
34-
includeAll: true,
35-
// @vscode/test-cli sets report.exclude.relativePath = false, which
36-
// makes test-exclude match against absolute paths. Patterns must
37-
// start with **/ so minimatch can match any prefix.
38-
include: ['**/out/**/*.js'],
39-
exclude: [
40-
'**/out/test/**',
41-
'**/out/semantic/summariser.js', // requires Copilot auth, not available in CI
42-
'**/out/semantic/summaryPipeline.js', // requires Copilot auth, not available in CI
43-
'**/out/semantic/vscodeAdapters.js', // requires Copilot auth, not available in CI
44-
'**/out/semantic/adapters.js', // type-only interfaces, no runtime behavior
45-
],
46-
reporter: ['text', 'lcov', 'html', 'json-summary'],
47-
output: './coverage'
48-
}
17+
tests: [
18+
{
19+
files: ["out/test/e2e/**/*.test.js", "out/test/providers/**/*.test.js", "out/test/unit/**/*.test.js"],
20+
version: "stable",
21+
workspaceFolder: testWorkspace,
22+
extensionDevelopmentPath: "./",
23+
srcDir: __dirname,
24+
mocha: {
25+
ui: "tdd",
26+
bail: true,
27+
timeout: 60000,
28+
color: true,
29+
slow: 10000,
30+
},
31+
launchArgs: ["--user-data-dir", userDataDir],
32+
},
33+
],
34+
coverage: {
35+
includeAll: true,
36+
// @vscode/test-cli sets report.exclude.relativePath = false, which
37+
// makes test-exclude match against absolute paths. Patterns must
38+
// start with **/ so minimatch can match any prefix.
39+
include: ["**/out/**/*.js"],
40+
exclude: [
41+
"**/out/test/**",
42+
"**/out/semantic/summariser.js", // requires Copilot auth, not available in CI
43+
"**/out/semantic/summaryPipeline.js", // requires Copilot auth, not available in CI
44+
"**/out/semantic/vscodeAdapters.js", // requires Copilot auth, not available in CI
45+
"**/out/semantic/adapters.js", // type-only interfaces, no runtime behavior
46+
],
47+
reporter: ["text", "lcov", "html", "json-summary"],
48+
output: "./coverage",
49+
},
4950
});

Makefile

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,10 @@ endif
3434
COVERAGE_THRESHOLDS_FILE := coverage-thresholds.json
3535

3636
UNAME := $(shell uname 2>/dev/null)
37-
VSCODE_TEST_CMD = npx vscode-test --coverage
38-
VSCODE_TEST_EXCLUDE_CMD = npx vscode-test --coverage --grep @exclude-ci --invert
37+
VSCODE_TEST_EXCLUDE_CMD = npx vscode-test --coverage --bail --grep @exclude-ci --invert
3938
ifeq ($(UNAME),Linux)
40-
VSCODE_TEST = xvfb-run -a $(VSCODE_TEST_CMD)
4139
VSCODE_TEST_EXCLUDE = xvfb-run -a $(VSCODE_TEST_EXCLUDE_CMD)
4240
else
43-
VSCODE_TEST = $(VSCODE_TEST_CMD)
4441
VSCODE_TEST_EXCLUDE = $(VSCODE_TEST_EXCLUDE_CMD)
4542
endif
4643

@@ -55,9 +52,9 @@ build:
5552

5653
## test: Fail-fast tests + coverage + threshold enforcement ([TEST-RULES]).
5754
test: build
58-
@echo "==> Testing (fail-fast + coverage + threshold)..."
55+
@echo "==> Testing (excluding @exclude-ci, fail-fast + coverage + threshold)..."
5956
npm run test:unit
60-
$(VSCODE_TEST)
57+
$(VSCODE_TEST_EXCLUDE)
6158
$(MAKE) _coverage_check
6259

6360
## lint: Run all linters/analyzers (read-only). Does NOT format.
@@ -97,12 +94,8 @@ _coverage_check:
9794
package: build
9895
npx vsce package
9996

100-
## test-exclude-ci: Run tests EXCLUDING those tagged @exclude-ci (fail-fast + coverage + threshold)
101-
test-exclude-ci: build
102-
@echo "==> Testing (excluding @exclude-ci, fail-fast + coverage + threshold)..."
103-
npm run test:unit
104-
$(VSCODE_TEST_EXCLUDE)
105-
$(MAKE) _coverage_check
97+
## test-exclude-ci: Alias for `test`; kept for existing CI workflows.
98+
test-exclude-ci: test
10699

107100
## reinstall: Full clean rebuild — uninstall extension, wipe artifacts + VSIX + node_modules, reinstall deps, package, install
108101
reinstall:
@@ -150,5 +143,5 @@ help:
150143
@echo ""
151144
@echo "Repo-specific:"
152145
@echo " package - Build VSIX package"
153-
@echo " test-exclude-ci - Run tests excluding those tagged @exclude-ci"
146+
@echo " test-exclude-ci - Alias for test"
154147
@echo " reinstall - Full clean: uninstall, wipe everything, rebuild, package, install VSIX"

coverage-thresholds.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
{
22
"_agent_pmo": "f481f8d",
33
"_doc": "Single source of truth for code coverage thresholds. See REPO-STANDARDS-SPEC [COVERAGE-THRESHOLDS-JSON]. Enforced by tools/check-coverage.mjs via `make test`. Ratchet UP only. Extended format (per-metric) overrides the spec's single default_threshold to enforce both line AND branch coverage per [COVERAGE-THRESHOLDS] (VS Code extension: 80% line / 70% branch — measured values here are well above).",
4-
"lines": 91.29,
5-
"functions": 93.16,
6-
"branches": 86.59,
7-
"statements": 91.29
4+
"lines": 92.11,
5+
"functions": 93.87,
6+
"branches": 87.33,
7+
"statements": 92.11
88
}

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,7 @@
394394
"format:check": "prettier --check \"src/**/*.ts\"",
395395
"pretest": "npm run compile",
396396
"test": "npm run test:unit && npm run test:e2e",
397-
"test:unit": "mocha out/test/unit/**/*.test.js",
397+
"test:unit": "mocha --bail out/test/unit/**/*.test.js",
398398
"test:e2e": "vscode-test",
399399
"test:coverage": "vscode-test --coverage",
400400
"coverage:check": "node tools/check-coverage.mjs",

src/extension.ts

Lines changed: 20 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,18 @@ import { TaskRunner } from "./runners/TaskRunner";
1010
import { QuickTasksProvider } from "./QuickTasksProvider";
1111
import { logger } from "./utils/logger";
1212
import { initDb, disposeDb } from "./db/lifecycle";
13-
import { summariseAllTasks, registerAllCommands } from "./semantic/summaryPipeline";
14-
import { createVSCodeFileSystem } from "./semantic/vscodeAdapters";
1513
import { forceSelectModel } from "./semantic/summariser";
1614
import { syncTagsFromConfig } from "./tags/tagSync";
1715
import { setupFileWatchers } from "./watchers";
1816
import { PrivateTaskDecorationProvider } from "./tree/PrivateTaskDecorationProvider";
1917
import { appState } from "./state";
18+
import {
19+
initAiSummaries,
20+
registerDiscoveredCommands,
21+
runSummarisation,
22+
syncAndSummarise,
23+
} from "./summaryOrchestration";
24+
import type { SummaryDeps } from "./summaryOrchestration";
2025

2126
const MAKE_EXECUTABLE_COMMAND = "commandtree.makeExecutable";
2227
const EXECUTE_PERMISSION_BITS = 0o111;
@@ -48,6 +53,14 @@ function getTaskRunner(): TaskRunner {
4853
return appState.taskRunner;
4954
}
5055

56+
function getSummaryDeps(workspaceRoot: string): SummaryDeps {
57+
return {
58+
workspaceRoot,
59+
treeProvider: getTreeProvider(),
60+
quickTasksProvider: getQuickTasksProvider(),
61+
};
62+
}
63+
5164
export async function activate(context: vscode.ExtensionContext): Promise<ExtensionExports | undefined> {
5265
const workspaceRoot = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath;
5366
logger.info("Extension activating", { workspaceRoot });
@@ -76,7 +89,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<Extens
7689
function runBackgroundStartup(workspaceRoot: string): void {
7790
initialDiscovery(workspaceRoot)
7891
.then(() => {
79-
initAiSummaries(workspaceRoot);
92+
initAiSummaries(getSummaryDeps(workspaceRoot));
8093
})
8194
.catch((e: unknown) => {
8295
logger.error("Initial discovery failed", {
@@ -96,7 +109,7 @@ function setupWatchers(context: vscode.ExtensionContext, workspaceRoot: string):
96109
setupFileWatchers({
97110
context,
98111
onTaskFileChange: () => {
99-
syncAndSummarise(workspaceRoot).catch((e: unknown) => {
112+
syncAndSummarise(getSummaryDeps(workspaceRoot)).catch((e: unknown) => {
100113
logger.error("Sync failed", {
101114
error: e instanceof Error ? e.message : "Unknown",
102115
});
@@ -115,7 +128,7 @@ function setupWatchers(context: vscode.ExtensionContext, workspaceRoot: string):
115128
async function initialDiscovery(workspaceRoot: string): Promise<void> {
116129
await syncQuickTasks();
117130
logger.info("syncQuickTasks complete", { taskCount: getTreeProvider().getAllTasks().length });
118-
await registerDiscoveredCommands(workspaceRoot);
131+
await registerDiscoveredCommands(getSummaryDeps(workspaceRoot));
119132
await syncTagsFromJson(workspaceRoot);
120133
}
121134

@@ -192,7 +205,7 @@ function registerFilterCommands(context: vscode.ExtensionContext): void {
192205
vscode.commands.registerCommand("commandtree.generateSummaries", async () => {
193206
const workspaceRoot = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath;
194207
if (workspaceRoot !== undefined) {
195-
await runSummarisation(workspaceRoot);
208+
await runSummarisation(getSummaryDeps(workspaceRoot));
196209
}
197210
}),
198211
vscode.commands.registerCommand("commandtree.selectModel", async () => {
@@ -201,7 +214,7 @@ function registerFilterCommands(context: vscode.ExtensionContext): void {
201214
vscode.window.showInformationMessage(`CommandTree: AI model set to ${result.value}`);
202215
const workspaceRoot = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath;
203216
if (workspaceRoot !== undefined) {
204-
await runSummarisation(workspaceRoot);
217+
await runSummarisation(getSummaryDeps(workspaceRoot));
205218
}
206219
} else {
207220
vscode.window.showWarningMessage(`CommandTree: ${result.error}`);
@@ -395,72 +408,6 @@ async function pickOrCreateTag(existingTags: string[], taskLabel: string): Promi
395408
});
396409
}
397410

398-
async function registerDiscoveredCommands(workspaceRoot: string): Promise<void> {
399-
const tasks = getTreeProvider().getAllTasks();
400-
if (tasks.length === 0) {
401-
return;
402-
}
403-
const result = await registerAllCommands({
404-
tasks,
405-
workspaceRoot,
406-
fs: createVSCodeFileSystem(),
407-
});
408-
if (!result.ok) {
409-
logger.warn("Command registration failed", { error: result.error });
410-
} else {
411-
logger.info("Commands registered in DB", { count: result.value });
412-
}
413-
}
414-
415-
function initAiSummaries(workspaceRoot: string): void {
416-
const aiConfig = vscode.workspace.getConfiguration("commandtree").get<boolean>("enableAiSummaries");
417-
if (aiConfig === false) {
418-
return;
419-
}
420-
vscode.commands.executeCommand("setContext", "commandtree.aiSummariesEnabled", true);
421-
runSummarisation(workspaceRoot).catch((e: unknown) => {
422-
logger.error("AI summarisation failed", {
423-
error: e instanceof Error ? e.message : "Unknown",
424-
});
425-
});
426-
}
427-
428-
async function runSummarisation(workspaceRoot: string): Promise<void> {
429-
const tasks = getTreeProvider().getAllTasks();
430-
logger.info("[SUMMARY] Starting", { taskCount: tasks.length });
431-
if (tasks.length === 0) {
432-
logger.warn("[SUMMARY] No tasks to summarise");
433-
return;
434-
}
435-
const summaryResult = await summariseAllTasks({
436-
tasks,
437-
workspaceRoot,
438-
fs: createVSCodeFileSystem(),
439-
onProgress: (done, total, label) => {
440-
logger.info(`[SUMMARY] ${label}`, { done, total });
441-
},
442-
});
443-
if (!summaryResult.ok) {
444-
logger.error("Summary pipeline failed", { error: summaryResult.error });
445-
vscode.window.showErrorMessage(`CommandTree: Summary failed — ${summaryResult.error}`);
446-
return;
447-
}
448-
if (summaryResult.value > 0) {
449-
await getTreeProvider().refresh();
450-
getQuickTasksProvider().updateTasks(getTreeProvider().getAllTasks());
451-
}
452-
vscode.window.showInformationMessage(`CommandTree: Summarised ${summaryResult.value} commands`);
453-
}
454-
455-
async function syncAndSummarise(workspaceRoot: string): Promise<void> {
456-
await syncQuickTasks();
457-
await registerDiscoveredCommands(workspaceRoot);
458-
const aiConfig = vscode.workspace.getConfiguration("commandtree").get<boolean>("enableAiSummaries");
459-
if (aiConfig !== false) {
460-
await runSummarisation(workspaceRoot);
461-
}
462-
}
463-
464411
function updateFilterContext(): void {
465412
vscode.commands.executeCommand("setContext", "commandtree.hasFilter", getTreeProvider().hasFilter());
466413
}

0 commit comments

Comments
 (0)