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/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 6e181e6..cd95569 100644 --- a/packages/core/components/load-pr.md +++ b/packages/core/components/load-pr.md @@ -4,8 +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 -- 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 - 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..7c2a378 100644 --- a/packages/core/test/commands.test.ts +++ b/packages/core/test/commands.test.ts @@ -14,22 +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, /git rev-parse HEAD/); - 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/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..3ab9a7f 100644 --- a/packages/opencode/.opencode/commands/pr/fix.md +++ b/packages/opencode/.opencode/commands/pr/fix.md @@ -37,20 +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 -- 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 - 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 59351c0..6345379 100644 --- a/packages/opencode/.opencode/commands/pr/review.md +++ b/packages/opencode/.opencode/commands/pr/review.md @@ -33,22 +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 -- 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 - 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 @@ -69,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/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/tools/index.mdx b/packages/web/src/content/docs/docs/reference/tools/index.mdx index 99fde34..95b9d4e 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. 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