Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0d2bd182b1 | ||
|
|
6daf86dd6e |
@@ -3,6 +3,7 @@
|
|||||||
// "enterprise": {
|
// "enterprise": {
|
||||||
// "url": "https://enterprise.dev.opencode.ai",
|
// "url": "https://enterprise.dev.opencode.ai",
|
||||||
// },
|
// },
|
||||||
|
"references": ["git@github.com:Effect-TS/effect.git"],
|
||||||
"provider": {
|
"provider": {
|
||||||
"opencode": {
|
"opencode": {
|
||||||
"options": {},
|
"options": {},
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import { Global } from "@/global"
|
|||||||
import path from "path"
|
import path from "path"
|
||||||
import { Plugin } from "@/plugin"
|
import { Plugin } from "@/plugin"
|
||||||
import { Skill } from "../skill"
|
import { Skill } from "../skill"
|
||||||
|
import { Reference } from "@/reference"
|
||||||
|
|
||||||
export namespace Agent {
|
export namespace Agent {
|
||||||
export const Info = z
|
export const Info = z
|
||||||
@@ -73,6 +74,17 @@ export namespace Agent {
|
|||||||
})
|
})
|
||||||
const user = PermissionNext.fromConfig(cfg.permission ?? {})
|
const user = PermissionNext.fromConfig(cfg.permission ?? {})
|
||||||
|
|
||||||
|
let explorePrompt = PROMPT_EXPLORE
|
||||||
|
if (cfg.references?.length) {
|
||||||
|
const refs = cfg.references.map((r) => Reference.parse(r))
|
||||||
|
const fresh = await Promise.all(refs.map((r) => Reference.ensureFresh(r)))
|
||||||
|
const valid = fresh.filter(Boolean) as Reference.Info[]
|
||||||
|
if (valid.length > 0) {
|
||||||
|
explorePrompt +=
|
||||||
|
"\n\n<references>\n" + valid.map((r) => `- ${r.url} -> ${r.path}`).join("\n") + "\n</references>"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const result: Record<string, Info> = {
|
const result: Record<string, Info> = {
|
||||||
build: {
|
build: {
|
||||||
name: "build",
|
name: "build",
|
||||||
@@ -143,12 +155,17 @@ export namespace Agent {
|
|||||||
read: "allow",
|
read: "allow",
|
||||||
external_directory: {
|
external_directory: {
|
||||||
[Truncate.GLOB]: "allow",
|
[Truncate.GLOB]: "allow",
|
||||||
|
[path.join(Global.Path.reference, "*")]: "allow",
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
user,
|
user,
|
||||||
),
|
),
|
||||||
description: `Fast agent specialized for exploring codebases. Use this when you need to quickly find files by patterns (eg. "src/components/**/*.tsx"), search code for keywords (eg. "API endpoints"), or answer questions about the codebase (eg. "how do API endpoints work?"). When calling this agent, specify the desired thoroughness level: "quick" for basic searches, "medium" for moderate exploration, or "very thorough" for comprehensive analysis across multiple locations and naming conventions.`,
|
description: `Fast agent specialized for exploring codebases. Use this when you need to quickly find files by patterns (eg. "src/components/**/*.tsx"), search code for keywords (eg. "API endpoints"), or answer questions about the codebase (eg. "how do API endpoints work?"). When calling this agent, specify the desired thoroughness level: "quick" for basic searches, "medium" for moderate exploration, or "very thorough" for comprehensive analysis across multiple locations and naming conventions.${
|
||||||
prompt: PROMPT_EXPLORE,
|
cfg.references?.length
|
||||||
|
? `\n\nAlways use this to answer questions about the following referenced projects:\n${cfg.references.map((r) => `- ${r}`).join("\n")}`
|
||||||
|
: ""
|
||||||
|
}`,
|
||||||
|
prompt: explorePrompt,
|
||||||
options: {},
|
options: {},
|
||||||
mode: "subagent",
|
mode: "subagent",
|
||||||
native: true,
|
native: true,
|
||||||
|
|||||||
@@ -15,4 +15,13 @@ Guidelines:
|
|||||||
- For clear communication, avoid using emojis
|
- For clear communication, avoid using emojis
|
||||||
- Do not create any files, or run bash commands that modify the user's system state in any way
|
- Do not create any files, or run bash commands that modify the user's system state in any way
|
||||||
|
|
||||||
|
Referenced projects:
|
||||||
|
When configured, you also have access to referenced projects - external codebases that may contain relevant code or patterns. Use these when:
|
||||||
|
- The user asks about code not found in the main project
|
||||||
|
- You need to understand how a library or dependency works
|
||||||
|
- The user mentions an external repository or package
|
||||||
|
- Searching for patterns across multiple codebases
|
||||||
|
|
||||||
|
Search referenced projects by using their absolute paths (provided in a <references> tag) with Glob, Grep, and Read tools.
|
||||||
|
|
||||||
Complete the user's search request efficiently and report your findings clearly.
|
Complete the user's search request efficiently and report your findings clearly.
|
||||||
|
|||||||
@@ -1192,6 +1192,10 @@ export namespace Config {
|
|||||||
.describe("Timeout in milliseconds for model context protocol (MCP) requests"),
|
.describe("Timeout in milliseconds for model context protocol (MCP) requests"),
|
||||||
})
|
})
|
||||||
.optional(),
|
.optional(),
|
||||||
|
references: z
|
||||||
|
.array(z.string())
|
||||||
|
.optional()
|
||||||
|
.describe("Git repositories or local paths to reference from subagents"),
|
||||||
})
|
})
|
||||||
.strict()
|
.strict()
|
||||||
.meta({
|
.meta({
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ export namespace Global {
|
|||||||
bin: path.join(data, "bin"),
|
bin: path.join(data, "bin"),
|
||||||
log: path.join(data, "log"),
|
log: path.join(data, "log"),
|
||||||
cache,
|
cache,
|
||||||
|
reference: path.join(cache, "references"),
|
||||||
config,
|
config,
|
||||||
state,
|
state,
|
||||||
}
|
}
|
||||||
@@ -31,6 +32,7 @@ await Promise.all([
|
|||||||
fs.mkdir(Global.Path.state, { recursive: true }),
|
fs.mkdir(Global.Path.state, { recursive: true }),
|
||||||
fs.mkdir(Global.Path.log, { recursive: true }),
|
fs.mkdir(Global.Path.log, { recursive: true }),
|
||||||
fs.mkdir(Global.Path.bin, { recursive: true }),
|
fs.mkdir(Global.Path.bin, { recursive: true }),
|
||||||
|
fs.mkdir(Global.Path.reference, { recursive: true }),
|
||||||
])
|
])
|
||||||
|
|
||||||
const CACHE_VERSION = "21"
|
const CACHE_VERSION = "21"
|
||||||
|
|||||||
@@ -0,0 +1,131 @@
|
|||||||
|
import path from "path"
|
||||||
|
import { mkdir, stat } from "fs/promises"
|
||||||
|
import { createHash } from "crypto"
|
||||||
|
import { Global } from "../global"
|
||||||
|
import { Config } from "../config/config"
|
||||||
|
import { Log } from "../util/log"
|
||||||
|
import { git } from "../util/git"
|
||||||
|
import { Instance } from "../project/instance"
|
||||||
|
|
||||||
|
export namespace Reference {
|
||||||
|
const log = Log.create({ service: "reference" })
|
||||||
|
|
||||||
|
const STALE_THRESHOLD_MS = 60 * 60 * 1000
|
||||||
|
|
||||||
|
export interface Info {
|
||||||
|
url: string
|
||||||
|
path: string
|
||||||
|
branch?: string
|
||||||
|
type: "git" | "local"
|
||||||
|
}
|
||||||
|
|
||||||
|
function hashUrl(url: string): string {
|
||||||
|
return createHash("sha256").update(url).digest("hex").slice(0, 16)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parse(url: string): Info {
|
||||||
|
if (url.startsWith("/") || url.startsWith("~") || url.startsWith(".")) {
|
||||||
|
const resolved = url.startsWith("~")
|
||||||
|
? path.join(Global.Path.home, url.slice(1))
|
||||||
|
: url.startsWith(".")
|
||||||
|
? path.resolve(Instance.worktree, url)
|
||||||
|
: url
|
||||||
|
return {
|
||||||
|
url,
|
||||||
|
path: resolved,
|
||||||
|
type: "local",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const branchMatch = url.match(/^(.+)#(.+)$/)
|
||||||
|
const gitUrl = branchMatch ? branchMatch[1] : url
|
||||||
|
const branch = branchMatch ? branchMatch[2] : undefined
|
||||||
|
|
||||||
|
return {
|
||||||
|
url: gitUrl,
|
||||||
|
path: path.join(Global.Path.reference, hashUrl(gitUrl)),
|
||||||
|
branch,
|
||||||
|
type: "git",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function isStale(ref: Info): Promise<boolean> {
|
||||||
|
if (ref.type === "local") return false
|
||||||
|
|
||||||
|
const fetchHead = path.join(ref.path, ".git", "FETCH_HEAD")
|
||||||
|
const s = await stat(fetchHead).catch(() => null)
|
||||||
|
if (!s) return true
|
||||||
|
|
||||||
|
return Date.now() - s.mtime.getTime() > STALE_THRESHOLD_MS
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetch(ref: Info): Promise<boolean> {
|
||||||
|
if (ref.type === "local") {
|
||||||
|
const exists = await stat(ref.path).catch(() => null)
|
||||||
|
if (!exists?.isDirectory()) {
|
||||||
|
log.error("local reference not found", { path: ref.path })
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
await mkdir(path.dirname(ref.path), { recursive: true })
|
||||||
|
|
||||||
|
const isCloned = await stat(path.join(ref.path, ".git")).catch(() => null)
|
||||||
|
|
||||||
|
if (!isCloned) {
|
||||||
|
log.info("cloning reference", { url: ref.url, branch: ref.branch })
|
||||||
|
const args = ["clone", "--depth", "1"]
|
||||||
|
if (ref.branch) {
|
||||||
|
args.push("--branch", ref.branch)
|
||||||
|
}
|
||||||
|
args.push(ref.url, ref.path)
|
||||||
|
|
||||||
|
const result = await git(args, { cwd: Global.Path.reference })
|
||||||
|
if (result.exitCode !== 0) {
|
||||||
|
log.error("failed to clone", { url: ref.url, stderr: result.stderr.toString() })
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("fetching reference", { url: ref.url })
|
||||||
|
const fetchResult = await git(["fetch"], { cwd: ref.path })
|
||||||
|
if (fetchResult.exitCode !== 0) {
|
||||||
|
log.warn("failed to fetch, using cached", { url: ref.url })
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ref.branch) {
|
||||||
|
const checkoutResult = await git(["checkout", ref.branch], { cwd: ref.path })
|
||||||
|
if (checkoutResult.exitCode !== 0) {
|
||||||
|
log.warn("failed to checkout branch, using current", { url: ref.url, branch: ref.branch })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function ensureFresh(ref: Info): Promise<Info | null> {
|
||||||
|
if (await isStale(ref)) {
|
||||||
|
const success = await fetch(ref)
|
||||||
|
if (!success && ref.type === "git") {
|
||||||
|
const exists = await stat(ref.path).catch(() => null)
|
||||||
|
if (!exists) return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ref
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function list(): Promise<Info[]> {
|
||||||
|
const cfg = await Config.get()
|
||||||
|
const urls = cfg.references ?? []
|
||||||
|
return urls.map(parse)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function directories(): Promise<string[]> {
|
||||||
|
const refs = await list()
|
||||||
|
const fresh = await Promise.all(refs.map(ensureFresh))
|
||||||
|
return fresh.filter(Boolean).map((r) => r!.path)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user