opencode-adaptive-snip 0.1.0
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/LICENSE +21 -0
- package/README.md +841 -0
- package/dist/analyze.d.ts +23 -0
- package/dist/analyze.js +333 -0
- package/dist/config.d.ts +2 -0
- package/dist/config.js +81 -0
- package/dist/events.d.ts +3 -0
- package/dist/events.js +57 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +20 -0
- package/dist/rules.d.ts +4 -0
- package/dist/rules.js +39 -0
- package/dist/snip.d.ts +4 -0
- package/dist/snip.js +57 -0
- package/dist/store.d.ts +12 -0
- package/dist/store.js +119 -0
- package/dist/types.d.ts +185 -0
- package/dist/types.js +54 -0
- package/dist/utils.d.ts +12 -0
- package/dist/utils.js +216 -0
- package/package.json +40 -0
package/README.md
ADDED
|
@@ -0,0 +1,841 @@
|
|
|
1
|
+
# opencode-adaptive-snip
|
|
2
|
+
|
|
3
|
+
AI 驱动的 Shell 命令预处理插件 — 自动学习哪些命令输出过多,自动注入 `snip` 前缀来压缩命令输出。
|
|
4
|
+
|
|
5
|
+
[English](#english) | [中文](#中文)
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 中文
|
|
10
|
+
|
|
11
|
+
### 做什么
|
|
12
|
+
|
|
13
|
+
`opencode-adaptive-snip` 是 OpenCode 插件,两个核心功能:
|
|
14
|
+
|
|
15
|
+
1. **Adaptive Snip 模式** — 拦截所有 `bash` 工具调用,匹配规则表,自动注入 `snip` 前缀
|
|
16
|
+
2. **Analyze 模式** — 监听 shell 会话,收集命令/输出对,批量发给 LLM 分析,自动生成 snip 规则
|
|
17
|
+
|
|
18
|
+
### 架构
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
┌─────────────────────────────────────────────────────┐
|
|
22
|
+
│ OpenCode Runtime │
|
|
23
|
+
├─────────────────────────────────────────────────────┤
|
|
24
|
+
│ tool.execute.before (snip.ts) │
|
|
25
|
+
│ ├─ 拦截 bash 命令 │
|
|
26
|
+
│ ├─ 规则匹配 (config → learned → fallback) │
|
|
27
|
+
│ ├─ 注入 snip 前缀 │
|
|
28
|
+
│ └─ 处理 pipe / operator / env var │
|
|
29
|
+
├─────────────────────────────────────────────────────┤
|
|
30
|
+
│ event (events.ts) │
|
|
31
|
+
│ ├─ session.next.shell.started → 记录命令 │
|
|
32
|
+
│ ├─ session.next.shell.ended → 收集输出 │
|
|
33
|
+
│ └─ 触发 analyze() │
|
|
34
|
+
├─────────────────────────────────────────────────────┤
|
|
35
|
+
│ LearnAnalyzer (analyze.ts) │
|
|
36
|
+
│ ├─ 缓冲 command/output 对 │
|
|
37
|
+
│ ├─ 构建 prompt → LLM 分析 │
|
|
38
|
+
│ ├─ 解析 JSON → mergeLearned() │
|
|
39
|
+
│ └─ 存储到 .opencode/snip-rules.json │
|
|
40
|
+
└─────────────────────────────────────────────────────┘
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### 安装
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
# 在 OpenCode 项目目录
|
|
47
|
+
opencode plugin opencode-adaptive-snip
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
该命令会安装 npm 包并自动更新 OpenCode 配置。需要安装到全局配置时使用:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
opencode plugin --global opencode-adaptive-snip
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### 配置
|
|
57
|
+
|
|
58
|
+
**推荐方式:独立配置文件**
|
|
59
|
+
|
|
60
|
+
插件自动发现项目根目录下的 `adaptive-snip.json`。使用 `opencode plugin opencode-adaptive-snip` 安装后,规则配置无需写入 `opencode.json`。
|
|
61
|
+
|
|
62
|
+
`adaptive-snip.json`(放在项目根目录):
|
|
63
|
+
```jsonc
|
|
64
|
+
{
|
|
65
|
+
"analyze": {
|
|
66
|
+
"enabled": true,
|
|
67
|
+
"autoLearn": true,
|
|
68
|
+
"batchSize": 20,
|
|
69
|
+
"minConfidence": 0.7,
|
|
70
|
+
"maxRules": 50,
|
|
71
|
+
"cooldownMinutes": 60
|
|
72
|
+
},
|
|
73
|
+
"rules": [
|
|
74
|
+
{ "pattern": "^go test", "snip": "snip --timeout 120", "source": "config" },
|
|
75
|
+
{ "pattern": "^npm (install|test|run build)", "snip": "snip", "source": "config" }
|
|
76
|
+
],
|
|
77
|
+
"fallback": { "prefix": "snip" },
|
|
78
|
+
"ruleFile": ".opencode/snip-rules.json"
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
`opencode.json`:
|
|
83
|
+
```jsonc
|
|
84
|
+
{
|
|
85
|
+
"plugin": [
|
|
86
|
+
"opencode-adaptive-snip"
|
|
87
|
+
]
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
配置优先级:`opencode.json 内联 options > adaptive-snip.json > 默认值`。
|
|
92
|
+
|
|
93
|
+
**备选方式:内联配置**
|
|
94
|
+
|
|
95
|
+
在 `opencode.json` 中直接写配置(仍支持):
|
|
96
|
+
|
|
97
|
+
```jsonc
|
|
98
|
+
{
|
|
99
|
+
"plugin": [
|
|
100
|
+
[
|
|
101
|
+
"opencode-adaptive-snip",
|
|
102
|
+
{
|
|
103
|
+
// 分析模式配置
|
|
104
|
+
"analyze": {
|
|
105
|
+
"enabled": true, // 启用分析模式
|
|
106
|
+
"autoLearn": true, // 自动学习规则
|
|
107
|
+
"batchSize": 20, // 每批分析的命令数量
|
|
108
|
+
"minConfidence": 0.7, // 最低置信度阈值
|
|
109
|
+
"maxRules": 50, // 最多保留的学习规则数
|
|
110
|
+
"cooldownMinutes": 60, // 两次分析的最小间隔(分钟)
|
|
111
|
+
"llmModel": { // 可选:指定分析用的模型
|
|
112
|
+
"providerID": "openai",
|
|
113
|
+
"modelID": "gpt-4o"
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
// 手动规则(最高优先级)
|
|
118
|
+
"rules": [
|
|
119
|
+
{
|
|
120
|
+
"pattern": "^go test",
|
|
121
|
+
"snip": "snip --timeout 120",
|
|
122
|
+
"flags": "i",
|
|
123
|
+
"description": "Go 测试输出通常很长",
|
|
124
|
+
"source": "config"
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
"pattern": "^npm (install|test|run build)",
|
|
128
|
+
"snip": "snip",
|
|
129
|
+
"source": "config"
|
|
130
|
+
}
|
|
131
|
+
],
|
|
132
|
+
|
|
133
|
+
// 回退 snip 配置(当无规则匹配时使用)
|
|
134
|
+
"fallback": {
|
|
135
|
+
"prefix": "snip"
|
|
136
|
+
},
|
|
137
|
+
|
|
138
|
+
// 学习规则存储路径(相对于项目根目录)
|
|
139
|
+
"ruleFile": ".opencode/snip-rules.json"
|
|
140
|
+
}
|
|
141
|
+
]
|
|
142
|
+
]
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### 完整配置示例
|
|
147
|
+
|
|
148
|
+
以下是一个**生产环境级别的完整配置**,包含所有选项和针对常见工具链的手动规则。保存为项目根目录的 `adaptive-snip.json` 即可自动生效。
|
|
149
|
+
|
|
150
|
+
```jsonc
|
|
151
|
+
{
|
|
152
|
+
// ═══════════════════════════════════════════
|
|
153
|
+
// 分析模式 — LLM 自动学习新规则
|
|
154
|
+
// ═══════════════════════════════════════════
|
|
155
|
+
"analyze": {
|
|
156
|
+
"enabled": true,
|
|
157
|
+
"autoLearn": true,
|
|
158
|
+
"batchSize": 20,
|
|
159
|
+
"minConfidence": 0.7,
|
|
160
|
+
"maxRules": 50,
|
|
161
|
+
"cooldownMinutes": 60,
|
|
162
|
+
// 不指定 llmModel → 使用 OpenCode 当前会话模型
|
|
163
|
+
},
|
|
164
|
+
|
|
165
|
+
// ═══════════════════════════════════════════
|
|
166
|
+
// 手动规则(最高优先级,不会被学习覆盖)
|
|
167
|
+
// ═══════════════════════════════════════════
|
|
168
|
+
"rules": [
|
|
169
|
+
// ── Go ──────────────────────────────
|
|
170
|
+
{
|
|
171
|
+
"pattern": "^go (test|bench|build|vet|lint|mod tidy)",
|
|
172
|
+
"snip": "snip --timeout 120",
|
|
173
|
+
"description": "Go 工具链 — 测试/构建输出冗长",
|
|
174
|
+
"source": "config"
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
"pattern": "^go run",
|
|
178
|
+
"snip": "snip --timeout 60",
|
|
179
|
+
"description": "go run 可能输出运行时日志",
|
|
180
|
+
"source": "config"
|
|
181
|
+
},
|
|
182
|
+
|
|
183
|
+
// ── Node / npm ──────────────────────
|
|
184
|
+
{
|
|
185
|
+
"pattern": "^npm (install|ci|test|run build|run lint|run dev)",
|
|
186
|
+
"snip": "snip --timeout 120",
|
|
187
|
+
"description": "npm 安装/测试/构建输出",
|
|
188
|
+
"source": "config"
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
"pattern": "^npx (vitest|jest|eslint|prettier|tsc|playwright)",
|
|
192
|
+
"snip": "snip --timeout 120",
|
|
193
|
+
"description": "npx 运行测试/lint/构建工具",
|
|
194
|
+
"source": "config"
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
"pattern": "^pnpm (install|test|build|lint)",
|
|
198
|
+
"snip": "snip --timeout 120",
|
|
199
|
+
"source": "config"
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
"pattern": "^yarn (install|test|build|lint)",
|
|
203
|
+
"snip": "snip --timeout 120",
|
|
204
|
+
"source": "config"
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
"pattern": "^bun (install|test|run build|run lint)",
|
|
208
|
+
"snip": "snip --timeout 120",
|
|
209
|
+
"description": "Bun 包管理/测试",
|
|
210
|
+
"source": "config"
|
|
211
|
+
},
|
|
212
|
+
|
|
213
|
+
// ── TypeScript ──────────────────────
|
|
214
|
+
{
|
|
215
|
+
"pattern": "^tsc",
|
|
216
|
+
"snip": "snip --timeout 60",
|
|
217
|
+
"description": "TypeScript 编译错误列表可能很长",
|
|
218
|
+
"source": "config"
|
|
219
|
+
},
|
|
220
|
+
|
|
221
|
+
// ── Python ──────────────────────────
|
|
222
|
+
{
|
|
223
|
+
"pattern": "^(python3?|python) (-m )?(pytest|unittest|mypy|ruff|black|isort)",
|
|
224
|
+
"snip": "snip --timeout 120",
|
|
225
|
+
"description": "Python 测试/lint/格式化",
|
|
226
|
+
"source": "config"
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
"pattern": "^pip (install|freeze|list)",
|
|
230
|
+
"snip": "snip --timeout 60",
|
|
231
|
+
"description": "pip 安装/列表输出",
|
|
232
|
+
"source": "config"
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
"pattern": "^(uv|poetry) (run|add|install|lock|build)",
|
|
236
|
+
"snip": "snip --timeout 120",
|
|
237
|
+
"description": "Python 现代包管理工具",
|
|
238
|
+
"source": "config"
|
|
239
|
+
},
|
|
240
|
+
|
|
241
|
+
// ── Rust ────────────────────────────
|
|
242
|
+
{
|
|
243
|
+
"pattern": "^cargo (test|build|check|clippy|bench|doc)",
|
|
244
|
+
"snip": "snip --timeout 180",
|
|
245
|
+
"description": "Rust 编译/测试输出极长",
|
|
246
|
+
"source": "config"
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
"pattern": "^rustc",
|
|
250
|
+
"snip": "snip --timeout 120",
|
|
251
|
+
"source": "config"
|
|
252
|
+
},
|
|
253
|
+
|
|
254
|
+
// ── C/C++ ──────────────────────────
|
|
255
|
+
{
|
|
256
|
+
"pattern": "^(make|cmake|ninja|gcc|g\\+\\+|clang\\+\\+)",
|
|
257
|
+
"snip": "snip --timeout 180",
|
|
258
|
+
"description": "C/C++ 编译输出可能极长",
|
|
259
|
+
"source": "config"
|
|
260
|
+
},
|
|
261
|
+
|
|
262
|
+
// ── Java / JVM ──────────────────────
|
|
263
|
+
{
|
|
264
|
+
"pattern": "^(mvn|gradle|./gradlew|./mvnw)",
|
|
265
|
+
"snip": "snip --timeout 180",
|
|
266
|
+
"description": "Maven/Gradle 构建输出",
|
|
267
|
+
"source": "config"
|
|
268
|
+
},
|
|
269
|
+
|
|
270
|
+
// ── Docker ──────────────────────────
|
|
271
|
+
{
|
|
272
|
+
"pattern": "^docker (build|compose|logs|ps|images|system)",
|
|
273
|
+
"snip": "snip --timeout 120",
|
|
274
|
+
"description": "Docker 构建/日志",
|
|
275
|
+
"source": "config"
|
|
276
|
+
},
|
|
277
|
+
|
|
278
|
+
// ── Kubernetes ──────────────────────
|
|
279
|
+
{
|
|
280
|
+
"pattern": "^kubectl (get|describe|logs|apply|delete)",
|
|
281
|
+
"snip": "snip --timeout 60",
|
|
282
|
+
"description": "K8s 资源查询/操作",
|
|
283
|
+
"source": "config"
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
"pattern": "^(helm|k9s|kustomize|argocd)",
|
|
287
|
+
"snip": "snip --timeout 60",
|
|
288
|
+
"source": "config"
|
|
289
|
+
},
|
|
290
|
+
|
|
291
|
+
// ── Git ─────────────────────────────
|
|
292
|
+
{
|
|
293
|
+
"pattern": "^git (log|diff|show|blame|status|branch)",
|
|
294
|
+
"snip": "snip --timeout 30",
|
|
295
|
+
"description": "Git 查询命令 — 日志/diff 可能很长",
|
|
296
|
+
"source": "config"
|
|
297
|
+
},
|
|
298
|
+
|
|
299
|
+
// ── Shell 批处理 ────────────────────
|
|
300
|
+
{
|
|
301
|
+
"pattern": "^(find|locate|tree|du|df|ls -l)",
|
|
302
|
+
"snip": "snip --timeout 30",
|
|
303
|
+
"description": "文件系统查询 — 大目录输出极多",
|
|
304
|
+
"source": "config"
|
|
305
|
+
},
|
|
306
|
+
{
|
|
307
|
+
"pattern": "^(ps|top|htop|netstat|ss|lsof)",
|
|
308
|
+
"snip": "snip --timeout 30",
|
|
309
|
+
"description": "系统进程/网络查询",
|
|
310
|
+
"source": "config"
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
"pattern": "^curl.*(-v|--verbose|--trace)",
|
|
314
|
+
"snip": "snip --timeout 30",
|
|
315
|
+
"description": "curl verbose 输出",
|
|
316
|
+
"source": "config"
|
|
317
|
+
},
|
|
318
|
+
|
|
319
|
+
// ── 包管理器通用 ────────────────────
|
|
320
|
+
{
|
|
321
|
+
"pattern": "^(apt|apt-get|brew|dnf|yum|pacman|zypper) (install|update|upgrade|search|list)",
|
|
322
|
+
"snip": "snip --timeout 120",
|
|
323
|
+
"description": "系统包管理安装/搜索",
|
|
324
|
+
"source": "config"
|
|
325
|
+
},
|
|
326
|
+
|
|
327
|
+
// ── Linter / Formatter 类 ───────────
|
|
328
|
+
{
|
|
329
|
+
"pattern": "^(eslint|prettier|biome|oxlint|stylelint|clang-format)",
|
|
330
|
+
"snip": "snip --timeout 60",
|
|
331
|
+
"description": "Linter/Formatter 输出",
|
|
332
|
+
"source": "config"
|
|
333
|
+
},
|
|
334
|
+
|
|
335
|
+
// ── 测试框架 ────────────────────────
|
|
336
|
+
{
|
|
337
|
+
"pattern": "^(vitest|jest|mocha|ava|tap|cypress|playwright test)",
|
|
338
|
+
"snip": "snip --timeout 120",
|
|
339
|
+
"description": "JS/TS 测试框架",
|
|
340
|
+
"source": "config"
|
|
341
|
+
},
|
|
342
|
+
|
|
343
|
+
// ── 构建工具 ────────────────────────
|
|
344
|
+
{
|
|
345
|
+
"pattern": "^(webpack|vite|rollup|esbuild|turbo|nx|lage) (build|serve|dev)",
|
|
346
|
+
"snip": "snip --timeout 120",
|
|
347
|
+
"description": "前端构建工具",
|
|
348
|
+
"source": "config"
|
|
349
|
+
}
|
|
350
|
+
],
|
|
351
|
+
|
|
352
|
+
// ═══════════════════════════════════════════
|
|
353
|
+
// 无规则匹配时:给所有命令自动加 "snip"
|
|
354
|
+
// 设为 null 则不加
|
|
355
|
+
// ═══════════════════════════════════════════
|
|
356
|
+
"fallback": {
|
|
357
|
+
"prefix": "snip"
|
|
358
|
+
},
|
|
359
|
+
|
|
360
|
+
// ═══════════════════════════════════════════
|
|
361
|
+
// 学习规则存储路径
|
|
362
|
+
// ═══════════════════════════════════════════
|
|
363
|
+
"ruleFile": ".opencode/snip-rules.json"
|
|
364
|
+
}
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
> 💡 **使用建议**:将此配置放入项目根目录的 `adaptive-snip.json`,然后根据你的项目技术栈增删 `rules` 中的条目。LLM 分析会自动补全你遗漏的规则。你手动配置的规则永远不会被 LLM 覆写。
|
|
368
|
+
|
|
369
|
+
### 配置项详解
|
|
370
|
+
|
|
371
|
+
#### `analyze` — 分析模式
|
|
372
|
+
|
|
373
|
+
| 字段 | 类型 | 默认值 | 说明 |
|
|
374
|
+
|------|------|--------|------|
|
|
375
|
+
| `enabled` | `boolean` | `false` | 是否启用 LLM 分析 |
|
|
376
|
+
| `autoLearn` | `boolean` | `false` | 自动保存学习到的规则(依赖 `enabled`) |
|
|
377
|
+
| `batchSize` | `number` | `20` | 积累多少个命令/输出对后触发分析 |
|
|
378
|
+
| `minConfidence` | `number` | `0.7` | 置信度阈值,低于此值的建议不保存 |
|
|
379
|
+
| `maxRules` | `number` | `50` | 学习规则数量上限 |
|
|
380
|
+
| `cooldownMinutes` | `number` | `60` | 两次分析之间的冷却时间 |
|
|
381
|
+
| `llmModel` | `object` | — | 指定 LLM 模型,不指定则使用 OpenCode 默认模型 |
|
|
382
|
+
|
|
383
|
+
控制分析模式有三种方式:
|
|
384
|
+
|
|
385
|
+
```jsonc
|
|
386
|
+
// 1. 禁用(默认)
|
|
387
|
+
{ "analyze": false }
|
|
388
|
+
|
|
389
|
+
// 2. 启用全默认
|
|
390
|
+
{ "analyze": true }
|
|
391
|
+
|
|
392
|
+
// 3. 自定义
|
|
393
|
+
{
|
|
394
|
+
"analyze": {
|
|
395
|
+
"enabled": true,
|
|
396
|
+
"autoLearn": true,
|
|
397
|
+
"batchSize": 30,
|
|
398
|
+
"llmModel": { "providerID": "anthropic", "modelID": "claude-sonnet-4-20250514" }
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
#### `rules` — 手动规则
|
|
404
|
+
|
|
405
|
+
用户手动配置的规则,**优先级最高**,不会被 LLM 学习覆盖。
|
|
406
|
+
|
|
407
|
+
```jsonc
|
|
408
|
+
{
|
|
409
|
+
"rules": [
|
|
410
|
+
{
|
|
411
|
+
"pattern": "^go (test|build|vet)", // 正则表达式
|
|
412
|
+
"snip": "snip --timeout 120", // snip 命令及参数
|
|
413
|
+
"flags": "i", // 可选:正则标志
|
|
414
|
+
"description": "Go 工具链输出", // 可选:说明
|
|
415
|
+
"source": "config" // 必须为 "config"
|
|
416
|
+
}
|
|
417
|
+
]
|
|
418
|
+
}
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
字段说明:
|
|
422
|
+
|
|
423
|
+
| 字段 | 类型 | 必填 | 说明 |
|
|
424
|
+
|------|------|------|------|
|
|
425
|
+
| `pattern` | `string` | ✅ | 正则表达式,匹配命令文本 |
|
|
426
|
+
| `snip` | `string` | ✅ | snip 命令前缀,如 `"snip"` 或 `"snip --timeout 120"` |
|
|
427
|
+
| `flags` | `string` | ❌ | 正则标志,如 `"i"`(不区分大小写) |
|
|
428
|
+
| `description` | `string` | ❌ | 规则说明 |
|
|
429
|
+
| `source` | `"config"` | ✅ | 必须为 `"config"` |
|
|
430
|
+
|
|
431
|
+
#### `fallback` — 回退前缀
|
|
432
|
+
|
|
433
|
+
当无规则匹配时,可以选择性使用回退 snip:
|
|
434
|
+
|
|
435
|
+
```jsonc
|
|
436
|
+
// 无匹配 → 不加 snip(默认)
|
|
437
|
+
{ "fallback": null }
|
|
438
|
+
|
|
439
|
+
// 无匹配 → 加 "snip" 前缀
|
|
440
|
+
{ "fallback": { "prefix": "snip" } }
|
|
441
|
+
|
|
442
|
+
// 无匹配 → 加 "snip --timeout 60" 前缀
|
|
443
|
+
{ "fallback": { "prefix": "snip --timeout 60" } }
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
#### `llmModel` — 指定分析模型
|
|
447
|
+
|
|
448
|
+
`llmModel` 是 `AnalyzeConfig` 下的一个字段,它让你可以指定用什么模型来进行 LLM 分析,而不依赖 OpenCode 的默认模型。当不指定时,插件使用 OpenCode 的默认模型(当前会话模型)。
|
|
449
|
+
|
|
450
|
+
```jsonc
|
|
451
|
+
{
|
|
452
|
+
"analyze": {
|
|
453
|
+
"enabled": true,
|
|
454
|
+
"autoLearn": true,
|
|
455
|
+
"llmModel": {
|
|
456
|
+
"providerID": "openai", // 提供商标识
|
|
457
|
+
"modelID": "gpt-4o" // 模型标识
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
`providerID` 和 `modelID` 的具体值取决于你的 OpenCode 配置了哪些 provider。例如:
|
|
464
|
+
|
|
465
|
+
- `{ "providerID": "anthropic", "modelID": "claude-sonnet-4-20250514" }`
|
|
466
|
+
- `{ "providerID": "openai", "modelID": "gpt-4o" }`
|
|
467
|
+
|
|
468
|
+
### 规则优先级
|
|
469
|
+
|
|
470
|
+
```
|
|
471
|
+
手动配置规则 (source: "config")
|
|
472
|
+
↓ 无匹配
|
|
473
|
+
学习规则 (source: "learned")
|
|
474
|
+
↓ 无匹配
|
|
475
|
+
回退前缀 (fallback.prefix)
|
|
476
|
+
↓ 无匹配
|
|
477
|
+
不加前缀
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
### 规则文件格式
|
|
481
|
+
|
|
482
|
+
学习规则自动保存到 `.opencode/snip-rules.json`:
|
|
483
|
+
|
|
484
|
+
```json
|
|
485
|
+
{
|
|
486
|
+
"version": 1,
|
|
487
|
+
"rules": [
|
|
488
|
+
{
|
|
489
|
+
"pattern": "^npm test",
|
|
490
|
+
"snip": "snip",
|
|
491
|
+
"confidence": 0.92,
|
|
492
|
+
"description": "npm test 输出包含大量测试日志",
|
|
493
|
+
"source": "learned",
|
|
494
|
+
"createdAt": "2026-05-09T10:30:00.000Z",
|
|
495
|
+
"updatedAt": "2026-05-09T10:30:00.000Z"
|
|
496
|
+
}
|
|
497
|
+
],
|
|
498
|
+
"analytics": {
|
|
499
|
+
"lastAnalysisAt": "2026-05-09T10:30:00.000Z",
|
|
500
|
+
"totalCommandsAnalyzed": 25
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
你可以手动编辑此文件来调整学习规则。
|
|
506
|
+
|
|
507
|
+
### 行为细节
|
|
508
|
+
|
|
509
|
+
#### 不会加 snip 前缀的命令
|
|
510
|
+
|
|
511
|
+
- **Shell 内置命令/敏感 shell 命令**:`cd`, `export`, `alias`, `source`, `echo`, `pwd` 等
|
|
512
|
+
- **已经包含 snip 前缀** 的命令(防重复注入)
|
|
513
|
+
- **环境变量前缀** 被保留:`FOO=bar cmd` → `FOO=bar snip cmd`
|
|
514
|
+
|
|
515
|
+
#### Pipe 和 Operator 处理
|
|
516
|
+
|
|
517
|
+
```bash
|
|
518
|
+
# 输入
|
|
519
|
+
npm test | grep FAIL
|
|
520
|
+
|
|
521
|
+
# 输出(仅第一个命令前加 snip)
|
|
522
|
+
snip npm test | grep FAIL
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
```bash
|
|
526
|
+
# 输入
|
|
527
|
+
go build && go test ./...
|
|
528
|
+
|
|
529
|
+
# 输出(匹配规则的非 operator 段会加 snip)
|
|
530
|
+
snip --timeout 120 go build && snip --timeout 120 go test ./...
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
#### 输出截断
|
|
534
|
+
|
|
535
|
+
分析模式下,命令输出超过 5000 字符会被截断后再发给 LLM。
|
|
536
|
+
|
|
537
|
+
### 调试
|
|
538
|
+
|
|
539
|
+
插件在控制台输出 `[adaptive-snip]` 前缀的日志:
|
|
540
|
+
|
|
541
|
+
- `[adaptive-snip] LLM analysis failed:` — 分析失败(非致命)
|
|
542
|
+
- `[adaptive-snip] Unpaired shell.ended event` — 事件配对异常
|
|
543
|
+
- `[adaptive-snip] analyze.autoLearn=true but analyze.enabled=false` — 配置冲突
|
|
544
|
+
|
|
545
|
+
### 开发
|
|
546
|
+
|
|
547
|
+
```bash
|
|
548
|
+
bun install
|
|
549
|
+
bun test
|
|
550
|
+
npm run typecheck
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
---
|
|
554
|
+
|
|
555
|
+
## English
|
|
556
|
+
|
|
557
|
+
### What It Does
|
|
558
|
+
|
|
559
|
+
`opencode-adaptive-snip` is an OpenCode plugin with two modes:
|
|
560
|
+
|
|
561
|
+
1. **Adaptive Snip** — intercepts all `bash` tool calls, matches rules, auto-prepends `snip` prefix
|
|
562
|
+
2. **Analyze** — monitors shell sessions, buffers command/output pairs, sends batches to LLM for analysis, auto-generates snip rules
|
|
563
|
+
|
|
564
|
+
### Architecture
|
|
565
|
+
|
|
566
|
+
```
|
|
567
|
+
┌─────────────────────────────────────────────────────┐
|
|
568
|
+
│ OpenCode Runtime │
|
|
569
|
+
├─────────────────────────────────────────────────────┤
|
|
570
|
+
│ tool.execute.before (snip.ts) │
|
|
571
|
+
│ ├─ Intercept bash commands │
|
|
572
|
+
│ ├─ Rule matching (config → learned → fallback) │
|
|
573
|
+
│ ├─ Inject snip prefix │
|
|
574
|
+
│ └─ Handle pipes / operators / env vars │
|
|
575
|
+
├─────────────────────────────────────────────────────┤
|
|
576
|
+
│ event (events.ts) │
|
|
577
|
+
│ ├─ session.next.shell.started → record command │
|
|
578
|
+
│ ├─ session.next.shell.ended → capture output │
|
|
579
|
+
│ └─ Trigger analyze() │
|
|
580
|
+
├─────────────────────────────────────────────────────┤
|
|
581
|
+
│ LearnAnalyzer (analyze.ts) │
|
|
582
|
+
│ ├─ Buffer command/output pairs │
|
|
583
|
+
│ ├─ Build prompt → LLM analysis │
|
|
584
|
+
│ ├─ Parse JSON → mergeLearned() │
|
|
585
|
+
│ └─ Save to .opencode/snip-rules.json │
|
|
586
|
+
└─────────────────────────────────────────────────────┘
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
### Installation
|
|
590
|
+
|
|
591
|
+
```bash
|
|
592
|
+
# In your OpenCode project
|
|
593
|
+
opencode plugin opencode-adaptive-snip
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
This installs the npm package and updates your OpenCode config. To install it in global config:
|
|
597
|
+
|
|
598
|
+
```bash
|
|
599
|
+
opencode plugin --global opencode-adaptive-snip
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
### Configuration
|
|
603
|
+
|
|
604
|
+
**Recommended: Separate config file**
|
|
605
|
+
|
|
606
|
+
The plugin auto-discovers `adaptive-snip.json` in the project root. After installing with `opencode plugin opencode-adaptive-snip`, rule configuration does not need to be placed in `opencode.json`.
|
|
607
|
+
|
|
608
|
+
`adaptive-snip.json` (place in project root):
|
|
609
|
+
```jsonc
|
|
610
|
+
{
|
|
611
|
+
"analyze": {
|
|
612
|
+
"enabled": true,
|
|
613
|
+
"autoLearn": true,
|
|
614
|
+
"batchSize": 20,
|
|
615
|
+
"minConfidence": 0.7,
|
|
616
|
+
"maxRules": 50,
|
|
617
|
+
"cooldownMinutes": 60
|
|
618
|
+
},
|
|
619
|
+
"rules": [
|
|
620
|
+
{ "pattern": "^go test", "snip": "snip --timeout 120", "source": "config" },
|
|
621
|
+
{ "pattern": "^npm (install|test|run build)", "snip": "snip", "source": "config" }
|
|
622
|
+
],
|
|
623
|
+
"fallback": { "prefix": "snip" },
|
|
624
|
+
"ruleFile": ".opencode/snip-rules.json"
|
|
625
|
+
}
|
|
626
|
+
```
|
|
627
|
+
|
|
628
|
+
`opencode.json`:
|
|
629
|
+
```jsonc
|
|
630
|
+
{
|
|
631
|
+
"plugin": [
|
|
632
|
+
"opencode-adaptive-snip"
|
|
633
|
+
]
|
|
634
|
+
}
|
|
635
|
+
```
|
|
636
|
+
|
|
637
|
+
Merge priority: `opencode.json inline options > adaptive-snip.json > defaults`.
|
|
638
|
+
|
|
639
|
+
**Alternative: Inline config**
|
|
640
|
+
|
|
641
|
+
Add config directly in `opencode.json` (still supported):
|
|
642
|
+
|
|
643
|
+
```jsonc
|
|
644
|
+
{
|
|
645
|
+
"plugin": [
|
|
646
|
+
[
|
|
647
|
+
"opencode-adaptive-snip",
|
|
648
|
+
{
|
|
649
|
+
"analyze": {
|
|
650
|
+
"enabled": true,
|
|
651
|
+
"autoLearn": true,
|
|
652
|
+
"batchSize": 20,
|
|
653
|
+
"minConfidence": 0.7,
|
|
654
|
+
"maxRules": 50,
|
|
655
|
+
"cooldownMinutes": 60,
|
|
656
|
+
"llmModel": {
|
|
657
|
+
"providerID": "openai",
|
|
658
|
+
"modelID": "gpt-4o"
|
|
659
|
+
}
|
|
660
|
+
},
|
|
661
|
+
"rules": [
|
|
662
|
+
{
|
|
663
|
+
"pattern": "^go test",
|
|
664
|
+
"snip": "snip --timeout 120",
|
|
665
|
+
"source": "config"
|
|
666
|
+
}
|
|
667
|
+
],
|
|
668
|
+
"fallback": { "prefix": "snip" },
|
|
669
|
+
"ruleFile": ".opencode/snip-rules.json"
|
|
670
|
+
}
|
|
671
|
+
]
|
|
672
|
+
]
|
|
673
|
+
}
|
|
674
|
+
```
|
|
675
|
+
|
|
676
|
+
### Complete Configuration Example
|
|
677
|
+
|
|
678
|
+
Below is a **production-grade configuration** with all options and manually-crafted rules for common toolchains. Save as `adaptive-snip.json` in your project root — it's auto-discovered.
|
|
679
|
+
|
|
680
|
+
```jsonc
|
|
681
|
+
{
|
|
682
|
+
// ═══════════════════════════════════════════
|
|
683
|
+
// Analyze mode — LLM learns new rules automatically
|
|
684
|
+
// ═══════════════════════════════════════════
|
|
685
|
+
"analyze": {
|
|
686
|
+
"enabled": true,
|
|
687
|
+
"autoLearn": true,
|
|
688
|
+
"batchSize": 20,
|
|
689
|
+
"minConfidence": 0.7,
|
|
690
|
+
"maxRules": 50,
|
|
691
|
+
"cooldownMinutes": 60
|
|
692
|
+
// Omit llmModel → uses OpenCode current session model
|
|
693
|
+
},
|
|
694
|
+
|
|
695
|
+
// ═══════════════════════════════════════════
|
|
696
|
+
// Manual rules (highest priority, never overwritten by learning)
|
|
697
|
+
// ═══════════════════════════════════════════
|
|
698
|
+
"rules": [
|
|
699
|
+
// ── Go ──────────────────────────────
|
|
700
|
+
{ "pattern": "^go (test|bench|build|vet|lint|mod tidy)", "snip": "snip --timeout 120", "description": "Go toolchain", "source": "config" },
|
|
701
|
+
{ "pattern": "^go run", "snip": "snip --timeout 60", "source": "config" },
|
|
702
|
+
|
|
703
|
+
// ── Node / npm ──────────────────────
|
|
704
|
+
{ "pattern": "^npm (install|ci|test|run build|run lint|run dev)", "snip": "snip --timeout 120", "source": "config" },
|
|
705
|
+
{ "pattern": "^npx (vitest|jest|eslint|prettier|tsc|playwright)", "snip": "snip --timeout 120", "source": "config" },
|
|
706
|
+
{ "pattern": "^pnpm (install|test|build|lint)", "snip": "snip --timeout 120", "source": "config" },
|
|
707
|
+
{ "pattern": "^yarn (install|test|build|lint)", "snip": "snip --timeout 120", "source": "config" },
|
|
708
|
+
{ "pattern": "^bun (install|test|run build|run lint)", "snip": "snip --timeout 120", "source": "config" },
|
|
709
|
+
|
|
710
|
+
// ── TypeScript ──────────────────────
|
|
711
|
+
{ "pattern": "^tsc", "snip": "snip --timeout 60", "description": "TypeScript compiler", "source": "config" },
|
|
712
|
+
|
|
713
|
+
// ── Python ──────────────────────────
|
|
714
|
+
{ "pattern": "^(python3?|python) (-m )?(pytest|unittest|mypy|ruff|black|isort)", "snip": "snip --timeout 120", "source": "config" },
|
|
715
|
+
{ "pattern": "^pip (install|freeze|list)", "snip": "snip --timeout 60", "source": "config" },
|
|
716
|
+
{ "pattern": "^(uv|poetry) (run|add|install|lock|build)", "snip": "snip --timeout 120", "source": "config" },
|
|
717
|
+
|
|
718
|
+
// ── Rust ────────────────────────────
|
|
719
|
+
{ "pattern": "^cargo (test|build|check|clippy|bench|doc)", "snip": "snip --timeout 180", "source": "config" },
|
|
720
|
+
{ "pattern": "^rustc", "snip": "snip --timeout 120", "source": "config" },
|
|
721
|
+
|
|
722
|
+
// ── C/C++ ──────────────────────────
|
|
723
|
+
{ "pattern": "^(make|cmake|ninja|gcc|g\\+\\+|clang\\+\\+)", "snip": "snip --timeout 180", "source": "config" },
|
|
724
|
+
|
|
725
|
+
// ── Java / JVM ──────────────────────
|
|
726
|
+
{ "pattern": "^(mvn|gradle|./gradlew|./mvnw)", "snip": "snip --timeout 180", "source": "config" },
|
|
727
|
+
|
|
728
|
+
// ── Docker ──────────────────────────
|
|
729
|
+
{ "pattern": "^docker (build|compose|logs|ps|images|system)", "snip": "snip --timeout 120", "source": "config" },
|
|
730
|
+
|
|
731
|
+
// ── Kubernetes ──────────────────────
|
|
732
|
+
{ "pattern": "^kubectl (get|describe|logs|apply|delete)", "snip": "snip --timeout 60", "source": "config" },
|
|
733
|
+
{ "pattern": "^(helm|k9s|kustomize|argocd)", "snip": "snip --timeout 60", "source": "config" },
|
|
734
|
+
|
|
735
|
+
// ── Git ─────────────────────────────
|
|
736
|
+
{ "pattern": "^git (log|diff|show|blame|status|branch)", "snip": "snip --timeout 30", "source": "config" },
|
|
737
|
+
|
|
738
|
+
// ── Shell batch queries ─────────────
|
|
739
|
+
{ "pattern": "^(find|locate|tree|du|df|ls -l)", "snip": "snip --timeout 30", "source": "config" },
|
|
740
|
+
{ "pattern": "^(ps|top|htop|netstat|ss|lsof)", "snip": "snip --timeout 30", "source": "config" },
|
|
741
|
+
{ "pattern": "^curl.*(-v|--verbose|--trace)", "snip": "snip --timeout 30", "source": "config" },
|
|
742
|
+
|
|
743
|
+
// ── System package managers ─────────
|
|
744
|
+
{ "pattern": "^(apt|apt-get|brew|dnf|yum|pacman|zypper) (install|update|upgrade|search|list)", "snip": "snip --timeout 120", "source": "config" },
|
|
745
|
+
|
|
746
|
+
// ── Linter / Formatter ──────────────
|
|
747
|
+
{ "pattern": "^(eslint|prettier|biome|oxlint|stylelint|clang-format)", "snip": "snip --timeout 60", "source": "config" },
|
|
748
|
+
|
|
749
|
+
// ── Test frameworks ─────────────────
|
|
750
|
+
{ "pattern": "^(vitest|jest|mocha|ava|tap|cypress|playwright test)", "snip": "snip --timeout 120", "source": "config" },
|
|
751
|
+
|
|
752
|
+
// ── Build tools ─────────────────────
|
|
753
|
+
{ "pattern": "^(webpack|vite|rollup|esbuild|turbo|nx|lage) (build|serve|dev)", "snip": "snip --timeout 120", "source": "config" }
|
|
754
|
+
],
|
|
755
|
+
|
|
756
|
+
// ═══════════════════════════════════════════
|
|
757
|
+
// Fallback: auto-prefix "snip" for unmatched commands
|
|
758
|
+
// Set null to disable
|
|
759
|
+
// ═══════════════════════════════════════════
|
|
760
|
+
"fallback": { "prefix": "snip" },
|
|
761
|
+
|
|
762
|
+
// ═══════════════════════════════════════════
|
|
763
|
+
// Learned rules storage path
|
|
764
|
+
// ═══════════════════════════════════════════
|
|
765
|
+
"ruleFile": ".opencode/snip-rules.json"
|
|
766
|
+
}
|
|
767
|
+
```
|
|
768
|
+
|
|
769
|
+
> 💡 **Tip**: Drop this into `adaptive-snip.json` in your project root, then add/remove `rules` entries to match your tech stack. The LLM analyzer will fill in gaps automatically. Your manual config rules are **never overwritten**.
|
|
770
|
+
|
|
771
|
+
### Options Reference
|
|
772
|
+
|
|
773
|
+
#### `analyze`
|
|
774
|
+
|
|
775
|
+
| Field | Type | Default | Description |
|
|
776
|
+
|-------|------|---------|-------------|
|
|
777
|
+
| `enabled` | `boolean` | `false` | Enable LLM analysis |
|
|
778
|
+
| `autoLearn` | `boolean` | `false` | Auto-save learned rules |
|
|
779
|
+
| `batchSize` | `number` | `20` | Pairs to buffer before analysis |
|
|
780
|
+
| `minConfidence` | `number` | `0.7` | Minimum confidence threshold |
|
|
781
|
+
| `maxRules` | `number` | `50` | Maximum learned rules |
|
|
782
|
+
| `cooldownMinutes` | `number` | `60` | Minimum interval between analyses |
|
|
783
|
+
| `llmModel` | `object` | — | Override LLM model for analysis |
|
|
784
|
+
|
|
785
|
+
#### `rules`
|
|
786
|
+
|
|
787
|
+
User-configured rules. **Highest priority**, never overwritten by learning.
|
|
788
|
+
|
|
789
|
+
| Field | Type | Required | Description |
|
|
790
|
+
|-------|------|----------|-------------|
|
|
791
|
+
| `pattern` | `string` | ✅ | Regex to match command text |
|
|
792
|
+
| `snip` | `string` | ✅ | Snip prefix |
|
|
793
|
+
| `flags` | `string` | ❌ | Regex flags (e.g. `"i"`) |
|
|
794
|
+
| `description` | `string` | ❌ | Human-readable description |
|
|
795
|
+
| `source` | `"config"` | ✅ | Must be `"config"` |
|
|
796
|
+
|
|
797
|
+
#### `fallback`
|
|
798
|
+
|
|
799
|
+
Fallback snip prefix when no rule matches:
|
|
800
|
+
|
|
801
|
+
- `null` — no fallback (default)
|
|
802
|
+
- `{ "prefix": "snip" }` — use `snip` for all unmatched commands
|
|
803
|
+
|
|
804
|
+
#### `llmModel`
|
|
805
|
+
|
|
806
|
+
Override which model to use for LLM analysis. When omitted, uses OpenCode's default model.
|
|
807
|
+
|
|
808
|
+
```jsonc
|
|
809
|
+
{
|
|
810
|
+
"providerID": "anthropic",
|
|
811
|
+
"modelID": "claude-sonnet-4-20250514"
|
|
812
|
+
}
|
|
813
|
+
```
|
|
814
|
+
|
|
815
|
+
### Rule Priority
|
|
816
|
+
|
|
817
|
+
```
|
|
818
|
+
Config rules (source: "config")
|
|
819
|
+
↓ no match
|
|
820
|
+
Learned rules (source: "learned")
|
|
821
|
+
↓ no match
|
|
822
|
+
Fallback prefix
|
|
823
|
+
↓ no match
|
|
824
|
+
No prefix added
|
|
825
|
+
```
|
|
826
|
+
|
|
827
|
+
### Skipped Commands
|
|
828
|
+
|
|
829
|
+
The snip hook skips:
|
|
830
|
+
|
|
831
|
+
- **Shell builtins**: `cd`, `export`, `alias`, `source`, `echo`, `pwd`, etc.
|
|
832
|
+
- **Already prefixed** commands (no double-injection)
|
|
833
|
+
- **Environment variables** are preserved: `FOO=bar cmd` → `FOO=bar snip cmd`
|
|
834
|
+
|
|
835
|
+
### Development
|
|
836
|
+
|
|
837
|
+
```bash
|
|
838
|
+
bun install
|
|
839
|
+
bun test
|
|
840
|
+
npm run typecheck
|
|
841
|
+
```
|