diff --git a/AGENTS.md b/AGENTS.md index b2d7712..6564ee8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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) @@ -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/ diff --git a/README.md b/README.md index efb0b11..70fc892 100644 --- a/README.md +++ b/README.md @@ -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** @@ -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 @@ -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. @@ -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 diff --git a/src/commands/batchApply.ts b/src/commands/batchApply.ts index b488861..db0ffa8 100644 --- a/src/commands/batchApply.ts +++ b/src/commands/batchApply.ts @@ -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"); diff --git a/src/commands/quickActions.ts b/src/commands/quickActions.ts index 3ed5880..d2e0d75 100644 --- a/src/commands/quickActions.ts +++ b/src/commands/quickActions.ts @@ -584,6 +584,44 @@ export async function runQuickAction(): Promise { 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 --heading --content `", + 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", @@ -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)}`, diff --git a/test/unit/batchApply.test.ts b/test/unit/batchApply.test.ts index 6c832d1..b12b056 100644 --- a/test/unit/batchApply.test.ts +++ b/test/unit/batchApply.test.ts @@ -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", () => { @@ -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"]); }); diff --git a/test/unit/patchloomCli.test.ts b/test/unit/patchloomCli.test.ts index 382c67c..04d15c0 100644 --- a/test/unit/patchloomCli.test.ts +++ b/test/unit/patchloomCli.test.ts @@ -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: { diff --git a/test/unit/quickActions.test.ts b/test/unit/quickActions.test.ts index 8116d7f..8a8d193 100644 --- a/test/unit/quickActions.test.ts +++ b/test/unit/quickActions.test.ts @@ -13,6 +13,7 @@ import { buildDocPrependQuickAction, buildDocSetQuickAction, buildMdInsertAfterHeadingQuickAction, + buildMdInsertAfterSectionQuickAction, buildMdInsertBeforeHeadingQuickAction, buildMdReplaceSectionQuickAction, buildMdTableAppendQuickAction, @@ -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"); diff --git a/walkthrough/install.md b/walkthrough/install.md index a1dfb72..dd3e0bc 100644 --- a/walkthrough/install.md +++ b/walkthrough/install.md @@ -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