From 5f926da7e5c9dca56df1c88b6032adac0b8288c9 Mon Sep 17 00:00:00 2001 From: Sebastien Tardif Date: Wed, 17 Jun 2026 20:29:07 -0700 Subject: [PATCH 1/5] feat: align extension with patchloom CLI v0.2.0 - Update PATCHLOOM_DOCS_URL to https://patchloom.github.io/patchloom/ - Add patch merge Quick Action (three-way merge via patchloom patch merge) - Support --allow-conflicts flag and exit code 8 (CONFLICTS) handling - Add 3 unit tests for buildPatchMergeQuickAction - Update README: CLI version recommendation, Quick Actions table - Update walkthrough with link to new MCP setup guide - Update AGENTS.md test count and quickActions description Signed-off-by: Sebastien Tardif --- AGENTS.md | 4 +-- README.md | 7 ++-- src/binary/patchloom.ts | 2 +- src/commands/quickActions.ts | 61 ++++++++++++++++++++++++++++++++++ test/unit/quickActions.test.ts | 28 ++++++++++++++++ walkthrough/configure-mcp.md | 2 ++ 6 files changed, 98 insertions(+), 6 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 95d7bf0..60321ac 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -33,7 +33,7 @@ src/ configureMcp.ts Configure MCP command: multi-target MCP config injection initializeProject.ts Initialize Project command: generate/diff AGENTS.md managedInstall.ts Managed Install commands: install, update, reinstall Patchloom binary - quickActions.ts Quick Action command: replace, tidy, doc set, search, create, doc get + quickActions.ts Quick Action command: replace, tidy, doc set, search, create, doc get, patch merge batchApply.ts Batch Apply command: atomic multi-operation plan via JSON setupWorkspace.ts Setup Workspace command: guided readiness walkthrough showStatus.ts Show Status command: diagnostics display @@ -53,7 +53,7 @@ test/ mcpConfig.test.ts MCP config with real temp directories (9 tests) outputChannel.test.ts Output channel logging wrapper (13 tests) patchloomCli.test.ts Patchloom CLI integration tests with real binary (23 tests) - quickActions.test.ts Quick action command building, path containment (26 tests) + quickActions.test.ts Quick action command building, path containment, patch merge (29 tests) downloadIntegration.test.ts HTTP download, redirect, streaming SHA-256 (9 tests) suite/ index.ts VS Code extension integration tests diff --git a/README.md b/README.md index 01bcceb..ed19618 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ Click it to see full diagnostics, including per-editor MCP configuration status ### Quick actions -`Patchloom: Quick Action` opens an interactive picker with six operations: +`Patchloom: Quick Action` opens an interactive picker with structured editing operations: | Action | What it does | |--------|-------------| @@ -85,6 +85,7 @@ Click it to see full diagnostics, including per-editor MCP configuration status | **Search text** | Find pattern matches across workspace files (results in output channel) | | **Create file** | Scaffold a new file and open it in the editor | | **Read structured value** | Read a JSON/YAML/TOML key and copy to clipboard | +| **Merge patch (three-way)** | Apply a stale patch using three-way merge (v0.2.0+) | ### Batch operations @@ -141,7 +142,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 `0.1.0` or newer. +Run `Patchloom: Open Releases` to download the latest release. The extension requires 0.1.0 or newer; 0.2.0 is recommended. **MCP config not injected** Run `Patchloom: Configure MCP` and select the target editor config. @@ -176,7 +177,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.1.0 or newer +- [Patchloom CLI](https://github.com/patchloom/patchloom) 0.1.0 or newer (0.2.0+ recommended for patch merge and strict transactions) ## Contributing diff --git a/src/binary/patchloom.ts b/src/binary/patchloom.ts index 25739b4..d7c1f24 100644 --- a/src/binary/patchloom.ts +++ b/src/binary/patchloom.ts @@ -15,7 +15,7 @@ const execFileAsync = promisify(execFile); export const MINIMUM_SUPPORTED_PATCHLOOM_VERSION = "0.1.0"; export const PATCHLOOM_RELEASES_URL = "https://github.com/patchloom/patchloom/releases"; -export const PATCHLOOM_DOCS_URL = "https://github.com/patchloom/patchloom#readme"; +export const PATCHLOOM_DOCS_URL = "https://patchloom.github.io/patchloom/"; export type PatchloomSource = "setting" | "path" | "managed" | "missing"; export type PatchloomCompatibility = "supported" | "unsupported" | "unknown"; diff --git a/src/commands/quickActions.ts b/src/commands/quickActions.ts index 9ad5f3d..b4ed11b 100644 --- a/src/commands/quickActions.ts +++ b/src/commands/quickActions.ts @@ -699,6 +699,54 @@ export async function runQuickAction(): Promise { await previewAndMaybeApply(binaryPath, target, buildMdReplaceSectionQuickAction(target.absolutePath, heading, content)); } }, + { + label: "Merge patch (three-way)", + description: "Apply a stale patch using three-way merge", + detail: "Builds `patchloom patch merge --apply [--allow-conflicts]`", + run: async () => { + const folder = await activeWorkspaceFolder({ + promptIfMany: true, + placeHolder: "Select workspace folder for Patchloom patch merge" + }); + if (!folder) { + await vscode.window.showWarningMessage("Open a workspace folder before running Patchloom patch merge."); + return; + } + + const patchUri = await vscode.window.showOpenDialog({ + canSelectMany: false, + defaultUri: folder.uri, + filters: { "Patch files": ["patch", "diff"], "All files": ["*"] }, + openLabel: "Select Patch" + }); + if (!patchUri || patchUri.length === 0) { + return; + } + + const allowConflicts = await vscode.window.showQuickPick([ + { label: "Fail on conflicts", description: "Recommended", picked: true, allow: false }, + { label: "Allow conflict markers", description: "Write <<<<<<< / >>>>>>> markers into files", allow: true } + ], { placeHolder: "How should unresolved conflicts be handled?" }); + if (!allowConflicts) { + return; + } + + const action = buildPatchMergeQuickAction(patchUri[0].fsPath, allowConflicts.allow); + const result = await executePatchloom(binaryPath, action.args, folder.uri.fsPath); + const log = getPatchloomLog(); + + if (result.exitCode === 8) { + log?.show(); + await vscode.window.showWarningMessage("Patch merge completed with unresolved conflicts. Check the output for details."); + } else if (result.exitCode !== 0) { + log?.show(); + await vscode.window.showErrorMessage(`Patch merge failed: ${formatCliOutput(result)}`); + } else { + log?.show(); + await vscode.window.showInformationMessage("Patch merged successfully."); + } + } + }, { label: "Undo last change", description: "Restore files from the last patchloom backup", @@ -921,6 +969,19 @@ export function buildMdInsertBeforeHeadingQuickAction(targetPath: string, headin }; } +export function buildPatchMergeQuickAction(patchPath: string, allowConflicts: boolean): PlannedQuickAction { + const args: string[] = ["patch", "merge", patchPath, "--apply"]; + if (allowConflicts) { + args.push("--allow-conflicts"); + } + return { + title: `Merge patch ${path.basename(patchPath)}`, + targetPath: patchPath, + targetArgIndices: [2], + args + }; +} + export function buildUndoQuickAction(workspacePath: string): PlannedQuickAction { return { title: "Undo last patchloom change", diff --git a/test/unit/quickActions.test.ts b/test/unit/quickActions.test.ts index e75aabc..7d9e2b3 100644 --- a/test/unit/quickActions.test.ts +++ b/test/unit/quickActions.test.ts @@ -15,6 +15,7 @@ import { buildMdReplaceSectionQuickAction, buildMdTableAppendQuickAction, buildMdUpsertBulletQuickAction, + buildPatchMergeQuickAction, buildReplaceQuickAction, buildSearchQuickAction, buildTidyQuickAction, @@ -406,3 +407,30 @@ test("retargetQuickAction works with md commands", () => { assert.equal(retargeted.args[0], "md"); assert.equal(retargeted.args[1], "table-append"); }); + +// --- patch merge Quick Action (v0.2.0) --- + +test("buildPatchMergeQuickAction builds a patch merge command", () => { + const action = buildPatchMergeQuickAction("/workspace/demo/changes.patch", false); + + assert.equal(action.title, "Merge patch changes.patch"); + assert.deepEqual(action.targetArgIndices, [2]); + assert.deepEqual(action.args, ["patch", "merge", "/workspace/demo/changes.patch", "--apply"]); +}); + +test("buildPatchMergeQuickAction includes allow-conflicts flag when enabled", () => { + const action = buildPatchMergeQuickAction("/workspace/demo/stale.diff", true); + + assert.equal(action.title, "Merge patch stale.diff"); + assert.deepEqual(action.targetArgIndices, [2]); + assert.deepEqual(action.args, ["patch", "merge", "/workspace/demo/stale.diff", "--apply", "--allow-conflicts"]); +}); + +test("retargetQuickAction works with patch merge command", () => { + const action = buildPatchMergeQuickAction("/workspace/demo/fix.patch", false); + const retargeted = retargetQuickAction(action, "/tmp/preview/fix.patch"); + + assert.equal(retargeted.args[2], "/tmp/preview/fix.patch"); + assert.equal(retargeted.args[0], "patch"); + assert.equal(retargeted.args[1], "merge"); +}); diff --git a/walkthrough/configure-mcp.md b/walkthrough/configure-mcp.md index 19f1988..1a4ad7c 100644 --- a/walkthrough/configure-mcp.md +++ b/walkthrough/configure-mcp.md @@ -16,3 +16,5 @@ for your editor. The command detects which editors are available and configures them automatically. + +See the [MCP setup guide](https://patchloom.github.io/patchloom/getting-started/mcp-setup.html) for advanced configuration. From 987c1aab4c9f5ca13b43b0a425f20714e1df4c47 Mon Sep 17 00:00:00 2001 From: Sebastien Tardif Date: Wed, 17 Jun 2026 20:34:28 -0700 Subject: [PATCH 2/5] refactor: replace local errorMessage with shared formatError managedInstall.ts had a local errorMessage(error) helper that duplicated formatError from util.ts but without the edge-case handling (empty messages, toString: undefined). Replace all 6 call sites with the shared helper. Signed-off-by: Sebastien Tardif --- src/commands/managedInstall.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/commands/managedInstall.ts b/src/commands/managedInstall.ts index 61dfee1..990f7e9 100644 --- a/src/commands/managedInstall.ts +++ b/src/commands/managedInstall.ts @@ -9,6 +9,7 @@ import { } from "../install/managed.js"; import { getPatchloomLog } from "../logging/outputChannel.js"; import { refreshStatusBar } from "../status/statusBar.js"; +import { formatError } from "../util.js"; const MANAGED_INSTALL_UNAVAILABLE = "Managed install is not available: extension storage path is not set."; @@ -19,10 +20,6 @@ const PROGRESS_OPTIONS: vscode.ProgressOptions = { cancellable: false, }; -function errorMessage(error: unknown): string { - return error instanceof Error ? error.message : String(error); -} - function stageLabel(stage: ManagedInstallStage): string { switch (stage) { case "fetching-version": @@ -74,7 +71,7 @@ export async function installPatchloom(): Promise { `Patchloom ${result.version} installed successfully.` ); } catch (error) { - const message = errorMessage(error); + const message = formatError(error); log?.log(`Managed install failed: ${message}`); await vscode.window.showErrorMessage(`Failed to install Patchloom: ${message}`); } @@ -149,7 +146,7 @@ export async function updatePatchloom(): Promise { `Patchloom updated to ${result.version}.` ); } catch (error) { - const message = errorMessage(error); + const message = formatError(error); log?.log(`Managed update failed: ${message}`); await vscode.window.showErrorMessage(`Failed to update Patchloom: ${message}`); } @@ -201,7 +198,7 @@ export async function reinstallPatchloom(): Promise { `Patchloom ${result.version} reinstalled successfully.` ); } catch (error) { - const message = errorMessage(error); + const message = formatError(error); log?.log(`Managed reinstall failed: ${message}`); await vscode.window.showErrorMessage(`Failed to reinstall Patchloom: ${message}`); } From 463f431755affe11b1f423d1198871f0cb5880dc Mon Sep 17 00:00:00 2001 From: Sebastien Tardif Date: Wed, 17 Jun 2026 20:35:59 -0700 Subject: [PATCH 3/5] docs: add missing command and settings to README - Add 'Open Documentation' to the Commands table - Add 4 missing settings: enable, trace.server, env, managedInstall.autoUpdate Signed-off-by: Sebastien Tardif --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index ed19618..4c9b7d9 100644 --- a/README.md +++ b/README.md @@ -117,6 +117,7 @@ The extension detects outdated CLI builds and warns with upgrade guidance. It re | `Patchloom: Update Patchloom` | Update a managed Patchloom install to the latest release | | `Patchloom: Reinstall Patchloom` | Re-download and reinstall the managed Patchloom binary | | `Patchloom: Open Settings` | Jump to Patchloom extension settings | +| `Patchloom: Open Documentation` | Open the Patchloom documentation site in a browser | | `Patchloom: Open Releases` | Open the Patchloom releases page in a browser | ## Settings @@ -125,6 +126,10 @@ The extension detects outdated CLI builds and warns with upgrade guidance. It re |---------|---------|-------------| | `patchloom.path` | `""` | Absolute path to the Patchloom binary. When empty, the extension searches `PATH` and then the managed install location. | | `patchloom.showStatusBar` | `true` | Show a status bar item reporting whether Patchloom is available. | +| `patchloom.enable` | `true` | Enable the extension. When disabled, the status bar is hidden and background checks are skipped. | +| `patchloom.trace.server` | `"off"` | Trace level for CLI output (`off`, `messages`, `verbose`). | +| `patchloom.env` | `{}` | Additional environment variables passed to the CLI (e.g., `{"PATCHLOOM_LOG": "debug"}`). | +| `patchloom.managedInstall.autoUpdate` | `true` | Automatically check for CLI updates on activation. | --- From 00e50d627ea2403d359f00e340f181ce9e4be5f8 Mon Sep 17 00:00:00 2001 From: Sebastien Tardif Date: Wed, 17 Jun 2026 20:37:10 -0700 Subject: [PATCH 4/5] chore: update transitive deps and fix markdown-it vulnerability npm audit fix resolves GHSA-6v5v-wf23-fmfq (markdown-it quadratic complexity DoS in smartquotes). Zero vulnerabilities remaining. Signed-off-by: Sebastien Tardif --- package-lock.json | 50 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 35 insertions(+), 15 deletions(-) diff --git a/package-lock.json b/package-lock.json index 49d43c1..ac59718 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2825,17 +2825,17 @@ } }, "node_modules/form-data": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", - "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", "dev": true, "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" + "hasown": "^2.0.4", + "mime-types": "^2.1.35" }, "engines": { "node": ">= 6" @@ -3168,9 +3168,9 @@ } }, "node_modules/hasown": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", - "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", "dev": true, "license": "MIT", "dependencies": { @@ -3736,10 +3736,20 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz", + "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -4134,15 +4144,25 @@ } }, "node_modules/markdown-it": { - "version": "14.1.1", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.1.tgz", - "integrity": "sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==", + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.2.0.tgz", + "integrity": "sha512-1TGiQiJVRQ3NPmZH6sx5Cfnmg6GQm9jvC1ch4TK511NjSJvjzKLzn5pPfZRNZkRPZP0HqCioSndqH8v2nRaWVQ==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/markdown-it" + } + ], "license": "MIT", "dependencies": { "argparse": "^2.0.1", "entities": "^4.4.0", - "linkify-it": "^5.0.0", + "linkify-it": "^5.0.1", "mdurl": "^2.0.0", "punycode.js": "^2.3.1", "uc.micro": "^2.1.0" From e58a9fbf94690f3584a26ebafdd1c27b3a38e684 Mon Sep 17 00:00:00 2001 From: Sebastien Tardif Date: Wed, 17 Jun 2026 20:37:58 -0700 Subject: [PATCH 5/5] docs: update AGENTS.md test counts and add missing test files - Fix stale test counts across 6 files (binary 48->53, initializeProject 19->23, managedLifecycle 12->22, outputChannel 13->10, patchloomCli 23->29, quickActions 29->46) - Add missing propertyBased.test.ts (13 tests) and verifyMcp.test.ts (15 tests) entries Signed-off-by: Sebastien Tardif --- AGENTS.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 60321ac..a56f199 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -46,14 +46,16 @@ src/ test/ unit/ Unit tests (node:test, dependency-injected, no VS Code API) batchApply.test.ts Batch template and operation count parsing (10 tests) - binary.test.ts Binary discovery, managed install, compatibility, workspace env (48 tests) + binary.test.ts Binary discovery, managed install, compatibility, workspace env (53 tests) binaryDiscovery.test.ts Real executable discovery on PATH (13 tests) - initializeProject.test.ts Status display, agents file classification, formatError (19 tests) - managedLifecycle.test.ts Managed install with real file I/O (12 tests) + initializeProject.test.ts Status display, agents file classification, formatError (23 tests) + managedLifecycle.test.ts Managed install with real file I/O (22 tests) mcpConfig.test.ts MCP config with real temp directories (9 tests) - outputChannel.test.ts Output channel logging wrapper (13 tests) - patchloomCli.test.ts Patchloom CLI integration tests with real binary (23 tests) - quickActions.test.ts Quick action command building, path containment, patch merge (29 tests) + outputChannel.test.ts Output channel logging wrapper (10 tests) + patchloomCli.test.ts Patchloom CLI integration tests with real binary (29 tests) + propertyBased.test.ts Property-based tests with fast-check (13 tests) + quickActions.test.ts Quick action command building, path containment, patch merge (46 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/ index.ts VS Code extension integration tests