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
16 changes: 9 additions & 7 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 (26 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
Expand Down
12 changes: 9 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
|--------|-------------|
Expand All @@ -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

Expand Down Expand Up @@ -116,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
Expand All @@ -124,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. |

---

Expand All @@ -141,7 +147,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.
Expand Down Expand Up @@ -176,7 +182,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

Expand Down
50 changes: 35 additions & 15 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/binary/patchloom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
11 changes: 4 additions & 7 deletions src/commands/managedInstall.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.";
Expand All @@ -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":
Expand Down Expand Up @@ -74,7 +71,7 @@ export async function installPatchloom(): Promise<void> {
`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}`);
}
Expand Down Expand Up @@ -149,7 +146,7 @@ export async function updatePatchloom(): Promise<void> {
`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}`);
}
Expand Down Expand Up @@ -201,7 +198,7 @@ export async function reinstallPatchloom(): Promise<void> {
`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}`);
}
Expand Down
61 changes: 61 additions & 0 deletions src/commands/quickActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -699,6 +699,54 @@ export async function runQuickAction(): Promise<void> {
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 <file> --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",
Expand Down Expand Up @@ -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",
Expand Down
28 changes: 28 additions & 0 deletions test/unit/quickActions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
buildMdReplaceSectionQuickAction,
buildMdTableAppendQuickAction,
buildMdUpsertBulletQuickAction,
buildPatchMergeQuickAction,
buildReplaceQuickAction,
buildSearchQuickAction,
buildTidyQuickAction,
Expand Down Expand Up @@ -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");
});
2 changes: 2 additions & 0 deletions walkthrough/configure-mcp.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Loading