fossilizer
v0.2.0
Published
Evidence-based code documentation. Every claim traceable to source.
Maintainers
Readme
fossilize
evidence-based code documentation. every claim traceable to source.
fossilize generates documentation from three sources of truth: what the AST says (function signatures, types, imports, exports), what the tests prove (behavioral contracts, error paths, edge cases), and what git says (who changed what, when, how often). every statement in the output is backed by evidence you can verify.
self-hosted: fossilize documents its own codebase -- 63 source files, 828 definitions, 2863 resolved cross-file links, 138 functions indexed, 47 with test evidence, medium confidence overall. 753 tests across 26 test suites.
install
published as fossilizer (the command it installs is fossilize):
npm install -g fossilizerquick start
npx fossilizer .after a global install the command is fossilize; the rest of this readme uses that form.
this parses your source and test files, builds a scope graph, maps tests to functions, loads coverage data, and generates evidence-backed documentation.
output goes to .fossilize/ by default:
docs.md-- markdown documentationdocs.json-- structured index (for agents, dashboards, tooling)docs.html-- standalone HTML report with navigation and searchsnapshot.json-- index snapshot for drift detectionarchitecture.mmd-- mermaid module dependency diagram
commands
fossilize [dir] -- generate documentation for a project
fossilize drift [dir] -- compare current code against the last snapshot, flag changes
fossilize arch [dir] -- show module dependency graph
fossilize init [dir] -- create a .fossilize.json config file (auto-detects directories)
fossilize index [dir] -- build and save the function index without generating docs
fossilize query <symbol> [dir] -- emit the evidence packs for symbols matching <symbol> as JSON on stdout (for agents and tooling)
flags
--output, -o -- output format: markdown, json, html, both (md+json), all
--coverage, -c -- path to coverage file (lcov or istanbul json)
--test-dir, -t -- additional test directory (repeatable)
--out-dir, -d -- output directory (default .fossilize)
--internal -- include non-exported functions
--watch, -w -- watch for changes and regenerate (debounced, incremental)
--quiet, -q -- suppress human-readable output, emit machine-readable JSON to stdout. useful for CI pipelines, scripting, and tooling integration
--ai -- generate short prose summaries per function using Claude Opus 4.8. opt-in and paid. requires ANTHROPIC_API_KEY in the environment
--ai-max <n> -- cap how many functions get an AI summary in a single run
config
create .fossilize.json in your project root (or run fossilize init):
{
"sourceDirs": ["src"],
"testDirs": ["tests"],
"coveragePath": "coverage/lcov.info",
"outDir": ".fossilize",
"output": "both"
}CLI flags override config file values. unknown fields in config produce warnings to stderr so typos are caught early.
what it generates
for each public function, fossilize produces:
- signature with params, return type, async status
- call graph -- who calls this function, what it calls
- middleware -- decorators (NestJS, Flask, Django) and Express/Koa/Hono middleware chains detected from route registrations
- test evidence -- which tests exercise it, what assertions they make, which describe block they belong to
- behavioral contract -- synthesized from test names and assertions: what inputs are handled, what outputs are verified, what errors are tested
- coverage -- runtime execution count, branch coverage
- git history -- last author, modification date, commit count
- confidence level -- high (signature + tests + coverage + git), medium (two evidence types), low (signature only)
ai summaries
by default fossilize generates zero prose. it aggregates evidence deterministically. pass --ai to add a 1 to 3 sentence narration per function, written by Claude Opus 4.8 and grounded strictly in the evidence fossilize already collected.
export ANTHROPIC_API_KEY=sk-ant-...
npx fossilize . --aithe model only sees structured evidence (signature, call graph, test names and assertions, coverage, contract). it never sees raw source bodies, and the system prompt forbids inventing anything not in the evidence. so summaries stay traceable, same as every other claim.
guardrails, because this is a paid api:
- opt-in only. no network call happens without
--aiorai.enabledin config. without a key it warns and skips, the rest of the pipeline runs unchanged. - hard monthly cap. a local ledger at
.fossilize/ai-ledger.jsontracks spend per month and aborts before exceedingmaxMonthlyUsd(default $10). worst case math is documented insrc/ai/ledger.ts. - content-hash cache.
.fossilize/ai-cache.jsonkeys each summary by a hash of its evidence. unchanged functions are never re-sent, so repeat runs are nearly free and a function can never re-trigger its own call. - per-run function cap via
maxFunctions(default 200) or--ai-max. - the api key is read only from the environment, never logged, never written to any snapshot, cache, ledger, or doc.
privacy note: with --ai, function metadata (signatures, test names, assertions) is sent to Anthropic. source file bodies are not.
config block (all optional):
{
"ai": {
"enabled": false,
"model": "claude-opus-4-8",
"maxFunctions": 200,
"maxMonthlyUsd": 10
}
}drift detection
npx fossilize drift .compares the current index against the last saved snapshot. detects:
- new functions added
- functions removed
- signature changes
- file moves
- coverage loss
- middleware chain changes
- export status changes
exits with code 1 if drift is detected. useful in CI to catch undocumented changes.
architecture graph
npx fossilize arch .outputs a mermaid diagram showing module dependencies, entry points, leaf modules, circular dependencies, and max dependency depth.
behavioral contracts
derived entirely from test evidence. for each tested function, fossilize synthesizes:
- accepts -- parameter types plus what input patterns are tested (null handling, empty input, boundary values, invalid input)
- returns -- return type plus what output properties are asserted (exact values, truthiness, collection membership, numeric bounds)
- errors -- what error conditions are tested (throws, rejects, graceful handling)
- side effects -- what mutations are verified (writes, logs, event emissions, external calls)
contracts are scored by completeness: a function with signature + tests + error handling + input validation gets "high" confidence.
ci integration
use --quiet for machine-readable output in pipelines:
# fail CI if docs have drifted from code
npx fossilize drift . --quiet
# generate docs and capture summary
RESULT=$(npx fossilize . --quiet)
echo "$RESULT" | jq .totalFunctionsthe drift command exits non-zero when changes are detected, making it a natural CI gate.
languages
typescript and python, via tree-sitter. the parser detects language from file extension and selects the appropriate grammar automatically.
how it works
- parse -- tree-sitter parses source and test files into concrete syntax trees
- extract -- language-specific extractors pull definitions, references, exports, decorators, middleware chains, and test cases from the CSTs
- scope graph -- cross-file resolution of imports, calls, re-exports through barrel files, and type references
- coverage -- lcov and istanbul/v8 JSON coverage files are parsed, normalized, and matched to functions by name or line range
- git -- blame and log commands extract historical evidence per file
- index -- all evidence is collected per function into a structured index with confidence scoring
- contracts -- test names and assertion patterns are synthesized into behavioral descriptions
- generate -- pluggable renderers produce markdown, JSON, or HTML output
- drift -- index snapshots are compared to detect undocumented changes
resolution and precision
a deterministic record is only useful if it is true, so resolution refuses to guess. a reference resolves in this order:
- same file -- a local definition shadows any import of the same name.
- import-scoped -- a name imported into the file resolves through that import's source module (so an imported
formatbinds to the file it came from, not some other same-named export). - unambiguous global -- a bare name resolves only when there is exactly one definition, or a single exported one. multiple equally-plausible candidates resolve to nothing rather than a wrong edge.
member calls are resolved by receiver type, and only when that type is statically known:
this.method()(and Pythonself.) binds to the enclosing class.this.field.method()binds through the field's declared type, taken from field declarations, constructor parameter-properties, andthis.field = param/this.field = new T()assignments.param.method()binds through the parameter's declared type.
a receiver whose type needs deeper inference (a return value, a multi-hop chain like this.config.dep.method()) is left unresolved rather than matched to a random same-named method. the guiding rule holds: a missing edge is honest, a wrong edge is not.
fossilize also emits extends, implements, and type_reference edges for inheritance and type relationships.
measuring it. the TypeScript compiler's type checker is scope- and import-aware, so it serves as a ground-truth oracle. a harness (tests/oracle/) diffs fossilize's call edges against the checker and reports precision and recall. on fossilize's own source the call graph scores roughly 0.996 precision / 0.98 recall. on real external repos precision holds in the 0.90 to 0.94 band. recall depends on style: ~0.96 for import-and-function-heavy code, and ~0.83 for instance-method-heavy code after typed-receiver resolution recovers the dependency-injection pattern (an instance-heavy repo measured 0.62 -> 0.83 recall once this.field.method() resolved through field types, with precision unchanged). the remaining gap is multi-hop chains the resolver deliberately declines rather than guess: a missing edge over a wrong one. reproduce with FOSS_BASELINE=1 npx vitest run tests/oracle/baseline-src.test.ts (self-host) or FOSS_REPO=/path/to/repo npx vitest run tests/oracle/baseline-repo.test.ts (any repo with a tsconfig).
drift, beyond structure
fossilize drift reports four buckets: new, removed, changed, and stale. a node is stale when its signature is unchanged but its behavioral evidence moved -- the synthesized contract or the test assertions changed. that is the dangerous case: the function looks identical to callers and to docs while provably doing something different. stale nodes count toward the non-zero drift exit code.
agent context packs
pass --min-confidence high (or medium/low) to emit documentation containing only nodes at or above that confidence. this produces a high-signal, evidence-backed context pack for feeding an LLM, without the low-confidence noise. the saved drift snapshot stays complete regardless, so filtering never causes false "removed" drift.
for targeted lookups, fossilize query <symbol> returns just the matching evidence packs as JSON on stdout, ordered by confidence:
# what does fossilize actually know about createUser?
npx fossilize query createUser . --min-confidence mediumeach result carries the signature, the proven call graph, mapped tests and assertions, coverage, git, the behavioral contract, and unresolvedCallCount (how many of the function's calls could not be resolved, so a consumer knows whether the call graph is complete). this is the read surface an agent queries instead of guessing from raw source.
programmatic API
import {
SourceParser,
ScopeGraphBuilder,
buildIndex,
generateMarkdown,
generateHtml,
synthesizeContracts,
buildArchitectureGraph,
detectDrift,
} from "fossilize";all pipeline stages are independently importable and composable. types are exported for consumers building custom tooling on top of the index.
license
MIT
