Skip to content

fix(query-recipes): make project recipes visible to the CLI parser#93

Merged
SutuSebastian merged 2 commits into
mainfrom
fix/project-recipes-pre-bootstrap
May 16, 2026
Merged

fix(query-recipes): make project recipes visible to the CLI parser#93
SutuSebastian merged 2 commits into
mainfrom
fix/project-recipes-pre-bootstrap

Conversation

@SutuSebastian
Copy link
Copy Markdown
Contributor

@SutuSebastian SutuSebastian commented May 15, 2026

Summary

Project recipes at <root>/.codemap/recipes/<id>.sql (an advertised feature since 0.6.x) were silently invisible to the CLI's --recipe <id> / --recipes-json / --print-sql <id> paths. The MCP and HTTP transports worked correctly throughout — they bootstrap before reaching the loader.

Root cause

parseQueryRest validates the recipe id by calling getQueryRecipeSql(id) (cmd-query.ts:461) BEFORE runQueryCmd calls bootstrapCodemap (cmd-query.ts:823). The loader's getRegistry() (query-recipes.ts:80) calls getProjectRoot(), which throws when _config is unset (pre-init); the throw was silently caught, projectDir became undefined, and the registry fell back to bundled-only. Result: parser fails with codemap: unknown recipe "<project-id>" even when the recipe is present on disk.

Fix

Two-line wiring:

  • src/application/query-recipes.ts — new setQueryRecipesProjectRoot(root) API. Caller-supplied root takes precedence over the runtime config (which isn't initialised yet during argv parse). Cache invalidates on change.
  • src/cli/main.ts — calls the setter once with the already-resolved root from parseBootstrapArgs, right after argv parse.

Single source of truth: the override is the same root bootstrapCodemap would resolve to. No parallel walk-up heuristic, no new env var.

Empirical proof in this repo

Added a real project recipe at .codemap/recipes/src-deprecated.sql that scopes the bundled deprecated-symbols audit to src/ only — useful for codemap's own deprecation lifecycle.

Before this PR:

$ bun src/index.ts query --recipe src-deprecated
codemap: unknown recipe "src-deprecated". Known recipes: barrel-chains, ...

After:

$ bun src/index.ts query --recipes-json | jq '[.[] | select(.source == "project")]'
[{"id": "src-deprecated", "source": "project"}]

$ bun src/index.ts query --recipe src-deprecated --json
[{"name":"buildMessageText", "kind":"function", "file_path":"src/application/output-formatters.ts", "line_start":67, ...}]

Regression tests

src/application/query-recipes.pre-bootstrap.test.ts — 3 tests exercising the override-only path (no initCodemap called):

  • Loads project recipes when only the override is set
  • Clears project recipes when override resets to undefined
  • Re-setting the override to a different root invalidates the cache

The dogfood recipe itself is a permanent live regression case — if this bug recurs, bun src/index.ts query --recipe src-deprecated from this repo will fail.

Migration

None required. Consumers with project recipes authored on 0.6.x–0.7.2 had them working via MCP / HTTP throughout. After upgrading, the CLI auto-picks them up.

Test plan

  • bun test — 958 pass, 0 fail (3 new + 955 existing).
  • bun run typecheck clean.
  • bun run lint clean.
  • bun run format:check clean.
  • End-to-end: bun src/index.ts query --recipe src-deprecated --json returns rows.
  • Cache invalidation: switching projectRoot mid-process produces the right registry.

Summary by CodeRabbit

  • New Features

    • Project recipes now resolve during early CLI argument parsing, so recipe-based queries work before full initialization.
    • Added a recipe to list/audit deprecated symbols limited to the source directory and documented how to run it.
  • Tests

    • Added regression tests covering early recipe discovery and cache/override invalidation behavior.

Review Change Stack

Project recipes at `<root>/.codemap/recipes/<id>.sql` were silently
invisible to the CLI's `--recipe <id>` / `--recipes-json` /
`--print-sql <id>` paths. The MCP and HTTP transports always worked —
they bootstrap before reaching the loader.

Root cause: `parseQueryRest` validates the recipe id by calling
`getQueryRecipeSql(id)` (`cmd-query.ts:461`) BEFORE `runQueryCmd`
calls `bootstrapCodemap` (`cmd-query.ts:823`). The loader's
`getRegistry()` (`query-recipes.ts:80`) calls `getProjectRoot()`,
which throws when `_config` is unset (pre-init), the throw is silently
caught, and `projectDir` becomes `undefined`. Result: bundled-only
registry at parse time, so the parser fails with `codemap: unknown
recipe "<project-id>"` even when the recipe is present on disk.

Fix:
- Add `setQueryRecipesProjectRoot(root)` to `query-recipes.ts`.
  Caller-supplied root takes precedence over the runtime config;
  cache invalidates on change.
- `main.ts` plumbs the resolved `root` (already returned by
  `parseBootstrapArgs`) into the override right after argv parse.

Single source of truth: same root `bootstrapCodemap` would resolve —
no parallel heuristic, no new env var.

Adds:
- `query-recipes.pre-bootstrap.test.ts` — 3 regression tests for
  the override-only discovery path (no `initCodemap` called).
- `.codemap/recipes/src-deprecated.sql` (+ `.md`) — dogfood project
  recipe that scopes the bundled `deprecated-symbols` audit to `src/`.
  Both useful in its own right AND a permanent live regression case.
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 15, 2026

🦋 Changeset detected

Latest commit: 6ecb862

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@stainless-code/codemap Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 15, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0fc3a3af-fd8a-420e-87ae-1c1afb3805ef

📥 Commits

Reviewing files that changed from the base of the PR and between 4ec39ba and 6ecb862.

📒 Files selected for processing (1)
  • src/application/query-recipes.pre-bootstrap.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/application/query-recipes.pre-bootstrap.test.ts

📝 Walkthrough

Walkthrough

This PR enables query recipe discovery during CLI argument parsing before bootstrapCodemap. It adds setQueryRecipesProjectRoot() to override the project root, wires it into main.ts early, validates the behavior with regression tests, and includes a src-scoped deprecated-symbols recipe.

Changes

Pre-bootstrap project recipe resolution

Layer / File(s) Summary
Project root override mechanism
src/application/query-recipes.ts
setQueryRecipesProjectRoot(root) stores an override and clears the recipe registry cache. Registry loading uses resolveCurrentProjectRoot() to prefer the override, falling back to getProjectRoot() with error handling. Test reset also clears the override.
CLI early integration
src/cli/main.ts
main() dynamically imports query-recipes and calls setQueryRecipesProjectRoot(root) after parseBootstrapArgs, ensuring project recipe discovery works during argument parsing before bootstrap.
Pre-bootstrap regression tests
src/application/query-recipes.pre-bootstrap.test.ts
Bun test suite validates override behavior: fixture recipes are discovered when override is set, resetting to undefined removes them, and switching override roots invalidates cached catalogs so previous recipes are not retained.
Codemap dogfood recipe
.codemap/recipes/src-deprecated.sql, .codemap/recipes/src-deprecated.md
SQL recipe queries deprecated symbols filtered to src/ production code only, with markdown documentation describing the scope and CLI invocation command.
Changeset documentation
.changeset/project-recipes-cli-pre-bootstrap.md
Documents the CLI pre-bootstrap project-recipe fix, the new setQueryRecipesProjectRoot(root) API, CLI wiring, regression tests, and dogfood recipe.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested labels

enhancement

Poem

🐰 A rabbit's ode to early discovery
I nudge the root before the bootstrap song,
Recipes wake early where they belong,
Caches cleared, none left behind,
Deprecated traces neatly confined,
Hooray — small fixes, hoppity and strong!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and clearly summarizes the main change: making project recipes visible to the CLI parser by fixing initialization order. It matches the core problem and solution described in the PR objectives.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/project-recipes-pre-bootstrap

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/application/query-recipes.pre-bootstrap.test.ts (1)

31-33: ⚡ Quick win

Use a per-test unique fixture recipe id to avoid future collisions.

Hardcoding "team-fixture" can become flaky if a bundled recipe ever adds the same id; these assertions use listQueryRecipeIds() and would then fail for the wrong reason.

Suggested patch
 describe("setQueryRecipesProjectRoot — pre-bootstrap CLI parse-phase path", () => {
   let projectRoot: string;
+  let fixtureId: string;

   beforeEach(() => {
     projectRoot = mkdtempSync(join(tmpdir(), "codemap-pre-bootstrap-"));
+    fixtureId = `team-fixture-${Date.now().toString(36)}`;
     mkdirSync(join(projectRoot, ".codemap", "recipes"), { recursive: true });
     writeFileSync(
-      join(projectRoot, ".codemap", "recipes", "team-fixture.sql"),
+      join(projectRoot, ".codemap", "recipes", `${fixtureId}.sql`),
       "SELECT 1 AS ok\n",
     );
     _resetRecipesCacheForTests();
   });
@@
   it("loads project recipes when only the override is set (no initCodemap)", () => {
     setQueryRecipesProjectRoot(projectRoot);
     const catalog = listQueryRecipeCatalog();
     const projectIds = catalog
       .filter((c) => c.source === "project")
       .map((c) => c.id);
-    expect(projectIds).toContain("team-fixture");
-    expect(getQueryRecipeSql("team-fixture")).toContain("SELECT 1");
+    expect(projectIds).toContain(fixtureId);
+    expect(getQueryRecipeSql(fixtureId)).toContain("SELECT 1");
   });
@@
   it("clears project recipes when override is reset to undefined", () => {
     setQueryRecipesProjectRoot(projectRoot);
-    expect(listQueryRecipeIds()).toContain("team-fixture");
+    expect(listQueryRecipeIds()).toContain(fixtureId);
     setQueryRecipesProjectRoot(undefined);
-    expect(listQueryRecipeIds()).not.toContain("team-fixture");
+    expect(listQueryRecipeIds()).not.toContain(fixtureId);
   });
@@
   it("re-setting the override to a new root invalidates the cache", () => {
     setQueryRecipesProjectRoot(projectRoot);
-    expect(listQueryRecipeIds()).toContain("team-fixture");
+    expect(listQueryRecipeIds()).toContain(fixtureId);
@@
       const ids = listQueryRecipeIds();
       expect(ids).toContain("other-fixture");
-      expect(ids).not.toContain("team-fixture");
+      expect(ids).not.toContain(fixtureId);
     } finally {
       rmSync(otherRoot, { recursive: true, force: true });
     }
   });
 });

Also applies to: 49-57, 62-75

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/application/query-recipes.pre-bootstrap.test.ts` around lines 31 - 33,
Replace the hardcoded recipe id "team-fixture" with a per-test unique id so
assertions against listQueryRecipeIds() can't collide with bundled recipes;
generate a unique recipeId (e.g., using crypto.randomUUID() or a timestamp
suffix), use that variable in the writeFileSync path/join call and in any
subsequent assertions or cleanup that reference the recipe id
(functions/identifiers to update include writeFileSync, join/projectRoot path
construction, and calls to listQueryRecipeIds()) so each test writes and asserts
against its own distinct recipe id.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/application/query-recipes.pre-bootstrap.test.ts`:
- Around line 31-33: Replace the hardcoded recipe id "team-fixture" with a
per-test unique id so assertions against listQueryRecipeIds() can't collide with
bundled recipes; generate a unique recipeId (e.g., using crypto.randomUUID() or
a timestamp suffix), use that variable in the writeFileSync path/join call and
in any subsequent assertions or cleanup that reference the recipe id
(functions/identifiers to update include writeFileSync, join/projectRoot path
construction, and calls to listQueryRecipeIds()) so each test writes and asserts
against its own distinct recipe id.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 385d6825-3d4b-446b-81d0-0df5ed2a3469

📥 Commits

Reviewing files that changed from the base of the PR and between d0f450a and 4ec39ba.

📒 Files selected for processing (6)
  • .changeset/project-recipes-cli-pre-bootstrap.md
  • .codemap/recipes/src-deprecated.md
  • .codemap/recipes/src-deprecated.sql
  • src/application/query-recipes.pre-bootstrap.test.ts
  • src/application/query-recipes.ts
  • src/cli/main.ts

Hardcoded `team-fixture` / `other-fixture` recipe ids could collide
with a future bundled recipe of the same name, making the assertions
fail for the wrong reason. Suffix both with a per-test timestamp+random
so each run uses distinct ids.

Addresses CodeRabbit nitpick on #93.
@SutuSebastian
Copy link
Copy Markdown
Contributor Author

Applied in 6ecb862 — both fixture ids (team-fixture + other-fixture) now get a per-test timestamp+random suffix in beforeEach. 3 tests still green.

@SutuSebastian SutuSebastian merged commit d92b917 into main May 16, 2026
11 checks passed
@SutuSebastian SutuSebastian deleted the fix/project-recipes-pre-bootstrap branch May 16, 2026 07:38
@github-actions github-actions Bot mentioned this pull request May 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant