oh-my-opencode 0.3.1 → 0.3.3

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/dist/index.js CHANGED
@@ -15,7 +15,7 @@ var __require = import.meta.require;
15
15
  var oracleAgent = {
16
16
  description: "Expert AI advisor with advanced reasoning capabilities for high-quality technical guidance, code reviews, architectural advice, and strategic planning.",
17
17
  mode: "subagent",
18
- model: "openai/gpt-5.1",
18
+ model: "openai/gpt-5.2",
19
19
  temperature: 0.1,
20
20
  reasoningEffort: "medium",
21
21
  textVerbosity: "high",
@@ -69,7 +69,7 @@ IMPORTANT: Only your last message is returned to the main agent and displayed to
69
69
 
70
70
  // src/agents/librarian.ts
71
71
  var librarianAgent = {
72
- description: "Specialized codebase understanding agent for multi-repository analysis, searching remote codebases, retrieving official documentation, and finding implementation examples using GitHub CLI and Context7. MUST BE USED when users ask to look up code in remote repositories, explain library internals, or find usage examples in open source.",
72
+ description: "Specialized codebase understanding agent for multi-repository analysis, searching remote codebases, retrieving official documentation, and finding implementation examples using GitHub CLI, Context7, and Web Search. MUST BE USED when users ask to look up code in remote repositories, explain library internals, or find usage examples in open source.",
73
73
  mode: "subagent",
74
74
  model: "anthropic/claude-haiku-4-5",
75
75
  temperature: 0.1,
@@ -88,72 +88,224 @@ Your role is to provide thorough, comprehensive analysis and explanations of cod
88
88
  - Explain how features work end-to-end across multiple repositories
89
89
  - Understand code evolution through commit history
90
90
  - Create visual diagrams when helpful for understanding complex systems
91
+ - **Provide EVIDENCE with GitHub permalinks** citing specific code from the exact version being used
91
92
 
92
93
  ## CORE DIRECTIVES
93
94
 
94
95
  1. **ACCURACY OVER SPEED**: Verify information against official documentation or source code. Do not guess APIs.
95
- 2. **CITATION REQUIRED**: Every claim about code behavior must be backed by a link to a file, a line of code, or a documentation page.
96
- 3. **SOURCE OF TRUTH**:
97
- - For **How-To**: Use \`context7\` (Official Docs).
96
+ 2. **CITATION WITH PERMALINKS REQUIRED**: Every claim about code behavior must be backed by:
97
+ - **GitHub Permalink**: \`https://github.com/owner/repo/blob/<commit-sha>/path/to/file#L10-L20\`
98
+ - Line numbers for specific code sections
99
+ - The exact version/commit being referenced
100
+ 3. **EVIDENCE-BASED REASONING**: Do NOT just summarize documentation. You must:
101
+ - Show the **specific code** that implements the behavior
102
+ - Explain **WHY** it works that way by citing the actual implementation
103
+ - Provide **permalinks** so users can verify your claims
104
+ 4. **SOURCE OF TRUTH**:
105
+ - For **How-To**: Use \`context7\` (Official Docs) + verify with source code.
98
106
  - For **Real-World Usage**: Use \`gh search code\` (GitHub).
99
- - For **Internal Logic**: Use \`gh repo view\` or \`read\` (Source Code).
107
+ - For **Internal Logic**: Clone repo to \`/tmp\` and read source directly.
100
108
  - For **Change History/Intent**: Use \`git log\` or \`git blame\` (Commit History).
101
- - For **Local Codebase Context**: Use \`Explore\` agent (File patterns, code search).
109
+ - For **Local Codebase Context**: Use \`Glob\`, \`Grep\`, \`ast_grep_search\` (File patterns, code search).
110
+ - For **Latest Information**: Use \`WebSearch\` for recent updates, blog posts, discussions.
111
+
112
+ ## MANDATORY PARALLEL TOOL EXECUTION
113
+
114
+ **CRITICAL**: You MUST execute **AT LEAST 5 tool calls in parallel** whenever possible.
115
+
116
+ When starting a research task, launch ALL of these simultaneously:
117
+ 1. \`context7_resolve-library-id\` - Get library documentation ID
118
+ 2. \`gh search code\` - Search for code examples
119
+ 3. \`WebSearch\` - Find latest discussions, blog posts, updates
120
+ 4. \`gh repo clone\` to \`/tmp\` - Clone repo for deep analysis
121
+ 5. \`Glob\` / \`Grep\` - Search local codebase for related code
122
+ 6. \`lsp_goto_definition\` / \`lsp_find_references\` - Trace definitions and usages
123
+ 7. \`ast_grep_search\` - AST-aware pattern matching
124
+
125
+ **Example parallel execution**:
126
+ \`\`\`
127
+ // Launch ALL 5+ tools in a SINGLE message:
128
+ - Tool 1: context7_resolve-library-id("react-query")
129
+ - Tool 2: gh search code "useQuery" --repo tanstack/query --language typescript
130
+ - Tool 3: WebSearch("tanstack query v5 migration guide 2024")
131
+ - Tool 4: bash: git clone --depth 1 https://github.com/TanStack/query.git /tmp/tanstack-query
132
+ - Tool 5: Glob("**/*query*.ts") - Find query-related files locally
133
+ - Tool 6: gh api repos/tanstack/query/releases/latest
134
+ - Tool 7: ast_grep_search(pattern: "useQuery($$$)", lang: "typescript")
135
+ \`\`\`
136
+
137
+ **NEVER** execute tools sequentially when they can run in parallel. Sequential execution is ONLY allowed when a tool's input depends on another tool's output.
102
138
 
103
139
  ## TOOL USAGE STANDARDS
104
140
 
105
- ### 1. GitHub CLI (\`gh\`)
106
- You have full access to the GitHub CLI via the \`bash\` tool. Use it to search, view, and analyze remote repositories.
141
+ ### 1. GitHub CLI (\`gh\`) - EXTENSIVE USE REQUIRED
142
+ You have full access to the GitHub CLI via the \`bash\` tool. Use it extensively.
107
143
 
108
144
  - **Searching Code**:
109
145
  - \`gh search code "query" --language "lang"\`
110
146
  - **ALWAYS** scope searches to an organization or user if known (e.g., \`user:microsoft\`).
111
147
  - **ALWAYS** include the file extension if known (e.g., \`extension:tsx\`).
112
- - **Viewing Files**:
113
- - \`gh repo view owner/repo --content path/to/file\`
114
- - Use this to inspect library internals without cloning the entire repo.
115
- - **Searching Issues**:
116
- - \`gh search issues "error message" --state closed\`
148
+ - **Viewing Files with Permalinks**:
149
+ - \`gh api repos/owner/repo/contents/path/to/file?ref=<sha>\`
150
+ - \`gh browse owner/repo --commit <sha> -- path/to/file\`
151
+ - Use this to get exact permalinks for citation.
152
+ - **Getting Commit SHA for Permalinks**:
153
+ - \`gh api repos/owner/repo/commits/HEAD --jq '.sha'\`
154
+ - \`gh api repos/owner/repo/git/refs/tags/v1.0.0 --jq '.object.sha'\`
155
+ - **Cloning for Deep Analysis**:
156
+ - \`gh repo clone owner/repo /tmp/repo-name -- --depth 1\`
157
+ - Clone to \`/tmp\` directory for comprehensive source analysis.
158
+ - After cloning, use \`git log\`, \`git blame\`, and direct file reading.
159
+ - **Searching Issues & PRs**:
160
+ - \`gh search issues "error message" --repo owner/repo --state closed\`
161
+ - \`gh search prs "feature" --repo owner/repo --state merged\`
117
162
  - Use this for debugging and finding resolved edge cases.
163
+ - **Getting Release Information**:
164
+ - \`gh api repos/owner/repo/releases/latest\`
165
+ - \`gh release list --repo owner/repo\`
118
166
 
119
167
  ### 2. Context7 (Documentation)
120
168
  Use this for authoritative API references and framework guides.
121
169
  - **Step 1**: Call \`context7_resolve-library-id\` with the library name.
122
170
  - **Step 2**: Call \`context7_get-library-docs\` with the ID and a specific topic (e.g., "authentication", "middleware").
171
+ - **IMPORTANT**: Documentation alone is NOT sufficient. Always cross-reference with actual source code.
172
+
173
+ ### 3. WebSearch - MANDATORY FOR LATEST INFO
174
+ Use WebSearch for:
175
+ - Latest library updates and changelogs
176
+ - Migration guides and breaking changes
177
+ - Community discussions and best practices
178
+ - Blog posts explaining implementation details
179
+ - Recent bug reports and workarounds
180
+
181
+ **Example searches**:
182
+ - \`"react 19 new features 2024"\`
183
+ - \`"tanstack query v5 breaking changes"\`
184
+ - \`"next.js app router migration guide"\`
185
+
186
+ ### 4. WebFetch
187
+ Use this to read content from URLs found during your search (e.g., StackOverflow threads, blog posts, non-standard documentation sites, GitHub blob pages).
188
+
189
+ ### 5. Repository Cloning to /tmp
190
+ **CRITICAL**: For deep source analysis, ALWAYS clone repositories to \`/tmp\`:
191
+
192
+ \`\`\`bash
193
+ # Clone with minimal history for speed
194
+ gh repo clone owner/repo /tmp/repo-name -- --depth 1
195
+
196
+ # Or clone specific tag/version
197
+ gh repo clone owner/repo /tmp/repo-name -- --depth 1 --branch v1.0.0
198
+
199
+ # Then explore the cloned repo
200
+ cd /tmp/repo-name
201
+ git log --oneline -n 10
202
+ cat package.json # Check version
203
+ \`\`\`
123
204
 
124
- ### 3. WebFetch
125
- Use this to read content from URLs found during your search (e.g., StackOverflow threads, blog posts, non-standard documentation sites).
205
+ **Benefits of cloning**:
206
+ - Full file access without API rate limits
207
+ - Can use \`git blame\`, \`git log\`, \`grep\`, etc.
208
+ - Enables comprehensive code analysis
209
+ - Can check out specific versions to match user's environment
126
210
 
127
- ### 4. Git History (\`git log\`, \`git blame\`)
128
- Use this for understanding code evolution and authorial intent in local repositories.
211
+ ### 6. Git History (\`git log\`, \`git blame\`)
212
+ Use this for understanding code evolution and authorial intent.
129
213
 
130
214
  - **Viewing Change History**:
131
215
  - \`git log --oneline -n 20 -- path/to/file\`
132
216
  - Use this to understand how a file evolved and why changes were made.
133
217
  - **Line-by-Line Attribution**:
134
- - \`git blame path/to/file\`
218
+ - \`git blame -L 10,20 path/to/file\`
135
219
  - Use this to identify who wrote specific code and when.
136
220
  - **Commit Details**:
137
221
  - \`git show <commit-hash>\`
138
222
  - Use this to see full context of a specific change.
223
+ - **Getting Permalinks from Blame**:
224
+ - Use commit SHA from blame to construct GitHub permalinks.
225
+
226
+ ### 7. Local Codebase Search (Glob, Grep, Read)
227
+ Use these for searching files and patterns in the local codebase.
228
+
229
+ - **Glob**: Find files by pattern (e.g., \`**/*.tsx\`, \`src/**/auth*.ts\`)
230
+ - **Grep**: Search file contents with regex patterns
231
+ - **Read**: Read specific files when you know the path
232
+
233
+ **Parallel Search Strategy**:
234
+ \`\`\`
235
+ // Launch multiple searches in parallel:
236
+ - Tool 1: Glob("**/*auth*.ts") - Find auth-related files
237
+ - Tool 2: Grep("authentication") - Search for auth patterns
238
+ - Tool 3: ast_grep_search(pattern: "function authenticate($$$)", lang: "typescript")
239
+ \`\`\`
240
+
241
+ ### 8. LSP Tools - DEFINITIONS & REFERENCES
242
+ Use LSP for finding definitions and references - these are its unique strengths over text search.
243
+
244
+ **Primary LSP Tools**:
245
+ - \`lsp_goto_definition\`: Jump to where a symbol is **defined** (resolves imports, type aliases, etc.)
246
+ - \`lsp_goto_definition(filePath: "/tmp/repo/src/file.ts", line: 42, character: 10)\`
247
+ - \`lsp_find_references\`: Find **ALL usages** of a symbol across the entire workspace
248
+ - \`lsp_find_references(filePath: "/tmp/repo/src/file.ts", line: 42, character: 10)\`
249
+
250
+ **When to Use LSP** (vs Grep/AST-grep):
251
+ - **lsp_goto_definition**: When you need to follow an import or find the source definition
252
+ - **lsp_find_references**: When you need to understand impact of changes (who calls this function?)
253
+
254
+ **Why LSP for these**:
255
+ - Grep finds text matches but can't resolve imports or type aliases
256
+ - AST-grep finds structural patterns but can't follow cross-file references
257
+ - LSP understands the full type system and can trace through imports
258
+
259
+ **Parallel Execution**:
260
+ \`\`\`
261
+ // When tracing code flow, launch in parallel:
262
+ - Tool 1: lsp_goto_definition(filePath, line, char) - Find where it's defined
263
+ - Tool 2: lsp_find_references(filePath, line, char) - Find all usages
264
+ - Tool 3: ast_grep_search(...) - Find similar patterns
265
+ - Tool 4: Grep(...) - Text fallback
266
+ \`\`\`
267
+
268
+ ### 9. AST-grep - AST-AWARE PATTERN SEARCH
269
+ Use AST-grep for structural code search that understands syntax, not just text.
270
+
271
+ **Key Features**:
272
+ - Supports 25+ languages (typescript, javascript, python, rust, go, etc.)
273
+ - Uses meta-variables: \`$VAR\` (single node), \`$$$\` (multiple nodes)
274
+ - Patterns must be complete AST nodes (valid code)
139
275
 
140
- ### 5. Explore Agent (Subagent)
141
- Use this when searching for files, patterns, or context within the local codebase.
276
+ **ast_grep_search Examples**:
277
+ \`\`\`
278
+ // Find all console.log calls
279
+ ast_grep_search(pattern: "console.log($MSG)", lang: "typescript")
280
+
281
+ // Find all async functions
282
+ ast_grep_search(pattern: "async function $NAME($$$) { $$$ }", lang: "typescript")
283
+
284
+ // Find React useState hooks
285
+ ast_grep_search(pattern: "const [$STATE, $SETTER] = useState($$$)", lang: "tsx")
286
+
287
+ // Find Python class definitions
288
+ ast_grep_search(pattern: "class $NAME($$$)", lang: "python")
142
289
 
143
- **PRIMARY GOAL**: Each Explore agent finds **ONE specific thing** with a clear, focused objective.
290
+ // Find all export statements
291
+ ast_grep_search(pattern: "export { $$$ }", lang: "typescript")
144
292
 
145
- - **When to Use**:
146
- - Finding files by patterns (e.g., "src/**/*.tsx")
147
- - Searching code for keywords (e.g., "API endpoints")
148
- - Understanding codebase structure or architecture
149
- - **Parallel Execution Strategy**:
150
- - **ALWAYS** spawn multiple Explore agents in parallel for different search targets.
151
- - Each agent should focus on ONE specific search task.
152
- - Example: If searching for "auth logic" and "API routes", spawn TWO separate agents.
153
- - **Context Passing**:
154
- - When contextual search is needed, pass **ALL relevant context** to the agent.
155
- - Include: what you're looking for, why, and any related information that helps narrow down the search.
156
- - The agent should have enough context to find exactly what's needed without guessing.
293
+ // Find function calls with specific argument patterns
294
+ ast_grep_search(pattern: "fetch($URL, { method: $METHOD })", lang: "typescript")
295
+ \`\`\`
296
+
297
+ **When to Use AST-grep vs Grep**:
298
+ - **AST-grep**: When you need structural matching (e.g., "find all function definitions")
299
+ - **Grep**: When you need text matching (e.g., "find all occurrences of 'TODO'")
300
+
301
+ **Parallel AST-grep Execution**:
302
+ \`\`\`
303
+ // When analyzing a codebase pattern, launch in parallel:
304
+ - Tool 1: ast_grep_search(pattern: "useQuery($$$)", lang: "tsx") - Find hook usage
305
+ - Tool 2: ast_grep_search(pattern: "export function $NAME($$$)", lang: "typescript") - Find exports
306
+ - Tool 3: Grep("useQuery") - Text fallback
307
+ - Tool 4: Glob("**/*query*.ts") - Find query-related files
308
+ \`\`\`
157
309
 
158
310
  ## SEARCH STRATEGY PROTOCOL
159
311
 
@@ -163,52 +315,78 @@ When given a request, follow this **STRICT** workflow:
163
315
  - If the user references a local file, read it first to understand imports and dependencies.
164
316
  - Identify the specific library or technology version.
165
317
 
166
- 2. **SELECT SOURCE**:
167
- - **Official Docs**: For "How do I use X?" or "What are the options for Y?"
168
- - **Remote Code**: For "Show me an example of X" or "How is X implemented internally?"
169
- - **Issues/PRs**: For "Why is X failing?" or "Is this a bug?"
170
- - **Git History**: For "Why was this changed?" or "Who introduced this?" or "When was this added?"
171
- - **Explore Agent**: For "Where is X defined?" or "How does this codebase handle Y?" or "Find all files matching Z pattern"
318
+ 2. **PARALLEL INVESTIGATION** (Launch 5+ tools simultaneously):
319
+ - \`context7\`: Get official documentation
320
+ - \`gh search code\`: Find implementation examples
321
+ - \`WebSearch\`: Get latest updates and discussions
322
+ - \`gh repo clone\`: Clone to /tmp for deep analysis
323
+ - \`Glob\` / \`Grep\` / \`ast_grep_search\`: Search local codebase
324
+ - \`gh api\`: Get release/version information
172
325
 
173
- 3. **EXECUTE & REFINE**:
174
- - Run the initial search.
175
- - If results are too broad (>50), add filters (\`path:\`, \`filename:\`).
176
- - If results are zero, broaden the search (remove quotes, remove language filter).
326
+ 3. **DEEP SOURCE ANALYSIS**:
327
+ - Navigate to the cloned repo in /tmp
328
+ - Find the specific file implementing the feature
329
+ - Use \`git blame\` to understand why code is written that way
330
+ - Get the commit SHA for permalink construction
177
331
 
178
- 4. **SYNTHESIZE**:
179
- - Present the findings clearly.
332
+ 4. **SYNTHESIZE WITH EVIDENCE**:
333
+ - Present findings with **GitHub permalinks**
180
334
  - **FORMAT**:
181
- - **RESOURCE**: [Name] ([URL])
182
- - **RELEVANCE**: Why this matters.
183
- - **CONTENT**: The code snippet or documentation summary.
335
+ - **CLAIM**: What you're asserting about the code
336
+ - **EVIDENCE**: The specific code that proves it
337
+ - **PERMALINK**: \`https://github.com/owner/repo/blob/<sha>/path#L10-L20\`
338
+ - **EXPLANATION**: Why this code behaves this way
339
+
340
+ ## CITATION FORMAT - MANDATORY
341
+
342
+ Every code-related claim MUST include:
343
+
344
+ \`\`\`markdown
345
+ **Claim**: [What you're asserting]
346
+
347
+ **Evidence** ([permalink](https://github.com/owner/repo/blob/abc123/src/file.ts#L42-L50)):
348
+ \\\`\\\`\\\`typescript
349
+ // The actual code from lines 42-50
350
+ function example() {
351
+ // ...
352
+ }
353
+ \\\`\\\`\\\`
354
+
355
+ **Explanation**: This code shows that [reason] because [specific detail from the code].
356
+ \`\`\`
184
357
 
185
358
  ## FAILURE RECOVERY
186
359
 
187
- - If \`context7\` fails to find docs, use \`gh repo view\` to read the repository's \`README.md\` or \`CONTRIBUTING.md\`.
360
+ - If \`context7\` fails to find docs, clone the repo to \`/tmp\` and read the source directly.
188
361
  - If code search yields nothing, search for the *concept* rather than the specific function name.
362
+ - If GitHub API has rate limits, use cloned repos in \`/tmp\` for analysis.
189
363
  - If unsure, **STATE YOUR UNCERTAINTY** and propose a hypothesis based on standard conventions.
190
364
 
191
365
  ## VOICE AND TONE
192
366
 
193
367
  - **PROFESSIONAL**: You are an expert archivist. Be concise and precise.
194
368
  - **OBJECTIVE**: Present facts found in the search. Do not offer personal opinions unless asked.
369
+ - **EVIDENCE-DRIVEN**: Always back claims with permalinks and code snippets.
195
370
  - **HELPFUL**: If a direct answer isn't found, provide the closest relevant examples or related documentation.
196
371
 
197
372
  ## MULTI-REPOSITORY ANALYSIS GUIDELINES
198
373
 
199
- - Use available tools extensively to explore repositories
200
- - Execute tools in parallel when possible for efficiency
374
+ - Clone multiple repos to /tmp for cross-repository analysis
375
+ - Execute AT LEAST 5 tools in parallel when possible for efficiency
201
376
  - Read files thoroughly to understand implementation details
202
377
  - Search for patterns and related code across multiple repositories
203
378
  - Use commit search to understand how code evolved over time
204
379
  - Focus on thorough understanding and comprehensive explanation across repositories
205
380
  - Create mermaid diagrams to visualize complex relationships or flows
381
+ - Always provide permalinks for cross-repository references
206
382
 
207
383
  ## COMMUNICATION
208
384
 
209
385
  You must use Markdown for formatting your responses.
210
386
 
211
- IMPORTANT: When including code blocks, you MUST ALWAYS specify the language for syntax highlighting. Always add the language identifier after the opening backticks.`
387
+ IMPORTANT: When including code blocks, you MUST ALWAYS specify the language for syntax highlighting. Always add the language identifier after the opening backticks.
388
+
389
+ **REMEMBER**: Your job is not just to find and summarize documentation. You must provide **EVIDENCE** showing exactly **WHY** the code works the way it does, with **permalinks** to the specific implementation so users can verify your claims.`
212
390
  };
213
391
 
214
392
  // src/agents/explore.ts
@@ -232,6 +410,22 @@ This is a READ-ONLY exploration task. You are STRICTLY PROHIBITED from:
232
410
 
233
411
  Your role is EXCLUSIVELY to search and analyze existing code. You do NOT have access to file editing tools - attempting to edit files will fail.
234
412
 
413
+ ## MANDATORY PARALLEL TOOL EXECUTION
414
+
415
+ **CRITICAL**: You MUST execute **AT LEAST 3 tool calls in parallel** for EVERY search task.
416
+
417
+ When starting a search, launch multiple tools simultaneously:
418
+ \`\`\`
419
+ // Example: Launch 3+ tools in a SINGLE message:
420
+ - Tool 1: Glob("**/*.ts") - Find all TypeScript files
421
+ - Tool 2: Grep("functionName") - Search for specific pattern
422
+ - Tool 3: Bash: git log --oneline -n 20 - Check recent changes
423
+ - Tool 4: Bash: git branch -a - See all branches
424
+ - Tool 5: ast_grep_search(pattern: "function $NAME($$$)", lang: "typescript") - AST search
425
+ \`\`\`
426
+
427
+ **NEVER** execute tools one at a time. Sequential execution is ONLY allowed when a tool's input strictly depends on another tool's output.
428
+
235
429
  ## Before You Search
236
430
 
237
431
  Before executing any search, you MUST first analyze the request in <analysis> tags:
@@ -240,7 +434,7 @@ Before executing any search, you MUST first analyze the request in <analysis> ta
240
434
  1. **Request**: What exactly did the user ask for?
241
435
  2. **Intent**: Why are they asking this? What problem are they trying to solve?
242
436
  3. **Expected Output**: What kind of answer would be most helpful?
243
- 4. **Search Strategy**: What tools and patterns will I use to find this?
437
+ 4. **Search Strategy**: What 3+ parallel tools will I use to find this?
244
438
  </analysis>
245
439
 
246
440
  Only after completing this analysis should you proceed with the actual search.
@@ -248,12 +442,14 @@ Only after completing this analysis should you proceed with the actual search.
248
442
  ## Success Criteria
249
443
 
250
444
  Your response is successful when:
445
+ - **Parallelism**: At least 3 tools were executed in parallel
251
446
  - **Completeness**: All relevant files matching the search intent are found
252
447
  - **Accuracy**: Returned paths are absolute and files actually exist
253
448
  - **Relevance**: Results directly address the user's underlying intent, not just literal request
254
449
  - **Actionability**: Caller can proceed without follow-up questions
255
450
 
256
451
  Your response has FAILED if:
452
+ - You execute fewer than 3 tools in parallel
257
453
  - You skip the <analysis> step before searching
258
454
  - Paths are relative instead of absolute
259
455
  - Obvious matches in the codebase are missed
@@ -263,14 +459,144 @@ Your response has FAILED if:
263
459
  - Rapidly finding files using glob patterns
264
460
  - Searching code and text with powerful regex patterns
265
461
  - Reading and analyzing file contents
462
+ - **Using Git CLI extensively for repository insights**
463
+ - **Using LSP tools for semantic code analysis**
464
+ - **Using AST-grep for structural code pattern matching**
266
465
 
267
- Guidelines:
466
+ ## Git CLI - USE EXTENSIVELY
467
+
468
+ You have access to Git CLI via Bash. Use it extensively for repository analysis:
469
+
470
+ ### Git Commands for Exploration (Always run 2+ in parallel):
471
+ \`\`\`bash
472
+ # Repository structure and history
473
+ git log --oneline -n 30 # Recent commits
474
+ git log --oneline --all -n 50 # All branches recent commits
475
+ git branch -a # All branches
476
+ git tag -l # All tags
477
+ git remote -v # Remote repositories
478
+
479
+ # File history and changes
480
+ git log --oneline -n 20 -- path/to/file # File change history
481
+ git log --oneline --follow -- path/to/file # Follow renames
482
+ git blame path/to/file # Line-by-line attribution
483
+ git blame -L 10,30 path/to/file # Blame specific lines
484
+
485
+ # Searching with Git
486
+ git log --grep="keyword" --oneline # Search commit messages
487
+ git log -S "code_string" --oneline # Search code changes (pickaxe)
488
+ git log -p --all -S "function_name" -- "*.ts" # Find when code was added/removed
489
+
490
+ # Diff and comparison
491
+ git diff HEAD~5..HEAD # Recent changes
492
+ git diff main..HEAD # Changes from main
493
+ git show <commit> # Show specific commit
494
+ git show <commit>:path/to/file # Show file at commit
495
+
496
+ # Statistics
497
+ git shortlog -sn # Contributor stats
498
+ git log --stat -n 10 # Recent changes with stats
499
+ \`\`\`
500
+
501
+ ### Parallel Git Execution Examples:
502
+ \`\`\`
503
+ // For "find where authentication is implemented":
504
+ - Tool 1: Grep("authentication|auth") - Search for auth patterns
505
+ - Tool 2: Glob("**/auth/**/*.ts") - Find auth-related files
506
+ - Tool 3: Bash: git log -S "authenticate" --oneline - Find commits adding auth code
507
+ - Tool 4: Bash: git log --grep="auth" --oneline - Find auth-related commits
508
+ - Tool 5: ast_grep_search(pattern: "function authenticate($$$)", lang: "typescript")
509
+
510
+ // For "understand recent changes":
511
+ - Tool 1: Bash: git log --oneline -n 30 - Recent commits
512
+ - Tool 2: Bash: git diff HEAD~10..HEAD --stat - Changed files
513
+ - Tool 3: Bash: git branch -a - All branches
514
+ - Tool 4: Glob("**/*.ts") - Find all source files
515
+ \`\`\`
516
+
517
+ ## LSP Tools - DEFINITIONS & REFERENCES
518
+
519
+ Use LSP specifically for finding definitions and references - these are what LSP does better than text search.
520
+
521
+ **Primary LSP Tools**:
522
+ - \`lsp_goto_definition(filePath, line, character)\`: Follow imports, find where something is **defined**
523
+ - \`lsp_find_references(filePath, line, character)\`: Find **ALL usages** across the workspace
524
+
525
+ **When to Use LSP** (vs Grep/AST-grep):
526
+ - **lsp_goto_definition**: Trace imports, find source definitions
527
+ - **lsp_find_references**: Understand impact of changes, find all callers
528
+
529
+ **Example**:
530
+ \`\`\`
531
+ // When tracing code flow:
532
+ - Tool 1: lsp_goto_definition(filePath, line, char) - Where is this defined?
533
+ - Tool 2: lsp_find_references(filePath, line, char) - Who uses this?
534
+ - Tool 3: ast_grep_search(...) - Find similar patterns
535
+ \`\`\`
536
+
537
+ ## AST-grep - STRUCTURAL CODE SEARCH
538
+
539
+ Use AST-grep for syntax-aware pattern matching (better than regex for code).
540
+
541
+ **Key Syntax**:
542
+ - \`$VAR\`: Match single AST node (identifier, expression, etc.)
543
+ - \`$$$\`: Match multiple nodes (arguments, statements, etc.)
544
+
545
+ **ast_grep_search Examples**:
546
+ \`\`\`
547
+ // Find function definitions
548
+ ast_grep_search(pattern: "function $NAME($$$) { $$$ }", lang: "typescript")
549
+
550
+ // Find async functions
551
+ ast_grep_search(pattern: "async function $NAME($$$) { $$$ }", lang: "typescript")
552
+
553
+ // Find React hooks
554
+ ast_grep_search(pattern: "const [$STATE, $SETTER] = useState($$$)", lang: "tsx")
555
+
556
+ // Find class definitions
557
+ ast_grep_search(pattern: "class $NAME { $$$ }", lang: "typescript")
558
+
559
+ // Find specific method calls
560
+ ast_grep_search(pattern: "console.log($$$)", lang: "typescript")
561
+
562
+ // Find imports
563
+ ast_grep_search(pattern: "import { $$$ } from $MODULE", lang: "typescript")
564
+ \`\`\`
565
+
566
+ **When to Use**:
567
+ - **AST-grep**: Structural patterns (function defs, class methods, hook usage)
568
+ - **Grep**: Text search (comments, strings, TODOs)
569
+ - **LSP**: Symbol-based search (find by name, type info)
570
+
571
+ ## Guidelines
572
+
573
+ ### Tool Selection:
268
574
  - Use **Glob** for broad file pattern matching (e.g., \`**/*.py\`, \`src/**/*.ts\`)
269
575
  - Use **Grep** for searching file contents with regex patterns
270
576
  - Use **Read** when you know the specific file path you need to read
271
577
  - Use **List** for exploring directory structure
272
- - Use **Bash** ONLY for read-only operations (ls, git status, git log, git diff, find)
273
- - NEVER use Bash for: mkdir, touch, rm, cp, mv, git add, git commit, npm install, pip install, or any file creation/modification
578
+ - Use **Bash** for Git commands and read-only operations
579
+ - Use **ast_grep_search** for structural code patterns (functions, classes, hooks)
580
+ - Use **lsp_goto_definition** to trace imports and find source definitions
581
+ - Use **lsp_find_references** to find all usages of a symbol
582
+
583
+ ### Bash Usage:
584
+ **ALLOWED** (read-only):
585
+ - \`git log\`, \`git blame\`, \`git show\`, \`git diff\`
586
+ - \`git branch\`, \`git tag\`, \`git remote\`
587
+ - \`git log -S\`, \`git log --grep\`
588
+ - \`ls\`, \`find\` (for directory exploration)
589
+
590
+ **FORBIDDEN** (state-changing):
591
+ - \`mkdir\`, \`touch\`, \`rm\`, \`cp\`, \`mv\`
592
+ - \`git add\`, \`git commit\`, \`git push\`, \`git checkout\`
593
+ - \`npm install\`, \`pip install\`, or any installation
594
+
595
+ ### Best Practices:
596
+ - **ALWAYS launch 3+ tools in parallel** in your first search action
597
+ - Use Git history to understand code evolution
598
+ - Use \`git blame\` to understand why code is written a certain way
599
+ - Use \`git log -S\` to find when specific code was added/removed
274
600
  - Adapt your search approach based on the thoroughness level specified by the caller
275
601
  - Return file paths as absolute paths in your final response
276
602
  - For clear communication, avoid using emojis
@@ -1831,6 +2157,147 @@ ${content}`;
1831
2157
  event: eventHandler
1832
2158
  };
1833
2159
  }
2160
+ // src/hooks/directory-readme-injector/index.ts
2161
+ import { existsSync as existsSync8, readFileSync as readFileSync5 } from "fs";
2162
+ import { dirname as dirname3, join as join10, resolve as resolve2 } from "path";
2163
+
2164
+ // src/hooks/directory-readme-injector/storage.ts
2165
+ import {
2166
+ existsSync as existsSync7,
2167
+ mkdirSync as mkdirSync4,
2168
+ readFileSync as readFileSync4,
2169
+ writeFileSync as writeFileSync3,
2170
+ unlinkSync as unlinkSync4
2171
+ } from "fs";
2172
+ import { join as join9 } from "path";
2173
+
2174
+ // src/hooks/directory-readme-injector/constants.ts
2175
+ import { join as join8 } from "path";
2176
+ var OPENCODE_STORAGE3 = join8(xdgData ?? "", "opencode", "storage");
2177
+ var README_INJECTOR_STORAGE = join8(OPENCODE_STORAGE3, "directory-readme");
2178
+ var README_FILENAME = "README.md";
2179
+
2180
+ // src/hooks/directory-readme-injector/storage.ts
2181
+ function getStoragePath2(sessionID) {
2182
+ return join9(README_INJECTOR_STORAGE, `${sessionID}.json`);
2183
+ }
2184
+ function loadInjectedPaths2(sessionID) {
2185
+ const filePath = getStoragePath2(sessionID);
2186
+ if (!existsSync7(filePath))
2187
+ return new Set;
2188
+ try {
2189
+ const content = readFileSync4(filePath, "utf-8");
2190
+ const data = JSON.parse(content);
2191
+ return new Set(data.injectedPaths);
2192
+ } catch {
2193
+ return new Set;
2194
+ }
2195
+ }
2196
+ function saveInjectedPaths2(sessionID, paths) {
2197
+ if (!existsSync7(README_INJECTOR_STORAGE)) {
2198
+ mkdirSync4(README_INJECTOR_STORAGE, { recursive: true });
2199
+ }
2200
+ const data = {
2201
+ sessionID,
2202
+ injectedPaths: [...paths],
2203
+ updatedAt: Date.now()
2204
+ };
2205
+ writeFileSync3(getStoragePath2(sessionID), JSON.stringify(data, null, 2));
2206
+ }
2207
+ function clearInjectedPaths2(sessionID) {
2208
+ const filePath = getStoragePath2(sessionID);
2209
+ if (existsSync7(filePath)) {
2210
+ unlinkSync4(filePath);
2211
+ }
2212
+ }
2213
+
2214
+ // src/hooks/directory-readme-injector/index.ts
2215
+ function createDirectoryReadmeInjectorHook(ctx) {
2216
+ const sessionCaches = new Map;
2217
+ function getSessionCache(sessionID) {
2218
+ if (!sessionCaches.has(sessionID)) {
2219
+ sessionCaches.set(sessionID, loadInjectedPaths2(sessionID));
2220
+ }
2221
+ return sessionCaches.get(sessionID);
2222
+ }
2223
+ function resolveFilePath(title) {
2224
+ if (!title)
2225
+ return null;
2226
+ if (title.startsWith("/"))
2227
+ return title;
2228
+ return resolve2(ctx.directory, title);
2229
+ }
2230
+ function findReadmeMdUp(startDir) {
2231
+ const found = [];
2232
+ let current = startDir;
2233
+ while (true) {
2234
+ const readmePath = join10(current, README_FILENAME);
2235
+ if (existsSync8(readmePath)) {
2236
+ found.push(readmePath);
2237
+ }
2238
+ if (current === ctx.directory)
2239
+ break;
2240
+ const parent = dirname3(current);
2241
+ if (parent === current)
2242
+ break;
2243
+ if (!parent.startsWith(ctx.directory))
2244
+ break;
2245
+ current = parent;
2246
+ }
2247
+ return found.reverse();
2248
+ }
2249
+ const toolExecuteAfter = async (input, output) => {
2250
+ if (input.tool.toLowerCase() !== "read")
2251
+ return;
2252
+ const filePath = resolveFilePath(output.title);
2253
+ if (!filePath)
2254
+ return;
2255
+ const dir = dirname3(filePath);
2256
+ const cache = getSessionCache(input.sessionID);
2257
+ const readmePaths = findReadmeMdUp(dir);
2258
+ const toInject = [];
2259
+ for (const readmePath of readmePaths) {
2260
+ const readmeDir = dirname3(readmePath);
2261
+ if (cache.has(readmeDir))
2262
+ continue;
2263
+ try {
2264
+ const content = readFileSync5(readmePath, "utf-8");
2265
+ toInject.push({ path: readmePath, content });
2266
+ cache.add(readmeDir);
2267
+ } catch {}
2268
+ }
2269
+ if (toInject.length === 0)
2270
+ return;
2271
+ for (const { path: path2, content } of toInject) {
2272
+ output.output += `
2273
+
2274
+ [Project README: ${path2}]
2275
+ ${content}`;
2276
+ }
2277
+ saveInjectedPaths2(input.sessionID, cache);
2278
+ };
2279
+ const eventHandler = async ({ event }) => {
2280
+ const props = event.properties;
2281
+ if (event.type === "session.deleted") {
2282
+ const sessionInfo = props?.info;
2283
+ if (sessionInfo?.id) {
2284
+ sessionCaches.delete(sessionInfo.id);
2285
+ clearInjectedPaths2(sessionInfo.id);
2286
+ }
2287
+ }
2288
+ if (event.type === "session.compacted") {
2289
+ const sessionID = props?.sessionID ?? props?.info?.id;
2290
+ if (sessionID) {
2291
+ sessionCaches.delete(sessionID);
2292
+ clearInjectedPaths2(sessionID);
2293
+ }
2294
+ }
2295
+ };
2296
+ return {
2297
+ "tool.execute.after": toolExecuteAfter,
2298
+ event: eventHandler
2299
+ };
2300
+ }
1834
2301
  // src/hooks/empty-task-response-detector.ts
1835
2302
  var EMPTY_RESPONSE_WARNING = `[Task Empty Response Warning]
1836
2303
 
@@ -2233,18 +2700,39 @@ function extractPromptText(parts) {
2233
2700
  var HIGH_VARIANT_MAP = {
2234
2701
  "claude-sonnet-4-5": "claude-sonnet-4-5-high",
2235
2702
  "claude-opus-4-5": "claude-opus-4-5-high",
2703
+ "gemini-3-pro": "gemini-3-pro-high",
2704
+ "gemini-3-pro-low": "gemini-3-pro-high",
2705
+ "gpt-5": "gpt-5-high",
2706
+ "gpt-5-mini": "gpt-5-mini-high",
2707
+ "gpt-5-nano": "gpt-5-nano-high",
2708
+ "gpt-5-pro": "gpt-5-pro-high",
2709
+ "gpt-5-chat-latest": "gpt-5-chat-latest-high",
2236
2710
  "gpt-5.1": "gpt-5.1-high",
2237
- "gpt-5.1-medium": "gpt-5.1-high",
2711
+ "gpt-5.1-chat-latest": "gpt-5.1-chat-latest-high",
2238
2712
  "gpt-5.1-codex": "gpt-5.1-codex-high",
2239
- "gemini-3-pro": "gemini-3-pro-high",
2240
- "gemini-3-pro-low": "gemini-3-pro-high"
2713
+ "gpt-5.1-codex-mini": "gpt-5.1-codex-mini-high",
2714
+ "gpt-5.1-codex-max": "gpt-5.1-codex-max-high",
2715
+ "gpt-5.2": "gpt-5.2-high",
2716
+ "gpt-5.2-chat-latest": "gpt-5.2-chat-latest-high",
2717
+ "gpt-5.2-pro": "gpt-5.2-pro-high"
2241
2718
  };
2242
2719
  var ALREADY_HIGH = new Set([
2243
2720
  "claude-sonnet-4-5-high",
2244
2721
  "claude-opus-4-5-high",
2722
+ "gemini-3-pro-high",
2723
+ "gpt-5-high",
2724
+ "gpt-5-mini-high",
2725
+ "gpt-5-nano-high",
2726
+ "gpt-5-pro-high",
2727
+ "gpt-5-chat-latest-high",
2245
2728
  "gpt-5.1-high",
2729
+ "gpt-5.1-chat-latest-high",
2246
2730
  "gpt-5.1-codex-high",
2247
- "gemini-3-pro-high"
2731
+ "gpt-5.1-codex-mini-high",
2732
+ "gpt-5.1-codex-max-high",
2733
+ "gpt-5.2-high",
2734
+ "gpt-5.2-chat-latest-high",
2735
+ "gpt-5.2-pro-high"
2248
2736
  ]);
2249
2737
  function getHighVariant(modelID) {
2250
2738
  if (ALREADY_HIGH.has(modelID)) {
@@ -2306,8 +2794,8 @@ function createThinkModeHook() {
2306
2794
  }
2307
2795
  // src/hooks/claude-code-hooks/config.ts
2308
2796
  import { homedir as homedir2 } from "os";
2309
- import { join as join8 } from "path";
2310
- import { existsSync as existsSync7 } from "fs";
2797
+ import { join as join11 } from "path";
2798
+ import { existsSync as existsSync9 } from "fs";
2311
2799
  function normalizeHookMatcher(raw) {
2312
2800
  return {
2313
2801
  matcher: raw.matcher ?? raw.pattern ?? "*",
@@ -2332,11 +2820,11 @@ function normalizeHooksConfig(raw) {
2332
2820
  function getClaudeSettingsPaths(customPath) {
2333
2821
  const home = homedir2();
2334
2822
  const paths = [
2335
- join8(home, ".claude", "settings.json"),
2336
- join8(process.cwd(), ".claude", "settings.json"),
2337
- join8(process.cwd(), ".claude", "settings.local.json")
2823
+ join11(home, ".claude", "settings.json"),
2824
+ join11(process.cwd(), ".claude", "settings.json"),
2825
+ join11(process.cwd(), ".claude", "settings.local.json")
2338
2826
  ];
2339
- if (customPath && existsSync7(customPath)) {
2827
+ if (customPath && existsSync9(customPath)) {
2340
2828
  paths.unshift(customPath);
2341
2829
  }
2342
2830
  return paths;
@@ -2360,7 +2848,7 @@ async function loadClaudeHooksConfig(customSettingsPath) {
2360
2848
  const paths = getClaudeSettingsPaths(customSettingsPath);
2361
2849
  let mergedConfig = {};
2362
2850
  for (const settingsPath of paths) {
2363
- if (existsSync7(settingsPath)) {
2851
+ if (existsSync9(settingsPath)) {
2364
2852
  try {
2365
2853
  const content = await Bun.file(settingsPath).text();
2366
2854
  const settings = JSON.parse(content);
@@ -2377,9 +2865,9 @@ async function loadClaudeHooksConfig(customSettingsPath) {
2377
2865
  }
2378
2866
 
2379
2867
  // src/hooks/claude-code-hooks/config-loader.ts
2380
- import { existsSync as existsSync8 } from "fs";
2868
+ import { existsSync as existsSync10 } from "fs";
2381
2869
  import { homedir as homedir3 } from "os";
2382
- import { join as join10 } from "path";
2870
+ import { join as join13 } from "path";
2383
2871
 
2384
2872
  // src/shared/logger.ts
2385
2873
  import * as fs3 from "fs";
@@ -2396,12 +2884,12 @@ function log(message, data) {
2396
2884
  }
2397
2885
 
2398
2886
  // src/hooks/claude-code-hooks/config-loader.ts
2399
- var USER_CONFIG_PATH = join10(homedir3(), ".config", "opencode", "opencode-cc-plugin.json");
2887
+ var USER_CONFIG_PATH = join13(homedir3(), ".config", "opencode", "opencode-cc-plugin.json");
2400
2888
  function getProjectConfigPath() {
2401
- return join10(process.cwd(), ".opencode", "opencode-cc-plugin.json");
2889
+ return join13(process.cwd(), ".opencode", "opencode-cc-plugin.json");
2402
2890
  }
2403
2891
  async function loadConfigFromPath(path3) {
2404
- if (!existsSync8(path3)) {
2892
+ if (!existsSync10(path3)) {
2405
2893
  return null;
2406
2894
  }
2407
2895
  try {
@@ -2494,14 +2982,14 @@ function parseFrontmatter(content) {
2494
2982
  import { spawn as spawn3 } from "child_process";
2495
2983
  import { exec } from "child_process";
2496
2984
  import { promisify } from "util";
2497
- import { existsSync as existsSync9 } from "fs";
2985
+ import { existsSync as existsSync11 } from "fs";
2498
2986
  var DEFAULT_ZSH_PATHS = ["/bin/zsh", "/usr/bin/zsh", "/usr/local/bin/zsh"];
2499
2987
  function findZshPath(customZshPath) {
2500
- if (customZshPath && existsSync9(customZshPath)) {
2988
+ if (customZshPath && existsSync11(customZshPath)) {
2501
2989
  return customZshPath;
2502
2990
  }
2503
2991
  for (const path3 of DEFAULT_ZSH_PATHS) {
2504
- if (existsSync9(path3)) {
2992
+ if (existsSync11(path3)) {
2505
2993
  return path3;
2506
2994
  }
2507
2995
  }
@@ -2519,7 +3007,7 @@ async function executeHookCommand(command, stdin, cwd, options) {
2519
3007
  finalCommand = `${zshPath} -lc '${escapedCommand}'`;
2520
3008
  }
2521
3009
  }
2522
- return new Promise((resolve2) => {
3010
+ return new Promise((resolve3) => {
2523
3011
  const proc = spawn3(finalCommand, {
2524
3012
  cwd,
2525
3013
  shell: true,
@@ -2536,14 +3024,14 @@ async function executeHookCommand(command, stdin, cwd, options) {
2536
3024
  proc.stdin?.write(stdin);
2537
3025
  proc.stdin?.end();
2538
3026
  proc.on("close", (code) => {
2539
- resolve2({
3027
+ resolve3({
2540
3028
  exitCode: code ?? 0,
2541
3029
  stdout: stdout.trim(),
2542
3030
  stderr: stderr.trim()
2543
3031
  });
2544
3032
  });
2545
3033
  proc.on("error", (err) => {
2546
- resolve2({
3034
+ resolve3({
2547
3035
  exitCode: 1,
2548
3036
  stderr: err.message
2549
3037
  });
@@ -2619,8 +3107,8 @@ async function resolveCommandsInText(text, depth = 0, maxDepth = 3) {
2619
3107
  return resolved;
2620
3108
  }
2621
3109
  // src/shared/file-reference-resolver.ts
2622
- import { existsSync as existsSync10, readFileSync as readFileSync4, statSync } from "fs";
2623
- import { join as join11, isAbsolute } from "path";
3110
+ import { existsSync as existsSync12, readFileSync as readFileSync6, statSync } from "fs";
3111
+ import { join as join14, isAbsolute } from "path";
2624
3112
  var FILE_REFERENCE_PATTERN = /@([^\s@]+)/g;
2625
3113
  function findFileReferences(text) {
2626
3114
  const matches = [];
@@ -2640,17 +3128,17 @@ function resolveFilePath(filePath, cwd) {
2640
3128
  if (isAbsolute(filePath)) {
2641
3129
  return filePath;
2642
3130
  }
2643
- return join11(cwd, filePath);
3131
+ return join14(cwd, filePath);
2644
3132
  }
2645
3133
  function readFileContent(resolvedPath) {
2646
- if (!existsSync10(resolvedPath)) {
3134
+ if (!existsSync12(resolvedPath)) {
2647
3135
  return `[file not found: ${resolvedPath}]`;
2648
3136
  }
2649
3137
  const stat = statSync(resolvedPath);
2650
3138
  if (stat.isDirectory()) {
2651
3139
  return `[cannot read directory: ${resolvedPath}]`;
2652
3140
  }
2653
- const content = readFileSync4(resolvedPath, "utf-8");
3141
+ const content = readFileSync6(resolvedPath, "utf-8");
2654
3142
  return content;
2655
3143
  }
2656
3144
  async function resolveFileReferencesInText(text, cwd = process.cwd(), depth = 0, maxDepth = 3) {
@@ -2873,17 +3361,17 @@ async function executePreToolUseHooks(ctx, config, extendedConfig) {
2873
3361
  }
2874
3362
 
2875
3363
  // src/hooks/claude-code-hooks/transcript.ts
2876
- import { join as join12 } from "path";
2877
- import { mkdirSync as mkdirSync4, appendFileSync as appendFileSync5, existsSync as existsSync11, writeFileSync as writeFileSync3, unlinkSync as unlinkSync4 } from "fs";
3364
+ import { join as join15 } from "path";
3365
+ import { mkdirSync as mkdirSync5, appendFileSync as appendFileSync5, existsSync as existsSync13, writeFileSync as writeFileSync4, unlinkSync as unlinkSync5 } from "fs";
2878
3366
  import { homedir as homedir4, tmpdir as tmpdir2 } from "os";
2879
3367
  import { randomUUID } from "crypto";
2880
- var TRANSCRIPT_DIR = join12(homedir4(), ".claude", "transcripts");
3368
+ var TRANSCRIPT_DIR = join15(homedir4(), ".claude", "transcripts");
2881
3369
  function getTranscriptPath(sessionId) {
2882
- return join12(TRANSCRIPT_DIR, `${sessionId}.jsonl`);
3370
+ return join15(TRANSCRIPT_DIR, `${sessionId}.jsonl`);
2883
3371
  }
2884
3372
  function ensureTranscriptDir() {
2885
- if (!existsSync11(TRANSCRIPT_DIR)) {
2886
- mkdirSync4(TRANSCRIPT_DIR, { recursive: true });
3373
+ if (!existsSync13(TRANSCRIPT_DIR)) {
3374
+ mkdirSync5(TRANSCRIPT_DIR, { recursive: true });
2887
3375
  }
2888
3376
  }
2889
3377
  function appendTranscriptEntry(sessionId, entry) {
@@ -2969,8 +3457,8 @@ async function buildTranscriptFromSession(client, sessionId, directory, currentT
2969
3457
  }
2970
3458
  };
2971
3459
  entries.push(JSON.stringify(currentEntry));
2972
- const tempPath = join12(tmpdir2(), `opencode-transcript-${sessionId}-${randomUUID()}.jsonl`);
2973
- writeFileSync3(tempPath, entries.join(`
3460
+ const tempPath = join15(tmpdir2(), `opencode-transcript-${sessionId}-${randomUUID()}.jsonl`);
3461
+ writeFileSync4(tempPath, entries.join(`
2974
3462
  `) + `
2975
3463
  `);
2976
3464
  return tempPath;
@@ -2989,8 +3477,8 @@ async function buildTranscriptFromSession(client, sessionId, directory, currentT
2989
3477
  ]
2990
3478
  }
2991
3479
  };
2992
- const tempPath = join12(tmpdir2(), `opencode-transcript-${sessionId}-${randomUUID()}.jsonl`);
2993
- writeFileSync3(tempPath, JSON.stringify(currentEntry) + `
3480
+ const tempPath = join15(tmpdir2(), `opencode-transcript-${sessionId}-${randomUUID()}.jsonl`);
3481
+ writeFileSync4(tempPath, JSON.stringify(currentEntry) + `
2994
3482
  `);
2995
3483
  return tempPath;
2996
3484
  } catch {
@@ -3002,7 +3490,7 @@ function deleteTempTranscript(path3) {
3002
3490
  if (!path3)
3003
3491
  return;
3004
3492
  try {
3005
- unlinkSync4(path3);
3493
+ unlinkSync5(path3);
3006
3494
  } catch {}
3007
3495
  }
3008
3496
 
@@ -3201,11 +3689,11 @@ ${USER_PROMPT_SUBMIT_TAG_CLOSE}`);
3201
3689
  }
3202
3690
 
3203
3691
  // src/hooks/claude-code-hooks/todo.ts
3204
- import { join as join13 } from "path";
3692
+ import { join as join16 } from "path";
3205
3693
  import { homedir as homedir5 } from "os";
3206
- var TODO_DIR = join13(homedir5(), ".claude", "todos");
3694
+ var TODO_DIR = join16(homedir5(), ".claude", "todos");
3207
3695
  function getTodoPath(sessionId) {
3208
- return join13(TODO_DIR, `${sessionId}-agent-${sessionId}.json`);
3696
+ return join16(TODO_DIR, `${sessionId}-agent-${sessionId}.json`);
3209
3697
  }
3210
3698
 
3211
3699
  // src/hooks/claude-code-hooks/stop.ts
@@ -3297,16 +3785,16 @@ setInterval(() => {
3297
3785
  }, CACHE_TTL);
3298
3786
 
3299
3787
  // src/features/hook-message-injector/injector.ts
3300
- import { existsSync as existsSync12, mkdirSync as mkdirSync5, readFileSync as readFileSync5, readdirSync as readdirSync2, writeFileSync as writeFileSync4 } from "fs";
3301
- import { join as join15 } from "path";
3788
+ import { existsSync as existsSync14, mkdirSync as mkdirSync6, readFileSync as readFileSync7, readdirSync as readdirSync2, writeFileSync as writeFileSync5 } from "fs";
3789
+ import { join as join18 } from "path";
3302
3790
 
3303
3791
  // src/features/hook-message-injector/constants.ts
3304
- import { join as join14 } from "path";
3792
+ import { join as join17 } from "path";
3305
3793
  import { homedir as homedir6 } from "os";
3306
- var xdgData2 = process.env.XDG_DATA_HOME || join14(homedir6(), ".local", "share");
3307
- var OPENCODE_STORAGE3 = join14(xdgData2, "opencode", "storage");
3308
- var MESSAGE_STORAGE2 = join14(OPENCODE_STORAGE3, "message");
3309
- var PART_STORAGE2 = join14(OPENCODE_STORAGE3, "part");
3794
+ var xdgData2 = process.env.XDG_DATA_HOME || join17(homedir6(), ".local", "share");
3795
+ var OPENCODE_STORAGE4 = join17(xdgData2, "opencode", "storage");
3796
+ var MESSAGE_STORAGE2 = join17(OPENCODE_STORAGE4, "message");
3797
+ var PART_STORAGE2 = join17(OPENCODE_STORAGE4, "part");
3310
3798
 
3311
3799
  // src/features/hook-message-injector/injector.ts
3312
3800
  function findNearestMessageWithFields(messageDir) {
@@ -3314,7 +3802,7 @@ function findNearestMessageWithFields(messageDir) {
3314
3802
  const files = readdirSync2(messageDir).filter((f) => f.endsWith(".json")).sort().reverse();
3315
3803
  for (const file of files) {
3316
3804
  try {
3317
- const content = readFileSync5(join15(messageDir, file), "utf-8");
3805
+ const content = readFileSync7(join18(messageDir, file), "utf-8");
3318
3806
  const msg = JSON.parse(content);
3319
3807
  if (msg.agent && msg.model?.providerID && msg.model?.modelID) {
3320
3808
  return msg;
@@ -3339,20 +3827,20 @@ function generatePartId2() {
3339
3827
  return `prt_${timestamp}${random}`;
3340
3828
  }
3341
3829
  function getOrCreateMessageDir(sessionID) {
3342
- if (!existsSync12(MESSAGE_STORAGE2)) {
3343
- mkdirSync5(MESSAGE_STORAGE2, { recursive: true });
3830
+ if (!existsSync14(MESSAGE_STORAGE2)) {
3831
+ mkdirSync6(MESSAGE_STORAGE2, { recursive: true });
3344
3832
  }
3345
- const directPath = join15(MESSAGE_STORAGE2, sessionID);
3346
- if (existsSync12(directPath)) {
3833
+ const directPath = join18(MESSAGE_STORAGE2, sessionID);
3834
+ if (existsSync14(directPath)) {
3347
3835
  return directPath;
3348
3836
  }
3349
3837
  for (const dir of readdirSync2(MESSAGE_STORAGE2)) {
3350
- const sessionPath = join15(MESSAGE_STORAGE2, dir, sessionID);
3351
- if (existsSync12(sessionPath)) {
3838
+ const sessionPath = join18(MESSAGE_STORAGE2, dir, sessionID);
3839
+ if (existsSync14(sessionPath)) {
3352
3840
  return sessionPath;
3353
3841
  }
3354
3842
  }
3355
- mkdirSync5(directPath, { recursive: true });
3843
+ mkdirSync6(directPath, { recursive: true });
3356
3844
  return directPath;
3357
3845
  }
3358
3846
  function injectHookMessage(sessionID, hookContent, originalMessage) {
@@ -3393,12 +3881,12 @@ function injectHookMessage(sessionID, hookContent, originalMessage) {
3393
3881
  sessionID
3394
3882
  };
3395
3883
  try {
3396
- writeFileSync4(join15(messageDir, `${messageID}.json`), JSON.stringify(messageMeta, null, 2));
3397
- const partDir = join15(PART_STORAGE2, messageID);
3398
- if (!existsSync12(partDir)) {
3399
- mkdirSync5(partDir, { recursive: true });
3884
+ writeFileSync5(join18(messageDir, `${messageID}.json`), JSON.stringify(messageMeta, null, 2));
3885
+ const partDir = join18(PART_STORAGE2, messageID);
3886
+ if (!existsSync14(partDir)) {
3887
+ mkdirSync6(partDir, { recursive: true });
3400
3888
  }
3401
- writeFileSync4(join15(partDir, `${partID}.json`), JSON.stringify(textPart, null, 2));
3889
+ writeFileSync5(join18(partDir, `${partID}.json`), JSON.stringify(textPart, null, 2));
3402
3890
  return true;
3403
3891
  } catch {
3404
3892
  return false;
@@ -3644,14 +4132,14 @@ ${result.message}`;
3644
4132
  };
3645
4133
  }
3646
4134
  // src/features/claude-code-command-loader/loader.ts
3647
- import { existsSync as existsSync13, readdirSync as readdirSync3, readFileSync as readFileSync6 } from "fs";
4135
+ import { existsSync as existsSync15, readdirSync as readdirSync3, readFileSync as readFileSync8 } from "fs";
3648
4136
  import { homedir as homedir7 } from "os";
3649
- import { join as join16, basename } from "path";
4137
+ import { join as join19, basename } from "path";
3650
4138
  function isMarkdownFile(entry) {
3651
4139
  return !entry.name.startsWith(".") && entry.name.endsWith(".md") && entry.isFile();
3652
4140
  }
3653
4141
  function loadCommandsFromDir(commandsDir, scope) {
3654
- if (!existsSync13(commandsDir)) {
4142
+ if (!existsSync15(commandsDir)) {
3655
4143
  return [];
3656
4144
  }
3657
4145
  const entries = readdirSync3(commandsDir, { withFileTypes: true });
@@ -3659,10 +4147,10 @@ function loadCommandsFromDir(commandsDir, scope) {
3659
4147
  for (const entry of entries) {
3660
4148
  if (!isMarkdownFile(entry))
3661
4149
  continue;
3662
- const commandPath = join16(commandsDir, entry.name);
4150
+ const commandPath = join19(commandsDir, entry.name);
3663
4151
  const commandName = basename(entry.name, ".md");
3664
4152
  try {
3665
- const content = readFileSync6(commandPath, "utf-8");
4153
+ const content = readFileSync8(commandPath, "utf-8");
3666
4154
  const { data, body } = parseFrontmatter(content);
3667
4155
  const wrappedTemplate = `<command-instruction>
3668
4156
  ${body.trim()}
@@ -3701,31 +4189,31 @@ function commandsToRecord(commands) {
3701
4189
  return result;
3702
4190
  }
3703
4191
  function loadUserCommands() {
3704
- const userCommandsDir = join16(homedir7(), ".claude", "commands");
4192
+ const userCommandsDir = join19(homedir7(), ".claude", "commands");
3705
4193
  const commands = loadCommandsFromDir(userCommandsDir, "user");
3706
4194
  return commandsToRecord(commands);
3707
4195
  }
3708
4196
  function loadProjectCommands() {
3709
- const projectCommandsDir = join16(process.cwd(), ".claude", "commands");
4197
+ const projectCommandsDir = join19(process.cwd(), ".claude", "commands");
3710
4198
  const commands = loadCommandsFromDir(projectCommandsDir, "project");
3711
4199
  return commandsToRecord(commands);
3712
4200
  }
3713
4201
  function loadOpencodeGlobalCommands() {
3714
- const opencodeCommandsDir = join16(homedir7(), ".config", "opencode", "command");
4202
+ const opencodeCommandsDir = join19(homedir7(), ".config", "opencode", "command");
3715
4203
  const commands = loadCommandsFromDir(opencodeCommandsDir, "opencode");
3716
4204
  return commandsToRecord(commands);
3717
4205
  }
3718
4206
  function loadOpencodeProjectCommands() {
3719
- const opencodeProjectDir = join16(process.cwd(), ".opencode", "command");
4207
+ const opencodeProjectDir = join19(process.cwd(), ".opencode", "command");
3720
4208
  const commands = loadCommandsFromDir(opencodeProjectDir, "opencode-project");
3721
4209
  return commandsToRecord(commands);
3722
4210
  }
3723
4211
  // src/features/claude-code-skill-loader/loader.ts
3724
- import { existsSync as existsSync14, readdirSync as readdirSync4, readFileSync as readFileSync7, statSync as statSync2, readlinkSync } from "fs";
4212
+ import { existsSync as existsSync16, readdirSync as readdirSync4, readFileSync as readFileSync9, statSync as statSync2, readlinkSync } from "fs";
3725
4213
  import { homedir as homedir8 } from "os";
3726
- import { join as join17, resolve as resolve2 } from "path";
4214
+ import { join as join20, resolve as resolve3 } from "path";
3727
4215
  function loadSkillsFromDir(skillsDir, scope) {
3728
- if (!existsSync14(skillsDir)) {
4216
+ if (!existsSync16(skillsDir)) {
3729
4217
  return [];
3730
4218
  }
3731
4219
  const entries = readdirSync4(skillsDir, { withFileTypes: true });
@@ -3733,18 +4221,18 @@ function loadSkillsFromDir(skillsDir, scope) {
3733
4221
  for (const entry of entries) {
3734
4222
  if (entry.name.startsWith("."))
3735
4223
  continue;
3736
- const skillPath = join17(skillsDir, entry.name);
4224
+ const skillPath = join20(skillsDir, entry.name);
3737
4225
  if (!entry.isDirectory() && !entry.isSymbolicLink())
3738
4226
  continue;
3739
4227
  let resolvedPath = skillPath;
3740
4228
  if (statSync2(skillPath, { throwIfNoEntry: false })?.isSymbolicLink()) {
3741
- resolvedPath = resolve2(skillPath, "..", readlinkSync(skillPath));
4229
+ resolvedPath = resolve3(skillPath, "..", readlinkSync(skillPath));
3742
4230
  }
3743
- const skillMdPath = join17(resolvedPath, "SKILL.md");
3744
- if (!existsSync14(skillMdPath))
4231
+ const skillMdPath = join20(resolvedPath, "SKILL.md");
4232
+ if (!existsSync16(skillMdPath))
3745
4233
  continue;
3746
4234
  try {
3747
- const content = readFileSync7(skillMdPath, "utf-8");
4235
+ const content = readFileSync9(skillMdPath, "utf-8");
3748
4236
  const { data, body } = parseFrontmatter(content);
3749
4237
  const skillName = data.name || entry.name;
3750
4238
  const originalDescription = data.description || "";
@@ -3775,7 +4263,7 @@ $ARGUMENTS
3775
4263
  return skills;
3776
4264
  }
3777
4265
  function loadUserSkillsAsCommands() {
3778
- const userSkillsDir = join17(homedir8(), ".claude", "skills");
4266
+ const userSkillsDir = join20(homedir8(), ".claude", "skills");
3779
4267
  const skills = loadSkillsFromDir(userSkillsDir, "user");
3780
4268
  return skills.reduce((acc, skill) => {
3781
4269
  acc[skill.name] = skill.definition;
@@ -3783,7 +4271,7 @@ function loadUserSkillsAsCommands() {
3783
4271
  }, {});
3784
4272
  }
3785
4273
  function loadProjectSkillsAsCommands() {
3786
- const projectSkillsDir = join17(process.cwd(), ".claude", "skills");
4274
+ const projectSkillsDir = join20(process.cwd(), ".claude", "skills");
3787
4275
  const skills = loadSkillsFromDir(projectSkillsDir, "project");
3788
4276
  return skills.reduce((acc, skill) => {
3789
4277
  acc[skill.name] = skill.definition;
@@ -3791,9 +4279,9 @@ function loadProjectSkillsAsCommands() {
3791
4279
  }, {});
3792
4280
  }
3793
4281
  // src/features/claude-code-agent-loader/loader.ts
3794
- import { existsSync as existsSync15, readdirSync as readdirSync5, readFileSync as readFileSync8 } from "fs";
4282
+ import { existsSync as existsSync17, readdirSync as readdirSync5, readFileSync as readFileSync10 } from "fs";
3795
4283
  import { homedir as homedir9 } from "os";
3796
- import { join as join18, basename as basename2 } from "path";
4284
+ import { join as join21, basename as basename2 } from "path";
3797
4285
  function parseToolsConfig(toolsStr) {
3798
4286
  if (!toolsStr)
3799
4287
  return;
@@ -3810,7 +4298,7 @@ function isMarkdownFile2(entry) {
3810
4298
  return !entry.name.startsWith(".") && entry.name.endsWith(".md") && entry.isFile();
3811
4299
  }
3812
4300
  function loadAgentsFromDir(agentsDir, scope) {
3813
- if (!existsSync15(agentsDir)) {
4301
+ if (!existsSync17(agentsDir)) {
3814
4302
  return [];
3815
4303
  }
3816
4304
  const entries = readdirSync5(agentsDir, { withFileTypes: true });
@@ -3818,10 +4306,10 @@ function loadAgentsFromDir(agentsDir, scope) {
3818
4306
  for (const entry of entries) {
3819
4307
  if (!isMarkdownFile2(entry))
3820
4308
  continue;
3821
- const agentPath = join18(agentsDir, entry.name);
4309
+ const agentPath = join21(agentsDir, entry.name);
3822
4310
  const agentName = basename2(entry.name, ".md");
3823
4311
  try {
3824
- const content = readFileSync8(agentPath, "utf-8");
4312
+ const content = readFileSync10(agentPath, "utf-8");
3825
4313
  const { data, body } = parseFrontmatter(content);
3826
4314
  const name = data.name || agentName;
3827
4315
  const originalDescription = data.description || "";
@@ -3848,7 +4336,7 @@ function loadAgentsFromDir(agentsDir, scope) {
3848
4336
  return agents;
3849
4337
  }
3850
4338
  function loadUserAgents() {
3851
- const userAgentsDir = join18(homedir9(), ".claude", "agents");
4339
+ const userAgentsDir = join21(homedir9(), ".claude", "agents");
3852
4340
  const agents = loadAgentsFromDir(userAgentsDir, "user");
3853
4341
  const result = {};
3854
4342
  for (const agent of agents) {
@@ -3857,7 +4345,7 @@ function loadUserAgents() {
3857
4345
  return result;
3858
4346
  }
3859
4347
  function loadProjectAgents() {
3860
- const projectAgentsDir = join18(process.cwd(), ".claude", "agents");
4348
+ const projectAgentsDir = join21(process.cwd(), ".claude", "agents");
3861
4349
  const agents = loadAgentsFromDir(projectAgentsDir, "project");
3862
4350
  const result = {};
3863
4351
  for (const agent of agents) {
@@ -3866,9 +4354,9 @@ function loadProjectAgents() {
3866
4354
  return result;
3867
4355
  }
3868
4356
  // src/features/claude-code-mcp-loader/loader.ts
3869
- import { existsSync as existsSync16 } from "fs";
4357
+ import { existsSync as existsSync18 } from "fs";
3870
4358
  import { homedir as homedir10 } from "os";
3871
- import { join as join19 } from "path";
4359
+ import { join as join22 } from "path";
3872
4360
 
3873
4361
  // src/features/claude-code-mcp-loader/env-expander.ts
3874
4362
  function expandEnvVars(value) {
@@ -3937,13 +4425,13 @@ function getMcpConfigPaths() {
3937
4425
  const home = homedir10();
3938
4426
  const cwd = process.cwd();
3939
4427
  return [
3940
- { path: join19(home, ".claude", ".mcp.json"), scope: "user" },
3941
- { path: join19(cwd, ".mcp.json"), scope: "project" },
3942
- { path: join19(cwd, ".claude", ".mcp.json"), scope: "local" }
4428
+ { path: join22(home, ".claude", ".mcp.json"), scope: "user" },
4429
+ { path: join22(cwd, ".mcp.json"), scope: "project" },
4430
+ { path: join22(cwd, ".claude", ".mcp.json"), scope: "local" }
3943
4431
  ];
3944
4432
  }
3945
4433
  async function loadMcpConfigFile(filePath) {
3946
- if (!existsSync16(filePath)) {
4434
+ if (!existsSync18(filePath)) {
3947
4435
  return null;
3948
4436
  }
3949
4437
  try {
@@ -4229,14 +4717,14 @@ var EXT_TO_LANG = {
4229
4717
  ".tfvars": "terraform"
4230
4718
  };
4231
4719
  // src/tools/lsp/config.ts
4232
- import { existsSync as existsSync17, readFileSync as readFileSync9 } from "fs";
4233
- import { join as join20 } from "path";
4720
+ import { existsSync as existsSync19, readFileSync as readFileSync11 } from "fs";
4721
+ import { join as join23 } from "path";
4234
4722
  import { homedir as homedir11 } from "os";
4235
4723
  function loadJsonFile(path3) {
4236
- if (!existsSync17(path3))
4724
+ if (!existsSync19(path3))
4237
4725
  return null;
4238
4726
  try {
4239
- return JSON.parse(readFileSync9(path3, "utf-8"));
4727
+ return JSON.parse(readFileSync11(path3, "utf-8"));
4240
4728
  } catch {
4241
4729
  return null;
4242
4730
  }
@@ -4244,9 +4732,9 @@ function loadJsonFile(path3) {
4244
4732
  function getConfigPaths() {
4245
4733
  const cwd = process.cwd();
4246
4734
  return {
4247
- project: join20(cwd, ".opencode", "oh-my-opencode.json"),
4248
- user: join20(homedir11(), ".config", "opencode", "oh-my-opencode.json"),
4249
- opencode: join20(homedir11(), ".config", "opencode", "opencode.json")
4735
+ project: join23(cwd, ".opencode", "oh-my-opencode.json"),
4736
+ user: join23(homedir11(), ".config", "opencode", "oh-my-opencode.json"),
4737
+ opencode: join23(homedir11(), ".config", "opencode", "opencode.json")
4250
4738
  };
4251
4739
  }
4252
4740
  function loadAllConfigs() {
@@ -4339,7 +4827,7 @@ function isServerInstalled(command) {
4339
4827
  const pathEnv = process.env.PATH || "";
4340
4828
  const paths = pathEnv.split(":");
4341
4829
  for (const p of paths) {
4342
- if (existsSync17(join20(p, cmd))) {
4830
+ if (existsSync19(join23(p, cmd))) {
4343
4831
  return true;
4344
4832
  }
4345
4833
  }
@@ -4389,8 +4877,8 @@ function getAllServers() {
4389
4877
  }
4390
4878
  // src/tools/lsp/client.ts
4391
4879
  var {spawn: spawn4 } = globalThis.Bun;
4392
- import { readFileSync as readFileSync10 } from "fs";
4393
- import { extname, resolve as resolve3 } from "path";
4880
+ import { readFileSync as readFileSync12 } from "fs";
4881
+ import { extname, resolve as resolve4 } from "path";
4394
4882
  class LSPServerManager {
4395
4883
  static instance;
4396
4884
  clients = new Map;
@@ -4540,7 +5028,7 @@ class LSPClient {
4540
5028
  }
4541
5029
  this.startReading();
4542
5030
  this.startStderrReading();
4543
- await new Promise((resolve4) => setTimeout(resolve4, 100));
5031
+ await new Promise((resolve5) => setTimeout(resolve5, 100));
4544
5032
  if (this.proc.exitCode !== null) {
4545
5033
  const stderr = this.stderrBuffer.join(`
4546
5034
  `);
@@ -4677,8 +5165,8 @@ stderr: ${stderr}` : ""));
4677
5165
  \r
4678
5166
  `;
4679
5167
  this.proc.stdin.write(header + msg);
4680
- return new Promise((resolve4, reject) => {
4681
- this.pending.set(id, { resolve: resolve4, reject });
5168
+ return new Promise((resolve5, reject) => {
5169
+ this.pending.set(id, { resolve: resolve5, reject });
4682
5170
  setTimeout(() => {
4683
5171
  if (this.pending.has(id)) {
4684
5172
  this.pending.delete(id);
@@ -4786,10 +5274,10 @@ ${msg}`);
4786
5274
  await new Promise((r) => setTimeout(r, 300));
4787
5275
  }
4788
5276
  async openFile(filePath) {
4789
- const absPath = resolve3(filePath);
5277
+ const absPath = resolve4(filePath);
4790
5278
  if (this.openedFiles.has(absPath))
4791
5279
  return;
4792
- const text = readFileSync10(absPath, "utf-8");
5280
+ const text = readFileSync12(absPath, "utf-8");
4793
5281
  const ext = extname(absPath);
4794
5282
  const languageId = getLanguageId(ext);
4795
5283
  this.notify("textDocument/didOpen", {
@@ -4804,7 +5292,7 @@ ${msg}`);
4804
5292
  await new Promise((r) => setTimeout(r, 1000));
4805
5293
  }
4806
5294
  async hover(filePath, line, character) {
4807
- const absPath = resolve3(filePath);
5295
+ const absPath = resolve4(filePath);
4808
5296
  await this.openFile(absPath);
4809
5297
  return this.send("textDocument/hover", {
4810
5298
  textDocument: { uri: `file://${absPath}` },
@@ -4812,7 +5300,7 @@ ${msg}`);
4812
5300
  });
4813
5301
  }
4814
5302
  async definition(filePath, line, character) {
4815
- const absPath = resolve3(filePath);
5303
+ const absPath = resolve4(filePath);
4816
5304
  await this.openFile(absPath);
4817
5305
  return this.send("textDocument/definition", {
4818
5306
  textDocument: { uri: `file://${absPath}` },
@@ -4820,7 +5308,7 @@ ${msg}`);
4820
5308
  });
4821
5309
  }
4822
5310
  async references(filePath, line, character, includeDeclaration = true) {
4823
- const absPath = resolve3(filePath);
5311
+ const absPath = resolve4(filePath);
4824
5312
  await this.openFile(absPath);
4825
5313
  return this.send("textDocument/references", {
4826
5314
  textDocument: { uri: `file://${absPath}` },
@@ -4829,7 +5317,7 @@ ${msg}`);
4829
5317
  });
4830
5318
  }
4831
5319
  async documentSymbols(filePath) {
4832
- const absPath = resolve3(filePath);
5320
+ const absPath = resolve4(filePath);
4833
5321
  await this.openFile(absPath);
4834
5322
  return this.send("textDocument/documentSymbol", {
4835
5323
  textDocument: { uri: `file://${absPath}` }
@@ -4839,7 +5327,7 @@ ${msg}`);
4839
5327
  return this.send("workspace/symbol", { query });
4840
5328
  }
4841
5329
  async diagnostics(filePath) {
4842
- const absPath = resolve3(filePath);
5330
+ const absPath = resolve4(filePath);
4843
5331
  const uri = `file://${absPath}`;
4844
5332
  await this.openFile(absPath);
4845
5333
  await new Promise((r) => setTimeout(r, 500));
@@ -4854,7 +5342,7 @@ ${msg}`);
4854
5342
  return { items: this.diagnosticsStore.get(uri) ?? [] };
4855
5343
  }
4856
5344
  async prepareRename(filePath, line, character) {
4857
- const absPath = resolve3(filePath);
5345
+ const absPath = resolve4(filePath);
4858
5346
  await this.openFile(absPath);
4859
5347
  return this.send("textDocument/prepareRename", {
4860
5348
  textDocument: { uri: `file://${absPath}` },
@@ -4862,7 +5350,7 @@ ${msg}`);
4862
5350
  });
4863
5351
  }
4864
5352
  async rename(filePath, line, character, newName) {
4865
- const absPath = resolve3(filePath);
5353
+ const absPath = resolve4(filePath);
4866
5354
  await this.openFile(absPath);
4867
5355
  return this.send("textDocument/rename", {
4868
5356
  textDocument: { uri: `file://${absPath}` },
@@ -4871,7 +5359,7 @@ ${msg}`);
4871
5359
  });
4872
5360
  }
4873
5361
  async codeAction(filePath, startLine, startChar, endLine, endChar, only) {
4874
- const absPath = resolve3(filePath);
5362
+ const absPath = resolve4(filePath);
4875
5363
  await this.openFile(absPath);
4876
5364
  return this.send("textDocument/codeAction", {
4877
5365
  textDocument: { uri: `file://${absPath}` },
@@ -4903,26 +5391,26 @@ ${msg}`);
4903
5391
  }
4904
5392
  }
4905
5393
  // src/tools/lsp/utils.ts
4906
- import { extname as extname2, resolve as resolve4 } from "path";
4907
- import { existsSync as existsSync18, readFileSync as readFileSync11, writeFileSync as writeFileSync5 } from "fs";
5394
+ import { extname as extname2, resolve as resolve5 } from "path";
5395
+ import { existsSync as existsSync20, readFileSync as readFileSync13, writeFileSync as writeFileSync6 } from "fs";
4908
5396
  function findWorkspaceRoot(filePath) {
4909
- let dir = resolve4(filePath);
4910
- if (!existsSync18(dir) || !__require("fs").statSync(dir).isDirectory()) {
5397
+ let dir = resolve5(filePath);
5398
+ if (!existsSync20(dir) || !__require("fs").statSync(dir).isDirectory()) {
4911
5399
  dir = __require("path").dirname(dir);
4912
5400
  }
4913
5401
  const markers = [".git", "package.json", "pyproject.toml", "Cargo.toml", "go.mod", "pom.xml", "build.gradle"];
4914
5402
  while (dir !== "/") {
4915
5403
  for (const marker of markers) {
4916
- if (existsSync18(__require("path").join(dir, marker))) {
5404
+ if (existsSync20(__require("path").join(dir, marker))) {
4917
5405
  return dir;
4918
5406
  }
4919
5407
  }
4920
5408
  dir = __require("path").dirname(dir);
4921
5409
  }
4922
- return __require("path").dirname(resolve4(filePath));
5410
+ return __require("path").dirname(resolve5(filePath));
4923
5411
  }
4924
5412
  async function withLspClient(filePath, fn) {
4925
- const absPath = resolve4(filePath);
5413
+ const absPath = resolve5(filePath);
4926
5414
  const ext = extname2(absPath);
4927
5415
  const server = findServerForExtension(ext);
4928
5416
  if (!server) {
@@ -5071,7 +5559,7 @@ function formatCodeActions(actions) {
5071
5559
  }
5072
5560
  function applyTextEditsToFile(filePath, edits) {
5073
5561
  try {
5074
- let content = readFileSync11(filePath, "utf-8");
5562
+ let content = readFileSync13(filePath, "utf-8");
5075
5563
  const lines = content.split(`
5076
5564
  `);
5077
5565
  const sortedEdits = [...edits].sort((a, b) => {
@@ -5096,7 +5584,7 @@ function applyTextEditsToFile(filePath, edits) {
5096
5584
  `));
5097
5585
  }
5098
5586
  }
5099
- writeFileSync5(filePath, lines.join(`
5587
+ writeFileSync6(filePath, lines.join(`
5100
5588
  `), "utf-8");
5101
5589
  return { success: true, editCount: edits.length };
5102
5590
  } catch (err) {
@@ -5127,7 +5615,7 @@ function applyWorkspaceEdit(edit) {
5127
5615
  if (change.kind === "create") {
5128
5616
  try {
5129
5617
  const filePath = change.uri.replace("file://", "");
5130
- writeFileSync5(filePath, "", "utf-8");
5618
+ writeFileSync6(filePath, "", "utf-8");
5131
5619
  result.filesModified.push(filePath);
5132
5620
  } catch (err) {
5133
5621
  result.success = false;
@@ -5137,8 +5625,8 @@ function applyWorkspaceEdit(edit) {
5137
5625
  try {
5138
5626
  const oldPath = change.oldUri.replace("file://", "");
5139
5627
  const newPath = change.newUri.replace("file://", "");
5140
- const content = readFileSync11(oldPath, "utf-8");
5141
- writeFileSync5(newPath, content, "utf-8");
5628
+ const content = readFileSync13(oldPath, "utf-8");
5629
+ writeFileSync6(newPath, content, "utf-8");
5142
5630
  __require("fs").unlinkSync(oldPath);
5143
5631
  result.filesModified.push(newPath);
5144
5632
  } catch (err) {
@@ -17838,13 +18326,13 @@ var lsp_code_action_resolve = tool({
17838
18326
  });
17839
18327
  // src/tools/ast-grep/constants.ts
17840
18328
  import { createRequire as createRequire4 } from "module";
17841
- import { dirname as dirname3, join as join22 } from "path";
17842
- import { existsSync as existsSync20, statSync as statSync3 } from "fs";
18329
+ import { dirname as dirname4, join as join25 } from "path";
18330
+ import { existsSync as existsSync22, statSync as statSync3 } from "fs";
17843
18331
 
17844
18332
  // src/tools/ast-grep/downloader.ts
17845
18333
  var {spawn: spawn5 } = globalThis.Bun;
17846
- import { existsSync as existsSync19, mkdirSync as mkdirSync6, chmodSync as chmodSync2, unlinkSync as unlinkSync5 } from "fs";
17847
- import { join as join21 } from "path";
18334
+ import { existsSync as existsSync21, mkdirSync as mkdirSync7, chmodSync as chmodSync2, unlinkSync as unlinkSync6 } from "fs";
18335
+ import { join as join24 } from "path";
17848
18336
  import { homedir as homedir12 } from "os";
17849
18337
  import { createRequire as createRequire3 } from "module";
17850
18338
  var REPO2 = "ast-grep/ast-grep";
@@ -17870,19 +18358,19 @@ var PLATFORM_MAP2 = {
17870
18358
  function getCacheDir2() {
17871
18359
  if (process.platform === "win32") {
17872
18360
  const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA;
17873
- const base2 = localAppData || join21(homedir12(), "AppData", "Local");
17874
- return join21(base2, "oh-my-opencode", "bin");
18361
+ const base2 = localAppData || join24(homedir12(), "AppData", "Local");
18362
+ return join24(base2, "oh-my-opencode", "bin");
17875
18363
  }
17876
18364
  const xdgCache2 = process.env.XDG_CACHE_HOME;
17877
- const base = xdgCache2 || join21(homedir12(), ".cache");
17878
- return join21(base, "oh-my-opencode", "bin");
18365
+ const base = xdgCache2 || join24(homedir12(), ".cache");
18366
+ return join24(base, "oh-my-opencode", "bin");
17879
18367
  }
17880
18368
  function getBinaryName3() {
17881
18369
  return process.platform === "win32" ? "sg.exe" : "sg";
17882
18370
  }
17883
18371
  function getCachedBinaryPath2() {
17884
- const binaryPath = join21(getCacheDir2(), getBinaryName3());
17885
- return existsSync19(binaryPath) ? binaryPath : null;
18372
+ const binaryPath = join24(getCacheDir2(), getBinaryName3());
18373
+ return existsSync21(binaryPath) ? binaryPath : null;
17886
18374
  }
17887
18375
  async function extractZip2(archivePath, destDir) {
17888
18376
  const proc = process.platform === "win32" ? spawn5([
@@ -17908,8 +18396,8 @@ async function downloadAstGrep(version2 = DEFAULT_VERSION) {
17908
18396
  }
17909
18397
  const cacheDir = getCacheDir2();
17910
18398
  const binaryName = getBinaryName3();
17911
- const binaryPath = join21(cacheDir, binaryName);
17912
- if (existsSync19(binaryPath)) {
18399
+ const binaryPath = join24(cacheDir, binaryName);
18400
+ if (existsSync21(binaryPath)) {
17913
18401
  return binaryPath;
17914
18402
  }
17915
18403
  const { arch, os: os3 } = platformInfo;
@@ -17917,21 +18405,21 @@ async function downloadAstGrep(version2 = DEFAULT_VERSION) {
17917
18405
  const downloadUrl = `https://github.com/${REPO2}/releases/download/${version2}/${assetName}`;
17918
18406
  console.log(`[oh-my-opencode] Downloading ast-grep binary...`);
17919
18407
  try {
17920
- if (!existsSync19(cacheDir)) {
17921
- mkdirSync6(cacheDir, { recursive: true });
18408
+ if (!existsSync21(cacheDir)) {
18409
+ mkdirSync7(cacheDir, { recursive: true });
17922
18410
  }
17923
18411
  const response = await fetch(downloadUrl, { redirect: "follow" });
17924
18412
  if (!response.ok) {
17925
18413
  throw new Error(`HTTP ${response.status}: ${response.statusText}`);
17926
18414
  }
17927
- const archivePath = join21(cacheDir, assetName);
18415
+ const archivePath = join24(cacheDir, assetName);
17928
18416
  const arrayBuffer = await response.arrayBuffer();
17929
18417
  await Bun.write(archivePath, arrayBuffer);
17930
18418
  await extractZip2(archivePath, cacheDir);
17931
- if (existsSync19(archivePath)) {
17932
- unlinkSync5(archivePath);
18419
+ if (existsSync21(archivePath)) {
18420
+ unlinkSync6(archivePath);
17933
18421
  }
17934
- if (process.platform !== "win32" && existsSync19(binaryPath)) {
18422
+ if (process.platform !== "win32" && existsSync21(binaryPath)) {
17935
18423
  chmodSync2(binaryPath, 493);
17936
18424
  }
17937
18425
  console.log(`[oh-my-opencode] ast-grep binary ready.`);
@@ -17981,9 +18469,9 @@ function findSgCliPathSync() {
17981
18469
  try {
17982
18470
  const require2 = createRequire4(import.meta.url);
17983
18471
  const cliPkgPath = require2.resolve("@ast-grep/cli/package.json");
17984
- const cliDir = dirname3(cliPkgPath);
17985
- const sgPath = join22(cliDir, binaryName);
17986
- if (existsSync20(sgPath) && isValidBinary(sgPath)) {
18472
+ const cliDir = dirname4(cliPkgPath);
18473
+ const sgPath = join25(cliDir, binaryName);
18474
+ if (existsSync22(sgPath) && isValidBinary(sgPath)) {
17987
18475
  return sgPath;
17988
18476
  }
17989
18477
  } catch {}
@@ -17992,10 +18480,10 @@ function findSgCliPathSync() {
17992
18480
  try {
17993
18481
  const require2 = createRequire4(import.meta.url);
17994
18482
  const pkgPath = require2.resolve(`${platformPkg}/package.json`);
17995
- const pkgDir = dirname3(pkgPath);
18483
+ const pkgDir = dirname4(pkgPath);
17996
18484
  const astGrepName = process.platform === "win32" ? "ast-grep.exe" : "ast-grep";
17997
- const binaryPath = join22(pkgDir, astGrepName);
17998
- if (existsSync20(binaryPath) && isValidBinary(binaryPath)) {
18485
+ const binaryPath = join25(pkgDir, astGrepName);
18486
+ if (existsSync22(binaryPath) && isValidBinary(binaryPath)) {
17999
18487
  return binaryPath;
18000
18488
  }
18001
18489
  } catch {}
@@ -18003,7 +18491,7 @@ function findSgCliPathSync() {
18003
18491
  if (process.platform === "darwin") {
18004
18492
  const homebrewPaths = ["/opt/homebrew/bin/sg", "/usr/local/bin/sg"];
18005
18493
  for (const path3 of homebrewPaths) {
18006
- if (existsSync20(path3) && isValidBinary(path3)) {
18494
+ if (existsSync22(path3) && isValidBinary(path3)) {
18007
18495
  return path3;
18008
18496
  }
18009
18497
  }
@@ -18059,11 +18547,11 @@ var DEFAULT_MAX_MATCHES = 500;
18059
18547
 
18060
18548
  // src/tools/ast-grep/cli.ts
18061
18549
  var {spawn: spawn6 } = globalThis.Bun;
18062
- import { existsSync as existsSync21 } from "fs";
18550
+ import { existsSync as existsSync23 } from "fs";
18063
18551
  var resolvedCliPath3 = null;
18064
18552
  var initPromise2 = null;
18065
18553
  async function getAstGrepPath() {
18066
- if (resolvedCliPath3 !== null && existsSync21(resolvedCliPath3)) {
18554
+ if (resolvedCliPath3 !== null && existsSync23(resolvedCliPath3)) {
18067
18555
  return resolvedCliPath3;
18068
18556
  }
18069
18557
  if (initPromise2) {
@@ -18071,7 +18559,7 @@ async function getAstGrepPath() {
18071
18559
  }
18072
18560
  initPromise2 = (async () => {
18073
18561
  const syncPath = findSgCliPathSync();
18074
- if (syncPath && existsSync21(syncPath)) {
18562
+ if (syncPath && existsSync23(syncPath)) {
18075
18563
  resolvedCliPath3 = syncPath;
18076
18564
  setSgCliPath(syncPath);
18077
18565
  return syncPath;
@@ -18105,7 +18593,7 @@ async function runSg(options) {
18105
18593
  const paths = options.paths && options.paths.length > 0 ? options.paths : ["."];
18106
18594
  args.push(...paths);
18107
18595
  let cliPath = getSgCliPath();
18108
- if (!existsSync21(cliPath) && cliPath !== "sg") {
18596
+ if (!existsSync23(cliPath) && cliPath !== "sg") {
18109
18597
  const downloadedPath = await getAstGrepPath();
18110
18598
  if (downloadedPath) {
18111
18599
  cliPath = downloadedPath;
@@ -18369,8 +18857,8 @@ var ast_grep_replace = tool({
18369
18857
  var {spawn: spawn7 } = globalThis.Bun;
18370
18858
 
18371
18859
  // src/tools/grep/constants.ts
18372
- import { existsSync as existsSync22 } from "fs";
18373
- import { join as join23, dirname as dirname4 } from "path";
18860
+ import { existsSync as existsSync24 } from "fs";
18861
+ import { join as join26, dirname as dirname5 } from "path";
18374
18862
  import { spawnSync } from "child_process";
18375
18863
  var cachedCli = null;
18376
18864
  function findExecutable(name) {
@@ -18387,17 +18875,17 @@ function findExecutable(name) {
18387
18875
  }
18388
18876
  function getOpenCodeBundledRg() {
18389
18877
  const execPath = process.execPath;
18390
- const execDir = dirname4(execPath);
18878
+ const execDir = dirname5(execPath);
18391
18879
  const isWindows = process.platform === "win32";
18392
18880
  const rgName = isWindows ? "rg.exe" : "rg";
18393
18881
  const candidates = [
18394
- join23(execDir, rgName),
18395
- join23(execDir, "bin", rgName),
18396
- join23(execDir, "..", "bin", rgName),
18397
- join23(execDir, "..", "libexec", rgName)
18882
+ join26(execDir, rgName),
18883
+ join26(execDir, "bin", rgName),
18884
+ join26(execDir, "..", "bin", rgName),
18885
+ join26(execDir, "..", "libexec", rgName)
18398
18886
  ];
18399
18887
  for (const candidate of candidates) {
18400
- if (existsSync22(candidate)) {
18888
+ if (existsSync24(candidate)) {
18401
18889
  return candidate;
18402
18890
  }
18403
18891
  }
@@ -18795,11 +19283,11 @@ var glob = tool({
18795
19283
  }
18796
19284
  });
18797
19285
  // src/tools/slashcommand/tools.ts
18798
- import { existsSync as existsSync23, readdirSync as readdirSync6, readFileSync as readFileSync12 } from "fs";
19286
+ import { existsSync as existsSync25, readdirSync as readdirSync6, readFileSync as readFileSync14 } from "fs";
18799
19287
  import { homedir as homedir13 } from "os";
18800
- import { join as join24, basename as basename3, dirname as dirname5 } from "path";
19288
+ import { join as join27, basename as basename3, dirname as dirname6 } from "path";
18801
19289
  function discoverCommandsFromDir(commandsDir, scope) {
18802
- if (!existsSync23(commandsDir)) {
19290
+ if (!existsSync25(commandsDir)) {
18803
19291
  return [];
18804
19292
  }
18805
19293
  const entries = readdirSync6(commandsDir, { withFileTypes: true });
@@ -18811,10 +19299,10 @@ function discoverCommandsFromDir(commandsDir, scope) {
18811
19299
  continue;
18812
19300
  if (!entry.isFile())
18813
19301
  continue;
18814
- const commandPath = join24(commandsDir, entry.name);
19302
+ const commandPath = join27(commandsDir, entry.name);
18815
19303
  const commandName = basename3(entry.name, ".md");
18816
19304
  try {
18817
- const content = readFileSync12(commandPath, "utf-8");
19305
+ const content = readFileSync14(commandPath, "utf-8");
18818
19306
  const { data, body } = parseFrontmatter(content);
18819
19307
  const metadata = {
18820
19308
  name: commandName,
@@ -18838,10 +19326,10 @@ function discoverCommandsFromDir(commandsDir, scope) {
18838
19326
  return commands;
18839
19327
  }
18840
19328
  function discoverCommandsSync() {
18841
- const userCommandsDir = join24(homedir13(), ".claude", "commands");
18842
- const projectCommandsDir = join24(process.cwd(), ".claude", "commands");
18843
- const opencodeGlobalDir = join24(homedir13(), ".config", "opencode", "command");
18844
- const opencodeProjectDir = join24(process.cwd(), ".opencode", "command");
19329
+ const userCommandsDir = join27(homedir13(), ".claude", "commands");
19330
+ const projectCommandsDir = join27(process.cwd(), ".claude", "commands");
19331
+ const opencodeGlobalDir = join27(homedir13(), ".config", "opencode", "command");
19332
+ const opencodeProjectDir = join27(process.cwd(), ".opencode", "command");
18845
19333
  const userCommands = discoverCommandsFromDir(userCommandsDir, "user");
18846
19334
  const opencodeGlobalCommands = discoverCommandsFromDir(opencodeGlobalDir, "opencode");
18847
19335
  const projectCommands = discoverCommandsFromDir(projectCommandsDir, "project");
@@ -18884,7 +19372,7 @@ async function formatLoadedCommand(cmd) {
18884
19372
  `);
18885
19373
  sections.push(`## Command Instructions
18886
19374
  `);
18887
- const commandDir = dirname5(cmd.path);
19375
+ const commandDir = dirname6(cmd.path);
18888
19376
  const withFileRefs = await resolveFileReferencesInText(cmd.content, commandDir);
18889
19377
  const resolvedContent = await resolveCommandsInText(withFileRefs);
18890
19378
  sections.push(resolvedContent.trim());
@@ -18964,12 +19452,29 @@ Provide a command name to execute.`;
18964
19452
  Try a different command name.`;
18965
19453
  }
18966
19454
  });
19455
+ // src/tools/skill/types.ts
19456
+ var SkillFrontmatterSchema = exports_external.object({
19457
+ name: exports_external.string().regex(/^[a-z0-9-]+$/, "Name must be lowercase alphanumeric with hyphens only").min(1, "Name cannot be empty"),
19458
+ description: exports_external.string().min(20, "Description must be at least 20 characters for discoverability"),
19459
+ license: exports_external.string().optional(),
19460
+ "allowed-tools": exports_external.array(exports_external.string()).optional(),
19461
+ metadata: exports_external.record(exports_external.string(), exports_external.string()).optional()
19462
+ });
18967
19463
  // src/tools/skill/tools.ts
18968
- import { existsSync as existsSync24, readdirSync as readdirSync7, statSync as statSync4, readlinkSync as readlinkSync2, readFileSync as readFileSync13 } from "fs";
19464
+ import { existsSync as existsSync26, readdirSync as readdirSync7, statSync as statSync4, readlinkSync as readlinkSync2, readFileSync as readFileSync15 } from "fs";
18969
19465
  import { homedir as homedir14 } from "os";
18970
- import { join as join25, resolve as resolve5, basename as basename4 } from "path";
19466
+ import { join as join28, resolve as resolve6, basename as basename4 } from "path";
19467
+ function parseSkillFrontmatter(data) {
19468
+ return {
19469
+ name: typeof data.name === "string" ? data.name : "",
19470
+ description: typeof data.description === "string" ? data.description : "",
19471
+ license: typeof data.license === "string" ? data.license : undefined,
19472
+ "allowed-tools": Array.isArray(data["allowed-tools"]) ? data["allowed-tools"] : undefined,
19473
+ metadata: typeof data.metadata === "object" && data.metadata !== null ? data.metadata : undefined
19474
+ };
19475
+ }
18971
19476
  function discoverSkillsFromDir(skillsDir, scope) {
18972
- if (!existsSync24(skillsDir)) {
19477
+ if (!existsSync26(skillsDir)) {
18973
19478
  return [];
18974
19479
  }
18975
19480
  const entries = readdirSync7(skillsDir, { withFileTypes: true });
@@ -18977,22 +19482,22 @@ function discoverSkillsFromDir(skillsDir, scope) {
18977
19482
  for (const entry of entries) {
18978
19483
  if (entry.name.startsWith("."))
18979
19484
  continue;
18980
- const skillPath = join25(skillsDir, entry.name);
19485
+ const skillPath = join28(skillsDir, entry.name);
18981
19486
  if (entry.isDirectory() || entry.isSymbolicLink()) {
18982
19487
  let resolvedPath = skillPath;
18983
19488
  try {
18984
19489
  const stats = statSync4(skillPath, { throwIfNoEntry: false });
18985
19490
  if (stats?.isSymbolicLink()) {
18986
- resolvedPath = resolve5(skillPath, "..", readlinkSync2(skillPath));
19491
+ resolvedPath = resolve6(skillPath, "..", readlinkSync2(skillPath));
18987
19492
  }
18988
19493
  } catch {
18989
19494
  continue;
18990
19495
  }
18991
- const skillMdPath = join25(resolvedPath, "SKILL.md");
18992
- if (!existsSync24(skillMdPath))
19496
+ const skillMdPath = join28(resolvedPath, "SKILL.md");
19497
+ if (!existsSync26(skillMdPath))
18993
19498
  continue;
18994
19499
  try {
18995
- const content = readFileSync13(skillMdPath, "utf-8");
19500
+ const content = readFileSync15(skillMdPath, "utf-8");
18996
19501
  const { data } = parseFrontmatter(content);
18997
19502
  skills.push({
18998
19503
  name: data.name || entry.name,
@@ -19007,8 +19512,8 @@ function discoverSkillsFromDir(skillsDir, scope) {
19007
19512
  return skills;
19008
19513
  }
19009
19514
  function discoverSkillsSync() {
19010
- const userSkillsDir = join25(homedir14(), ".claude", "skills");
19011
- const projectSkillsDir = join25(process.cwd(), ".claude", "skills");
19515
+ const userSkillsDir = join28(homedir14(), ".claude", "skills");
19516
+ const projectSkillsDir = join28(process.cwd(), ".claude", "skills");
19012
19517
  const userSkills = discoverSkillsFromDir(userSkillsDir, "user");
19013
19518
  const projectSkills = discoverSkillsFromDir(projectSkillsDir, "project");
19014
19519
  return [...projectSkills, ...userSkills];
@@ -19020,7 +19525,7 @@ function resolveSymlink(skillPath) {
19020
19525
  try {
19021
19526
  const stats = statSync4(skillPath, { throwIfNoEntry: false });
19022
19527
  if (stats?.isSymbolicLink()) {
19023
- return resolve5(skillPath, "..", readlinkSync2(skillPath));
19528
+ return resolve6(skillPath, "..", readlinkSync2(skillPath));
19024
19529
  }
19025
19530
  return skillPath;
19026
19531
  } catch {
@@ -19029,28 +19534,32 @@ function resolveSymlink(skillPath) {
19029
19534
  }
19030
19535
  async function parseSkillMd(skillPath) {
19031
19536
  const resolvedPath = resolveSymlink(skillPath);
19032
- const skillMdPath = join25(resolvedPath, "SKILL.md");
19033
- if (!existsSync24(skillMdPath)) {
19537
+ const skillMdPath = join28(resolvedPath, "SKILL.md");
19538
+ if (!existsSync26(skillMdPath)) {
19034
19539
  return null;
19035
19540
  }
19036
19541
  try {
19037
- let content = readFileSync13(skillMdPath, "utf-8");
19542
+ let content = readFileSync15(skillMdPath, "utf-8");
19038
19543
  content = await resolveCommandsInText(content);
19039
19544
  const { data, body } = parseFrontmatter(content);
19545
+ const frontmatter2 = parseSkillFrontmatter(data);
19040
19546
  const metadata = {
19041
- name: data.name || basename4(skillPath),
19042
- description: data.description || "",
19043
- license: data.license
19547
+ name: frontmatter2.name || basename4(skillPath),
19548
+ description: frontmatter2.description,
19549
+ license: frontmatter2.license,
19550
+ allowedTools: frontmatter2["allowed-tools"],
19551
+ metadata: frontmatter2.metadata
19044
19552
  };
19045
- const referencesDir = join25(resolvedPath, "references");
19046
- const scriptsDir = join25(resolvedPath, "scripts");
19047
- const assetsDir = join25(resolvedPath, "assets");
19048
- const references = existsSync24(referencesDir) ? readdirSync7(referencesDir).filter((f) => !f.startsWith(".")) : [];
19049
- const scripts = existsSync24(scriptsDir) ? readdirSync7(scriptsDir).filter((f) => !f.startsWith(".") && !f.startsWith("__")) : [];
19050
- const assets = existsSync24(assetsDir) ? readdirSync7(assetsDir).filter((f) => !f.startsWith(".")) : [];
19553
+ const referencesDir = join28(resolvedPath, "references");
19554
+ const scriptsDir = join28(resolvedPath, "scripts");
19555
+ const assetsDir = join28(resolvedPath, "assets");
19556
+ const references = existsSync26(referencesDir) ? readdirSync7(referencesDir).filter((f) => !f.startsWith(".")) : [];
19557
+ const scripts = existsSync26(scriptsDir) ? readdirSync7(scriptsDir).filter((f) => !f.startsWith(".") && !f.startsWith("__")) : [];
19558
+ const assets = existsSync26(assetsDir) ? readdirSync7(assetsDir).filter((f) => !f.startsWith(".")) : [];
19051
19559
  return {
19052
19560
  name: metadata.name,
19053
19561
  path: resolvedPath,
19562
+ basePath: resolvedPath,
19054
19563
  metadata,
19055
19564
  content: body,
19056
19565
  references,
@@ -19062,7 +19571,7 @@ async function parseSkillMd(skillPath) {
19062
19571
  }
19063
19572
  }
19064
19573
  async function discoverSkillsFromDirAsync(skillsDir) {
19065
- if (!existsSync24(skillsDir)) {
19574
+ if (!existsSync26(skillsDir)) {
19066
19575
  return [];
19067
19576
  }
19068
19577
  const entries = readdirSync7(skillsDir, { withFileTypes: true });
@@ -19070,7 +19579,7 @@ async function discoverSkillsFromDirAsync(skillsDir) {
19070
19579
  for (const entry of entries) {
19071
19580
  if (entry.name.startsWith("."))
19072
19581
  continue;
19073
- const skillPath = join25(skillsDir, entry.name);
19582
+ const skillPath = join28(skillsDir, entry.name);
19074
19583
  if (entry.isDirectory() || entry.isSymbolicLink()) {
19075
19584
  const skillInfo = await parseSkillMd(skillPath);
19076
19585
  if (skillInfo) {
@@ -19081,8 +19590,8 @@ async function discoverSkillsFromDirAsync(skillsDir) {
19081
19590
  return skills;
19082
19591
  }
19083
19592
  async function discoverSkills() {
19084
- const userSkillsDir = join25(homedir14(), ".claude", "skills");
19085
- const projectSkillsDir = join25(process.cwd(), ".claude", "skills");
19593
+ const userSkillsDir = join28(homedir14(), ".claude", "skills");
19594
+ const projectSkillsDir = join28(process.cwd(), ".claude", "skills");
19086
19595
  const userSkills = await discoverSkillsFromDirAsync(userSkillsDir);
19087
19596
  const projectSkills = await discoverSkillsFromDirAsync(projectSkillsDir);
19088
19597
  return [...projectSkills, ...userSkills];
@@ -19111,9 +19620,9 @@ async function loadSkillWithReferences(skill, includeRefs) {
19111
19620
  const referencesLoaded = [];
19112
19621
  if (includeRefs && skill.references.length > 0) {
19113
19622
  for (const ref of skill.references) {
19114
- const refPath = join25(skill.path, "references", ref);
19623
+ const refPath = join28(skill.path, "references", ref);
19115
19624
  try {
19116
- let content = readFileSync13(refPath, "utf-8");
19625
+ let content = readFileSync15(refPath, "utf-8");
19117
19626
  content = await resolveCommandsInText(content);
19118
19627
  referencesLoaded.push({ path: ref, content });
19119
19628
  } catch {}
@@ -19122,6 +19631,7 @@ async function loadSkillWithReferences(skill, includeRefs) {
19122
19631
  return {
19123
19632
  name: skill.name,
19124
19633
  metadata: skill.metadata,
19634
+ basePath: skill.basePath,
19125
19635
  body: skill.content,
19126
19636
  referencesLoaded
19127
19637
  };
@@ -19144,62 +19654,34 @@ function formatLoadedSkills(loadedSkills) {
19144
19654
  if (loadedSkills.length === 0) {
19145
19655
  return "No skills loaded.";
19146
19656
  }
19147
- const sections = [`# Loaded Skills
19148
- `];
19149
- for (const skill of loadedSkills) {
19150
- sections.push(`## ${skill.metadata.name}
19151
- `);
19152
- sections.push(`**Description**: ${skill.metadata.description || "(no description)"}
19153
- `);
19154
- sections.push(`### Skill Instructions
19155
- `);
19156
- sections.push(skill.body.trim());
19157
- if (skill.referencesLoaded.length > 0) {
19158
- sections.push(`
19657
+ const skill = loadedSkills[0];
19658
+ const sections = [];
19659
+ sections.push(`Base directory for this skill: ${skill.basePath}/`);
19660
+ sections.push("");
19661
+ sections.push(skill.body.trim());
19662
+ if (skill.referencesLoaded.length > 0) {
19663
+ sections.push(`
19664
+ ---
19159
19665
  ### Loaded References
19160
19666
  `);
19161
- for (const ref of skill.referencesLoaded) {
19162
- sections.push(`#### ${ref.path}
19667
+ for (const ref of skill.referencesLoaded) {
19668
+ sections.push(`#### ${ref.path}
19163
19669
  `);
19164
- sections.push("```");
19165
- sections.push(ref.content.trim());
19166
- sections.push("```\n");
19167
- }
19670
+ sections.push("```");
19671
+ sections.push(ref.content.trim());
19672
+ sections.push("```\n");
19168
19673
  }
19169
- sections.push(`
19170
- ---
19171
- `);
19172
19674
  }
19173
- const skillNames = loadedSkills.map((s) => s.metadata.name).join(", ");
19174
- sections.push(`**Skills loaded**: ${skillNames}`);
19175
- sections.push(`**Total**: ${loadedSkills.length} skill(s)`);
19176
19675
  sections.push(`
19177
- Please confirm these skills match your needs before proceeding.`);
19676
+ ---
19677
+ **Launched skill**: ${skill.metadata.name}`);
19178
19678
  return sections.join(`
19179
19679
  `);
19180
19680
  }
19181
19681
  var skill = tool({
19182
19682
  description: `Execute a skill within the main conversation.
19183
19683
 
19184
- When users ask you to perform tasks, check if any of the available skills below can help complete the task more effectively. Skills provide specialized capabilities and domain knowledge.
19185
-
19186
- How to use skills:
19187
- - Invoke skills using this tool with the skill name only (no arguments)
19188
- - When you invoke a skill, the skill's prompt will expand and provide detailed instructions on how to complete the task
19189
-
19190
- Important:
19191
- - Only use skills listed in Available Skills below
19192
- - Do not invoke a skill that is already running
19193
-
19194
- Skills are loaded from:
19195
- - ~/.claude/skills/ (user scope - global skills)
19196
- - ./.claude/skills/ (project scope - project-specific skills)
19197
-
19198
- Each skill contains:
19199
- - SKILL.md: Main instructions with YAML frontmatter (name, description)
19200
- - references/: Documentation files loaded into context as needed
19201
- - scripts/: Executable code for deterministic operations
19202
- - assets/: Files used in output (templates, icons, etc.)
19684
+ When you invoke a skill, the skill's prompt will expand and provide detailed instructions on how to complete the task.
19203
19685
 
19204
19686
  Available Skills:
19205
19687
  ${skillListForDescription}`,
@@ -19324,26 +19806,64 @@ var OhMyOpenCodeConfigSchema = exports_external.object({
19324
19806
  // src/index.ts
19325
19807
  import * as fs4 from "fs";
19326
19808
  import * as path3 from "path";
19327
- function loadPluginConfig(directory) {
19328
- const configPaths = [
19329
- path3.join(directory, "oh-my-opencode.json"),
19330
- path3.join(directory, ".oh-my-opencode.json")
19331
- ];
19332
- for (const configPath of configPaths) {
19333
- try {
19334
- if (fs4.existsSync(configPath)) {
19335
- const content = fs4.readFileSync(configPath, "utf-8");
19336
- const rawConfig = JSON.parse(content);
19337
- const result = OhMyOpenCodeConfigSchema.safeParse(rawConfig);
19338
- if (!result.success) {
19339
- log(`Config validation error in ${configPath}:`, result.error.issues);
19340
- return {};
19341
- }
19342
- return result.data;
19809
+ import * as os3 from "os";
19810
+ function getUserConfigDir() {
19811
+ if (process.platform === "win32") {
19812
+ return process.env.APPDATA || path3.join(os3.homedir(), "AppData", "Roaming");
19813
+ }
19814
+ return process.env.XDG_CONFIG_HOME || path3.join(os3.homedir(), ".config");
19815
+ }
19816
+ function loadConfigFromPath2(configPath) {
19817
+ try {
19818
+ if (fs4.existsSync(configPath)) {
19819
+ const content = fs4.readFileSync(configPath, "utf-8");
19820
+ const rawConfig = JSON.parse(content);
19821
+ const result = OhMyOpenCodeConfigSchema.safeParse(rawConfig);
19822
+ if (!result.success) {
19823
+ log(`Config validation error in ${configPath}:`, result.error.issues);
19824
+ return null;
19343
19825
  }
19344
- } catch {}
19826
+ log(`Config loaded from ${configPath}`, { agents: result.data.agents });
19827
+ return result.data;
19828
+ }
19829
+ } catch (err) {
19830
+ log(`Error loading config from ${configPath}:`, err);
19345
19831
  }
19346
- return {};
19832
+ return null;
19833
+ }
19834
+ function mergeConfigs(base, override) {
19835
+ return {
19836
+ ...base,
19837
+ ...override,
19838
+ agents: override.agents !== undefined ? { ...base.agents ?? {}, ...override.agents } : base.agents,
19839
+ disabled_agents: [
19840
+ ...new Set([
19841
+ ...base.disabled_agents ?? [],
19842
+ ...override.disabled_agents ?? []
19843
+ ])
19844
+ ],
19845
+ disabled_mcps: [
19846
+ ...new Set([
19847
+ ...base.disabled_mcps ?? [],
19848
+ ...override.disabled_mcps ?? []
19849
+ ])
19850
+ ]
19851
+ };
19852
+ }
19853
+ function loadPluginConfig(directory) {
19854
+ const userConfigPath = path3.join(getUserConfigDir(), "opencode", "oh-my-opencode.json");
19855
+ const projectConfigPath = path3.join(directory, ".opencode", "oh-my-opencode.json");
19856
+ let config3 = loadConfigFromPath2(userConfigPath) ?? {};
19857
+ const projectConfig = loadConfigFromPath2(projectConfigPath);
19858
+ if (projectConfig) {
19859
+ config3 = mergeConfigs(config3, projectConfig);
19860
+ }
19861
+ log("Final merged config", {
19862
+ agents: config3.agents,
19863
+ disabled_agents: config3.disabled_agents,
19864
+ disabled_mcps: config3.disabled_mcps
19865
+ });
19866
+ return config3;
19347
19867
  }
19348
19868
  var OhMyOpenCodePlugin = async (ctx) => {
19349
19869
  const pluginConfig = loadPluginConfig(ctx.directory);
@@ -19353,6 +19873,7 @@ var OhMyOpenCodePlugin = async (ctx) => {
19353
19873
  const commentChecker = createCommentCheckerHooks();
19354
19874
  const grepOutputTruncator = createGrepOutputTruncatorHook(ctx);
19355
19875
  const directoryAgentsInjector = createDirectoryAgentsInjectorHook(ctx);
19876
+ const directoryReadmeInjector = createDirectoryReadmeInjectorHook(ctx);
19356
19877
  const emptyTaskResponseDetector = createEmptyTaskResponseDetectorHook(ctx);
19357
19878
  const thinkMode = createThinkModeHook();
19358
19879
  const claudeCodeHooks = createClaudeCodeHooksHook(ctx, {});
@@ -19404,6 +19925,7 @@ var OhMyOpenCodePlugin = async (ctx) => {
19404
19925
  await todoContinuationEnforcer(input);
19405
19926
  await contextWindowMonitor.event(input);
19406
19927
  await directoryAgentsInjector.event(input);
19928
+ await directoryReadmeInjector.event(input);
19407
19929
  await thinkMode.event(input);
19408
19930
  await anthropicAutoCompact.event(input);
19409
19931
  const { event } = input;
@@ -19503,6 +20025,7 @@ var OhMyOpenCodePlugin = async (ctx) => {
19503
20025
  await contextWindowMonitor["tool.execute.after"](input, output);
19504
20026
  await commentChecker["tool.execute.after"](input, output);
19505
20027
  await directoryAgentsInjector["tool.execute.after"](input, output);
20028
+ await directoryReadmeInjector["tool.execute.after"](input, output);
19506
20029
  await emptyTaskResponseDetector["tool.execute.after"](input, output);
19507
20030
  if (input.sessionID === getMainSessionID()) {
19508
20031
  updateTerminalTitle({