From dc32f4c7818fab0167d2b6ef28f94625a03e39ec Mon Sep 17 00:00:00 2001 From: Sebastien Tardif Date: Mon, 10 Aug 2026 08:48:26 -0400 Subject: [PATCH 1/2] 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 --- AGENTS.md | 4 +-- README.md | 27 ++++++++++++------- src/commands/batchApply.ts | 2 ++ src/commands/configureMcp.ts | 2 +- src/util.ts | 20 +++++++++++--- test/unit/batchApply.test.ts | 25 +++++++++++++----- test/unit/initializeProject.test.ts | 41 +++++++++++++++++++++++++++++ walkthrough/install.md | 16 ++++++----- 8 files changed, 108 insertions(+), 29 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 2839f73..173a2a9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -48,10 +48,10 @@ 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 (15 tests) + batchApply.test.ts Batch template and operation count parsing (16 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 (34 tests) + initializeProject.test.ts Status display, agents file classification, formatError (37 tests) managedLifecycle.test.ts Managed install with real file I/O (22 tests) mcpConfig.test.ts MCP config with real temp directories (12 tests) outputChannel.test.ts Output channel logging wrapper (10 tests) diff --git a/README.md b/README.md index f511343..c4f954c 100644 --- a/README.md +++ b/README.md @@ -32,9 +32,9 @@ Or search for **Patchloom** in the Extensions view (`Ctrl+Shift+X` / `Cmd+Shift+ curl -LsSf https://github.com/patchloom/patchloom/releases/latest/download/patchloom-installer.sh | sh # shell script cargo install patchloom # from source scoop bucket add patchloom https://github.com/patchloom/scoop-bucket - scoop install patchloom # Windows (Scoop) - choco install patchloom # Windows (Chocolatey; first listing may wait on moderation) - winget install Patchloom.Patchloom # Windows (WinGet; when the community PR is approved) + scoop install patchloom # Windows (Scoop; preferred Windows channel) + winget install Patchloom.Patchloom # Windows (WinGet; run winget source update if the package is missing) + choco install patchloom # Windows (Chocolatey; often lags moderation) ``` 2. Open a project and run **Patchloom: Setup Workspace** @@ -66,7 +66,7 @@ Run `Patchloom: Setup Workspace` to walk through everything your project needs: When configuring, pick **Full tool inventory** (default) or **Core pack**. Core sets `PATCHLOOM_MCP_SURFACE=core` on the server entry. -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. +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. ### Status bar @@ -118,7 +118,7 @@ Workspace Quick Actions and Batch Apply pass `--contain` so CLI paths stay insid ### Batch operations -`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. +`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. ### Output channel @@ -176,23 +176,32 @@ 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.24.0 is recommended. +Run `Patchloom: Open Releases` to download the latest release. The extension requires 0.3.0 or newer; **0.28.0** is recommended. **Path rejected by workspace guard** 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. +**Empty or blank path** +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. + **Batch replace shape** 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. **Create or rename destination already exists** 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. -**Binary or invalid UTF-8 target** -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. +**Binary, invalid UTF-8, or non-regular file** +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. **Fuzzy match span refused** 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. +**Doc selector needs multi-match op** +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`). + +**Ambiguous markdown heading** +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`). + **MCP config not injected** Run `Patchloom: Configure MCP` and select the target editor config. @@ -226,7 +235,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.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) +- [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) ## Contributing diff --git a/src/commands/batchApply.ts b/src/commands/batchApply.ts index 4665208..fac536f 100644 --- a/src/commands/batchApply.ts +++ b/src/commands/batchApply.ts @@ -6,11 +6,13 @@ import { getPatchloomLog } from "../logging/outputChannel.js"; import { activeWorkspaceFolder } from "../workspace/readiness.js"; // Batch replace is PATH OLD NEW (not CLI `replace OLD --new NEW path`). See CLI 0.18+ batch --help. +// doc.update is the multi-match sibling of doc.set (CLI 0.27+ suggested_op hints this). 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", "replace src/example.ts \"anchor_line\" --insert-after=\"new sibling line\"", "doc.set package.json version \"2.0.0\"", + "doc.update data.json \"items[*].enabled\" true", "doc.merge multi-doc.yaml 0 \"{\\\"debug\\\": true}\"", "file.append src/example.ts \"new appended line\"", "md.insert_after_section README.md \"## Config\" \"## FAQ\"", diff --git a/src/commands/configureMcp.ts b/src/commands/configureMcp.ts index 0156eee..c89b75d 100644 --- a/src/commands/configureMcp.ts +++ b/src/commands/configureMcp.ts @@ -47,7 +47,7 @@ export async function configureMcp(): Promise { [ { label: "Full tool inventory", - description: "Default (58 tools on CLI 0.24+)", + description: "Default (58 tools on CLI 0.24+; verified on 0.28)", surface: "full" as const }, { diff --git a/src/util.ts b/src/util.ts index 8aa0c0c..c5cf291 100644 --- a/src/util.ts +++ b/src/util.ts @@ -12,7 +12,8 @@ export function formatError(error: unknown): string { /** * Prefer machine-readable CLI JSON error envelopes (error_kind + error) when * present so agents and the UI surface kinds like guard_rejected (CLI 0.18+) - * instead of a flattened multi-line dump. + * instead of a flattened multi-line dump. On CLI 0.27+, append suggested_op + * when present so users can retry with doc.update / doc.delete_where. */ export function formatCliOutput(result: { exitCode: number; stdout: string; stderr: string }): string { const jsonError = extractCliJsonError(result.stdout) ?? extractCliJsonError(result.stderr); @@ -34,19 +35,30 @@ function extractCliJsonError(stream: string): string | undefined { return undefined; } try { - const parsed = JSON.parse(trimmed) as { error?: unknown; error_kind?: unknown }; + const parsed = JSON.parse(trimmed) as { + error?: unknown; + error_kind?: unknown; + suggested_op?: unknown; + }; if (typeof parsed.error !== "string" || parsed.error.length === 0) { return undefined; } // CLI often prefixes "guard_rejected: …" already; avoid "kind: kind: …". + let message: string; if ( typeof parsed.error_kind === "string" && parsed.error_kind.length > 0 && !parsed.error.startsWith(`${parsed.error_kind}:`) ) { - return `${parsed.error_kind}: ${parsed.error}`; + message = `${parsed.error_kind}: ${parsed.error}`; + } else { + message = parsed.error; } - return parsed.error; + // CLI 0.27+: fail-closed doc navigation may hint the multi-match sibling. + if (typeof parsed.suggested_op === "string" && parsed.suggested_op.length > 0) { + message = `${message} (suggested_op: ${parsed.suggested_op})`; + } + return message; } catch { return undefined; } diff --git a/test/unit/batchApply.test.ts b/test/unit/batchApply.test.ts index ad68cb0..47ab25b 100644 --- a/test/unit/batchApply.test.ts +++ b/test/unit/batchApply.test.ts @@ -6,19 +6,20 @@ import { parseBatchOperationCount } from "../../src/commands/batchApply.js"; -test("buildBatchTemplate returns line-oriented format with eight operations", () => { +test("buildBatchTemplate returns line-oriented format with nine operations", () => { const template = buildBatchTemplate(); const lines = template.split("\n").filter((line) => line.trim().length > 0); - assert.equal(lines.length, 8); + assert.equal(lines.length, 9); assert.ok(lines[0].startsWith("replace "), "first line should be a replace operation"); assert.ok(lines[1].startsWith("replace ") && lines[1].includes("--fuzzy"), "second line should be fuzzy replace"); assert.ok(lines[2].startsWith("replace ") && lines[2].includes("--insert-after"), "third line should be insert-after"); assert.ok(lines[3].startsWith("doc.set "), "fourth line should be a doc.set operation"); - assert.ok(lines[4].startsWith("doc.merge "), "fifth line should be multi-doc doc.merge"); - assert.ok(lines[5].startsWith("file.append "), "sixth line should be a file.append operation"); - assert.ok(lines[6].startsWith("md.insert_after_section "), "seventh line should be md.insert_after_section"); - assert.ok(lines[7].startsWith("tidy.fix "), "eighth line should be a tidy.fix operation"); + assert.ok(lines[4].startsWith("doc.update "), "fifth line should be multi-match doc.update"); + assert.ok(lines[5].startsWith("doc.merge "), "sixth line should be multi-doc doc.merge"); + assert.ok(lines[6].startsWith("file.append "), "seventh line should be a file.append operation"); + assert.ok(lines[7].startsWith("md.insert_after_section "), "eighth line should be md.insert_after_section"); + assert.ok(lines[8].startsWith("tidy.fix "), "ninth line should be a tidy.fix operation"); }); test("buildBatchTemplate ends with a newline", () => { @@ -119,6 +120,18 @@ test("buildBatchTemplate includes replace --insert-after example (CLI 0.16)", () ); }); +test("buildBatchTemplate includes doc.update multi-match example (CLI 0.27+ suggested_op sibling)", () => { + const lines = buildBatchTemplate().split("\n"); + const updateLine = lines.find((l) => l.startsWith("doc.update ")); + assert.ok(updateLine, "template should contain a doc.update line"); + assert.match( + updateLine, + /doc\.update \S+ ".+" \S+/, + "doc.update should have path, selector, and value" + ); + assert.match(updateLine, /\[\*\]|\[.+=.+\]/, "selector should use wildcard or predicate form"); +}); + test("buildBatchApplyArgs prefixes global --contain before batch --apply", () => { assert.deepEqual(buildBatchApplyArgs(), ["--contain", "batch", "--apply"]); }); diff --git a/test/unit/initializeProject.test.ts b/test/unit/initializeProject.test.ts index f8ab0ae..8afbfc2 100644 --- a/test/unit/initializeProject.test.ts +++ b/test/unit/initializeProject.test.ts @@ -137,6 +137,47 @@ test("formatCliOutput surfaces fuzzy_span_suspicious kind (CLI 0.22+)", () => { ); }); +test("formatCliOutput surfaces empty path invalid_input (CLI 0.28+)", () => { + const stdout = JSON.stringify({ + ok: false, + error: "path must not be empty", + error_kind: "invalid_input", + applied: false + }); + assert.equal( + formatCliOutput({ exitCode: 1, stdout, stderr: "" }), + "invalid_input: path must not be empty" + ); +}); + +test("formatCliOutput appends suggested_op when present (CLI 0.27+)", () => { + const stdout = JSON.stringify({ + ok: false, + error: + "selector uses wildcard/predicate, which is not valid for doc.set (single path only)", + error_kind: "invalid_input", + suggested_op: "doc.update", + applied: false + }); + assert.equal( + formatCliOutput({ exitCode: 1, stdout, stderr: "" }), + "invalid_input: selector uses wildcard/predicate, which is not valid for doc.set (single path only) (suggested_op: doc.update)" + ); +}); + +test("formatCliOutput surfaces ambiguous kind (CLI 0.25+)", () => { + const stdout = JSON.stringify({ + ok: false, + error: 'ambiguous heading: "H" matches 2 times', + error_kind: "ambiguous", + applied: false + }); + assert.equal( + formatCliOutput({ exitCode: 1, stdout, stderr: "" }), + 'ambiguous: ambiguous heading: "H" matches 2 times' + ); +}); + test("classifyAgentsFile returns missing when AGENTS.md does not exist", () => { assert.equal(classifyAgentsFile(undefined, "# Rules\n"), "missing"); }); diff --git a/walkthrough/install.md b/walkthrough/install.md index 1674a87..e840023 100644 --- a/walkthrough/install.md +++ b/walkthrough/install.md @@ -21,28 +21,30 @@ npm install -g patchloom # or one-shot: npx patchloom --version ``` -## Scoop (Windows) +## Scoop (Windows, preferred) ```bash scoop bucket add patchloom https://github.com/patchloom/scoop-bucket scoop install patchloom ``` -## Chocolatey (Windows) +Scoop tracks GitHub Releases promptly. Prefer it when you manage Windows installs yourself. + +## WinGet (Windows) ```bash -choco install patchloom +winget install Patchloom.Patchloom ``` -First listing on the community repository may wait on Chocolatey moderation. +After a new release, you may need `winget source update` before the package appears. Microsoft publish can lag the GitHub tag by a short window. -## WinGet (Windows) +## Chocolatey (Windows) ```bash -winget install Patchloom.Patchloom +choco install patchloom ``` -Available after the community package PR is approved. +Community moderation often lags Scoop and GitHub portable assets. ## Cargo From 18a3178406cc09a23ac0375155eda6786d77e25a Mon Sep 17 00:00:00 2001 From: Sebastien Tardif Date: Mon, 10 Aug 2026 08:52:44 -0400 Subject: [PATCH 2/2] 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 --- AGENTS.md | 2 +- README.md | 17 +++++--- package.json | 2 +- src/binary/patchloom.ts | 16 +++++++- src/status/details.ts | 14 +++++++ test/unit/initializeProject.test.ts | 60 ++++++++++++++++++++++++++++- walkthrough/install.md | 42 ++++++++------------ 7 files changed, 117 insertions(+), 36 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 173a2a9..c44be2d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -51,7 +51,7 @@ test/ batchApply.test.ts Batch template and operation count parsing (16 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 (37 tests) + initializeProject.test.ts Status display, agents file classification, formatError (39 tests) managedLifecycle.test.ts Managed install with real file I/O (22 tests) mcpConfig.test.ts MCP config with real temp directories (12 tests) outputChannel.test.ts Output channel logging wrapper (10 tests) diff --git a/README.md b/README.md index c4f954c..b28d06a 100644 --- a/README.md +++ b/README.md @@ -25,17 +25,16 @@ Or search for **Patchloom** in the Extensions view (`Ctrl+Shift+X` / `Cmd+Shift+ ## Get started in 30 seconds -1. Install the [Patchloom CLI](https://github.com/patchloom/patchloom) (or run **Patchloom: Install Patchloom** from the command palette) +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) ```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 bucket add patchloom https://github.com/patchloom/scoop-bucket - scoop install patchloom # Windows (Scoop; preferred Windows channel) - winget install Patchloom.Patchloom # Windows (WinGet; run winget source update if the package is missing) - choco install patchloom # Windows (Chocolatey; often lags moderation) + scoop install patchloom # Windows (Scoop; preferred PATH channel) ``` + 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. 2. Open a project and run **Patchloom: Setup Workspace**

@@ -175,8 +174,14 @@ The extension detects outdated CLI builds and warns with upgrade guidance. It re **Patchloom not found** 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.28.0** is recommended. +**CLI compatibility warning / upgrade path** +The extension requires Patchloom **0.3.0** or newer; **0.28.0** is recommended. Prefer channels that track GitHub Releases the same day: + +1. **Patchloom: Update Patchloom** (or **Install Patchloom**) for the extension managed install (checksum-verified download from GitHub Releases) +2. **Scoop** on Windows: `scoop update patchloom` after `scoop install patchloom` +3. Homebrew / npm / cargo / the official installer script on macOS and Linux + +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. **Path rejected by workspace guard** 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. diff --git a/package.json b/package.json index bef9cb6..0c4ba6a 100644 --- a/package.json +++ b/package.json @@ -162,7 +162,7 @@ { "id": "installCli", "title": "Install the Patchloom CLI", - "description": "Install the Patchloom CLI via the managed installer, Homebrew, or cargo.\n\n[Install Patchloom](command:patchloom.installBinary)", + "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)", "media": { "markdown": "walkthrough/install.md" }, diff --git a/src/binary/patchloom.ts b/src/binary/patchloom.ts index 5cc53e6..07dad45 100644 --- a/src/binary/patchloom.ts +++ b/src/binary/patchloom.ts @@ -182,11 +182,23 @@ export async function ensurePatchloomReadyOrNotify( if (patchloomNeedsUpgrade(status)) { const vscode = await import("vscode"); + // Prefer managed install/update (GitHub Releases) over lagging community packages. + const canUpdateManaged = status.source === "managed" || status.managedInstall?.exists === true; + const canInstallManaged = status.managedInstall !== undefined; + const primaryAction = canUpdateManaged + ? "Update Patchloom" + : canInstallManaged + ? "Install Patchloom" + : "Open Releases"; const choice = await vscode.window.showWarningMessage( `${status.compatibilityMessage}${contextSuffix ? `\n\n${contextSuffix}` : ""}`, - "Open Releases" + primaryAction ); - if (choice === "Open Releases") { + if (choice === "Update Patchloom") { + await vscode.commands.executeCommand("patchloom.updateBinary"); + } else if (choice === "Install Patchloom") { + await vscode.commands.executeCommand("patchloom.installBinary"); + } else if (choice === "Open Releases") { await vscode.commands.executeCommand("patchloom.openPatchloomReleases"); } return null; diff --git a/src/status/details.ts b/src/status/details.ts index b7184e1..da3b133 100644 --- a/src/status/details.ts +++ b/src/status/details.ts @@ -57,6 +57,20 @@ export function preferredStatusAction(status: PatchloomStatus, workspaceReadines } if (patchloomNeedsUpgrade(status)) { + // Prefer managed install/update so users stay on GitHub Releases (not lagging + // community packages such as winget/Chocolatey). + if (status.source === "managed" || status.managedInstall?.exists) { + return { + title: "Update Patchloom", + command: "patchloom.updateBinary" + }; + } + if (status.managedInstall) { + return { + title: "Install Patchloom", + command: "patchloom.installBinary" + }; + } return { title: "Open Releases", command: "patchloom.openPatchloomReleases" diff --git a/test/unit/initializeProject.test.ts b/test/unit/initializeProject.test.ts index 8afbfc2..356843f 100644 --- a/test/unit/initializeProject.test.ts +++ b/test/unit/initializeProject.test.ts @@ -344,7 +344,65 @@ test("buildStatusDetails surfaces managed install failure diagnostics", () => { assert.match(details, /Managed install diagnostic: Checksum mismatch/); }); -test("preferredStatusAction points outdated CLI users to releases", () => { +test("preferredStatusAction points outdated PATH CLI users to managed install", () => { + const action = preferredStatusAction({ + ready: true, + source: "path", + message: "Using Patchloom from PATH.", + binaryPath: "/usr/local/bin/patchloom", + version: "patchloom 0.0.9", + detectedVersion: "0.0.9", + compatibility: "unsupported", + minimumSupportedVersion: MINIMUM_SUPPORTED_PATCHLOOM_VERSION, + compatibilityMessage: `Patchloom 0.0.9 is older than the minimum supported version ${MINIMUM_SUPPORTED_PATCHLOOM_VERSION}.`, + managedInstall: { + exists: false, + binaryPath: "/managed/managed-bin/patchloom", + target: { + platform: "darwin", + arch: "arm64", + targetTriple: "aarch64-apple-darwin", + archiveFormat: ".tar.xz" + } + } + }); + + assert.deepEqual(action, { + title: "Install Patchloom", + command: "patchloom.installBinary" + }); +}); + +test("preferredStatusAction points outdated managed CLI users to update", () => { + const action = preferredStatusAction({ + ready: true, + source: "managed", + message: "Using managed Patchloom install.", + binaryPath: "/managed/managed-bin/patchloom", + version: "patchloom 0.0.9", + detectedVersion: "0.0.9", + compatibility: "unsupported", + minimumSupportedVersion: MINIMUM_SUPPORTED_PATCHLOOM_VERSION, + compatibilityMessage: `Patchloom 0.0.9 is older than the minimum supported version ${MINIMUM_SUPPORTED_PATCHLOOM_VERSION}.`, + managedInstall: { + exists: true, + binaryPath: "/managed/managed-bin/patchloom", + target: { + platform: "darwin", + arch: "arm64", + targetTriple: "aarch64-apple-darwin", + archiveFormat: ".tar.xz" + } + } + }); + + assert.deepEqual(action, { + title: "Update Patchloom", + command: "patchloom.updateBinary" + }); +}); + +test("preferredStatusAction falls back to releases when managed install unavailable", () => { const action = preferredStatusAction({ ready: true, source: "path", diff --git a/walkthrough/install.md b/walkthrough/install.md index e840023..07ae266 100644 --- a/walkthrough/install.md +++ b/walkthrough/install.md @@ -1,51 +1,43 @@ # Install the Patchloom CLI -Patchloom needs the CLI binary to work. Choose one of these methods: +Patchloom needs the CLI binary to work. Prefer channels that track +GitHub Releases promptly (same day as the tag). -## Managed Install (Recommended) +## Managed Install (Recommended on all platforms) Click **Install Patchloom** above to download and install the CLI automatically. The extension handles download, checksum verification, -and installation. +and installation from GitHub Releases. -## Homebrew +To upgrade later, run **Patchloom: Update Patchloom** (managed install +only). That path always pulls the latest GitHub release. -```bash -brew install patchloom/tap/patchloom -``` - -## npm - -```bash -npm install -g patchloom -# or one-shot: npx patchloom --version -``` - -## Scoop (Windows, preferred) +## Scoop (preferred Windows PATH install) ```bash scoop bucket add patchloom https://github.com/patchloom/scoop-bucket scoop install patchloom +scoop update patchloom ``` -Scoop tracks GitHub Releases promptly. Prefer it when you manage Windows installs yourself. +Scoop tracks the project release bucket. Use it when you want the CLI on +`PATH` yourself. Avoid winget and Chocolatey for install or upgrade: +both lag GitHub Releases and often leave you on an old version. -## WinGet (Windows) +## Homebrew (macOS / Linux) ```bash -winget install Patchloom.Patchloom +brew install patchloom/tap/patchloom +brew upgrade patchloom ``` -After a new release, you may need `winget source update` before the package appears. Microsoft publish can lag the GitHub tag by a short window. - -## Chocolatey (Windows) +## npm ```bash -choco install patchloom +npm install -g patchloom +# or one-shot: npx patchloom --version ``` -Community moderation often lags Scoop and GitHub portable assets. - ## Cargo ```bash