feature-diagram
SkillAI & modelsPer-feature Mermaid flowchart — routes / models / jobs / screens. Consumes /extract-features inventory.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the feature-diagram skill
What this skill tells your AI
The instructions your AI receives, as published by me2resh/apexyard in .claude/skills/feature-diagram/SKILL.md and read by ahel’s review.
Writing rule
When this skill writes a durable artifact, read .claude/rules/writing-standard.md. Use the controlled technical writing profile.
/feature-diagram — Per-feature Mermaid Sub-graph
Reads the Feature Inventory at projects/<name>/feature-inventory.md (produced by /extract-features) and writes a per-feature Mermaid flowchart LR showing the HTTP routes, data models, async jobs, and UI screens that participate in one feature. Output: projects/<name>/features/<slug>.md (one file per feature).
This skill closes the gap left by the rest of the architecture-doc family:
| Skill | Slice |
|---|---|
/c4 | Whole-system topology (L1 + L2) |
/dfd | Whole-system data flows + trust boundaries |
/process | One business process (BPMN control flow) |
/journey | One feature, product-facing user flow (HTML modal-per-page) |
/feature-diagram (this skill) | One feature, architectural slice (Mermaid flowchart) |
See AgDR-0035-per-feature-diagrams.md for the design rationale.
Path resolution
Read the per-project docs dir via portfolio_projects_dir from .claude/hooks/_lib-portfolio-paths.sh. Source the helper at the top of any bash block that touches that path:
source "$(git rev-parse --show-toplevel)/.claude/hooks/_lib-read-config.sh"
source "$(git rev-parse --show-toplevel)/.claude/hooks/_lib-portfolio-paths.sh"
projects_dir=$(portfolio_projects_dir)
Defaults match today's single-fork layout (./projects). Adopters in split-portfolio mode override the portfolio.{registry, projects_dir} keys in .claude/project-config.json — the helper resolves whichever mode they're in. See docs/multi-project.md.
Write targets (see me2resh/apexyard#373 + #443): paths documented as projects/<name>/X in this skill are canonical adopter-facing forms — implement them in bash as "${projects_dir}/<name>/X". Never construct from "${PWD}/projects/...", "$(git rev-parse --show-toplevel)/projects/...", or a literal ./projects/... — those break in split-portfolio v2 mode where projects_dir resolves to a sibling repo.
REQUIRED per-block preamble (see #443): Claude executes each bash block as a separate shell invocation. The projects_dir assignment from the Path resolution section above does NOT carry into later blocks. Every bash block that writes to a projects/<name>/X path MUST start with this three-line preamble so it's self-contained:
source "$(git rev-parse --show-toplevel)/.claude/hooks/_lib-read-config.sh"
source "$(git rev-parse --show-toplevel)/.claude/hooks/_lib-portfolio-paths.sh"
projects_dir=$(portfolio_projects_dir)
# ... now write to "${projects_dir}/<name>/X"
The Path resolution section's example sources the helper once for documentation purposes; it does not absolve later blocks from sourcing it themselves. Treat each bash fence as a fresh process.
Usage
/feature-diagram create-order # current project (cwd inside workspace/<name>/ or single-project fork)
/feature-diagram create-order curios-dog # registered project
/feature-diagram reset-password --force # overwrite existing diagram
If <project-name> is omitted and cwd is the ops-fork root with multiple registered projects, the skill asks which project's inventory to read.
Output location
projects/<name>/features/<slug>.md ← the per-feature diagram (one file per feature)
projects/<name>/feature-inventory.md ← updated to link the Feature column to the new file
The features/ directory is created if missing.
Activated role
When /feature-diagram runs, activate the Tech Lead role — they own architectural slicing and the "which surfaces participate in this feature" view. See .claude/rules/role-triggers.md for the full activation protocol.
Process
1. Resolve the target
- If
<project-name>is given and the registry has it → use that project. - If no
<project-name>and cwd is insideworkspace/<name>/→ use that project. - If no
<project-name>and cwd is the ops fork root → ask which registered project (list fromapexyard.projects.yaml). - If exactly one project is registered → use it without asking.
2. Locate the inventory
The inventory file must exist at <projects_dir>/<name>/feature-inventory.md. If missing, stop and tell the operator:
No feature inventory at projects/<name>/feature-inventory.md.
Run /extract-features <project> first — that produces the inventory
this skill reads from.
3. Resolve the feature slug
The slug matches a row in the inventory's consolidated feature matrix. Match in this order:
- Exact slug match in an existing
[link text](features/<slug>.md)cell — re-runs. - Feature title match (kebab-case the title and compare) — e.g. operator passes
create-orderand a row saysCreate order. - Substring match — e.g.
passwordmatchesReset password via email. Ambiguous matches (≥ 2 rows) ask the operator to disambiguate.
If no match → exit 2 with a helpful message listing the available slugs:
No feature matches slug 'creat-order' in projects/<name>/feature-inventory.md.
Available slugs:
create-order
reset-password-via-email
bulk-delete-users
...
Re-run with one of these, or check the inventory file.
4. Extract the row's surfaces
For the matched row, parse the inventory's per-axis findings tables (HTTP routes, Data models, Async jobs, UI screens) and collect every element whose Notes / File / Source column ties back to this feature.
The inventory's row has a Source column that already names the axes that corroborated the feature (e.g. route + test + UI, model only). Use that as the primary filter:
Source says | Subgraphs populated |
|---|---|
route + test + UI + job | All four (Routes, Screens, Jobs; Models if any route writes to one) |
route + UI | Routes + Screens |
model only | Models (plus a coverage-gap note) |
doc only | Empty diagram with a coverage-gap note pointing at the README/CHANGELOG entry |
When the inventory row references specific handlers / models / jobs / components by file path (the per-axis tables in the inventory are keyed by File), follow those references and include each as a node in the matching subgraph.
5. Generate the Mermaid
Build a flowchart LR with four named subgraphs:
flowchart LR
subgraph Screens["UI Screens"]
screen_<id>["<Display name><br/>(<file>)"]
...
end
subgraph Routes["HTTP Routes"]
route_<id>["<METHOD> <path><br/>(<file>)"]
...
end
subgraph Models["Data Models"]
model_<id>["<Name><br/>(<file>)"]
...
end
subgraph Jobs["Async Jobs"]
job_<id>["<Name><br/>(<file>)"]
...
end
%% Edges inferred from the inventory row
screen_<id> -->|submits| route_<id>
route_<id> -->|reads/writes| model_<id>
route_<id> -->|enqueues| job_<id>
job_<id> -->|reads/writes| model_<id>
Empty subgraphs stay visible — render <Subgraph>_empty["(none)"] as a placeholder so the four-quadrant shape is constant across features:
subgraph Jobs["Async Jobs"]
Jobs_empty["(none)"]
end
Node IDs are stable: screen_<slug>, route_<slug>, model_<slug>, job_<slug> where <slug> is a kebab-case-ified version of the element's name. Stability matters because operators may hand-add a comment or annotation under a specific node ID; re-runs should keep those grounded.
Edge inference rules — same as documented in AgDR-0035:
| Source axis | Target axis | Edge label |
|---|---|---|
| Screen | Route | "submits" if the screen has form fields; "calls" otherwise |
| Route | Model | "reads/writes" |
| Route | Job | "enqueues" |
| Job | Model | "reads/writes" (only if the inventory row mentions both as participating) |
Don't invent edges. If the inventory row doesn't corroborate a screen → route mapping (no shared URL prefix, no shared component name), don't draw the edge — emit the nodes only and add a one-line coverage-gap note at the bottom of the file.
6. Write the file
Path: <projects_dir>/<name>/features/<slug>.md. Output template:
# <Feature title>
> Per-feature architectural slice for **<Feature title>**. Generated from the consolidated feature matrix in [`../feature-inventory.md`](../feature-inventory.md).
**Status**: <Active | Deprecated | Untested | Documented but not in code>
**Source axes**: <route + test + UI + job>
**Notes**: <Notes column from the inventory row>
## Diagram
```mermaid
flowchart LR
...
```
## Participating elements
### HTTP Routes (<N>)
| Method | Path | Handler | File |
|--------|------|---------|------|
| ... | ... | ... | ... |
### Data Models (<N>)
| Model | Fields | File |
|-------|--------|------|
| ... | ... | ... |
### Async Jobs (<N>)
| Job | Trigger | Handler | File |
|-----|---------|---------|------|
| ... | ... | ... | ... |
### UI Screens (<N>)
| Route | Component | Fields | File |
|-------|-----------|--------|------|
| ... | ... | ... | ... |
## Coverage gaps
- <Any edges the inventory didn't corroborate; any axes that came up empty>
---
_Generated by `/feature-diagram` on YYYY-MM-DD. Re-run when the feature's surfaces change._
Behaviour on existing file:
- File doesn't exist → write directly.
- File exists, no
--force→ prompt the operator with a default-no offer to overwrite. Print a diff against the proposed content if possible. - File exists,
--force→ overwrite without prompting; print a one-lineOverwriting <path>to stderr.
7. Update the inventory back-link
In <projects_dir>/<name>/feature-inventory.md's consolidated feature matrix table, replace the matched row's Feature cell with a markdown link:
| 1 | [Create order](features/create-order.md) | API + UI | Active | route + test + UI | POST /api/orders; charges Stripe; sends confirmation email |
Idempotent — if the cell already contains a link to the same target file, leave it. If the cell contains a link to a different target file (rare; manual rename), overwrite.
If the inventory is read-only (no write permission, or the operator passed --no-update-inventory) the back-link step is skipped with a one-line warning.
8. Lint the generated Mermaid
Run lint.sh against the output file:
SKILL_DIR="$(dirname "$(realpath "$0")")"
"$SKILL_DIR/lint.sh" "$out_file" || lint_rc=$?
The lint wraps the shared _lib-mermaid-lint.sh — extracts the ```mermaid flowchart block and validates each via mmdc (mermaid-cli). Graceful-degrades when Node / npx is unavailable (exit 3, advisory only).
Treat exit 1 (parse error) as a hard fail — print the lint output, ask the operator whether to (a) auto-regenerate the offending block, (b) keep the file as-is and fix by hand, or (c) re-run with --skip-lint if mmdc is misbehaving. Exit 3 (Node missing) prints a one-line warning and proceeds.
9. Confirm to the user
✓ <project>: per-feature diagram written
Diagram: <projects_dir>/<name>/features/<slug>.md
Inventory back-link: updated row <N> in feature-inventory.md
Routes: 3 Models: 2 Jobs: 1 Screens: 1
Mermaid lint: 1 of 1 block parsed cleanly
Preview: open the file on GitHub — Mermaid renders inline.
Re-run /feature-diagram <slug> --force when the feature's surfaces change.
Rules
- Read-only against the codebase. Never modify the project's source. Only writes to
projects/<name>/features/<slug>.mdand updates the inventory's back-link. - The inventory is a hard dependency. If
feature-inventory.mddoesn't exist, refuse with a pointer to/extract-features. Don't auto-run it (that's a heavyweight scan; the operator should know they're triggering it). - Don't auto-overwrite. Existing diagrams require explicit
--force. The diagrams may have been hand-edited. - Empty subgraphs stay visible. Four quadrants every time; render
(none)placeholders so the reader's eye doesn't have to re-orient between features. - Don't invent edges. Every edge must trace to a column in the inventory row. Missing corroboration becomes a coverage-gap note, not a fabricated arrow.
- Stable node IDs.
screen_<slug>/route_<slug>/model_<slug>/job_<slug>— slug is kebab-case-ified element name. Reproducibility matters; operators may annotate by ID. - Footer signature is mandatory. Every generated file ends with the
Generated by /feature-diagram on YYYY-MM-DDline so future readers know it's regenerable. - Refuse if the slug doesn't match a row. Exit 2 with the list of available slugs — no silent best-guess fallback.
When to use this
| Trigger | Use /feature-diagram? |
|---|---|
| Onboarding a new engineer to a specific feature | Yes — pairs with /handover and /extract-features |
| Planning a refactor scoped to one feature | Yes — visualises the blast radius before code edits |
| Greenfield rewrite, slicing the v1 scope by feature | Yes — each "must-have v1" feature gets a per-feature diagram showing what must be preserved |
| Major arch change to one feature (new model added, new job triggered) | Yes — re-run with --force |
| Whole-system view | No — use /c4 (topology) or /dfd (data flows) |
| User-flow preview for stakeholders | No — use /journey (product-facing HTML) |
Out of scope (v1)
- Cross-feature diagrams (showing how features depend on each other) — listed as out-of-scope in #288.
- Per-feature sequence diagrams — use
templates/architecture/sequence.mdmanually if needed. - Trust-boundary overlays — that's
/dfd's slice. If the operator wants security-scoped feature analysis, run/dfdand/feature-diagramside by side. - Auto-running
/extract-features— the inventory is a hard dependency, not an optional auto-trigger. - Cross-project per-feature diagrams — single project per invocation. Multi-project per-feature is a separate concern.
Anti-patterns
- Don't substitute
/feature-diagramfor/c4. The per-feature slice shows ONE feature's surfaces. System-wide topology lives in/c4. - Don't run
/feature-diagramfor every row on every PR. It's refresh-on-arch-change, not per-PR. The whole inventory only changes when features ship; the per-feature diagrams only change when the feature's routes / models / jobs / screens change. - Don't hand-edit a generated diagram and lose the footer. Re-runs detect the footer; without it the skill can't tell if a file is regenerable. If you must hand-edit, keep the footer line in place.
- Don't treat coverage gaps as failures. The inventory has gaps by design (the scanner can't see business rules, permission matrices, or implicit features). Per-feature diagrams inherit those gaps — that's a feature, not a bug; the gaps are surfaced explicitly so a human can fill them in.
Part of ApexYard — multi-project SDLC framework for Claude Code · MIT.
Signals
- GitHub stars
- 501
- Forks
- 274
- Last commit
- Sep 2026
ahel review
K6low
bundled executables the agent is told to run
Automated review, not a security audit. Ruleset v1+k2.
Advanced
- Catalog kind
- skill
- Gateway key
feature-diagram- Source
- github.com/me2resh/apexyard