Skip to content

Commit ce3f36e

Browse files
authored
Merge branch 'main' into feat/release-skill-auto-semver
2 parents d0ebca0 + c578535 commit ce3f36e

153 files changed

Lines changed: 5921 additions & 5345 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/roadmap/ROADMAP.md

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1080,7 +1080,7 @@ npm workspaces (`package.json` `workspaces`), `pnpm-workspace.yaml`, and `lerna.
10801080

10811081
## Phase 5 -- TypeScript Migration
10821082

1083-
> **Status:** In Progress — 76 of 283 source files migrated (~27%), 207 `.js` files remaining
1083+
> **Status:** In Progress — 178 of 283 source files migrated (~63%), 105 `.js` files remaining
10841084
10851085
**Goal:** Migrate the codebase from plain JavaScript to TypeScript, leveraging the clean module boundaries established in Phase 3. Incremental module-by-module migration starting from leaf modules inward.
10861086

@@ -1135,19 +1135,15 @@ Migrate modules that implement domain logic and Phase 3 interfaces. Some migrate
11351135

11361136
### 5.5 -- Orchestration & Public API Migration (In Progress)
11371137

1138-
Migrate top-level orchestration, features, and entry points. Some migrated via [#555](https://github.com/optave/codegraph/pull/555), 159 files remaining.
1138+
Migrate top-level orchestration, features, and entry points. Some migrated via [#555](https://github.com/optave/codegraph/pull/555), 141 files remaining.
11391139

1140-
**Migrated:** `domain/graph/builder.ts` + `context.ts` + `helpers.ts` + `pipeline.ts`, `domain/graph/watcher.ts`, `domain/search/{generator,index,models}.ts`, `mcp/{index,middleware,server,tool-registry}.ts`, `features/export.ts`, `index.ts`
1140+
**Migrated:** `domain/graph/builder.ts` + `context.ts` + `helpers.ts` + `pipeline.ts`, `domain/graph/watcher.ts`, `domain/search/{generator,index,models}.ts`, `mcp/{index,middleware,server,tool-registry}.ts`, `features/export.ts`, `index.ts`, `ast-analysis/` (all 18 files), `features/` (all 20 files), `presentation/` (all 28 files), `mcp/tools/` (all 36 files)
11411141

1142-
**Remaining (159):**
1142+
**Remaining (57):**
11431143

11441144
| Module | Files | Notes |
11451145
|--------|-------|-------|
11461146
| `cli.js` + `cli/` | 55 | Commander entry point, 43 command handlers (`commands/`), barrel, shared CLI utilities |
1147-
| `mcp/tools/` | 36 | Individual MCP tool handlers + barrel |
1148-
| `presentation/` | 28 | Presentation formatters (14 files), `queries-cli/` (7 files), sequence-renderer, viewer, export, etc. |
1149-
| `features/` | 21 | audit, batch, boundaries, cfg, check, cochange, communities, complexity, dataflow, flow, graph-enrichment, manifesto, owners, sequence, snapshot, structure, triage, ast, branch-compare, `shared/find-nodes` |
1150-
| `ast-analysis/` | 18 | AST analysis framework, visitors (4), language-specific rules (9), engine, metrics, shared, visitor-utils |
11511147
| `index.js` | 1 | Public API exports (stale — `.ts` exists) |
11521148

11531149
**Stale `.js` counterparts to delete (13 files):** `domain/graph/builder.js`, `domain/graph/builder/{context,helpers,pipeline}.js`, `domain/graph/watcher.js`, `domain/search/{generator,index,models}.js`, `features/export.js`, `mcp/{index,middleware,server,tool-registry}.js` — these have `.ts` counterparts already
Lines changed: 117 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,22 @@ import path from 'node:path';
1919
import { performance } from 'node:perf_hooks';
2020
import { bulkNodeIdsByFile } from '../db/index.js';
2121
import { debug } from '../infrastructure/logger.js';
22+
import type {
23+
AnalysisOpts,
24+
AnalysisTiming,
25+
ASTNodeRow,
26+
BetterSqlite3Database,
27+
CfgBlock,
28+
CfgEdge,
29+
DataflowResult,
30+
Definition,
31+
EngineOpts,
32+
ExtractorOutput,
33+
TreeSitterNode,
34+
Visitor,
35+
WalkOptions,
36+
WalkResults,
37+
} from '../types.js';
2238
import { computeLOCMetrics, computeMaintainabilityIndex } from './metrics.js';
2339
import {
2440
AST_TYPE_MAPS,
@@ -35,6 +51,35 @@ import { createCfgVisitor } from './visitors/cfg-visitor.js';
3551
import { createComplexityVisitor } from './visitors/complexity-visitor.js';
3652
import { createDataflowVisitor } from './visitors/dataflow-visitor.js';
3753

54+
// ─── Visitor result shapes (internal, not exported) ──────────────────────
55+
56+
interface ComplexityFuncResult {
57+
funcNode: TreeSitterNode;
58+
funcName: string | null;
59+
metrics: {
60+
cognitive: number;
61+
cyclomatic: number;
62+
maxNesting: number;
63+
halstead?: { volume: number; difficulty: number; effort: number; bugs: number };
64+
};
65+
}
66+
67+
interface CfgFuncResult {
68+
funcNode: TreeSitterNode;
69+
blocks: CfgBlock[];
70+
edges: CfgEdge[];
71+
cyclomatic?: number;
72+
}
73+
74+
interface SetupResult {
75+
visitors: Visitor[];
76+
walkerOpts: WalkOptions;
77+
astVisitor: Visitor | null;
78+
complexityVisitor: Visitor | null;
79+
cfgVisitor: Visitor | null;
80+
dataflowVisitor: Visitor | null;
81+
}
82+
3883
// ─── Extension sets for quick language-support checks ────────────────────
3984

4085
const CFG_EXTENSIONS = buildExtensionSet(CFG_RULES);
@@ -44,15 +89,19 @@ const WALK_EXTENSIONS = buildExtensionSet(AST_TYPE_MAPS);
4489

4590
// ─── Lazy imports (heavy modules loaded only when needed) ────────────────
4691

47-
let _parserModule = null;
48-
async function getParserModule() {
92+
let _parserModule: Awaited<typeof import('../domain/parser.js')> | null = null;
93+
async function getParserModule(): Promise<typeof import('../domain/parser.js')> {
4994
if (!_parserModule) _parserModule = await import('../domain/parser.js');
5095
return _parserModule;
5196
}
5297

5398
// ─── WASM pre-parse ─────────────────────────────────────────────────────
5499

55-
async function ensureWasmTreesIfNeeded(fileSymbols, opts, rootDir) {
100+
async function ensureWasmTreesIfNeeded(
101+
fileSymbols: Map<string, ExtractorOutput>,
102+
opts: AnalysisOpts,
103+
rootDir: string,
104+
): Promise<void> {
56105
const doComplexity = opts.complexity !== false;
57106
const doCfg = opts.cfg !== false;
58107
const doDataflow = opts.dataflow !== false;
@@ -91,34 +140,40 @@ async function ensureWasmTreesIfNeeded(fileSymbols, opts, rootDir) {
91140
try {
92141
const { ensureWasmTrees } = await getParserModule();
93142
await ensureWasmTrees(fileSymbols, rootDir);
94-
} catch (err) {
95-
debug(`ensureWasmTrees failed: ${err.message}`);
143+
} catch (err: unknown) {
144+
debug(`ensureWasmTrees failed: ${(err as Error).message}`);
96145
}
97146
}
98147
}
99148

100149
// ─── Per-file visitor setup ─────────────────────────────────────────────
101150

102-
function setupVisitors(db, relPath, symbols, langId, opts) {
151+
function setupVisitors(
152+
db: BetterSqlite3Database,
153+
relPath: string,
154+
symbols: ExtractorOutput,
155+
langId: string,
156+
opts: AnalysisOpts,
157+
): SetupResult {
103158
const ext = path.extname(relPath).toLowerCase();
104159
const defs = symbols.definitions || [];
105160
const doAst = opts.ast !== false;
106161
const doComplexity = opts.complexity !== false;
107162
const doCfg = opts.cfg !== false;
108163
const doDataflow = opts.dataflow !== false;
109164

110-
const visitors = [];
111-
const walkerOpts = {
112-
functionNodeTypes: new Set(),
113-
nestingNodeTypes: new Set(),
114-
getFunctionName: (_node) => null,
165+
const visitors: Visitor[] = [];
166+
const walkerOpts: WalkOptions = {
167+
functionNodeTypes: new Set<string>(),
168+
nestingNodeTypes: new Set<string>(),
169+
getFunctionName: (_node: TreeSitterNode) => null,
115170
};
116171

117172
// AST-store visitor
118-
let astVisitor = null;
173+
let astVisitor: Visitor | null = null;
119174
const astTypeMap = AST_TYPE_MAPS.get(langId);
120175
if (doAst && astTypeMap && WALK_EXTENSIONS.has(ext) && !symbols.astNodes?.length) {
121-
const nodeIdMap = new Map();
176+
const nodeIdMap = new Map<string, number>();
122177
for (const row of bulkNodeIdsByFile(db, relPath)) {
123178
nodeIdMap.set(`${row.name}|${row.kind}|${row.line}`, row.id);
124179
}
@@ -127,7 +182,7 @@ function setupVisitors(db, relPath, symbols, langId, opts) {
127182
}
128183

129184
// Complexity visitor (file-level mode)
130-
let complexityVisitor = null;
185+
let complexityVisitor: Visitor | null = null;
131186
const cRules = COMPLEXITY_RULES.get(langId);
132187
const hRules = HALSTEAD_RULES.get(langId);
133188
if (doComplexity && cRules) {
@@ -138,20 +193,21 @@ function setupVisitors(db, relPath, symbols, langId, opts) {
138193
complexityVisitor = createComplexityVisitor(cRules, hRules, { fileLevelWalk: true, langId });
139194
visitors.push(complexityVisitor);
140195

141-
for (const t of cRules.nestingNodes) walkerOpts.nestingNodeTypes.add(t);
196+
for (const t of cRules.nestingNodes) walkerOpts.nestingNodeTypes?.add(t);
142197

143198
const dfRules = DATAFLOW_RULES.get(langId);
144-
walkerOpts.getFunctionName = (node) => {
199+
walkerOpts.getFunctionName = (node: TreeSitterNode): string | null => {
145200
const nameNode = node.childForFieldName('name');
146201
if (nameNode) return nameNode.text;
147-
if (dfRules) return getFuncName(node, dfRules);
202+
// biome-ignore lint/suspicious/noExplicitAny: DataflowRulesConfig is structurally compatible at runtime
203+
if (dfRules) return getFuncName(node, dfRules as any);
148204
return null;
149205
};
150206
}
151207
}
152208

153209
// CFG visitor
154-
let cfgVisitor = null;
210+
let cfgVisitor: Visitor | null = null;
155211
const cfgRulesForLang = CFG_RULES.get(langId);
156212
if (doCfg && cfgRulesForLang && CFG_EXTENSIONS.has(ext)) {
157213
const needsWasmCfg = defs.some(
@@ -168,7 +224,7 @@ function setupVisitors(db, relPath, symbols, langId, opts) {
168224
}
169225

170226
// Dataflow visitor
171-
let dataflowVisitor = null;
227+
let dataflowVisitor: Visitor | null = null;
172228
const dfRules = DATAFLOW_RULES.get(langId);
173229
if (doDataflow && dfRules && DATAFLOW_EXTENSIONS.has(ext) && !symbols.dataflow) {
174230
dataflowVisitor = createDataflowVisitor(dfRules);
@@ -180,14 +236,15 @@ function setupVisitors(db, relPath, symbols, langId, opts) {
180236

181237
// ─── Result storage helpers ─────────────────────────────────────────────
182238

183-
function storeComplexityResults(results, defs, langId) {
184-
const complexityResults = results.complexity || [];
185-
const resultByLine = new Map();
239+
function storeComplexityResults(results: WalkResults, defs: Definition[], langId: string): void {
240+
// biome-ignore lint/complexity/useLiteralKeys: bracket notation required by noPropertyAccessFromIndexSignature
241+
const complexityResults = (results['complexity'] || []) as ComplexityFuncResult[];
242+
const resultByLine = new Map<number, ComplexityFuncResult[]>();
186243
for (const r of complexityResults) {
187244
if (r.funcNode) {
188245
const line = r.funcNode.startPosition.row + 1;
189246
if (!resultByLine.has(line)) resultByLine.set(line, []);
190-
resultByLine.get(line).push(r);
247+
resultByLine.get(line)?.push(r);
191248
}
192249
}
193250
for (const def of defs) {
@@ -221,14 +278,15 @@ function storeComplexityResults(results, defs, langId) {
221278
}
222279
}
223280

224-
function storeCfgResults(results, defs) {
225-
const cfgResults = results.cfg || [];
226-
const cfgByLine = new Map();
281+
function storeCfgResults(results: WalkResults, defs: Definition[]): void {
282+
// biome-ignore lint/complexity/useLiteralKeys: bracket notation required by noPropertyAccessFromIndexSignature
283+
const cfgResults = (results['cfg'] || []) as CfgFuncResult[];
284+
const cfgByLine = new Map<number, CfgFuncResult[]>();
227285
for (const r of cfgResults) {
228286
if (r.funcNode) {
229287
const line = r.funcNode.startPosition.row + 1;
230288
if (!cfgByLine.has(line)) cfgByLine.set(line, []);
231-
cfgByLine.get(line).push(r);
289+
cfgByLine.get(line)?.push(r);
232290
}
233291
}
234292
for (const def of defs) {
@@ -254,7 +312,7 @@ function storeCfgResults(results, defs) {
254312
def.complexity.cyclomatic = cfgResult.cyclomatic;
255313
const { loc, halstead } = def.complexity;
256314
const volume = halstead ? halstead.volume : 0;
257-
const commentRatio = loc?.loc > 0 ? loc.commentLines / loc.loc : 0;
315+
const commentRatio = loc && loc.loc > 0 ? loc.commentLines / loc.loc : 0;
258316
def.complexity.maintainabilityIndex = computeMaintainabilityIndex(
259317
volume,
260318
cfgResult.cyclomatic,
@@ -269,14 +327,22 @@ function storeCfgResults(results, defs) {
269327

270328
// ─── Build delegation ───────────────────────────────────────────────────
271329

272-
async function delegateToBuildFunctions(db, fileSymbols, rootDir, opts, engineOpts, timing) {
330+
async function delegateToBuildFunctions(
331+
db: BetterSqlite3Database,
332+
fileSymbols: Map<string, ExtractorOutput>,
333+
rootDir: string,
334+
opts: AnalysisOpts,
335+
engineOpts: EngineOpts | undefined,
336+
timing: AnalysisTiming,
337+
): Promise<void> {
273338
if (opts.ast !== false) {
274339
const t0 = performance.now();
275340
try {
276341
const { buildAstNodes } = await import('../features/ast.js');
277-
await buildAstNodes(db, fileSymbols, rootDir, engineOpts);
278-
} catch (err) {
279-
debug(`buildAstNodes failed: ${err.message}`);
342+
// biome-ignore lint/suspicious/noExplicitAny: ExtractorOutput is a superset of the local FileSymbols expected by buildAstNodes
343+
await buildAstNodes(db, fileSymbols as Map<string, any>, rootDir, engineOpts);
344+
} catch (err: unknown) {
345+
debug(`buildAstNodes failed: ${(err as Error).message}`);
280346
}
281347
timing.astMs = performance.now() - t0;
282348
}
@@ -285,9 +351,10 @@ async function delegateToBuildFunctions(db, fileSymbols, rootDir, opts, engineOp
285351
const t0 = performance.now();
286352
try {
287353
const { buildComplexityMetrics } = await import('../features/complexity.js');
288-
await buildComplexityMetrics(db, fileSymbols, rootDir, engineOpts);
289-
} catch (err) {
290-
debug(`buildComplexityMetrics failed: ${err.message}`);
354+
// biome-ignore lint/suspicious/noExplicitAny: ExtractorOutput is a superset of the local FileSymbols expected by buildComplexityMetrics
355+
await buildComplexityMetrics(db, fileSymbols as Map<string, any>, rootDir, engineOpts);
356+
} catch (err: unknown) {
357+
debug(`buildComplexityMetrics failed: ${(err as Error).message}`);
291358
}
292359
timing.complexityMs = performance.now() - t0;
293360
}
@@ -297,8 +364,8 @@ async function delegateToBuildFunctions(db, fileSymbols, rootDir, opts, engineOp
297364
try {
298365
const { buildCFGData } = await import('../features/cfg.js');
299366
await buildCFGData(db, fileSymbols, rootDir, engineOpts);
300-
} catch (err) {
301-
debug(`buildCFGData failed: ${err.message}`);
367+
} catch (err: unknown) {
368+
debug(`buildCFGData failed: ${(err as Error).message}`);
302369
}
303370
timing.cfgMs = performance.now() - t0;
304371
}
@@ -308,27 +375,23 @@ async function delegateToBuildFunctions(db, fileSymbols, rootDir, opts, engineOp
308375
try {
309376
const { buildDataflowEdges } = await import('../features/dataflow.js');
310377
await buildDataflowEdges(db, fileSymbols, rootDir, engineOpts);
311-
} catch (err) {
312-
debug(`buildDataflowEdges failed: ${err.message}`);
378+
} catch (err: unknown) {
379+
debug(`buildDataflowEdges failed: ${(err as Error).message}`);
313380
}
314381
timing.dataflowMs = performance.now() - t0;
315382
}
316383
}
317384

318385
// ─── Public API ──────────────────────────────────────────────────────────
319386

320-
/**
321-
* Run all enabled AST analyses in a coordinated pass.
322-
*
323-
* @param {object} db - open better-sqlite3 database (read-write)
324-
* @param {Map<string, object>} fileSymbols - Map<relPath, { definitions, calls, _tree, _langId, ... }>
325-
* @param {string} rootDir - absolute project root path
326-
* @param {object} opts - build options (ast, complexity, cfg, dataflow toggles)
327-
* @param {object} [engineOpts] - engine options
328-
* @returns {Promise<{ astMs: number, complexityMs: number, cfgMs: number, dataflowMs: number }>}
329-
*/
330-
export async function runAnalyses(db, fileSymbols, rootDir, opts, engineOpts) {
331-
const timing = { astMs: 0, complexityMs: 0, cfgMs: 0, dataflowMs: 0 };
387+
export async function runAnalyses(
388+
db: BetterSqlite3Database,
389+
fileSymbols: Map<string, ExtractorOutput>,
390+
rootDir: string,
391+
opts: AnalysisOpts,
392+
engineOpts?: EngineOpts,
393+
): Promise<AnalysisTiming> {
394+
const timing: AnalysisTiming = { astMs: 0, complexityMs: 0, cfgMs: 0, dataflowMs: 0 };
332395

333396
const doAst = opts.ast !== false;
334397
const doComplexity = opts.complexity !== false;
@@ -361,13 +424,14 @@ export async function runAnalyses(db, fileSymbols, rootDir, opts, engineOpts) {
361424
const defs = symbols.definitions || [];
362425

363426
if (astVisitor) {
364-
const astRows = results['ast-store'] || [];
427+
const astRows = (results['ast-store'] || []) as ASTNodeRow[];
365428
if (astRows.length > 0) symbols.astNodes = astRows;
366429
}
367430

368431
if (complexityVisitor) storeComplexityResults(results, defs, langId);
369432
if (cfgVisitor) storeCfgResults(results, defs);
370-
if (dataflowVisitor) symbols.dataflow = results.dataflow;
433+
// biome-ignore lint/complexity/useLiteralKeys: bracket notation required by noPropertyAccessFromIndexSignature
434+
if (dataflowVisitor) symbols.dataflow = results['dataflow'] as DataflowResult;
371435
}
372436

373437
timing._unifiedWalkMs = performance.now() - t0walk;

0 commit comments

Comments
 (0)