CLAUDE.md · git:20260927.3b4d9a1 · 2026-09-27 · sha256 703197f10f270f29

CLAUDE.md git:20260927.3b4d9a1A

Immutable. This exact content never changes and is served at /api/v1/blob/703197f10f270f29.

# claude-spring-architect

Meta-repository: generates Spring Boot projects already prepared for AI-assisted
development. **It is not a Java application** — it has no `pom.xml`, does not compile,
has no Maven tests. What gets edited here are instruction files, data, and a hook.

Purpose, features and status: `@README.md`.

## Dependencies

`java` (JDK 21+) · `git` · `curl`. Nothing else. Zero Python, zero shell, zero `mvn` on
PATH — the wrapper comes in the Initializr's `starter.tgz`.

## Commands

| Action | Command |
|---|---|
| Diagnose the setup on this machine | `/arch-doctor` |
| Create a project from a blueprint | `/init-project` |
| Design a new extension of this `.claude/` | `/claude-code-architect-designer` |
| Validate frontmatter of skills and agents | `claude plugin validate .claude/skills` |
| Run the hook by hand | `java .claude/hooks/ArchHook.java doctor` |
| Check every compose service is up, and no foreign container holds its ports | `java .claude/hooks/ArchHook.java compose` |
| Render the execution trail of a run by hand | `java .claude/hooks/ArchHook.java audit flush` |
| Write a target project's `.claude/` from this one, transformed for a blueprint | `java .claude/hooks/ArchHook.java export <dest> --blueprint <id> [--dry-run]` |
| Validate frontmatter of all extension files, `.mcp.json`, every hook registration in `settings.json` and in `project-bootstrap`'s template, every `` !`…` `` injection's paths, and the `export` manifest against what is on disk | `java .claude/hooks/ArchHook.java schema` |
| List and inspect this repo's MCP servers | `claude mcp list` · `/mcp` |

## Architecture of the AI files

Clean Architecture applied to `.claude/` itself. Dependencies point in one direction:

```
hooks/ + settings.json      infra/enforcement — deterministic
        ↓ verifies
skills/                     procedure + exemplars
        ↓ invokes                    ↑ delegates via context: fork
agents/                     isolated execution
        ↓ cites, never copies
rules/ + blueprints/ + .mcp.json   norms and data   ← LEAF

decisions/                  history — nobody reads it at runtime, outside the graph
```

`claude-code-architect-designer` writes upward into `hooks/ + settings.json`, after
approval — a design-time edge, like `project-bootstrap` writing `src/`. At runtime the
direction above is unchanged.

## Invariants (non-negotiable)

1. **`rules/` is a leaf.** A norm never mentions a skill, an agent, or a command. If it
   needs to, it's a procedure and belongs in a skill.
2. **Each norm has a single owning file.** Others cite it by path
   (`@.claude/rules/naming.md`). A norm written in two places has diverged — that's a bug.
3. **Code boilerplate lives in `templates/` inside the skill that emits it**, with the
   `.example` suffix. Never inside a norm, never pasted into the body of a `SKILL.md`.
4. **No new `commands/` are created.** Slash commands were merged into skills: write
   `skills/<name>/SKILL.md` and control invocation with `disable-model-invocation`.
5. **An agent exists only for one of three reasons:** preserving context, restricting
   tools, or changing model. If none applies, it's a skill.
6. **If a rule must always hold, it's a hook or `permissions.deny`** — not prose in
   markdown. Owner: `claude-code-architect-designer`, forms 7 and 8, after approval and
   always with a record in `decisions/`. The mirror holds too — a hook against a failure
   nobody observed is a process per event confirming what was already true.
7. **Architectures are data.** Adding a blueprint must never require editing a skill, an
   agent, or a command.
8. **Java and Spring Boot versions are never written from memory.** They are resolved at
   runtime via Spring Initializr; without network access, ask.
9. **The generated project is self-contained.** Whoever clones it does not have
   `claude-spring-architect`. Everything cited from inside the project must exist inside the
   project: norms, development skills, agents, `ArchHook.java` and
   `schemas/extensions.json`. `ArchHook.java export` writes all of it (step 6.6 of
   `project-bootstrap`), and **what travels is data, in the `export` block of
   `@.claude/schemas/extensions.json`** — creation skills excluded on purpose, and the
   blueprint **catalog** too, with one exception the manifest names: the **active**
   blueprint travels (`export.blueprint_copy`), because the stamp records its id, an
   update has to resolve it, and a blueprint written during adoption exists nowhere
   else. A new norm, skill or agent is
   complete once that block lists it: every skill and agent on disk in `include` or
   `exclude`, a `derived_paths` entry for a rule with a package territory, and
   `ArchHook.java schema` failing by name on either gap.
   **An MCP server has the same obligation, per server, not per file:** it is declared
   for this meta-repo (`.mcp.json`), for the generated project
   (`project-bootstrap/templates/mcp.json.example`, copied by `export.optional_copy`),
   or both — and the file it's written into **is** that declaration. A server useful to both is
   written in both files on purpose; that is not a duplication bug, see invariant 2.
   **A hook, the same:** the registration is per file (`.claude/settings.json`,
   `project-bootstrap/templates/settings.json.example`); a mode inside `ArchHook.java`
   needs nothing, the `export` mode copies that file whole.
   **A skill has a second obligation on top of `export`:** a class in `skill_classes`,
   which is what gives it a body shape and a write territory. A skill in no class has no
   territory — `schema` fails by name on it, in this repo and inside a generated project.
   **An agent has the same second obligation**, in `agent_classes`: its class fixes the
   sections its body carries, the frontmatter fields it must declare (`model` and `tools`
   always — two of the three reasons it is allowed to exist), whether it may write at all
   (`executor`), and the paths it may write. Territory there is keyed on `agent_type`, so it
   holds no matter which skill phase the caller left open.
10. **Recognized frontmatter fields, `.mcp.json`'s server fields, and `settings.json`'s
    hook events and entry fields are data with a single owner.** The list lives in
    `.claude/schemas/extensions.json`, `ArchHook.java schema` is what reads it, and any
    other file that displays it is derived and must match it exactly. A corollary of 2
    and 7, written separately because its failure mode is silent: the runtime ignores an
    unknown field without any error — an unknown *event* too, and a `matcher` on an event
    that never reads one — and `claude plugin validate` lets it all through, looking at
    neither `.mcp.json` nor `settings.json`. Binds the Java too: a mode reads its lists
    from `extensions.json`, never from a constant in the source.
11. **No literal secret in a versioned file.** `.mcp.json` (this repo's or the copy
    inside a generated project) carries only `${VAR}` / `${VAR:-default}` expansion,
    `oauth`, or `headersHelper` — never a token, key, or password spelled out. Verified
    by `ArchHook.java schema`'s secret scan over `headers`/`env`. A leaked token in git
    history is irreversible; that is why this is a hook and not a review checklist.

## Routing — when X, read Y

| If the task involves | Go to |
|---|---|
| Creating a project from scratch | skill `project-bootstrap` |
| Installing this `.claude/` into a project that was never generated here, or pulling a newer version into one that was | skill `arch-adopt` — manual only, runs **inside the target project**, refuses a dirty worktree, and writes through `ArchHook.java export`. It travels into the generated project, unlike the other creation skills: that is how a project updates itself once the plugin that delivered it is gone |
| Creating a skill, agent, norm, or `CLAUDE.md` section — and deciding which of the eight | skill `claude-code-architect-designer` |
| Creating a hook, a new `ArchHook.java` mode, or a `permissions.allow`/`deny` line — and deciding whether the answer is a guarantee at all | skill `claude-code-architect-designer`, forms 7 and 8. Writes it after approval, always with a record in `decisions/`, and warns that `settings.json` is read only at startup |
| Designing a use case before implementing it; knowing whether a request is one or several | skill `use-case-design` |
| Aggregate, value object, invariant, ports of an already-designed use case | skill `domain-modeling` |
| Table, JPA mapping, migration, index, slow query, datasource properties | skill `persistence-architect` |
| REST adapter, controller, DTO, status, OpenAPI | skill `rest-api-architect` |
| Tests, coverage, installing ArchUnit | skill `test-architect` |
| Orchestrating a full feature (use case → domain → REST → persistence → tests) | skill `new-feature` — manual only: the user types `/new-feature <description>`, the model can't invoke it. One use case per run |
| Creating a git repo, committing, or pushing the project just generated or just implemented | skill `git-publish` — chained automatically after `/init-project` and after `java-spring-boot-developer` succeeds; behind two confirmations |
| Docker, docker-compose, adding a service (DB, broker) to a project, Testcontainers image consistency at the compose level, choosing an observability backend (Jaeger or Grafana+Tempo+Prometheus) behind the OTLP collector | skill `docker-architect` |
| A versioned file citing a `docs/use-cases/UC-NNN-slug/` folder that was deleted or renamed | `ArchHook.java doctor` — the `UC references` line, so `/arch-doctor` reports it. Reads `doctor.uc_references` from `@.claude/schemas/extensions.json`; silent in this meta-repo, which has no `docs/use-cases/` |
| A container that "started" but isn't answering, a port already allocated, OTLP traffic reaching the wrong collector, a compose `image:` tag that disagrees with the one `src/test` pins in `DockerImageName.parse` | `ArchHook.java compose` — hook, not skill. Also folded into `doctor`, so `/arch-doctor` reports it. The tag comparison needs no Docker: it reads both files |
| Kafka producer/consumer, publishing or consuming a domain event over a broker, topic/partition/DLQ | skill `messaging-architect` |
| Design pattern, growing `if`/`switch` chain | skill `java-patterns` |
| Connecting to an external system (Jira, database, GitHub, Figma), a server exposing `mcp__*` tools, `.mcp.json` | skill `claude-code-architect-designer` |
| Auditing what any skill or agent run in a **generated project** cost and what it chained — invoked by `/command` or by the model | `ArchHook.java audit` — hook, not skill; wired only into `project-bootstrap/templates/settings.json.example`, installed by step 6.6 |
| Reading that trail back — spend per skill and agent across runs, which report to open | skill `audit-usage` — aggregation by `ArchHook.java audit summary`, the skill renders; runs in the **generated project**, where the trail exists; here it reports the trail is off. An observer: the hook leaves it no report, via `audit.exclude_skills` in `@.claude/schemas/extensions.json` |
| Which norm covers what | `@.claude/rules/00-index.md` |
| Which class a skill is, what it may write, and what sections its body must carry | `skill_classes` in `@.claude/schemas/extensions.json` — `ArchHook.java schema` validates the body, `guard` enforces the territory |
| The same for an agent — plus which frontmatter fields it owes and whether it may write at all | `agent_classes` in `@.claude/schemas/extensions.json`. Three classes: `driver` (interviews and delegates, writes a tree that doesn't exist yet), `executor` (implements an approved spec), `installer` (one-shot setup, narrow fixed paths). `executor: true` is the single owner of who may write — the old `guard.executor_agents` list is gone |
| Which frontmatter fields are valid in each file type | `@.claude/skills/claude-code-architect-designer/references/frontmatter-fields.md` |
| Why a skill, norm, or agent exists in the form it's in | `@.claude/decisions/README.md` |
| Contract every blueprint fulfills | `@.claude/blueprints/_schema.md` |
| Frontmatter fields the runtime recognizes | `@claude-help.md` |
| How each piece of Claude Code works | `@claude-help.md` |

## Known pitfalls

- **A skill cannot have the name of a native slash command.** The folder name becomes
  the command, and `/doctor`, `/init`, `/context`, `/memory` already exist in the
  runtime. That's why this repo's diagnostic skill is called `arch-doctor`. Shadowing a
  native command doesn't produce an error — it runs the wrong command.
- **`.claude/settings.json` is only read at session startup.** Editing hooks mid-session
  has no effect — `claude` must be restarted.
- **A hook fails silently in four ways, none an error.** Unknown event name — never
  fires. `matcher` on an event that doesn't read one — filters nothing. Pipe or `&&`
  inside `"command"` — part of the filename (exec form: `command` is the binary, `args`
  the arguments). A mode that throws — exits 0 through `main`'s catch, looks like it
  passed. `schema` catches the first three, from `settings` in
  `@.claude/schemas/extensions.json`; the fourth only by running the mode by hand.
- **A skill writes only its class's territory, and `guard` blocks the rest with exit 2.**
  Deny by default: the message names the class and its `write_allow`, and the fix is the
  data (`skill_classes` in `@.claude/schemas/extensions.json`), never a retry. A design run
  is docs-only and cannot even *call* a `build`-class skill — `docker-architect` included:
  the missing compose service is recorded in the partial and materialized afterwards by
  `/docker-architect`. Territory is unrestricted while no skill phase is open, which is why
  editing a file by hand is never blocked.
- **A subagent's write is judged by `agent_classes`, not by the caller's phase.** Every write
  a subagent makes carries its `agent_type`, and `guard` checks that path against the agent's
  own `write_allow` — so an open design phase neither widens nor narrows it, and nothing
  closes the phase on an `Agent` call any more (it used to, which silently unrestricted the
  *caller* for the rest of the turn). An agent no class lists falls back to the caller's
  phase, because nothing else describes what it may write.
- **The outbox belongs to `persistence-architect`, all of it** — table, columns, claim
  query, and the `app.outbox.*` values that pace the claim. `messaging-architect` declares
  that the case needs one and which delivery guarantee the relay must honour, and never a
  column name; it keeps the relay's broker side. A § 6 row naming columns is a divergence,
  and a column decision that would drop a declared guarantee stops the pipeline instead of
  being settled by precedence.
- **The bounded context is a project fact, not a per-use-case answer.** It is the first
  segment of every topic name, asked with the coordinates in `/init-project` and written
  into the generated project's root `CLAUDE.md`. Whoever designs messaging reads it; a
  prefix chosen inside one use case gives one system two namespaces.
- **`$ARGUMENTS` in the body of a skill is interpolated at every occurrence**, not only
  under `## Target`. A sentence that talks *about* the argument reaches the model with the
  real value inside it: `/new-feature`'s "invoke `test-architect` with empty `$ARGUMENTS`
  (setup mode)" arrived as "with empty `UC-003-initiate-kyc-verification` (setup mode)" —
  an order to use setup mode, naming the argument that means design mode. Write "the
  argument" or "the target above"; `ArchHook.java schema` rejects the literal, reading
  `arguments` from `@.claude/schemas/extensions.json`.
- **`grep -A2 "^services:" docker-compose.yml` is not the list of services.** It reads two
  lines and stops, so it drops the services declared further down and reports the children
  of `volumes:` as services. Every piece that needs that list — `docker-architect`'s
  injection and step 3, `messaging-architect` step 9, `persistence-architect` step 9 — uses
  the `awk` one-liner bounded to the `services:` block. A wrong portrait is worse than
  none: it invites the skill to recreate a service that already exists.
- **The runtime silently ignores unknown frontmatter.** An invented field is decoration,
  not behavior. List of native fields in `@claude-help.md`.
- **`AskUserQuestion` rejects a question with fewer than 2 options**, and rejects the
  **whole batch** with it: `InputValidationError ... "too_small" ... path:
  ["questions",1,"options"]`. A question with one option isn't a question — decide it,
  and record the decision where the answer would have gone. The batch also has an upper
  bound of 4 questions per call. The generated project carries the same pitfall in its own
  `CLAUDE.md`, from `project-bootstrap/templates/root.CLAUDE.md.example`.
- **Everything the model must obey lives in the body of the file**, never in
  frontmatter. `metadata.*` was removed from skills and agents: ownership, `reads`,
  `handoff`, and contracts live in the `## Contrato` section of the body. Do not put
  `metadata:` back into a `SKILL.md` — it costs tokens on every invocation and enforces
  nothing.
- **The `CLAUDE.md` at the root of a generated project is not this file.** It is
  produced from
  `.claude/skills/project-bootstrap/templates/root.CLAUDE.md.example`.
- **`claude plugin validate` does not validate fields.** It accepts `metadata:`, accepts
  camelCase in a skill, and accepts an invented field, always with `✔ Validation
  passed`. It does not look at `.claude/agents/`, `.claude/rules/`, or
  `.claude/settings.json`. It catches malformed YAML, nothing else. What validates
  fields is `java .claude/hooks/ArchHook.java schema`.
- **`.claude/decisions/` is neither a norm nor living documentation.** It records what
  was decided on the date, not what holds true today. It has no `paths`, does not enter
  `00-index.md`, and does not go into the generated project. It is superseded by a new
  record; the old one is not rewritten.
- **This repository does not run `./mvnw`.** `ArchHook` exits 0 when it doesn't find
  the wrapper; here that's expected, not a failure.
- **`allowed-tools` with `Bash(command:*)` checks each segment of the pipe separately.**
  An injection `` !`a | b | c` `` in the body needs a rule for `a`, `b`, and `c`; miss
  one and the whole command is blocked before it runs. Write injections as a single
  command (`ls .claude/skills`, not `find … | sed | sort`). This only affects skills
  that restrict Bash: `allowed-tools: Bash` without a filter lets the whole pipeline
  through.
- **A frontmatter `` !`…` `` injection runs in the session's persistent shell, at whatever
  cwd it currently holds.** `cd` into a skill directory in one `Bash` call contaminates
  every injection of every skill invoked afterward, and a relative `test -f` then reports
  a file as absent while it exists. Read a template with `Read` at an absolute path, never
  `cd` + `cat`. Every injection in this repo resolves paths from
  `"${CLAUDE_PROJECT_DIR:-.}"`, and `ArchHook.java schema` blocks one that doesn't — a
  genuinely cwd-independent injection needs a regex in `injections.exempt_patterns` of
  `@.claude/schemas/extensions.json`.
- **The `audit` mode is off in this repository, on purpose.** It switches itself on by
  the presence of `.claude/audit-usage/`, and this meta-repo doesn't create the
  directory: the trail is a feature of the *generated* project, wired in
  `project-bootstrap/templates/settings.json.example`, not in this repo's
  `settings.json`. Copying those hook entries here would start auditing the design of
  the tool instead of its use. What it records and why it isn't a skill:
  `@.claude/decisions/0035-auditoria-execucao-hook.md`. The consequence for
  `/audit-usage` is that running it **here** correctly reports the trail is off — its
  territory is the generated project, and it is exercised there, not in this repo.
- **The trail doesn't record its observers.** `/audit-usage` and `/arch-doctor` carry
  `disable-model-invocation: true` but change nothing, so they're listed in
  `audit.exclude_skills` in `@.claude/schemas/extensions.json` and leave no report of
  their own. Invoking one still *closes* the run in progress — which it already did, and
  which is the only way, inside a live session, to get a report that isn't stamped
  `⏳ em andamento`. Why: `@.claude/decisions/0036-skill-audit-usage.md`.
- **Only the hook-protocol modes of `ArchHook.java` read stdin** — `check`, `format`,
  `tests`, `schema`, `audit`, `guard`. Invoking one of those by hand without `</dev/null`
  blocks until something closes stdin, with no output: a command that looks hung, not
  failed. `export`, `doctor` and `compose` are invoked by people and read nothing.
- **`.mcp.json` is only read at session startup**, same as `settings.json`. Adding or
  editing a server mid-session has no effect until `claude` is restarted.
- **A project-scoped server in `.mcp.json` needs one-time human approval** the first
  time it loads (`claude mcp list` shows pending ones). That prompt is the trust
  boundary a cloned repository can't skip — never work around it with
  `enableAllProjectMcpServers`.
- **Server precedence is `local > project > user`, silently.** A personal server with
  the same name as the team's `.mcp.json` one shadows it — no warning either way.
- **An unset `${VAR}` in `.mcp.json` doesn't fail generation.** The server loads with
  the literal `${VAR}` text and fails to connect at runtime instead. `claude mcp list`
  surfaces the missing-variable warning; frontmatter schema validation does not.