# Graphify-Labs/graphify — codebase unpack

> Canonical page: https://codevetter.com/unpack/graphify

This is a deterministic Repo Unpack scan of Graphify-Labs/graphify — a structural map, not a review, security audit, or maintainer-endorsed description.

- Scanned commit: 20a20d30d8e7eef77675651f0199d87f913bd3e7 (2026-09-20)
- Last upstream commit: test(ts): de-flake the normalizer scaling test that reddened v8 CI
- Generated by codevetter 1.12.0 on 2026-09-22
- 897 files scanned · 11.1 MB

## Languages

- Python: 402 files
- Markdown: 362 files
- C: 9 files
- JSON: 8 files
- TypeScript: 8 files
- YAML: 8 files
- C#: 7 files
- SQL: 7 files

## Repository structure

- tests/ — 405 files
- graphify/ — 223 files
- tools/ — 174 files
- docs/ — 37 files
- worked/ — 34 files
- .github/ — 8 files
- scripts/ — 1 files

Probable source entrypoint: `pyproject.toml` (inferred from the tree, not upstream docs)

## Package

- pyproject.toml: graphifyy v0.9.65 — 56 declared dependencies

## Structural health signals

Average score 6.7/10 across 96 files; 40 hotspots. These are deterministic review leads, not proof of a bug.

## Recent history

- 20a20d30d8e7 (2026-09-20) test(ts): de-flake the normalizer scaling test that reddened v8 CI
- 7ca736cf94cd (2026-09-20) release: 0.9.65 — Pillow CVE floor, Go/Swift/Java/Verilog extractor fixes, JS block scoping, fail-closed eviction deadlock fix, vis-network crash + tooltip encoding, repo-link/docs
- 86d8aadd3b08 (2026-09-20) fix(export): complete the vis-network crash fix and pin the tooltip XSS boundary (#3699, #3686 follow-ups)
- 124c26348829 (2026-09-20) fix(export): prevent vis-network stack overflow crash on HTML render
- 785ee5a426a8 (2026-09-19) Fix HTML entity encoding in graph tooltips

## Overview

graphify (PyPI package `graphifyy`, CLI `graphify`) is a Python 3.10+ CLI plus an installable AI-assistant skill that turns a folder of code, docs, papers, images, and video/audio into a persistent, queryable knowledge graph under graphify-out/ (graph.json, graph.html, GRAPH_REPORT.md). Code is mapped locally and deterministically with ~30 tree-sitter grammars and per-language resolvers, while docs/media go through an optional semantic LLM pass (host-agent subagents or configured API backends). Every edge carries an EXTRACTED/INFERRED/AMBIGUOUS confidence tag, and the finished graph is queried through CLI subcommands (query/path/explain/affected/god-nodes) or an MCP server (stdio or HTTP). It targets users of 20+ coding assistants (Claude Code, Codex, Cursor, Gemini CLI, etc.) via generated per-platform skill bundles.

## System map

A single-package Python CLI (`graphifyy`) structured as a staged pipeline: detect -> extract -> build -> cluster -> analyze -> report -> export, with each stage in its own module communicating through plain dicts and NetworkX graphs. The same library is driven two ways: headless via `graphify extract` subcommands in cli.py, or by an AI assistant following the packaged skill.md runbook that shells out to the library. Persistence is entirely file-based under graphify-out/ (graph.json, manifest.json, cache/, sidecars). Deployment surfaces are the PyPI wheel, per-platform skill files, and a Docker image that runs the MCP HTTP server.
- The canonical pipeline is detect() -> extract() -> build() -> cluster() -> analyze helpers -> report.generate() -> export.to_*(); each stage is a separate module exchanging plain dicts/NetworkX graphs with no shared state outside graphify-out/. — [ARCHITECTURE.md#L8-11](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/ARCHITECTURE.md#L8-L11)
- Two console entry points ship in the wheel: `graphify` -> graphify.__main__:main and `graphify-mcp` -> graphify.serve:_main. — [pyproject.toml#L110-112](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/pyproject.toml#L110-L112)
- graphify/__main__.py is a thin dispatcher: it re-exports the install subsystem from graphify/install.py (~90 names) and dispatch_command from graphify/cli.py, then main() wraps _run_cli() with broken-pipe handling. — [graphify/__main__.py#L29-131](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/__main__.py#L29-L131) [graphify/__main__.py#L486-527](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/__main__.py#L486-L527)
- dispatch_command(cmd) in graphify/cli.py is the single command table for all non-install subcommands: provider, prs, hook, query, affected, god-nodes, save-result, reflect, path, explain, diagnose, add, watch, cluster-only/label, update, hook-check, hook-guard, check-update, tree, merge-driver, merge-graphs, clone, export, benchmark, global, extract, cache-check, merge-chunks, merge-semantic. — [graphify/cli.py#L1064-4832](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/cli.py#L1064-L4832)
- All persistent state lives under the output directory `graphify-out`, overridable via the GRAPHIFY_OUT env var, centralized in graphify/paths.py (GRAPHIFY_OUT, out_path, default_graph_json) so security/path guards honor the same override. — [graphify/paths.py#L26](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/paths.py#L26) [graphify/paths.py#L359-375](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/paths.py#L359-L375)
- graph.json is a NetworkX node-link JSON file; serve.py _load_graph rehydrates it with json_graph.node_link_graph, forces directed=True for rendering, and attaches a work-memory overlay sidecar. — [graphify/serve.py#L44-85](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/serve.py#L44-L85)
- AST extraction scales via concurrent.futures.ProcessPoolExecutor (_extract_parallel -> _extract_single_file worker, module-level for pickling), capped by cpu_count or GRAPHIFY_MAX_WORKERS, clamped to 61 on Windows, with sequential fallback on BrokenProcessPool/OSError. — [graphify/extract.py#L6558-6745](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/extract.py#L6558-L6745)
- The MCP server is built once by serve.py::_build_server and served over stdio (serve()) or Streamable HTTP via starlette/uvicorn (serve_http), with optional X-API-Key/Bearer auth and a warning when binding 0.0.0.0 unauthenticated. — [graphify/serve.py#L1795-1860](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/serve.py#L1795-L1860) [graphify/serve.py#L2561-2615](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/serve.py#L2561-L2615)
- The Dockerfile builds a python:3.12-slim image installing the [mcp] extra, runs as non-root uid 10001, and launches `python -m graphify.serve` with an HTTP transport default; graph.json is mounted at runtime, never baked in. — [Dockerfile#L10-25](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/Dockerfile#L10-L25)
- Core dependencies are networkx, numpy, rapidfuzz, tree-sitter plus ~26 tree-sitter grammar packages; optional extras (pdf, office, video, mcp, neo4j, falkordb, sql, postgres, terraform, pascal, ocaml, commonlisp, robot, llm backends, all) gate niche extractors and integrations. — [pyproject.toml#L14-45](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/pyproject.toml#L14-L45) [pyproject.toml#L52-108](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/pyproject.toml#L52-L108)
- Per-assistant skill bundles are generated artifacts: tools/skillgen/gen.py renders graphify/skill*.md, graphify/skills/<host>/references/, and graphify/always_on/*.md from tools/skillgen/fragments/, with CI drift guards (--check, --audit-coverage, --monolith-roundtrip). — [tools/skillgen/gen.py#L1-25](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/tools/skillgen/gen.py#L1-L25) [.github/workflows/ci.yml#L11-47](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/.github/workflows/ci.yml#L11-L47)
- Publishing is trusted-publishing OIDC to PyPI on GitHub release (publish.yml), with a version==tag guard; release-graph.yml dogfoods the tool by building a graph of graphify itself and uploading graphify-self-graph.tar.gz as a release asset. — [.github/workflows/publish.yml#L1-45](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/.github/workflows/publish.yml#L1-L45) [.github/workflows/release-graph.yml#L1-60](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/.github/workflows/release-graph.yml#L1-L60)
- The repo's own AGENTS.md instructs agents to read graphify-out/GRAPH_REPORT.md and run `graphify update .` after code changes — the project dogfoods its own graph. — [AGENTS.md](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/AGENTS.md)

## Feature catalog

User-facing surface = one CLI with ~30 subcommands, a generated `/graphify` skill for 20+ AI assistants, an MCP server exposing 10 graph tools, and a large set of export formats and corpus ingestors. Features span: corpus detection (code/docs/papers/images/video), AST extraction across ~40 language extensions, semantic (LLM) extraction with pluggable backends, community detection + report, multiple export sinks, git-hook auto-rebuild, cross-repo/global graphs, PR triage via `gh`, and work-memory reflection.
- `graphify extract <path>` is the headless full pipeline (detect -> AST -> semantic -> merge -> build -> cluster -> outputs) with flags --backend/--model/--mode deep/--out/--no-cluster/--code-only/--no-dedup/--postgres/--cargo/--max-workers/--token-budget/--exclude/--no-gitignore/--force/--allow-partial/--timing. — [graphify/cli.py#L3218-3420](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/cli.py#L3218-L3420)
- Graph query commands: `query` (BFS/DFS with --budget/--context), `path` (shortest path), `explain`, `affected` (reverse traversal over DEFAULT_AFFECTED_RELATIONS incl. calls/imports/inherits/dynamic_import), `god-nodes`, and `diagnose multigraph`. — [graphify/cli.py#L1202-1322](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/cli.py#L1202-L1322) [graphify/affected.py#L11-29](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/affected.py#L11-L29) [graphify/cli.py#L1549-1950](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/cli.py#L1549-L1950)
- Export formats via `graphify export <fmt>`: html, callflow-html, obsidian, wiki, svg, graphml, neo4j, falkordb; push_to_neo4j upserts via the neo4j driver with label/relation sanitization against Cypher injection. — [graphify/cli.py#L2782-2790](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/cli.py#L2782-L2790) [graphify/exporters/graphdb.py#L9-40](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/exporters/graphdb.py#L9-L40)
- The MCP server registers 10 tools — query_graph, get_node, get_neighbors, get_community, god_nodes, graph_stats, shortest_path, list_prs, get_pr_impact, triage_prs — and injects an optional `project_path` into every schema for multi-project serving. — [graphify/serve.py#L1866-2019](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/serve.py#L1866-L2019)
- `graphify install/uninstall` supports ~20 assistant platforms through _PLATFORM_CONFIG (claude, codex, opencode, kilo, aider, copilot, claw, droid, trae, trae-cn, hermes, kiro, pi, codebuddy, antigravity, windows, kimi, amp, ...), each mapping a generated skill-<platform>.md to a per-platform destination dir. — [graphify/install.py#L120-260](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/install.py#L120-L260) [graphify/__main__.py#L536](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/__main__.py#L536)
- `graphify hook install` writes post-commit and post-checkout git hooks that detect a working Python interpreter (pinned path, .graphify_python file, launcher shebang, uv tool envs, PATH fallback) and detached-launch `graphify.watch._rebuild_code` on changed files. — [graphify/hooks.py#L19-138](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/hooks.py#L19-L138) [graphify/hooks.py#L144-352](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/hooks.py#L144-L352)
- `graphify update <path>` / `watch` / post-commit hooks all funnel into watch.py::_rebuild_code: AST-only incremental rebuild (no LLM), serialized by a per-repo flock with a .pending_changes queue drained by the lock holder. — [graphify/cli.py#L2403-2461](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/cli.py#L2403-L2461) [graphify/watch.py#L1355-1441](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/watch.py#L1355-L1441)
- Semantic/LLM extraction supports backends claude, kimi, ollama, gemini, openai, deepseek, azure, bedrock, and a `claude-cli` path that shells to the claude binary; `graphify provider add/list/show/remove` registers custom OpenAI-compatible providers in a user-global JSON file. — [graphify/llm.py#L101-200](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/llm.py#L101-L200) [graphify/cli.py#L1065-1181](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/cli.py#L1065-L1181)
- Non-code ingestors: mcp_ingest.py turns .mcp.json/claude_desktop_config.json into mcp_server/mcp_command/mcp_package/env_var nodes (env VALUES never read); manifest_ingest.py makes package manifests (pyproject, go.mod, pom.xml...) into depends_on edges; pg_introspect.py introspects live Postgres via psycopg; cargo_introspect.py handles Rust workspaces; transcribe.py does faster-whisper/yt-dlp video-to-text; google_workspace.py converts .gdoc/.gsheet shortcuts. — [graphify/mcp_ingest.py#L1-40](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/mcp_ingest.py#L1-L40) [graphify/pg_introspect.py#L1-35](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/pg_introspect.py#L1-L35) [graphify/transcribe.py#L1-30](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/transcribe.py#L1-L30) [graphify/extract.py#L6502-6516](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/extract.py#L6502-L6516)
- Cross-repo features: `merge-graphs` union-merges graph.json files, `merge-driver` is a git merge driver for graph.json, and `graphify global add/remove/list` maintains a cross-repo graph at ~/.graphify/global-graph.json with per-repo tags. — [graphify/cli.py#L2551-2758](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/cli.py#L2551-L2758) [graphify/global_graph.py#L1-30](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/global_graph.py#L1-L30) [graphify/cli.py#L3152-3217](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/cli.py#L3152-L3217)
- Work-memory features: `save-result` stores Q&A outcomes into graphify-out/memory/, and `graphify reflect` aggregates useful/dead_end/corrected signals with time-decay scoring into graphify-out/reflections/LESSONS.md (deterministic, no LLM). — [graphify/reflect.py#L1-40](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/reflect.py#L1-L40) [graphify/cli.py#L1461-1548](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/cli.py#L1461-L1548)
- Auxiliary commands: `prs` (PR dashboard via gh CLI), `clone` (clone GitHub repo into ~/.graphify/repos), `add <url>` (fetch URL to ./raw with author/contributor tags), `benchmark` (corpus vs subgraph token comparison), `tree` (HTML tree view), `cache-check`, `cluster-only`, `label`, `check-update`, `merge-chunks`, `merge-semantic`, `diagnose`. — [graphify/cli.py#L1182-1201](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/cli.py#L1182-L1201) [graphify/cli.py#L2480-2606](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/cli.py#L2480-L2606) [graphify/cli.py#L3135-3151](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/cli.py#L3135-L3151) [graphify/cli.py#L4649-4831](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/cli.py#L4649-L4831) [graphify/prs.py#L140-215](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/prs.py#L140-L215)
- Hook-integration commands `hook-check` and `hook-guard` emit PreToolUse nudge/deny JSON payloads consumed by Claude Code/Gemini hooks; strict mode (GRAPHIFY_HOOK_STRICT=1) denies the first raw source read per session and redirects the agent to `graphify query`. — [graphify/cli.py#L18-81](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/cli.py#L18-L81) [graphify/cli.py#L814-944](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/cli.py#L814-L944) [graphify/cli.py#L2463-2479](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/cli.py#L2463-L2479)

## Data flow

Input boundary is detect(), which walks the corpus, classifies files into code/document/paper/image/video, and filters sensitive paths. Code files flow through extract(): suffix/filename/shebang dispatch to per-language extractors emitting {nodes, edges} dicts, then cross-file resolution passes (LanguageResolver registry) turn imports/member calls into INFERRED edges. Docs/papers/images flow through the semantic pass — either host-agent subagents writing chunk JSONs (skill.md path) or llm.extract_corpus_parallel packing token-budgeted chunks against a configured backend. Merged extractions are validated, deduplicated, built into a NetworkX graph, clustered, and exported to graphify-out/graph.json plus reports; incremental updates re-extract only changed files via manifest.json hashing.
- detect() classifies files via CODE_EXTENSIONS (~90 suffixes), DOC_EXTENSIONS, PAPER/IMAGE/VIDEO/OFFICE extension sets, a shebang interpreter check for extensionless executables, and filters _SENSITIVE_PATTERNS (.env, .pem, id_rsa, .npmrc, ...) and credential-store dirs (.ssh/.aws/.gnupg/.gcloud) before anything is read for extraction. — [graphify/detect.py#L25-49](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/detect.py#L25-L49) [graphify/detect.py#L110-147](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/detect.py#L110-L147)
- Office/PDF inputs are screened as potential zip/XML bombs before parsing: _zip_within_caps enforces a 50 MiB raw cap, 512 MiB decompressed cap, and 200:1 compression-ratio limit with chunked streaming so the check itself never materializes the bomb. — [graphify/detect.py#L55-108](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/detect.py#L55-L108)
- _get_extractor() dispatches a file to its extractor by exact filename first (.blade.php, MCP config, package manifests), then content sniffing for ambiguous suffixes (.h ObjC/C++ markers, .m ObjC-vs-MATLAB), then shebang for extensionless files (_SHEBANG_DISPATCH), then the _DISPATCH suffix table covering ~40 languages. — [graphify/extract.py#L6502-6545](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/extract.py#L6502-L6545) [graphify/extract.py#L6270-6373](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/extract.py#L6270-L6373) [graphify/extract.py#L6411-6426](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/extract.py#L6411-L6426)
- Each extractor returns {nodes:[{id,label,source_file,source_location}], edges:[{source,target,relation,confidence}]}; validate.py::validate_extraction enforces this schema before build() consumes it, and confidence is EXTRACTED/INFERRED/AMBIGUOUS. — [ARCHITECTURE.md#L50-73](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/ARCHITECTURE.md#L50-L73) [graphify/validate.py#L10](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/validate.py#L10)
- extract() is two-phase: phase 1 checks the per-file AST cache (load_cached, keyed by content hash under cache/ast/v{pkg_version}-s{schema}/); phase 2 runs uncached files through ProcessPoolExecutor or sequentially, then runs registered LanguageResolver passes gated on which suffixes are present. — [graphify/extract.py#L6787-6918](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/extract.py#L6787-L6918) [graphify/resolver_registry.py#L59-85](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/resolver_registry.py#L59-L85) [graphify/cache.py#L22-40](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/cache.py#L22-L40)
- The semantic pass wraps every file in a hash-stamped `<untrusted_source path=... sha256=...>` block, neutralizes known jailbreak tokens via _neutralise_injection_sentinels, packs chunks by tiktoken/char-estimate token budgets (_pack_chunks_by_tokens), and parses LLM output with _parse_llm_json's balanced-object salvage. — [graphify/llm.py#L28-38](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/llm.py#L28-L38) [graphify/llm.py#L572-660](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/llm.py#L572-L660) [graphify/llm.py#L2143-2203](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/llm.py#L2143-L2203) [graphify/llm.py#L1037-1199](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/llm.py#L1037-L1199)
- The skill path's merge order is AST nodes first, then semantic nodes deduplicated by id — so a semantic node silently overwrites an AST node with the same id (richer labels win); build_from_json then mints `external` stub nodes for import-family edges whose targets are outside the graph. — [graphify/build.py#L1-21](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/build.py#L1-L21) [graphify/build.py#L75-137](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/build.py#L75-L137) [graphify/skill.md#L361-391](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/skill.md#L361-L391)
- build.py collapses parallel edges to a single edge per (source,target) pair with a 'specific beats generic' precedence — references/uses/mentions lose to calls/imports/inherits — and drops cross-language-family phantom edges via _EDGE_LANG_FAMILY. — [graphify/build.py#L56-77](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/build.py#L56-L77) [graphify/build.py#L139-150](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/build.py#L139-L150)
- cluster() partitions the graph (Leiden via graspologic or native backend, hub exclusion optional), analyze.py computes god_nodes/surprising_connections/suggest_questions/find_import_cycles, and report.generate() renders GRAPH_REPORT.md; export.to_json writes graph.json but refuses to overwrite a larger existing graph (shrink guard, bypassed by --force/GRAPHIFY_FORCE). — [graphify/cluster.py#L232-373](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/cluster.py#L232-L373) [graphify/analyze.py#L109-180](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/analyze.py#L109-L180) [graphify/export.py#L229-271](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/export.py#L229-L271) [graphify/skill.md#L413-437](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/skill.md#L413-L437)
- Incremental updates are driven by graphify-out/manifest.json: detect_incremental diffs the corpus against stamped per-file hashes; _stamped_manifest_files only re-stamps semantic files that actually produced output so failed/partial extractions are re-queued next run; _stale_graph_sources prunes nodes whose source_file left the corpus. — [graphify/cli.py#L88-168](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/cli.py#L88-L168) [graphify/cli.py#L221-250](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/cli.py#L221-L250) [graphify/manifest.py#L1-4](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/manifest.py#L1-L4)
- Read path: `graphify query` loads graph.json (size-capped by check_graph_file_size_cap, default 512 MiB, overridable via GRAPHIFY_MAX_GRAPH_BYTES), rebuilds an undirected NetworkX graph preserving per-edge _src/_tgt direction markers, runs _query_graph_text BFS/DFS, logs to querylog, and touches a query stamp. — [graphify/cli.py#L1244-1322](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/cli.py#L1244-L1322) [graphify/security.py#L32-66](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/security.py#L32-L66) [graphify/security.py#L357-383](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/security.py#L357-L383)
- Post-commit hook flow: git hook shell block resolves a Python interpreter, detached-launches a python -c payload that sets a timeout watchdog, queues changed paths via .pending_changes, takes a non-blocking flock, and calls _rebuild_code; a concurrent commit's changes are merged in by the lock holder rather than dropped. — [graphify/hooks.py#L144-352](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/hooks.py#L144-L352) [graphify/watch.py#L166-224](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/watch.py#L166-L224) [graphify/watch.py#L1355-1441](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/watch.py#L1355-L1441)

## Behavior traces

Three traced flows: (1) `graphify install` copies the platform's generated skill bundle and registers hooks/always-on instruction blocks; (2) `graphify extract .` runs the full headless pipeline detect->extract->resolve->build->cluster->export with shrink guards; (3) `graphify query`/`graphify update` read and incrementally maintain graph.json. A fourth trace covers the git post-commit auto-rebuild. All flows converge on the same modules: detect/extract/build/cluster/report/export under graphify/.
- Startup trace (`graphify <cmd>`): __main__.main() -> _run_cli() reconfigures stdout/stderr to utf-8, checks every platform skill destination's .graphify_version stamp for staleness (skipped for install/uninstall/hook-check/hook-guard), prints usage if no args, then routes install commands to install.dispatch_install_cli and everything else to cli.dispatch_command. — [graphify/__main__.py#L486-536](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/__main__.py#L486-L536) [graphify/__main__.py#L163-233](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/__main__.py#L163-L233)
- Install trace: dispatch_install_cli -> _canonical_platform/_PLATFORM_CONFIG lookup -> _platform_skill_destination -> _copy_skill_file copies graphify/skill-<host>.md (plus skills/<host>/references/ sidecar via _install_skill_references) into e.g. ~/.claude/skills/graphify/SKILL.md, then _write_version_stamp; hook platforms additionally write PreToolUse hook config (e.g. `graphify hook-guard` for Claude, `graphify hook-check` for Codex). — [graphify/install.py#L120-260](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/install.py#L120-L260) [graphify/__main__.py#L29-119](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/__main__.py#L29-L119) [graphify/__main__.py#L396-414](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/__main__.py#L396-L414)
- Extract trace (headless): cli 'extract' parses flags -> persists corpus-shaping options (_write_build_cfg for excludes/gitignore) -> detect or detect_incremental -> collect_files -> extract() [cache check -> _extract_parallel/_extract_sequential -> run_language_resolvers] -> llm.extract_corpus_parallel for doc/image/paper chunks (unless --code-only) -> merge -> deduplicate_entities -> build_from_json -> cluster + score_all -> to_json (shrink-guarded) -> report.generate -> save_manifest. — [graphify/cli.py#L3218-3467](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/cli.py#L3218-L3467) [graphify/extract.py#L6787-6918](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/extract.py#L6787-L6918) [graphify/resolver_registry.py#L59-85](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/resolver_registry.py#L59-L85)
- Skill-driven trace (`/graphify .`): skill.md instructs the host agent to resolve a Python interpreter into graphify-out/.graphify_python, run detect() writing .graphify_detect.json, run AST extraction to .graphify_ast.json, dispatch parallel general-purpose subagents writing .graphify_chunk_NN.json files (or call llm.extract_corpus_parallel when GEMINI_API_KEY/GOOGLE_API_KEY is set), merge to .graphify_semantic.json then .graphify_extract.json, then build_from_json -> cluster -> to_json -> GRAPH_REPORT.md, label communities, export html, save manifest, and clean temp files. — [graphify/skill.md#L65-194](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/skill.md#L65-L194) [graphify/skill.md#L196-356](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/skill.md#L196-L356) [graphify/skill.md#L393-449](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/skill.md#L393-L449) [graphify/skill.md#L482-624](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/skill.md#L482-L624)
- Query trace: cli 'query' resolves --graph (default graphify-out/graph.json), enforces .json suffix + size cap, json.loads -> node_link_graph (undirected with per-edge _src/_tgt direction preserved) -> serve._query_graph_text(question, mode=bfs|dfs, depth, token_budget, context_filters) -> querylog.log_query -> _touch_query_stamp -> print. — [graphify/cli.py#L1202-1322](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/cli.py#L1202-L1322) [graphify/serve.py#L1349-1400](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/serve.py#L1349-L1400)
- Update trace: cli 'update' reads GRAPHIFY_FORCE/--force, recovers the scan root from graphify-out/.graphify_root (utf-8-sig read), then watch._rebuild_code(watch_path, block_on_lock=True) -> _rebuild_lock flock -> detect() with persisted excludes -> re-extract changed code files only -> build_merge into existing graph -> to_json -> manifest update; doc/image changes still require the skill's --update path with an LLM. — [graphify/cli.py#L2403-2461](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/cli.py#L2403-L2461) [graphify/watch.py#L1355-1474](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/watch.py#L1355-L1474)
- Post-commit hook trace: git hook -> _WORKTREE_GUARD skips linked worktrees -> _PYTHON_DETECT resolves interpreter (pinned exe -> .graphify_python -> launcher shebang -> uv tool dirs -> python3) -> _detached_launch spawns detached python -c rebuild -> _REBUILD_BODY_COMMIT sets SIGALRM/threading timeout (GRAPHIFY_REBUILD_TIMEOUT, kills multiprocessing children), validates .graphify_root stays in-repo, calls _rebuild_code(changed_paths), then best-effort reflect() to refresh LESSONS.md. — [graphify/hooks.py#L19-138](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/hooks.py#L19-L138) [graphify/hooks.py#L144-223](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/hooks.py#L144-L223) [graphify/hooks.py#L318-371](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/hooks.py#L318-L371)
- MCP request trace: serve()/serve_http -> _build_server(graph_path) -> _GraphContextCache (pinned default + LRU of project graphs, GRAPHIFY_MAX_CONTEXTS default 8) -> tool call _select_graph(project_path) -> _load_ctx -> _load_graph (size cap + node_link_graph) -> handler (e.g. _tool_query_graph -> _query_graph_text) with ToolError mapped to isError results. — [graphify/serve.py#L121-180](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/serve.py#L121-L180) [graphify/serve.py#L1795-1860](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/serve.py#L1795-L1860) [graphify/serve.py#L2021-2049](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/serve.py#L2021-L2049)
- Skill version-check trace: on any non-silent invocation, _run_cli iterates {_platform_skill_destination(name) for name in _PLATFORM_CONFIG} and calls _check_skill_version, which warns when the on-disk skill is older (suggests targeted `graphify install --platform X`) or NEWER than the package (warns that install would downgrade). — [graphify/__main__.py#L163-233](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/__main__.py#L163-L233) [graphify/__main__.py#L516-527](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/__main__.py#L516-L527)

## Testing signals

Testing is pytest-based with ~400 test files under tests/, one file per module plus many regression tests named after GitHub issues (test_issue_3405_*, etc.). Fixtures cover cross-language corpora (crate_a/crate_b Cargo workspaces, cpp/objc dirs, SQL samples). conftest.py sandboxes HOME and strips backend env vars so installers and detect_backend never touch the developer's real config. CI runs the suite on Python 3.10-3.14 plus a skillgen drift-guard job and non-blocking bandit/pip-audit scans.
- pytest is the test framework: pyproject sets testpaths=['tests'], norecursedirs excludes worked/ and corpus dirs, and the dev dependency group adds hypothesis, pytest-cov, ruff, pyright, bandit, pip-audit. — [pyproject.toml#L114-133](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/pyproject.toml#L114-L133) [pyproject.toml#L152-158](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/pyproject.toml#L152-L158)
- conftest.py autouse fixtures give every test a throwaway HOME (protecting ~/.claude, ~/.agents, etc. from installer tests) and delete every env var detect_backend() consults so backend-detection tests are hermetic. — [tests/conftest.py#L50-77](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/tests/conftest.py#L50-L77)
- ARCHITECTURE.md's module table is load-bearing documentation: tests/test_architecture_doc.py imports every symbol the doc names, so the architecture doc cannot drift from the code without a red test. — [ARCHITECTURE.md#L15-34](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/ARCHITECTURE.md#L15-L34) [tests/test_architecture_doc.py](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/tests/test_architecture_doc.py)
- The extractor migration is guarded by tests/test_extractors_registry.py — each ported language gets a module-import + facade-identity + registry-identity test before the move lands. — [tests/test_extractors_registry.py](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/tests/test_extractors_registry.py) [graphify/extractors/MIGRATION.md#L81-96](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/extractors/MIGRATION.md#L81-L96)
- tests/fixtures/ contains real mini-corpora: crate_a/crate_b Cargo workspaces for cross-crate call tests, cpp_paired/cpp_samedir dirs, objc_mixed, pascal_cross_file, robot_keywords.resource, sample_*.sql, and a committed extraction.json. — [tests/fixtures/crate_a/Cargo.toml](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/tests/fixtures/crate_a/Cargo.toml) [tests/fixtures/crate_b/Cargo.toml](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/tests/fixtures/crate_b/Cargo.toml)
- CI test job runs `uv sync --all-extras --frozen` then `pytest tests/ -q --tb=short` on Python 3.10, 3.12, 3.13, 3.14, plus an install smoke test (`graphify --help`, `graphify install`). — [.github/workflows/ci.yml#L49-80](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/.github/workflows/ci.yml#L49-L80)
- A separate skillgen-check CI job enforces that generated skill artifacts match their fragments: --check byte-diff, --audit-coverage per host, --schema-singleton file_type enum, --monolith-roundtrip and --always-on-roundtrip against the pinned v8 baseline commit. — [.github/workflows/ci.yml#L11-47](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/.github/workflows/ci.yml#L11-L47) [tools/skillgen/gen.py#L37-56](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/tools/skillgen/gen.py#L37-L56)
- Security scanning exists but is advisory: bandit -r graphify -ll and pip-audit --strict both run with continue-on-error: true, so a new HIGH finding does not fail the build. — [.github/workflows/ci.yml#L81-106](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/.github/workflows/ci.yml#L81-L106)
- Coverage breadth: dedicated test files exist for the CLI surface (test_cli_help, test_explain_cli, test_query_cli, test_path_cli, test_extract_cli), security (test_security, test_injection_sentinel_coverage), caching/incremental (test_cache, test_incremental, test_partial_cache), serve/MCP (test_serve, test_serve_http, test_query_mcp_direction), and ~40 per-language extraction tests (test_csharp_*, test_js_*, test_pascal_*, test_terraform, ...). — [tests/test_cli_help.py](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/tests/test_cli_help.py) [tests/test_serve_http.py](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/tests/test_serve_http.py) [tests/test_security.py](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/tests/test_security.py) [tests/test_incremental.py](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/tests/test_incremental.py)
- Performance benchmarks live in-repo as tests/bench_extract.py and tests/bench_query_scoring.py, separate from the unit suite. — [tests/bench_extract.py](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/tests/bench_extract.py) [tests/bench_query_scoring.py](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/tests/bench_query_scoring.py)
- Gap: the skill.md orchestration path (host-agent subagents writing chunk JSONs) is validated only indirectly via install-string and skillgen tests; no test exercises a real end-to-end /graphify run against a live assistant. The LLM client code is tested at the parsing/retry layer (test_llm_parser, test_llm_backends) but no live-API tests exist — consistent with the 'pure unit tests, no network calls' claim in ARCHITECTURE.md. (inferred) — [ARCHITECTURE.md#L94-102](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/ARCHITECTURE.md#L94-L102) [tests/test_llm_parser.py](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/tests/test_llm_parser.py) [tests/test_llm_backends.py](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/tests/test_llm_backends.py)
- Regression-heavy style: dozens of tests are named for fixed issues (test_issue_3405_python_resolution, test_issue_3472_source_file_collision) and source comments cite issue numbers (#1392, #1666, #1941...) pervasively, meaning the test suite is the primary record of past failure modes. — [tests/test_issue_3405_python_resolution.py](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/tests/test_issue_3405_python_resolution.py) [graphify/extract.py#L6595-6602](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/extract.py#L6595-L6602)

## Risk map

The main risks are (a) SSRF/SSRF-adjacent fetching — mitigated by a purpose-built guarded-connection stack; (b) prompt injection from attacker-controlled corpus files into the semantic LLM pass — mitigated but explicitly 'table stakes'; (c) the 8,300-line extract.py monolith mid-migration where the shared _extract_generic core and cross-file resolvers concentrate most defect density; (d) shell-out paths (git hooks, detached rebuilds, gh/git subprocesses, claude CLI) with heavy escaping/Windows edge-case handling; and (e) graph.json integrity guards (size cap, shrink guard, path validation) that protect but can also mask silent data loss if misread.
- All outbound fetching goes through security.py: validate_url allows only http/https, blocks private/reserved/loopback/link-local/CGN ranges and cloud metadata hosts, and _SSRFGuarded*Connection subclasses resolve+validate DNS once then connect to that exact IP to defeat DNS-rebind TOCTOU; redirects are re-validated by _NoFileRedirectHandler. — [graphify/security.py#L83-251](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/security.py#L83-L251)
- Fetch size caps: safe_fetch aborts streaming reads at 50 MB (safe_fetch_text at 10 MB) and raises HTTPError on non-2xx; graph-file loads are capped at 512 MiB (GRAPHIFY_MAX_GRAPH_BYTES) to prevent memory-bomb json.loads. — [graphify/security.py#L20-66](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/security.py#L20-L66) [graphify/security.py#L258-308](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/security.py#L258-L308)
- Prompt-injection surface: corpus files are wrapped in <untrusted_source sha256=...> blocks, _neutralise_injection_sentinels defangs chat-template tokens ([INST], <<SYS>>, forged closing tags), and sanitize_label/sanitize_metadata strip control chars + HTML-escape before export/MCP output — but SECURITY.md itself calls this 'table-stakes' and admits evasion is possible. — [graphify/llm.py#L572-598](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/llm.py#L572-L598) [graphify/security.py#L394-460](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/security.py#L394-L460) [SECURITY.md](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/SECURITY.md)
- detect.py silently skips credential-shaped files (.env, .pem/.key/.p12, id_rsa/id_ed25519, .npmrc/.pypirc/.git-credentials/.boto, secring) and refuses to descend into .ssh/.gnupg/.aws/.gcloud dirs — an attacker naming real source `id_rsa.py`-style or a user pointing graphify at a secrets dir still loses coverage silently. — [graphify/detect.py#L110-147](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/detect.py#L110-L147)
- The HTTP MCP transport is opt-in and unauthenticated by default: serve_http warns loudly when binding 0.0.0.0 without --api-key/GRAPHIFY_API_KEY, and _ApiKeyMiddleware does a constant-time compare of X-API-Key/Bearer tokens — but a user can still expose the graph read-only to the network. — [graphify/serve.py#L2447-2470](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/serve.py#L2447-L2470) [graphify/serve.py#L2561-2615](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/serve.py#L2561-L2615) [Dockerfile#L1-9](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/Dockerfile#L1-L9)
- extract.py is ~8,300 lines holding the shared _extract_generic core plus ~20 in-place cross-file resolution passes (member calls for swift/python/typescript/cpp/csharp/java/objc/kotlin/rust, qualified calls, interface dispatch); extractors/MIGRATION.md documents the ongoing one-language-per-PR extraction — this is the blast-radius hotspot where most issue-numbered regressions live. — [graphify/extract.py#L3003-5256](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/extract.py#L3003-L5256) [graphify/extractors/MIGRATION.md#L1-34](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/extractors/MIGRATION.md#L1-L34)
- hooks.py embeds multi-stage shell+Python payloads in Python string constants with strict character constraints (no `"`, `$`, backtick, backslash inside the -c payload); a careless edit to _REBUILD_BODY_* or _LAUNCHER_TEMPLATE can silently break every installed hook — and the failure mode is a stale graph, not an error. — [graphify/hooks.py#L140-352](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/hooks.py#L140-L352)
- Shell-outs: prs.py shells to `gh`/`git` (subprocess.run, no shell=True), cli._clone_repo runs git clone, llm._call_claude_cli shells to the claude binary, transcribe.py invokes yt-dlp — each is a supply-chain/PATH dependency that fails closed only if the binary is absent. — [graphify/prs.py#L140-215](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/prs.py#L140-L215) [graphify/cli.py#L1000-1058](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/cli.py#L1000-L1058) [graphify/llm.py#L1675-1730](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/llm.py#L1675-L1730)
- install.py writes into user config dirs (~/.claude, ~/.codex, ~/.config/..., ~/.agents) and mutates assistant config files (settings.json hooks, AGENTS.md/CLAUDE.md sections) — a mis-install or stale uninstaller can corrupt the user's assistant setup; the conftest HOME sandbox exists precisely because this is dangerous in tests. — [graphify/install.py#L1-30](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/install.py#L1-L30) [tests/conftest.py#L50-65](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/tests/conftest.py#L50-L65)
- scip_ingest.py is a self-declared skeleton ('NOT a full SCIP protobuf implementation ... Not wired to the CLI in this phase') — dead/legacy code carrying an ingestion surface with no caller or test coverage beyond import checks. — [graphify/scip_ingest.py#L1-20](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/scip_ingest.py#L1-L20)
- Graph-integrity guards can mask failure modes: the to_json shrink guard (#479) refuses to overwrite a larger graph (good) but requires users to discover --force/GRAPHIFY_FORCE; the semantic 'unverified shrink' guard similarly stalls writes until --allow-partial; corrupt graph.json exits the process on CLI paths while MCP converts it to tool errors. — [graphify/export.py#L229-271](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/export.py#L229-L271) [graphify/cli.py#L171-218](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/cli.py#L171-L218) [graphify/serve.py#L86-91](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/serve.py#L86-L91)
- Incremental correctness depends on manifest stamping discipline — a long trail of fixed bugs (#933, #1666, #1897, #1909, #1941, #2015, #2543) shows files could be silently marked done with no extraction output; _stamped_manifest_files is the single chokepoint an agent must not weaken. — [graphify/cli.py#L88-168](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/cli.py#L88-L168) [graphify/cli.py#L221-250](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/cli.py#L221-L250)
- Windows portability is a recurring defect class addressed throughout (os_replace_with_fallback for WinError 5/32/17, MAX_PATH filename budgets, 61-worker process cap, PowerShell-safe hook payloads) — code touching filesystem identity or subprocess spawning must handle both platforms explicitly. — [graphify/paths.py#L29-99](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/paths.py#L29-L99) [graphify/paths.py#L378-447](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/paths.py#L378-L447) [graphify/extract.py#L6640-6655](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/extract.py#L6640-L6655)

## Extension points

The codebase has clear, documented seams for extension: a suffix/filename/shebang dispatch table for new language extractors, a LanguageResolver registry for cross-file resolution passes, a BACKENDS dict + custom-provider JSON for LLM backends, a _PLATFORM_CONFIG table + skillgen fragments for new assistant platforms, and one-function-per-format exporters. ARCHITECTURE.md carries an explicit 5-step checklist for adding a language.
- New languages plug into extract.py's _DISPATCH suffix table (plus _SHEBANG_DISPATCH for extensionless, filename routers for MCP configs/package manifests, and content sniffs for ambiguous .h/.m); ARCHITECTURE.md lists the full checklist: extractor fn, _DISPATCH + collect_files registration, CODE_EXTENSIONS + watch _WATCHED_EXTENSIONS, pyproject grammar dep, fixture + tests. — [graphify/extract.py#L6270-6373](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/extract.py#L6270-L6373) [graphify/extract.py#L6411-6545](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/extract.py#L6411-L6545) [ARCHITECTURE.md#L75-81](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/ARCHITECTURE.md#L75-L81)
- Cross-file/language-specific resolution passes register via resolver_registry.register(LanguageResolver(name, suffixes, resolve)) — e.g. terraform_modules, swift/python/ruby/typescript/csharp/java/rust member calls, kotlin import targets, csharp interface dispatch — and run_language_resolvers activates only resolvers whose suffixes appear in the corpus, logging-and-continuing on failure. — [graphify/resolver_registry.py#L28-85](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/resolver_registry.py#L28-L85) [graphify/extract.py#L5143-5256](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/extract.py#L5143-L5256)
- extractors/ is the destination for new/migrated language extractors: MIGRATION.md prescribes verbatim moves, a mandatory facade re-export in extract.py, registry entry in extractors/__init__.py (LANGUAGE_EXTRACTORS seed), and a registry test — with strict 'never import graphify.extract from this package' directionality. — [graphify/extractors/MIGRATION.md#L36-107](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/extractors/MIGRATION.md#L36-L107) [graphify/extractors/__init__.py#L1-30](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/extractors/__init__.py#L1-L30)
- LLM backends extend via the BACKENDS dict (base_url, default_model, env_key, pricing, temperature, vision) plus a call path (_call_openai_compat covers most); custom providers are added at runtime with `graphify provider add --base-url --default-model --env-key` persisted to a user-global JSON via _custom_providers_path. — [graphify/llm.py#L101-200](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/llm.py#L101-L200) [graphify/llm.py#L225-308](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/llm.py#L225-L308) [graphify/cli.py#L1101-1158](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/cli.py#L1101-L1158)
- New assistant platforms extend _PLATFORM_CONFIG (skill_file, skill_dst, claude_md, skill_refs) and get generated artifacts by adding fragments under tools/skillgen/fragments/ + an entry in platforms.toml; install destinations and always-on blocks are resolved from packaged assets under graphify/skills/ and graphify/always_on/. — [graphify/install.py#L120-260](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/install.py#L120-L260) [tools/skillgen/gen.py#L1-56](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/tools/skillgen/gen.py#L1-L56) [pyproject.toml#L144-150](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/pyproject.toml#L144-L150)
- Exporters are one-function-per-format in export.py (to_json, to_obsidian, to_canvas, to_graphml, to_svg, to_cypher) with html/callflow-html and neo4j/falkordb push in the exporters/ subpackage; a new format adds a function plus a `graphify export` subcommand arm. — [graphify/export.py#L271-535](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/export.py#L271-L535) [graphify/exporters/graphdb.py#L9-40](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/exporters/graphdb.py#L9-L40) [graphify/cli.py#L2782-2790](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/cli.py#L2782-L2790)
- Optional-dependency gating is itself an extension seam: _EXTRA_FOR_EXTENSION maps suffixes to extras (.sql->sql, .tf->terraform, .ml->ocaml, .robot->robot...) so a grammar-backed language can ship as an extra without breaking the base install, and the #1745 warning names the missing extra to the user. — [graphify/extract.py#L6376-6399](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/extract.py#L6376-L6399) [pyproject.toml#L52-108](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/pyproject.toml#L52-L108)
- The MCP tool list is extended by appending a types.Tool to the _tools list in _build_server and a handler in the dispatch dict — project_path is injected into every schema automatically, so new tools inherit multi-project support for free. — [graphify/serve.py#L1866-2019](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/serve.py#L1866-L2019) [graphify/serve.py#L2250](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/serve.py#L2250)
- New edge relations/relations-of-interest surface in affected traversal via DEFAULT_AFFECTED_RELATIONS and in build-time stub minting via _EXTERNAL_STUB_RELATIONS — both are small frozensets designed to be extended. — [graphify/affected.py#L11-29](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/affected.py#L11-L29) [graphify/build.py#L64-77](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/build.py#L64-L77)
- Caching tiers are separately pluggable: the AST cache is versioned per package release + _AST_CACHE_SCHEMA bump, while the semantic cache is keyed by a fingerprint of the extraction prompt file — so changing the extraction-spec.md prompt invalidates semantic entries without touching AST entries. — [graphify/cache.py#L22-98](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/cache.py#L22-L98)

## Contributor handoff

Read ARCHITECTURE.md first — its module table is enforced by a test. Then skim __main__.py (dispatch + re-exports), cli.py::dispatch_command (command table), extract.py (_get_extractor/_DISPATCH + the resolver tail), and paths.py (output-dir constants, atomic writes, cross-platform path helpers). Run `uv sync --all-extras --frozen` then `pytest tests/ -q`. Conventions: issue-numbered comments, atomic writes via paths.write_*_atomic, never cache zero-node extractions, keep the __main__ re-export surface stable, and never edit generated skill*.md files — edit tools/skillgen/fragments and regen.
- First reads: ARCHITECTURE.md (pinned by tests/test_architecture_doc.py), graphify/__main__.py, graphify/cli.py dispatch_command, graphify/extract.py's _get_extractor/_DISPATCH and LanguageResolver registration block, graphify/paths.py, and graphify/security.py. — [ARCHITECTURE.md](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/ARCHITECTURE.md) [graphify/cli.py#L1064](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/cli.py#L1064) [graphify/extract.py#L6502-6545](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/extract.py#L6502-L6545) [graphify/paths.py](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/paths.py) [graphify/security.py](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/security.py)
- Build/test: `uv sync --all-extras --frozen` (lock is committed; CI forbids re-resolving), `uv run --frozen pytest tests/ -q`; lint is a minimal ruff ruleset (E9,F63,F7,F82), pyright basic mode on graphify+tests; skill artifact drift check is `python -m tools.skillgen --check`. — [.github/workflows/ci.yml#L33-74](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/.github/workflows/ci.yml#L33-L74) [pyproject.toml#L160-174](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/pyproject.toml#L160-L174)
- graphify/skill*.md, graphify/skills/*/references/*.md, and graphify/always_on/*.md are GENERATED — never hand-edit; change tools/skillgen/fragments/, run `python -m tools.skillgen`, and let --check/--audit-coverage/--monolith-roundtrip verify. — [tools/skillgen/gen.py#L1-25](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/tools/skillgen/gen.py#L1-L25) [pyproject.toml#L144-150](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/pyproject.toml#L144-L150)
- Import direction rule: graphify/extract.py imports from graphify/extractors/, never the reverse; extractor ports must be verbatim byte-identical moves, one language per PR, with a facade re-export kept in extract.py. — [graphify/extractors/MIGRATION.md#L36-61](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/extractors/MIGRATION.md#L36-L61)
- All file writes to graphify-out/ artifacts go through paths.write_text_atomic/write_json_atomic (temp file + os.replace + os_replace_with_fallback for Windows); never Path.write_text for graph.json/manifest/labels. — [graphify/paths.py#L101-165](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/paths.py#L101-L165)
- Never cache a zero-node extraction result — extract.py deliberately skips save_cached when an extractable file returns no nodes so reruns self-heal; mirroring that, _stamped_manifest_files must stay the chokepoint deciding which files count as 'done'. — [graphify/extract.py#L6595-6602](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/extract.py#L6595-L6602) [graphify/cli.py#L88-168](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/cli.py#L88-L168)
- extract(paths, root=...) : always pass root explicitly — without it node ids/source_file anchor to the common parent of the passed list, baking machine-specific path segments into portable graph.json artifacts. — [ARCHITECTURE.md#L36-48](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/ARCHITECTURE.md#L36-L48) [graphify/extract.py#L6787-6893](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/extract.py#L6787-L6893)
- __main__.py's re-export list is a public API: install and cli symbols are re-imported there so `from graphify.__main__ import X` keeps working — do not remove names or move dispatch without preserving the re-exports. — [graphify/__main__.py#L27-131](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/__main__.py#L27-L131)
- Stored/portable paths must be classified with paths.is_absolute_any_platform (POSIX+Windows), not Path.is_absolute — the corpus travels between OSes; but live-filesystem resolution keeps using Path.is_absolute. — [graphify/paths.py#L378-410](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/paths.py#L378-L410)
- Edge/node conventions to preserve: confidence is exactly EXTRACTED|INFERRED|AMBIGUOUS; generic relations (references/uses/mentions) must lose edge-collapse to specific ones; import-family edges to undeclared targets mint `external` stub nodes; source_file is stored relative to the SCAN root. — [ARCHITECTURE.md#L50-73](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/ARCHITECTURE.md#L50-L73) [graphify/build.py#L56-137](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/build.py#L56-L137)
- Trap: `graphify query` keeps the loaded graph undirected (BFS needs callers+callees) while path/explain force directed; edge direction lives in _src/_tgt per-link markers — do not 'simplify' either side without reading the #2309 comments. — [graphify/cli.py#L1259-1284](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/cli.py#L1259-L1284) [graphify/serve.py#L56-76](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/serve.py#L56-L76)
- Hook/hook-guard payloads must stay shell-agnostic (pure `graphify` subcommand calls, no bash syntax) and the embedded python -c rebuild bodies must not contain `"`, `$`, backtick or backslash — constraints are documented inline and enforced by tests like test_hook_guard/test_hooks. — [graphify/hooks.py#L140-145](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/hooks.py#L140-L145) [graphify/__main__.py#L273-282](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/__main__.py#L273-L282) [tests/test_hooks.py](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/tests/test_hooks.py)

Source repository: https://github.com/Graphify-Labs/graphify
Full evidence corpus: https://github.com/Codevetter/codevetter/tree/main/benchmarks/repo-unpacks/graphify.json

## Public product links

- [CodeVetter](https://codevetter.com/)
- [Download](https://codevetter.com/download)
- [Documentation](https://codevetter.com/docs/)
- [Source](https://github.com/Codevetter/codevetter)
