likec4-discover
Generate a LikeC4 architecture model from code. Scripts do deterministic work; you do only naming/grouping. Never hand-write DSL syntax.
Quick one-liner (best-effort: raw titles, all proposals promoted, nothing pruned):
<skill>/scripts/discover.sh quick --root <repo> --out ./generated --system <name>
# flooded diagram (too many elements)? use file-level + routes instead:
<skill>/scripts/discover.sh quick --root <repo> --out ./generated --system <name> --granularity file
Defaults when the user gives no args: --root ., --out ./generated, --system <basename-of-root-sanitized> (fallback cloud). Run quick immediately without asking; only ask when overwriting an existing --out dir or exceeding budgets.
For curated output, run the steps below with manual labeling in between.
Requires likec4-dsl skill for DSL reference (via npx skills add https://likec4.dev/). This skill is write-path only (scan -> IR -> .c4); use @likec4/mcp for read-path queries.
Workflow
1. Scan (deterministic, no LLM judgment)
Run from the repo root:
<skill>/scripts/discover.sh scan --root ./src --out /tmp/ir.json
# flags: --root <dir> (default .), --out <file|-> (default stdout),
# --max-files 2000, --include-tests (default false),
# --include "<glob>" (repeatable, replaces defaults),
# --exclude "<glob>" (repeatable, merges with defaults),
# --no-symbols | --symbol-kinds <csv> (file-level granularity, see below)
# (calls scripts/scan.mjs; same flags)
scan.mjs walks TS/JS with node stdlib (import regex + exported class/function names for titles) and spawns scan.py --root <dir> once for Python (ast stdlib only; skipped automatically when the repo has no .py files). Granularity flags (--no-symbols, --symbol-kinds) are forwarded to scan.py.
- Granularity (default): file = parent element; module classes, top-level functions, and route handlers (FastAPI decorators / Express-style registrations) = nested components with
parent + line evidence. Symbol-free files fall back to file = component.
- Too many elements? When a scan yields dozens of files, per-function symbols flood the diagram with helpers (
pad2, sleep, esc, ...). Prefer file-level + routes: quick --granularity file, or step by step: scan --symbol-kinds route → auto-label --keep-symbols route → emit --drop-symbols. Routes always survive pruning (they carry the HTTP surface); plain functions/variables/classes are dropped. Rule of thumb: switch to file granularity when any single file contributes >5 symbols or the labeled IR exceeds ~40 elements.
- Infra/actor candidates arrive as
proposals[] (with source evidence) — promote or drop each, never auto-add.
- Default ignores:
node_modules, dist, build, .venv, __pycache__, .git + symlinks + .gitignore rules. --include replaces default extensions; --exclude merges.
- Output envelope:
{ "elements": [...] } per references/ir-schema.md. Cap ~200 elements; script exits non-zero with a clear error when over budget (narrow --include or raise budget explicitly).
2. Group and label (only LLM step)
- Default hierarchy = directory tree + fan-in/fan-out from IR
imports/calls. Keep it unless clearly wrong.
- Assign each element
fqn as <system>.<area>.<Name> and a human title. Preserve file, lang, imports, calls, extends, technology.
- Guardrails (see
references/dsl-min.md):
- Identifier:
[A-Za-z_][A-Za-z0-9_-]*, no dots, must not start with a digit. Dots separate FQN levels only.
- FQN unique project-wide. Cross-file refs always use full FQN.
- Kinds only:
person, system, container, component, database. Relationships only: http, kafka, s3, promql, inproc (+ calls, imports, inherits aliases). Fixed sets — never invent kinds.
- No direct parent-child
-> relationships.
- Every element keeps
tags: ["autogenerated"] in IR (metadata only, not rendered). Provenance in output is a file header comment (// Generated by likec4-discover ...), never per-element tags (they render as badge spam).
- Write the labeled IR to
/tmp/ir.labeled.json. Ask before exceeding element/file budgets.
3. Emit (deterministic, string template by default)
<skill>/scripts/discover.sh emit --in /tmp/ir.labeled.json --out ./generated --system <name>
# file-level + routes safety net (drops helper symbols even if the labeled IR
# still contains them):
# <skill>/scripts/discover.sh emit --in /tmp/ir.labeled.json --out ./generated --system <name> --drop-symbols
# emits spec.c4/model.c4/views.c4, runs --check when likec4 is installed,
# then validates with the likec4 CLI when available
emit.mjs has zero dependencies. It sanitizes identifiers, dedupes FQNs, and fails closed on duplicates or bad identifiers. --check additionally runs LikeC4.fromSource().getErrors() when the likec4 package is installed (skipped with a note otherwise); template output stays canonical.
- Editing replaces, never merges. Confirm before overwriting an existing
./generated.
4. Validate (required in CI, progressive locally)
npx -y likec4@1.59.3 validate --no-layout --json ./generated
# gate: .valid == true, .stats.filteredErrors == 0
- See
references/validation.md for the computedModel().elementsWhere() vitest recipe.
- If
likec4 is absent locally, emit.mjs self-check (dup FQN / bad identifier) is the minimum gate.
- Lossy round-trip (
toDSL/writeDSL) is one-way generation, never a formatter.
5. Preview / serve
npx -y likec4@1.59.3 start ./generated
# or: likec4 preview-view via MCP for file-less iteration
Constraints
- Never hand-write
.c4 syntax; always go through emit.mjs.
- Never invent element/relationship kinds or tags beyond the spec defaults without declaring them in
spec.c4.
- Never exceed file/element budgets silently — ask first.
deployment {} and dynamic views are out of scope for v1.
- For drift detection, see Erode (deferred, not part of this skill).
File map
scripts/scan.mjs, scripts/scan.py — deterministic scanners.
scripts/discover.sh — one entry point: scan (→ scan.mjs), emit (→ emit.mjs --check + CLI validate when available), and quick (scan → auto-label.mjs → emit → validate in one go).
scripts/auto-label.mjs — best-effort labeling for quick (prettified titles, import wiring incl. barrels/aliases, all proposals promoted). Reuses sanitize from emit.mjs; mirrors its default FQN assignment.
scripts/emit.mjs — deterministic emitter (template canonical + optional --check). Importable (sanitize, emitModel, checkModel) with unit tests (node --test test/*.test.mjs from repo root).
scripts/scan.py — deterministic scanner with unit tests (python3 -m unittest discover -s test from repo root).
references/ir-schema.md — IR field table + examples.
references/dsl-min.md — spec/model/views + FQN rules, condensed.
references/validation.md — validate commands + vitest recipe.
references/troubleshooting.md — common identifier/FQN/predicate errors.
assets/templates/ — starting spec.c4, model.c4, views.c4.
1---2name: likec4-discover3description: Scan TypeScript/Python repo to IR and generate validated LikeC4 model (specification+model+views). Use when user asks for architecture diagram, C4 model, likec4, code-to-diagram, or codebase overview.4license: MIT5---67# likec4-discover89Generate a LikeC4 architecture model from code. Scripts do deterministic work; you do only naming/grouping. Never hand-write DSL syntax.1011Quick one-liner (best-effort: raw titles, all proposals promoted, nothing pruned):1213```bash14<skill>/scripts/discover.sh quick --root <repo> --out ./generated --system <name>15# flooded diagram (too many elements)? use file-level + routes instead:16<skill>/scripts/discover.sh quick --root <repo> --out ./generated --system <name> --granularity file17```1819Defaults when the user gives no args: `--root .`, `--out ./generated`, `--system <basename-of-root-sanitized>` (fallback `cloud`). Run quick immediately without asking; only ask when overwriting an existing `--out` dir or exceeding budgets.2021For curated output, run the steps below with manual labeling in between.2223Requires `likec4-dsl` skill for DSL reference (via `npx skills add https://likec4.dev/`). This skill is write-path only (`scan -> IR -> .c4`); use `@likec4/mcp` for read-path queries.2425## Workflow2627### 1. Scan (deterministic, no LLM judgment)2829Run from the repo root:3031```bash32<skill>/scripts/discover.sh scan --root ./src --out /tmp/ir.json33# flags: --root <dir> (default .), --out <file|-> (default stdout),34# --max-files 2000, --include-tests (default false),35# --include "<glob>" (repeatable, replaces defaults),36# --exclude "<glob>" (repeatable, merges with defaults),37# --no-symbols | --symbol-kinds <csv> (file-level granularity, see below)38# (calls scripts/scan.mjs; same flags)39```4041- `scan.mjs` walks TS/JS with node stdlib (import regex + exported class/function names for titles) and spawns `scan.py --root <dir>` once for Python (`ast` stdlib only; skipped automatically when the repo has no `.py` files). Granularity flags (`--no-symbols`, `--symbol-kinds`) are forwarded to `scan.py`.42- Granularity (default): file = parent element; module classes, top-level functions, and route handlers (FastAPI decorators / Express-style registrations) = nested components with `parent` + `line` evidence. Symbol-free files fall back to file = component.43- Too many elements? When a scan yields dozens of files, per-function symbols flood the diagram with helpers (`pad2`, `sleep`, `esc`, ...). Prefer file-level + routes: `quick --granularity file`, or step by step: `scan --symbol-kinds route` → `auto-label --keep-symbols route` → `emit --drop-symbols`. Routes always survive pruning (they carry the HTTP surface); plain functions/variables/classes are dropped. Rule of thumb: switch to `file` granularity when any single file contributes >5 symbols or the labeled IR exceeds ~40 elements.44- Infra/actor candidates arrive as `proposals[]` (with `source` evidence) — promote or drop each, never auto-add.45- Default ignores: `node_modules, dist, build, .venv, __pycache__, .git` + symlinks + `.gitignore` rules. `--include` replaces default extensions; `--exclude` merges.46- Output envelope: `{ "elements": [...] }` per `references/ir-schema.md`. Cap ~200 elements; script exits non-zero with a clear error when over budget (narrow `--include` or raise budget explicitly).4748### 2. Group and label (only LLM step)49501. Default hierarchy = directory tree + fan-in/fan-out from IR `imports`/`calls`. Keep it unless clearly wrong.512. Assign each element `fqn` as `<system>.<area>.<Name>` and a human `title`. Preserve `file`, `lang`, `imports`, `calls`, `extends`, `technology`.523. Guardrails (see `references/dsl-min.md`):53 - Identifier: `[A-Za-z_][A-Za-z0-9_-]*`, no dots, must not start with a digit. Dots separate FQN levels only.54 - FQN unique project-wide. Cross-file refs always use full FQN.55 - Kinds only: `person, system, container, component, database`. Relationships only: `http, kafka, s3, promql, inproc` (+ `calls, imports, inherits` aliases). Fixed sets — never invent kinds.56 - No direct parent-child `->` relationships.57 - Every element keeps `tags: ["autogenerated"]` in IR (metadata only, not rendered). Provenance in output is a file header comment (`// Generated by likec4-discover ...`), never per-element tags (they render as badge spam).584. Write the labeled IR to `/tmp/ir.labeled.json`. Ask before exceeding element/file budgets.5960### 3. Emit (deterministic, string template by default)6162```bash63<skill>/scripts/discover.sh emit --in /tmp/ir.labeled.json --out ./generated --system <name>64# file-level + routes safety net (drops helper symbols even if the labeled IR65# still contains them):66# <skill>/scripts/discover.sh emit --in /tmp/ir.labeled.json --out ./generated --system <name> --drop-symbols67# emits spec.c4/model.c4/views.c4, runs --check when likec4 is installed,68# then validates with the likec4 CLI when available69```7071- `emit.mjs` has zero dependencies. It sanitizes identifiers, dedupes FQNs, and fails closed on duplicates or bad identifiers. `--check` additionally runs `LikeC4.fromSource().getErrors()` when the `likec4` package is installed (skipped with a note otherwise); template output stays canonical.72- Editing replaces, never merges. Confirm before overwriting an existing `./generated`.7374### 4. Validate (required in CI, progressive locally)7576```bash77npx -y likec4@1.59.3 validate --no-layout --json ./generated78# gate: .valid == true, .stats.filteredErrors == 079```8081- See `references/validation.md` for the `computedModel().elementsWhere()` vitest recipe.82- If `likec4` is absent locally, `emit.mjs` self-check (dup FQN / bad identifier) is the minimum gate.83- Lossy round-trip (`toDSL`/`writeDSL`) is one-way generation, never a formatter.8485### 5. Preview / serve8687```bash88npx -y likec4@1.59.3 start ./generated89# or: likec4 preview-view via MCP for file-less iteration90```9192## Constraints9394- Never hand-write `.c4` syntax; always go through `emit.mjs`.95- Never invent element/relationship kinds or tags beyond the spec defaults without declaring them in `spec.c4`.96- Never exceed file/element budgets silently — ask first.97- `deployment {}` and dynamic views are out of scope for v1.98- For drift detection, see Erode (deferred, not part of this skill).99100## File map101102- `scripts/scan.mjs`, `scripts/scan.py` — deterministic scanners.103- `scripts/discover.sh` — one entry point: `scan` (→ `scan.mjs`), `emit` (→ `emit.mjs --check` + CLI validate when available), and `quick` (scan → `auto-label.mjs` → emit → validate in one go).104- `scripts/auto-label.mjs` — best-effort labeling for `quick` (prettified titles, import wiring incl. barrels/aliases, all proposals promoted). Reuses `sanitize` from `emit.mjs`; mirrors its default FQN assignment.105- `scripts/emit.mjs` — deterministic emitter (template canonical + optional `--check`). Importable (`sanitize`, `emitModel`, `checkModel`) with unit tests (`node --test test/*.test.mjs` from repo root).106- `scripts/scan.py` — deterministic scanner with unit tests (`python3 -m unittest discover -s test` from repo root).107- `references/ir-schema.md` — IR field table + examples.108- `references/dsl-min.md` — spec/model/views + FQN rules, condensed.109- `references/validation.md` — validate commands + vitest recipe.110- `references/troubleshooting.md` — common identifier/FQN/predicate errors.111- `assets/templates/` — starting `spec.c4`, `model.c4`, `views.c4`.