↑ ↓ 移动 · Enter 打开 · Esc 关闭点按一个结果

架构 · harness 如何运作

在模型与磁盘之间,有一个 hook。

Claude Code 提供模型及其工具。Nerviz 在其周围加入五类组件。其中四类引导模型;第五类在每个事件上以进程方式运行,并且可以拒绝该操作。

§ 一个功能,穿过各个 hook

一个用例遇上 harness

bank-app 的 UC-005,从第一条提示到构建通过,共九步。终端显示运行了哪些 hook 模式以及它们的输出:拒绝信息就是 hook 自己的原文。

1 / 9

一条提示开启本轮

你输入 /new-feature。guard prompt 关闭上一个阶段,记录磁盘上已有的内容,并为 new-feature(类 orchestrator)打开一个阶段。skill 正文进入上下文,并保留到本次运行结束。

§ 五类组件

谁加载,谁调用,谁能说不

  • CLAUDE.md

    索引与路由表。规范本身位于它所指向的 rule 或 skill 中。

    进入上下文
    始终,在会话开始时
    调用者
    无人调用:自动加载
    能否拦截
    否
  • rule

    一条规范:必须成立的事。叶子节点:从不提及 skill、agent 或命令。

    进入上下文
    当匹配其 paths 的文件被触及时
    调用者
    无人调用:自动加载
    能否拦截
    否
  • skill

    一段流程。它的类决定所用模型、正文的章节,以及可写入的位置。

    进入上下文
    被调用时加载,并保留到后续轮次
    调用者
    你(/名称)或模型
    能否拦截
    否
  • agent

    隔离执行:独立的上下文、受限的工具、独立的模型。

    进入上下文
    被委派时,在全新的上下文中
    调用者
    某个 skill,通过 Agent 工具
    能否拦截
    否
  • hookexit 2

    ArchHook.jar:Java 实现,十一种模式,不依赖 shell。在 settings.json 中注册。

    进入上下文
    从不以文本形式进入:作为进程运行
    调用者
    运行时,在事件发生时
    能否拦截
    能,exit 2

agent 只为三个理由之一而存在:保留上下文、限制工具,或切换模型。凡是必须始终成立的,都是 hook 或 permissions.deny 规则,而不是 markdown 里的文字。

§ 清点目录

有多少 skill 和 agent

nerviz 中的每个 skill 和 agent 都属于 extensions.json 中的一个类,任何未被类列出的文件都会让 schema 失败。因此类就是目录:这些数字读自最新发布版本中的类定义,从不手工填写。

32skills

其中 27 个会导出到每个生成的项目

  • design 13
  • orchestrator 3
  • build 7
  • observer 2
  • meta 1
  • ops 4
  • report 2
7agents

其中 5 个会导出到每个生成的项目

  • driver 1
  • executor 1
  • installer 4
  • verifier 1
数量随时间变化
327
2026-09-112026-10-27
最近新增
  1. project-configskill · ops新2026-10-24
  2. platform-upgradeskill · build2026-10-19
  3. new-projectskill · ops2026-10-15
  4. mapstruct-installeragent · installer2026-10-14
  5. ai-architectskill · design2026-10-13

读自 nerviz 发布版本 v0.44.0 的 extensions.json,发布于 2026-10-27。

§ 谁调用谁

三个命令,三条链

每个命令开启自己的一条链:

  • /init-project 创建项目,只运行一次。
  • /new-feature 设计一个用例并交付规格,不写代码。
  • /new-feature-implement 根据已批准的规格构建代码。

虚线组件仅在适用时运行。

  • skill
  • agent
  • 仅在适用时
§ 写入范围即数据

每个类只写自己的路径

每个 skill 属于七个类之一,每个 agent 属于四个类之一。类在 extensions.json 中列出可写的路径,guard 拒绝其余一切,默认拒绝。选择一个类:

bank-app/
  • .claude/
  • CLAUDE.md
  • docs/
  • use-cases/BACKLOG.md
  • use-cases/UC-005-record-kyc-verdict/20-persistencia.md
  • use-cases/UC-005-record-kyc-verdict/UC-005-spec.md
  • lessons-learned/
  • src/main/java/…/domain/model/Customer.java
  • src/test/java/…/domain/model/CustomerTest.java
  • pom.xml
  • docker-compose.yml
  • .env.example
成员

write_allow

  • 可写入
  • 拒绝:超出写入范围
  • 拒绝:规格已冻结
  • 仅状态收尾与清单勾选
§ rule 按路径加载

触及文件,即获得其规范

每条 rule 都声明 paths。当匹配的文件被触及时,rule 自动进入上下文,无需任何人记得引用它。这里是 bank-app 的 21 条 rule,其 glob 由 export 按它的蓝图写入。选择一个文件:

  • architecture-ddd.md
  • value-objects.md
  • naming.md
  • code-quality.md
  • error-handling.md
  • logging.md
  • lombok.md
  • personal-data.md
  • secrets.md
  • api-rest.md
  • api-graphql.md
  • inbound-admission.md
  • authorization.md
  • observability.md
  • persistence.md
  • messaging.md
  • scheduling.md
  • http-client.md
  • ai.md
  • caching.md
  • search.md
  • testing.md
  • transport-security.md
  • spec-diagrams.md
  • 00-index.md
§ hook 地图

每个事件,每种模式

生成的项目在 .claude/settings.json 中注册的内容。每一项都运行 java -jar .claude/hooks/ArchHook.jar <mode>,采用 exec 形式,在 Linux、macOS 和 Windows 上完全一致。选一个事件,看它何时触发。

一个回合中,事件触发的先后顺序

UserPromptSubmitaudit promptguard prompt

你按下回车。hook 在 Claude 读到任何一个字之前运行。

你输入了

you你输入命令
hookUserPromptSubmit建立本次运行的记录,并取一份 git 基线
claudeClaude读取提示

此事件上的模式

  • audit prompt
    当提示是一个被审计的 skill 时,建立本次运行的记录,以便把 token 和文件归到它名下。项目可关闭
  • guard prompt
    关闭上一个阶段,打开提示所指的阶段,让各个 guard 知道正在运行哪个 skill。
PreToolUseguard callguard writeguard bashschemaaudit callaudit ask

Claude 即将使用一个工具。hook 仍来得及说不。

你输入了

claudeClaudepersistence-architect 想写入 docker-compose.yml
hookPreToolUseguard write 检查它所属类别的领地
✓ 工具照常运行
✕ exit 2:原因回到 Claude,什么也没有写入

此事件上的模式

  • guard call
    不让设计运行触及构建类 skill。可拦截

    拦截: 在设计运行打开期间,对构建类 skill 的 Skill 调用。

  • guard write
    每个 skill 和智能体只在自己类别的领地内写入,已批准的规格保持冻结。可拦截

    拦截: 对正在运行的 skill 或智能体领地之外的写入,以及对已批准用例的任何写入(规格的 status 收尾和清单勾选除外)。

  • guard bash
    同一项检查,从 shell 一侧进行:heredoc、sed -i 或 tee 不能做 Write 不能做的事。可拦截

    拦截: 通过 shell 写入其类别不能写的位置或冻结的用例,以及任何写法的 force push。

  • schema
    保持 harness 的指令文件有效,让损坏的 skill、智能体或 hook 条目立刻失败,而不是在以后的运行中才暴露。可拦截

    拦截: 无效的 SKILL.md、智能体、规则或 settings.json 的 hook 条目,以及 .mcp.json 里的密钥。

  • audit call
    把每次 Skill、Task 或 Agent 调用记为链上的一个节点。项目可关闭
  • audit ask
    记下 Claude 正在等你,这段等待就不会被算作工作时间。项目可关闭
SubagentStartcontext subagenttests agent-start

Claude 把工作交给子智能体。hook 可以在子智能体的第一个回合之前载入上下文。

你输入了

claudeClaude把用例的代码委派出去
hookSubagentStartcontext subagent 交付设计模式目录
subjava-spring-boot-developer带着该目录的上下文开始

此事件上的模式

  • context subagent
    在第一个回合之前,把设计模式目录交给类别需要它的每个智能体。其他智能体什么也收不到。
  • tests agent-start
    记下有一个写入型子智能体正在运行,让测试等它。
PostToolUsecheckschemaformataudit fileaudit answer

一个工具刚刚运行完。改动已经发生,hook 来检查它。

你输入了

subjava-spring-boot-developer编辑 Customer.java
hookPostToolUsecheck、format 和审计轨迹检查该文件
✓ 没问题:工作继续
✕ exit 2:文件已经写入,Claude 被要求立刻修复

此事件上的模式

  • check
    在写代码的同时守住架构:每次编辑都判断边界,而不是等到评审。可拦截

    拦截: 禁止的 import 或限定名,或所改模块的增量编译失败。文件已经写入,所以 Claude 被要求修复。

  • schema
    保持 harness 的指令文件有效,让损坏的 skill、智能体或 hook 条目立刻失败,而不是在以后的运行中才暴露。可拦截

    拦截: 无效的 SKILL.md、智能体、规则或 settings.json 的 hook 条目,以及 .mcp.json 里的密钥。

  • format
    在被改动的模块上运行 spotless:apply,格式化从不占用一个回合。
  • audit file
    记录一次运行触及的每个文件。项目可关闭
  • audit answer
    在报告中记录每个问题和你的回答(已脱敏)。项目可关闭
SubagentStopaudit agenttests agent-end

子智能体完成并交回结果。

你输入了

subjava-spring-boot-developer完成
hookSubagentStop关闭它的报告,并放行测试
claudeClaude收到结果

此事件上的模式

  • audit agent
    关闭子智能体的报告。它的 token 来自它自己的 transcript。项目可关闭
  • tests agent-end
    写入型子智能体结束时,放行测试。
Stopschemaguard sweepcompose gatetestsaudit flush

Claude 认为自己做完了。这是回合结束前的最后一道关口。

你输入了

claudeClaude说自己完成了
hookStopschema、guard sweep、compose gate 和 tests 检查真正发生的改动
✓ 所有关口都通过:回合结束
✕ 某道关口失败:Claude 继续工作并修复

此事件上的模式

  • schema
    保持 harness 的指令文件有效,让损坏的 skill、智能体或 hook 条目立刻失败,而不是在以后的运行中才暴露。可拦截

    拦截: 无效的 SKILL.md、智能体、规则或 settings.json 的 hook 条目,以及 .mcp.json 里的密钥。

  • guard sweep
    回合结束时的兜底:把磁盘与 git 基线比对,抓住工具时的 guard 读不出来的写入。可拦截

    拦截: 本回合中其类别不能写入的任何改动。它负责发现,不负责预防。

  • compose gate
    起不来的栈在回合结束时就被发现,而不是以后在某人的机器上。可拦截项目可调整

    拦截: 当某个 compose 服务处于 exited 或 dead 时,回合的结束。

  • tests
    回合不会在测试变红时结束。可拦截

    拦截: 当自 HEAD 以来改动的模块上 verify 失败时,回合的结束。写入型子智能体运行期间它会等待。

  • audit flush
    根据日志生成本次运行的报告,并写入台账行。项目可关闭
PermissionRequest · PermissionDenied · PostToolUseFailure · PreCompact · StopFailure · SessionEndaudit permaudit failaudit compactaudit stopfailaudit close

六个安静的事件。它们只写入审计轨迹,没有一个会拦截。

你输入了

claudeClaude运行 git push,它保持在 ask
you你回答不
hookPermissionDenied在轨迹里写一行

此事件上的模式

  • audit perm
    记录被请求或被拒绝的权限。项目可关闭
  • audit fail
    把失败的工具记为返工。项目可关闭
  • audit compact
    记录一次上下文压缩。项目可关闭
  • audit stopfail
    记录停止时的失败。项目可关闭
  • audit close
    会话结束时关闭仍打开的运行。项目可关闭

mode 可用 exit 2 拒绝 · mode 从不拦截 ·可拦截 项目可调整 项目可关闭

每种模式为何存在、拦截什么、项目能改什么:全部列在一张表里,并附详细说明。

打开模式目录 →
§ harness 自身的测试

上面的每一次拒绝都在 CI 中运行

validate.yml 在每次 push 和 pull request 时运行。hook 测试启动的正是注册项所运行的 java -jar .claude/hooks/ArchHook.jar,覆盖 Ubuntu、macOS 和 Windows。另一个 job 检查提示图的不变量,还有一个 job 把每个蓝图导出两次,并要求两棵目录树完全一致。

main 上的最近一次运行main 分支上 validate.yml 的实时状态,来自 GitHub
validate.yml · 9fb82ed · 2026-10-13 · 6m17s
  1. hooks · ubuntu-latest✓1m32s
  2. hooks · macos-latest✓1m37s
  3. hooks · windows-latest✓3m15s
  4. design invariants✓33s
  5. export is reproducible and self-contained✓55s
  6. exemplar imports resolve against the real classpath✓6m13s

六个 job 并行运行。每个 hook job 都在测试之前运行 build --verify:提交的 jar 必须与源码编译结果逐字节一致,因此测试运行的是经过审查的字节。

打开这次运行 →
245/ 260

自 2026-09-11 起 validate.yml 的运行通过。11 次失败,4 次取消。统计于 2026-10-13。

失败的运行在 Actions 中保持公开。上方徽章是实时的;数字是快照。

全部运行 →

该运行的每一个 step,回放

validate.yml 中全部 58 个具名 step,使用 GitHub 为上面这次运行记录的时间,以 12 倍速播放。hooks job 在三个 runner 上运行每个 step,每个 runner 一个圆点:Ubuntu、macOS 和 Windows。

6m17s / 6m17s12×
hooks3m15s
  1. Set up job · checkout · setup-java
  2. doctor
  3. committed ArchHook.jar is what the source compiles to
  4. the jar runs what the hooks run
  5. hook blocks forbidden import, from the payload and from argv
  6. format, check and tests hand Maven the module the edited file belongs to
  7. tests defers while a writer subagent of the session runs, and only then
  8. hook blocks a cwd-relative frontmatter injection
  9. schema blocks a migrations entry no project would see
  10. hook reports a compose image tag that disagrees with src/test
  11. guard keeps each skill inside its class's territory
  12. guard keeps each agent inside its class's territory
  13. guard bash refuses force pushes and holds shell writes to the phase
  14. guard sweep reports this turn's writes, never pre-existing dirt
  15. guard status prints the open phase the cockpit draws
  16. schema blocks a mod that would load half-way
  17. compose gate blocks a published service no host client can reach
  18. doctor gate fails on every gated line and only on those
  19. schema blocks a settings file that re-enables a colliding plugin
  20. doctor names a personal skill that shadows a project skill, and never gates
  21. doctor checks the agent instruction files against the blueprint, and never gates
  22. doctor sweeps every source file for forbidden imports, with check's own match
  23. export notes only real overwrites, and never guesses the stamp's commit
  24. context subagent hands the catalog to pattern_catalog agents only
  25. audit renders where the run spent, redacts tool errors, skips what class or project turns off
  26. audit genesis fills GENESIS from the transcripts, once
design invariants33s
  1. Set up job · checkout · setup-java
  2. frontmatter schema (extensions.json is the single owner — invariant 10)
  3. decisions/ doesn't grow paths or enter 00-index.md
  4. no hardcoded Spring/Java version outside decisions/ (invariant 8)
  5. new blueprint doesn't touch prompts
  6. rules is a leaf of the graph
  7. norm contains no code boilerplate
  8. no new commands/
  9. exemplars have the .example suffix at the end
  10. exemplar bodies carry no comment but Javadoc
  11. XML exemplars are well-formed
  12. spec template diagrams name only what their text defines
  13. cache exemplars decide every library default, and only they cache
  14. AI exemplars decide every library default, and only they reach the library
  15. GraphQL exemplars decide every limit and batch every child list
  16. document-family exemplars name collection, alias and version, and run a replica set
  17. search exemplars keep the index a strict, versioned projection
  18. auditing exemplars keep the history whole, its actor named and its prune safe
  19. REST exemplars version every handler from one configuration
  20. admission exemplars keep one bounded limiter per caller and an inbound cap ahead of idempotency
  21. blueprint-declared templates exist on disk
  22. issue forms cited by skills exist
  23. compose service templates merge and parse
  24. each norm has a single owner
  25. no dependencies outside the Java ecosystem
  26. every norm `paths` matches some blueprint
export is reproducible and self-contained55s
  1. Set up job · checkout · setup-java
  2. export twice per blueprint, require identical trees
  3. the exported tree carries nothing that only exists here
  4. the exported tree passes its own schema
  5. re-export deletes what export.retired names
exemplar imports resolve against the real classpath6m13s
  1. Set up job · checkout · setup-java
  2. reference project from the Initializr
  3. build the classpath
  4. every exemplar import exists in a JAR
  5. exemplars don't use known deprecated symbols

播放器的每一步,以及证明它的测试

  1. 步骤 4

    设计类 skill 无法写入 docker-compose.yml。引发这一切的那个案例就是其中一个用例。

    $ java .claude/.ci/SkillTerritoryTest.java✅ compose file outside the territory — blocked (exit 2)✅ compose allowed once docker-architect joined (exit 0)✅ the implement thread itself still may not write compose (exit 2)59 个用例,全部通过

    同样的写入范围也适用于 shell 写入,所有 force push 写法都会被拒绝。

    $ java .claude/.ci/BashGuardTest.java✅ `-f` — blocked✅ executor: `sed -i -e` on a file outside its territory — blocked29 个用例,全部通过
  2. 步骤 6

    agent 按它自己的类判定,无论调用者留下了哪个阶段。

    $ java .claude/.ci/AgentTerritoryTest.java✅ the executor writes main source (exit 0)✅ archunit-installer is refused docker-compose.yml (exit 2)37 个用例,全部通过

    设计模式目录只会交给其类要求它的 agent。

    $ java .claude/.ci/SubagentContextTest.java✅ general-purpose receives nothing✅ context subagent: catalog to pattern_catalog agents only, within max_chars.
  3. 步骤 7

    禁止的 import 以 exit 2 拒绝,无论来自 hook 载荷还是 check <path>。

    $ java .claude/.ci/BoundaryTest.java  ✓ stdin payload blocks the forbidden import  ✓ check <path> blocks it with stdin left open✅ Boundary enforced: forbidden import blocked with exit 2, from the payload and from argv.
  4. 步骤 9

    Stop 的 sweep 只指出本轮写入的内容,而且不会重复拦截。

    $ java .claude/.ci/SweepTest.java✅ write outside the territory — reported, pre-existing dirt is not✅ stop_hook_active — does not block twice

    tests 在写入型 subagent 运行期间等待,且仅在此时等待。

    $ java .claude/.ci/TestsDeferTest.java✅ tests: deferred while a writer subagent runs, Maven called otherwise.

提示图,像代码一样被检查

design invariants job 把 harness 自身架构的每条规则变成一个会失败的步骤。其中一些,按 workflow 中的名称:

  • frontmatter schema (extensions.json is the single owner — invariant 10)
  • rules is a leaf of the graph
  • new blueprint doesn't touch prompts
  • no hardcoded Spring/Java version outside decisions/ (invariant 8)
  • each norm has a single owner
  • every norm `paths` matches some blueprint

在每个生成的项目中也是如此

项目自带 CI。它的 architectural boundaries job 运行 doctor gate:当 hook jar、hooks、schema 或边界损坏,或任何源文件包含禁止的 import 时,它就会失败。

bank-app · build.yml · a3cc28a · 2026-10-15
  • architectural boundariesjava .claude/hooks/ArchHook.java doctor gate10s
  • build and tests3m37s
打开 bank-app 的运行 →