# Gap Analysis — v3.14.x: Making Codebase Analysis First-Class Cortex Core

> **2026-07-27 update (issue #196):** `infrastructure/git_diff.py` (the
> Move-1 dependency below) was deleted — it never gained a caller in
> this repo, and its `{file, diff_type, lines, truncated}` output
> contract now lives as an independent, live, tested implementation in
> `cortex-viz/cortex_viz/server/git_diff_engine.py` (`diff_for_path`),
> whose own comments record that the port from Cortex was never done
> ("the git_diff / http_file_diff helpers were never ported"). Move 1
> below is stale; if `core/git_diff_to_symbols.py` is still wanted, it
> should read git-diff content by shelling out itself or by depending
> on `cortex-viz`'s engine, not by reviving this module.

**Date:** 2026-04-24
**Scope:** v3.14.0 / v3.14.1 (commits `f966b9d` through `a7cde07`)
**Method:** Three-genius cross-check — Alexander (pattern language /
wholeness), Coase (transaction cost / build-vs-buy), Von Neumann
(cross-domain isomorphism).

All three analyses converge on the same structural picture. This
document records the convergence, lists the concrete gaps in severity
order, and proposes an ordered generative sequence that closes each
gap with the smallest possible change.

---

## 1. The picture all three analyses produce

v3.14 introduced a second, parallel graph subsystem alongside the
mature memory/entity subsystem Cortex already had. The new subsystem
delivers real value (workflow graph, AST symbols, call/import edges,
query_workflow_graph MCP tool), but it sits as a **peripheral island**
— its structures, dynamics, and consumers do not connect to the
memory/consolidation/retrieval core that Cortex is built on.

- **Alexander:** the codebase-analysis surface has no strong center;
  two parallel analyzers with no shared port (`core/` has no
  `CodebaseAnalysisPort`).
- **Coase:** the boundary between Cortex and `automatised-pipeline` is
  in the wrong place — ~1200 LOC of bridge code duplicates capabilities
  that already exist in-process in Cortex. 4 of 5 capabilities required
  for "first-class core" are already built; only `git_diff → symbols`
  is genuinely missing.
- **Von Neumann:** the static topology of the codebase graph and the
  entity graph is isomorphic; the dynamics (heat, plasticity,
  spreading activation, pruning) are not. Partial unification — a
  behavioral overlay on top of the structural truth layer — is the
  right move.

**The single highest-leverage move all three converge on:** treat AST
edges (`defined_in`, `calls`, `imports`) as immutable structural facts
at `confidence=1.0`, and add a **behavioral overlay** (`heat`,
`co_edited`, `last_touched`) governed by the same thermodynamics that
already runs the memory subsystem. One column, one wiring change, and
three separate consumers (`pipeline_impact_bump`, `change_impact`,
`plasticity`) collapse into one-line calls into pre-existing
primitives.

---

## 2. Gaps in severity order

| # | Severity | Gap | Evidence | Fix |
|---|---|---|---|---|
| **G1** | BLOCKER | No `heat` / `last_touched` / `co_edited` field on FILE / SYMBOL nodes | `workflow_graph_schema.py:43` (no behavioral fields); `pipeline_impact_bump.py:143-182` reinvents heat-bump via ILIKE content scan | Add `heat: float` + `last_touched: datetime` to `WorkflowNode`; reuse `update_entities_heat_batch` (`pg_store_entities.py:19`) to update in bulk |
| **G2** | BLOCKER | No `CodebaseAnalysisPort` protocol in `core/`; two parallel analyzers (tree-sitter in-house + AP-bridge) with no shared interface | `handlers/codebase_analyze.py:311-322` (tree-sitter path) vs `handlers/ingest_codebase.py:40-43` (AP path) — neither imports the other | Define `core/codebase_analysis_port.py` with one Protocol; wire both paths as adapters |
| **G3** | BLOCKER | Consolidation is blind to the codebase graph | `handlers/consolidation/plasticity.py:49` — `find_co_accessed_pairs` knows entities/memories only, not SYMBOL nodes or CALLS/IMPORTS edges | Add `store.get_code_edges(kind=…)` + union it into `_find_co_accessed_pairs` |
| **G4** | BLOCKER | `git_diff → impacted symbols` is the one genuine capability Cortex does NOT have in-process | Only `automatised-pipeline.detect_changes` provides it; `hooks/pipeline_impact_bump.py:116-120` calls it over MCP on every Edit/Write | Build `core/git_diff_to_symbols.py` — parse unified diff, map changed line ranges to symbol definitions via existing `FileAnalysis` |
| **G5** | MAJOR | 634-LOC file violates §4.1 (500-line cap) | `infrastructure/workflow_graph_source_ast.py` — Cypher constants, path helpers, provenance parsing, verify_symbols all co-resident | Split into `_symbols.py` + `_edges.py` + facade (≤150 LOC) |
| **G6** | MAJOR | `query_workflow_graph` is the natural data source for impact / spreading-activation / retrieval-boost — but nothing internal consumes it | `core/spreading_activation.py`, `handlers/recall`, `hooks/pipeline_impact_bump.py` all walk their own paths | Route `hooks/pipeline_impact_bump._bump_heat_for_symbols` through `query_workflow_graph(neighbour_of="symbol:…", edge_kind=["calls","imports"], depth=2)` |
| **G7** | MAJOR | Codebase graph has no decay / pruning cycle | `workflow_graph_builder.py:454` — dead symbols never collected; `calls`/`imports` edges unseen across re-parse never fade | Add codebase pass to `consolidation_engine.py`: decay `last_touched`, prune SYMBOL nodes whose file is deleted |
| **G8** | MAJOR | `codebase_graph.compute_impact` (`codebase_graph.py:221-272`) returns unweighted BFS — no decay, no ranking | Change-impact UI shows all N-hop reachables as equally relevant | Replace `_bfs` body with `spread_activation(edges, [target], decay=0.65, threshold=0.1)` (`spreading_activation.py:76`) — one line, reuses existing primitive |
| **G9** | MAJOR | `codebase_analyze.py` writes entities but never emits workflow-graph SYMBOL nodes from its tree-sitter output | `codebase_analyze.py:343-380` → PG inserts only; no `WorkflowGraphBuilder` call | When AP is disabled, workflow graph loses its SYMBOL ring even though we parsed the code ourselves |
| **G10** | MINOR | No case-canonicalization on symbols | `codebase_graph.py:154` — `symbol_to_file.setdefault(base_name, path)` silently shadows `Foo.bar` vs `foo.bar` across files | Reuse `shared/entity_canonical.canonicalize_entity_name` |
| **G11** | MINOR | No in-core cache for native-path analyzer output | `codebase_analyze._run_graph_analysis` recomputes on every call; AP side has `find_cached_graph` / `memoise_graph_path` | Extend `memoise_graph_path` to cover the native path |

---

## 3. The structural-vs-behavioral distinction (von Neumann)

The reason full unification with the memory subsystem is WRONG: AST
edges are **structural facts authored by the parser**, not beliefs
learned from evidence. Decaying `defined_in` is nonsensical — a
symbol either exists in the file or it doesn't. So the edges produced
by `codebase_parser.py` / `ast_extractors.py` stay at
`confidence=1.0`, never decay, never get reinforced.

What DOES decay, reinforce, spread, and fade is the **behavioral
overlay**:

| Layer | Fields | Dynamics | Authoritative writer |
|---|---|---|---|
| **Structural (truth)** | `defined_in`, `calls`, `imports`, `inherits`, `member_of`, `confidence=1.0` | Never decay; rebuilt on re-parse | `codebase_parser.py` / `automatised-pipeline` |
| **Behavioral (belief)** | `heat`, `last_touched`, `co_edited` weight, `recent_activation` | Thermodynamic — reuses `decay_cycle`, `synaptic_plasticity`, `spread_activation` | Hooks (`pipeline_impact_bump`, `post_tool_capture`) + consolidation |

The two layers share the same node IDs and the same storage table —
nothing needs to be moved. A single `ALTER TABLE` on the relationships
/ entities side to add behavioral columns is enough.

---

## 4. Generative sequence — 7 ordered moves

Each move is minimum-footprint and only touches one file-cluster. Each
move unblocks the next. No rewrites; preserve what's already working.

1. **Build `core/git_diff_to_symbols.py` (G4).** New file, <200 LOC,
   parses unified diff via `infrastructure/git_diff.py` output and maps
   changed line ranges to symbols via existing `FileAnalysis`. Closes
   the only genuine capability gap; everything else uses it.

2. **Add behavioral columns (`heat`, `last_touched`) to SYMBOL and
   FILE nodes (G1).** One migration; reuse the existing
   `entities.heat` column mechanics — these already live in
   `pg_store_entities.py`. Update `workflow_graph_schema.py:43` +
   `workflow_graph_builder.py:454` to populate.

3. **Rewrite `hooks/pipeline_impact_bump.py` (G4 + G6).** Replace the
   external MCP `detect_changes` call + ILIKE sweep with
   `core.git_diff_to_symbols` + `update_entities_heat_batch` on the
   impacted SYMBOL IDs. Keep the AP call feature-flagged as validator
   for one release. Crossing #5 in Coase matrix → REPLICATE. Net: hook
   drops from 229 LOC to ~50 LOC; per-edit latency from tens-of-ms to
   tens-of-μs.

4. **Define `core/codebase_analysis_port.py` Protocol (G2).** Two
   adapters: `infrastructure/codebase_analysis_adapter_native.py`
   (wraps existing tree-sitter) and
   `infrastructure/codebase_analysis_adapter_ap.py` (wraps
   `ap_bridge`). Both satisfy the protocol. Composition-root picks by
   feature flag. Strong center now exists at V1.

5. **Split `infrastructure/workflow_graph_source_ast.py` (G5).**
   Behavior-preserving only: facade + `_symbols.py` + `_edges.py`.
   Fixes §4.1 violation; de-risks subsequent surgery on the AP side.

6. **Add `store.get_code_edges(kind=…)` + union it into
   `plasticity._find_co_accessed_pairs` (G3).** One method on the
   store + one line in plasticity. Consolidation finally reads the
   graph it helps maintain. Hebbian LTP starts firing on real call
   structure.

7. **Unify impact/blast-radius through spreading activation (G8).**
   Replace `codebase_graph.compute_impact._bfs` body with a call to
   existing `core/spreading_activation.spread_activation`. One line,
   reuses the primitive the memory side already uses; gives weighted,
   decayed, top-K blast radius for free. `handlers/change_impact.py`
   renders ranked results instead of uniform BFS output.

After these 7 moves:
- Duplicated bridge code drops by ~1000 LOC
- `pipeline_impact_bump.py` collapses
- `change_impact.py` becomes a thin formatter
- Consolidation (`plasticity`, `memify`) gains codebase awareness with
  zero new algorithms — the algorithms already exist on the memory
  side
- ADR-0046 can be updated from "AP is primary source" to "AP is
  optional enrichment; in-process tree-sitter is primary"

---

## 5. What stays external (Coase — KEEP)

- `search_codebase` (BM25 + TF-IDF + RRF over AST) — real specialist
  capability, low usage volume, keep via AP.
- `health_check` — trivial, bootstrap-only.
- Cypher / Kuzu — no reason to bring in a second graph engine when
  PostgreSQL holds the same edges.

---

## 6. ADR impact

ADR-0046 ("automatised-pipeline integration") describes a mandatory
dependency. After this sequence:
- ADR-0046 status → **Superseded in part.** In-process tree-sitter
  becomes the primary path. AP stays optional for `search_codebase`
  enrichment.
- New ADR needed: "Codebase analysis as first-class core capability
  with structural + behavioral layering" — references this gap
  analysis.

---

## 7. Open questions (to resolve before starting move 1)

- **Should behavioral columns live on `WorkflowNode` (`nodes_v2`
  table) or on `entities`?** My read: reuse `entities` since ENTITY
  and SYMBOL are closely related (a symbol IS a named thing in a
  domain). Saves a migration.
- **Do we keep the AP bridge's 13-call `query_graph` bulk pull pattern
  for the v3.14.2 transition window, or delete it once in-process is
  proven?** Lean toward keeping for one minor version, then removing
  in v3.15.
- **Granularity of `heat` decay on SYMBOL:** per-edit bump (+0.15) +
  24h half-life matches the memory side. Does that fit? Empirical
  question — run benchmark before/after.

---

## 8. Cross-reference

- Alexander brief + full answer — stored as subagent session output.
- Coase brief + full answer — stored as subagent session output.
- Von Neumann brief + full answer — stored as subagent session output.
- ADR-0046 at `docs/adr/ADR-0046-automatised-pipeline-integration.md`.
- Affected files (full list): see appendix below (~30 files across
  `core/`, `infrastructure/`, `handlers/`, `hooks/`).

