CodeVetter · Repo Unpack · Contributor handoff
Graphify-Labs/graphify
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.
- 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`.
- 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.
- 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.
- 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.
- 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'.
- 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.
- __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.
- 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.
- 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.
- 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.
- 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.