feat(scout): materialize configured reference repos (#26692)

This commit is contained in:
Shoubhit Dash
2026-05-10 17:57:11 +05:30
committed by GitHub
parent 903d81819d
commit 5cf9abe743
19 changed files with 848 additions and 200 deletions

View File

@@ -10,6 +10,7 @@ import { Truncate } from "@/tool/truncate"
import { Agent } from "../../src/agent/agent"
import { TestInstance } from "../fixture/fixture"
import { testEffect } from "../lib/effect"
import { Reference } from "@/reference/reference"
const it = testEffect(
Layer.mergeAll(
@@ -18,6 +19,7 @@ const it = testEffect(
Ripgrep.defaultLayer,
Truncate.defaultLayer,
Agent.defaultLayer,
Reference.defaultLayer,
),
)

View File

@@ -10,6 +10,7 @@ import { Agent } from "../../src/agent/agent"
import { Ripgrep } from "../../src/file/ripgrep"
import { AppFileSystem } from "@opencode-ai/core/filesystem"
import { testEffect } from "../lib/effect"
import { Reference } from "@/reference/reference"
const it = testEffect(
Layer.mergeAll(
@@ -18,6 +19,7 @@ const it = testEffect(
Ripgrep.defaultLayer,
Truncate.defaultLayer,
Agent.defaultLayer,
Reference.defaultLayer,
),
)

View File

@@ -4,6 +4,8 @@ import path from "path"
import { Agent } from "../../src/agent/agent"
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
import { AppFileSystem } from "@opencode-ai/core/filesystem"
import { Flag } from "@opencode-ai/core/flag/flag"
import { Global } from "@opencode-ai/core/global"
import { LSP } from "@/lsp/lsp"
import { Permission } from "../../src/permission"
import { Instance } from "../../src/project/instance"
@@ -15,6 +17,7 @@ import { Tool } from "@/tool/tool"
import { Filesystem } from "@/util/filesystem"
import { disposeAllInstances, provideInstance, TestInstance, tmpdirScoped } from "../fixture/fixture"
import { testEffect } from "../lib/effect"
import { Reference } from "@/reference/reference"
const FIXTURES_DIR = path.join(import.meta.dir, "fixtures")
@@ -40,6 +43,7 @@ const it = testEffect(
CrossSpawnSpawner.defaultLayer,
Instruction.defaultLayer,
LSP.defaultLayer,
Reference.defaultLayer,
Truncate.defaultLayer,
),
)
@@ -81,6 +85,49 @@ const fail = Effect.fn("ReadToolTest.fail")(function* (
const full = (p: string) => (process.platform === "win32" ? Filesystem.normalizePath(p) : p)
const glob = (p: string) =>
process.platform === "win32" ? Filesystem.normalizePathPattern(p) : p.replaceAll("\\", "/")
const experimentalScout = <A, E, R>(self: Effect.Effect<A, E, R>) =>
Effect.acquireUseRelease(
Effect.sync(() => {
const previous = Flag.OPENCODE_EXPERIMENTAL_SCOUT
Flag.OPENCODE_EXPERIMENTAL_SCOUT = true
return previous
}),
() => self,
(previous) =>
Effect.sync(() => {
Flag.OPENCODE_EXPERIMENTAL_SCOUT = previous
}),
)
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
}),
)
const git = Effect.fn("ReadToolTest.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 put = Effect.fn("ReadToolTest.put")(function* (p: string, content: string | Buffer | Uint8Array) {
const fs = yield* AppFileSystem.Service
yield* fs.writeWithDirs(p, content)
@@ -212,6 +259,46 @@ describe("tool.read external_directory permission", () => {
expect(ext).toBeUndefined()
}),
)
it.live("does not ask for external_directory permission when reading configured references", () =>
experimentalScout(
Effect.gen(function* () {
const fs = yield* AppFileSystem.Service
const cache = path.join(Global.Path.repos, "github.com", "opencode-read-reference", "repo")
yield* fs.remove(cache, { recursive: true }).pipe(Effect.ignore)
yield* Effect.addFinalizer(() => fs.remove(cache, { recursive: true }).pipe(Effect.ignore))
const source = yield* tmpdirScoped({ git: true })
const remoteRoot = yield* tmpdirScoped()
const remoteDir = path.join(remoteRoot, "opencode-read-reference")
const remoteRepo = path.join(remoteDir, "repo.git")
yield* put(path.join(source, "notes.md"), "reference notes")
yield* git(source, ["add", "."])
yield* git(source, ["commit", "-m", "add notes"])
yield* fs.makeDirectory(remoteDir, { recursive: true }).pipe(Effect.orDie)
yield* git(remoteRoot, ["clone", "--bare", source, remoteRepo])
const dir = yield* tmpdirScoped({
git: true,
config: {
reference: {
docs: "opencode-read-reference/repo",
},
},
})
const { items, next } = asks()
const result = yield* githubBase(
`file://${remoteRoot}/`,
exec(dir, { filePath: path.join(cache, "notes.md") }, next),
)
const ext = items.find((item) => item.permission === "external_directory")
expect(result.output).toContain("reference notes")
expect(ext).toBeUndefined()
}),
),
)
})
describe("tool.read env file permissions", () => {

View File

@@ -25,6 +25,7 @@ import { Format } from "@/format"
import { Ripgrep } from "@/file/ripgrep"
import * as Truncate from "@/tool/truncate"
import { InstanceState } from "@/effect/instance-state"
import { Reference } from "@/reference/reference"
const node = CrossSpawnSpawner.defaultLayer
const originalExperimentalScout = Flag.OPENCODE_EXPERIMENTAL_SCOUT
@@ -42,6 +43,7 @@ const registryLayer = ToolRegistry.layer.pipe(
Layer.provide(Session.defaultLayer),
Layer.provide(Provider.defaultLayer),
Layer.provide(Git.defaultLayer),
Layer.provide(Reference.defaultLayer),
Layer.provide(LSP.defaultLayer),
Layer.provide(Instruction.defaultLayer),
Layer.provide(AppFileSystem.defaultLayer),