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
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ src/
workspace/readiness.ts Workspace readiness: environment detection, folder selection
test/
unit/ Unit tests (node:test, dependency-injected, no VS Code API)
batchApply.test.ts Batch template and operation count parsing (12 tests)
batchApply.test.ts Batch template and operation count parsing (13 tests)
binary.test.ts Binary discovery, managed install, compatibility, workspace env (59 tests)
binaryDiscovery.test.ts Real executable discovery on PATH (13 tests)
initializeProject.test.ts Status display, agents file classification, formatError (26 tests)
Expand All @@ -56,7 +56,7 @@ test/
outputChannel.test.ts Output channel logging wrapper (10 tests)
patchloomCli.test.ts Patchloom CLI integration with real binary + managed install e2e MCP (34 tests incl. e2e)
propertyBased.test.ts Property-based tests with fast-check (13 tests)
quickActions.test.ts Quick action command building, path containment, patch merge (50 tests)
quickActions.test.ts Quick action command building, path containment, patch merge (51 tests)
verifyMcp.test.ts MCP server verify and JSON-RPC response parsing (15 tests)
downloadIntegration.test.ts HTTP download, redirect, streaming SHA-256 (9 tests)
suite/
Expand Down
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +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) (or run **Patchloom: Install Patchloom** from the command palette)
```sh
brew install patchloom/tap/patchloom # macOS / Linux (Homebrew)
npm install -g patchloom # npm (Node.js)
curl -LsSf https://github.com/patchloom/patchloom/releases/latest/download/patchloom-installer.sh | sh # shell script
cargo install patchloom # from source
scoop install patchloom # Windows (Scoop; requires the official bucket)
```
2. Open a project and run **Patchloom: Setup Workspace**

Expand Down Expand Up @@ -87,9 +89,10 @@ Click it to see full diagnostics, including per-editor MCP configuration status
| **Append to file** | Append content to an existing file |
| **Prepend to file** | Prepend content to the start of an existing file (CLI 0.9+) |
| **Read structured value** | Read a JSON/YAML/TOML key and copy to clipboard |
| **Insert after section** | Insert a sibling markdown section after a full section body (CLI 0.14+) |
| **Merge patch (three-way)** | Apply a stale patch using three-way merge (v0.2.0+) |

Workspace Quick Actions and Batch Apply pass `--contain` so CLI paths stay inside the workspace root (CLI 0.10+). Patch merge skips containment when the patch file may live outside the workspace.
Workspace Quick Actions and Batch Apply pass `--contain` so CLI paths stay inside the workspace root (CLI 0.10+). Containment is relative to the effective working directory (the workspace folder). Patch merge skips containment when the patch file may live outside the workspace.

### Batch operations

Expand Down Expand Up @@ -151,7 +154,7 @@ The extension detects outdated CLI builds and warns with upgrade guidance. It re
Set `patchloom.path` in settings, or add the CLI to your `PATH`.

**CLI compatibility warning**
Run `Patchloom: Open Releases` to download the latest release. The extension requires 0.3.0 or newer; 0.10.0 is recommended.
Run `Patchloom: Open Releases` to download the latest release. The extension requires 0.3.0 or newer; 0.15.2 is recommended.

**MCP config not injected**
Run `Patchloom: Configure MCP` and select the target editor config.
Expand Down Expand Up @@ -186,7 +189,7 @@ File bugs and feature requests at [patchloom/patchloom-vscode/issues](https://gi
## Requirements

- VS Code 1.90 or newer (or compatible editors: Cursor, Windsurf, VSCodium)
- [Patchloom CLI](https://github.com/patchloom/patchloom) 0.3.0 or newer (0.10.0+ recommended for 54 MCP tools, correct preview exit codes, optional `--contain` path guarding, schema-driven MCP descriptions, and agent reliability fixes)
- [Patchloom CLI](https://github.com/patchloom/patchloom) 0.3.0 or newer (0.15.2+ recommended for 56 MCP tools, JSON `applied` honesty, doc query envelopes, `md insert-after-section`, optional `--contain` path guarding, fuzzy replace floors, and agent reliability fixes)

## Contributing

Expand Down
2 changes: 2 additions & 0 deletions src/commands/batchApply.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ import { activeWorkspaceFolder } from "../workspace/readiness.js";

export const BATCH_TEMPLATE = [
"replace src/example.ts \"old text\" \"new text\"",
"replace src/example.ts \"typo_here\" \"fixed\" --fuzzy --min-fuzzy-score 0.80",
"doc.set package.json version \"2.0.0\"",
"file.append src/example.ts \"new appended line\"",
"md.insert_after_section README.md \"## Config\" \"## FAQ\"",
"tidy.fix src/example.ts",
""
].join("\n");
Expand Down
47 changes: 47 additions & 0 deletions src/commands/quickActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,44 @@ export async function runQuickAction(): Promise<void> {
await previewAndMaybeApply(binaryPath, target, buildMdInsertAfterHeadingQuickAction(target.absolutePath, heading, content));
}
},
{
label: "Insert after section",
description: "Insert a sibling section after a full markdown section body (CLI 0.14+)",
detail: "Builds `patchloom md insert-after-section <file> --heading <h> --content <text>`",
run: async () => {
const target = await pickWorkspaceFileTarget("Select a markdown file for Patchloom insert-after-section");
if (!target) {
return;
}

if (!isMarkdownPath(target.absolutePath)) {
await vscode.window.showWarningMessage(
`${target.relativePath} is not a markdown file.`
);
return;
}

const heading = await vscode.window.showInputBox({
prompt: "Heading whose full section ends before the insertion",
placeHolder: "## Config",
validateInput: (value) => value.length > 0 ? undefined : "Heading is required."
});
if (heading === undefined) {
return;
}

const content = await vscode.window.showInputBox({
prompt: "Sibling content to insert after the section body",
placeHolder: "## FAQ\n\nCommon questions.",
validateInput: (value) => value.length > 0 ? undefined : "Content is required."
});
if (content === undefined) {
return;
}

await previewAndMaybeApply(binaryPath, target, buildMdInsertAfterSectionQuickAction(target.absolutePath, heading, content));
}
},
{
label: "Insert before heading",
description: "Insert content before a markdown heading",
Expand Down Expand Up @@ -1017,6 +1055,15 @@ export function buildMdInsertAfterHeadingQuickAction(targetPath: string, heading
};
}

export function buildMdInsertAfterSectionQuickAction(targetPath: string, heading: string, content: string): PlannedQuickAction {
return {
title: `Insert after section "${heading}" in ${path.basename(targetPath)}`,
targetPath,
targetArgIndices: [2],
args: ["md", "insert-after-section", targetPath, "--heading", heading, "--content", content]
};
}

export function buildMdInsertBeforeHeadingQuickAction(targetPath: string, heading: string, content: string): PlannedQuickAction {
return {
title: `Insert before "${heading}" in ${path.basename(targetPath)}`,
Expand Down
26 changes: 21 additions & 5 deletions test/unit/batchApply.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,17 @@ import {
parseBatchOperationCount
} from "../../src/commands/batchApply.js";

test("buildBatchTemplate returns line-oriented format with four operations", () => {
test("buildBatchTemplate returns line-oriented format with six operations", () => {
const template = buildBatchTemplate();
const lines = template.split("\n").filter((line) => line.trim().length > 0);

assert.equal(lines.length, 4);
assert.equal(lines.length, 6);
assert.ok(lines[0].startsWith("replace "), "first line should be a replace operation");
assert.ok(lines[1].startsWith("doc.set "), "second line should be a doc.set operation");
assert.ok(lines[2].startsWith("file.append "), "third line should be a file.append operation");
assert.ok(lines[3].startsWith("tidy.fix "), "fourth line should be a tidy.fix operation");
assert.ok(lines[1].startsWith("replace ") && lines[1].includes("--fuzzy"), "second line should be fuzzy replace");
assert.ok(lines[2].startsWith("doc.set "), "third line should be a doc.set operation");
assert.ok(lines[3].startsWith("file.append "), "fourth line should be a file.append operation");
assert.ok(lines[4].startsWith("md.insert_after_section "), "fifth line should be md.insert_after_section");
assert.ok(lines[5].startsWith("tidy.fix "), "sixth line should be a tidy.fix operation");
});

test("buildBatchTemplate ends with a newline", () => {
Expand Down Expand Up @@ -78,6 +80,20 @@ test("buildBatchTemplate file.append line has file and quoted content", () => {
assert.match(appendLine, /file\.append \S+ ".+"/, "file.append should have file and quoted content");
});

test("buildBatchTemplate includes fuzzy replace and md.insert_after_section examples", () => {
const lines = buildBatchTemplate().split("\n");
const fuzzyLine = lines.find((l) => l.includes("--fuzzy"));
const sectionLine = lines.find((l) => l.startsWith("md.insert_after_section "));
assert.ok(fuzzyLine, "template should contain a fuzzy replace example");
assert.match(fuzzyLine, /--min-fuzzy-score/, "fuzzy replace should include min-fuzzy-score");
assert.ok(sectionLine, "template should contain md.insert_after_section");
assert.match(
sectionLine,
/md\.insert_after_section \S+ ".+" ".+"/,
"md.insert_after_section should use path + heading + content positionals"
);
});

test("buildBatchApplyArgs prefixes global --contain before batch --apply", () => {
assert.deepEqual(buildBatchApplyArgs(), ["--contain", "batch", "--apply"]);
});
2 changes: 1 addition & 1 deletion test/unit/patchloomCli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -788,7 +788,7 @@ describe("managed install end-to-end MCP", { timeout: 120_000 }, async () => {
}) + "\n");

// Call doc_set to change port from 3000 to 8080 (relative path).
// MCP param is `selector` (CLI arg name); not `key`.
// MCP param is `selector` (CLI selector path); verified against patchloom 0.15.x.
child.stdin!.write(JSON.stringify({
jsonrpc: "2.0", id: 3, method: "tools/call",
params: {
Expand Down
17 changes: 17 additions & 0 deletions test/unit/quickActions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
buildDocPrependQuickAction,
buildDocSetQuickAction,
buildMdInsertAfterHeadingQuickAction,
buildMdInsertAfterSectionQuickAction,
buildMdInsertBeforeHeadingQuickAction,
buildMdReplaceSectionQuickAction,
buildMdTableAppendQuickAction,
Expand Down Expand Up @@ -422,6 +423,22 @@ test("buildMdInsertAfterHeadingQuickAction builds a md insert-after-heading comm
]);
});

test("buildMdInsertAfterSectionQuickAction builds a md insert-after-section command", () => {
const action = buildMdInsertAfterSectionQuickAction(
"/workspace/demo/README.md",
"## Config",
"## FAQ\n\nCommon questions."
);

assert.equal(action.title, 'Insert after section "## Config" in README.md');
assert.deepEqual(action.targetArgIndices, [2]);
assert.deepEqual(action.args, [
"md", "insert-after-section", "/workspace/demo/README.md",
"--heading", "## Config",
"--content", "## FAQ\n\nCommon questions."
]);
});

test("buildMdInsertBeforeHeadingQuickAction builds a md insert-before-heading command", () => {
const action = buildMdInsertBeforeHeadingQuickAction("/workspace/demo/CHANGELOG.md", "## v1.0.0", "## v1.1.0\n\n- New feature");

Expand Down
14 changes: 14 additions & 0 deletions walkthrough/install.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,20 @@ and installation.
brew install patchloom/tap/patchloom
```

## npm

```bash
npm install -g patchloom
# or one-shot: npx patchloom --version
```

## Scoop (Windows)

```bash
scoop bucket add patchloom https://github.com/patchloom/scoop-bucket
scoop install patchloom
```

## Cargo

```bash
Expand Down
Loading