From deddacf06324159bbd6e3056d704f9a48e8ca098 Mon Sep 17 00:00:00 2001 From: Josh Holmer Date: Fri, 15 May 2026 08:14:56 -0400 Subject: [PATCH] fix: only include semantic search tool hint when enabled --- .../opencode/src/kilocode/tool/registry.ts | 11 ++++ packages/opencode/src/tool/grep.txt | 1 - packages/opencode/src/tool/registry.ts | 47 ++++++++------- .../kilocode/tool-registry-indexing.test.ts | 58 ++++++++++++++++++- 4 files changed, 93 insertions(+), 24 deletions(-) diff --git a/packages/opencode/src/kilocode/tool/registry.ts b/packages/opencode/src/kilocode/tool/registry.ts index 8db1561d477..503214db543 100644 --- a/packages/opencode/src/kilocode/tool/registry.ts +++ b/packages/opencode/src/kilocode/tool/registry.ts @@ -13,6 +13,9 @@ const log = Log.create({ service: "kilocode-tool-registry" }) type Deps = { agent: Agent.Interface; truncate: Truncate.Interface } export namespace KiloToolRegistry { + const hint = + "- When you are doing an open-ended search where you do not know the exact symbol name, use the `semantic_search` tool first to narrow down the search scope, then follow up with `Grep` and/or `Read`" + /** Resolve Kilo-specific tool Infos outside any InstanceState, so their Truncate/Agent deps are * satisfied at the outer registry scope instead of leaking into InstanceState's Effect. */ export function infos() { @@ -84,4 +87,12 @@ export namespace KiloToolRegistry { ...(Flag.KILO_CLIENT === "vscode" && cfg.experimental?.agent_manager_tool === true ? [tools.manager] : []), ] } + + export function describe(tools: Tool.Def[], extra: { semantic?: Tool.Def }): Tool.Def[] { + if (!extra.semantic) return tools + return tools.map((tool) => { + if (tool.id !== "glob" && tool.id !== "grep") return tool + return { ...tool, description: `${tool.description}\n${hint}` } + }) + } } diff --git a/packages/opencode/src/tool/grep.txt b/packages/opencode/src/tool/grep.txt index e9d53d057dd..7cc933291b7 100644 --- a/packages/opencode/src/tool/grep.txt +++ b/packages/opencode/src/tool/grep.txt @@ -5,5 +5,4 @@ - Returns file paths and line numbers with at least one match sorted by modification time - Use this tool when you need to find files containing specific patterns - If you need to identify/count the number of matches within files, use the Bash tool with `rg` (ripgrep) directly. Do NOT use `grep`. -- When you are doing an open-ended search where you do not know the exact symbol name, use the SemanticSearch tool instead - When you are doing a deep search that may require multiple tool invocations, use the Task tool instead diff --git a/packages/opencode/src/tool/registry.ts b/packages/opencode/src/tool/registry.ts index 6c64c82cec9..6a477aba09e 100644 --- a/packages/opencode/src/tool/registry.ts +++ b/packages/opencode/src/tool/registry.ts @@ -226,28 +226,31 @@ export const layer: Layer.Layer< return { custom, - builtin: [ - tool.invalid, - ...(questionEnabled ? [tool.question] : []), - tool.bash, - tool.read, - tool.glob, - tool.grep, - tool.edit, - tool.write, - tool.task, - tool.fetch, - tool.todo, - tool.search, - tool.skill, - tool.patch, - // kilocode_change start - tool.plan, - ...(["cli", "vscode"].includes(Flag.KILO_CLIENT) ? [tool.suggest] : []), - ...KiloToolRegistry.extra(kilo, cfg), - // kilocode_change end - ...(Flag.KILO_EXPERIMENTAL_LSP_TOOL ? [tool.lsp] : []), - ], + builtin: KiloToolRegistry.describe( + [ + tool.invalid, + ...(questionEnabled ? [tool.question] : []), + tool.bash, + tool.read, + tool.glob, + tool.grep, + tool.edit, + tool.write, + tool.task, + tool.fetch, + tool.todo, + tool.search, + tool.skill, + tool.patch, + // kilocode_change start + tool.plan, + ...(["cli", "vscode"].includes(Flag.KILO_CLIENT) ? [tool.suggest] : []), + ...KiloToolRegistry.extra(kilo, cfg), + // kilocode_change end + ...(Flag.KILO_EXPERIMENTAL_LSP_TOOL ? [tool.lsp] : []), + ], + kilo, + ), // kilocode_change task: tool.task, read: tool.read, } diff --git a/packages/opencode/test/kilocode/tool-registry-indexing.test.ts b/packages/opencode/test/kilocode/tool-registry-indexing.test.ts index b776ca5f3ee..1958b6e8925 100644 --- a/packages/opencode/test/kilocode/tool-registry-indexing.test.ts +++ b/packages/opencode/test/kilocode/tool-registry-indexing.test.ts @@ -1,10 +1,12 @@ import { afterEach, describe, expect, spyOn, test } from "bun:test" import { Effect, Layer, Schema } from "effect" import * as Log from "@opencode-ai/core/util/log" +import { Agent } from "../../src/agent/agent" import { KiloIndexing } from "../../src/kilocode/indexing" import { KilocodeBootstrap } from "../../src/kilocode/bootstrap" import { KiloSessions } from "../../src/kilo-sessions/kilo-sessions" import { KiloToolRegistry } from "../../src/kilocode/tool/registry" +import { ModelID, ProviderID } from "../../src/provider/schema" import { ToolRegistry } from "../../src/tool/registry" import type * as Tool from "../../src/tool/tool" import { Instance } from "../../src/project/instance" @@ -13,7 +15,11 @@ import * as CrossSpawnSpawner from "@opencode-ai/core/cross-spawn-spawner" import { testEffect } from "../lib/effect" const node = CrossSpawnSpawner.defaultLayer -const it = testEffect(Layer.mergeAll(ToolRegistry.defaultLayer, node)) +const it = testEffect(Layer.mergeAll(Agent.defaultLayer, ToolRegistry.defaultLayer, node)) +const ref = { + providerID: ProviderID.make("test"), + modelID: ModelID.make("test-model"), +} afterEach(async () => { await disposeAllInstances() @@ -120,6 +126,56 @@ describe("kilocode tool registry indexing", () => { ), ) + it.live("omits semantic_search hint from glob and grep descriptions when indexing is not ready", () => + provideTmpdirInstance( + () => + Effect.gen(function* () { + const ready = spyOn(KiloIndexing, "ready").mockReturnValue(false) + + try { + const agent = yield* Agent.Service + const build = yield* agent.get("build") + const registry = yield* ToolRegistry.Service + const tools = yield* registry.tools({ ...ref, agent: build }) + const glob = tools.find((tool) => tool.id === "glob")?.description ?? "" + const grep = tools.find((tool) => tool.id === "grep")?.description ?? "" + + expect(glob).not.toContain("semantic_search") + expect(grep).not.toContain("semantic_search") + } finally { + ready.mockRestore() + } + }), + { git: true }, + ), + ) + + it.live("includes semantic_search hint in glob and grep descriptions when indexing is ready", () => + provideTmpdirInstance( + () => + Effect.gen(function* () { + const ready = spyOn(KiloIndexing, "ready").mockReturnValue(true) + + try { + const agent = yield* Agent.Service + const build = yield* agent.get("build") + const registry = yield* ToolRegistry.Service + const tools = yield* registry.tools({ ...ref, agent: build }) + const ids = tools.map((tool) => tool.id) + const glob = tools.find((tool) => tool.id === "glob")?.description ?? "" + const grep = tools.find((tool) => tool.id === "grep")?.description ?? "" + + expect(ids).toContain("semantic_search") + expect(glob).toContain("semantic_search") + expect(grep).toContain("semantic_search") + } finally { + ready.mockRestore() + } + }), + { git: true }, + ), + ) + test("conditionally includes Kilo registry extras", () => { const prev = process.env["KILO_CLIENT"] const def = (id: string): Tool.Def => ({