sync
This commit is contained in:
parent
656fa191c1
commit
e8ee1e239f
2 changed files with 2 additions and 210 deletions
1
.opencode/.gitignore
vendored
1
.opencode/.gitignore
vendored
|
|
@ -3,3 +3,4 @@ package.json
|
||||||
bun.lock
|
bun.lock
|
||||||
.gitignore
|
.gitignore
|
||||||
package-lock.json
|
package-lock.json
|
||||||
|
plans
|
||||||
|
|
|
||||||
|
|
@ -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 |
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue