opencode-repos 0.1.0

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.
@@ -0,0 +1,987 @@
1
+ # OpenCode Repos Plugin
2
+
3
+ ## Context
4
+
5
+ ### Original Request
6
+ Create an OpenCode plugin to manage repository references for AI agents. Currently, the user manually clones repos for reference, sometimes reuses them, and the workflow is inefficient. The plugin should cache GitHub repos and auto-discover local repos already on the filesystem.
7
+
8
+ **Core Use Case**: Agent working in Project A (e.g., backend) needs to understand Project B (e.g., firmware/frontend) to integrate properly. The plugin provides infrastructure to quickly access and explore cross-project codebases, enabling agents to understand underlying code and write proper integration code.
9
+
10
+ ### Interview Summary
11
+ **Key Discussions**:
12
+ - Storage: Global cache at `~/.cache/opencode-repos/` with manifest
13
+ - Config: `~/.config/opencode/opencode-repos.json` for search paths
14
+ - Repo format: Short name `vercel/next.js`, with branch support `vercel/next.js@canary`
15
+ - Auth: SSH for private repos (leverage existing keys)
16
+ - Clone strategy: Shallow by default (depth=1), separate dirs per branch
17
+ - Discovery: Smart clone checks cache -> local index -> clone fresh
18
+ - Local scan: Uses `fd` to find repos in configured search paths
19
+
20
+ **Research Findings**:
21
+ - tmux plugin pattern: ~480 lines single file, stateless, Zod schemas
22
+ - rate-limit plugin: multi-file, event hooks, config loading
23
+ - npm name `opencode-repos` is available
24
+ - Plugin receives `$` (Bun shell) for command execution
25
+ - **CRITICAL**: oh-my-opencode plugin shows plugins CAN define custom agents via `config` handler
26
+ - Agents are registered by modifying `config.agent` in the config handler
27
+ - `AgentConfig` from `@opencode-ai/sdk` defines agent structure
28
+
29
+ ### Metis Review
30
+ **Identified Gaps** (addressed):
31
+ - SSH auth in plugin context: Added validation task before implementation
32
+ - Concurrency: Added file locking for manifest writes
33
+ - Error recovery: Added cleanup for partial clones
34
+ - Git edge cases: Disable hooks, no submodules, shallow only
35
+ - Config validation: Handle missing/invalid gracefully
36
+ - Phased approach: MVP first (clone, list, read), then scan/update
37
+
38
+ ### OpenCode Plugin API Discovery (from oh-my-opencode analysis)
39
+ **Custom Agent Registration Pattern**:
40
+ ```typescript
41
+ // Plugins can define custom agents via config handler
42
+ const plugin: Plugin = async (ctx) => {
43
+ return {
44
+ config: async (config) => {
45
+ config.agent = {
46
+ ...config.agent,
47
+ "repo-explorer": {
48
+ description: "Specialized agent for exploring external codebases",
49
+ mode: "subagent",
50
+ model: "anthropic/claude-sonnet-4-5",
51
+ temperature: 0.1,
52
+ permission: { edit: "deny", write: "deny", task: "deny" },
53
+ prompt: "You are a codebase exploration specialist..."
54
+ }
55
+ }
56
+ },
57
+ tool: { /* tools */ }
58
+ }
59
+ }
60
+ ```
61
+
62
+ **AgentConfig Interface** (from @opencode-ai/sdk):
63
+ - `description`: What the agent does
64
+ - `mode`: "primary" | "subagent"
65
+ - `model?`: Model to use
66
+ - `temperature?`: Sampling temperature
67
+ - `permission?`: Tool permissions (allow/deny/ask per tool)
68
+ - `prompt`: System prompt for the agent
69
+ - `tools?`: Tool whitelist
70
+
71
+ ---
72
+
73
+ ## Work Objectives
74
+
75
+ ### Core Objective
76
+ Build an OpenCode plugin that provides agents with efficient cross-codebase intelligence through:
77
+ 1. **Tools** for repo management (clone, list, read, scan, update, remove)
78
+ 2. **Custom `repo-explorer` agent** purpose-built for exploring external codebases
79
+ 3. **`repo_explore` tool** that spawns the explorer agent in a repo's context
80
+
81
+ The goal is enabling agents to deeply understand other projects (firmware, dependencies, related services) to write proper integration code.
82
+
83
+ ### Concrete Deliverables
84
+ - `~/personal/projects/opencode-repos/index.ts` - Main plugin file with tools + config handler
85
+ - `~/personal/projects/opencode-repos/package.json` - Package manifest
86
+ - `~/personal/projects/opencode-repos/tsconfig.json` - TypeScript config
87
+ - `~/personal/projects/opencode-repos/README.md` - Documentation
88
+ - `~/personal/projects/opencode-repos/src/manifest.ts` - Manifest types and operations
89
+ - `~/personal/projects/opencode-repos/src/git.ts` - Git operations
90
+ - `~/personal/projects/opencode-repos/src/scanner.ts` - Local repo scanner
91
+ - `~/personal/projects/opencode-repos/src/agents/repo-explorer.ts` - Explorer agent definition
92
+ - `~/personal/projects/opencode-repos/src/__tests__/manifest.test.ts` - Manifest tests
93
+ - `~/personal/projects/opencode-repos/src/__tests__/git.test.ts` - Git operation tests
94
+
95
+ ### Definition of Done
96
+ - [x] `bun test` passes with all tests green
97
+ - [x] Plugin loads in OpenCode without errors
98
+ - [x] Can clone public repo via `repo_clone("vercel/next.js")`
99
+ - [x] Can clone private repo via SSH
100
+ - [x] Can list cached repos via `repo_list`
101
+ - [x] Can read files via `repo_read`
102
+ - [x] Can scan local repos via `repo_scan`
103
+ - [x] `repo-explorer` agent appears in agent list
104
+ - [x] Can explore a repo via `repo_explore("vercel/next.js", "How does routing work?")`
105
+ - [x] Reference in opencode.jsonc works: `file:///Users/liamvinberg/personal/projects/opencode-repos/index.ts`
106
+
107
+ ### Must Have
108
+ - Smart clone (check cache -> local -> clone fresh)
109
+ - Shallow clones only (depth=1)
110
+ - SSH auth support for private repos
111
+ - Manifest persistence with atomic writes
112
+ - File locking for concurrent access
113
+ - Branch support with separate directories
114
+ - Local repo discovery via fd
115
+ - Configurable search paths
116
+ - Metadata tracking (clone date, last accessed, size, etc.)
117
+ - **Custom `repo-explorer` agent** for cross-codebase exploration
118
+ - **`repo_explore` tool** that spawns explorer in repo context
119
+ - **Config handler** to register the agent
120
+
121
+ ### Must NOT Have (Guardrails)
122
+ - No GitHub/GitLab API integration (pure git only)
123
+ - No auto-clone on `repo_read` (explicit clone required)
124
+ - No submodule support (v1 limitation)
125
+ - No LFS support (document as limitation)
126
+ - No commit/push/branch creation (read-only)
127
+ - No search within repos (use existing grep/glob tools)
128
+ - No multiple remotes per repo
129
+ - No auto-update on access
130
+ - No depth override (shallow only)
131
+ - No diff/blame features (use git directly)
132
+
133
+ ---
134
+
135
+ ## Verification Strategy (MANDATORY)
136
+
137
+ ### Test Decision
138
+ - **Infrastructure exists**: NO (new project)
139
+ - **User wants tests**: Basic tests with bun test
140
+ - **Framework**: bun test (built-in)
141
+
142
+ ### Test Setup
143
+ ```bash
144
+ bun init # Creates package.json with test support
145
+ bun test # Built-in test runner
146
+ ```
147
+
148
+ ### Test Structure
149
+ Each TODO includes test requirements. Tests focus on:
150
+ 1. Manifest operations (parse, write, lock)
151
+ 2. Git operations (clone, update)
152
+ 3. Scanner operations (find repos, match remotes)
153
+
154
+ ---
155
+
156
+ ## Task Flow
157
+
158
+ ```
159
+ 0. Setup project
160
+ |
161
+ 1. Manifest types/ops --> 2. Git operations (parallel)
162
+ | |
163
+ v v
164
+ 3. Tool: repo_clone (depends on 1, 2)
165
+ |
166
+ 4. Tool: repo_list --> 5. Tool: repo_read (parallel)
167
+ |
168
+ 6. Scanner module
169
+ |
170
+ 7. Tool: repo_scan
171
+ |
172
+ 8. Tools: repo_update, repo_remove (parallel)
173
+ |
174
+ 9. Agent: repo-explorer definition
175
+ |
176
+ 10. Tool: repo_explore + Config handler
177
+ |
178
+ 11. Documentation
179
+ ```
180
+
181
+ ## Parallelization
182
+
183
+ | Group | Tasks | Reason |
184
+ |-------|-------|--------|
185
+ | A | 1, 2 | Independent modules |
186
+ | B | 4, 5 | Independent tools after repo_clone |
187
+ | C | 8a, 8b | Independent tools after scan |
188
+
189
+ | Task | Depends On | Reason |
190
+ |------|------------|--------|
191
+ | 3 | 1, 2 | Uses manifest and git modules |
192
+ | 4-5 | 3 | Need clone working first |
193
+ | 7 | 6 | Scanner tool needs scanner module |
194
+ | 8 | 7 | Update/remove build on full foundation |
195
+ | 9 | 5 | Agent needs repo_read to work |
196
+ | 10 | 9 | Tool needs agent definition |
197
+ | 11 | 10 | Docs after all features complete |
198
+
199
+ ---
200
+
201
+ ## TODOs
202
+
203
+ - [x] 0. Setup project structure
204
+
205
+ **What to do**:
206
+ - Create directory `~/personal/projects/opencode-repos/`
207
+ - Initialize with `bun init`
208
+ - Create `tsconfig.json` matching tmux plugin pattern
209
+ - Add `@opencode-ai/plugin` as peer and dev dependency
210
+ - Create `src/` directory for modules
211
+ - Create `src/__tests__/` directory for tests
212
+
213
+ **Must NOT do**:
214
+ - Don't add unnecessary dependencies
215
+ - Don't deviate from tmux plugin structure
216
+
217
+ **Parallelizable**: NO (foundation for all other tasks)
218
+
219
+ **References**:
220
+
221
+ **Pattern References**:
222
+ - `~/personal/projects/opencode-tmux/package.json:1-28` - Package structure pattern
223
+ - `~/personal/projects/opencode-tmux/tsconfig.json` - TypeScript config pattern
224
+
225
+ **Acceptance Criteria**:
226
+ - [x] Directory exists at `~/personal/projects/opencode-repos/`
227
+ - [x] `package.json` has name `opencode-repos`, type `module`
228
+ - [x] `@opencode-ai/plugin` in peerDependencies and devDependencies
229
+ - [x] `tsconfig.json` targets ES2022
230
+ - [x] `bun test` runs (even with no tests yet)
231
+
232
+ **Commit**: YES
233
+ - Message: `chore: initial project setup`
234
+ - Files: `package.json`, `tsconfig.json`
235
+ - Pre-commit: `bun test`
236
+
237
+ ---
238
+
239
+ - [x] 1. Implement manifest types and operations
240
+
241
+ **What to do**:
242
+ - Define TypeScript interfaces for manifest structure
243
+ - Implement manifest read/write with atomic operations
244
+ - Implement file locking using lockfile
245
+ - Handle missing/corrupted manifest gracefully
246
+ - Write tests for manifest operations
247
+
248
+ **Must NOT do**:
249
+ - Don't use complex database (simple JSON)
250
+ - Don't skip file locking
251
+
252
+ **Parallelizable**: YES (with task 2)
253
+
254
+ **References**:
255
+
256
+ **Pattern References**:
257
+ - `~/personal/projects/opencode-rate-limit-fallback/src/config.ts` - Config loading pattern with fallbacks
258
+
259
+ **Type Definitions**:
260
+ ```typescript
261
+ interface RepoEntry {
262
+ type: 'cached' | 'local'
263
+ path: string
264
+ clonedAt?: string // ISO timestamp
265
+ lastAccessed: string // ISO timestamp
266
+ lastUpdated?: string // ISO timestamp
267
+ sizeBytes?: number
268
+ defaultBranch: string
269
+ shallow: boolean
270
+ }
271
+
272
+ interface Manifest {
273
+ version: 1
274
+ repos: Record<string, RepoEntry> // key: "owner/repo@branch"
275
+ localIndex: Record<string, string> // remote URL -> local path
276
+ }
277
+
278
+ interface Config {
279
+ localSearchPaths: string[]
280
+ }
281
+ ```
282
+
283
+ **External References**:
284
+ - https://www.npmjs.com/package/proper-lockfile - File locking pattern (reference only, implement manually with Bun)
285
+
286
+ **Acceptance Criteria**:
287
+ - [x] `src/manifest.ts` exports: `loadManifest`, `saveManifest`, `withManifestLock`
288
+ - [x] Creates manifest if not exists with empty repos
289
+ - [x] Atomic writes (write to .tmp, rename)
290
+ - [x] File locking prevents concurrent writes
291
+ - [x] `bun test src/__tests__/manifest.test.ts` passes
292
+
293
+ **Commit**: YES
294
+ - Message: `feat: implement manifest operations with locking`
295
+ - Files: `src/manifest.ts`, `src/__tests__/manifest.test.ts`
296
+ - Pre-commit: `bun test`
297
+
298
+ ---
299
+
300
+ - [x] 2. Implement git operations module
301
+
302
+ **What to do**:
303
+ - Implement `cloneRepo(url, destPath, options)` with shallow clone
304
+ - Implement `updateRepo(path)` with fetch + reset
305
+ - Implement `getRepoInfo(path)` to get remote, branch, etc.
306
+ - Implement `parseRepoSpec(spec)` to parse `owner/repo@branch`
307
+ - Disable git hooks on clone
308
+ - Handle errors gracefully with cleanup
309
+ - Write tests for git operations
310
+
311
+ **Must NOT do**:
312
+ - Don't support submodules
313
+ - Don't allow depth override
314
+ - Don't add push/commit operations
315
+ - Don't execute git hooks
316
+
317
+ **Parallelizable**: YES (with task 1)
318
+
319
+ **References**:
320
+
321
+ **Pattern References**:
322
+ - `~/personal/projects/opencode-tmux/index.ts:74-94` - Bun shell command execution pattern
323
+
324
+ **Implementation Details**:
325
+ ```typescript
326
+ // Clone command pattern
327
+ await $`git clone --depth=1 --single-branch --branch ${branch} --config core.hooksPath=/dev/null ${url} ${destPath}`
328
+
329
+ // Update command pattern
330
+ await $`git -C ${path} fetch origin ${branch} --depth=1`
331
+ await $`git -C ${path} reset --hard origin/${branch}`
332
+ ```
333
+
334
+ **Acceptance Criteria**:
335
+ - [x] `src/git.ts` exports: `cloneRepo`, `updateRepo`, `getRepoInfo`, `parseRepoSpec`, `buildGitUrl`
336
+ - [x] `cloneRepo` always uses `--depth=1`
337
+ - [x] `cloneRepo` disables hooks via `--config core.hooksPath=/dev/null`
338
+ - [x] `cloneRepo` cleans up on failure (removes partial directory)
339
+ - [x] `parseRepoSpec("vercel/next.js@canary")` returns `{ owner: "vercel", repo: "next.js", branch: "canary" }`
340
+ - [x] `parseRepoSpec("vercel/next.js")` returns `{ owner: "vercel", repo: "next.js", branch: null }`
341
+ - [x] `buildGitUrl` returns SSH URL: `git@github.com:owner/repo.git`
342
+ - [x] `bun test src/__tests__/git.test.ts` passes
343
+
344
+ **Commit**: YES
345
+ - Message: `feat: implement git operations module`
346
+ - Files: `src/git.ts`, `src/__tests__/git.test.ts`
347
+ - Pre-commit: `bun test`
348
+
349
+ ---
350
+
351
+ - [x] 3. Implement repo_clone tool
352
+
353
+ **What to do**:
354
+ - Create main plugin export in `index.ts`
355
+ - Implement `repo_clone` tool with Zod schema
356
+ - Smart flow: check manifest -> clone if not exists -> return path
357
+ - Update manifest with new entry
358
+ - Track lastAccessed on every clone call
359
+ - Validate SSH auth works for private repos
360
+
361
+ **Must NOT do**:
362
+ - Don't auto-clone in other tools
363
+ - Don't clone if already exists (return cached path)
364
+
365
+ **Parallelizable**: NO (depends on 1, 2)
366
+
367
+ **References**:
368
+
369
+ **Pattern References**:
370
+ - `~/personal/projects/opencode-tmux/index.ts:242-261` - Plugin export pattern
371
+ - `~/personal/projects/opencode-tmux/index.ts:276-323` - Tool definition with Zod schema
372
+
373
+ **Tool Signature**:
374
+ ```typescript
375
+ repo_clone: tool({
376
+ description: "Clone a repository to local cache or return path if already cached. Supports public and private (SSH) repos.",
377
+ args: {
378
+ repo: tool.schema.string().describe("Repository in format 'owner/repo' or 'owner/repo@branch'"),
379
+ force: tool.schema.boolean().optional().default(false).describe("Force re-clone even if cached"),
380
+ },
381
+ async execute(args) {
382
+ // 1. Parse repo spec
383
+ // 2. Check manifest for existing entry
384
+ // 3. If exists and !force, update lastAccessed, return path
385
+ // 4. If force or !exists, clone to cache
386
+ // 5. Update manifest
387
+ // 6. Return path and status
388
+ }
389
+ })
390
+ ```
391
+
392
+ **Acceptance Criteria**:
393
+ - [x] `index.ts` exports plugin following tmux pattern
394
+ - [x] `repo_clone("vercel/next.js")` clones to `~/.cache/opencode-repos/vercel/next.js@main/`
395
+ - [x] `repo_clone("vercel/next.js@canary")` clones to `~/.cache/opencode-repos/vercel/next.js@canary/`
396
+ - [x] Second call to same repo returns cached path without cloning
397
+ - [x] `force: true` re-clones even if cached
398
+ - [x] Manifest updated after successful clone
399
+ - [x] Returns markdown with path and status
400
+
401
+ **Commit**: YES
402
+ - Message: `feat: implement repo_clone tool`
403
+ - Files: `index.ts`
404
+ - Pre-commit: `bun test`
405
+
406
+ ---
407
+
408
+ - [x] 4. Implement repo_list tool
409
+
410
+ **What to do**:
411
+ - Add `repo_list` tool to plugin
412
+ - List all repos from manifest (cached + local)
413
+ - Show metadata: type, path, dates, size
414
+ - Format as markdown table
415
+ - Support filtering by type (cached/local/all)
416
+
417
+ **Must NOT do**:
418
+ - Don't calculate size on every call (use cached value)
419
+ - Don't scan filesystem (use manifest only)
420
+
421
+ **Parallelizable**: YES (with task 5)
422
+
423
+ **References**:
424
+
425
+ **Pattern References**:
426
+ - `~/personal/projects/opencode-tmux/index.ts:414-471` - tmux_list tool pattern with scoped output
427
+
428
+ **Tool Signature**:
429
+ ```typescript
430
+ repo_list: tool({
431
+ description: "List all registered repositories (cached and local)",
432
+ args: {
433
+ type: tool.schema.enum(["all", "cached", "local"]).optional().default("all"),
434
+ },
435
+ async execute(args) {
436
+ // Load manifest
437
+ // Filter by type
438
+ // Format as markdown table
439
+ }
440
+ })
441
+ ```
442
+
443
+ **Output Format**:
444
+ ```markdown
445
+ ## Registered Repositories
446
+
447
+ | Repo | Type | Branch | Last Accessed | Size |
448
+ |------|------|--------|---------------|------|
449
+ | vercel/next.js | cached | canary | 2024-01-20 | 52MB |
450
+ | my-project | local | main | 2024-01-19 | - |
451
+
452
+ Total: 2 repos (1 cached, 1 local)
453
+ ```
454
+
455
+ **Acceptance Criteria**:
456
+ - [x] `repo_list()` returns markdown table of all repos
457
+ - [x] `repo_list({ type: "cached" })` filters to cached only
458
+ - [x] Shows repo name, type, branch, last accessed, size
459
+ - [x] Returns "No repositories registered" if empty
460
+
461
+ **Commit**: YES
462
+ - Message: `feat: implement repo_list tool`
463
+ - Files: `index.ts`
464
+ - Pre-commit: `bun test`
465
+
466
+ ---
467
+
468
+ - [x] 5. Implement repo_read tool
469
+
470
+ **What to do**:
471
+ - Add `repo_read` tool to plugin
472
+ - Read file(s) from a registered repo
473
+ - Validate repo exists in manifest
474
+ - Update lastAccessed timestamp
475
+ - Support glob patterns for multiple files
476
+
477
+ **Must NOT do**:
478
+ - Don't auto-clone if repo not found (error instead)
479
+ - Don't read binary files (skip with warning)
480
+
481
+ **Parallelizable**: YES (with task 4)
482
+
483
+ **References**:
484
+
485
+ **Pattern References**:
486
+ - `~/personal/projects/opencode-tmux/index.ts:277-323` - Tool with multiple optional args
487
+
488
+ **Tool Signature**:
489
+ ```typescript
490
+ repo_read: tool({
491
+ description: "Read files from a registered repository. Repo must be cloned first via repo_clone.",
492
+ args: {
493
+ repo: tool.schema.string().describe("Repository in format 'owner/repo' or 'owner/repo@branch'"),
494
+ path: tool.schema.string().describe("File path within repo, supports glob patterns"),
495
+ maxLines: tool.schema.number().optional().default(500).describe("Max lines per file"),
496
+ },
497
+ async execute(args) {
498
+ // 1. Look up repo in manifest
499
+ // 2. Error if not found
500
+ // 3. Resolve path within repo
501
+ // 4. Read file(s)
502
+ // 5. Update lastAccessed
503
+ // 6. Return content
504
+ }
505
+ })
506
+ ```
507
+
508
+ **Acceptance Criteria**:
509
+ - [x] `repo_read({ repo: "vercel/next.js", path: "README.md" })` returns file content
510
+ - [x] `repo_read({ repo: "vercel/next.js", path: "src/*.ts" })` returns multiple files
511
+ - [x] Returns error if repo not in manifest: "Repository not found. Run repo_clone first."
512
+ - [x] Updates lastAccessed in manifest after read
513
+ - [x] Truncates large files with note: "[truncated at 500 lines]"
514
+
515
+ **Commit**: YES
516
+ - Message: `feat: implement repo_read tool`
517
+ - Files: `index.ts`
518
+ - Pre-commit: `bun test`
519
+
520
+ ---
521
+
522
+ - [x] 6. Implement scanner module
523
+
524
+ **What to do**:
525
+ - Implement local repo scanner using `fd`
526
+ - Find all git repos in configured search paths
527
+ - Extract remote URL to match against repo specs
528
+ - Build local index mapping remote -> local path
529
+ - Handle repos with no remote gracefully
530
+
531
+ **Must NOT do**:
532
+ - Don't scan entire filesystem (only configured paths)
533
+ - Don't follow symlinks into loops
534
+ - Don't scan nested git repos (max-depth limit)
535
+
536
+ **Parallelizable**: NO (depends on foundation)
537
+
538
+ **References**:
539
+
540
+ **Implementation Pattern**:
541
+ ```typescript
542
+ // Find all .git directories
543
+ const gitDirs = await $`fd -H -t d '^.git$' --max-depth 4 ${searchPath}`.text()
544
+
545
+ // For each, get remote
546
+ for (const gitDir of gitDirs.split('\n').filter(Boolean)) {
547
+ const repoPath = path.dirname(gitDir)
548
+ const remote = await $`git -C ${repoPath} remote get-url origin`.text().catch(() => null)
549
+ if (remote) {
550
+ // Parse remote to get owner/repo
551
+ // Add to local index
552
+ }
553
+ }
554
+ ```
555
+
556
+ **Acceptance Criteria**:
557
+ - [x] `src/scanner.ts` exports: `scanLocalRepos`, `matchRemoteToSpec`
558
+ - [x] `scanLocalRepos(paths)` returns array of `{ path, remote, branch }`
559
+ - [x] Handles repos without remote (skips them)
560
+ - [x] Respects max-depth of 4 to avoid deep nesting
561
+ - [x] `matchRemoteToSpec("git@github.com:vercel/next.js.git")` returns `"vercel/next.js"`
562
+ - [x] Handles both SSH and HTTPS remote formats
563
+
564
+ **Commit**: YES
565
+ - Message: `feat: implement local repo scanner`
566
+ - Files: `src/scanner.ts`
567
+ - Pre-commit: `bun test`
568
+
569
+ ---
570
+
571
+ - [x] 7. Implement repo_scan tool
572
+
573
+ **What to do**:
574
+ - Add `repo_scan` tool to plugin
575
+ - Load config for search paths
576
+ - Use scanner module to find repos
577
+ - Update manifest with local entries
578
+ - Report what was found
579
+
580
+ **Must NOT do**:
581
+ - Don't delete existing cached entries
582
+ - Don't scan if no search paths configured
583
+
584
+ **Parallelizable**: NO (depends on 6)
585
+
586
+ **References**:
587
+
588
+ **Tool Signature**:
589
+ ```typescript
590
+ repo_scan: tool({
591
+ description: "Scan local filesystem for git repositories and register them. Configure search paths in ~/.config/opencode/opencode-repos.json",
592
+ args: {
593
+ paths: tool.schema.array(tool.schema.string()).optional().describe("Override search paths (default: from config)"),
594
+ },
595
+ async execute(args) {
596
+ // 1. Load config for default paths (or use args.paths)
597
+ // 2. Validate paths exist
598
+ // 3. Scan each path
599
+ // 4. Register found repos as 'local' type
600
+ // 5. Return summary
601
+ }
602
+ })
603
+ ```
604
+
605
+ **Config File** (`~/.config/opencode/opencode-repos.json`):
606
+ ```json
607
+ {
608
+ "localSearchPaths": [
609
+ "~/projects",
610
+ "~/personal/projects",
611
+ "~/code"
612
+ ]
613
+ }
614
+ ```
615
+
616
+ **Acceptance Criteria**:
617
+ - [x] `repo_scan()` uses paths from config file
618
+ - [x] `repo_scan({ paths: ["~/custom"] })` overrides config
619
+ - [x] Found repos added to manifest as `type: "local"`
620
+ - [x] Returns summary: "Found 5 repos in 3 paths. 2 new, 3 already registered."
621
+ - [x] Handles missing config: "No search paths configured. Create ~/.config/opencode/opencode-repos.json"
622
+
623
+ **Commit**: YES
624
+ - Message: `feat: implement repo_scan tool`
625
+ - Files: `index.ts`
626
+ - Pre-commit: `bun test`
627
+
628
+ ---
629
+
630
+ - [x] 8a. Implement repo_update tool
631
+
632
+ **What to do**:
633
+ - Add `repo_update` tool to plugin
634
+ - Pull latest changes for cached repos
635
+ - Show status for local repos (don't modify)
636
+ - Update lastUpdated timestamp
637
+
638
+ **Must NOT do**:
639
+ - Don't modify local repos (only show status)
640
+ - Don't update if there are local changes
641
+
642
+ **Parallelizable**: YES (with 8b)
643
+
644
+ **References**:
645
+
646
+ **Tool Signature**:
647
+ ```typescript
648
+ repo_update: tool({
649
+ description: "Update a cached repository to latest. For local repos, shows git status without modifying.",
650
+ args: {
651
+ repo: tool.schema.string().describe("Repository in format 'owner/repo' or 'owner/repo@branch'"),
652
+ },
653
+ async execute(args) {
654
+ // 1. Look up in manifest
655
+ // 2. If cached: fetch + reset
656
+ // 3. If local: show status only
657
+ // 4. Update lastUpdated
658
+ }
659
+ })
660
+ ```
661
+
662
+ **Acceptance Criteria**:
663
+ - [x] `repo_update("vercel/next.js")` fetches and resets cached repo
664
+ - [x] Returns: "Updated vercel/next.js@canary to latest (abc1234)"
665
+ - [x] For local repos: "Local repo - showing status only:\n[git status output]"
666
+ - [x] Updates lastUpdated in manifest
667
+
668
+ **Commit**: YES (combined with 8b)
669
+ - Message: `feat: implement repo_update and repo_remove tools`
670
+ - Files: `index.ts`
671
+ - Pre-commit: `bun test`
672
+
673
+ ---
674
+
675
+ - [x] 8b. Implement repo_remove tool
676
+
677
+ **What to do**:
678
+ - Add `repo_remove` tool to plugin
679
+ - For cached: delete directory and manifest entry
680
+ - For local: remove from manifest only (don't delete files)
681
+ - Confirm dangerous operations
682
+
683
+ **Must NOT do**:
684
+ - Don't delete local repo files (only unregister)
685
+ - Don't remove without confirmation for cached
686
+
687
+ **Parallelizable**: YES (with 8a)
688
+
689
+ **References**:
690
+
691
+ **Tool Signature**:
692
+ ```typescript
693
+ repo_remove: tool({
694
+ description: "Remove a repository. Cached repos are deleted from disk. Local repos are unregistered only.",
695
+ args: {
696
+ repo: tool.schema.string().describe("Repository in format 'owner/repo' or 'owner/repo@branch'"),
697
+ confirm: tool.schema.boolean().optional().default(false).describe("Confirm deletion for cached repos"),
698
+ },
699
+ async execute(args) {
700
+ // 1. Look up in manifest
701
+ // 2. If cached: require confirm, delete dir
702
+ // 3. If local: just remove from manifest
703
+ // 4. Update manifest
704
+ }
705
+ })
706
+ ```
707
+
708
+ **Acceptance Criteria**:
709
+ - [x] `repo_remove("vercel/next.js")` without confirm: "This will delete cached repo. Use confirm: true to proceed."
710
+ - [x] `repo_remove("vercel/next.js", { confirm: true })` deletes and removes from manifest
711
+ - [x] `repo_remove("my-local")` unregisters without deleting: "Unregistered my-local (files preserved at /path)"
712
+
713
+ **Commit**: YES (combined with 8a)
714
+ - Message: `feat: implement repo_update and repo_remove tools`
715
+ - Files: `index.ts`
716
+ - Pre-commit: `bun test`
717
+
718
+ ---
719
+
720
+ - [x] 9. Define repo-explorer agent
721
+
722
+ **What to do**:
723
+ - Create agent definition in `src/agents/repo-explorer.ts`
724
+ - Define agent config following oh-my-opencode patterns
725
+ - Agent is read-only (no edit, write, task permissions)
726
+ - Agent has access to: read, glob, grep, bash (for git commands)
727
+ - Craft system prompt for codebase exploration
728
+
729
+ **Must NOT do**:
730
+ - Don't give agent write/edit permissions
731
+ - Don't allow agent to spawn sub-tasks
732
+ - Don't allow agent to modify the repo
733
+
734
+ **Parallelizable**: NO (depends on core tools working)
735
+
736
+ **References**:
737
+
738
+ **Pattern References**:
739
+ - `/tmp/oh-my-opencode/src/agents/explore.ts` - Explorer agent pattern
740
+ - `/tmp/oh-my-opencode/src/agents/oracle.ts` - Read-only agent pattern
741
+
742
+ **Agent Definition**:
743
+ ```typescript
744
+ import type { AgentConfig } from "@opencode-ai/sdk"
745
+
746
+ export function createRepoExplorerAgent(): AgentConfig {
747
+ return {
748
+ description: "Specialized agent for exploring external codebases. Use when you need to understand another project's architecture, APIs, patterns, or implementation details.",
749
+ mode: "subagent" as const,
750
+ temperature: 0.1,
751
+ permission: {
752
+ edit: "deny",
753
+ write: "deny",
754
+ task: "deny",
755
+ delegate_task: "deny",
756
+ },
757
+ prompt: `You are a codebase exploration specialist. Your job is to deeply understand external codebases and report your findings clearly.
758
+
759
+ ## Your Capabilities
760
+ - Read and analyze source code
761
+ - Search for patterns and implementations
762
+ - Understand project structure and architecture
763
+ - Identify APIs, interfaces, and integration points
764
+
765
+ ## Your Approach
766
+ 1. Start with high-level structure (README, package.json, main entry points)
767
+ 2. Identify key directories and their purposes
768
+ 3. Trace code paths relevant to the question
769
+ 4. Report findings with specific file references and code examples
770
+
771
+ ## Output Format
772
+ - Be specific: cite file paths and line numbers
773
+ - Include relevant code snippets
774
+ - Explain how components interact
775
+ - Note any patterns or conventions used
776
+
777
+ You are READ-ONLY. You cannot modify files or create new ones.`
778
+ }
779
+ }
780
+ ```
781
+
782
+ **Acceptance Criteria**:
783
+ - [x] `src/agents/repo-explorer.ts` exports `createRepoExplorerAgent`
784
+ - [x] Agent has `mode: "subagent"`
785
+ - [x] Agent has `permission: { edit: "deny", write: "deny", task: "deny" }`
786
+ - [x] Agent prompt focuses on codebase exploration
787
+
788
+ **Commit**: YES
789
+ - Message: `feat: define repo-explorer agent`
790
+ - Files: `src/agents/repo-explorer.ts`
791
+ - Pre-commit: `bun test`
792
+
793
+ ---
794
+
795
+ - [x] 10. Implement repo_explore tool and config handler
796
+
797
+ **What to do**:
798
+ - Add config handler to register `repo-explorer` agent
799
+ - Implement `repo_explore` tool that:
800
+ 1. Ensures repo is cloned/available
801
+ 2. Spawns `repo-explorer` agent in that repo's context
802
+ 3. Returns the exploration results
803
+ - Use OpenCode SDK client to spawn the agent session
804
+
805
+ **Must NOT do**:
806
+ - Don't allow exploration without clone
807
+ - Don't give the spawned agent elevated permissions
808
+
809
+ **Parallelizable**: NO (depends on agent definition)
810
+
811
+ **References**:
812
+
813
+ **Pattern References**:
814
+ - `/tmp/oh-my-opencode/src/plugin-handlers/config-handler.ts:280-300` - Config handler pattern for agent registration
815
+ - `/tmp/oh-my-opencode/src/tools/call-omo-agent/tools.ts:186-197` - Agent invocation via client.session.prompt
816
+
817
+ **Implementation Pattern**:
818
+ ```typescript
819
+ // Config handler to register agent
820
+ config: async (config) => {
821
+ const explorerAgent = createRepoExplorerAgent()
822
+ config.agent = {
823
+ ...config.agent,
824
+ "repo-explorer": explorerAgent,
825
+ }
826
+ },
827
+
828
+ // Tool to spawn explorer in repo context
829
+ repo_explore: tool({
830
+ description: "Explore a repository to understand its codebase. Spawns a specialized exploration agent that analyzes the repo and answers your question.",
831
+ args: {
832
+ repo: tool.schema.string().describe("Repository in format 'owner/repo' or 'owner/repo@branch'"),
833
+ question: tool.schema.string().describe("What you want to understand about the codebase"),
834
+ },
835
+ async execute(args, ctx) {
836
+ // 1. Ensure repo is available (clone if needed)
837
+ const repoPath = await ensureRepo(args.repo)
838
+
839
+ // 2. Create exploration prompt with repo context
840
+ const prompt = `Explore the codebase at ${repoPath} and answer: ${args.question}
841
+
842
+ Working directory: ${repoPath}
843
+ Available tools: read, glob, grep, bash`
844
+
845
+ // 3. Spawn repo-explorer agent via SDK
846
+ const result = await ctx.client.session.prompt({
847
+ body: {
848
+ agent: "repo-explorer",
849
+ parts: [{ type: "text", text: prompt }],
850
+ }
851
+ })
852
+
853
+ return result
854
+ }
855
+ })
856
+ ```
857
+
858
+ **Acceptance Criteria**:
859
+ - [x] Config handler registers `repo-explorer` agent
860
+ - [x] `repo_explore({ repo: "vercel/next.js", question: "How does routing work?" })` spawns explorer
861
+ - [x] Explorer runs in context of the specified repo
862
+ - [x] Returns exploration results as markdown
863
+ - [x] Auto-clones repo if not cached
864
+
865
+ **Commit**: YES
866
+ - Message: `feat: implement repo_explore tool and config handler`
867
+ - Files: `index.ts`
868
+ - Pre-commit: `bun test`
869
+
870
+ ---
871
+
872
+ - [x] 11. Documentation and final polish
873
+
874
+ **What to do**:
875
+ - Write comprehensive README.md
876
+ - Document all tools with examples
877
+ - Document the `repo-explorer` agent
878
+ - Document config file format
879
+ - Add installation instructions
880
+ - Document limitations (no submodules, no LFS, etc.)
881
+ - Update package.json with keywords, repository, etc.
882
+
883
+ **Must NOT do**:
884
+ - Don't over-document obvious things
885
+ - Don't promise features not implemented
886
+
887
+ **Parallelizable**: NO (final task)
888
+
889
+ **References**:
890
+
891
+ **Pattern References**:
892
+ - `~/personal/projects/opencode-tmux/README.md` - README structure and format
893
+
894
+ **README Structure**:
895
+ ```markdown
896
+ # opencode-repos
897
+
898
+ Repository cache, registry, and cross-codebase intelligence for OpenCode agents.
899
+
900
+ ## Installation
901
+ ## Configuration
902
+ ## Tools
903
+ ### repo_clone
904
+ ### repo_list
905
+ ### repo_read
906
+ ### repo_scan
907
+ ### repo_update
908
+ ### repo_remove
909
+ ### repo_explore
910
+ ## Custom Agent: repo-explorer
911
+ ## Use Cases
912
+ ### Cross-project integration
913
+ ### Understanding dependencies
914
+ ### Firmware/backend exploration
915
+ ## Limitations
916
+ ## Development
917
+ ```
918
+
919
+ **Acceptance Criteria**:
920
+ - [x] README.md exists with installation and usage
921
+ - [x] All 7 tools documented with examples
922
+ - [x] `repo-explorer` agent documented with use cases
923
+ - [x] Config file format documented
924
+ - [x] Limitations section lists: no submodules, no LFS, shallow only
925
+ - [x] `package.json` has keywords: `opencode`, `opencode-plugin`, `repos`, `cache`, `codebase`
926
+
927
+ **Commit**: YES
928
+ - Message: `docs: add comprehensive documentation`
929
+ - Files: `README.md`, `package.json`
930
+ - Pre-commit: `bun test`
931
+
932
+ ---
933
+
934
+ ## Commit Strategy
935
+
936
+ | After Task | Message | Files | Verification |
937
+ |------------|---------|-------|--------------|
938
+ | 0 | `chore: initial project setup` | package.json, tsconfig.json | `bun test` |
939
+ | 1 | `feat: implement manifest operations with locking` | src/manifest.ts, tests | `bun test` |
940
+ | 2 | `feat: implement git operations module` | src/git.ts, tests | `bun test` |
941
+ | 3 | `feat: implement repo_clone tool` | index.ts | `bun test` |
942
+ | 4 | `feat: implement repo_list tool` | index.ts | `bun test` |
943
+ | 5 | `feat: implement repo_read tool` | index.ts | `bun test` |
944
+ | 6 | `feat: implement local repo scanner` | src/scanner.ts | `bun test` |
945
+ | 7 | `feat: implement repo_scan tool` | index.ts | `bun test` |
946
+ | 8a+8b | `feat: implement repo_update and repo_remove tools` | index.ts | `bun test` |
947
+ | 9 | `feat: define repo-explorer agent` | src/agents/repo-explorer.ts | `bun test` |
948
+ | 10 | `feat: implement repo_explore tool and config handler` | index.ts | `bun test` |
949
+ | 11 | `docs: add comprehensive documentation` | README.md, package.json | `bun test` |
950
+
951
+ ---
952
+
953
+ ## Success Criteria
954
+
955
+ ### Verification Commands
956
+ ```bash
957
+ # All tests pass
958
+ bun test
959
+
960
+ # Plugin loads without error (check in opencode)
961
+ # Add to opencode.jsonc: "file:///Users/liamvinberg/personal/projects/opencode-repos/index.ts"
962
+
963
+ # Functional tests (manual in opencode)
964
+ repo_clone("vercel/next.js") # Should clone and return path
965
+ repo_clone("vercel/next.js") # Should return cached path (no clone)
966
+ repo_list() # Should show the repo
967
+ repo_read("vercel/next.js", "README.md") # Should return content
968
+ repo_scan() # Should find local repos
969
+ repo_update("vercel/next.js") # Should update
970
+ repo_remove("vercel/next.js", true) # Should delete
971
+
972
+ # Agent verification
973
+ # Check that repo-explorer appears in available agents
974
+ # Test cross-codebase exploration:
975
+ repo_explore("vercel/next.js", "How does the App Router work?")
976
+ ```
977
+
978
+ ### Final Checklist
979
+ - [x] All "Must Have" present
980
+ - [x] All "Must NOT Have" absent
981
+ - [x] All tests pass (`bun test`)
982
+ - [x] Plugin loads in OpenCode
983
+ - [x] Can clone public and private repos
984
+ - [x] Local scan discovers existing repos
985
+ - [x] `repo-explorer` agent registered and available
986
+ - [x] `repo_explore` tool spawns explorer correctly
987
+ - [x] Documentation complete