关于 harness-tour
harness-tour 是面向 Claude 项目的交互式技能,通过故意触发违规来演示 harness 规则如何被 hook 实时拦截。它提供短版和完整巡览,并在结束时清理演示文件、保留审计记录。
主要功能
- 01短版演示 R1–R3,完整演示 R1–R10
- 02预检 harness 状态后再启动演示
- 03展示 hook 的实时拦截原因
- 04清理演示文件及对应索引行
- 05读取审计记录作为演示结果
适用场景
- 01向项目成员展示 harness 规则如何阻止违规操作
- 02检查 Claude 会话是否加载项目 hook
- 03用完整巡览查看阻止、记录和提醒类规则
MCP 相关
该技能使用 host hook 展示规则拦截,不是 MCP 服务;README 明确区分 hook 与 MCP。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ rheinmir/ setup --skill harness-tour安装后在 Claude Code 中输入/harness-tour确认已加载。
详细介绍
技能:harness-tour
内容
目的与背景
- 目的: 让用户亲眼看到(而不是阅读)harness 如何运作:你——Claude——在真实项目中故意违反规则,被 hook 实时拦截,并进行讲解。结束时不留下任何残留。
- 触发条件(何时使用): 用户输入
/harness-tour,或说“harness tour”“看看 harness 有什么作用”“测试短版”“测试完整版”(disable-model-invocation: true——仅在用户调用时运行)。提供两种时长:SHORT(默认,R1·R2·R3)和 FULL(R1–R10)。 - 非目标: 不安装/修改 harness(仅在 MISSING 时建议安装命令),没有 hook 时不进行演示,不触碰项目现有文件,不替代
demo.sh/test-broad.sh(无需会话的机器演示版本)。
心智模型
ARGUMENTS → 时长(SHORT|FULL)→ 预检查 harness(POC|PROD|MISSING)→ 每个场景:讲解 → 在 1-rule 路径映射上故意违规 → hook 实时拦截 → 原文摘录 → 修复 → 清理 tour-demo* + 同一轮中的索引行 → 结尾摘录 audit.jsonl。规则分为 3 组:实时拦截(R1、R2、R3、R5、R7、R9)· 记录/提醒但不拦截(R4、R8、R10)· 仓库层级(R6)。
输入和输出约定
| 字段 | 必填? | 含义 | |
|---|---|---|---|
| 输入 | ARGUMENTS | 否 | 包含 full/10/"完整" → FULL;否则 → SHORT |
| 输入 | 会话在已安装 harness 的项目根目录中打开 | 是 | 必须加载 .claude/settings.json,否则为 MISSING |
| 输出 | 讲解 + 每个场景拦截信息的 stderr 原文摘录 | 是 | 例如 [R1 no-write-raw] …、[R3 index-sync] … |
| 输出 | 项目整洁 | 是 | 删除所有已 PASS 的 tour-demo* + 对应索引行;保留 harness 产物 |
| 输出 | 结尾 | 是 | 总结刚才拦截的规则 + cat .claude/audit/*.jsonl 的最后几行;FULL 还会附上 10 条规则的表格 |
规则与能力
- RULE-01(MUST):每个场景都要:讲解 → 违规 → 原文摘录拦截原因 → 修复。不可简略。
- RULE-02(MUST):只操作名为
tour-demo*的文件;绝对不能修改/删除项目现有文件。 - RULE-03(MUST):某个本应拦截的场景却未被拦截 → 停止演示,报告“rule X 的防护栏失效”——这是需要处理的真实 bug。 (不适用于不拦截组 R4/R6/R8/R10——这些规则本来就不会拦截。)
- 能力:在
llmwiki/中写入/删除演示文件tour-demo*+ 修改wiki/index.md(演示行);读取审计产物;运行本地 harness 脚本。依赖宿主的 hook(PreToolUse/Stop)——没有 hook 就不演示。
失败边界
- 预检查没有任何输出 → 被阻止(MISSING):停止,建议安装 PoC 的命令并在根目录打开新会话,不进行演示。
- 本应拦截的场景却未被拦截 → 失败:停止演示,报告“rule X 的防护栏失效”(R4/R6/R8/R10 除外)。
- 清理时删除文件却漏掉索引行 → Stop hook R3 会拦截本轮清理 → 在同一轮中删除残留行,然后结束。
操作方式
主要工作流
| 步骤 | 类型 | 输入 | 操作 | 输出/退出 | 失败/下一步 |
|---|---|---|---|---|---|
| W01 | 确定性 | ARGUMENTS | 选择演示时长 | SHORT 或 FULL | — |
| W02 | 确定性 | 根目录会话 | 预检查 harness(使用下方的 grep/test 命令) | POC / PROD | 没有输出 → MISSING,停止 |
| W03 | 有副作用 | 路径 → 规则映射 | 按顺序演示 SHORT(3 个场景)或 FULL(R1–R10)剧本,每个场景开始前先讲解 | 拦截信息的 stderr 原文摘录 + PASS 文件 | 未被拦截 → 停止,报告防护栏失效 |
| W04 | 有副作用 | 已 PASS 的 tour-demo* 文件 | 在同一轮中清理:删除文件 + 索引行 | 项目整洁 | Stop R3 拦截 → 删除残留行 |
| W05 | 确定性 | audit | 结尾:总结规则 + cat .claude/audit/*.jsonl | 报告 | — |
各步骤详情(W01–W05 的事实来源):
选择演示时长(读取 ARGUMENTS)
- 包含
full/10/ "完整" → FULL(R1–R10)。 - 其他情况(默认,或
short/3)→ SHORT(3 个场景:R1 · R2 · R3)。
预检查 — 检测当前会话中正在运行的 harness
在会话的 ROOT 目录运行:
{ grep -q llmwiki-validate .claude/settings.json 2>/dev/null && test -f harness/poc-vendor-neutral/bin/llmwiki-validate.py && echo POC; }
test -f llmwiki/.claude/hooks/pre_tool_use.py && echo PROD
- 输出
POC→ 主推方式。PoC 现已覆盖 R1–R10(5 条规则在 PreToolUse 阶段拦截,R3/R4/R8/R10 通过事件 hook 实现)。 - 输出
PROD→ production harness。R1–R10 相同,以下 short/full 场景也相同;区别仅在于 hook 脚本名称 (pre_tool_use.py/post_tool_use.py/stop.py),且 R1 也可能通过permissions.deny拦截。演示方式完全相同。 - 没有任何输出 → MISSING。停止。(不要假装演示),建议安装(主推 PoC):
然后在该项目的 root 新开 Claude 会话(以便加载curl -fsSL https://raw.githubusercontent.com/Rheinmir/setup/orca/harness/poc-vendor-neutral/bootstrap.sh | bash.claude/settings.json)→ 重新输入/harness-tour。
Hook 只有在会话**从已安装配置的正确项目 root 加载
.claude/settings.json**时才会拦截。在源代码仓库 / 其他 root 中打开会话 → 没有 hook → tour 不会触发(演示时没有拦截 = 作秀,禁止)。
Harness 是 HOOK,不是 MCP(用户问“为什么 /mcp 里看不到”时解释)
拦截层是 PreToolUse hook → 通过 /hooks 或 .claude/settings.json 中的 hooks 块检查,不在 /mcp 中。
MCP 是另一回事(供模型调用的工具)。PoC:hook 调用 llmwiki-validate claude-hook。Production:pre_tool_use.py……
路径 → 规则映射表(使用正确路径,确保每个场景只触发 1 条规则)
多条规则都会检查 wiki/ → 路径选错会导致 2 条规则同时拦截,造成混乱。标准映射如下(依据 policy.yaml):
| 规则 | 用于触发违规的文件 | 为什么只有该规则会触发 |
|---|---|---|
| R1 no-write-raw | llmwiki/raw/tour-demo.md | deny_write glob raw/** |
| R2 origin-required | llmwiki/wiki/sources/draft/tour-demo.md = ---\ntype: draft\n---\n# Tour Demo(有 frontmatter,没有 ## Origin,没有 ## Plan) | frontmatter → R9 通过;缺少 Origin → R2 拦截 |
| R3 index-sync | (Stop)刚创建的 R2 文件,尚未加入 wiki/index.md | m_stop 扫描 concepts/entities/sources/draft,并与 index 对比 |
| R5 folder-structure | llmwiki/wiki/tour-demo.md(就在 wiki/ 下,basename 异常) | forbid_root:wiki/*.md 不在允许列表中 |
| R7 proposal-complete | llmwiki/wiki/sources/draft/tour-demo-proposal.md — 有 frontmatter + ## Origin + ## Plan + proposed,缺少 ## Agent Task Assignment 和 Sequence diagram | frontmatter → R9 通过,Origin → R2 通过 → R7 拦截 |
| R9 okf-frontmatter | llmwiki/wiki/concepts/tour-demo.md — 有 ## Origin,但没有 YAML frontmatter | 有 Origin,因此 R2 通过;缺少 type: → R9 拦截 |
⚠️ R9 现在覆盖
sources/+draft/两者(与 global production 一致 — 草稿也需要 frontmatter)。由于 R2/R9 都会检查所有 content dir → 通过内容而非路径进行隔离:文件有 frontmatter 但缺少 Origin → 只有 R2;文件有 Origin 但缺少 frontmatter → 只有 R9。
实时不拦截(仅供查看 artifact,明确说明“不会拦截”):R4(.claude/audit/audit.jsonl + log.md)、
R8(SessionStart 时的 [harness] N rule… 行)、R10(.claude/audit/.docs-gate.json 统计 prompt)。
仓库层:R6(pre-commit + CI .github/workflows/harness.yml + skill /verify-before-commit)。
简短剧情(默认)——3 个场景,按顺序演示,每个场景前先旁白
开场:“我会故意违反 3 条规则。看看 hook 如何实时拦截我——不是我主动让步。”
场景 1 — R1(PreToolUse · 拒绝写入 raw/):
- 旁白:“raw/ 是人类的收件箱,Agent 不能写入。”
- Write
llmwiki/raw/tour-demo.md→ exit 2 → 拦截。逐字引用 stderr[R1 no-write-raw] …。不要重试。文件不会创建。
场景 2 — R2(PreToolUse · 强制要求 ## Origin):
- 旁白:“创建来源文件时‘忘记’
## Origin→ 就无法追溯来源。” - Write
llmwiki/wiki/sources/draft/tour-demo.md=---\ntype: draft\n---\n# Tour Demo(包含 frontmatter,以便通过 R9,但没有## Origin)→ exit 2 → 拦截。引用[R2 origin-required] …。 - 修复:将正确内容重新写入同一文件,添加
## Origin\n- harness-tour demo(保留原 frontmatter)→ 这次通过,文件写入成功。
场景 3 — R3(Stop · 索引说谎):
- 旁白:“刚创建了 tour-demo 文件,但我故意没有把它写入
wiki/index.md。现在尝试结束本轮。” - 在此结束 response,不更新 index → Stop hook(
harness-events.py stop)返回 exit 2,并给出不匹配项列表。 - 被拦截时:逐字引用
[R3 index-sync] wiki/index.md 未列出:…,然后在wiki/index.md中只为 tour-demo 添加 1 行,接着清理。
→ 接下来是清理(见下文)。
完整剧情(当 ARGUMENTS 包含 full 时)——依次演示 R1–R10
演示 6 个实时拦截规则(R1、R2、R3、R5、R7、R9)+ 3 个不拦截、展示产物的规则(R4、R8、R10)+ 1 个仓库级规则(R6)。 在介绍不拦截的规则组之前明确说明:“接下来的 3 条规则不会在输入时拦截——它们会记录/提醒;我会展示产物作为证据。”
R1 — Write llmwiki/raw/tour-demo.md → 拦截(拒绝写入 raw/)。引用错误,不重试。
R2 — Write llmwiki/wiki/sources/draft/tour-demo.md(包含 frontmatter ---\ntype: draft\n---,以便通过 R9,但缺少 ## Origin)→ 拦截 → 添加 ## Origin → 通过。
**R3 —(Stop)**在 R2 文件尚未加入 index.md 时结束本轮 → Stop 拦截 → 添加 index 行 → 继续。
(在 FULL 中,为了保持流程连贯,可以把 R3 合并到最后:创建以下通过检查的文件后,尝试结束一轮,此时 index 仍有遗漏 → 拦截 → 更新 index。)
**R4 — log-append(不拦截):**一次 Write 通过后,只有 .claude/audit/audit.jsonl 新增一行(tool+path+timestamp),并重新生成 .claude/audit/log.md。用 cat 查看末尾几行作为证据——“机器记录,我删不掉。”
R5 — Write llmwiki/wiki/tour-demo.md(直接位于 wiki/ 下)→ 拦截 [R5 folder-structure] …(wiki 文件必须位于 concepts/entities/… 下)。不重试。
**R6 — verify-before-commit(仓库级,不实时拦截):**展示提交门禁,包括 pre-commit hook + CI .github/workflows/harness.yml(对变更的 .md 调用核心 files mode)+ skill /verify-before-commit(Claude 在提交前自行检查新代码)。这是仓库级机制,不属于 session——不直接演示拦截。
R7 — Write llmwiki/wiki/sources/draft/tour-demo-proposal.md,包含 frontmatter + ## Origin + ## Plan + proposed,但缺少 ## Agent Task Assignment 和 Sequence diagram → 拦截 [R7 proposal-complete] … → 补上这 2 个部分 → 通过。
**R8 — pattern-health(不拦截):**运行 python3 harness/poc-vendor-neutral/bin/harness-events.py session,显示 [harness] N 条规则正在守护…(若 policy 与 remote 不一致,还会显示 drift 警告)。这是 SessionStart 时打印的内容。
R9 — Write llmwiki/wiki/concepts/tour-demo.md,包含 ## Origin,但没有 YAML frontmatter → 拦截 [R9 okf-frontmatter] …(缺少 type)→ 在文件顶部补上 ---\ntype: concept\n--- → 通过。
R10 — docs-gate(不拦截):cat .claude/audit/.docs-gate.json 可显示 prompt 计数器;说明每隔 LLMWIKI_DOCS_GATE_EVERY(默认 5)轮,UserPromptSubmit 会插入指令,建议调用 /docs-site-macos。
**完整剧情结束:**列出 10 条规则的表格——哪些实时拦截,哪些记录/提醒,哪些属于仓库级;引用 .claude/audit/*.jsonl 作为证据。
清理(必须——在同一轮中完成,否则 R3 会拦截)
删除所有已 PASS(已创建)的 tour-demo* 文件,并且在同一轮中删除 wiki/index.md 中对应的行
(删除文件却保留行 → 索引不一致 → Stop hook 会拦截本次清理):
- SHORT:
wiki/sources/draft/tour-demo.md+ 对应行。 - FULL:另外删除
wiki/sources/draft/tour-demo-proposal.md、wiki/concepts/tour-demo.md+ 对应行。 - 被拦截的文件(raw/tour-demo、wiki/tour-demo R5)不存在 → 无需删除。
- Harness 工件(
audit.jsonl、log.md、.docs-gate.json)是有效的机器记录 → 保留,不要删除。 - 绝对不要碰项目中其他已有文件。
**结束语:**用一段话总结刚才拦截的规则 + “刚才所有工具调用都在 .claude/audit/*.jsonl 中——机器记录下来了,我不会忘记”(cat 最后几行)。机器演示版无需会话:bash harness/poc-vendor-neutral/demo.sh(13 条断言)· test-broad.sh(63 条断言)。
分支
| ID | 类型 | 条件 | 行为 | 跳过 / 失败 | 汇合 |
|---|---|---|---|---|---|
| B01 | user_optional | ARGUMENTS 含 full / 10 / “完整” | 用 FULL R1–R10 脚本替代 SHORT | 默认 → SHORT | W04 |
| B02 | conditional_required | pre-check 输出 PROD | 完全照常演示,仅 hook 脚本名称不同(pre_tool_use.py/post_tool_use.py/stop.py),R1 可能通过 permissions.deny 拦截 | — | W03 |
| B03 | recovery | pre-check 无输出(MISSING) | 停止,建议执行 PoC bootstrap.sh 的 curl 命令(Pre-check 块),然后在项目根目录开启新会话 | 不演示 | 结束 |
| B04 | user_optional | 用户询问“为什么 /mcp 看不到” | 解释 harness 是 HOOK(检查 /hooks),不是 MCP | — | W03 |
验证与停止条件
每个实时拦截场景只有在 hook 实际返回 exit 2 且 stderr 原样摘录时才计入;拦截后不重试(已指定的修复步骤除外)。任何实时拦截规则未能拦截时,立即停止 Tour。全部完成的条件是不存在 tour-demo*,且 wiki/index.md 内容匹配。
示例
- **正向:**在已安装 PoC 的项目上运行
/harness-tour→ pre-check 输出POC→ Writellmwiki/raw/tour-demo.md被[R1 no-write-raw] …拦截 → R2 拦截,添加## Origin后通过 → 本轮结束时被 Stop[R3 index-sync] …拦截 → 添加对应行 → 在同一轮中清理wiki/sources/draft/tour-demo.md+ 对应行。 - **边界情况/失败:**在源代码仓库中运行
/harness-tour full(未加载项目的.claude/settings.json)→ pre-check 无输出 → MISSING,停止,建议执行 curl bootstrap 命令并开启新会话;不演示任何场景。