arch-adopt · git:20260926.5fdae93 · 2026-09-26 · sha256 c868f95f5450f745
arch-adopt git:20260926.5fdae93A
Immutable. This exact content never changes and is served at /api/v1/blob/c868f95f5450f745.
---
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
---
# 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 nothing by itself.** Every file 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.
## 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
Read `base_url`, `git_url`, `auth_env` and `default_ref` from the `source` block of
`.claude/schemas/extensions.json` — this project's copy if it has one, otherwise from the
plugin's. Never write any of those four values into a command from memory.
With `--source <path>`, skip this step and the next: that path is the source tree, 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 /tmp/arch-src.tgz
mkdir -p /tmp/arch-src && tar xzf /tmp/arch-src.tgz --strip-components=1 -C /tmp/arch-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
**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.
**Install** — no stamp. 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 /tmp/arch-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.
Option 3 chosen: copy `/tmp/arch-src/.claude/blueprints/custom-template/custom.template.yaml`
to `/tmp/arch-src/.claude/blueprints/<name>/<name>.yaml`, fill `packages.map` and
`architecture_paths` from the layout you just read, and use `<name>` as the id. It lives
in the fetched tree, not in the project: what the project keeps is the result.
### 5 · Write
```bash
CLAUDE_PROJECT_DIR=/tmp/arch-src \
java /tmp/arch-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 </dev/null
git status --short
```
`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.
## 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>
Review: git diff · Undo: git checkout -- .claude
Next: /arch-doctor for the full diagnosis
```
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.
## Contract
**Reads** `.claude/schemas/extensions.json` (the `source` block — never a URL from
memory), `.claude/.arch-provenance.json` when it exists, `src/main/java/**` to detect the
layout, and the fetched source's `.claude/blueprints/*/*.yaml`.
**Writes** nothing directly in the project: `ArchHook.java export` does, and the list of
what it writes is the `export` block of the fetched `extensions.json`. May write
`/tmp/arch-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.
**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`.
**Hands off to** `/arch-doctor` for the diagnosis, and to the user for the diff.