From f5432a995a1d2a159f386197feb7ae555b447063 Mon Sep 17 00:00:00 2001 From: Edmund Miller Date: Tue, 10 Mar 2026 13:23:13 -0500 Subject: [PATCH] feat(opencode): load repo root AGENTS for external reads --- packages/opencode/src/session/instruction.ts | 39 +++++++++++++++++++ .../opencode/test/session/instruction.test.ts | 29 ++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/packages/opencode/src/session/instruction.ts b/packages/opencode/src/session/instruction.ts index 86f73d0fd238..b828cbf73e4d 100644 --- a/packages/opencode/src/session/instruction.ts +++ b/packages/opencode/src/session/instruction.ts @@ -7,6 +7,7 @@ import { Instance } from "../project/instance" import { Flag } from "@/flag/flag" import { Log } from "../util/log" import { Glob } from "../util/glob" +import { git } from "../util/git" import type { MessageV2 } from "./message-v2" const log = Log.create({ service: "instruction" }) @@ -42,6 +43,28 @@ async function resolveRelative(instruction: string): Promise { return Filesystem.globUp(instruction, Flag.OPENCODE_CONFIG_DIR, Flag.OPENCODE_CONFIG_DIR).catch(() => []) } +function normalize(cwd: string, value: string) { + const text = value.replace(/[\r\n]+$/, "") + if (!text) return + const resolved = Filesystem.windowsPath(text) + if (path.isAbsolute(resolved)) return path.normalize(resolved) + return path.resolve(cwd, resolved) +} + +async function repo(filepath: string) { + const cwd = path.dirname(filepath) + const result = await git(["rev-parse", "--show-toplevel"], { cwd }) + if (result.exitCode !== 0) return + return normalize(cwd, result.text()) +} + +async function main(filepath: string) { + const dir = await repo(filepath) + if (!dir) return + const file = path.resolve(path.join(dir, "AGENTS.md")) + if (await Filesystem.exists(file)) return file +} + export namespace InstructionPrompt { const state = Instance.state(() => { return { @@ -187,6 +210,22 @@ export namespace InstructionPrompt { current = path.dirname(current) } + if (Instance.containsPath(target)) { + return results + } + + const found = await main(target) + if (!found || found === target || system.has(found) || already.has(found) || isClaimed(messageID, found)) { + return results + } + + claim(messageID, found) + const content = await Filesystem.readText(found).catch(() => undefined) + if (!content) { + return results + } + + results.push({ filepath: found, content: "Instructions from: " + found + "\n" + content }) return results } } diff --git a/packages/opencode/test/session/instruction.test.ts b/packages/opencode/test/session/instruction.test.ts index e0bf94a9500d..7e2cea12861d 100644 --- a/packages/opencode/test/session/instruction.test.ts +++ b/packages/opencode/test/session/instruction.test.ts @@ -1,4 +1,5 @@ import { afterEach, beforeEach, describe, expect, test } from "bun:test" +import { $ } from "bun" import path from "path" import { InstructionPrompt } from "../../src/session/instruction" import { Instance } from "../../src/project/instance" @@ -68,6 +69,34 @@ describe("InstructionPrompt.resolve", () => { }, }) }) + + test("returns repo root AGENTS.md for files outside current project", async () => { + await using external = await tmpdir({ + init: async (dir) => { + await Bun.write(path.join(dir, "AGENTS.md"), "# External Root") + await Bun.write(path.join(dir, "subdir", "AGENTS.md"), "# External Subdir") + await Bun.write(path.join(dir, "subdir", "nested", "file.ts"), "const x = 1") + }, + }) + await using tmp = await tmpdir() + + await $`git init`.cwd(external.path).quiet() + await $`git init`.cwd(tmp.path).quiet() + + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const results = await InstructionPrompt.resolve( + [], + path.join(external.path, "subdir", "nested", "file.ts"), + "test-message-3", + ) + + expect(results.length).toBe(1) + expect(results[0].filepath).toBe(path.join(external.path, "AGENTS.md")) + }, + }) + }) }) describe("InstructionPrompt.systemPaths OPENCODE_CONFIG_DIR", () => {