Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3eb88aa861 | ||
|
|
652f343c95 | ||
|
|
9ba41558de | ||
|
|
50727171a6 | ||
|
|
14ff86c547 | ||
|
|
e4036185f0 | ||
|
|
d34154bc68 | ||
|
|
9e00be91af |
@@ -65,7 +65,7 @@ oh-my-opencode/
|
||||
|
||||
| Agent | Model | Purpose |
|
||||
|-------|-------|---------|
|
||||
| oracle | GPT-5.1 | Code review, strategic planning |
|
||||
| oracle | GPT-5.2 | Code review, strategic planning |
|
||||
| librarian | Claude Haiku | Documentation, example lookup |
|
||||
| explore | Grok | File/codebase exploration |
|
||||
| frontend-ui-ux-engineer | Gemini | UI generation |
|
||||
|
||||
@@ -166,6 +166,7 @@ OpenCode 는 아주 확장가능하고 아주 커스터마이저블합니다.
|
||||
│ └── Button.tsx # 이 파일을 읽으면 위 3개 AGENTS.md 모두 주입
|
||||
```
|
||||
`Button.tsx`를 읽으면 순서대로 주입됩니다: `project/AGENTS.md` → `src/AGENTS.md` → `components/AGENTS.md`. 각 디렉토리의 컨텍스트는 세션당 한 번만 주입됩니다. Claude Code의 CLAUDE.md 기능에서 영감을 받았습니다.
|
||||
- **Directory README.md Injector**: 파일을 읽을 때 `README.md` 내용을 자동으로 주입합니다. AGENTS.md Injector와 동일하게 동작하며, 파일 디렉토리부터 프로젝트 루트까지 탐색합니다. LLM 에이전트에게 프로젝트 문서 컨텍스트를 제공합니다. 각 디렉토리의 README는 세션당 한 번만 주입됩니다.
|
||||
- **Think Mode**: 확장된 사고(Extended Thinking)가 필요한 상황을 자동으로 감지하고 모드를 전환합니다. 사용자가 깊은 사고를 요청하는 표현(예: "think deeply", "ultrathink")을 감지하면, 추론 능력을 극대화하도록 모델 설정을 동적으로 조정합니다.
|
||||
- **Anthropic Auto Compact**: Anthropic 모델 사용 시 컨텍스트 한계에 도달하면 대화 기록을 자동으로 압축하여 효율적으로 관리합니다.
|
||||
- **Empty Task Response Detector**: 서브 에이전트가 수행한 작업이 비어있거나 무의미한 응답을 반환하는 경우를 감지하여, 오류 없이 우아하게 처리합니다.
|
||||
@@ -173,7 +174,7 @@ OpenCode 는 아주 확장가능하고 아주 커스터마이저블합니다.
|
||||
|
||||
### Agents
|
||||
|
||||
- **oracle** (`openai/gpt-5.1`): 아키텍처, 코드 리뷰, 전략 수립을 위한 전문가 조언자. GPT-5.1의 뛰어난 논리적 추론과 깊은 분석 능력을 활용합니다. AmpCode 에서 영감을 받았습니다.
|
||||
- **oracle** (`openai/gpt-5.2`): 아키텍처, 코드 리뷰, 전략 수립을 위한 전문가 조언자. GPT-5.2의 뛰어난 논리적 추론과 깊은 분석 능력을 활용합니다. AmpCode 에서 영감을 받았습니다.
|
||||
- **librarian** (`anthropic/claude-haiku-4-5`): 멀티 레포 분석, 문서 조회, 구현 예제 담당. Haiku의 빠른 속도, 적절한 지능, 훌륭한 도구 호출 능력, 저렴한 비용을 활용합니다. AmpCode 에서 영감을 받았습니다.
|
||||
- **explore** (`opencode/grok-code`): 빠른 코드베이스 탐색, 파일 패턴 매칭. Claude Code는 Haiku를 쓰지만, 우리는 Grok을 씁니다. 현재 무료이고, 극도로 빠르며, 파일 탐색 작업에 충분한 지능을 갖췄기 때문입니다. Claude Code 에서 영감을 받았습니다.
|
||||
- **frontend-ui-ux-engineer** (`google/gemini-3-pro-preview`): 개발자로 전향한 디자이너라는 설정을 갖고 있습니다. 멋진 UI를 만듭니다. 아름답고 창의적인 UI 코드를 생성하는 데 탁월한 Gemini를 사용합니다.
|
||||
|
||||
@@ -163,13 +163,14 @@ I believe in the right tool for the job. For your wallet's sake, use CLIProxyAPI
|
||||
│ └── Button.tsx # Reading this injects ALL 3 AGENTS.md files
|
||||
```
|
||||
When reading `Button.tsx`, the hook injects contexts in order: `project/AGENTS.md` → `src/AGENTS.md` → `components/AGENTS.md`. Each directory's context is injected only once per session. Inspired by Claude Code's CLAUDE.md feature.
|
||||
- **Directory README.md Injector**: Automatically injects `README.md` contents when reading files. Works identically to the AGENTS.md Injector, searching upward from the file's directory to project root. Provides project documentation context to the LLM agent. Each directory's README is injected only once per session.
|
||||
- **Think Mode**: Automatic extended thinking detection and mode switching. Detects when user requests deep thinking (e.g., "think deeply", "ultrathink") and dynamically adjusts model settings for enhanced reasoning.
|
||||
- **Anthropic Auto Compact**: Automatically compacts conversation history when approaching context limits for Anthropic models.
|
||||
- **Empty Task Response Detector**: Detects when subagent tasks return empty or meaningless responses and handles gracefully.
|
||||
- **Grep Output Truncator**: Prevents grep output from overwhelming the context by truncating excessively long results.
|
||||
|
||||
### Agents
|
||||
- **oracle** (`openai/gpt-5.1`): The architect. Expert in code reviews and strategy. Uses GPT-5.1 for its unmatched logic and reasoning capabilities. Inspired by AmpCode.
|
||||
- **oracle** (`openai/gpt-5.2`): The architect. Expert in code reviews and strategy. Uses GPT-5.2 for its unmatched logic and reasoning capabilities. Inspired by AmpCode.
|
||||
- **librarian** (`anthropic/claude-haiku-4-5`): Multi-repo analysis, documentation lookup, and implementation examples. Haiku is chosen for its speed, competence, excellent tool usage, and cost-efficiency. Inspired by AmpCode.
|
||||
- **explore** (`opencode/grok-code`): Fast exploration and pattern matching. Claude Code uses Haiku; we use Grok. It is currently free, blazing fast, and intelligent enough for file traversal. Inspired by Claude Code.
|
||||
- **frontend-ui-ux-engineer** (`google/gemini-3-pro-preview`): A designer turned developer. Creates stunning UIs. Uses Gemini because its creativity and UI code generation are superior.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "oh-my-opencode",
|
||||
"version": "0.3.1",
|
||||
"version": "0.3.3",
|
||||
"description": "OpenCode plugin - custom agents (oracle, librarian) and enhanced features",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
|
||||
@@ -21,6 +21,22 @@ This is a READ-ONLY exploration task. You are STRICTLY PROHIBITED from:
|
||||
|
||||
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.
|
||||
|
||||
## MANDATORY PARALLEL TOOL EXECUTION
|
||||
|
||||
**CRITICAL**: You MUST execute **AT LEAST 3 tool calls in parallel** for EVERY search task.
|
||||
|
||||
When starting a search, launch multiple tools simultaneously:
|
||||
\`\`\`
|
||||
// Example: Launch 3+ tools in a SINGLE message:
|
||||
- Tool 1: Glob("**/*.ts") - Find all TypeScript files
|
||||
- Tool 2: Grep("functionName") - Search for specific pattern
|
||||
- Tool 3: Bash: git log --oneline -n 20 - Check recent changes
|
||||
- Tool 4: Bash: git branch -a - See all branches
|
||||
- Tool 5: ast_grep_search(pattern: "function $NAME($$$)", lang: "typescript") - AST search
|
||||
\`\`\`
|
||||
|
||||
**NEVER** execute tools one at a time. Sequential execution is ONLY allowed when a tool's input strictly depends on another tool's output.
|
||||
|
||||
## Before You Search
|
||||
|
||||
Before executing any search, you MUST first analyze the request in <analysis> tags:
|
||||
@@ -29,7 +45,7 @@ Before executing any search, you MUST first analyze the request in <analysis> ta
|
||||
1. **Request**: What exactly did the user ask for?
|
||||
2. **Intent**: Why are they asking this? What problem are they trying to solve?
|
||||
3. **Expected Output**: What kind of answer would be most helpful?
|
||||
4. **Search Strategy**: What tools and patterns will I use to find this?
|
||||
4. **Search Strategy**: What 3+ parallel tools will I use to find this?
|
||||
</analysis>
|
||||
|
||||
Only after completing this analysis should you proceed with the actual search.
|
||||
@@ -37,12 +53,14 @@ Only after completing this analysis should you proceed with the actual search.
|
||||
## Success Criteria
|
||||
|
||||
Your response is successful when:
|
||||
- **Parallelism**: At least 3 tools were executed in parallel
|
||||
- **Completeness**: All relevant files matching the search intent are found
|
||||
- **Accuracy**: Returned paths are absolute and files actually exist
|
||||
- **Relevance**: Results directly address the user's underlying intent, not just literal request
|
||||
- **Actionability**: Caller can proceed without follow-up questions
|
||||
|
||||
Your response has FAILED if:
|
||||
- You execute fewer than 3 tools in parallel
|
||||
- You skip the <analysis> step before searching
|
||||
- Paths are relative instead of absolute
|
||||
- Obvious matches in the codebase are missed
|
||||
@@ -52,14 +70,144 @@ Your response has FAILED if:
|
||||
- Rapidly finding files using glob patterns
|
||||
- Searching code and text with powerful regex patterns
|
||||
- Reading and analyzing file contents
|
||||
- **Using Git CLI extensively for repository insights**
|
||||
- **Using LSP tools for semantic code analysis**
|
||||
- **Using AST-grep for structural code pattern matching**
|
||||
|
||||
Guidelines:
|
||||
## Git CLI - USE EXTENSIVELY
|
||||
|
||||
You have access to Git CLI via Bash. Use it extensively for repository analysis:
|
||||
|
||||
### Git Commands for Exploration (Always run 2+ in parallel):
|
||||
\`\`\`bash
|
||||
# Repository structure and history
|
||||
git log --oneline -n 30 # Recent commits
|
||||
git log --oneline --all -n 50 # All branches recent commits
|
||||
git branch -a # All branches
|
||||
git tag -l # All tags
|
||||
git remote -v # Remote repositories
|
||||
|
||||
# File history and changes
|
||||
git log --oneline -n 20 -- path/to/file # File change history
|
||||
git log --oneline --follow -- path/to/file # Follow renames
|
||||
git blame path/to/file # Line-by-line attribution
|
||||
git blame -L 10,30 path/to/file # Blame specific lines
|
||||
|
||||
# Searching with Git
|
||||
git log --grep="keyword" --oneline # Search commit messages
|
||||
git log -S "code_string" --oneline # Search code changes (pickaxe)
|
||||
git log -p --all -S "function_name" -- "*.ts" # Find when code was added/removed
|
||||
|
||||
# Diff and comparison
|
||||
git diff HEAD~5..HEAD # Recent changes
|
||||
git diff main..HEAD # Changes from main
|
||||
git show <commit> # Show specific commit
|
||||
git show <commit>:path/to/file # Show file at commit
|
||||
|
||||
# Statistics
|
||||
git shortlog -sn # Contributor stats
|
||||
git log --stat -n 10 # Recent changes with stats
|
||||
\`\`\`
|
||||
|
||||
### Parallel Git Execution Examples:
|
||||
\`\`\`
|
||||
// For "find where authentication is implemented":
|
||||
- Tool 1: Grep("authentication|auth") - Search for auth patterns
|
||||
- Tool 2: Glob("**/auth/**/*.ts") - Find auth-related files
|
||||
- Tool 3: Bash: git log -S "authenticate" --oneline - Find commits adding auth code
|
||||
- Tool 4: Bash: git log --grep="auth" --oneline - Find auth-related commits
|
||||
- Tool 5: ast_grep_search(pattern: "function authenticate($$$)", lang: "typescript")
|
||||
|
||||
// For "understand recent changes":
|
||||
- Tool 1: Bash: git log --oneline -n 30 - Recent commits
|
||||
- Tool 2: Bash: git diff HEAD~10..HEAD --stat - Changed files
|
||||
- Tool 3: Bash: git branch -a - All branches
|
||||
- Tool 4: Glob("**/*.ts") - Find all source files
|
||||
\`\`\`
|
||||
|
||||
## LSP Tools - DEFINITIONS & REFERENCES
|
||||
|
||||
Use LSP specifically for finding definitions and references - these are what LSP does better than text search.
|
||||
|
||||
**Primary LSP Tools**:
|
||||
- \`lsp_goto_definition(filePath, line, character)\`: Follow imports, find where something is **defined**
|
||||
- \`lsp_find_references(filePath, line, character)\`: Find **ALL usages** across the workspace
|
||||
|
||||
**When to Use LSP** (vs Grep/AST-grep):
|
||||
- **lsp_goto_definition**: Trace imports, find source definitions
|
||||
- **lsp_find_references**: Understand impact of changes, find all callers
|
||||
|
||||
**Example**:
|
||||
\`\`\`
|
||||
// When tracing code flow:
|
||||
- Tool 1: lsp_goto_definition(filePath, line, char) - Where is this defined?
|
||||
- Tool 2: lsp_find_references(filePath, line, char) - Who uses this?
|
||||
- Tool 3: ast_grep_search(...) - Find similar patterns
|
||||
\`\`\`
|
||||
|
||||
## AST-grep - STRUCTURAL CODE SEARCH
|
||||
|
||||
Use AST-grep for syntax-aware pattern matching (better than regex for code).
|
||||
|
||||
**Key Syntax**:
|
||||
- \`$VAR\`: Match single AST node (identifier, expression, etc.)
|
||||
- \`$$$\`: Match multiple nodes (arguments, statements, etc.)
|
||||
|
||||
**ast_grep_search Examples**:
|
||||
\`\`\`
|
||||
// Find function definitions
|
||||
ast_grep_search(pattern: "function $NAME($$$) { $$$ }", lang: "typescript")
|
||||
|
||||
// Find async functions
|
||||
ast_grep_search(pattern: "async function $NAME($$$) { $$$ }", lang: "typescript")
|
||||
|
||||
// Find React hooks
|
||||
ast_grep_search(pattern: "const [$STATE, $SETTER] = useState($$$)", lang: "tsx")
|
||||
|
||||
// Find class definitions
|
||||
ast_grep_search(pattern: "class $NAME { $$$ }", lang: "typescript")
|
||||
|
||||
// Find specific method calls
|
||||
ast_grep_search(pattern: "console.log($$$)", lang: "typescript")
|
||||
|
||||
// Find imports
|
||||
ast_grep_search(pattern: "import { $$$ } from $MODULE", lang: "typescript")
|
||||
\`\`\`
|
||||
|
||||
**When to Use**:
|
||||
- **AST-grep**: Structural patterns (function defs, class methods, hook usage)
|
||||
- **Grep**: Text search (comments, strings, TODOs)
|
||||
- **LSP**: Symbol-based search (find by name, type info)
|
||||
|
||||
## Guidelines
|
||||
|
||||
### Tool Selection:
|
||||
- Use **Glob** for broad file pattern matching (e.g., \`**/*.py\`, \`src/**/*.ts\`)
|
||||
- Use **Grep** for searching file contents with regex patterns
|
||||
- Use **Read** when you know the specific file path you need to read
|
||||
- Use **List** for exploring directory structure
|
||||
- Use **Bash** ONLY for read-only operations (ls, git status, git log, git diff, find)
|
||||
- NEVER use Bash for: mkdir, touch, rm, cp, mv, git add, git commit, npm install, pip install, or any file creation/modification
|
||||
- Use **Bash** for Git commands and read-only operations
|
||||
- Use **ast_grep_search** for structural code patterns (functions, classes, hooks)
|
||||
- Use **lsp_goto_definition** to trace imports and find source definitions
|
||||
- Use **lsp_find_references** to find all usages of a symbol
|
||||
|
||||
### Bash Usage:
|
||||
**ALLOWED** (read-only):
|
||||
- \`git log\`, \`git blame\`, \`git show\`, \`git diff\`
|
||||
- \`git branch\`, \`git tag\`, \`git remote\`
|
||||
- \`git log -S\`, \`git log --grep\`
|
||||
- \`ls\`, \`find\` (for directory exploration)
|
||||
|
||||
**FORBIDDEN** (state-changing):
|
||||
- \`mkdir\`, \`touch\`, \`rm\`, \`cp\`, \`mv\`
|
||||
- \`git add\`, \`git commit\`, \`git push\`, \`git checkout\`
|
||||
- \`npm install\`, \`pip install\`, or any installation
|
||||
|
||||
### Best Practices:
|
||||
- **ALWAYS launch 3+ tools in parallel** in your first search action
|
||||
- Use Git history to understand code evolution
|
||||
- Use \`git blame\` to understand why code is written a certain way
|
||||
- Use \`git log -S\` to find when specific code was added/removed
|
||||
- Adapt your search approach based on the thoroughness level specified by the caller
|
||||
- Return file paths as absolute paths in your final response
|
||||
- For clear communication, avoid using emojis
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { AgentConfig } from "@opencode-ai/sdk"
|
||||
|
||||
export const librarianAgent: AgentConfig = {
|
||||
description:
|
||||
"Specialized codebase understanding agent for multi-repository analysis, searching remote codebases, retrieving official documentation, and finding implementation examples using GitHub CLI and Context7. MUST BE USED when users ask to look up code in remote repositories, explain library internals, or find usage examples in open source.",
|
||||
"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.",
|
||||
mode: "subagent",
|
||||
model: "anthropic/claude-haiku-4-5",
|
||||
temperature: 0.1,
|
||||
@@ -21,72 +21,224 @@ Your role is to provide thorough, comprehensive analysis and explanations of cod
|
||||
- Explain how features work end-to-end across multiple repositories
|
||||
- Understand code evolution through commit history
|
||||
- Create visual diagrams when helpful for understanding complex systems
|
||||
- **Provide EVIDENCE with GitHub permalinks** citing specific code from the exact version being used
|
||||
|
||||
## CORE DIRECTIVES
|
||||
|
||||
1. **ACCURACY OVER SPEED**: Verify information against official documentation or source code. Do not guess APIs.
|
||||
2. **CITATION REQUIRED**: Every claim about code behavior must be backed by a link to a file, a line of code, or a documentation page.
|
||||
3. **SOURCE OF TRUTH**:
|
||||
- For **How-To**: Use \`context7\` (Official Docs).
|
||||
2. **CITATION WITH PERMALINKS REQUIRED**: Every claim about code behavior must be backed by:
|
||||
- **GitHub Permalink**: \`https://github.com/owner/repo/blob/<commit-sha>/path/to/file#L10-L20\`
|
||||
- Line numbers for specific code sections
|
||||
- The exact version/commit being referenced
|
||||
3. **EVIDENCE-BASED REASONING**: Do NOT just summarize documentation. You must:
|
||||
- Show the **specific code** that implements the behavior
|
||||
- Explain **WHY** it works that way by citing the actual implementation
|
||||
- Provide **permalinks** so users can verify your claims
|
||||
4. **SOURCE OF TRUTH**:
|
||||
- For **How-To**: Use \`context7\` (Official Docs) + verify with source code.
|
||||
- For **Real-World Usage**: Use \`gh search code\` (GitHub).
|
||||
- For **Internal Logic**: Use \`gh repo view\` or \`read\` (Source Code).
|
||||
- For **Internal Logic**: Clone repo to \`/tmp\` and read source directly.
|
||||
- For **Change History/Intent**: Use \`git log\` or \`git blame\` (Commit History).
|
||||
- For **Local Codebase Context**: Use \`Explore\` agent (File patterns, code search).
|
||||
- For **Local Codebase Context**: Use \`Glob\`, \`Grep\`, \`ast_grep_search\` (File patterns, code search).
|
||||
- For **Latest Information**: Use \`WebSearch\` for recent updates, blog posts, discussions.
|
||||
|
||||
## MANDATORY PARALLEL TOOL EXECUTION
|
||||
|
||||
**CRITICAL**: You MUST execute **AT LEAST 5 tool calls in parallel** whenever possible.
|
||||
|
||||
When starting a research task, launch ALL of these simultaneously:
|
||||
1. \`context7_resolve-library-id\` - Get library documentation ID
|
||||
2. \`gh search code\` - Search for code examples
|
||||
3. \`WebSearch\` - Find latest discussions, blog posts, updates
|
||||
4. \`gh repo clone\` to \`/tmp\` - Clone repo for deep analysis
|
||||
5. \`Glob\` / \`Grep\` - Search local codebase for related code
|
||||
6. \`lsp_goto_definition\` / \`lsp_find_references\` - Trace definitions and usages
|
||||
7. \`ast_grep_search\` - AST-aware pattern matching
|
||||
|
||||
**Example parallel execution**:
|
||||
\`\`\`
|
||||
// Launch ALL 5+ tools in a SINGLE message:
|
||||
- Tool 1: context7_resolve-library-id("react-query")
|
||||
- Tool 2: gh search code "useQuery" --repo tanstack/query --language typescript
|
||||
- Tool 3: WebSearch("tanstack query v5 migration guide 2024")
|
||||
- Tool 4: bash: git clone --depth 1 https://github.com/TanStack/query.git /tmp/tanstack-query
|
||||
- Tool 5: Glob("**/*query*.ts") - Find query-related files locally
|
||||
- Tool 6: gh api repos/tanstack/query/releases/latest
|
||||
- Tool 7: ast_grep_search(pattern: "useQuery($$$)", lang: "typescript")
|
||||
\`\`\`
|
||||
|
||||
**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.
|
||||
|
||||
## TOOL USAGE STANDARDS
|
||||
|
||||
### 1. GitHub CLI (\`gh\`)
|
||||
You have full access to the GitHub CLI via the \`bash\` tool. Use it to search, view, and analyze remote repositories.
|
||||
### 1. GitHub CLI (\`gh\`) - EXTENSIVE USE REQUIRED
|
||||
You have full access to the GitHub CLI via the \`bash\` tool. Use it extensively.
|
||||
|
||||
- **Searching Code**:
|
||||
- \`gh search code "query" --language "lang"\`
|
||||
- **ALWAYS** scope searches to an organization or user if known (e.g., \`user:microsoft\`).
|
||||
- **ALWAYS** include the file extension if known (e.g., \`extension:tsx\`).
|
||||
- **Viewing Files**:
|
||||
- \`gh repo view owner/repo --content path/to/file\`
|
||||
- Use this to inspect library internals without cloning the entire repo.
|
||||
- **Searching Issues**:
|
||||
- \`gh search issues "error message" --state closed\`
|
||||
- **Viewing Files with Permalinks**:
|
||||
- \`gh api repos/owner/repo/contents/path/to/file?ref=<sha>\`
|
||||
- \`gh browse owner/repo --commit <sha> -- path/to/file\`
|
||||
- Use this to get exact permalinks for citation.
|
||||
- **Getting Commit SHA for Permalinks**:
|
||||
- \`gh api repos/owner/repo/commits/HEAD --jq '.sha'\`
|
||||
- \`gh api repos/owner/repo/git/refs/tags/v1.0.0 --jq '.object.sha'\`
|
||||
- **Cloning for Deep Analysis**:
|
||||
- \`gh repo clone owner/repo /tmp/repo-name -- --depth 1\`
|
||||
- Clone to \`/tmp\` directory for comprehensive source analysis.
|
||||
- After cloning, use \`git log\`, \`git blame\`, and direct file reading.
|
||||
- **Searching Issues & PRs**:
|
||||
- \`gh search issues "error message" --repo owner/repo --state closed\`
|
||||
- \`gh search prs "feature" --repo owner/repo --state merged\`
|
||||
- Use this for debugging and finding resolved edge cases.
|
||||
- **Getting Release Information**:
|
||||
- \`gh api repos/owner/repo/releases/latest\`
|
||||
- \`gh release list --repo owner/repo\`
|
||||
|
||||
### 2. Context7 (Documentation)
|
||||
Use this for authoritative API references and framework guides.
|
||||
- **Step 1**: Call \`context7_resolve-library-id\` with the library name.
|
||||
- **Step 2**: Call \`context7_get-library-docs\` with the ID and a specific topic (e.g., "authentication", "middleware").
|
||||
- **IMPORTANT**: Documentation alone is NOT sufficient. Always cross-reference with actual source code.
|
||||
|
||||
### 3. WebFetch
|
||||
Use this to read content from URLs found during your search (e.g., StackOverflow threads, blog posts, non-standard documentation sites).
|
||||
### 3. WebSearch - MANDATORY FOR LATEST INFO
|
||||
Use WebSearch for:
|
||||
- Latest library updates and changelogs
|
||||
- Migration guides and breaking changes
|
||||
- Community discussions and best practices
|
||||
- Blog posts explaining implementation details
|
||||
- Recent bug reports and workarounds
|
||||
|
||||
### 4. Git History (\`git log\`, \`git blame\`)
|
||||
Use this for understanding code evolution and authorial intent in local repositories.
|
||||
**Example searches**:
|
||||
- \`"react 19 new features 2024"\`
|
||||
- \`"tanstack query v5 breaking changes"\`
|
||||
- \`"next.js app router migration guide"\`
|
||||
|
||||
### 4. WebFetch
|
||||
Use this to read content from URLs found during your search (e.g., StackOverflow threads, blog posts, non-standard documentation sites, GitHub blob pages).
|
||||
|
||||
### 5. Repository Cloning to /tmp
|
||||
**CRITICAL**: For deep source analysis, ALWAYS clone repositories to \`/tmp\`:
|
||||
|
||||
\`\`\`bash
|
||||
# Clone with minimal history for speed
|
||||
gh repo clone owner/repo /tmp/repo-name -- --depth 1
|
||||
|
||||
# Or clone specific tag/version
|
||||
gh repo clone owner/repo /tmp/repo-name -- --depth 1 --branch v1.0.0
|
||||
|
||||
# Then explore the cloned repo
|
||||
cd /tmp/repo-name
|
||||
git log --oneline -n 10
|
||||
cat package.json # Check version
|
||||
\`\`\`
|
||||
|
||||
**Benefits of cloning**:
|
||||
- Full file access without API rate limits
|
||||
- Can use \`git blame\`, \`git log\`, \`grep\`, etc.
|
||||
- Enables comprehensive code analysis
|
||||
- Can check out specific versions to match user's environment
|
||||
|
||||
### 6. Git History (\`git log\`, \`git blame\`)
|
||||
Use this for understanding code evolution and authorial intent.
|
||||
|
||||
- **Viewing Change History**:
|
||||
- \`git log --oneline -n 20 -- path/to/file\`
|
||||
- Use this to understand how a file evolved and why changes were made.
|
||||
- **Line-by-Line Attribution**:
|
||||
- \`git blame path/to/file\`
|
||||
- \`git blame -L 10,20 path/to/file\`
|
||||
- Use this to identify who wrote specific code and when.
|
||||
- **Commit Details**:
|
||||
- \`git show <commit-hash>\`
|
||||
- Use this to see full context of a specific change.
|
||||
- **Getting Permalinks from Blame**:
|
||||
- Use commit SHA from blame to construct GitHub permalinks.
|
||||
|
||||
### 5. Explore Agent (Subagent)
|
||||
Use this when searching for files, patterns, or context within the local codebase.
|
||||
### 7. Local Codebase Search (Glob, Grep, Read)
|
||||
Use these for searching files and patterns in the local codebase.
|
||||
|
||||
**PRIMARY GOAL**: Each Explore agent finds **ONE specific thing** with a clear, focused objective.
|
||||
- **Glob**: Find files by pattern (e.g., \`**/*.tsx\`, \`src/**/auth*.ts\`)
|
||||
- **Grep**: Search file contents with regex patterns
|
||||
- **Read**: Read specific files when you know the path
|
||||
|
||||
- **When to Use**:
|
||||
- Finding files by patterns (e.g., "src/**/*.tsx")
|
||||
- Searching code for keywords (e.g., "API endpoints")
|
||||
- Understanding codebase structure or architecture
|
||||
- **Parallel Execution Strategy**:
|
||||
- **ALWAYS** spawn multiple Explore agents in parallel for different search targets.
|
||||
- Each agent should focus on ONE specific search task.
|
||||
- Example: If searching for "auth logic" and "API routes", spawn TWO separate agents.
|
||||
- **Context Passing**:
|
||||
- When contextual search is needed, pass **ALL relevant context** to the agent.
|
||||
- Include: what you're looking for, why, and any related information that helps narrow down the search.
|
||||
- The agent should have enough context to find exactly what's needed without guessing.
|
||||
**Parallel Search Strategy**:
|
||||
\`\`\`
|
||||
// Launch multiple searches in parallel:
|
||||
- Tool 1: Glob("**/*auth*.ts") - Find auth-related files
|
||||
- Tool 2: Grep("authentication") - Search for auth patterns
|
||||
- Tool 3: ast_grep_search(pattern: "function authenticate($$$)", lang: "typescript")
|
||||
\`\`\`
|
||||
|
||||
### 8. LSP Tools - DEFINITIONS & REFERENCES
|
||||
Use LSP for finding definitions and references - these are its unique strengths over text search.
|
||||
|
||||
**Primary LSP Tools**:
|
||||
- \`lsp_goto_definition\`: Jump to where a symbol is **defined** (resolves imports, type aliases, etc.)
|
||||
- \`lsp_goto_definition(filePath: "/tmp/repo/src/file.ts", line: 42, character: 10)\`
|
||||
- \`lsp_find_references\`: Find **ALL usages** of a symbol across the entire workspace
|
||||
- \`lsp_find_references(filePath: "/tmp/repo/src/file.ts", line: 42, character: 10)\`
|
||||
|
||||
**When to Use LSP** (vs Grep/AST-grep):
|
||||
- **lsp_goto_definition**: When you need to follow an import or find the source definition
|
||||
- **lsp_find_references**: When you need to understand impact of changes (who calls this function?)
|
||||
|
||||
**Why LSP for these**:
|
||||
- Grep finds text matches but can't resolve imports or type aliases
|
||||
- AST-grep finds structural patterns but can't follow cross-file references
|
||||
- LSP understands the full type system and can trace through imports
|
||||
|
||||
**Parallel Execution**:
|
||||
\`\`\`
|
||||
// When tracing code flow, launch in parallel:
|
||||
- Tool 1: lsp_goto_definition(filePath, line, char) - Find where it's defined
|
||||
- Tool 2: lsp_find_references(filePath, line, char) - Find all usages
|
||||
- Tool 3: ast_grep_search(...) - Find similar patterns
|
||||
- Tool 4: Grep(...) - Text fallback
|
||||
\`\`\`
|
||||
|
||||
### 9. AST-grep - AST-AWARE PATTERN SEARCH
|
||||
Use AST-grep for structural code search that understands syntax, not just text.
|
||||
|
||||
**Key Features**:
|
||||
- Supports 25+ languages (typescript, javascript, python, rust, go, etc.)
|
||||
- Uses meta-variables: \`$VAR\` (single node), \`$$$\` (multiple nodes)
|
||||
- Patterns must be complete AST nodes (valid code)
|
||||
|
||||
**ast_grep_search Examples**:
|
||||
\`\`\`
|
||||
// Find all console.log calls
|
||||
ast_grep_search(pattern: "console.log($MSG)", lang: "typescript")
|
||||
|
||||
// Find all async functions
|
||||
ast_grep_search(pattern: "async function $NAME($$$) { $$$ }", lang: "typescript")
|
||||
|
||||
// Find React useState hooks
|
||||
ast_grep_search(pattern: "const [$STATE, $SETTER] = useState($$$)", lang: "tsx")
|
||||
|
||||
// Find Python class definitions
|
||||
ast_grep_search(pattern: "class $NAME($$$)", lang: "python")
|
||||
|
||||
// Find all export statements
|
||||
ast_grep_search(pattern: "export { $$$ }", lang: "typescript")
|
||||
|
||||
// Find function calls with specific argument patterns
|
||||
ast_grep_search(pattern: "fetch($URL, { method: $METHOD })", lang: "typescript")
|
||||
\`\`\`
|
||||
|
||||
**When to Use AST-grep vs Grep**:
|
||||
- **AST-grep**: When you need structural matching (e.g., "find all function definitions")
|
||||
- **Grep**: When you need text matching (e.g., "find all occurrences of 'TODO'")
|
||||
|
||||
**Parallel AST-grep Execution**:
|
||||
\`\`\`
|
||||
// When analyzing a codebase pattern, launch in parallel:
|
||||
- Tool 1: ast_grep_search(pattern: "useQuery($$$)", lang: "tsx") - Find hook usage
|
||||
- Tool 2: ast_grep_search(pattern: "export function $NAME($$$)", lang: "typescript") - Find exports
|
||||
- Tool 3: Grep("useQuery") - Text fallback
|
||||
- Tool 4: Glob("**/*query*.ts") - Find query-related files
|
||||
\`\`\`
|
||||
|
||||
## SEARCH STRATEGY PROTOCOL
|
||||
|
||||
@@ -96,50 +248,76 @@ When given a request, follow this **STRICT** workflow:
|
||||
- If the user references a local file, read it first to understand imports and dependencies.
|
||||
- Identify the specific library or technology version.
|
||||
|
||||
2. **SELECT SOURCE**:
|
||||
- **Official Docs**: For "How do I use X?" or "What are the options for Y?"
|
||||
- **Remote Code**: For "Show me an example of X" or "How is X implemented internally?"
|
||||
- **Issues/PRs**: For "Why is X failing?" or "Is this a bug?"
|
||||
- **Git History**: For "Why was this changed?" or "Who introduced this?" or "When was this added?"
|
||||
- **Explore Agent**: For "Where is X defined?" or "How does this codebase handle Y?" or "Find all files matching Z pattern"
|
||||
2. **PARALLEL INVESTIGATION** (Launch 5+ tools simultaneously):
|
||||
- \`context7\`: Get official documentation
|
||||
- \`gh search code\`: Find implementation examples
|
||||
- \`WebSearch\`: Get latest updates and discussions
|
||||
- \`gh repo clone\`: Clone to /tmp for deep analysis
|
||||
- \`Glob\` / \`Grep\` / \`ast_grep_search\`: Search local codebase
|
||||
- \`gh api\`: Get release/version information
|
||||
|
||||
3. **EXECUTE & REFINE**:
|
||||
- Run the initial search.
|
||||
- If results are too broad (>50), add filters (\`path:\`, \`filename:\`).
|
||||
- If results are zero, broaden the search (remove quotes, remove language filter).
|
||||
3. **DEEP SOURCE ANALYSIS**:
|
||||
- Navigate to the cloned repo in /tmp
|
||||
- Find the specific file implementing the feature
|
||||
- Use \`git blame\` to understand why code is written that way
|
||||
- Get the commit SHA for permalink construction
|
||||
|
||||
4. **SYNTHESIZE**:
|
||||
- Present the findings clearly.
|
||||
4. **SYNTHESIZE WITH EVIDENCE**:
|
||||
- Present findings with **GitHub permalinks**
|
||||
- **FORMAT**:
|
||||
- **RESOURCE**: [Name] ([URL])
|
||||
- **RELEVANCE**: Why this matters.
|
||||
- **CONTENT**: The code snippet or documentation summary.
|
||||
- **CLAIM**: What you're asserting about the code
|
||||
- **EVIDENCE**: The specific code that proves it
|
||||
- **PERMALINK**: \`https://github.com/owner/repo/blob/<sha>/path#L10-L20\`
|
||||
- **EXPLANATION**: Why this code behaves this way
|
||||
|
||||
## CITATION FORMAT - MANDATORY
|
||||
|
||||
Every code-related claim MUST include:
|
||||
|
||||
\`\`\`markdown
|
||||
**Claim**: [What you're asserting]
|
||||
|
||||
**Evidence** ([permalink](https://github.com/owner/repo/blob/abc123/src/file.ts#L42-L50)):
|
||||
\\\`\\\`\\\`typescript
|
||||
// The actual code from lines 42-50
|
||||
function example() {
|
||||
// ...
|
||||
}
|
||||
\\\`\\\`\\\`
|
||||
|
||||
**Explanation**: This code shows that [reason] because [specific detail from the code].
|
||||
\`\`\`
|
||||
|
||||
## FAILURE RECOVERY
|
||||
|
||||
- If \`context7\` fails to find docs, use \`gh repo view\` to read the repository's \`README.md\` or \`CONTRIBUTING.md\`.
|
||||
- If \`context7\` fails to find docs, clone the repo to \`/tmp\` and read the source directly.
|
||||
- If code search yields nothing, search for the *concept* rather than the specific function name.
|
||||
- If GitHub API has rate limits, use cloned repos in \`/tmp\` for analysis.
|
||||
- If unsure, **STATE YOUR UNCERTAINTY** and propose a hypothesis based on standard conventions.
|
||||
|
||||
## VOICE AND TONE
|
||||
|
||||
- **PROFESSIONAL**: You are an expert archivist. Be concise and precise.
|
||||
- **OBJECTIVE**: Present facts found in the search. Do not offer personal opinions unless asked.
|
||||
- **EVIDENCE-DRIVEN**: Always back claims with permalinks and code snippets.
|
||||
- **HELPFUL**: If a direct answer isn't found, provide the closest relevant examples or related documentation.
|
||||
|
||||
## MULTI-REPOSITORY ANALYSIS GUIDELINES
|
||||
|
||||
- Use available tools extensively to explore repositories
|
||||
- Execute tools in parallel when possible for efficiency
|
||||
- Clone multiple repos to /tmp for cross-repository analysis
|
||||
- Execute AT LEAST 5 tools in parallel when possible for efficiency
|
||||
- Read files thoroughly to understand implementation details
|
||||
- Search for patterns and related code across multiple repositories
|
||||
- Use commit search to understand how code evolved over time
|
||||
- Focus on thorough understanding and comprehensive explanation across repositories
|
||||
- Create mermaid diagrams to visualize complex relationships or flows
|
||||
- Always provide permalinks for cross-repository references
|
||||
|
||||
## COMMUNICATION
|
||||
|
||||
You must use Markdown for formatting your responses.
|
||||
|
||||
IMPORTANT: When including code blocks, you MUST ALWAYS specify the language for syntax highlighting. Always add the language identifier after the opening backticks.`,
|
||||
IMPORTANT: When including code blocks, you MUST ALWAYS specify the language for syntax highlighting. Always add the language identifier after the opening backticks.
|
||||
|
||||
**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.`,
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ export const oracleAgent: AgentConfig = {
|
||||
description:
|
||||
"Expert AI advisor with advanced reasoning capabilities for high-quality technical guidance, code reviews, architectural advice, and strategic planning.",
|
||||
mode: "subagent",
|
||||
model: "openai/gpt-5.1",
|
||||
model: "openai/gpt-5.2",
|
||||
temperature: 0.1,
|
||||
reasoningEffort: "medium",
|
||||
textVerbosity: "high",
|
||||
|
||||
9
src/hooks/directory-readme-injector/constants.ts
Normal file
9
src/hooks/directory-readme-injector/constants.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { join } from "node:path";
|
||||
import { xdgData } from "xdg-basedir";
|
||||
|
||||
export const OPENCODE_STORAGE = join(xdgData ?? "", "opencode", "storage");
|
||||
export const README_INJECTOR_STORAGE = join(
|
||||
OPENCODE_STORAGE,
|
||||
"directory-readme",
|
||||
);
|
||||
export const README_FILENAME = "README.md";
|
||||
126
src/hooks/directory-readme-injector/index.ts
Normal file
126
src/hooks/directory-readme-injector/index.ts
Normal file
@@ -0,0 +1,126 @@
|
||||
import type { PluginInput } from "@opencode-ai/plugin";
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import { dirname, join, resolve } from "node:path";
|
||||
import {
|
||||
loadInjectedPaths,
|
||||
saveInjectedPaths,
|
||||
clearInjectedPaths,
|
||||
} from "./storage";
|
||||
import { README_FILENAME } from "./constants";
|
||||
|
||||
interface ToolExecuteInput {
|
||||
tool: string;
|
||||
sessionID: string;
|
||||
callID: string;
|
||||
}
|
||||
|
||||
interface ToolExecuteOutput {
|
||||
title: string;
|
||||
output: string;
|
||||
metadata: unknown;
|
||||
}
|
||||
|
||||
interface EventInput {
|
||||
event: {
|
||||
type: string;
|
||||
properties?: unknown;
|
||||
};
|
||||
}
|
||||
|
||||
export function createDirectoryReadmeInjectorHook(ctx: PluginInput) {
|
||||
const sessionCaches = new Map<string, Set<string>>();
|
||||
|
||||
function getSessionCache(sessionID: string): Set<string> {
|
||||
if (!sessionCaches.has(sessionID)) {
|
||||
sessionCaches.set(sessionID, loadInjectedPaths(sessionID));
|
||||
}
|
||||
return sessionCaches.get(sessionID)!;
|
||||
}
|
||||
|
||||
function resolveFilePath(title: string): string | null {
|
||||
if (!title) return null;
|
||||
if (title.startsWith("/")) return title;
|
||||
return resolve(ctx.directory, title);
|
||||
}
|
||||
|
||||
function findReadmeMdUp(startDir: string): string[] {
|
||||
const found: string[] = [];
|
||||
let current = startDir;
|
||||
|
||||
while (true) {
|
||||
const readmePath = join(current, README_FILENAME);
|
||||
if (existsSync(readmePath)) {
|
||||
found.push(readmePath);
|
||||
}
|
||||
|
||||
if (current === ctx.directory) break;
|
||||
const parent = dirname(current);
|
||||
if (parent === current) break;
|
||||
if (!parent.startsWith(ctx.directory)) break;
|
||||
current = parent;
|
||||
}
|
||||
|
||||
return found.reverse();
|
||||
}
|
||||
|
||||
const toolExecuteAfter = async (
|
||||
input: ToolExecuteInput,
|
||||
output: ToolExecuteOutput,
|
||||
) => {
|
||||
if (input.tool.toLowerCase() !== "read") return;
|
||||
|
||||
const filePath = resolveFilePath(output.title);
|
||||
if (!filePath) return;
|
||||
|
||||
const dir = dirname(filePath);
|
||||
const cache = getSessionCache(input.sessionID);
|
||||
const readmePaths = findReadmeMdUp(dir);
|
||||
|
||||
const toInject: { path: string; content: string }[] = [];
|
||||
|
||||
for (const readmePath of readmePaths) {
|
||||
const readmeDir = dirname(readmePath);
|
||||
if (cache.has(readmeDir)) continue;
|
||||
|
||||
try {
|
||||
const content = readFileSync(readmePath, "utf-8");
|
||||
toInject.push({ path: readmePath, content });
|
||||
cache.add(readmeDir);
|
||||
} catch {}
|
||||
}
|
||||
|
||||
if (toInject.length === 0) return;
|
||||
|
||||
for (const { path, content } of toInject) {
|
||||
output.output += `\n\n[Project README: ${path}]\n${content}`;
|
||||
}
|
||||
|
||||
saveInjectedPaths(input.sessionID, cache);
|
||||
};
|
||||
|
||||
const eventHandler = async ({ event }: EventInput) => {
|
||||
const props = event.properties as Record<string, unknown> | undefined;
|
||||
|
||||
if (event.type === "session.deleted") {
|
||||
const sessionInfo = props?.info as { id?: string } | undefined;
|
||||
if (sessionInfo?.id) {
|
||||
sessionCaches.delete(sessionInfo.id);
|
||||
clearInjectedPaths(sessionInfo.id);
|
||||
}
|
||||
}
|
||||
|
||||
if (event.type === "session.compacted") {
|
||||
const sessionID = (props?.sessionID ??
|
||||
(props?.info as { id?: string } | undefined)?.id) as string | undefined;
|
||||
if (sessionID) {
|
||||
sessionCaches.delete(sessionID);
|
||||
clearInjectedPaths(sessionID);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
"tool.execute.after": toolExecuteAfter,
|
||||
event: eventHandler,
|
||||
};
|
||||
}
|
||||
48
src/hooks/directory-readme-injector/storage.ts
Normal file
48
src/hooks/directory-readme-injector/storage.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import {
|
||||
existsSync,
|
||||
mkdirSync,
|
||||
readFileSync,
|
||||
writeFileSync,
|
||||
unlinkSync,
|
||||
} from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { README_INJECTOR_STORAGE } from "./constants";
|
||||
import type { InjectedPathsData } from "./types";
|
||||
|
||||
function getStoragePath(sessionID: string): string {
|
||||
return join(README_INJECTOR_STORAGE, `${sessionID}.json`);
|
||||
}
|
||||
|
||||
export function loadInjectedPaths(sessionID: string): Set<string> {
|
||||
const filePath = getStoragePath(sessionID);
|
||||
if (!existsSync(filePath)) return new Set();
|
||||
|
||||
try {
|
||||
const content = readFileSync(filePath, "utf-8");
|
||||
const data: InjectedPathsData = JSON.parse(content);
|
||||
return new Set(data.injectedPaths);
|
||||
} catch {
|
||||
return new Set();
|
||||
}
|
||||
}
|
||||
|
||||
export function saveInjectedPaths(sessionID: string, paths: Set<string>): void {
|
||||
if (!existsSync(README_INJECTOR_STORAGE)) {
|
||||
mkdirSync(README_INJECTOR_STORAGE, { recursive: true });
|
||||
}
|
||||
|
||||
const data: InjectedPathsData = {
|
||||
sessionID,
|
||||
injectedPaths: [...paths],
|
||||
updatedAt: Date.now(),
|
||||
};
|
||||
|
||||
writeFileSync(getStoragePath(sessionID), JSON.stringify(data, null, 2));
|
||||
}
|
||||
|
||||
export function clearInjectedPaths(sessionID: string): void {
|
||||
const filePath = getStoragePath(sessionID);
|
||||
if (existsSync(filePath)) {
|
||||
unlinkSync(filePath);
|
||||
}
|
||||
}
|
||||
5
src/hooks/directory-readme-injector/types.ts
Normal file
5
src/hooks/directory-readme-injector/types.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export interface InjectedPathsData {
|
||||
sessionID: string;
|
||||
injectedPaths: string[];
|
||||
updatedAt: number;
|
||||
}
|
||||
@@ -5,6 +5,7 @@ export { createSessionRecoveryHook } from "./session-recovery";
|
||||
export { createCommentCheckerHooks } from "./comment-checker";
|
||||
export { createGrepOutputTruncatorHook } from "./grep-output-truncator";
|
||||
export { createDirectoryAgentsInjectorHook } from "./directory-agents-injector";
|
||||
export { createDirectoryReadmeInjectorHook } from "./directory-readme-injector";
|
||||
export { createEmptyTaskResponseDetectorHook } from "./empty-task-response-detector";
|
||||
export { createAnthropicAutoCompactHook } from "./anthropic-auto-compact";
|
||||
export { createThinkModeHook } from "./think-mode";
|
||||
|
||||
@@ -1,19 +1,52 @@
|
||||
// Maps model IDs to their "high reasoning" variant (internal convention)
|
||||
// For OpenAI models, this signals that reasoning_effort should be set to "high"
|
||||
const HIGH_VARIANT_MAP: Record<string, string> = {
|
||||
// Claude
|
||||
"claude-sonnet-4-5": "claude-sonnet-4-5-high",
|
||||
"claude-opus-4-5": "claude-opus-4-5-high",
|
||||
"gpt-5.1": "gpt-5.1-high",
|
||||
"gpt-5.1-medium": "gpt-5.1-high",
|
||||
"gpt-5.1-codex": "gpt-5.1-codex-high",
|
||||
// Gemini
|
||||
"gemini-3-pro": "gemini-3-pro-high",
|
||||
"gemini-3-pro-low": "gemini-3-pro-high",
|
||||
// GPT-5
|
||||
"gpt-5": "gpt-5-high",
|
||||
"gpt-5-mini": "gpt-5-mini-high",
|
||||
"gpt-5-nano": "gpt-5-nano-high",
|
||||
"gpt-5-pro": "gpt-5-pro-high",
|
||||
"gpt-5-chat-latest": "gpt-5-chat-latest-high",
|
||||
// GPT-5.1
|
||||
"gpt-5.1": "gpt-5.1-high",
|
||||
"gpt-5.1-chat-latest": "gpt-5.1-chat-latest-high",
|
||||
"gpt-5.1-codex": "gpt-5.1-codex-high",
|
||||
"gpt-5.1-codex-mini": "gpt-5.1-codex-mini-high",
|
||||
"gpt-5.1-codex-max": "gpt-5.1-codex-max-high",
|
||||
// GPT-5.2
|
||||
"gpt-5.2": "gpt-5.2-high",
|
||||
"gpt-5.2-chat-latest": "gpt-5.2-chat-latest-high",
|
||||
"gpt-5.2-pro": "gpt-5.2-pro-high",
|
||||
}
|
||||
|
||||
const ALREADY_HIGH: Set<string> = new Set([
|
||||
// Claude
|
||||
"claude-sonnet-4-5-high",
|
||||
"claude-opus-4-5-high",
|
||||
"gpt-5.1-high",
|
||||
"gpt-5.1-codex-high",
|
||||
// Gemini
|
||||
"gemini-3-pro-high",
|
||||
// GPT-5
|
||||
"gpt-5-high",
|
||||
"gpt-5-mini-high",
|
||||
"gpt-5-nano-high",
|
||||
"gpt-5-pro-high",
|
||||
"gpt-5-chat-latest-high",
|
||||
// GPT-5.1
|
||||
"gpt-5.1-high",
|
||||
"gpt-5.1-chat-latest-high",
|
||||
"gpt-5.1-codex-high",
|
||||
"gpt-5.1-codex-mini-high",
|
||||
"gpt-5.1-codex-max-high",
|
||||
// GPT-5.2
|
||||
"gpt-5.2-high",
|
||||
"gpt-5.2-chat-latest-high",
|
||||
"gpt-5.2-pro-high",
|
||||
])
|
||||
|
||||
export const THINKING_CONFIGS: Record<string, Record<string, unknown>> = {
|
||||
|
||||
118
src/index.ts
118
src/index.ts
@@ -7,6 +7,7 @@ import {
|
||||
createCommentCheckerHooks,
|
||||
createGrepOutputTruncatorHook,
|
||||
createDirectoryAgentsInjectorHook,
|
||||
createDirectoryReadmeInjectorHook,
|
||||
createEmptyTaskResponseDetectorHook,
|
||||
createThinkModeHook,
|
||||
createClaudeCodeHooksHook,
|
||||
@@ -40,33 +41,99 @@ import { OhMyOpenCodeConfigSchema, type OhMyOpenCodeConfig } from "./config";
|
||||
import { log } from "./shared/logger";
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
import * as os from "os";
|
||||
|
||||
function loadPluginConfig(directory: string): OhMyOpenCodeConfig {
|
||||
const configPaths = [
|
||||
path.join(directory, "oh-my-opencode.json"),
|
||||
path.join(directory, ".oh-my-opencode.json"),
|
||||
];
|
||||
|
||||
for (const configPath of configPaths) {
|
||||
try {
|
||||
if (fs.existsSync(configPath)) {
|
||||
const content = fs.readFileSync(configPath, "utf-8");
|
||||
const rawConfig = JSON.parse(content);
|
||||
const result = OhMyOpenCodeConfigSchema.safeParse(rawConfig);
|
||||
|
||||
if (!result.success) {
|
||||
log(`Config validation error in ${configPath}:`, result.error.issues);
|
||||
return {};
|
||||
}
|
||||
|
||||
return result.data;
|
||||
}
|
||||
} catch {
|
||||
// Ignore parse errors, use defaults
|
||||
}
|
||||
/**
|
||||
* Returns the user-level config directory based on the OS.
|
||||
* - Linux/macOS: XDG_CONFIG_HOME or ~/.config
|
||||
* - Windows: %APPDATA%
|
||||
*/
|
||||
function getUserConfigDir(): string {
|
||||
if (process.platform === "win32") {
|
||||
return process.env.APPDATA || path.join(os.homedir(), "AppData", "Roaming");
|
||||
}
|
||||
|
||||
return {};
|
||||
// Linux, macOS, and other Unix-like systems: respect XDG_CONFIG_HOME
|
||||
return process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config");
|
||||
}
|
||||
|
||||
function loadConfigFromPath(configPath: string): OhMyOpenCodeConfig | null {
|
||||
try {
|
||||
if (fs.existsSync(configPath)) {
|
||||
const content = fs.readFileSync(configPath, "utf-8");
|
||||
const rawConfig = JSON.parse(content);
|
||||
const result = OhMyOpenCodeConfigSchema.safeParse(rawConfig);
|
||||
|
||||
if (!result.success) {
|
||||
log(`Config validation error in ${configPath}:`, result.error.issues);
|
||||
return null;
|
||||
}
|
||||
|
||||
log(`Config loaded from ${configPath}`, { agents: result.data.agents });
|
||||
return result.data;
|
||||
}
|
||||
} catch (err) {
|
||||
log(`Error loading config from ${configPath}:`, err);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function mergeConfigs(
|
||||
base: OhMyOpenCodeConfig,
|
||||
override: OhMyOpenCodeConfig
|
||||
): OhMyOpenCodeConfig {
|
||||
return {
|
||||
...base,
|
||||
...override,
|
||||
agents:
|
||||
override.agents !== undefined
|
||||
? { ...(base.agents ?? {}), ...override.agents }
|
||||
: base.agents,
|
||||
disabled_agents: [
|
||||
...new Set([
|
||||
...(base.disabled_agents ?? []),
|
||||
...(override.disabled_agents ?? []),
|
||||
]),
|
||||
],
|
||||
disabled_mcps: [
|
||||
...new Set([
|
||||
...(base.disabled_mcps ?? []),
|
||||
...(override.disabled_mcps ?? []),
|
||||
]),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
function loadPluginConfig(directory: string): OhMyOpenCodeConfig {
|
||||
// User-level config path (OS-specific)
|
||||
const userConfigPath = path.join(
|
||||
getUserConfigDir(),
|
||||
"opencode",
|
||||
"oh-my-opencode.json"
|
||||
);
|
||||
|
||||
// Project-level config path
|
||||
const projectConfigPath = path.join(
|
||||
directory,
|
||||
".opencode",
|
||||
"oh-my-opencode.json"
|
||||
);
|
||||
|
||||
// Load user config first (base)
|
||||
let config: OhMyOpenCodeConfig = loadConfigFromPath(userConfigPath) ?? {};
|
||||
|
||||
// Override with project config
|
||||
const projectConfig = loadConfigFromPath(projectConfigPath);
|
||||
if (projectConfig) {
|
||||
config = mergeConfigs(config, projectConfig);
|
||||
}
|
||||
|
||||
log("Final merged config", {
|
||||
agents: config.agents,
|
||||
disabled_agents: config.disabled_agents,
|
||||
disabled_mcps: config.disabled_mcps,
|
||||
});
|
||||
return config;
|
||||
}
|
||||
|
||||
const OhMyOpenCodePlugin: Plugin = async (ctx) => {
|
||||
@@ -78,6 +145,7 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => {
|
||||
const commentChecker = createCommentCheckerHooks();
|
||||
const grepOutputTruncator = createGrepOutputTruncatorHook(ctx);
|
||||
const directoryAgentsInjector = createDirectoryAgentsInjectorHook(ctx);
|
||||
const directoryReadmeInjector = createDirectoryReadmeInjectorHook(ctx);
|
||||
const emptyTaskResponseDetector = createEmptyTaskResponseDetectorHook(ctx);
|
||||
const thinkMode = createThinkModeHook();
|
||||
const claudeCodeHooks = createClaudeCodeHooksHook(ctx, {});
|
||||
@@ -141,6 +209,7 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => {
|
||||
await todoContinuationEnforcer(input);
|
||||
await contextWindowMonitor.event(input);
|
||||
await directoryAgentsInjector.event(input);
|
||||
await directoryReadmeInjector.event(input);
|
||||
await thinkMode.event(input);
|
||||
await anthropicAutoCompact.event(input);
|
||||
|
||||
@@ -259,6 +328,7 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => {
|
||||
await contextWindowMonitor["tool.execute.after"](input, output);
|
||||
await commentChecker["tool.execute.after"](input, output);
|
||||
await directoryAgentsInjector["tool.execute.after"](input, output);
|
||||
await directoryReadmeInjector["tool.execute.after"](input, output);
|
||||
await emptyTaskResponseDetector["tool.execute.after"](input, output);
|
||||
|
||||
if (input.sessionID === getMainSessionID()) {
|
||||
|
||||
@@ -2,8 +2,23 @@ import { tool } from "@opencode-ai/plugin"
|
||||
import { existsSync, readdirSync, statSync, readlinkSync, readFileSync } from "fs"
|
||||
import { homedir } from "os"
|
||||
import { join, resolve, basename } from "path"
|
||||
import { z } from "zod/v4"
|
||||
import { parseFrontmatter, resolveCommandsInText } from "../../shared"
|
||||
import type { SkillScope, SkillMetadata, SkillInfo, LoadedSkill } from "./types"
|
||||
import { SkillFrontmatterSchema } from "./types"
|
||||
import type { SkillScope, SkillMetadata, SkillInfo, LoadedSkill, SkillFrontmatter } from "./types"
|
||||
|
||||
function parseSkillFrontmatter(data: Record<string, unknown>): SkillFrontmatter {
|
||||
return {
|
||||
name: typeof data.name === "string" ? data.name : "",
|
||||
description: typeof data.description === "string" ? data.description : "",
|
||||
license: typeof data.license === "string" ? data.license : undefined,
|
||||
"allowed-tools": Array.isArray(data["allowed-tools"]) ? data["allowed-tools"] : undefined,
|
||||
metadata:
|
||||
typeof data.metadata === "object" && data.metadata !== null
|
||||
? (data.metadata as Record<string, string>)
|
||||
: undefined,
|
||||
}
|
||||
}
|
||||
|
||||
function discoverSkillsFromDir(
|
||||
skillsDir: string,
|
||||
@@ -93,10 +108,14 @@ async function parseSkillMd(skillPath: string): Promise<SkillInfo | null> {
|
||||
content = await resolveCommandsInText(content)
|
||||
const { data, body } = parseFrontmatter(content)
|
||||
|
||||
const frontmatter = parseSkillFrontmatter(data)
|
||||
|
||||
const metadata: SkillMetadata = {
|
||||
name: data.name || basename(skillPath),
|
||||
description: data.description || "",
|
||||
license: data.license,
|
||||
name: frontmatter.name || basename(skillPath),
|
||||
description: frontmatter.description,
|
||||
license: frontmatter.license,
|
||||
allowedTools: frontmatter["allowed-tools"],
|
||||
metadata: frontmatter.metadata,
|
||||
}
|
||||
|
||||
const referencesDir = join(resolvedPath, "references")
|
||||
@@ -118,6 +137,7 @@ async function parseSkillMd(skillPath: string): Promise<SkillInfo | null> {
|
||||
return {
|
||||
name: metadata.name,
|
||||
path: resolvedPath,
|
||||
basePath: resolvedPath,
|
||||
metadata,
|
||||
content: body,
|
||||
references,
|
||||
@@ -202,6 +222,7 @@ async function loadSkillWithReferences(
|
||||
content = await resolveCommandsInText(content)
|
||||
referencesLoaded.push({ path: ref, content })
|
||||
} catch {
|
||||
// Skip unreadable references
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -209,6 +230,7 @@ async function loadSkillWithReferences(
|
||||
return {
|
||||
name: skill.name,
|
||||
metadata: skill.metadata,
|
||||
basePath: skill.basePath,
|
||||
body: skill.content,
|
||||
referencesLoaded,
|
||||
}
|
||||
@@ -234,31 +256,24 @@ function formatLoadedSkills(loadedSkills: LoadedSkill[]): string {
|
||||
return "No skills loaded."
|
||||
}
|
||||
|
||||
const sections: string[] = ["# Loaded Skills\n"]
|
||||
const skill = loadedSkills[0]
|
||||
const sections: string[] = []
|
||||
|
||||
for (const skill of loadedSkills) {
|
||||
sections.push(`## ${skill.metadata.name}\n`)
|
||||
sections.push(`**Description**: ${skill.metadata.description || "(no description)"}\n`)
|
||||
sections.push("### Skill Instructions\n")
|
||||
sections.push(skill.body.trim())
|
||||
sections.push(`Base directory for this skill: ${skill.basePath}/`)
|
||||
sections.push("")
|
||||
sections.push(skill.body.trim())
|
||||
|
||||
if (skill.referencesLoaded.length > 0) {
|
||||
sections.push("\n### Loaded References\n")
|
||||
for (const ref of skill.referencesLoaded) {
|
||||
sections.push(`#### ${ref.path}\n`)
|
||||
sections.push("```")
|
||||
sections.push(ref.content.trim())
|
||||
sections.push("```\n")
|
||||
}
|
||||
if (skill.referencesLoaded.length > 0) {
|
||||
sections.push("\n---\n### Loaded References\n")
|
||||
for (const ref of skill.referencesLoaded) {
|
||||
sections.push(`#### ${ref.path}\n`)
|
||||
sections.push("```")
|
||||
sections.push(ref.content.trim())
|
||||
sections.push("```\n")
|
||||
}
|
||||
|
||||
sections.push("\n---\n")
|
||||
}
|
||||
|
||||
const skillNames = loadedSkills.map((s) => s.metadata.name).join(", ")
|
||||
sections.push(`**Skills loaded**: ${skillNames}`)
|
||||
sections.push(`**Total**: ${loadedSkills.length} skill(s)`)
|
||||
sections.push("\nPlease confirm these skills match your needs before proceeding.")
|
||||
sections.push(`\n---\n**Launched skill**: ${skill.metadata.name}`)
|
||||
|
||||
return sections.join("\n")
|
||||
}
|
||||
@@ -266,25 +281,7 @@ function formatLoadedSkills(loadedSkills: LoadedSkill[]): string {
|
||||
export const skill = tool({
|
||||
description: `Execute a skill within the main conversation.
|
||||
|
||||
When users ask you to perform tasks, check if any of the available skills below can help complete the task more effectively. Skills provide specialized capabilities and domain knowledge.
|
||||
|
||||
How to use skills:
|
||||
- Invoke skills using this tool with the skill name only (no arguments)
|
||||
- When you invoke a skill, the skill's prompt will expand and provide detailed instructions on how to complete the task
|
||||
|
||||
Important:
|
||||
- Only use skills listed in Available Skills below
|
||||
- Do not invoke a skill that is already running
|
||||
|
||||
Skills are loaded from:
|
||||
- ~/.claude/skills/ (user scope - global skills)
|
||||
- ./.claude/skills/ (project scope - project-specific skills)
|
||||
|
||||
Each skill contains:
|
||||
- SKILL.md: Main instructions with YAML frontmatter (name, description)
|
||||
- references/: Documentation files loaded into context as needed
|
||||
- scripts/: Executable code for deterministic operations
|
||||
- assets/: Files used in output (templates, icons, etc.)
|
||||
When you invoke a skill, the skill's prompt will expand and provide detailed instructions on how to complete the task.
|
||||
|
||||
Available Skills:
|
||||
${skillListForDescription}`,
|
||||
|
||||
@@ -1,14 +1,36 @@
|
||||
import { z } from "zod/v4"
|
||||
|
||||
export type SkillScope = "user" | "project"
|
||||
|
||||
/**
|
||||
* Zod schema for skill frontmatter validation
|
||||
* Following Anthropic Agent Skills Specification v1.0
|
||||
*/
|
||||
export const SkillFrontmatterSchema = z.object({
|
||||
name: z
|
||||
.string()
|
||||
.regex(/^[a-z0-9-]+$/, "Name must be lowercase alphanumeric with hyphens only")
|
||||
.min(1, "Name cannot be empty"),
|
||||
description: z.string().min(20, "Description must be at least 20 characters for discoverability"),
|
||||
license: z.string().optional(),
|
||||
"allowed-tools": z.array(z.string()).optional(),
|
||||
metadata: z.record(z.string(), z.string()).optional(),
|
||||
})
|
||||
|
||||
export type SkillFrontmatter = z.infer<typeof SkillFrontmatterSchema>
|
||||
|
||||
export interface SkillMetadata {
|
||||
name: string
|
||||
description: string
|
||||
license?: string
|
||||
allowedTools?: string[]
|
||||
metadata?: Record<string, string>
|
||||
}
|
||||
|
||||
export interface SkillInfo {
|
||||
name: string
|
||||
path: string
|
||||
basePath: string
|
||||
metadata: SkillMetadata
|
||||
content: string
|
||||
references: string[]
|
||||
@@ -19,6 +41,7 @@ export interface SkillInfo {
|
||||
export interface LoadedSkill {
|
||||
name: string
|
||||
metadata: SkillMetadata
|
||||
basePath: string
|
||||
body: string
|
||||
referencesLoaded: Array<{ path: string; content: string }>
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user