From dbf3a6f5587bd2de77e4c3db7d84de386fc41fbd Mon Sep 17 00:00:00 2001 From: carlos-alm <127798846+carlos-alm@users.noreply.github.com> Date: Wed, 8 Apr 2026 23:10:28 -0600 Subject: [PATCH 1/3] fix(native): strip pre-release suffix in semverCompare `semverCompare('3.9.3-dev.6', '3.9.1')` returned -1 (less than) because `Number('3-dev')` is NaN, which the `|| 0` fallback turned into 0, making the comparison `0 < 1`. This caused `shouldSkipNativeOrchestrator` to flag all pre-release builds as "buggy", disabling the native orchestrator fast path introduced in #897. Strip `-` before splitting on `.` so the numeric comparison sees `3.9.3` vs `3.9.1` correctly. --- src/infrastructure/update-check.ts | 6 +++--- tests/unit/update-check.test.ts | 9 +++++++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/infrastructure/update-check.ts b/src/infrastructure/update-check.ts index b0199892..d8438088 100644 --- a/src/infrastructure/update-check.ts +++ b/src/infrastructure/update-check.ts @@ -18,11 +18,11 @@ interface UpdateCache { /** * Minimal semver comparison. Returns -1, 0, or 1. - * Only handles numeric x.y.z (no pre-release tags). + * Strips pre-release suffixes (e.g. "3.9.3-dev.6" → "3.9.3") before comparing. */ export function semverCompare(a: string, b: string): -1 | 0 | 1 { - const pa = a.split('.').map(Number); - const pb = b.split('.').map(Number); + const pa = a.replace(/-.*$/, '').split('.').map(Number); + const pb = b.replace(/-.*$/, '').split('.').map(Number); for (let i = 0; i < 3; i++) { const na = pa[i] || 0; const nb = pb[i] || 0; diff --git a/tests/unit/update-check.test.ts b/tests/unit/update-check.test.ts index 46621d8b..abb35aad 100644 --- a/tests/unit/update-check.test.ts +++ b/tests/unit/update-check.test.ts @@ -51,6 +51,15 @@ describe('semverCompare', () => { it('major takes priority over minor and patch', () => { expect(semverCompare('1.9.9', '2.0.0')).toBe(-1); }); + + it('strips pre-release suffixes before comparing', () => { + // 3.9.3-dev.6 should be treated as 3.9.3, which is > 3.9.1 + expect(semverCompare('3.9.3-dev.6', '3.9.1')).toBe(1); + expect(semverCompare('3.9.3-dev.6', '3.9.3')).toBe(0); + expect(semverCompare('3.9.3-dev.6', '3.9.4')).toBe(-1); + // Both sides with pre-release + expect(semverCompare('2.0.0-beta.1', '1.9.9-alpha.3')).toBe(1); + }); }); // ─── checkForUpdates ──────────────────────────────────────────────── From d99013f9d1229f0e825ff5f15fd39c8a1d42a8b5 Mon Sep 17 00:00:00 2001 From: carlos-alm <127798846+carlos-alm@users.noreply.github.com> Date: Thu, 9 Apr 2026 00:50:13 -0600 Subject: [PATCH 2/3] perf(query): short-circuit diffImpact when no functions affected MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Skip co-change, ownership, and boundary lookups when findAffectedFunctions returns empty — all callers return early on this case anyway. Also pass the already-loaded config to checkBoundaryViolations to avoid a redundant loadConfig call. Saves ~2-3ms of fixed overhead per diffImpact invocation when the diff touches no function bodies (the common case for comment/import/type-only changes and the benchmark probe). Closes #904 --- src/domain/analysis/diff-impact.ts | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/src/domain/analysis/diff-impact.ts b/src/domain/analysis/diff-impact.ts index f1e2fc16..e92121f8 100644 --- a/src/domain/analysis/diff-impact.ts +++ b/src/domain/analysis/diff-impact.ts @@ -307,6 +307,32 @@ export function diffImpactData( } const affectedFunctions = findAffectedFunctions(db, changedRanges, noTests); + + // Short-circuit: when no function-level changes detected, skip expensive + // lookups (BFS, co-change, ownership, boundary checks). All callers + // (CLI, MCP, benchmark) return early on empty affectedFunctions. + if (affectedFunctions.length === 0) { + const base = { + changedFiles: changedRanges.size, + newFiles: [...newFiles], + affectedFunctions: [] as unknown[], + affectedFiles: [] as string[], + historicallyCoupled: [] as unknown[], + ownership: null, + boundaryViolations: [] as unknown[], + boundaryViolationCount: 0, + summary: { + functionsChanged: 0, + callersAffected: 0, + filesAffected: 0, + historicallyCoupledCount: 0, + ownersAffected: 0, + boundaryViolationCount: 0, + }, + }; + return paginateResult(base, 'affectedFunctions', { limit: opts.limit, offset: opts.offset }); + } + const includeImplementors = opts.includeImplementors !== false; const { functionResults, allAffected } = buildFunctionImpactResults( db, @@ -325,7 +351,7 @@ export function diffImpactData( db, changedRanges, noTests, - opts, + { ...opts, config }, repoRoot, ); From 14bf7c47bb53c93a02582941f26272c1140b5389 Mon Sep 17 00:00:00 2001 From: carlos-alm <127798846+carlos-alm@users.noreply.github.com> Date: Thu, 9 Apr 2026 01:28:35 -0600 Subject: [PATCH 3/3] fix(query): preserve boundary checks in diffImpact short-circuit (#905) The short-circuit path was hardcoding boundaryViolations: [] when no functions were affected. Since boundary checks are file-scoped (not function-scoped), import or type-alias changes can still produce real violations. Preserve the check and align the return shape (summary: null) with the two existing early-exit paths. --- src/domain/analysis/diff-impact.ts | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/domain/analysis/diff-impact.ts b/src/domain/analysis/diff-impact.ts index e92121f8..2d263edb 100644 --- a/src/domain/analysis/diff-impact.ts +++ b/src/domain/analysis/diff-impact.ts @@ -309,9 +309,17 @@ export function diffImpactData( const affectedFunctions = findAffectedFunctions(db, changedRanges, noTests); // Short-circuit: when no function-level changes detected, skip expensive - // lookups (BFS, co-change, ownership, boundary checks). All callers - // (CLI, MCP, benchmark) return early on empty affectedFunctions. + // lookups (BFS, co-change, ownership). Boundary checks are preserved + // because they are file-scoped and can surface real violations even when + // no function bodies were touched (e.g. import or type-alias changes). if (affectedFunctions.length === 0) { + const { boundaryViolations, boundaryViolationCount } = checkBoundaryViolations( + db, + changedRanges, + noTests, + { ...opts, config }, + repoRoot, + ); const base = { changedFiles: changedRanges.size, newFiles: [...newFiles], @@ -319,16 +327,9 @@ export function diffImpactData( affectedFiles: [] as string[], historicallyCoupled: [] as unknown[], ownership: null, - boundaryViolations: [] as unknown[], - boundaryViolationCount: 0, - summary: { - functionsChanged: 0, - callersAffected: 0, - filesAffected: 0, - historicallyCoupledCount: 0, - ownersAffected: 0, - boundaryViolationCount: 0, - }, + boundaryViolations, + boundaryViolationCount, + summary: null as null, }; return paginateResult(base, 'affectedFunctions', { limit: opts.limit, offset: opts.offset }); }