Skip to content

Commit baa0f0e

Browse files
committed
feat: agent-rules surface and MCP core pack options
Expose CLI 0.24 agent-rules --surface (full/core) in Initialize Project, and let Configure MCP set PATCHLOOM_MCP_SURFACE=core for the 11-tool coding-agent pack. Add tests, walkthrough copy, and README docs; bump @types/node; cover invalid_encoding in formatCliOutput. Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
1 parent 72d6b0e commit baa0f0e

11 files changed

Lines changed: 153 additions & 20 deletions

File tree

AGENTS.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,9 @@ test/
5151
batchApply.test.ts Batch template and operation count parsing (15 tests)
5252
binary.test.ts Binary discovery, managed install, compatibility, workspace env (59 tests)
5353
binaryDiscovery.test.ts Real executable discovery on PATH (13 tests)
54-
initializeProject.test.ts Status display, agents file classification, formatError (32 tests)
54+
initializeProject.test.ts Status display, agents file classification, formatError (34 tests)
5555
managedLifecycle.test.ts Managed install with real file I/O (22 tests)
56-
mcpConfig.test.ts MCP config with real temp directories (9 tests)
56+
mcpConfig.test.ts MCP config with real temp directories (12 tests)
5757
outputChannel.test.ts Output channel logging wrapper (10 tests)
5858
patchloomCli.test.ts Patchloom CLI integration with real binary + managed install e2e MCP (40 tests incl. e2e)
5959
propertyBased.test.ts Property-based tests with fast-check (13 tests)

README.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ Run `Patchloom: Setup Workspace` to walk through everything your project needs:
5454

5555
### Agent rules generation
5656

57-
`Patchloom: Initialize Project` generates an `AGENTS.md` file from `patchloom agent-rules`. If one already exists, the extension opens a diff so you can merge updates manually.
57+
`Patchloom: Initialize Project` generates an `AGENTS.md` file from `patchloom agent-rules`. You pick integration mode (CLI/MCP/all), shell platform, and surface (`full` document or `core` pack for system-prompt injection, CLI 0.24+). If `AGENTS.md` already exists, the extension opens a diff so you can merge updates manually.
5858

5959
### MCP server configuration
6060

@@ -64,7 +64,9 @@ Run `Patchloom: Setup Workspace` to walk through everything your project needs:
6464
- **Cursor** (`.cursor/mcp.json`)
6565
- **Windsurf** (`~/.codeium/windsurf/mcp_config.json`)
6666

67-
CLI 0.24.0 exposes **58** MCP tools by default (including `list_files` and `apply_fragment`). For coding agents that need a smaller handshake inventory, set `PATCHLOOM_MCP_SURFACE=core` in the server environment (11 tools: `read_file`, `search_files`, `list_files`, `replace_text`, `batch_replace`, `doc_get`, `doc_set`, `doc_query`, `md_replace_section`, `execute_plan`, `server_info`). Absolute paths that resolve inside the MCP workspace root are allowed; `../` and outside paths still reject.
67+
When configuring, pick **Full tool inventory** (default) or **Core pack**. Core sets `PATCHLOOM_MCP_SURFACE=core` on the server entry.
68+
69+
CLI 0.24.0 exposes **58** MCP tools by default (including `list_files` and `apply_fragment`). The core pack is 11 tools: `read_file`, `search_files`, `list_files`, `replace_text`, `batch_replace`, `doc_get`, `doc_set`, `doc_query`, `md_replace_section`, `execute_plan`, `server_info`. Absolute paths that resolve inside the MCP workspace root are allowed; `../` and outside paths still reject.
6870

6971
### Status bar
7072

@@ -133,8 +135,8 @@ The extension detects outdated CLI builds and warns with upgrade guidance. It re
133135
| Command | Description |
134136
|---------|-------------|
135137
| `Patchloom: Setup Workspace` | Guided walkthrough for binary, AGENTS.md, and MCP readiness |
136-
| `Patchloom: Initialize Project` | Generate or diff `AGENTS.md` from `patchloom agent-rules` (mode: all/cli/mcp, platform: all/linux/windows) |
137-
| `Patchloom: Configure MCP` | Inject Patchloom MCP server config into editor config files |
138+
| `Patchloom: Initialize Project` | Generate or diff `AGENTS.md` from `patchloom agent-rules` (mode, platform, surface full/core) |
139+
| `Patchloom: Configure MCP` | Inject Patchloom MCP server config (full or core tool surface) into editor config files |
138140
| `Patchloom: Quick Action` | Build a Patchloom CLI command from an interactive picker |
139141
| `Patchloom: Batch Apply` | Open a batch plan and execute all operations atomically |
140142
| `Patchloom: Show Output` | Open the Patchloom output channel for CLI logs and diagnostics |

package-lock.json

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

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@
261261
},
262262
"devDependencies": {
263263
"@types/mocha": "^10.0.10",
264-
"@types/node": "^26.1.1",
264+
"@types/node": "^26.1.2",
265265
"@types/vscode": "^1.90.0",
266266
"@vscode/test-electron": "^3.1.0",
267267
"@vscode/vsce": "^3.0.0",

src/commands/configureMcp.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,32 @@ export async function configureMcp(): Promise<void> {
4343
return;
4444
}
4545

46+
const surfacePick = await vscode.window.showQuickPick(
47+
[
48+
{
49+
label: "Full tool inventory",
50+
description: "Default (58 tools on CLI 0.24+)",
51+
surface: "full" as const
52+
},
53+
{
54+
label: "Core pack",
55+
description: "Sets PATCHLOOM_MCP_SURFACE=core (11 tools; CLI 0.22+)",
56+
surface: "core" as const
57+
}
58+
],
59+
{ placeHolder: "Which MCP tool surface should the server expose?" }
60+
);
61+
if (!surfacePick) {
62+
return;
63+
}
64+
4665
const selectedKinds = selections.map((selection) => selection.target.kind);
4766
const results = await configureMcpTargets({
4867
workspaceFolderPath,
4968
includeKinds: selectedKinds,
5069
includeUserTarget: environment.supportsUserMcpConfig,
5170
patchloomPathSetting: binaryPath,
71+
mcpSurface: surfacePick.surface,
5272
readFile: async (filePath) => {
5373
try {
5474
return await fs.readFile(filePath, "utf8");

src/commands/initializeProject.ts

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,31 @@ export async function initializeProject(): Promise<void> {
5353
return;
5454
}
5555

56+
const surfacePick = await vscode.window.showQuickPick(
57+
[
58+
{
59+
label: "Full document",
60+
description: "Default agent-rules (full tool inventory)",
61+
surface: "full" as const
62+
},
63+
{
64+
label: "Core pack",
65+
description: "Short rules for system-prompt injection (CLI 0.24+ --surface core)",
66+
surface: "core" as const
67+
}
68+
],
69+
{ placeHolder: "Which agent-rules surface?" }
70+
);
71+
if (!surfacePick) {
72+
return;
73+
}
74+
5675
let rules: string;
5776
try {
5877
rules = await generateAgentRules(binaryPath, folder.uri.fsPath, {
5978
mode: modePick.mode,
60-
platform: platformPick.platform
79+
platform: platformPick.platform,
80+
surface: surfacePick.surface
6181
});
6282
} catch (error) {
6383
await vscode.window.showErrorMessage(`Failed to run patchloom agent-rules in ${folder.name}: ${formatError(error)}`);
@@ -116,13 +136,16 @@ export function classifyAgentsFile(existingContent: string | undefined, generate
116136

117137
export type AgentRulesMode = "all" | "cli" | "mcp";
118138
export type AgentRulesPlatform = "all" | "linux" | "windows";
139+
/** CLI 0.24+ agent-rules surface (full document vs short core pack). */
140+
export type AgentRulesSurface = "full" | "core";
119141

120142
export interface AgentRulesOptions {
121143
readonly mode?: AgentRulesMode;
122144
readonly platform?: AgentRulesPlatform;
145+
readonly surface?: AgentRulesSurface;
123146
}
124147

125-
/** Build `patchloom agent-rules` argv, omitting default `all` flags. */
148+
/** Build `patchloom agent-rules` argv, omitting default `all` / `full` flags. */
126149
export function buildAgentRulesArgs(options: AgentRulesOptions = {}): string[] {
127150
const args = ["agent-rules"];
128151
if (options.mode && options.mode !== "all") {
@@ -131,6 +154,9 @@ export function buildAgentRulesArgs(options: AgentRulesOptions = {}): string[] {
131154
if (options.platform && options.platform !== "all") {
132155
args.push("--platform", options.platform);
133156
}
157+
if (options.surface && options.surface !== "full") {
158+
args.push("--surface", options.surface);
159+
}
134160
return args;
135161
}
136162

src/mcp/config.ts

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,18 @@ export interface McpInspectionInputs {
2525
readonly includeUserTarget?: boolean;
2626
}
2727

28+
export type McpSurface = "full" | "core";
29+
2830
export interface McpApplyInputs extends McpInspectionInputs {
2931
readonly writeFile: (filePath: string, content: string) => Promise<void>;
3032
readonly patchloomPathSetting?: string;
3133
readonly includeKinds?: readonly McpTargetKind[];
34+
/**
35+
* MCP tool inventory for coding agents (CLI 0.22+ / 0.24+).
36+
* `core` sets `PATCHLOOM_MCP_SURFACE=core` on the server entry (11 tools).
37+
* Default `full` omits the env var so the CLI uses its full inventory.
38+
*/
39+
readonly mcpSurface?: McpSurface;
3240
}
3341

3442
export async function inspectMcpTargets(inputs: McpInspectionInputs): Promise<McpTargetStatus[]> {
@@ -56,11 +64,12 @@ export async function configureMcpTargets(inputs: McpApplyInputs): Promise<McpTa
5664
const targets = resolveMcpTargets(inputs.workspaceFolderPath, inputs.homeDir, inputs.includeUserTarget)
5765
.filter((target) => !includeKinds || includeKinds.has(target.kind));
5866
const results: McpTargetResult[] = [];
67+
const mcpSurface = inputs.mcpSurface ?? "full";
5968

6069
for (const target of targets) {
6170
const content = await readFile(target.filePath);
6271
const original = parseJsonObject(content);
63-
const updated = withPatchloomEntry(target.kind, original, patchloomCommand);
72+
const updated = withPatchloomEntry(target.kind, original, patchloomCommand, mcpSurface);
6473
const serialized = `${JSON.stringify(updated, null, 2)}\n`;
6574
const previousSerialized = content === undefined ? undefined : `${JSON.stringify(original, null, 2)}\n`;
6675
const changed = previousSerialized !== serialized;
@@ -113,15 +122,27 @@ export function resolveMcpTargets(
113122
return targets;
114123
}
115124

116-
export function buildPatchloomMcpEntry(commandPath: string): Record<string, unknown> {
117-
return {
125+
export function buildPatchloomMcpEntry(
126+
commandPath: string,
127+
mcpSurface: McpSurface = "full"
128+
): Record<string, unknown> {
129+
const entry: Record<string, unknown> = {
118130
command: commandPath,
119131
args: ["mcp-server"]
120132
};
133+
if (mcpSurface === "core") {
134+
entry.env = { PATCHLOOM_MCP_SURFACE: "core" };
135+
}
136+
return entry;
121137
}
122138

123-
function withPatchloomEntry(kind: McpTargetKind, config: Record<string, unknown>, commandPath: string): Record<string, unknown> {
124-
const entry = buildPatchloomMcpEntry(commandPath);
139+
function withPatchloomEntry(
140+
kind: McpTargetKind,
141+
config: Record<string, unknown>,
142+
commandPath: string,
143+
mcpSurface: McpSurface = "full"
144+
): Record<string, unknown> {
145+
const entry = buildPatchloomMcpEntry(commandPath, mcpSurface);
125146
if (kind === "windsurf-user") {
126147
const servers = objectValue(config.mcpServers);
127148
return {

test/unit/initializeProject.test.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,19 @@ test("formatCliOutput surfaces binary kind (CLI 0.20+)", () => {
111111
);
112112
});
113113

114+
test("formatCliOutput surfaces invalid_encoding kind (CLI 0.20+)", () => {
115+
const stdout = JSON.stringify({
116+
ok: false,
117+
error: "target is not valid UTF-8 text: notes.txt",
118+
error_kind: "invalid_encoding",
119+
applied: false
120+
});
121+
assert.equal(
122+
formatCliOutput({ exitCode: 1, stdout, stderr: "" }),
123+
"invalid_encoding: target is not valid UTF-8 text: notes.txt"
124+
);
125+
});
126+
114127
test("formatCliOutput surfaces fuzzy_span_suspicious kind (CLI 0.22+)", () => {
115128
const stdout = JSON.stringify({
116129
ok: false,
@@ -459,6 +472,7 @@ test("configureMcpTargets creates or updates only the selected target kinds", as
459472
test("buildAgentRulesArgs omits default all modes", () => {
460473
assert.deepEqual(buildAgentRulesArgs(), ["agent-rules"]);
461474
assert.deepEqual(buildAgentRulesArgs({ mode: "all", platform: "all" }), ["agent-rules"]);
475+
assert.deepEqual(buildAgentRulesArgs({ surface: "full" }), ["agent-rules"]);
462476
});
463477

464478
test("buildAgentRulesArgs includes non-default mode and platform", () => {
@@ -477,6 +491,18 @@ test("buildAgentRulesArgs includes non-default mode and platform", () => {
477491
]);
478492
});
479493

494+
test("buildAgentRulesArgs includes --surface core (CLI 0.24+)", () => {
495+
assert.deepEqual(buildAgentRulesArgs({ surface: "core" }), [
496+
"agent-rules",
497+
"--surface",
498+
"core"
499+
]);
500+
assert.deepEqual(
501+
buildAgentRulesArgs({ mode: "mcp", platform: "linux", surface: "core" }),
502+
["agent-rules", "--mode", "mcp", "--platform", "linux", "--surface", "core"]
503+
);
504+
});
505+
480506
test("generateAgentRules logs error to output channel on CLI failure", async () => {
481507
const logged: { exitCode: number; stdout: string; stderr: string }[] = [];
482508
const commands: { binary: string; args: readonly string[]; cwd: string }[] = [];

test/unit/mcpConfig.test.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import * as os from "node:os";
44
import * as path from "node:path";
55
import test from "node:test";
66
import {
7+
buildPatchloomMcpEntry,
78
configureMcpTargets,
89
inspectMcpTargets,
910
resolveMcpTargets
@@ -23,6 +24,19 @@ async function readJson(filePath: string): Promise<Record<string, unknown>> {
2324
return JSON.parse(content) as Record<string, unknown>;
2425
}
2526

27+
test("buildPatchloomMcpEntry omits env for full surface", () => {
28+
const entry = buildPatchloomMcpEntry("/usr/bin/patchloom");
29+
assert.equal(entry.command, "/usr/bin/patchloom");
30+
assert.deepEqual(entry.args, ["mcp-server"]);
31+
assert.equal(entry.env, undefined);
32+
});
33+
34+
test("buildPatchloomMcpEntry sets PATCHLOOM_MCP_SURFACE for core pack", () => {
35+
const entry = buildPatchloomMcpEntry("patchloom", "core");
36+
assert.deepEqual(entry.args, ["mcp-server"]);
37+
assert.deepEqual(entry.env, { PATCHLOOM_MCP_SURFACE: "core" });
38+
});
39+
2640
test("configureMcpTargets writes VS Code mcp.json to a real temp workspace", async () => {
2741
await withTempDir(async (workspace) => {
2842
const results = await configureMcpTargets({
@@ -46,6 +60,28 @@ test("configureMcpTargets writes VS Code mcp.json to a real temp workspace", asy
4660
const entry = servers.patchloom as Record<string, unknown>;
4761
assert.equal(entry.command, "/usr/local/bin/patchloom");
4862
assert.deepEqual(entry.args, ["mcp-server"]);
63+
assert.equal(entry.env, undefined, "full surface should not inject env");
64+
});
65+
});
66+
67+
test("configureMcpTargets writes core surface env when requested", async () => {
68+
await withTempDir(async (workspace) => {
69+
await configureMcpTargets({
70+
workspaceFolderPath: workspace,
71+
homeDir: workspace,
72+
includeKinds: ["vscode-workspace"],
73+
patchloomPathSetting: "patchloom",
74+
mcpSurface: "core",
75+
writeFile: async (filePath, content) => {
76+
await fs.mkdir(path.dirname(filePath), { recursive: true });
77+
await fs.writeFile(filePath, content, "utf8");
78+
}
79+
});
80+
81+
const written = await readJson(path.join(workspace, ".vscode", "mcp.json"));
82+
const servers = written.servers as Record<string, unknown>;
83+
const entry = servers.patchloom as Record<string, unknown>;
84+
assert.deepEqual(entry.env, { PATCHLOOM_MCP_SURFACE: "core" });
4985
});
5086
});
5187

walkthrough/configure-mcp.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ The Model Context Protocol (MCP) lets AI agents call Patchloom
44
operations directly: search, replace, tidy, and more.
55

66
Click **Configure MCP** above to set up the MCP server configuration
7-
for your editor.
7+
for your editor. Choose the **full** tool inventory or the **core** pack
8+
(sets `PATCHLOOM_MCP_SURFACE=core` for a smaller 11-tool handshake).
89

910
## Supported Editors
1011

0 commit comments

Comments
 (0)