opencode-adaptive-snip
v0.7.7
Published
OpenCode plugin that learns verbose shell commands and automatically prefixes them with snip.
Maintainers
Readme
opencode-adaptive-snip
AI 驱动的 Shell 命令预处理插件 — 自动学习哪些命令输出过多,自动注入 snip 前缀来压缩命令输出。
中文
做什么
opencode-adaptive-snip 是 OpenCode 插件,两个核心功能:
- Adaptive Snip 模式 — 拦截所有
bash工具调用,匹配规则表,自动注入snip前缀 - Analyze 模式 — 通过 tool.execute.after 收集 bash 执行结果,批量发给 LLM 分析,自动生成 snip 规则
架构
┌─────────────────────────────────────────────────────┐
│ OpenCode Runtime │
├─────────────────────────────────────────────────────┤
│ tool.execute.before (snip.ts) │
│ ├─ 拦截 bash 命令 │
│ ├─ 规则匹配 (config → learned → fallback) │
│ ├─ 注入 snip 前缀 │
│ └─ 处理 pipe / operator / env var │
├─────────────────────────────────────────────────────┤
│ tool.execute.after (command-tracker.ts) │
│ ├─ tool.execute.before 记录 command + timestamp │
│ ├─ tool.execute.after 配对 output │
│ └─ 触发 analyze() │
├─────────────────────────────────────────────────────┤
│ LearnAnalyzer (analyze.ts) │
│ ├─ 缓冲 command/output 对 │
│ ├─ 构建 prompt → LLM 分析 │
│ ├─ 解析 JSON → mergeLearned() │
│ └─ 存储到 .opencode/snip-rules.json │
└─────────────────────────────────────────────────────┘Agent 兼容性
插件完全兼容 OpenCode 所有 agent 模式,包括 Sisyphus、Ultraworker 等。
tool.execute.before/tool.execute.afterhooks 运行在 OpenCode 运行时层,不依赖特定 agent- 分析 session 通过独立的
client.session.create()创建,不使用 agent context 的模型 - 配置
analyze.llmModel时,该模型会同时传给session.create()和session.prompt(),确保一致性 - 如果分析 LLM 需要指定模型,通过
analyze.llmModel配置覆盖 - 仅有的限制:如果同时使用了多个插件链,adaptive-snip 的 output truncation (5000 chars) 可能被后续插件覆盖。推荐将 adaptive-snip 放在 opencode.json plugins 数组前列
安装
# 在 OpenCode 项目目录
opencode plugin opencode-adaptive-snip该命令会安装 npm 包并自动更新 OpenCode 配置。需要安装到全局配置时使用:
opencode plugin --global opencode-adaptive-snip配置
推荐方式:独立配置文件
插件自动发现项目根目录下的 adaptive-snip.json。使用 opencode plugin opencode-adaptive-snip 安装后,规则配置无需写入 opencode.json。
adaptive-snip.json(放在项目根目录):
{
"analyze": {
"enabled": true,
"autoLearn": true,
"batchSize": 20,
"minConfidence": 0.7,
"maxRules": 50,
"cooldownMinutes": 60
},
"rules": [
{ "pattern": "^go test", "snip": "snip --timeout 120", "source": "config" },
{ "pattern": "^npm (install|test|run build)", "snip": "snip", "source": "config" }
],
"fallback": { "prefix": "snip" },
"ruleFile": ".opencode/snip-rules.json"
}opencode.json:
{
"plugin": [
"opencode-adaptive-snip"
]
}配置优先级:opencode.json 内联 options > adaptive-snip.json > 默认值。
备选方式:内联配置
在 opencode.json 中直接写配置(仍支持):
{
"plugin": [
[
"opencode-adaptive-snip",
{
// 分析模式配置
"analyze": {
"enabled": true, // 启用分析模式
"autoLearn": true, // 自动学习规则
"batchSize": 20, // 每批分析的命令数量
"minConfidence": 0.7, // 最低置信度阈值
"maxRules": 50, // 最多保留的学习规则数
"cooldownMinutes": 60, // 两次分析的最小间隔(分钟)
"llmModel": { // 可选:指定分析用的模型
"providerID": "openai",
"modelID": "gpt-4o"
}
},
// 手动规则(最高优先级)
"rules": [
{
"pattern": "^go test",
"snip": "snip --timeout 120",
"flags": "i",
"description": "Go 测试输出通常很长",
"source": "config"
},
{
"pattern": "^npm (install|test|run build)",
"snip": "snip",
"source": "config"
}
],
// 回退 snip 配置(当无规则匹配时使用)
"fallback": {
"prefix": "snip"
},
// 学习规则存储路径(相对于项目根目录)
"ruleFile": ".opencode/snip-rules.json"
}
]
]
}完整配置示例
以下是一个生产环境级别的完整配置,包含所有选项和针对常见工具链的手动规则。保存为项目根目录的 adaptive-snip.json 即可自动生效。
{
// ═══════════════════════════════════════════
// 分析模式 — LLM 自动学习新规则
// ═══════════════════════════════════════════
"analyze": {
"enabled": true,
"autoLearn": true,
"batchSize": 20,
"minConfidence": 0.7,
"maxRules": 50,
"cooldownMinutes": 60,
// 不指定 llmModel → 使用 OpenCode 当前会话模型
},
// ═══════════════════════════════════════════
// 手动规则(最高优先级,不会被学习覆盖)
// ═══════════════════════════════════════════
"rules": [
// ── Go ──────────────────────────────
{
"pattern": "^go (test|bench|build|vet|lint|mod tidy)",
"snip": "snip --timeout 120",
"description": "Go 工具链 — 测试/构建输出冗长",
"source": "config"
},
{
"pattern": "^go run",
"snip": "snip --timeout 60",
"description": "go run 可能输出运行时日志",
"source": "config"
},
// ── Node / npm ──────────────────────
{
"pattern": "^npm (install|ci|test|run build|run lint|run dev)",
"snip": "snip --timeout 120",
"description": "npm 安装/测试/构建输出",
"source": "config"
},
{
"pattern": "^npx (vitest|jest|eslint|prettier|tsc|playwright)",
"snip": "snip --timeout 120",
"description": "npx 运行测试/lint/构建工具",
"source": "config"
},
{
"pattern": "^pnpm (install|test|build|lint)",
"snip": "snip --timeout 120",
"source": "config"
},
{
"pattern": "^yarn (install|test|build|lint)",
"snip": "snip --timeout 120",
"source": "config"
},
{
"pattern": "^bun (install|test|run build|run lint)",
"snip": "snip --timeout 120",
"description": "Bun 包管理/测试",
"source": "config"
},
// ── TypeScript ──────────────────────
{
"pattern": "^tsc",
"snip": "snip --timeout 60",
"description": "TypeScript 编译错误列表可能很长",
"source": "config"
},
// ── Python ──────────────────────────
{
"pattern": "^(python3?|python) (-m )?(pytest|unittest|mypy|ruff|black|isort)",
"snip": "snip --timeout 120",
"description": "Python 测试/lint/格式化",
"source": "config"
},
{
"pattern": "^pip (install|freeze|list)",
"snip": "snip --timeout 60",
"description": "pip 安装/列表输出",
"source": "config"
},
{
"pattern": "^(uv|poetry) (run|add|install|lock|build)",
"snip": "snip --timeout 120",
"description": "Python 现代包管理工具",
"source": "config"
},
// ── Rust ────────────────────────────
{
"pattern": "^cargo (test|build|check|clippy|bench|doc)",
"snip": "snip --timeout 180",
"description": "Rust 编译/测试输出极长",
"source": "config"
},
{
"pattern": "^rustc",
"snip": "snip --timeout 120",
"source": "config"
},
// ── C/C++ ──────────────────────────
{
"pattern": "^(make|cmake|ninja|gcc|g\\+\\+|clang\\+\\+)",
"snip": "snip --timeout 180",
"description": "C/C++ 编译输出可能极长",
"source": "config"
},
// ── Java / JVM ──────────────────────
{
"pattern": "^(mvn|gradle|./gradlew|./mvnw)",
"snip": "snip --timeout 180",
"description": "Maven/Gradle 构建输出",
"source": "config"
},
// ── Docker ──────────────────────────
{
"pattern": "^docker (build|compose|logs|ps|images|system)",
"snip": "snip --timeout 120",
"description": "Docker 构建/日志",
"source": "config"
},
// ── Kubernetes ──────────────────────
{
"pattern": "^kubectl (get|describe|logs|apply|delete)",
"snip": "snip --timeout 60",
"description": "K8s 资源查询/操作",
"source": "config"
},
{
"pattern": "^(helm|k9s|kustomize|argocd)",
"snip": "snip --timeout 60",
"source": "config"
},
// ── Git ─────────────────────────────
{
"pattern": "^git (log|diff|show|blame|status|branch)",
"snip": "snip --timeout 30",
"description": "Git 查询命令 — 日志/diff 可能很长",
"source": "config"
},
// ── Shell 批处理 ────────────────────
{
"pattern": "^(find|locate|tree|du|df|ls -l)",
"snip": "snip --timeout 30",
"description": "文件系统查询 — 大目录输出极多",
"source": "config"
},
{
"pattern": "^(ps|top|htop|netstat|ss|lsof)",
"snip": "snip --timeout 30",
"description": "系统进程/网络查询",
"source": "config"
},
{
"pattern": "^curl.*(-v|--verbose|--trace)",
"snip": "snip --timeout 30",
"description": "curl verbose 输出",
"source": "config"
},
// ── 包管理器通用 ────────────────────
{
"pattern": "^(apt|apt-get|brew|dnf|yum|pacman|zypper) (install|update|upgrade|search|list)",
"snip": "snip --timeout 120",
"description": "系统包管理安装/搜索",
"source": "config"
},
// ── Linter / Formatter 类 ───────────
{
"pattern": "^(eslint|prettier|biome|oxlint|stylelint|clang-format)",
"snip": "snip --timeout 60",
"description": "Linter/Formatter 输出",
"source": "config"
},
// ── 测试框架 ────────────────────────
{
"pattern": "^(vitest|jest|mocha|ava|tap|cypress|playwright test)",
"snip": "snip --timeout 120",
"description": "JS/TS 测试框架",
"source": "config"
},
// ── 构建工具 ────────────────────────
{
"pattern": "^(webpack|vite|rollup|esbuild|turbo|nx|lage) (build|serve|dev)",
"snip": "snip --timeout 120",
"description": "前端构建工具",
"source": "config"
}
],
// ═══════════════════════════════════════════
// 无规则匹配时:给所有命令自动加 "snip"
// 设为 null 则不加
// ═══════════════════════════════════════════
"fallback": {
"prefix": "snip"
},
// ═══════════════════════════════════════════
// 学习规则存储路径
// ═══════════════════════════════════════════
"ruleFile": ".opencode/snip-rules.json"
}💡 使用建议:将此配置放入项目根目录的
adaptive-snip.json,然后根据你的项目技术栈增删rules中的条目。LLM 分析会自动补全你遗漏的规则。你手动配置的规则永远不会被 LLM 覆写。
配置项详解
analyze — 分析模式
| 字段 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| enabled | boolean | false | 是否启用 LLM 分析 |
| autoLearn | boolean | false | 自动保存学习到的规则(依赖 enabled) |
| batchSize | number | 20 | 积累多少个命令/输出对后触发分析 |
| minConfidence | number | 0.7 | 置信度阈值,低于此值的建议不保存 |
| maxRules | number | 50 | 学习规则数量上限 |
| cooldownMinutes | number | 60 | 两次分析之间的冷却时间 |
| llmModel | object | — | 指定 LLM 模型,不指定则使用 OpenCode 默认模型 |
| notification | "off" \| "minimal" \| "detailed" | "minimal" | 通知级别:off=不通知, minimal=仅分析摘要, detailed=完整分析报告 |
控制分析模式有三种方式:
// 1. 禁用(默认)
{ "analyze": false }
// 2. 启用全默认
{ "analyze": true }
// 3. 自定义
{
"analyze": {
"enabled": true,
"autoLearn": true,
"batchSize": 30,
"llmModel": { "providerID": "anthropic", "modelID": "claude-sonnet-4-20250514" }
}
}rules — 手动规则
用户手动配置的规则,优先级最高,不会被 LLM 学习覆盖。
{
"rules": [
{
"pattern": "^go (test|build|vet)", // 正则表达式
"snip": "snip --timeout 120", // snip 命令及参数
"flags": "i", // 可选:正则标志
"description": "Go 工具链输出", // 可选:说明
"source": "config" // 必须为 "config"
}
]
}字段说明:
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| pattern | string | ✅ | 正则表达式,匹配命令文本 |
| snip | string | ✅ | snip 命令前缀,如 "snip" 或 "snip --timeout 120" |
| flags | string | ❌ | 正则标志,如 "i"(不区分大小写) |
| description | string | ❌ | 规则说明 |
| source | "config" | ✅ | 必须为 "config" |
pattern — 正则安全建议
Rule 的 pattern 字段使用 JavaScript 正则表达式。避免复杂组合导致性能问题:
- 避免复杂 lookahead/backreference — 可能导致灾难性回溯
- 优先用简单锚定模式:
^python(3)? (-m )?(pytest|...)优于(?=[ ;&\|])python - 实际测试你的 pattern 是否能匹配预期命令
插件会做基础 ReDoS 校验(嵌套量词如 (a+)+ 会被拒绝),但保持 pattern 简单始终更安全。
fallback — 回退前缀
当无规则匹配时,可以选择性使用回退 snip:
// 无匹配 → 不加 snip(默认)
{ "fallback": null }
// 无匹配 → 加 "snip" 前缀
{ "fallback": { "prefix": "snip" } }
// 无匹配 → 加 "snip --timeout 60" 前缀
{ "fallback": { "prefix": "snip --timeout 60" } }llmModel — 指定分析模型
llmModel 是 AnalyzeConfig 下的一个字段,它让你可以指定用什么模型来进行 LLM 分析,而不依赖 OpenCode 的默认模型。当不指定时,插件使用 OpenCode 的默认模型(当前会话模型)。
{
"analyze": {
"enabled": true,
"autoLearn": true,
"llmModel": {
"providerID": "openai", // 提供商标识
"modelID": "gpt-4o" // 模型标识
}
}
}providerID 和 modelID 的具体值取决于你的 OpenCode 配置了哪些 provider。例如:
{ "providerID": "anthropic", "modelID": "claude-sonnet-4-20250514" }{ "providerID": "openai", "modelID": "gpt-4o" }
规则优先级
手动配置规则 (source: "config")
↓ 无匹配
学习规则 (source: "learned")
↓ 无匹配
回退前缀 (fallback.prefix)
↓ 无匹配
不加前缀规则文件格式
学习规则自动保存到 .opencode/snip-rules.json:
{
"version": 1,
"rules": [
{
"pattern": "^npm test",
"snip": "snip",
"confidence": 0.92,
"description": "npm test 输出包含大量测试日志",
"source": "learned",
"createdAt": "2026-05-09T10:30:00.000Z",
"updatedAt": "2026-05-09T10:30:00.000Z"
}
],
"analytics": {
"lastAnalysisAt": "2026-05-09T10:30:00.000Z",
"totalSuggestionsLearned": 25
}
}你可以手动编辑此文件来调整学习规则。
行为细节
不会加 snip 前缀的命令
- Shell 内置命令/敏感 shell 命令:
cd,export,alias,source,echo,pwd等 - 已经包含 snip 前缀 的命令(防重复注入)
- 环境变量前缀 被保留:
FOO=bar cmd→FOO=bar snip cmd
Pipe 和 Operator 处理
# 输入
npm test | grep FAIL
# 输出(仅第一个命令前加 snip)
snip npm test | grep FAIL# 输入
go build && go test ./...
# 输出(匹配规则的非 operator 段会加 snip)
snip --timeout 120 go build && snip --timeout 120 go test ./...输出截断
分析模式下,命令输出超过 5000 字符会被截断后再发给 LLM。
调试
插件在控制台输出 [adaptive-snip] 前缀的日志:
[adaptive-snip] LLM analysis failed:— 分析失败(非致命)[adaptive-snip] Unpaired shell.ended event— 事件配对异常[adaptive-snip] analyze.autoLearn=true but analyze.enabled=false— 配置冲突
开发
bun install
bun test
npm run typecheckEnglish
What It Does
opencode-adaptive-snip is an OpenCode plugin with two modes:
- Adaptive Snip — intercepts all
bashtool calls, matches rules, auto-prependssnipprefix - Analyze — collects bash execution results via tool.execute.after, buffers command/output pairs, sends batches to LLM for analysis, auto-generates snip rules
Architecture
┌─────────────────────────────────────────────────────┐
│ OpenCode Runtime │
├─────────────────────────────────────────────────────┤
│ tool.execute.before (snip.ts) │
│ ├─ Intercept bash commands │
│ ├─ Rule matching (config → learned → fallback) │
│ ├─ Inject snip prefix │
│ └─ Handle pipes / operators / env vars │
├─────────────────────────────────────────────────────┤
│ tool.execute.after (command-tracker.ts) │
│ ├─ tool.execute.before records command + timestamp │
│ ├─ tool.execute.after pairs with output │
│ └─ Trigger analyze() │
├─────────────────────────────────────────────────────┤
│ LearnAnalyzer (analyze.ts) │
│ ├─ Buffer command/output pairs │
│ ├─ Build prompt → LLM analysis │
│ ├─ Parse JSON → mergeLearned() │
│ └─ Save to .opencode/snip-rules.json │
└─────────────────────────────────────────────────────┘Agent Compatibility
The plugin is fully compatible with all OpenCode agent modes, including Sisyphus, Ultraworker, and others.
tool.execute.before/tool.execute.afterhooks run at the OpenCode runtime layer, independent of any specific agent- Analysis sessions use independent
client.session.create()calls, not the agent context's model - When
analyze.llmModelis configured, it is passed to bothsession.create()andsession.prompt()for consistency - To specify a model for analysis LLM, use the
analyze.llmModelconfig override - The only limitation: if multiple plugins are chained, adaptive-snip's output truncation (5000 chars) may be overwritten by subsequent plugins. It is recommended to place adaptive-snip early in the opencode.json plugins array
Installation
# In your OpenCode project
opencode plugin opencode-adaptive-snipThis installs the npm package and updates your OpenCode config. To install it in global config:
opencode plugin --global opencode-adaptive-snipConfiguration
Recommended: Separate config file
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.
adaptive-snip.json (place in project root):
{
"analyze": {
"enabled": true,
"autoLearn": true,
"batchSize": 20,
"minConfidence": 0.7,
"maxRules": 50,
"cooldownMinutes": 60
},
"rules": [
{ "pattern": "^go test", "snip": "snip --timeout 120", "source": "config" },
{ "pattern": "^npm (install|test|run build)", "snip": "snip", "source": "config" }
],
"fallback": { "prefix": "snip" },
"ruleFile": ".opencode/snip-rules.json"
}opencode.json:
{
"plugin": [
"opencode-adaptive-snip"
]
}Merge priority: opencode.json inline options > adaptive-snip.json > defaults.
Alternative: Inline config
Add config directly in opencode.json (still supported):
{
"plugin": [
[
"opencode-adaptive-snip",
{
"analyze": {
"enabled": true,
"autoLearn": true,
"batchSize": 20,
"minConfidence": 0.7,
"maxRules": 50,
"cooldownMinutes": 60,
"llmModel": {
"providerID": "openai",
"modelID": "gpt-4o"
}
},
"rules": [
{
"pattern": "^go test",
"snip": "snip --timeout 120",
"source": "config"
}
],
"fallback": { "prefix": "snip" },
"ruleFile": ".opencode/snip-rules.json"
}
]
]
}Complete Configuration Example
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.
{
// ═══════════════════════════════════════════
// Analyze mode — LLM learns new rules automatically
// ═══════════════════════════════════════════
"analyze": {
"enabled": true,
"autoLearn": true,
"batchSize": 20,
"minConfidence": 0.7,
"maxRules": 50,
"cooldownMinutes": 60
// Omit llmModel → uses OpenCode current session model
},
// ═══════════════════════════════════════════
// Manual rules (highest priority, never overwritten by learning)
// ═══════════════════════════════════════════
"rules": [
// ── Go ──────────────────────────────
{ "pattern": "^go (test|bench|build|vet|lint|mod tidy)", "snip": "snip --timeout 120", "description": "Go toolchain", "source": "config" },
{ "pattern": "^go run", "snip": "snip --timeout 60", "source": "config" },
// ── Node / npm ──────────────────────
{ "pattern": "^npm (install|ci|test|run build|run lint|run dev)", "snip": "snip --timeout 120", "source": "config" },
{ "pattern": "^npx (vitest|jest|eslint|prettier|tsc|playwright)", "snip": "snip --timeout 120", "source": "config" },
{ "pattern": "^pnpm (install|test|build|lint)", "snip": "snip --timeout 120", "source": "config" },
{ "pattern": "^yarn (install|test|build|lint)", "snip": "snip --timeout 120", "source": "config" },
{ "pattern": "^bun (install|test|run build|run lint)", "snip": "snip --timeout 120", "source": "config" },
// ── TypeScript ──────────────────────
{ "pattern": "^tsc", "snip": "snip --timeout 60", "description": "TypeScript compiler", "source": "config" },
// ── Python ──────────────────────────
{ "pattern": "^(python3?|python) (-m )?(pytest|unittest|mypy|ruff|black|isort)", "snip": "snip --timeout 120", "source": "config" },
{ "pattern": "^pip (install|freeze|list)", "snip": "snip --timeout 60", "source": "config" },
{ "pattern": "^(uv|poetry) (run|add|install|lock|build)", "snip": "snip --timeout 120", "source": "config" },
// ── Rust ────────────────────────────
{ "pattern": "^cargo (test|build|check|clippy|bench|doc)", "snip": "snip --timeout 180", "source": "config" },
{ "pattern": "^rustc", "snip": "snip --timeout 120", "source": "config" },
// ── C/C++ ──────────────────────────
{ "pattern": "^(make|cmake|ninja|gcc|g\\+\\+|clang\\+\\+)", "snip": "snip --timeout 180", "source": "config" },
// ── Java / JVM ──────────────────────
{ "pattern": "^(mvn|gradle|./gradlew|./mvnw)", "snip": "snip --timeout 180", "source": "config" },
// ── Docker ──────────────────────────
{ "pattern": "^docker (build|compose|logs|ps|images|system)", "snip": "snip --timeout 120", "source": "config" },
// ── Kubernetes ──────────────────────
{ "pattern": "^kubectl (get|describe|logs|apply|delete)", "snip": "snip --timeout 60", "source": "config" },
{ "pattern": "^(helm|k9s|kustomize|argocd)", "snip": "snip --timeout 60", "source": "config" },
// ── Git ─────────────────────────────
{ "pattern": "^git (log|diff|show|blame|status|branch)", "snip": "snip --timeout 30", "source": "config" },
// ── Shell batch queries ─────────────
{ "pattern": "^(find|locate|tree|du|df|ls -l)", "snip": "snip --timeout 30", "source": "config" },
{ "pattern": "^(ps|top|htop|netstat|ss|lsof)", "snip": "snip --timeout 30", "source": "config" },
{ "pattern": "^curl.*(-v|--verbose|--trace)", "snip": "snip --timeout 30", "source": "config" },
// ── System package managers ─────────
{ "pattern": "^(apt|apt-get|brew|dnf|yum|pacman|zypper) (install|update|upgrade|search|list)", "snip": "snip --timeout 120", "source": "config" },
// ── Linter / Formatter ──────────────
{ "pattern": "^(eslint|prettier|biome|oxlint|stylelint|clang-format)", "snip": "snip --timeout 60", "source": "config" },
// ── Test frameworks ─────────────────
{ "pattern": "^(vitest|jest|mocha|ava|tap|cypress|playwright test)", "snip": "snip --timeout 120", "source": "config" },
// ── Build tools ─────────────────────
{ "pattern": "^(webpack|vite|rollup|esbuild|turbo|nx|lage) (build|serve|dev)", "snip": "snip --timeout 120", "source": "config" }
],
// ═══════════════════════════════════════════
// Fallback: auto-prefix "snip" for unmatched commands
// Set null to disable
// ═══════════════════════════════════════════
"fallback": { "prefix": "snip" },
// ═══════════════════════════════════════════
// Learned rules storage path
// ═══════════════════════════════════════════
"ruleFile": ".opencode/snip-rules.json"
}💡 Tip: Drop this into
adaptive-snip.jsonin your project root, then add/removerulesentries to match your tech stack. The LLM analyzer will fill in gaps automatically. Your manual config rules are never overwritten.
Options Reference
analyze
| Field | Type | Default | Description |
|-------|------|---------|-------------|
| enabled | boolean | false | Enable LLM analysis |
| autoLearn | boolean | false | Auto-save learned rules |
| batchSize | number | 20 | Pairs to buffer before analysis |
| minConfidence | number | 0.7 | Minimum confidence threshold |
| maxRules | number | 50 | Maximum learned rules |
| cooldownMinutes | number | 60 | Minimum interval between analyses |
| llmModel | object | — | Override LLM model for analysis |
| notification | "off" \| "minimal" \| "detailed" | "minimal" | Notification level: off=no output, minimal=analysis summary, detailed=full report |
rules
User-configured rules. Highest priority, never overwritten by learning.
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| pattern | string | ✅ | Regex to match command text |
| snip | string | ✅ | Snip prefix |
| flags | string | ❌ | Regex flags (e.g. "i") |
| description | string | ❌ | Human-readable description |
| source | "config" | ✅ | Must be "config" |
pattern -- Regex Safety
Rule patterns use JavaScript regular expressions. Avoid complexity that could cause performance issues:
- Avoid lookaheads/backreferences in complex combinations -- they can cause catastrophic backtracking
- Prefer simple anchored patterns:
^python(3)? (-m )?(pytest|...)over(?=[ ;&\|])python - Test patterns with the actual commands you expect to match
The plugin validates patterns for basic ReDoS safety (nested quantifiers like (a+)+ are rejected), but simpler patterns are always safer.
fallback
Fallback snip prefix when no rule matches:
null— no fallback (default){ "prefix": "snip" }— usesnipfor all unmatched commands
llmModel
Override which model to use for LLM analysis. When omitted, uses OpenCode's default model.
{
"providerID": "anthropic",
"modelID": "claude-sonnet-4-20250514"
}Rule Priority
Config rules (source: "config")
↓ no match
Learned rules (source: "learned")
↓ no match
Fallback prefix
↓ no match
No prefix addedSkipped Commands
The snip hook skips:
- Shell builtins:
cd,export,alias,source,echo,pwd, etc. - Already prefixed commands (no double-injection)
- Environment variables are preserved:
FOO=bar cmd→FOO=bar snip cmd
Development
bun install
bun test
npm run typecheckTesting & Linting
| Tool | What it checks | Script |
|------|---------------|--------|
| bun test | Unit tests in src/__tests__/ | bun test |
| tsd | Type-level tests in src/*.test-d.ts | npm run tsd |
| eslint with typescript-eslint | Code style and correctness | npm run lint (add -- --fix to auto-fix) |
| knip | Dead code and unused exports | npm run knip |
| publint | Package quality (exports, fields, compatibility) | npm run publint |
| tsc -p tsconfig.build.json | TypeScript type checking | npm run typecheck |
Run all checks:
npm run typecheck && npm run lint && bun test && npm run tsd