Skip to content

Commit f121d84

Browse files
feat(recipes): ship unimported-exports (research note § 1.2) (#66)
Third recipe from research note § 1 capability inventory. Surfaces exports with no detectable import — useful starting candidate list for "what's unused?" but 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. Future recipe with recursive CTE walking re_export_source closes the gap (research note § 1.2 caveat). 2. Unresolved imports ignored — imports.resolved_path IS NULL (tsconfig path aliases the resolver couldn't resolve; external packages) — 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). Override in project- local recipe to include them. SQL approach: - direct_uses CTE: imports.resolved_path matches export's file AND specifiers JSON contains the export's name (or "*" namespace) - Filter: not in direct_uses, is_default = 0, kind != 're-export' Action template: review-for-deletion (auto_fixable: false). Agents must verify before deletion. Verification: - Recipe loads cleanly via --recipes-json - bun run check passes (format, typecheck, all 23 golden queries) Rule 10 lockstep: both templates/agents/ AND .agents/ codemap rule + skill gain trigger row + quick-reference row + recipe-id list entry. Patch changeset: pre-v1; additive bundled recipe; no schema bump.
1 parent 1b7a5c7 commit f121d84

7 files changed

Lines changed: 83 additions & 2 deletions

File tree

.agents/rules/codemap.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ If the question looks like any of these → use the index:
116116
| "Worst-covered exported functions" | `--recipe worst-covered-exports` |
117117
| "Which components touch deprecated APIs?" | `--recipe components-touching-deprecated` |
118118
| "What's risky to refactor right now?" | `--recipe refactor-risk-ranking` |
119+
| "Which exports has nobody imported?" | `--recipe unimported-exports` |
119120

120121
## When Grep / Read IS appropriate
121122

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

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

.agents/skills/codemap/SKILL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ After **`bun run build`**, **`node dist/index.mjs query …`** or a linked **`co
3434

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

37-
**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`**.
37+
**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`**.
3838

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
"@stainless-code/codemap": patch
3+
---
4+
5+
feat(recipes): ship `unimported-exports` recipe (research note § 1.2)
6+
7+
Surfaces exports that have no detectable import. Useful as a starting candidate list for "what's unused?" — explicitly **NOT** a "safe to delete" list.
8+
9+
V1 limitations documented in the recipe `.md`:
10+
11+
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.
12+
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.
13+
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.
14+
15+
Action template `review-for-deletion` (auto_fixable: false) — agents flag for manual verification before deletion.
16+
17+
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.

templates/agents/rules/codemap.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ If the question looks like any of these → use the index:
125125
| "Worst-covered exported functions" | `--recipe worst-covered-exports` |
126126
| "Which components touch deprecated APIs?" | `--recipe components-touching-deprecated` |
127127
| "What's risky to refactor right now?" | `--recipe refactor-risk-ranking` |
128+
| "Which exports has nobody imported?" | `--recipe unimported-exports` |
128129

129130
## When Grep / Read IS appropriate
130131

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

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

templates/agents/skills/codemap/SKILL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ Use **`codemap --root /path/to/project`** (or **`CODEMAP_ROOT`**) to index anoth
3434

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

37-
**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`**.
37+
**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`**.
3838

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

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
---
2+
actions:
3+
- type: review-for-deletion
4+
auto_fixable: false
5+
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."
6+
---
7+
8+
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.
9+
10+
## V1 limitations (false-positive classes)
11+
12+
The recipe ships intentionally simple. Three known classes of false positive:
13+
14+
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.
15+
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.
16+
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.
17+
18+
## What's NOT covered (orthogonal recipes)
19+
20+
- **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").
21+
- **Component-touching-deprecated** style cross-checks — not applicable here; this recipe is about EXPORTS, not symbol references inside files.
22+
23+
## Tuning axes for project-local overrides
24+
25+
- **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.
26+
- **Filter to a directory** — add `AND e.file_path LIKE 'src/lib/%'` to scope the audit to a single owner / package.
27+
- **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).
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
-- Exports never directly imported (V1: resolved-path matching only).
2+
-- An export is "directly used" if any imports row's resolved_path matches its
3+
-- file AND the specifiers JSON contains its name (or "*" for namespace imports).
4+
--
5+
-- V1 limitations (documented in unimported-exports.md):
6+
-- 1. Re-export chains: if A re-exports `bar` from B, and consumers import `bar`
7+
-- from A, the recipe doesn't follow the chain — false positive on B.bar.
8+
-- Workaround: skip rows with `kind = 're-export'` or hand-check via
9+
-- `re_export_source` column.
10+
-- 2. Unresolved imports (`resolved_path IS NULL`, e.g. tsconfig path aliases
11+
-- that codemap's resolver can't resolve) get IGNORED — false positives if
12+
-- they actually reference an export.
13+
-- 3. Default exports skipped (often framework entry points like Next.js
14+
-- page.tsx, Storybook stories, vite.config.ts).
15+
WITH direct_uses AS (
16+
SELECT DISTINCT e.id
17+
FROM exports e
18+
JOIN imports i ON i.resolved_path = e.file_path
19+
CROSS JOIN json_each(i.specifiers) j
20+
WHERE j.value = e.name OR j.value = '*'
21+
)
22+
SELECT
23+
e.name,
24+
e.kind,
25+
e.file_path,
26+
e.is_default,
27+
e.re_export_source
28+
FROM exports e
29+
WHERE e.id NOT IN (SELECT id FROM direct_uses)
30+
AND e.is_default = 0
31+
AND e.kind != 're-export'
32+
ORDER BY e.file_path, e.name
33+
LIMIT 50

0 commit comments

Comments
 (0)