opencode-repos 0.2.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +180 -0
- package/README.md +103 -3
- package/index.ts +1595 -159
- package/package.json +1 -1
- package/src/__tests__/git.test.ts +40 -4
- package/src/__tests__/manifest.test.ts +5 -5
- package/src/agents/repo-explorer.ts +2 -1
- package/src/git.ts +49 -7
- package/src/manifest.ts +22 -15
- package/.sisyphus/boulder.json +0 -8
- package/.sisyphus/notepads/opencode-repos/decisions.md +0 -15
- package/.sisyphus/notepads/opencode-repos/learnings.md +0 -384
- package/.sisyphus/plans/opencode-repos.md +0 -987
- package/.tmux-sessionizer +0 -8
package/AGENTS.md
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# AGENTS.md - opencode-repos
|
|
2
|
+
|
|
3
|
+
Guidelines for AI agents working in this repository.
|
|
4
|
+
|
|
5
|
+
## Build & Development Commands
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
bun install # Install dependencies
|
|
9
|
+
bunx tsc --noEmit # Type checking
|
|
10
|
+
bun test # Run all tests
|
|
11
|
+
bun test src/__tests__/git.test.ts # Run single file
|
|
12
|
+
bun test --test-name-pattern "parseRepoSpec" # Run matching tests
|
|
13
|
+
bun test --watch # Watch mode
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Project Structure
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
opencode-repos/
|
|
20
|
+
├── index.ts # Main plugin - tool definitions
|
|
21
|
+
├── src/
|
|
22
|
+
│ ├── manifest.ts # Manifest CRUD + file locking
|
|
23
|
+
│ ├── git.ts # Git operations (clone, update, parse)
|
|
24
|
+
│ ├── scanner.ts # Local filesystem repo scanner
|
|
25
|
+
│ ├── agents/repo-explorer.ts
|
|
26
|
+
│ └── __tests__/ # Test files (*.test.ts)
|
|
27
|
+
├── package.json
|
|
28
|
+
└── tsconfig.json
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Code Style
|
|
32
|
+
|
|
33
|
+
### Runtime: Bun (NOT Node.js)
|
|
34
|
+
|
|
35
|
+
This project uses Bun exclusively. Do not use Node.js, npm, pnpm, or yarn.
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
import { $ } from "bun"
|
|
39
|
+
await $`git clone ${url} ${dest}`.quiet() // Shell commands
|
|
40
|
+
|
|
41
|
+
const file = Bun.file(path) // File operations
|
|
42
|
+
await Bun.write(path, content)
|
|
43
|
+
await Bun.sleep(100) // Sleep
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Imports
|
|
47
|
+
|
|
48
|
+
Order: types → external → internal → relative. Use `node:` prefix for built-ins.
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
import type { Plugin } from "@opencode-ai/plugin"
|
|
52
|
+
import { tool } from "@opencode-ai/plugin"
|
|
53
|
+
import { $ } from "bun"
|
|
54
|
+
import { homedir } from "node:os"
|
|
55
|
+
import { parseRepoSpec } from "./src/git"
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### TypeScript
|
|
59
|
+
|
|
60
|
+
- Strict mode enabled
|
|
61
|
+
- Never use `any`, `@ts-ignore`, or `@ts-expect-error`
|
|
62
|
+
- Export interfaces for public types
|
|
63
|
+
- Use type guards: `(r): r is LocalRepoInfo => r !== null`
|
|
64
|
+
|
|
65
|
+
### Naming Conventions
|
|
66
|
+
|
|
67
|
+
| Type | Convention | Example |
|
|
68
|
+
|------|------------|---------|
|
|
69
|
+
| Functions/Variables | camelCase | `parseRepoSpec`, `repoPath` |
|
|
70
|
+
| Constants | SCREAMING_SNAKE | `CACHE_DIR`, `LOCK_STALE_MS` |
|
|
71
|
+
| Interfaces/Types | PascalCase | `RepoEntry`, `CloneOptions` |
|
|
72
|
+
| Files | kebab-case | `repo-explorer.ts` |
|
|
73
|
+
|
|
74
|
+
### Error Handling
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
// Type-narrow errors
|
|
78
|
+
try {
|
|
79
|
+
await cloneRepo(url, destPath, { branch })
|
|
80
|
+
} catch (error) {
|
|
81
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
82
|
+
throw new Error(`Failed to clone ${repoKey}: ${message}`)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Cleanup on failure
|
|
86
|
+
try {
|
|
87
|
+
await $`git clone ...`.quiet()
|
|
88
|
+
} catch (error) {
|
|
89
|
+
try { await rm(destPath, { recursive: true, force: true }) } catch {}
|
|
90
|
+
throw error
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Silent catch for optional cleanup
|
|
94
|
+
await unlink(LOCK_PATH).catch(() => {})
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Async Patterns
|
|
98
|
+
|
|
99
|
+
- Use Promise.all for parallel operations
|
|
100
|
+
- Use `.quiet()` on shell commands
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
const [remote, branch] = await Promise.all([
|
|
104
|
+
$`git -C ${path} remote get-url origin`.text(),
|
|
105
|
+
$`git -C ${path} branch --show-current`.text(),
|
|
106
|
+
])
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Testing
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
import { test, expect, describe } from "bun:test"
|
|
113
|
+
|
|
114
|
+
describe("parseRepoSpec", () => {
|
|
115
|
+
test("parses owner/repo without branch", () => {
|
|
116
|
+
expect(parseRepoSpec("vercel/next.js")).toEqual({
|
|
117
|
+
owner: "vercel", repo: "next.js", branch: null
|
|
118
|
+
})
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
test("throws on invalid input", () => {
|
|
122
|
+
expect(() => parseRepoSpec("invalid")).toThrow("Invalid repo spec")
|
|
123
|
+
})
|
|
124
|
+
})
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Tool Definitions
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
repo_clone: tool({
|
|
131
|
+
description: "Brief description with example usage",
|
|
132
|
+
args: {
|
|
133
|
+
repo: tool.schema.string().describe("Format: 'owner/repo@branch'"),
|
|
134
|
+
force: tool.schema.boolean().optional().default(false).describe("..."),
|
|
135
|
+
},
|
|
136
|
+
async execute(args) {
|
|
137
|
+
return `## Markdown formatted response`
|
|
138
|
+
},
|
|
139
|
+
})
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Formatting
|
|
143
|
+
|
|
144
|
+
- Only add comments for complex logic
|
|
145
|
+
- No emojis in logs or comments
|
|
146
|
+
- Use ISO 8601: `new Date().toISOString()`
|
|
147
|
+
|
|
148
|
+
### File Locking
|
|
149
|
+
|
|
150
|
+
Use `withManifestLock` for any manifest mutations:
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
await withManifestLock(async () => {
|
|
154
|
+
const manifest = await loadManifest()
|
|
155
|
+
manifest.repos[key] = entry
|
|
156
|
+
await saveManifest(manifest)
|
|
157
|
+
})
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Key Patterns
|
|
161
|
+
|
|
162
|
+
### Repo Spec Parsing
|
|
163
|
+
|
|
164
|
+
Format: `owner/repo` or `owner/repo@branch`
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
const spec = parseRepoSpec("vercel/next.js@canary")
|
|
168
|
+
// { owner: "vercel", repo: "next.js", branch: "canary" }
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Tool Return Format
|
|
172
|
+
|
|
173
|
+
Tools return markdown-formatted strings:
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
return `## Repository Cloned
|
|
177
|
+
|
|
178
|
+
**Repository**: ${args.repo}
|
|
179
|
+
**Path**: ${result.path}`
|
|
180
|
+
```
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# opencode-repos
|
|
2
2
|
|
|
3
|
-
Repository cache, registry, and cross-codebase intelligence for
|
|
3
|
+
Repository cache, registry, and cross-codebase intelligence for opencode agents.
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
@@ -55,11 +55,17 @@ repo_explore({
|
|
|
55
55
|
repo: "vercel/next.js",
|
|
56
56
|
question: "How does the App Router work?"
|
|
57
57
|
})
|
|
58
|
+
|
|
59
|
+
// Resolve a repo automatically and explore it
|
|
60
|
+
repo_query({
|
|
61
|
+
query: "react",
|
|
62
|
+
question: "How does the reconciliation algorithm work?"
|
|
63
|
+
})
|
|
58
64
|
```
|
|
59
65
|
|
|
60
66
|
## Configuration
|
|
61
67
|
|
|
62
|
-
Create `~/.config/opencode/opencode-repos.json` to configure
|
|
68
|
+
Create `~/.config/opencode/opencode-repos.json` to configure the plugin:
|
|
63
69
|
|
|
64
70
|
```json
|
|
65
71
|
{
|
|
@@ -67,10 +73,53 @@ Create `~/.config/opencode/opencode-repos.json` to configure local repository sc
|
|
|
67
73
|
"~/projects",
|
|
68
74
|
"~/personal/projects",
|
|
69
75
|
"~/code"
|
|
70
|
-
]
|
|
76
|
+
],
|
|
77
|
+
"includeProjectParent": true,
|
|
78
|
+
"cleanupMaxAgeDays": 30,
|
|
79
|
+
"cacheDir": "/tmp/opencode-repos",
|
|
80
|
+
"useHttps": false,
|
|
81
|
+
"autoSyncOnExplore": true,
|
|
82
|
+
"autoSyncIntervalHours": 24,
|
|
83
|
+
"defaultBranch": "main",
|
|
84
|
+
"debug": false,
|
|
85
|
+
"repoExplorerModel": "opencode/grok-code",
|
|
86
|
+
"debugLogPath": "~/.cache/opencode-repos/debug.log"
|
|
71
87
|
}
|
|
72
88
|
```
|
|
73
89
|
|
|
90
|
+
| Option | Default | Description |
|
|
91
|
+
|--------|---------|-------------|
|
|
92
|
+
| `localSearchPaths` | `[]` | Directories to scan for local git repositories |
|
|
93
|
+
| `includeProjectParent` | `true` | Also search the parent of the current project directory |
|
|
94
|
+
| `cleanupMaxAgeDays` | `30` | Auto-delete cached repos not accessed in this many days |
|
|
95
|
+
| `cacheDir` | `/tmp/opencode-repos` | Where to store cloned repositories |
|
|
96
|
+
| `useHttps` | `false` | Use HTTPS instead of SSH for cloning (useful behind firewalls) |
|
|
97
|
+
| `autoSyncOnExplore` | `true` | Auto-fetch latest before exploring a repo |
|
|
98
|
+
| `autoSyncIntervalHours` | `24` | Minimum hours between auto-fetch updates |
|
|
99
|
+
| `defaultBranch` | `"main"` | Default branch when none specified (some repos use "master") |
|
|
100
|
+
| `debug` | `false` | Include debug details in tool responses |
|
|
101
|
+
| `repoExplorerModel` | `"opencode/grok-code"` | Model used by the repo-explorer subagent |
|
|
102
|
+
| `debugLogPath` | `"~/.cache/opencode-repos/debug.log"` | File path for debug logs |
|
|
103
|
+
|
|
104
|
+
Cleanup runs automatically on plugin load.
|
|
105
|
+
|
|
106
|
+
To avoid repeated prompts when exploring external repos, update your OpenCode permissions (`~/.config/opencode/opencode.jsonc`) to allow external directories as needed.
|
|
107
|
+
|
|
108
|
+
Example permission config:
|
|
109
|
+
|
|
110
|
+
```json
|
|
111
|
+
{
|
|
112
|
+
"permission": {
|
|
113
|
+
"external_directory": {
|
|
114
|
+
"*": "ask",
|
|
115
|
+
"/tmp/opencode-repos/*": "allow"
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
If you hit intermittent subagent session errors, enable debug logging and retry. The plugin includes a small retry/backoff when prompting the repo-explorer subagent.
|
|
122
|
+
|
|
74
123
|
## Tools
|
|
75
124
|
|
|
76
125
|
### repo_find
|
|
@@ -101,6 +150,56 @@ repo_find({ query: "react" })
|
|
|
101
150
|
|
|
102
151
|
---
|
|
103
152
|
|
|
153
|
+
### repo_query
|
|
154
|
+
|
|
155
|
+
Resolve a repository automatically and explore it with the repo-explorer agent. If multiple repos match, it asks you to disambiguate. Accepts explicit repo lists for multi-repo questions.
|
|
156
|
+
|
|
157
|
+
**Arguments:**
|
|
158
|
+
- `query` (string, optional): Repository name, owner/repo, or absolute local path. Examples: `"next.js"`, `"vercel/next.js"`, `"/Users/me/projects/app"`
|
|
159
|
+
- `repos` (string[], optional): Explicit repositories to explore (`owner/repo` or `owner/repo@branch`)
|
|
160
|
+
- `question` (string, required): What you want to understand about the codebase
|
|
161
|
+
|
|
162
|
+
**Examples:**
|
|
163
|
+
```typescript
|
|
164
|
+
// Automatic resolution
|
|
165
|
+
repo_query({
|
|
166
|
+
query: "react",
|
|
167
|
+
question: "How does reconciliation work?"
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
// Absolute local path
|
|
171
|
+
repo_query({
|
|
172
|
+
query: "/Users/me/projects/firmware",
|
|
173
|
+
question: "Where is the BLE handshake implemented?"
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
// Explicit repo list
|
|
177
|
+
repo_query({
|
|
178
|
+
repos: ["acme/firmware", "acme/app"],
|
|
179
|
+
question: "Where is the BLE handshake implemented?"
|
|
180
|
+
})
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
**Returns:** Exploration output from one or more repositories
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
### repo_pick_dir
|
|
188
|
+
|
|
189
|
+
Open a native folder picker and return the selected path. Call this immediately after the user asks for a local repo to avoid delayed popups if they step away.
|
|
190
|
+
|
|
191
|
+
**Arguments:**
|
|
192
|
+
- `prompt` (string, optional): Prompt text shown in the picker dialog
|
|
193
|
+
|
|
194
|
+
**Examples:**
|
|
195
|
+
```typescript
|
|
196
|
+
repo_pick_dir({ prompt: "Select your firmware repo" })
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
**Returns:** Selected folder path
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
104
203
|
### repo_clone
|
|
105
204
|
|
|
106
205
|
Clone a repository to local cache or return path if already cached.
|
|
@@ -354,6 +453,7 @@ repo_read({ repo: "my-org/api-service", path: "src/routes/*.ts" })
|
|
|
354
453
|
- **GitHub only**: Remote URL parsing only supports GitHub (SSH and HTTPS formats)
|
|
355
454
|
- **Read-only for local repos**: The plugin never modifies local repositories (type: "local")
|
|
356
455
|
- **No diff/blame**: Use git directly for advanced git operations
|
|
456
|
+
- **Single branch per repo**: Each repo has one directory; switching branches replaces the working tree (no parallel branch access)
|
|
357
457
|
|
|
358
458
|
---
|
|
359
459
|
|