From 0a7fddfab2fff466c24a6479019efb7c74a58fb3 Mon Sep 17 00:00:00 2001 From: carlos-alm <127798846+carlos-alm@users.noreply.github.com> Date: Thu, 26 Mar 2026 02:25:06 -0600 Subject: [PATCH 1/3] fix(build): jsonc comment stripping in tsconfig and version-aware incremental rebuilds The // comment regex in loadPathAliases stripped // inside JSON string values (e.g. https:// URLs), causing "Bad control character" parse failures. Fix: match quoted strings first and preserve them, only stripping // outside strings. Add codegraph_version check to checkEngineSchemaMismatch so incremental builds auto-promote to full rebuild when the codegraph version changes, preventing stale graph data after upgrades. --- src/domain/graph/builder/helpers.ts | 2 +- src/domain/graph/builder/pipeline.ts | 15 +++++++++++++++ tests/unit/builder.test.ts | 22 ++++++++++++++++++++++ 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/src/domain/graph/builder/helpers.ts b/src/domain/graph/builder/helpers.ts index 15451b76..e56d4e9b 100644 --- a/src/domain/graph/builder/helpers.ts +++ b/src/domain/graph/builder/helpers.ts @@ -132,8 +132,8 @@ export function loadPathAliases(rootDir: string): PathAliases { try { const raw = fs .readFileSync(configPath, 'utf-8') - .replace(/\/\/.*$/gm, '') .replace(/\/\*[\s\S]*?\*\//g, '') + .replace(/("(?:[^"\\]|\\.)*")|\/\/.*$/gm, (_, str) => str ?? '') .replace(/,\s*([\]}])/g, '$1'); const config = JSON.parse(raw) as { compilerOptions?: { baseUrl?: string; paths?: Record }; diff --git a/src/domain/graph/builder/pipeline.ts b/src/domain/graph/builder/pipeline.ts index 7418163b..7b2e162e 100644 --- a/src/domain/graph/builder/pipeline.ts +++ b/src/domain/graph/builder/pipeline.ts @@ -4,6 +4,7 @@ * This is the heart of the builder refactor (ROADMAP 3.9): the monolithic buildGraph() * is decomposed into independently testable stages that communicate via PipelineContext. */ +import fs from 'node:fs'; import path from 'node:path'; import { performance } from 'node:perf_hooks'; import { closeDb, getBuildMeta, initSchema, MIGRATIONS, openDb } from '../../../db/index.js'; @@ -25,6 +26,13 @@ import { parseFiles } from './stages/parse-files.js'; import { resolveImports } from './stages/resolve-imports.js'; import { runAnalyses } from './stages/run-analyses.js'; +const __pipelineDir = path.dirname(new URL(import.meta.url).pathname.replace(/^\/([A-Z]:)/i, '$1')); +const CODEGRAPH_VERSION = ( + JSON.parse( + fs.readFileSync(path.join(__pipelineDir, '..', '..', '..', '..', 'package.json'), 'utf-8'), + ) as { version: string } +).version; + // ── Setup helpers ─────────────────────────────────────────────────────── function initializeEngine(ctx: PipelineContext): void { @@ -57,6 +65,13 @@ function checkEngineSchemaMismatch(ctx: PipelineContext): void { ); ctx.forceFullRebuild = true; } + const prevVersion = getBuildMeta(ctx.db, 'codegraph_version'); + if (prevVersion && prevVersion !== CODEGRAPH_VERSION) { + info( + `Codegraph version changed (${prevVersion} → ${CODEGRAPH_VERSION}), promoting to full rebuild.`, + ); + ctx.forceFullRebuild = true; + } } function loadAliases(ctx: PipelineContext): void { diff --git a/tests/unit/builder.test.ts b/tests/unit/builder.test.ts index 2d15c664..a7140e2a 100644 --- a/tests/unit/builder.test.ts +++ b/tests/unit/builder.test.ts @@ -200,6 +200,28 @@ describe('loadPathAliases', () => { fs.rmSync(dir, { recursive: true, force: true }); }); + it('preserves // inside string values (e.g. $schema URLs)', () => { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-jsonc-url-')); + fs.writeFileSync( + path.join(dir, 'tsconfig.json'), + `{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + // A comment + "compilerOptions": { + /* Language & Environment */ + "baseUrl": "./src", + "paths": { + "@/*": ["*"] + } + } +}`, + ); + const aliases = loadPathAliases(dir); + expect(aliases.baseUrl).toContain('src'); + expect(aliases.paths['@/*']).toBeDefined(); + fs.rmSync(dir, { recursive: true, force: true }); + }); + it('prefers tsconfig.json over jsconfig.json', () => { const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-both-')); fs.writeFileSync( From 23ef9d0b58a98f5e3eb3eab263c21c81e6351005 Mon Sep 17 00:00:00 2001 From: carlos-alm <127798846+carlos-alm@users.noreply.github.com> Date: Thu, 26 Mar 2026 02:50:48 -0600 Subject: [PATCH 2/3] refactor: extract CODEGRAPH_VERSION to shared/version.ts (#631) Eliminates duplicate version reading from package.json in pipeline.ts and finalize.ts. Both now import from a single shared module, removing the maintenance risk of mismatched relative paths. --- src/domain/graph/builder/pipeline.ts | 9 +-------- src/domain/graph/builder/stages/finalize.ts | 9 +-------- src/shared/version.ts | 10 ++++++++++ 3 files changed, 12 insertions(+), 16 deletions(-) create mode 100644 src/shared/version.ts diff --git a/src/domain/graph/builder/pipeline.ts b/src/domain/graph/builder/pipeline.ts index 7b2e162e..47f54ffc 100644 --- a/src/domain/graph/builder/pipeline.ts +++ b/src/domain/graph/builder/pipeline.ts @@ -4,12 +4,12 @@ * This is the heart of the builder refactor (ROADMAP 3.9): the monolithic buildGraph() * is decomposed into independently testable stages that communicate via PipelineContext. */ -import fs from 'node:fs'; import path from 'node:path'; import { performance } from 'node:perf_hooks'; import { closeDb, getBuildMeta, initSchema, MIGRATIONS, openDb } from '../../../db/index.js'; import { detectWorkspaces, loadConfig } from '../../../infrastructure/config.js'; import { info, warn } from '../../../infrastructure/logger.js'; +import { CODEGRAPH_VERSION } from '../../../shared/version.js'; import type { BuildGraphOpts, BuildResult } from '../../../types.js'; import { getActiveEngine } from '../../parser.js'; import { setWorkspaces } from '../resolve.js'; @@ -26,13 +26,6 @@ import { parseFiles } from './stages/parse-files.js'; import { resolveImports } from './stages/resolve-imports.js'; import { runAnalyses } from './stages/run-analyses.js'; -const __pipelineDir = path.dirname(new URL(import.meta.url).pathname.replace(/^\/([A-Z]:)/i, '$1')); -const CODEGRAPH_VERSION = ( - JSON.parse( - fs.readFileSync(path.join(__pipelineDir, '..', '..', '..', '..', 'package.json'), 'utf-8'), - ) as { version: string } -).version; - // ── Setup helpers ─────────────────────────────────────────────────────── function initializeEngine(ctx: PipelineContext): void { diff --git a/src/domain/graph/builder/stages/finalize.ts b/src/domain/graph/builder/stages/finalize.ts index f90b8b0a..3badf394 100644 --- a/src/domain/graph/builder/stages/finalize.ts +++ b/src/domain/graph/builder/stages/finalize.ts @@ -3,21 +3,14 @@ * * WASM cleanup, stats logging, drift detection, build metadata, registry, journal. */ -import fs from 'node:fs'; import path from 'node:path'; import { performance } from 'node:perf_hooks'; import { closeDb, getBuildMeta, setBuildMeta } from '../../../../db/index.js'; import { debug, info, warn } from '../../../../infrastructure/logger.js'; +import { CODEGRAPH_VERSION } from '../../../../shared/version.js'; import { writeJournalHeader } from '../../journal.js'; import type { PipelineContext } from '../context.js'; -const __builderDir = path.dirname(new URL(import.meta.url).pathname.replace(/^\/([A-Z]:)/i, '$1')); -const CODEGRAPH_VERSION = ( - JSON.parse( - fs.readFileSync(path.join(__builderDir, '..', '..', '..', '..', '..', 'package.json'), 'utf-8'), - ) as { version: string } -).version; - export async function finalize(ctx: PipelineContext): Promise { const { db, allSymbols, rootDir, isFullBuild, hasEmbeddings, config, opts, schemaVersion } = ctx; diff --git a/src/shared/version.ts b/src/shared/version.ts new file mode 100644 index 00000000..fe6f0a69 --- /dev/null +++ b/src/shared/version.ts @@ -0,0 +1,10 @@ +import fs from 'node:fs'; +import path from 'node:path'; + +const __sharedDir = path.dirname(new URL(import.meta.url).pathname.replace(/^\/([A-Z]:)/i, '$1')); + +export const CODEGRAPH_VERSION: string = ( + JSON.parse(fs.readFileSync(path.join(__sharedDir, '..', '..', 'package.json'), 'utf-8')) as { + version: string; + } +).version; From d4a67568ac36bc37c46073a371db1a367aec2313 Mon Sep 17 00:00:00 2001 From: carlos-alm <127798846+carlos-alm@users.noreply.github.com> Date: Thu, 26 Mar 2026 02:51:14 -0600 Subject: [PATCH 3/3] fix: combine JSONC comment stripping into single string-aware regex (#631) Merges block comment, line comment, and string preservation into one regex pass so block comments inside string values are also protected. --- src/domain/graph/builder/helpers.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/domain/graph/builder/helpers.ts b/src/domain/graph/builder/helpers.ts index e56d4e9b..3d1be287 100644 --- a/src/domain/graph/builder/helpers.ts +++ b/src/domain/graph/builder/helpers.ts @@ -132,8 +132,7 @@ export function loadPathAliases(rootDir: string): PathAliases { try { const raw = fs .readFileSync(configPath, 'utf-8') - .replace(/\/\*[\s\S]*?\*\//g, '') - .replace(/("(?:[^"\\]|\\.)*")|\/\/.*$/gm, (_, str) => str ?? '') + .replace(/("(?:[^"\\]|\\.)*")|\/\*[\s\S]*?\*\/|\/\/.*$/gm, (_, str) => str ?? '') .replace(/,\s*([\]}])/g, '$1'); const config = JSON.parse(raw) as { compilerOptions?: { baseUrl?: string; paths?: Record };