Codebase Awareness
Haft doesn't just track decisions — it understands your codebase. It knows which modules exist, how they depend on each other, which parts of your architecture are governed by decisions, and which invariants apply to every file you touch.
Module detection
When you run /h-status or haft_query(action="coverage"), haft scans
your project and detects modules using language-specific conventions:
| Language | What counts as a module | How it's detected |
|---|---|---|
| Go | Every directory with .go files | Uses Go's stdlib go/parser |
| JS/TS | package.json boundaries + directories with index.ts | Monorepo workspaces + barrel file detection |
| Python | Directories with __init__.py | Package marker detection |
| Rust | Cargo.toml crates + mod.rs modules | Crate boundary + module tree detection |
| C/C++ | Directories with .c, .cpp, .h, .hpp files | compile_commands.json (CMake, Bazel, Meson) with directory-based fallback |
Module scanning respects .gitignore (local and global) and an optional
.haftignore file for project-specific exclusions.
Decision coverage
Coverage answers: "which parts of my architecture have engineering decisions, and which are blind spots?"
## Module Coverage (9 modules, 77% governed)
✓ src/internal/artifact — 3 decisions
✓ src/internal/codebase — 2 decisions
✗ src/assurance — no decisions (blind)
✗ src/cmd/indexer — no decisions (blind) Coverage uses three states:
- Covered — at least one active decision with R_eff >= 0.5 covers files in this module
- Partial — has decisions, but all evidence is degraded (R_eff < 0.5)
- Blind — no decisions reference any file in this module
Only DecisionRecord artifacts count as governance. Notes are observations, not
architectural contracts — they don't inflate coverage.
Dependency graph
Haft parses import statements to build a module dependency graph. For Go, it uses
the stdlib go/parser. JS/TS, Python, Rust, and C/C++ use regex-based import detection.
C/C++ resolves #include "..." paths using -I flags from
compile_commands.json when available.
The dependency graph powers impact propagation: when drift is detected in module A, haft checks which modules depend on A and flags their decisions too.
Drift detected in auth/:
auth/middleware.go — MODIFIED (+8 -2)
Impact propagation:
→ api/ depends on auth/ — governed by dec-003 "API rate limiting"
→ payments/ depends on auth/ — governed by dec-007 "Payment auth flow"
⚠ billing/ depends on auth/ — no decisions (blind, potential unmonitored impact) Drift detection
After a decision is implemented, the agent snapshots the SHA-256 hashes of affected files
(the baseline). On every /h-verify scan, haft recomputes hashes and detects:
- MODIFIED — file changed since baseline
- FILE MISSING — file deleted or moved
- No drift — file unchanged
Drift is a signal, not automatic invalidation. The agent reads the actual diff and judges whether the change is material (broke an invariant) or cosmetic (comments, formatting).
Knowledge graph
Beyond flat module detection, haft maintains a knowledge graph that connects files, modules, decisions, and invariants. This graph powers several queries that agents and CLI commands use internally:
| Query | What it returns |
|---|---|
FindDecisionsForFile | All active decisions whose affected files include the given path |
FindInvariantsForFile | All invariants from all decisions governing a file — collected transitively through the dependency graph |
FindModuleForFile | The module boundary a file belongs to |
TransitiveDependents | All modules that depend on a given module, directly or transitively |
ComputeImpactSet | Given a set of changed files, returns all decisions and invariants that may be affected — the union of direct governance and transitive dependency impact |
ComputeImpactSet is what /h-verify scan uses under the hood. It's
also available to any agent through MCP, so custom workflows can query impact before
making changes.
Invariant injection
When an agent begins implementing a decision, haft collects invariants from all decisions governing the affected files — not just the decision being implemented. The agent receives these as constraints in its prompt.
Implementing dec-012 "Payment retry logic"
Affected files: payments/retry.go, payments/circuit.go
Injected invariants (from all governing decisions):
[dec-007] "Payment auth flow"
— auth token must be refreshed before retry, not reused from failed attempt
— max 3 retries with exponential backoff
[dec-003] "API rate limiting"
— retry requests count against rate limit budget
— no bypass of rate limiter for internal retries This prevents the classic problem: implementing one decision while accidentally violating invariants from another decision that governs the same files.
Invariant verification
Some invariants are structural — they describe relationships between modules, not runtime behavior. Haft can verify these against the live dependency graph:
- "no dependency from X to Y" — verified by checking that the dependency graph has no path from module X to module Y
- "no circular dependencies" — verified by checking the dependency graph for cycles
These checks run during /h-verify scan. When a structural invariant is violated,
the decision is flagged with the specific dependency chain that breaks it.
Invariant violation: dec-005 "Layered architecture"
Invariant: "no dependency from domain/ to infrastructure/"
Violation: domain/user.go imports infrastructure/postgres/conn.go
Chain: domain/ → infrastructure/postgres/ Structural invariant verification is deterministic — it checks the actual import graph, not heuristics. If the graph says there's a dependency, there is one.
Using it in practice
| When | What to do |
|---|---|
After /h-onboard | Check coverage — which modules are blind? Prioritize the critical ones. |
| During code review | If a PR touches files under a decision, the drift report will flag it. |
| Before a release | /h-verify scan — see all stale decisions, code drift, and invariant violations in one view. |
| New team member | /h-status shows the full architecture decision landscape at a glance. |
| Before implementing | Invariant injection ensures the agent knows all constraints on affected files. |