Skip to content
Merged
Show file tree
Hide file tree
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
5 changes: 5 additions & 0 deletions .changeset/unresolved-calls-staging.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@stainless-code/codemap": minor
---

Add two-phase call resolution (`unresolved_calls` staging, `calls.callee_symbol_id` / `callee_resolution_kind`). Schema version 36 — existing indexes rebuild on next `codemap` run.
2 changes: 1 addition & 1 deletion .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ Then open a PR on GitHub into **`main`**.
- **Public API** — Anything exported from the package entry (`src/index.ts` → `src/api.ts`, `config.ts`, shared types) should have **JSDoc** that reads well in hovers and in published typings.
- **Layers** — Keep boundaries clear: [architecture.md](../docs/architecture.md) (`cli` → `application` → infrastructure). Don’t let CLI concerns leak into parsers or the DB layer.
- **Before you open / update a PR** — `bun run check` (or at least `bun run test` + `bun run typecheck` while iterating).
- **Golden queries (Tier A)** — If you change `fixtures/minimal/` or schema/query behavior expected by [fixtures/golden/](../fixtures/golden/), run `bun scripts/query-golden.ts --update`, review diffs, and commit updated JSON under `fixtures/golden/minimal/`. Prefer **fixing the indexer** when output changes for the wrong reason; only refresh goldens when the new rows are correct. See [docs/golden-queries.md](../docs/golden-queries.md).
- **Golden queries (Tier A)** — If you change `fixtures/minimal/` or schema/query behavior expected by [fixtures/golden/](../fixtures/golden/), run `bun scripts/query-golden.ts --update`, review diffs, and commit updated JSON under `fixtures/golden/minimal/`. Prefer **fixing the indexer** when output changes for the wrong reason; only refresh goldens when the new rows are correct. See [docs/golden-queries.md](../docs/golden-queries.md) and the coverage map [docs/testing-coverage.md](../docs/testing-coverage.md). New tables should add a substrate scenario (or extend `index-table-stats`); `bun run test:scripts` runs `query-golden-coverage-matrix.test.mjs` (every bundled recipe + `SUBSTRATE_SCENARIO_BY_TABLE` pin-downs).
- **Golden queries (Tier B)** — Against a **local** clone, use `bun run test:golden:external` with `CODEMAP_ROOT` / `--root`. Copy [fixtures/golden/scenarios.external.example.json](../fixtures/golden/scenarios.external.example.json) to `scenarios.external.json` if you need custom scenarios; goldens under `fixtures/golden/external/` are gitignored — do not commit snapshots from proprietary trees.
- **Style** — Match Oxfmt/Oxlint; prefer **straight-line code** and extracted helpers over long nested blocks.

Expand Down
2 changes: 2 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Each topic has exactly one canonical file. Other files cross-reference by relati
| [agents.md](./agents.md) | **`codemap agents init`** — bundled **`templates/agents/`** (thin pointer files) → **`.agents/`** in consumer projects; full content served live by **`codemap skill`** / **`codemap rule`** + **`codemap://skill`** / **`codemap://rule`** from `templates/agent-content/`; section assembler + `*.gen.md` renderers, **[pointer protocol](./agents.md#pointer-protocol-and-staleness-detection)** + staleness nag, per-file IDE symlink/copy, **`--interactive`**, **`--mcp`**, **`<state-dir>/.gitignore`** reconciler (root `.gitignore` untouched). |
| [benchmark.md](./benchmark.md) | [**Indexing another project**](./benchmark.md#indexing-another-project) · [**Benchmark script**](./benchmark.md#the-benchmark-script) · [**Query stdout (table vs JSON)**](./benchmark.md#query-stdout-table-vs-json-benchmarkquery) · [**Custom scenarios**](./benchmark.md#custom-scenarios-codemap_benchmark_config) (`CODEMAP_BENCHMARK_CONFIG`) · [**Agent eval harness**](./benchmark.md#agent-eval-harness) · [`fixtures/minimal/`](../fixtures/minimal/). |
| [golden-queries.md](./golden-queries.md) | Golden `query` **design & policy** (Tier A/B, no proprietary trees); runner: [scripts/query-golden.ts](../scripts/query-golden.ts). |
| [testing-coverage.md](./testing-coverage.md) | Maintainer map: harness layers, substrate golden scenarios, recipe/table guard (`query-golden-coverage-matrix.test.mjs`). |
| [fixtures/golden/](../fixtures/golden/) | [scenarios.json](../fixtures/golden/scenarios.json) + [minimal/](../fixtures/golden/minimal/) — **`bun run test:golden`**; Tier B: [scenarios.external.example.json](../fixtures/golden/scenarios.external.example.json) + **`bun run test:golden:external`** ([benchmark § Fixtures](./benchmark.md#fixtures)). |
| [fixtures/benchmark/](../fixtures/benchmark/) | Tracked [scenarios.example.json](../fixtures/benchmark/scenarios.example.json) — copy to `*.local.json` (gitignored) for [`CODEMAP_BENCHMARK_CONFIG`](./benchmark.md#custom-scenarios-codemap_benchmark_config). |
| [fixtures/qa/](../fixtures/qa/) | [prompts.external.template.md](../fixtures/qa/prompts.external.template.md) — optional chat QA prompts for an external index (`*.local.md` gitignored). |
Expand Down Expand Up @@ -62,6 +63,7 @@ Cross-cutting topics that span multiple files. Each has exactly one canonical ho
| **`CLAUDE.md` / `AGENTS.md` / `GEMINI.md` / Copilot** — managed **`codemap-pointer`** sections, merge vs **`--force`** | [agents.md § Pointer files](./agents.md#pointer-files) | Link here; do not duplicate the situation table |
| End-user CLI (index, **`query --json`**, **`query --recipe`**, **`query --recipes-json`**, **`query --print-sql`**, **`skill`**, **`rule`**, agents, flags, env) — query has no row cap; use SQL **`LIMIT`**; **`--json`** errors include SQL, DB open, and bootstrap failures; bundled `templates/agent-content/skill/*.md` examples default to **`--json`** | [../README.md § CLI](../README.md#cli) | [architecture § CLI usage](./architecture.md#cli-usage) summarizes and links back; [agents.md](./agents.md) |
| Golden query regression (`test:golden`, `test:golden:external`, `--update`) | [golden-queries.md](./golden-queries.md) | CONTRIBUTING § Golden queries; [benchmark § Fixtures](./benchmark.md#fixtures) |
| In-repo test bench harness map (layers, substrate pin-down scenarios, `CAPABILITIES.json`) | [testing-coverage.md](./testing-coverage.md) | [fixtures/README.md](../fixtures/README.md); optional scale: [plans/in-repo-test-bench.md](./plans/in-repo-test-bench.md) Phase 4 only |
| Agent eval harness (`test:agent-eval`, `scripts/agent-eval/`) | [benchmark § Agent eval harness](./benchmark.md#agent-eval-harness) | Reuses golden scenarios via `goldenId`; probe + live + log structural cost A/B; exploratory MCP vs agent findings in [research/agent-eval-findings-2026-05.md](./research/agent-eval-findings-2026-05.md) |
| **`CODEMAP_BENCHMARK_CONFIG`** (per-repo benchmark JSON) | [benchmark § Custom scenarios](./benchmark.md#custom-scenarios-codemap_benchmark_config) | [fixtures/benchmark/scenarios.example.json](../fixtures/benchmark/scenarios.example.json) only |
| `bun run qa:external` — index + disk checks + `benchmark.ts` on **`CODEMAP_*`** | [.github/CONTRIBUTING.md](../.github/CONTRIBUTING.md) | [scripts/qa-external-repo.ts](../scripts/qa-external-repo.ts) (invocation only) |
Expand Down
49 changes: 34 additions & 15 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ Optional **`<state-dir>/config.{ts,js,json}`** (default `.codemap/config.*`; def

**Fresh database:** the default CLI **`codemap`** (incremental) calls **`createSchema()`** in **`runCodemapIndex`** before **`getChangedFiles()`**, so the **`meta`** table exists before **`getMeta(..., "last_indexed_commit")`** runs on an empty **`.codemap/index.db`**.

Current schema version: **35** — see [Schema Versioning](#schema-versioning) for details.
Live schema version: `SCHEMA_VERSION` in [`src/db.ts`](../src/db.ts) — see [Schema Versioning](#schema-versioning) for bump policy.

All base tables use `STRICT` mode; **`source_fts`** is an FTS5 virtual table (no `STRICT`). Tables marked with `WITHOUT ROWID` store data directly in the primary key B-tree. PRAGMAs and index design: [SQLite Performance Configuration](#sqlite-performance-configuration).

Expand Down Expand Up @@ -245,23 +245,42 @@ All base tables use `STRICT` mode; **`source_fts`** is an FTS5 virtual table (no

### `calls` — Function-scoped call edges, deduped per file (`STRICT`)

| Column | Type | Description |
| ------------------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------- |
| id | INTEGER PK | Auto-increment row id |
| file_path | TEXT FK | References `files(path)` ON DELETE CASCADE |
| caller_name | TEXT | Name of the calling function/method |
| caller_scope | TEXT | Dot-joined scope path (e.g. `UserService.run`). Anonymous scopes encode as `$anon_<localId>` to avoid sibling-callback collisions |
| callee_name | TEXT | Name of the called function, `obj.method` / `obj.foo.bar` for member chains (recursive flatten), `this.method` for self |
| line_start | INTEGER | 1-based line of the callee identifier token (per [R.6]) |
| column_start | INTEGER | 0-based byte column of the callee token |
| column_end | INTEGER | One-past-last column |
| args_count | INTEGER | Argument count; NULL when a spread argument is present |
| is_method_call | INTEGER | 1 when callee is a member expression (`obj.method()`) |
| is_constructor_call | INTEGER | 1 for `new Foo()` (`NewExpression`) |
| is_optional_chain | INTEGER | 1 when the call uses optional chaining (`?.`) |
| Column | Type | Description |
| ---------------------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| id | INTEGER PK | Auto-increment row id |
| file_path | TEXT FK | References `files(path)` ON DELETE CASCADE |
| caller_name | TEXT | Name of the calling function/method |
| caller_scope | TEXT | Dot-joined scope path (e.g. `UserService.run`). Anonymous scopes encode as `$anon_<localId>` to avoid sibling-callback collisions |
| callee_name | TEXT | Name of the called function, `obj.method` / `obj.foo.bar` for member chains (recursive flatten), `this.method` for self |
| line_start | INTEGER | 1-based line of the callee identifier token (per [R.6]) |
| column_start | INTEGER | 0-based byte column of the callee token |
| column_end | INTEGER | One-past-last column |
| args_count | INTEGER | Argument count; NULL when a spread argument is present |
| is_method_call | INTEGER | 1 when callee is a member expression (`obj.method()`) |
| is_constructor_call | INTEGER | 1 for `new Foo()` (`NewExpression`) |
| is_optional_chain | INTEGER | 1 when the call uses optional chaining (`?.`) |
| callee_symbol_id | INTEGER FK | Resolved callee in `symbols` (NULL when unresolved / external global) |
| callee_resolution_kind | TEXT | `same-file`, `imported`, `re-exported`, `global`, or `unresolved` — set by `resolveCalls` after bindings; NULL on method calls (`is_method_call = 1`, deferred) |

Edges are deduped per (caller_scope, callee, call vs constructor) per file: if `foo` calls `bar` three times in the same file, only one row is stored. `foo()` and `new Foo()` with the same callee name remain distinct rows. Same-named methods in different classes get distinct `caller_scope` values. Module-level calls (outside any function) are excluded — only function-scoped calls are tracked.

**Call resolution:** `src/application/call-resolver.ts` runs after bindings on full rebuild and after incremental file updates. Incremental scope is `expandHeritageResolveScope` over changed/requested paths **and** deletion-only paths (includes importers). Method calls (`is_method_call = 1`) are not name-bound yet (`callee_*` stay NULL; not queued). Unresolved sites are staged in `unresolved_calls`; `meta.unresolved_calls_residual` is the global queue `COUNT(*)`.

### `unresolved_calls` — Staging queue for unresolved call sites (`STRICT`)

| Column | Type | Description |
| -------------- | ---------- | ---------------------------------------------- |
| id | INTEGER PK | Auto-increment row id |
| file_path | TEXT FK | References `files(path)` ON DELETE CASCADE |
| caller_scope | TEXT | Caller scope path at the call site |
| callee_name | TEXT | Callee identifier as stored on the `calls` row |
| line_start | INTEGER | 1-based line of the call site |
| column_start | INTEGER | 0-based column of the callee token (nullable) |
| reference_kind | TEXT | Default `call` |
| created_at | TEXT | ISO timestamp when the row was queued |

Bundled recipes: `unresolved-call-sites`, `call-resolution-stats`.

### `type_members` — Properties and methods of interfaces and object-literal types (`STRICT`)

| Column | Type | Description |
Expand Down
Loading
Loading