From 08b6d809fa66ad7590f9fc1ff4ba9e659cd9bcf0 Mon Sep 17 00:00:00 2001 From: dbpolito Date: Thu, 25 Jun 2026 13:17:38 -0300 Subject: [PATCH 1/3] feat: add worktree load tool - register worktree_load in core and OpenCode tool configuration - update PR workflows to load local branch and HEAD through worktree context - document the new tool and cover registration/template behavior in tests --- README.md | 2 +- kompass.jsonc | 1 + kompass.schema.json | 6 +- packages/core/components/load-pr.md | 5 +- packages/core/index.ts | 1 + packages/core/kompass.jsonc | 1 + packages/core/lib/config.ts | 5 ++ packages/core/test/commands.test.ts | 4 +- packages/core/test/worktree-load.test.ts | 69 +++++++++++++++++++ packages/core/tools/index.ts | 2 + packages/core/tools/worktree-load.ts | 25 +++++++ .../opencode/.opencode/commands/pr/fix.md | 5 +- .../opencode/.opencode/commands/pr/review.md | 5 +- packages/opencode/.opencode/kompass.jsonc | 3 + packages/opencode/README.md | 2 +- packages/opencode/index.ts | 9 +++ packages/opencode/kompass.jsonc | 1 + .../opencode/test/commands-config.test.ts | 3 + .../opencode/test/tool-registration.test.ts | 10 ++- .../docs/reference/components/load-pr.mdx | 1 + .../docs/docs/reference/tools/index.mdx | 5 ++ .../docs/reference/tools/worktree-load.mdx | 23 +++++++ 22 files changed, 176 insertions(+), 12 deletions(-) create mode 100644 packages/core/test/worktree-load.test.ts create mode 100644 packages/core/tools/worktree-load.ts create mode 100644 packages/web/src/content/docs/docs/reference/tools/worktree-load.mdx diff --git a/README.md b/README.md index 74b9b21..fd3c6a2 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Kompass keeps AI coding agents on course with token-efficient, composable workfl - Commands cover direct work (`/ask`, `/commit`, `/merge`, `/skill/create`, `/skill/optimize`), orchestration (`/dev`, `/ship`, `/todo`), ticket planning/sync, and PR review/shipping flows. - Agents are intentionally narrow: `worker` is generic, `planner` is no-edit planning, `navigator` owns multi-step orchestration, and `reviewer` is a no-edit review specialist. -- Structured tools keep workflows grounded in repo and GitHub state: `changes_load`, `command_expansion` (resolve a slash command and return the expanded prompt for immediate delegation), `pr_load`, `pr_sync`, `ticket_load`, `ticket_sync`. +- Structured tools keep workflows grounded in repo and GitHub state: `changes_load`, `command_expansion` (resolve a slash command and return the expanded prompt for immediate delegation), `pr_load`, `pr_sync`, `ticket_load`, `ticket_sync`, `worktree_load`. - Reusable command-template components live in `packages/core/components/` and are documented in the components reference. ## Prerequisites diff --git a/kompass.jsonc b/kompass.jsonc index 9cad009..1a0b453 100644 --- a/kompass.jsonc +++ b/kompass.jsonc @@ -48,6 +48,7 @@ "pr_sync": { "enabled": true }, "ticket_load": { "enabled": true }, "ticket_sync": { "enabled": true }, + "worktree_load": { "enabled": true }, }, "components": { diff --git a/kompass.schema.json b/kompass.schema.json index a1811dd..2d37ef2 100644 --- a/kompass.schema.json +++ b/kompass.schema.json @@ -208,6 +208,9 @@ }, "ticket_load": { "$ref": "#/$defs/toolConfig" + }, + "worktree_load": { + "$ref": "#/$defs/toolConfig" } } }, @@ -393,7 +396,8 @@ "pr_load", "pr_sync", "ticket_sync", - "ticket_load" + "ticket_load", + "worktree_load" ] } } diff --git a/packages/core/components/load-pr.md b/packages/core/components/load-pr.md index 6e181e6..f4d1d01 100644 --- a/packages/core/components/load-pr.md +++ b/packages/core/components/load-pr.md @@ -4,8 +4,9 @@ - Do not run separate git or GitHub commands just to discover the PR before calling `<%= it.config.tools.pr_load.name %>` - Store the result as `<%= it.result %>` - Store the PR head branch as `` from `<%= it.result %>.pr.headRefName` when it is available -- Run `git branch --show-current` and store the trimmed result as `` when it is available -- Run `git rev-parse HEAD` and store the trimmed result as `` when it is available +- Call `<%= it.config.tools.worktree_load.name %>` and store the result as `` +- Store the local branch as `` from `.branch` when it is available +- Store the local HEAD commit as `` from `.headOid` when it is available - Treat the loaded PR body, discussion, review history, and any attachments or linked artifacts returned by the loader as part of the source context - Review attached images, screenshots, videos, PDFs, and other linked files whenever they can affect the requested fix, review outcome, reproduction steps, or acceptance criteria - If any relevant attachment cannot be accessed, note that gap and continue only when the remaining PR context is still sufficient to proceed reliably diff --git a/packages/core/index.ts b/packages/core/index.ts index 1064336..4a597f2 100644 --- a/packages/core/index.ts +++ b/packages/core/index.ts @@ -28,6 +28,7 @@ export { createPrLoadTool } from "./tools/pr-load.ts"; export { createPrSyncTool } from "./tools/pr-sync.ts"; export { createTicketLoadTool } from "./tools/ticket-load.ts"; export { createTicketSyncTool } from "./tools/ticket-sync.ts"; +export { createWorktreeLoadTool } from "./tools/worktree-load.ts"; export type { Shell, ShellPromise, diff --git a/packages/core/kompass.jsonc b/packages/core/kompass.jsonc index 9cad009..1a0b453 100644 --- a/packages/core/kompass.jsonc +++ b/packages/core/kompass.jsonc @@ -48,6 +48,7 @@ "pr_sync": { "enabled": true }, "ticket_load": { "enabled": true }, "ticket_sync": { "enabled": true }, + "worktree_load": { "enabled": true }, }, "components": { diff --git a/packages/core/lib/config.ts b/packages/core/lib/config.ts index 3f29cca..98b69c6 100644 --- a/packages/core/lib/config.ts +++ b/packages/core/lib/config.ts @@ -20,6 +20,7 @@ export const DEFAULT_TOOL_NAMES = [ "pr_sync", "ticket_sync", "ticket_load", + "worktree_load", ] as const; export const DEFAULT_COMMAND_NAMES = [ @@ -130,6 +131,7 @@ export interface KompassConfig { pr_sync?: ToolConfig; ticket_sync?: ToolConfig; ticket_load?: ToolConfig; + worktree_load?: ToolConfig; }; components?: { "align-pr-branch"?: ComponentConfig; @@ -180,6 +182,7 @@ export interface MergedKompassConfig { pr_sync: ToolConfig; ticket_sync: ToolConfig; ticket_load: ToolConfig; + worktree_load: ToolConfig; }; components: { enabled: string[]; @@ -465,6 +468,7 @@ const defaultToolConfig: Record = { pr_sync: { enabled: true }, ticket_sync: { enabled: true }, ticket_load: { enabled: true }, + worktree_load: { enabled: true }, }; function getToggleEntry( @@ -616,6 +620,7 @@ export function mergeWithDefaults( pr_sync: { ...defaultToolConfig.pr_sync, ...config?.tools?.pr_sync }, ticket_sync: { ...defaultToolConfig.ticket_sync, ...config?.tools?.ticket_sync }, ticket_load: { ...defaultToolConfig.ticket_load, ...config?.tools?.ticket_load }, + worktree_load: { ...defaultToolConfig.worktree_load, ...config?.tools?.worktree_load }, }, components: { enabled: getEnabledNames( diff --git a/packages/core/test/commands.test.ts b/packages/core/test/commands.test.ts index 0fa8290..49324ac 100644 --- a/packages/core/test/commands.test.ts +++ b/packages/core/test/commands.test.ts @@ -18,7 +18,9 @@ describe("resolveCommands", () => { const commands = await resolveCommands(process.cwd()); const template = commands["pr/review"]?.template ?? ""; - assert.match(template, /git rev-parse HEAD/); + assert.match(template, /worktree_load/); + assert.match(template, /\.headOid/); + assert.doesNotMatch(template, /Run `git rev-parse HEAD` and store/); assert.match(template, /`` equals ``/); assert.match(template, /Run `gh pr checkout `/); assert.match(template, /do not retry checkout unless the user explicitly asks/); diff --git a/packages/core/test/worktree-load.test.ts b/packages/core/test/worktree-load.test.ts new file mode 100644 index 0000000..3c61135 --- /dev/null +++ b/packages/core/test/worktree-load.test.ts @@ -0,0 +1,69 @@ +import { describe, test } from "node:test"; +import assert from "node:assert/strict"; + +import { createToolContextForDirectory } from "../scripts/_tool-runner.ts"; +import type { Shell, ShellPromise } from "../tools/shared.ts"; +import { createWorktreeLoadTool } from "../tools/worktree-load.ts"; + +describe("worktree_load", () => { + test("loads branch and head state", async () => { + const tool = createWorktreeLoadTool(createMockShell([ + { contains: "git branch --show-current", stdout: "feature\n" }, + { contains: "git rev-parse HEAD", stdout: "abc123\n" }, + ])); + + const output = await tool.execute({}, createToolContextForDirectory("/tmp/repo")); + + assert.deepEqual(JSON.parse(output), { + branch: "feature", + headOid: "abc123", + isDetached: false, + }); + }); + + test("reports detached head", async () => { + const tool = createWorktreeLoadTool(createMockShell([ + { contains: "git branch --show-current", stdout: "" }, + { contains: "git rev-parse HEAD", stdout: "merge123\n" }, + ])); + + const output = await tool.execute({}, createToolContextForDirectory("/tmp/repo")); + + assert.deepEqual(JSON.parse(output), { + headOid: "merge123", + isDetached: true, + }); + }); +}); + +function createMockShell( + fixtures: Array<{ contains: string; stdout: string; stderr?: string; exitCode?: number }>, +): Shell { + return (strings: TemplateStringsArray, ...expressions: unknown[]) => { + let command = strings[0] ?? ""; + expressions.forEach((expression, index) => { + command += String(expression) + (strings[index + 1] ?? ""); + }); + + const fixture = fixtures.find((item) => command.includes(item.contains)); + if (!fixture) throw new Error(`Unhandled command: ${command}`); + + return createShellPromise({ + stdout: fixture.stdout, + stderr: fixture.stderr ?? "", + exitCode: fixture.exitCode ?? 0, + }); + }; +} + +function createShellPromise(result: { stdout: string; stderr: string; exitCode: number }): ShellPromise { + return { + cwd() { return this; }, + quiet() { return this; }, + nothrow() { return this; }, + text() { return result.stdout; }, + json() { return JSON.parse(result.stdout); }, + exitCode: result.exitCode, + stderr: Buffer.from(result.stderr), + }; +} diff --git a/packages/core/tools/index.ts b/packages/core/tools/index.ts index e61fe87..4a64e00 100644 --- a/packages/core/tools/index.ts +++ b/packages/core/tools/index.ts @@ -10,6 +10,7 @@ import { createPrLoadTool } from "./pr-load.ts"; import { createPrSyncTool } from "./pr-sync.ts"; import { createTicketLoadTool } from "./ticket-load.ts"; import { createTicketSyncTool } from "./ticket-sync.ts"; +import { createWorktreeLoadTool } from "./worktree-load.ts"; import type { Shell, ToolDefinition } from "./shared.ts"; const toolCreators: Record ToolDefinition> = { @@ -19,6 +20,7 @@ const toolCreators: Record ToolDefini pr_sync: ($) => createPrSyncTool($), ticket_sync: ($) => createTicketSyncTool($), ticket_load: ($) => createTicketLoadTool($), + worktree_load: ($) => createWorktreeLoadTool($), }; export async function createTools($: Shell, projectRoot: string) { diff --git a/packages/core/tools/worktree-load.ts b/packages/core/tools/worktree-load.ts new file mode 100644 index 0000000..0da59b7 --- /dev/null +++ b/packages/core/tools/worktree-load.ts @@ -0,0 +1,25 @@ +import { + stringifyJson, + type Shell, + type ToolDefinition, + type ToolExecutionContext, +} from "./shared.ts"; + +export function createWorktreeLoadTool($: Shell) { + return { + description: "Load local git worktree state", + args: {}, + async execute(_args: Record, ctx: ToolExecutionContext) { + const branchProc = await $`git branch --show-current`.cwd(ctx.worktree).quiet().nothrow(); + const headProc = await $`git rev-parse HEAD`.cwd(ctx.worktree).quiet().nothrow(); + const branch = branchProc.exitCode === 0 ? branchProc.text().trim() : ""; + const headOid = headProc.exitCode === 0 ? headProc.text().trim() : ""; + + return stringifyJson({ + ...(branch ? { branch } : {}), + ...(headOid ? { headOid } : {}), + ...(headOid ? { isDetached: !branch } : {}), + }); + }, + } satisfies ToolDefinition>; +} diff --git a/packages/opencode/.opencode/commands/pr/fix.md b/packages/opencode/.opencode/commands/pr/fix.md index ab03e0c..9af7bb0 100644 --- a/packages/opencode/.opencode/commands/pr/fix.md +++ b/packages/opencode/.opencode/commands/pr/fix.md @@ -37,8 +37,9 @@ $ARGUMENTS - Do not run separate git or GitHub commands just to discover the PR before calling `kompass_pr_load` - Store the result as `` - Store the PR head branch as `` from `.pr.headRefName` when it is available -- Run `git branch --show-current` and store the trimmed result as `` when it is available -- Run `git rev-parse HEAD` and store the trimmed result as `` when it is available +- Call `kompass_worktree_load` and store the result as `` +- Store the local branch as `` from `.branch` when it is available +- Store the local HEAD commit as `` from `.headOid` when it is available - Treat the loaded PR body, discussion, review history, and any attachments or linked artifacts returned by the loader as part of the source context - Review attached images, screenshots, videos, PDFs, and other linked files whenever they can affect the requested fix, review outcome, reproduction steps, or acceptance criteria - If any relevant attachment cannot be accessed, note that gap and continue only when the remaining PR context is still sufficient to proceed reliably diff --git a/packages/opencode/.opencode/commands/pr/review.md b/packages/opencode/.opencode/commands/pr/review.md index 59351c0..f2868df 100644 --- a/packages/opencode/.opencode/commands/pr/review.md +++ b/packages/opencode/.opencode/commands/pr/review.md @@ -33,8 +33,9 @@ $ARGUMENTS - Do not run separate git or GitHub commands just to discover the PR before calling `kompass_pr_load` - Store the result as `` - Store the PR head branch as `` from `.pr.headRefName` when it is available -- Run `git branch --show-current` and store the trimmed result as `` when it is available -- Run `git rev-parse HEAD` and store the trimmed result as `` when it is available +- Call `kompass_worktree_load` and store the result as `` +- Store the local branch as `` from `.branch` when it is available +- Store the local HEAD commit as `` from `.headOid` when it is available - Treat the loaded PR body, discussion, review history, and any attachments or linked artifacts returned by the loader as part of the source context - Review attached images, screenshots, videos, PDFs, and other linked files whenever they can affect the requested fix, review outcome, reproduction steps, or acceptance criteria - If any relevant attachment cannot be accessed, note that gap and continue only when the remaining PR context is still sufficient to proceed reliably diff --git a/packages/opencode/.opencode/kompass.jsonc b/packages/opencode/.opencode/kompass.jsonc index 624904a..ea98894 100644 --- a/packages/opencode/.opencode/kompass.jsonc +++ b/packages/opencode/.opencode/kompass.jsonc @@ -103,6 +103,9 @@ }, "ticket_load": { "enabled": true + }, + "worktree_load": { + "enabled": true } }, "defaults": { diff --git a/packages/opencode/README.md b/packages/opencode/README.md index 74b9b21..fd3c6a2 100644 --- a/packages/opencode/README.md +++ b/packages/opencode/README.md @@ -20,7 +20,7 @@ Kompass keeps AI coding agents on course with token-efficient, composable workfl - Commands cover direct work (`/ask`, `/commit`, `/merge`, `/skill/create`, `/skill/optimize`), orchestration (`/dev`, `/ship`, `/todo`), ticket planning/sync, and PR review/shipping flows. - Agents are intentionally narrow: `worker` is generic, `planner` is no-edit planning, `navigator` owns multi-step orchestration, and `reviewer` is a no-edit review specialist. -- Structured tools keep workflows grounded in repo and GitHub state: `changes_load`, `command_expansion` (resolve a slash command and return the expanded prompt for immediate delegation), `pr_load`, `pr_sync`, `ticket_load`, `ticket_sync`. +- Structured tools keep workflows grounded in repo and GitHub state: `changes_load`, `command_expansion` (resolve a slash command and return the expanded prompt for immediate delegation), `pr_load`, `pr_sync`, `ticket_load`, `ticket_sync`, `worktree_load`. - Reusable command-template components live in `packages/core/components/` and are documented in the components reference. ## Prerequisites diff --git a/packages/opencode/index.ts b/packages/opencode/index.ts index f44eb46..db287a1 100644 --- a/packages/opencode/index.ts +++ b/packages/opencode/index.ts @@ -10,6 +10,7 @@ import { createPrSyncTool, createTicketLoadTool, createTicketSyncTool, + createWorktreeLoadTool, getEnabledToolNames, type MergedKompassConfig, type Shell, @@ -365,6 +366,14 @@ const opencodeToolCreators: Record = { execute: (args, context) => definition.execute(args, context), }); }, + worktree_load(_: PluginInput["client"], __: MergedKompassConfig, _projectRoot: string, shell: Shell) { + const definition = createWorktreeLoadTool(shell); + return tool({ + description: definition.description, + args: {}, + execute: (args, context) => definition.execute(args, context), + }); + }, }; export async function createOpenCodeTools( diff --git a/packages/opencode/kompass.jsonc b/packages/opencode/kompass.jsonc index 9cad009..1a0b453 100644 --- a/packages/opencode/kompass.jsonc +++ b/packages/opencode/kompass.jsonc @@ -48,6 +48,7 @@ "pr_sync": { "enabled": true }, "ticket_load": { "enabled": true }, "ticket_sync": { "enabled": true }, + "worktree_load": { "enabled": true }, }, "components": { diff --git a/packages/opencode/test/commands-config.test.ts b/packages/opencode/test/commands-config.test.ts index dea389d..a296889 100644 --- a/packages/opencode/test/commands-config.test.ts +++ b/packages/opencode/test/commands-config.test.ts @@ -96,10 +96,12 @@ describe("applyCommandsConfig", () => { assert.match(reviewTemplate, /`kompass_changes_load`/); assert.match(reviewTemplate, /`kompass_pr_sync`/); assert.match(reviewTemplate, /`kompass_ticket_load`/); + assert.match(reviewTemplate, /`kompass_worktree_load`/); assert.doesNotMatch(reviewTemplate, /`pr_load`/); assert.doesNotMatch(reviewTemplate, /`changes_load`/); assert.doesNotMatch(reviewTemplate, /`pr_sync`/); assert.doesNotMatch(reviewTemplate, /`ticket_load`/); + assert.doesNotMatch(reviewTemplate, /`worktree_load`/); }); test("pr/fix template uses pr_sync only", async () => { @@ -128,6 +130,7 @@ describe("applyCommandsConfig", () => { "tools": { "changes_load": { "enabled": false }, "pr_load": { "enabled": false }, + "worktree_load": { "enabled": false }, "ticket_sync": { "enabled": true, "name": "custom_ticket_name" diff --git a/packages/opencode/test/tool-registration.test.ts b/packages/opencode/test/tool-registration.test.ts index cdc6b7f..83d1bd0 100644 --- a/packages/opencode/test/tool-registration.test.ts +++ b/packages/opencode/test/tool-registration.test.ts @@ -137,12 +137,14 @@ describe("createOpenCodeTools", () => { assert.ok(tools.kompass_pr_sync); assert.ok(tools.kompass_ticket_load); assert.ok(tools.kompass_ticket_sync); + assert.ok(tools.kompass_worktree_load); assert.equal(tools.changes_load, undefined); assert.equal(tools.command_expansion, undefined); assert.equal(tools.pr_load, undefined); assert.equal(tools.pr_sync, undefined); assert.equal(tools.ticket_load, undefined); assert.equal(tools.ticket_sync, undefined); + assert.equal(tools.worktree_load, undefined); }); }); @@ -185,7 +187,8 @@ describe("createOpenCodeTools", () => { "enabled": true, "name": "custom_ticket_name" }, - "ticket_load": { "enabled": false } + "ticket_load": { "enabled": false }, + "worktree_load": { "enabled": false } } }`, ); @@ -215,7 +218,10 @@ describe("createOpenCodeTools", () => { "command_expansion": { "enabled": false }, - "pr_load": { + "worktree_load": { + "enabled": false + }, + "pr_load": { "enabled": true, "name": "pull_request_context", }, diff --git a/packages/web/src/content/docs/docs/reference/components/load-pr.mdx b/packages/web/src/content/docs/docs/reference/components/load-pr.mdx index 18ee76a..4856e88 100644 --- a/packages/web/src/content/docs/docs/reference/components/load-pr.mdx +++ b/packages/web/src/content/docs/docs/reference/components/load-pr.mdx @@ -6,6 +6,7 @@ description: Shared partial for loading PR context through `pr_load`. ## Behavior - calls `pr_load` with an optional explicit PR ref +- calls `worktree_load` with the PR head commit when available so PR workflows can align local checkout state - stores the result in the caller-provided placeholder - treats PR body, discussion, review history, and linked artifacts as source context - asks commands to review relevant screenshots, PDFs, and other attachments when they matter diff --git a/packages/web/src/content/docs/docs/reference/tools/index.mdx b/packages/web/src/content/docs/docs/reference/tools/index.mdx index 99fde34..f5a0a78 100644 --- a/packages/web/src/content/docs/docs/reference/tools/index.mdx +++ b/packages/web/src/content/docs/docs/reference/tools/index.mdx @@ -13,6 +13,7 @@ Current built-in tools: - `pr_sync` - `ticket_load` - `ticket_sync` +- `worktree_load` ## Built-in tools @@ -39,3 +40,7 @@ Loads a ticket from GitHub issue references, local files, or raw text, with opti ### `ticket_sync` Creates or updates GitHub issues, renders checklist sections, and can append issue comments. + +### `worktree_load` + +Loads local git worktree branch and HEAD state, optionally comparing HEAD to an expected commit SHA. diff --git a/packages/web/src/content/docs/docs/reference/tools/worktree-load.mdx b/packages/web/src/content/docs/docs/reference/tools/worktree-load.mdx new file mode 100644 index 0000000..20af2a5 --- /dev/null +++ b/packages/web/src/content/docs/docs/reference/tools/worktree-load.mdx @@ -0,0 +1,23 @@ +--- +title: worktree_load +description: Load local git worktree branch and HEAD state. +--- + +## Purpose + +Use `worktree_load` when a workflow needs local checkout state without mixing that state into PR or ticket payloads. + +## Inputs + +- none + +## Returns + +- `branch` when the worktree is on a named branch +- `headOid` for the current local `HEAD` +- `isDetached` + +## Useful for + +- PR branch alignment checks +- Detecting detached CI checkouts From bba40ca351c164ddd20db293820b109f59c67b06 Mon Sep 17 00:00:00 2001 From: dbpolito Date: Thu, 25 Jun 2026 13:32:26 -0300 Subject: [PATCH 2/3] docs: clarify worktree load behavior --- .../web/src/content/docs/docs/reference/components/load-pr.mdx | 2 +- packages/web/src/content/docs/docs/reference/tools/index.mdx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/web/src/content/docs/docs/reference/components/load-pr.mdx b/packages/web/src/content/docs/docs/reference/components/load-pr.mdx index 4856e88..114157a 100644 --- a/packages/web/src/content/docs/docs/reference/components/load-pr.mdx +++ b/packages/web/src/content/docs/docs/reference/components/load-pr.mdx @@ -6,7 +6,7 @@ description: Shared partial for loading PR context through `pr_load`. ## Behavior - calls `pr_load` with an optional explicit PR ref -- calls `worktree_load` with the PR head commit when available so PR workflows can align local checkout state +- calls `worktree_load` to read local branch and HEAD state for checkout alignment - stores the result in the caller-provided placeholder - treats PR body, discussion, review history, and linked artifacts as source context - asks commands to review relevant screenshots, PDFs, and other attachments when they matter diff --git a/packages/web/src/content/docs/docs/reference/tools/index.mdx b/packages/web/src/content/docs/docs/reference/tools/index.mdx index f5a0a78..95b9d4e 100644 --- a/packages/web/src/content/docs/docs/reference/tools/index.mdx +++ b/packages/web/src/content/docs/docs/reference/tools/index.mdx @@ -43,4 +43,4 @@ Creates or updates GitHub issues, renders checklist sections, and can append iss ### `worktree_load` -Loads local git worktree branch and HEAD state, optionally comparing HEAD to an expected commit SHA. +Loads local git worktree branch and HEAD state. From c9ed34384df569904d4512d40ce9c35829677f37 Mon Sep 17 00:00:00 2001 From: dbpolito Date: Thu, 25 Jun 2026 15:15:24 -0300 Subject: [PATCH 3/3] fix: align PR worktree context - load worktree context explicitly in PR fix and review commands - avoid detached-head checkout during read-only PR reviews - update generated OpenCode commands, docs, and tests --- packages/core/commands/pr/fix.md | 6 ++- packages/core/commands/pr/review.md | 21 +++++++---- packages/core/components/align-pr-branch.md | 21 +++++++---- packages/core/components/load-pr.md | 3 -- packages/core/test/commands.test.ts | 15 +++++--- .../opencode/.opencode/commands/pr/fix.md | 15 +++++--- .../opencode/.opencode/commands/pr/review.md | 37 +++++++++++-------- .../docs/reference/components/load-pr.mdx | 1 - 8 files changed, 73 insertions(+), 46 deletions(-) diff --git a/packages/core/commands/pr/fix.md b/packages/core/commands/pr/fix.md index e8913d1..29bdf32 100644 --- a/packages/core/commands/pr/fix.md +++ b/packages/core/commands/pr/fix.md @@ -28,9 +28,13 @@ $ARGUMENTS <%~ include("@load-pr", { config: it.config, ref: "", result: "" }) %> +### Load Worktree Context + +- Call `<%= it.config.tools.worktree_load.name %>` and store the result as `` + ### Align Local Branch -<%~ include("@align-pr-branch", { action: "analyzing repository files or making code changes for this PR", scope: "inspect or modify local code for this PR", requiresBranch: true }) -%> +<%~ include("@align-pr-branch", { config: it.config, action: "analyzing repository files or making code changes for this PR", scope: "inspect or modify local code for this PR", requiresBranch: true }) -%> ### Load Changes diff --git a/packages/core/commands/pr/review.md b/packages/core/commands/pr/review.md index 6c7fb91..8f83f87 100644 --- a/packages/core/commands/pr/review.md +++ b/packages/core/commands/pr/review.md @@ -24,9 +24,13 @@ $ARGUMENTS <%~ include("@load-pr", { config: it.config, ref: "", result: "" }) %> -### Align Local Branch +### Load Worktree Context -<%~ include("@align-pr-branch", { action: "inspecting local repository files for this PR review", scope: "inspect local repository code for this PR", requiresBranch: false }) -%> +- Call `<%= it.config.tools.worktree_load.name %>` and store the result as `` + +### Align Review Context + +<%~ include("@align-pr-branch", { config: it.config, action: "inspecting local repository files for this PR review", scope: "inspect local repository code for this PR", requiresBranch: false }) -%> ### Load Ticket Context @@ -43,18 +47,19 @@ Call `<%= it.config.tools.changes_load.name %>` with `base: `, ``, and `` -2. Use `` whenever local repository files need to be inspected alongside the diff -3. Derive `` from `` and ``: +2. Use `` as the source of truth for the PR head diff +3. Inspect local repository files only when `` equals `` after review alignment; otherwise rely on `` and avoid treating local checkout files as PR-head files +4. Derive `` from `` and ``: - Include direct author replies that explicitly decline, defer, or intentionally narrow a suggestion and explain why they do not plan to implement it - Treat each matching author reply as higher priority than `` for that same concern, unless the current diff introduces a materially different defect with a concrete failure mode - Do not re-raise the same concern solely because `` still implies a broader scope -4. Derive `` from ``: +5. Derive `` from ``: - Treat resolved threads as settled - Treat threads as settled when they already contain feedback from `` and a later reply makes it clear the concern was intentionally declined, deferred, or answered without a code change request - Treat threads as settled when the author's reply directly answers the concern and the current diff does not add a materially different failure mode -5. Derive `` from `` authored by `` -6. Use diff hunks in `` to map inline comments to the correct lines -7. Derive `` as findings that are: +6. Derive `` from `` authored by `` +7. Use diff hunks in `` to map inline comments to the correct lines +8. Derive `` as findings that are: - new in this diff - from a previously unreviewed changed area - clearly missed material defects with a concrete failure mode diff --git a/packages/core/components/align-pr-branch.md b/packages/core/components/align-pr-branch.md index b05385e..377b7d5 100644 --- a/packages/core/components/align-pr-branch.md +++ b/packages/core/components/align-pr-branch.md @@ -1,18 +1,25 @@ - If `` is unavailable, STOP and report that the PR head branch could not be determined +- Store the local branch as `` from `.branch` when it is available +- Store the local HEAD commit as `` from `.headOid` when it is available <% if (it.requiresBranch) { -%> - If `` equals `` and `` equals ``, store `` as `` and do not checkout again - If `` differs from `` or `` differs from ``: - Run `gh pr checkout ` before <%= it.action %> - - After checkout, store the active branch as `` - - Run `git rev-parse HEAD` again and store the trimmed result as `` + - Call `<%= it.config.tools.worktree_load.name %>` again and store the result as `` + - Store the local branch as `` from `.branch` when it is available + - Store the local HEAD commit as `` from `.headOid` when it is available + - Store `` as `` when it is available - If checkout fails or times out, STOP and report that the PR branch could not be checked out locally; do not retry checkout unless the user explicitly asks - Do not <%= it.scope %> until `` equals `` and `` equals `` <% } else { -%> -- If `` equals ``, store `` as `` when `` is available; otherwise store `` as ``. Do not checkout because the worktree is already at the PR head commit. -- If `` differs from ``: +- If `` equals `` and `` equals ``, store `` as `` and do not checkout again +- If `` is available and (`` differs from `` or `` differs from ``): - Run `gh pr checkout ` before <%= it.action %> - - After checkout, store the active branch as `` - - Run `git rev-parse HEAD` again and store the trimmed result as `` + - Call `<%= it.config.tools.worktree_load.name %>` again and store the result as `` + - Store the local branch as `` from `.branch` when it is available + - Store the local HEAD commit as `` from `.headOid` when it is available + - Store `` as `` when it is available - If checkout fails or times out, STOP and report that the PR branch could not be checked out locally; do not retry checkout unless the user explicitly asks -- Do not <%= it.scope %> until `` equals `` +- If `` is unavailable, store `` as `` when it is available; otherwise store `` as ``. Do not checkout from detached HEAD for read-only PR review. +- Do not <%= it.scope %> from local files unless `` equals ``; use `` and `` as the source of truth for the PR head diff when local HEAD differs <% } -%> diff --git a/packages/core/components/load-pr.md b/packages/core/components/load-pr.md index f4d1d01..cd95569 100644 --- a/packages/core/components/load-pr.md +++ b/packages/core/components/load-pr.md @@ -4,9 +4,6 @@ - Do not run separate git or GitHub commands just to discover the PR before calling `<%= it.config.tools.pr_load.name %>` - Store the result as `<%= it.result %>` - Store the PR head branch as `` from `<%= it.result %>.pr.headRefName` when it is available -- Call `<%= it.config.tools.worktree_load.name %>` and store the result as `` -- Store the local branch as `` from `.branch` when it is available -- Store the local HEAD commit as `` from `.headOid` when it is available - Treat the loaded PR body, discussion, review history, and any attachments or linked artifacts returned by the loader as part of the source context - Review attached images, screenshots, videos, PDFs, and other linked files whenever they can affect the requested fix, review outcome, reproduction steps, or acceptance criteria - If any relevant attachment cannot be accessed, note that gap and continue only when the remaining PR context is still sufficient to proceed reliably diff --git a/packages/core/test/commands.test.ts b/packages/core/test/commands.test.ts index 49324ac..7c2a378 100644 --- a/packages/core/test/commands.test.ts +++ b/packages/core/test/commands.test.ts @@ -14,24 +14,27 @@ describe("resolveCommands", () => { assert.deepEqual(commands["pr/review"]?.config, { enabled: true }); }); - test("skips PR checkout when pr/review is already at PR head", async () => { + test("pr/review aligns named local branches but not detached head", async () => { const commands = await resolveCommands(process.cwd()); const template = commands["pr/review"]?.template ?? ""; - assert.match(template, /worktree_load/); - assert.match(template, /\.headOid/); - assert.doesNotMatch(template, /Run `git rev-parse HEAD` and store/); - assert.match(template, /`` equals ``/); + assert.match(template, /### Load Worktree Context/); + assert.match(template, /Call `worktree_load` and store the result/); assert.match(template, /Run `gh pr checkout `/); - assert.match(template, /do not retry checkout unless the user explicitly asks/); + assert.match(template, /Do not checkout from detached HEAD for read-only PR review/); + assert.match(template, /Inspect local repository files only when `` equals ``/); }); test("requires PR branch for pr/fix checkout alignment", async () => { const commands = await resolveCommands(process.cwd()); const template = commands["pr/fix"]?.template ?? ""; + assert.match(template, /### Load Worktree Context/); + assert.match(template, /Call `worktree_load` and store the result/); assert.match(template, /`` equals `` and `` equals ``/); assert.match(template, /`` differs from `` or `` differs from ``/); + assert.match(template, /Call `worktree_load` again/); + assert.match(template, /Store `` as `` when it is available/); assert.match(template, /Do not inspect or modify local code for this PR until `` equals `` and `` equals ``/); }); }); diff --git a/packages/opencode/.opencode/commands/pr/fix.md b/packages/opencode/.opencode/commands/pr/fix.md index 9af7bb0..3ab9a7f 100644 --- a/packages/opencode/.opencode/commands/pr/fix.md +++ b/packages/opencode/.opencode/commands/pr/fix.md @@ -37,21 +37,26 @@ $ARGUMENTS - Do not run separate git or GitHub commands just to discover the PR before calling `kompass_pr_load` - Store the result as `` - Store the PR head branch as `` from `.pr.headRefName` when it is available -- Call `kompass_worktree_load` and store the result as `` -- Store the local branch as `` from `.branch` when it is available -- Store the local HEAD commit as `` from `.headOid` when it is available - Treat the loaded PR body, discussion, review history, and any attachments or linked artifacts returned by the loader as part of the source context - Review attached images, screenshots, videos, PDFs, and other linked files whenever they can affect the requested fix, review outcome, reproduction steps, or acceptance criteria - If any relevant attachment cannot be accessed, note that gap and continue only when the remaining PR context is still sufficient to proceed reliably +### Load Worktree Context + +- Call `kompass_worktree_load` and store the result as `` + ### Align Local Branch - If `` is unavailable, STOP and report that the PR head branch could not be determined +- Store the local branch as `` from `.branch` when it is available +- Store the local HEAD commit as `` from `.headOid` when it is available - If `` equals `` and `` equals ``, store `` as `` and do not checkout again - If `` differs from `` or `` differs from ``: - Run `gh pr checkout ` before analyzing repository files or making code changes for this PR - - After checkout, store the active branch as `` - - Run `git rev-parse HEAD` again and store the trimmed result as `` + - Call `kompass_worktree_load` again and store the result as `` + - Store the local branch as `` from `.branch` when it is available + - Store the local HEAD commit as `` from `.headOid` when it is available + - Store `` as `` when it is available - If checkout fails or times out, STOP and report that the PR branch could not be checked out locally; do not retry checkout unless the user explicitly asks - Do not inspect or modify local code for this PR until `` equals `` and `` equals `` diff --git a/packages/opencode/.opencode/commands/pr/review.md b/packages/opencode/.opencode/commands/pr/review.md index f2868df..6345379 100644 --- a/packages/opencode/.opencode/commands/pr/review.md +++ b/packages/opencode/.opencode/commands/pr/review.md @@ -33,23 +33,29 @@ $ARGUMENTS - Do not run separate git or GitHub commands just to discover the PR before calling `kompass_pr_load` - Store the result as `` - Store the PR head branch as `` from `.pr.headRefName` when it is available -- Call `kompass_worktree_load` and store the result as `` -- Store the local branch as `` from `.branch` when it is available -- Store the local HEAD commit as `` from `.headOid` when it is available - Treat the loaded PR body, discussion, review history, and any attachments or linked artifacts returned by the loader as part of the source context - Review attached images, screenshots, videos, PDFs, and other linked files whenever they can affect the requested fix, review outcome, reproduction steps, or acceptance criteria - If any relevant attachment cannot be accessed, note that gap and continue only when the remaining PR context is still sufficient to proceed reliably -### Align Local Branch +### Load Worktree Context + +- Call `kompass_worktree_load` and store the result as `` + +### Align Review Context - If `` is unavailable, STOP and report that the PR head branch could not be determined -- If `` equals ``, store `` as `` when `` is available; otherwise store `` as ``. Do not checkout because the worktree is already at the PR head commit. -- If `` differs from ``: +- Store the local branch as `` from `.branch` when it is available +- Store the local HEAD commit as `` from `.headOid` when it is available +- If `` equals `` and `` equals ``, store `` as `` and do not checkout again +- If `` is available and (`` differs from `` or `` differs from ``): - Run `gh pr checkout ` before inspecting local repository files for this PR review - - After checkout, store the active branch as `` - - Run `git rev-parse HEAD` again and store the trimmed result as `` + - Call `kompass_worktree_load` again and store the result as `` + - Store the local branch as `` from `.branch` when it is available + - Store the local HEAD commit as `` from `.headOid` when it is available + - Store `` as `` when it is available - If checkout fails or times out, STOP and report that the PR branch could not be checked out locally; do not retry checkout unless the user explicitly asks -- Do not inspect local repository code for this PR until `` equals `` +- If `` is unavailable, store `` as `` when it is available; otherwise store `` as ``. Do not checkout from detached HEAD for read-only PR review. +- Do not inspect local repository code for this PR from local files unless `` equals ``; use `` and `` as the source of truth for the PR head diff when local HEAD differs ### Load Ticket Context @@ -70,18 +76,19 @@ Call `kompass_changes_load` with `base: `, `head: `, ``, and `` -2. Use `` whenever local repository files need to be inspected alongside the diff -3. Derive `` from `` and ``: +2. Use `` as the source of truth for the PR head diff +3. Inspect local repository files only when `` equals `` after review alignment; otherwise rely on `` and avoid treating local checkout files as PR-head files +4. Derive `` from `` and ``: - Include direct author replies that explicitly decline, defer, or intentionally narrow a suggestion and explain why they do not plan to implement it - Treat each matching author reply as higher priority than `` for that same concern, unless the current diff introduces a materially different defect with a concrete failure mode - Do not re-raise the same concern solely because `` still implies a broader scope -4. Derive `` from ``: +5. Derive `` from ``: - Treat resolved threads as settled - Treat threads as settled when they already contain feedback from `` and a later reply makes it clear the concern was intentionally declined, deferred, or answered without a code change request - Treat threads as settled when the author's reply directly answers the concern and the current diff does not add a materially different failure mode -5. Derive `` from `` authored by `` -6. Use diff hunks in `` to map inline comments to the correct lines -7. Derive `` as findings that are: +6. Derive `` from `` authored by `` +7. Use diff hunks in `` to map inline comments to the correct lines +8. Derive `` as findings that are: - new in this diff - from a previously unreviewed changed area - clearly missed material defects with a concrete failure mode diff --git a/packages/web/src/content/docs/docs/reference/components/load-pr.mdx b/packages/web/src/content/docs/docs/reference/components/load-pr.mdx index 114157a..18ee76a 100644 --- a/packages/web/src/content/docs/docs/reference/components/load-pr.mdx +++ b/packages/web/src/content/docs/docs/reference/components/load-pr.mdx @@ -6,7 +6,6 @@ description: Shared partial for loading PR context through `pr_load`. ## Behavior - calls `pr_load` with an optional explicit PR ref -- calls `worktree_load` to read local branch and HEAD state for checkout alignment - stores the result in the caller-provided placeholder - treats PR body, discussion, review history, and linked artifacts as source context - asks commands to review relevant screenshots, PDFs, and other attachments when they matter