code-archaeologist
Skill1 starsChanged 49 days agoMITView on GitHub
- Structure (class level) — who references/imports whom. data/structure/graph.json + data/structure/vault/<Entity>.md - Flow (method level) — who calls whom, so a real request path can be traced (OrderController.create_order -> OrderService.place_order -> OrderRepository.save). data/flow/flow_graph.json + data/flow/notes/<Class.method>.md data/ is grouped by map: structure/, flow/, report/<map>/ (the review pass), cache/ (AI-summary + freshness state; you rarely touch it). Both maps share one viewer, data/explorer.html, switched from its header. Paths. Every command below names the skill folder as .agents/skills/code-archaeologist. The installer rewrites that to…
- Reads credentials
- Installs packages
1 other project uses a version of this.
Adopted by
Most recently taken up by
- GarphenGate/moltline-osstheir own version, first seen Sep 19, 2026
Other versions1 project uses a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 1
- GarphenGate/moltline-oss★ 0same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository2 other files it gives its agents.
- AGENTS.mdAGENTS.md
- code-archaeologist-explorerSkill
The file
We split this skill into the rules it gives an agent
10 rules. About 0 are common, 4 are distinctive, and 6 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈4
Distinctive · estimate
This author's own choices. Worth a look.
6
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 20
Paths: Every command below names the skill folder as .agents/skills/code-archaeologist.
- Very specific: line 68
If instead it says "runtime found but not loadable", the wheels were installed under a different Python version than the one running now — …
- Very specific: line 72
If a grammar is missing, the build still succeeds and those files simply produce no nodes.
- Distinctive: line 84
NEVER read raw source for architecture, flow or review questions.
- Very specific: line 87
Start from the graph, not from grep: search.py (Command 4) to find node ids, trace_path.py (Commands 5-6) for the path or blast-radius.
- Distinctive: line 424
Use it for "review this codebase" / "where is the risk" / "what should we refactor first", then answer from the brief and the report — …
- Very specific: line 447
Test files are recognized by convention across languages — test/, tests/, __tests__/, spec/ directories.
- Distinctive: line 490
After any code change, rebuild the map (Command 3).
- Very specific: line 522
Call resolution is heuristic, not type inference: self.<dep>.m() via __init__ hints or assignments, typed params/locals, same-class …
- Distinctive: line 530
Every script resolves paths from the skill root, so it runs from any working directory.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
name: code-archaeologist description: Zero-RAG codebase navigation with two maps — a project-structure graph (which classes reference which) and a method-level flow graph (which method calls which, i.e. request/execution flow), plus a review pass (health grade, risk scan, git hotspots). Use to explain architecture, trace how a request flows through methods, find what breaks if a class/method changes, or review a codebase for smells, risky code and change hotspots.---
Skill: Code Archaeologist & Living Wiki Navigator
Overview
Zero-RAG codebase navigation from local graphs and Markdown notes. Two maps:
- Structure (class level) — who references/imports whom.
data/structure/graph.json + data/structure/vault/<Entity>.md
- Flow (method level) — who calls whom, so a real request path can be traced
(OrderController.create_order -> OrderService.place_order -> OrderRepository.save).
data/flow/flow_graph.json + data/flow/notes/<Class.method>.md
data/ is grouped by map: structure/, flow/, report/<map>/ (the review pass), cache/
(AI-summary + freshness state; you rarely touch it). Both maps share one viewer,
data/explorer.html, switched from its header.
Paths. Every command below names the skill folder as .agents/skills/code-archaeologist. The
installer rewrites that to wherever it installs the skill; if the folder was copied by hand (for
example to .claude/skills/code-archaeologist), use that folder in every command instead.
Setup — preflight before the first build
Once per machine/checkout, before the first project / flow / both build.
1. Python 3.10+ — required. The pipeline is stdlib apart from tree-sitter, which step 2 covers.
python --version # or python3; needs 3.10+Older or missing: stop and tell the user, the skill cannot run.
2. tree-sitter — for every language, Python included. Grammars are installed per language, so install only what the repo actually contains. Do this yourself — don't ask the user to.
cd .agents/skills/code-archaeologist && python -c "import tree_sitter; print('runtime ok')"
python .agents/skills/code-archaeologist/scripts/core/grammars.py --install python typescript # the repo's languages
python .agents/skills/code-archaeologist/scripts/core/grammars.py --install # or every grammarThese are wheels — no compiler, grammar bundled, one package per language — at the pinned
versions the skill was tested against (grammars.PINS; --print shows the pip command instead of
running it). Run it with the same python that runs the skill: the runtime wheel is built for one
Python version. Languages: python, javascript, typescript, tsx, java, go, csharp,
kotlin, rust, swift, scala, groovy, dart, c, cpp, ruby, php, elixir;
the runtime is always included. Two mappings are worth
knowing: tree-sitter-javascript covers .js and .jsx, and tree-sitter-typescript
covers both .ts and .tsx (one wheel, two grammars — .tsx genuinely needs the second one).
tree-sitter-python is not optional if the repo has any Python. It used to be read with the
standard library, so it needed nothing; it is now read the same way as everything else, and without
that wheel a Python repo produces an almost empty graph — with a warning saying so.
Install it yourself — this does not touch the user's Python. --target vendor puts the wheels
in <skill>/vendor, which the skill's .gitignore excludes: nothing enters the environment the
user runs their own projects in, no version can collide with theirs, and deleting the skill folder
removes every trace. Keep all three
flags — --target is the whole point, --only-binary :all: fails loudly instead of trying to
compile, and --no-cache-dir stops pip writing the wheels outside the skill folder.
The build tells you exactly what is missing and the exact command:
! csharp skipped: no tree-sitter grammar installed. Run `pip install ... --target <skill>/vendor tree-sitter-c-sharp` to enable it.If instead it says "runtime found but not loadable", the wheels were installed under a
different Python version than the one running now — re-run the install command above, which
rebuilds vendor/ for this interpreter.
If a grammar is missing, the build still succeeds and those files simply produce no nodes.
brief prints a SKIPPED block naming the language and the fix. Never answer a "what calls X",
"is this dead code" or "how big is this codebase" question while a language is skipped without
saying so — the graph is smaller than the codebase, and nothing else in the output makes that
visible.
brief also prints an UNPINNED line when an installed grammar is not the version this skill
was tested against. It is a warning, not a failure: the build is fine, but if that language's
output looks wrong, the version gap is the first thing to suspect. Re-running the install command
above puts it back on the pin.
Operating Principles
1. NEVER read raw source for architecture, flow or review questions.
2. Pick the map: structure for "how is this organized / who uses X"; flow for "how does a
request travel / what calls what".
3. Start from the graph, not from grep: search.py (Command 4) to find node ids, trace_path.py
(Commands 5-6) for the path or blast-radius. Ids are bare (place_order,
OrderService.place_order) unless two files define the same name — then each definition
is qualified by its file (tests_map.build, report.build). So find an id with
search.py --name 'build$' rather than typing one from memory; and a call to a shared name from
a file that does not define it was dropped, not guessed.
4. Then take the facts in ONE call with context.py --node <id> (Command 7). Read individual notes
(data/structure/vault/<Entity>.md, data/flow/notes/<Class.method>.md) only when the pack is
not enough, and only for nodes on the discovered path.
5. Preserve [[EntityName]] / [[Class.method]] wikilinks in answers so they stay navigable.
6. Check freshness before trusting a map (Command 9). If it says stale, rebuild first — see
"Keeping the maps current".
7. For review questions ("is this healthy?", "where is the risk?", "what to refactor first?"), run
the report (Command 14), read the brief (Command 1), and open
data/report/<map>/architecture_report.md only for the detail the brief points at.
8. Edges are a lower bound — say so, and name the loss. In every language a call is drawn only
when the receiver's type can be read from the source; anything else is dropped, never guessed.
- What is trustworthy is the declarations; what is partial is resolution. So "nothing
calls X" only ever means "nothing the extractor could resolve calls X".
- A node names its own loss in precision: interface-dispatch (the call stops at an
interface), overloads (the arguments did not say which overload was meant, so that call was
dropped; every overload is its own node), name-matched (a call through an object with no
declared type was dropped, and the graph defines that name — listed in untyped).
- A call through an interface lands on the interface's own node (declaration: true) and stops
there. Each implementation is joined to that node by an implements link, so traces and
--impact-of reach every implementation -- but which one runs is still unanswered, unless
the scanned source settles which Spring bean is injected (a @Qualifier, one @Primary, the
only bean, a @Bean method): then the call links straight to that bean's method. A bean
registered outside the scanned source (a library's auto-configuration) is invisible to that
rule.
- context.py and the report print the caveat for you and name the node's loss — pass it on,
and say it plainly when a whole answer (dead code, blast radius, "who calls this") rests on it.
Pick the command: what the user asked → what to run
Find the row before reaching for a tool. Every question below is answered from artifacts, so the answer costs a command, not a source read.
| The user asks | Run | Map | ||||
| "explain this repo", "where do I start" | 1 brief | both | ||||
| "where is X handled?", "find the Y class" | 4 search.py --name | either | ||||
| "who calls X?" / "what does X call?" | 4 search.py --calls / --called-by | flow | ||||
| "how does a request get from A to B?" | 5 trace_path.py --from --to | flow | ||||
| "how are these two classes connected?" | 5 trace_path.py --from --to | structure | ||||
| "what breaks if I change X?" | 6 trace_path.py --impact-of | flow | ||||
| "what does this PR / my current diff affect?" | 6 --impact-of-diff | flow | ||||
| "tell me everything about X" | 7 context.py --node | flow | ||||
| "which tests cover X?" | 7 context.py --node → Covered by | flow | ||||
| "is this codebase healthy?", "what do we fix first?" | 14 report, then 1 brief | both | ||||
| "any security problems?" | 11 scan_security.py | either | ||||
| "what's dead / rotting?", "TODOs?" | 15 debt.py | either | ||||
| "what's untested?" | 16 tests_map.py | flow | ||||
| "what's been copy-pasted?" | 17 duplicates.py | flow | ||||
| "what changes most / who owns this?" | 12 git_insights.py | either | ||||
| "how big / complex is X?" | 13 metrics.py | either | ||||
| "any cycles / layering problems?" | 10 analyze.py | either | ||||
| "show me the architecture" (for a human) | 8 build_html.py → explorer.html | both | "is the map still accurate?" | 9 check | both | |
| anything, and the map is stale or missing | 2 / 3 project / flow / both | — |
Two rules that decide the Map column when it says "either":
- structure answers how is this organised, who uses this class — nodes are classes, components and module groups. - flow answers what calls what, how does a request travel — nodes are methods and functions.
Node ids differ between the two maps, so never carry an id from one into the other, and never read a report built for one map against the other.
Available Tool Commands
> First build in this project? Run the Setup preflight above first. > Paths below assume the default install; if the skill lives elsewhere, that prefix is already > rewritten to match.
1. Orientation brief — start here
Fixed-size digest of what is built: counts and grade per map, staleness, size, entry points, and
the top longest / most complex / most churned / riskiest nodes. Costs the same on a 200-file repo
as on a 5-file one, so use it instead of reading architecture_report.json.
python .agents/skills/code-archaeologist/scripts/archaeologist.py brief --src ./src--map structure switches the detail sections, --top N sizes them, --json for tooling. --src
is optional here too — without it the freshness line uses the roots the last build recorded.
2. Build the Project Structure map
python .agents/skills/code-archaeologist/scripts/archaeologist.py project --src ./srcNodes are classes, React components and one <Name>Module page per file of module-level
functions, from Python and JS/TS alike. A JS/TS function that returns JSX gets kind: component
and layer: ui, and an import ... from "./x" -- or "@/x" through the tsconfig/jsconfig
paths alias -- becomes an edge to whatever that file defines.
3. Build the Flow / Request-Flow map
python .agents/skills/code-archaeologist/scripts/archaeologist.py flow --src ./src
python .agents/skills/code-archaeologist/scripts/archaeologist.py both --src ./src # both maps
python .agents/skills/code-archaeologist/scripts/archaeologist.py flow --src ./backend ./frontend--src takes several roots, so a monorepo lands in one graph (node source keeps its root
prefix: backend/…, frontend/…). Every language is parsed by tree-sitter (grammar missing →
those files skipped with a warning). Frontend fetch/axios
calls are matched to backend route handlers by HTTP method + path, giving cross-stack http
edges — so one trace can run frontend → API → service → repository.
Route handlers are recognised in FastAPI, Flask (including @app.route on a module-level def
and methods=["GET", "POST"]), Express (named handler or inline arrow), Nest (@Controller
prefix + @Get/@Post suffix), Spring (Java and Kotlin), ASP.NET, Go routers and actix-web /
Rocket — and, from a route table away from the handler, Django urlpatterns, Rails
routes.rb, Laravel routes/*.php and Phoenix routers. A table route whose handler names no
single node is not attached (the build says how many). A node's routes is a list — say all of them when asked what
a handler serves. When a call's path does not match a route exactly, the link falls back to a
route whose path is a suffix of it, and only when exactly one route matches; an ambiguous path is
left unlinked rather than guessed, so a missing http edge means "could not tell", not "no
caller".
4. Find the nodes (instead of grepping)
Filter the graph and get ids back for the commands below. Never grep source to find "where is X handled".
python .agents/skills/code-archaeologist/scripts/query/search.py --name "payment|charge"
python .agents/skills/code-archaeologist/scripts/query/search.py --doc "refund"
python .agents/skills/code-archaeologist/scripts/query/search.py --layer repository --kind method
python .agents/skills/code-archaeologist/scripts/query/search.py --calls OrderRepository.save
python .agents/skills/code-archaeologist/scripts/query/search.py --called-by OrderService.place_order
python .agents/skills/code-archaeologist/scripts/query/search.py --lang java --file controllers/
python .agents/skills/code-archaeologist/scripts/query/search.py --orphans --format jsonFilters AND together; --graph picks the map (default: flow), --limit caps rows (default 40),
--format json for tooling. --name and --doc take a regex; --file is a substring of the path.
--lang matches the tag on the node, which is coarser than the file census: py, js,
java, go, csharp, kotlin, rust, swift, scala, groovy, dart, c, cpp, ruby,
php, elixir. js covers all of .js, .jsx, .ts and .tsx — there is no ts or
tsx node tag, so --lang ts returns no match even in a TypeScript repo. Use --lang js, or
--file .tsx when you want one extension. (The finer tags ts/tsx/jsx do appear in
metrics.json's file census and the explorer's language mix, which read the extension.)
--calls and --called-by are the two directions of one edge — "who calls X" and "what does X
call" — and are the cheapest way to answer a connectivity question without a full trace. Ids are
bare (place_order) unless two files define the same name, in which case each is qualified by
its file (report.build), so search for an id rather than typing one from memory.
5. Trace Execution Flow
Structure is the default graph; flow needs --graph. Output is one line per path (A > B > C);
add --all for every path, --format json only when something machine-reads it.
python .agents/skills/code-archaeologist/scripts/query/trace_path.py --from <SourceClass> --to <TargetClass>
python .agents/skills/code-archaeologist/scripts/query/trace_path.py \
--graph .agents/skills/code-archaeologist/data/flow/flow_graph.json \
--from OrderController.create_order --to OrderRepository.save6. Blast-Radius / Impact Analysis
Everything upstream of a node — and, with --impact-of-diff, of a whole changeset (changed files
are mapped to nodes, then their impact is unioned). Works on either graph.
python .agents/skills/code-archaeologist/scripts/query/trace_path.py --impact-of <ClassName>
python .agents/skills/code-archaeologist/scripts/query/trace_path.py \
--graph .agents/skills/code-archaeologist/data/flow/flow_graph.json --impact-of <Class.method>
python .agents/skills/code-archaeologist/scripts/query/trace_path.py --impact-of-diff # working tree
python .agents/skills/code-archaeologist/scripts/query/trace_path.py --impact-of-diff --staged
python .agents/skills/code-archaeologist/scripts/query/trace_path.py --impact-of-diff --base origin/main7. Context pack for a node (one call, budgeted)
Everything known about a node from the artifacts: kind/layer/source, size and complexity, churn and owner, description, immediate callers and callees each with their own description, and the risks attributed to it. Replaces "trace, then open five notes".
python .agents/skills/code-archaeologist/scripts/query/context.py --node OrderService.place_order
python .agents/skills/code-archaeologist/scripts/query/context.py --node A B --depth 2 --max-chars 8000
python .agents/skills/code-archaeologist/scripts/query/context.py --diff # every node the diff touches--max-chars (default 6000) is a hard budget: neighbor lists shrink until it fits, and it says
when it trimmed. --graph picks the map (default: flow), --format json for tooling. When tests
call the node, the pack lists them under Covered by — real call edges, so it answers "which
tests should I run for this change".
8. Regenerate the HTML explorer
archaeologist.py refreshes data/explorer.html on every build; this rebuilds it alone. When the
user only wants the page created or replaced, the separate /code-archaeologist-explorer skill
does that end to end (both maps, report, render).
python .agents/skills/code-archaeologist/scripts/query/build_html.pyOne page holds both maps (header switch): grade, tiles and file tree on the left; seven views (Flowchart, which it opens on, then Graph, Treemap, Matrix, Tree, Cluster, Bundle) in the middle; FILE / PATTERNS / SECURITY tabs on the right. A map with no report still renders, minus the grade and review tabs. A Tests checkbox appears when the map has test nodes. It starts unchecked, so the page opens on the architecture without the suite hanging off it; tick it to draw the test nodes.
It is one self-contained file — data and the graph library are inlined, so it opens from
file:// with the network off and can be emailed to someone with no repo access. Hand it to a
human; it is not a way for you to read the graph (use Commands 4–7 for that). What to tell
them it does:
- Left rail — health ring, a colour-by selector (layer / folder / churn / risk), stat tiles,
language mix, a node search (by name or path), and a file tree that filters the canvas to a file's nodes and whatever they link to directly. Each section folds from its own heading
to give the tree room, and both rails drag to resize from their inner border.
- Centre — the seven views, folder hulls, and a blast radius toggle that shades everything
reachable from the selected node. Freeze holds the current view: clicking another node
still opens its details but no longer re-narrows the Flowchart or moves the canvas. In the
Flowchart, a badge before each call's arrowhead numbers a node's calls in the order
they are written -- not the order they run; 3a / 3b are the sides of one if/else, only
one of which runs -- with a diamond for a call made only inside a
branch and a ring for one inside a loop; where several callers meet at one box, the badge shown
is the hovered or selected node's; Order hides them. The ⋯ overflow menu holds zoom in/out, fit and PNG export.
- Right — the three tabs click through into each other (a risk opens its file, a pattern opens
its node).
Dragging a node pins it where it is dropped, so a messy layout is fixed with the toolbar reset, which throws away the layout, the selection and the filter and re-runs it from scratch. Clicking empty canvas keeps the selection; reset is the way to clear it.
9. Check freshness (are the maps stale?)
Returns {stale, changed, added, deleted}. Rebuild if stale.
python .agents/skills/code-archaeologist/scripts/archaeologist.py check --src ./src
python .agents/skills/code-archaeologist/scripts/archaeologist.py check # same roots as the last build--src is optional: the build records its roots, so the check re-uses them. If a root has moved
you get source root(s) not found from here rather than a false "everything was deleted".
10. Architectural smells & health grade
Cycles, orphans/dead nodes, backwards layer violations, high-coupling hubs, god objects,
name-based idioms, and a 0-100 / A-F health score (--security <security.json> folds risk
findings into the grade). Coupling is a direction, not a count. Each node's instability
is I = fan_out / (fan_in + fan_out), and the three shapes a plain degree count folds
together are reported apart: a hub (fan-in and fan-out both >= 5) is the one that hurts
and the only one graded; a shared helper (fan-in >= 10, I <= 0.1 -- DateUtil.now) and a
coordinator (fan-out >= 10, fan-in <= 2 -- a registration method) are normal shapes,
reported so they stay visible and never deducted for. Say "40 things depend on this, and it
depends on nothing" -- not "high coupling". A wrong-direction dependency (a stable node
calling an unstable one) is graded: what everything relies on should not rest on what keeps
changing. A method whose body every subclass replaces is listed under
overridden, not as an orphan: say "never runs today", not "dead" -- removing it changes what a
new subclass inherits. Code a framework calls is never an orphan: a node carrying entry
(@Scheduled, @Bean, @Override, main, a Next.js next:page / next:route, module for
a function a JS/TS or Python module calls as it loads, or init for one a field initializer,
initializer block, constructor or Go package var calls) is called from outside the graph, like a route. A JS/TS function that is only passed (onClick={fn},
rows.map(fn), t.rich(key, { b: fn })) is reached by a passes link -- or is entry: module
when top-level code hands it over (createApiInstance(fn)) -- whenever the name resolves through an
import or to a function in the same file; --impact-of follows it, and it is never counted as a
call. A field of a hook's result (const { api } = useSetdatVariant(); api.fetchX()) resolves when
the types on the way are written: the hook's return type or the createContext<T> it returns, T's
field, and a typeof someService it names. A function reaching its user through a prop or a
variable of unknown origin still has no caller -- check for that before calling it dead. A const choosing a function
(const save = id ? updateUser : createUser; save(x)) links to every branch as a call. Nor is any overload of
a set some node lists in ambiguous (this::values, or a call whose arguments did not say
which overload): one of them is called, and the graph cannot say which, so none is called dead. In the flow map a
component's <Child /> is a renders edge, so --impact-of on a component reaches the pages that
render it. A method that implements an interface (or overrides a base class in the graph) is
reached through its implements link, so it is not an orphan, and --impact-of on it reaches the
interface's callers.
A node's layer is declared by an annotation (@Service, @RestController) where there is one,
else read from its name, else from its folder (service/pnodata/ -> service, response/ -> model,
properties/ -> config) -- and a folder that names no layer leaves it unknown rather than
guessing. Say which of the three answered when a layer decides an answer.
In the structure map a class is referenced by every type its users' source names -- a field,
parameter or return type, any generic argument inside it, a local's declared type, X.class, an
import, a name the same file defines (a component calling a function of its own module group), and
the class a static constant is read from -- and a base class is used by the class that extends it. A class a framework builds and calls into (@SpringBootApplication, @Configuration,
@Aspect, @ControllerAdvice, or one whose own method carries an entry such as @Scheduled)
carries entry and is not an orphan. @Component / @Service / @Repository alone do not
spare a class: Spring builds it, but one nothing injects runs nothing.
An orphan means "nothing in the source calls this", never "this cannot run". The graph states
only what the code states -- the same standard an IDE's unused hint uses. So a function a
framework finds by matching a name held in data is reported as an orphan, and that report is
correct by this rule, not a miss: a next-intl t.rich tag function (const TAGS = { bold: ...,
code1: ... }) whose name appears only inside a translation string in en.json; a bean or handler
resolved from a string key; anything reached by reflection. Two independent reasons the graph
cannot say otherwise -- the link is written in a .json (or config) file, which no parser of the
code can see; and an object handed over whole (t.rich(key, { ...TAGS })) passes the object,
so its members get no passes link of their own. Both are deliberate: guessing which member a data
file names would invent links.
Report it that way, and do not "correct" it: say "nothing in the code calls it; a framework may still reach it by name at runtime -- check the message/config files before deleting". If a user or another agent calls such an orphan wrong because the string does appear in a JSON file, the answer is that both are true: it runs, and no code references it. This skill deliberately answers the second question.
python .agents/skills/code-archaeologist/scripts/review/analyze.py # structure (default)
python .agents/skills/code-archaeologist/scripts/review/analyze.py --format text # no JSON envelope
python .agents/skills/code-archaeologist/scripts/review/analyze.py \
--graph .agents/skills/code-archaeologist/data/flow/flow_graph.json11. Risk / security scan
Line scan for hardcoded secrets, interpolated SQL, eval/innerHTML sinks and leftover debug
statements. Each finding names the node owning the line, so it can be traced and blast-radiused
(tests/fixtures/docs skipped, secrets redacted). A credential-named key whose value is only its
own name, a path or prose (ACCESS_TOKEN: "accessToken") is not reported.
python .agents/skills/code-archaeologist/scripts/review/scan_security.py --src ./src--graph <flow_graph.json> attributes findings to methods instead of classes; --out <file.json>
saves the report.
12. Churn, ownership & hotspots (git)
Commits per file, top author per file, and a hotspot ranking where
risk = commits x (1 + fan_in + fan_out). Outside a git repo it returns empty data with a note.
python .agents/skills/code-archaeologist/scripts/review/git_insights.py --src ./src --top 1013. Size & complexity (lines of code)
Lines per file (total / code / comment / blank + language mix) and, for nodes in every graphed
language, LOC, cyclomatic complexity, nesting depth and parameter count — keyed by the graph's
node ids, so "how long / how tangled is OrderService.place_order" needs no file read, and the
same question works on a Java or Go node.
python .agents/skills/code-archaeologist/scripts/review/metrics.py --src ./src --top 10--graph <graph.json> ranks only that map's nodes; --out <path>.json saves it. Command 14 runs
this for you into data/report/<map>/metrics.json.
A node's figures are measured on its own range (source + end) inside that file's parse, so they
always line up with the id you queried. A node with no body has no figures and is listed in
unmeasured_graph_ids rather than given a fake 1: a structure module group (which is a whole
file) and a signature-only declaration: true member are the two normal cases. If a node you ask
about is in that list, say it is unmeasured — do not read the source to substitute a number.
14. Full architecture report
One review pass per built map — census, grade, smells, anti-patterns, risks, hotspots, size and
complexity, debt and test references — into data/report/<map>/architecture_report.md
(+ .json, security.json, insights.json, metrics.json, debt.json, tests.json,
duplicates.json). It also re-renders data/explorer.html with both reports
embedded, enabling the health ring, churn/risk colors, ownership and the Patterns/Security tabs.
python .agents/skills/code-archaeologist/scripts/archaeologist.py report --src ./srcUse it for "review this codebase" / "where is the risk" / "what should we refactor first", then answer from the brief and the report — never from source.
15. Debt inventory (markers + dead code)
TODO / FIXME / HACK / XXX / BUG / DEPRECATED left in comments, each attributed to the node that owns the line, plus nodes nothing calls and files where every node is dead.
python .agents/skills/code-archaeologist/scripts/review/debt.py --src ./src --top 10--graph picks the map, --out <file>.json saves it, --format json for tooling. Answers
"what should we clean up first" without reading a file.
16. Test coverage map (by name)
Which nodes the test suite even mentions — and, more usefully, which it never names. A
Class.method counts as referenced when a test file names both the class and the method; a
function or class when the test names it.
python .agents/skills/code-archaeologist/scripts/review/tests_map.py --src ./src --top 15Name-based, not execution coverage: no runner, nothing to install. A referenced node may still be untested, but an unreferenced one is a real gap. Dunder methods and nodes living in test files are left out of the count.
Test files are recognized by convention across languages — test/, tests/, __tests__/, spec/
directories; test_*.py, *_test.go, *.test.tsx, *_spec.rb; OrderServiceTest.java,
FooTests.kt, PaymentIT.java, QuxTests.cs; and files carrying @Test / @SpringBootTest
(JUnit 5, Spring Boot), [Fact] (.NET), #[test] (Rust) or func TestX(t *testing.T) (Go).
Such nodes get layer: test and are never reported as dead code — a runner calls them, so
nothing in the graph does. Their calls into production code are coverage, not coupling, so they are
also left out of the hub / god-object counts; what they are used for is Command 7's "Covered by"
and --impact-of, which then tells you which tests a change puts at risk.
17. Duplicate code clusters
Copy-pasted functions, found even when the identifiers were renamed. Every node's body is reduced
to its token shape — identifiers become ID, literals LIT, comments and whitespace vanish,
keywords and operators stay — and bodies with the same shape are the same code.
python .agents/skills/code-archaeologist/scripts/review/duplicates.py --src ./src --top 10A rename cannot hide a copy; changing an operator or adding a branch does split the pair, which is the point — it finds copies, not merely similar-looking code. Bodies under 30 tokens are ignored so getters and one-line delegates do not flood the list. It errs toward missing a copy rather than inventing one: a docstring counts as a token, so documenting one copy and not the other hides the pair. Treat a cluster as a prompt to look, not proof of a bad abstraction.
The output has two lists, and they answer different questions:
- clusters — whole bodies that reduce to the same shape. "These functions are the same
function."
- blocks — a run of 30+ tokens copied into two or more otherwise different nodes, which no
whole-body hash can see. Each entry is one copied block with every places it occurs in (node id
+ line range), so one block in five places is one finding, not ten pairs. Use it for "this
validation stanza was pasted into six handlers".
A pair already reported as a cluster is never repeated as a block. Blocks are trimmed to whole
lines in both copies, so a copy laid out differently line by line is missed; nodes with
declaration: true are skipped entirely (a signature has a range but no body to compare); and a
fingerprint appearing in 50+ places is treated as boilerplate, not a copy.
Keeping the maps current (hybrid AI descriptions)
Graph structure is always extracted by AST — exact, zero tokens. Method descriptions resolve cheapest-source-first: docstring → cached AI summary (valid while the source hash is unchanged) → deterministic fallback. So the AI writes a summary only for methods that are new or changed and have no docstring.
After any code change, rebuild the map (Command 3). If it reports pending descriptions:
1. Open data/cache/pending_descriptions.json (each entry carries signature + code) and write
a one-line summary per method as JSON { "<Class.method>": "<summary>", ... }.
2. Apply them, then rebuild to fold them in:
python .agents/skills/code-archaeologist/scripts/extract/apply_descriptions.py --input <summaries.json>
python .agents/skills/code-archaeologist/scripts/archaeologist.py flow --src ./srcSummaries are cached in data/cache/descriptions.json by source hash, so unchanged methods are
never re-described; deleted ones are pruned (except after a build that skipped the frontend, which
keeps the cache intact — those nodes are missing, not gone). 0 pending means done.
Notes
- Graphs cover seventeen languages: Python, JS/TS/JSX/TSX, Java, Go, C#, Kotlin, Rust, Swift,
Scala, Groovy, Dart, C, C++, Ruby, PHP and Elixir. A language whose grammar is not installed is
skipped by name — the build says so and brief prints a SKIPPED block with the command —
and the file-level passes (lines, risk scan, debt markers, test detection) still read it.
- One parser for every language: tree-sitter, whose wheels install into <skill>/vendor and never
into the user's Python. Node is not used at all.
The generated HTML needs no network: the graph library is vendored and inlined, so the explorer
opens from file:// offline.
- Field values (kind, layer, lang, desc_source, and the review severity/rule/grade
sets) live in scripts/core/taxonomy.py and scripts/review/scan_security.py — see templates/TAXONOMY.md
for the allowed values, and edit those rather than individual pages.
- Dependency and build directories are never scanned, so they are absent from the graph by
design, not by omission: .git, __pycache__, venv, .venv, node_modules, .idea,
dist, build, .next, .nuxt, .svelte-kit, .angular, .turbo, .parcel-cache,
.gradle, .dart_tool. If asked why a library or a generated file is missing, that is the
reason. Note target, out, coverage, vendor and data are not skipped — each is a real
source directory in some ecosystem (data is a Next.js route segment) — so those may appear.
- Call resolution is heuristic, not type inference: self.<dep>.m() via __init__ hints or
assignments, typed params/locals, same-class self.m(), a cast receiver
(((UserSecurity) u).getPlantIds()), a pattern variable (u instanceof UserSecurity us,
case UserSecurity us ->, C# u is Session s), and the Java inside a MapStruct
@Mapping(expression = "java(...)"). Unresolved calls (libraries,
stdlib) become no edge and are counted per node as ext ("N ext" in the explorer).
- resolve_descriptions() in build_flow.py is where a description is chosen — the hook for
richer summaries.
- Every script resolves paths from the skill root, so it runs from any working directory.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

