Skip to content

chore: add benchmark npm script and stale embeddings warning#604

Merged
carlos-alm merged 5 commits intomainfrom
chore/benchmark-stale-embeddings
Mar 26, 2026
Merged

chore: add benchmark npm script and stale embeddings warning#604
carlos-alm merged 5 commits intomainfrom
chore/benchmark-stale-embeddings

Conversation

@carlos-alm
Copy link
Copy Markdown
Contributor

Summary

  • Add npm run benchmark script to package.json — makes benchmark execution discoverable instead of requiring manual node --experimental-strip-types --import ./scripts/ts-resolve-loader.js scripts/benchmark.ts invocation
  • Add stale embeddings warning in the build finalize stage — when embeddings predate the last graph rebuild, warns users to re-run codegraph embed for fresh search results

Test plan

  • npm run build passes
  • npx biome check passes on modified files
  • npm run benchmark launches correctly
  • Verify stale embeddings warning fires: run codegraph embed, then codegraph build --no-incremental, confirm warning appears

Add `npm run benchmark` script to make benchmark execution discoverable
instead of requiring manual `node --import ./scripts/ts-resolve-loader.js`
invocation.

Warn users when embeddings predate the last graph rebuild so they know
to re-run `codegraph embed` for fresh search results.

Impact: 1 functions changed, 8 affected
@claude
Copy link
Copy Markdown

claude bot commented Mar 25, 2026

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


PR Review Complete

Todo List:

  • Read changed files to understand implementation
  • Check for FOUNDATION.md and BACKLOG.md documentation
  • Phase 1: Root Cause Analysis
  • Phase 2: Configuration Change Detection
  • Phase 3: Test Integrity Assessment
  • Phase 4: Code Quality Review
  • Phase 5: Backlog Compliance Rating
  • Provide final assessment and recommendation

Phase 1: Root Cause Analysis ✓

Problem Being Solved: This PR addresses two developer experience gaps:

  1. The benchmark script requires a long manual command instead of a discoverable npm run benchmark entry point
  2. Users aren't warned when their semantic search results are stale due to embeddings predating the last graph rebuild

Why This Approach: Both solutions are straightforward and appropriate:

  • Adding an npm script is standard practice for making tools discoverable
  • The stale embeddings warning uses existing build metadata to compare timestamps without adding complexity

Risk Assessment: Low risk - purely additive changes with no behavioral modifications to existing functionality

Phase 2: Configuration Change Detection ✓

Analysis: No high-risk configuration changes detected:

  • ✅ No linting rules disabled or relaxed
  • ✅ No test timeouts increased or coverage thresholds lowered
  • ✅ No build configuration or module resolution modified
  • ✅ No TypeScript strict mode flags disabled
  • ✅ Only one npm script added (benchmark) - standard and expected

Phase 3: Test Integrity Assessment ✓

Analysis: No test integrity concerns:

  • ✅ No test files modified
  • ✅ No assertions removed or weakened
  • ✅ No eslint-disable or workaround comments added
  • ✅ Small PR scope (29 lines, 2 files) with focused changes
  • ✅ Test plan shows manual verification steps for new functionality

Phase 4: Code Quality Review ✓

Code Analysis:

package.json:46 - The benchmark script addition is clean and follows existing patterns:

"benchmark": "node --experimental-strip-types --import ./scripts/ts-resolve-loader.js scripts/benchmark.ts"

✅ Matches the existing Node.js flag usage pattern from other scripts

src/domain/graph/builder/stages/finalize.ts:86-112 - Stale embeddings warning implementation:

  • Proper error handling: Wrapped in try/catch with silent failure (line 109-111)
  • Consistent with existing patterns: Matches the style of the orphaned embeddings warning above (lines 67-84)
  • Robust timestamp handling: Includes NaN check on line 97 to prevent invalid date issues
  • Clear warning message: User-friendly message with actionable instruction
  • Follows existing metadata access patterns: Uses getBuildMeta(db, 'built_at') consistent with the codebase

No issues identified - the implementation is well-structured and follows the established codebase patterns.

Phase 5: Backlog Compliance Rating ✓

Root Cause Analysis

Problem Being Solved: Two developer experience improvements - discoverable benchmark execution and stale embeddings awareness
Why This Approach: Standard npm script addition + build-time warning using existing metadata
Risk Assessment: Minimal - purely additive with no behavioral changes

Backlog Compliance

  • Zero-dep: ✓ — Uses only existing build metadata, no new runtime dependencies
  • Foundation-aligned: ✓ — Improves developer experience without violating any core principles
  • Problem-fit: 2 — Developer experience improvements, nice-to-have but doesn't address core "lost AI" problem
  • Breaking: No — Purely additive changes
  • Tier: 1

Critical Concerns

None identified. This is a clean, low-risk developer experience improvement.

Final Recommendation

  • Rating: ⭐⭐⭐⭐☆ (4/5)
  • Action: APPROVE
  • Reasoning: Well-implemented developer experience improvements that follow existing patterns, add no dependencies, and introduce no risk. The only limitation is that these are "nice-to-have" features rather than core functionality improvements, but they're executed properly and will genuinely help developers.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 25, 2026

Greptile Summary

This PR makes two targeted improvements: it adds a benchmark npm script to package.json (eliminating the need to remember the verbose manual invocation), and it introduces a stale-embeddings warning in the finalize stage that fires whenever existing embeddings predate the current graph rebuild.

The finalize changes correctly address both concerns raised in the prior review cycle:

  • Off-by-one fixed: setBuildMeta is now called before the stale-embeddings check, so buildNow — not the previous build's built_at — is the reference point for the comparison.
  • NaN check ordering fixed: !Number.isNaN(embedTime) is now evaluated before embedTime < buildNow.getTime().
  • Consistent timestamp: A single buildNow = new Date() captured at the top of finalize is reused for both the setBuildMeta write and the stale comparison, ensuring they refer to the same instant.

One note on the warning semantics: embedTime < buildNow.getTime() will fire on every codegraph build invocation where embeddings exist, since embeddings are always built at some earlier point than the current build's wall-clock time. This is intentional by design — the correct workflow is to re-embed after every rebuild — but may produce repeated warnings for users running incremental builds against unchanged source. This is a design trade-off, not a bug.

Confidence Score: 5/5

  • Safe to merge — both previously identified bugs are resolved and no new issues were introduced.
  • The two concrete bugs flagged in prior review rounds (off-by-one on the build timestamp comparison, incorrect NaN guard ordering) are both fixed cleanly. The benchmark script addition is trivially correct. No logic errors, security concerns, or data-loss risks remain. The always-firing stale warning is a deliberate design choice and is documented clearly in the PR description.
  • No files require special attention.

Important Files Changed

Filename Overview
package.json Adds benchmark npm script using the same --experimental-strip-types --import ts-resolve-loader.js pattern already used by other dev scripts. Straightforward and consistent.
src/domain/graph/builder/stages/finalize.ts Adds stale-embeddings warning by capturing a single buildNow timestamp, moving setBuildMeta earlier (so the current build's built_at is persisted before the comparison), and comparing embedding_meta.built_at against buildNow. Addresses both prior review concerns (off-by-one and NaN ordering). Logic is sound.

Sequence Diagram

sequenceDiagram
    participant User
    participant Finalize
    participant DB

    User->>Finalize: codegraph build

    Finalize->>Finalize: capture buildNow timestamp
    Finalize->>DB: getBuildMeta — reads previous build counts
    Finalize->>Finalize: incremental drift detection

    Finalize->>DB: setBuildMeta with current built_at
    Note over Finalize,DB: Moved early so downstream<br/>stale check sees current build time

    Finalize->>DB: count orphaned embeddings
    alt orphaned count is positive
        Finalize->>User: warn orphaned embeddings
    end

    Finalize->>DB: query embedding_meta built_at
    alt embedTime exists and precedes buildNow
        Finalize->>User: warn stale embeddings
    end

    Finalize->>DB: closeDb
    Finalize->>User: build complete
Loading

Reviews (2): Last reviewed commit: "Merge remote-tracking branch 'origin/cho..." | Re-trigger Greptile

Comment on lines +97 to +107
if (embedTime < now && !Number.isNaN(embedTime)) {
const prevBuildAt = getBuildMeta(db, 'built_at');
if (prevBuildAt) {
const prevBuildTime = new Date(prevBuildAt).getTime();
if (embedTime < prevBuildTime) {
warn(
'Embeddings were built before the last graph rebuild. Run "codegraph embed" to update.',
);
}
}
}
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.

P1 Stale warning fires one build cycle too late

The comparison uses prevBuildAt — which is the previous build's built_at, because setBuildMeta (line 141) hasn't run yet for the current build. This means the warning fires one complete rebuild cycle after embeddings actually become stale.

Concrete example:

  1. codegraph buildbuilt_at = T0 stored in metadata
  2. codegraph embedembedding_meta.built_at = T1 (T1 > T0)
  3. codegraph build (first rebuild after embed) → prevBuildAt = T0, embedTime = T1, T1 < T0 is falseno warning (but embeddings ARE now stale relative to this rebuild)
  4. codegraph build (second rebuild) → prevBuildAt = T2 > T1 → warning fires ✓

This also explains why the test plan step — "run codegraph embed, then codegraph build --no-incremental, confirm warning appears" — would not produce a warning on the first try unless there was already a completed build after the embed in the stored metadata.

The fix is to compare embedTime against now (the current build's effective timestamp) rather than prevBuildAt. The now variable is already available but currently only used as a guard that is always true:

      if (embedBuiltAt) {
        const embedTime = new Date(embedBuiltAt).getTime();
        if (!Number.isNaN(embedTime)) {
          const now = Date.now();
          if (embedTime < now) {
            warn(
              'Embeddings were built before the last graph rebuild. Run "codegraph embed" to update.',
            );
          }
        }
      }

Note: with this approach, prevBuildAt is no longer needed. If the concern is false-positives (always warning), consider storing a build-start timestamp in ctx and comparing against that instead, or move the setBuildMeta call for built_at to the top of finalize so prevBuildAt reflects the current build.

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 in 4157d7b. Moved setBuildMeta before the stale-embeddings check so built_at in the DB reflects the current build timestamp when the comparison runs. This eliminates the off-by-one: on the first rebuild after an embed, the warning now fires immediately.

Additionally captured a single buildNow = new Date() at the top of finalize and use it for both the built_at metadata write and the stale comparison, ensuring consistency.

Comment on lines +95 to +97
const embedTime = new Date(embedBuiltAt).getTime();
const now = Date.now();
if (embedTime < now && !Number.isNaN(embedTime)) {
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 Redundant guard and inverted NaN-check order

embedTime < now is effectively always true for any real timestamp (it only fails for future-dated or NaN values), so it adds no filtering. Additionally, the !Number.isNaN(embedTime) guard should precede the comparison rather than follow it — evaluating NaN < now is safe (it returns false) but the intent reads incorrectly.

Suggested change
const embedTime = new Date(embedBuiltAt).getTime();
const now = Date.now();
if (embedTime < now && !Number.isNaN(embedTime)) {
const embedTime = new Date(embedBuiltAt).getTime();
if (!Number.isNaN(embedTime)) {

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 in 4157d7b. The NaN check now precedes the comparison (), and the redundant \ guard is replaced with a direct comparison against \ (the current build's wall-clock timestamp captured at the top of finalize). The old always-true \ pattern is gone.

- Move setBuildMeta before stale-embeddings check so built_at reflects
  the current build, fixing the off-by-one where the warning fired one
  rebuild cycle late
- Fix NaN-check order: validate embedTime before comparing
- Remove redundant embedTime < now guard (always true for real timestamps)
- Use single buildNow timestamp for both metadata and comparison

Impact: 1 functions changed, 8 affected
…' into fix/review-pr-604

Impact: 16 functions changed, 52 affected
@carlos-alm
Copy link
Copy Markdown
Contributor Author

@greptileai

@carlos-alm carlos-alm merged commit 6c4aca5 into main Mar 26, 2026
22 checks passed
@carlos-alm carlos-alm deleted the chore/benchmark-stale-embeddings branch March 26, 2026 00:14
@github-actions github-actions bot locked and limited conversation to collaborators Mar 26, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant