Skip to main content

Whyfile

PyPI version Python versions License: MIT CI Ruff

Your codebase remembers what it does. It forgot why.

whyfile reconstructs that why, keeps it honest — every unit tagged authored or reconstructed, so a model's guess never poses as a decision you made — and puts it on a gate that fails the PR quietly breaking it. Every repo has a Makefile; this is the missing Whyfile. (Formerly graphify-intent; see ADR-0024.)

Why "Whyfile"?

Every repo has a Makefile that says how to build, a Dockerfile that says how to run, and a lockfile that says exactly what ships. None of them say why. Whyfile is the missing file: the decisions, constraints, and trade-offs behind your code, extracted into a queryable graph, anchored to the code they govern, and kept honest by provenance tiers that separate what a human recorded from what a model reconstructed.

The name is also the roadmap. Intent-driven development has one invariant: no unexplained change. Every change either conforms to recorded intent, supersedes it explicitly, or records a new decision. The conformance gate (whyfile intent-diff --gate) enforces it: a pull request that silently violates a recorded constraint does not merge. The why goes on file, and the file bears load.

What it does

whyfile reconstructs the why your code lost: the decisions, the dead ends, and the constraints the awkward bits are quietly paying for — the reasoning that never became a comment because nobody writes # WHY: above the thing they decided not to do. Surfacing rationale that's already written down is table stakes; two things make reconstructed intent trustworthy instead of plausible-sounding fiction:

  • It's kept honest by provenance. Every unit is tagged authored (a human recorded it) or reconstructed (a model inferred it), grounded to the exact source span it came from, so a model's guess never outranks the record or poses as a decision you made.
  • It's load-bearing, not decorative. whyfile intent-diff --gate fails a pull request that changes constraint-governed code without conforming to the constraint or explicitly superseding the decision behind it. The why doesn't just sit in a graph you query — it can block your merge.

Under the hood it reads a graphify knowledge graph alongside your prose docs, runs a short pipeline of LLM passes (extract → anchor → cross-doc relate, plus an opt-in concept-resolution pass), and writes a sidecar JSON, an enriched graph.json, and a report. Three LLM-free query commands read it back. Think of it as the month of code archaeology every new hire does on their way in — done once, checked in, and enforced.

flowchart LR
    D["docs/*.md"] --> A
    G["graph.json"] --> B
    A["Pass A<br/>extract intent"] --> B["Pass B<br/>anchor to concepts"]
    B --> C["Pass C<br/>cross-doc intent"]
    B --> R["Pass D<br/>concept resolution<br/>(opt-in)"]
    A --> OUT
    C --> OUT
    R --> OUT
    OUT["outputs:<br/>.whyfile.json<br/>graph.enriched.json<br/>enrichment_report.md"]

Quick start

# Prefers an API key (ANTHROPIC_API_KEY) — exact prompt tokens, visible cost.
# Falls back to your Claude Pro/Max subscription (needs the `claude` CLI).
whyfile \
  --graph graphify-out/graph.json \
  --docs docs/ \
  --passes A,B,C

See it on this repo

We point the tool at itself, because it's the most honest demo we can give you. A full run over this project's own docs/adr/ trail grows 172 code concepts into 234 nodes and pulls 62 intent units (18 decisions, 18 mechanisms, 16 constraints, 10 trade-offs) out of the prose, each grounded to the exact span it came from (100% grounding), 53 anchored back to the code they explain.

Then the why bears load. Open a PR that edits cache.py — code governed by four constraints recorded in ADR-0015 — without accounting for them, and the gate stops the merge:

$ whyfile intent-diff --files src/whyfile/cache.py --gate
Intent diff: introduces 0, supersedes 0, governed 1, conformance-review 1
  ! src/whyfile/cache.py governed by constraint: Restores extraction, not re-sync, …
Conformance gate: action required — conform to each constraint, or supersede its decision record:
  ✗ src/whyfile/cache.py — Restores extraction, not re-sync (ADR-0015) → conform, or supersede ADR-0015
  ✗ src/whyfile/cache.py — Consistent --docs spelling required (ADR-0015) → conform, or supersede ADR-0015
  ✗ src/whyfile/cache.py — End-of-run sidecar write loses work (ADR-0015) → conform, or supersede ADR-0015
  ✗ src/whyfile/cache.py — Corrupt entry treated as miss (ADR-0015) → conform, or supersede ADR-0015
$ echo $?
5

That exit 5 is the product. The change conforms to each recorded constraint, or the same PR supersedes ADR-0015 out loud — silently breaking a decision the code is built on is not a path the merge leaves open.

And it stays honest about what it knows. Ask why the code looks the way it does, and every answer wears its provenance — reconstructed (a model inferred it) never dressed up as authored (a human recorded it):

$ whyfile explain cache.py --format text
cache.py (code)
  • Orphan cache entries accumulate [reconstructed tradeoff] — Orphan cache entries accumulate
    over a corpus's lifetime with no automatic pruning.
    why: Deferring cleanup accepts unbounded cache growth to avoid deleting live entries, because
    auto-prune cannot safely distinguish orphaned entries from ones excluded by the current run.
    alternatives: Auto-prune or an immediate --prune-cache flag, deferred because --docs subsetting
    makes automatic pruning unsafe.

That rationale isn't a comment in cache.py — it lives in ADR-0015, which the tool read, distilled, tagged reconstructed, and wired back to the file the decision governs. Surfacing that round trip is table stakes. Gating on it — and never letting a model's guess pose as ground truth — is the product. (The query commands are covered in Querying the intent layer.)

Team collaboration

whyfile turns the tribal "why is the code like this?" into a layer that is queryable, PR-reviewable, and survives turnover. Three jobs a team gets for free (all LLM-free, $0 at query time):

  • Onboard. A new hire runs whyfile explain <file> instead of interrupting a senior.
  • Review. whyfile changed --base main surfaces the intent a PR touches, so a reviewer sees the constraints a change is bound by; whyfile review-context --base main turns that into a brief an external reviewer like Codex can act on directly.
  • Retain. whyfile coverage shows which code has recorded intent and which is dark, and whyfile digest reports what was decided since a checkpoint.

The PR check

git diff --name-only origin/main...HEAD | whyfile changed --format markdown

Maps each changed file to the intent that governs it (constraints and trade-offs first). It is informational by default (exit 0). Add --fail-on-constraint to turn it into a soft gate that exits 5 when a PR touches a file bound by a constraint, so the constraint gets a reviewer's acknowledgement.

Development

git clone <repo>
cd whyfile
pip install -e ".[dev]"
python -m pytest tests/ -v

Tests cover every module: IDs, section splitting with spans, relation/confidence validation, all four passes (including Pass D's candidate resolution and embedding fallback), merge/enriched-graph assembly, the report, backend resolution, and an end-to-end smoke test. The LLM boundary and the graphify-runtime probe are injected/mocked, so the suite makes no network calls and needs no API key or live backend.

Building the plane while we fly it

Structure tools map imports and call graphs: the what and the how-it-connects. What they can't hand you is the reasoning: which decision a file is the consequence of, what got tried and thrown away, why the ugly workaround earns its keep. whyfile is a thin reasoning layer over the structural graph graphify already builds: a decision/strategy view, not another entity extractor, and deliberately not a vector DB or a RAG stack (that would be a much heavier machine than the job needs).

This whole docs/adr/ trail is fair game — decisions, rationale, and the alternatives considered — written by hand as we built it, so the reasoning actually moves:

  • ADR-0002 bets the whole tool on intent, not generic concept extraction.
  • ADR-0004 ships v1 embedding-free and defers cross-document resolution, which ADR-0009 later delivers.
  • ADR-0010 openly supersedes an earlier README claim after a clean install exposed a silently broken package.
  • ADR-0011 catches extraction quietly running on Opus and puts it on a budget.

That recurring beat — a decision that doesn't survive contact with the code — is the whole reason this tool exists.

Explore the docs

License and attribution

whyfile is licensed under the MIT License, Copyright (c) 2026 Will Neill.

This project is an independent post-processor built to interoperate with graphify by Safi Shamsi. It reuses graphify's graph schema and relation vocabulary and calls graphify as a separately-installed runtime dependency; no graphify source code is bundled with or distributed as part of this project. graphify is licensed under the MIT License (Copyright (c) 2026 Safi Shamsi); see the ACKNOWLEDGEMENT AND ATTRIBUTION section of this repository's LICENSE file and the upstream license for the full text. With thanks to the graphify project.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

whyfile-0.6.0.tar.gz (360.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

whyfile-0.6.0-py3-none-any.whl (129.3 kB view details)

Uploaded Python 3

File details

Details for the file whyfile-0.6.0.tar.gz.

File metadata

  • Download URL: whyfile-0.6.0.tar.gz
  • Upload date:
  • Size: 360.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for whyfile-0.6.0.tar.gz
Algorithm Hash digest
SHA256 f10861b01205539728e8240227f2025b5325670a9ccf628f41c18ffc47e0435a
MD5 4c6adf5168f81afe0fec4bb46545a9f3
BLAKE2b-256 7ba846420e2524b98613ff849815dd98a1fe294ff1368d990e3ee71201ccfef6

See more details on using hashes here.

Provenance

The following attestation bundles were made for whyfile-0.6.0.tar.gz:

Publisher: publish.yml on whyfile/whyfile

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file whyfile-0.6.0-py3-none-any.whl.

File metadata

  • Download URL: whyfile-0.6.0-py3-none-any.whl
  • Upload date:
  • Size: 129.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for whyfile-0.6.0-py3-none-any.whl
Algorithm Hash digest
SHA256 4763f20e8685be95925af7fa6654abb32603d38376d255505a84162201d74f36
MD5 69124b64b5263ee65962c064ef22843d
BLAKE2b-256 be36f45802534df67b238f382a61c024a105c956f75b6b352139c15997924670

See more details on using hashes here.

Provenance

The following attestation bundles were made for whyfile-0.6.0-py3-none-any.whl:

Publisher: publish.yml on whyfile/whyfile

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.8.3

2 files

0.8.2

2 files

0.8.1

2 files

0.8.0

2 files

0.7.0

2 files

This release

0.6.0 This release

2 files

0.5.0

2 files

0.4.0

2 files

0.3.1

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page