首页
/ caveman opencode 插件速查:模式、斜杠命令与自然语言触发的完整解析

caveman opencode 插件速查:模式、斜杠命令与自然语言触发的完整解析

2026-09-06 13:29:36作者:鲍丁臣Ursa

本文以 caveman 仓库中 opencode 插件的 caveman-help 命令卡片为核心,完整继承其模式速查表,并结合 plugin.jscaveman-parse.jscaveman-config.js 的源码实现,讲清每个斜杠命令背后的解析逻辑、默认模式的四级解析顺序、自然语言开关的触发规则,以及"代码/提交/安全警告自动退出"这一行为在规则文件中的落地方式。读完可以掌握在 opencode 中激活、切换、退出 caveman 压缩模式的全部途径及其底层机制。

速查卡:caveman-help 的原始内容

caveman-help.md 是 opencode 插件中 /caveman-help 斜杠命令的提示词模板(frontmatter 中的 description 为 "Quick reference card for caveman modes, slash commands, and triggers")。它的正文就是一张速查表,外加两句行为说明:

Command What
/caveman Activate at default level (full)
/caveman lite Light compression — ~30% tokens dropped
/caveman ultra Maximum compression
/caveman wenyan[-lite|-ultra] Classical Chinese compression
/caveman off Deactivate
/caveman-commit Terse commit message
/caveman-review One-line review findings
/caveman-compress <file> Compress a Markdown file
/caveman-stats Lifetime token-savings

速查表之外还有两条关键说明,同样需要纳入日常使用习惯:

  1. 自然语言也可以切换:"turn on caveman"、"stop caveman"、"normal mode" 都能改变模式状态,不依赖斜杠命令;
  2. 自动退出(drop out):当任务涉及代码、commit 消息、安全警告时,caveman 风格自动让位于正常英文,无需手动关闭。

下面逐条结合仓库源码展开,验证上述卡片与实际行为是否一致、背后如何实现。

模式分级:/caveman 的合法参数

/caveman 模板 caveman.md 的 frontmatter 声明了完整可传级别:lite | full | ultra | wenyan-lite | wenyan-full | wenyan-ultra | off。与速查表对应关系如下:

  • /caveman:按配置的默认级别激活(默认 full);
  • /caveman lite:轻度压缩,约 30% token 缩减;
  • /caveman ultra:最大压缩;
  • /caveman wenyan[-lite|-ultra]:文言文(Classical Chinese)压缩,wenyan-full 是规范写法,配置中存储为 wenyan
  • /caveman off:退出模式。

caveman-activate.md 给出了压缩风格的实际规则——"Respond terse like smart caveman. All technical substance stay. Only fluff die.",具体为:丢掉冠词(a/an/the)、填充词(just/really/basically)、客套与模糊表达;允许碎片句;技术术语与代码保持原样;句式遵循 [thing] [action] [reason]. [next step].;反例是 "Sure! I'd be happy to help you with that.",正例是 "Bug in auth middleware. Fix:"。

从源码结构看,合法模式的单一事实来源是 caveman-config.js 中的白名单:

const VALID_MODES = [
  'off', 'lite', 'full', 'ultra',
  'wenyan-lite', 'wenyan', 'wenyan-full', 'wenyan-ultra',
  'commit', 'review', 'compress'
];

注意后三个 commitreviewcompress 属于独立模式——它们由各自的斜杠命令触发,不能通过 /caveman <arg> 选择。这一约束在 caveman-parse.js 中显式建模:

// Modes handled by their own slash commands (/caveman-commit, etc.) — not
// selectable via /caveman <arg>.
const INDEPENDENT_MODES = new Set(['commit', 'review', 'compress']);

如果用户真的输入 /caveman commitresolveModeArg 会返回 unresolved 并指明这是独立模式,而不是静默吞掉或误激活;无法识别的级别同样返回 unresolved(注释明确写着 "never silently overwrite with the default")。插件侧的 applyModeChange 只处理 set/clear 两种动作,其余一律忽略,因此非法参数不会改变任何状态。

默认模式从哪来:四级解析顺序

速查卡说 /caveman 是 "Activate at default level (full)",这个 "default" 的解析顺序在 caveman-config.js 的文件头注释中定义:

  1. 环境变量 CAVEMAN_DEFAULT_MODE(最高优先级);
  2. 仓库本地配置:从当前工作目录向上查找最近的 <cwd>/.caveman/config.json<cwd>/.caveman.json(团队可以按项目钉住默认模式,避免污染每个贡献者的用户级配置);
  3. 用户配置文件中的 defaultMode 字段:$XDG_CONFIG_HOME/caveman/config.json~/.config/caveman/config.json(macOS/Linux)或 %APPDATA%\caveman\config.json(Windows);
  4. 兜底值 'full'

查找仓库配置时最多向上走 64 层,且拒绝符号链接(与标志文件写入的 lstat 安全检查保持一致),见 findRepoConfigPath

独立模式命令:commit / review / compress / stats

速查表中四个独立命令各自对应 commands/ 目录下的一个提示词模板文件,其内容比速查卡的一行描述更具体,值得逐一看清。

/caveman-commit — 简短提交信息

caveman-commit.md 的指令是:为当前已暂存变更生成提交消息,要求遵循 Conventional Commits 格式,subject 不超过 50 字符、祈使句、type 后小写、无句号;body 只在 "why" 无法从 subject 看出的情况下才写,且解释 why 而非 what,去掉填充词与模糊表达。

/caveman-review — 一行一条的发现

caveman-review.md 规定:审查当前 diff(或指定文件),每个问题一行,格式为 L<行号>: <severity> <问题>. <修复>.;严重度用 🔴 critical / 🟡 warn / 🟢 nit 标记;跳过非问题;按文件分组,结尾给一行结论。

/caveman-compress — 压缩 Markdown 文件

caveman-compress.md 的约束值得注意:

  • 对给定文件运行 caveman-compress skill,把散文改写为 caveman 风格,但代码块、行内代码、URL、文件路径、命令与 Markdown 结构必须原样保留
  • 覆盖前把原文件备份为 <file>.original.md
  • 只压缩自然语言文件(.md.txt.typ.tex、无扩展名),拒绝源码/配置文件(.py.js.ts.json.yaml.toml.sh 等);
  • 绝不压缩已有的 *.original.md 备份。

仓库中 tests/caveman-compress/ 目录存放了该 skill 的成对测试样例(如 todo-list.mdtodo-list.original.md),可直接用来核对压缩前后差异。

/caveman-stats — 累计节省统计

caveman-stats.md 要求读取终身历史日志 ~/.config/caveman/.caveman-history.jsonl(或 caveman-stats 脚本写入的位置),输出总节省 token 数、会话数、平均压缩比,用一张短表呈现。

自然语言触发:不敲斜杠也能开关模式

速查卡写道 "Natural language also works: 'turn on caveman', 'stop caveman', 'normal mode'." 这些短语的实现集中在共享解析器 caveman-parse.jsparseModeChange() 中,它被 Claude Code hook 与 opencode 插件共用(源码注释称之为 #602 引入的 "single source of truth",防止两侧正则漂移)。

退出意图优先判定。 激活短语与退出短语可能同时出现(如 "turn caveman mode off"),所以解析器先计算 wantsOffcaveman-parse.js#L150-L160),匹配以下模式之一即返回 { action: 'clear' }

  • stop|disable|deactivate|quit|exit|kill (the) caveman
  • caveman (mode) off|stop|disabled
  • turn off (the) caveman
  • normal mode 开头的指令(允许 "please/go back to/switch back to/return to" 前缀),或句中同时含 "normal mode" 与 "caveman"

normal mode 之所以限制在句首或带 caveman 上下文,是为了不误伤 "how do I exit vim normal mode" 这类讨论编辑器模式的句子。

激活短语带防误触设计。 激活匹配(caveman-parse.js#L184-L207)除了 "turn on caveman"、"activate caveman"、"talk like caveman" 等短语外,还接受简短化请求("less tokens"、"be brief"、"be terse"、"fewer tokens"、"shorter answers")——但排除 "be brief in the summary" 这类限定在单个段落的一次性指令。同时有一个 isQuestion 守卫:以 what/how/why/is/can/explain 等开头的句子("what is caveman mode?")不会被当成激活命令。

引用文本中的触发词会被抹除。 源码注释记录了真实事故(issue #838):用户在 bug 报告里引用了 help 卡片自己的一行文字 "Say 'stop caveman' or 'normal mode'",结果把模式关掉了。修复方式是匹配前先用 QUOTED_SPAN_REGEX 把双引号与反引号包裹的片段替换为空格(caveman-parse.js#L72),只把 " 与 视为定界符——撇号在英文中太常见,若配对会连命令本身一起抹掉。

裸命令与参数清洗。/caveman 走默认模式;带参数时,normalizeModeArg() 先剥掉首尾粘连的标点(/caveman ultra; 也能解析出 ultra),而 "参数存在但被清洗为空"(如 /caveman ?)返回 unresolved 而不是激活——源码注释的解释是:这种情况更可能是用户在求助,不应被顺手切进模式。

opencode 的模板展开需要特殊处理。 opencode 会把输入的 /caveman ultra 在到达 chat.message hook 之前替换为 caveman.md 模板的散文正文,因此解析器开启 expandedTpl 选项后,从模板固定前缀 ^activate caveman mode:[ \t]*(\S*) 中恢复级别(caveman-parse.js#L170-L182);三个独立命令则按各自模板的首句识别(generate a commit message for the current staged changes → commit,review the current diff → review,compress the file at: → compress)。另外 opencode 非交互 run 路径投递的消息带一层字面引号,unwrapQuotes 选项负责剥掉。

自动退出:代码、提交与安全警告为什么恢复英文

速查卡最后一句 "Code, commits, security warnings: caveman drops out automatically." 的规则定义在 caveman-activate.md

  • Auto-Clarity:遇到安全警告、不可逆操作、用户表现困惑时退出 caveman 风格,之后恢复;
  • Boundaries:代码、commit、PR 一律用正常英文书写。

caveman.md 模板尾部还补充了会话语义:"Behavior persists until session ends or user says 'stop caveman' / 'normal mode'. Code, commits, security warnings: write normal English." 也就是说自动退出是局部的(只对当前输出段落生效),模式状态本身在整个会话内保持,直到用户显式关闭或会话结束。

底层实现:opencode 插件的三个钩子

速查卡能生效,靠的是 plugin.js 中的 opencode 插件实现(Bun ESM 模块,默认导出 CavemanPlugin 工厂)。它通过三个 hook 承载全部动态状态:

1. eventsession.created)— 会话开始时写标志文件。 handleSessionCreated 调用 getDefaultMode():默认是 off 则删除标志文件,否则用 safeWriteFlag 写入 ~/.config/opencode/.caveman-active。opencode >= 1.15 通过单一 event 分发器按 event.type 路由(旧的直接键名会被静默忽略),插件同时在工厂加载时断言一次标志,覆盖一次性 opencode run 中首个 session.created 早于插件事件装配的竞态。标志目录解析优先 $XDG_CONFIG_HOME,否则 ~/.config/opencodeplugin.js#L99-L106)。

2. chat.message — 解析用户消息中的模式变更。 hook 遍历消息的 text parts,对每个 part 调用 parseModeChange(part.text, { getDefaultMode, expandedTpl: true, unwrapQuotes: true }),有返回值就通过 applyModeChange 落到标志文件上(set 写入、clear 删除)。源码注释明确:返回值被丢弃,状态变更全部经由标志文件完成,不依赖 hook 返回值。

3. experimental.chat.system.transform — 每轮注入强化行。 当标志文件指示处于非独立模式时,插件向系统提示词注入一行强化语:CAVEMAN MODE ACTIVE (<mode>) — session ruleset applies.reinforcementLine),让模型每轮都记得会话规则。该注入是幂等的:用正则 CAVEMAN MODE ACTIVE \([a-z-]+\) — session ruleset applies\. 查找已存在行并原地替换,避免 opencode 复用 output.system 数组时逐轮堆叠、悄悄吃掉上下文窗口(plugin.js#L183-L210)。

安全细节。 safeWriteFlag 复用 caveman-config.js 的加固实现:O_NOFOLLOW、临时文件+原子 rename、0600 权限、拒绝符号链接、所有权检查。由于 opencode 在编译后的 Bun 二进制中运行插件、require() 磁盘文件会被拒绝,插件没有走模块加载器,而是把 caveman-config.jscaveman-parse.js 的源码读出来用 new Function(...) 手工求值,createRequire 只解析 Node 内置模块(fs/path/os)——见 loadConfig() 及其上方注释。

安装与使用前提

src/plugins/opencode/README.md 说明:

  • 安装命令为 bin/install.js --only opencode,它会复制 plugin.jspackage.jsoncommands/*.md 以及改名后的 caveman-config.cjs~/.config/opencode/plugins/caveman/,并在 opencode.json 中补一个 "plugin" 数组条目;
  • 没有 statusline 徽章:opencode TUI 不暴露插件可写的 statusline,想在自己的 shell 提示词中显示模式,可直接读取标志文件 ~/.config/opencode/.caveman-active
  • 没有 session.created 系统提示注入:always-on 规则集来自安装器同时写入的 ~/.config/opencode/AGENTS.md,即使插件运行时损坏规则仍然加载;
  • 插件代码复用主仓库的 caveman-config.js,以仓库内插件形式发布,避免二次发布节奏以及与第三方 opencode-caveman npm 包的命名冲突。

目录结构总览(安装后):

~/.config/opencode/plugins/caveman/
├── package.json
├── plugin.js              # 动态状态:标志写入、命令解析、强化注入
├── caveman-config.cjs     # 复制自 src/hooks/caveman-config.js
├── caveman-parse.cjs      # 复制自 src/hooks/caveman-parse.js
└── commands/              # 六个斜杠命令模板,含 caveman-help.md 速查卡

适用前提小结:以上行为对应 opencode >= 1.15 的插件事件模型;/caveman-help 本身不改变任何状态,它只是一张由 caveman-help.md 模板驱动的速查卡;所有模式状态都收敛到 ~/.config/opencode/.caveman-active 一个标志文件,这也是排查"为什么现在没压缩/怎么退不出来"时应先检查的位置。

登录后查看全文
热门项目推荐
相关项目推荐