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/README.ko.md +2 -1
- package/README.md +2 -1
- package/dist/hooks/directory-readme-injector/constants.d.ts +3 -0
- package/dist/hooks/directory-readme-injector/index.d.ts +22 -0
- package/dist/hooks/directory-readme-injector/storage.d.ts +3 -0
- package/dist/hooks/directory-readme-injector/types.d.ts +5 -0
- package/dist/hooks/index.d.ts +1 -0
- package/dist/index.js +846 -323
- package/dist/tools/skill/tools.d.ts +2 -1
- package/dist/tools/skill/types.d.ts +17 -0
- package/package.json +1 -1
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.
|
|
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
|
|
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
|
|
96
|
-
|
|
97
|
-
-
|
|
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**:
|
|
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 \`
|
|
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
|
|
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
|
|
114
|
-
-
|
|
115
|
-
-
|
|
116
|
-
|
|
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
|
-
|
|
125
|
-
|
|
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
|
-
###
|
|
128
|
-
Use this for understanding code evolution and authorial intent
|
|
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
|
-
|
|
141
|
-
|
|
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
|
-
|
|
290
|
+
// Find all export statements
|
|
291
|
+
ast_grep_search(pattern: "export { $$$ }", lang: "typescript")
|
|
144
292
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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. **
|
|
167
|
-
-
|
|
168
|
-
-
|
|
169
|
-
-
|
|
170
|
-
-
|
|
171
|
-
-
|
|
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. **
|
|
174
|
-
-
|
|
175
|
-
-
|
|
176
|
-
-
|
|
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
|
|
332
|
+
4. **SYNTHESIZE WITH EVIDENCE**:
|
|
333
|
+
- Present findings with **GitHub permalinks**
|
|
180
334
|
- **FORMAT**:
|
|
181
|
-
- **
|
|
182
|
-
- **
|
|
183
|
-
- **
|
|
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,
|
|
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
|
-
-
|
|
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
|
|
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
|
-
|
|
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**
|
|
273
|
-
-
|
|
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-
|
|
2711
|
+
"gpt-5.1-chat-latest": "gpt-5.1-chat-latest-high",
|
|
2238
2712
|
"gpt-5.1-codex": "gpt-5.1-codex-high",
|
|
2239
|
-
"
|
|
2240
|
-
"
|
|
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
|
-
"
|
|
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
|
|
2310
|
-
import { existsSync as
|
|
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
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
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 &&
|
|
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 (
|
|
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
|
|
2868
|
+
import { existsSync as existsSync10 } from "fs";
|
|
2381
2869
|
import { homedir as homedir3 } from "os";
|
|
2382
|
-
import { join as
|
|
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 =
|
|
2887
|
+
var USER_CONFIG_PATH = join13(homedir3(), ".config", "opencode", "opencode-cc-plugin.json");
|
|
2400
2888
|
function getProjectConfigPath() {
|
|
2401
|
-
return
|
|
2889
|
+
return join13(process.cwd(), ".opencode", "opencode-cc-plugin.json");
|
|
2402
2890
|
}
|
|
2403
2891
|
async function loadConfigFromPath(path3) {
|
|
2404
|
-
if (!
|
|
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
|
|
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 &&
|
|
2988
|
+
if (customZshPath && existsSync11(customZshPath)) {
|
|
2501
2989
|
return customZshPath;
|
|
2502
2990
|
}
|
|
2503
2991
|
for (const path3 of DEFAULT_ZSH_PATHS) {
|
|
2504
|
-
if (
|
|
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((
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
2623
|
-
import { join as
|
|
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
|
|
3131
|
+
return join14(cwd, filePath);
|
|
2644
3132
|
}
|
|
2645
3133
|
function readFileContent(resolvedPath) {
|
|
2646
|
-
if (!
|
|
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 =
|
|
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
|
|
2877
|
-
import { mkdirSync as
|
|
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 =
|
|
3368
|
+
var TRANSCRIPT_DIR = join15(homedir4(), ".claude", "transcripts");
|
|
2881
3369
|
function getTranscriptPath(sessionId) {
|
|
2882
|
-
return
|
|
3370
|
+
return join15(TRANSCRIPT_DIR, `${sessionId}.jsonl`);
|
|
2883
3371
|
}
|
|
2884
3372
|
function ensureTranscriptDir() {
|
|
2885
|
-
if (!
|
|
2886
|
-
|
|
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 =
|
|
2973
|
-
|
|
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 =
|
|
2993
|
-
|
|
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
|
-
|
|
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
|
|
3692
|
+
import { join as join16 } from "path";
|
|
3205
3693
|
import { homedir as homedir5 } from "os";
|
|
3206
|
-
var TODO_DIR =
|
|
3694
|
+
var TODO_DIR = join16(homedir5(), ".claude", "todos");
|
|
3207
3695
|
function getTodoPath(sessionId) {
|
|
3208
|
-
return
|
|
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
|
|
3301
|
-
import { join as
|
|
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
|
|
3792
|
+
import { join as join17 } from "path";
|
|
3305
3793
|
import { homedir as homedir6 } from "os";
|
|
3306
|
-
var xdgData2 = process.env.XDG_DATA_HOME ||
|
|
3307
|
-
var
|
|
3308
|
-
var MESSAGE_STORAGE2 =
|
|
3309
|
-
var PART_STORAGE2 =
|
|
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 =
|
|
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 (!
|
|
3343
|
-
|
|
3830
|
+
if (!existsSync14(MESSAGE_STORAGE2)) {
|
|
3831
|
+
mkdirSync6(MESSAGE_STORAGE2, { recursive: true });
|
|
3344
3832
|
}
|
|
3345
|
-
const directPath =
|
|
3346
|
-
if (
|
|
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 =
|
|
3351
|
-
if (
|
|
3838
|
+
const sessionPath = join18(MESSAGE_STORAGE2, dir, sessionID);
|
|
3839
|
+
if (existsSync14(sessionPath)) {
|
|
3352
3840
|
return sessionPath;
|
|
3353
3841
|
}
|
|
3354
3842
|
}
|
|
3355
|
-
|
|
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
|
-
|
|
3397
|
-
const partDir =
|
|
3398
|
-
if (!
|
|
3399
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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 (!
|
|
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 =
|
|
4150
|
+
const commandPath = join19(commandsDir, entry.name);
|
|
3663
4151
|
const commandName = basename(entry.name, ".md");
|
|
3664
4152
|
try {
|
|
3665
|
-
const content =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
4214
|
+
import { join as join20, resolve as resolve3 } from "path";
|
|
3727
4215
|
function loadSkillsFromDir(skillsDir, scope) {
|
|
3728
|
-
if (!
|
|
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 =
|
|
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 =
|
|
4229
|
+
resolvedPath = resolve3(skillPath, "..", readlinkSync(skillPath));
|
|
3742
4230
|
}
|
|
3743
|
-
const skillMdPath =
|
|
3744
|
-
if (!
|
|
4231
|
+
const skillMdPath = join20(resolvedPath, "SKILL.md");
|
|
4232
|
+
if (!existsSync16(skillMdPath))
|
|
3745
4233
|
continue;
|
|
3746
4234
|
try {
|
|
3747
|
-
const content =
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
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 (!
|
|
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 =
|
|
4309
|
+
const agentPath = join21(agentsDir, entry.name);
|
|
3822
4310
|
const agentName = basename2(entry.name, ".md");
|
|
3823
4311
|
try {
|
|
3824
|
-
const content =
|
|
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 =
|
|
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 =
|
|
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
|
|
4357
|
+
import { existsSync as existsSync18 } from "fs";
|
|
3870
4358
|
import { homedir as homedir10 } from "os";
|
|
3871
|
-
import { join as
|
|
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:
|
|
3941
|
-
{ path:
|
|
3942
|
-
{ path:
|
|
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 (!
|
|
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
|
|
4233
|
-
import { join as
|
|
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 (!
|
|
4724
|
+
if (!existsSync19(path3))
|
|
4237
4725
|
return null;
|
|
4238
4726
|
try {
|
|
4239
|
-
return JSON.parse(
|
|
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:
|
|
4248
|
-
user:
|
|
4249
|
-
opencode:
|
|
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 (
|
|
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
|
|
4393
|
-
import { extname, resolve as
|
|
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((
|
|
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((
|
|
4681
|
-
this.pending.set(id, { resolve:
|
|
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 =
|
|
5277
|
+
const absPath = resolve4(filePath);
|
|
4790
5278
|
if (this.openedFiles.has(absPath))
|
|
4791
5279
|
return;
|
|
4792
|
-
const text =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
4907
|
-
import { existsSync as
|
|
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 =
|
|
4910
|
-
if (!
|
|
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 (
|
|
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(
|
|
5410
|
+
return __require("path").dirname(resolve5(filePath));
|
|
4923
5411
|
}
|
|
4924
5412
|
async function withLspClient(filePath, fn) {
|
|
4925
|
-
const absPath =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
5141
|
-
|
|
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
|
|
17842
|
-
import { existsSync as
|
|
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
|
|
17847
|
-
import { join as
|
|
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 ||
|
|
17874
|
-
return
|
|
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 ||
|
|
17878
|
-
return
|
|
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 =
|
|
17885
|
-
return
|
|
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 =
|
|
17912
|
-
if (
|
|
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 (!
|
|
17921
|
-
|
|
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 =
|
|
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 (
|
|
17932
|
-
|
|
18419
|
+
if (existsSync21(archivePath)) {
|
|
18420
|
+
unlinkSync6(archivePath);
|
|
17933
18421
|
}
|
|
17934
|
-
if (process.platform !== "win32" &&
|
|
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 =
|
|
17985
|
-
const sgPath =
|
|
17986
|
-
if (
|
|
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 =
|
|
18483
|
+
const pkgDir = dirname4(pkgPath);
|
|
17996
18484
|
const astGrepName = process.platform === "win32" ? "ast-grep.exe" : "ast-grep";
|
|
17997
|
-
const binaryPath =
|
|
17998
|
-
if (
|
|
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 (
|
|
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
|
|
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 &&
|
|
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 &&
|
|
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 (!
|
|
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
|
|
18373
|
-
import { join as
|
|
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 =
|
|
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
|
-
|
|
18395
|
-
|
|
18396
|
-
|
|
18397
|
-
|
|
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 (
|
|
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
|
|
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
|
|
19288
|
+
import { join as join27, basename as basename3, dirname as dirname6 } from "path";
|
|
18801
19289
|
function discoverCommandsFromDir(commandsDir, scope) {
|
|
18802
|
-
if (!
|
|
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 =
|
|
19302
|
+
const commandPath = join27(commandsDir, entry.name);
|
|
18815
19303
|
const commandName = basename3(entry.name, ".md");
|
|
18816
19304
|
try {
|
|
18817
|
-
const content =
|
|
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 =
|
|
18842
|
-
const projectCommandsDir =
|
|
18843
|
-
const opencodeGlobalDir =
|
|
18844
|
-
const opencodeProjectDir =
|
|
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 =
|
|
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
|
|
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
|
|
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 (!
|
|
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 =
|
|
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 =
|
|
19491
|
+
resolvedPath = resolve6(skillPath, "..", readlinkSync2(skillPath));
|
|
18987
19492
|
}
|
|
18988
19493
|
} catch {
|
|
18989
19494
|
continue;
|
|
18990
19495
|
}
|
|
18991
|
-
const skillMdPath =
|
|
18992
|
-
if (!
|
|
19496
|
+
const skillMdPath = join28(resolvedPath, "SKILL.md");
|
|
19497
|
+
if (!existsSync26(skillMdPath))
|
|
18993
19498
|
continue;
|
|
18994
19499
|
try {
|
|
18995
|
-
const content =
|
|
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 =
|
|
19011
|
-
const projectSkillsDir =
|
|
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
|
|
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 =
|
|
19033
|
-
if (!
|
|
19537
|
+
const skillMdPath = join28(resolvedPath, "SKILL.md");
|
|
19538
|
+
if (!existsSync26(skillMdPath)) {
|
|
19034
19539
|
return null;
|
|
19035
19540
|
}
|
|
19036
19541
|
try {
|
|
19037
|
-
let content =
|
|
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:
|
|
19042
|
-
description:
|
|
19043
|
-
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 =
|
|
19046
|
-
const scriptsDir =
|
|
19047
|
-
const assetsDir =
|
|
19048
|
-
const references =
|
|
19049
|
-
const scripts =
|
|
19050
|
-
const assets =
|
|
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 (!
|
|
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 =
|
|
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 =
|
|
19085
|
-
const projectSkillsDir =
|
|
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 =
|
|
19623
|
+
const refPath = join28(skill.path, "references", ref);
|
|
19115
19624
|
try {
|
|
19116
|
-
let content =
|
|
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
|
|
19148
|
-
|
|
19149
|
-
for
|
|
19150
|
-
|
|
19151
|
-
|
|
19152
|
-
|
|
19153
|
-
`
|
|
19154
|
-
|
|
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
|
-
|
|
19162
|
-
|
|
19667
|
+
for (const ref of skill.referencesLoaded) {
|
|
19668
|
+
sections.push(`#### ${ref.path}
|
|
19163
19669
|
`);
|
|
19164
|
-
|
|
19165
|
-
|
|
19166
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
19328
|
-
|
|
19329
|
-
|
|
19330
|
-
path3.join(
|
|
19331
|
-
|
|
19332
|
-
|
|
19333
|
-
|
|
19334
|
-
|
|
19335
|
-
|
|
19336
|
-
|
|
19337
|
-
|
|
19338
|
-
|
|
19339
|
-
|
|
19340
|
-
|
|
19341
|
-
}
|
|
19342
|
-
return
|
|
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
|
-
|
|
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({
|