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 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 OpenCode agents.
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 local repository scanning:
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