map-containers
C4 container diagram of one software system: deployables, the stores and brokers their tracked configuration binds, and the edges those endpoints resolve to. Use when 'what actually runs', 'deployables and databases', 'modular monolith', 'which services bind to which broker'. Skip for which repositories exist (map-landscape), inside one deployable (map-components), or environment topology.
- License 许可
- MIT
- 最近更新
- 2026-11-15
- Stars
- ★ 30
- Forks
- 7
01 前言 Front Matter
- shell
- bash
- metadata
- { "summary": "Chart deployables, the stores they bind, and contained modules", "workflow-stage": "explore" }
- description
- C4 container diagram of one software system: deployables, the stores and brokers their tracked configuration binds, and the edges those endpoints resolve to. Use when 'what actually runs', 'deployables and databases', 'modular monolith', 'which services bind to which broker'. Skip for which repositories exist (map-landscape), inside one deployable (map-components), or environment topology.
- argument-hint
- [system] [--dialect likec4|c4-plantuml] [--out <dir>]
- user-invocable
- true
- disable-model-invocation
- false
02 资源文件 4
- 参考文档evals/evals.json5.8 KB
- 脚本scripts/collect-containers.sh33.2 KB
- 脚本scripts/collect-containers.test.sh48.7 KB
- 脚本scripts/render-containers.sh11.3 KB
03 SKILL.md 正文
Repository context
The current repository is both the CONSUMER, whose convention home declares where artifacts land, and the DEFAULT SUBJECT, the repository whose tracked files name deployables and stores.
Collect with an individual Bash call, one command per call: the project root,
git rev-parse --show-toplevel. Treat a failure (not a repository, git unavailable) as an unknown
value and carry on; ${CLAUDE_PROJECT_DIR} is the resolver's --root either way.
An optional [system] argument is only a focal label. It does not select a subdirectory and it
does not walk for nested repositories. Pass it to the collector as --focal. When it is absent,
the focal name is the github.com origin repository name, otherwise the directory basename.
Purpose
Answer "what actually runs, and which stores does it bind" from tracked project files, Dockerfiles, process manifests, and configuration, dirty files flagged. Every deployable cites the fact that made it a container. Every store cites a config key. The scripts collect and render. Do not draw a node the script did not emit, and do not turn a directory name into a service.
This is the C4 container rung. map-landscape is the rung above it. What is inside one deployable
is /architecture:map-components. A request trace is /architecture:map-flow.
Resolve home and dialect
Read ${CLAUDE_PLUGIN_ROOT}/reference/config.md first. This skill writes into architecture_dir.
It does not read landscape_dialect and it does not add a dialect key. The diagram dialect is
diagram_dialect.system from the authoring-formats topic doc.
Run bash "${CLAUDE_PLUGIN_ROOT}/lib/resolve-convention-home.sh" --root "${CLAUDE_PROJECT_DIR}" and
follow the exit code. Never parse the root instruction file yourself. Exit 0 means read
<home>/architecture/README.md for architecture_dir. Exit 1, 2, and 3 mean there is no declared
home to read.
In order: --out <dir> wins for this run alone, then a declared architecture_dir, then one
question. architecture_dir has NO default. An undeclared and unconfirmed home, including every
non-interactive run, STOPS and points at /architecture:setup. Do not invent a directory.
Resolve diagram_dialect.system by restating this ladder, then running the resolver rather than
parsing the topic doc yourself. The ladder is a resolution order, not a task list:
1. Anchor at the repository root: `${CLAUDE_PROJECT_DIR}` when set, otherwise
`git rev-parse --show-toplevel`. Never a CWD-relative read.
2. Resolve the convention home `<home>` with the bundled resolver above. Never hand-parse the root
file.
3. The printed home is repo-relative: join it to the root, then pass
`<root>/<home>/authoring-formats/README.md` to the resolver.
4. Layer order is one layer deep: an explicit `--dialect` argument, then the team convention doc.
There is no personal override.
5. `diagram_dialect.system` has NO default. Allowed values are `likec4` and `c4-plantuml`. When it
is unset, write `containers.json` and `containers.md` with the tables, and draw no diagram block.
6. Degrade soft, and say so. No pointer, no doc, no key, or an unrecognized value (mermaid
included, which the convention refuses) each resolve to emitting no view. The resolver names
the cause on stderr. Do not hard-fail and do not ask the operator to create the surface
mid-task.
7. Report provenance: the key, the value, and the layer (`argument`, `team convention doc <path>`,
or `unset (no C4 view emitted)`).
bash "${CLAUDE_PLUGIN_ROOT}/lib/resolve-diagram-dialect.sh" --kind system \
--formats "<root>/<home>/authoring-formats/README.md"
Omit --formats when no convention home resolved. Stdout is likec4, c4-plantuml, or none.
An explicit --dialect likec4|c4-plantuml on the invocation wins and the resolver is not required.
This skill never writes the consumer's root instruction file or its topic doc. /architecture:setup apply owns both.
Build the record
When <architecture_dir>/dependency-graph.json already exists and is schema_version 1, pass it as
--graph. Containment then comes from that graph's project edges and is not re-derived. Otherwise
omit --graph. The collector reads ProjectReference Include attributes itself and records
containment as project-references.
"${CLAUDE_SKILL_DIR}/scripts/collect-containers.sh" \
--repo "<subject-repo>" --out "<architecture_dir>/containers.json" \
--focal "<system-or-omit>" --graph "<dependency-graph.json-or-omit>"
Keep the ${CLAUDE_SKILL_DIR} anchor;
${CLAUDE_PLUGIN_ROOT}/reference/config.md § Script paths
says why.
The record is schema_version 1 in the one-object-per-line layout the script writes. Container lines
start with {"id":. Module lines start with {"container":. Edge lines start with {"from":.
Finding lines, in an optional findings array, start with {"kind":. A deployable kind is
web, api, worker, cli, function, or process. A store kind is store. technology is
a runtime, framework, or image, or the literal unknown. An edge kind is uses or
shared-infrastructure. A store with three owners is three shared-infrastructure edges, one per
pair of owners, and each cites the config keys of both. A uses edge runs from a deployable to
another deployable and cites the config key that names the endpoint and the fact that resolved it.
The store kinds read are sql (a relational or document database), storage (a blob storage
account), broker (a message broker), cache, and search (a search index service). A compose
service whose image is postgres, mysql, mariadb, mssql, mongo, redis, rabbitmq, nats, kafka,
elasticsearch, or opensearch is a store of the matching kind. Any other kind of store is not read,
so none is drawn.
A search store's technology is Azure AI Search, OpenSearch, Elasticsearch, or unknown,
chosen from the host by store_technology in collect-containers.sh; a compose service's is its
image. A search store is drawn as a database element.
An http or https URL is a search store, not an endpoint, when its host is a search-service domain
store_technology names or when the last segment of its config key contains elasticsearch,
opensearch, searchendpoint, or searchservice. Such a URL with a bare host, such as
ElasticsearchUrl: http://elasticsearch:9200 naming a compose service, draws no store, edge, or
finding; the compose service's image charts the store, and nothing links a deployable to it.
An endpoint is an http or https URL a deployable's own configuration names. It is an edge only
when it resolves to exactly one other deployable through a cited fact: a compose service whose
build context is that deployable's directory (the URL host is the service name and any declared
port equals the URL's, or is 80 for an http URL and 443 for an https URL that names no port),
or a launchSettings.json applicationUrl on that deployable (host
localhost or 127.0.0.1, same port). A name's resemblance to a deployable resolves nothing. An
endpoint that resolves to no deployable or to several draws no edge and is an external-endpoint
finding carrying its redacted host. A deployable naming itself draws no edge.
A sql store is one host, port, and database. The database comes from Initial Catalog or
Database in a connection string, or the first path segment of a postgres://, postgresql://,
mysql://, or mongodb:// URL. Two databases on one server are two stores and no shared edge. A
connection that names no database is the store database unknown, and a shared edge between its
owners says same server, database unknown. A sql store's technology is its URL scheme
(mongodb, postgres, mysql), Azure SQL for a database.windows.net host, and otherwise
unknown. A connection string names no scheme, so it is never labeled SQL.
Redaction is ${CLAUDE_PLUGIN_ROOT}/lib/redact-connection.sh (the awk beside it). A password,
token, account key, or URL userinfo must not appear in the record, the diagram, or stdout. A
database name is kept only when it is a plain identifier that carries no credential.
The collector matches tracked files. It does not execute them. It lists the files at HEAD and reads
each one from the working tree, so an edit to a tracked file is charted. When git status reports
tracked files that differ from HEAD, the record carries a dirty-tracked-files finding with their
count, and the collector says so on stderr. Untracked files are never read. A class library is a
contained module, not a container, even when its directory name sounds like a service.
A test project is not a deployable. A project that sets IsTestProject to true, or references
Microsoft.NET.Test.Sdk or a package whose id starts with xunit, NUnit, MSTest, or
Microsoft.Testing.Platform, is left off the diagram even when it would otherwise be one (an
OutputType Exe, or a web, worker, or functions SDK). The record carries an
excluded-test-projects finding with their count and paths.
Render
"${CLAUDE_SKILL_DIR}/scripts/render-containers.sh" \
--record "<architecture_dir>/containers.json" --out "<architecture_dir>" \
--dialect "<likec4|c4-plantuml|none>"
Write containers.json first, then render from it. c4-plantuml writes containers.md with one
fenced plantuml block (C4_Container: a System_Boundary holding Container, ContainerDb, and
ContainerQueue elements). likec4 writes containers.md with one fenced likec4 block (the same
elements nested in the software system, and a container view). none writes containers.md
with the tables and no diagram block. Contained modules are named on their deployable. They are
not drawn as containers.
The script prints one summary line on stdout:
containers: focal=<name> deployables=<n> stores=<n> modules=<n> edges=<n> shared=<n> unknown_technology=<n> thin=<yes|no> dialect=<likec4|c4-plantuml|none> excluded_tests=<n> dirty_tracked_files=<n>.
Keep it for the report. A result is thin when deployables=0. shared= counts
shared-infrastructure edges, so it counts owner pairs, not stores.
Exit 1 means the record is unreadable, not schema_version 1, or not in the one-object-per-line layout. Nothing was written. Report that message. Do not reformat the record by hand and do not treat a layout failure as an empty diagram.
Close with the report
End every run with this block, in this order, filled from the record and the script exits:
- Artifacts: each path written, or
none writtenwhen the run stopped before a home existed. - Focal: the system name, and that it is the repository being charted.
- Deployables: the summary's
deployables=count, and that kind came from output type, host builder, Dockerfile, or process manifest. - Stores: the summary's
stores=count, and that the kinds read are sql, storage, broker, cache, and search. Every store cites a file and a config key. - Edges: the summary's
edges=count, which includesusesandshared-infrastructureedges, and the count ofexternal-endpointfindings. Eachusesedge cites the config key and the resolving fact. - Modules: the summary's
modules=count. A modular monolith is one container. - Shared: the summary's
shared=count. Each shared-infrastructure edge is one pair of owners and cites the config keys of both. - Excluded: the summary's
excluded_tests=count of test projects left off the diagram. - Working tree: the summary's
dirty_tracked_files=count. Above zero, say the record charts the working-tree content of those files, not HEAD. - Dialect:
diagram_dialect.system, its value, and the layer:argument,team convention doc <path>, orunset (no C4 view emitted). - Technology:
unknown_technology=from the summary.unknownis literal, not a guess. - Containment source:
dependency-graph.jsonorproject-references. - Thin result:
no, oryesbecause no deployable was found. Name/architecture:map-landscapewhen the question was which repositories exist. - Redaction: the record keeps host, service kind, and a sql database name. It does not keep the raw value.
Interactive view
After the report, offer an interactive view of containers.json in one sentence. The markdown and the record
stay authoritative. Build it only with ${CLAUDE_PLUGIN_ROOT}/scripts/build-view.mjs containers, never
hand-written; the shared medium resolver decides the destination, with this plugin's medium option
${user_config.medium}, or "" when that reads empty or unsubstituted (never put a
${user_config.*} string in a shell command), and its report line is printed. Procedure:
${CLAUDE_PLUGIN_ROOT}/reference/rendered-view.md.
What this skill does NOT do
- Draw environment topology, replicas, or scaling. That is
/architecture:map-deployment. - Chart the modules inside one deployable as their own diagram. That is
/architecture:map-components. - Trace a request. That is
/architecture:map-flow. - Add a dialect key, read
landscape_dialect, or draw mermaid C4. - Execute configuration, fetch anything, or edit a project file. The only writes are
containers.jsonandcontainers.mdunder the resolved output directory. - Invent a home. No declared, no
--out, and no confirmedarchitecture_diris a stop, not a default. - Treat two projects in one repository as an edge. An edge needs a cited store binding or a
configured endpoint that resolves to a charted deployable. An endpoint that resolves to none is
an
external-endpointfinding, not an edge.
Next
- Inside one deployable, or one request's path through it:
/architecture:map-componentsor/architecture:map-flow. - Neighboring repositories need a landscape:
/architecture:map-landscape. - The view opens a topology or binding change to decide:
/planning:design system. - The view settles a decision worth keeping:
/architecture:record-decision.
Gotchas
- A container diagram is one software system. It draws the containers inside that one system, and no deployment concerns. Pointer: when checking a container view's scope or elements, fetch C4 container diagram § Scope, § Primary elements and § Notes live.
- C4-PlantUML container syntax: read against the README, never run. The renderer writes the macros. No PlantUML run has parsed this output. Pointer: when checking the macros or the include the renderer writes, fetch C4-PlantUML README § Container diagram and § Including the C4-PlantUML library live.
- LikeC4 container syntax: parsed by the CLI. The record is a probe:
likec4@1.59.4 validateexits 0 on the golden blocks in${CLAUDE_PLUGIN_ROOT}/lib/likec4-golden/(containers.c4,containers-stores.c4,containers-broker.c4,containers-endpoints.c4), whichcollect-containers.test.shdiffs against. Pointer: when checking the syntax the collector writes, fetch LikeC4 specification § Element kind, model § Element, views § Scoped views and styling § Shape live. As of: 2026-11-10. Recheck trigger: any of those sections changes that syntax, or a newerlikec4release ships: setLIKEC4_VALIDATE=1when running the test to re-run the CLI. - A search store is read from a host or a compose image. A host ending
.search.windows.netis labeledAzure AI Search. Pointer: when checking the service's endpoint form, fetch Create a search service § Name your service live. The other hosts the collector labels are listed instore_technology; a search service on any other host is labeledunknown. - An endpoint is not an edge until it resolves. A
localhostURL with noapplicationUrlon a charted deployable, a service name with no compose build context, and a URL naming an external service are allexternal-endpointfindings. Ausesedge is one per ordered pair. - Directory names are not deployables.
Microsoft.NET.Sdk.Webin a directory named Worker is a web host. A class library in a directory named Api is a module. Host-builder usage is read only for a project that is already an entry point (web SDK, worker SDK, functions, orOutputTypeExe). - A modular monolith is one container. Libraries reached by project references are listed as
contained modules. They are not drawn as containers. When
dependency-graph.jsonis passed, those edges are the only containment. AProjectReferencethe graph does not have is not a module. - Shared infrastructure cites both keys. Two deployables that name the same broker host produce
one
shared-infrastructureedge whose evidence lists both config keys. Three produce three, one per pair. Sitting in the same repository does not. Two deployables on one SQL server with different databases share nothing. - Redaction keeps the shape. Host, service kind, and a sql database name are the fact. Userinfo,
passwords, account keys, and secret-only values produce no field. The raw value is not stored.
package.jsonis not scanned.containers.jsonis not scanned again. - A URL that names several hosts is not read.
mongodb://a:27017,b:27017/dbyields no store. Report it as a gap when the repository uses that form. unknownis a technology value. A Dockerfile with noFROM, aFROMthat names a build variable ($BASE), a compose service with no image, or a SQL connection string with no URL scheme is technologyunknown. Do not invent a runtime from the service name. The image is the lastFROM, without its digest and without--platformand other options. A lastFROMthat names an earlier stage takes that stage's image.- The dialect key is
diagram_dialect.system, and it has no default. Every C4 view of the code reads the key the authoring-formats convention assigns to C4 system views, the same key/planning:designreads for its design container view. It refuses mermaid. Unset,containers.mdcarries the tables and no diagram, and the report says no view was emitted. The key is documented in${CLAUDE_PLUGIN_ROOT}/reference/config.md. Mermaid stays out, andrender-containers.shhas no mermaid C4 path. The decision, its pointer and its recheck trigger live in the convention's record. - A reformatted record reads as empty unless the reader refuses it.
render-containers.shexits 1 on any other shape and writes nothing. - Configuration is untrusted text. The assignment scanner matches it. It does not source it, eval it, or interpolate it into a command.
- A quote in repository-controlled text is replaced, not preserved. A name lands inside a quoted diagram literal. The delimiter is swapped for one that cannot close the literal.
04 同仓库更多 Skills
track
Backlog reads through the bound tracker: search open and closed items, view one item with its comments and state history. Use when another skill searches or reads tracker items.
reviewer-media
Reviewer media for one pull request: decides whether it needs a narrated walkthrough, builds the storyboard from /review:explain-change's digest, has /explainer-video:produce render it and hands the manifest to /source-control:pull-request media. Use when asked for 'reviewer media', 'a walkthrough video for this PR', 'a produced video of this change', or when pull-request ready or monitor hands a PR over.
route
Model and effort per agent role (orchestrator, worker, verifier, retrieval) for a multi-agent run, resolved through the bundled defaults and the user, team and personal override config layers into fenced JSON with single-agent and fan-out variants and the layer behind each value. Use when asked 'which model for the subagents', 'route the roles' or 'model routing', or before a skill passes a role's model on a spawn.
route
Model and effort per agent role (orchestrator, worker, verifier, retrieval) for a multi-agent run, resolved through the bundled defaults and the user, team and personal override config layers into fenced JSON with single-agent and fan-out variants and the layer behind each value. Use when asked 'which model for the subagents', 'route the roles' or 'model routing', or before a skill passes a role's model on a spawn.
principles
Module-design vocabulary for architecture findings, and the checks built on it. Use when: 'what is a deep module', 'what is a seam', 'deep vs shallow module', 'what is the deletion test', 'leverage and locality', 'module design vocabulary', 'which word do we use for X in architecture findings'. Scanning a codebase for friction is /architecture:improve.
write
Structured five-field bug report (title, steps to reproduce, expected vs actual, justified severity, suggested fix location) from an informal description, read-only. Use when the user names a defect they observed, or asks for one written up ('report a bug', 'file a bug', 'write this up as a bug').