首页
/ caveman opencode 插件的 /caveman 命令:七级压缩模式切换的模板机制与解析管线

caveman opencode 插件的 /caveman 命令:七级压缩模式切换的模板机制与解析管线

2026-09-06 13:44:27作者:滑思眉Philip

caveman 是一个"用更少的 token 表达同等技术内容"的 Claude Code / opencode 压缩技能,其 opencode 插件通过 src/plugins/opencode/commands/caveman.md 这个斜杠命令模板实现会话级压缩模式切换。本文以该命令文件为主体,完整讲清 /caveman 的模板语法、七种可选模式级别与行为规则,并向下深入到 plugin.js 的模板展开解析、caveman-parse.js 的单一解析源,以及 caveman-config.js 的防符号链接 flag 文件写入机制——读完你能完整理解一次 /caveman ultra 从输入到模式落地的全链路。

1. 命令文件全貌:13 行的完整定义

caveman.md 的全部正文如下,frontmatter 与正文一个字符都不少:

---
description: Activate caveman mode (lite | full | ultra | wenyan-lite | wenyan-full | wenyan-ultra | off)
---
Activate caveman mode: $ARGUMENTS

If no level given, use full. If "off", deactivate.

Respond terse like smart caveman. Drop articles, filler, pleasantries, hedging.
Fragments OK. Technical terms exact. Code unchanged.
Pattern: [thing] [action] [reason]. [next step].

Behavior persists until session ends or user says "stop caveman" / "normal mode".
Code, commits, security warnings: write normal English.

它由两部分构成:

  • YAML frontmatterdescription 字段声明命令用途与全部可接受参数(lite | full | ultra | wenyan-lite | wenyan-full | wenyan-ultra | off)。opencode 用该字段在 TUI 的命令补全里展示说明。
  • 命令正文:第一行 Activate caveman mode: $ARGUMENTS 是参数占位符——用户输入 /caveman ultra 时,opencode 会把整段正文展开为 Activate caveman mode: ultra,替换后再进入插件的 chat.message 钩子。这一点是理解后文"模板展开"机制的关键:插件拿到的从来不是原始斜杠命令,而是替换后的散文。

正文随后给出四条语义契约:

  1. 缺省级别:未给参数时按 full 处理;显式 off 表示停用。
  2. 压缩风格:像聪明的原始人一样简洁作答——去掉冠词、填充词(just/really/basically)、客套与含糊措辞;允许句子片段;技术术语保持精确;代码原样不动。
  3. 句型模板[thing] [action] [reason]. [next step].(对象 + 动作 + 原因,然后下一步)。
  4. 生命周期与边界:行为持续至会话结束或用户说 "stop caveman" / "normal mode";代码、commit、安全警告一律恢复正常英文。

/caveman 不是孤立的:同目录 commands/ 下还有五个姊妹命令,README 将其归纳为"六个斜杠命令提示模板":

命令文件 作用
caveman.md 激活/切换会话压缩模式(本文主角)
caveman-commit.md 为暂存区生成 Conventional Commits 风格的极简 commit 信息(subject ≤50 字符、祈使句、小写)
caveman-review.md 单行式代码评审,格式 L<行号>: <severity> <问题>. <修复>.,按文件分组并以一行结论收尾
caveman-compress.md 用 caveman-compress 技能压缩指定 markdown 文件,原文件备份为 <file>.original.md
caveman-stats.md 读取 ~/.config/caveman/.caveman-history.jsonl,输出累计节省 token、会话数、平均压缩比
caveman-help.md 模式/命令/触发词速查卡

2. 模式级别全表:从 30% 压缩到文言压缩

/caveman 的参数空间比 frontmatter 一行描述更有结构。结合 caveman-help.md 的速查卡、caveman-config.js 中的 VALID_MODES 白名单和 caveman-parse.js 的解析逻辑,可以整理出完整的级别表:

输入参数 效果 存储值
(无参数) 激活配置默认级别;未配置时即 full 默认模式
lite 轻度压缩,约 30% token 削减 lite
full 标准压缩(缺省行为) full
ultra 最大压缩 ultra
wenyan-lite 文言文轻度压缩 wenyan-lite
wenyan / wenyan-full 文言文标准压缩 wenyan(别名归一)
wenyan-ultra 文言文最大压缩 wenyan-ultra
off / stop / disable 停用,删除 flag 文件 —(文件被删除)

几个值得注意的解析细节(均来自 caveman-parse.js):

  • wenyan-full 是别名L102-L103 将其归一为 wenyan,配置侧只存储 wenyan。所以 frontmatter 列出的七个值中,wenyan-fullwenyan 等价。
  • 停用词不止一个offstopdisable 三个词都会触发 clearL101),而命令文件正文只提到了 "off"。
  • 独立模式不可经此命令选择commitreviewcompress 三个真实存在于 VALID_MODES 的级别属于 INDEPENDENT_MODESL53),它们由各自的斜杠命令激活;若有人输入 /caveman commit,解析器返回 unresolved 而非静默回落到默认值——这是有意设计,避免"错误的级别被默认值悄悄覆盖"。
  • 标点容忍normalizeModeArg 会剥掉 /caveman ultra; 这类粘连的尾随标点与首部引号,保证 "ultra" 也能解析。
  • 裸命令 ≠ 空参数:真正"没写参数"的裸 /caveman 才激活默认级别;而 /caveman ? 这种参数被归一化后为空的情况会返回 unresolved,避免把疑似求助的输入直接切换进压缩模式。

3. 行为规则:压缩风格、持久化与自动清晰边界

命令正文声明的规则,其完整版本由安装器写入 ~/.config/opencode/AGENTS.md(Tier-3 常驻规则集),仓库中的源文件是 src/rules/caveman-activate.md。对照阅读可以补齐命令正文未展开的两条关键机制:

Respond terse like smart caveman. All technical substance stay. Only fluff die.

Rules:
- Drop: articles (a/an/the), filler (just/really/basically), pleasantries, hedging
- Fragments OK. Short synonyms. Technical terms exact. Code unchanged.
- Pattern: [thing] [action] [reason]. [next step].
- Not: "Sure! I'd be happy to help you with that."
- Yes: "Bug in auth middleware. Fix:"

Switch level: /caveman lite|full|ultra|wenyan-lite|wenyan-full|wenyan-ultra
Stop: "stop caveman" or "normal mode"

Auto-Clarity: drop caveman for security warnings, irreversible actions, user confused. Resume after.

Boundaries: code/commits/PRs written normal.

命令文件中的 Code, commits, security warnings: write normal English. 对应这里最完整的两条边界:

  • Auto-Clarity(自动清晰):遇到安全警告、不可逆操作或用户困惑时自动退出原始人风格,事后再恢复。也就是说"边界"不仅是静态清单,还包含运行时条件。
  • Boundaries(静态边界):代码、commit、PR 一律正常书写——压缩的永远是自然语言,不是代码语义。
  • 正反例:规则集直接给出否定例("Sure! I'd be happy to help you with that.")与肯定例("Bug in auth middleware. Fix:"),这是提示词工程中少见的"few-shot by contrast"写法,让模型对"去掉什么"有可对照的锚点。

自然语言开关与斜杠命令等效(caveman-parse.js L150-L207):

说法 效果
"activate caveman"、"turn on caveman"、"talk like caveman"、"caveman mode on" 按配置默认级别激活
"less tokens"、"be brief"、"fewer tokens"、"shorter answers" 同上(简洁请求也视为激活意图,但仅限非限定语境)
"stop caveman"、"turn off the caveman"、"caveman off" 停用
"normal mode"(句首,或含 caveman 上下文) 停用

其中有两处防御值得注意:其一,引用不触发——QUOTED_SPAN_REGEXL72)在匹配前把引号与反引号包裹的片段空白化,因此粘贴一段引用了 "stop caveman" 的 bug 报告不会误触发停用(这是 #838 修复的真实缺陷场景);其二,问句不激活——以 what/how/does/is 等开头的问句("what is caveman mode?")被排除在激活模式之外。

4. 解析管线:opencode 的模板展开如何被识别

这是理解 /caveman 在 opencode 中"为什么长这样"的核心。opencode 会在 chat.message 钩子触发之前,把用户输入的 /caveman ultra 替换为命令文件的正文,于是插件看到的是:

Activate caveman mode: ultra

If no level given, use full. If "off", deactivate.

Respond terse like smart caveman. ...

原始斜杠形式永远不会抵达插件。plugin.js L170-L178chat.message 钩子遍历消息的文本 part,对每个 part 调用:

const change = parseModeChange(part.text, {
  getDefaultMode, expandedTpl: true, unwrapQuotes: true
});
if (change) applyModeChange(change);

两个选项是 opencode 专属的:

  • expandedTpl: true:启用"模板展开体"识别。caveman-parse.js L170-L182 先检查三个独立模式的固定前缀(generate a commit message... → commit、review the current diff → review、compress the file at: → compress),然后对 caveman 命令用正则 /^activate caveman mode:[ \t]*(\S*)/未折叠空白的第一行里提取级别——因为第一行尾部就是 $ARGUMENTS 的落点。该分支必须跑在通用自然语言激活匹配之前,否则 "Activate caveman mode: ultra" 会命中 "activate ... caveman" 触发词,把级别吞掉、按默认值激活(#602 的原始缺陷)。
  • unwrapQuotes: true:非交互的 opencode run 路径会把整条消息包在字面引号里,先剥掉一层再解析(L121-L124)。

解析结果的消费端是 plugin.js L122-L131applyModeChangeclear → 删除 flag 文件;set → 经 safeWriteFlag 写入模式值;unresolved 被直接忽略(不覆盖现状、不产生副作用)。

5. 状态落地:flag 文件与四级默认值解析

opencode 插件的全部动态状态就是一个文件:~/.config/opencode/.caveman-activeplugin.js L99-L106,路径从 $XDG_CONFIG_HOME~/.config/opencode 推导)。"off" 的表示方式是文件不存在,这一点由 readFlag 的 null 返回语义保证。

每次模式变化写入前,safeWriteFlagcaveman-config.js L168-L274)执行一套针对"可预测路径被符号链接劫持"的防护:

  • 临时文件 + 原子 renameO_NOFOLLOW | O_CREAT | O_EXCL,权限 0600
  • 父目录若是符号链接,先解析真实路径并做所有权校验(Unix 校验 uid,Windows 退化为"必须落在用户 home 之下");
  • flag 文件本身若是符号链接则直接拒绝;
  • Windows 锁竞争(EPERM/EBUSY 等)下三次重试 + 退避,finally 保证临时文件不残留。

读取端 readFlagL289-L319)对称防御:拒符号链接、64 字节硬上限(最长合法值 wenyan-ultra 仅 12 字节)、且返回值必须命中 VALID_MODES 白名单,否则一律 null。这意味着即便 flag 文件被写入任意内容,也只会被解释为"模式未激活"。

默认值从哪来? 命令文件说 "If no level given, use full",但裸 /caveman 实际调用的是 getDefaultMode(),其解析顺序(caveman-config.js L118-L138,与文件头注释一致)是:

  1. 环境变量 CAVEMAN_DEFAULT_MODE(值必须在 VALID_MODES 内);
  2. 仓库级配置——从 cwd 向上逐级查找 .caveman/config.json.caveman.json(最多 64 层,拒符号链接),让团队可以按项目固定默认模式而不污染每个贡献者的用户配置;
  3. 用户配置:$XDG_CONFIG_HOME/caveman/config.json(Windows 为 %APPDATA%\caveman\config.json)中的 defaultMode 字段;
  4. 兜底 full——这才是命令文件里 "use full" 的真正含义:它是所有覆盖都不存在时的缺省,而非无条件行为。

因此 {"defaultMode": "lite"} 写进仓库的 .caveman/config.json,之后裸 /caveman 就会进入 lite 级别;CAVEMAN_DEFAULT_MODE=off 则让"会话创建即默认激活"变成 no-op。

6. 会话创建与逐轮强化:三个钩子的分工

plugin.js 通过三个钩子把上面的状态变成持续生效的行为:

  • 插件工厂 + event 钩子L145-L161):会话启动时按默认模式写 flag。工厂加载时立即写一次,是为覆盖一次性 opencode run 下"首个 session.created 先于插件事件分发"的竞态;此后每次 session.created 事件再重新断言,使长驻 TUI 进程里的新会话也携带正确模式。若默认模式是 off,则删除 flag。

  • chat.message 钩子:即第 4 节所述的模式变更解析;其返回值被 opencode 忽略,状态变化完全经由 flag 文件完成。

  • experimental.chat.system.transform 钩子L183-L209):每次 LLM 请求前检查 flag;若处于激活且非独立模式,向系统提示词注入一行强化:

    CAVEMAN MODE ACTIVE (full) — session ruleset applies.
    

    注入是幂等重写而非追加:用正则 /CAVEMAN MODE ACTIVE \([a-z-]+\) — session ruleset applies\./g 找到已存在的旧行原地替换,找不到才追加到系统提示词末尾。注释(L188-L192)说明了原因——若 opencode 跨轮复用同一 system 数组而无条件追加,系统提示词会无界增长、静默吞噬上下文窗口。

还有一个值得了解的非对称设计src/plugins/opencode/README.md 的 "What it does NOT do" 一节):插件不从 session.created 注入系统提示词——opencode 的文档未暴露该钩子的返回形状,所以常驻规则集改由安装器写入 ~/.config/opencode/AGENTS.md,规则因此不依赖插件运行时是否健康;另外 opencode TUI 没有可写状态栏,模式想显示在 shell 提示符里只能直接读 .caveman-active

7. 适用前提、已知限制与排查

plugin.js 头部注释 看,以下事实构成适用边界:

  1. 版本前提:钩子路由(单一 event 处理器按 event.type === 'session.created' 分发)要求 opencode ≥ 1.15.x;旧版中直接以 'session.created' / 'tui.prompt.append' 为顶层钩子键的写法会被静默忽略(对应 issue #418、#421)。排查"模式没生效"时应先确认版本与事件分发。
  2. 模块加载限制:opencode 插件运行在编译后的 Bun 二进制里,磁盘文件的 require() 被拒、import() 一个 CJS 文件得到空命名空间。因此 caveman-config.js / caveman-parse.js 是以 new Function(...) 求值加载的(loadConfig),安装时改名为 caveman-config.cjs / caveman-parse.cjssrc/plugins/opencode/package.json 声明 "type": "module")。解析逻辑刻意保持与 Claude Code 侧的 caveman-mode-tracker.js 同源共享(#602),两侧不会漂移。
  3. flag 是尽力而为safeWriteFlag 在任何文件系统错误下静默失败(可设 CAVEMAN_DEBUG=1 输出诊断),模式状态丢失不会报错,只能靠 readFlag 的 null 语义降级为"未激活"。
  4. 不独立的 npm 包:插件复用主仓库的 caveman-config.js,作为仓内插件随主安装器分发(bin/install.js --only opencodesrc/plugins/opencode 目录连同 caveman-config.cjs 拷入 ~/.config/opencode/plugins/caveman/ 并在 opencode.json 中追加 "plugin" 条目),避免第二次发布节奏与第三方 opencode-caveman 包的命名冲突。

8. 小结

caveman.md 作为 opencode 插件的入口命令,表面上是 13 行提示词,实际上是一个完整状态机的用户面:

  • 模板层:frontmatter 的 description 支撑命令补全,$ARGUMENTS 承载级别参数,正文定义缺省(full)、停用(off)与压缩风格契约;
  • 解析层parseModeChange 以单一共享源识别模板展开体、斜杠命令与自然语言三类输入,带引用防误触、问句排除、标点容忍与"拒绝静默覆盖"的防御语义;
  • 状态层.caveman-active flag 文件 + 四级默认值解析 + 符号链接安全的原子读写;
  • 行为层:会话创建写 flag、逐轮幂等注入 CAVEMAN MODE ACTIVE (mode) 强化行,规则本体则驻留在 AGENTS.md 中独立于插件运行时存活。

理解这条链路后,你在 opencode 里输入 /caveman lite 时发生的每一件事——从模板替换、正则提取、模式白名单校验、原子落盘,到下一次请求的系统提示词注入——都是可追踪、可验证的。

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