Skip to content
Open
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
54 changes: 26 additions & 28 deletions src/domain/graph/builder/stages/native-orchestrator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1524,26 +1524,16 @@ export async function tryNativeOrchestrator(
// stale native binaries). WASM handles those — backfill via WASM so both
// engines process the same file set (#967).
//
// Detect the gap once (fs walk + 2 DB queries, ~20–30ms) and use it for
// both gating and the backfill itself. On dirty incrementals/full builds
// the orchestrator signals trigger backfill, so the walk happens once
// (instead of redundantly inside backfill). On quiet incrementals we
// still pay the walk so we can detect brand-new files in dropped-language
// extensions — a gap that the orchestrator's `detect_removed_files`
// filter (#1070) leaves open (#1083, #1091). The pre-check is cheap
// because the expensive part (WASM re-parse of the missing set) is
// gated below.
const removedCount = result.removedCount ?? 0;
const changedCount = result.changedCount ?? 0;
// Detect the gap once (fs walk + 2 DB queries) and use it for both gating
// and the backfill itself. On quiet incrementals we still pay the walk so
// we can detect brand-new files in dropped-language extensions — a gap that
// the orchestrator's `detect_removed_files` filter (#1070) leaves open
// (#1083, #1091). The pre-check is cheap because the expensive part (WASM
// re-parse of the missing set) is gated below.
const gapDetectStart = performance.now();
const gap = detectDroppedLanguageGap(ctx);
if (
result.isFullBuild ||
removedCount > 0 ||
changedCount > 0 ||
gap.missingAbs.length > 0 ||
gap.staleRel.length > 0
) {
const backfillHappened = gap.missingAbs.length > 0 || gap.staleRel.length > 0;
if (backfillHappened) {
await backfillNativeDroppedFiles(ctx, gap);
}
const gapDetectMs = performance.now() - gapDetectStart;
Comment on lines 1533 to 1539

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 backfillHappened re-evaluates the same boolean expression that already guards the await on the line above. If the condition ever changes (e.g. a third field is added to gap), both sites must be updated in sync. Hoist the variable before the if so the condition is written exactly once.

Suggested change
const gapDetectStart = performance.now();
const gap = detectDroppedLanguageGap(ctx);
if (
result.isFullBuild ||
removedCount > 0 ||
changedCount > 0 ||
gap.missingAbs.length > 0 ||
gap.staleRel.length > 0
) {
if (gap.missingAbs.length > 0 || gap.staleRel.length > 0) {
await backfillNativeDroppedFiles(ctx, gap);
}
const backfillHappened = gap.missingAbs.length > 0 || gap.staleRel.length > 0;
const gapDetectMs = performance.now() - gapDetectStart;
const gapDetectStart = performance.now();
const gap = detectDroppedLanguageGap(ctx);
const backfillHappened = gap.missingAbs.length > 0 || gap.staleRel.length > 0;
if (backfillHappened) {
await backfillNativeDroppedFiles(ctx, gap);
}
const gapDetectMs = performance.now() - gapDetectStart;

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Claude Code

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — hoisted backfillHappened before the if so the boolean expression is written exactly once. The variable declaration now serves as the if guard directly.

Expand Down Expand Up @@ -1624,19 +1614,27 @@ export async function tryNativeOrchestrator(
// Re-count nodes/edges now that all edge-writing post-passes have run: the
// Rust orchestrator captured its counts before the JS post-passes added
// edges, so both its summary and build_meta under-report (#1452).
//
// Fast path: skip the COUNT(*) scan when no post-pass wrote any edges.
// COUNT(*) on large tables (50K+ edges) is non-trivial, especially via the
// NativeDbProxy napi-rs round-trip. When all post-passes were no-ops, the
// Rust orchestrator's counts are still accurate — no re-count needed.
let finalNodeCount = result.nodeCount ?? 0;
let finalEdgeCount = result.edgeCount ?? 0;
try {
const counts = (ctx.db as unknown as BetterSqlite3Database)
.prepare('SELECT (SELECT COUNT(*) FROM nodes) AS n, (SELECT COUNT(*) FROM edges) AS e')
.get() as { n: number; e: number };
if (counts.n !== finalNodeCount || counts.e !== finalEdgeCount) {
finalNodeCount = counts.n;
finalEdgeCount = counts.e;
setBuildMeta(ctx.db, { node_count: finalNodeCount, edge_count: finalEdgeCount });
const postPassWroteData = backfillHappened || chaEdgeCount > 0 || thisDispatchTargetIds.size > 0;
if (postPassWroteData) {
try {
const counts = (ctx.db as unknown as BetterSqlite3Database)
.prepare('SELECT (SELECT COUNT(*) FROM nodes) AS n, (SELECT COUNT(*) FROM edges) AS e')
.get() as { n: number; e: number };
if (counts.n !== finalNodeCount || counts.e !== finalEdgeCount) {
finalNodeCount = counts.n;
finalEdgeCount = counts.e;
setBuildMeta(ctx.db, { node_count: finalNodeCount, edge_count: finalEdgeCount });
}
} catch (err) {
debug(`Post-pass node/edge re-count failed: ${toErrorMessage(err)}`);
}
} catch (err) {
debug(`Post-pass node/edge re-count failed: ${toErrorMessage(err)}`);
}
info(
`Native build orchestrator completed: ${finalNodeCount} nodes, ${finalEdgeCount} edges, ${result.fileCount ?? 0} files`,
Expand Down
Loading