Compare commits

...

15 Commits

Author SHA1 Message Date
github-actions[bot]
edf0e7d946 release: v0.1.12 2025-12-05 02:10:36 +00:00
YeonGyu-Kim
baa7fadab1 fix(comment-checker): use runtime wasm path resolution instead of require.resolve
require.resolve() was evaluated at build time, hardcoding CI paths.
Now uses import.meta.resolve() at runtime to find wasm files.
2025-12-05 11:09:41 +09:00
github-actions[bot]
a06bbeb9ee release: v0.1.11 2025-12-05 02:05:44 +00:00
YeonGyu-Kim
f3a92db203 chore: bump version to 0.1.12 2025-12-05 11:02:43 +09:00
YeonGyu-Kim
fd6e230889 perf(comment-checker): add LSP-style background language warming
- Warmup common languages (python, typescript, javascript, tsx, go, rust, java) on plugin init
- Non-blocking background initialization using Promise.then() pattern
- First parse call uses pre-cached language - zero user wait time
- Refactor parser manager with ManagedLanguage interface for better state tracking
2025-12-05 11:02:35 +09:00
YeonGyu-Kim
50ea492065 chore: bump version to 0.1.11 2025-12-05 10:56:21 +09:00
YeonGyu-Kim
f5f2053b7a fix(comment-checker): fix error skip bug and add parser/language caching
- Fix overly broad error detection that skipped comments when LSP warnings present
- Add Parser class and language WASM caching for ~3.5x faster subsequent parses
- Add debug logging controlled by COMMENT_CHECKER_DEBUG=1 env var
2025-12-05 10:56:08 +09:00
github-actions[bot]
6c16baea9a release: v0.1.10 2025-12-05 01:00:03 +00:00
YeonGyu-Kim
2ad7e193fd fix(comment-checker): support args.path for OpenCode Write/Edit tools 2025-12-05 09:57:50 +09:00
github-actions[bot]
d62f1dd207 release: v0.1.9 2025-12-05 00:45:40 +00:00
YeonGyu-Kim
aff7cad615 fix: resolve tree-sitter wasm initialization error with locateFile option 2025-12-05 09:45:04 +09:00
github-actions[bot]
e021ec954a release: v0.1.8 2025-12-05 00:30:07 +00:00
YeonGyu-Kim
1390970973 fix: skip publish if version already exists on registry 2025-12-05 09:29:08 +09:00
YeonGyu-Kim
a72bfe5c02 docs: consolidate README.en.md into README.md 2025-12-05 09:29:08 +09:00
YeonGyu-Kim
f10c15d83d feat: wire comment-checker hook to main plugin 2025-12-05 09:29:08 +09:00
10 changed files with 645 additions and 227 deletions

View File

@@ -1,191 +0,0 @@
English | [한국어](README.ko.md)
## Contents
- [Oh My OpenCode](#oh-my-opencode)
- [Installation](#installation)
- [Configuration](#configuration)
- [Disable specific MCPs](#disable-specific-mcps)
- [TL;DR](#tldr)
- [Why OpenCode \& Why Oh My OpenCode](#why-opencode--why-oh-my-opencode)
- [Features](#features)
- [Hooks](#hooks)
- [Agents](#agents)
- [Tools](#tools)
- [Built-in LSP Tools](#built-in-lsp-tools)
- [Built-in AST-Grep Tools](#built-in-ast-grep-tools)
- [Safe Grep](#safe-grep)
- [Built-in MCPs](#built-in-mcps)
- [Other Features](#other-features)
- [Author's Note](#authors-note)
- [Warnings](#warnings)
# Oh My OpenCode
Oh My OpenCode
oMoMoMoMoMo···
If you work in tech, you likely appreciated [Claude Code](https://www.claude.com/product/claude-code).
If you are a hacker, you will fucking falling in love with [OpenCode](https://github.com/sst/opencode).
You don't write code just for a paycheck? You write because you genuinely love it?
To you, OpenCode will feel like the paradigm shift from Windows to Linux. Not you? It's still worth the investment. Give it 10 minutes. Your work and life will improve. I promise.
## Installation
Add to `~/.config/opencode/opencode.json`:
```json
{
"plugin": [
"oh-my-opencode"
]
}
```
## Configuration
You can configure Oh My OpenCode by creating a `oh-my-opencode.json` (or `.oh-my-opencode.json`) file in your project root.
### JSON Schema Support
For autocompletion and validation in VS Code (or other editors), add the `$schema` property to your configuration file.
**Using Remote Schema (Recommended):**
```json
{
"$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-opencode/master/dist/oh-my-opencode.schema.json",
"disabled_mcps": ["context7"]
}
```
**Using Local Schema:**
```json
{
"$schema": "./node_modules/oh-my-opencode/dist/oh-my-opencode.schema.json"
}
```
### Disable specific MCPs
If you want to disable specific built-in MCPs, you can use the `disabled_mcps` option.
```json
{
"disabled_mcps": ["context7", "websearch_exa"]
}
```
## TL;DR
- **Model Setup Required**
- This plugin utilizes models from [OpenCode Zen](https://opencode.ai/docs/zen/), Google, OpenAI, and Anthropic.
- For Anthropic models, use [OpenCode's built-in Claude Code Max Plan login](https://opencode.ai/docs/providers/#anthropic).
- For OpenAI models, I recommend the [OpenCode-OpenAI-Codex-Auth plugin](https://github.com/numman-ali/opencode-openai-codex-auth).
- For Gemini and GPT, use [VibeProxy (macOS Only)](https://github.com/automazeio/vibeproxy) or [CLIProxyAPI](https://github.com/router-for-me/CLIProxyAPI).
- **Truth be told, the rest is optional. Just install it. Once set up, no further learning is required. It simply evolves.**
- **Paste this document into Claude Code, OpenCode, or Cursor, instruct it to install, and ask for assistance if needed. It works seamlessly.**
- If you are an LLM Agent, install the required plugins and dependencies, then run `opencode auth login` via tmux to assist the user with configuration.
## Why OpenCode & Why Oh My OpenCode
OpenCode is limitlessly extensible and customizable. Zero screen flicker.
[LSP](https://opencode.ai/docs/lsp/), [linters, formatters](https://opencode.ai/docs/formatters/)? Automatic and fully configurable.
You can mix and orchestrate models to your exact specifications.
It is feature-rich. It is elegant. It handles the terminal without hesitation. It is high-performance.
But here is the catch: the learning curve is steep. There is a lot to master.
Inspired by [AmpCode](https://ampcode.com) and [Claude Code](https://code.claude.com/docs/en/overview), I have implemented their features here—often with superior execution.
Because this is OpenCode.
Consider this a superior AmpCode, a superior Claude Code, or simply a specialized distribution.
I believe in the right tool for the job. For your wallet's sake, use CLIProxyAPI or VibeProxy. Employ the best LLMs from frontier labs. You are in command.
**Note**: This setup is highly opinionated. It represents the generic component of my personal configuration, so it evolves constantly. I have spent tokens worth $20,000 just for my personal programming usages, and this plugin represents the apex of that experience. You simply inherit the best. If you have superior ideas, PRs are welcome.
## Features
### Hooks
- **Todo Continuation Enforcer**: Forces the agent to complete all tasks before exiting. Eliminates the common LLM issue of "giving up halfway".
- **Context Window Monitor**: Implements [Context Window Anxiety Management](https://agentic-patterns.com/patterns/context-window-anxiety-management/). When context usage exceeds 70%, it reminds the agent that resources are sufficient, preventing rushed or low-quality output.
- **Session Notification**: Sends a native OS notification when the job is done (macOS, Linux, Windows).
- **Session Recovery**: Automatically recovers from API errors by injecting missing tool results and correcting thinking block violations, ensuring session stability.
- **Comment Checker**: Detects and reports unnecessary comments after code modifications. Smartly ignores valid patterns (BDD, directives, docstrings, shebangs) to keep the codebase clean from AI-generated artifacts.
### 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.
- **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.
- **document-writer** (`google/gemini-3-pro-preview`): A technical writing expert. Gemini is a wordsmith; it writes prose that flows naturally.
### Tools
#### Built-in LSP Tools
[OpenCode provides LSP](https://opencode.ai/docs/lsp/), but only for analysis. Oh My OpenCode equips you with navigation and refactoring tools matching the same specification.
- **lsp_hover**: Get type info, docs, signatures at position
- **lsp_goto_definition**: Jump to symbol definition
- **lsp_find_references**: Find all usages across workspace
- **lsp_document_symbols**: Get file's symbol outline
- **lsp_workspace_symbols**: Search symbols by name across project
- **lsp_diagnostics**: Get errors/warnings before build
- **lsp_servers**: List available LSP servers
- **lsp_prepare_rename**: Validate rename operation
- **lsp_rename**: Rename symbol across workspace
- **lsp_code_actions**: Get available quick fixes/refactorings
- **lsp_code_action_resolve**: Apply a code action
#### Built-in AST-Grep Tools
- **ast_grep_search**: AST-aware code pattern search (25 languages)
- **ast_grep_replace**: AST-aware code replacement
#### Safe Grep
- **safe_grep**: Content search with safety limits (5min timeout, 10MB output).
- The default `grep` lacks safeguards. On a large codebase, a broad pattern can cause CPU overload and indefinite hanging.
- `safe_grep` enforces strict limits.
- **Note**: Default `grep` is disabled to prevent Agent confusion. `safe_grep` delivers full `grep` functionality with safety assurance.
#### Built-in MCPs
- **websearch_exa**: Exa AI web search. Performs real-time web searches and can scrape content from specific URLs. Returns LLM-optimized context from relevant websites.
- **context7**: Library documentation lookup. Fetches up-to-date documentation for any library to assist with accurate coding.
### Other Features
- **Terminal Title**: Auto-updates terminal title with session status (idle ○, processing ◐, tool ⚡, error ✖). Supports tmux.
## Author's Note
Install Oh My OpenCode. Do not waste time configuring OpenCode from scratch.
I have resolved the friction so you don't have to. The answers are in this plugin. If OpenCode is Arch Linux, Oh My OpenCode is [Omarchy](https://omarchy.org/).
Enjoy the multi-model stability and rich feature set that other harnesses promise but fail to deliver.
I will continue testing and updating here. I am the primary user of this project.
- Who possesses the best raw logic?
- Who is the debugging god?
- Who writes the best prose?
- Who dominates frontend?
- Who owns backend?
- Which model is fastest for daily driving?
- What new features are other harnesses shipping?
Do not overthink it. I have done the thinking. I will integrate the best practices. I will update this.
If this sounds arrogant and you have a superior solution, send a PR. You are welcome.
As of now, I have no affiliation with any of the projects or models mentioned here. This plugin is purely based on personal experimentation and preference.
I constructed 99% of this project using OpenCode. I focused on functional verification. This documentation has been personally reviewed and comprehensively rewritten, so you can rely on it with confidence.
## Warnings
- If you are on [1.0.132](https://github.com/sst/opencode/releases/tag/v1.0.132) or lower, OpenCode has a bug that might break config.
- [The fix](https://github.com/sst/opencode/pull/5040) was merged after 1.0.132, so use a newer version.

View File

@@ -1,4 +1,4 @@
[English](README.en.md) | 한국어
[English](README.md) | 한국어
## 목차

View File

@@ -1 +0,0 @@
README.en.md

191
README.md Normal file
View File

@@ -0,0 +1,191 @@
English | [한국어](README.ko.md)
## Contents
- [Oh My OpenCode](#oh-my-opencode)
- [Installation](#installation)
- [Configuration](#configuration)
- [Disable specific MCPs](#disable-specific-mcps)
- [TL;DR](#tldr)
- [Why OpenCode \& Why Oh My OpenCode](#why-opencode--why-oh-my-opencode)
- [Features](#features)
- [Hooks](#hooks)
- [Agents](#agents)
- [Tools](#tools)
- [Built-in LSP Tools](#built-in-lsp-tools)
- [Built-in AST-Grep Tools](#built-in-ast-grep-tools)
- [Safe Grep](#safe-grep)
- [Built-in MCPs](#built-in-mcps)
- [Other Features](#other-features)
- [Author's Note](#authors-note)
- [Warnings](#warnings)
# Oh My OpenCode
Oh My OpenCode
oMoMoMoMoMo···
If you work in tech, you likely appreciated [Claude Code](https://www.claude.com/product/claude-code).
If you are a hacker, you will fucking falling in love with [OpenCode](https://github.com/sst/opencode).
You don't write code just for a paycheck? You write because you genuinely love it?
To you, OpenCode will feel like the paradigm shift from Windows to Linux. Not you? It's still worth the investment. Give it 10 minutes. Your work and life will improve. I promise.
## Installation
Add to `~/.config/opencode/opencode.json`:
```json
{
"plugin": [
"oh-my-opencode"
]
}
```
## Configuration
You can configure Oh My OpenCode by creating a `oh-my-opencode.json` (or `.oh-my-opencode.json`) file in your project root.
### JSON Schema Support
For autocompletion and validation in VS Code (or other editors), add the `$schema` property to your configuration file.
**Using Remote Schema (Recommended):**
```json
{
"$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-opencode/master/dist/oh-my-opencode.schema.json",
"disabled_mcps": ["context7"]
}
```
**Using Local Schema:**
```json
{
"$schema": "./node_modules/oh-my-opencode/dist/oh-my-opencode.schema.json"
}
```
### Disable specific MCPs
If you want to disable specific built-in MCPs, you can use the `disabled_mcps` option.
```json
{
"disabled_mcps": ["context7", "websearch_exa"]
}
```
## TL;DR
- **Model Setup Required**
- This plugin utilizes models from [OpenCode Zen](https://opencode.ai/docs/zen/), Google, OpenAI, and Anthropic.
- For Anthropic models, use [OpenCode's built-in Claude Code Max Plan login](https://opencode.ai/docs/providers/#anthropic).
- For OpenAI models, I recommend the [OpenCode-OpenAI-Codex-Auth plugin](https://github.com/numman-ali/opencode-openai-codex-auth).
- For Gemini and GPT, use [VibeProxy (macOS Only)](https://github.com/automazeio/vibeproxy) or [CLIProxyAPI](https://github.com/router-for-me/CLIProxyAPI).
- **Truth be told, the rest is optional. Just install it. Once set up, no further learning is required. It simply evolves.**
- **Paste this document into Claude Code, OpenCode, or Cursor, instruct it to install, and ask for assistance if needed. It works seamlessly.**
- If you are an LLM Agent, install the required plugins and dependencies, then run `opencode auth login` via tmux to assist the user with configuration.
## Why OpenCode & Why Oh My OpenCode
OpenCode is limitlessly extensible and customizable. Zero screen flicker.
[LSP](https://opencode.ai/docs/lsp/), [linters, formatters](https://opencode.ai/docs/formatters/)? Automatic and fully configurable.
You can mix and orchestrate models to your exact specifications.
It is feature-rich. It is elegant. It handles the terminal without hesitation. It is high-performance.
But here is the catch: the learning curve is steep. There is a lot to master.
Inspired by [AmpCode](https://ampcode.com) and [Claude Code](https://code.claude.com/docs/en/overview), I have implemented their features here—often with superior execution.
Because this is OpenCode.
Consider this a superior AmpCode, a superior Claude Code, or simply a specialized distribution.
I believe in the right tool for the job. For your wallet's sake, use CLIProxyAPI or VibeProxy. Employ the best LLMs from frontier labs. You are in command.
**Note**: This setup is highly opinionated. It represents the generic component of my personal configuration, so it evolves constantly. I have spent tokens worth $20,000 just for my personal programming usages, and this plugin represents the apex of that experience. You simply inherit the best. If you have superior ideas, PRs are welcome.
## Features
### Hooks
- **Todo Continuation Enforcer**: Forces the agent to complete all tasks before exiting. Eliminates the common LLM issue of "giving up halfway".
- **Context Window Monitor**: Implements [Context Window Anxiety Management](https://agentic-patterns.com/patterns/context-window-anxiety-management/). When context usage exceeds 70%, it reminds the agent that resources are sufficient, preventing rushed or low-quality output.
- **Session Notification**: Sends a native OS notification when the job is done (macOS, Linux, Windows).
- **Session Recovery**: Automatically recovers from API errors by injecting missing tool results and correcting thinking block violations, ensuring session stability.
- **Comment Checker**: Detects and reports unnecessary comments after code modifications. Smartly ignores valid patterns (BDD, directives, docstrings, shebangs) to keep the codebase clean from AI-generated artifacts.
### 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.
- **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.
- **document-writer** (`google/gemini-3-pro-preview`): A technical writing expert. Gemini is a wordsmith; it writes prose that flows naturally.
### Tools
#### Built-in LSP Tools
[OpenCode provides LSP](https://opencode.ai/docs/lsp/), but only for analysis. Oh My OpenCode equips you with navigation and refactoring tools matching the same specification.
- **lsp_hover**: Get type info, docs, signatures at position
- **lsp_goto_definition**: Jump to symbol definition
- **lsp_find_references**: Find all usages across workspace
- **lsp_document_symbols**: Get file's symbol outline
- **lsp_workspace_symbols**: Search symbols by name across project
- **lsp_diagnostics**: Get errors/warnings before build
- **lsp_servers**: List available LSP servers
- **lsp_prepare_rename**: Validate rename operation
- **lsp_rename**: Rename symbol across workspace
- **lsp_code_actions**: Get available quick fixes/refactorings
- **lsp_code_action_resolve**: Apply a code action
#### Built-in AST-Grep Tools
- **ast_grep_search**: AST-aware code pattern search (25 languages)
- **ast_grep_replace**: AST-aware code replacement
#### Safe Grep
- **safe_grep**: Content search with safety limits (5min timeout, 10MB output).
- The default `grep` lacks safeguards. On a large codebase, a broad pattern can cause CPU overload and indefinite hanging.
- `safe_grep` enforces strict limits.
- **Note**: Default `grep` is disabled to prevent Agent confusion. `safe_grep` delivers full `grep` functionality with safety assurance.
#### Built-in MCPs
- **websearch_exa**: Exa AI web search. Performs real-time web searches and can scrape content from specific URLs. Returns LLM-optimized context from relevant websites.
- **context7**: Library documentation lookup. Fetches up-to-date documentation for any library to assist with accurate coding.
### Other Features
- **Terminal Title**: Auto-updates terminal title with session status (idle ○, processing ◐, tool ⚡, error ✖). Supports tmux.
## Author's Note
Install Oh My OpenCode. Do not waste time configuring OpenCode from scratch.
I have resolved the friction so you don't have to. The answers are in this plugin. If OpenCode is Arch Linux, Oh My OpenCode is [Omarchy](https://omarchy.org/).
Enjoy the multi-model stability and rich feature set that other harnesses promise but fail to deliver.
I will continue testing and updating here. I am the primary user of this project.
- Who possesses the best raw logic?
- Who is the debugging god?
- Who writes the best prose?
- Who dominates frontend?
- Who owns backend?
- Which model is fastest for daily driving?
- What new features are other harnesses shipping?
Do not overthink it. I have done the thinking. I will integrate the best practices. I will update this.
If this sounds arrogant and you have a superior solution, send a PR. You are welcome.
As of now, I have no affiliation with any of the projects or models mentioned here. This plugin is purely based on personal experimentation and preference.
I constructed 99% of this project using OpenCode. I focused on functional verification. This documentation has been personally reviewed and comprehensively rewritten, so you can rely on it with confidence.
## Warnings
- If you are on [1.0.132](https://github.com/sst/opencode/releases/tag/v1.0.132) or lower, OpenCode has a bug that might break config.
- [The fix](https://github.com/sst/opencode/pull/5040) was merged after 1.0.132, so use a newer version.

View File

@@ -0,0 +1,162 @@
# Comment-Checker TypeScript Port 구현 계획
## 1. 아키텍처 개요
### 1.1 핵심 도전 과제
**OpenCode Hook의 제약사항:**
- `tool.execute.before`: `output.args`에서 파일 경로/내용 접근 가능
- `tool.execute.after`: `tool_input`**제공되지 않음** (Claude Code와의 핵심 차이점)
- **해결책**: Before hook에서 데이터를 캡처하여 callID로 키잉된 Map에 저장, After hook에서 조회
### 1.2 디렉토리 구조
```
src/hooks/comment-checker/
├── index.ts # Hook factory, 메인 엔트리포인트
├── types.ts # 모든 타입 정의
├── constants.ts # 언어 레지스트리, 쿼리 템플릿, 디렉티브 목록
├── detector.ts # CommentDetector - web-tree-sitter 기반 코멘트 감지
├── filters/
│ ├── index.ts # 필터 barrel export
│ ├── bdd.ts # BDD 패턴 필터
│ ├── directive.ts # 린터/타입체커 디렉티브 필터
│ ├── docstring.ts # 독스트링 필터
│ └── shebang.ts # Shebang 필터
├── output/
│ ├── index.ts # 출력 barrel export
│ ├── formatter.ts # FormatHookMessage
│ └── xml-builder.ts # BuildCommentsXML
└── utils.ts # 유틸리티 함수
```
### 1.3 데이터 흐름
```
[write/edit 도구 실행]
┌──────────────────────┐
│ tool.execute.before │
│ - 파일 경로 캡처 │
│ - pendingCalls Map │
│ 에 저장 │
└──────────┬───────────┘
[도구 실제 실행]
┌──────────────────────┐
│ tool.execute.after │
│ - pendingCalls에서 │
│ 데이터 조회 │
│ - 파일 읽기 │
│ - 코멘트 감지 │
│ - 필터 적용 │
│ - 메시지 주입 │
└──────────────────────┘
```
---
## 2. 구현 순서
### Phase 1: 기반 구조
1. `src/hooks/comment-checker/` 디렉토리 생성
2. `types.ts` - 모든 타입 정의
3. `constants.ts` - 언어 레지스트리, 디렉티브 패턴
### Phase 2: 필터 구현
4. `filters/bdd.ts` - BDD 패턴 필터
5. `filters/directive.ts` - 디렉티브 필터
6. `filters/docstring.ts` - 독스트링 필터
7. `filters/shebang.ts` - Shebang 필터
8. `filters/index.ts` - 필터 조합
### Phase 3: 코어 로직
9. `detector.ts` - web-tree-sitter 기반 코멘트 감지
10. `output/xml-builder.ts` - XML 출력
11. `output/formatter.ts` - 메시지 포매팅
### Phase 4: Hook 통합
12. `index.ts` - Hook factory 및 상태 관리
13. `src/hooks/index.ts` 업데이트 - export 추가
### Phase 5: 의존성 및 빌드
14. `package.json` 업데이트 - web-tree-sitter 추가
15. typecheck 및 build 검증
---
## 3. 핵심 구현 사항
### 3.1 언어 레지스트리 (38개 언어)
```typescript
const LANGUAGE_REGISTRY: Record<string, LanguageConfig> = {
python: { extensions: [".py"], commentQuery: "(comment) @comment", docstringQuery: "..." },
javascript: { extensions: [".js", ".jsx"], commentQuery: "(comment) @comment" },
typescript: { extensions: [".ts"], commentQuery: "(comment) @comment" },
tsx: { extensions: [".tsx"], commentQuery: "(comment) @comment" },
go: { extensions: [".go"], commentQuery: "(comment) @comment" },
rust: { extensions: [".rs"], commentQuery: "(line_comment) @comment (block_comment) @comment" },
// ... 38개 전체
}
```
### 3.2 필터 로직
**BDD 필터**: `given, when, then, arrange, act, assert`
**Directive 필터**: `noqa, pyright:, eslint-disable, @ts-ignore` 등 30+
**Docstring 필터**: `IsDocstring || starts with /**`
**Shebang 필터**: `starts with #!`
### 3.3 출력 형식 (Go 버전과 100% 동일)
```
COMMENT/DOCSTRING DETECTED - IMMEDIATE ACTION REQUIRED
Your recent changes contain comments or docstrings, which triggered this hook.
You need to take immediate action. You must follow the conditions below.
(Listed in priority order - you must always act according to this priority order)
CRITICAL WARNING: This hook message MUST NEVER be ignored...
<comments file="/path/to/file.py">
<comment line-number="10">// comment text</comment>
</comments>
```
---
## 4. 생성할 파일 목록
1. `src/hooks/comment-checker/types.ts`
2. `src/hooks/comment-checker/constants.ts`
3. `src/hooks/comment-checker/filters/bdd.ts`
4. `src/hooks/comment-checker/filters/directive.ts`
5. `src/hooks/comment-checker/filters/docstring.ts`
6. `src/hooks/comment-checker/filters/shebang.ts`
7. `src/hooks/comment-checker/filters/index.ts`
8. `src/hooks/comment-checker/output/xml-builder.ts`
9. `src/hooks/comment-checker/output/formatter.ts`
10. `src/hooks/comment-checker/output/index.ts`
11. `src/hooks/comment-checker/detector.ts`
12. `src/hooks/comment-checker/index.ts`
## 5. 수정할 파일 목록
1. `src/hooks/index.ts` - export 추가
2. `package.json` - web-tree-sitter 의존성
---
## 6. Definition of Done
- [ ] write/edit 도구 실행 시 코멘트 감지 동작
- [ ] 4개 필터 모두 정상 작동
- [ ] 최소 5개 언어 지원 (Python, JS, TS, TSX, Go)
- [ ] Go 버전과 동일한 출력 형식
- [ ] typecheck 통과
- [ ] build 성공

View File

@@ -0,0 +1,12 @@
#!/bin/bash
set -e
cd /Users/yeongyu/local-workspaces/oh-my-opencode
echo "=== Pushing to origin ==="
git push -f origin master
echo "=== Triggering workflow ==="
gh workflow run publish.yml --repo code-yeongyu/oh-my-opencode --ref master -f bump=patch -f version=$1
echo "=== Done! ==="
echo "Usage: ./local-ignore/push-and-release.sh 0.1.6"

View File

@@ -1,6 +1,6 @@
{
"name": "oh-my-opencode",
"version": "0.1.7",
"version": "0.1.12",
"description": "OpenCode plugin - custom agents (oracle, librarian) and enhanced features",
"main": "dist/index.js",
"types": "dist/index.d.ts",

View File

@@ -87,11 +87,25 @@ async function gitTagAndRelease(newVersion: string, changelog: string): Promise<
await $`gh release create v${newVersion} --title "v${newVersion}" --notes ${releaseNotes}`
}
async function checkVersionExists(version: string): Promise<boolean> {
try {
const res = await fetch(`https://registry.npmjs.org/${PACKAGE_NAME}/${version}`)
return res.ok
} catch {
return false
}
}
async function main() {
const previous = await fetchPreviousVersion()
const newVersion = versionOverride || (bump ? bumpVersion(previous, bump) : bumpVersion(previous, "patch"))
console.log(`New version: ${newVersion}\n`)
if (await checkVersionExists(newVersion)) {
console.log(`Version ${newVersion} already exists on npm. Skipping publish.`)
process.exit(0)
}
await updatePackageVersion(newVersion)
const changelog = await generateChangelog(previous)
await buildAndPublish()

View File

@@ -1,5 +1,194 @@
import type { CommentInfo, CommentType } from "./types"
import { getLanguageByExtension, QUERY_TEMPLATES, DOCSTRING_QUERIES } from "./constants"
import * as fs from "fs"
// =============================================================================
// Debug logging
// =============================================================================
const DEBUG = process.env.COMMENT_CHECKER_DEBUG === "1"
const DEBUG_FILE = "/tmp/comment-checker-debug.log"
function debugLog(...args: unknown[]) {
if (DEBUG) {
const msg = `[${new Date().toISOString()}] [comment-checker:detector] ${args.map(a => typeof a === 'object' ? JSON.stringify(a, null, 2) : String(a)).join(' ')}\n`
fs.appendFileSync(DEBUG_FILE, msg)
}
}
// =============================================================================
// Parser Manager (LSP-style background initialization)
// =============================================================================
interface ManagedLanguage {
language: unknown
initPromise?: Promise<unknown>
isInitializing: boolean
lastUsedAt: number
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let parserClass: any = null
let parserInitPromise: Promise<void> | null = null
const languageCache = new Map<string, ManagedLanguage>()
const LANGUAGE_NAME_MAP: Record<string, string> = {
golang: "go",
csharp: "c_sharp",
cpp: "cpp",
}
const COMMON_LANGUAGES = [
"python",
"typescript",
"javascript",
"tsx",
"go",
"rust",
"java",
]
async function initParserClass(): Promise<void> {
if (parserClass) return
if (parserInitPromise) {
await parserInitPromise
return
}
parserInitPromise = (async () => {
debugLog("importing web-tree-sitter...")
parserClass = (await import("web-tree-sitter")).default
// Find wasm path relative to web-tree-sitter package at runtime
const webTreeSitterPath = import.meta.resolve("web-tree-sitter")
const packageDir = webTreeSitterPath.replace(/\/[^/]+$/, "").replace("file://", "")
const treeSitterWasmPath = `${packageDir}/tree-sitter.wasm`
debugLog("wasm path:", treeSitterWasmPath)
await parserClass.init({
locateFile: () => treeSitterWasmPath,
})
debugLog("Parser class initialized")
})()
await parserInitPromise
}
async function getParser() {
await initParserClass()
return new parserClass()
}
async function loadLanguageWasm(langName: string): Promise<unknown | null> {
const mappedLang = LANGUAGE_NAME_MAP[langName] || langName
try {
const wasmModule = await import(`tree-sitter-wasms/out/tree-sitter-${langName}.wasm`)
return wasmModule.default
} catch {
if (mappedLang !== langName) {
try {
const wasmModule = await import(`tree-sitter-wasms/out/tree-sitter-${mappedLang}.wasm`)
return wasmModule.default
} catch {
return null
}
}
return null
}
}
async function getLanguage(langName: string): Promise<unknown | null> {
const cached = languageCache.get(langName)
if (cached) {
if (cached.initPromise) {
await cached.initPromise
}
cached.lastUsedAt = Date.now()
debugLog("using cached language:", langName)
return cached.language
}
debugLog("loading language wasm:", langName)
const initPromise = (async () => {
await initParserClass()
const wasmPath = await loadLanguageWasm(langName)
if (!wasmPath) {
debugLog("failed to load language wasm:", langName)
return null
}
return await parserClass!.Language.load(wasmPath)
})()
languageCache.set(langName, {
language: null as unknown,
initPromise,
isInitializing: true,
lastUsedAt: Date.now(),
})
const language = await initPromise
const managed = languageCache.get(langName)
if (managed) {
managed.language = language
managed.initPromise = undefined
managed.isInitializing = false
}
debugLog("language loaded and cached:", langName)
return language
}
function warmupLanguage(langName: string): void {
if (languageCache.has(langName)) return
debugLog("warming up language (background):", langName)
const initPromise = (async () => {
await initParserClass()
const wasmPath = await loadLanguageWasm(langName)
if (!wasmPath) return null
return await parserClass!.Language.load(wasmPath)
})()
languageCache.set(langName, {
language: null as unknown,
initPromise,
isInitializing: true,
lastUsedAt: Date.now(),
})
initPromise.then((language) => {
const managed = languageCache.get(langName)
if (managed) {
managed.language = language
managed.initPromise = undefined
managed.isInitializing = false
debugLog("warmup complete:", langName)
}
}).catch((err) => {
debugLog("warmup failed:", langName, err)
languageCache.delete(langName)
})
}
export function warmupCommonLanguages(): void {
debugLog("starting background warmup for common languages...")
initParserClass().then(() => {
for (const lang of COMMON_LANGUAGES) {
warmupLanguage(lang)
}
}).catch((err) => {
debugLog("warmup initialization failed:", err)
})
}
// =============================================================================
// Public API
// =============================================================================
export function isSupportedFile(filePath: string): boolean {
return getLanguageByExtension(filePath) !== null
@@ -35,48 +224,34 @@ export async function detectComments(
content: string,
includeDocstrings = true
): Promise<CommentInfo[]> {
debugLog("detectComments called:", { filePath, contentLength: content.length })
const langName = getLanguageByExtension(filePath)
if (!langName) {
debugLog("unsupported language for:", filePath)
return []
}
const queryPattern = QUERY_TEMPLATES[langName]
if (!queryPattern) {
debugLog("no query pattern for:", langName)
return []
}
try {
const Parser = (await import("web-tree-sitter")).default
await Parser.init()
const parser = new Parser()
let wasmPath: string
try {
const wasmModule = await import(`tree-sitter-wasms/out/tree-sitter-${langName}.wasm`)
wasmPath = wasmModule.default
} catch {
const languageMap: Record<string, string> = {
golang: "go",
csharp: "c_sharp",
cpp: "cpp",
}
const mappedLang = languageMap[langName] || langName
try {
const wasmModule = await import(`tree-sitter-wasms/out/tree-sitter-${mappedLang}.wasm`)
wasmPath = wasmModule.default
} catch {
return []
}
const parser = await getParser()
const language = await getLanguage(langName)
if (!language) {
debugLog("language not available:", langName)
return []
}
const language = await Parser.Language.load(wasmPath)
parser.setLanguage(language)
const tree = parser.parse(content)
const comments: CommentInfo[] = []
const query = language.query(queryPattern)
const query = (language as { query: (pattern: string) => { matches: (node: unknown) => Array<{ captures: Array<{ node: { text: string; type: string; startPosition: { row: number } } }> }> } }).query(queryPattern)
const matches = query.matches(tree.rootNode)
for (const match of matches) {
@@ -106,7 +281,7 @@ export async function detectComments(
const docQuery = DOCSTRING_QUERIES[langName]
if (docQuery) {
try {
const docQueryObj = language.query(docQuery)
const docQueryObj = (language as { query: (pattern: string) => { matches: (node: unknown) => Array<{ captures: Array<{ node: { text: string; startPosition: { row: number } } }> }> } }).query(docQuery)
const docMatches = docQueryObj.matches(tree.rootNode)
for (const match of docMatches) {
@@ -135,8 +310,10 @@ export async function detectComments(
comments.sort((a, b) => a.lineNumber - b.lineNumber)
debugLog("detected comments:", comments.length)
return comments
} catch {
} catch (err) {
debugLog("detectComments failed:", err)
return []
}
}

View File

@@ -1,8 +1,20 @@
import type { PendingCall, FileComments } from "./types"
import { detectComments, isSupportedFile } from "./detector"
import { detectComments, isSupportedFile, warmupCommonLanguages } from "./detector"
import { applyFilters } from "./filters"
import { formatHookMessage } from "./output"
import * as fs from "fs"
const DEBUG = process.env.COMMENT_CHECKER_DEBUG === "1"
const DEBUG_FILE = "/tmp/comment-checker-debug.log"
function debugLog(...args: unknown[]) {
if (DEBUG) {
const msg = `[${new Date().toISOString()}] [comment-checker:hook] ${args.map(a => typeof a === 'object' ? JSON.stringify(a, null, 2) : String(a)).join(' ')}\n`
fs.appendFileSync(DEBUG_FILE, msg)
}
}
const pendingCalls = new Map<string, PendingCall>()
const PENDING_CALL_TTL = 60_000
@@ -18,27 +30,40 @@ function cleanupOldPendingCalls(): void {
setInterval(cleanupOldPendingCalls, 10_000)
export function createCommentCheckerHooks() {
debugLog("createCommentCheckerHooks called")
// Background warmup - LSP style (non-blocking)
warmupCommonLanguages()
return {
"tool.execute.before": async (
input: { tool: string; sessionID: string; callID: string },
output: { args: Record<string, unknown> }
): Promise<void> => {
debugLog("tool.execute.before:", { tool: input.tool, callID: input.callID, args: output.args })
const toolLower = input.tool.toLowerCase()
if (toolLower !== "write" && toolLower !== "edit" && toolLower !== "multiedit") {
debugLog("skipping non-write/edit tool:", toolLower)
return
}
const filePath = (output.args.filePath ?? output.args.file_path) as string | undefined
const filePath = (output.args.filePath ?? output.args.file_path ?? output.args.path) as string | undefined
const content = output.args.content as string | undefined
debugLog("extracted filePath:", filePath)
if (!filePath) {
debugLog("no filePath found")
return
}
if (!isSupportedFile(filePath)) {
debugLog("unsupported file:", filePath)
return
}
debugLog("registering pendingCall:", { callID: input.callID, filePath, tool: toolLower })
pendingCalls.set(input.callID, {
filePath,
content,
@@ -52,14 +77,28 @@ export function createCommentCheckerHooks() {
input: { tool: string; sessionID: string; callID: string },
output: { title: string; output: string; metadata: unknown }
): Promise<void> => {
debugLog("tool.execute.after:", { tool: input.tool, callID: input.callID })
const pendingCall = pendingCalls.get(input.callID)
if (!pendingCall) {
debugLog("no pendingCall found for:", input.callID)
return
}
pendingCalls.delete(input.callID)
debugLog("processing pendingCall:", pendingCall)
if (output.output.toLowerCase().includes("error")) {
// Only skip if the output indicates a tool execution failure
// (not LSP warnings/errors or other incidental "error" strings)
const outputLower = output.output.toLowerCase()
const isToolFailure =
outputLower.includes("error:") ||
outputLower.includes("failed to") ||
outputLower.includes("could not") ||
outputLower.startsWith("error")
if (isToolFailure) {
debugLog("skipping due to tool failure in output")
return
}
@@ -68,15 +107,23 @@ export function createCommentCheckerHooks() {
if (pendingCall.content) {
content = pendingCall.content
debugLog("using content from args")
} else {
debugLog("reading file:", pendingCall.filePath)
const file = Bun.file(pendingCall.filePath)
content = await file.text()
debugLog("file content length:", content.length)
}
debugLog("calling detectComments...")
const rawComments = await detectComments(pendingCall.filePath, content)
debugLog("raw comments:", rawComments.length)
const filteredComments = applyFilters(rawComments)
debugLog("filtered comments:", filteredComments.length)
if (filteredComments.length === 0) {
debugLog("no comments after filtering")
return
}
@@ -88,8 +135,11 @@ export function createCommentCheckerHooks() {
]
const message = formatHookMessage(fileComments)
debugLog("appending message to output")
output.output += `\n\n${message}`
} catch {}
} catch (err) {
debugLog("tool.execute.after failed:", err)
}
},
}
}

View File

@@ -1,6 +1,6 @@
import type { Plugin } from "@opencode-ai/plugin"
import { createBuiltinAgents } from "./agents"
import { createTodoContinuationEnforcer, createContextWindowMonitorHook, createSessionRecoveryHook } from "./hooks"
import { createTodoContinuationEnforcer, createContextWindowMonitorHook, createSessionRecoveryHook, createCommentCheckerHooks } from "./hooks"
import { updateTerminalTitle } from "./features/terminal"
import { builtinTools } from "./tools"
import { createBuiltinMcps } from "./mcp"
@@ -43,6 +43,7 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => {
const todoContinuationEnforcer = createTodoContinuationEnforcer(ctx)
const contextWindowMonitor = createContextWindowMonitorHook(ctx)
const sessionRecovery = createSessionRecoveryHook(ctx)
const commentChecker = createCommentCheckerHooks()
updateTerminalTitle({ sessionId: "main" })
@@ -161,7 +162,9 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => {
}
},
"tool.execute.before": async (input, _output) => {
"tool.execute.before": async (input, output) => {
await commentChecker["tool.execute.before"](input, output)
if (input.sessionID === mainSessionID) {
updateTerminalTitle({
sessionId: input.sessionID,
@@ -175,6 +178,7 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => {
"tool.execute.after": async (input, output) => {
await contextWindowMonitor["tool.execute.after"](input, output)
await commentChecker["tool.execute.after"](input, output)
if (input.sessionID === mainSessionID) {
updateTerminalTitle({