melodic-software

claude-code-plugins

"Chart a repository and the systems it references as a C4 System Landscape plus an application-portfolio table: extract typed reference edges and portfolio facts from tested scripts, commit the result as a landscape record, report drift against it, and render both artifacts into the declared architecture home. Use when: 'map our landscape', 'system landscape', 'what systems do we have', 'what does this repo depend on', 'application portfolio', 'who owns which repo', 'what runtimes are we on', 'chart our repositories', 'C4 context across repos', 'inventory our systems', 'has our landscape drifted'. Skip when: the question is module-level structure inside one codebase (shallow modules, seam placement) which is /architecture:improve, fleet cleanup (stale branches, orphaned worktrees) which is /repo-fleet-hygiene:audit, org settings which is /github:audit, or doc-versus-code drift inside one repository which is /codebase-health:audit."

melodic-software 28 5 Updated 3w ago

Resources

3
GitHub

Install

npx skillscat add melodic-software/claude-code-plugins/plugins-architecture-skills-map-landscape

Install via the SkillsCat registry.

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 the rest of the landscape.

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.

Purpose

Answer "what systems does this organization have, who owns them, what do they run on, and how do
they relate" from what a repository already says about its neighbors. Every fact traces to a named
file; every edge traces to a matched string in a tracked file; both are collected by scripts, never
derived by hand. The committed record makes the answer re-runnable, so the second run reports what
moved instead of quietly replacing the first.

Resolve home and dialect

Read ${CLAUDE_PLUGIN_ROOT}/reference/config.md first; it owns the keys, the topic-doc location,
and the resolution order. This skill reports against that contract rather than restating it.

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 and landscape_dialect; exit 1 (no pointer
line), 2 (usage), and 3 (FAIL, surface the resolver's own message) all mean there is no declared
home to read.

Per key, in order: --out <dir> wins for this run alone, then a declared topic-doc value, then an
inference PROPOSED from repository evidence and confirmed by the operator, then one question. Two
outcomes are non-negotiable when nothing answers: landscape_dialect falls back to mermaid, and
architecture_dir has NO default, so an undeclared and unconfirmed home, including every
non-interactive run, STOPS and points at /architecture:setup.

This skill never writes the consumer's root instruction file or its topic doc. /architecture:setup apply owns both.

Choose the subject repositories

  1. No scope argument: the current repository, plus every repository its tracked files reference,
    one hop out. This is the default and the primary use. Facts are collected from the current
    checkout; the referenced repositories are nodes with edges and no probed facts unless they are
    also checked out locally or --remote is passed.
  2. --repos <path>[,<path>...]: exactly those repositories. Facts come from every path; edges
    come from the first path only (the --edges-from default). No discovery runs at all.
  3. --root <dir> (repeatable): discovery, delegated to the repo-fleet-hygiene plugin when it
    is installed and an announced bundled walk when it is not. Read
    scope-modes.md before
    running this mode; the collaborator's plan is wider than the roots you asked for, and charting it
    unfiltered grows the landscape to the operator's whole configured fleet.

The session's working directory is never WALKED for nested repositories under any mode. Reading the
current repository as the default subject is not a walk: it is one path, resolved from
git rev-parse --show-toplevel.

Build the record

One call assembles both collectors into the committed record:

"${CLAUDE_SKILL_DIR}/scripts/landscape-record.sh" \
  --source "<how the subject set was chosen>" --remote "<not used | used, owned only | used, all>" \
  --edges-from <subject-repo> <repo-path>...

The ${CLAUDE_SKILL_DIR} anchor matters. A bare relative path resolves against the session's working
directory, which is not where the script lives.

--source and --remote are recorded verbatim, so a later reader can tell an explicit list from a
fleet plan, and a local-only run from one that fetched. --edges-from is the repository whose
tracked files supply the edges: the current repository under the default and --root modes, the
first path under --repos unless the operator names another.

The record carries repositories[] from portfolio-facts.sh (name, remote, owner, runtime,
tooling, target_framework, dependencies[], dev_dependencies[], last_touched, evidence{})
and edges[] from reference-edges.sh (from, to as owner/repo, type, relation, count,
files[]).

It also records subject_owner, the organization the graph was drawn from, resolved by the edge
extractor so the nodes and the edges cannot disagree about it. That is what makes a checkout
internal: having a repository on disk says where someone works, not who owns the system, so a
cross-owner checkout is the same external system the edges to it already call external.

Anything no probe could derive is the literal unknown. Carry it through to the artifacts as-is;
never replace it with a guess, and never fill it from a commit author, a directory name, or
ecosystem memory.

runtime and dependencies are runtime scope, what the repository RUNS ON. tooling and
dev_dependencies are development scope, what it is BUILT WITH: npm devDependencies, PEP 735
dependency groups, a requirements-ci.txt, anything under a dot-directory. Report them as separate
facts; a linter is not a runtime.

Edge relation is internal when the target owner matches the subject's own and external
otherwise. An external repository is reference material: drawn and recorded, never written to, never
fetched from unless --remote=all. Edges are the script's output, not your judgment: do not add one
the script did not extract, and do not delete one for looking incidental, because a low cites
count IS the signal that the reference is weak.

Report drift, and honor --check

When <architecture_dir>/landscape.json already exists, compare before writing anything:

"${CLAUDE_SKILL_DIR}/scripts/landscape-record.sh" \
  --drift-against "<architecture_dir>/landscape.json" --edges-from <subject-repo> <repo-path>...

Exit 0 means the committed record still matches. Exit 3 means it drifted; the report names
repositories and edges added or removed, facts whose value changed, and cited evidence files that no
longer exist. A last_touched that moved is reported as moved on <repo> and does NOT set the exit
code, because the subject repository advances its own HEAD on every commit. Surface the whole
report, gating and non-gating lines alike, before the artifacts. Exit 1 naming the
one-object-per-line layout means someone reformatted the committed record (compacted, or one key per
line), so neither the comparison nor the renderer can read it. Report that, not drift and not a thin
result, and regenerate the record.

--check stops there: run the comparison, print the report, write NOTHING, and report the
comparison's exit code as the run's outcome, so a lane invoking the script directly fails on drift.
That is the CI shape, and the only mode in which this skill writes no file at all when a home is
declared. Do not "helpfully" refresh the record so the next run is clean.

Remote facts, only when asked

Without --remote, no network call is made at all and the record says remote: not used. That is
the default and it is not negotiable by a referenced repository looking empty.

With --remote (or --remote=all for externals too), read
scope-modes.md for the
presence gate, the fact list, the evidence shape, and the rule that a local checkout always wins.

Fetched facts reach the record through --remote-facts <file>, one JSON object per line in the
shape portfolio-facts.sh emits. Without it the flag records only that a fetch happened, and every
referenced repository stays factless. Pass the same file to the drift run: a merged record compared
against a local-only collection reads every fetched repository as removed.

Emit artifacts

Render, then annotate. The renderer does the mechanical work; you write only prose.

"${CLAUDE_SKILL_DIR}/scripts/render-landscape.sh" \
  --record "<architecture_dir>/landscape.json" --out "<architecture_dir>" \
  --dialect "<landscape_dialect>" --notes "<architecture_dir>/landscape-notes.md"

Write landscape.json first, then render from it. The script writes landscape.md (mermaid) or
landscape.dsl (structurizr) plus portfolio.md, and appends landscape-notes.md verbatim when it
exists. --top-external <N> sets how many external systems the diagram draws, most referenced
first; every internal system is always drawn and the remainder is counted under the diagram.

The renderer prints one summary line on stdout and nothing else:
landscape: internal=<i> external=<e> drawn_systems=<s> edges=<n> drawn_edges=<d> unresolved_edges=<u> thin=<yes|no>.
Keep it for the report; --help defines each field and the thin threshold.

landscape-notes.md is the ONLY file in the architecture directory you author, and the only one you
never overwrite: read it, extend it, leave what a person wrote alone, and mark an annotation as an
annotation. Annotations say what a system is FOR, which the extractor cannot know.

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 written (--check).
  • Repositories charted: internal= and external=, quoted from the summary line (from the
    record under --check).
  • Edges by type: uses-workflow, installs-plugin, depends-on, cites, each with its count.
  • Thin result: no, or yes with the reason and the one remedy that fits, taken from the
    summary line. Under --check it reads not assessed (--check).
    • edges=0: the subject names no other repository. Lead by saying this is the wrong tool for the
      question: a landscape answers which systems exist and how they reference each other. How one
      deployable is built inside is /architecture:map-components. Module-design friction inside
      that code is /architecture:improve. How the code behaves is /discovery:explore. Then offer
      --repos <repository-that-names-others>,<this>, the edge-supplying one first.
    • unresolved_edges above 0: no charted repository has the name the edges come from, so the
      repository that supplies them is not charted. Add it to --repos, first.
    • edges minus unresolved_edges above drawn_edges: --top-external hid some edges.
      Re-render with a larger --top-external; nothing needs collecting again.
    • drawn_systems at most 2 with drawn edges: --root <dir>, or --repos with the current
      subject kept first, adds the organization's other repositories as nodes.
    • Whenever internal=1, point to /architecture:map-components for the modules inside this
      repository's deployable, whatever the counts. Also name /architecture:improve when the
      question is module design. Never offer --remote here: it fills facts for existing nodes and
      adds no system and no edge.
  • Unknown facts: how many fields across the record are the literal unknown.
  • Discovery source: default (current repository plus reference graph), explicit list, fleet
    plan, or bundled walk.
  • Remote: not used, used for owned repositories, used for all, or requested and unavailable with
    the missing backend named.
  • Drift: none, the drift summary, or no committed record to compare against.

What this skill does NOT do

  • Baseline-versus-target gap analysis, capability maps, or work-breakdown structures.
  • The rungs below this one. Modules inside one deployable are /architecture:map-components.
    Deployables are /architecture:map-containers. Configured neighbors are
    /architecture:map-context. Build edges are /architecture:map-dependencies. A traced entry
    point is /architecture:map-flow. Messages are /architecture:map-events. A declared schema
    is /architecture:map-data. Infrastructure is /architecture:map-deployment. An explicit
    state machine is /architecture:map-states. This skill is the landscape altitude only.
  • Transitive hops beyond one. A repository named by a repository this one names is not charted.
  • Modify any repository other than the consumer, or write outside <architecture_dir> within it.
    External repositories are read-only reference in every mode.
  • Reach the network without --remote.
  • Write the consumer's root instruction file, inside or outside the marked region. That is
    /architecture:setup apply.
  • Invent a home. No declared, no --out, and no confirmed architecture_dir is a stop, not a
    default.

Next

  • The question is the modules inside one deployable: /architecture:map-components.
  • The question is module-design friction: /architecture:improve.
  • The question is how the code behaves: /discovery:explore.
  • The landscape settles a decision worth keeping: /architecture:record-decision.

Gotchas

  • The two dialects are not symmetric, and mermaid is the loose one. Structurizr has a dedicated
    systemLandscape view type. Mermaid has no landscape diagram type, so the mermaid output
    is a C4Context diagram used without a focal system. The mermaid-C4 experimental fact and
    the recheck trigger (experimental banner drops, or mermaid documents a dedicated landscape
    type) live in ${CLAUDE_PLUGIN_ROOT}/reference/config.md under C4 dialect surfaces. This
    skill does not carry a second stamp.
  • A referenced repository is not a checked-out one, and the portfolio says so. Under the default
    scope only the subject repository has probed facts. Everything else is a node with edges and an
    unknown row. That is the honest answer without --remote, not a gap to fill by guessing.
  • cites is the weakest type and the loudest one. It means a name appears in tracked text,
    nothing more, and on a documentation-heavy repository it outnumbers every other type several times
    over. Read a high cites count as "this repository talks about that one", never as a dependency.
  • A dot-directory manifest is tooling, never a runtime. Cache and build directories (.venv,
    .mypy_cache, .tox) are pruned outright; the CI and container config directories (.github,
    .gitlab, .circleci, .devcontainer) are kept, and every manifest under one is pinned to
    development scope whatever its content says. So a repository of shell and markdown whose CI
    installs ruff reports runtime: shell with tooling: python. The same rule makes a
    package.json carrying only devDependencies report tooling, which is why target_framework can
    be unknown while a Tooling entry is present.
  • A checkout on disk is not a claim of ownership. A repository whose owner differs from
    subject_owner renders as an external system with its probed facts intact, outside every
    enterprise boundary, even though this run read its files. It is drawn whatever --top-external
    says: that cap trims the tail of repositories the run only read about, never the set someone
    asked it to chart.
  • A quote or a pipe read out of a manifest is replaced, not preserved. A target framework, an
    owner from CODEOWNERS and a repository name are all repository-controlled text that lands
    inside a quoted string in both dialects and inside a cell in the portfolio table. Neither diagram
    grammar has a portable escape for its own delimiter, so the delimiter is swapped for one that
    cannot close the literal. A value that comes out altered is a value that was never a fact.
  • owner is a ladder, and commit authors are not on it. The CODEOWNERS default * rule's
    first owner, then the owner segment of the origin remote, then unknown. Who edits a repository
    most is not who owns it, so neither the script nor the write-up looks at git authorship.
  • last_touched is local HEAD unless a remote fact replaced it, and nothing fetches by default.
    A stale checkout reports a stale date. The generated-on line carries the remote state so a reader
    can tell which it is.
  • The record holds no filesystem paths. The fact collector reports where each checkout sits and
    the record drops it, because a committed artifact naming one machine's directory layout differs on
    every other machine that regenerates it. Read the collector directly when a run needs the path.
  • The fleet plan is a temp artifact, and wider than the roots you asked for. Both traps belong
    to --root alone and are stated in full in
    scope-modes.md.