diff --git a/README.md b/README.md
index 28e4369..1fe2efb 100644
--- a/README.md
+++ b/README.md
@@ -28,6 +28,10 @@ Or search for **Patchloom** in the Extensions view (`Ctrl+Shift+X` / `Cmd+Shift+
1. Install the [Patchloom CLI](https://github.com/patchloom/patchloom/releases) (or run **Patchloom: Install Patchloom** from the command palette)
2. Open a project and run **Patchloom: Setup Workspace**
+
+
+
+
The extension finds the CLI automatically. If it's not on `PATH`, point `patchloom.path` to it in settings.
---
@@ -52,7 +56,17 @@ Run `Patchloom: Setup Workspace` to walk through everything your project needs:
### Status bar
-The status bar shows binary readiness and CLI version at a glance. Click it to see full diagnostics.
+The status bar shows MCP and binary readiness at a glance:
+
+- **$(plug) Patchloom MCP** when the MCP server is configured
+- **$(check) Patchloom** when the binary is ready but MCP is not yet set up
+- **$(warning) Patchloom** when the binary is missing or needs an upgrade
+
+Click it to see full diagnostics, including per-editor MCP configuration status (VS Code, Cursor, Windsurf).
+
+### Verify MCP Server
+
+`Patchloom: Verify MCP Server` spawns `patchloom mcp-server`, sends a JSON-RPC `initialize` handshake, and confirms the server responds correctly. Reports the server name and version on success, or a diagnostic error on failure.
### Quick actions
@@ -92,6 +106,7 @@ The extension detects outdated CLI builds and warns with upgrade guidance. It re
| `Patchloom: Batch Apply` | Open a JSON batch plan and execute all operations atomically |
| `Patchloom: Show Output` | Open the Patchloom output channel for CLI logs and diagnostics |
| `Patchloom: Show Status` | Display binary readiness, version, compatibility, and workspace state |
+| `Patchloom: Verify MCP Server` | Spawn the MCP server and verify it responds to a JSON-RPC initialize request |
| `Patchloom: Install Patchloom` | Download and install the Patchloom CLI with checksum verification |
| `Patchloom: Update Patchloom` | Update a managed Patchloom install to the latest release |
| `Patchloom: Reinstall Patchloom` | Re-download and reinstall the managed Patchloom binary |
diff --git a/images/setup-workspace-demo.gif b/images/setup-workspace-demo.gif
new file mode 100644
index 0000000..d63ad5f
Binary files /dev/null and b/images/setup-workspace-demo.gif differ
diff --git a/package.json b/package.json
index d5b59f9..97bc8ef 100644
--- a/package.json
+++ b/package.json
@@ -51,7 +51,8 @@
"onCommand:patchloom.showOutput",
"onCommand:patchloom.installBinary",
"onCommand:patchloom.updateBinary",
- "onCommand:patchloom.reinstallBinary"
+ "onCommand:patchloom.reinstallBinary",
+ "onCommand:patchloom.verifyMcp"
],
"contributes": {
"commands": [
@@ -114,6 +115,11 @@
"command": "patchloom.reinstallBinary",
"title": "Reinstall Patchloom",
"category": "Patchloom"
+ },
+ {
+ "command": "patchloom.verifyMcp",
+ "title": "Verify MCP Server",
+ "category": "Patchloom"
}
],
"configuration": {
diff --git a/src/commands/setupWorkspace.ts b/src/commands/setupWorkspace.ts
index 7663362..9d79fd1 100644
--- a/src/commands/setupWorkspace.ts
+++ b/src/commands/setupWorkspace.ts
@@ -37,32 +37,34 @@ export async function setupWorkspace(): Promise {
if (readiness.hasAgentsFile === false) {
const choice = await vscode.window.showInformationMessage(
- "AGENTS.md is missing for this workspace. Create it now from patchloom agent-rules?",
- "Initialize Project"
+ "Step 1/2: AGENTS.md is missing for this workspace. Create it now from patchloom agent-rules?",
+ "Initialize Project",
+ "Skip"
);
if (choice === "Initialize Project") {
await vscode.commands.executeCommand("patchloom.initializeProject");
}
- return;
}
if (readiness.hasMcpConfig === false) {
const choice = await vscode.window.showInformationMessage(
- "Patchloom MCP config is missing. Configure supported editors now?",
- "Configure MCP"
+ `${readiness.hasAgentsFile === false ? "Step 2/2: " : ""}Patchloom MCP config is missing. Configure supported editors now?`,
+ "Configure MCP",
+ "Skip"
);
if (choice === "Configure MCP") {
await vscode.commands.executeCommand("patchloom.configureMcp");
}
- return;
}
- const environment = describeWorkspaceEnvironment(vscode.env.remoteName);
- const environmentSuffix = environment.note ? ` ${environment.note}` : "";
- const workspaceTarget = readiness.workspaceName ? ` for ${readiness.workspaceName}` : "";
- await vscode.window.showInformationMessage(
- `Patchloom workspace setup looks good${workspaceTarget}. Binary, AGENTS.md, and MCP config are already in place.${environmentSuffix}`
- );
+ if (readiness.hasAgentsFile !== false && readiness.hasMcpConfig !== false) {
+ const environment = describeWorkspaceEnvironment(vscode.env.remoteName);
+ const environmentSuffix = environment.note ? ` ${environment.note}` : "";
+ const workspaceTarget = readiness.workspaceName ? ` for ${readiness.workspaceName}` : "";
+ await vscode.window.showInformationMessage(
+ `Patchloom workspace setup looks good${workspaceTarget}. Binary, AGENTS.md, and MCP config are already in place.${environmentSuffix}`
+ );
+ }
}
export async function openPatchloomSettings(): Promise {
diff --git a/src/commands/verifyMcp.ts b/src/commands/verifyMcp.ts
new file mode 100644
index 0000000..d734ded
--- /dev/null
+++ b/src/commands/verifyMcp.ts
@@ -0,0 +1,182 @@
+import { spawn } from "node:child_process";
+import { patchloomNeedsUpgrade, resolvePatchloomStatus } from "../binary/patchloom.js";
+import { getPatchloomLog } from "../logging/outputChannel.js";
+
+export interface VerifyMcpInputs {
+ readonly binaryPath: string;
+ readonly spawnProcess?: typeof spawnMcpServer;
+}
+
+export interface VerifyMcpResult {
+ readonly ok: boolean;
+ readonly serverName?: string;
+ readonly serverVersion?: string;
+ readonly message: string;
+}
+
+export async function verifyMcp(): Promise {
+ const vscode = await import("vscode");
+ const status = await resolvePatchloomStatus();
+ if (!status.ready || !status.binaryPath) {
+ await vscode.window.showWarningMessage(status.message);
+ return;
+ }
+
+ if (patchloomNeedsUpgrade(status)) {
+ await vscode.window.showWarningMessage(
+ `${status.compatibilityMessage}\n\nUpgrade Patchloom before verifying the MCP server.`
+ );
+ return;
+ }
+
+ const result = await vscode.window.withProgress(
+ {
+ location: vscode.ProgressLocation.Notification,
+ title: "Patchloom",
+ cancellable: false
+ },
+ async (progress) => {
+ progress.report({ message: "Verifying MCP server..." });
+ return verifyMcpServer({ binaryPath: status.binaryPath! });
+ }
+ );
+
+ const log = getPatchloomLog();
+ log?.log(`MCP verify: ${result.message}`);
+
+ if (result.ok) {
+ await vscode.window.showInformationMessage(result.message);
+ } else {
+ await vscode.window.showErrorMessage(result.message);
+ }
+}
+
+export async function verifyMcpServer(inputs: VerifyMcpInputs): Promise {
+ const spawnFn = inputs.spawnProcess ?? spawnMcpServer;
+ try {
+ return await spawnFn(inputs.binaryPath);
+ } catch (error) {
+ return {
+ ok: false,
+ message: `MCP server failed to start: ${error instanceof Error ? error.message : String(error)}`
+ };
+ }
+}
+
+function spawnMcpServer(binaryPath: string): Promise {
+ return new Promise((resolve) => {
+ const child = spawn(binaryPath, ["mcp-server"], {
+ stdio: ["pipe", "pipe", "pipe"],
+ timeout: 10_000,
+ windowsHide: true
+ });
+
+ let stdout = "";
+ let stderr = "";
+ let resolved = false;
+
+ const finish = (result: VerifyMcpResult): void => {
+ if (resolved) {
+ return;
+ }
+ resolved = true;
+ child.kill();
+ resolve(result);
+ };
+
+ const timer = setTimeout(() => {
+ finish({
+ ok: false,
+ message: "MCP server did not respond within 10 seconds."
+ });
+ }, 10_000);
+
+ child.stdout.on("data", (chunk: Buffer) => {
+ stdout += chunk.toString();
+ const result = parseInitializeResponse(stdout);
+ if (result) {
+ clearTimeout(timer);
+ finish(result);
+ }
+ });
+
+ child.stderr.on("data", (chunk: Buffer) => {
+ stderr += chunk.toString();
+ });
+
+ child.on("error", (error) => {
+ clearTimeout(timer);
+ finish({
+ ok: false,
+ message: `MCP server process error: ${error.message}`
+ });
+ });
+
+ child.on("close", (code) => {
+ clearTimeout(timer);
+ if (!resolved) {
+ finish({
+ ok: false,
+ message: `MCP server exited with code ${code ?? "unknown"}. ${stderr.trim()}`
+ });
+ }
+ });
+
+ const request = JSON.stringify({
+ jsonrpc: "2.0",
+ id: 1,
+ method: "initialize",
+ params: {
+ protocolVersion: "2024-11-05",
+ capabilities: {},
+ clientInfo: { name: "patchloom-vscode-verify", version: "1.0.0" }
+ }
+ });
+ child.stdin.write(request + "\n");
+ child.stdin.end();
+ });
+}
+
+export function parseInitializeResponse(data: string): VerifyMcpResult | undefined {
+ for (const line of data.split("\n")) {
+ const trimmed = line.trim();
+ if (!trimmed) {
+ continue;
+ }
+ let parsed: unknown;
+ try {
+ parsed = JSON.parse(trimmed);
+ } catch {
+ continue;
+ }
+ if (!parsed || typeof parsed !== "object") {
+ continue;
+ }
+ const response = parsed as Record;
+ if (response.jsonrpc !== "2.0") {
+ continue;
+ }
+
+ if (response.error) {
+ const error = response.error as Record;
+ return {
+ ok: false,
+ message: `MCP server returned error: ${error.message ?? JSON.stringify(error)}`
+ };
+ }
+
+ if (response.result && typeof response.result === "object") {
+ const result = response.result as Record;
+ const serverInfo = result.serverInfo as Record | undefined;
+ return {
+ ok: true,
+ serverName: serverInfo?.name as string | undefined,
+ serverVersion: serverInfo?.version as string | undefined,
+ message: serverInfo
+ ? `MCP server verified: ${serverInfo.name} ${serverInfo.version ?? ""}`.trim()
+ : "MCP server responded successfully."
+ };
+ }
+ }
+ return undefined;
+}
diff --git a/src/extension.ts b/src/extension.ts
index 22be6d7..ea395ac 100644
--- a/src/extension.ts
+++ b/src/extension.ts
@@ -6,6 +6,7 @@ import { installPatchloom, updatePatchloom, reinstallPatchloom } from "./command
import { runQuickAction } from "./commands/quickActions.js";
import { setupWorkspace, openPatchloomReleases, openPatchloomSettings } from "./commands/setupWorkspace.js";
import { showStatus } from "./commands/showStatus.js";
+import { verifyMcp } from "./commands/verifyMcp.js";
import { setManagedInstallRoot } from "./install/managed.js";
import { createPatchloomLog, getPatchloomLog, setPatchloomLog } from "./logging/outputChannel.js";
import { disposeStatusBar, refreshStatusBar } from "./status/statusBar.js";
@@ -29,6 +30,7 @@ export function activate(context: vscode.ExtensionContext): void {
vscode.commands.registerCommand("patchloom.installBinary", installPatchloom),
vscode.commands.registerCommand("patchloom.updateBinary", updatePatchloom),
vscode.commands.registerCommand("patchloom.reinstallBinary", reinstallPatchloom),
+ vscode.commands.registerCommand("patchloom.verifyMcp", verifyMcp),
new vscode.Disposable(disposeStatusBar),
vscode.workspace.onDidChangeConfiguration((event) => {
if (event.affectsConfiguration("patchloom")) {
diff --git a/src/status/details.ts b/src/status/details.ts
index cdca296..eb846b6 100644
--- a/src/status/details.ts
+++ b/src/status/details.ts
@@ -4,6 +4,7 @@ import {
patchloomNeedsUpgrade,
PatchloomStatus
} from "../binary/patchloom.js";
+import type { McpTargetStatus } from "../mcp/config.js";
import { WorkspaceReadiness } from "../workspace/readiness.js";
export interface SetupAction {
@@ -38,9 +39,7 @@ export function buildStatusDetails(status: PatchloomStatus, workspaceReadiness?:
workspaceReadiness?.hasAgentsFile === undefined
? undefined
: `AGENTS.md: ${workspaceReadiness.hasAgentsFile ? "present" : "missing"}`,
- workspaceReadiness?.hasMcpConfig === undefined
- ? undefined
- : `MCP config: ${workspaceReadiness.hasMcpConfig ? "present" : "missing"}`
+ ...formatMcpTargetDetails(workspaceReadiness?.mcpTargets)
].filter((line): line is string => Boolean(line)).join("\n");
}
@@ -80,4 +79,15 @@ export function preferredStatusAction(status: PatchloomStatus, workspaceReadines
}
return undefined;
+}
+
+function formatMcpTargetDetails(targets?: readonly McpTargetStatus[]): string[] {
+ if (!targets || targets.length === 0) {
+ return ["MCP config: no targets available"];
+ }
+
+ return targets.map((target) => {
+ const icon = target.configured ? "\u2713" : "\u2717";
+ return `MCP ${target.label}: ${icon} ${target.configured ? "configured" : "not configured"}`;
+ });
}
\ No newline at end of file
diff --git a/src/status/statusBar.ts b/src/status/statusBar.ts
index 73e459f..fbe0533 100644
--- a/src/status/statusBar.ts
+++ b/src/status/statusBar.ts
@@ -21,9 +21,11 @@ export async function refreshStatusBar(): Promise {
const workspaceReadiness = await inspectWorkspaceReadiness();
const action = preferredStatusAction(status, workspaceReadiness);
- statusBarItem.text = status.ready && !patchloomNeedsUpgrade(status)
- ? "$(check) Patchloom"
- : "$(warning) Patchloom";
+ statusBarItem.text = !status.ready || patchloomNeedsUpgrade(status)
+ ? "$(warning) Patchloom"
+ : workspaceReadiness?.hasMcpConfig
+ ? "$(plug) Patchloom MCP"
+ : "$(check) Patchloom";
statusBarItem.command = action?.command ?? "patchloom.showStatus";
statusBarItem.tooltip = buildStatusDetails(status, workspaceReadiness);
statusBarItem.show();
diff --git a/src/workspace/readiness.ts b/src/workspace/readiness.ts
index bf59272..821d6d6 100644
--- a/src/workspace/readiness.ts
+++ b/src/workspace/readiness.ts
@@ -1,5 +1,5 @@
import type * as VSCode from "vscode";
-import { inspectMcpTargets } from "../mcp/config.js";
+import { inspectMcpTargets, type McpTargetStatus } from "../mcp/config.js";
export type WorkspaceEnvironmentSupport = "supported" | "limited" | "unverified";
@@ -16,6 +16,7 @@ export interface WorkspaceReadiness {
readonly hasWorkspace: boolean;
readonly hasAgentsFile?: boolean;
readonly hasMcpConfig?: boolean;
+ readonly mcpTargets?: readonly McpTargetStatus[];
readonly workspaceCount: number;
readonly environmentLabel: string;
readonly environmentSupport: WorkspaceEnvironmentSupport;
@@ -40,11 +41,13 @@ export async function inspectWorkspaceReadiness(options: WorkspaceReadinessOptio
});
const workspaceCount = vscode.workspace.workspaceFolders?.length ?? 0;
if (!folder) {
+ const targets = await inspectMcpTargets({
+ includeUserTarget: environment.supportsUserMcpConfig
+ });
return {
hasWorkspace: false,
- hasMcpConfig: (await inspectMcpTargets({
- includeUserTarget: environment.supportsUserMcpConfig
- })).some((target) => target.configured),
+ hasMcpConfig: targets.some((target) => target.configured),
+ mcpTargets: targets,
workspaceCount,
environmentLabel: environment.label,
environmentSupport: environment.support,
@@ -52,7 +55,7 @@ export async function inspectWorkspaceReadiness(options: WorkspaceReadinessOptio
};
}
- const mcpTargets = await inspectMcpTargets({
+ const targets = await inspectMcpTargets({
workspaceFolderPath: folder.uri.fsPath,
includeUserTarget: environment.supportsUserMcpConfig
});
@@ -61,7 +64,8 @@ export async function inspectWorkspaceReadiness(options: WorkspaceReadinessOptio
workspaceName: folder.name,
hasWorkspace: true,
hasAgentsFile: await fileExists(vscode.Uri.joinPath(folder.uri, "AGENTS.md")),
- hasMcpConfig: mcpTargets.some((target) => target.configured),
+ hasMcpConfig: targets.some((target) => target.configured),
+ mcpTargets: targets,
workspaceCount,
environmentLabel: environment.label,
environmentSupport: environment.support,
diff --git a/test/suite/index.ts b/test/suite/index.ts
index 2dc409d..c98473a 100644
--- a/test/suite/index.ts
+++ b/test/suite/index.ts
@@ -13,7 +13,8 @@ const EXPECTED_COMMANDS = [
"patchloom.showStatus",
"patchloom.installBinary",
"patchloom.updateBinary",
- "patchloom.reinstallBinary"
+ "patchloom.reinstallBinary",
+ "patchloom.verifyMcp"
];
export async function run(): Promise {
diff --git a/test/unit/verifyMcp.test.ts b/test/unit/verifyMcp.test.ts
new file mode 100644
index 0000000..7123541
--- /dev/null
+++ b/test/unit/verifyMcp.test.ts
@@ -0,0 +1,256 @@
+import assert from "node:assert/strict";
+import test from "node:test";
+import { parseInitializeResponse, verifyMcpServer } from "../../src/commands/verifyMcp.js";
+import { buildStatusDetails } from "../../src/status/details.js";
+
+// --- parseInitializeResponse ---
+
+test("parseInitializeResponse extracts server info from valid response", () => {
+ const data = JSON.stringify({
+ jsonrpc: "2.0",
+ id: 1,
+ result: {
+ protocolVersion: "2024-11-05",
+ serverInfo: { name: "patchloom", version: "0.2.0" },
+ capabilities: { tools: {} }
+ }
+ });
+
+ const result = parseInitializeResponse(data);
+ assert.ok(result);
+ assert.equal(result.ok, true);
+ assert.equal(result.serverName, "patchloom");
+ assert.equal(result.serverVersion, "0.2.0");
+ assert.match(result.message, /patchloom/);
+ assert.match(result.message, /0\.2\.0/);
+});
+
+test("parseInitializeResponse handles response without serverInfo", () => {
+ const data = JSON.stringify({
+ jsonrpc: "2.0",
+ id: 1,
+ result: {
+ protocolVersion: "2024-11-05",
+ capabilities: {}
+ }
+ });
+
+ const result = parseInitializeResponse(data);
+ assert.ok(result);
+ assert.equal(result.ok, true);
+ assert.equal(result.serverName, undefined);
+ assert.match(result.message, /responded successfully/);
+});
+
+test("parseInitializeResponse detects JSON-RPC error response", () => {
+ const data = JSON.stringify({
+ jsonrpc: "2.0",
+ id: 1,
+ error: { code: -32600, message: "Invalid Request" }
+ });
+
+ const result = parseInitializeResponse(data);
+ assert.ok(result);
+ assert.equal(result.ok, false);
+ assert.match(result.message, /Invalid Request/);
+});
+
+test("parseInitializeResponse returns undefined for empty string", () => {
+ assert.equal(parseInitializeResponse(""), undefined);
+});
+
+test("parseInitializeResponse returns undefined for non-JSON lines", () => {
+ assert.equal(parseInitializeResponse("not json\nalso not json\n"), undefined);
+});
+
+test("parseInitializeResponse skips non-jsonrpc lines", () => {
+ const data = '{"status":"ok"}\n' + JSON.stringify({
+ jsonrpc: "2.0",
+ id: 1,
+ result: { protocolVersion: "2024-11-05", capabilities: {} }
+ });
+
+ const result = parseInitializeResponse(data);
+ assert.ok(result);
+ assert.equal(result.ok, true);
+});
+
+test("parseInitializeResponse handles multi-line output with blank lines", () => {
+ const data = "\n\n" + JSON.stringify({
+ jsonrpc: "2.0",
+ id: 1,
+ result: {
+ protocolVersion: "2024-11-05",
+ serverInfo: { name: "patchloom", version: "0.1.0" },
+ capabilities: {}
+ }
+ }) + "\n";
+
+ const result = parseInitializeResponse(data);
+ assert.ok(result);
+ assert.equal(result.ok, true);
+ assert.equal(result.serverName, "patchloom");
+});
+
+// --- verifyMcpServer with injected spawnProcess ---
+
+test("verifyMcpServer returns success from injected spawn", async () => {
+ const result = await verifyMcpServer({
+ binaryPath: "/usr/local/bin/patchloom",
+ spawnProcess: async () => ({
+ ok: true,
+ serverName: "patchloom",
+ serverVersion: "0.2.0",
+ message: "MCP server verified: patchloom 0.2.0"
+ })
+ });
+
+ assert.equal(result.ok, true);
+ assert.equal(result.serverName, "patchloom");
+});
+
+test("verifyMcpServer returns failure from injected spawn", async () => {
+ const result = await verifyMcpServer({
+ binaryPath: "/usr/local/bin/patchloom",
+ spawnProcess: async () => ({
+ ok: false,
+ message: "MCP server exited with code 1. binary not found"
+ })
+ });
+
+ assert.equal(result.ok, false);
+ assert.match(result.message, /exited with code 1/);
+});
+
+test("verifyMcpServer catches thrown errors from spawn", async () => {
+ const result = await verifyMcpServer({
+ binaryPath: "/nonexistent/patchloom",
+ spawnProcess: async () => {
+ throw new Error("ENOENT: spawn failed");
+ }
+ });
+
+ assert.equal(result.ok, false);
+ assert.match(result.message, /ENOENT/);
+});
+
+test("verifyMcpServer catches non-Error thrown values", async () => {
+ const result = await verifyMcpServer({
+ binaryPath: "/nonexistent/patchloom",
+ spawnProcess: async () => {
+ throw "string error";
+ }
+ });
+
+ assert.equal(result.ok, false);
+ assert.match(result.message, /string error/);
+});
+
+// --- buildStatusDetails with per-editor MCP targets ---
+
+test("buildStatusDetails shows per-editor MCP breakdown", () => {
+ const details = buildStatusDetails(
+ {
+ ready: true,
+ source: "path",
+ message: "Using Patchloom from PATH.",
+ binaryPath: "/usr/local/bin/patchloom",
+ version: "patchloom 0.1.0"
+ },
+ {
+ hasWorkspace: true,
+ workspaceName: "demo",
+ hasAgentsFile: true,
+ hasMcpConfig: true,
+ mcpTargets: [
+ { kind: "vscode-workspace", label: "VS Code workspace", filePath: "/demo/.vscode/mcp.json", exists: true, configured: true },
+ { kind: "cursor-workspace", label: "Cursor workspace", filePath: "/demo/.cursor/mcp.json", exists: false, configured: false },
+ { kind: "windsurf-user", label: "Windsurf user", filePath: "/home/.codeium/windsurf/mcp_config.json", exists: false, configured: false }
+ ],
+ workspaceCount: 1,
+ environmentLabel: "Local",
+ environmentSupport: "supported"
+ }
+ );
+
+ assert.match(details, /VS Code workspace.*configured/);
+ assert.match(details, /Cursor workspace.*not configured/);
+ assert.match(details, /Windsurf user.*not configured/);
+});
+
+test("buildStatusDetails shows fallback when mcpTargets is undefined", () => {
+ const details = buildStatusDetails(
+ {
+ ready: true,
+ source: "path",
+ message: "Using Patchloom from PATH.",
+ binaryPath: "/usr/local/bin/patchloom",
+ version: "patchloom 0.1.0"
+ },
+ {
+ hasWorkspace: true,
+ workspaceName: "demo",
+ hasAgentsFile: true,
+ hasMcpConfig: true,
+ workspaceCount: 1,
+ environmentLabel: "Local",
+ environmentSupport: "supported"
+ }
+ );
+
+ assert.match(details, /MCP config: no targets available/);
+});
+
+test("buildStatusDetails shows checkmark for configured targets", () => {
+ const details = buildStatusDetails(
+ {
+ ready: true,
+ source: "path",
+ message: "Using Patchloom from PATH.",
+ binaryPath: "/usr/local/bin/patchloom",
+ version: "patchloom 0.1.0"
+ },
+ {
+ hasWorkspace: true,
+ workspaceName: "demo",
+ hasAgentsFile: true,
+ hasMcpConfig: true,
+ mcpTargets: [
+ { kind: "vscode-workspace", label: "VS Code workspace", filePath: "/demo/.vscode/mcp.json", exists: true, configured: true }
+ ],
+ workspaceCount: 1,
+ environmentLabel: "Local",
+ environmentSupport: "supported"
+ }
+ );
+
+ assert.match(details, /\u2713/);
+ assert.match(details, /configured/);
+});
+
+test("buildStatusDetails shows X for unconfigured targets", () => {
+ const details = buildStatusDetails(
+ {
+ ready: true,
+ source: "path",
+ message: "Using Patchloom from PATH.",
+ binaryPath: "/usr/local/bin/patchloom",
+ version: "patchloom 0.1.0"
+ },
+ {
+ hasWorkspace: true,
+ workspaceName: "demo",
+ hasAgentsFile: true,
+ hasMcpConfig: false,
+ mcpTargets: [
+ { kind: "cursor-workspace", label: "Cursor workspace", filePath: "/demo/.cursor/mcp.json", exists: false, configured: false }
+ ],
+ workspaceCount: 1,
+ environmentLabel: "Local",
+ environmentSupport: "supported"
+ }
+ );
+
+ assert.match(details, /\u2717/);
+ assert.match(details, /not configured/);
+});