Files
Colby Mchenry 0a91d0f512 perf(resolution): fix O(K²) import-node blowup in "Resolving refs" (#915) (#965)
* perf(resolution): resolve imports to definitions, not sibling import nodes (#915)

"Resolving refs" crawled (tens of minutes) on large projects — most painfully
ones mixing a big front-end and back-end. An external package or module imported
across hundreds/thousands of files (react, a shared UI package, Python
logging/typing) is re-declared as an `import` node in every importing file, so
its unresolved import ref fell through to the exact-name matcher, which scored
all K same-named import nodes via findBestMatch — K refs x K candidates = O(K^2)
per package, producing only meaningless import->import edges.

Fix: exclude `import`-kind nodes as name-match targets (they're statements, not
definitions; real import->definition resolution is the import resolver's job).
Plus two safe constant-factor wins in findBestMatch: hoist the per-candidate
ref.filePath split, and skip cross-language candidates when a same-language one
exists (provably the same winner — same-language scores >=50, cross-language
maxes at 35).

Measured: superset (Py+TS) candidates scored 7.5M -> 833K (9x), non-import edges
preserved (+1618 now resolve to real defs), ~22K useless import->import edges
removed; kubernetes (Go) computePathProximity 37.2s -> 5.0s; synthetic 8k-file
mixed repo (K=4000) resolution 16.0s -> 1.7s. Full suite green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: correct stale better-sqlite3/wasm references to node:sqlite

The SQLite backend has been Node's built-in node:sqlite (real SQLite, WAL + FTS5,
from the bundled runtime) for a while — there is no native build step and no
node-sqlite3-wasm fallback. README and the docs site were already updated; this
catches the stragglers:

- CLAUDE.md: the src/db/ backend description and the sqlite-backend test note.
- src/db/index.ts, src/mcp/tools.ts: two code comments that still blamed "the
  wasm backend" for non-WAL behavior (reworded to "when WAL isn't in effect").

Leaves tree-sitter grammar wasm (web-tree-sitter / --liftoff-only) untouched —
that's a different, still-current use of wasm.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(telemetry): drop the dead sqlite_backend field (schema v2)

node:sqlite is now the only backend, so the `index` event's `sqlite_backend`
field was a constant ("native") carrying no signal — and the `install` event
never actually sent it. Remove the field and the backendKind() helper, bump the
telemetry SCHEMA_VERSION 1 -> 2, and update TELEMETRY.md + docs/design/telemetry.md.

The ingest worker is deliberately left tolerant: `index` doesn't require the
field and schema_version validates as nonNegInt(99), so v2 events ingest fine and
old clients still sending v1 + sqlite_backend keep validating too. Added a legacy
comment there explaining it's safe to drop once old-client share is negligible.

telemetry.test.ts: the assertion pinning schema_version and a stale-claim fixture
line updated 1 -> 2. All telemetry tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 11:26:23 -05:00
..

codegraph telemetry ingest worker

The first-party endpoint behind telemetry.getcodegraph.com. This directory is in the public repo on purpose: it is the exact code that receives codegraph's anonymous usage telemetry, so anyone can audit what is stored. The schema contract (every event, every field, and everything that is never collected) is in docs/design/telemetry.md.

What it does, in one breath: validates incoming batches against a strict allowlist (unknown events dropped, unknown properties stripped), never reads or forwards the client IP, rate-limits per machine ID, and forwards to PostHog off the response path. It ships nowhere with the npm package — the engine's files allowlist excludes it.

Endpoint contract

  • POST /v1/events — JSON body: envelope (machine_id UUID, codegraph_version, os, arch, node_major, ci, schema_version) + events: [{event, ts?, props?}]. Responds 204 when accepted (including events dropped by the allowlist), honest 4xx for malformed/oversized/rate-limited requests. Clients treat every response as final — no retries.
  • GET / — plain-text pointer to the docs and the off-switches.

Deploy

Prereqs: the getcodegraph.com zone on the deploying Cloudflare account (the custom domain route auto-provisions DNS + cert), wrangler ≥ 4.36 (the ratelimits binding).

cd telemetry-worker
npm install
npx wrangler login                      # once
npx wrangler secret put POSTHOG_KEY     # the phc_… project write key — never committed
npm run deploy

The PostHog project itself must have "Discard client IP data" enabled — defense in depth on top of this worker never forwarding IPs ($geoip_disable is also set per event).

Local dev & checks

cp .dev.vars.example .dev.vars   # placeholder key; also feeds `wrangler types`
npm run check                    # wrangler types + tsc --noEmit + deploy --dry-run
npm run dev                      # http://localhost:8787

curl -i localhost:8787/v1/events -H 'content-type: application/json' -d '{
  "machine_id": "00000000-0000-4000-8000-000000000000",
  "codegraph_version": "0.9.9", "os": "darwin", "arch": "arm64",
  "node_major": 22, "ci": false, "schema_version": 1,
  "events": [{ "event": "usage_rollup",
               "props": { "kind": "mcp_tool", "name": "codegraph_explore",
                          "count": 12, "error_count": 0, "client_name": "Claude Code" } }]
}'

Changing the schema

The allowlist in src/index.ts mirrors docs/design/telemetry.md (and the user-facing TELEMETRY.md). A field is added by one PR touching all of them together — that is the whole point of the design.