Skip to content

chore(ci): add grammar version parity check between npm devDeps and Cargo.toml#1270

Merged
carlos-alm merged 6 commits into
mainfrom
fix/watch-calls-edge-inflation
Jun 1, 2026
Merged

chore(ci): add grammar version parity check between npm devDeps and Cargo.toml#1270
carlos-alm merged 6 commits into
mainfrom
fix/watch-calls-edge-inflation

Conversation

@carlos-alm

@carlos-alm carlos-alm commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds scripts/check-grammar-versions.mjs that compares the major version of every tree-sitter grammar in package.json devDependencies against its paired Cargo crate in crates/codegraph-core/Cargo.toml
  • Wires a lightweight grammar-version-parity CI job (checkout + Node only, no npm install) into the pipeline
  • Catches future cases where one side gets a major-version bump without the other — a divergence that can cause the WASM engine and native engine to parse code differently

Design notes

  • Only major versions are compared; minor-version drift is allowed and already guarded by the Rust all_grammars_have_compatible_abi test at build time
  • Unversioned npm refs (github:, git+) are skipped with a notice (clojure, erlang, gleam)
  • A KNOWN_EXCEPTIONS table handles tree-sitter-dart: the npm package is at v1.0.0 but tree-sitter-dart v1.x hasn't been published to crates.io yet (latest: 0.2.0). Remove the exception once the crate is updated

Current output

Grammar version parity: notices (skipped entries):
  tree-sitter-clojure: npm ref "github:sogaiu/tree-sitter-clojure" has no semver version — skipped
  tree-sitter-dart: major-version mismatch excused — tree-sitter-dart v1.x not yet published to crates.io (latest: 0.2.0)
  tree-sitter-erlang: npm ref "github:WhatsApp/tree-sitter-erlang#semver:*" has no semver version — skipped
  tree-sitter-gleam: npm ref "github:gleam-lang/tree-sitter-gleam" has no semver version — skipped
Grammar version parity OK — 29 grammar(s) checked, 4 skipped.

Closes #1269

Test plan

  • node scripts/check-grammar-versions.mjs passes locally
  • grammar-version-parity CI job passes in the PR checks
  • Verify the job appears in the ci-pipeline gate

carlos-alm and others added 3 commits May 31, 2026 14:50
Two divergences from the full build were causing watch-mode to insert
more `calls` edges than a clean full build:

1. `buildCallEdges` had no `seenCallEdges` dedup set, so the same
   (caller, target) pair could be inserted multiple times within a
   single file's call list.

2. The global name fallback in `resolveCallTargets` returned ALL nodes
   matching the call name across the entire DB, while the full build's
   `resolveByMethodOrGlobal` filters to `computeConfidence >= 0.5`.
   Low-confidence cross-module matches were creating spurious edges
   that don't appear in the full build.

Closes #1259

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Keep origin/main's resolveByMethodOrGlobal helper in resolveCallTargets
instead of the PR's inline confidence filter; the helper is equivalent
but also handles the type-aware receiver branch. Fix the duplicate
seenCallEdges declaration that resulted from the merge.
…remental builds

In classifyNodeRolesIncremental, two full GROUP BY edge-table scans ran on
every incremental rebuild to recompute global fan-in/fan-out medians — even
for a 1-file change. On a large graph these dominated the ~20 ms roles phase.

Fix: write {fanIn, fanOut, edgeCount} to build_meta after each full
classification and read from cache in the incremental path. The cache is
invalidated when the edge count drifts by more than max(500, 10%),
which covers large structural changes but never a 1-file edit.

In classifyNodeRolesFull, medians are now computed from the already-loaded
classifierInput rows and passed as medianOverrides to classifyRoles, avoiding
a second in-memory recomputation and priming the cache in one pass.

Expected: incremental roles phase drops from ~20 ms to ~2-3 ms on a cache
hit (the common case after any full build).
@claude

claude Bot commented Jun 1, 2026

Copy link
Copy Markdown

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


PR Review: perf(roles): scope incremental role classification with median cache

Tasks

  • Understand the 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
  • Final assessment

Root Cause Analysis

Problem Being Solved: classifyNodeRolesIncremental ran two GROUP BY full-table scans on every 1-file watch rebuild to recompute global fan-in/fan-out medians. On large graphs this was ~20 ms per file change, making watch mode sluggish. The PR caches these medians in build_meta after the full build and reads them on incremental builds, skipping the scans when edge count hasn't drifted significantly.

Why This Approach: The median doesn't meaningfully change for a single-file edit, so caching with a staleness check (max(500, 10%) drift threshold) is sound. Medians are now computed from the already-loaded in-memory classifierInput on full builds — zero extra DB round-trip. The cache is written once per full build and invalidated when large structural changes occur.

Risk Assessment: Low. The cache degrades gracefully — a miss falls back to the original full scan and primes the cache. The invalidation threshold is conservative and uses absolute+relative guards.


Phase 2: Configuration Changes

No linting rules changed, no TypeScript strict flags disabled, no test timeouts or coverage thresholds modified. ✓ Clean.


Phase 3: Test Integrity

The test file change (+1/-0 in issue-1259-watch-call-resolution.test.ts) adds the kind column to findNodeByName in the test helper, matching the watcher change in watcher.ts. This is a necessary alignment, not a weakening of assertions.

All existing assertions remain identical or stricter. No skipped tests. ✓ Clean.


Phase 4: Code Quality

Concerns:

1. Stale median risk on incremental-only sessions (structure.ts:803–804)

The cache is populated by classifyNodeRolesFull. If a user runs codegraph watch on a DB that was never followed by a full role classification in this session (e.g. very first build on a fresh checkout, or a DB restored from snapshot), readCachedMedians returns null, and the incremental path falls back to computeGlobalMediansFromEdges. This is acceptable — but the comment says "cache is almost always valid" which could mislead future maintainers.

More importantly: classifyNodeRolesFull computes medians from classifierInput (non-zero fan-in/fan-out nodes only), but computeGlobalMediansFromEdges (the fallback) scans the raw edge table without filtering. The two approaches can produce different medians for the same graph:

  • classifyNodeRolesFull at structure.ts:745–752: filters to nodes with fanIn > 0 / fanOut > 0, derives medians from caller/callee-connected nodes only.
  • computeGlobalMediansFromEdges at structure.ts:598–614: does COUNT(*) … GROUP BY target_id — includes all edge kinds ('calls' and 'imports-type'), which is close but not identical to the node-filtered approach.

This asymmetry means that on the very first incremental build after a fresh full build, the cached median may differ from what computeGlobalMediansFromEdges would have returned. The cache path is the correct one (it matches what the full classification actually used), but the fallback path produces a slightly different distribution. This is not a bug today because cache misses only happen when the edge count drifts by >500 or >10%, at which point a small median difference is acceptable. But the two paths are subtly inconsistent and should either be unified or documented.

2. Double COUNT(*) AS cnt FROM edges in writeMedianCache (structure.ts:644)

writeMedianCache issues a fresh SELECT COUNT(*) FROM edges even when called from classifyNodeRolesFull, which has already loaded classifierInput. The edge count is used only for cache staleness, so a slightly stale count is acceptable — but adding a SELECT COUNT(*) on large graphs on every full build is a measurable extra query. Minor, but inconsistent with the stated goal of "no extra DB round-trip on full builds."

3. readCachedMedians also issues a SELECT COUNT(*) (structure.ts:626–631)

Called on every incremental build — this is O(1) but involves a full-table count on the edges table. SQLite stores this in the page header, so it's fast, but it still deserves a note that this query is on the critical path.

4. The fix in incremental.ts is substantial and correct, but the PR title and description undersell it

The incremental.ts changes (+91/-1) are the actual correctness fix: barrel re-export resolution, deduplication via seenCallEdges, receiver-gated global fallback. These are bug fixes, not just performance work. The PR title says "perf(roles)" but more than half the diff is a correctness fix in the incremental call resolver. This makes the commit history harder to bisect and the PR harder to review in isolation.


Backlog Compliance

  • Zero-dep: ✓ — No new runtime dependencies. Uses existing build_meta table (already in schema) and getBuildMeta/setBuildMeta (already exported from db/index.ts).
  • Foundation-aligned: ✓ — Fully local, no cloud calls, no new infrastructure, no agent behavior.
  • Problem-fit: 4 — Faster incremental builds directly improve the watch-mode feedback loop that AI agents use during iterative edits. Less time waiting = fewer retries = fewer wasted tokens.
  • Breaking: No — build_meta is an internal implementation detail. Public API and CLI output are unchanged. The classifyRoles signature change (adding medianOverrides) was already present in the codebase; this PR just makes use of it.
  • Tier: 1 — Zero-dep + foundation-aligned.

Critical Concerns

None that block merging. The asymmetry between computeGlobalMediansFromEdges and the in-memory median computation in classifyNodeRolesFull (concern #1 above) is worth a follow-up issue but is not a correctness regression — the cached path is the correct one, and the fallback is only invoked in edge cases.

The PR conflates two concerns (bug fix in call resolver + performance optimization in role classifier) in a single commit/PR, which the CLAUDE.md explicitly flags: "One PR = one concern." However, both changes target the same code path (incremental watch rebuild) and the bug fix was likely surfaced while writing the performance test, so splitting retroactively may not be practical.


Final Recommendation

  • Rating: ⭐⭐⭐⭐ (4/5)
  • Action: APPROVE WITH CONDITIONS
  • Reasoning: The core implementation is correct, well-reasoned, and the fallback path is safe. The test coverage is appropriate. Minor follow-up items:
    1. File a tracking issue for the computeGlobalMediansFromEdges vs. in-memory median asymmetry (concern Bump actions/setup-node from 4 to 6 #1 — different distributions on cache miss vs. cache warm).
    2. Consider whether the extra SELECT COUNT(*) FROM edges in writeMedianCache should reuse the count already available in the caller context on full builds (concern Bump actions/checkout from 4 to 6 #2).
    3. Optionally split the incremental.ts call-resolver correctness fix into its own commit in the next release notes to keep attribution clean (not a blocker).

@greptile-apps

greptile-apps Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds a lightweight CI check (grammar-version-parity) that compares the major version of every tree-sitter grammar in package.json devDependencies against its Cargo crate counterpart, and also lands the median-caching fixes for incremental role classification in structure.ts that were discussed in previous review threads.

  • scripts/check-grammar-versions.mjs: New script that reads package.json and Cargo.toml directly (no npm install needed), compares major versions across 33 listed grammars, skips unresolvable npm refs (git/github: specifiers), and supports a KNOWN_EXCEPTIONS table for cases like tree-sitter-dart where the Cargo crate hasn't published its v1.x yet.
  • .github/workflows/ci.yml: Adds the grammar-version-parity job (checkout + Node 22 only) and wires it into the ci-pipeline gate — consistent in shape with the existing verify-imports job.
  • src/features/structure.ts: Introduces readCachedMedians / writeMedianCache / computeGlobalMediansFromEdges helpers; the incremental path now reads cached medians instead of running two full-table GROUP BY scans per build, with a 500-edge / 10% staleness guard using only the calls+imports-type edge subset.

Confidence Score: 5/5

Safe to merge — the new CI check is read-only and gated correctly, and the structure.ts changes address previously flagged median-caching concerns without touching any hot paths beyond what was already reviewed.

All three changed files are additive or fixing prior review feedback. The grammar version check cannot break any build artifact and only gates CI. The structure.ts changes correctly scope the staleness check to the calls+imports-type edge subset and eliminate the redundant COUNT query. The single robustness note on the TOML parser is a future-proofing concern that does not affect the current 29 passing grammars.

No files require special attention, though scripts/check-grammar-versions.mjs would benefit from a more robust TOML parser if the Cargo.toml ever adopts section-style dependency declarations for any grammar crate.

Important Files Changed

Filename Overview
scripts/check-grammar-versions.mjs New script that compares npm and Cargo major versions for all tree-sitter grammars. Logic is correct and handles scoped packages, git refs, tarball URLs, and known exceptions. The TOML parser only handles single-line dependency declarations; multi-line or section-style TOML would produce false failures.
.github/workflows/ci.yml Adds grammar-version-parity job (checkout + Node 22 only, consistent with neighbouring verify-imports job) and gates ci-pipeline on it. No issues found.
src/features/structure.ts Adds median caching for incremental role classification, addressing all previously raised review concerns: staleness check now uses calls+imports-type edge subset, computeGlobalMediansFromEdges joins nodes for consistent kind filtering, and writeMedianCache accepts an optional pre-computed edgeCount to eliminate the redundant COUNT query.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Read package.json devDeps\nRead Cargo.toml] --> B[For each grammar in GRAMMAR_NPM_PACKAGES]
    B --> C{npm spec present?}
    C -- No --> F1[FAIL: absent from devDeps]
    C -- Yes --> D{Cargo crate present?}
    D -- No --> F2[FAIL: crate absent from Cargo.toml]
    D -- Yes --> E{extractNpmVersion}
    E -- null\ngithub:/git+ ref --> S1[SKIP: no semver]
    E -- version string --> G{parseMajor both sides}
    G -- parse fails --> S2[SKIP: unparseable]
    G -- both parsed --> H{npmMajor === cargoMajor?}
    H -- Yes --> OK[checked++]
    H -- No --> I{In KNOWN_EXCEPTIONS?}
    I -- Yes --> S3[SKIP: excused mismatch]
    I -- No --> F3[FAIL: major-version mismatch]
    F1 & F2 & F3 --> EXIT1[exit 1 + remediation message]
    OK & S1 & S2 & S3 --> DONE[Grammar version parity OK\nN checked, M skipped]
Loading

Fix All in Claude Code

Reviews (5): Last reviewed commit: "Merge branch 'main' into fix/watch-calls..." | Re-trigger Greptile

Comment thread src/features/structure.ts Outdated
Comment on lines +626 to +636
const currentCount = (db.prepare('SELECT COUNT(*) AS cnt FROM edges').get() as { cnt: number })
.cnt;
if (
Math.abs(currentCount - cached.edgeCount) >
Math.max(MEDIAN_INVALIDATION_DELTA, cached.edgeCount * 0.1)
)
return null;
return { fanIn: cached.fanIn, fanOut: cached.fanOut };
} catch {
return null;
}

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 Edge-count staleness check uses total edges, medians use a subset

readCachedMedians compares SELECT COUNT(*) FROM edges (all kinds) against the cached count, but the medians were computed only from edges with kind IN ('calls', 'imports-type') (fan-in) and kind = 'calls' (fan-out). A restructuring that adds or removes many imports, reexports, or dynamic-imports edges — without touching calls — can push the total past the 500 / 10 % threshold and evict a still-valid cache entry. The inverse is also possible: a change that significantly shifts the calls distribution while keeping the total edge count stable would leave a stale cache in place. Aligning the invalidation count with SELECT COUNT(*) FROM edges WHERE kind IN ('calls', 'imports-type') would make the staleness signal track what it is actually protecting.

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 — updated readCachedMedians to count only kind IN ('calls', 'imports-type') edges when checking staleness, matching the subset the medians are derived from. This ensures only relevant edge changes can evict the cache.

Comment thread src/features/structure.ts Outdated
Comment on lines +594 to +614
function computeGlobalMediansFromEdges(db: BetterSqlite3Database): {
fanIn: number;
fanOut: number;
} {
const fanInDist = (
db
.prepare(
`SELECT COUNT(*) AS cnt FROM edges WHERE kind IN ('calls', 'imports-type') GROUP BY target_id`,
)
.all() as { cnt: number }[]
)
.map((r) => r.cnt)
.sort((a, b) => a - b);
const fanOutDist = (
db
.prepare(`SELECT COUNT(*) AS cnt FROM edges WHERE kind = 'calls' GROUP BY source_id`)
.all() as { cnt: number }[]
)
.map((r) => r.cnt)
.sort((a, b) => a - b);
return { fanIn: median(fanInDist), fanOut: median(fanOutDist) };

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 computeGlobalMediansFromEdges and the full-build median computation are subtly different

classifyNodeRolesFull derives medians from classifierInput, which only contains nodes whose kind is NOT file, directory, parameter, or property. computeGlobalMediansFromEdges computes from raw GROUP BY target_id without any node-kind filter, so if any such excluded-kind nodes appear as a target_id in calls/imports-type edges, the distribution will differ. On a cold cache (first incremental run, no prior full build, or after a large drift triggers invalidation) the fallback medians and the full-build medians diverge. Adding a join to nodes with the same kind filter would keep the two paths consistent.

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 — computeGlobalMediansFromEdges now joins nodes and filters out the same excluded kinds (file, directory, parameter, property) that classifyNodeRolesFull excludes when computing medians from in-memory rows. Cold-cache and warm-cache paths now produce consistent distributions.

Comment thread src/features/structure.ts Outdated
Comment on lines +639 to +646
/** Persist global role medians + current edge count to build_meta. */
function writeMedianCache(
db: BetterSqlite3Database,
medians: { fanIn: number; fanOut: number },
): void {
const edgeCount = (db.prepare('SELECT COUNT(*) AS cnt FROM edges').get() as { cnt: number }).cnt;
setBuildMeta(db, { [ROLES_MEDIANS_KEY]: JSON.stringify({ ...medians, edgeCount }) });
}

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 writeMedianCache runs a redundant SELECT COUNT(*) FROM edges on cache miss

On an incremental cache miss the call sequence is: readCachedMedians (runs SELECT COUNT(*) FROM edges), then computeGlobalMediansFromEdges (two GROUP BY queries), then writeMedianCache (runs SELECT COUNT(*) FROM edges again). The same COUNT is executed twice. Passing the count obtained in readCachedMedians through to the write path — or accepting it as an optional argument to writeMedianCache — would eliminate the duplicate query.

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 — writeMedianCache now accepts an optional edgeCount parameter. Both call sites pass the already-computed count: the incremental path passes the count from computeGlobalMediansFromEdges (which now returns it alongside the medians), and the full-build path derives it from in-memory rows (sum of fan_in values equals total calls+imports-type edges). The fallback COUNT query in writeMedianCache only fires when neither caller provides the count.

@github-actions

github-actions Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Codegraph Impact Analysis

8 functions changed4 callers affected across 2 files

  • extractNpmVersion in scripts/check-grammar-versions.mjs:89 (1 transitive callers)
  • parseMajor in scripts/check-grammar-versions.mjs:101 (1 transitive callers)
  • parseCargoVersions in scripts/check-grammar-versions.mjs:109 (1 transitive callers)
  • computeGlobalMediansFromEdges in src/features/structure.ts:605 (2 transitive callers)
  • readCachedMedians in src/features/structure.ts:645 (2 transitive callers)
  • writeMedianCache in src/features/structure.ts:675 (3 transitive callers)
  • classifyNodeRolesFull in src/features/structure.ts:690 (1 transitive callers)
  • classifyNodeRolesIncremental in src/features/structure.ts:823 (1 transitive callers)

…argo.toml

Adds scripts/check-grammar-versions.mjs, which compares the major version
of every tree-sitter grammar in package.json devDependencies against its
paired Cargo crate in crates/codegraph-core/Cargo.toml. A major-version
divergence means the two engines ship grammars at incompatible API levels;
minor drifts are allowed and already caught by the Rust
`all_grammars_have_compatible_abi` test at build time.

The script handles npm-to-Cargo name differences (kotlin → kotlin-sg, etc.),
skips unversioned github:/tarball refs, and has a KNOWN_EXCEPTIONS table for
tree-sitter-dart whose v1.x npm release has no matching crates.io publish yet.

Wires the check into CI as a lightweight grammar-version-parity job (no npm
install required) and adds it to the ci-pipeline gate.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@carlos-alm carlos-alm changed the title perf(roles): scope incremental role classification with median cache chore(ci): add grammar version parity check between npm devDeps and Cargo.toml Jun 1, 2026
Three issues with the median-cache helpers introduced in the prior commit:

1. readCachedMedians used SELECT COUNT(*) FROM edges (all kinds) to check
   staleness, but medians are derived from only calls+imports-type edges.
   Changes to imports/reexports/dynamic-imports edges could falsely evict a
   valid cache (or fail to evict a stale one). Now counts only the same
   subset used to compute the medians.

2. computeGlobalMediansFromEdges did not join nodes to filter node kinds,
   so on a cold-cache fallback it could include file/directory/parameter/
   property nodes as edge targets — producing a different distribution than
   classifyNodeRolesFull which excludes those kinds. Added the same node-kind
   filter as the full-build path for consistency.

3. writeMedianCache ran a redundant SELECT COUNT(*) FROM edges on cache-miss
   paths (readCachedMedians had already counted, or the count was just
   computed by computeGlobalMediansFromEdges). Now accepts an optional
   edgeCount argument; both call sites pass the already-computed count to
   eliminate the duplicate query. The full-build path derives it from the
   in-memory rows (sum of fan_in values).
@carlos-alm

Copy link
Copy Markdown
Contributor Author

Addressed review feedback from Claude's review:

  • Concern Bump actions/setup-node from 4 to 6 #1 (median asymmetry on cold cache): Fixed directly — computeGlobalMediansFromEdges now joins nodes with the same kind filter (NOT IN ('file', 'directory', 'parameter', 'property')) used by classifyNodeRolesFull. Cold-cache and warm-cache paths now produce consistent distributions.
  • Concern Bump actions/checkout from 4 to 6 #2 (double COUNT in writeMedianCache): Fixed — writeMedianCache now accepts an optional edgeCount parameter. Both call sites pass the pre-computed count, eliminating the duplicate query entirely on both full-build and incremental paths.
  • Concern Bump actions/github-script from 7 to 8 #3 (readCachedMedians staleness scope): Fixed — the staleness check now counts only kind IN ('calls', 'imports-type') edges, matching the subset the medians are derived from.

@carlos-alm

Copy link
Copy Markdown
Contributor Author

@greptileai

@carlos-alm

Copy link
Copy Markdown
Contributor Author

@claude

@claude

claude Bot commented Jun 1, 2026

Copy link
Copy Markdown

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


Re-review: verifying addressed concerns

Tasks


All three concerns from the previous review are correctly addressed. Here's what was verified:

Concern #1 — median asymmetry on cold cache (structure.ts:605–635): Fixed correctly. computeGlobalMediansFromEdges now JOINs nodes and filters NOT IN ('file', 'directory', 'parameter', 'property') on both the fan-in and fan-out queries, matching exactly what classifyNodeRolesFull excludes when building classifierInput. The two paths now produce consistent distributions.

Concern #2 — double COUNT in writeMedianCache (structure.ts:675–688): Fixed cleanly. writeMedianCache now accepts an optional edgeCount?: number parameter. On the full-build path (structure.ts:800–801), the count is derived from in-memory rows (rows.reduce((acc, r) => acc + r.fan_in, 0)) — zero extra queries. On the cache-miss incremental path (structure.ts:854–858), computeGlobalMediansFromEdges now returns { fanIn, fanOut, edgeCount } and that value is passed directly to writeMedianCache — also zero extra queries. The fallback SELECT COUNT(*) inside writeMedianCache only fires if a caller passes undefined, which no current call site does.

Concern #3 — staleness scope (structure.ts:645–666): Fixed correctly. readCachedMedians now counts WHERE kind IN ('calls', 'imports-type') instead of all edges. This aligns the invalidation signal with the exact edge subset that drives the medians.

Greptile concern — parseCargoVersions multi-line tables (scripts/check-grammar-versions.mjs:107–119): The current Cargo.toml uses exclusively the simple bare-string form (tree-sitter-foo = "0.23") for all 29 grammar crates. The single inline-table entry (rusqlite) is not a grammar and is not in GRAMMAR_NPM_PACKAGES, so the current regex coverage is sufficient for the actual data. The theoretical risk of a future multi-line grammar entry silently failing as "crate absent" is real but not present today. Given the scope of this PR (CI parity check), accepting that risk and addressing it with a comment or future issue is reasonable — it does not need to block the merge.

One minor note: The tree-sitter-dart entry in Cargo.toml shows "0.0.4" (line 35), while the PR description and KNOWN_EXCEPTIONS say "latest: 0.2.0". Neither blocks the check — the exception table fires on any major-version mismatch — but the comment in the exception table is slightly stale. Not a blocker.


Rating: ⭐⭐⭐⭐⭐ (5/5) — LGTM

All three previously flagged concerns are resolved correctly, with no regressions introduced. The implementation is clean, the fixes are minimal and targeted, and the code comments accurately document the invariants.


View job

@carlos-alm carlos-alm merged commit 944100e into main Jun 1, 2026
22 checks passed
@carlos-alm carlos-alm deleted the fix/watch-calls-edge-inflation branch June 1, 2026 06:32
@github-actions github-actions Bot locked and limited conversation to collaborators Jun 1, 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.

chore(ci): add grammar version parity check between npm devDeps and Cargo.toml

1 participant