Skip to content

feat(architecture): default map-landscape to this repository and its reference graph - #4057

Merged
kyle-sexton merged 17 commits into
mainfrom
claude/architecture-map-landscape-3fuoh1
Sep 11, 2026
Merged

feat(architecture): default map-landscape to this repository and its reference graph#4057
kyle-sexton merged 17 commits into
mainfrom
claude/architecture-map-landscape-3fuoh1

Conversation

@kyle-sexton

@kyle-sexton kyle-sexton commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

Closes #4033
Closes #4034
Closes #4035
Closes #4036
Closes #4037

Summary

/architecture:map-landscape stopped and asked for a scope argument on a bare
invocation, and even with --repos <this checkout> it drew one node with no
edges, because a relationship required the other repository to be a local
checkout. On a cloud checkout of this repository that is the whole answer: one
box, despite the tracked files naming nineteen other organisation repositories.

A repository already says which systems it builds against, in its workflows,
marketplace sources, module paths and docs. This reworks the skill to read them,
commit the result, and report what moved on the next run.

Running it on this repository now yields eleven organisation systems and
sixty-five external ones, with 24 uses-workflow references to ci-workflows
and 54 cites to standards.

Reviewing this PR: the commits are the intended review unit and segment along
the child issues in order. Reading commit by commit gives the same incremental
progression a stacked PR would, without the merge ordering.

Child Commits
baseline + brief 13710207, 8eb390bc
#4034 scope axis 2cb2c1ad, 3bc25b12
#4035 edges + record a5ef14b9, e2de646a, 35465ae8
#4036 renderer 8f55c30c
#4037 remote + skill body b1c125d0, 4ce96d02
merge + gate fixes c9a71394, 9e3f926d, c971100c, 9315a815
review round 7cbb9b2d

Fix

Scope hygiene in the facts probe (#4034). portfolio-facts.sh separates
runtime scope from development scope on the axis every surveyed standard uses
(CycloneDX scope, SPDX dependency relationships, npm devDependencies, PEP 735
dependency groups, the GitHub dependency-submission API). The record gains
tooling and dev_dependencies beside runtime and dependencies, each with
its own evidence. Cache and build dot-directories are pruned; the CI and
container config directories are kept, and every manifest under one is pinned to
development scope whatever its content says. This checkout reports
runtime: shell with tooling: node, python rather than claiming a Python
runtime from its CI pins.

Typed reference edges (#4035). reference-edges.sh reads one repository's
tracked files and emits typed, counted edges. Each type trusts exactly one
syntax: uses-workflow a workflow uses: step, installs-plugin a marketplace
source, depends-on a module path, cites a github.com URL or a bare
owner/repo whose owner matches the subject's own. A single owner/repo regex
over all tracked text was rejected because on this checkout it matches
sponsors/, en/ out of a docs URL, and every fixture in every test file.

Default scope and the committed record (#4035). A bare invocation charts the
current repository plus its reference graph, one hop out. --repos and --root
stay as explicit overrides; --out redirects one run. landscape-record.sh
assembles both collectors into a schema_version 1 landscape.json, one object
per line so review sees a real diff, and compares a fresh collection against the
committed one. --check runs the comparison, writes nothing, and exits non-zero.

Rendering (#4036). render-landscape.sh writes landscape.md (mermaid
C4Context) or landscape.dsl (Structurizr systemLandscape) plus
portfolio.md from the record, doing only work the record decides. Internal
systems sort by owner first so each owner forms one contiguous boundary;
externals sort by reference count, and --top-external keeps the ones carrying
the most evidence while counting the remainder rather than dropping it silently.

Opt-in remote facts (#4037). --remote fills facts for referenced
repositories that are not checked out here. Off by default, presence-gated on the
GitHub MCP tools or an authenticated gh, degrading to local-only with the
missing backend named. A local checkout always wins for a fact both sources
carry. Fetching itself is not scripted: these are MCP calls the model makes, and
only reasoning-free work is scripted. What the fetch produces reaches the record
through landscape-record.sh --remote-facts <file>.

Decisions, adjudicated against official sources

Five open decisions were researched against official specifications and then
resolved by independent fresh-context agents that were not shown the
implementer's reasoning. All five confirmed the implementation; the research
corrected two claims this PR body previously made, and the adjudication surfaced
six defects, fixed in c971100c.

Decision Verdict Basis
One PR vs. a five-deep stack One PR Splitting at the plan's boundaries would ship child 3 with a red suite and land two entry-point scripts unreachable from the skill body, which is Google's "prevents checking in unused APIs", literally
path excluded from the record Omit No consumer reads it, verified across every producer and consumer; npm, Cargo, uv and Poetry all record local paths relatively or not at all; CycloneDX forbids absolute paths on privacy grounds (CWE-497)
last_touched non-gating Keep informational Nine tools (Jest, Verify, insta, CycloneDX, detect-secrets, OpenAPI Generator, Go, npm) remove inherently-changing fields from baseline comparison; no tool gates on one
SKILL.md length Leave uncapped at 200 Anthropic publishes one number, 500. The body is well under a published under-5k-token budget
Externals outside any boundary Keep Both official C4 System Context examples do exactly this; the alternative crosses Mermaid's width-halving cliff

Two corrections to earlier claims in this body:

  • The "217 lines against a soft target of 200" framing was wrong. No 200-line
    figure exists in any Anthropic source.
    The docs, the engineering blog, the
    anthropics/skills README and all 41 bundled skills were checked. The only
    traceable origin is a third-party community wiki. Anthropic's own
    skill-creator is 485 lines, its validator enforces no line check, and the
    first-party playbook says the counts are "approximate and you can feel free to
    go longer if needed." This is not a gap; the local threshold has no upstream
    basis, and 90 of this repo's 261 skills breach it.

  • An undisclosed deviation from architecture/map-landscape: render both landscape dialects and the portfolio table from landscape.json with a tested script #4036, now disclosed. That issue specified
    "one external boundary". This PR does not implement it: externals render as
    top-level System_Ext outside every boundary. The instruction has zero
    support in any authoritative source
    . Across 24 c4model.com pages, the
    Structurizr docs and cookbook, the Mermaid docs, and the C4-PlantUML README
    there is no boundary labelled "external"; every organisational boundary in
    every official example names a real organisation. "External" is a relation, not
    an organisation, and both dialects already carry it on the element. The
    recommendation is to close that part of architecture/map-landscape: render both landscape dialects and the portfolio table from landscape.json with a tested script #4036 as unsupported. The defect here
    was silence, not the code.

Review round: five findings, all real, all fixed in 7cbb9b2d

The flip to ready drew a Codex review and a Claude security review. Every
finding was verified against the code before acting; none was rejected.

  1. Repository content broke out of the string literal it was written into
    (security review, IMPORTANT). A target framework is read out of
    <TargetFrameworks> with only XML tags stripped, and a raw " is legal,
    unescaped XML element text. The renderer wrote it straight into a quoted
    System(...) or softwareSystem string, so a crafted .csproj could splice
    arbitrary diagram syntax, a live click directive included, into a committed
    artifact. The root cause was unquote() stripping the JSON delimiters without
    undoing \"/\\. Values are now decoded out of the record, and their
    delimiters replaced for the target grammar at the one point the model rows are
    emitted. Replaced rather than escaped: neither dialect offers an escape for
    its own delimiter that could be verified as portable, and a quote in a
    repository name, a CODEOWNERS owner or a target framework is corrupt data, not
    a fact worth preserving byte for byte. The same class in the portfolio table
    is closed too: a | ended its cell and shifted every column after it.
  2. Two names differing only in punctuation collapsed onto one alias
    (Codex, P2). a-b and a_b are both legal GitHub names and can sit in one
    org. Both dialects declared the system twice and pointed every relationship at
    whichever declaration won. Aliases are now handed out once, in emission order,
    with a counted suffix on a repeat.
  3. A quoted uses: scalar dropped its edge in silence (Codex, P2). The
    opening quote stayed on the owner segment, failed the character check, and the
    edge vanished with no diagnostic, so any repository writing ordinary quoted
    YAML charted an incomplete graph.
  4. A checkout on disk was read as a claim of ownership (Codex, P2). Every
    locally collected repository was internal whatever its owner, so a third-party
    clone sat inside an enterprise boundary while the edges to it said external.
    This is the internal-side twin of the unknown boundary caption fixed in
    c971100c. The record now names its subject_owner, resolved by the edge
    extractor through a new --print-owner so the nodes and the edges cannot
    disagree about who the subject is. A probed repository is drawn whatever
    --top-external says, because that cap trims the tail of repositories a run
    only read about, not the set someone asked it to chart.
  5. --remote could not produce its promised output (Codex, P1). It recorded
    a status string and nothing else; no command accepted the fetched values, so
    every referenced repository stayed factless however much was fetched, and an
    archived repository could not be marked as scope-modes.md promises.
    landscape-record.sh --remote-facts <file> now merges them, before both
    emission and comparison so a merged record compares clean against itself. A
    local checkout wins outright rather than field by field: a probe that read the
    files is a better witness than an API summary of them, and a row assembled
    from both is one no single source stands behind. archived leads the node
    description and marks the portfolio row; default_branch and visibility are
    carried in the record and scope-modes.md now says so rather than leaving the
    reader to infer it.

Two things found while fixing rather than while reviewing:

  • The committed landscape.md was stale against its own record: it drew two
    external systems where the record has five. The pre-fix renderer reproduces
    five from the same record, so this was a missed regeneration, not a renderer
    bug. Corrected.
  • A first version of the quoted-uses: test passed against the unfixed script. A
    same-owner bare owner/repo token in the same file is also a cites hit, so
    asserting the target alone was satisfied by the citation while the
    uses-workflow edge stayed missing. The assertions now match on type as well.

Verification

Gate Result
landscape-record.test.sh 70 cases, 0 failed
render-landscape.test.sh 96 cases, 0 failed
reference-edges.test.sh 51 cases, 0 failed
portfolio-facts.test.sh 79 cases, 0 failed
Every new case against the pre-fix script 15 renderer, 5 extractor, all fail
shellcheck --rcfile .shellcheckrc clean
shfmt -i 2 clean
markdownlint-cli2 0 issues
check-skill.sh map-landscape PASS, 0 errors, 237/500 lines
scripts/validate-plugins.sh catalog and every manifest validated
Record regenerated twice byte-identical; drift reports clean both times
Artifacts rendered twice byte-identical, and match what is committed

Six defects found during the decision adjudication, fixed in c971100c:

  1. The record fed itself. Once landscape.json was tracked, the extractor
    read it back as evidence. It names every repository it charted, so each run
    raised every citation count by one and listed the record among its own
    sources. It hid because git grep scans only tracked files, so the first
    build (record still untracked) looked clean and committing it armed the loop.
    A drift gate that can never report clean is not a gate. The extractor now
    skips this skill's own artifacts by basename, and the record is a fixed point.
  2. A record built with --remote carried repositories no local-only run can
    produce; every one reported as removed repository, which gates. The
    comparison now checks the recorded posture against the run's own.
  3. Two checkouts sharing a basename collapsed onto one identity and silently
    matched the wrong row. Now reported as ambiguous.
  4. A clean comparison carrying non-gating differences claimed the record
    "matches" and then listed what moved.
  5. A repository with no resolvable owner was drawn inside a boundary captioned
    unknown, the same category error as the "external" boundary, from the
    internal side. Now drawn outside every boundary in both dialects.
  6. The Structurizr External tag was inert with no styles block, so the DSL
    artifact silently lost a distinction the mermaid one keeps.

One pre-existing failure, not this branch's:
plugins/claude-ops/skills/plugins/scripts/cache-content-check.test.sh fails 2
of 26 on its process-budget probe, reproducibly, on a tree with none of this
branch's changes applied. It passes in CI. This diff touches no file under
plugins/claude-ops/.

Related

  • Refs architecture/map-landscape: default to the current repository and its reference graph, with a committed record and drift reporting #4033 for the interview decisions Q1 through Q12
  • Contract-slice graduation. docs/topics/map-landscape-redesign/ was
    contract tier and is pruned per docs/conventions/topic-docs/README.md. The
    durable decisions graduated to
    docs/adr/0032-chart-a-landscape-from-references-and-commit-it-as-a-record.md.
    Pre-prune commit SHA: c9a71394c2e2e829e0e7f16bf9fd8f64f1a504b8. The Brief is
    pasted below so it survives the prune.
  • Follow-up, not in this PR: the LINE_SOFT_CAP=200 in
    plugins/skill-quality/scripts/check-skill.sh is a bare assignment with no
    recorded derivation, sitting under the traceable LINE_HARD_CAP=500. With 90
    of 261 skills breaching it, it emits a standing warning authors learn to read
    past, which erodes the other 24 checks in the same script. It deserves a
    derivation comment, a raise, or retirement.
  • Reviewer consent, given. Google's guidance for a large change asks for
    consent in advance rather than disclosure after the fact. Consent to take this
    as one unit covering five issues was given before the flip to ready.
Pruned contract: docs/topics/map-landscape-redesign/PLAN.md

TLDR

  • A bare /architecture:map-landscape charts the current repository plus its reference graph, one
    hop out; --repos and --root stay as explicit overrides.
  • References become typed, counted edges extracted per source type by a tested script; other-owner
    repositories render as external systems, kept read-only.
  • The committed record is docs/architecture/landscape.json; both rendered artifacts derive from it
    through a reasoning-free render script, and every re-run reports drift, with --check for CI.
  • The facts script gains a runtime-versus-development scope axis, prunes .github/ and other
    dot-directories from runtime probes, and adds a Tooling column.
  • --remote facts are opt-in and presence-gated; a fixed closing report and a rewritten description
    make single-repository-plus-references the primary use.

Goal

Rework the map-landscape skill so that one run in one repository yields a useful, committed,
re-runnable landscape of that repository and everything it references. Today the skill only reads
local checkouts and stops on a bare invocation, so on a cloud checkout of this repository it drew one
node with no edges even though the tracked files name nineteen other organisation repositories.

Constraints

  • Facts and edges come from tested scripts; the model adds prose annotations only. Rendering scripts
    do only reasoning-free work (tables, alias sanitising, boundary grouping, labels).
  • The declared-home doctrine in plugins/architecture/reference/config.md stays: an interactive run
    with no home proposes docs/architecture, --out <dir> overrides one run, and a non-interactive run
    with neither still stops.
  • No network call is made unless --remote is passed. Externals are never fetched from unless
    --remote=all, and nothing ever writes to another repository.
  • Scripts stay bash plus POSIX awk/grep/sed, matching portfolio-facts.sh; no jq, no python.
  • The working directory is never walked for nested repositories.
  • Skill bodies follow .claude/rules/skill-bodies-state-current-rules.md; validation runs through
    scripts/affected-tests.sh --run.

Acceptance criteria

  • A bare run in this checkout produces a landscape with ci-workflows and standards as nodes and
    typed edges to them, without any argument.
  • Every edge cites its source file and type; every fact cites its evidence.
  • IF no home is declared and the run is non-interactive, THEN nothing is written.
  • IF --remote is absent, THEN no network call is made.
  • WHILE a committed docs/architecture/landscape.json exists, a re-run reports drift instead of
    silently overwriting, and --check exits non-zero on drift.
  • This checkout no longer reports Python as a runtime; its CI pins appear under Tooling.
  • Rendering a fixed facts-plus-edges fixture twice yields byte-identical artifacts.
  • Existing evals are updated, and the no-scope-names-both-forms eval is replaced by one asserting
    the bare default.

Captured assumptions

  • The GitHub MCP or an authenticated gh is available when --remote is used; otherwise the flag
    reports unavailability and continues local-only. Revisit if a consumer needs remote facts with
    neither.
  • Mermaid stays the default dialect. Revisit if Structurizr becomes the organisation default.
  • Same-owner is decided from the current repository's origin owner segment. Revisit if a consumer
    has repositories split across owners it considers one enterprise.
  • A separate ci_tooling bucket was rejected in favour of a scope axis because every surveyed
    standard (CycloneDX scope, SPDX dependency relationships, npm devDependencies, PEP 735 dependency
    groups, GitHub dependency-graph scope) separates runtime from development on scope, and GitHub
    Linguist vendors .github/ out of language statistics. Revisit if a manifest family appears whose
    scope cannot be read from the file.

Out-of-scope

  • Transitive hops beyond one without --remote.
  • Branch or worktree hygiene (/repo-fleet-hygiene:audit), GitHub organisation settings
    (/github:audit), doc-versus-code drift inside one repository (/codebase-health:audit), and
    module-level structure (/architecture:improve).
  • Container-level or component-level C4 views.
  • Writing the consumer's root instruction file; /architecture:setup apply owns that.

Deferred questions

None. All twelve interview questions were answered; see the memory-tier ledger.

Sequencing

One tracking issue with four children, in order, each shipped as its own draft PR:

  1. Manifest scope hygiene in portfolio-facts.sh (scope axis, dot-directory pruning, Tooling).
  2. Core reshape: bare default scope, reference-edge script, landscape.json record with drift and
    --check, closing report, description and README rewrite, evals.
  3. Render script for both dialects.
  4. --remote facts.

🤖 Generated with Claude Code

https://claude.ai/code/session_012Drcna7MoR2BX9KrLqRy5i

Run /architecture:map-landscape in explicit --repos mode against this
checkout and land the two artifacts under docs/architecture, the home the
skill's own inference proposes when nothing is declared. Both files carry the
generated-on line, the discovery source, and per-fact evidence from
portfolio-facts.sh; no relationship line is drawn because a single repository
has nothing to name.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Drcna7MoR2BX9KrLqRy5i
Persist the interview contract for reworking the map-landscape skill: a
bare run charts the current repository plus its one-hop reference graph,
edges are typed and script-extracted, docs/architecture/landscape.json is
the committed record with drift reporting, the facts script gains a
runtime-versus-development scope axis, and --remote facts are opt-in.
Twelve interview questions answered, none deferred; sequencing is one
tracking issue with four children.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Drcna7MoR2BX9KrLqRy5i
…ortfolio facts

The facts probe classified this repository as node,python with sixteen
dependencies, all of them CI pins, because a requirements file under
.github/ matched the Python glob and a devDependencies-only package.json
counted as a Node runtime. A repository runs on its runtime and is built
with its tooling, and every standard that separates the two draws the line
on scope rather than path: CycloneDX scope, SPDX DEV_DEPENDENCY_OF and
BUILD_TOOL_OF, npm devDependencies, PEP 735 dependency groups, and the
GitHub dependency graph.

The record gains tooling and dev_dependencies beside runtime and
dependencies, each with its own evidence. target_framework follows the
primary runtime only, so an engine constraint for a linter no longer fills
it. Cache and build dot-directories are pruned from the manifest index,
the CI and container config directories are kept, and every manifest under
a dot-directory is pinned to development scope: the path rule decides
visibility, the scope axis decides runtime versus tooling. A root-level
manifest now beats a deeper one, which previously reported whichever
component sorted first.

This checkout now reports runtime shell with tooling node,python, and its
CI pins as development-scope dependencies citing both manifests.

Refs #4034
Refs #4033

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Drcna7MoR2BX9KrLqRy5i
…cted facts

The first run recorded this repository as a node,python runtime with sixteen
dependencies, all of them CI tool pins. The scope split corrects that to a
shell runtime with node and python tooling and no runtime dependencies, so
the committed artifacts are regenerated to match rather than left asserting
facts the probe no longer produces.

The portfolio table gains the Tooling column and states the scope split it
now reports. The landscape names why the nineteen repositories this checkout
references are absent from the diagram.

Refs #4034
Refs #4033

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Drcna7MoR2BX9KrLqRy5i
… tracked files

A landscape drawn only from locally checked-out repositories shows one node
and no edges: the related systems are named by reference, not by adjacency
on disk. This checkout names eleven sibling repositories and several third
parties, none of which the current skill can draw.

reference-edges.sh reads tracked files and emits one JSON record per
(target, type) pair with a reference count and the citing files. Four types,
each anchored on a syntax that only ever names a repository: uses-workflow
(a workflow or composite-action step), installs-plugin (a marketplace source
or install line), depends-on (a Go module path), and cites (a github.com URL,
or a bare owner/repo whose owner matches this repository's own).

The owner requirement on bare tokens is what makes the weakest type usable.
A single owner/repo regex over all tracked text is why this was left to the
model before: on this repository it matches sponsors/ out of a funding URL,
en/ out of a docs.github.com path, and every acme/billing in a fixture.
Alongside that, the extractors reject test and eval files, GitHub's own
product surfaces, documentation placeholders, local marketplace sources,
and self-references folded case-insensitively; ref pins, .git suffixes, and
sentence-ending periods are stripped from the repository segment.

Against this checkout it finds 77 edges, including 24 uses-workflow
references to ci-workflows, matching an independent count of the same
workflow lines.

Refs #4035
Refs #4033

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Drcna7MoR2BX9KrLqRy5i
The landscape showed one node because the skill can only relate repositories
that are checked out locally, and none of this repository's siblings are.
With the edge extractor those references become edges, so the diagram now
carries the seven same-owner systems this repository names most, the two
most-referenced external ones, and a typed, counted relationship for each.

The omissions are stated rather than silently dropped: five same-owner
repositories referenced once or twice, and sixty-three further external ones
that are tool and documentation citations rather than related systems. Node
descriptions are marked as annotations, since the extractor reports that a
repository is referenced and how, never what it is for.

Refs #4035
Refs #4033

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Drcna7MoR2BX9KrLqRy5i
A landscape collected fresh on every run has no memory: nothing tells the
operator that a system appeared, an edge vanished, or a runtime changed since
the last time anyone looked. landscape-record.sh composes the fact and edge
collectors into one schema_version 1 record the repository commits, one object
per line so review sees a real diff, and compares a fresh collection against a
committed one.

The drift report names repositories and edges added or removed, fields whose
value changed, and cited evidence files that are no longer in the checkout.
`path` is excluded from the comparison: it says where a checkout sits on one
machine, not what the system is. Exit 3 on drift gives callers a check gate.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Drcna7MoR2BX9KrLqRy5i
Table rows, dependency truncation, diagram alias sanitising, boundary grouping
and node labels are all mechanical, and rendering them by hand made two runs on
identical facts produce different files. render-landscape.sh reads the record
and writes landscape.md (mermaid C4Context) or landscape.dsl (Structurizr
systemLandscape) plus portfolio.md, doing only work the record decides.

Internal systems sort by owner first so each owner forms one contiguous
boundary; externals sort by reference count so --top-external keeps the ones
that carry the most evidence, and the remainder is counted rather than dropped
silently. Prose comes from an annotations file this script only appends.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Drcna7MoR2BX9KrLqRy5i
…references

A bare invocation stopped and asked for a scope argument, and even an explicit
one drew a single node, because relationships required the other repository to
be a local checkout. A repository already names its neighbours in its workflows,
marketplace sources, module paths and docs; the skill now reads them.

The default scope is the current repository plus its reference graph, one hop
out. --repos and --root stay as explicit overrides, --out redirects one run, and
the working directory is still never walked for nested repositories. The record
is committed and every re-run compares against it before writing, with --check
as the read-only CI shape. --remote is opt-in, presence-gated, and loses to a
local checkout for any fact both can supply.

The --root collaborator protocol and the --remote contract move to a scope-modes
reference spoke: both are rare paths whose detail does not belong in context on
a default run.

This repository's own artifacts are regenerated through the new pipeline, with
the annotations it cannot derive in landscape-notes.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Drcna7MoR2BX9KrLqRy5i
…-only

A landscape that quietly drops archived repositories hides exactly the
dependencies worth acting on, so a --remote run charts and marks them.

--check gains the instruction it was missing: report the comparison's exit code
as the run's outcome and do not refresh the record to make the next run clean.
A check lane that fixes what it is checking has stopped being a check.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Drcna7MoR2BX9KrLqRy5i
Three files conflicted. Resolutions:

- plugin.json: keep 0.9.0, which is ahead of the 0.8.7 main shipped, with this
  branch's rewritten landscape description.
- CHANGELOG.md: main claimed 0.8.6 for the config.md dialect mapping while this
  branch had claimed it for the scope axis, so the scope-axis entry moves into
  0.9.0 where the rest of this work already sits. Main's 0.8.6 and 0.8.7 entries
  are kept verbatim.
- SKILL.md: take main's mermaid-C4 gotcha, which defers the experimental stamp
  and recheck trigger to reference/config.md instead of carrying a second dated
  one here. Every other gotcha on this branch is kept.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Drcna7MoR2BX9KrLqRy5i
exec-bit: the two new test scripts carry a shebang without the executable
bit, which the repo-wide gate rejects.

changed-skills: the render suite read `npx --no-install` exiting non-zero as
"the renderer emits bad markdown", when on a host without the package
installed it only means the tool is absent. The two are indistinguishable at
the exit code, so the check now probes on a file known to be clean first and
skips explicitly when that probe fails.

contract-slice-diff: docs/topics/ is contract tier, committed on a task branch
and pruned before merge. The Brief's durable decisions graduate to
docs/adr/0032: charting from a repository's own references, the one-syntax-per
edge type rule, runtime versus development as a scope axis, the committed
record the artifacts derive from, and other-owner repositories as read-only
reference. Its Evidence section carries the standards survey and the concrete
false matches that ruled out a naive reference regex. The slice itself is gone;
the PR body names the pre-prune commit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Drcna7MoR2BX9KrLqRy5i
… more

Adjudicating this branch's open decisions against official sources surfaced six
defects. Every design decision was confirmed; these are the bugs found on the
way.

The serious one: once landscape.json was tracked, the edge extractor read it
back as evidence. The record names every repository it charted, so each run
raised every citation count by one and listed the record among its own sources.
It hid because git grep scans only tracked files, so the first build (with the
record still untracked) looked clean and committing it armed the loop. A drift
gate that can never report clean is not a gate. This skill's own artifact names
are fixed by its contract while only their directory varies, so the extractor
now skips them by basename, and regenerating twice is byte-identical.

A record built with remote facts carries repositories no local-only run can
produce, and every one of them reported as removed. The comparison now checks
the recorded posture against the run's own and says when the two sides are not
the same kind of thing.

Two checkouts sharing a basename collapsed onto one identity and silently
matched the wrong row; that is now reported rather than guessed at. A clean
comparison carrying non-gating differences no longer says the record "matches"
and then lists what moved. A repository with no resolvable owner is drawn
outside every boundary in both dialects, because a boundary is captioned with an
organisation and `unknown` is the absence of one. The Structurizr External tag
gains the styles block that makes it render, since `location` was removed
upstream and the tag is the only carrier left for that fact.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Drcna7MoR2BX9KrLqRy5i
@kyle-sexton
kyle-sexton marked this pull request as ready for review September 11, 2026 05:20
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 11, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-11T05:25:41.207591Z c971100 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@claude

claude Bot commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 3m 3s —— View job


Working on it

  • Gather context (PR body, changed files, custom instructions)
  • Invoke /review:code-review — skip gate checked, PR is open/ready, in scope
  • Deep review of the 4 new/modified core scripts (parallel subagents dispatched)
  • Review SKILL.md / docs / evals for consistency with script behavior
  • Adversarially verify candidate findings, drop rejected ones
  • Post inline comments for anchorable findings
  • Post summary comment with cross-cutting findings

@claude

claude Bot commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 6m 7s —— View job


CI security review

  • Gather context (PR body, changed files, CLAUDE.md/AGENTS.md conventions)
  • Check skip gate (open PR, security-relevant surface: new bash collectors read repo content and write committed docs — reviewing)
  • Fetch PR diff via gh pr diff and read changed files
  • Hunt for logic flaws, injection surfaces, trust-boundary issues, secret handling
  • Adversarially verify surviving candidates (traced full data flow field-by-field through both scripts)
  • Post findings

One finding, posted inline on plugins/architecture/skills/map-landscape/scripts/render-landscape.sh (L375-403):

IMPORTANT — untrusted manifest content breaks out of the quoted Mermaid/Structurizr string literals it's interpolated into. render-landscape.sh writes target_framework (folded into the node description) straight into quoted System(...)/softwareSystem "..." literals via bare printf "%s", with no re-escaping. For a .NET repository, target_framework is read verbatim from <TargetFrameworks> in portfolio-facts.sh#L656-L660 — only XML tags are stripped, and a raw " is legal, unescaped XML element text. A crafted .csproj (<TargetFrameworks>net9.0" } click n1 "javascript:alert(1)" "pwn</TargetFrameworks>) terminates the generated string early and splices attacker-controlled syntax into the committed landscape.md/landscape.dsl, live for any downstream Mermaid/Structurizr renderer with a looser security level than GitHub's sandboxed preview. The landscape.json record itself is fine — portfolio-facts.sh JSON-escapes correctly on the way in — the gap is entirely in the render step's unquote(), which undoes JSON quoting without re-escaping for the target grammar. Full detail, exact call sites, and a suggested fix are on the inline comment.

No other issues met the high-signal bar: the other new/changed scripts (reference-edges.sh, landscape-record.sh, portfolio-facts.sh elsewhere) properly JSON-escape emitted fields, validate the character set of anything used to build a git grep/regex pattern from repository-controlled input (owner, edge targets), make no network calls outside the opt-in, presence-gated --remote path (which is MCP/gh calls the model makes, not scripted), and never eval or otherwise execute repository content. GitHub Actions hardening is out of scope for this lane per the skill's charter (zizmor's lane).
· branch claude/architecture-map-landscape-3fuoh1

@github-actions

Copy link
Copy Markdown

Claude has reviewed this PR 1 time. The lane skips further automatic reviews after 5; deleting this comment resets the count.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c971100c26

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread plugins/architecture/skills/map-landscape/reference/scope-modes.md
Comment thread plugins/architecture/skills/map-landscape/scripts/render-landscape.sh Outdated
@github-actions

Copy link
Copy Markdown

Last security-reviewed head: c971100c26f8fd14e1b305519e65d1765d98ff11. On the next push, the relevance gate compares only the commits since this SHA; delete this comment to force a full re-review.

… descriptions

Both are generated from the plugin manifest and the skill frontmatter, and this
branch rewrote the plugin description and the skill summary without rerunning
the generators. The drift check that catches it runs only in the full test
lanes, which a draft pull request skips, so it stayed invisible until the flip
to ready armed those lanes.

The catalog was the reported failure on both test-linux shards. Fixing it
surfaced the cheat sheet behind it: the validator stops at the first drift, so
one stale generated file masks the next.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Drcna7MoR2BX9KrLqRy5i
Repository-controlled text could break out of the string literal it was
written into. A target framework is read out of a manifest with only XML
tags stripped, and a raw quote is legal there, so a crafted value could
splice arbitrary syntax into a committed diagram. Values are now decoded
out of the record and their delimiters replaced for the target grammar,
and a pipe is escaped before it reaches a portfolio-table cell.

Two names differing only in punctuation collapsed onto one diagram
identifier, so both dialects declared the system twice and pointed every
relationship at whichever declaration won. Aliases are handed out once,
in emission order, and a repeat gets a counted suffix.

A quoted `uses:` scalar left its opening quote on the owner segment,
which failed the character check and dropped the edge without a word.

A checkout on disk was read as a claim of ownership: every locally
collected repository was internal whatever its owner, so a third-party
clone sat inside an enterprise boundary while the edges to it said
external. The record now names its `subject_owner`, resolved by the edge
extractor so the nodes and the edges cannot disagree, and a cross-owner
checkout renders external with its probed facts intact. It is drawn
whatever `--top-external` says, because that cap trims the tail of
repositories a run only read about, not the set someone asked to chart.

`--remote` recorded only a status string, so fetched facts had nowhere
to land and every referenced repository stayed factless however much was
fetched. `--remote-facts <file>` merges them, a local checkout winning
outright over an entry of the same name, and an archived repository is
marked in the node description and the portfolio row as the remote-facts
contract already promised.

The committed landscape.md was also stale against its own record: it
drew two external systems where the record has five.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Drcna7MoR2BX9KrLqRy5i
Integrating main brought in tracked files that the reference graph reads,
so `anthropics/skills` gains two citations. The `stale-base-overlap` gate
refused the branch for the same reason it refuses any stale base: both
sides had touched docs/CATALOG.md, and a squash cut from there can revert
what landed on the base after the branch point.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Drcna7MoR2BX9KrLqRy5i
@kyle-sexton
kyle-sexton merged commit c359b0e into main Sep 11, 2026
12 checks passed
@kyle-sexton
kyle-sexton deleted the claude/architecture-map-landscape-3fuoh1 branch September 11, 2026 06:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment