agentleFS
me2reshme2resh/apexyard/.claude/skills/feature-diagram/SKILL.md

feature-diagram

Skill542 starsChanged 11 days agoMITView on GitHub

Per-feature Mermaid flowchart — routes / models / jobs / screens. Consumes /extract-features inventory.

We split this skill into the rules it gives an agent

16 rules. About 0 are common, 4 are distinctive, and 12 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈4

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 12

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Read the per-project docs dir via portfolio_projects_dir from .claude/hooks/_lib-portfolio-paths.sh.

    line 28
  2. Very specific:

    If <project-name> is omitted and cwd is the ops-fork root with multiple registered projects, the skill asks which project's inventory to …

    line 59
  3. Very specific:

    When /feature-diagram runs, activate the Tech Lead role — they own architectural slicing and the "which surfaces participate in this …

    line 72
  4. Very specific:

    If <project-name> is given and the registry has it → use that project.

    line 78
  5. Very specific:

    If no <project-name> and cwd is inside workspace/<name>/ → use that project.

    line 79
  6. Very specific:

    If no <project-name> and cwd is the ops fork root → ask which registered project (list from apexyard.projects.yaml).

    line 80
  7. Distinctive:

    If exactly one project is registered → use it without asking.

    line 81
  8. Distinctive:

    If no match → exit 2 with a helpful message listing the available slugs:

    line 101
  9. Very specific:

    When the inventory row references specific handlers / models / jobs / components by file path (the per-axis tables in the inventory are …

    line 128
  10. Distinctive:

    Don't invent edges. If the inventory row doesn't corroborate a screen → route mapping (no shared URL prefix, no shared component name), …

    line 179
  11. Very specific:

    If the inventory is read-only (no write permission, or the operator passed --no-update-inventory) the back-link step is skipped with a …

    line 252
  12. Very specific:

    Run lint.sh against the output file:

    line 256
  13. Very specific:

    Read-only against the codebase: Never modify the project's source.

    line 284
  14. Very specific:

    The inventory is a hard dependency: If feature-inventory.md doesn't exist, refuse with a pointer to /extract-features.

    line 285
  15. Distinctive:

    Don't invent edges: Every edge must trace to a column in the inventory row.

    line 288
  16. Very specific:

    Footer signature is mandatory: Every generated file ends with the Generated by /feature-diagram on YYYY-MM-DD line so future readers know …

    line 290

≈ “Common” is an estimate: at least 1 in 5 of the 308,232 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Writing: read docs/skills/shared/writing-rule.md before you write a durable artifact.

/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:

SkillSlice
/c4Whole-system topology (L1 + L2)
/dfdWhole-system data flows + trust boundaries
/processOne business process (BPMN control flow)
/journeyOne 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 sample-app            # 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 inside workspace/<name>/ → use that project.
  • If no <project-name> and cwd is the ops fork root → ask which registered project (list from apexyard.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:

  1. Exact slug match in an existing [link text](features/<slug>.md) cell — re-runs.
  2. Feature title match (kebab-case the title and compare) — e.g. operator passes create-order and a row says Create order.
  3. Substring match — e.g. password matches Reset 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 saysSubgraphs populated
route + test + UI + jobAll four (Routes, Screens, Jobs; Models if any route writes to one)
route + UIRoutes + Screens
model onlyModels (plus a coverage-gap note)
doc onlyEmpty 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 axisTarget axisEdge label
ScreenRoute"submits" if the screen has form fields; "calls" otherwise
RouteModel"reads/writes"
RouteJob"enqueues"
JobModel"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-line Overwriting <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

  1. Read-only against the codebase. Never modify the project's source. Only writes to projects/<name>/features/<slug>.md and updates the inventory's back-link.
  2. The inventory is a hard dependency. If feature-inventory.md doesn'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).
  3. Don't auto-overwrite. Existing diagrams require explicit --force. The diagrams may have been hand-edited.
  4. Empty subgraphs stay visible. Four quadrants every time; render (none) placeholders so the reader's eye doesn't have to re-orient between features.
  5. 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.
  6. 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.
  7. Footer signature is mandatory. Every generated file ends with the Generated by /feature-diagram on YYYY-MM-DD line so future readers know it's regenerable.
  8. 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

TriggerUse /feature-diagram?
Onboarding a new engineer to a specific featureYes — pairs with /handover and /extract-features
Planning a refactor scoped to one featureYes — visualises the blast radius before code edits
Greenfield rewrite, slicing the v1 scope by featureYes — 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 viewNo — use /c4 (topology) or /dfd (data flows)
User-flow preview for stakeholdersNo — 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.md manually if needed.
  • Trust-boundary overlays — that's /dfd's slice. If the operator wants security-scoped feature analysis, run /dfd and /feature-diagram side 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-diagram for /c4. The per-feature slice shows ONE feature's surfaces. System-wide topology lives in /c4.
  • Don't run /feature-diagram for 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.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

Reviews can't be read right now.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.