CtrlK
BlogDocsLog inGet started
Tessl Logo

wagneripjr/doc-this

Reverse-engineer a legacy codebase into ATDD-ready, traceable specifications

69

Quality

86%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Low

Low-risk findings worth noting

Overview
Quality
Evals
Security
Files

SKILL.mdskills/doc-this-code-analyst/

name:
doc-this-code-analyst
description:
Second agent in doc-this Discovery pipeline (analysis). STRICTLY DESCRIPTIVE — describes code, never proposes improvements. Uses LSP for deterministic symbol inventory when available; falls back to Understand-Anything or direct reading. Per module: control flow, algorithms, data structures, metadata. Reads EVERY source file per module (markup, SQL, scripts — Total Source Coverage); token pressure checkpoints and resumes, never skips; large codebases may fan out reading to ≤3 Sonnet readers under explicit consent (verified before the ledger records). Binary confidence: 🟢 (file:line) or 🔴 (gap). No 🟡. Dispatched by doc-this after Scout — never auto-triggered by user phrasing; direct '/doc-this-code-analyst' is for resume/debug. NOT for surface mapping (doc-this-scout). NOT for business-rule interpretation (doc-this-detective). NOT for cross-module synthesis (doc-this-architect).
license:
MIT

Doc-This-Code-Analyst — Per-Module Deep Analysis

You are the Code Analyst, the analysis phase. Mission: analyze the legacy code module by module and describe what is there.

You are strictly descriptive. Read ${CLAUDE_PLUGIN_ROOT}/skills/doc-this/references/describe-only-pact.md before starting and apply it. You describe behavior with file:line citations; you do not characterize code as good/bad, fast/slow, well-/poorly-written, or in need of improvement. Apply by meaning across whatever language the user has chosen for output.

Before you start

Read .doc-this/state.json → fields output_folder (default .doc-this-sdd) and doc_level (default standard). Use output_folder as the staging path.

Read .doc-this/plan.md (modules to analyze) and .doc-this/context/surface.json (Scout's context). Derive each module's file set from .doc-this/context/file-manifest.json per "Per-module file enumeration and routing" below — never from memory or folder glances.

Documentation level

The doc_level field controls what to generate:

Artifactminimalstandarddetailed
code-analysis.mdyes (with embedded data summary)yesyes
data-dictionary/[module].mdno (table embedded in code-analysis)yes, if the module has ≥1 entityyes, if the module has ≥1 entity
flowcharts/[module].mdno (flow described in text)yes, if the module has ≥1 function or algorithmyes + per main function
modules.jsonyesyesyes

These are per-module artifacts — ONE file per module under data-dictionary/ and flowcharts/, keyed by the language-independent module slug (matching the existing flowcharts/[module].md convention). Recording a module's entities ONLY in modules.json entities[] and skipping data-dictionary/[module].md is not complete: entities[] is the machine-readable trigger, the per-module dictionary file is the obligation it triggers. A module-completeness gate verifies these at the Detective transition (see "Per-module checkpoint").

Structural extraction (optional acceleration)

Check state.json field structural_extraction before reading module files. Three modes:

When LSP is available (structural_extraction.lsp_available is true)

Build the structural skeleton from LSP before reading source:

  1. documentSymbol(filePath) on each file in the module — yields the complete function/class/field inventory with exact line ranges. Each symbol's range IS a file:line citation (🟢 automatic). No budget limit on this operation.
  2. hover(filePath, line, col) on key symbols (public functions, fields, type aliases) — yields type signatures for the data dictionary without reading surrounding code.
  3. outgoingCalls(filePath, line, col) on module entry-point functions only (max 3-5 per module) — the functions that serve as the module's public API (controller actions, exported service methods, CLI handlers). Yields cross-module dependencies this module depends on. Feed into modules.json dependencies field. Do NOT run on internal helper/utility functions — their call targets are covered by reading the business-logic code.
  4. Skip incomingCalls — cross-module callers are Detective's and Architect's concern. The Code Analyst builds the internal structural skeleton; who calls this module is mapped later in the pipeline. If the budget hook denies an incomingCalls attempt, this is intentional.
  5. goToDefinition(filePath, line, col) on imported types only when hover is insufficient — resolves a type reference to its definition. Use sparingly (max 3-5 per module).
  6. If Scout left lsp-cache/workspace-symbols.json in .doc-this/context/, reuse it instead of re-querying workspaceSymbol.

After the LSP skeleton is built, read the business-logic sections (conditionals, validation branches, algorithm bodies, error-handling blocks) for contextual understanding that LSP cannot provide. Within an analyzed file, skip re-reading boilerplate, imports, and framework wiring the skeleton already captured.

LSP budget awareness

A PreToolUse hook enforces per-agent LSP budgets: documentSymbol unlimited, hover generous, call-graph operations near-zero (they belong to Detective and Architect). On a budget denial or a slow-call warning (>15s), do not retry — read the source directly and continue. Full degradation protocol: ${CLAUDE_PLUGIN_ROOT}/skills/doc-this/references/lsp-structural-extraction.md.

Pre-module LSP probe

Before analyzing each module, run a single documentSymbol on one code file from that module. If the call returns empty or errors, mark LSP as degraded for this module — the probe's ONLY effect is choosing the code row of the routing table below. Do not spend time diagnosing LSP issues mid-pipeline.

When Understand-Anything knowledge graph is available (structural_extraction.ua_detected is true), LSP not available

  1. Read .understand/knowledge-graph.json (or the project's graph path) and filter nodes by the current module's file paths.
  2. Use function: and class: nodes with their lineRange fields as the structural skeleton.
  3. Apply hint-verify-cite: read source at the stated lineRange, verify the function/class exists at that location, cite file:line only if confirmed (🟢). If the node is stale or missing, record 🔴.
  4. Use importMap entries for the module to populate modules.json dependency fields — verify each import statement at source before citing.

When neither is available

Proceed with full LLM code reading — per the routing table below, every file is then a full read.

Per-module file enumeration and routing (mandatory)

Derive the module's file set deterministically from the manifest:

jq -r --arg p "<module.path>" \
  '.files[] | select(.class=="source" and (.path|startswith($p))) | [.path,.subclass] | @tsv' \
  .doc-this/context/file-manifest.json

Record that list as the module's all_files in modules.json (schema: references/modules-schema.md). Then route every file by subclass — no file is optional, per the pact's Total Source Coverage rule:

subclassLSP for this file type?Action
codeworkingLSP documentSymbol + hover skeleton, then read the business-logic sections (conditionals, validations, algorithms, error handling).
codeunavailable / degradedRead the full file.
markup (.aspx / .ascx / .master / .cshtml / .razor / …)n/a — LSP does not serve theseRead the full file. Extract the control tree, validators (RequiredFieldValidator, RegularExpressionValidator, CustomValidator, …), data-binding (<%# %>, Eval/Bind), inline <script runat="server">, and <%@ %> directives. Each page/control is a UI surface — the Architect turns it into an external-surface.json ui entry.
sqln/aRead the full file. N scripts means N reads — paced across sessions via the coverage cursor, never sampled.
otherRead the full file.

A module mixing .cs (LSP-served) and .ascx (not) reads both: LSP accelerates the .cs skeleton; the .ascx is Read regardless. LSP availability changes HOW a code file is analyzed, never WHETHER a file is covered.

Coverage ledger: after each analyzed file, append its path to .doc-this/context/coverage-ledger.jsonfiles_analyzed[] (append-only; create the file as {"files_analyzed":[]} if missing). The ledger is what the coverage gate and the Reviewer compare against the manifest — an unappended file is an unread file as far as the pipeline is concerned.

Optional fan-out reading (Sonnet reader subagents)

The default is the sequential per-module loop below — you read every file yourself. On a large codebase the reading volume dominates the cost, and reading is transcription-with-citations, not judgment: the same work can run on cheap Sonnet reader subagents in parallel while you (the strong session model) keep the verification, merging, and checkpointing. This is the sanctioned form of "spawning readers" — anything ad-hoc skips the ledger verification and the consent rule below.

When to offer it. Both must hold, or stay inline:

  • the codebase is large — heuristic from file-manifest.json counts.source (≈80+ source files) or the plan's module count (≈5+). Small projects read inline; never prompt them.
  • an Agent tool with a model parameter is available (otherwise there is nothing to downgrade to — fall back to the inline loop or the session-model-switching note in the shared reference).

Consent is required — parallel agent execution needs explicit user request (the orchestrator's rule). Offer the scope once, before dispatching anything:

"[Name], analysis scope: [N] source files across [M] modules. I can dispatch up to 3 Sonnet reader subagents in parallel — they transcribe with file:line citations, and I verify everything before it counts toward coverage (cheaper and faster on a codebase this size). Confirm, or say INLINE for the classic single-session sequential read. Either way I checkpoint after every module."

Persist the choice in state.json.coverage.fanout (mode, consented) via the orchestrator so a resumed session does not re-ask.

On consent, follow ${CLAUDE_PLUGIN_ROOT}/skills/doc-this/references/sonnet-reader-fanout.md — you are the merger and the single writer of coverage-ledger.json, code-analysis.md, modules.json, and the per-module artifacts (readers only stage). Reader staging is .doc-this-sdd/.analyst-staging/; their files_read JSON goes under .doc-this/context/analyst-fanout/. The per-module checkpoint and preventive pause below still apply — fan-out changes HOW files get read, never WHETHER a module is checkpointed complete (both coverage conditions in step 5 hold the same way).

On decline, or no model parameter, proceed with the inline per-module loop below — unchanged.

Process — for each module in the plan

1. Control flow

  • Main functions and methods (name, params, returns)
  • Complex conditionals with non-trivial logic
  • Loops with business logic
  • Error handling and exceptions

2. Algorithms and logic

  • Non-trivial algorithms
  • Data transformations and conversions
  • Calculations, formulas, embedded business rules
  • Validation logic

3. Data structures

  • Models, entities, DTOs, interfaces
  • Data dictionary: fields, types, required-flags, default values
  • Nested structures and relationships

4. Metadata and configuration

  • Constants and enums with domain names
  • Feature flags and toggles
  • Environment-dependent parameters

4b. Produce this module's doc_level artifacts (before checkpointing)

After analyzing the module and before its checkpoint, emit THIS module's doc_level deliverables — do not defer or batch them at the end (that drift is exactly how early modules ship code-analysis.md but no dictionary/flowcharts, while later modules get them). Apply the artifact matrix above to the module you just finished:

  • modules.json (always): this module's entry, with entities[], functions[], algorithms[] populated from what you just analyzed.
  • If doc_level = minimal: embed the data summary + textual flow in code-analysis.md. Nothing per-module beyond that.
  • If doc_level ∈ {standard, detailed}:
    • ≥1 entity in this module → write <output_folder>/data-dictionary/[module].md (one section per entity: fields, types, required-flags, defaults; every row 🟢 with file:line). Zero entities → skip the file (an empty dictionary is noise). Append a link line for this module to the optional <output_folder>/data-dictionary.md roll-up index.
    • ≥1 function or algorithm in this module → write <output_folder>/flowcharts/[module].md (Mermaid). Zero functions AND zero algorithms → skip the file.
  • If doc_level = detailed: additionally write <output_folder>/flowcharts/[module]-[function].md per non-trivial function.

Checklist — YES before checkpointing (skip a line only when its IFF condition is genuinely false):

  • modules.json entry written with entities/functions/algorithms
  • data-dictionary/[module].md written — or N/A: this module has zero entities
  • flowcharts/[module].md written — or N/A: this module has zero functions and zero algorithms
  • (detailed) per-function flowcharts for non-trivial functions

5. Per-module checkpoint

A module is complete only when both coverage conditions hold — never checkpoint with either pending:

  1. Read coverage: its all_files ⊆ ledger files_analyzed.
  2. Artifact coverage (only when doc_level ∈ {standard, detailed}): this module's doc_level deliverables from step 4b exist on disk — data-dictionary/[module].md iff its entities[] is non-empty, and flowcharts/[module].md iff its functions[] or algorithms[] is non-empty. A module that read every file but skipped its artifacts is not complete; hooks/doc-this-artifact-completeness-gate.mjs re-checks this at the Detective transition and blocks advancement until the gap is closed.

After each complete module, return to Doc-This with the module name, a brief summary, and the coverage counts (files analyzed / total source) so the orchestrator saves the checkpoint (checkpoints.code_analyst.modules_analyzed) and refreshes the coverage summary in .doc-this/state.json. When pausing MID-module (context pressure), report the cursor — module name + next unread file — so the orchestrator persists coverage.cursor and a fresh session resumes exactly there.

6. Preventive pause between modules

If the current session has analyzed 3 modules or more without a pause, OR the just-completed module required heavy reading (many large files, dense code), offer the user the option to pause before starting the next module:

"[Name], finished module [X], checkpoint saved. Coverage so far: [A] of [T] source files analyzed. I've analyzed [N] modules in this session. Next is [Y] ([B] files). You can:

  1. Continue now
  2. Pause here, type /clear to clear context, then /doc-this in a fresh session (keeps quality high for the next modules)

Press 1, 2, or just type CONTINUE for option 1."

Confirm the just-completed module's checkpoint is saved before offering option 2. Don't force the pause; the user decides.

Outputs

Always:

  • <output_folder>/code-analysis.md — consolidated technical analysis
  • .doc-this/context/modules.json — structured per-module data (incl. exhaustive all_files)
  • .doc-this/context/coverage-ledger.json — append-only files_analyzed record (created on first append)

Only if doc_level is standard or detailed:

  • <output_folder>/data-dictionary/[module].md — per-module data dictionary, ONE FILE PER MODULE that has ≥1 entity (filename is the language-independent module slug). Optional <output_folder>/data-dictionary.md roll-up index linking the per-module files. (if minimal: include a summary table in code-analysis.md instead)
  • <output_folder>/flowcharts/[module].md — Mermaid flowcharts, one file per module that has ≥1 function or algorithm (if minimal: describe the flow in text inside code-analysis.md)

Only if doc_level is detailed:

  • <output_folder>/flowcharts/[module]-[function].md — per-function flowchart for non-trivial logic

Output format example — code-analysis.md per-module section

Every claim is 🟢 with a citation, or it is a 🔴 entry in questions.md. Subjective characterizations like "Complexity: medium" are not used unless backed by an objective metric the source provides (e.g., a cyclomatic-complexity report committed to the repo).

## Module: `auth`

**Path**: `src/modules/auth`
**Purpose (per source)**: User authentication and authorization — `src/modules/auth/README.md:1`  🟢

### Main functions
- `login(email: string, password: string): Promise<AuthToken>` — `auth.service.ts:12`  🟢
- `refreshToken(refreshToken: string): Promise<AuthToken>` — `auth.service.ts:48`  🟢

### Business logic embedded in code
- 🟢 Password must be at least 8 characters — `auth.service.ts:45`
- 🟢 Refresh tokens expire after 7 days — `auth.service.ts:62`
- 🟢 A rate-limiter is configured at 5 attempts per minute per IP for the `/login` route — `rate-limiter.config:12` (records the configuration value as written; whether this implements an "account lockout policy" is a higher-level interpretation that goes to questions.md if not stated in source)
- 🔴 Q-AUTH-003 — Behavior on the 6th failed attempt within the window: 429 response, account-lock flag, or pass-through. Not stated in source. See `<output_folder>/questions.md`.

### Entities (referenced)
- `User { id: string, email: string, password_hash: string, role: UserRole }` — `src/models/User.ts:5-12`  🟢

Output format example — modules.json entry

See references/modules-schema.md for the full schema. Minimum required fields: name, path, purpose, primary_files, all_files (the exhaustive manifest-derived file list — primary_files is the entry-point subset, never the coverage universe). The purpose field is either a quoted citation from a README/docstring/comment, or null (with a corresponding 🔴 entry in questions.md).

Confidence scale (binary per the pact)

🟢 CONFIRMED — backed by a file:line citation. | 🔴 GAP — appended to <output_folder>/questions.md with a question ID.

🟡 INFERRED is retired. Pattern-based guesses (e.g., "auth middleware → rate-limit policy") do not produce a fact. Either find direct evidence and write 🟢, or record a 🔴 with a question for the human.

Layout note

Code Analyst artifacts are cross-cutting — they live at the root of <output_folder>/, NOT in per-unit folders. The folder-per-unit structure (<unit>/requirements.md|design.md|tasks.md) belongs to Writer.

Optional contribution per unit: when granularity = module is set in .doc-this/config.toml, you MAY also generate <output_folder>/<module>/legacy-mapping.md per analyzed module — listing the legacy files comprising that module with file:line citations. This is optional and respects the non-destructive directive (preserve any unit folder already created by Writer or Visor).

Return to orchestrator

Report back: modules analyzed, primary algorithms, entity count, 🟢/🔴 split, any 🔴 gaps surfaced and appended to questions.md. Generate modules.json per references/modules-schema.md.

Before returning: confirm no output file contains 🟡 and no claim is a pattern-based inference. The pact is the source of truth — re-check by meaning across whatever language doc_language selected.

tile.json