|
|
@@ -1,209 +0,0 @@
|
|
|
-# Plan: Project References Feature
|
|
|
-
|
|
|
-Add support for referencing external git repositories in `opencode.json` that can be queried via a dedicated subagent.
|
|
|
-
|
|
|
-## Overview
|
|
|
-
|
|
|
-Users can specify references in config:
|
|
|
-
|
|
|
-```json
|
|
|
-{
|
|
|
- "references": ["git@github.com:Effect-TS/effect.git", "git@github.com:foo/bar.git#v1.0.0", "/local/path/to/repo"]
|
|
|
-}
|
|
|
-```
|
|
|
-
|
|
|
-These get cloned/cached to `~/.cache/opencode/references/` and a `reference` subagent can search across them.
|
|
|
-
|
|
|
----
|
|
|
-
|
|
|
-## Files to Create
|
|
|
-
|
|
|
-### `src/reference/index.ts`
|
|
|
-
|
|
|
-Core reference management module with:
|
|
|
-
|
|
|
-- `parse(url: string): Reference` - Parse git URL or local path, extract branch if present (`#branch` suffix)
|
|
|
-- `cachePath(ref: Reference): string` - SHA-256 hash of URL for deterministic directory names
|
|
|
-- `isStale(ref: Reference): Promise<boolean>` - Check if `FETCH_HEAD` mtime > 1 hour old
|
|
|
-- `fetch(ref: Reference): Promise<void>` - Clone with `--depth 1` or `git fetch`, handle branch checkout
|
|
|
-- `ensureFresh(ref: Reference): Promise<void>` - Lazy fetch wrapper (skip if not stale)
|
|
|
-- `list(): Promise<Reference[]>` - Get references from config
|
|
|
-- `directories(): Promise<string[]>` - Get all cached reference paths for permissions
|
|
|
-
|
|
|
-Types:
|
|
|
-
|
|
|
-```ts
|
|
|
-interface Reference {
|
|
|
- url: string // Original URL/path
|
|
|
- path: string // Cache directory (for git) or resolved path (for local)
|
|
|
- branch?: string // Optional branch/tag from URL fragment
|
|
|
- type: "git" | "local"
|
|
|
-}
|
|
|
-```
|
|
|
-
|
|
|
-URL parsing supports:
|
|
|
-
|
|
|
-- `git@github.com:foo/bar.git` → git
|
|
|
-- `git@github.com:foo/bar.git#main` → git with branch
|
|
|
-- `https://github.com/foo/bar.git#v1.0.0` → git with tag
|
|
|
-- `/absolute/path` → local
|
|
|
-- `~/relative/path` → local (expanded)
|
|
|
-
|
|
|
-### `src/agent/prompt/reference.txt`
|
|
|
-
|
|
|
-Prompt for reference subagent:
|
|
|
-
|
|
|
-```
|
|
|
-You are a multi-project code search specialist. You search across referenced projects
|
|
|
-to answer questions about external codebases.
|
|
|
-
|
|
|
-Available references: {references}
|
|
|
-
|
|
|
-Guidelines:
|
|
|
-- Search across ALL referenced projects unless the user specifies one
|
|
|
-- Report which project(s) contained relevant findings
|
|
|
-- Use Glob for file patterns, Grep for content search, Read for specific files
|
|
|
-- Return absolute paths so users can locate findings
|
|
|
-- Do not modify any files or run destructive commands
|
|
|
-```
|
|
|
-
|
|
|
----
|
|
|
-
|
|
|
-## Files to Modify
|
|
|
-
|
|
|
-### `src/config/config.ts`
|
|
|
-
|
|
|
-Add `references` field to `Config.Info` schema (around line 1174):
|
|
|
-
|
|
|
-```ts
|
|
|
-references: z.array(z.string()).optional().describe(
|
|
|
- "Git repositories or local paths to reference from subagents"
|
|
|
-),
|
|
|
-```
|
|
|
-
|
|
|
-### `src/global/index.ts`
|
|
|
-
|
|
|
-Add reference cache path and create directory:
|
|
|
-
|
|
|
-```ts
|
|
|
-// Add to Path object:
|
|
|
-reference: path.join(cache, "references"),
|
|
|
-
|
|
|
-// Add to mkdir promise array:
|
|
|
-fs.mkdir(path.join(cache, "references"), { recursive: true }),
|
|
|
-```
|
|
|
-
|
|
|
-### `src/agent/agent.ts`
|
|
|
-
|
|
|
-Add `reference` agent to built-in agents (after `explore` around line 155):
|
|
|
-
|
|
|
-```ts
|
|
|
-reference: {
|
|
|
- name: "reference",
|
|
|
- description: `Search across referenced projects configured in opencode.json under "references". Use this to query code in external repositories.`,
|
|
|
- permission: PermissionNext.merge(
|
|
|
- defaults,
|
|
|
- PermissionNext.fromConfig({
|
|
|
- "*": "deny",
|
|
|
- grep: "allow",
|
|
|
- glob: "allow",
|
|
|
- list: "allow",
|
|
|
- bash: "allow",
|
|
|
- webfetch: "allow",
|
|
|
- websearch: "allow",
|
|
|
- codesearch: "allow",
|
|
|
- read: "allow",
|
|
|
- lsp: "allow",
|
|
|
- external_directory: {
|
|
|
- [Truncate.GLOB]: "allow",
|
|
|
- // Reference paths added dynamically via Reference.directories()
|
|
|
- },
|
|
|
- }),
|
|
|
- user,
|
|
|
- ),
|
|
|
- prompt: PROMPT_REFERENCE,
|
|
|
- options: {},
|
|
|
- mode: "subagent",
|
|
|
- native: true,
|
|
|
-},
|
|
|
-```
|
|
|
-
|
|
|
-Need to inject reference paths into `external_directory` permissions. This requires:
|
|
|
-
|
|
|
-1. Loading references via `Reference.list()`
|
|
|
-2. Ensuring they're fresh via `Reference.ensureFresh()`
|
|
|
-3. Adding their paths to `external_directory` permissions
|
|
|
-
|
|
|
-**Challenge**: The agent definition is loaded once at startup, but references may change. Solution: Inject reference paths dynamically in the Task tool when creating the subagent session.
|
|
|
-
|
|
|
-### `src/tool/task.ts`
|
|
|
-
|
|
|
-When creating session for `reference` subagent (around line 72):
|
|
|
-
|
|
|
-```ts
|
|
|
-// Before Session.create:
|
|
|
-let extraPermissions = []
|
|
|
-if (params.subagent_type === "reference") {
|
|
|
- const refs = await Reference.list()
|
|
|
- await Promise.all(refs.map((r) => Reference.ensureFresh(r)))
|
|
|
- const paths = refs.map((r) => path.join(r.path, "*"))
|
|
|
- extraPermissions = paths.map((p) => ({
|
|
|
- permission: "external_directory" as const,
|
|
|
- pattern: p,
|
|
|
- action: "allow" as const,
|
|
|
- }))
|
|
|
-}
|
|
|
-
|
|
|
-// Add extraPermissions to the permission array in Session.create
|
|
|
-```
|
|
|
-
|
|
|
-Also need to inject available references into the prompt. Modify `SessionPrompt.resolvePromptParts` or add a way to inject context.
|
|
|
-
|
|
|
-### `src/session/prompt.ts` (or similar)
|
|
|
-
|
|
|
-Inject reference list into reference agent's system prompt:
|
|
|
-
|
|
|
-```ts
|
|
|
-// When agent.name === "reference", prepend reference info to prompt
|
|
|
-const refs = await Reference.list()
|
|
|
-const refList = refs.map((r) => `- ${r.url} at ${r.path}`).join("\n")
|
|
|
-// Inject into prompt or parts
|
|
|
-```
|
|
|
-
|
|
|
----
|
|
|
-
|
|
|
-## Edge Cases
|
|
|
-
|
|
|
-1. **Clone failures**: Log warning, continue with other references. Don't block subagent.
|
|
|
-2. **Network failures**: Use cached version if exists, even if stale. Only error if never cloned.
|
|
|
-3. **Branch not found**: Log error, skip that reference.
|
|
|
-4. **Local path missing**: Log warning, skip reference.
|
|
|
-5. **Invalid URL format**: Log warning, skip reference.
|
|
|
-6. **Shallow clone**: Use `--depth 1` for faster clones. For branch-specific URLs, use `--branch <ref> --depth 1`.
|
|
|
-
|
|
|
----
|
|
|
-
|
|
|
-## Verification
|
|
|
-
|
|
|
-1. Add to `opencode.json`: `{"references": ["git@github.com:effect-ts/effect.git"]}`
|
|
|
-2. Invoke `@reference` with query like "show me the Effect schema module"
|
|
|
-3. Verify repo cloned to `~/.cache/opencode/references/<hash>/`
|
|
|
-4. Verify subagent can search/read files in the cloned repo
|
|
|
-5. Wait 1+ hour, invoke again, verify fetch happens
|
|
|
-6. Test with local path: `{"references": ["/path/to/local/repo"]}`
|
|
|
-7. Test with branch: `{"references": ["git@github.com:foo/bar.git#main"]}`
|
|
|
-8. Verify error handling with invalid URL
|
|
|
-
|
|
|
----
|
|
|
-
|
|
|
-## Files Summary
|
|
|
-
|
|
|
-| File | Action |
|
|
|
-| -------------------------------- | ------------------------------------------- |
|
|
|
-| `src/reference/index.ts` | Create |
|
|
|
-| `src/agent/prompt/reference.txt` | Create |
|
|
|
-| `src/config/config.ts` | Add `references` to schema |
|
|
|
-| `src/global/index.ts` | Add `reference` path |
|
|
|
-| `src/agent/agent.ts` | Add `reference` agent |
|
|
|
-| `src/tool/task.ts` | Inject ref permissions + paths into session |
|
|
|
-| `src/session/prompt.ts` | Inject reference list into system prompt |
|