opencode-ultra 0.4.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,30 +1,52 @@
1
1
  # opencode-ultra
2
2
 
3
- [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) をベースにした OpenCode 1.2.x プラグイン。
4
- マルチエージェントオーケストレーション・キーワード駆動モード切替・ルール注入を軽量な単一プラグインで実現する。
5
-
6
- ## oh-my-opencode との違い
7
-
8
- | | oh-my-opencode | opencode-ultra |
9
- |---|---|---|
10
- | 構成 | フルスタック (LSP, AST-Grep, MCP, Hook 25+, Ralph Loop, Comment Checker...) | **オーケストレーション特化** — 同等機能を軽量に再実装 |
11
- | エージェント表 | ハードコード | **設定から動的生成** — `oh-my-opencode.json` のオーバーライドが Sisyphus のプロンプトに自動反映 |
12
- | プラン承認 | なし (即実行) | **Plan-First Protocol** — spawn_agent 前に Phase 形式のプランを提示、ユーザー承認を待つ |
13
- | 並列制御 | バックグラウンドタスク API | **Semaphore ベース** — グローバル/プロバイダー/モデル単位で同時実行制限 |
14
- | Categories | ハードコード | **設定可能** カテゴリでモデル/バリアントを一括切替 |
15
- | Ralph Loop | 外部ツール依存 | **ビルトインツール** — `<promise>DONE</promise>` マーカーで完了検知 |
16
- | Comment Checker | 外部バイナリ (AST-Grep) | **正規表現ベース** — AIスロップパターン検知 |
17
- | Todo Enforcer | `tool.execute.after` 方式 | **`session.idle` イベント方式** セッション idle 時に未完了 TODO を検査 |
18
- | MCP | Exa, Context7, grep_app | **Context7 のみ** (API キー不要で動作、キーありでレートリミット緩和) |
19
- | 依存 | 多数 (Exa, tmux...) | **最小限** (`@opencode-ai/plugin`, `jsonc-parser`, `zod`) |
20
- | 設定形式 | JSON | **JSONC** (コメント・末尾カンマ対応) |
3
+ [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) をベースに、micode / opencode-skillful の良い部分を取り込んだ OpenCode 1.2.x プラグイン。
4
+ マルチエージェントオーケストレーション・キーワード駆動モード切替・ルール注入・セッション継続・AST検索を軽量な単一プラグインで実現する。
5
+
6
+ ## 機能一覧
7
+
8
+ ### ツール (7)
9
+
10
+ | ツール | 説明 |
11
+ |--------|------|
12
+ | `spawn_agent` | 並列エージェント実行 (ConcurrencyPool 制御付き) |
13
+ | `ralph_loop` | 自律ループ実行 (`<promise>DONE</promise>` で完了検知) |
14
+ | `cancel_ralph` | 実行中の Ralph Loop をキャンセル |
15
+ | `batch_read` | 複数ファイル並列読み込み (最大20) |
16
+ | `ledger_save` | Continuity Ledger の保存 (.opencode/ledgers/) |
17
+ | `ledger_load` | Continuity Ledger の読み込み (名前指定 or 最新) |
18
+ | `ast_search` | AST-aware コード検索 (ast-grep/sg バイナリ使用、未インストール時は自動スキップ) |
19
+
20
+ ### フック (9)
21
+
22
+ | フック | hook ポイント | 説明 |
23
+ |--------|-------------|------|
24
+ | `keyword-detector` | chat.message | ultrawork/search/analyze/think キーワード検知 |
25
+ | `rules-injector` | system.transform | .opencode/rules.md を system prompt に注入 |
26
+ | `context-injector` | system.transform | ARCHITECTURE.md / CODE_STYLE.md を自動注入 |
27
+ | `fragment-injector` | system.transform | エージェント毎のカスタムプロンプト断片注入 |
28
+ | `prompt-renderer` | system.transform | モデル別 system prompt フォーマット最適化 (markdown/xml/json) |
29
+ | `comment-checker` | tool.execute.after | Write/Edit 後の AI スロップコメント検知 |
30
+ | `token-truncation` | tool.execute.after | 30000文字超のツール出力を先頭40%+末尾40%に圧縮 |
31
+ | `todo-enforcer` | event (session.idle) | 未完了 TODO を検知して強制継続 |
32
+ | `session-compaction` | experimental.session.compacting | セッション圧縮時に structured summary 生成 |
33
+
34
+ ### その他
35
+
36
+ | 機能 | 説明 |
37
+ |------|------|
38
+ | **Built-in Agent Demotion** | OpenCode 標準エージェント (build/plan/triage/docs) を subagent に降格 |
39
+ | **MCP 自動登録** | Context7 を自動登録 (API キー任意) |
40
+ | **Plan-First Protocol** | spawn_agent 前に Phase 形式のプランを提示、ユーザー承認を待つ |
41
+ | **Categories** | spawn_agent の category パラメータでモデル/バリアントを一括切替 |
42
+ | **Concurrency Pool** | Semaphore ベースの global/provider/model 三層並列制御 |
21
43
 
22
44
  ## アーキテクチャ
23
45
 
24
46
  ```
25
47
  User
26
48
 
27
- │ "ultrawork" / "ulw" キーワード
49
+ │ "ultrawork" / "ulw" / "think hard" キーワード
28
50
 
29
51
 
30
52
  ┌──────────────────────────────────────────────┐
@@ -32,27 +54,37 @@ User
32
54
  │ │
33
55
  │ chat.message hook │
34
56
  │ ├─ keyword-detector │
35
- │ │ └─ ultrawork / search / analyze
57
+ │ │ └─ ultrawork / search / analyze / think
36
58
  │ └─ ultrawork → variant: "max" │
37
59
  │ │
38
60
  │ system.transform hook │
39
61
  │ ├─ keyword message injection │
40
- └─ rules-injector (.opencode/rules.md) │
62
+ ├─ rules-injector (.opencode/rules.md) │
63
+ │ ├─ context-injector (ARCHITECTURE/CODE_STYLE)│
64
+ │ ├─ fragment-injector (per-agent fragments) │
65
+ │ └─ prompt-renderer (markdown/xml/json) │
41
66
  │ │
42
67
  │ tool.execute.after hook │
43
- └─ comment-checker (Write/Edit 後に検査) │
68
+ ├─ comment-checker (Write/Edit 後に検査) │
69
+ │ └─ token-truncation (30000文字超を圧縮) │
70
+ │ │
71
+ │ session.compacting hook │
72
+ │ └─ session-compaction (structured summary) │
44
73
  │ │
45
74
  │ event hook │
46
75
  │ ├─ session cleanup │
47
76
  │ └─ todo-enforcer (session.idle 時に検査) │
48
77
  │ │
49
78
  │ config hook │
50
- └─ register agents to OpenCode │
79
+ ├─ register agents to OpenCode │
80
+ │ └─ demote built-in agents to subagent │
51
81
  │ │
52
82
  │ tools │
53
83
  │ ├─ spawn_agent (並列実行 + 並列制御) │
54
- │ ├─ ralph_loop (自律ループ実行)
55
- └─ cancel_ralph (ループキャンセル)
84
+ │ ├─ ralph_loop / cancel_ralph
85
+ ├─ batch_read (複数ファイル並列読み込み)
86
+ │ ├─ ledger_save / ledger_load (文脈継続) │
87
+ │ └─ ast_search (構文木検索, optional) │
56
88
  │ │
57
89
  │ MCP registration │
58
90
  │ └─ context7 (自動登録) │
@@ -72,13 +104,13 @@ User
72
104
 
73
105
  ## エージェント構成
74
106
 
75
- Sisyphus (オーケストレーター) が直接ツールを使わず、全てを spawn_agent 経由でサブエージェントに委任する。
107
+ Sisyphus (オーケストレーター) が直接コードを読み、実装はサブエージェントに委任する。
76
108
 
77
109
  ### ビルトインデフォルト
78
110
 
79
111
  | Agent | 役割 | デフォルトモデル | モード |
80
112
  |-------|------|-----------------|--------|
81
- | **sisyphus** | オーケストレーター — 分析・計画・委任 | anthropic/claude-opus-4-5 | primary |
113
+ | **sisyphus** | オーケストレーター — 読み込み+分析+計画+委任 | openai-codex/gpt-5.3-codex | primary |
82
114
  | **oracle** | 設計・デバッグ・アーキテクチャ判断 | openai/gpt-5.2 | subagent |
83
115
  | **explore** | 高速コードベース偵察 | anthropic/claude-haiku-4-5 | subagent |
84
116
  | **librarian** | ドキュメント・ベストプラクティス調査 | anthropic/claude-sonnet-4-5 | subagent |
@@ -88,314 +120,175 @@ Sisyphus (オーケストレーター) が直接ツールを使わず、全て
88
120
  | **atlas** | タスク管理・進捗追跡 | anthropic/claude-sonnet-4-5 | subagent |
89
121
  | **multimodal-looker** | 画像・スクリーンショット解析 | anthropic/claude-sonnet-4-5 | subagent |
90
122
 
91
- **全てのモデルは `oh-my-opencode.json` でオーバーライド可能。** Sisyphus のプロンプト内エージェント表にはオーバーライド後の実際のモデル名が動的に埋め込まれる。
92
-
93
- ### Sisyphus の制約
123
+ 全てのモデルは `oh-my-opencode.json` でオーバーライド可能。
94
124
 
95
- Sisyphus は以下のツールが**物理的に無効化**されている:
125
+ ### Sisyphus のツール権限
96
126
 
97
127
  ```
98
- Grep: false, Glob: false, Read: false,
99
- Write: false, Edit: false, Bash: false
128
+ Grep: true, Glob: true, Read: true ← 直接読み込み可能
129
+ Write: false, Edit: false, Bash: false ← サブエージェントに委任
100
130
  ```
101
131
 
102
- 使えるのは `spawn_agent`、`ralph_loop`、`cancel_ralph` と TODO 系ツールのみ。これにより高コストな Orchestrator トークンの浪費を防ぎ、適切なモデルへの委任を強制する。
103
-
104
- ## Plan-First Protocol
132
+ ## Continuity Ledger
105
133
 
106
- Sisyphus は spawn_agent を呼ぶ前に、必ず以下の形式でプランを提示する:
107
-
108
- ```
109
- ## Execution Plan
110
-
111
- ### Phase 1: 偵察 (parallel)
112
- - [ ] API ルート探索 → explore (gpt-5.3-codex-spark)
113
- - [ ] フレームワーク仕様調査 → librarian (glm-4.7)
114
-
115
- ### Phase 2: 実装 (after Phase 1)
116
- - [ ] エンドポイント実装 → hephaestus (gpt-5.2)
117
-
118
- ### Phase 3: Review
119
- - [ ] コードレビュー → momus (gpt-5.3-codex)
120
-
121
- Estimated agents: 4 | Phases: 3
122
- ```
123
-
124
- - 独立タスクは同一 Phase にグループ化 (並列実行)
125
- - 依存関係があるタスクは別 Phase
126
- - エージェント名 + 実際のモデル名を表示
127
- - **「このプランで進めますか?」で承認待ち — OK するまで実行しない**
128
-
129
- ## spawn_agent ツール
130
-
131
- Sisyphus が使う並列エージェント実行ツール。
134
+ セッション間の文脈を引き継ぐマークダウンドキュメント。`.opencode/ledgers/` に保存される。
132
135
 
133
136
  ```typescript
134
- spawn_agent({
135
- agents: [
136
- { agent: "explore", prompt: "Find all API routes in src/", description: "Route discovery" },
137
- { agent: "librarian", prompt: "Research Express middleware best practices", description: "Middleware docs" },
138
- { agent: "hephaestus", prompt: "Implement the feature", description: "Implementation", category: "deep" }
139
- ]
140
- })
141
- ```
142
-
143
- - 複数エージェントは **Promise.all で並列実行**(ConcurrencyPool による同時実行制御付き)
144
- - 各エージェントは**エフェメラルセッション**で起動 — 完了後に自動削除
145
- - 内部セッションには `internalSessions` フラグを付与し、**フックの再帰を防止**
146
- - 進捗は `toolCtx.metadata({ title })` でステータスバーに表示
147
- - Toast 通知で個別エージェントの完了を通知
148
- - **`category` パラメータ**でモデル/バリアントをカテゴリから自動解決
137
+ // 保存
138
+ ledger_save({ name: "auth-refactor", content: "## Context\n..." })
139
+ // Saved to .opencode/ledgers/auth-refactor.md
149
140
 
150
- ### 並列制御 (Concurrency)
141
+ // 読み込み (名前指定)
142
+ ledger_load({ name: "auth-refactor" })
151
143
 
152
- `background_task` が設定されていると、Semaphore ベースの並列制御が有効になる。
153
-
154
- ```
155
- spawn_agent に 5 エージェント渡す
156
- → defaultConcurrency: 3 なら同時 3 個まで
157
- → 残りは空きを待って順次実行
144
+ // 読み込み (最新)
145
+ ledger_load({})
158
146
  ```
159
147
 
160
- | 設定フィールド | 目的 | 動作 |
161
- |---------------|------|------|
162
- | `defaultConcurrency` | 全体の同時実行上限 | 全エージェント合計で N 個まで同時実行 |
163
- | `providerConcurrency` | プロバイダー別上限 (例: openai: 8) | 同一プロバイダーのエージェントを N 個に制限 |
164
- | `modelConcurrency` | モデル別上限 (例: gpt-5.2: 2) | 同一モデルのエージェントを N 個に制限 |
148
+ ## Fragment Injection
165
149
 
166
- セマフォは model → provider → global の順で acquire し、global → provider → model の順で release する。エラー時も確実に release される。
167
-
168
- `background_task` 未設定の場合は制限なし(従来通り全エージェント同時発火)。
169
-
170
- ## Categories
171
-
172
- spawn_agent の `category` パラメータでモデル/バリアントを一括切替できる。
173
-
174
- ### ビルトインカテゴリ
175
-
176
- | カテゴリ | 用途 | デフォルトモデル | バリアント |
177
- |---------|------|-----------------|-----------|
178
- | `visual-engineering` | フロントエンド/UI実装 | google/gemini-3-pro | — |
179
- | `ultrabrain` | 最高精度タスク | openai/gpt-5.3-codex | xhigh |
180
- | `deep` | 深い分析・複雑ロジック | openai/gpt-5.3-codex | medium |
181
- | `quick` | 高速・低コストタスク | anthropic/claude-haiku-4-5 | — |
182
- | `writing` | ドキュメント・文章 | zai-coding-plan/glm-4.7 | — |
183
-
184
- 設定ファイルでカスタムカテゴリの追加やビルトインの上書きが可能:
150
+ エージェント毎にカスタムプロンプト断片を注入する。
185
151
 
186
152
  ```jsonc
187
153
  {
188
- "categories": {
189
- "quick": { "model": "openai/gpt-4o-mini" },
190
- "my-custom": { "model": "custom/model", "variant": "high" }
154
+ "fragments": {
155
+ "hephaestus": [".opencode/fragments/strict-coding.md"],
156
+ "sisyphus": [".opencode/fragments/planning-rules.md"]
191
157
  }
192
158
  }
193
159
  ```
194
160
 
195
- ## Ralph Loop
196
-
197
- 自律ループ実行ツール。エージェントが `<promise>DONE</promise>` を出力するか、最大イテレーション数に達するまで繰り返し実行する。
198
-
199
- ```typescript
200
- ralph_loop({
201
- prompt: "Implement all unit tests for the auth module",
202
- agent: "hephaestus", // default
203
- maxIterations: 10 // default
204
- })
205
- ```
206
-
207
- - 各イテレーションで前回の進捗をレビューし、続きから再開するよう指示
208
- - ステータスバーに `Ralph Loop [3/10] — hephaestus` のように進捗表示
209
- - `cancel_ralph` ツールで実行中のループを全てキャンセル可能
210
-
211
- ## Todo Enforcer
212
-
213
- セッションが idle になったとき、最後の assistant メッセージに未完了の TODO(`- [ ]`)がある場合、強制継続プロンプトを送信する。
214
-
215
- - **最大 5 回**(デフォルト)の強制で無限ループを防止
216
- - spawn_agent のサブセッションには適用されない
217
- - `disabled_hooks: ["todo-enforcer"]` で無効化可能
218
- - 設定で最大回数を変更可能: `"todo_enforcer": { "maxEnforcements": 3 }`
219
-
220
- ## Comment Checker
221
-
222
- Write / Edit ツール実行後に、書き込まれたファイルのコメント品質を検査する。
161
+ 指定ファイルの内容が該当エージェントの system prompt に追加される。
223
162
 
224
- ### 検出するパターン
163
+ ## Per-model Prompt Renderer
225
164
 
226
- - コメント比率が 30% を超えるファイル
227
- - 80 文字超の行コメント (`// This function does...`)
228
- - 説明的すぎる冒頭 (`// This`, `// The`, `// We`, `// Here`, `// Note:`)
229
- - AI プレースホルダー (`// TODO: implement`)
230
- - lint 回避コメント (`// eslint-disable`)
231
- - 300 文字超の巨大 JSDoc
232
-
233
- 閾値超えの場合、ツール出力に `[Comment Checker]` 警告を追加する。
165
+ モデルに応じて system prompt のフォーマットを最適化する。
234
166
 
235
167
  ```jsonc
236
168
  {
237
- "comment_checker": {
238
- "maxRatio": 0.3, // コメント比率閾値 (default: 0.3)
239
- "slopThreshold": 3 // AIスロップパターン数閾値 (default: 3)
240
- }
241
- }
242
- ```
243
-
244
- ## MCP 自動登録
245
-
246
- プラグイン初期化時に以下の MCP サーバーを自動登録する:
247
-
248
- | MCP | パッケージ | API キー |
249
- |-----|-----------|----------|
250
- | **Context7** | `@upstash/context7-mcp` | オプション(あるとレートリミット緩和) |
251
-
252
- API キーの渡し方:
253
-
254
- ```jsonc
255
- {
256
- "mcp_api_keys": {
257
- "context7": "ctx7sk-..."
169
+ "prompt_renderer": {
170
+ "default": "markdown",
171
+ "model_overrides": {
172
+ "anthropic/claude-sonnet-4-5": "xml",
173
+ "openai/gpt-5.2": "json"
174
+ }
258
175
  }
259
176
  }
260
177
  ```
261
178
 
262
- または環境変数 `CONTEXT7_API_KEY` を設定。
263
-
264
- `disabled_mcps: ["context7"]` で無効化可能。
179
+ | フォーマット | 出力例 |
180
+ |-------------|--------|
181
+ | `markdown` | そのまま (デフォルト) |
182
+ | `xml` | `<section name="Rules">...</section>` |
183
+ | `json` | `{"section":"Rules","content":"..."}` |
265
184
 
266
185
  ## キーワード検知
267
186
 
268
- ユーザーのプロンプトからキーワードを検知し、system prompt に動作モード指示を注入する。
269
- コードブロック (`` ``` `` / `` ` ``) 内のキーワードは無視する。
270
-
271
187
  | キーワード | モード | 効果 |
272
188
  |-----------|--------|------|
273
- | `ultrawork` / `ulw` | ultrawork | variant "max" に設定。全エージェント活用・並列実行・TODO 追跡を強制。ユーザーメッセージからキーワードを除去 |
274
- | `search` / `find` / `探して` / `検索` 等 | search | 複数 explore + librarian エージェントを並列起動。網羅的検索を強制 |
275
- | `analyze` / `調査` / `なぜ` / `debug` 等 | analyze | コンテキスト収集フェーズを実行。複雑な場合は Oracle に委任 |
189
+ | `ultrawork` / `ulw` | ultrawork | variant "max"、全エージェント活用 |
190
+ | `search` / `find` / `探して` 等 | search | 網羅的検索 |
191
+ | `analyze` / `調査` / `debug` 等 | analyze | コンテキスト収集 |
192
+ | `think hard` / `じっくり` / `熟考` 等 | think | Extended thinking 有効化 |
276
193
 
277
194
  日本語・中国語キーワードにも対応。
278
195
 
279
- ## ルール注入
280
-
281
- `<project>/.opencode/rules.md` が存在する場合、その内容を system prompt に `## Project Rules` として自動注入する。ファイルの mtime でキャッシュし、変更時のみ再読み込み。
282
-
283
196
  ## 設定
284
197
 
285
- 2 段階の設定マージ:
198
+ 2段階の設定マージ:
286
199
 
287
200
  1. **ユーザー設定**: `~/.config/opencode/oh-my-opencode.json[c]`
288
201
  2. **プロジェクト設定**: `<project>/.opencode/oh-my-opencode.json[c]`
289
202
 
290
- プロジェクト設定がユーザー設定をオーバーライドする。
291
-
292
203
  ### 設定スキーマ
293
204
 
294
205
  ```jsonc
295
206
  {
296
- // エージェントごとのモデル・プロンプトオーバーライド
207
+ // エージェントオーバーライド
297
208
  "agents": {
298
209
  "sisyphus": { "model": "openai/gpt-5.2" },
299
- "oracle": { "model": "zai-coding-plan/glm-5" },
300
- "explore": { "model": "openai/gpt-5.3-codex-spark" }
210
+ "oracle": { "model": "zai-coding-plan/glm-5" }
301
211
  },
302
212
 
303
- // カテゴリ (spawn_agent の category パラメータで使用)
213
+ // カテゴリ
304
214
  "categories": {
305
215
  "quick": { "model": "openai/gpt-4o-mini" }
306
216
  },
307
217
 
218
+ // Fragment Injection
219
+ "fragments": {
220
+ "hephaestus": [".opencode/fragments/strict.md"]
221
+ },
222
+
223
+ // Per-model Renderer
224
+ "prompt_renderer": {
225
+ "default": "markdown",
226
+ "model_overrides": {}
227
+ },
228
+
308
229
  // 無効化
309
230
  "disabled_agents": [],
310
- "disabled_hooks": ["keyword-detector", "rules-injector", "todo-enforcer", "comment-checker"],
311
- "disabled_tools": ["ralph_loop", "cancel_ralph", "spawn_agent"],
312
- "disabled_mcps": ["context7"],
231
+ "disabled_hooks": [], // keyword-detector, rules-injector, context-injector,
232
+ // fragment-injector, prompt-renderer, comment-checker,
233
+ // token-truncation, todo-enforcer, session-compaction
234
+ "disabled_tools": [], // spawn_agent, ralph_loop, cancel_ralph,
235
+ // batch_read, ledger_save, ledger_load, ast_search
236
+ "disabled_mcps": [], // context7
237
+
238
+ // Built-in Agent Demotion (default: true)
239
+ "demote_builtin": true,
313
240
 
314
241
  // 並列制御
315
242
  "background_task": {
316
243
  "defaultConcurrency": 3,
317
- "providerConcurrency": {
318
- "openai": 8,
319
- "zai-coding-plan": 10
320
- },
321
- "modelConcurrency": {
322
- "openai/gpt-5.2": 2
323
- }
244
+ "providerConcurrency": { "openai": 8 },
245
+ "modelConcurrency": { "openai/gpt-5.2": 2 }
324
246
  },
325
247
 
326
- // Comment Checker 設定
327
- "comment_checker": {
328
- "maxRatio": 0.3,
329
- "slopThreshold": 3
330
- },
248
+ // Token Truncation
249
+ "token_truncation": { "maxChars": 30000 },
331
250
 
332
- // Todo Enforcer 設定
333
- "todo_enforcer": {
334
- "maxEnforcements": 5
335
- },
251
+ // Comment Checker
252
+ "comment_checker": { "maxRatio": 0.3, "slopThreshold": 3 },
253
+
254
+ // Todo Enforcer
255
+ "todo_enforcer": { "maxEnforcements": 5 },
336
256
 
337
257
  // MCP API キー
338
- "mcp_api_keys": {
339
- "context7": "ctx7sk-..."
340
- }
258
+ "mcp_api_keys": { "context7": "ctx7sk-..." }
341
259
  }
342
260
  ```
343
261
 
344
- ### エージェントオーバーライドで使えるフィールド
345
-
346
- | フィールド | 型 | 説明 |
347
- |-----------|---|------|
348
- | `model` | string | 使用モデル (provider/model 形式) |
349
- | `description` | string | エージェントの説明 |
350
- | `prompt` | string | システムプロンプト全体を差し替え |
351
- | `prompt_append` | string | プロンプト末尾に追記 |
352
- | `disable` | boolean | 個別に無効化 |
353
- | `mode` | `"subagent"` / `"primary"` / `"all"` | 実行モード |
354
- | `maxTokens` | number | 最大出力トークン |
355
- | `thinking` | `{ type, budgetTokens }` | 思考トークン設定 |
356
- | `reasoningEffort` | `"low"` / `"medium"` / `"high"` / `"xhigh"` | 推論レベル |
357
- | `temperature` | number (0-2) | サンプリング温度 |
358
- | `top_p` | number (0-1) | Top-p |
359
- | `category` | string | デフォルトカテゴリ |
360
- | `variant` | string | モデルバリアント |
361
-
362
- ## フック再帰防止
363
-
364
- spawn_agent / ralph_loop で生成されたエフェメラルセッションは `internalSessions` Set で追跡される。`chat.message`、`system.transform`、`tool.execute.after`、`event` の各フックは内部セッションに対してスキップされ、キーワード検知・ルール注入・Comment Checker・Todo Enforcer がサブエージェントに二重適用されることを防ぐ。
365
-
366
262
  ## プロジェクト構成
367
263
 
368
264
  ```
369
265
  opencode-ultra/
370
266
  ├── src/
371
- │ ├── index.ts # プラグインエントリポイント (hook/tool/MCP 統合)
372
- │ ├── config.ts # 設定ロード・マージ・バリデーション (Zod)
267
+ │ ├── index.ts # プラグインエントリポイント
268
+ │ ├── config.ts # 設定ロード・マージ・バリデーション
373
269
  │ ├── agents/
374
- │ │ ├── types.ts # AgentDef 型定義
375
- │ │ └── index.ts # ビルトインエージェント定義 + 動的プロンプト生成
270
+ │ │ ├── types.ts # AgentDef 型定義
271
+ │ │ └── index.ts # ビルトインエージェント + 動的プロンプト生成
376
272
  │ ├── hooks/
377
- │ │ ├── keyword-detector.ts # ultrawork/search/analyze キーワード検知
378
- │ │ ├── rules-injector.ts # .opencode/rules.md 注入 (mtime キャッシュ)
379
- │ │ ├── todo-enforcer.ts # session.idle 時の未完了 TODO 強制継続
380
- │ │ └── comment-checker.ts # Write/Edit 後の AI スロップ検知
273
+ │ │ ├── keyword-detector.ts # ultrawork/search/analyze/think 検知
274
+ │ │ ├── rules-injector.ts # rules.md + ARCHITECTURE.md + CODE_STYLE.md 注入
275
+ │ │ ├── fragment-injector.ts # エージェント毎の断片注入
276
+ │ │ ├── prompt-renderer.ts # モデル別フォーマット変換
277
+ │ │ ├── comment-checker.ts # AI スロップコメント検知
278
+ │ │ ├── token-truncation.ts # ツール出力の賢い圧縮
279
+ │ │ ├── todo-enforcer.ts # 未完了 TODO 強制継続
280
+ │ │ └── session-compaction.ts # セッション圧縮サマリ生成
381
281
  │ ├── tools/
382
- │ │ ├── spawn-agent.ts # spawn_agent ツール (並列実行 + 並列制御 + カテゴリ)
383
- │ │ └── ralph-loop.ts # ralph_loop / cancel_ralph ツール (自律ループ)
384
- │ ├── concurrency/
385
- │ │ ├── semaphore.ts # 汎用セマフォ
386
- │ │ ├── pool.ts # ConcurrencyPool (global/provider/model 三層制御)
387
- │ └── index.ts # バレルエクスポート
388
- │ ├── categories/
389
- │ └── index.ts # ビルトインカテゴリ + resolveCategory
390
- ├── mcp/
391
- │ │ ├── servers.ts # ビルトイン MCP サーバー定義
392
- │ │ └── index.ts # MCP 登録ロジック
393
- │ └── shared/
394
- │ ├── paths.ts # XDG パス解決
395
- │ ├── jsonc.ts # JSONC パーサー
396
- │ ├── log.ts # [opencode-ultra] プレフィックスログ
397
- │ └── index.ts # バレルエクスポート
398
- ├── __test__/ # Bun テスト (70 tests)
282
+ │ │ ├── spawn-agent.ts # 並列エージェント実行
283
+ │ │ ├── ralph-loop.ts # 自律ループ実行
284
+ ├── batch-read.ts # 複数ファイル並列読み込み
285
+ │ │ ├── continuity-ledger.ts # セッション間文脈継続
286
+ │ │ └── ast-search.ts # AST-aware コード検索
287
+ ├── concurrency/ # Semaphore + ConcurrencyPool
288
+ │ ├── categories/ # ビルトインカテゴリ
289
+ ├── mcp/ # MCP サーバー登録
290
+ └── shared/ # ユーティリティ
291
+ ├── __test__/ # Bun テスト (92 tests, 20 files)
399
292
  ├── package.json
400
293
  ├── tsconfig.json
401
294
  └── .gitignore
@@ -404,13 +297,8 @@ opencode-ultra/
404
297
  ## ビルド・テスト
405
298
 
406
299
  ```bash
407
- # 依存インストール
408
300
  bun install
409
-
410
- # ビルド
411
- bun build src/index.ts --outdir dist --target bun --format esm
412
-
413
- # テスト
301
+ bun run build
414
302
  bun test
415
303
  ```
416
304
 
@@ -1,13 +1,14 @@
1
+ import type { ModelRef } from "../shared/types";
1
2
  export type PromptFormat = "markdown" | "xml" | "json";
2
3
  export type PromptRendererConfig = {
3
4
  default: PromptFormat;
4
5
  model_overrides: Record<string, PromptFormat>;
5
6
  };
6
- export declare function resolvePromptFormat(inputModel: any, config?: Partial<PromptRendererConfig>): PromptFormat;
7
+ export declare function resolvePromptFormat(inputModel: ModelRef | undefined, config?: Partial<PromptRendererConfig>): PromptFormat;
7
8
  export declare function formatSystemSection(format: PromptFormat, content: string): string;
8
9
  export declare function createPromptRendererHook(internalSessions: Set<string>, config?: Partial<PromptRendererConfig>): (input: {
9
10
  sessionID?: string;
10
- model?: unknown;
11
+ model?: ModelRef;
11
12
  }, output: {
12
13
  system: string[];
13
14
  }) => void;
@@ -11,7 +11,7 @@ type MessageLike = {
11
11
  };
12
12
  export declare function buildSessionSummary(messages: MessageLike[]): string;
13
13
  export declare function createSessionCompactionHook(ctx: {
14
- client: any;
14
+ client: Pick<import("../shared/types").ExtendedClient, "session">;
15
15
  }, internalSessions: Set<string>): (input: {
16
16
  sessionID: string;
17
17
  }, output: {
package/dist/index.js CHANGED
@@ -14369,6 +14369,49 @@ function log(message, data) {
14369
14369
  console.error(`${PREFIX} ${message}`);
14370
14370
  }
14371
14371
  }
14372
+ // src/shared/ttl-map.ts
14373
+ class TtlMap {
14374
+ map = new Map;
14375
+ maxSize;
14376
+ ttlMs;
14377
+ constructor(opts = {}) {
14378
+ this.maxSize = opts.maxSize ?? 1000;
14379
+ this.ttlMs = opts.ttlMs ?? 0;
14380
+ }
14381
+ get(key) {
14382
+ const entry = this.map.get(key);
14383
+ if (!entry)
14384
+ return;
14385
+ if (this.ttlMs > 0 && Date.now() - entry.createdAt > this.ttlMs) {
14386
+ this.map.delete(key);
14387
+ return;
14388
+ }
14389
+ return entry.value;
14390
+ }
14391
+ set(key, value) {
14392
+ if (this.map.has(key)) {
14393
+ this.map.delete(key);
14394
+ }
14395
+ if (this.map.size >= this.maxSize) {
14396
+ const oldest = this.map.keys().next().value;
14397
+ if (oldest !== undefined)
14398
+ this.map.delete(oldest);
14399
+ }
14400
+ this.map.set(key, { value, createdAt: Date.now() });
14401
+ }
14402
+ has(key) {
14403
+ return this.get(key) !== undefined;
14404
+ }
14405
+ delete(key) {
14406
+ return this.map.delete(key);
14407
+ }
14408
+ get size() {
14409
+ return this.map.size;
14410
+ }
14411
+ clear() {
14412
+ this.map.clear();
14413
+ }
14414
+ }
14372
14415
  // src/config.ts
14373
14416
  var AgentOverrideSchema = exports_external.object({
14374
14417
  model: exports_external.string().optional(),
@@ -14824,7 +14867,7 @@ var THINK_MESSAGE = `Extended thinking enabled. Take your time to reason thoroug
14824
14867
  // src/hooks/rules-injector.ts
14825
14868
  import * as fs2 from "fs";
14826
14869
  import * as path2 from "path";
14827
- var cache = new Map;
14870
+ var cache = new TtlMap({ maxSize: 50, ttlMs: 10 * 60 * 1000 });
14828
14871
  function loadCachedFile(filePath) {
14829
14872
  try {
14830
14873
  if (!fs2.existsSync(filePath))
@@ -14882,7 +14925,7 @@ function loadCodeStyle(projectDir) {
14882
14925
  // src/hooks/fragment-injector.ts
14883
14926
  import * as fs3 from "fs";
14884
14927
  import * as path3 from "path";
14885
- var cache2 = new Map;
14928
+ var cache2 = new TtlMap({ maxSize: 100, ttlMs: 10 * 60 * 1000 });
14886
14929
  async function readCached(absPath) {
14887
14930
  try {
14888
14931
  const st = await fs3.promises.stat(absPath);
@@ -27322,7 +27365,8 @@ function resolveCategory(categoryName, configCategories) {
27322
27365
 
27323
27366
  // src/tools/spawn-agent.ts
27324
27367
  function showToast(ctx, title, message, variant = "info") {
27325
- ctx.client?.tui?.showToast?.({
27368
+ const client = ctx.client;
27369
+ client.tui?.showToast?.({
27326
27370
  body: { title, message, variant, duration: 2000 }
27327
27371
  })?.catch?.(() => {});
27328
27372
  }
@@ -27427,6 +27471,18 @@ spawn_agent({
27427
27471
  if (!agents || agents.length === 0) {
27428
27472
  return "No agents specified.";
27429
27473
  }
27474
+ for (let i = 0;i < agents.length; i++) {
27475
+ const t = agents[i];
27476
+ if (!t.agent || typeof t.agent !== "string" || t.agent.trim().length === 0) {
27477
+ return `Error: agents[${i}].agent is required (non-empty string)`;
27478
+ }
27479
+ if (!t.prompt || typeof t.prompt !== "string" || t.prompt.trim().length === 0) {
27480
+ return `Error: agents[${i}].prompt is required (non-empty string)`;
27481
+ }
27482
+ if (!t.description || typeof t.description !== "string") {
27483
+ return `Error: agents[${i}].description is required`;
27484
+ }
27485
+ }
27430
27486
  const agentNames = agents.map((a) => a.agent);
27431
27487
  log("spawn_agent", { count: agents.length, agents: agentNames });
27432
27488
  showToast(ctx, "spawn_agent", `${agents.length} agents: ${agentNames.join(", ")}`);
@@ -28190,6 +28246,8 @@ class Semaphore {
28190
28246
  });
28191
28247
  }
28192
28248
  release() {
28249
+ if (this.active <= 0)
28250
+ return;
28193
28251
  this.active--;
28194
28252
  const next = this.queue.shift();
28195
28253
  if (next)
@@ -28279,7 +28337,8 @@ async function registerMcps(ctx, disabled, apiKeys) {
28279
28337
  if (config3.env) {
28280
28338
  body.env = config3.env;
28281
28339
  }
28282
- await ctx.client.mcp?.add?.({ body });
28340
+ const client = ctx.client;
28341
+ await client.mcp?.add?.({ body });
28283
28342
  log(`MCP ${name} registered${apiKey ? " (with API key)" : ""}`);
28284
28343
  } catch (err) {
28285
28344
  log(`MCP ${name} registration failed: ${err}`);
@@ -28313,10 +28372,10 @@ var OpenCodeUltra = async (ctx) => {
28313
28372
  const todoEnforcer = createTodoEnforcer(ctx, internalSessions, pluginConfig.todo_enforcer?.maxEnforcements);
28314
28373
  const commentCheckerHook = createCommentCheckerHook(internalSessions, pluginConfig.comment_checker?.maxRatio, pluginConfig.comment_checker?.slopThreshold);
28315
28374
  const tokenTruncationHook = createTokenTruncationHook(internalSessions, pluginConfig.token_truncation?.maxChars);
28316
- const sessionCompactionHook = createSessionCompactionHook(ctx, internalSessions);
28375
+ const sessionCompactionHook = createSessionCompactionHook({ client: ctx.client }, internalSessions);
28317
28376
  const fragmentInjector = createFragmentInjector(ctx, internalSessions, pluginConfig.fragments);
28318
28377
  const promptRendererHook = createPromptRendererHook(internalSessions, pluginConfig.prompt_renderer);
28319
- const pendingKeywords = new Map;
28378
+ const pendingKeywords = new TtlMap({ maxSize: 200, ttlMs: 5 * 60 * 1000 });
28320
28379
  log("Config loaded", {
28321
28380
  agentCount: Object.keys(agents).length,
28322
28381
  disabledHooks: [...disabledHooks],
@@ -28397,7 +28456,8 @@ var OpenCodeUltra = async (ctx) => {
28397
28456
  if (output.message.variant === undefined) {
28398
28457
  output.message.variant = "max";
28399
28458
  }
28400
- ctx.client?.tui?.showToast?.({
28459
+ const client = ctx.client;
28460
+ client.tui?.showToast?.({
28401
28461
  body: {
28402
28462
  title: "ULTRAWORK MODE",
28403
28463
  message: "Maximum precision engaged. All agents at your disposal.",
@@ -1,3 +1,5 @@
1
1
  export { getConfigDir, getCacheDir, getDataDir } from "./paths";
2
2
  export { parseJsonc } from "./jsonc";
3
3
  export { log } from "./log";
4
+ export type { ExtendedClient, ModelRef, ToastOptions, SystemTransformInput } from "./types";
5
+ export { TtlMap } from "./ttl-map";
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Simple Map with max size eviction (LRU-like: evicts oldest entry when full).
3
+ * Optionally supports TTL-based expiry.
4
+ */
5
+ export declare class TtlMap<K, V> {
6
+ private map;
7
+ private maxSize;
8
+ private ttlMs;
9
+ constructor(opts?: {
10
+ maxSize?: number;
11
+ ttlMs?: number;
12
+ });
13
+ get(key: K): V | undefined;
14
+ set(key: K, value: V): void;
15
+ has(key: K): boolean;
16
+ delete(key: K): boolean;
17
+ get size(): number;
18
+ clear(): void;
19
+ }
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Extended type definitions for OpenCode client APIs that are not (yet) in @opencode-ai/sdk types.
3
+ * These describe the actual runtime shape observed in OpenCode 1.2.x.
4
+ */
5
+ /** Model identifier as passed through hook input */
6
+ export interface ModelRef {
7
+ providerID?: string;
8
+ modelID?: string;
9
+ }
10
+ /** Toast notification (TUI-only, non-standard) */
11
+ export interface ToastOptions {
12
+ body: {
13
+ title: string;
14
+ message: string;
15
+ variant?: "info" | "success" | "warning" | "error";
16
+ duration?: number;
17
+ };
18
+ }
19
+ /** Extended OpenCode client with optional TUI and MCP methods */
20
+ export interface ExtendedClient {
21
+ session: {
22
+ create: (opts: {
23
+ body: Record<string, unknown>;
24
+ query?: {
25
+ directory?: string;
26
+ };
27
+ }) => Promise<{
28
+ data?: {
29
+ id?: string;
30
+ };
31
+ }>;
32
+ prompt: (opts: {
33
+ path: {
34
+ id: string;
35
+ };
36
+ body: {
37
+ parts: Array<{
38
+ type: string;
39
+ text: string;
40
+ }>;
41
+ agent?: string;
42
+ };
43
+ query?: {
44
+ directory?: string;
45
+ };
46
+ }) => Promise<unknown>;
47
+ messages: (opts: {
48
+ path: {
49
+ id: string;
50
+ };
51
+ query?: {
52
+ directory?: string;
53
+ };
54
+ }) => Promise<{
55
+ data?: Array<{
56
+ info?: {
57
+ role?: string;
58
+ };
59
+ parts?: Array<{
60
+ type: string;
61
+ text?: string;
62
+ }>;
63
+ }>;
64
+ }>;
65
+ delete: (opts: {
66
+ path: {
67
+ id: string;
68
+ };
69
+ query?: {
70
+ directory?: string;
71
+ };
72
+ }) => Promise<unknown>;
73
+ listMessages?: (opts: {
74
+ id: string;
75
+ }) => Promise<{
76
+ messages?: unknown[];
77
+ } | unknown[]>;
78
+ };
79
+ tui?: {
80
+ showToast?: (opts: ToastOptions) => Promise<void>;
81
+ };
82
+ mcp?: {
83
+ add?: (opts: {
84
+ body: Record<string, unknown>;
85
+ }) => Promise<void>;
86
+ };
87
+ }
88
+ /** System transform hook input */
89
+ export interface SystemTransformInput {
90
+ sessionID?: string;
91
+ model?: ModelRef;
92
+ agent?: string;
93
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-ultra",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "Lightweight OpenCode 1.2.x plugin — ultrawork mode, multi-agent orchestration, rules injection",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",