Ver Fonte

chore(opencode): remove scout agent (#30435)

Shoubhit Dash há 3 meses atrás
pai
commit
a639fe7a08
30 ficheiros alterados com 26 adições e 925 exclusões
  1. 0 2
      packages/core/src/plugin/agent.ts
  2. 1 1
      packages/opencode/specs/effect/guide.md
  3. 1 1
      packages/opencode/specs/effect/todo.md
  4. 0 4
      packages/opencode/src/acp/tool.ts
  5. 0 36
      packages/opencode/src/agent/agent.ts
  6. 0 36
      packages/opencode/src/agent/prompt/scout.txt
  7. 1 1
      packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx
  8. 0 1
      packages/opencode/src/config/config.ts
  9. 0 2
      packages/opencode/src/config/permission.ts
  10. 1 1
      packages/opencode/src/config/reference.ts
  11. 1 1
      packages/opencode/src/effect/runtime-flags.ts
  12. 4 4
      packages/opencode/src/reference/reference.ts
  13. 1 1
      packages/opencode/src/session/prompt/reference.ts
  14. 3 4
      packages/opencode/src/skill/prompt/customize-opencode.md
  15. 0 12
      packages/opencode/src/tool/registry.ts
  16. 0 77
      packages/opencode/src/tool/repo_clone.ts
  17. 0 5
      packages/opencode/src/tool/repo_clone.txt
  18. 0 279
      packages/opencode/src/tool/repo_overview.ts
  19. 0 4
      packages/opencode/src/tool/repo_overview.txt
  20. 0 4
      packages/opencode/test/acp/tool.test.ts
  21. 1 24
      packages/opencode/test/agent/agent.test.ts
  22. 1 1
      packages/opencode/test/effect/runtime-flags.test.ts
  23. 4 4
      packages/opencode/test/reference/reference.test.ts
  24. 1 1
      packages/opencode/test/session/prompt.test.ts
  25. 2 2
      packages/opencode/test/tool/glob.test.ts
  26. 2 2
      packages/opencode/test/tool/grep.test.ts
  27. 2 2
      packages/opencode/test/tool/read.test.ts
  28. 0 23
      packages/opencode/test/tool/registry.test.ts
  29. 0 234
      packages/opencode/test/tool/repo_clone.test.ts
  30. 0 156
      packages/opencode/test/tool/repo_overview.test.ts

+ 0 - 2
packages/core/src/plugin/agent.ts

@@ -115,8 +115,6 @@ export const Plugin = PluginV2.define({
       { action: "question", resource: "*", effect: "deny" },
       { action: "plan_enter", resource: "*", effect: "deny" },
       { action: "plan_exit", resource: "*", effect: "deny" },
-      { action: "repo_clone", resource: "*", effect: "deny" },
-      { action: "repo_overview", resource: "*", effect: "deny" },
       { action: "read", resource: "*", effect: "allow" },
       { action: "read", resource: "*.env", effect: "ask" },
       { action: "read", resource: "*.env.*", effect: "ask" },

+ 1 - 1
packages/opencode/specs/effect/guide.md

@@ -70,7 +70,7 @@ mutable `Flag` or late `process.env` reads.
 Tests should vary behavior with explicit layer variants:
 
 ```ts
-const it = testEffect(MyService.defaultLayer.pipe(Layer.provide(RuntimeFlags.layer({ experimentalScout: true }))))
+const it = testEffect(MyService.defaultLayer.pipe(Layer.provide(RuntimeFlags.layer({ experimentalReferences: true }))))
 ```
 
 Do not mutate `process.env` or `Flag` after services/layers are built.

+ 1 - 1
packages/opencode/specs/effect/todo.md

@@ -172,7 +172,7 @@ Recently completed:
 - [x] Built-in websearch provider selection uses the same runtime flags as
       tool visibility.
 - [x] Removed global default-plugin disabling from test preload.
-- [x] `RF-1` Scout reads routed through runtime flags (#27318).
+- [x] `RF-1` Reference reads routed through runtime flags (#27318).
 - [x] `RF-2` Plan-mode prompt read routed through runtime flags (#27320).
 - [x] `RF-3` Event-system reads routed through runtime flags (#27323).
 - [x] `RF-4` Workspaces reads routed through runtime flags for session

+ 0 - 4
packages/opencode/src/acp/tool.ts

@@ -52,8 +52,6 @@ export function toToolKind(toolName: string): ToolKind {
 
     case "grep":
     case "glob":
-    case "repo_clone":
-    case "repo_overview":
     case "context":
     case "context7_resolve_library_id":
     case "context7_get_library_docs":
@@ -78,8 +76,6 @@ export function toLocations(toolName: string, input: ToolInput): ToolCallLocatio
 
     case "grep":
     case "glob":
-    case "repo_clone":
-    case "repo_overview":
     case "context":
     case "context7_resolve_library_id":
     case "context7_get_library_docs":

+ 0 - 36
packages/opencode/src/agent/agent.ts

@@ -11,7 +11,6 @@ import { ProviderTransform } from "@/provider/transform"
 import PROMPT_GENERATE from "./generate.txt"
 import PROMPT_COMPACTION from "./prompt/compaction.txt"
 import PROMPT_EXPLORE from "./prompt/explore.txt"
-import PROMPT_SCOUT from "./prompt/scout.txt"
 import PROMPT_SUMMARY from "./prompt/summary.txt"
 import PROMPT_TITLE from "./prompt/title.txt"
 import { Permission } from "@/permission"
@@ -22,7 +21,6 @@ import { Plugin } from "@/plugin"
 import { Skill } from "../skill"
 import { Effect, Context, Layer, Schema } from "effect"
 import { InstanceState } from "@/effect/instance-state"
-import { RuntimeFlags } from "@/effect/runtime-flags"
 import * as Option from "effect/Option"
 import * as OtelTracer from "@effect/opentelemetry/Tracer"
 import { type DeepMutable } from "@opencode-ai/core/schema"
@@ -89,7 +87,6 @@ export const layer = Layer.effect(
     const plugin = yield* Plugin.Service
     const skill = yield* Skill.Service
     const provider = yield* Provider.Service
-    const flags = yield* RuntimeFlags.Service
 
     const state = yield* InstanceState.make<State>(
       Effect.fn("Agent.state")(function* (ctx) {
@@ -115,8 +112,6 @@ export const layer = Layer.effect(
           question: "deny",
           plan_enter: "deny",
           plan_exit: "deny",
-          repo_clone: "deny",
-          repo_overview: "deny",
           // mirrors github.com/github/gitignore Node.gitignore pattern for .env files
           read: {
             "*": "allow",
@@ -204,36 +199,6 @@ export const layer = Layer.effect(
             mode: "subagent",
             native: true,
           },
-          ...(flags.experimentalScout
-            ? {
-                scout: {
-                  name: "scout",
-                  permission: Permission.merge(
-                    defaults,
-                    Permission.fromConfig({
-                      "*": "deny",
-                      grep: "allow",
-                      glob: "allow",
-                      webfetch: "allow",
-                      websearch: "allow",
-                      read: "allow",
-                      repo_clone: "allow",
-                      repo_overview: "allow",
-                      external_directory: {
-                        ...readonlyExternalDirectory,
-                        [path.join(Global.Path.repos, "*")]: "allow",
-                      },
-                    }),
-                    user,
-                  ),
-                  description: `Docs and dependency-source specialist. Use this when you need to inspect external documentation, clone dependency repositories into the managed cache, and research library implementation details without modifying the user's workspace.`,
-                  prompt: PROMPT_SCOUT,
-                  options: {},
-                  mode: "subagent" as const,
-                  native: true,
-                },
-              }
-            : {}),
           compaction: {
             name: "compaction",
             mode: "primary",
@@ -462,7 +427,6 @@ export const defaultLayer = layer.pipe(
   Layer.provide(Auth.defaultLayer),
   Layer.provide(Config.defaultLayer),
   Layer.provide(Skill.defaultLayer),
-  Layer.provide(RuntimeFlags.defaultLayer),
 )
 
 export * as Agent from "./agent"

+ 0 - 36
packages/opencode/src/agent/prompt/scout.txt

@@ -1,36 +0,0 @@
-You are `scout`, a read-only research agent for external libraries, dependency source, and documentation.
-
-Your purpose is to investigate code outside the local workspace and return evidence-backed findings without modifying the user's workspace.
-
-Use this agent when asked to:
-- inspect dependency repositories or library source
-- compare local code against upstream implementations
-- research public GitHub repositories the environment can clone
-- explain how a library or framework works by reading its source and docs
-- investigate third-party APIs, workflows, or behavior outside the current workspace
-
-Working style:
-1. When the task involves a GitHub repository or dependency source, use `repo_clone` first.
-2. After cloning, use `Glob`, `Grep`, and `Read` to inspect the cloned repository.
-3. Use `WebFetch` for official documentation pages when source alone is not enough.
-4. Prefer direct code and documentation evidence over assumptions.
-5. If multiple external repositories are relevant, inspect each one before drawing conclusions.
-
-Research standards:
-- cite exact absolute file paths and line references whenever possible
-- separate what is verified from what is inferred
-- if the answer depends on branch state, note that you are reading the repository's current default clone state unless the caller specifies otherwise
-- if a repository cannot be cloned or accessed, say so explicitly and continue with whatever evidence is still available
-- call out uncertainty clearly instead of smoothing over gaps
-
-Output expectations:
-- start with the direct answer
-- then explain the evidence repository by repository or source by source
-- include file references when relevant
-- keep the explanation organized and easy to scan
-
-Constraints:
-- do not modify files or run tools that change the user's workspace
-- return absolute file paths for cloned-repo findings in your final response
-
-Complete the user's research request efficiently and report your findings clearly.

+ 1 - 1
packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx

@@ -325,7 +325,7 @@ export function Autocomplete(props: {
       ...(problem
         ? [`Problem: ${problem}`]
         : [
-            "For targeted context, inspect the reference path directly with Read, Glob, and Grep. For broader research, call the task tool with subagent scout and include this reference path.",
+            "Inspect the configured reference with Read, Glob, and Grep when useful.",
           ]),
     ].join("\n")
   }

+ 0 - 1
packages/opencode/src/config/config.ts

@@ -209,7 +209,6 @@ export const Info = Schema.Struct({
         // subagent
         general: Schema.optional(ConfigAgent.Info),
         explore: Schema.optional(ConfigAgent.Info),
-        scout: Schema.optional(ConfigAgent.Info),
         // specialized
         title: Schema.optional(ConfigAgent.Info),
         summary: Schema.optional(ConfigAgent.Info),

+ 0 - 2
packages/opencode/src/config/permission.ts

@@ -27,8 +27,6 @@ const InputObject = Schema.StructWithRest(
     question: Schema.optional(Action),
     webfetch: Schema.optional(Action),
     websearch: Schema.optional(Action),
-    repo_clone: Schema.optional(Rule),
-    repo_overview: Schema.optional(Rule),
     lsp: Schema.optional(Rule),
     doom_loop: Schema.optional(Action),
     skill: Schema.optional(Rule),

+ 1 - 1
packages/opencode/src/config/reference.ts

@@ -7,7 +7,7 @@ const Git = Schema.Struct({
     description: "Git repository URL, host/path reference, or GitHub owner/repo shorthand",
   }),
   branch: Schema.optional(Schema.String).annotate({
-    description: "Branch or ref Scout should clone and inspect",
+    description: "Branch or ref to clone and inspect",
   }),
 })
 

+ 1 - 1
packages/opencode/src/effect/runtime-flags.ts

@@ -39,7 +39,7 @@ export class Service extends ConfigService.Service<Service>()("@opencode/Runtime
   }).pipe(Config.map((flags) => flags.enabled || flags.legacy)),
   enableExperimentalModels: bool("OPENCODE_ENABLE_EXPERIMENTAL_MODELS"),
   enableQuestionTool: bool("OPENCODE_ENABLE_QUESTION_TOOL"),
-  experimentalScout: enabledByExperimental("OPENCODE_EXPERIMENTAL_SCOUT"),
+  experimentalReferences: enabledByExperimental("OPENCODE_EXPERIMENTAL_REFERENCES"),
   experimentalBackgroundSubagents: enabledByExperimental("OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS"),
   experimentalLspTy: bool("OPENCODE_EXPERIMENTAL_LSP_TY"),
   experimentalLspTool: enabledByExperimental("OPENCODE_EXPERIMENTAL_LSP_TOOL"),

+ 4 - 4
packages/opencode/src/reference/reference.ts

@@ -125,7 +125,7 @@ const materializers = Effect.fn("Reference.materializers")(function* (
 })
 
 function materializeAll(input: { flags: RuntimeFlags.Info; materializers: Materializer[] }) {
-  if (!input.flags.experimentalScout) return Effect.void
+  if (!input.flags.experimentalReferences) return Effect.void
   return Effect.forEach(
     input.materializers,
     Effect.fnUntraced(function* (item) {
@@ -205,7 +205,7 @@ export const layer = Layer.effect(
 
     return Service.of({
       init: Effect.fn("Reference.init")(function* () {
-        if (!flags.experimentalScout) return
+        if (!flags.experimentalReferences) return
         yield* InstanceState.useEffect(state, (s) => s.materializeAll).pipe(Effect.forkIn(scope), Effect.asVoid)
       }),
       list: Effect.fn("Reference.list")(function* () {
@@ -215,13 +215,13 @@ export const layer = Layer.effect(
         return yield* InstanceState.use(state, (s) => s.references.find((reference) => reference.name === name))
       }),
       ensure: Effect.fn("Reference.ensure")(function* (target?: string) {
-        if (!flags.experimentalScout) return
+        if (!flags.experimentalReferences) return
         const full = normalizedTarget(target)
         if (!full) return yield* InstanceState.useEffect(state, (s) => s.materializeAll)
         return yield* InstanceState.useEffect(state, (s) => materializeByPath(s.materializeByPath, full))
       }),
       contains: Effect.fn("Reference.contains")(function* (target?: string) {
-        if (!flags.experimentalScout) return false
+        if (!flags.experimentalReferences) return false
         const full = normalizedTarget(target)
         if (!full) return false
         return yield* InstanceState.use(state, (s) => containsGitReferencePath(s.references, full))

+ 1 - 1
packages/opencode/src/session/prompt/reference.ts

@@ -64,7 +64,7 @@ export function referenceTextPart(input: {
       ...(metadata.problem
         ? [`Problem: ${metadata.problem}`]
         : [
-            "For targeted context, inspect the reference path directly with Read, Glob, and Grep. For broader research, call the task tool with subagent scout and include this reference path.",
+            "Inspect the configured reference with Read, Glob, and Grep when useful.",
           ]),
     ].join("\n"),
     metadata: { reference: metadata },

+ 3 - 4
packages/opencode/src/skill/prompt/customize-opencode.md

@@ -227,8 +227,7 @@ file, `disable: true` in frontmatter.
 
 ### Built-in agents
 
-opencode ships with `build`, `plan`, `general`, `explore`, plus optionally
-`scout` (gated on `OPENCODE_EXPERIMENTAL_SCOUT`). Hidden internal agents:
+opencode ships with `build`, `plan`, `general`, `explore`. Hidden internal agents:
 `compaction`, `title`, `summary`. To override a built-in's fields, define the
 same key in `agent: { <name>: { ... } }`.
 
@@ -335,8 +334,8 @@ rules last.
 everything" and is rarely what the user wants.
 
 Known permission keys: `read, edit, glob, grep, list, bash, task,
-external_directory, todowrite, question, webfetch, websearch, repo_clone,
-repo_overview, lsp, doom_loop, skill`. Some of these (`todowrite,
+external_directory, todowrite, question, webfetch, websearch, lsp, doom_loop,
+skill`. Some of these (`todowrite,
 question, webfetch, websearch, doom_loop`) only accept a flat
 action, not a per-pattern object.
 

+ 0 - 12
packages/opencode/src/tool/registry.ts

@@ -23,9 +23,6 @@ import { Plugin } from "../plugin"
 import { Provider } from "@/provider/provider"
 
 import { WebSearchTool } from "./websearch"
-import { RepoCloneTool } from "./repo_clone"
-import { RepoOverviewTool } from "./repo_overview"
-import { RepositoryCache } from "@/reference/repository-cache"
 import * as Log from "@opencode-ai/core/util/log"
 import { LspTool } from "./lsp"
 import * as Truncate from "./truncate"
@@ -48,7 +45,6 @@ import { Instruction } from "../session/instruction"
 import { AppFileSystem } from "@opencode-ai/core/filesystem"
 import { EventV2Bridge } from "@/event-v2-bridge"
 import { Agent } from "../agent/agent"
-import { Git } from "@/git"
 import { Skill } from "../skill"
 import { Permission } from "@/permission"
 import { Reference } from "@/reference/reference"
@@ -97,8 +93,6 @@ export const layer: Layer.Layer<
   | Session.Service
   | BackgroundJob.Service
   | Provider.Service
-  | Git.Service
-  | RepositoryCache.Service
   | Reference.Service
   | LSP.Service
   | Instruction.Service
@@ -130,8 +124,6 @@ export const layer: Layer.Layer<
     const plan = yield* PlanExitTool
     const webfetch = yield* WebFetchTool
     const websearch = yield* WebSearchTool
-    const repoClone = yield* RepoCloneTool
-    const repoOverview = yield* RepoOverviewTool
     const shell = yield* ShellTool
     const globtool = yield* GlobTool
     const writetool = yield* WriteTool
@@ -241,8 +233,6 @@ export const layer: Layer.Layer<
           fetch: Tool.init(webfetch),
           todo: Tool.init(todo),
           search: Tool.init(websearch),
-          repo_clone: Tool.init(repoClone),
-          repo_overview: Tool.init(repoOverview),
           skill: Tool.init(skilltool),
           patch: Tool.init(patchtool),
           question: Tool.init(question),
@@ -265,7 +255,6 @@ export const layer: Layer.Layer<
             tool.fetch,
             tool.todo,
             tool.search,
-            ...(flags.experimentalScout ? [tool.repo_clone, tool.repo_overview] : []),
             tool.skill,
             tool.patch,
             ...(flags.experimentalLspTool ? [tool.lsp] : []),
@@ -388,7 +377,6 @@ export const defaultLayer = Layer.suspend(() =>
       Layer.provide(Session.defaultLayer),
       Layer.provide(BackgroundJob.defaultLayer),
       Layer.provide(Provider.defaultLayer),
-      Layer.provide(Layer.mergeAll(Git.defaultLayer, RepositoryCache.defaultLayer)),
       Layer.provide(Reference.defaultLayer),
       Layer.provide(LSP.defaultLayer),
       Layer.provide(Instruction.defaultLayer),

+ 0 - 77
packages/opencode/src/tool/repo_clone.ts

@@ -1,77 +0,0 @@
-import { Effect, Schema } from "effect"
-import DESCRIPTION from "./repo_clone.txt"
-import * as Tool from "./tool"
-import { repositoryCachePath } from "@/util/repository"
-import { RepositoryCache } from "@/reference/repository-cache"
-
-export const Parameters = Schema.Struct({
-  repository: Schema.String.annotate({
-    description: "Repository to clone, as a git URL, host/path reference, or GitHub owner/repo shorthand",
-  }),
-  refresh: Schema.optional(Schema.Boolean).annotate({
-    description: "When true, fetches the latest remote state into the managed cache",
-  }),
-  branch: Schema.optional(Schema.String).annotate({
-    description: "Branch or ref to clone and inspect",
-  }),
-})
-
-type Metadata = {
-  repository: string
-  host: string
-  remote: string
-  localPath: string
-  status: "cached" | "cloned" | "refreshed"
-  head?: string
-  branch?: string
-}
-
-export const RepoCloneTool = Tool.define<typeof Parameters, Metadata, RepositoryCache.Service>(
-  "repo_clone",
-  Effect.gen(function* () {
-    const cache = yield* RepositoryCache.Service
-
-    return {
-      description: DESCRIPTION,
-      parameters: Parameters,
-      execute: (params: Schema.Schema.Type<typeof Parameters>, ctx: Tool.Context<Metadata>) =>
-        Effect.gen(function* () {
-          const reference = yield* RepositoryCache.parseRemoteReference(params.repository)
-          if (params.branch) yield* RepositoryCache.validateBranch(params.branch)
-
-          const repository = reference.label
-          const remote = reference.remote
-          const localPath = repositoryCachePath(reference)
-
-          yield* ctx.ask({
-            permission: "repo_clone",
-            patterns: [repository],
-            always: [repository],
-            metadata: {
-              repository,
-              remote,
-              path: localPath,
-              refresh: Boolean(params.refresh),
-              branch: params.branch,
-            },
-          })
-
-          const result = yield* cache.ensure({ reference, refresh: params.refresh, branch: params.branch })
-          return {
-            title: repository,
-            metadata: result,
-            output: [
-              `Repository ready: ${repository}`,
-              `Status: ${result.status}`,
-              `Local path: ${localPath}`,
-              ...(result.branch ? [`Branch: ${result.branch}`] : []),
-              ...(result.head ? [`HEAD: ${result.head}`] : []),
-            ].join("\n"),
-          }
-        }).pipe(
-          Effect.catchIf(RepositoryCache.isError, (error) => Effect.fail(new Error(error.message))),
-          Effect.orDie,
-        ),
-    } satisfies Tool.DefWithoutID<typeof Parameters, Metadata>
-  }),
-)

+ 0 - 5
packages/opencode/src/tool/repo_clone.txt

@@ -1,5 +0,0 @@
-- Clone or refresh a repository into OpenCode's managed cache under the data directory
-- Accepts git URLs, forge host/path references, or GitHub owner/repo shorthand
-- Returns the cached absolute local path so other tools can explore the cloned source
-- Use this before Read, Glob, or Grep when the code you need lives outside the current workspace
-- This tool is intended for dependency and documentation research workflows, not for modifying the user's workspace

+ 0 - 279
packages/opencode/src/tool/repo_overview.ts

@@ -1,279 +0,0 @@
-import path from "path"
-import { Effect, Schema } from "effect"
-import { AppFileSystem } from "@opencode-ai/core/filesystem"
-import { Git } from "@/git"
-import { assertExternalDirectoryEffect } from "./external-directory"
-import DESCRIPTION from "./repo_overview.txt"
-import * as Tool from "./tool"
-import { parseRepositoryReference, repositoryCachePath } from "@/util/repository"
-import { InstanceState } from "@/effect/instance-state"
-
-export const Parameters = Schema.Struct({
-  repository: Schema.optional(Schema.String).annotate({
-    description: "Cached repository to inspect, as a git URL, host/path reference, or GitHub owner/repo shorthand",
-  }),
-  path: Schema.optional(Schema.String).annotate({
-    description: "Directory path to inspect instead of a cached repository",
-  }),
-  depth: Schema.optional(Schema.Number).annotate({
-    description: "Maximum structure depth to include. Defaults to 3.",
-  }),
-})
-
-type Metadata = {
-  path: string
-  repository?: string
-  branch?: string
-  head?: string
-  package_manager?: string
-  ecosystems: string[]
-  dependency_files: string[]
-  entrypoints: string[]
-  depth: number
-  truncated: boolean
-}
-
-const IGNORED_DIRS = new Set([
-  ".git",
-  "node_modules",
-  "__pycache__",
-  ".venv",
-  "dist",
-  "build",
-  ".next",
-  "target",
-  "vendor",
-])
-const STRUCTURE_LIMIT = 200
-const DEPENDENCY_FILES = [
-  "package.json",
-  "package-lock.json",
-  "bun.lock",
-  "bun.lockb",
-  "pnpm-lock.yaml",
-  "yarn.lock",
-  "requirements.txt",
-  "pyproject.toml",
-  "go.mod",
-  "Cargo.toml",
-  "Gemfile",
-  "build.gradle",
-  "build.gradle.kts",
-  "pom.xml",
-  "composer.json",
-]
-
-function packageManager(files: Set<string>) {
-  if (files.has("bun.lock") || files.has("bun.lockb")) return "bun"
-  if (files.has("pnpm-lock.yaml")) return "pnpm"
-  if (files.has("yarn.lock")) return "yarn"
-  if (files.has("package-lock.json")) return "npm"
-}
-
-function ecosystems(files: Set<string>) {
-  return [
-    ...(files.has("package.json") ? ["Node.js"] : []),
-    ...(files.has("pyproject.toml") || files.has("requirements.txt") ? ["Python"] : []),
-    ...(files.has("go.mod") ? ["Go"] : []),
-    ...(files.has("Cargo.toml") ? ["Rust"] : []),
-    ...(files.has("Gemfile") ? ["Ruby"] : []),
-    ...(files.has("build.gradle") || files.has("build.gradle.kts") || files.has("pom.xml") ? ["Java/Kotlin"] : []),
-    ...(files.has("composer.json") ? ["PHP"] : []),
-  ]
-}
-
-function commonEntrypoints(files: Set<string>) {
-  return [
-    "index.ts",
-    "index.tsx",
-    "index.js",
-    "index.mjs",
-    "main.ts",
-    "main.js",
-    "src/index.ts",
-    "src/index.tsx",
-    "src/index.js",
-    "src/main.ts",
-    "src/main.js",
-  ].filter((file) => files.has(file))
-}
-
-export const RepoOverviewTool = Tool.define<typeof Parameters, Metadata, AppFileSystem.Service | Git.Service>(
-  "repo_overview",
-  Effect.gen(function* () {
-    const fs = yield* AppFileSystem.Service
-    const git = yield* Git.Service
-
-    const resolveTarget = Effect.fn("RepoOverviewTool.resolveTarget")(function* (
-      params: Schema.Schema.Type<typeof Parameters>,
-    ) {
-      if (params.path) {
-        const full = path.isAbsolute(params.path)
-          ? params.path
-          : path.resolve(yield* InstanceState.directory, params.path)
-        return { path: full, repository: params.repository }
-      }
-
-      if (!params.repository) throw new Error("Either repository or path is required")
-
-      const parsed = parseRepositoryReference(params.repository)
-      if (!parsed) throw new Error("Repository must be a git URL, host/path reference, or GitHub owner/repo shorthand")
-
-      const repository = parsed.label
-      return {
-        repository,
-        path: repositoryCachePath(parsed),
-      }
-    })
-
-    const structure = Effect.fn("RepoOverviewTool.structure")(function* (root: string, depth: number) {
-      let truncated = false
-      const lines: string[] = []
-
-      const visit: (dir: string, level: number) => Effect.Effect<void> = Effect.fnUntraced(function* (
-        dir: string,
-        level: number,
-      ) {
-        if (level >= depth || lines.length >= STRUCTURE_LIMIT) {
-          truncated = truncated || lines.length >= STRUCTURE_LIMIT
-          return
-        }
-
-        const entries = yield* fs.readDirectoryEntries(dir).pipe(Effect.orElseSucceed(() => []))
-        const sorted = yield* Effect.forEach(
-          entries,
-          Effect.fnUntraced(function* (entry) {
-            if (IGNORED_DIRS.has(entry.name)) return undefined
-            const full = path.join(dir, entry.name)
-            const info = yield* fs.stat(full).pipe(Effect.catch(() => Effect.succeed(undefined)))
-            if (!info) return undefined
-            return { name: entry.name, full, directory: info.type === "Directory" }
-          }),
-          { concurrency: 16 },
-        ).pipe(
-          Effect.map((items) =>
-            items
-              .filter((item): item is { name: string; full: string; directory: boolean } => Boolean(item))
-              .sort((a, b) => Number(b.directory) - Number(a.directory) || a.name.localeCompare(b.name)),
-          ),
-        )
-
-        for (const entry of sorted) {
-          if (lines.length >= STRUCTURE_LIMIT) {
-            truncated = true
-            return
-          }
-
-          lines.push(`${"  ".repeat(level)}${entry.name}${entry.directory ? "/" : ""}`)
-          if (entry.directory) yield* visit(entry.full, level + 1)
-        }
-      })
-
-      yield* visit(root, 0)
-      return { lines, truncated }
-    })
-
-    return {
-      description: DESCRIPTION,
-      parameters: Parameters,
-      execute: (params: Schema.Schema.Type<typeof Parameters>, ctx: Tool.Context<Metadata>) =>
-        Effect.gen(function* () {
-          const target = yield* resolveTarget(params)
-          const depth =
-            !params.depth || !Number.isInteger(params.depth) || params.depth < 1 || params.depth > 6 ? 3 : params.depth
-
-          yield* assertExternalDirectoryEffect(ctx, target.path, { kind: "directory" })
-          yield* ctx.ask({
-            permission: "repo_overview",
-            patterns: [target.repository ?? target.path],
-            always: [target.repository ?? target.path],
-            metadata: {
-              repository: target.repository,
-              path: target.path,
-              depth,
-            },
-          })
-
-          const info = yield* fs.stat(target.path).pipe(Effect.catch(() => Effect.succeed(undefined)))
-          if (!info) {
-            if (target.repository)
-              throw new Error(`Repository is not cloned: ${target.repository}. Use repo_clone first.`)
-            throw new Error(`Directory not found: ${target.path}`)
-          }
-          if (info.type !== "Directory") throw new Error(`Path is not a directory: ${target.path}`)
-
-          const entries = yield* fs.readDirectoryEntries(target.path).pipe(Effect.orElseSucceed(() => []))
-          const topLevel = new Set(entries.map((entry) => entry.name))
-          const dependencyFiles = DEPENDENCY_FILES.filter((file) => topLevel.has(file))
-          const packageJson = topLevel.has("package.json")
-            ? ((yield* fs
-                .readJson(path.join(target.path, "package.json"))
-                .pipe(Effect.orElseSucceed(() => ({})))) as Record<string, unknown>)
-            : {}
-
-          const entrypoints = [
-            ...(typeof packageJson.main === "string" ? [`main: ${packageJson.main}`] : []),
-            ...(typeof packageJson.module === "string" ? [`module: ${packageJson.module}`] : []),
-            ...(typeof packageJson.types === "string" ? [`types: ${packageJson.types}`] : []),
-            ...(typeof packageJson.bin === "string" ? [`bin: ${packageJson.bin}`] : []),
-            ...(packageJson.bin && typeof packageJson.bin === "object" && !Array.isArray(packageJson.bin)
-              ? Object.keys(packageJson.bin as Record<string, unknown>).map((name) => `bin: ${name}`)
-              : []),
-            ...(packageJson.exports && typeof packageJson.exports === "object" && !Array.isArray(packageJson.exports)
-              ? Object.keys(packageJson.exports as Record<string, unknown>)
-                  .slice(0, 10)
-                  .map((name) => `exports: ${name}`)
-              : []),
-          ]
-
-          const common = commonEntrypoints(
-            new Set([
-              ...topLevel,
-              ...entries
-                .filter((entry) => entry.name === "src")
-                .flatMap(() => ["src/index.ts", "src/index.tsx", "src/index.js", "src/main.ts", "src/main.js"]),
-            ]),
-          )
-          const structureResult = yield* structure(target.path, depth)
-          const branch = yield* git.branch(target.path)
-          const head = yield* git.run(["rev-parse", "HEAD"], { cwd: target.path })
-          const headText = head.exitCode === 0 ? head.text().trim() : undefined
-
-          const metadata: Metadata = {
-            path: target.path,
-            repository: target.repository,
-            branch,
-            head: headText,
-            package_manager: packageManager(topLevel),
-            ecosystems: ecosystems(topLevel),
-            dependency_files: dependencyFiles,
-            entrypoints: [...entrypoints, ...common.map((file) => `file: ${file}`)],
-            depth,
-            truncated: structureResult.truncated,
-          }
-
-          return {
-            title: target.repository ?? path.basename(target.path),
-            metadata,
-            output: [
-              `Path: ${target.path}`,
-              ...(target.repository ? [`Repository: ${target.repository}`] : []),
-              ...(branch ? [`Branch: ${branch}`] : []),
-              ...(headText ? [`HEAD: ${headText}`] : []),
-              ...(metadata.ecosystems.length ? [`Ecosystems: ${metadata.ecosystems.join(", ")}`] : []),
-              ...(metadata.package_manager ? [`Package manager: ${metadata.package_manager}`] : []),
-              ...(metadata.dependency_files.length
-                ? [`Dependency files: ${metadata.dependency_files.join(", ")}`]
-                : []),
-              ...(metadata.entrypoints.length
-                ? ["Likely entrypoints:", ...metadata.entrypoints.map((entry) => `- ${entry}`)]
-                : []),
-              "Top-level structure:",
-              ...structureResult.lines,
-              ...(structureResult.truncated ? ["(Structure truncated)"] : []),
-            ].join("\n"),
-          }
-        }).pipe(Effect.orDie),
-    } satisfies Tool.DefWithoutID<typeof Parameters, Metadata>
-  }),
-)

+ 0 - 4
packages/opencode/src/tool/repo_overview.txt

@@ -1,4 +0,0 @@
-- Summarize the structure and likely entrypoints of a cloned repository or local directory
-- Accepts either a cached repository reference or a directory path
-- Reports detected ecosystems, dependency files, package manager, likely entrypoints, and a compact structure tree
-- Use this after repo_clone to orient quickly before deeper Read, Glob, or Grep investigation

+ 0 - 4
packages/opencode/test/acp/tool.test.ts

@@ -19,8 +19,6 @@ describe("acp tool conversion", () => {
     expect(toToolKind("write")).toBe("edit")
     expect(toToolKind("grep")).toBe("search")
     expect(toToolKind("glob")).toBe("search")
-    expect(toToolKind("repo_clone")).toBe("search")
-    expect(toToolKind("repo_overview")).toBe("search")
     expect(toToolKind("context7_resolve_library_id")).toBe("search")
     expect(toToolKind("context7_get_library_docs")).toBe("search")
     expect(toToolKind("read")).toBe("read")
@@ -33,8 +31,6 @@ describe("acp tool conversion", () => {
     expect(toLocations("write", { filePath: "/tmp/c.ts" })).toEqual([{ path: "/tmp/c.ts" }])
     expect(toLocations("grep", { path: "/repo/src" })).toEqual([{ path: "/repo/src" }])
     expect(toLocations("glob", { path: "/repo/test" })).toEqual([{ path: "/repo/test" }])
-    expect(toLocations("repo_clone", { path: "/repo" })).toEqual([{ path: "/repo" }])
-    expect(toLocations("repo_overview", { path: "/repo" })).toEqual([{ path: "/repo" }])
     expect(toLocations("context7_get_library_docs", { path: "/docs" })).toEqual([{ path: "/docs" }])
     expect(toLocations("bash", { filePath: "/tmp/nope.ts", path: "/tmp" })).toEqual([])
     expect(toLocations("read", { path: "/tmp/missing-file-path.ts" })).toEqual([])

+ 1 - 24
packages/opencode/test/agent/agent.test.ts

@@ -26,7 +26,6 @@ const agentLayer = (flags: Partial<RuntimeFlags.Info> = {}) =>
   )
 
 const it = testEffect(agentLayer())
-const scout = testEffect(agentLayer({ experimentalScout: true }))
 
 // Helper to evaluate permission for a tool with wildcard pattern
 function evalPerm(agent: Agent.Info | undefined, permission: string): PermissionLegacy.Action | undefined {
@@ -56,7 +55,6 @@ it.instance("returns default native agents when no config", () =>
     expect(names).toContain("plan")
     expect(names).toContain("general")
     expect(names).toContain("explore")
-    expect(names).not.toContain("scout")
     expect(names).toContain("compaction")
     expect(names).toContain("title")
     expect(names).toContain("summary")
@@ -71,8 +69,6 @@ it.instance("build agent has correct default properties", () =>
     expect(build?.native).toBe(true)
     expect(evalPerm(build, "edit")).toBe("allow")
     expect(evalPerm(build, "bash")).toBe("allow")
-    expect(evalPerm(build, "repo_clone")).toBe("deny")
-    expect(evalPerm(build, "repo_overview")).toBe("deny")
   }),
 )
 
@@ -110,31 +106,12 @@ it.instance("explore agent asks for external directories and allows whitelisted
   }),
 )
 
-scout.instance("scout agent allows repo cloning and repo cache reads", () =>
-  Effect.gen(function* () {
-    const scout = yield* load((svc) => svc.get("scout"))
-    expect(scout).toBeDefined()
-    expect(scout?.mode).toBe("subagent")
-    expect(evalPerm(scout, "repo_clone")).toBe("allow")
-    expect(evalPerm(scout, "repo_overview")).toBe("allow")
-    expect(evalPerm(scout, "edit")).toBe("deny")
-    expect(
-      Permission.evaluate(
-        "external_directory",
-        path.join(Global.Path.repos, "github.com", "owner", "repo", "README.md"),
-        scout!.permission,
-      ).action,
-    ).toBe("allow")
-  }),
-)
-
-scout.instance(
+it.instance(
   "reference config does not create subagents",
   () =>
     Effect.gen(function* () {
       const agents = yield* load((svc) => svc.list())
       const names = agents.map((agent) => agent.name)
-      expect(names).toContain("scout")
       expect(names).not.toContain("effect")
       expect(names).not.toContain("effectFull")
       expect(names).not.toContain("localdocs")

+ 1 - 1
packages/opencode/test/effect/runtime-flags.test.ts

@@ -49,7 +49,7 @@ describe("RuntimeFlags", () => {
       expect(flags.enableParallel).toBe(true)
       expect(flags.enableExperimentalModels).toBe(true)
       expect(flags.enableQuestionTool).toBe(true)
-      expect(flags.experimentalScout).toBe(true)
+      expect(flags.experimentalReferences).toBe(true)
       expect(flags.experimentalBackgroundSubagents).toBe(true)
       expect(flags.experimentalLspTy).toBe(false)
       expect(flags.experimentalLspTool).toBe(true)

+ 4 - 4
packages/opencode/test/reference/reference.test.ts

@@ -27,12 +27,12 @@ const referenceLayer = (flags: Partial<RuntimeFlags.Info> = {}) =>
 const it = testEffect(
   Layer.mergeAll(AppFileSystem.defaultLayer, CrossSpawnSpawner.defaultLayer, Git.defaultLayer, referenceLayer()),
 )
-const scout = testEffect(
+const references = testEffect(
   Layer.mergeAll(
     AppFileSystem.defaultLayer,
     CrossSpawnSpawner.defaultLayer,
     Git.defaultLayer,
-    referenceLayer({ experimentalScout: true }),
+    referenceLayer({ experimentalReferences: true }),
   ),
 )
 
@@ -197,7 +197,7 @@ describe("reference", () => {
     }),
   )
 
-  scout.live("materializes configured git references during init", () =>
+  references.live("materializes configured git references during init", () =>
     provideTmpdirInstance(
       (_dir) =>
         Effect.gen(function* () {
@@ -243,7 +243,7 @@ describe("reference", () => {
     ),
   )
 
-  scout.live("refreshes configured git references on new instance init", () =>
+  references.live("refreshes configured git references on new instance init", () =>
     Effect.gen(function* () {
       const fs = yield* AppFileSystem.Service
       const cache = path.join(Global.Path.repos, "github.com", "opencode-reference-refresh", "repo")

+ 1 - 1
packages/opencode/test/session/prompt.test.ts

@@ -2009,7 +2009,7 @@ noLLMServer.instance(
 
       expect(reference?.metadata?.reference).toMatchObject({ name: "docs", kind: "local", path: docs })
       expect(synthetic.some((part) => part.text.includes(`Reference root: ${docs}`))).toBe(true)
-      expect(synthetic.some((part) => part.text.includes("subagent scout"))).toBe(true)
+      expect(synthetic.some((part) => part.text.includes("Inspect the configured reference"))).toBe(true)
 
       yield* sessions.remove(session.id)
     }),

+ 2 - 2
packages/opencode/test/tool/glob.test.ts

@@ -39,7 +39,7 @@ const toolLayer = (flags: Partial<RuntimeFlags.Info> = {}) =>
   )
 
 const it = testEffect(toolLayer())
-const scout = testEffect(toolLayer({ experimentalScout: true }))
+const references = testEffect(toolLayer({ experimentalReferences: true }))
 
 const ctx = {
   sessionID: SessionID.make("ses_test"),
@@ -143,7 +143,7 @@ describe("tool.glob", () => {
     }),
   )
 
-  scout.instance(
+  references.instance(
     "does not ask for external_directory permission inside configured git references",
     () =>
       Effect.gen(function* () {

+ 2 - 2
packages/opencode/test/tool/grep.test.ts

@@ -42,7 +42,7 @@ const toolLayer = (flags: Partial<RuntimeFlags.Info> = {}) =>
   )
 
 const it = testEffect(toolLayer())
-const scout = testEffect(toolLayer({ experimentalScout: true }))
+const references = testEffect(toolLayer({ experimentalReferences: true }))
 const rooted = testEffect(Layer.mergeAll(toolLayer(), testInstanceStoreLayer))
 
 const ctx = {
@@ -215,7 +215,7 @@ describe("tool.grep", () => {
     }),
   )
 
-  scout.instance(
+  references.instance(
     "does not ask for external_directory permission inside configured git references",
     () =>
       Effect.gen(function* () {

+ 2 - 2
packages/opencode/test/tool/read.test.ts

@@ -63,7 +63,7 @@ const readLayer = (flags: Partial<RuntimeFlags.Info> = {}) =>
   )
 
 const it = testEffect(Layer.mergeAll(readLayer(), testInstanceStoreLayer))
-const scout = testEffect(Layer.mergeAll(readLayer({ experimentalScout: true }), testInstanceStoreLayer))
+const references = testEffect(Layer.mergeAll(readLayer({ experimentalReferences: true }), testInstanceStoreLayer))
 
 const init = Effect.fn("ReadToolTest.init")(function* () {
   const info = yield* ReadTool
@@ -264,7 +264,7 @@ describe("tool.read external_directory permission", () => {
     }),
   )
 
-  scout.live("does not ask for external_directory permission when reading configured references", () =>
+  references.live("does not ask for external_directory permission when reading configured references", () =>
     Effect.gen(function* () {
       const fs = yield* AppFileSystem.Service
       const cache = path.join(Global.Path.repos, "github.com", "opencode-read-reference", "repo")

+ 0 - 23
packages/opencode/test/tool/registry.test.ts

@@ -98,9 +98,6 @@ const brokenPluginLayer = Layer.succeed(
 )
 
 const it = testEffect(Layer.mergeAll(registryLayer(), node, Agent.defaultLayer))
-const scout = testEffect(
-  Layer.mergeAll(registryLayer({ flags: { experimentalScout: true } }), node, Agent.defaultLayer),
-)
 const withBrokenPlugin = testEffect(
   Layer.mergeAll(registryLayer({ plugin: brokenPluginLayer }), node, Agent.defaultLayer),
 )
@@ -110,26 +107,6 @@ afterEach(async () => {
 })
 
 describe("tool.registry", () => {
-  it.instance("hides repo research tools unless experimental", () =>
-    Effect.gen(function* () {
-      const registry = yield* ToolRegistry.Service
-      const ids = yield* registry.ids()
-
-      expect(ids).not.toContain("repo_clone")
-      expect(ids).not.toContain("repo_overview")
-    }),
-  )
-
-  scout.instance("shows repo research tools when experimental scout is enabled", () =>
-    Effect.gen(function* () {
-      const registry = yield* ToolRegistry.Service
-      const ids = yield* registry.ids()
-
-      expect(ids).toContain("repo_clone")
-      expect(ids).toContain("repo_overview")
-    }),
-  )
-
   it.instance("does not expose task_status", () =>
     Effect.gen(function* () {
       const registry = yield* ToolRegistry.Service

+ 0 - 234
packages/opencode/test/tool/repo_clone.test.ts

@@ -1,234 +0,0 @@
-import { afterEach, describe, expect } from "bun:test"
-import path from "path"
-import { pathToFileURL } from "node:url"
-import { Cause, Effect, Exit, Layer } from "effect"
-import { AppFileSystem } from "@opencode-ai/core/filesystem"
-import { Agent } from "../../src/agent/agent"
-import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
-import { Git } from "../../src/git"
-import { Global } from "@opencode-ai/core/global"
-import { MessageID, SessionID } from "../../src/session/schema"
-import { Truncate } from "../../src/tool/truncate"
-import { RepoCloneTool } from "../../src/tool/repo_clone"
-import { RepositoryCache } from "../../src/reference/repository-cache"
-import { disposeAllInstances, TestInstance, tmpdirScoped } from "../fixture/fixture"
-import { testEffect } from "../lib/effect"
-
-afterEach(async () => {
-  await disposeAllInstances()
-})
-
-const ctx = {
-  sessionID: SessionID.make("ses_test"),
-  messageID: MessageID.make("msg_test"),
-  callID: "",
-  agent: "scout",
-  abort: AbortSignal.any([]),
-  messages: [],
-  metadata: () => Effect.void,
-  ask: () => Effect.void,
-}
-
-const it = testEffect(
-  Layer.mergeAll(
-    Agent.defaultLayer,
-    AppFileSystem.defaultLayer,
-    CrossSpawnSpawner.defaultLayer,
-    Git.defaultLayer,
-    RepositoryCache.defaultLayer,
-    Truncate.defaultLayer,
-  ),
-)
-
-const init = Effect.fn("RepoCloneToolTest.init")(function* () {
-  const info = yield* RepoCloneTool
-  return yield* info.init()
-})
-
-const git = Effect.fn("RepoCloneToolTest.git")(function* (cwd: string, args: string[]) {
-  return yield* Effect.promise(async () => {
-    const proc = Bun.spawn(["git", ...args], {
-      cwd,
-      stdout: "pipe",
-      stderr: "pipe",
-    })
-    const [stdout, stderr, code] = await Promise.all([
-      new Response(proc.stdout).text(),
-      new Response(proc.stderr).text(),
-      proc.exited,
-    ])
-    if (code !== 0) {
-      throw new Error(stderr.trim() || stdout.trim() || `git ${args.join(" ")} failed`)
-    }
-    return stdout.trim()
-  })
-})
-
-const githubBase = <A, E, R>(url: string, self: Effect.Effect<A, E, R>) =>
-  Effect.acquireUseRelease(
-    Effect.sync(() => {
-      const previous = process.env.OPENCODE_REPO_CLONE_GITHUB_BASE_URL
-      process.env.OPENCODE_REPO_CLONE_GITHUB_BASE_URL = url
-      return previous
-    }),
-    () => self,
-    (previous) =>
-      Effect.sync(() => {
-        if (previous) process.env.OPENCODE_REPO_CLONE_GITHUB_BASE_URL = previous
-        else delete process.env.OPENCODE_REPO_CLONE_GITHUB_BASE_URL
-      }),
-  )
-
-describe("tool.repo_clone", () => {
-  it.instance("clones a repo into the managed cache and reuses it on subsequent calls", () =>
-    Effect.gen(function* () {
-      const fs = yield* AppFileSystem.Service
-      const source = yield* tmpdirScoped({ git: true })
-      const remoteRoot = yield* tmpdirScoped()
-      const remoteDir = path.join(remoteRoot, "owner")
-      const remoteRepo = path.join(remoteDir, "repo.git")
-
-      yield* Effect.promise(() => Bun.write(path.join(source, "README.md"), "v1\n"))
-      yield* git(source, ["add", "."])
-      yield* git(source, ["commit", "-m", "add readme"])
-      yield* fs.makeDirectory(remoteDir, { recursive: true }).pipe(Effect.orDie)
-      yield* git(remoteRoot, ["clone", "--bare", source, remoteRepo])
-
-      const tool = yield* init()
-      const cloned = yield* githubBase(`file://${remoteRoot}/`, tool.execute({ repository: "owner/repo" }, ctx))
-      const cached = yield* githubBase(
-        `file://${remoteRoot}/`,
-        tool.execute({ repository: "https://github.com/owner/repo.git" }, ctx),
-      )
-
-      expect(cloned.metadata.status).toBe("cloned")
-      expect(cloned.metadata.localPath).toBe(path.join(Global.Path.repos, "github.com", "owner", "repo"))
-      expect(cached.metadata.status).toBe("cached")
-      expect(yield* fs.readFileString(path.join(cloned.metadata.localPath, "README.md"))).toBe("v1\n")
-    }),
-  )
-
-  it.instance("refresh updates an existing cached clone", () =>
-    Effect.gen(function* () {
-      const fs = yield* AppFileSystem.Service
-      const source = yield* tmpdirScoped({ git: true })
-      const remoteRoot = yield* tmpdirScoped()
-      const remoteDir = path.join(remoteRoot, "owner")
-      const remoteRepo = path.join(remoteDir, "repo.git")
-
-      yield* Effect.promise(() => Bun.write(path.join(source, "README.md"), "v1\n"))
-      yield* git(source, ["add", "."])
-      yield* git(source, ["commit", "-m", "add readme"])
-      yield* fs.makeDirectory(remoteDir, { recursive: true }).pipe(Effect.orDie)
-      yield* git(remoteRoot, ["clone", "--bare", source, remoteRepo])
-
-      const branch = yield* git(source, ["branch", "--show-current"])
-      yield* git(source, ["remote", "add", "origin", remoteRepo])
-      yield* git(source, ["push", "-u", "origin", `${branch}:${branch}`])
-
-      const tool = yield* init()
-      const first = yield* githubBase(`file://${remoteRoot}/`, tool.execute({ repository: "owner/repo" }, ctx))
-
-      yield* Effect.promise(() => Bun.write(path.join(source, "README.md"), "v2\n"))
-      yield* git(source, ["add", "."])
-      yield* git(source, ["commit", "-m", "update readme"])
-      yield* git(source, ["push", "origin", `${branch}:${branch}`])
-
-      const refreshed = yield* githubBase(
-        `file://${remoteRoot}/`,
-        tool.execute({ repository: "owner/repo", refresh: true }, ctx),
-      )
-
-      expect(first.metadata.status).toBe("cloned")
-      expect(refreshed.metadata.status).toBe("refreshed")
-      expect(yield* fs.readFileString(path.join(first.metadata.localPath, "README.md"))).toBe("v2\n")
-    }),
-  )
-
-  it.instance("clones a configured branch", () =>
-    Effect.gen(function* () {
-      const fs = yield* AppFileSystem.Service
-      const source = yield* tmpdirScoped({ git: true })
-      const remoteRoot = yield* tmpdirScoped()
-      const remoteDir = path.join(remoteRoot, "owner")
-      const remoteRepo = path.join(remoteDir, "repo.git")
-
-      yield* Effect.promise(() => Bun.write(path.join(source, "README.md"), "main\n"))
-      yield* git(source, ["add", "."])
-      yield* git(source, ["commit", "-m", "add readme"])
-      yield* git(source, ["checkout", "-b", "docs"])
-      yield* Effect.promise(() => Bun.write(path.join(source, "DOCS.md"), "docs\n"))
-      yield* git(source, ["add", "."])
-      yield* git(source, ["commit", "-m", "add docs"])
-      yield* fs.makeDirectory(remoteDir, { recursive: true }).pipe(Effect.orDie)
-      yield* git(remoteRoot, ["clone", "--bare", source, remoteRepo])
-
-      const tool = yield* init()
-      const result = yield* githubBase(
-        `file://${remoteRoot}/`,
-        tool.execute({ repository: "owner/repo", branch: "docs" }, ctx),
-      )
-
-      expect(result.metadata.status).toBe("cloned")
-      expect(result.metadata.branch).toBe("docs")
-      expect(yield* fs.readFileString(path.join(result.metadata.localPath, "DOCS.md"))).toBe("docs\n")
-    }),
-  )
-
-  it.instance("rejects invalid repository inputs", () =>
-    Effect.gen(function* () {
-      const dir = (yield* TestInstance).directory
-      const tool = yield* init()
-      const inputs = [
-        { repository: "not-a-repo", message: "git URL" },
-        { repository: "git@github.com:../../../etc/passwd", message: "git URL" },
-        { repository: "-u:foo/bar", message: "git URL" },
-        { repository: pathToFileURL(path.join(dir, "local.git")).href, message: "Local file" },
-      ]
-
-      yield* Effect.forEach(
-        inputs,
-        (input) =>
-          Effect.gen(function* () {
-            const result = yield* tool.execute({ repository: input.repository }, ctx).pipe(Effect.exit)
-
-            expect(Exit.isFailure(result)).toBe(true)
-            if (Exit.isFailure(result)) {
-              const error = Cause.squash(result.cause)
-              expect(error instanceof Error ? error.message : String(error)).toContain(input.message)
-            }
-          }),
-        { discard: true },
-      )
-    }),
-  )
-
-  it.instance("rejects local file repository URLs", () =>
-    Effect.gen(function* () {
-      const source = yield* tmpdirScoped({ git: true })
-      const tool = yield* init()
-      const result = yield* tool.execute({ repository: pathToFileURL(source).href }, ctx).pipe(Effect.exit)
-
-      expect(Exit.isFailure(result)).toBe(true)
-      if (Exit.isFailure(result)) {
-        const error = Cause.squash(result.cause)
-        expect(error instanceof Error ? error.message : String(error)).toContain("Local file")
-      }
-    }),
-  )
-
-  it.instance("rejects invalid branch inputs", () =>
-    Effect.gen(function* () {
-      const tool = yield* init()
-      const result = yield* tool.execute({ repository: "owner/repo", branch: "bad..branch" }, ctx).pipe(Effect.exit)
-
-      expect(Exit.isFailure(result)).toBe(true)
-      if (Exit.isFailure(result)) {
-        const error = Cause.squash(result.cause)
-        expect(error instanceof Error ? error.message : String(error)).toContain(
-          "Branch must contain only alphanumeric characters",
-        )
-      }
-    }),
-  )
-})

+ 0 - 156
packages/opencode/test/tool/repo_overview.test.ts

@@ -1,156 +0,0 @@
-import { afterEach, describe, expect } from "bun:test"
-import path from "path"
-import { Cause, Effect, Exit, Layer } from "effect"
-import { AppFileSystem } from "@opencode-ai/core/filesystem"
-import { Agent } from "../../src/agent/agent"
-import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
-import { Git } from "../../src/git"
-import { Global } from "@opencode-ai/core/global"
-import { MessageID, SessionID } from "../../src/session/schema"
-import { Truncate } from "../../src/tool/truncate"
-import { RepoOverviewTool } from "../../src/tool/repo_overview"
-import { disposeAllInstances, TestInstance, tmpdirScoped } from "../fixture/fixture"
-import { testEffect } from "../lib/effect"
-
-afterEach(async () => {
-  await disposeAllInstances()
-})
-
-const ctx = {
-  sessionID: SessionID.make("ses_test"),
-  messageID: MessageID.make("msg_test"),
-  callID: "",
-  agent: "scout",
-  abort: AbortSignal.any([]),
-  messages: [],
-  metadata: () => Effect.void,
-  ask: () => Effect.void,
-}
-
-const it = testEffect(
-  Layer.mergeAll(
-    Agent.defaultLayer,
-    AppFileSystem.defaultLayer,
-    CrossSpawnSpawner.defaultLayer,
-    Git.defaultLayer,
-    Truncate.defaultLayer,
-  ),
-)
-
-const init = Effect.fn("RepoOverviewToolTest.init")(function* () {
-  const info = yield* RepoOverviewTool
-  return yield* info.init()
-})
-
-describe("tool.repo_overview", () => {
-  it.instance("summarizes a local repository path", () =>
-    Effect.gen(function* () {
-      const repo = yield* tmpdirScoped({ git: true })
-      const fs = yield* AppFileSystem.Service
-      yield* fs.writeWithDirs(
-        path.join(repo, "package.json"),
-        JSON.stringify(
-          {
-            name: "example-repo",
-            main: "dist/index.js",
-            module: "dist/index.mjs",
-            types: "dist/index.d.ts",
-            exports: {
-              ".": "./dist/index.js",
-              "./server": "./dist/server.js",
-            },
-            bin: {
-              example: "./bin/example.js",
-            },
-          },
-          null,
-          2,
-        ),
-      )
-      yield* fs.writeWithDirs(path.join(repo, "bun.lock"), "")
-      yield* fs.writeWithDirs(path.join(repo, "README.md"), "# Example\n")
-      yield* fs.writeWithDirs(path.join(repo, "src", "index.ts"), "export const value = 1\n")
-
-      const tool = yield* init()
-      const result = yield* tool.execute({ path: repo }, ctx)
-
-      expect(result.metadata.path).toBe(repo)
-      expect(result.metadata.ecosystems).toContain("Node.js")
-      expect(result.metadata.package_manager).toBe("bun")
-      expect(result.metadata.dependency_files).toEqual(expect.arrayContaining(["package.json", "bun.lock"]))
-      expect(result.metadata.entrypoints).toEqual(
-        expect.arrayContaining([
-          "main: dist/index.js",
-          "module: dist/index.mjs",
-          "types: dist/index.d.ts",
-          "exports: .",
-          "exports: ./server",
-          "bin: example",
-          "file: src/index.ts",
-        ]),
-      )
-      expect(result.output).toContain("Top-level structure:")
-      expect(result.output).toContain("src/")
-      expect(result.output).toContain("README.md")
-    }),
-  )
-
-  it.instance("resolves relative paths from the instance directory", () =>
-    Effect.gen(function* () {
-      const dir = (yield* TestInstance).directory
-      const fs = yield* AppFileSystem.Service
-      yield* fs.writeWithDirs(path.join(dir, "nested", "README.md"), "# Nested\n")
-
-      const tool = yield* init()
-      const result = yield* tool.execute({ path: "nested" }, ctx)
-
-      expect(result.metadata.path).toBe(path.join(dir, "nested"))
-      expect(result.output).toContain("README.md")
-    }),
-  )
-
-  it.instance("resolves a cached repository from repository shorthand", () =>
-    Effect.gen(function* () {
-      const fs = yield* AppFileSystem.Service
-      const cached = path.join(Global.Path.repos, "github.com", "owner", "repo")
-      yield* fs.writeWithDirs(path.join(cached, "package.json"), JSON.stringify({ name: "cached-repo" }, null, 2))
-      yield* fs.writeWithDirs(path.join(cached, "README.md"), "cached\n")
-
-      const tool = yield* init()
-      const result = yield* tool.execute({ repository: "owner/repo" }, ctx)
-
-      expect(result.metadata.path).toBe(cached)
-      expect(result.metadata.repository).toBe("owner/repo")
-      expect(result.output).toContain("Repository: owner/repo")
-      expect(result.output).toContain(`Path: ${cached}`)
-    }),
-  )
-
-  it.instance("fails clearly when a repository is not cloned", () =>
-    Effect.gen(function* () {
-      const tool = yield* init()
-      const result = yield* tool.execute({ repository: "missing/repo" }, ctx).pipe(Effect.exit)
-
-      expect(Exit.isFailure(result)).toBe(true)
-      if (Exit.isFailure(result)) {
-        const error = Cause.squash(result.cause)
-        expect(error instanceof Error ? error.message : String(error)).toContain("Use repo_clone first")
-      }
-    }),
-  )
-
-  it.instance("resolves cached repositories from host/path references", () =>
-    Effect.gen(function* () {
-      const fs = yield* AppFileSystem.Service
-      const cached = path.join(Global.Path.repos, "gitlab.com", "group", "repo")
-      yield* fs.writeWithDirs(path.join(cached, "README.md"), "cached\n")
-
-      const tool = yield* init()
-      const result = yield* tool.execute({ repository: "gitlab.com/group/repo" }, ctx)
-
-      expect(result.metadata.path).toBe(cached)
-      expect(result.metadata.repository).toBe("gitlab.com/group/repo")
-      expect(result.output).toContain("Repository: gitlab.com/group/repo")
-    }),
-  )
-})