Skip to content
rayofgoodnessPublic

About

AI-agent-agnostic documentation-map generator for FE/BE codebases

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

docmap

AI-agent-agnostic documentation-map generator for FE/BE codebases.

docmap deterministically discovers the modules of a project (via a framework adapter), then delegates writing each module's business-logic description to a headless AI agent of your choice — Claude Code, Codex, or Gemini CLI. The result is a .docmap/ tree that mirrors your project's module structure: one root doc per module plus one doc per element, with a top-level .docmap/index.md cross-module relationship graph.

Discovery (what exists, what depends on what) is always deterministic and free — no AI call is needed to preview it. Only the prose explaining why a module exists and what its business logic does is generated by an agent.

Supported stacks

  • Magento 2 (PHP) — modules via registration.php; elements from Controller/, Model/, Observer/, Plugin/, Block/, Api/, Helper/, Setup/; cross-module relations from di.xml (preference/plugin), events.xml, and PHP use statements.
  • Nuxt 4 — layers/top-level dirs as modules (pages, components, composables, stores, server); relations from imports, $fetch(...) calls resolved against Nitro server routes, and Pinia store usage.
  • NestJS — every *.module.ts defines a module boundary (its directory, minus any nested directory that has its own *.module.ts); elements classified by filename suffix (.controller., .service., .guard., .dto., ...); relations parsed straight from each @Module({ imports: [...] }) — the actual DI graph, not a guess.
  • Vue 3 (standalone, no Nuxt) — src/{views|pages, components, composables|hooks, stores|store, layouts, router} as modules; relations from imports, useXStore() usage, and each vue-router route's component (static or () => import(...)) resolved to its view.
  • Generic fallback — any other stack. Top-level directories become modules, cross-module edges come from import-statement heuristics. Any file is documentable by default (code, markdown docs, yaml/shell deploy scripts, sql — whatever the project actually uses) except known binary/asset types (images, fonts, archives, lockfiles, ...); a directory with nothing but those isn't treated as a module. Always available so the tool works on day one of a new project or an unsupported language.

Install

npm install -g @ray_of_goodness_/docmap   # global — gives you the `docmap` command everywhere
# or, without installing anything:
npx @ray_of_goodness_/docmap init

The package is scoped (@ray_of_goodness_/docmap) but the CLI binary is always just docmap, regardless of how it's installed.

Quick start

docmap init              # scaffold docmap.config.json + .docmap/, gitignore .docmap/
docmap scan               # preview modules & relations, no AI calls
docmap generate --runner claude   # write .docmap/ using Claude Code
docmap status             # missing / up-to-date / stale, per module

Re-running generate only calls the agent for modules whose source files changed since the last run (tracked via a content fingerprint stored in each doc's frontmatter) — safe and cheap to run repeatedly, e.g. in CI or a pre-commit hook.

init adds a .docmap/ entry to the project's .gitignore (creating the file if it doesn't exist yet) — generated docs aren't meant to be versioned. Every command also respects the project's .gitignore: files it ignores are never scanned or documented, no config needed. Use --dir <path> on scan/generate/status to restrict discovery to one subdirectory of the project — modules entirely outside it are dropped from the output.

Commands

docmap init

Scaffolds a new project for docmap. Writes docmap.config.json (with defaults — see Configuration) unless one already exists, creates the (empty) .docmap/ output directory, and adds a .docmap/ entry to .gitignore (creating that file too if it's missing). Safe to re-run: an existing config is left untouched, the .gitignore entry is only added once.

docmap init                            # defaults: framework auto-detect, language en
docmap init --lang uk                  # generated prose in Ukrainian
docmap init --framework nuxt4          # skip auto-detection, force an adapter

docmap scan

Runs discovery only — no AI agent is invoked, so this is instant and free. Finds the framework adapter, lists every module it found with its element count and cross-module relations (with each relation's confidence: deterministic, from a framework-specific signal like Magento's di.xml, or heuristic, from an import-statement guess). This is what actually gets sent to the agent in generate, so it doubles as a "preview what will be documented" step before spending any AI budget.

docmap scan                  # human-readable tree
docmap scan --json           # same data as JSON, for scripting
docmap scan --dir app/pages  # only look inside app/pages
docmap scan --framework magento2   # override auto-detection

scan --json output contract

scan --json's output is a stable, versioned, public contract for external tooling (CI bots, dashboards, scripts) to build against — it is not just a dump of docmap's internal data structures, which are free to change shape between releases. Breaking changes bump schema_version.

{
  "schema_version": 1,
  "framework": "nuxt4",
  "modules": [
    {
      "id": "moduleA",
      "name": "moduleA",
      "path": "app/moduleA",       // project-root-relative
      "elements": [
        { "id": "moduleA::index", "kind": "component", "files": ["app/moduleA/index.ts"] }
      ],
      "relations": [
        { "type": "import", "fromId": "moduleA::index", "toId": "moduleB::helper", "toModule": "moduleB", "detail": "...", "confidence": "heuristic" }
      ]
    }
  ]
}

Every path in this output — modules[].path, elements[].files[] — is project-root-relative (relPath). Absolute filesystem paths (absPath) are intentionally never included, so the output is safe to share outside the machine it was generated on and stable across checkouts at different locations. Treat relPath-style paths as relative to the project root you ran docmap scan from.

Cross-stack peers

For a split-repo setup (e.g. a Nuxt frontend calling a separate Magento backend), configure peers in docmap.config.json to match this project's REST/GraphQL call sites against a sibling project's declared API surface (etc/webapi.xml routes, etc/schema.graphqls fields):

{
  "peers": [
    { "name": "backend", "path": "../eva-magento" }
  ]
}

Each peer is discovered read-only: docmap scans the peer project to learn its modules and API surface, but never calls resolveRelations on it and never generates docs for it — the peer's own internal relations and its dependents/used-by index are not populated, only its module/element/summaryHints shape is used for matching. If you want the peer's own full docmap output (its internal relations, its own used-by index), run docmap generate on that project directly.

A matched call site becomes a cross-project api-call relation, tagged peer:<name>::<moduleId> on toId/toModule so it's never confused with a local module id. These show up in:

  • docmap scan --json's relations[], with toModule peer-prefixed.
  • The ## Relationships mermaid diagram in index.md (dashed, since the match is heuristic), with the peer module rendered as its own node.
  • The ## Integration surface table in index.md — endpoint/operation → frontend consumer → backend owner, for regression-testing "what breaks if this backend module changes."
  • ## Scenarios end-to-end chains that cross into a peer backend.

docmap generate

The full pipeline: discovery, then one AI agent call per module (not per element — cheaper, and gives the agent the whole module's context at once) to write the module's README.md and each element's .md file under .docmap/. A module is skipped if its content fingerprint already matches the doc on disk from a previous run, so re-running after a small change only regenerates what actually changed.

A module with more elements than maxFilesPerPrompt is automatically split into several agent calls (batches of maxFilesPerPrompt) instead of one — every element ends up documented from its actual source, rather than the agent being asked to write hundreds of blocks for files it was never shown. Only the first batch writes the module overview; later batches only write per-element blocks. The module-level relationship summary sent in every prompt is deduplicated (distinct type + target pairs, capped at 40 lines) — a module where 200 files all call the same useCartStore() produces one summary line, not 200.

Flag Effect
--module <id> Only generate this module (repeatable — pass it multiple times for several)
--runner <name> claude | codex | gemini | mock — which agent CLI to shell out to
--lang <lang> Override config.language for this run
--force Ignore the fingerprint cache, regenerate every targeted module
--dry-run Build the prompts and write them to .docmap/.cache/prompts/<module>.txt (or <module>.batch<N>.txt for a split module) with a rough token estimate — no agent is called, nothing under .docmap/ (besides the cache) is touched
--concurrency <n> Max parallel agent calls (default 2, capped at 8)
--fail-fast Stop after the first module that errors, instead of continuing through the rest
--model <model> Passed straight through to the runner CLI (e.g. claude --model <model>)
--framework <name> Override auto-detection
--dir <path> Restrict discovery to one subdirectory — see Quick start
--skill <path> A markdown file (a Claude Code SKILL.md works as-is — YAML frontmatter is stripped) whose content is folded into every prompt as project-specific documentation instructions: house style, tone, what to emphasize, conventions discovery can't infer on its own
docmap generate --runner claude                       # generate/refresh everything
docmap generate --runner claude --module checkout      # just one module
docmap generate --runner mock                          # zero-cost structural preview
docmap generate --dry-run                               # see the prompts without spending anything
docmap generate --runner claude --skill docs-style.md   # follow custom house-style instructions

A module ends up with status: error in its report (not written to disk) only if its first batch's response is missing the module-overview block, even after one retry with a stricter prompt (config.maxRetries) — the run continues past it rather than aborting, unless --fail-fast is set. A later batch that comes back empty doesn't fail the module: its elements get a placeholder doc and a warning is logged, but everything else that did generate is still written.

docmap status

Cheap, no-AI check of where every module's docs stand: missing (never generated), up-to-date (fingerprint matches source), or stale (source changed since the doc was written). Good for CI — fail the build if anything is stale or missing.

docmap status
docmap status --json
docmap status --dir server   # only report on one subdirectory

docmap install-skills

Copies the thin agent-integration wrappers from the package's skills/ directory into the target project, so Claude Code / Codex / Gemini CLI pick them up automatically in that project going forward (no logic in them beyond "run docmap generate, then summarize the result" — see Using it from an agent).

docmap install-skills                  # all three: claude, codex, gemini
docmap install-skills --agent claude   # just .claude/skills/docmap/

docmap clean

Deletes the entire .docmap/ directory. Requires --yes — without it, the command refuses and exits non-zero, so it can't be fired by accident (e.g. via a mistyped script or muscle memory from a different command).

docmap clean --yes

--dry-run and --runner mock (on generate) both exist to let you sanity-check cost/structure before spending anything: --dry-run shows you the exact prompts that would be sent, mock runs the full pipeline with a deterministic placeholder instead of a real agent call — useful in CI or for previewing the module tree.

Business-logic regression guard

docmap generate is safe to re-run because it skips any module whose fingerprint hasn't changed — but the moment a module's source does change, generate will happily overwrite its doc with prose describing the new code, whatever that new code does. Nothing compares the new behavior to the old one first. A refactor that quietly starts cancelling orders that used to be untouchable slides straight into the doc as the new normal — the previous doc, the project's eталон (reference baseline) for what the code is supposed to do, is gone the moment generate runs again.

docmap verify closes that gap. For each stale module it hands the agent both the old doc (the eталон) and the current source, and asks it to judge — per documented invariant — whether the code still honors it, changed it, or violated it, plus whether any new undocumented behavior showed up. Nothing is overwritten; verify only reports.

Recommended workflow (run verify before generate regenerates anything):

code changes
  → docmap status            # what's stale
  → docmap verify            # did any documented behavior break
  → human reviews the report:
      BREAKING and unintended  → fix the code
      change was intentional   → docmap generate (bakes in the new eталон)
docmap verify                              # check every stale module
docmap verify --module checkout            # just one module
docmap verify --strict                     # CHANGED also fails the build
docmap verify --since HEAD~5               # focus the prompt on a git diff
docmap verify --against .history/checkout/2026-08-01-1200.md   # a specific past baseline
Flag Effect
--module <id> Only verify this module (repeatable)
--runner <name> claude | codex | gemini | mock — which agent CLI to shell out to
--json Machine-readable report on stdout, for CI/scripting
--strict CHANGED also fails the build (exit 1) — by default only BREAKING does
--since <ref> Focuses each stale module's prompt on the git diff against <ref>, instead of handing over the whole module
--against <path> Verify against a specific .docmap/.history/ snapshot instead of the current on-disk doc

Exit code is non-zero when any module comes back BREAKING (and, with --strict, also when any comes back CHANGED) — wire it into CI the same way as docmap status. A human-readable report is always written to .docmap/.reports/verify-<yyyy-MM-dd-HHmm>.md: a summary table of every module's status/verdict, plus per-invariant detail for anything CHANGED or BREAKING.

Invariants

Business rules are only comparable module-over-module if they're a list, not a paragraph of prose. Every generated module doc has an ## Invariants section — a numbered list of observable, checkable business rules (e.g. "an order can only be cancelled from pending/on-hold", "a discount never exceeds 50%"). Each item gets an id (I1, I2, ...); verify's report references those same ids when it says an invariant was KEPT, CHANGED, or VIOLATED.

Doc history

Before generate overwrites a module's doc, the previous version is snapshotted to .docmap/.history/<module>/<generated_at>.md — so a generate run never destroys the eталон verify would have compared against. Retention is controlled by config.historyKeep (default 5 snapshots per module, oldest rotated out first; 0 disables history entirely). Point verify --against at one of these snapshots to check current source against an older baseline instead of the latest doc.

Known limitation: --since and an already-stale baseline

--since <ref> only narrows the diff shown to the agent for modules verify decides to check — it does not change which modules get checked. A module is still skipped as "unchanged" whenever its fingerprint matches its on-disk doc, even if the --since diff touched it. In practice that means: if generate already ran again after a regressing change (so the doc's fingerprint caught up with the bad code before anyone ran verify), the doc has silently adopted the regression as its new eталон, and --since won't surface it — verify --against a .docmap/.history/ snapshot from before the regression is the way to check that case.

Configuration

docmap.config.json (or .docmaprc[.json], resolved via cosmiconfig):

Key Default Notes
framework "auto" magento2 | nuxt4 | nestjs | vue3 | generic | auto
language "en"/"uk" Language code written into generated prose
runner "claude" claude | codex | gemini | mock
model — Passed through to the runner CLI, if set
concurrency 2 Parallel agent calls (max 8)
include / exclude — / node_modules, vendor, .git, dist, build, .docmap Glob-style path filters, matched per scanned subtree
scanDir — Same as --dir: restrict discovery to one subdirectory
skill — Same as --skill: path to project-specific documentation instructions
maxFilesPerPrompt 20 Source files included per module prompt
maxFileExcerptBytes 12000 Per-file excerpt cap
maxRetries 1 Retries when the agent's output is missing the required markers
timeoutMs 600000 Per-agent-call timeout
elementDocThreshold 1 Modules with this many elements or fewer get their docs folded into the module README instead of separate files
historyKeep 5 Previous-version snapshots kept per module under .docmap/.history/ before each overwrite; 0 disables history
peers [] Sibling projects to match cross-stack API calls against — see Cross-stack peers

Using it from an agent

docmap install-skills drops thin wrappers into the project so Claude Code, Codex, and Gemini CLI can drive docmap generate themselves as part of a conversation — the wrappers contain no logic beyond "run the CLI, then summarize what changed." See skills/.

Doc format

Each module doc (.docmap/<modulePath>/README.md) and element doc share a YAML frontmatter (docmap_version, id, status, fingerprint, generated_by, elements/dependencies or relations, ...) plus a fixed set of body sections: Purpose, Responsibilities, Business Logic, Inputs / Outputs, Relationships. The format is direction-agnostic by design: a future "interview mode" will write the same files with status: planned (spec-first) before any code exists, letting an agent build the implementation from the doc instead of the other way around.

Development

npm install
npm run build       # tsup -> dist/cli.js
npm test            # vitest — all tests run against the mock runner, no live LLM calls
npm run typecheck

License

MIT

About

AI-agent-agnostic documentation-map generator for FE/BE codebases

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages