Skip to content

Commit 8bad5bd

Browse files
authored
feat: align extension with patchloom CLI 0.28.0 (#226)
* feat: align extension with patchloom CLI 0.28.0 Recommend CLI 0.28.0 and surface new agent peels (empty path fail-closed, suggested_op, ambiguous headings, not_regular_file). Batch template adds doc.update multi-match example; install docs prefer Scoop on Windows. Verified against live patchloom 0.28.0: 58/11 MCP tools, replace/doc/batch contracts unchanged. npm run check: 304 tests, coverage, package ok. Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca> * docs: prefer managed/Scoop on Windows; avoid winget and Chocolatey Recommend GitHub-tracking install and upgrade paths only. Status and ready-check upgrade actions prefer Update/Install Patchloom managed over Open Releases so users do not stick on lagging community packages. Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca> --------- Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
1 parent 460a13a commit 8bad5bd

11 files changed

Lines changed: 212 additions & 52 deletions

File tree

AGENTS.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,10 @@ src/
4848
workspace/readiness.ts Workspace readiness: environment detection, folder selection
4949
test/
5050
unit/ Unit tests (node:test, dependency-injected, no VS Code API)
51-
batchApply.test.ts Batch template and operation count parsing (15 tests)
51+
batchApply.test.ts Batch template and operation count parsing (16 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 (34 tests)
54+
initializeProject.test.ts Status display, agents file classification, formatError (39 tests)
5555
managedLifecycle.test.ts Managed install with real file I/O (22 tests)
5656
mcpConfig.test.ts MCP config with real temp directories (12 tests)
5757
outputChannel.test.ts Output channel logging wrapper (10 tests)

README.md

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,17 +25,16 @@ Or search for **Patchloom** in the Extensions view (`Ctrl+Shift+X` / `Cmd+Shift+
2525

2626
## Get started in 30 seconds
2727

28-
1. Install the [Patchloom CLI](https://github.com/patchloom/patchloom) (or run **Patchloom: Install Patchloom** from the command palette)
28+
1. Install the [Patchloom CLI](https://github.com/patchloom/patchloom) (or run **Patchloom: Install Patchloom** from the command palette; recommended: tracks GitHub Releases with checksum verification)
2929
```sh
3030
brew install patchloom/tap/patchloom # macOS / Linux (Homebrew)
3131
npm install -g patchloom # npm (Node.js)
3232
curl -LsSf https://github.com/patchloom/patchloom/releases/latest/download/patchloom-installer.sh | sh # shell script
3333
cargo install patchloom # from source
3434
scoop bucket add patchloom https://github.com/patchloom/scoop-bucket
35-
scoop install patchloom # Windows (Scoop)
36-
choco install patchloom # Windows (Chocolatey; first listing may wait on moderation)
37-
winget install Patchloom.Patchloom # Windows (WinGet; when the community PR is approved)
35+
scoop install patchloom # Windows (Scoop; preferred PATH channel)
3836
```
37+
On Windows, prefer the extension managed installer or Scoop. Avoid winget and Chocolatey for install or upgrade: both lag GitHub Releases and often leave you on an old CLI.
3938
2. Open a project and run **Patchloom: Setup Workspace**
4039

4140
<p align="center">
@@ -66,7 +65,7 @@ Run `Patchloom: Setup Workspace` to walk through everything your project needs:
6665

6766
When configuring, pick **Full tool inventory** (default) or **Core pack**. Core sets `PATCHLOOM_MCP_SURFACE=core` on the server entry.
6867

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.
68+
CLI **0.28.0** (and 0.24+) 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; empty paths, `../`, and outside paths still reject with stable `error_kind` peels.
7069

7170
### Status bar
7271

@@ -118,7 +117,7 @@ Workspace Quick Actions and Batch Apply pass `--contain` so CLI paths stay insid
118117

119118
### Batch operations
120119

121-
`Patchloom: Batch Apply` opens a line-oriented plan template where you can compose multiple operations (replace, fuzzy replace, doc set, multi-doc `doc.merge`, file append, markdown section inserts, tidy). The extension pipes the plan to `patchloom batch --apply` so all changes land atomically.
120+
`Patchloom: Batch Apply` opens a line-oriented plan template where you can compose multiple operations (replace, fuzzy replace, `doc.set`, multi-match `doc.update`, multi-doc `doc.merge`, file append, markdown section inserts, tidy). The extension pipes the plan to `patchloom batch --apply` so all changes land atomically.
122121

123122
### Output channel
124123

@@ -175,24 +174,39 @@ The extension detects outdated CLI builds and warns with upgrade guidance. It re
175174
**Patchloom not found**
176175
Set `patchloom.path` in settings, or add the CLI to your `PATH`.
177176

178-
**CLI compatibility warning**
179-
Run `Patchloom: Open Releases` to download the latest release. The extension requires 0.3.0 or newer; 0.24.0 is recommended.
177+
**CLI compatibility warning / upgrade path**
178+
The extension requires Patchloom **0.3.0** or newer; **0.28.0** is recommended. Prefer channels that track GitHub Releases the same day:
179+
180+
1. **Patchloom: Update Patchloom** (or **Install Patchloom**) for the extension managed install (checksum-verified download from GitHub Releases)
181+
2. **Scoop** on Windows: `scoop update patchloom` after `scoop install patchloom`
182+
3. Homebrew / npm / cargo / the official installer script on macOS and Linux
183+
184+
Do **not** rely on winget or Chocolatey to stay current. Those community packages lag moderation and Microsoft publish, so upgrades often stay stuck on older CLI versions.
180185

181186
**Path rejected by workspace guard**
182187
Quick Actions and Batch Apply pass `--contain` so paths stay inside the open workspace folder. On CLI 0.18+, sandbox escapes report `error_kind: guard_rejected` (not a generic `invalid_input`). Keep targets under the workspace root, or open the folder that owns the files.
183188

189+
**Empty or blank path**
190+
On CLI 0.28+, empty, whitespace-only, or format-character-only paths fail early with `error_kind: invalid_input` and message `path must not be empty` (they no longer look like workspace-root failures). Prefer a real relative or workspace-absolute path.
191+
184192
**Batch replace shape**
185193
Batch lines use `replace PATH OLD NEW` (and optional flags such as `--fuzzy`). Do not paste CLI form `replace OLD --new NEW path` into a batch plan; CLI 0.18+ returns a clear parse error with the PATH OLD NEW hint.
186194

187195
**Create or rename destination already exists**
188196
On CLI 0.19+, create/rename conflicts report `error_kind: already_exists` (not a generic `invalid_input`). Use the force flag when overwriting is intentional, or pick a free destination path.
189197

190-
**Binary or invalid UTF-8 target**
191-
On CLI 0.20+, sole-path loads of binary or invalid UTF-8 files report `error_kind: binary` or `invalid_encoding` (not a soft `no_matches`). Use a text file, or force-create when overwriting non-text is intentional.
198+
**Binary, invalid UTF-8, or non-regular file**
199+
On CLI 0.20+, sole-path loads of binary or invalid UTF-8 files report `error_kind: binary` or `invalid_encoding` (not a soft `no_matches`). On CLI 0.26+, FIFOs and other special nodes refuse with multi-path `refused[].reason: not_regular_file` (not a permission error). Use a regular text file, or force-create when overwriting non-text is intentional.
192200

193201
**Fuzzy match span refused**
194202
On CLI 0.22+, over-wide fuzzy matches can report `error_kind: fuzzy_span_suspicious`. Prefer an exact `old` string, structured `doc`/`md`/`ast` edits, or `apply-fragment` with a unique anchor.
195203

204+
**Doc selector needs multi-match op**
205+
On CLI 0.27+, `doc set` / `doc ensure` / `doc delete` with a predicate or wildcard selector stay `error_kind: invalid_input` and may include `suggested_op` (`doc.update` or `doc.delete_where`). The extension surfaces that hint in the Output channel and notifications. Use the multi-match op (or a concrete index path such as `items.0.val`).
206+
207+
**Ambiguous markdown heading**
208+
On CLI 0.25+, section ops that match the same heading more than once report `error_kind: ambiguous`. Make the heading unique or use a level-qualified query (for example `## Rules`).
209+
196210
**MCP config not injected**
197211
Run `Patchloom: Configure MCP` and select the target editor config.
198212

@@ -226,7 +240,7 @@ File bugs and feature requests at [patchloom/patchloom-vscode/issues](https://gi
226240
## Requirements
227241

228242
- VS Code 1.90 or newer (or compatible editors: Cursor, Windsurf, VSCodium)
229-
- [Patchloom CLI](https://github.com/patchloom/patchloom) 0.3.0 or newer (0.24.0+ recommended for `list_files` MCP inventory, `apply-fragment`, `error_kind` peels including `binary` / `invalid_encoding` / `fuzzy_span_suspicious` / `already_exists` / `guard_rejected`, optional `PATCHLOOM_MCP_SURFACE=core` 11-tool pack, multi-doc `doc merge --selector`, line-oriented inserts, batch `replace PATH OLD NEW` hints, 58 MCP tools, JSON `applied` honesty, and agent-facing envelopes)
243+
- [Patchloom CLI](https://github.com/patchloom/patchloom) 0.3.0 or newer (**0.28.0+ recommended** for empty-path fail-closed (`path must not be empty`), `suggested_op` on fail-closed doc navigation, `not_regular_file` soft peels, ambiguous markdown headings, `list_files` MCP inventory, `apply-fragment`, full `error_kind` peels (`binary` / `invalid_encoding` / `fuzzy_span_suspicious` / `already_exists` / `guard_rejected` / `ambiguous`), optional `PATCHLOOM_MCP_SURFACE=core` 11-tool pack, multi-doc `doc merge --selector`, line-oriented inserts, batch `replace PATH OLD NEW` hints, 58 MCP tools, and agent-facing JSON envelopes)
230244

231245
## Contributing
232246

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@
162162
{
163163
"id": "installCli",
164164
"title": "Install the Patchloom CLI",
165-
"description": "Install the Patchloom CLI via the managed installer, Homebrew, or cargo.\n\n[Install Patchloom](command:patchloom.installBinary)",
165+
"description": "Install the Patchloom CLI via the managed installer (recommended), Scoop on Windows, Homebrew, or cargo. Avoid winget and Chocolatey; they lag releases.\n\n[Install Patchloom](command:patchloom.installBinary)",
166166
"media": {
167167
"markdown": "walkthrough/install.md"
168168
},

src/binary/patchloom.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -182,11 +182,23 @@ export async function ensurePatchloomReadyOrNotify(
182182

183183
if (patchloomNeedsUpgrade(status)) {
184184
const vscode = await import("vscode");
185+
// Prefer managed install/update (GitHub Releases) over lagging community packages.
186+
const canUpdateManaged = status.source === "managed" || status.managedInstall?.exists === true;
187+
const canInstallManaged = status.managedInstall !== undefined;
188+
const primaryAction = canUpdateManaged
189+
? "Update Patchloom"
190+
: canInstallManaged
191+
? "Install Patchloom"
192+
: "Open Releases";
185193
const choice = await vscode.window.showWarningMessage(
186194
`${status.compatibilityMessage}${contextSuffix ? `\n\n${contextSuffix}` : ""}`,
187-
"Open Releases"
195+
primaryAction
188196
);
189-
if (choice === "Open Releases") {
197+
if (choice === "Update Patchloom") {
198+
await vscode.commands.executeCommand("patchloom.updateBinary");
199+
} else if (choice === "Install Patchloom") {
200+
await vscode.commands.executeCommand("patchloom.installBinary");
201+
} else if (choice === "Open Releases") {
190202
await vscode.commands.executeCommand("patchloom.openPatchloomReleases");
191203
}
192204
return null;

src/commands/batchApply.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@ import { getPatchloomLog } from "../logging/outputChannel.js";
66
import { activeWorkspaceFolder } from "../workspace/readiness.js";
77

88
// Batch replace is PATH OLD NEW (not CLI `replace OLD --new NEW path`). See CLI 0.18+ batch --help.
9+
// doc.update is the multi-match sibling of doc.set (CLI 0.27+ suggested_op hints this).
910
export const BATCH_TEMPLATE = [
1011
"replace src/example.ts \"old text\" \"new text\"",
1112
"replace src/example.ts \"typo_here\" \"fixed\" --fuzzy --min-fuzzy-score 0.80",
1213
"replace src/example.ts \"anchor_line\" --insert-after=\"new sibling line\"",
1314
"doc.set package.json version \"2.0.0\"",
15+
"doc.update data.json \"items[*].enabled\" true",
1416
"doc.merge multi-doc.yaml 0 \"{\\\"debug\\\": true}\"",
1517
"file.append src/example.ts \"new appended line\"",
1618
"md.insert_after_section README.md \"## Config\" \"## FAQ\"",

src/commands/configureMcp.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ export async function configureMcp(): Promise<void> {
4747
[
4848
{
4949
label: "Full tool inventory",
50-
description: "Default (58 tools on CLI 0.24+)",
50+
description: "Default (58 tools on CLI 0.24+; verified on 0.28)",
5151
surface: "full" as const
5252
},
5353
{

src/status/details.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,20 @@ export function preferredStatusAction(status: PatchloomStatus, workspaceReadines
5757
}
5858

5959
if (patchloomNeedsUpgrade(status)) {
60+
// Prefer managed install/update so users stay on GitHub Releases (not lagging
61+
// community packages such as winget/Chocolatey).
62+
if (status.source === "managed" || status.managedInstall?.exists) {
63+
return {
64+
title: "Update Patchloom",
65+
command: "patchloom.updateBinary"
66+
};
67+
}
68+
if (status.managedInstall) {
69+
return {
70+
title: "Install Patchloom",
71+
command: "patchloom.installBinary"
72+
};
73+
}
6074
return {
6175
title: "Open Releases",
6276
command: "patchloom.openPatchloomReleases"

src/util.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ export function formatError(error: unknown): string {
1212
/**
1313
* Prefer machine-readable CLI JSON error envelopes (error_kind + error) when
1414
* present so agents and the UI surface kinds like guard_rejected (CLI 0.18+)
15-
* instead of a flattened multi-line dump.
15+
* instead of a flattened multi-line dump. On CLI 0.27+, append suggested_op
16+
* when present so users can retry with doc.update / doc.delete_where.
1617
*/
1718
export function formatCliOutput(result: { exitCode: number; stdout: string; stderr: string }): string {
1819
const jsonError = extractCliJsonError(result.stdout) ?? extractCliJsonError(result.stderr);
@@ -34,19 +35,30 @@ function extractCliJsonError(stream: string): string | undefined {
3435
return undefined;
3536
}
3637
try {
37-
const parsed = JSON.parse(trimmed) as { error?: unknown; error_kind?: unknown };
38+
const parsed = JSON.parse(trimmed) as {
39+
error?: unknown;
40+
error_kind?: unknown;
41+
suggested_op?: unknown;
42+
};
3843
if (typeof parsed.error !== "string" || parsed.error.length === 0) {
3944
return undefined;
4045
}
4146
// CLI often prefixes "guard_rejected: …" already; avoid "kind: kind: …".
47+
let message: string;
4248
if (
4349
typeof parsed.error_kind === "string" &&
4450
parsed.error_kind.length > 0 &&
4551
!parsed.error.startsWith(`${parsed.error_kind}:`)
4652
) {
47-
return `${parsed.error_kind}: ${parsed.error}`;
53+
message = `${parsed.error_kind}: ${parsed.error}`;
54+
} else {
55+
message = parsed.error;
4856
}
49-
return parsed.error;
57+
// CLI 0.27+: fail-closed doc navigation may hint the multi-match sibling.
58+
if (typeof parsed.suggested_op === "string" && parsed.suggested_op.length > 0) {
59+
message = `${message} (suggested_op: ${parsed.suggested_op})`;
60+
}
61+
return message;
5062
} catch {
5163
return undefined;
5264
}

test/unit/batchApply.test.ts

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,20 @@ import {
66
parseBatchOperationCount
77
} from "../../src/commands/batchApply.js";
88

9-
test("buildBatchTemplate returns line-oriented format with eight operations", () => {
9+
test("buildBatchTemplate returns line-oriented format with nine operations", () => {
1010
const template = buildBatchTemplate();
1111
const lines = template.split("\n").filter((line) => line.trim().length > 0);
1212

13-
assert.equal(lines.length, 8);
13+
assert.equal(lines.length, 9);
1414
assert.ok(lines[0].startsWith("replace "), "first line should be a replace operation");
1515
assert.ok(lines[1].startsWith("replace ") && lines[1].includes("--fuzzy"), "second line should be fuzzy replace");
1616
assert.ok(lines[2].startsWith("replace ") && lines[2].includes("--insert-after"), "third line should be insert-after");
1717
assert.ok(lines[3].startsWith("doc.set "), "fourth line should be a doc.set operation");
18-
assert.ok(lines[4].startsWith("doc.merge "), "fifth line should be multi-doc doc.merge");
19-
assert.ok(lines[5].startsWith("file.append "), "sixth line should be a file.append operation");
20-
assert.ok(lines[6].startsWith("md.insert_after_section "), "seventh line should be md.insert_after_section");
21-
assert.ok(lines[7].startsWith("tidy.fix "), "eighth line should be a tidy.fix operation");
18+
assert.ok(lines[4].startsWith("doc.update "), "fifth line should be multi-match doc.update");
19+
assert.ok(lines[5].startsWith("doc.merge "), "sixth line should be multi-doc doc.merge");
20+
assert.ok(lines[6].startsWith("file.append "), "seventh line should be a file.append operation");
21+
assert.ok(lines[7].startsWith("md.insert_after_section "), "eighth line should be md.insert_after_section");
22+
assert.ok(lines[8].startsWith("tidy.fix "), "ninth line should be a tidy.fix operation");
2223
});
2324

2425
test("buildBatchTemplate ends with a newline", () => {
@@ -119,6 +120,18 @@ test("buildBatchTemplate includes replace --insert-after example (CLI 0.16)", ()
119120
);
120121
});
121122

123+
test("buildBatchTemplate includes doc.update multi-match example (CLI 0.27+ suggested_op sibling)", () => {
124+
const lines = buildBatchTemplate().split("\n");
125+
const updateLine = lines.find((l) => l.startsWith("doc.update "));
126+
assert.ok(updateLine, "template should contain a doc.update line");
127+
assert.match(
128+
updateLine,
129+
/doc\.update \S+ ".+" \S+/,
130+
"doc.update should have path, selector, and value"
131+
);
132+
assert.match(updateLine, /\[\*\]|\[.+=.+\]/, "selector should use wildcard or predicate form");
133+
});
134+
122135
test("buildBatchApplyArgs prefixes global --contain before batch --apply", () => {
123136
assert.deepEqual(buildBatchApplyArgs(), ["--contain", "batch", "--apply"]);
124137
});

0 commit comments

Comments
 (0)