活架构
MotleyAI2026-10-27查看 483复制 1290
生活架构层的参考(LikeC4 结构模型 + arc42 原理 + 模型真相导入执行 + 交叉检查)——其语法、检查以及如何与 /la:pr + OpenSpec 流程一起维护它。构建第一个模型是 la:arch-init;划分边界是 la:arch-cleanup。
📘如何使用这个技能
- 1复制技能内容·点击上方「Copy SKILL.md」复制全部内容,或 Download 下载到本地
- 2打开你的 AI 客户端·支持 Claude Code、Trae、Cursor、ChatGPT、DeepSeek、Kimi 等任意 AI 助手
- 3粘贴为系统提示/知识文件·Claude Code:放入 .claude/skills/ 目录;普通对话:作为首条消息发送并说「请严格按这份技能文档执行」
- 4直接提出你的需求·AI 会按技能定义的工作流、格式和约束来处理,无需再解释背景
💡 提示:技能生效后,每次对话只需一句需求,AI 会按 SKILL.md 的流程执行。
SKILL.md13033 chars
---
name: living-architecture
description: Reference for the living-architecture layer (LikeC4 structure model + arc42 principles + model-truth import enforcement + cross-check) — its syntax, the checks, and how to maintain it alongside the /la:pr + OpenSpec flow. Building the first model is la:arch-init; carving a boundary is la:arch-cleanup.
---
**Preflight:** run `la-doctor --plugin <this skill's base directory>` once per session before using any `la-*` or `dr-*` command; if it fails, stop and show the user its output.
# Living architecture
Four layers, one node id everywhere. Behaviour lives in OpenSpec; everything
cross-cutting lives here:
| Layer | Question | Artifact | Property |
|---|---|---|---|
| Behaviour | what does it do, now? | `openspec/specs/<id>/spec.md` | living, merged by OpenSpec |
| Structure | how is it arranged, now? | LikeC4 model + views | living; ONE model, diagrams are projected views |
| Rationale | why is it this way, and what must all code here obey? | arc42 file per node | living, edited in place |
| Enforcement | is the code actually like this? | `arch_check` model-truth (the one import law) + LikeC4 model validation, run in the flow gates | executable, blocking |
**History is NOT a layer here.** The openspec archive (per-change design +
deltas, dated, immutable) plus git history IS the decision trail. Never create
ADR files or append-only decision logs (they are merge hell). "Why it is like
this now" belongs in the node's arc42, present tense; "how we got here" is the
archive.
## Alignment rules
- **arc42 = LikeC4 = module structure align perfectly.** A *precise* node's id
is its LikeC4 element id, its arc42 filename, and its package name — the same
string.
- **OpenSpec is looser.** A spec either belongs to one node (listed in that
node's metadata `specs` — the node is then a behaviour *leaf*) or is
**cross-cutting** (listed once in `index.yaml` with the fully qualified nodes it touches).
Never duplicate a spec across nodes; `arch_check` enforces exactly-once.
- **Model depth is a dial, not an obligation.** Model precisely where you are
actively enforcing boundaries; sweep the rest into a few *virtual bucket
nodes* (tagged `#virtual`, claiming several packages, no alignment claim,
governed only at bucket granularity — no nested elements, no child-level arrows).
Deepen a bucket into precise nodes the first time real
work touches it. Never model deeper than you're willing to keep true.
## File layout — the tree is virtual
Each tool keeps its native layout; shared node ids join them. The LikeC4 model
maps nodes to code; `index.yaml` holds only repo-wide settings.
```
repo/
openspec/specs/<spec-id>/spec.md
architecture/
model.c4 # the ONE LikeC4 model: one specification and one model block
views.c4 # one views block; each view scoped to one language root
index.yaml # repo-wide settings (below)
system.arc42.md # root narrative + global principles
<node>.arc42.md # only where a node earns prose
living-architecture.yaml # repo config: format, and architecture: true once the model exists
```
These are the only LikeC4 files allowed anywhere under `architecture/` (`.c4` or
`.likec4`). Nothing else may sit at their top level. Any other layout makes
`la-arch-check` exit 2 and `la-arch-diagrams` exit 1, with one message naming
every violation. The repo's format is `format` in `living-architecture.yaml`:
the `la` version that introduced it. Every command refuses any other format,
naming the `la` to upgrade to for a newer one and `la-arch-migrate` for an older
or missing one; `la-arch-migrate` converts the repo (verified, all-or-nothing)
and stamps the current format.
The checker is `la-arch-check` (the one import law + cross-check, run by the
gates); it needs no per-repo code. `la-arch-diagrams` regenerates the embedded
view diagrams.
The model has one top-level **root element per language** `index.yaml` declares,
whose id is the language (`python`, `typescript`); it carries no metadata. A node
is a direct child of a root; it is virtual iff its element kind is declared
`#virtual`. Relations are written inside a root with names relative to it
(`sql -> core` inside `python { }` is `python.sql -> python.core`), so a relation
across roots cannot be written. Element ids are fully qualified in findings and
in `touches:`. A node declares its mapping in one `metadata { }` block:
```
specification {
element node
element bucket {
#virtual
}
tag legacy
tag virtual
}
model {
python = system 'My package' { // the python root (any element kind)
sql = node 'SQL' { // precise node python.sql
metadata {
package 'mypkg.sql' // required
claims ['mypkg.sqlutil'] // optional: further units it owns
arc42 'architecture/sql.arc42.md' // optional
specs ['sql-dialects'] // specs owned by THIS node alone
}
render = node 'Render' // maps to mypkg.sql.render
dialects = node 'Dialects' // maps to mypkg.sql.dialects
}
surfaces = bucket 'Surfaces' { // virtual bucket
metadata {
packages ['mypkg.api', 'mypkg.mcp', 'mypkg.cli']
}
}
surfaces -> sql
}
}
```
TypeScript units are extensionless paths relative to the section's
`source_root` (`package 'src/daemon'`, nested `pty` maps to `src/daemon/pty`); a
unit is a directory with a visible source file or exactly one module file, and a
unit naming both is ambiguous. Test files, declaration files
(`la-config get lang.typescript.declaration_globs`) and installed dependencies are
invisible; type-only imports never count.
Keys and types come from the shared node schema; values are `key 'value'` or
`key ['a', 'b']` — single quotes, no escapes, arrays may span lines, no trailing
comma. Nested elements, at any depth, carry no metadata: `<node>.a.b` maps to
`<package>.a.b`, and the import law applies wherever the model nests. Malformed
metadata is a setup error (exit 2) naming the element.
`index.yaml`:
```yaml
python: # one section per language; at least one
root_package: mypkg # required: the unit of the python root
source_root: src # optional: directory containing root_package
typescript:
root_package: src
source_root: web # optional
tsconfig: web/tsconfig.app.json # optional: default the nearest tsconfig
legacy_arrows: {baseline: 8} # exact count of #legacy arrows in the model (ratchet)
cross_cutting_specs:
queries: {touches: [python.core, python.engine, python.sql]}
x-anything: {} # repo-owned keys start with x-; the tools ignore them
```
`views.c4` scopes each view to a root, `view <id> of <root> { ... }`, with
include names relative to the root; the root itself is never drawn.
## arc42 node file — required shape
arc42-lite, per node, always present tense, updated in place:
1. **Purpose & context** — 3–10 lines.
2. **Building blocks** — pointer to the node's LikeC4 view. Never a duplicate
drawing, never a prose restatement of the model.
3. **Principles** — numbered rules that ALL code in this node, old and new,
MUST obey. Each tagged `[enforced: <check|test id>]` or
`[review]`. Prefer enforced; every `[review]` principle is a standing
candidate for a new fitness function. Keep them normative and minimal: when
work strains a principle, first make the code comply; edit one only when it
genuinely forbids something needed, in the smallest general wording — never
descriptive text or implementation detail (function names, mechanisms).
4. **Rationale** — why the current shape is right. Where it derives from a
specific change, link the archived openspec change id — do not restate
history.
## Enforcement wiring — the one import law
There is no separate import-linter config: the entire import law lives in the
LikeC4 model and is checked by `la-arch-check` (**model-truth**). The
model's relations must exactly match the AST-measured runtime import edges, at
every granularity the model declares:
- a module attributes to its finest mapped element, else its node;
- every edge is licensed by its most-specific covering arrow — a declared
arrow is the only thing that permits an import, and silence is a ban;
- a measured edge with no covering arrow, a declared arrow with no measured
edge, and an undeclared edge between two nested elements are all findings.
Type-only imports are excluded. Grandfathered crossings are dashed
`#legacy` arrows in the model itself (one arrow per real edge, no wildcards);
"layer direction" is just *no upward arrow declared*, and a child boundary is
just *no arrow declared between those nested elements* — so one law subsumes what used
to be separate `layers` / `forbidden` / boundary contracts.
Python edges come from `ast`, TypeScript edges from the TypeScript compiler
under the repo's tsconfig. Either twin checks a mixed repo: each language's facts
come from its own twin (PyPI or npm, found on PATH or run through `uvx`/`npx`), and
model-truth runs once over all of them.
**The enforcement bundle** = `la-arch-check` + LikeC4 model validation
(`npx likec4 validate`; if the installed CLI version lacks it, use the lightest
command that parses the model, e.g. `npx likec4 build`) + `la-typecheck` against
its committed baseline. It runs, blocking, in:
- the **pr-review** gate (that stage runs it when `architecture: true`),
- the **arch-cleanup** move gate,
- CI — `arch_check` is cheap and deterministic, so wire it there once the setup
has settled (not at init), pinned to a release, in the form of the repo's
ecosystem: `uvx --no-build --from living-architecture==<version> la-arch-check`
(after `astral-sh/setup-uv`, pinned by commit SHA) or
`npx -y -p living-architecture@<version> la-arch-check` (after
`actions/setup-node`, pinned by commit SHA).
**Ratchet rules (hard):**
- `#legacy` arrows may only ever be REMOVED, never added. Wanting to add one
means the architecture is changing — change the model and arc42 deliberately
instead, with the user's explicit OK.
- `legacy_arrows.baseline` in `index.yaml` is the exact `#legacy` count;
`arch_check` fails if the model's count differs, and the baseline is lowered
as arrows are retired. Progress is monotonic and machine-checked.
**`la-arch-check` verifies, from the model and `index.yaml`:**
- `architecture/` holds exactly `model.c4` and `views.c4`, each with only its blocks (else exit 2);
- every node's metadata matches the node schema (else exit 2);
- every precise node's `package` and `claims` (and every bucket's `packages`)
exist on disk; every nested element's unit exists under its node's package
and does not collide with another declared unit; a bucket contains no
elements; every `arc42` path exists;
- every top-level unit of each language's `root_package` is claimed by exactly
one node — no orphan packages;
- every spec group (a directory under `openspec/specs/` or under a non-archived
change's `specs/`) is mapped exactly once: in exactly one node's `specs` or in
`cross_cutting_specs:`; every node named in a `touches:` list exists;
- model-truth holds at every declared granularity, the `#legacy` count equals
`legacy_arrows.baseline`, and each mapped doc's embedded view diagram is
byte-fresh;
- every principle item in an arc42 file carries a status tag —
`[enforced: arch_check:<check-id>]`, `[enforced: test:<path>]`, `[review]`,
or `[target: <issue key>]` (a future-state clause; keys must match the
repo's `issue_key_pattern`); an item may add one `[lang: <language>]` naming
a declared language it alone binds.
## Maintenance
- No model yet (`architecture/index.yaml` absent) → run the **la:arch-init**
skill; it builds the first model from a measured scaffold.
- Otherwise keep the model, `index.yaml`, and arc42 in sync IN THE SAME PR as
any structural change; deepen buckets / add views for new subsystems; tighten
boundaries via the **la:arch-cleanup** skill.
## Interaction with /la:pr
- At pr-plan time, read `architecture/index.yaml` and the model plus the arc42 and view of
every node the change touches. The plan must state which principles apply,
and must not violate the model's import law — or must explicitly include the
model + arc42 change as part of the same change.
- At pr-review time, the enforcement bundle is part of the convergence gate.
- New capability → decide with the user where its spec attaches (one node's
metadata `specs`, or `cross_cutting_specs` in `index.yaml` with a `touches:`
list) and record it in the same change.
- Pure structural refactors (moving code across boundaries) are their own
changes via **la:arch-cleanup** — never smuggled into feature PRs.