Between the model and the disk, a hook.
Claude Code brings the model and its tools. Nerviz adds five kinds of piece around them. Four of them guide the model. The fifth runs as a process on every event, and it can refuse the action.
A use case meets the harness
bank-app's UC-005, from the first prompt to the green build, in nine steps. The terminal shows which hook modes ran and what they printed: the refusals are the hooks' own text.
A prompt opens the turn
You type /new-feature. guard prompt closes the previous phase, records what is already on disk, and opens a phase for new-feature, class orchestrator. Its body enters the context and stays for the rest of the run.
Who loads, who calls, who can say no
- CLAUDE.md
Index and routing table. The norm itself lives in the rule or skill it points to.
- Enters context
- Always, at session start
- Invoked by
- Nobody: it loads
- Can block
- No
- rule
A norm: what must be true. A leaf: it never names a skill, an agent or a command.
- Enters context
- When a file matching its
pathsis touched - Invoked by
- Nobody: it loads
- Can block
- No
- skill
A procedure. Its class fixes its model, the sections of its body and where it may write.
- Enters context
- On invocation, then stays for the turns that follow
- Invoked by
- You, with
/name, or the model - Can block
- No
- agent
Isolated execution: its own context, restricted tools and its own model.
- Enters context
- On delegation, in a fresh context
- Invoked by
- A skill, through the Agent tool
- Can block
- No
- hookexit 2
ArchHook.jar: Java, eleven modes, no shell. Wired insettings.json.- Enters context
- Never as text: it runs as a process
- Invoked by
- The runtime, on the event
- Can block
- Yes, exit 2
An agent exists for one of three reasons only: to preserve context, to restrict tools, or to switch model. Anything that must always hold is a hook or a permissions.deny rule, never prose in markdown.
How many skills and agents
Every skill and agent in nerviz belongs to a class in extensions.json, and schema fails on any file no class lists. So the classes are the catalog: these numbers are read from them at the latest release, never typed by hand.
27 of them are exported into every generated project
design13orchestrator3build7observer2meta1ops4report2
5 of them are exported into every generated project
driver1executor1installer4verifier1
project-confignew2026-10-24platform-upgrade2026-10-19new-project2026-10-15mapstruct-installer2026-10-14ai-architect2026-10-13
Read from extensions.json in nerviz release v0.44.0, published 2026-10-27.
Three commands, three chains
Each command opens a chain of its own:
/init-projectcreates the project, once./new-featuredesigns one use case and delivers a spec, no code./new-feature-implementbuilds the code from the approved spec.
Dashed pieces run only when they apply.
use-case-design00-caso-de-uso.mddomain-modeling10-dominio.mdrest-api-architect30-rest.mdunless the trigger is GraphQL onlygraphql-api-architect31-graphql.mdwhen the trigger names a GraphQL adaptersecurity-architect32-seguranca.mdwhen access needs more than the filter chain giveshttp-client-architect28-cliente-http.mdwhen a port calls another system over HTTPai-architect29-ia.mdwhen a port is an LLM or a vector storesearch-architect23-busca.mdwhen the Search index row names a projectionmessaging-architect25-mensageria.mdwhen an event needs Kafka deliveryjobs-architect35-jobs.mdon a schedule, an outbox relay, a reconciliation, a re-index or a history prunepersistence-architect20-persistencia.mdcache-architect22-cache.mdwhen Freshness states a tolerancetest-architect40-testes.md- you approve · spec frozen
mapstruct-installerpre-flight, when no mapper mechanism is recorded; first and alone if MapStruct is chosencommons-logging-installerpre-flight, when commons is emptyjava-spring-boot-developergroup domainjava-spring-boot-developergroup adaptersjava-spring-boot-developergroup tests- build green · spec implemented
compose-service-installerwhen the spec decided a new compose service; runs docker-architect
- skill
- agent
- only when it applies
Every class writes only its own paths
Each skill belongs to one of seven classes, each agent to one of four. The class lists what it may write in extensions.json, and guard refuses everything else, deny by default. Pick a class:
.claude/CLAUDE.mddocs/use-cases/BACKLOG.mduse-cases/UC-005-record-kyc-verdict/20-persistencia.mduse-cases/UC-005-record-kyc-verdict/UC-005-spec.mdlessons-learned/src/main/java/…/domain/model/Customer.javasrc/test/java/…/domain/model/CustomerTest.javapom.xmldocker-compose.yml.env.example
- may write
- refused: outside the territory
- refused: frozen spec
- status close and checklist only
Touch a file, get its norms
Every rule declares paths. When a matching file is touched, the rule enters the context on its own, and nobody has to remember to cite it. These are bank-app's 21 rules, with the globs export wrote for its blueprint. Pick a file:
architecture-ddd.mdvalue-objects.mdnaming.mdcode-quality.mderror-handling.mdlogging.mdlombok.mdpersonal-data.mdsecrets.mdapi-rest.mdapi-graphql.mdinbound-admission.mdauthorization.mdobservability.mdpersistence.mdmessaging.mdscheduling.mdhttp-client.mdai.mdcaching.mdsearch.mdtesting.mdtransport-security.mdspec-diagrams.md00-index.md
Every event, every mode
What a generated project registers in .claude/settings.json. Every entry runs java -jar .claude/hooks/ArchHook.jar <mode>, in exec form, the same on Linux, macOS and Windows. Pick an event to see when it fires.
The events, in the order they fire in a turn
audit promptguard promptYou press Enter. The hook runs before Claude reads a word.
You typed
Modes on this event
audit promptOpens the record of the run when the prompt is an audited skill, so tokens and files can be attributed to it.project can switch offguard promptCloses the previous phase and opens the one the prompt names, so the guards know which skill is running.
guard callguard writeguard bashschemaaudit callaudit askClaude is about to use a tool. The hook still has time to say no.
You typed
persistence-architect wants to write docker-compose.ymlguard write checks the territory of its classModes on this event
guard callKeeps a design run from reaching a build skill.can blockBlocks: A
Skillcall to a build-class skill while a design run is open.guard writeEvery skill and agent writes only inside the territory of its class, and an approved spec stays frozen.can blockBlocks: A write outside the territory of the running skill or agent, and any write into an approved use case except its status close and checklist toggles.
guard bashThe same check, reached from the shell: a heredoc,sed -iorteemust not do what a Write may not.can blockBlocks: A shell write into a place its class cannot write or into a frozen use case, and a force push in any spelling.
schemaKeeps the instruction files of the harness valid, so a broken skill, agent or hook entry fails at once and not in a later run.can blockBlocks: An invalid
SKILL.md, agent, rule orsettings.jsonhook entry, and a secret in.mcp.json.audit callRecords each Skill, Task or Agent call as a node of the chain.project can switch offaudit askNotes that Claude is waiting for you, so the wait is not counted as work.project can switch off
context subagenttests agent-startClaude hands work to a subagent. The hook can load context before the subagent's first turn.
You typed
context subagent hands over the patterns catalogModes on this event
context subagentHands the design patterns catalog to every agent whose class asks for it, before its first turn. Other agents get nothing.tests agent-startNotes that a writer subagent is running, so the tests wait for it.
checkschemaformataudit fileaudit answerA tool has just run. The change is made, and the hook looks at it.
You typed
Customer.javacheck, format and the trail look at the fileModes on this event
checkHolds the architecture while the code is written: the boundary is judged on each edit, not at review.can blockBlocks: A forbidden import or qualified name, or a failed incremental compile of the touched module. The file is already written, so Claude is told to fix it.
schemaKeeps the instruction files of the harness valid, so a broken skill, agent or hook entry fails at once and not in a later run.can blockBlocks: An invalid
SKILL.md, agent, rule orsettings.jsonhook entry, and a secret in.mcp.json.formatRunsspotless:applyon the touched module, so formatting never costs a turn.audit fileRecords each file a run touched.project can switch offaudit answerRecords each question and your answer, redacted, in the report.project can switch off
audit agenttests agent-endThe subagent finishes and hands its result back.
You typed
Modes on this event
audit agentCloses the subagent's report. Its tokens come from its own transcript.project can switch offtests agent-endFrees the tests when the writer subagent ends.
schemaguard sweepcompose gatetestsaudit flushClaude thinks it is done. This is the last gate before the turn ends.
You typed
schema, guard sweep, compose gate and tests look at what really changedModes on this event
schemaKeeps the instruction files of the harness valid, so a broken skill, agent or hook entry fails at once and not in a later run.can blockBlocks: An invalid
SKILL.md, agent, rule orsettings.jsonhook entry, and a secret in.mcp.json.guard sweepThe backstop at the end of the turn: it compares the disk with the git baseline, so it catches the writes no tool-time guard could read.can blockBlocks: Any change this turn made that its class may not write. It detects; it does not prevent.
compose gateA stack that does not start is caught when the turn ends, not later on someone's machine.can blockproject can tuneBlocks: The end of the turn while a compose service is
exitedordead.testsA turn does not end on red tests.can blockBlocks: The end of the turn while
verifyfails on the modules changed sinceHEAD. It waits while a writer subagent runs.audit flushRenders the run's report from the log and writes the ledger line.project can switch off
audit permaudit failaudit compactaudit stopfailaudit closeSix quieter events. They only write to the audit trail, and none of them blocks.
You typed
git push, which stays on askModes on this event
audit permRecords a permission asked or denied.project can switch offaudit failRecords a tool that failed, as rework.project can switch offaudit compactRecords a context compaction.project can switch offaudit stopfailRecords a failure while stopping.project can switch offaudit closeCloses the open run when the session ends.project can switch off
mode can refuse with exit 2 · mode never blocks ·can block project can tune project can switch off
Why each mode exists, what it blocks, and what a project can change: all of them, in a table, with the detail.
Open the mode catalog →Every refusal above runs in CI
validate.yml runs on every push and pull request. The hook tests launch the same java -jar .claude/hooks/ArchHook.jar the registrations run, on Ubuntu, macOS and Windows. Another job checks the invariants of the prompt graph, and another exports every blueprint twice and requires identical trees.
- hooks · ubuntu-latest1m32s
- hooks · macos-latest1m37s
- hooks · windows-latest3m15s
- design invariants33s
- export is reproducible and self-contained55s
- exemplar imports resolve against the real classpath6m13s
The six jobs run in parallel. In each hook job, build --verify runs before the tests: the committed jar must be byte for byte what the source compiles to, so the tests exercise reviewed bytes.
runs of validate.yml passed since 2026-09-11. 11 failed and 4 were cancelled. Counted on 2026-10-13.
The red ones stay public in Actions. The badge above is live; the numbers are a snapshot.
All runs →Every step of that run, replayed
All 58 named steps of validate.yml, with the times GitHub recorded for the run above, played 12× faster. The hooks job runs each step on three runners, one dot per runner: Ubuntu, macOS and Windows.
- Set up job · checkout · setup-java
- doctor
- committed ArchHook.jar is what the source compiles to
- the jar runs what the hooks run
- hook blocks forbidden import, from the payload and from argv
- format, check and tests hand Maven the module the edited file belongs to
- tests defers while a writer subagent of the session runs, and only then
- hook blocks a cwd-relative frontmatter injection
- schema blocks a migrations entry no project would see
- hook reports a compose image tag that disagrees with src/test
- guard keeps each skill inside its class's territory
- guard keeps each agent inside its class's territory
- guard bash refuses force pushes and holds shell writes to the phase
- guard sweep reports this turn's writes, never pre-existing dirt
- guard status prints the open phase the cockpit draws
- schema blocks a mod that would load half-way
- compose gate blocks a published service no host client can reach
- doctor gate fails on every gated line and only on those
- schema blocks a settings file that re-enables a colliding plugin
- doctor names a personal skill that shadows a project skill, and never gates
- doctor checks the agent instruction files against the blueprint, and never gates
- doctor sweeps every source file for forbidden imports, with check's own match
- export notes only real overwrites, and never guesses the stamp's commit
- context subagent hands the catalog to pattern_catalog agents only
- audit renders where the run spent, redacts tool errors, skips what class or project turns off
- audit genesis fills GENESIS from the transcripts, once
- Set up job · checkout · setup-java
- frontmatter schema (extensions.json is the single owner — invariant 10)
- decisions/ doesn't grow paths or enter 00-index.md
- no hardcoded Spring/Java version outside decisions/ (invariant 8)
- new blueprint doesn't touch prompts
- rules is a leaf of the graph
- norm contains no code boilerplate
- no new commands/
- exemplars have the .example suffix at the end
- exemplar bodies carry no comment but Javadoc
- XML exemplars are well-formed
- spec template diagrams name only what their text defines
- cache exemplars decide every library default, and only they cache
- AI exemplars decide every library default, and only they reach the library
- GraphQL exemplars decide every limit and batch every child list
- document-family exemplars name collection, alias and version, and run a replica set
- search exemplars keep the index a strict, versioned projection
- auditing exemplars keep the history whole, its actor named and its prune safe
- REST exemplars version every handler from one configuration
- admission exemplars keep one bounded limiter per caller and an inbound cap ahead of idempotency
- blueprint-declared templates exist on disk
- issue forms cited by skills exist
- compose service templates merge and parse
- each norm has a single owner
- no dependencies outside the Java ecosystem
- every norm `paths` matches some blueprint
- Set up job · checkout · setup-java
- export twice per blueprint, require identical trees
- the exported tree carries nothing that only exists here
- the exported tree passes its own schema
- re-export deletes what export.retired names
- Set up job · checkout · setup-java
- reference project from the Initializr
- build the classpath
- every exemplar import exists in a JAR
- exemplars don't use known deprecated symbols
Each step of the player, and the test that proves it
- step 4
A design skill cannot write
docker-compose.yml. The case that started it all is one of the cases.$ 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 cases, all green
The same territory holds for shell writes, and every force-push spelling is refused.
$ java .claude/.ci/BashGuardTest.java✅ `-f` — blocked✅ executor: `sed -i -e` on a file outside its territory — blocked29 cases, all green
- step 6
An agent is judged by its own class, whatever phase its caller left open.
$ java .claude/.ci/AgentTerritoryTest.java✅ the executor writes main source (exit 0)✅ archunit-installer is refused docker-compose.yml (exit 2)37 cases, all green
The design patterns catalog reaches only the agents whose class asks for it.
$ java .claude/.ci/SubagentContextTest.java✅ general-purpose receives nothing✅ context subagent: catalog to pattern_catalog agents only, within max_chars.
- step 7
A forbidden import exits 2, from the hook payload and from
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.
- step 9
The
Stopsweep names only what this turn wrote, and never blocks twice.$ java .claude/.ci/SweepTest.java✅ write outside the territory — reported, pre-existing dirt is not✅ stop_hook_active — does not block twice
testswaits while a writer subagent runs, and only then.$ java .claude/.ci/TestsDeferTest.java✅ tests: deferred while a writer subagent runs, Maven called otherwise.
The prompt graph, checked like code
The design invariants job turns each rule of the harness's own architecture into a failing step. Some of them, as named in the workflow:
frontmatter schema (extensions.json is the single owner — invariant 10)rules is a leaf of the graphnew blueprint doesn't touch promptsno hardcoded Spring/Java version outside decisions/ (invariant 8)each norm has a single ownerevery norm `paths` matches some blueprint
And in every generated project
The project carries its own CI. Its architectural boundaries job runs doctor gate, which fails when the hook jar, the hooks, the schema or the boundaries are broken, or when any source file holds a forbidden import.
- architectural boundaries
java .claude/hooks/ArchHook.java doctor gate10s - build and tests3m37s