Skip to content
Back to skills

Arch Adopt

ASecurity

Installs or updates this architecture's `.claude/` inside a Java project — norms with their globs derived for the project's own packages, the development skills and agents, the hook and its schema. Use when the project was never generated by `/init-project` and should adopt the architecture, or when a project already has it and should pull a newer version. Explicit invocation only.

  • 3 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 6, 2026
ai-agentsgojavabashgitsecurity

Works with

  • claude code

Security analysis

A100/100

Scanned October 6, 2026

npx -y skills add nerviz-ai/nerviz --skill arch-adopt --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Arch Adopt?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Arch Adopt
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/nerviz-ai-arch-adopt/badge)](https://www.skillsdirectory.com/skills/nerviz-ai-arch-adopt)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: arch-adopt
description: >
  Installs or updates this architecture's `.claude/` inside a Java project — norms with
  their globs derived for the project's own packages, the development skills and agents,
  the hook and its schema. Use when the project was never generated by `/init-project`
  and should adopt the architecture, or when a project already has it and should pull a
  newer version. Explicit invocation only.
argument-hint: "[--ref <tag|sha|main>] [--source <local path>] [--blueprint <id>]"
disable-model-invocation: true
allowed-tools: Read, Write, Edit, Bash, Glob, AskUserQuestion, Skill
model: opus
---

# Arch Adopt

Pulls this architecture into the project the session is rooted at. One operation, two
starting states: the project has no `.claude/` of this shape yet (install), or it has one
and is behind (update). The difference is the provenance stamp, not a different command.

**Writes one line by itself.** Every file under `.claude/` is written by `ArchHook.java
export`, from the copy it just fetched. This skill decides *which* source, *which*
blueprint, and *whether it is safe to write* — three questions a deterministic mode cannot
answer — and then gets out of the way. The one exception is the project's bounded
context, a user's answer no mode can derive (step 8).

## Procedure

### 1 · Refuse to write over uncommitted work

```bash
git rev-parse --is-inside-work-tree && git status --porcelain
```

Not a git repository, or the output is not empty → **stop**. An update overwrites, and
`git diff` is how the person reviews it and `git checkout` is how they undo it; without a
clean tree neither works. Say that, and say what to run (`git init` and a first commit,
or commit/stash what is pending). Don't offer to commit for them.

### 2 · Resolve the source

Everything this procedure downloads goes in one directory, named once here and used by
every step below — an environment that hands out its own temporary directory needs to
change this line and no other:

```bash
SRC=${SRC:-/tmp/arch-src}
```

`base_url`, `git_url`, `auth_env` and `default_ref` come from the `source` block of
`.claude/schemas/extensions.json`. **Never write any of those four values into a command
from memory** — that is the whole reason they are data. Three places to look, in order,
and the third is not a fallback but the normal case on a first install:

1. **This project's copy**, when it has one with a `source` block.
2. **The plugin's copy** — but the published plugin ships only its `SKILL.md`, so this
   path usually finds nothing. Not a bug: the architecture is fetched, not vendored.
3. **The source's copy, fetched.** The plugin's `.plugin-source.json` carries `raw_url`
   and `ref`, which is enough:

   ```bash
   curl -sS --fail -L "<raw_url, {ref} and {path} replaced for .claude/schemas/extensions.json>" \
     -o "$SRC-extensions.json"
   ```

   A project generated before the `source` block existed has a `.claude/schemas/extensions.json`
   without one — `grep '"source"'` comes back empty. That is case 3, not a broken project.

With `--source <path>`, skip the rest of this step and the next: that path is `$SRC`,
which is how an unreleased change is tested.

```bash
git ls-remote --tags --refs --sort=-v:refname <git_url> | head -1
```

The newest tag is the default. `--ref <tag|sha|main>` overrides it; `main` is only ever
used when asked for explicitly, because it moves. No tags at all → say so and use `main`,
stating that the stamp will record a moving ref.

### 3 · Fetch

```bash
AUTH=()
[ -n "${GH_TOKEN:-}" ] && AUTH=(-H "Authorization: Bearer $GH_TOKEN")
curl -sS --fail --retry 3 --retry-all-errors --max-time 120 -L "${AUTH[@]}" \
  "<base_url, {ref} replaced>" -o "$SRC.tgz"
mkdir -p "$SRC" && tar xzf "$SRC.tgz" --strip-components=1 -C "$SRC"
```

`$GH_TOKEN` is the variable `source.auth_env` names — read the name from the file, and
never print the value. The header is only sent when the variable is set: a public source
needs no credential. `--fail` is what turns an HTTP error into an error; without it a 404
page lands in the tarball and `tar` fails with something that says nothing about access.

### 4 · Decide the blueprint

Three states, not two. Read them in this order.

**Update** — `.claude/.arch-provenance.json` exists: read `blueprint` from it and use it.
Don't re-ask; a project doesn't change architecture by updating. `--blueprint` overrides,
and then say plainly that this rewrites every rule's territory. When the id names a
blueprint the source does not have, the project's own copy at
`.claude/blueprints/<id>/<id>.yaml` is the one to use — that is why the export wrote it
there.

**Adopted before the stamp existed** — no `.arch-provenance.json`, but
`.claude/rules/00-index.md` is present. The project already has an architecture and is not
a fresh install: `00-index.md`'s own paragraph names the blueprint it was generated from.
Read it, show it, and ask for confirmation in one question — never re-run the detection
below and never re-ask from a blank slate. Every project generated before D54 is in this
state, and asking it to pick an architecture it already has is how it ends up with two.

**Install** — no stamp and no `00-index.md`. Read the project's package layout and compare
it with the blueprints in the fetched source:

```bash
find src/main/java -type d -mindepth 3 | sed 's|src/main/java/||' | tr '/' '.' | sort
ls "$SRC/.claude/blueprints"
```

Match those packages against each blueprint's `packages.map` **values**, and offer the
closest one first. Then use `AskUserQuestion` — the choice is the user's, not yours:

1. the detected blueprint, named, with the two or three packages that matched;
2. the other blueprints that have a `<id>.yaml`;
3. build a `custom.template.yaml` — **and say what it costs**: the architecture is then
   described by a file nobody has generated a project from, every rule's territory comes
   from it, and a wrong `packages.map` leaves norms silently unloaded. Offer it, don't
   steer to it.

**Near-match is its own case, and it is the dangerous one.** When the layout matches every
package of a blueprint except one or two, say so before anything else: name the packages
that differ, on both sides, and ask whether they are the same role under two names. A
blueprint that gained a key after this project was generated looks exactly like this —
`application.shared` versus an `application/service/` the executor once invented — and
accepting the blueprint as-is rewrites every norm to a package the code does not have.
Nothing breaks that day; the next `/new-feature` creates the second package. Options:
adopt the blueprint and rename the code, or build a variant of it (option 3) that keeps
the project's own names.

Option 3 chosen: copy `$SRC/.claude/blueprints/custom-template/custom.template.yaml`
to `$SRC/.claude/blueprints/<name>/<name>.yaml`, fill `packages.map` and
`architecture_paths` from the layout you just read, and use `<name>` as the id. The export
copies the blueprint it used into `.claude/blueprints/<id>/<id>.yaml` inside the project,
so the variant survives the temporary directory and the next update can resolve it.

### 5 · Write

**On an update, first note which migrations this project has already been shown** — the
export overwrites the file that records them. Read the `id` of every entry in
`migrations.entries` of the project's own `.claude/schemas/extensions.json`; no `migrations`
block there means none. Keep the list for step 9.

```bash
CLAUDE_PROJECT_DIR="$SRC" java "$SRC/.claude/hooks/ArchHook.java" export . \
  --blueprint <id> --ref <ref>
```

Add `--dry-run` first whenever the project already has files at those paths, and show the
user the list before the real run. Read the mode's own output: a line reporting a
surviving citation to `decisions/` or `blueprints/` means a dead path just landed in the
project, and it belongs in the report, not in silence.

### 6 · Verify, then hand the diff over

```bash
java .claude/hooks/ArchHook.java schema </dev/null
java .claude/hooks/ArchHook.java doctor
git status --short
```

`schema` keeps the `</dev/null`: it is one of the modes the runtime invokes as a hook, so
it reads a JSON payload from stdin and waits for one when nothing closes it. `export`,
`doctor` and `compose` do not read stdin at all.

`schema` must exit 0. `doctor`'s `Provenance` line should read "unchanged since" — it
compares the stamp's per-file digests, so anything it lists right after an export is
something the export could not write. Do **not** commit: the diff is the user's review.

### 7 · SonarQube, when the project has none

Grep the root build file for the scanner: `sonar-maven-plugin` in `pom.xml`,
`org.sonarqube` in `build.gradle`. Found → skip this step and say "already configured" in
the report. Not found → invoke `sonarqube-setup` through the `Skill` tool; it asks its own
question (an existing server, or a local container) and writes outside `.claude/` under
its own territory, not this skill's. This skill still writes nothing outside `.claude/`
but step 8's line.

On an **install** the skill did not exist when the session started — `.claude/skills/`
was created by step 5 — so the runtime has not listed it yet. Ask the user to run
`/reload-skills`, then invoke it; on an update it is picked up live. Only schema exit 0
reaches this step: a broken `.claude/` is reported first.

### 7.5 · Transport security, when the root `CLAUDE.md` has no `**Transport:` paragraph

```bash
grep -n "^\*\*Transport:" CLAUDE.md 2>/dev/null
```

Found → skip this step and say "already decided" in the report. Not found → invoke
`transport-security-setup` through the `Skill` tool; it asks its own questions (where TLS
terminates, the local profile or local edge), records a configuration written by hand instead of
rewriting it, and writes outside `.claude/` under its own territory, not this skill's. Same
`/reload-skills` caveat as step 7 on an install.

### 8 · Bounded context, when the root `CLAUDE.md` has none

The bounded context is the first segment of every topic name — a project fact, like the
base package. `/init-project` asks for it at creation; a project adopted here, or
generated before it did, never got the question, and the first messaging design stops on
the missing line four skills into a run (lessons-learned-016 § 2). This step is the one
place that asks for it outside creation.

```bash
grep -i "bounded context" CLAUDE.md 2>/dev/null
grep -rhoE "^ *(topic|topics?\.[a-z-]+): *[a-z0-9.-]+" src/main/resources/ 2>/dev/null
```

Found in `CLAUDE.md` → skip, and say "already declared" in the report. Not found → one
`AskUserQuestion`: the first segment of the existing topics when there are any (a
project that already publishes has already chosen), otherwise the build file's
`artifactId`; the other as the second option when they differ. Then write the
`**Bounded context:` paragraph of `$SRC/.claude/skills/project-bootstrap/templates/root.CLAUDE.md.example`
with `{{boundedContext}}` filled — that template owns the wording — right below the
first heading of the root `CLAUDE.md`, or as the whole file when there is none. Never a
prefix you chose: the answer is the user's.

### 9 · Migrations — shown, never run

A convention change leaves the code generated before it behind: the norms now say one
thing and the existing classes another, and the next `/new-feature` writes the new layout
next to the old one. Every such change is an entry in `migrations.entries` of the fetched
`$SRC/.claude/schemas/extensions.json` — a `note` saying what changed and a `prompt` that
does the move.

Install → skip, and say "not applicable — install" in the report: there is no earlier
convention to migrate from. On an update, the entries to show are the fetched ones whose
`id` is **not** in the list step 5 kept **and** whose `blueprints` names the active
blueprint. None → "none pending".

For each one, print its `note`, then its `prompt` verbatim in a fenced block, and say it
once, plainly: the prompt is for the user to paste into Claude Code in this project, now
or later, or never. **Never run it, never start the move, never ask whether to** — the
refactor is a decision about their code, made in a session they open for it, against the
diff they just reviewed. The entry is recorded as shown the moment the export wrote the
new `extensions.json`; it won't be printed again on the next update, so the report is
where it stays.

## Report

```
✅ <installed | updated> — blueprint <id>, ref <ref> (<short sha>)

Source ....... <base_url host>, fetched at <time>
Files ........ <n> written · <n> unchanged · <n> overwritten
Warnings ..... <none | the surviving-citation lines, verbatim>
Schema ....... <exit 0 | the failure>
Provenance ... <the doctor line>
SonarQube .... <already configured | configured by sonarqube-setup — <its first line> | pending: run /reload-skills, then /sonarqube-setup>
Transport .... <already decided | decided by transport-security-setup — <its first line> | pending: run /reload-skills, then /transport-security-setup>
Bounded ctx .. <already declared | written: <name>>
Migrations ... <none pending | not applicable — install | <n> below, to paste when you choose>

Review: git diff · Undo: git checkout -- .claude
Next: /arch-doctor for the full diagnosis
```

Then each pending migration from step 9: its `note`, and its `prompt` in a fenced block.

On an update that overwrote files the user had edited, list them. `doctor`'s Provenance
line names them **before** the export too — run it first when the project already has a
stamp, and put that list in front of the user before writing anything.

## Why this is a manually-invoked skill

Form 2 (`disable-model-invocation: true`): it rewrites `.claude/` from a remote source,
so it may only run when a person asks for it by name — axis 2 of the interview, "a human
command inside the target project". The closest rejected form was Form 1: an
auto-invocable skill with this description would fire on any sentence about updating the
architecture, and the first thing it does is overwrite the norms the session is reading.
The transformation itself is deliberately **not** here — it is `ArchHook.java export`,
Form 7c, because it runs unattended on other people's machines and the same input has to
produce the same tree.

Pinned to `opus`: it merges into a project it did not write, and may write the only copy of that project's blueprint (`@.claude/decisions/0081-skill-model-required-per-class.md`).

## Contract

**Class:** build — the territory is `skill_classes.build`'s override for this skill in
`@.claude/schemas/extensions.json`: the target project's `.claude/` tree, its
`.gitignore`, and its root `CLAUDE.md` for step 8's bounded-context line, nothing else.
`ArchHook.java guard` enforces it.

**Unfiltered Bash:** adoption fetches the source (`git ls-remote`, `curl`, `tar`), inspects the worktree (`git status`, `git rev-parse`, `find`), and runs `java … export` — spread over two tools that fetch from the network and a command list that grows with the source's layout. Writes stay under `guard`/`guard bash`, and a force push is blocked by `guard bash` (`guard.force_push`).

**Reads** `.claude/schemas/extensions.json` (the `source` block — never a URL from
memory — and, before the export, the `migrations` ids already shown), `.claude/.arch-provenance.json` when it exists, `src/main/java/**` to detect the
layout, and the fetched source's `.claude/blueprints/*/*.yaml`.

**Writes** one thing directly in the project: the bounded-context line of the root
`CLAUDE.md` (step 8), only when absent, worded by the fetched `root.CLAUDE.md.example`.
Everything else `ArchHook.java export` writes, and the list of what it writes is the
`export` block of the fetched `extensions.json`. May write `$SRC/**` — the fetched tree,
including a `custom.template.yaml` built in step 4.

**Refuses** a dirty worktree, a directory that is not a git repository, and a fetch it
cannot authenticate. Never prints the value of `source.auth_env`, and never commits.

**Leaves no audit report** — `audited: false` in its own `skill_classes.build.overrides`
entry, out of reach of the project's `.claude/audit-usage/audited.json`: its output is the
diff above, and a report of its own would land in it. That file is the project's, and
`export` never names it, so an update leaves it as it was.

**Travels into the project** — unlike `project-bootstrap` and `init-project`, which only
serve before the project exists. This one is how a project updates itself once the plugin
that delivered it is gone, so it is in `export.skills.include`.

**Chains** `sonarqube-setup` in step 7 when the build file has no scanner, and
`transport-security-setup` in step 7.5 when the root `CLAUDE.md` records no transport decision —
the two pieces of this procedure that write outside `.claude/`, each under its own territory,
never this one's.

**Hands off to** `/arch-doctor` for the diagnosis, and to the user for the diff.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.