diff --git a/kompass.schema.json b/kompass.schema.json index 94a0647..a1811dd 100644 --- a/kompass.schema.json +++ b/kompass.schema.json @@ -302,6 +302,10 @@ "description": { "type": "string" }, + "name": { + "type": "string", + "minLength": 1 + }, "promptPath": { "type": "string" }, @@ -320,6 +324,10 @@ "enabled": { "type": "boolean" }, + "name": { + "type": "string", + "minLength": 1 + }, "description": { "type": "string" }, @@ -341,6 +349,10 @@ "enabled": { "type": "boolean" }, + "name": { + "type": "string", + "minLength": 1 + }, "template": { "type": "string", "minLength": 1 diff --git a/packages/core/agents/index.ts b/packages/core/agents/index.ts index fb060de..4fabcf6 100644 --- a/packages/core/agents/index.ts +++ b/packages/core/agents/index.ts @@ -1,4 +1,15 @@ -import { loadKompassConfig, mergeWithDefaults, type AgentDefinition } from "../lib/config.ts"; +import { renderTemplate } from "../lib/components.ts"; +import { + getConfiguredAgentNames, + getConfiguredCommandNames, + getConfiguredToolNames, + loadKompassConfig, + mergeWithDefaults, + type AgentDefinition, + type AgentName, + type CommandName, + type ToolName, +} from "../lib/config.ts"; import { loadProjectText } from "../lib/text.ts"; export interface ResolvedAgentDefinition @@ -18,20 +29,54 @@ export function getAgentDefinitions(config: ReturnType export async function resolveAgents( projectRoot: string, + options?: { + names?: { + tools?: Partial>; + commands?: Partial>; + agents?: Partial>; + }; + }, ): Promise> { const userConfig = await loadKompassConfig(projectRoot); const config = mergeWithDefaults(userConfig); const agentDefinitions = getAgentDefinitions(config); + const names = { + tools: { + ...getConfiguredToolNames(config.tools), + ...(options?.names?.tools ?? {}), + }, + commands: { + ...getConfiguredCommandNames(config.commands), + ...(options?.names?.commands ?? {}), + }, + agents: { + ...getConfiguredAgentNames(config.agents), + ...(options?.names?.agents ?? {}), + }, + }; const agents: Record = {}; for (const name of config.agents.enabled) { const definition = agentDefinitions[name]; if (!definition) continue; - agents[name] = { + const resolvedName = names.agents[name as AgentName]?.name ?? name; + + agents[resolvedName] = { description: definition.description, mode: definition.mode, - ...(definition.promptPath ? { prompt: await loadProjectText(definition.promptPath) } : {}), + ...(definition.promptPath + ? { + prompt: renderTemplate(await loadProjectText(definition.promptPath), {}, { + config: { + shared: config.shared, + tools: names.tools, + commands: names.commands, + agents: names.agents, + }, + }), + } + : {}), permission: definition.permission, }; } diff --git a/packages/core/agents/navigator.md b/packages/core/agents/navigator.md index 3028d2d..6016874 100644 --- a/packages/core/agents/navigator.md +++ b/packages/core/agents/navigator.md @@ -12,7 +12,7 @@ You coordinate structured, multi-step workflows. When you see a `...` block, you MUST make TWO tool calls in sequence: -1. **Expand**: Call `command_expansion` with `command` from the tag and `body` set to the rendered block content +1. **Expand**: Call `<%= it.config.tools.command_expansion.name %>` with `command` from the tag and `body` set to the rendered block content 2. **Delegate**: IMMEDIATELY call `task` with `subagent_type: AGENT_NAME` and `prompt` set to the expanded text from step 1 **CRITICAL RULES:** diff --git a/packages/core/agents/reviewer.md b/packages/core/agents/reviewer.md index eafa384..7a80d50 100644 --- a/packages/core/agents/reviewer.md +++ b/packages/core/agents/reviewer.md @@ -10,7 +10,7 @@ Before reviewing, always check repository guidance: ## Review Workflow -1. **Load Changes**: Use `changes_load` to get the changed-file set and structured diffs +1. **Load Changes**: Use `<%= it.config.tools.changes_load.name %>` to get the changed-file set and structured diffs - In CI or shallow clones, pass explicit base and head refs - For branch comparisons, treat the returned commit list as the authoritative scope: review the commits ahead of base and use file diffs only as supporting context - Scan the summary first to understand scope, file states, and risk clusters @@ -18,7 +18,7 @@ Before reviewing, always check repository guidance: 2. **Read Code**: Read every changed file individually before finalizing - Read full current files for added/modified/renamed/copied/untracked files - - `changes_load` gives the changed-file set and diffs, but not full deleted-file contents; for deleted files, inspect previous contents from git + - `<%= it.config.tools.changes_load.name %>` gives the changed-file set and diffs, but not full deleted-file contents; for deleted files, inspect previous contents from git - Use diffs to prioritize, but don't treat hunks as sufficient context - Prefer loading changed files directly in the current session so that file context remains available during analysis and write-up - Use a helper agent only when the changed-file set is too large to review comfortably in one session after the changed paths are already known diff --git a/packages/core/commands/branch.md b/packages/core/commands/branch.md index 34908d6..607a7de 100644 --- a/packages/core/commands/branch.md +++ b/packages/core/commands/branch.md @@ -21,7 +21,7 @@ $ARGUMENTS ### Load Changes -<%~ include("@change-summary", { rules: "- pass `uncommitted: true` to get uncommitted changes only" }) %> +<%~ include("@change-summary", { config: it.config, rules: "- pass `uncommitted: true` to get uncommitted changes only" }) %> - Store the loaded change result as `` - Store the current branch as `` when it is available diff --git a/packages/core/commands/commit-and-push.md b/packages/core/commands/commit-and-push.md index 901e2b3..b745067 100644 --- a/packages/core/commands/commit-and-push.md +++ b/packages/core/commands/commit-and-push.md @@ -21,7 +21,7 @@ $ARGUMENTS ### Load Changes -<%~ include("@change-summary", { rules: "- pass `uncommitted: true` to get uncommitted changes only" }) %> +<%~ include("@change-summary", { config: it.config, rules: "- pass `uncommitted: true` to get uncommitted changes only" }) %> - Store the loaded change result as `` ### Check Blockers diff --git a/packages/core/commands/commit.md b/packages/core/commands/commit.md index 0a51818..d17f8f4 100644 --- a/packages/core/commands/commit.md +++ b/packages/core/commands/commit.md @@ -21,7 +21,7 @@ $ARGUMENTS ### Load Changes -<%~ include("@change-summary", { rules: "- pass `uncommitted: true` to get uncommitted changes only" }) %> +<%~ include("@change-summary", { config: it.config, rules: "- pass `uncommitted: true` to get uncommitted changes only" }) %> - Store the loaded change result as `` ### Check Blockers diff --git a/packages/core/commands/dev.md b/packages/core/commands/dev.md index dcd6d4b..48ab76a 100644 --- a/packages/core/commands/dev.md +++ b/packages/core/commands/dev.md @@ -24,7 +24,7 @@ $ARGUMENTS ### Load Request Context - If `` is defined: -<%~ include("@load-ticket", { source: "", result: "" }) %> +<%~ include("@load-ticket", { config: it.config, source: "", result: "" }) %> - Otherwise, treat `` as `` - If `` cannot be determined, STOP and report that the implementation request is missing diff --git a/packages/core/commands/index.ts b/packages/core/commands/index.ts index e4c3676..8e139e0 100644 --- a/packages/core/commands/index.ts +++ b/packages/core/commands/index.ts @@ -1,5 +1,14 @@ import { renderTemplate } from "../lib/components.ts"; -import { loadKompassConfig, mergeWithDefaults } from "../lib/config.ts"; +import { + getConfiguredAgentNames, + getConfiguredCommandNames, + getConfiguredToolNames, + loadKompassConfig, + mergeWithDefaults, + type AgentName, + type CommandName, + type ToolName, +} from "../lib/config.ts"; import { loadProjectText } from "../lib/text.ts"; interface CommandDefinition { @@ -144,13 +153,34 @@ async function loadComponents( export async function resolveCommands( projectRoot: string, - options?: { ci?: boolean }, + options?: { + ci?: boolean; + names?: { + tools?: Partial>; + commands?: Partial>; + agents?: Partial>; + }; + }, ): Promise> { const userConfig = await loadKompassConfig(projectRoot); const config = mergeWithDefaults(userConfig); const isCi = options?.ci ?? !!process.env.CI; const components = await loadComponents(config.components.paths); + const names = { + tools: { + ...getConfiguredToolNames(config.tools), + ...(options?.names?.tools ?? {}), + }, + commands: { + ...getConfiguredCommandNames(config.commands), + ...(options?.names?.commands ?? {}), + }, + agents: { + ...getConfiguredAgentNames(config.agents), + ...(options?.names?.agents ?? {}), + }, + }; const commands: Record = {}; for (const name of config.commands.enabled) { @@ -171,6 +201,9 @@ export async function resolveCommands( ...commandConfig, config: { shared: config.shared, + tools: names.tools, + commands: names.commands, + agents: names.agents, }, }; @@ -182,9 +215,11 @@ export async function resolveCommands( continue; } - commands[name] = { + const resolvedName = names.commands[name as CommandName]?.name ?? name; + + commands[resolvedName] = { description: definition.description, - agent: definition.agent, + agent: names.agents[definition.agent as AgentName]?.name ?? definition.agent, subtask: definition.subtask ?? !isCi, template, ...(Object.keys(commandConfig).length > 0 ? { config: commandConfig } : {}), diff --git a/packages/core/commands/pr/create.md b/packages/core/commands/pr/create.md index 2d4d8df..2e234f8 100644 --- a/packages/core/commands/pr/create.md +++ b/packages/core/commands/pr/create.md @@ -30,7 +30,7 @@ $ARGUMENTS ### Load & Analyze Changes -<%~ include("@change-summary", { rules: "- If `` is defined: call `changes_load` with the `base` parameter set to ``\n- Otherwise: call `changes_load` with no parameters\n- Never pass `uncommitted: true` in this command" }) %> +<%~ include("@change-summary", { config: it.config, rules: "- If `` is defined: call `" + it.config.tools.changes_load.name + "` with the `base` parameter set to ``\n- Otherwise: call `" + it.config.tools.changes_load.name + "` with no parameters\n- Never pass `uncommitted: true` in this command" }) %> - Store the loaded change result as `` - Store the current branch from `` as `` when it is available @@ -44,7 +44,7 @@ $ARGUMENTS - Report: "There are uncommitted changes. Please commit or stash them before creating a PR." - List the changed files from `` - Do NOT proceed further -- Treat this as a blocker only when `changes_load` returns `comparison: "uncommitted"` from the default call above; never force that mode during PR creation +- Treat this as a blocker only when `<%= it.config.tools.changes_load.name %>` returns `comparison: "uncommitted"` from the default call above; never force that mode during PR creation - If `` equals ``: - STOP immediately - Report: "You are currently on the base branch (). Please checkout a feature branch before creating a PR." @@ -84,8 +84,8 @@ $ARGUMENTS ### Prepare Ticket Reference When `` is `auto`, create the ticket before creating the PR: -<%~ include("@changes-summary") %> -- Use `ticket_sync` with `refUrl` unset +<%~ include("@changes-summary", { config: it.config }) %> +- Use `<%= it.config.tools.ticket_sync.name %>` with `refUrl` unset - Set `assignees` to `[@me]` so the created ticket is assigned to yourself as the author - Store the created issue reference or URL as `` @@ -106,7 +106,7 @@ Run `git push` and use its output as the source of truth. ### Create PR -Use `pr_sync` to create the pull request: +Use `<%= it.config.tools.pr_sync.name %>` to create the pull request: - This step is PR creation only - Omit `review`, `replies`, `commentBody`, and `commitId` entirely unless you are intentionally updating or reviewing an existing PR instead of creating one - Generate a concise title (max 70 chars) summarizing the change and store it as `` @@ -127,7 +127,7 @@ Use `pr_sync` to create the pull request: - Do NOT rely on the branch diff alone to describe the PR; the description must match the commits ahead of `` - Keep it compact and directional - Store the returned URL as `` -- If `pr_sync` reports that a PR already exists for the branch, treat the result as an existing PR +- If `<%= it.config.tools.pr_sync.name %>` reports that a PR already exists for the branch, treat the result as an existing PR - Track whether the branch was pushed during this run and report that status in the final response ### Output diff --git a/packages/core/commands/pr/fix.md b/packages/core/commands/pr/fix.md index 311a0ac..ceb7231 100644 --- a/packages/core/commands/pr/fix.md +++ b/packages/core/commands/pr/fix.md @@ -22,11 +22,11 @@ $ARGUMENTS - If `` looks like a PR number (e.g., "123") or URL, store it as `` - If `` includes extra fix guidance, scope constraints, or priorities, store it as `` - Otherwise, store `` as `review` -- If empty, leave `` undefined and let `pr_load` resolve the default PR context +- If empty, leave `` undefined and let `<%= it.config.tools.pr_load.name %>` resolve the default PR context ### Load PR Context -<%~ include("@load-pr", { ref: "", result: "" }) %> +<%~ include("@load-pr", { config: it.config, ref: "", result: "" }) %> ### Align Local Branch @@ -34,7 +34,7 @@ $ARGUMENTS ### Load Changes -Call `changes_load` with `base: `, `head: `, and `depthHint: ` only when it is a positive integer. Store as ``. +Call `<%= it.config.tools.changes_load.name %>` with `base: `, `head: `, and `depthHint: ` only when it is a positive integer. Store as ``. ### Analyze Feedback @@ -113,17 +113,17 @@ If validation passes: Only after commit and push succeed, reply to addressed threads: - Keep replies short and factual—clear signals, no chatter -- Use `pr_sync` to post comments or replies: +- Use `<%= it.config.tools.pr_sync.name %>` to post comments or replies: ``` # General PR comment -pr_sync refUrl="" commentBody="" +<%= it.config.tools.pr_sync.name %> refUrl="" commentBody="" # Reply to a specific review thread (use comment.id from threads.comments) -pr_sync refUrl="" replies=[{"inReplyTo": , "body": ""}] +<%= it.config.tools.pr_sync.name %> refUrl="" replies=[{"inReplyTo": , "body": ""}] # Follow-up inline review comment on a specific line -pr_sync refUrl="" commitId="" review={"comments": [{"path": "", "line": , "body": ""}]} +<%= it.config.tools.pr_sync.name %> refUrl="" commitId="" review={"comments": [{"path": "", "line": , "body": ""}]} ``` Confirm which feedback was addressed and which was intentionally not followed. diff --git a/packages/core/commands/pr/review.md b/packages/core/commands/pr/review.md index 3f56f9d..40a8967 100644 --- a/packages/core/commands/pr/review.md +++ b/packages/core/commands/pr/review.md @@ -18,11 +18,11 @@ $ARGUMENTS - If `` looks like a PR number (e.g., "123") or URL, store it as `` - If `` includes review focus areas, related tickets, or special concerns, store it as `` -- If empty, leave `` undefined and let `pr_load` resolve the default PR context +- If empty, leave `` undefined and let `<%= it.config.tools.pr_load.name %>` resolve the default PR context ### Load PR Context -<%~ include("@load-pr", { ref: "", result: "" }) %> +<%~ include("@load-pr", { config: it.config, ref: "", result: "" }) %> ### Align Local Branch @@ -32,12 +32,12 @@ $ARGUMENTS If `` links to exactly one clear ticket: - Store that reference as `` -<%~ include("@load-ticket", { source: "", result: "", comments: true }) %> +<%~ include("@load-ticket", { config: it.config, source: "", result: "", comments: true }) %> - Use `` for consideration during review ### Load Changes -Call `changes_load` with `base: `, `head: `, and `depthHint: ` only when it is a positive integer. Store as ``. +Call `<%= it.config.tools.changes_load.name %>` with `base: `, `head: `, and `depthHint: ` only when it is a positive integer. Store as ``. ### Review Changes @@ -87,17 +87,17 @@ For multi-line: add `startLine`. For deleted lines: use `side: "LEFT"`. **If `` is `★★★★★`:** <% if (it.config.shared.prApprove === true) { -%> - Already approved → skip -- Otherwise → first call `pr_sync` with `refUrl: ` and only `review.approve: true` -- If that approval call fails, immediately call `pr_sync` again with `refUrl: ` and `review.body` starting with `★★★★★` +- Otherwise → first call `<%= it.config.tools.pr_sync.name %>` with `refUrl: ` and only `review.approve: true` +- If that approval call fails, immediately call `<%= it.config.tools.pr_sync.name %>` again with `refUrl: ` and `review.body` starting with `★★★★★` - If there are no positive summary notes for the fallback review, the fallback body must be exactly `★★★★★` - Do not pass `review.comments` in the approval attempt or the fallback review <% } else { -%> -- `pr_sync` with `refUrl: ` and `review.body` starting with `★★★★★` +- `<%= it.config.tools.pr_sync.name %>` with `refUrl: ` and `review.body` starting with `★★★★★` - If there are no positive summary notes, the body must be exactly `★★★★★` - Do not pass `review.comments` <% } %> **If `` is below `★★★★★`:** -- Call `pr_sync` with: +- Call `<%= it.config.tools.pr_sync.name %>` with: - `refUrl: ` - `review.body`: the grade line first (for example `★★★☆☆`), followed by any non-inline notes - `review.comments`: inline comments (changed lines only) - **skip lines or concerns already covered by open threads in `` unless the new diff introduces a materially different failure mode** @@ -105,7 +105,7 @@ For multi-line: add `startLine`. For deleted lines: use `side: "LEFT"`. - Never omit the grade from `review.body` in this branch - Do not pass any other fields -If `pr_sync` returns a review URL, store it as ``. +If `<%= it.config.tools.pr_sync.name %>` returns a review URL, store it as ``. ### Output diff --git a/packages/core/commands/review.md b/packages/core/commands/review.md index 96b1403..3f6f43a 100644 --- a/packages/core/commands/review.md +++ b/packages/core/commands/review.md @@ -23,7 +23,7 @@ $ARGUMENTS ### Load Changes -Call `changes_load`: +Call `<%= it.config.tools.changes_load.name %>`: - If `` is defined: pass `base: ` - Otherwise: call with no parameters (auto-detects uncommitted vs branch comparison) @@ -47,7 +47,7 @@ Following the reviewer agent guidance: While reading files: - Load any relevant nested `AGENTS.md` in the current session before applying review criteria -- For deleted files, inspect prior contents from git because `changes_load` does not provide full deleted-file contents +- For deleted files, inspect prior contents from git because `<%= it.config.tools.changes_load.name %>` does not provide full deleted-file contents - Use a helper agent only if the changed-file set is too large to review comfortably in one session after the changed paths are already known ### Output diff --git a/packages/core/commands/rmslop.md b/packages/core/commands/rmslop.md index 8a148d5..867aec6 100644 --- a/packages/core/commands/rmslop.md +++ b/packages/core/commands/rmslop.md @@ -22,7 +22,7 @@ $ARGUMENTS ### Load Changes -- Call `changes_load` to get the diff against `` when defined, otherwise use the default comparison +- Call `<%= it.config.tools.changes_load.name %>` to get the diff against `` when defined, otherwise use the default comparison - Store the result as `` - Store `.comparison` as `` diff --git a/packages/core/commands/ship.md b/packages/core/commands/ship.md index edae165..27a1e6a 100644 --- a/packages/core/commands/ship.md +++ b/packages/core/commands/ship.md @@ -23,7 +23,7 @@ $ARGUMENTS ### Delegate Branch Creation - + Branch naming guidance: @@ -35,7 +35,7 @@ Branch naming guidance: ### Delegate Commit - + Additional context: @@ -46,7 +46,7 @@ Additional context: ### Delegate PR Creation - +"> Base branch: Additional context: diff --git a/packages/core/commands/ticket/ask.md b/packages/core/commands/ticket/ask.md index 5d59dd1..88fdb8d 100644 --- a/packages/core/commands/ticket/ask.md +++ b/packages/core/commands/ticket/ask.md @@ -25,7 +25,7 @@ $ARGUMENTS ### Load Ticket Context -<%~ include("@load-ticket", { source: "", result: "", comments: true }) %> +<%~ include("@load-ticket", { config: it.config, source: "", result: "", comments: true }) %> - If `` is missing or `` cannot be loaded, STOP and report that the ticket context is missing or invalid ### Draft The Answer @@ -36,7 +36,7 @@ $ARGUMENTS ### Sync Ticket -- Use `ticket_sync` with: +- Use `<%= it.config.tools.ticket_sync.name %>` with: - `refUrl: ` - `comments: []` - Store the returned ticket URL as `` diff --git a/packages/core/commands/ticket/create.md b/packages/core/commands/ticket/create.md index f2f59b7..6e0545f 100644 --- a/packages/core/commands/ticket/create.md +++ b/packages/core/commands/ticket/create.md @@ -18,11 +18,11 @@ $ARGUMENTS - **Branch name**: If `` looks like a branch reference (e.g., "main", "origin/develop"), store it as `` - **Additional context**: If `` provides guidance (audience, focus areas, related issues, notes), store it as `` -- **Empty**: If no `` are provided, proceed with defaults and rely on `changes_load` to decide the comparison mode +- **Empty**: If no `` are provided, proceed with defaults and rely on `<%= it.config.tools.changes_load.name %>` to decide the comparison mode ### Load & Analyze Changes -<%~ include("@change-summary", { rules: "- If `` is defined: call `changes_load` with the `base` parameter set to ``\n- Otherwise: call `changes_load` with no parameters" }) %> +<%~ include("@change-summary", { config: it.config, rules: "- If `` is defined: call `" + it.config.tools.changes_load.name + "` with the `base` parameter set to ``\n- Otherwise: call `" + it.config.tools.changes_load.name + "` with no parameters" }) %> - Store the loaded change result as `` - When `.comparison` is not `uncommitted`, describe the ticket from the commits ahead of the resolved base branch, not from branch names alone @@ -35,8 +35,8 @@ $ARGUMENTS ### Create Ticket -Use `ticket_sync` with `refUrl` unset to create the ticket: -<%~ include("@changes-summary") %> +Use `<%= it.config.tools.ticket_sync.name %>` with `refUrl` unset to create the ticket: +<%~ include("@changes-summary", { config: it.config }) %> - Set `assignees` to `[@me]` so the created ticket is assigned to yourself as the author - Store the generated title as `` - Store the created issue URL as `` diff --git a/packages/core/commands/ticket/dev.md b/packages/core/commands/ticket/dev.md index a5d6e6e..50e5759 100644 --- a/packages/core/commands/ticket/dev.md +++ b/packages/core/commands/ticket/dev.md @@ -4,7 +4,7 @@ Implement a ticket by orchestrating development, branching, commit-and-push, and ## Additional Context -Use `` to refine scope, sequencing, and tradeoffs across the delegated `/dev`, `/branch`, `/commit-and-push`, and `/pr/create` steps. +Use `` to refine scope, sequencing, and tradeoffs across the delegated `/<%= it.config.commands.dev.name %>`, `/<%= it.config.commands.branch.name %>`, `/<%= it.config.commands["commit-and-push"].name %>`, and `/<%= it.config.commands["pr/create"].name %>` steps. ## Workflow @@ -22,7 +22,7 @@ $ARGUMENTS ### Load Ticket Context -<%~ include("@load-ticket", { source: "", result: "" }) %> +<%~ include("@load-ticket", { config: it.config, source: "", result: "" }) %> - Store the ticket reference for PR creation as `` by preferring the original reference, otherwise using the canonical ticket URL from `` when one is available, otherwise using `SKIPPED` - Store a concise ticket summary as `` - If `` cannot be loaded, STOP and report that the ticket source is missing or invalid @@ -31,7 +31,7 @@ $ARGUMENTS ### Delegate Implementation - + Ticket reference: Ticket context: Additional context: @@ -42,7 +42,7 @@ Additional context: ### Delegate Branch Creation - + Branch naming guidance: Additional context: @@ -53,7 +53,7 @@ Additional context: ### Delegate Commit And Push - +"> Ticket reference: Ticket summary: Additional context: @@ -65,7 +65,7 @@ Additional context: ### Delegate PR Creation - +"> Ticket reference: Ticket context: Additional context: diff --git a/packages/core/commands/ticket/plan-and-sync.md b/packages/core/commands/ticket/plan-and-sync.md index 0543543..a357a15 100644 --- a/packages/core/commands/ticket/plan-and-sync.md +++ b/packages/core/commands/ticket/plan-and-sync.md @@ -32,7 +32,7 @@ $ARGUMENTS ### Load Planning Context - If `` is defined: -<%~ include("@load-ticket", { source: "", result: "", comments: true }) %> +<%~ include("@load-ticket", { config: it.config, source: "", result: "", comments: true }) %> - Otherwise, treat the relevant request and conversation context as `` - If `` is empty or missing, STOP and report that planning context could not be determined @@ -67,7 +67,7 @@ $ARGUMENTS ### Sync Ticket -- Use `ticket_sync` to store the plan in the ticket flow: +- Use `<%= it.config.tools.ticket_sync.name %>` to store the plan in the ticket flow: - set `title` to `` - set `description` to `` - set `checklists` to two sections: diff --git a/packages/core/commands/ticket/plan.md b/packages/core/commands/ticket/plan.md index d16272e..95042cf 100644 --- a/packages/core/commands/ticket/plan.md +++ b/packages/core/commands/ticket/plan.md @@ -32,7 +32,7 @@ $ARGUMENTS ### Load Planning Context - If `` is defined: -<%~ include("@load-ticket", { source: "", result: "", comments: true }) %> +<%~ include("@load-ticket", { config: it.config, source: "", result: "", comments: true }) %> - Otherwise, treat the relevant request and conversation context as `` - If `` is empty or missing, STOP and report that planning context could not be determined diff --git a/packages/core/commands/todo.md b/packages/core/commands/todo.md index 5692348..721fc25 100644 --- a/packages/core/commands/todo.md +++ b/packages/core/commands/todo.md @@ -42,7 +42,7 @@ $ARGUMENTS ### Delegate Planning - +"> Task: Task context: Additional context: @@ -65,7 +65,7 @@ Additional context: - Only run the revised planning block below when the user requests changes - If the user approves the current plan, skip the revised planning block and continue to implementation - +"> Task: Task context: Current plan: @@ -80,7 +80,7 @@ Additional context: ### Delegate Implementation - + Plan: Task: Task context: @@ -92,7 +92,7 @@ Additional context: ### Delegate Commit - + Task: Additional context: diff --git a/packages/core/components/change-summary.md b/packages/core/components/change-summary.md index 852430b..aa19d95 100644 --- a/packages/core/components/change-summary.md +++ b/packages/core/components/change-summary.md @@ -1,5 +1,5 @@ #### Step 1: Load Changes -- call `changes_load` +- call `<%= it.config.tools.changes_load.name %>` <%= it.rules ?? "" %> - Store the returned result as `` - Use `` as the source of truth; no additional git analysis commands are needed diff --git a/packages/core/components/changes-summary.md b/packages/core/components/changes-summary.md index 1313bb3..3621e6f 100644 --- a/packages/core/components/changes-summary.md +++ b/packages/core/components/changes-summary.md @@ -10,4 +10,4 @@ - Merge tiny themes together instead of creating a section per file or implementation detail - Do not restate the full diff - Do not use execution-status notes such as `Validation not run in this session` as checklist items -- If `changes_load` reports uncommitted work, make that clear in the ticket wording +- If `<%= it.config.tools.changes_load.name %>` reports uncommitted work, make that clear in the ticket wording diff --git a/packages/core/components/load-pr.md b/packages/core/components/load-pr.md index b980349..a4aa2b1 100644 --- a/packages/core/components/load-pr.md +++ b/packages/core/components/load-pr.md @@ -1,7 +1,7 @@ -- Use `pr_load` as the source of truth for PR selection -- If `<%= it.ref %>` is defined, call `pr_load` with `pr: <%= it.ref %>` -- Otherwise, call `pr_load` with no arguments -- Do not run separate git or GitHub commands just to discover the PR before calling `pr_load` +- Use `<%= it.config.tools.pr_load.name %>` as the source of truth for PR selection +- If `<%= it.ref %>` is defined, call `<%= it.config.tools.pr_load.name %>` with `pr: <%= it.ref %>` +- Otherwise, call `<%= it.config.tools.pr_load.name %>` with no arguments +- 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 diff --git a/packages/core/components/load-ticket.md b/packages/core/components/load-ticket.md index f013b0d..9093767 100644 --- a/packages/core/components/load-ticket.md +++ b/packages/core/components/load-ticket.md @@ -1,4 +1,4 @@ -- Use `ticket_load` with `source: <%= it.source %>`<% if (it.comments === true) { %> and `comments: true`<% } %> +- Use `<%= it.config.tools.ticket_load.name %>` with `source: <%= it.source %>`<% if (it.comments === true) { %> and `comments: true`<% } %> - Store the result as `<%= it.result %>` - Treat the loaded ticket body, discussion, and any attachments or linked artifacts returned by the loader as part of the source context - Review attached images, PDFs, and other linked files whenever they can affect requirements, acceptance criteria, reproduction steps, design direction, or the requested answer diff --git a/packages/core/index.ts b/packages/core/index.ts index a91d9da..1064336 100644 --- a/packages/core/index.ts +++ b/packages/core/index.ts @@ -3,6 +3,9 @@ export type { ResolvedAgentDefinition } from "./agents/index.ts"; export { resolveCommands, commandDefinitions } from "./commands/index.ts"; export type { ResolvedCommandDefinition } from "./commands/index.ts"; export { + getConfiguredAgentNames, + getConfiguredCommandNames, + getConfiguredToolNames, getConfiguredToolName, getEnabledToolNames, loadKompassConfig, @@ -10,6 +13,8 @@ export { } from "./lib/config.ts"; export type { AgentDefinition, + AgentName, + CommandName, KompassConfig, MergedKompassConfig, ToolName, diff --git a/packages/core/lib/config.ts b/packages/core/lib/config.ts index 9537d0c..3f29cca 100644 --- a/packages/core/lib/config.ts +++ b/packages/core/lib/config.ts @@ -6,6 +6,7 @@ import { fileURLToPath } from "node:url"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); export interface AgentDefinition { + name?: string; description: string; promptPath?: string; permission: Record; @@ -74,6 +75,7 @@ export interface ToggleConfig { } export interface CommandConfig extends ToggleConfig { + name?: string; template?: string; [key: string]: unknown; } @@ -528,6 +530,36 @@ export function getConfiguredToolName( return tools[toolName].name ?? toolName; } +export function getConfiguredToolNames( + tools: MergedKompassConfig["tools"], +): Record { + return Object.fromEntries( + DEFAULT_TOOL_NAMES.map((toolName) => [toolName, { name: getConfiguredToolName(tools, toolName) }]), + ) as Record; +} + +export function getConfiguredCommandNames( + commands: MergedKompassConfig["commands"], +): Record { + return Object.fromEntries( + DEFAULT_COMMAND_NAMES.map((commandName) => [ + commandName, + { name: commands.entries[commandName]?.name ?? commandName }, + ]), + ) as Record; +} + +export function getConfiguredAgentNames( + agents: MergedKompassConfig["agents"], +): Record { + return Object.fromEntries( + DEFAULT_AGENT_NAMES.map((agentName) => [ + agentName, + { name: agents[agentName].name ?? agentName }, + ]), + ) as Record; +} + export function mergeWithDefaults( config: KompassConfig | null, ): MergedKompassConfig { diff --git a/packages/core/tools/dispatch.ts b/packages/core/tools/dispatch.ts index db37a27..0c5a56c 100644 --- a/packages/core/tools/dispatch.ts +++ b/packages/core/tools/dispatch.ts @@ -1,4 +1,5 @@ import { resolveCommands } from "../commands/index.ts"; +import type { AgentName, CommandName, ToolName } from "../lib/config.ts"; import type { ToolDefinition, ToolExecutionContext } from "./shared.ts"; export type CommandExpansion = { @@ -9,7 +10,11 @@ export type CommandExpansion = { }; type ResolveCommandExpansionOptions = { - rewriteBody?: (body: string) => string; + names?: { + tools?: Partial>; + commands?: Partial>; + agents?: Partial>; + }; }; type CommandExpansionInput = { @@ -47,7 +52,7 @@ export async function resolveCommandExpansion( throw new Error("command_expansion requires a command"); } - const commands = await resolveCommands(projectRoot); + const commands = await resolveCommands(projectRoot, { names: options?.names }); const definition = commands[normalizedCommand]; if (!definition) { @@ -59,11 +64,7 @@ export async function resolveCommandExpansion( }; } - let prompt = expandCommandTemplate(definition.template, normalizedBody); - - if (options?.rewriteBody) { - prompt = options.rewriteBody(prompt); - } + const prompt = expandCommandTemplate(definition.template, normalizedBody); return { command: normalizedCommand, diff --git a/packages/opencode/cache.ts b/packages/opencode/cache.ts index 0aacf05..3eb5a19 100644 --- a/packages/opencode/cache.ts +++ b/packages/opencode/cache.ts @@ -1,23 +1,32 @@ import { - getEnabledToolNames, + getConfiguredAgentNames, + getConfiguredCommandNames, loadKompassConfig, mergeWithDefaults, + type AgentName, + type CommandName, resolveAgents, resolveCommands, type MergedKompassConfig, type ResolvedAgentDefinition, type ResolvedCommandDefinition, + type ToolName, } from "../core/index.ts"; import { getConfiguredOpenCodeToolName, - prefixKompassToolReferences, } from "./tool-names.ts"; const mergedConfigCache = new Map>(); -const configuredToolNamesCache = new Map>>(); +const configuredNamesCache = new Map>(); const resolvedAgentsCache = new Map>>(); const resolvedCommandsCache = new Map>>(); +type ConfiguredNames = { + tools: Record; + commands: Record; + agents: Record; +}; + function readThroughCache(cache: Map>, key: string, load: () => Promise): Promise { const cached = cache.get(key); if (cached) return cached; @@ -37,29 +46,34 @@ export function loadMergedKompassConfig(projectRoot: string): Promise> { - return readThroughCache(configuredToolNamesCache, projectRoot, async () => { +export function loadConfiguredNames(projectRoot: string): Promise { + return readThroughCache(configuredNamesCache, projectRoot, async () => { const config = await loadMergedKompassConfig(projectRoot); - return Object.fromEntries( - getEnabledToolNames(config.tools).map((toolName) => [ - toolName, - getConfiguredOpenCodeToolName(toolName, config.tools[toolName].name), - ]), - ); + return { + tools: Object.fromEntries( + Object.entries(config.tools).map(([toolName, toolConfig]) => [ + toolName, + { name: getConfiguredOpenCodeToolName(toolName, toolConfig.name) }, + ]), + ) as Record, + commands: getConfiguredCommandNames(config.commands), + agents: getConfiguredAgentNames(config.agents), + }; }); } -export async function rewriteKompassToolReferences(projectRoot: string, input: string): Promise { - const configuredToolNames = await loadConfiguredToolNames(projectRoot); - return prefixKompassToolReferences(input, configuredToolNames); -} - export function loadResolvedAgents(projectRoot: string): Promise> { - return readThroughCache(resolvedAgentsCache, projectRoot, () => resolveAgents(projectRoot)); + return readThroughCache(resolvedAgentsCache, projectRoot, async () => { + const names = await loadConfiguredNames(projectRoot); + return resolveAgents(projectRoot, { names }); + }); } export function loadResolvedCommands(projectRoot: string): Promise> { const ciKey = process.env.CI ? "ci" : "non-ci"; const cacheKey = `${projectRoot}:${ciKey}`; - return readThroughCache(resolvedCommandsCache, cacheKey, () => resolveCommands(projectRoot)); + return readThroughCache(resolvedCommandsCache, cacheKey, async () => { + const names = await loadConfiguredNames(projectRoot); + return resolveCommands(projectRoot, { names }); + }); } diff --git a/packages/opencode/config.ts b/packages/opencode/config.ts index 9c58f7d..26bab67 100644 --- a/packages/opencode/config.ts +++ b/packages/opencode/config.ts @@ -1,7 +1,7 @@ import type { Config } from "@opencode-ai/plugin"; import type { AgentConfig } from "@opencode-ai/sdk"; -import { loadResolvedAgents, loadResolvedCommands, rewriteKompassToolReferences } from "./cache.ts"; +import { loadResolvedAgents, loadResolvedCommands } from "./cache.ts"; import type { PluginLogger } from "./logging.ts"; type ApplyConfigOptions = { @@ -21,7 +21,7 @@ export async function applyAgentsConfig( const agentConfig: AgentConfig = { description: definition.description, permission: definition.permission, - ...(definition.prompt ? { prompt: await rewriteKompassToolReferences(projectRoot, definition.prompt) } : {}), + ...(definition.prompt ? { prompt: definition.prompt } : {}), ...(definition.mode ? { mode: definition.mode } : {}), }; cfg.agent[name] = agentConfig; @@ -48,7 +48,7 @@ export async function applyCommandsConfig( description: definition.description, agent: definition.agent, subtask: definition.subtask, - template: await rewriteKompassToolReferences(projectRoot, definition.template), + template: definition.template, }; options?.logger?.info("Loaded Kompass command", { diff --git a/packages/opencode/index.ts b/packages/opencode/index.ts index 87adc02..07eabbd 100644 --- a/packages/opencode/index.ts +++ b/packages/opencode/index.ts @@ -12,12 +12,11 @@ import { type MergedKompassConfig, type Shell, } from "../core/index.ts"; -import { loadConfiguredToolNames, loadMergedKompassConfig } from "./cache.ts"; +import { loadConfiguredNames, loadMergedKompassConfig } from "./cache.ts"; import { applyAgentsConfig, applyCommandsConfig } from "./config.ts"; import { createPluginLogger, getErrorDetails, type PluginLogger } from "./logging.ts"; import { getConfiguredOpenCodeToolName, - prefixKompassToolReferences, } from "./tool-names.ts"; const AGENT_HANDOFF_MARKER = "generate a prompt and call the task tool with subagent:"; @@ -150,10 +149,8 @@ const opencodeToolCreators: Record = { body: tool.schema.string().describe("Literal body content from the delegate block").optional(), }, execute: async (args, context) => { - const configuredToolNames = await loadConfiguredToolNames(projectRoot); - const definition = createCommandExpansionTool(projectRoot, { - rewriteBody: (body) => prefixKompassToolReferences(body, configuredToolNames), - }); + const names = await loadConfiguredNames(projectRoot); + const definition = createCommandExpansionTool(projectRoot, { names }); context.metadata({ title: `Command /${args.command.trim()}`, diff --git a/packages/opencode/scripts/compile.ts b/packages/opencode/scripts/compile.ts index f9b2f6f..b5ce0a4 100644 --- a/packages/opencode/scripts/compile.ts +++ b/packages/opencode/scripts/compile.ts @@ -11,22 +11,21 @@ import { mkdir, writeFile, rm, access } from "node:fs/promises"; import path from "node:path"; import { fileURLToPath } from "node:url"; import YAML from "yaml"; - -const __dirname = path.dirname(fileURLToPath(import.meta.url)); -const PACKAGE_ROOT = path.resolve(__dirname, ".."); -const WORKSPACE_ROOT = path.resolve(PACKAGE_ROOT, "..", ".."); -const OUTPUT_DIR = path.resolve(PACKAGE_ROOT, ".opencode"); import { + getConfiguredAgentNames, + getConfiguredCommandNames, getEnabledToolNames, loadKompassConfig, mergeWithDefaults, resolveAgents, resolveCommands, } from "../../core/index.ts"; -import { - getConfiguredOpenCodeToolName, - prefixKompassToolReferences, -} from "../tool-names.ts"; +import { getConfiguredOpenCodeToolName } from "../tool-names.ts"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const PACKAGE_ROOT = path.resolve(__dirname, ".."); +const WORKSPACE_ROOT = path.resolve(PACKAGE_ROOT, "..", ".."); +const OUTPUT_DIR = path.resolve(PACKAGE_ROOT, ".opencode"); function ensureTrailingNewline(text: string) { return text.endsWith("\n") ? text : `${text}\n`; @@ -53,16 +52,20 @@ async function main() { const config = mergeWithDefaults(userConfig); const enabledTools = getEnabledToolNames(config.tools); const configuredToolNames = Object.fromEntries( - enabledTools.map((toolName) => [ + Object.entries(config.tools).map(([toolName, toolConfig]) => [ toolName, - getConfiguredOpenCodeToolName(toolName, config.tools[toolName].name), + { name: getConfiguredOpenCodeToolName(toolName, toolConfig.name) }, ]), ); - const rewriteToolNames = (input: string) => prefixKompassToolReferences(input, configuredToolNames); + const names = { + tools: configuredToolNames, + commands: getConfiguredCommandNames(config.commands), + agents: getConfiguredAgentNames(config.agents), + }; // Compile commands console.log("\nCompiling commands..."); - const compiledCommands = await resolveCommands(WORKSPACE_ROOT, { ci: false }); + const compiledCommands = await resolveCommands(WORKSPACE_ROOT, { ci: false, names }); console.log(` Compiled ${Object.keys(compiledCommands).length} commands`); // Create output directories @@ -87,7 +90,7 @@ async function main() { agent: command.agent, }); const content = ensureTrailingNewline( - `---\n${frontmatter}---\n\n${rewriteToolNames(command.template)}`, + `---\n${frontmatter}---\n\n${command.template}`, ); await writeFile(filepath, content); console.log(` commands/${name}.md`); @@ -95,7 +98,7 @@ async function main() { // Compile agents console.log("\nCompiling agents..."); - const resolvedAgents = await resolveAgents(WORKSPACE_ROOT); + const resolvedAgents = await resolveAgents(WORKSPACE_ROOT, { names }); for (const [agentName, agent] of Object.entries(resolvedAgents)) { if (!agent) continue; @@ -111,7 +114,7 @@ async function main() { }); const content = ensureTrailingNewline( - `---\n${frontmatter}---\n${agent.prompt ? `\n${rewriteToolNames(agent.prompt)}` : ""}`, + `---\n${frontmatter}---\n${agent.prompt ? `\n${agent.prompt}` : ""}`, ); await writeFile(filepath, content); console.log(` agents/${filename}`); diff --git a/packages/opencode/test/agents-config.test.ts b/packages/opencode/test/agents-config.test.ts index 82945fc..c9aa3ca 100644 --- a/packages/opencode/test/agents-config.test.ts +++ b/packages/opencode/test/agents-config.test.ts @@ -1,5 +1,6 @@ import { describe, test } from "node:test"; import assert from "node:assert/strict"; +import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises"; import os from "node:os"; import path from "node:path"; @@ -91,4 +92,32 @@ describe("applyAgentsConfig", () => { todowrite: "allow", }); }); + + test("registers configured agent aliases", async () => { + const tempDir = await mkdtemp(path.join(os.tmpdir(), "kompass-agent-alias-")); + + try { + await mkdir(path.join(tempDir, ".opencode"), { recursive: true }); + await writeFile( + path.join(tempDir, ".opencode", "kompass.jsonc"), + `{ + "agents": { + "reviewer": { + "enabled": true, + "name": "code-reviewer" + } + } + }`, + ); + + const cfg: { agent?: Record } = {}; + + await applyAgentsConfig(cfg as never, tempDir); + + assert.ok(cfg.agent?.["code-reviewer"]); + assert.equal(cfg.agent?.reviewer, undefined); + } finally { + await rm(tempDir, { recursive: true, force: true }); + } + }); }); diff --git a/packages/opencode/test/commands-config.test.ts b/packages/opencode/test/commands-config.test.ts index 5f8a046..dea389d 100644 --- a/packages/opencode/test/commands-config.test.ts +++ b/packages/opencode/test/commands-config.test.ts @@ -162,6 +162,36 @@ describe("applyCommandsConfig", () => { } }); + test("registers configured command aliases", async () => { + delete process.env.CI; + const tempDir = await mkdtemp(path.join(os.tmpdir(), "kompass-command-alias-")); + + try { + await mkdir(path.join(tempDir, ".opencode"), { recursive: true }); + await writeFile( + path.join(tempDir, ".opencode", "kompass.jsonc"), + `{ + "commands": { + "pr/create": { + "enabled": true, + "name": "open-pr" + } + } + }`, + ); + + const cfg: { command?: Record } = {}; + + await applyCommandsConfig(cfg as never, tempDir); + + assert.ok(cfg.command?.["open-pr"]); + assert.equal(cfg.command?.["pr/create"], undefined); + assert.match(cfg.command?.ship.template ?? "", /command="open-pr"/); + } finally { + await rm(tempDir, { recursive: true, force: true }); + } + }); + test("supports object-based command toggles", async () => { delete process.env.CI; const tempDir = await mkdtemp(path.join(os.tmpdir(), "kompass-command-entries-")); diff --git a/packages/opencode/tool-names.ts b/packages/opencode/tool-names.ts index 3f46738..f2e76aa 100644 --- a/packages/opencode/tool-names.ts +++ b/packages/opencode/tool-names.ts @@ -8,20 +8,3 @@ export function getConfiguredOpenCodeToolName( ) { return configuredName ?? getOpenCodeToolName(toolName); } - -export function prefixKompassToolReferences( - input: string, - toolNames: Record, -) { - let output = input; - - for (const [toolName, resolvedToolName] of Object.entries(toolNames)) { - const escapedToolName = toolName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - output = output.replaceAll( - new RegExp(`(?.enabled` +- `.name` - `.template` - legacy `enabled` and `templates` fields are still recognized for compatibility ### `agents` -Controls bundled agent definitions and enablement. +Controls bundled agent definitions, enablement, and adapter-facing agent names. +- `name` - `description` - `promptPath` - `permission` @@ -66,7 +68,7 @@ Adapter-specific configuration. Today this includes: ## Tool alias example -You can keep a Kompass tool enabled but expose it under a custom adapter-facing name: +You can keep bundled surfaces enabled but expose them under custom adapter-facing names. OpenCode tool defaults use `kompass_`, such as `kompass_ticket_load`; command and agent defaults use their config keys. Built-in command, component, and agent templates read resolved names from render config values like `<%= it.config.tools.ticket_load.name %>`. ```jsonc { @@ -75,6 +77,18 @@ You can keep a Kompass tool enabled but expose it under a custom adapter-facing "enabled": true, "name": "custom_ticket_name" } + }, + "commands": { + "pr/create": { + "enabled": true, + "name": "open-pr" + } + }, + "agents": { + "reviewer": { + "enabled": true, + "name": "code-reviewer" + } } } ```