diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..b85968c --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Xingyu Chen + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 4ff05d7..0d31531 100644 --- a/README.md +++ b/README.md @@ -1,192 +1,188 @@ -# codemap +# 🧹 codemap -A [Claude Code](https://claude.com/claude-code) **Agent Skill** that builds and -incrementally maintains an **interactive architecture map + per-module code-quality -audit** for any codebase. +**A code janitor for AI coding agents.** Point it at any repo and it draws an +**interactive architecture map**, scores **every module 0–100** for technical debt, and +helps you **clean up the spaghetti** — incrementally, one commit at a time. -It decomposes a project into *functional* modules (not files), draws their dependency -graph as a layered, clickable HTML page, and scores each module 0–100 for code health — -hunting for monkeypatching, fallbacks, legacy/dead code, stubs, dual-format handling, -bloat, duplication, and glue. Every module's score comes from an **independent -subagent** against a fixed rubric. It's **incremental**: a per-module content hash means -re-runs only re-audit what changed. +![Claude Code skill](https://img.shields.io/badge/Claude%20Code-skill-f59e0b) +![works with Codex](https://img.shields.io/badge/works%20with-Codex%20%2F%20any%20agent-7c8794) +![Python 3 · stdlib only](https://img.shields.io/badge/python-3%20·%20stdlib%20only-3776ab) +![language agnostic](https://img.shields.io/badge/langs-Py%20·%20TS%20·%20Rust%20·%20C%23%20·%20C%2B%2B-555) +![license MIT](https://img.shields.io/badge/license-MIT-blue) -## What you get +> Every codebase rots into a 屎山 (spaghetti / big ball of mud) eventually — monkeypatches, +> silent fallbacks, dead "legacy" paths, half-finished stubs, copy-pasted duplication, +> god-files, and valueless glue. **codemap finds that rot, ranks it, and hands an AI agent +> a clean punch-list to fix it** — with a regression-gated fix loop so cleanup never breaks +> your build. -Three coupled artifacts, kept in sync: +![architecture map](examples/01-map.png) -| File | What | Where (default) | +--- + +## Why codemap + +Most "architecture diagram" tools draw *files and imports*. codemap is different: + +- **Functional modules, not files.** It groups code into the capabilities that actually + matter (a store, a handler group, a feature, a plugin) and lays them out along the + real data-flow. +- **It grades the rot.** Every module gets a health **score (0–100) and grade (A–F)** plus + concrete `file:line` findings, hunting specifically for the smells that make code + unmaintainable: `monkeypatch`, `fallback`, `silent-except`, `legacy`/dead code, `stub`, + `fake-output`, `dual-format`, `bloat`, `duplication`, `glue`, `god-component`, … +- **Independent, honest scoring.** Each module is audited by a **separate AI subagent** + against a fixed rubric — no single pass rubber-stamping the whole repo. +- **Incremental + git-aware.** A per-module content hash + the last-run commit mean re-runs + only re-audit what changed, and `update` shows you the **commits since last time** and + which modules they touched. +- **Cleanup that can't regress.** `fix` runs a four-role loop — lock a test baseline → + fix → an **independent acceptance check** proves the pre-fix tests still pass → re-score. + +It's the maintenance pass you never have time to do, turned into something an agent can +run on a schedule. + +## Screenshots + +| Select a module — dependencies + audit | Audit report | Editable standard | |---|---|---| -| `config.json` | your saved **preferences** (UI language, output location, title) | `/.codemap/` | -| `modules.json` | the **source of truth** (modules, deps, coupling, LoC, hash, score, findings) | `/.codemap/` | -| `architecture-map.html` | self-contained **interactive map** (health coloring, filters, dependency highlighting, audit report) | `/.codemap/` | -| `architecture-audit.md` | the written **report** (per-layer scores, LoC table, worst offenders, themes) | `/.codemap/` | +| ![module](examples/02-module.png) | ![report](examples/03-report.png) | ![standard](examples/04-standard.png) | -Everything lives in **`/.codemap/`** by default. On first run (`generate`) the -tool asks your preferences — UI language, output location (point it at `docs/` if you -want the HTML/MD committed/visible), project title — and saves them to -`.codemap/config.json`. The HTML and MD are **generated** from `modules.json` and must -never be hand-edited. - -### Interactive map features -- Layered bands top→bottom along the data-flow; click a module to highlight what it - **calls** (downstream) and what **depends on it** (upstream). -- Per-module **health score + grade (A–F)**, smell tags, and concrete `file:line` findings. -- Color modes: **coupling** or **health** (problems pop amber/red, healthy modules - recede to a muted green — colorblind-friendly, the cue is saturation not just hue). -- **Filters**: by grade level (≤ B/C/D/F) and by issue tag; live match count. -- **Audit report** view: averages, grade spread, worst offenders, cross-cutting themes. -- **Standard page**: a built-in "Standard" view explaining the score→grade rubric, the - finding severities, and every smell tag — so the scores are self-documenting. -- **i18n**: set `meta.lang` to `"en"` or `"zh"` (module names are never translated). +- **Click any module** to highlight what it calls (downstream) and what depends on it + (upstream), with its score, smell tags, and `file:line` findings. +- **Health vs coupling** color modes — problems pop amber/red, healthy modules recede to a + muted green (colorblind-friendly; the cue is saturation, not just hue). +- **Filter** by grade (≤ B/C/D/F) or by issue tag; jump straight to the worst offenders. +- **Editable Standard page** — change descriptions, **add your own issue tags** to capture + *your* definition of a problem, and Export to `standard.json`; future audits use it. +- **i18n** — English or Chinese UI (`meta.lang`); module names are never translated. +- **Copy-fix button** on each module — copies `/codemap fix ` to paste into your agent. ## Languages -Language-agnostic. The scripts count LoC and hash bytes for **any** text source, and -`paths` are plain globs, so it works for Python, **TypeScript/JS, Rust, C#/.NET, C/C++**, -Go, Java, Swift, and more. Build/test/generated trees are excluded out of the box -(`target/`, `bin/`, `obj/`, `node_modules/`, `cmake-build*`, `__pycache__/`, `dist/`, -`*.d.ts`, `*.Designer.cs`, …). The audit rubric names *behaviors*, not syntax — -`reference/STANDARDS.md` maps each smell to its per-language form (e.g. `any-escape` = -`as any` / `dynamic` / `void*` / `reinterpret_cast` / `unsafe`). +Language-agnostic. LoC and hashing work on **any** text source and `paths` are plain globs, +so it covers **Python, TypeScript/JS, Rust, C#/.NET, C/C++, Go, Java, Swift**, and more. +Build/test/generated trees are excluded out of the box (`target/`, `bin/`, `obj/`, +`node_modules/`, `cmake-build*`, `__pycache__/`, `dist/`, `*.d.ts`, `*.Designer.cs`, …). +The rubric names *behaviors*, not syntax — `reference/STANDARDS.md` maps each smell to its +per-language form (e.g. `any-escape` = `as any` / `dynamic` / `void*` / `reinterpret_cast` +/ `unsafe`). ## Requirements -- **Python 3** (standard library only — no `pip install`, no external packages). -- **An AI coding agent** to drive the audit/fix/test steps — **Claude Code** (native - skill) or **any other agent that can read instructions and spawn sub-tasks**, e.g. - OpenAI **Codex** (see [Using with Codex / other agents](#using-with-codex--other-agents)). +- **Python 3** — standard library only. No `pip install`, no external packages. +- **An AI coding agent** to drive the audit/fix/test steps: **Claude Code** (native skill) + or **any agent that reads instructions and spawns sub-tasks**, e.g. OpenAI **Codex** + (see [Using with Codex](#using-with-codex--other-agents)). - A browser to open the generated HTML. That's it. ## Install -A skill is just a folder under `~/.claude/skills/`. Clone this repo into it: +A Claude Code skill is just a folder under `~/.claude/skills/`: ```bash -git clone ~/.claude/skills/codemap +git clone https://github.com/Asixa/codemap-skill ~/.claude/skills/codemap ``` -(Windows PowerShell: `git clone $env:USERPROFILE\.claude\skills\codemap`.) +(Windows PowerShell: `git clone https://github.com/Asixa/codemap-skill $env:USERPROFILE\.claude\skills\codemap`.) -Restart Claude Code (or start a new session). The skill appears as `/codemap`. +Restart Claude Code (or start a new session). The skill appears as **`/codemap`**. ## Usage -Talk to Claude in natural language, or use the subcommands. Claude reads `SKILL.md` -and runs the scripts; the **audit / fix / test** steps spawn independent subagents. +Talk to Claude in plain language, or use the subcommands. On the first run, codemap asks +your preferences (UI language, output location, project title) and saves them to +`/.codemap/config.json`. Everything it produces lives in `/.codemap/`. | Command | Does | |---|---| -| `/codemap generate` | first build: decompose → scan → audit every module → render | -| `/codemap check` | read-only: is the map stale? lists drifted / new / deleted modules | -| `/codemap update` | incremental: re-audit only changed modules, re-render | -| `/codemap test ` | generate tests (regression net) for a module | -| `/codemap fix ` | regression-gated fix: lock baseline → fix → independent acceptance → re-score | +| `/codemap generate` | first build: ask prefs → decompose into modules → scan → audit every module → render | +| `/codemap check` | read-only: is the map stale? shows commits since last run + drifted / new / deleted modules | +| `/codemap update` | incremental + git-aware: re-audit only changed modules, re-render | +| `/codemap test ` | generate a regression-net of tests for a module | +| `/codemap fix ` | regression-gated cleanup: lock baseline → fix → independent acceptance → re-score | -You can also run the deterministic scripts directly (no AI needed for these): +The deterministic scripts (no AI needed) can also be run by hand: ```bash S=~/.claude/skills/codemap -# what changed since last audit — incl. a `git` block listing the commits since the -# last codemap run (meta.rev) and which modules they touched -python3 $S/scripts/scan.py --root . --state .codemap/modules.json -# after an update, cache the current HEAD as the new baseline for next time -python3 $S/scripts/scan.py --root . --state .codemap/modules.json --stamp-rev -# find modules to act on without reading the whole state (token-cheap, for agents) +# what changed since last run — a `git` block lists commits + affected modules +python3 $S/scripts/scan.py --root . --state .codemap/modules.json +# cache the current HEAD as the new baseline (end of an update) +python3 $S/scripts/scan.py --root . --state .codemap/modules.json --stamp-rev +# pick targets cheaply, without reading the whole state (for agents) python3 $S/scripts/query.py --state .codemap/modules.json --max-grade C --format ids python3 $S/scripts/query.py --state .codemap/modules.json --tag dual-format -# regenerate the HTML + MD from the state -python3 $S/scripts/render.py --state .codemap/modules.json \ - --template $S/assets/template.html \ - --out-html .codemap/architecture-map.html --out-md .codemap/architecture-audit.md +# regenerate the HTML + report from the state +python3 $S/scripts/render.py --state .codemap/modules.json --template $S/assets/template.html \ + --out-html .codemap/codemap.html --out-md .codemap/codemap.md ``` > On Windows use `python` instead of `python3`. ## Using with Codex / other agents -The skill mechanism is Claude-specific, but the **engine is tool-agnostic**: the four -scripts are deterministic stdlib Python, and the workflow + rubric are plain Markdown -(`SKILL.md`, `reference/STANDARDS.md`). Any capable agent can drive it. +The skill mechanism is Claude-specific, but the **engine is tool-agnostic** — four +deterministic stdlib-Python scripts plus a Markdown workflow and rubric. **OpenAI Codex** +auto-reads the shipped **`AGENTS.md`**. To use codemap from Codex (or Cursor, Aider, …): -**OpenAI Codex** auto-reads an `AGENTS.md` in the working directory — this repo ships one -that points Codex at the workflow and rubric. To use codemap from Codex (or Cursor, -Aider, etc.): - -1. Make the tool available — clone this repo somewhere the agent can read it, e.g. - `git clone ~/.codemap` (or vendor it into your project). -2. Tell the agent: *"Use the codemap tool at `` to build/update the architecture - map for this project. Follow its `SKILL.md`; score each module with a separate - sub-task using `reference/STANDARDS.md`."* -3. The agent runs the same commands shown above (`scan.py` → per-module audit → - `apply_audit.py` → `render.py`), using `query.py` to pick targets cheaply. - -The deterministic parts (scan / query / render / apply_audit) you can also run **by -hand** with no agent at all — only the *scoring*, *fixing*, and *test-writing* need a -model, and those just follow `reference/STANDARDS.md`. +1. Clone this repo somewhere the agent can read, e.g. `git clone ~/.codemap`. +2. Tell the agent: *"Use the codemap tool at `` to map/audit this project — follow + its `SKILL.md`; score each module with a separate sub-task per `reference/STANDARDS.md`."* +3. It runs the same `scan → audit → apply_audit → render` loop, using `query.py` to target. ## How it works ``` -modules.json ──scan.py──▶ + LoC & content hash per module (stale = hash != auditedHash) - │ (decomposition + descriptions are authored by the model) +modules.json ──scan.py──▶ + LoC, content hash & git diff (stale = hash != auditedHash) + │ (decomposition + module descriptions: authored by the agent) │◀─apply_audit.py── one INDEPENDENT subagent's score per module (fixed rubric) │◀─query.py────────── token-cheap targeting (by grade / tag / severity / staleness) - └──render.py────────▶ architecture-map.html + architecture-audit.md + └──render.py────────▶ codemap.html + codemap.md ``` -- **Four separate subagent roles, never merged**: *auditor* (scores), *test-author* - (writes tests), *fixer* (changes code), *acceptance/verifier* (proves no regression). - A `fix` is accepted only when an independent acceptance subagent shows the pre-fix - green tests are still green and the build is clean. -- Tests are excluded from a module's audit scope (they're the regression net, tracked - separately in the module's `tests` field). +`modules.json` is the source of truth (commit it for an audit history); the HTML/MD are +pure projections, regenerated by `render.py`. **Four separate subagent roles, never +merged:** *auditor* (scores), *test-author* (writes tests), *fixer* (changes code), +*acceptance/verifier* (proves no regression). Tests are the regression net and are kept +out of a module's own audit scope. + +## Customizing the standard (capture *your* "屎山") + +The scoring standard is **data, not code** (`reference/standard.json`: rubric, severities, +coupling, and issue tags with descriptions). Open the **Standard** page in the map → **Edit** +→ tweak descriptions, **add your own tags**, then **Export** to +`/.codemap/standard.json`. Custom tags flow through the whole map and are used by +future audits. The prose version + the exact subagent prompt live in `reference/STANDARDS.md`. ## Repository layout ``` codemap/ - SKILL.md # the orchestration instructions Claude reads - README.md # this file + SKILL.md # the orchestration the agent reads + AGENTS.md # entry point for Codex / other agents + README.md + LICENSE # MIT reference/ - STANDARDS.md # the scoring rubric, smell taxonomy, severity, subagent prompts - DATA_MODEL.md # the modules.json schema - scripts/ # deterministic, stdlib-only Python - scan.py # LoC + content hash + staleness report - query.py # filter modules (grade/tag/severity/...) → ids/paths/findings - apply_audit.py # merge one subagent's audit result into the state - render.py # modules.json → HTML + MD + STANDARDS.md # scoring rubric, smell taxonomy, severities, subagent prompts + DATA_MODEL.md # modules.json schema + standard.json # the machine-readable default standard (overridable per project) + scripts/ # deterministic, stdlib-only Python + scan.py # LoC + content hash + git diff + staleness + query.py # filter modules (grade/tag/severity/…) → ids/paths/findings + apply_audit.py # merge one subagent's audit into the state + render.py # modules.json → HTML + report assets/ - template.html # the interactive map shell (data injected at render time) + template.html # the interactive map shell (data injected at render time) + examples/ # the screenshots above ``` -## Customizing the standard (capture your own "what's a problem") +## License -The standard is **data, not code** — it lives in `reference/standard.json` (rubric, -severities, coupling, and the issue tags with descriptions). Two ways to customize: +[MIT](LICENSE) © 2026 Xingyu Chen. -- **In the map**: open the **Standard** page (header button), click **Edit**, change any - description, add your own tags (`+ Tag`), then **Export** → save the downloaded - `standard.json` to `/.codemap/standard.json`. Edits are kept in the - browser until you export. Custom tags flow through the whole map (cards, filters, - report) and are used by future audits. -- **By file**: copy `reference/standard.json` to `/.codemap/standard.json` - and edit it. `render.py` prefers the project file over the skill default. +--- -The prose version + the exact subagent audit prompt live in `reference/STANDARDS.md` -(keep the two in sync when changing the shipped defaults). - -## "Fix this" buttons & automation - -Each module's detail panel has a **Copy fix prompt** button — it copies `/codemap fix -` to the clipboard for you to paste into Claude Code (or adapt for Codex). The -page intentionally does **not** auto-launch an agent: a generated static HTML has no -privileged channel to the Claude CLI/agent, and auto-launching the CLI is fragile and -increasingly restricted. Copy-to-clipboard is the robust, future-proof bridge. - -## Notes - -- `modules.json` is meant to be **committed** with your project — it's the audit history - and what makes diffs/incrementality reviewable. -- The engine is **language-agnostic**: `paths` globs and LoC counting work for any stack; - the audit subagent reads whatever code the globs point at. +Keywords: code quality · technical debt · refactoring · code janitor · legacy code +cleanup · architecture visualization · dependency graph · static analysis · code audit · +Claude Code skill · Codex · AI agents · spaghetti code · 屎山. diff --git a/SKILL.md b/SKILL.md index 7016623..800abba 100644 --- a/SKILL.md +++ b/SKILL.md @@ -12,15 +12,15 @@ description: >- "is the arch map current", "update the architecture diagram", "fix module X". --- -# Architecture Audit & Map +# codemap — Architecture Map & Code-Quality Audit Builds and maintains three coupled artifacts for a project: 1. **`modules.json`** — the source of truth: every *functional* module (not file) with its paths, dependencies, coupling, LoC, content hash, score, grade, tags, findings. -2. **`architecture-map.html`** — a self-contained interactive map (layered modules, +2. **`codemap.html`** — a self-contained interactive map (layered modules, dependency highlighting, health coloring, audit-report view). -3. **`architecture-audit.md`** — the written report (per-layer scores, per-module LoC +3. **`codemap.md`** — the written report (per-layer scores, per-module LoC table, worst offenders, cross-cutting themes). The HTML and MD are **always regenerated** from `modules.json` by `render.py`. Never @@ -51,7 +51,7 @@ prompt) and `standard.json` (the machine copy) in sync if you change the default - `config.json` — the user's saved preferences (UI language, output location, title…). - `modules.json` — the state (source of truth). - `standard.json` — optional per-project custom audit standard. - - `architecture-map.html` + `architecture-audit.md` — the generated outputs (default). + - `codemap.html` + `codemap.md` — the generated outputs (default). The output location is a user preference: if they want the HTML/MD committed/visible, let them point it at `docs/` instead (ask — see `generate` step 0). Set @@ -129,7 +129,7 @@ Use when no `modules.json` exists yet (this is also "init"). Write `config.json` like: ```json {"lang":"zh","project":"My App","subtitle":"…","outputDir":".codemap", - "htmlFile":"architecture-map.html","mdFile":"architecture-audit.md"} + "htmlFile":"codemap.html","mdFile":"codemap.md"} ``` and apply it to `meta` when you build `modules.json`. Re-read `config.json` on later runs so preferences persist. diff --git a/examples/01-map.png b/examples/01-map.png new file mode 100644 index 0000000..3a9cf14 Binary files /dev/null and b/examples/01-map.png differ diff --git a/examples/02-module.png b/examples/02-module.png new file mode 100644 index 0000000..9e8231e Binary files /dev/null and b/examples/02-module.png differ diff --git a/examples/03-report.png b/examples/03-report.png new file mode 100644 index 0000000..2405ff9 Binary files /dev/null and b/examples/03-report.png differ diff --git a/examples/04-standard.png b/examples/04-standard.png new file mode 100644 index 0000000..84edbb8 Binary files /dev/null and b/examples/04-standard.png differ diff --git a/examples/sample-project/codemap.html b/examples/sample-project/codemap.html new file mode 100644 index 0000000..618f4fa --- /dev/null +++ b/examples/sample-project/codemap.html @@ -0,0 +1,628 @@ + + + + + + +Functional Architecture Map + + + +
+
+
+
+

Functional Architecture Map

+
+
+
+ + + + + + + + +
+
+
+ + + + + + +
+
click a module · scroll to pan
+
+ +
+ + + + + diff --git a/examples/sample-project/codemap.md b/examples/sample-project/codemap.md new file mode 100644 index 0000000..e7047fd --- /dev/null +++ b/examples/sample-project/codemap.md @@ -0,0 +1,186 @@ + + +# Acme Storefront — Functional Module Quality Audit + +> **Interactive view:** [`.codemap/codemap.html`](codemap.html) — per-module scores, findings, LoC, and the dependency graph. This file is the written report. + +**Generated:** 2026-01-01 · **Modules:** 49 · **Size:** ≈ 28,600 LoC · 214 files (sample) + +## Health by layer + +| Layer | Modules | Avg score | +|---|--:|--:| +| Frontend · Shell & Routing | 3 | 87 | +| Frontend · Pages | 7 | 73 | +| Frontend · State Stores | 6 | 79 | +| Frontend · Transport | 5 | 80 | +| Backend · API Routes | 6 | 77 | +| Backend · Services | 8 | 73 | +| Backend · Domain Core | 5 | 89 | +| Backend · Persistence | 3 | 82 | +| Backend · Workers & Jobs | 3 | 77 | +| Integrations | 3 | 74 | + +## Per-module lines of code & score + +_LoC is the representative file/folder per module; folder-level modules overlap and are not additive._ + +### Frontend · Shell & Routing + +| Module | LoC | Score | Tags | +|---|--:|:--|:--| +| App Shell | 420 | 84 B | — | +| Navigation | 240 | 88 B | — | +| Router | 180 | 90 A | — | + +### Frontend · Pages + +| Module | LoC | Score | Tags | +|---|--:|:--|:--| +| Admin | 2,100 | 66 C | bloat, any-escape | +| Checkout | 1,860 | 58 D | god-component, dual-format, fallback | +| Cart | 1,420 | 70 C | god-component | +| Product | 1,240 | 72 C | bloat | +| Catalog | 980 | 78 B | — | +| Account | 760 | 82 B | — | +| Search | 540 | 84 B | — | + +### Frontend · State Stores + +| Module | LoC | Score | Tags | +|---|--:|:--|:--| +| checkoutStore | 480 | 62 C | dual-format, legacy | +| catalogStore | 410 | 86 B | — | +| cartStore | 360 | 74 C | duplication | +| authStore | 290 | 80 B | — | +| searchStore | 220 | 82 B | — | +| uiStore | 150 | 88 B | — | + +### Frontend · Transport + +| Module | LoC | Score | Tags | +|---|--:|:--|:--| +| apiClient | 690 | 68 C | glue, bloat | +| wsClient | 230 | 84 B | — | +| adminClient | 180 | 80 B | — | +| paymentsClient | 140 | 86 B | — | +| pricingClient | 110 | 84 B | — | + +### Backend · API Routes + +| Module | LoC | Score | Tags | +|---|--:|:--|:--| +| orders routes | 610 | 74 C | dual-format | +| API Gateway | 540 | 88 B | — | +| payments routes | 480 | 55 D | stub, fallback | +| auth routes | 420 | 78 B | silent-except | +| products routes | 360 | 84 B | — | +| search routes | 240 | 82 B | — | + +### Backend · Services + +| Module | LoC | Score | Tags | +|---|--:|:--|:--| +| OrderService | 1,480 | 64 C | duplication, bloat | +| PricingEngine | 880 | 70 C | over-fit | +| InventoryService | 540 | 78 B | — | +| AuthService | 520 | 80 B | — | +| SearchService | 470 | 76 B | fallback | +| CatalogService | 430 | 86 B | — | +| NotificationService | 300 | 84 B | — | +| PaymentService | 260 | 48 D | stub, fake-output | + +### Backend · Domain Core + +| Module | LoC | Score | Tags | +|---|--:|:--|:--| +| Order | 380 | 88 B | — | +| Product | 260 | 90 A | — | +| User | 210 | 88 B | — | +| Money | 120 | 92 A | — | +| TokenUtil | 90 | 86 B | — | + +### Backend · Persistence + +| Module | LoC | Score | Tags | +|---|--:|:--|:--| +| Repository | 640 | 72 C | duplication | +| Migrations | 220 | 85 B | — | +| DB Pool | 180 | 90 A | — | + +### Backend · Workers & Jobs + +| Module | LoC | Score | Tags | +|---|--:|:--|:--| +| WebhookDispatcher | 340 | 68 C | silent-except, legacy | +| EmailWorker | 260 | 80 B | — | +| Templates | 150 | 84 B | — | + +### Integrations + +| Module | LoC | Score | Tags | +|---|--:|:--|:--| +| Stripe Gateway | 280 | 78 B | — | +| Shipping Provider | 230 | 74 C | glue | +| Analytics | 190 | 70 C | silent-except | + +## Worst offenders + +- **PaymentService (48/D)** — services/payment.py:31: charge()/refund() return a canned `{status:'succeeded'}` — sandbox stub, no real gateway call. +- **payments routes (55/D)** — api/payments.py:44: webhook handler always returns 200 without verifying the signature (stub). +- **Checkout (58/D)** — src/pages/Checkout.tsx: 1860-line god-component mixing the address/shipping/payment steps, validation and direct API calls. +- **checkoutStore (62/C)** — src/stores/checkout.ts:40: reads both snake_case and camelCase address fields (dual-format). +- **OrderService (64/C)** — services/order.py: 1480-line service; the order state machine is duplicated between place() and fulfill(). +- **Admin (66/C)** — src/pages/Admin.tsx: 2100-line page: reports, tables and editors all in one file. +- **apiClient (68/C)** — src/transport/apiClient.ts: ~50 one-line get/post wrappers that only forward args (glue) — generate or collapse to a typed client. +- **WebhookDispatcher (68/C)** — workers/webhooks.py:55: `except: pass` swallows delivery errors — failed webhooks vanish. +- **Cart (70/C)** +- **PricingEngine (70/C)** — services/pricing.py:120: discount rules hardcoded to the current promo set (over-fit). + +## All findings + +### HIGH (4) + +- **Checkout** · `src/pages/Checkout.tsx` — 1860-line god-component mixing the address/shipping/payment steps, validation and direct API calls. +- **payments routes** · `api/payments.py:44` — webhook handler always returns 200 without verifying the signature (stub). +- **OrderService** · `services/order.py` — 1480-line service; the order state machine is duplicated between place() and fulfill(). +- **PaymentService** · `services/payment.py:31` — charge()/refund() return a canned `{status:'succeeded'}` — sandbox stub, no real gateway call. + +### MED (13) + +- **Product** · `src/pages/Product.tsx` — 1240-line component: gallery, variant picker and reviews in one file. +- **Checkout** · `src/pages/Checkout.tsx:412` — reads both `postal_code` and `postalCode` from the address form (dual-format). +- **Admin** · `src/pages/Admin.tsx` — 2100-line page: reports, tables and editors all in one file. +- **cartStore** · `src/stores/cart.ts:90` — cart totals re-implemented here and in PricingEngine (duplication). +- **checkoutStore** · `src/stores/checkout.ts:40` — reads both snake_case and camelCase address fields (dual-format). +- **apiClient** · `src/transport/apiClient.ts` — ~50 one-line get/post wrappers that only forward args (glue) — generate or collapse to a typed client. +- **orders routes** · `api/orders.py:88` — accepts both the legacy and v2 cart payload shapes (dual-format). +- **payments routes** · `api/payments.py:70` — falls back to marking the order paid when the provider call times out. +- **OrderService** · `services/order.py:620` — inventory reservation logic copy-pasted from InventoryService. +- **PricingEngine** · `services/pricing.py:120` — discount rules hardcoded to the current promo set (over-fit). +- **PaymentService** · `services/payment.py:88` — 'TODO: wire the real provider before launch.' +- **Repository** · `data/repo.py` — per-entity CRUD copy-pasted across 9 repositories — extract a base. +- **WebhookDispatcher** · `workers/webhooks.py:55` — `except: pass` swallows delivery errors — failed webhooks vanish. + +### LOW (9) + +- **Checkout** · `src/pages/Checkout.tsx:980` — silent catch around the shipping-rate fetch falls back to a flat rate. +- **Admin** · `src/pages/Admin.tsx:300` — several `as any` casts around the chart library. +- **checkoutStore** · `src/stores/checkout.ts:8` — legacy single-step draft kept for old links. +- **apiClient** · `src/transport/apiClient.ts:1` — one 690-line file mixing transport with the whole endpoint surface. +- **auth routes** · `api/auth.py:140` — broad except around the OAuth token exchange logs but swallows the cause. +- **SearchService** · `services/search.py:80` — documented fallback to SQL LIKE when Elasticsearch is unreachable. +- **WebhookDispatcher** · `workers/webhooks.py:12` — legacy v1 payload path kept alongside v2. +- **Shipping Provider** · `integrations/shipping.py` — adapter forwards every field unchanged (glue). +- **Analytics** · `integrations/analytics.py:22` — fire-and-forget send swallows failures silently. + +## Cross-cutting themes + +- **Payments is the weakest area.** PaymentService and the payments routes are still sandbox stubs (fake-output / stub) — real provider integration is unfinished, yet it is already wired into checkout. +- **Checkout and Order carry the most debt.** checkout_page, cartStore and OrderService are god-components with duplicated state-machine logic; the multi-step checkout mixes UI, validation and API calls in one file. +- **Dual-format is creeping in at the order boundary.** orders routes, checkoutStore and the checkout page accept both legacy and v2 payload shapes — normalize once at the transport layer instead. +- **apiClient is mostly glue.** ~50 near-identical endpoint wrappers add no value; generate them or collapse to a single typed client. + diff --git a/examples/sample-project/modules.json b/examples/sample-project/modules.json new file mode 100644 index 0000000..88bdd3b --- /dev/null +++ b/examples/sample-project/modules.json @@ -0,0 +1,1081 @@ +{ + "meta": { + "project": "Acme Storefront", + "subtitle": "Sample project — a codemap demo", + "lang": "en", + "generatedAt": "2026-01-01", + "htmlPath": ".codemap/codemap.html", + "mdPath": ".codemap/codemap.md", + "spineDesc": "A shopper opens a Product page → the cartStore calls the API Gateway → the orders route hands off to OrderService → which writes the Order aggregate through the Repository.", + "tracked_loc": 28640, + "tracked_files": 214, + "locLine": "≈ 28,600 LoC · 214 files (sample)" + }, + "bands": [ + { + "id": "shell", + "tier": "fe", + "t": "Frontend · Shell & Routing", + "d": "layout, routing, navigation" + }, + { + "id": "pages", + "tier": "fe", + "t": "Frontend · Pages", + "d": "screens the user sees" + }, + { + "id": "stores", + "tier": "fe", + "t": "Frontend · State Stores", + "d": "one store per domain" + }, + { + "id": "transport", + "tier": "fe", + "t": "Frontend · Transport", + "d": "REST + WebSocket clients" + }, + { + "id": "wire", + "wire": true, + "t": "◀ REST / WebSocket ▶" + }, + { + "id": "api", + "tier": "be", + "t": "Backend · API Routes", + "d": "HTTP endpoints" + }, + { + "id": "services", + "tier": "be", + "t": "Backend · Services", + "d": "business logic" + }, + { + "id": "domain", + "tier": "be", + "t": "Backend · Domain Core", + "d": "entities & value objects" + }, + { + "id": "data", + "tier": "be", + "t": "Backend · Persistence", + "d": "data access" + }, + { + "id": "workers", + "tier": "be", + "t": "Backend · Workers & Jobs", + "d": "async processing" + }, + { + "id": "integrations", + "tier": "be", + "t": "Integrations", + "d": "third-party adapters" + } + ], + "spine": [ + "product_page", + "cart_store", + "api_client", + "api_gw", + "orders_api", + "order_svc", + "order_model", + "repo" + ], + "reportThemes": [ + [ + "Payments is the weakest area", + "PaymentService and the payments routes are still sandbox stubs (fake-output / stub) — real provider integration is unfinished, yet it is already wired into checkout." + ], + [ + "Checkout and Order carry the most debt", + "checkout_page, cartStore and OrderService are god-components with duplicated state-machine logic; the multi-step checkout mixes UI, validation and API calls in one file." + ], + [ + "Dual-format is creeping in at the order boundary", + "orders routes, checkoutStore and the checkout page accept both legacy and v2 payload shapes — normalize once at the transport layer instead." + ], + [ + "apiClient is mostly glue", + "~50 near-identical endpoint wrappers add no value; generate them or collapse to a single typed client." + ] + ], + "modules": [ + { + "id": "app_shell", + "label": "App Shell", + "band": "shell", + "path": "src/app/AppShell.tsx", + "coupling": "core", + "deps": [ + "router", + "ui_store", + "auth_store" + ], + "desc": "Root component: layout, providers, top-level routing and the error boundary.", + "loc": 420, + "score": 84, + "grade": "B", + "tags": [], + "findings": [] + }, + { + "id": "router", + "label": "Router", + "band": "shell", + "path": "src/app/router.tsx", + "coupling": "high", + "deps": [], + "desc": "Client-side route table and auth guards.", + "loc": 180, + "score": 90, + "grade": "A", + "tags": [], + "findings": [] + }, + { + "id": "nav", + "label": "Navigation", + "band": "shell", + "path": "src/app/Nav.tsx", + "coupling": "low", + "deps": [ + "router", + "auth_store" + ], + "desc": "Top nav, sidebar and breadcrumbs.", + "loc": 240, + "score": 88, + "grade": "B", + "tags": [], + "findings": [] + }, + { + "id": "catalog_page", + "label": "Catalog", + "band": "pages", + "path": "src/pages/Catalog.tsx", + "coupling": "med", + "deps": [ + "catalog_store", + "search_store" + ], + "desc": "Product listing with filters, sorting and pagination.", + "loc": 980, + "score": 78, + "grade": "B", + "tags": [], + "findings": [] + }, + { + "id": "product_page", + "label": "Product", + "band": "pages", + "path": "src/pages/Product.tsx", + "coupling": "med", + "deps": [ + "catalog_store", + "cart_store" + ], + "desc": "Product detail: gallery, variant picker, reviews, add-to-cart.", + "loc": 1240, + "score": 72, + "grade": "C", + "tags": [ + "bloat" + ], + "findings": [ + { + "sev": "MED", + "loc": "src/pages/Product.tsx", + "text": "1240-line component: gallery, variant picker and reviews in one file." + } + ] + }, + { + "id": "cart_page", + "label": "Cart", + "band": "pages", + "path": "src/pages/Cart.tsx", + "coupling": "med", + "deps": [ + "cart_store", + "pricing_client" + ], + "desc": "Cart view, line-item editing and promo codes.", + "loc": 1420, + "score": 70, + "grade": "C", + "tags": [ + "god-component" + ], + "findings": [] + }, + { + "id": "checkout_page", + "label": "Checkout", + "band": "pages", + "path": "src/pages/Checkout.tsx", + "coupling": "high", + "deps": [ + "cart_store", + "auth_store", + "payments_client" + ], + "desc": "Multi-step checkout: address, shipping, payment, review.", + "loc": 1860, + "score": 58, + "grade": "D", + "tags": [ + "god-component", + "dual-format", + "fallback" + ], + "findings": [ + { + "sev": "HIGH", + "loc": "src/pages/Checkout.tsx", + "text": "1860-line god-component mixing the address/shipping/payment steps, validation and direct API calls." + }, + { + "sev": "MED", + "loc": "src/pages/Checkout.tsx:412", + "text": "reads both `postal_code` and `postalCode` from the address form (dual-format)." + }, + { + "sev": "LOW", + "loc": "src/pages/Checkout.tsx:980", + "text": "silent catch around the shipping-rate fetch falls back to a flat rate." + } + ] + }, + { + "id": "account_page", + "label": "Account", + "band": "pages", + "path": "src/pages/Account.tsx", + "coupling": "low", + "deps": [ + "auth_store" + ], + "desc": "Profile, order history and saved addresses.", + "loc": 760, + "score": 82, + "grade": "B", + "tags": [], + "findings": [] + }, + { + "id": "admin_page", + "label": "Admin", + "band": "pages", + "path": "src/pages/Admin.tsx", + "coupling": "med", + "deps": [ + "admin_client" + ], + "desc": "Internal dashboard: orders, inventory and reports.", + "loc": 2100, + "score": 66, + "grade": "C", + "tags": [ + "bloat", + "any-escape" + ], + "findings": [ + { + "sev": "MED", + "loc": "src/pages/Admin.tsx", + "text": "2100-line page: reports, tables and editors all in one file." + }, + { + "sev": "LOW", + "loc": "src/pages/Admin.tsx:300", + "text": "several `as any` casts around the chart library." + } + ] + }, + { + "id": "search_page", + "label": "Search", + "band": "pages", + "path": "src/pages/Search.tsx", + "coupling": "low", + "deps": [ + "search_store" + ], + "desc": "Search results with facets.", + "loc": 540, + "score": 84, + "grade": "B", + "tags": [], + "findings": [] + }, + { + "id": "cart_store", + "label": "cartStore", + "band": "stores", + "path": "src/stores/cart.ts", + "coupling": "high", + "deps": [ + "api_client" + ], + "desc": "Cart line items, totals and promo state.", + "loc": 360, + "score": 74, + "grade": "C", + "tags": [ + "duplication" + ], + "findings": [ + { + "sev": "MED", + "loc": "src/stores/cart.ts:90", + "text": "cart totals re-implemented here and in PricingEngine (duplication)." + } + ] + }, + { + "id": "auth_store", + "label": "authStore", + "band": "stores", + "path": "src/stores/auth.ts", + "coupling": "core", + "deps": [ + "api_client" + ], + "desc": "Session, tokens and the current user.", + "loc": 290, + "score": 80, + "grade": "B", + "tags": [], + "findings": [] + }, + { + "id": "catalog_store", + "label": "catalogStore", + "band": "stores", + "path": "src/stores/catalog.ts", + "coupling": "high", + "deps": [ + "api_client" + ], + "desc": "Products, categories and cached pages.", + "loc": 410, + "score": 86, + "grade": "B", + "tags": [], + "findings": [] + }, + { + "id": "search_store", + "label": "searchStore", + "band": "stores", + "path": "src/stores/search.ts", + "coupling": "med", + "deps": [ + "api_client" + ], + "desc": "Query, facets and results.", + "loc": 220, + "score": 82, + "grade": "B", + "tags": [], + "findings": [] + }, + { + "id": "ui_store", + "label": "uiStore", + "band": "stores", + "path": "src/stores/ui.ts", + "coupling": "low", + "deps": [], + "desc": "Modals, toasts and theme.", + "loc": 150, + "score": 88, + "grade": "B", + "tags": [], + "findings": [] + }, + { + "id": "checkout_store", + "label": "checkoutStore", + "band": "stores", + "path": "src/stores/checkout.ts", + "coupling": "med", + "deps": [ + "cart_store" + ], + "desc": "Checkout step state and form drafts.", + "loc": 480, + "score": 62, + "grade": "C", + "tags": [ + "dual-format", + "legacy" + ], + "findings": [ + { + "sev": "MED", + "loc": "src/stores/checkout.ts:40", + "text": "reads both snake_case and camelCase address fields (dual-format)." + }, + { + "sev": "LOW", + "loc": "src/stores/checkout.ts:8", + "text": "legacy single-step draft kept for old links." + } + ] + }, + { + "id": "api_client", + "label": "apiClient", + "band": "transport", + "path": "src/transport/apiClient.ts", + "coupling": "core", + "deps": [ + "api_gw" + ], + "desc": "REST client — ~50 thin endpoint wrappers plus auth and retry.", + "loc": 690, + "score": 68, + "grade": "C", + "tags": [ + "glue", + "bloat" + ], + "findings": [ + { + "sev": "MED", + "loc": "src/transport/apiClient.ts", + "text": "~50 one-line get/post wrappers that only forward args (glue) — generate or collapse to a typed client." + }, + { + "sev": "LOW", + "loc": "src/transport/apiClient.ts:1", + "text": "one 690-line file mixing transport with the whole endpoint surface." + } + ] + }, + { + "id": "ws_client", + "label": "wsClient", + "band": "transport", + "path": "src/transport/wsClient.ts", + "coupling": "med", + "deps": [ + "api_gw" + ], + "desc": "WebSocket for live order and stock updates.", + "loc": 230, + "score": 84, + "grade": "B", + "tags": [], + "findings": [] + }, + { + "id": "payments_client", + "label": "paymentsClient", + "band": "transport", + "path": "src/transport/payments.ts", + "coupling": "med", + "deps": [ + "stripe_gw" + ], + "desc": "Thin bridge to the payments API.", + "loc": 140, + "score": 86, + "grade": "B", + "tags": [], + "findings": [] + }, + { + "id": "admin_client", + "label": "adminClient", + "band": "transport", + "path": "src/transport/admin.ts", + "coupling": "low", + "deps": [ + "api_gw" + ], + "desc": "Admin-only API client.", + "loc": 180, + "score": 80, + "grade": "B", + "tags": [], + "findings": [] + }, + { + "id": "pricing_client", + "label": "pricingClient", + "band": "transport", + "path": "src/transport/pricing.ts", + "coupling": "low", + "deps": [ + "api_gw" + ], + "desc": "Live price/quote client.", + "loc": 110, + "score": 84, + "grade": "B", + "tags": [], + "findings": [] + }, + { + "id": "api_gw", + "label": "API Gateway", + "band": "api", + "path": "api/app.py", + "coupling": "core", + "deps": [ + "auth_api", + "products_api", + "orders_api", + "payments_api", + "search_api" + ], + "desc": "HTTP app: routing, middleware, auth and the request lifecycle.", + "loc": 540, + "score": 88, + "grade": "B", + "tags": [], + "findings": [] + }, + { + "id": "auth_api", + "label": "auth routes", + "band": "api", + "path": "api/auth.py", + "coupling": "high", + "deps": [ + "auth_svc" + ], + "desc": "Login, signup, token refresh and OAuth.", + "loc": 420, + "score": 78, + "grade": "B", + "tags": [ + "silent-except" + ], + "findings": [ + { + "sev": "LOW", + "loc": "api/auth.py:140", + "text": "broad except around the OAuth token exchange logs but swallows the cause." + } + ] + }, + { + "id": "products_api", + "label": "products routes", + "band": "api", + "path": "api/products.py", + "coupling": "med", + "deps": [ + "catalog_svc" + ], + "desc": "Product and category CRUD + listing.", + "loc": 360, + "score": 84, + "grade": "B", + "tags": [], + "findings": [] + }, + { + "id": "orders_api", + "label": "orders routes", + "band": "api", + "path": "api/orders.py", + "coupling": "high", + "deps": [ + "order_svc" + ], + "desc": "Cart, order placement and status.", + "loc": 610, + "score": 74, + "grade": "C", + "tags": [ + "dual-format" + ], + "findings": [ + { + "sev": "MED", + "loc": "api/orders.py:88", + "text": "accepts both the legacy and v2 cart payload shapes (dual-format)." + } + ] + }, + { + "id": "payments_api", + "label": "payments routes", + "band": "api", + "path": "api/payments.py", + "coupling": "high", + "deps": [ + "payment_svc" + ], + "desc": "Charge, refund and webhooks (provider integration WIP).", + "loc": 480, + "score": 55, + "grade": "D", + "tags": [ + "stub", + "fallback" + ], + "findings": [ + { + "sev": "HIGH", + "loc": "api/payments.py:44", + "text": "webhook handler always returns 200 without verifying the signature (stub)." + }, + { + "sev": "MED", + "loc": "api/payments.py:70", + "text": "falls back to marking the order paid when the provider call times out." + } + ] + }, + { + "id": "search_api", + "label": "search routes", + "band": "api", + "path": "api/search.py", + "coupling": "low", + "deps": [ + "search_svc" + ], + "desc": "Query and indexing endpoints.", + "loc": 240, + "score": 82, + "grade": "B", + "tags": [], + "findings": [] + }, + { + "id": "auth_svc", + "label": "AuthService", + "band": "services", + "path": "services/auth.py", + "coupling": "high", + "deps": [ + "user_model", + "token_util" + ], + "desc": "Credentials, sessions and password hashing.", + "loc": 520, + "score": 80, + "grade": "B", + "tags": [], + "findings": [] + }, + { + "id": "order_svc", + "label": "OrderService", + "band": "services", + "path": "services/order.py", + "coupling": "core", + "deps": [ + "order_model", + "inventory_svc", + "pricing_svc", + "payment_svc" + ], + "desc": "Order placement, the state machine and fulfillment.", + "loc": 1480, + "score": 64, + "grade": "C", + "tags": [ + "duplication", + "bloat" + ], + "findings": [ + { + "sev": "HIGH", + "loc": "services/order.py", + "text": "1480-line service; the order state machine is duplicated between place() and fulfill()." + }, + { + "sev": "MED", + "loc": "services/order.py:620", + "text": "inventory reservation logic copy-pasted from InventoryService." + } + ] + }, + { + "id": "pricing_svc", + "label": "PricingEngine", + "band": "services", + "path": "services/pricing.py", + "coupling": "high", + "deps": [ + "product_model" + ], + "desc": "Prices, taxes, discounts and promotions.", + "loc": 880, + "score": 70, + "grade": "C", + "tags": [ + "over-fit" + ], + "findings": [ + { + "sev": "MED", + "loc": "services/pricing.py:120", + "text": "discount rules hardcoded to the current promo set (over-fit)." + } + ] + }, + { + "id": "inventory_svc", + "label": "InventoryService", + "band": "services", + "path": "services/inventory.py", + "coupling": "med", + "deps": [ + "product_model", + "repo" + ], + "desc": "Stock levels and reservations.", + "loc": 540, + "score": 78, + "grade": "B", + "tags": [], + "findings": [] + }, + { + "id": "payment_svc", + "label": "PaymentService", + "band": "services", + "path": "services/payment.py", + "coupling": "high", + "deps": [ + "stripe_gw" + ], + "desc": "Charges/refunds — currently a sandbox stub returning canned results.", + "loc": 260, + "score": 48, + "grade": "D", + "tags": [ + "stub", + "fake-output" + ], + "findings": [ + { + "sev": "HIGH", + "loc": "services/payment.py:31", + "text": "charge()/refund() return a canned `{status:'succeeded'}` — sandbox stub, no real gateway call." + }, + { + "sev": "MED", + "loc": "services/payment.py:88", + "text": "'TODO: wire the real provider before launch.'" + } + ] + }, + { + "id": "catalog_svc", + "label": "CatalogService", + "band": "services", + "path": "services/catalog.py", + "coupling": "med", + "deps": [ + "product_model", + "repo" + ], + "desc": "Product/category reads with caching.", + "loc": 430, + "score": 86, + "grade": "B", + "tags": [], + "findings": [] + }, + { + "id": "search_svc", + "label": "SearchService", + "band": "services", + "path": "services/search.py", + "coupling": "med", + "deps": [ + "repo" + ], + "desc": "Index and query; falls back to SQL LIKE when the search cluster is down.", + "loc": 470, + "score": 76, + "grade": "B", + "tags": [ + "fallback" + ], + "findings": [ + { + "sev": "LOW", + "loc": "services/search.py:80", + "text": "documented fallback to SQL LIKE when Elasticsearch is unreachable." + } + ] + }, + { + "id": "notif_svc", + "label": "NotificationService", + "band": "services", + "path": "services/notify.py", + "coupling": "low", + "deps": [ + "email_worker" + ], + "desc": "Email/SMS/push fan-out.", + "loc": 300, + "score": 84, + "grade": "B", + "tags": [], + "findings": [] + }, + { + "id": "order_model", + "label": "Order", + "band": "domain", + "path": "domain/order.py", + "coupling": "core", + "deps": [ + "product_model", + "user_model" + ], + "desc": "Order aggregate: items, totals and status.", + "loc": 380, + "score": 88, + "grade": "B", + "tags": [], + "findings": [] + }, + { + "id": "product_model", + "label": "Product", + "band": "domain", + "path": "domain/product.py", + "coupling": "high", + "deps": [], + "desc": "Product, variant and category entities.", + "loc": 260, + "score": 90, + "grade": "A", + "tags": [], + "findings": [] + }, + { + "id": "user_model", + "label": "User", + "band": "domain", + "path": "domain/user.py", + "coupling": "high", + "deps": [], + "desc": "User, address and role entities.", + "loc": 210, + "score": 88, + "grade": "B", + "tags": [], + "findings": [] + }, + { + "id": "money_util", + "label": "Money", + "band": "domain", + "path": "domain/money.py", + "coupling": "med", + "deps": [], + "desc": "Currency-safe money arithmetic.", + "loc": 120, + "score": 92, + "grade": "A", + "tags": [], + "findings": [] + }, + { + "id": "token_util", + "label": "TokenUtil", + "band": "domain", + "path": "domain/token.py", + "coupling": "med", + "deps": [], + "desc": "JWT sign/verify helpers.", + "loc": 90, + "score": 86, + "grade": "B", + "tags": [], + "findings": [] + }, + { + "id": "repo", + "label": "Repository", + "band": "data", + "path": "data/repo.py", + "coupling": "core", + "deps": [ + "db", + "migrations" + ], + "desc": "Data-access layer over the database.", + "loc": 640, + "score": 72, + "grade": "C", + "tags": [ + "duplication" + ], + "findings": [ + { + "sev": "MED", + "loc": "data/repo.py", + "text": "per-entity CRUD copy-pasted across 9 repositories — extract a base." + } + ] + }, + { + "id": "db", + "label": "DB Pool", + "band": "data", + "path": "data/db.py", + "coupling": "high", + "deps": [], + "desc": "Connection pool and query helpers.", + "loc": 180, + "score": 90, + "grade": "A", + "tags": [], + "findings": [] + }, + { + "id": "migrations", + "label": "Migrations", + "band": "data", + "path": "data/migrations", + "coupling": "low", + "deps": [], + "desc": "Schema migrations.", + "loc": 220, + "score": 85, + "grade": "B", + "tags": [], + "findings": [] + }, + { + "id": "email_worker", + "label": "EmailWorker", + "band": "workers", + "path": "workers/email.py", + "coupling": "low", + "deps": [ + "notif_tmpl" + ], + "desc": "Async email queue consumer.", + "loc": 260, + "score": 80, + "grade": "B", + "tags": [], + "findings": [] + }, + { + "id": "webhook_dispatcher", + "label": "WebhookDispatcher", + "band": "workers", + "path": "workers/webhooks.py", + "coupling": "med", + "deps": [], + "desc": "Outbound webhooks with retry.", + "loc": 340, + "score": 68, + "grade": "C", + "tags": [ + "silent-except", + "legacy" + ], + "findings": [ + { + "sev": "MED", + "loc": "workers/webhooks.py:55", + "text": "`except: pass` swallows delivery errors — failed webhooks vanish." + }, + { + "sev": "LOW", + "loc": "workers/webhooks.py:12", + "text": "legacy v1 payload path kept alongside v2." + } + ] + }, + { + "id": "notif_tmpl", + "label": "Templates", + "band": "workers", + "path": "workers/templates.py", + "coupling": "low", + "deps": [], + "desc": "Email/notification templates.", + "loc": 150, + "score": 84, + "grade": "B", + "tags": [], + "findings": [] + }, + { + "id": "stripe_gw", + "label": "Stripe Gateway", + "band": "integrations", + "path": "integrations/stripe.py", + "coupling": "med", + "deps": [], + "desc": "Adapter to the Stripe payments API.", + "loc": 280, + "score": 78, + "grade": "B", + "tags": [], + "findings": [] + }, + { + "id": "shipping_gw", + "label": "Shipping Provider", + "band": "integrations", + "path": "integrations/shipping.py", + "coupling": "low", + "deps": [], + "desc": "Adapter to a shipping rate/label API.", + "loc": 230, + "score": 74, + "grade": "C", + "tags": [ + "glue" + ], + "findings": [ + { + "sev": "LOW", + "loc": "integrations/shipping.py", + "text": "adapter forwards every field unchanged (glue)." + } + ] + }, + { + "id": "analytics_sink", + "label": "Analytics", + "band": "integrations", + "path": "integrations/analytics.py", + "coupling": "low", + "deps": [], + "desc": "Event sink to the analytics pipeline.", + "loc": 190, + "score": 70, + "grade": "C", + "tags": [ + "silent-except" + ], + "findings": [ + { + "sev": "LOW", + "loc": "integrations/analytics.py:22", + "text": "fire-and-forget send swallows failures silently." + } + ] + } + ] +} \ No newline at end of file diff --git a/reference/DATA_MODEL.md b/reference/DATA_MODEL.md index efaeb0a..d342dbc 100644 --- a/reference/DATA_MODEL.md +++ b/reference/DATA_MODEL.md @@ -15,8 +15,8 @@ subagents edit it) and re-render. "generatedAt": "2026-01-01", "rev": "abc1234", // git HEAD at the last codemap run (the baseline // `update`/`check` diff from; set by scan.py --stamp-rev) - "htmlPath": ".codemap/architecture-map.html", // output location (for reciprocal links) - "mdPath": ".codemap/architecture-audit.md", + "htmlPath": ".codemap/codemap.html", // output location (for reciprocal links) + "mdPath": ".codemap/codemap.md", "spineDesc": "A user edits … → … → persistence.", // shown on the spine view "tracked_loc": 184900, // filled by scan.py --write (union, de-duped) "tracked_files": 980, diff --git a/scripts/render.py b/scripts/render.py index cc7bff7..522a501 100644 --- a/scripts/render.py +++ b/scripts/render.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""render.py — regenerate architecture-map.html + architecture-audit.md from modules.json. +"""render.py — regenerate codemap.html + codemap.md from modules.json. modules.json is the single source of truth. The HTML and MD are pure projections of it and must never be hand-edited. Run scan.py --write before rendering so LoC / @@ -8,7 +8,7 @@ content hashes are current. Usage: python render.py --state modules.json \ --template assets/template.html \ - --out-html architecture-map.html --out-md architecture-audit.md + --out-html codemap.html --out-md codemap.md Stdlib only. """ @@ -70,12 +70,12 @@ def render_md(state): out = [] proj = meta.get("project", "Project") out.append("\n") out.append(f"# {proj} — Functional Module Quality Audit\n") - out.append(f"> **Interactive view:** [`{meta.get('htmlPath','architecture-map.html')}`]" - f"({os.path.basename(meta.get('htmlPath','architecture-map.html'))}) — " + out.append(f"> **Interactive view:** [`{meta.get('htmlPath','codemap.html')}`]" + f"({os.path.basename(meta.get('htmlPath','codemap.html'))}) — " "per-module scores, findings, LoC, and the dependency graph. This file is the written report.\n") gen = meta.get("generatedAt", "") loc_line = meta.get("locLine") or (