Skip to content

fix(native): add post-native pass timings to result.phases#1491

Open
carlos-alm wants to merge 2 commits into
fix/cha-incremental-scope-1441from
fix/native-phase-timings-1434
Open

fix(native): add post-native pass timings to result.phases#1491
carlos-alm wants to merge 2 commits into
fix/cha-incremental-scope-1441from
fix/native-phase-timings-1434

Conversation

@carlos-alm

Copy link
Copy Markdown
Contributor

Times each post-native pass in tryNativeOrchestrator and exposes them in result.phases:

  • gapDetectMs — dropped-language gap detection + WASM backfill
  • chaMs — CHA expansion post-pass (interface dispatch → concrete implementations)
  • thisDispatchMs — this/super dispatch WASM re-parse post-pass
  • reclassifyMs — scoped role re-classification after edge-writing passes
  • techniqueBackfillMs — technique-column UPDATE on native-written edges

Previously only thisDispatchMs was reported, causing wall-clock vs phaseSum to diverge by 1.1s+ on 1-file rebuilds — making benchmark regressions undiagnosable from committed history (as in the v3.12.0 publish-gate failure).

Also fixes a pre-existing biome format issue on the callToMethods chunked query result cast (long inline type annotation on line 580 of the original).

Updates update-incremental-report.ts to render the new phases in a collapsible <details> block under each engine's 1-file rebuild section, so historical benchmark reports show full phase breakdowns.

Closes #1434

Times each JS post-pass in tryNativeOrchestrator and exposes the
measurements in BuildResult.phases:

- gapDetectMs  — dropped-language gap detection + backfill
- chaMs        — CHA expansion (interface dispatch)
- thisDispatchMs — this/super dispatch WASM re-parse (was already
                   tracked but now properly named alongside the rest)
- reclassifyMs — scoped role re-classification after edge insertion
- techniqueBackfillMs — technique-column UPDATE on native-written edges

Previously only thisDispatchMs was reported, causing wall-clock vs
phaseSum to diverge by 1.1s+ on 1-file rebuilds and making benchmark
regressions undiagnosable from committed history.

Updates update-incremental-report.ts to render the new phases in a
collapsible details block under each engine's 1-file rebuild section.

Closes #1434
@claude

claude Bot commented Jun 13, 2026

Copy link
Copy Markdown

Claude finished @carlos-alm's task —— View job


PR Review: fix(native): add post-native pass timings to result.phases

  • Gather context and understand request
  • Read changed files
  • Phase 1: Root Cause Analysis
  • Phase 2: Configuration Change Detection
  • Phase 3: Test Integrity
  • Phase 4: Code Quality
  • Phase 5: Backlog Compliance

Phase 1: Root Cause Analysis

Problem Being Solved: The native orchestrator was only reporting thisDispatchMs in result.phases. Four other post-native JS passes (gapDetectMs, chaMs, reclassifyMs, techniqueBackfillMs) were running but their time was silently absorbed into wall-clock totals, making wall-clock vs phaseSum diverge by 1.1s+ on 1-file rebuilds. This made benchmark regressions (like the v3.12.0 publish-gate failure) undiagnosable from history.

Why This Approach: The fix is structurally correct — instead of ad-hoc performance.now() calls for just one pass, it introduces a typed PostPassTimings interface (native-orchestrator.ts:940) and measures all five post-passes uniformly, then plumbs them through formatNativeTimingResult (line 949) into BuildResult.phases. This is the right place for this: the timings are produced in tryNativeOrchestrator, so capturing them there and surfacing in the result is clean and non-invasive.

Risk Assessment: Very low. The change is purely additive — it adds new fields to BuildResult.phases (all optional/new) and new timing captures around existing function calls. No logic is altered.


Phase 2: Configuration Change Detection

No linting rules, TypeScript flags, test timeouts, or build config are changed. The biome format fix (long type annotation on line ~580 split to multi-line) is a cosmetic cleanup, not a rule relaxation. No concerns.


Phase 3: Test Integrity

No test files are modified. The PR description mentions this closes #1434 (a bug about missing phase timings) but adds no new tests covering the timing reporting. This is a mild gap — there's no regression guard ensuring the new phases don't silently disappear again. However, given that timing fields are internal diagnostics rather than correctness guarantees, this is acceptable for a low-risk observability change.


Phase 4: Code Quality

src/types.ts (lines 1261–1288): The new fields are correctly added as optional to BuildResult.phases. Existing optional fields (protoMethodsMs, chaMs, thisDispatchMs) already had ?: — the PR now adds gapDetectMs, reclassifyMs, techniqueBackfillMs alongside them. The JSDoc comments are accurate and match the implementation.

src/domain/graph/builder/stages/native-orchestrator.ts:

  • PostPassTimings interface (line 940) is a clean local type. No complaint.
  • formatNativeTimingResult (line 949) receives it and spreads correctly into the phases record. The +(...).toFixed(1) rounding pattern is consistent with existing fields. ✓
  • Timing capture for gapDetectMs (line 1538–1549): performance.now() wraps detectDroppedLanguageGap + backfillNativeDroppedFiles, capturing wall-clock of both. This is the correct scope — both are part of the same logical phase.
  • chaMs (line 1558–1564): wraps runPostNativeCha — correct.
  • thisDispatchMs (line 1568–1577): was already captured inside runPostNativeThisDispatch as Date.now() diff, returned as elapsedMs. The PR just renames the destructured variable from elapsedMs: thisDispatchMs — clean. Note the slight inconsistency: thisDispatchMs uses Date.now() (ms precision) while the new phases use performance.now() (sub-ms). This is pre-existing and not introduced here.
  • reclassifyMs (line 1587–1613): The early-return path when chaEdgeCount === 0 && thisDispatchTargetIds.size === 0 correctly keeps reclassifyMs = 0. ✓
  • techniqueBackfillMs (line 1620–1622): wraps the backfill call cleanly. ✓
  • Early-exit paths (lines 1509–1517): When DB reopen fails, formatNativeTimingResult is called with all zeros for PostPassTimings. This is the correct defensive behavior — returning a partial result with accurate native phases and zero post-pass times is better than panicking.

One minor concerngapDetectMs includes both detection and backfill. The label in the report renderer ('gap detect + backfill') correctly reflects this, but the BuildResult JSDoc says "dropped-language gap detection + backfill" which matches. No issue.

scripts/update-incremental-report.ts (lines 188–199): The collapsible <details> block is extended with a nativePostPhases array rendered only when engineKey === 'native'. The new phases are listed in the same order they execute. Backward compatibility is maintained because each phase is guarded by if (ph[key] != null) — old benchmark JSON entries without these fields just skip those rows. ✓

Biome format fix (lines 580–584): The chunkRows result cast was a long inline type annotation. Moving the as Array<{...}> to a trailing separate line is a valid format improvement.


Phase 5: Backlog Compliance

  • Zero-dep: ✓ — No new dependencies added. Uses performance from Node's built-in node:perf_hooks which was already imported.
  • Foundation-aligned: ✓ — Purely local observability instrumentation. No cloud, no external services, no agent behaviors violated.
  • Problem-fit: 3/5 — Improves debuggability of benchmark regressions (directly enabling diagnosis of the v3.12.0 gate failure and future native build regressions). Doesn't directly reduce token waste for AI agents navigating codebases, but is a necessary observability improvement for the build pipeline.
  • Breaking: No — All new fields in BuildResult.phases are additive and optional. Existing consumers ignoring unknown fields are unaffected. The WASM path doesn't set these fields, so there's no WASM contract change.
  • Tier: 1 (zero-dep ✓ + foundation-aligned ✓)

Critical Concerns

None.

One minor nit (non-blocking): runPostNativeThisDispatch captures timing via Date.now() (integer ms) while all other post-pass timings now use performance.now() (floating-point sub-ms). This asymmetry already existed before this PR, but since the PR is now establishing PostPassTimings as the canonical shape, it would be cleaner to migrate thisDispatchMs to performance.now() as well. Not a blocker — worth a follow-up issue.


Final Assessment

## Root Cause Analysis
**Problem Being Solved**: 4 of 5 post-native JS passes had no timing instrumentation,
  causing wall-clock vs phaseSum to diverge by 1.1s+ and making benchmark regressions
  undiagnosable from history.
**Why This Approach**: Introduces a typed PostPassTimings struct, measures all 5 passes
  uniformly, and surfaces them in BuildResult.phases — the correct architectural location.
**Risk Assessment**: Very low. Purely additive, no logic changes.

## Backlog Compliance
- Zero-dep: ✓
- Foundation-aligned: ✓
- Problem-fit: 3/5 — build observability improvement, not directly AI-navigation-facing
- Breaking: No
- Tier: 1

## Critical Concerns
None.

## Final Recommendation
- Rating: ⭐⭐⭐⭐ (4/5)
- Action: APPROVE
- Reasoning: Clean, well-scoped fix with correct architecture. All timing captures are
  placed at the right call boundaries, the PostPassTimings interface is appropriately typed,
  the benchmark renderer is backward-compatible, and no rules are silenced or tests weakened.
  The minor Date.now() vs performance.now() inconsistency for thisDispatchMs is pre-existing
  and non-blocking.

@greptile-apps

greptile-apps Bot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds wall-clock timing for all five JS post-passes in tryNativeOrchestrator (gapDetectMs, chaMs, thisDispatchMs, reclassifyMs, techniqueBackfillMs) and exposes them in BuildResult.phases, closing a 1.1 s+ gap between wall-clock and phaseSum that was making benchmark regressions undiagnosable. It also fixes the pre-existing Date.now()performance.now() inconsistency in runPostNativeThisDispatch.

  • native-orchestrator.ts: Wraps each post-pass in a performance.now() bracket; introduces PostPassTimings interface and threads it through formatNativeTimingResult; early-return path correctly zeroes all five timings.
  • types.ts: Extends BuildResult.phases with five optional native-only fields. Declaration order is reversed relative to execution order (see inline comment).
  • update-incremental-report.ts: Adds a <details> phase-breakdown block to the "Latest results" section, gated on oneFilePhases being present (already emitted by the benchmark runner; older entries degrade gracefully).

Confidence Score: 5/5

Safe to merge. All five new timers use performance.now() consistently, both the normal and early-return paths supply every required field, and the report renderer degrades gracefully for older benchmark entries lacking phase data.

The change is additive: new timing brackets around existing logic, a widened function signature, and five optional type fields. No existing behavior is altered and both call sites of formatNativeTimingResult have been updated.

No files require special attention. src/types.ts has a minor field ordering inconsistency but no functional impact.

Important Files Changed

Filename Overview
src/domain/graph/builder/stages/native-orchestrator.ts Adds performance.now() timing brackets around gapDetect, CHA, reclassify, and techniqueBackfill post-passes; replaces Date.now() with performance.now() in runPostNativeThisDispatch; introduces PostPassTimings interface and plumbs it through formatNativeTimingResult. Logic is correct in both the normal and early-return paths.
src/types.ts Adds five optional phase fields to BuildResult.phases. Declaration order (chaMs -> thisDispatchMs -> gapDetectMs) does not match execution order (gapDetectMs -> chaMs -> thisDispatchMs), which is mildly confusing for readers cross-referencing the type against the orchestrator.
scripts/update-incremental-report.ts Adds a collapsible details phase-breakdown block to the Latest results section, gated on e.oneFilePhases being present. The benchmark runner already emits oneFilePhases, older entries degrade gracefully, and table rendering in GitHub Markdown is correct.

Sequence Diagram

sequenceDiagram
    participant O as tryNativeOrchestrator
    participant GD as detectDroppedLanguageGap / backfill
    participant CHA as runPostNativeCha
    participant TD as runPostNativeThisDispatch
    participant RC as classifyNodeRoles
    participant TB as backfillEdgeTechniques

    O->>GD: "gapDetectStart = performance.now()"
    GD-->>O: gap
    O->>O: conditional backfillNativeDroppedFiles
    O->>O: "gapDetectMs = performance.now() - gapDetectStart"

    O->>CHA: "chaStart = performance.now()"
    CHA-->>O: "{newEdgeCount, affectedFiles}"
    O->>O: "chaMs = performance.now() - chaStart"

    O->>TD: (performance.now() timer inside)
    TD-->>O: "{elapsedMs: thisDispatchMs, targetIds, affectedFiles}"

    O->>RC: "reclassifyStart = performance.now() (if edges added)"
    RC-->>O: roles updated
    O->>O: "reclassifyMs = performance.now() - reclassifyStart"

    O->>TB: "techniqueBackfillStart = performance.now()"
    TB-->>O: done
    O->>O: "techniqueBackfillMs = performance.now() - techniqueBackfillStart"

    O->>O: "formatNativeTimingResult({gapDetectMs,chaMs,thisDispatchMs,reclassifyMs,techniqueBackfillMs})"
Loading

Reviews (3): Last reviewed commit: "fix(native): remove orphaned protoMethod..." | Re-trigger Greptile

Comment thread src/types.ts Outdated
Comment on lines +1271 to +1272
/** Wall-clock time for the prototype-method post-pass (native path only). */
protoMethodsMs?: number;

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 Orphaned type field with no producer

protoMethodsMs is declared in BuildResult.phases but is never set by formatNativeTimingResult, never included in the PostPassTimings interface in native-orchestrator.ts, and never rendered in update-incremental-report.ts. It is also absent from the PR description's list of new phases. Because it is optional this causes no runtime errors, but it will appear in IDE autocomplete as a usable field that always reads undefined, which could silently mislead future callers trying to sum phase times.

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 — removed the orphaned protoMethodsMs field from BuildResult.phases in types.ts. The field was never populated by the orchestrator, report script, or PostPassTimings interface. Commit 45e9af7.

@@ -933,12 +937,20 @@ async function runPostNativeThisDispatch(
return { elapsedMs: Date.now() - t0, targetIds, affectedFiles };

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 Mixed timer APIs across phase measurements

runPostNativeThisDispatch measures its elapsed time with Date.now() (whole-millisecond integer precision), while all four new timings added in this PR — gapDetectMs, chaMs, reclassifyMs, and techniqueBackfillMs — use performance.now() (sub-millisecond float precision). The values all flow into formatNativeTimingResult and are rounded with .toFixed(1), but thisDispatchMs will always land on a .0 boundary while the others can have fractional parts, making the phase sum slightly inconsistent. If these timings are ever compared programmatically (e.g. to compute an unaccounted residual), the difference in resolution can produce artificial noise.

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 — migrated runPostNativeThisDispatch from Date.now() (integer ms) to performance.now() (sub-ms float) in both the timer start and elapsed calculation. All five post-pass timings in PostPassTimings now use the same timer API and resolution. Commit 45e9af7.

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 — migrated runPostNativeThisDispatch from Date.now() (integer ms) to performance.now() (sub-ms float) so all five post-pass timings use the same timer API. The fix landed in PR #1494 (commit 45e9af7) which is stacked on top of this one.

@github-actions

github-actions Bot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

Codegraph Impact Analysis

10 functions changed6 callers affected across 6 files

  • runPostNativeCha in src/domain/graph/builder/stages/native-orchestrator.ts:420 (4 transitive callers)
  • runPostNativeThisDispatch in src/domain/graph/builder/stages/native-orchestrator.ts:699 (4 transitive callers)
  • PostPassTimings.gapDetectMs in src/domain/graph/builder/stages/native-orchestrator.ts:941 (0 transitive callers)
  • PostPassTimings.chaMs in src/domain/graph/builder/stages/native-orchestrator.ts:942 (0 transitive callers)
  • PostPassTimings.thisDispatchMs in src/domain/graph/builder/stages/native-orchestrator.ts:943 (0 transitive callers)
  • PostPassTimings.reclassifyMs in src/domain/graph/builder/stages/native-orchestrator.ts:944 (0 transitive callers)
  • PostPassTimings.techniqueBackfillMs in src/domain/graph/builder/stages/native-orchestrator.ts:945 (0 transitive callers)
  • formatNativeTimingResult in src/domain/graph/builder/stages/native-orchestrator.ts:949 (4 transitive callers)
  • tryNativeOrchestrator in src/domain/graph/builder/stages/native-orchestrator.ts:1370 (5 transitive callers)
  • BuildResult.phases in src/types.ts:1261 (0 transitive callers)

…ision

Remove the `protoMethodsMs` optional field from `BuildResult.phases` that
was declared in types.ts but never populated by the orchestrator, report
script, or PostPassTimings interface — preventing it from silently appearing
as an always-undefined field in IDE autocomplete.

Migrate `runPostNativeThisDispatch` from `Date.now()` (integer ms) to
`performance.now()` (sub-ms float) so all five post-pass timings in
`PostPassTimings` use the same timer API and resolution.
@carlos-alm

Copy link
Copy Markdown
Contributor Author

@greptileai

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant