Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 88 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,96 @@

All notable changes to CodeLens will be documented in this file.

The format is based on [Keep a Changelog](https://keepa.changelog.com/en/1.1.0/),
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0/html).

## [8.2.0] — Unreleased

### Graph Data Model (issue #8)

Replaces the ad-hoc flat-registry graph traversal with a true node + edge graph
backed by SQLite. This unblocks structural queries like "who calls this function
across the entire codebase", "blast radius if I rename this class", and
"circular dependency chains" — engines no longer need to reimplement partial
graph traversal logic.

### Added

- **`scripts/graph_model.py`** — New module implementing the graph data model:
- `init_graph_schema(conn)` — Creates `graph_nodes` + `graph_edges` tables
and 6 indexes (idempotent, called during database initialization).
- `populate_graph_tables(workspace, db_path)` — Reads the flat backend
registry and bulk-inserts all nodes + edges in a single transaction.
Clears stale rows first so re-scans don't duplicate.
- `query_callers(node_id, db_path, max_depth=1)` — BFS over CALLS edges
in reverse (who calls this node).
- `query_callees(node_id, db_path, max_depth=1)` — BFS over CALLS edges
forward (what this node calls).
- `clear_graph_tables(db_path)` — DELETE FROM both tables.
- `find_nodes_by_name`, `graph_tables_exist`, `graph_tables_populated`,
`graph_stats` — introspection helpers for engines and tests.

- **Graph schema** (additive, prefixed `graph_` to avoid collisions):
```sql
graph_nodes(id, node_id UNIQUE, node_type, name, file, line, extra_json)
graph_edges(id, source_id, target_id, edge_type, file, line,
confidence, extra_json)
```
Node types: `function|class|file|module|route|type|interface`
Edge types: `CALLS|IMPORTS|DEFINES|INHERITS|IMPLEMENTS|USES_TYPE`
(Only `CALLS` is populated in v8.2; other types are reserved for future
engine migrations — `impact`, `circular`, `dependents`.)

- **`trace --use-graph` / `--no-graph` flags** — The `trace` command now
queries the graph tables by default, with the flat-registry path retained
as fallback. Use `--no-graph` to force the flat path for A/B testing.

- **`tests/test_graph_model.py`** — 20 test cases covering schema init,
population, query_callers, query_callees, re-population idempotency, and
the trace pilot A/B comparison.

### Changed

- **`scripts/persistent_registry.py`** — Calls `init_graph_schema(conn)`
during `_init_schema` so the graph tables always exist by the time any
engine tries to query them. Additive — existing tables untouched.
- **`scripts/commands/scan.py`** — After the flat backend registry is built,
calls `populate_graph_tables(workspace, db_path)` to populate the graph
tables in a single bulk transaction. Scan output now includes a `graph`
field with node + edge counts.
- **`scripts/trace_engine.py`** — Pilot engine migration: `trace_symbol` is
now a dispatcher that picks between `trace_via_graph` (default) and
`trace_via_flat` (fallback). Falls back to flat automatically when graph
tables are empty (pre-8.2 databases). Output shape is identical regardless
of backend — callers and formatters don't need to know which backend ran.

### Non-Breaking

- All 56 existing CLI commands continue to work unchanged.
- Existing flat tables (`symbols`, `refs`, `files`, `analysis_cache`,
`scan_metadata`) and JSON registries (`frontend.json`, `backend.json`)
are untouched.
- The graph tables are additive — no existing table or column was modified.
- Scan performance impact is negligible (single bulk INSERT in one
transaction; <5ms on the clean_app fixture with 31 nodes + 97 edges).

### Migration Notes for Engine Authors

The flat registry remains the source of truth during scan. The graph tables
are a derived projection that engines can query for structural traversals.
To migrate an engine to the graph backend:

1. Check `graph_model.graph_tables_populated(db_path)` — if False, fall back
to the flat path (don't hard-fail).
2. Use `graph_model.find_nodes_by_name(name, db_path)` to find start nodes.
3. Use `graph_model.query_callers` / `query_callees` for BFS traversal.
4. Preserve the existing flat-path output shape so callers and formatters
don't break. See `trace_engine.trace_via_graph` for a reference impl.

Future engine migrations (post-v8.2): `impact`, `circular`, `dependents`.

---

## [8.1.0] — 2026-06-13

### F1 Benchmark Improvements
Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# CodeLens v8.1 — AI-Native Code Intelligence
# CodeLens v8.2 — AI-Native Code Intelligence

> **Before an AI writes a new class/id/function, CodeLens must be checked. This is not optional.**

CodeLens is an AI-native code intelligence platform that gives AI agents **full visibility** into a codebase before they write any code. It prevents collision, overwrite of existing logic, security vulnerabilities, and dead code through 56 CLI commands, an MCP server with 54 tools (49 static + 5 dynamic), AST-based taint analysis, live CVE/OSV scanning, and a plugin system with OWASP Top 10 + Compliance rule packs.
CodeLens is an AI-native code intelligence platform that gives AI agents **full visibility** into a codebase before they write any code. It prevents collision, overwrite of existing logic, security vulnerabilities, and dead code through 56 CLI commands, an MCP server with 54 tools (49 static + 5 dynamic), AST-based taint analysis, live CVE/OSV scanning, a plugin system with OWASP Top 10 + Compliance rule packs, and a true graph data model (nodes + edges) for structural code queries.

## Features

Expand All @@ -11,6 +11,7 @@ CodeLens is an AI-native code intelligence platform that gives AI agents **full
- **AST Taint Engine** — Tree-sitter based taint analysis with return-value propagation, scope hierarchy, and branch condition refinement
- **Live CVE/OSV Scanning** — Real-time vulnerability data from OSV.dev API with SQLite cache, 9 ecosystems (PyPI, npm, crates.io, Go, Maven, NuGet, RubyGems, Pub, Hex)
- **Cross-File Call Graph** — Workspace-wide call graph with import resolution and bidirectional taint propagation
- **Graph Data Model (v8.2)** — True node + edge graph (`graph_nodes` + `graph_edges` SQLite tables) for structural queries: callers, callees, blast radius, circular chains. Populated during scan; `trace` engine migrated to use it by default with `--use-graph` / `--no-graph` flag for A/B testing
- **Plugin System** — 4 plugin types (rule_pack/engine/formatter/command), 3-tier discovery (local → user → built-in), OWASP Top 10 (36 rules) + Compliance (53 rules: PCI-DSS v4.0 + HIPAA)
- **VS Code Extension** — Diagnostics Provider, Code Actions, Guard hooks, Health status bar
- **CI/CD Integration** — GitHub Actions workflows, SARIF v2.1.0 output, PR decoration, `check` quality-gate command
Expand Down Expand Up @@ -227,6 +228,7 @@ codelens/
│ ├── framework_detect.py # Framework auto-detection
│ ├── incremental.py # Incremental scan support
│ ├── edge_resolver.py # Cross-file edge resolution
│ ├── graph_model.py # Graph data model (nodes + edges) — issue #8
│ ├── search_engine.py # Regex code search
│ ├── trace_engine.py # Call chain tracing
│ ├── impact_engine.py # Change impact analysis
Expand Down
46 changes: 46 additions & 0 deletions references/agent-integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -1169,6 +1169,7 @@ workspace/
codelens.config.json ← Configuration
frontend.json ← Frontend registry (classes + ids)
backend.json ← Backend registry (nodes + edges)
codelens.db ← SQLite: symbols/refs/files/analysis_cache + graph_nodes/graph_edges (v8.2+)
mtimes.json ← File modification times cache
```

Expand Down Expand Up @@ -1228,6 +1229,51 @@ def quick_lookup(workspace, name):
return results
```

### 10.4 Graph Data Model (v8.2+)

For structural queries (callers, callees, blast radius, circular chains),
agents should prefer the graph data model over iterating the flat registry.
The graph tables (`graph_nodes` + `graph_edges`) are populated automatically
during `scan` and live in `.codelens/codelens.db` alongside the existing
SQLite tables.

```python
from graph_model import (
find_nodes_by_name, query_callers, query_callees,
graph_tables_populated, graph_stats,
)

db_path = os.path.join(workspace, '.codelens', 'codelens.db')

# Always check population first — pre-8.2 dbs won't have graph data.
if not graph_tables_populated(db_path):
# Fall back to flat registry iteration (see 10.3 above).
pass

# Find a function node by name (case-insensitive + fuzzy match).
nodes = find_nodes_by_name('my_function', db_path)

# Who calls this function? (BFS over CALLS edges in reverse)
callers = query_callers(nodes[0]['node_id'], db_path, max_depth=3)

# What does this function call? (BFS over CALLS edges forward)
callees = query_callees(nodes[0]['node_id'], db_path, max_depth=3)
```

**When to use the graph vs the flat registry:**

| Use case | Backend |
|----------|---------|
| Structural traversal (callers/callees/cycles/blast-radius) | **Graph** (`graph_model`) |
| Single-symbol lookup (does `foo` exist?) | Flat (`backend.json`) |
| Frontend class/id reference lookup | Flat (`frontend.json`) |
| Cross-language edge resolution (Tauri IPC) | Flat (edge_resolver) |

The graph is a derived projection of the flat registry — it is rebuilt from
`backend.json` on every scan. Engines that need the original node metadata
(`status`, `async`, `impl_for`, `component`) can read it from the graph
node's `extra` field, which preserves all non-id/fn/type/file/line fields.

---

## 11. Multi-Agent Coordination
Expand Down
20 changes: 20 additions & 0 deletions scripts/commands/scan.py
Original file line number Diff line number Diff line change
Expand Up @@ -905,6 +905,22 @@ def cmd_scan(workspace: str, incremental: bool = False, plugins: Optional[list]
}
save_backend_registry(workspace, backend_registry)

# ─── Graph Data Model Population (issue #8) ─────────────────
# After the flat backend registry is built, populate the graph_nodes +
# graph_edges tables from it in a single bulk transaction. This is
# additive and non-breaking — the flat registry remains the source of
# truth; the graph tables are a derived projection that engines can
# query for structural traversals (callers/callees/cycles/blast-radius).
# Failures here MUST NOT break the scan — the graph is an optimization
# layer; engines fall back to the flat registry if it's missing.
graph_population = {"nodes": 0, "edges": 0}
try:
from graph_model import populate_graph_tables, _default_db_path
db_path = _default_db_path(workspace)
graph_population = populate_graph_tables(workspace, db_path)
except Exception:
logger.warning("Graph table population failed", exc_info=True)

# Update mtimes cache
all_files = []
for file_list in files.values():
Expand Down Expand Up @@ -998,6 +1014,10 @@ def cmd_scan(workspace: str, incremental: bool = False, plugins: Optional[list]
"changed_files_count": len(changed_files) if changed_files else 0,
"unsupported_langs": fw.get("unsupported_langs", []) if fw else [],
"lang_note": _build_lang_note(fw) if fw else None,
"graph": {
"nodes": graph_population.get("nodes", 0),
"edges": graph_population.get("edges", 0),
},
}

# Add plugin rules data if plugins were requested
Expand Down
24 changes: 23 additions & 1 deletion scripts/commands/trace.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@


def add_args(parser):
"""Add trace-specific arguments to the parser."""
parser.add_argument("name", help="Symbol name to trace")
parser.add_argument("workspace", nargs="?", default=None,
help="Path to workspace root (auto-detected if omitted)")
Expand All @@ -15,15 +16,36 @@ def add_args(parser):
help="Domain to trace")
parser.add_argument("--max-results", type=int, default=MAX_CHAIN_RESULTS,
help=f"Max chain entries to return (default {MAX_CHAIN_RESULTS})")
# v8.2 (issue #8): toggle between the new graph backend (default) and the
# legacy flat-registry backend. Default is graph with automatic fallback
# to flat when the graph tables are empty. Use --no-graph to force the
# flat path for A/B comparison.
parser.add_argument(
"--use-graph",
dest="use_graph",
action="store_true",
default=True,
help="Use the graph_nodes/graph_edges backend (default). "
"Falls back to flat registry if graph tables are empty.",
)
parser.add_argument(
"--no-graph",
dest="use_graph",
action="store_false",
help="Force the legacy flat-registry backend (A/B testing).",
)


def execute(args, workspace):
"""Execute the trace command."""
use_graph = getattr(args, "use_graph", True)
return trace_symbol(
args.name, workspace,
direction=args.direction,
max_depth=args.depth,
domain=args.domain,
max_results=args.max_results
max_results=args.max_results,
use_graph=use_graph,
)


Expand Down
Loading
Loading