Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
12 changes: 12 additions & 0 deletions .agents/rules/codemap.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ If the question looks like any of these → use the index:
| "How many files/symbols/components are there?" | any table with `COUNT(*)` |
| "What are the CSS classes in X?" | `css_classes` |
| "What keyframe animations exist?" | `css_keyframes` |
| "What fields does interface/type X have?" | `type_members` |
| "Is symbol X deprecated?" / "What does X do?" | `symbols` (`doc_comment`) |
| "Who calls X?" / "What does X call?" | `calls` |

## When Grep / Read IS appropriate

Expand Down Expand Up @@ -93,6 +96,15 @@ bun src/index.ts query --json "<SQL>"
| CSS design tokens | `SELECT name, value, scope FROM css_variables WHERE name LIKE '--%...'` |
| CSS module classes | `SELECT name, file_path FROM css_classes WHERE is_module = 1` |
| CSS keyframes | `SELECT name, file_path FROM css_keyframes` |
| Type/interface shape | `SELECT name, type, is_optional, is_readonly FROM type_members WHERE symbol_name = '...'` |
| Deprecated symbols | `SELECT name, kind, file_path, doc_comment FROM symbols WHERE doc_comment LIKE '%@deprecated%'` |
| Symbol docs | `SELECT name, signature, doc_comment FROM symbols WHERE name = '...' AND doc_comment IS NOT NULL` |
| Const values | `SELECT name, value, file_path FROM symbols WHERE kind = 'const' AND value IS NOT NULL AND name LIKE '%...'` |
| Class members | `SELECT name, kind, signature FROM symbols WHERE parent_name = '...'` |
| Top-level only | `SELECT name, kind, signature FROM symbols WHERE parent_name IS NULL AND file_path LIKE '%...'` |
| Who calls X? | `SELECT DISTINCT caller_name, file_path FROM calls WHERE callee_name = '...'` |
| What does X call? | `SELECT DISTINCT callee_name FROM calls WHERE caller_name = '...'` |
| Call hotspots | `SELECT callee_name, COUNT(*) as fan_in FROM calls GROUP BY callee_name ORDER BY fan_in DESC LIMIT 10` |

**Use `DISTINCT`** on dependency and import queries — a file importing multiple specifiers from the same module produces duplicate rows.

Expand Down
67 changes: 66 additions & 1 deletion .agents/skills/codemap/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,34 @@ LIMIT 10
| kind | TEXT | `function`, `class`, `type`, `interface`, `enum`, `const` |
| line_start | INTEGER | Start line (1-based) |
| line_end | INTEGER | End line (1-based) |
| signature | TEXT | e.g. `createHandler()`, `type UserProps` |
| signature | TEXT | Reconstructed signature with generics and return types |
| is_exported | INTEGER | 1 if exported |
| is_default_export | INTEGER | 1 if default export |
| members | TEXT | JSON enum members (NULL for non-enums) |
| doc_comment | TEXT | Leading JSDoc text (cleaned), NULL when absent |
| value | TEXT | Literal value for consts (`"ok"`, `42`, `true`, `null`) |
| parent_name | TEXT | Enclosing symbol name (class/function), NULL = top-level |

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

| Column | Type | Description |
| ----------- | ---------- | ------------------------------- |
| id | INTEGER PK | Auto-increment ID |
| file_path | TEXT FK | References `files(path)` |
| caller_name | TEXT | Calling function/method name |
| callee_name | TEXT | Called function or `obj.method` |

### `type_members` — Properties of interfaces and object-literal type aliases

| Column | Type | Description |
| ----------- | ---------- | -------------------------------------------------- |
| id | INTEGER PK | Auto-increment ID |
| file_path | TEXT FK | References `files(path)` |
| symbol_name | TEXT | Parent interface / type alias name |
| name | TEXT | Property or method name |
| type | TEXT | Type annotation (e.g. `string`, `(key) => number`) |
| is_optional | INTEGER | 1 if `?` modifier |
| is_readonly | INTEGER | 1 if `readonly` modifier |

### `imports` — Import statements

Expand Down Expand Up @@ -190,6 +215,46 @@ FROM symbols WHERE name LIKE '%Config%' ORDER BY name;
SELECT name, kind, signature
FROM symbols WHERE file_path LIKE '%settings-provider%' AND is_exported = 1;

-- Enum values (what are the valid members of an enum?)
SELECT name, members FROM symbols
WHERE kind = 'enum' AND name = 'TransactionStatus';

-- Interface / type shape (what fields does a type have?)
SELECT name, type, is_optional, is_readonly FROM type_members
WHERE symbol_name = 'UserSession';

-- Deprecated symbols (find @deprecated via JSDoc)
SELECT name, kind, file_path, doc_comment FROM symbols
WHERE doc_comment LIKE '%@deprecated%';

-- Symbol documentation
SELECT name, signature, doc_comment FROM symbols
WHERE name = 'formatCurrency' AND doc_comment IS NOT NULL;

-- Const values (config flags, magic strings)
SELECT name, value, file_path FROM symbols
WHERE kind = 'const' AND value IS NOT NULL AND name LIKE '%URL%';

-- Class methods (what does class X expose?)
SELECT name, kind, signature FROM symbols
WHERE parent_name = 'UserService' ORDER BY name;

-- Top-level symbols only (skip nested helpers)
SELECT name, kind, signature FROM symbols
WHERE parent_name IS NULL AND file_path LIKE '%utils%';

-- Who calls function X? (fan-in)
SELECT DISTINCT caller_name, file_path FROM calls
WHERE callee_name = 'fetchUser';

-- What does function X call? (fan-out)
SELECT DISTINCT callee_name FROM calls
WHERE caller_name = 'processUser';

-- Most-called functions (hotspots)
SELECT callee_name, COUNT(*) as fan_in FROM calls
GROUP BY callee_name ORDER BY fan_in DESC LIMIT 10;

-- File overview (imports + exports)
SELECT 'import' as dir, source as name, specifiers as detail
FROM imports WHERE file_path LIKE '%OrderRow%'
Expand Down
14 changes: 14 additions & 0 deletions .changeset/richer-symbol-metadata.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
"@stainless-code/codemap": minor
---

Richer symbol metadata: generics, return types, JSDoc, type members, const values, symbol nesting, call graph

- Signatures now include generic type parameters, return type annotations, and heritage clauses (extends/implements)
- New `doc_comment` column on symbols extracts leading JSDoc comments
- New `type_members` table indexes properties and methods of interfaces and object-literal types
- New `value` column on symbols captures const literal values (strings, numbers, booleans, null)
- New `parent_name` column on symbols tracks scope nesting; class methods/properties/getters extracted as individual symbols
- New `calls` table tracks function-scoped call edges (deduped per file) for fan-in/fan-out and impact analysis
- Enum members extracted into `members` column as JSON
- SCHEMA_VERSION bumped to 2
54 changes: 41 additions & 13 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ A local SQLite database (`.codemap.db`) indexes the project tree and stores stru
| `application/` | Indexing use cases and engine (`run-index`, `index-engine`, types) |
| `worker-pool.ts` | Parallel parse workers (Bun / Node) |
| `db.ts` | SQLite adapter — schema DDL, typed CRUD, connection management |
| `parser.ts` | TS/TSX/JS/JSX extraction via `oxc-parser` — symbols, imports, exports, components, markers |
| `parser.ts` | TS/TSX/JS/JSX extraction via `oxc-parser` — symbols (with JSDoc + generics + return types), type members, imports, exports, components, markers |
| `css-parser.ts` | CSS extraction via `lightningcss` — custom properties, classes, keyframes, `@theme` blocks |
| `resolver.ts` | Import path resolution via `oxc-resolver` — respects `tsconfig` aliases, builds dependency graph |
| `constants.ts` | Shared constants — e.g. `LANG_MAP` |
Expand Down Expand Up @@ -173,17 +173,44 @@ All tables use `STRICT` mode. Tables marked with `WITHOUT ROWID` store data dire

### `symbols` — Functions, constants, classes, interfaces, types, enums (`STRICT`)

| Column | Type | Description |
| ----------------- | ---------- | --------------------------------------------------------------------- |
| id | INTEGER PK | Auto-increment row id |
| file_path | TEXT FK | References `files(path)` ON DELETE CASCADE |
| name | TEXT | Symbol name |
| kind | TEXT | `function`, `const`, `class`, `interface`, `type`, `enum` |
| line_start | INTEGER | Start line (1-based) |
| line_end | INTEGER | End line |
| signature | TEXT | Reconstructed signature (e.g. `usePermissions(): PermissionsContext`) |
| is_exported | INTEGER | 1 if exported |
| is_default_export | INTEGER | 1 if default export |
| Column | Type | Description |
| ----------------- | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| id | INTEGER PK | Auto-increment row id |
| file_path | TEXT FK | References `files(path)` ON DELETE CASCADE |
| name | TEXT | Symbol name |
| kind | TEXT | `function`, `const`, `class`, `interface`, `type`, `enum` |
| line_start | INTEGER | Start line (1-based) |
| line_end | INTEGER | End line |
| signature | TEXT | Reconstructed signature with generics and return types (e.g. `identity<T>(val): T`, `interface Repo<T> extends Iterable<T>`, `class Store<T> extends Base<T> implements IStore<T>`) |
| is_exported | INTEGER | 1 if exported |
| is_default_export | INTEGER | 1 if default export |
| members | TEXT | JSON array of enum members (NULL for non-enums). Each entry: `{"name":"…","value":"…"}` (value omitted for implicit-value enums) |
| doc_comment | TEXT | Leading JSDoc comment text (cleaned: `*` prefixes stripped, trimmed). NULL when absent. Preserves `@deprecated`, `@param`, etc. tags |
| value | TEXT | Literal value for `const` declarations (strings, numbers, booleans, `null`). NULL for non-literal or non-const symbols. Handles `as const` and simple template literals |
| parent_name | TEXT | Name of the enclosing symbol (class, function) for nested symbols. NULL for top-level (module scope). Class methods/properties point to their class |

### `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 |
| callee_name | TEXT | Name of the called function or `obj.method` for member calls |

Edges are deduped per file: if `foo` calls `bar` three times in the same file, only one row is stored. Module-level calls (outside any function) are excluded — only function-scoped calls are tracked.

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

| Column | Type | Description |
| ----------- | ---------- | --------------------------------------------------------- |
| id | INTEGER PK | Auto-increment row id |
| file_path | TEXT FK | References `files(path)` ON DELETE CASCADE |
| symbol_name | TEXT | Name of the parent interface or type alias |
| name | TEXT | Property or method name |
| type | TEXT | Type annotation string (e.g. `string`, `(key) => number`) |
| is_optional | INTEGER | 1 if `?` modifier present |
| is_readonly | INTEGER | 1 if `readonly` modifier present |

### `imports` — Import statements (`STRICT`)

Expand Down Expand Up @@ -283,7 +310,8 @@ All tables have covering indexes tuned for AI agent query patterns. See [Coverin

Uses the Rust-based `oxc-parser` via NAPI bindings to parse TypeScript/TSX/JS/JSX files into an AST. Extracts:

- **Symbols**: Functions, arrow functions, classes, interfaces, type aliases, enums — with reconstructed signatures
- **Symbols**: Functions, arrow functions, classes, interfaces, type aliases, enums — with reconstructed signatures including generic type parameters (e.g. `<T extends Base>`), return type annotations (e.g. `: Promise<void>`), class/interface heritage (`extends`, `implements`)
- **Enum members**: String and numeric values for each member, stored as JSON (e.g. `[{"name":"Active","value":"active"}]`)
- **Imports**: All `import` statements with specifiers, source paths, and type-only flags
- **Exports**: Named exports, default exports, re-exports
- **Components**: React components detected via PascalCase name + (JSX return **or** hook usage). A PascalCase function in `.tsx`/`.jsx` that neither returns JSX nor calls hooks is indexed only as a symbol, not a component. Extracts props type and hooks used
Expand Down
2 changes: 2 additions & 0 deletions src/adapters/builtin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ function parseTsJs(ctx: ParseContext): ParsedFilePayload {
exports: data.exports,
components: data.components,
markers: data.markers,
typeMembers: data.typeMembers,
calls: data.calls,
};
}

Expand Down
2 changes: 2 additions & 0 deletions src/adapters/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ export type ParsedFilePayload = Pick<
| "exports"
| "components"
| "markers"
| "typeMembers"
| "calls"
| "cssVariables"
| "cssClasses"
| "cssKeyframes"
Expand Down
11 changes: 11 additions & 0 deletions src/application/index-engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import {
insertCssVariables,
insertCssClasses,
insertCssKeyframes,
insertTypeMembers,
insertCalls,
getAllFileHashes,
SCHEMA_VERSION,
type CodemapDatabase,
Expand Down Expand Up @@ -220,6 +222,10 @@ function insertParsedResults(
insertComponents(db, parsed.components);
}
if (parsed.markers?.length) insertMarkers(db, parsed.markers);
if (parsed.typeMembers?.length) {
insertTypeMembers(db, parsed.typeMembers);
}
if (parsed.calls?.length) insertCalls(db, parsed.calls);
}
} catch (err) {
console.error(
Expand All @@ -246,6 +252,8 @@ export function fetchTableStats(db: CodemapDatabase): IndexTableStats {
(SELECT COUNT(*) FROM components) as components,
(SELECT COUNT(*) FROM dependencies) as dependencies,
(SELECT COUNT(*) FROM markers) as markers,
(SELECT COUNT(*) FROM type_members) as type_members,
(SELECT COUNT(*) FROM calls) as calls,
(SELECT COUNT(*) FROM css_variables) as css_vars,
(SELECT COUNT(*) FROM css_classes) as css_classes,
(SELECT COUNT(*) FROM css_keyframes) as css_keyframes`,
Expand Down Expand Up @@ -360,6 +368,9 @@ export async function indexFiles(
if (data.exports.length) insertExports(db, data.exports);
if (data.components.length) insertComponents(db, data.components);
if (data.markers.length) insertMarkers(db, data.markers);
if (data.typeMembers.length)
insertTypeMembers(db, data.typeMembers);
if (data.calls.length) insertCalls(db, data.calls);
}
} catch (err) {
console.error(
Expand Down
2 changes: 2 additions & 0 deletions src/application/run-index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ function emptyStats(): IndexTableStats {
components: 0,
dependencies: 0,
markers: 0,
type_members: 0,
calls: 0,
css_vars: 0,
css_classes: 0,
css_keyframes: 0,
Expand Down
2 changes: 2 additions & 0 deletions src/application/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ export interface IndexTableStats extends Record<string, number> {
components: number;
dependencies: number;
markers: number;
type_members: number;
calls: number;
css_vars: number;
css_classes: number;
css_keyframes: number;
Expand Down
Loading
Loading