Skip to content

Commit f3d6533

Browse files
authored
Merge branch 'main' into dependabot/npm_and_yarn/vitest/coverage-v8-4.1.0
2 parents a8aab7d + f1ed6b3 commit f3d6533

17 files changed

Lines changed: 556 additions & 71 deletions

File tree

docs/roadmap/ROADMAP.md

Lines changed: 74 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ Codegraph is a strong local-first code graph CLI. This roadmap describes planned
1616
| [**2**](#phase-2--foundation-hardening) | Foundation Hardening | Parser registry, complete MCP, test coverage, enhanced config, multi-repo MCP | **Complete** (v1.4.0) |
1717
| [**2.5**](#phase-25--analysis-expansion) | Analysis Expansion | Complexity metrics, community detection, flow tracing, co-change, manifesto, boundary rules, check, triage, audit, batch, hybrid search | **Complete** (v2.6.0) |
1818
| [**2.7**](#phase-27--deep-analysis--graph-enrichment) | Deep Analysis & Graph Enrichment | Dataflow analysis, intraprocedural CFG, AST node storage, expanded node/edge types, extractors refactoring, CLI consolidation, interactive viewer, exports command, normalizeSymbol | **Complete** (v3.0.0) |
19-
| [**3**](#phase-3--architectural-refactoring) | Architectural Refactoring (Vertical Slice) | Unified AST analysis framework, command/query separation, repository pattern, queries.js decomposition, composable MCP, CLI commands, domain errors, presentation layer, domain grouping, curated API, unified graph model | **In Progress** (v3.1.3) |
19+
| [**3**](#phase-3--architectural-refactoring) | Architectural Refactoring (Vertical Slice) | Unified AST analysis framework, command/query separation, repository pattern, queries.js decomposition, composable MCP, CLI commands, domain errors, builder pipeline, presentation layer, domain grouping, curated API, unified graph model, qualified names | **In Progress** (v3.1.3) |
2020
| [**4**](#phase-4--typescript-migration) | TypeScript Migration | Project setup, core type definitions, leaf -> core -> orchestration module migration, test migration | Planned |
2121
| [**5**](#phase-5--intelligent-embeddings) | Intelligent Embeddings | LLM-generated descriptions, enhanced embeddings, build-time semantic metadata, module summaries | Planned |
2222
| [**6**](#phase-6--natural-language-queries) | Natural Language Queries | `ask` command, conversational sessions, LLM-narrated graph queries, onboarding tools | Planned |
@@ -667,7 +667,7 @@ src/
667667
src/
668668
db/
669669
connection.js # Open, WAL mode, pragma tuning
670-
migrations.js # Schema versions (currently 13 migrations)
670+
migrations.js # Schema versions (currently 15 migrations)
671671
query-builder.js # Lightweight SQL builder for common filtered queries
672672
repository/
673673
index.js # Barrel re-export
@@ -775,9 +775,9 @@ Reduced `index.js` from ~190 named exports (243 lines) to 48 curated exports (57
775775

776776
> **Removed: Decompose complexity.js** — Subsumed by 3.1. The standalone complexity decomposition from the previous revision is now part of the unified AST analysis framework (3.1). The `complexity.js` per-language rules become `ast-analysis/rules/complexity/{lang}.js` alongside CFG and dataflow rules.
777777
778-
### 3.8 -- Domain Error Hierarchy
778+
### 3.8 -- Domain Error Hierarchy
779779

780-
Replace ad-hoc error handling (mix of thrown `Error`, returned `null`, `logger.warn()`, `process.exit(1)`) across 50 modules with structured domain errors.
780+
Structured domain errors replace ad-hoc error handling across the codebase. 8 error classes in `src/errors.js`: `CodegraphError`, `ParseError`, `DbError`, `ConfigError`, `ResolutionError`, `EngineError`, `AnalysisError`, `BoundaryError`. The CLI catches domain errors and formats for humans; MCP returns structured `{ isError, code }` responses.
781781

782782
```js
783783
class CodegraphError extends Error { constructor(message, { code, file, cause }) { ... } }
@@ -790,41 +790,43 @@ class AnalysisError extends CodegraphError { code = 'ANALYSIS_FAILED' }
790790
class BoundaryError extends CodegraphError { code = 'BOUNDARY_VIOLATION' }
791791
```
792792

793-
The CLI catches domain errors and formats for humans. MCP returns structured error responses. No more `process.exit()` from library code.
793+
-`src/errors.js` — 8 domain error classes with `code`, `file`, `cause` fields
794+
- ✅ CLI top-level catch formats domain errors for humans
795+
- ✅ MCP returns structured error responses
796+
- ✅ Domain errors adopted across config, boundaries, triage, and query modules
794797

795798
**New file:** `src/errors.js`
796799

797-
### 3.9 -- Builder Pipeline Architecture
800+
### 3.9 -- Builder Pipeline Architecture
798801

799-
Refactor `buildGraph()` (1,355 lines) from a mega-function into explicit, independently testable pipeline stages. Phase 2.7 added 4 opt-in stages, bringing the total to 11 core + 4 optional.
802+
Refactored `buildGraph()` from a monolithic mega-function into explicit, independently testable pipeline stages. `src/builder.js` is now a 12-line barrel re-export. `src/builder/pipeline.js` orchestrates 9 stages via `PipelineContext`. Each stage is a separate file in `src/builder/stages/`.
800803

801-
```js
802-
const pipeline = [
803-
// Core (always)
804-
collectFiles, // (rootDir, config) => filePaths[]
805-
detectChanges, // (filePaths, db) => { changed, removed, isFullBuild }
806-
parseFiles, // (filePaths, engineOpts) => Map<file, symbols>
807-
insertNodes, // (symbolMap, db) => nodeIndex
808-
resolveImports, // (symbolMap, rootDir, aliases) => importEdges[]
809-
buildCallEdges, // (symbolMap, nodeIndex) => callEdges[]
810-
buildClassEdges, // (symbolMap, nodeIndex) => classEdges[]
811-
resolveBarrels, // (edges, symbolMap) => resolvedEdges[]
812-
insertEdges, // (allEdges, db) => stats
813-
extractASTNodes, // (fileSymbols, db) => astStats (always, post-parse)
814-
buildStructure, // (db, fileSymbols, rootDir) => structureStats
815-
classifyRoles, // (db) => roleStats
816-
emitChangeJournal, // (rootDir, changes) => void
817-
818-
// Opt-in (dynamic imports)
819-
computeComplexity, // --complexity: (db, rootDir, engine) => complexityStats
820-
buildDataflowEdges, // --dataflow: (db, fileSymbols, rootDir) => dataflowStats
821-
buildCFGData, // --cfg: (db, fileSymbols, rootDir) => cfgStats
822-
]
804+
```
805+
src/
806+
builder.js # 12-line barrel re-export
807+
builder/
808+
context.js # PipelineContext — shared state across stages
809+
pipeline.js # Orchestrator: setup → stages → timing
810+
helpers.js # batchInsertNodes, collectFiles, fileHash, etc.
811+
incremental.js # Incremental build logic
812+
stages/
813+
collect-files.js # Discover source files
814+
detect-changes.js # Incremental: hash comparison, removed detection
815+
parse-files.js # Parse via native/WASM engine
816+
insert-nodes.js # Batch-insert nodes, children, contains/parameter_of edges
817+
resolve-imports.js # Import resolution with aliases
818+
build-edges.js # Call edges, class edges, barrel resolution
819+
build-structure.js # Directory/file hierarchy
820+
run-analyses.js # Complexity, CFG, dataflow, AST store
821+
finalize.js # Build meta, timing, db close
823822
```
824823

825-
Watch mode reuses the same stages triggered per-file, eliminating the `watcher.js` divergence.
824+
-`PipelineContext` shared state replaces function parameters
825+
- ✅ 9 sequential stages, each independently testable
826+
-`src/builder.js` reduced to barrel re-export
827+
- ✅ Timing tracked per-stage in `ctx.timing`
826828

827-
**Affected files:** `src/builder.js`, `src/watcher.js`
829+
**Affected files:** `src/builder.js` → split into `src/builder/`
828830

829831
### 3.10 -- Embedder Subsystem Extraction
830832

@@ -852,49 +854,70 @@ The pluggable store interface enables future O(log n) ANN search (e.g., `hnswlib
852854

853855
**Affected files:** `src/embedder.js` -> split into `src/embeddings/`
854856

855-
### 3.11 -- Unified Graph Model
857+
### 3.11 -- Unified Graph Model
856858

857-
Unify the four parallel graph representations (structure.js, cochange.js, communities.js, viewer.js) into a shared in-memory graph model.
859+
Unified the four parallel graph representations into a shared in-memory `CodeGraph` model. The `src/graph/` directory contains the model, 3 builders, 6 algorithms, and 2 classifiers. Algorithms are composable — run community detection on the dependency graph, the temporal graph, or a merged graph.
858860

859861
```
860862
src/
861863
graph/
862-
model.js # Shared in-memory graph (nodes + edges + metadata)
864+
index.js # Barrel re-export
865+
model.js # CodeGraph class: nodes Map, directed/undirected adjacency
863866
builders/
864-
dependency.js # Build from SQLite edges
867+
index.js # Barrel
868+
dependency.js # Build from SQLite call/import edges
865869
structure.js # Build from file/directory hierarchy
866-
temporal.js # Build from git history (co-changes)
870+
temporal.js # Build from git co-change history
867871
algorithms/
872+
index.js # Barrel
868873
bfs.js # Breadth-first traversal
869-
shortest-path.js # Path finding
870-
tarjan.js # Cycle detection
874+
shortest-path.js # Dijkstra path finding
875+
tarjan.js # Strongly connected components / cycle detection
871876
louvain.js # Community detection
872-
centrality.js # Fan-in/fan-out, betweenness
873-
clustering.js # Cohesion, coupling, density
877+
centrality.js # Fan-in/fan-out, betweenness centrality
874878
classifiers/
875-
roles.js # Node role classification
876-
risk.js # Risk scoring
879+
index.js # Barrel
880+
roles.js # Node role classification (hub, utility, leaf, etc.)
881+
risk.js # Composite risk scoring
877882
```
878883

879-
Algorithms become composable -- run community detection on the dependency graph, the temporal graph, or a merged graph.
884+
-`CodeGraph` in-memory model with nodes Map, successors/predecessors adjacency
885+
- ✅ 3 builders: dependency (SQLite edges), structure (file hierarchy), temporal (git co-changes)
886+
- ✅ 6 algorithms: BFS, shortest-path, Tarjan SCC, Louvain community, centrality
887+
- ✅ 2 classifiers: role classification, risk scoring
888+
-`structure.js`, `communities.js`, `cycles.js`, `triage.js`, `viewer.js` refactored to use graph model
880889

881890
**Affected files:** `src/structure.js`, `src/cochange.js`, `src/communities.js`, `src/cycles.js`, `src/triage.js`, `src/viewer.js`
882891

883-
### 3.12 -- Qualified Names & Hierarchical Scoping (Partially Addressed)
892+
### 3.12 -- Qualified Names & Hierarchical Scoping
884893

885-
> **Phase 2.7 progress:** `parent_id` column, `contains` edges, `parameter_of` edges, and `childrenData()` query now model one-level parent-child relationships. This addresses ~80% of the use case.
894+
> **Phase 2.7 progress:** `parent_id` column, `contains` edges, `parameter_of` edges, and `childrenData()` query now model one-level parent-child relationships.
886895
887-
Remaining work -- enrich the node model with deeper scope information:
896+
Node model enriched with `qualified_name`, `scope`, and `visibility` columns (migration v15). Enables direct lookups like "all methods of class X" via `findNodesByScope()` and qualified name resolution via `findNodeByQualifiedName()` — no edge traversal needed.
888897

889898
```sql
890-
ALTER TABLE nodes ADD COLUMN qualified_name TEXT; -- 'DateHelper.format'
891-
ALTER TABLE nodes ADD COLUMN scope TEXT; -- 'DateHelper'
899+
ALTER TABLE nodes ADD COLUMN qualified_name TEXT; -- 'DateHelper.format', 'freeFunction.x'
900+
ALTER TABLE nodes ADD COLUMN scope TEXT; -- 'DateHelper', null for top-level
892901
ALTER TABLE nodes ADD COLUMN visibility TEXT; -- 'public' | 'private' | 'protected'
902+
CREATE INDEX idx_nodes_qualified_name ON nodes(qualified_name);
903+
CREATE INDEX idx_nodes_scope ON nodes(scope);
893904
```
894905

895-
Enables queries like "all methods of class X" without traversing edges. The `parent_id` FK only goes one level -- deeply nested scopes (namespace > class > method > closure) aren't fully represented. `qualified_name` would allow direct lookup.
896-
897-
**Affected files:** `src/db.js`, `src/extractors/`, `src/queries.js`, `src/builder.js`
906+
- ✅ Migration v15: `qualified_name`, `scope`, `visibility` columns + indexes
907+
-`batchInsertNodes` expanded to 9 columns (name, kind, file, line, end_line, parent_id, qualified_name, scope, visibility)
908+
-`insert-nodes.js` computes qualified_name and scope during insertion: methods get scope from class prefix, children get `parent.child` qualified names
909+
- ✅ Visibility extraction for all 8 language extractors:
910+
- JS/TS: `accessibility_modifier` nodes + `#` private field detection
911+
- Java/C#/PHP: `modifiers`/`visibility_modifier` AST nodes via shared `extractModifierVisibility()`
912+
- Python: convention-based (`__name` → private, `_name` → protected)
913+
- Go: capitalization convention (uppercase → public, lowercase → private)
914+
- Rust: `visibility_modifier` child (`pub` → public, else private)
915+
-`findNodesByScope(db, scopeName, opts)` — query by scope with optional kind/file filters
916+
-`findNodeByQualifiedName(db, qualifiedName)` — direct lookup without edge traversal
917+
-`childrenData()` returns `qualifiedName`, `scope`, `visibility` for parent and children
918+
- ✅ Integration tests covering qualified_name, scope, visibility, and childrenData output
919+
920+
**Affected files:** `src/db/migrations.js`, `src/db/repository/nodes.js`, `src/builder/helpers.js`, `src/builder/stages/insert-nodes.js`, `src/extractors/*.js`, `src/extractors/helpers.js`, `src/analysis/symbol-lookup.js`
898921

899922
### 3.13 -- Testing Pyramid with InMemoryRepository
900923

src/analysis/symbol-lookup.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,11 +209,17 @@ export function childrenData(name, customDbPath, opts = {}) {
209209
kind: node.kind,
210210
file: node.file,
211211
line: node.line,
212+
scope: node.scope || null,
213+
visibility: node.visibility || null,
214+
qualifiedName: node.qualified_name || null,
212215
children: children.map((c) => ({
213216
name: c.name,
214217
kind: c.kind,
215218
line: c.line,
216219
endLine: c.end_line || null,
220+
qualifiedName: c.qualified_name || null,
221+
scope: c.scope || null,
222+
visibility: c.visibility || null,
217223
})),
218224
};
219225
});

src/builder/helpers.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -183,17 +183,17 @@ export const BATCH_CHUNK = 200;
183183

184184
/**
185185
* Batch-insert node rows via multi-value INSERT statements.
186-
* Each row: [name, kind, file, line, end_line, parent_id]
186+
* Each row: [name, kind, file, line, end_line, parent_id, qualified_name, scope, visibility]
187187
*/
188188
export function batchInsertNodes(db, rows) {
189189
if (!rows.length) return;
190-
const ph = '(?,?,?,?,?,?)';
190+
const ph = '(?,?,?,?,?,?,?,?,?)';
191191
for (let i = 0; i < rows.length; i += BATCH_CHUNK) {
192192
const chunk = rows.slice(i, i + BATCH_CHUNK);
193193
const vals = [];
194-
for (const r of chunk) vals.push(r[0], r[1], r[2], r[3], r[4], r[5]);
194+
for (const r of chunk) vals.push(r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7], r[8]);
195195
db.prepare(
196-
'INSERT OR IGNORE INTO nodes (name,kind,file,line,end_line,parent_id) VALUES ' +
196+
'INSERT OR IGNORE INTO nodes (name,kind,file,line,end_line,parent_id,qualified_name,scope,visibility) VALUES ' +
197197
chunk.map(() => ph).join(','),
198198
).run(...vals);
199199
}

src/builder/stages/insert-nodes.js

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,29 @@ export async function insertNodes(ctx) {
5050

5151
const insertAll = db.transaction(() => {
5252
// Phase 1: Batch insert all file nodes + definitions + exports
53+
// Row format: [name, kind, file, line, end_line, parent_id, qualified_name, scope, visibility]
5354
const phase1Rows = [];
5455
for (const [relPath, symbols] of allSymbols) {
55-
phase1Rows.push([relPath, 'file', relPath, 0, null, null]);
56+
phase1Rows.push([relPath, 'file', relPath, 0, null, null, null, null, null]);
5657
for (const def of symbols.definitions) {
57-
phase1Rows.push([def.name, def.kind, relPath, def.line, def.endLine || null, null]);
58+
// Methods already have 'Class.method' as name — use as qualified_name.
59+
// For methods, scope is the class portion; for top-level defs, scope is null.
60+
const dotIdx = def.name.lastIndexOf('.');
61+
const scope = dotIdx !== -1 ? def.name.slice(0, dotIdx) : null;
62+
phase1Rows.push([
63+
def.name,
64+
def.kind,
65+
relPath,
66+
def.line,
67+
def.endLine || null,
68+
null,
69+
def.name,
70+
scope,
71+
def.visibility || null,
72+
]);
5873
}
5974
for (const exp of symbols.exports) {
60-
phase1Rows.push([exp.name, exp.kind, relPath, exp.line, null, null]);
75+
phase1Rows.push([exp.name, exp.kind, relPath, exp.line, null, null, exp.name, null, null]);
6176
}
6277
}
6378
batchInsertNodes(db, phase1Rows);
@@ -84,13 +99,17 @@ export async function insertNodes(ctx) {
8499
const defId = nodeIdMap.get(`${def.name}|${def.kind}|${def.line}`);
85100
if (!defId) continue;
86101
for (const child of def.children) {
102+
const qualifiedName = `${def.name}.${child.name}`;
87103
childRows.push([
88104
child.name,
89105
child.kind,
90106
relPath,
91107
child.line,
92108
child.endLine || null,
93109
defId,
110+
qualifiedName,
111+
def.name,
112+
child.visibility || null,
94113
]);
95114
}
96115
}

src/db.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,10 @@ export {
2929
findImportTargets,
3030
findIntraFileCallEdges,
3131
findNodeById,
32+
findNodeByQualifiedName,
3233
findNodeChildren,
3334
findNodesByFile,
35+
findNodesByScope,
3436
findNodesForTriage,
3537
findNodesWithFanIn,
3638
getCallableNodes,

src/db/migrations.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,17 @@ export const MIGRATIONS = [
229229
CREATE INDEX IF NOT EXISTS idx_nodes_exported ON nodes(exported);
230230
`,
231231
},
232+
{
233+
version: 15,
234+
up: `
235+
ALTER TABLE nodes ADD COLUMN qualified_name TEXT;
236+
ALTER TABLE nodes ADD COLUMN scope TEXT;
237+
ALTER TABLE nodes ADD COLUMN visibility TEXT;
238+
UPDATE nodes SET qualified_name = name WHERE qualified_name IS NULL;
239+
CREATE INDEX IF NOT EXISTS idx_nodes_qualified_name ON nodes(qualified_name);
240+
CREATE INDEX IF NOT EXISTS idx_nodes_scope ON nodes(scope);
241+
`,
242+
},
232243
];
233244

234245
export function getBuildMeta(db, key) {
@@ -309,4 +320,34 @@ export function initSchema(db) {
309320
} catch {
310321
/* already exists */
311322
}
323+
try {
324+
db.exec('ALTER TABLE nodes ADD COLUMN qualified_name TEXT');
325+
} catch {
326+
/* already exists */
327+
}
328+
try {
329+
db.exec('ALTER TABLE nodes ADD COLUMN scope TEXT');
330+
} catch {
331+
/* already exists */
332+
}
333+
try {
334+
db.exec('ALTER TABLE nodes ADD COLUMN visibility TEXT');
335+
} catch {
336+
/* already exists */
337+
}
338+
try {
339+
db.exec('UPDATE nodes SET qualified_name = name WHERE qualified_name IS NULL');
340+
} catch {
341+
/* nodes table may not exist yet */
342+
}
343+
try {
344+
db.exec('CREATE INDEX IF NOT EXISTS idx_nodes_qualified_name ON nodes(qualified_name)');
345+
} catch {
346+
/* already exists */
347+
}
348+
try {
349+
db.exec('CREATE INDEX IF NOT EXISTS idx_nodes_scope ON nodes(scope)');
350+
} catch {
351+
/* already exists */
352+
}
312353
}

src/db/repository/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,10 @@ export {
3232
countNodes,
3333
findFileNodes,
3434
findNodeById,
35+
findNodeByQualifiedName,
3536
findNodeChildren,
3637
findNodesByFile,
38+
findNodesByScope,
3739
findNodesForTriage,
3840
findNodesWithFanIn,
3941
getFunctionNodeId,

0 commit comments

Comments
 (0)