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,8 @@
1
+ {
2
+ "active_plan": "/Users/liamvinberg/personal/projects/opencode-repos/.sisyphus/plans/opencode-repos.md",
3
+ "started_at": "2026-01-20T14:19:14.152Z",
4
+ "session_ids": [
5
+ "ses_42439dcabffexjguXrkWPjNecT"
6
+ ],
7
+ "plan_name": "opencode-repos"
8
+ }
@@ -0,0 +1,15 @@
1
+ # Decisions - opencode-repos
2
+
3
+ ## Task 0: Project Setup
4
+
5
+ ### Placeholder Test File
6
+ **Decision**: Created `src/__tests__/setup.test.ts` with a trivial passing test.
7
+ **Rationale**: `bun test` exits with code 1 when no tests found, which would fail verification. A placeholder ensures clean test runs during development.
8
+
9
+ ### tsconfig.json Pattern
10
+ **Decision**: Followed opencode-tmux tsconfig exactly rather than bun init default.
11
+ **Rationale**: Consistency with existing plugin ecosystem. The tmux pattern is proven to work with OpenCode plugin system.
12
+
13
+ ### Directory Structure
14
+ **Decision**: Using `src/` directory for modules rather than flat structure.
15
+ **Rationale**: Plan specifies src/ directory. Allows separation of implementation from root-level plugin entry point.
@@ -0,0 +1,384 @@
1
+ # Learnings - opencode-repos
2
+
3
+ ## Task 0: Project Setup
4
+
5
+ ### Plugin Version Discovery
6
+ - @opencode-ai/plugin does not have version 0.0.1
7
+ - Earliest version is 1.1.x series
8
+ - tmux plugin pattern: peerDependencies uses `*`, devDependencies uses `^1.1.25`
9
+
10
+ ### Bun Test Behavior
11
+ - `bun test` exits with code 1 when no test files found
12
+ - Need at least one placeholder test for clean CI/verification
13
+ - Test file pattern: `**{.test,.spec,_test_,_spec_}.{js,ts,jsx,tsx}`
14
+
15
+ ### tsconfig.json
16
+ - Bun init creates tsconfig with comments (invalid JSON for some tools)
17
+ - Replaced with clean JSON matching tmux plugin pattern
18
+ - Key settings: ES2022 target, ESNext module, bundler moduleResolution
19
+
20
+ ## Task 1: Manifest Types and Operations
21
+
22
+ ### Atomic File Writes
23
+ - Pattern: write to .tmp file, then rename (atomic at OS level)
24
+ - `fs.rename()` is atomic on POSIX systems
25
+ - Prevents partial writes from corrupting manifest
26
+
27
+ ### File Locking Pattern
28
+ - Manual lock file approach works well with Bun
29
+ - Check lock existence, check staleness (>5 min), create lock, execute, release
30
+ - Use `stat().mtimeMs` to check lock age
31
+ - Stale lock detection prevents deadlocks from crashed processes
32
+
33
+ ### Bun File APIs
34
+ - `Bun.file(path).exists()` for existence check
35
+ - `Bun.file(path).text()` for reading
36
+ - `Bun.write(path, content)` for writing
37
+ - `Bun.sleep(ms)` for delays in lock retry loop
38
+
39
+ ### Test Isolation
40
+ - Tests modify real cache dir (~/.cache/opencode-repos)
41
+ - Use beforeEach/afterEach to clean up
42
+ - `rm(path, { recursive: true, force: true })` for cleanup
43
+
44
+ ### Graceful Error Handling
45
+ - Missing manifest: return empty, don't throw
46
+ - Corrupted JSON: log warning, return empty
47
+ - Lock acquisition failure: throw after max attempts
48
+
49
+ ## Task 2: Git Operations Module
50
+
51
+ ### Bun Shell Execution
52
+ - Import `$` from "bun" for shell commands
53
+ - Use `.quiet()` to suppress stdout/stderr output
54
+ - Use `.text()` to get string output from commands
55
+ - Pattern: `await $\`git command\`.quiet()` for silent execution
56
+
57
+ ### Git Clone Flags
58
+ - `--depth=1`: Shallow clone (single commit)
59
+ - `--single-branch`: Only fetch specified branch
60
+ - `--branch ${branch}`: Specify branch to clone
61
+ - `--config core.hooksPath=/dev/null`: Disable git hooks
62
+
63
+ ### Git Directory Operations
64
+ - Use `-C ${path}` to run git commands in a specific directory
65
+ - Avoids need to change working directory
66
+
67
+ ### Cleanup on Failure
68
+ - Wrap clone in try/catch
69
+ - On failure, remove destination directory with `rm(destPath, { recursive: true, force: true })`
70
+ - Nested try/catch for cleanup to ignore cleanup errors
71
+
72
+ ### Repo Spec Parsing
73
+ - Format: "owner/repo" or "owner/repo@branch"
74
+ - Split on "@" first for branch extraction
75
+ - Split on "/" for owner/repo
76
+ - Branch can contain "/" (e.g., "feature/my-branch")
77
+ - Validate: owner and repo must not be empty
78
+
79
+ ### Integration Testing
80
+ - Use `tmpdir()` for test directories
81
+ - Use `beforeAll`/`afterAll` for setup/teardown
82
+ - GitHub's octocat/Hello-World is a good public test repo
83
+ - Commit hashes match pattern `/^[a-f0-9]{40}$/`
84
+
85
+ ## Task 3: Plugin Export and repo_clone Tool
86
+
87
+ ### Plugin Export Pattern
88
+ - Import `Plugin` type and `tool` from "@opencode-ai/plugin"
89
+ - Plugin is an async function returning object with `tool` property
90
+ - Tools are defined using `tool({ description, args, execute })`
91
+ - Export both named (`OpencodeRepos`) and default export
92
+
93
+ ### Tool Schema Pattern
94
+ - Use `tool.schema.string()`, `tool.schema.boolean()` for arg types
95
+ - Chain `.optional()`, `.default(value)`, `.describe()` for metadata
96
+ - Args are automatically validated before execute() is called
97
+
98
+ ### Smart Clone Flow
99
+ - Check manifest first (within lock) for existing entry
100
+ - If cached and not force: update lastAccessed, return path
101
+ - If force: delete existing directory before clone
102
+ - Clone to `~/.cache/opencode-repos/{owner}/{repo}@{branch}/`
103
+ - Update manifest with new entry after successful clone
104
+
105
+ ### Error Handling in Tools
106
+ - Wrap error messages: `error instanceof Error ? error.message : String(error)`
107
+ - Throw descriptive errors with repo context
108
+ - Return markdown-formatted success messages
109
+
110
+ ### Manifest Locking
111
+ - All manifest read/write operations wrapped in `withManifestLock()`
112
+ - Ensures concurrent clone operations don't corrupt manifest
113
+ - Lock is released even if operation throws
114
+
115
+ ## Task 4: repo_list Tool
116
+
117
+ ### List Tool Pattern
118
+ - Reference: opencode-tmux tmux_list tool (lines 414-471)
119
+ - Use enum schema for filtering: `tool.schema.enum(["all", "cached", "local"])`
120
+ - Return markdown-formatted output with tables
121
+
122
+ ### Markdown Table Output
123
+ - Header row with column names
124
+ - Separator row with dashes
125
+ - Data rows with pipe-separated values
126
+ - Summary line at end with counts
127
+
128
+ ### Repo Key Parsing
129
+ - Keys stored as "owner/repo@branch"
130
+ - Extract repo name: `repoKey.substring(0, repoKey.lastIndexOf("@"))`
131
+ - Branch available from entry.defaultBranch
132
+
133
+ ### Size Formatting
134
+ - sizeBytes may not exist (local repos don't track size yet)
135
+ - Format as MB: `Math.round(entry.sizeBytes / 1024 / 1024)`
136
+ - Show "-" if sizeBytes undefined
137
+
138
+ ### Filtering Logic
139
+ - Object.entries() to iterate manifest.repos
140
+ - Filter by entry.type matching args.type
141
+ - "all" type returns everything
142
+
143
+ ## Task 5: repo_read Tool
144
+
145
+ ### Glob Pattern Support
146
+ - Use `glob` npm package (v10.x) for file pattern matching
147
+ - Check for `*` or `?` in path to determine if glob needed
148
+ - `glob(pattern, { nodir: true })` returns only files, not directories
149
+ - Single file paths work without glob
150
+
151
+ ### File Reading Pattern
152
+ - Use `readFile(path, "utf-8")` from node:fs/promises
153
+ - Split content by newlines for line counting
154
+ - Truncate at maxLines with `[truncated at N lines, M total]` message
155
+ - Handle errors per-file to continue reading other files
156
+
157
+ ### Relative Path Display
158
+ - Store repoPath from manifest entry
159
+ - Calculate relative: `filePath.replace(repoPath + "/", "")`
160
+ - Display relative paths in output for readability
161
+
162
+ ### Manifest Update After Read
163
+ - Update lastAccessed timestamp after successful read
164
+ - Use withManifestLock for atomic update
165
+ - Re-load manifest inside lock to avoid stale data
166
+
167
+ ### Error Handling
168
+ - Repo not found: Return helpful message with repo_clone suggestion
169
+ - No files found: Return message with the pattern that failed
170
+ - File read error: Include in output but continue with other files
171
+
172
+ ## Task 6: Scanner Module
173
+
174
+ ### fd Command for Git Discovery
175
+ - `fd -H -t d '^.git$' --max-depth 4 ${searchPath}` finds all .git directories
176
+ - `-H`: Include hidden files/dirs (required since .git is hidden)
177
+ - `-t d`: Type directory only
178
+ - `'^.git$'`: Regex for exact match ".git"
179
+ - `--max-depth 4`: Limit depth to avoid deep nested structures
180
+
181
+ ### Remote URL Parsing
182
+ - SSH format: `git@github.com:owner/repo.git` -> extract with regex `/git@github\.com:(.+)/`
183
+ - HTTPS format: `https://github.com/owner/repo.git` -> extract with regex `/https:\/\/github\.com\/(.+)/`
184
+ - Remove `.git` suffix first with `.replace(/\.git$/, "")`
185
+ - Return null for unsupported formats (GitLab, Bitbucket, etc.)
186
+
187
+ ### Graceful Error Handling
188
+ - Invalid search path: catch and continue to next path
189
+ - Repo without remote: catch git error and continue
190
+ - Repo with git errors: catch and continue
191
+ - No repos found: return empty array (not an error)
192
+
193
+ ### Git Commands for Repo Info
194
+ - `git -C ${repoPath} remote get-url origin`: Get remote URL
195
+ - `git -C ${repoPath} branch --show-current`: Get current branch
196
+ - Default to "main" if branch is empty (detached HEAD state)
197
+
198
+ ## Task 7: repo_scan Tool
199
+
200
+ ### Config Loading Pattern
201
+ - Config file location: `~/.config/opencode/opencode-repos.json`
202
+ - Use `existsSync()` to check file existence before reading
203
+ - Parse JSON with try/catch, return null on failure
204
+ - Config interface: `{ localSearchPaths: string[] }`
205
+
206
+ ### Tool Args Override Pattern
207
+ - Accept optional `paths` array argument
208
+ - If provided, use args.paths directly
209
+ - If not provided, fall back to config file
210
+ - If neither available, return helpful error with config file example
211
+
212
+ ### Manifest Update for Local Repos
213
+ - Local repos use `type: "local"` (vs `type: "cached"` for cloned)
214
+ - Local repos have `shallow: false` (they're full clones)
215
+ - Add to both `manifest.repos` and `manifest.localIndex`
216
+ - `localIndex` maps remote URL to local path for quick lookups
217
+
218
+ ### Repo Key Construction
219
+ - Use `matchRemoteToSpec()` to convert remote URL to "owner/repo" format
220
+ - Skip non-GitHub remotes (matchRemoteToSpec returns null)
221
+ - Construct key as `${spec}@${branch}` to match cached repo format
222
+ - Check for existing entry before adding to avoid duplicates
223
+
224
+ ### Summary Output Format
225
+ - Report total found, new registered, and already existing
226
+ - Include helpful message about repo_list when new repos added
227
+ - Handle zero repos found case with list of searched paths
228
+
229
+ ## Task 8a: repo_update Tool
230
+
231
+ ### Update vs Status Pattern
232
+ - Cached repos: fetch + reset to update to latest
233
+ - Local repos: show status only, never modify
234
+ - Differentiate by `entry.type === "local"` check
235
+
236
+ ### Git Update Commands
237
+ - `updateRepo(path, branch)` from git module handles fetch + reset
238
+ - `getRepoInfo(path)` returns commit hash for confirmation message
239
+ - Commit hash truncated to 7 chars for display: `info.commit.substring(0, 7)`
240
+
241
+ ### Git Status for Local Repos
242
+ - Use `git -C ${path} status --short` for compact status
243
+ - Empty output means working tree is clean
244
+ - Display "Working tree clean" when status is empty
245
+
246
+ ### Manifest Timestamp Updates
247
+ - Update both `lastUpdated` and `lastAccessed` on successful update
248
+ - Use `withManifestLock` for atomic manifest updates
249
+ - Re-load manifest inside lock to avoid stale data
250
+
251
+ ### Error Recovery Suggestions
252
+ - Repo not found: suggest `repo_clone`
253
+ - Update failed: suggest `repo_clone({ force: true })` to re-clone
254
+ - Corrupted repos can be fixed by force re-clone
255
+
256
+ ## Task 8b: repo_remove Tool
257
+
258
+ ### Removal Behavior by Type
259
+ - Cached repos: require confirmation, delete directory + unregister from manifest
260
+ - Local repos: unregister only, preserve files on disk
261
+ - Distinction prevents accidental deletion of user-managed repos
262
+
263
+ ### Confirmation Pattern
264
+ - Use `confirm: boolean` arg with default false
265
+ - Without confirm: return message explaining what will happen
266
+ - With confirm: proceed with deletion
267
+ - Local repos don't need confirmation since files aren't deleted
268
+
269
+ ### Manifest Cleanup for Local Repos
270
+ - Delete from `manifest.repos[repoKey]`
271
+ - Also delete from `manifest.localIndex` by finding matching path
272
+ - Iterate localIndex entries to find remote URL that maps to entry.path
273
+
274
+ ### Error Recovery on Delete Failure
275
+ - If `rm()` fails, still try to unregister from manifest
276
+ - Wrap manifest cleanup in nested try/catch to ignore cleanup errors
277
+ - Return message noting manual cleanup may be needed
278
+ - User can manually delete directory at the path shown
279
+
280
+ ### rm() Options
281
+ - `{ recursive: true, force: true }` for directory deletion
282
+ - `force: true` prevents errors if directory doesn't exist
283
+ - `recursive: true` required for non-empty directories
284
+
285
+ ## Task 9: Define repo-explorer Agent
286
+
287
+ ### AgentConfig Type Location
288
+ - Import from `@opencode-ai/sdk` not `@opencode-ai/plugin`
289
+ - Type defined in `node_modules/@opencode-ai/sdk/dist/gen/types.gen.d.ts`
290
+ - v2 types have more permissions than v1 types
291
+
292
+ ### AgentConfig Properties
293
+ - `description`: When to use this agent (shown in agent selection)
294
+ - `mode`: "subagent" | "primary" | "all" - subagent for spawned agents
295
+ - `temperature`: 0.1 for focused, deterministic exploration
296
+ - `permission`: Object with permission rules per tool category
297
+ - `prompt`: System prompt defining agent behavior
298
+
299
+ ### Permission Structure (v1 SDK)
300
+ - Available permissions: `edit`, `bash`, `webfetch`, `doom_loop`, `external_directory`
301
+ - Values: "ask" | "allow" | "deny"
302
+ - `task` and `delegate_task` permissions NOT available in v1 SDK types
303
+ - For read-only agent: `edit: "deny"` is sufficient
304
+
305
+ ### System Prompt Best Practices
306
+ - Clearly state agent capabilities
307
+ - Define exploration approach (high-level to detailed)
308
+ - Specify output format (file paths, code snippets, explanations)
309
+ - State constraints explicitly (read-only, no modifications)
310
+ - Include example questions the agent can answer
311
+
312
+ ### Agent File Location
313
+ - Created in `src/agents/` directory
314
+ - Export function pattern: `createRepoExplorerAgent(): AgentConfig`
315
+ - Function returns config object, not the agent itself
316
+
317
+ ## Task 10: repo_explore Tool and Config Handler
318
+
319
+ ### Plugin Config Handler Pattern
320
+ - Plugin can return `config` function alongside `tool` object
321
+ - Config handler receives OpenCode config object
322
+ - Register agents by merging into `config.agent`: `config.agent = { ...config.agent, "name": agentConfig }`
323
+ - Config handler runs before tools are available
324
+
325
+ ### Agent Spawning via ctx.client
326
+ - Tool execute function receives `ctx` as second parameter
327
+ - `ctx.client.session.prompt()` spawns agent sessions
328
+ - Body format: `{ agent: "agent-name", parts: [{ type: "text", text: prompt }] }`
329
+ - Agent name must match what was registered in config handler
330
+
331
+ ### Auto-Clone Pattern for repo_explore
332
+ - Check manifest for existing repo entry
333
+ - If not found, clone it first (reuse repo_clone logic inline)
334
+ - Use same CACHE_DIR and manifest update pattern
335
+ - Return helpful error if clone fails
336
+
337
+ ### Exploration Prompt Structure
338
+ - Include working directory path for agent context
339
+ - State the question clearly
340
+ - List available tools (read, glob, grep, bash)
341
+ - Provide guidance on exploration approach
342
+ - Agent's system prompt handles the rest
343
+
344
+ ### Manifest Access Tracking
345
+ - Update `lastAccessed` after successful exploration
346
+ - Use `withManifestLock` for atomic update
347
+ - Re-load manifest inside lock to avoid stale data
348
+
349
+ ## Task 11: Documentation and Final Polish
350
+
351
+ ### README Structure
352
+ - Follow opencode-tmux README pattern for consistency
353
+ - Include: Features, Installation, Quick Start, Configuration, Tools, Agent, Use Cases, Limitations, Development
354
+ - Each tool section: description, arguments, examples, returns
355
+ - Use TypeScript code blocks for examples
356
+
357
+ ### package.json Keywords
358
+ - Required keywords: opencode, opencode-plugin, repos, repository, cache, codebase
359
+ - Keywords help with npm discoverability
360
+ - Keep description concise but comprehensive
361
+
362
+ ### Plugin API Learnings
363
+ - `PluginInput` has `client` property for SDK access
364
+ - `ToolContext` has `sessionID` for current session
365
+ - `client.session.prompt()` requires `path.id` with session ID
366
+ - Response handling: check `response.error`, extract text from `response.data.parts`
367
+
368
+ ### Type Safety with SDK Response
369
+ - Parts array contains various types (text, tool, file, etc.)
370
+ - Filter by `p.type === "text"` to get text parts
371
+ - Use `"text" in p` guard for safe property access
372
+ - TypeScript type predicates need to match full Part type
373
+
374
+ ### LSP vs tsc Discrepancies
375
+ - LSP may show stale errors after file changes
376
+ - Always verify with `bunx tsc --noEmit` as source of truth
377
+ - tsc clean = code is correct, ignore stale LSP errors
378
+
379
+ ### Project Completion Summary
380
+ - 7 tools implemented: repo_clone, repo_list, repo_read, repo_scan, repo_update, repo_remove, repo_explore
381
+ - 1 custom agent: repo-explorer (read-only codebase exploration)
382
+ - 27 tests passing across 3 test files
383
+ - Config file support for local search paths
384
+ - Comprehensive README with examples for all tools