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
2 changes: 2 additions & 0 deletions .agents/rules/codemap.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ If the question looks like any of these → use the index:
| "Worst-covered exported functions" | `--recipe worst-covered-exports` |
| "Which components touch deprecated APIs?" | `--recipe components-touching-deprecated` |
| "What's risky to refactor right now?" | `--recipe refactor-risk-ranking` |
| "Which exports has nobody imported?" | `--recipe unimported-exports` |

## When Grep / Read IS appropriate

Expand Down Expand Up @@ -167,6 +168,7 @@ bun src/index.ts query --json "<SQL>"
| Untested + dead exports | `bun src/index.ts query --json --recipe untested-and-dead` |
| Components touching `@deprecated` | `bun src/index.ts query --json --recipe components-touching-deprecated` |
| Refactor-risk-ranked files | `bun src/index.ts query --json --recipe refactor-risk-ranking` |
| Exports nobody imports | `bun src/index.ts query --json --recipe unimported-exports` |

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

Expand Down
2 changes: 1 addition & 1 deletion .agents/skills/codemap/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ After **`bun run build`**, **`node dist/index.mjs query …`** or a linked **`co

Replace placeholders (`'...'`) with your module path, file glob, or symbol name.

**CLI shortcuts:** **`bun src/index.ts query --json --recipe <id>`** runs bundled SQL (preferred for agents). **`bun src/index.ts query --recipe <id>`** without **`--json`** prints a table. **`bun src/index.ts query --recipes-json`** prints every bundled recipe (**`id`**, **`description`**, **`sql`**, optional **`actions`**) as JSON (no index / DB required). **`bun src/index.ts query --print-sql <id>`** prints one recipe’s SQL only. Ids include **`fan-out`**, **`fan-out-sample`** (**`GROUP_CONCAT`** samples), **`fan-out-sample-json`** (same, but **`json_group_array`** — needs SQLite JSON1), **`fan-in`**, **`index-summary`**, **`files-largest`**, **`components-by-hooks`**, **`components-touching-deprecated`** (UNION of hook + call paths to `@deprecated` symbols), **`markers-by-kind`**, **`deprecated-symbols`**, **`refactor-risk-ranking`** (per-file `(fan_in + 1) × (100 - avg_coverage_pct)`), **`visibility-tags`**, **`barrel-files`**, **`files-hashes`** — see **`bun src/index.ts query --help`**.
**CLI shortcuts:** **`bun src/index.ts query --json --recipe <id>`** runs bundled SQL (preferred for agents). **`bun src/index.ts query --recipe <id>`** without **`--json`** prints a table. **`bun src/index.ts query --recipes-json`** prints every bundled recipe (**`id`**, **`description`**, **`sql`**, optional **`actions`**) as JSON (no index / DB required). **`bun src/index.ts query --print-sql <id>`** prints one recipe’s SQL only. Ids include **`fan-out`**, **`fan-out-sample`** (**`GROUP_CONCAT`** samples), **`fan-out-sample-json`** (same, but **`json_group_array`** — needs SQLite JSON1), **`fan-in`**, **`index-summary`**, **`files-largest`**, **`components-by-hooks`**, **`components-touching-deprecated`** (UNION of hook + call paths to `@deprecated` symbols), **`markers-by-kind`**, **`deprecated-symbols`**, **`refactor-risk-ranking`** (per-file `(fan_in + 1) × (100 - avg_coverage_pct)`), **`unimported-exports`** (exports with no detectable importer; v1 doesn't follow re-export chains — see recipe `.md` for caveats), **`visibility-tags`**, **`barrel-files`**, **`files-hashes`** — see **`bun src/index.ts query --help`**.

**Output flags** (compose with **`--recipe`** or ad-hoc SQL):

Expand Down
17 changes: 17 additions & 0 deletions .changeset/unimported-exports-recipe.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
"@stainless-code/codemap": patch
---

feat(recipes): ship `unimported-exports` recipe (research note § 1.2)

Surfaces exports that have no detectable import. Useful as a starting candidate list for "what's unused?" — explicitly **NOT** a "safe to delete" list.

V1 limitations documented in the recipe `.md`:

1. **Re-export chains not followed** — false positives if A re-exports `bar` from B and consumers import `bar` from A. Tracked under research note § 1.2; future recipe with recursive CTE walking `re_export_source` will close the gap.
2. **Unresolved imports ignored** — when `imports.resolved_path IS NULL` (codemap's resolver couldn't resolve a `tsconfig.json` path alias or external package), those rows don't count toward "used" matching.
3. **Default exports skipped** — common framework entry points (Next.js `page.tsx`, Storybook stories, `vite.config.ts`) skipped to reduce noise. Override in project-local recipe if you want to include them.

Action template `review-for-deletion` (auto_fixable: false) — agents flag for manual verification before deletion.

Agent rule + skill lockstep updated per `docs/README.md` Rule 10 — both `templates/agents/` and `.agents/` codemap rule + skill gain trigger-pattern row, quick-reference row, and recipe-id list update.
2 changes: 2 additions & 0 deletions templates/agents/rules/codemap.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ If the question looks like any of these → use the index:
| "Worst-covered exported functions" | `--recipe worst-covered-exports` |
| "Which components touch deprecated APIs?" | `--recipe components-touching-deprecated` |
| "What's risky to refactor right now?" | `--recipe refactor-risk-ranking` |
| "Which exports has nobody imported?" | `--recipe unimported-exports` |

## When Grep / Read IS appropriate

Expand Down Expand Up @@ -176,6 +177,7 @@ codemap query --json "<SQL>"
| Untested + dead exports | `codemap query --json --recipe untested-and-dead` |
| Components touching `@deprecated` | `codemap query --json --recipe components-touching-deprecated` |
| Refactor-risk-ranked files | `codemap query --json --recipe refactor-risk-ranking` |
| Exports nobody imports | `codemap query --json --recipe unimported-exports` |

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

Expand Down
2 changes: 1 addition & 1 deletion templates/agents/skills/codemap/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ Use **`codemap --root /path/to/project`** (or **`CODEMAP_ROOT`**) to index anoth

Replace placeholders (`'...'`) with your module path, file glob, or symbol name.

**CLI shortcuts:** **`codemap query --json --recipe <id>`** runs bundled SQL (preferred for agents). **`codemap query --recipe <id>`** without **`--json`** prints a table. **`codemap query --recipes-json`** prints every bundled recipe (**`id`**, **`description`**, **`sql`**, optional **`actions`**) as JSON (no index / DB required). **`codemap query --print-sql <id>`** prints one recipe’s SQL only. Ids include **`fan-out`**, **`fan-out-sample`** (**`GROUP_CONCAT`** samples), **`fan-out-sample-json`** (same, but **`json_group_array`** — needs SQLite JSON1), **`fan-in`**, **`index-summary`**, **`files-largest`**, **`components-by-hooks`**, **`components-touching-deprecated`** (UNION of hook + call paths to `@deprecated` symbols), **`markers-by-kind`**, **`deprecated-symbols`**, **`refactor-risk-ranking`** (per-file `(fan_in + 1) × (100 - avg_coverage_pct)`), **`visibility-tags`**, **`barrel-files`**, **`files-hashes`** — see **`codemap query --help`**.
**CLI shortcuts:** **`codemap query --json --recipe <id>`** runs bundled SQL (preferred for agents). **`codemap query --recipe <id>`** without **`--json`** prints a table. **`codemap query --recipes-json`** prints every bundled recipe (**`id`**, **`description`**, **`sql`**, optional **`actions`**) as JSON (no index / DB required). **`codemap query --print-sql <id>`** prints one recipe’s SQL only. Ids include **`fan-out`**, **`fan-out-sample`** (**`GROUP_CONCAT`** samples), **`fan-out-sample-json`** (same, but **`json_group_array`** — needs SQLite JSON1), **`fan-in`**, **`index-summary`**, **`files-largest`**, **`components-by-hooks`**, **`components-touching-deprecated`** (UNION of hook + call paths to `@deprecated` symbols), **`markers-by-kind`**, **`deprecated-symbols`**, **`refactor-risk-ranking`** (per-file `(fan_in + 1) × (100 - avg_coverage_pct)`), **`unimported-exports`** (exports with no detectable importer; v1 doesn't follow re-export chains — see recipe `.md` for caveats), **`visibility-tags`**, **`barrel-files`**, **`files-hashes`** — see **`codemap query --help`**.

**Output flags** (compose with **`--recipe`** or ad-hoc SQL):

Expand Down
27 changes: 27 additions & 0 deletions templates/recipes/unimported-exports.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
actions:
- type: review-for-deletion
auto_fixable: false
description: "Export with no detectable import — candidate for deletion. VERIFY against the v1 caveats below before deleting; codemap's import-resolution doesn't follow re-export chains or `tsconfig.json` path aliases that the resolver can't resolve."
---

Exports that have no row in `imports` referencing their file AND name. Surfaces the **direct-use-only** subset of "unused exports" — useful as a starting candidate list, but **NEVER as a "safe to delete" list** without manual verification.

## V1 limitations (false-positive classes)

The recipe ships intentionally simple. Three known classes of false positive:

1. **Re-export chains** — codemap's `exports.re_export_source` column tracks barrel-style `export { foo } from './foo'` re-exports, but this v1 recipe **does not follow the chain**. If `src/index.ts` re-exports `bar` from `src/bar.ts`, and consumers `import { bar } from '~/'` (hitting `src/index.ts`), this recipe falsely flags `bar` in `src/bar.ts` as unimported. Workaround: filter out rows with `re_export_source IS NOT NULL` in a project-local override, OR cross-check against `barrel-files` recipe output.
2. **Unresolved imports** — when `imports.resolved_path IS NULL` (e.g. `tsconfig.json` path aliases codemap's resolver can't resolve, or external-package imports), those rows are ignored. If the unresolved import actually targets the export, it's a false positive. Codemap's resolver covers most TS / JS shapes; this is a corner case for unusual config.
3. **Default exports skipped** — `is_default = 0` filter. Default exports are commonly framework entry points (Next.js `page.tsx`, Storybook stories, `vite.config.ts`) that codemap doesn't model; flagging them produces high false-positive noise. To include them, drop the `AND e.is_default = 0` clause in a project-local override.

## What's NOT covered (orthogonal recipes)

- **Re-export chain handling** — wait for a future recipe with recursive CTE walking `re_export_source`. Tracked under research note § 1.2 ("re-export chains need a JOIN through `re_export_source` to avoid false positives").
- **Component-touching-deprecated** style cross-checks — not applicable here; this recipe is about EXPORTS, not symbol references inside files.

## Tuning axes for project-local overrides

- **Strip framework entry-point patterns** — add `AND e.file_path NOT LIKE '%/page.tsx' AND e.file_path NOT LIKE '%/layout.tsx' AND e.file_path NOT LIKE '%.stories.tsx'` to exclude common Next.js / Storybook conventions.
- **Filter to a directory** — add `AND e.file_path LIKE 'src/lib/%'` to scope the audit to a single owner / package.
- **Include re-exports** — drop `AND e.kind != 're-export'` if you want to flag stale re-exports too (e.g. a barrel that re-exports a symbol nobody imports anymore).
33 changes: 33 additions & 0 deletions templates/recipes/unimported-exports.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
-- Exports never directly imported (V1: resolved-path matching only).
-- An export is "directly used" if any imports row's resolved_path matches its
-- file AND the specifiers JSON contains its name (or "*" for namespace imports).
--
-- V1 limitations (documented in unimported-exports.md):
-- 1. Re-export chains: if A re-exports `bar` from B, and consumers import `bar`
-- from A, the recipe doesn't follow the chain — false positive on B.bar.
-- Workaround: skip rows with `kind = 're-export'` or hand-check via
-- `re_export_source` column.
-- 2. Unresolved imports (`resolved_path IS NULL`, e.g. tsconfig path aliases
-- that codemap's resolver can't resolve) get IGNORED — false positives if
-- they actually reference an export.
-- 3. Default exports skipped (often framework entry points like Next.js
-- page.tsx, Storybook stories, vite.config.ts).
WITH direct_uses AS (
SELECT DISTINCT e.id
FROM exports e
JOIN imports i ON i.resolved_path = e.file_path
CROSS JOIN json_each(i.specifiers) j
WHERE j.value = e.name OR j.value = '*'
)
SELECT
e.name,
e.kind,
e.file_path,
e.is_default,
e.re_export_source
FROM exports e
WHERE e.id NOT IN (SELECT id FROM direct_uses)
AND e.is_default = 0
AND e.kind != 're-export'
ORDER BY e.file_path, e.name
LIMIT 50
Loading