Skip to content

Commit 386ffa6

Browse files
feat(mcp): MCP/HTTP affected tool (Phase 2) (#133)
* feat(mcp): add affected tool for MCP and HTTP Extract shared affected-engine from cmd-affected; register affected on MCP/HTTP with paths/changed_since/test_glob/max_depth. Document in mcp-instructions and skill; include in CODEMAP_MCP_TOOLS allowlist. * fix(mcp): address PR review findings for affected tool Move tryRecordRecipeRun to handleAffected/runAffectedCmd (recipe_recency boundary). Align max_depth integer validation, HTTP 400 vs 500, agent changed_since error labels. Expand tests and architecture/docs updates. * fix(mcp): tighten affected review nits — integer params and recency tests Require integer coercion for recipe number params, document recency skip on empty paths, and add MCP/HTTP/CLI parity tests for max_depth validation and recipe_recency writes.
1 parent 8a1de53 commit 386ffa6

23 files changed

Lines changed: 937 additions & 161 deletions

.changeset/mcp-affected-tool.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"codemap": patch
3+
---
4+
5+
Add MCP/HTTP `affected` tool — same preprocessor as `codemap affected`, composes the `affected-tests` recipe. Respects `CODEMAP_MCP_TOOLS` allowlist.

.codemap/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,6 @@
33
index.db
44
index.db-shm
55
index.db-wal
6+
index.lock
7+
errors.log
68
audit-cache/

docs/architecture.md

Lines changed: 14 additions & 11 deletions
Large diffs are not rendered by default.

docs/glossary.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -470,7 +470,7 @@ Boolean flag on a project-local recipe entry that has the same `id` as a bundled
470470

471471
### `recipe_recency` (table) / recipe recency / `recipeRecency: false`
472472

473-
Per-recipe `last_run_at` (epoch ms) + `run_count` for agent-host ranking — surfaces inline on every `--recipes-json` entry and the matching `codemap://recipes` / `codemap://recipes/{id}` resources (live read every call; the resource cache was dropped to avoid freezing recency at first-read for the server-process lifetime). Counts only successful recipe runs; failed runs / param-validation rejections / SQL errors don't write. Default ON; opt-out via `.codemap/config` `recipeRecency: false` (short-circuits before any DB write — no rows ever land). 90-day rolling window enforced eagerly on the write path (single transactional `DELETE` + `INSERT … ON CONFLICT` inside `recordRecipeRun`); reads filter at SELECT, never mutate. Local-only — no upload primitive (resists telemetry-creep PRs by construction). Two write sites — `handleQueryRecipe` in `application/tool-handlers.ts` (covers MCP + HTTP) and `runQueryCmd` in `cli/cmd-query.ts` (CLI) — both call `tryRecordRecipeRun` (the failure-isolated wrapper around `recordRecipeRun`) from `application/recipe-recency.ts`. Failure-isolated: a recency-write throw NEVER blocks the recipe response (warning to stderr unless `quiet`). Schema: see [architecture.md § `recipe_recency`](./architecture.md#recipe_recency--per-recipe-last-run--run-count-user-data-strict-without-rowid).
473+
Per-recipe `last_run_at` (epoch ms) + `run_count` for agent-host ranking — surfaces inline on every `--recipes-json` entry and the matching `codemap://recipes` / `codemap://recipes/{id}` resources (live read every call; the resource cache was dropped to avoid freezing recency at first-read for the server-process lifetime). Counts only successful recipe runs; failed runs / param-validation rejections / SQL errors don't write. Default ON; opt-out via `.codemap/config` `recipeRecency: false` (short-circuits before any DB write — no rows ever land). 90-day rolling window enforced eagerly on the write path (single transactional `DELETE` + `INSERT … ON CONFLICT` inside `recordRecipeRun`); reads filter at SELECT, never mutate. Local-only — no upload primitive (resists telemetry-creep PRs by construction). Write sites — `handleQueryRecipe` + `handleAffected` in `application/tool-handlers.ts` (MCP + HTTP) and `runQueryCmd` in `cli/cmd-query.ts` + `runAffectedCmd` in `cli/cmd-affected.ts` (CLI) — each calls `tryRecordRecipeRun` (the failure-isolated wrapper around `recordRecipeRun`) from `application/recipe-recency.ts`. Failure-isolated: a recency-write throw NEVER blocks the recipe response (warning to stderr unless `quiet`). Schema: see [architecture.md § `recipe_recency`](./architecture.md#recipe_recency--per-recipe-last-run--run-count-user-data-strict-without-rowid).
474474

475475
### research
476476

docs/plans/affected-tests-recipe.md

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
11
# Affected tests recipe — plan
22

3-
> **Status:** open · **Priority:** P1 · **Effort:** M (~1–2 weeks)
3+
> **Status:** shipped · **Priority:** P1 · **Effort:** M (~1–2 weeks)
44
>
55
> **Motivator:** CI can skip full test suites if only a subgraph changed. Codemap already has `dependencies` and `test_suites` — missing a recipe + CLI alias to list test files transitively impacted by changed sources.
66
>
7-
> **Roadmap:** [§ Backlog](../roadmap.md#backlog) (test-impact item) · [agent-surface-and-ops § P1](./agent-surface-and-ops.md#p1)
7+
> **Roadmap:** [§ Backlog](../roadmap.md#backlog) (test-impact item) · [agent-surface-and-ops § P1](./agent-surface-and-ops.md#p1) · **Shipped:** [#132](https://github.com/stainless-code/codemap/pull/132) (recipe + CLI), [#133](https://github.com/stainless-code/codemap/pull/133) (MCP/HTTP `affected` tool)
88
99
---
1010

1111
## Pre-locked decisions
1212

13-
| # | Decision | Source |
14-
| --- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------ |
15-
| L.1 | **Moat-A clean**`affected-tests` recipe satisfies the agent surface; **`query_recipe`** is the MCP/HTTP path. Optional dedicated CLI verb **`codemap affected`** for CI (`stdin` / git path discovery) — not a 6th outcome alias. | [Moat A](../roadmap.md#moats-load-bearing) |
16-
| L.2 | Algorithm: reverse BFS on `dependencies` from changed files → filter test paths via `test_suites.file_path` and configurable globs. | Uses existing substrate |
17-
| L.3 | **Stdin support** — accept changed paths from `git diff --name-only` (same ergonomics as CI scripts). | CLI ergonomics |
18-
| L.4 | Not a verdict — output is file paths only; CI composes exit policy. | Moat A |
13+
| # | Decision | Source |
14+
| --- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ |
15+
| L.1 | **Moat-A clean**`affected-tests` recipe is the substrate; **`query_recipe`** remains the Moat-A path. Optional **`codemap affected`** CLI + MCP/HTTP **`affected`** tool for ergonomics — not a 6th outcome alias. | [Moat A](../roadmap.md#moats-load-bearing) |
16+
| L.2 | Algorithm: reverse BFS on `dependencies` from changed files → filter test paths via `test_suites.file_path` and configurable globs. | Uses existing substrate |
17+
| L.3 | **Stdin support** — accept changed paths from `git diff --name-only` (same ergonomics as CI scripts). | CLI ergonomics |
18+
| L.4 | Not a verdict — output is file paths only; CI composes exit policy. | Moat A |
1919

2020
---
2121

@@ -26,8 +26,8 @@
2626
**Params (frontmatter):**
2727

2828
- `changed_files` — multiline or repeated (from `--params` or stdin preprocessor)
29-
- `test_glob` — default `**/*.{test,spec}.{ts,tsx,js,jsx}`
30-
- `max_depth` — optional cap on BFS
29+
- `test_glob`optional SQLite GLOB; when set, replaces default suffix globs (`test_suites` always included)
30+
- `max_depth` — optional non-negative integer BFS cap (default 50)
3131

3232
**SQL shape:**
3333

@@ -48,7 +48,9 @@ Dedicated `cmd-affected.ts` (not an outcome alias — 5-alias cap unchanged). Sh
4848

4949
## Agent surface (Moat A)
5050

51-
No dedicated MCP tool required — agents call **`query_recipe`** with `recipe: "affected-tests"` and `params.changed_files` (ASCII RS between paths when multiple). The recipe is the Moat-A substrate; the CLI verb is CI ergonomics only.
51+
**Substrate:** **`query_recipe`** with `recipe: "affected-tests"` and `params.changed_files` (ASCII RS between paths when multiple).
52+
53+
**Convenience surfaces (Phase 2):** MCP/HTTP **`affected`** (`paths?`, `changed_since?`, …) and CLI **`codemap affected`** — thin composers over the same engine + recipe. Moat-A reviewers still verify via `query --recipe affected-tests`.
5254

5355
---
5456

@@ -59,7 +61,13 @@ No dedicated MCP tool required — agents call **`query_recipe`** with `recipe:
5961
3. Document test-file conventions in recipe `.md`
6062
4. Optional GitHub Action input `mode: affected` in [github-marketplace-action](./github-marketplace-action.md) (follow-up)
6163

62-
**Out of scope:** dedicated MCP/HTTP `affected` tool (same outcome reachable via `query_recipe`; revisit only if agent eval shows friction).
64+
**Out of scope (v1):** ~~dedicated MCP/HTTP `affected` tool~~ — shipped Phase 2 follow-up (`affected` tool; same engine as CLI). `query_recipe` remains the Moat-A substrate.
65+
66+
---
67+
68+
## Phase 2 (shipped)
69+
70+
MCP/HTTP **`affected`**`{ paths?, changed_since?, test_glob?, max_depth? }` → shared `affected-engine``affected-tests` recipe. Documented in `mcp-instructions`; respects `CODEMAP_MCP_TOOLS` allowlist. [#133](https://github.com/stainless-code/codemap/pull/133).
6371

6472
---
6573

@@ -68,6 +76,7 @@ No dedicated MCP tool required — agents call **`query_recipe`** with `recipe:
6876
- [x] Recipe returns test file paths for a known fixture delta
6977
- [x] Stdin mode works in shell pipeline
7078
- [x] Documented in README + skill
79+
- [x] MCP/HTTP `affected` tool (Phase 2)
7180

7281
---
7382

docs/plans/agent-surface-delivery.md

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@
1010

1111
## Quick resume
1212

13-
| Next action | Detail |
14-
| -------------------- | ---------------------------------------------------------------------------------- |
15-
| **Review / merge** | PR 5 — affected tests ([#132](https://github.com/stainless-code/codemap/pull/132)) |
16-
| **Start next** | **PR 6** — MCP trace tools (`trace` / `explore` / `node`) |
17-
| **Do not start yet** | PR 9 (eval harness) until PR 8 |
13+
| Next action | Detail |
14+
| -------------------- | --------------------------------------------------------------------------- |
15+
| **Review / merge** | [#133](https://github.com/stainless-code/codemap/pull/133) — MCP `affected` |
16+
| **Start next** | **PR 6** — MCP trace tools (`trace` / `explore` / `node`) |
17+
| **Do not start yet** | PR 9 (eval harness) until PR 8 |
1818

1919
Update the table below when a PR merges or a new branch opens.
2020

@@ -35,15 +35,15 @@ Merge each PR to `main` directly. No long-lived integration branch (`feat/agent-
3535

3636
Max **3 parallel tracks** at once.
3737

38-
| PR | Plans | Status | Blocked by | Parallel with |
39-
| ----- | ----------------------------------------------------------------------------------------------------------------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------- | --------------------------------- |
40-
| **3** | [`index-lock-and-error-log`](./index-lock-and-error-log.md)[`parse-worker-hardening`](./parse-worker-hardening.md) (stack) | merged | [#129](https://github.com/stainless-code/codemap/pull/129), [#130](https://github.com/stainless-code/codemap/pull/130) | 4, 5 |
41-
| **4** | Recipe half of [`mcp-trace-explore-tools`](./mcp-trace-explore-tools.md) (`call-path`, `symbol-neighborhood` SQL + tests) | merged | [#131](https://github.com/stainless-code/codemap/pull/131) | 3, 5 |
42-
| **5** | [`affected-tests-recipe`](./affected-tests-recipe.md) | open | [#132](https://github.com/stainless-code/codemap/pull/132) | 3, 4 |
43-
| **6** | MCP half of trace (`trace` / `explore` / `node` tools) + update instructions | planned | PR 1, PR 4 ||
44-
| **7** | [`field-qualified-search`](./field-qualified-search.md) | planned | PR 1 | 4, 5 if `mcp-server.ts` untouched |
45-
| **8** | [`agents-init-mcp-wiring`](./agents-init-mcp-wiring.md) | planned | PR 1 | 3–5 |
46-
| **9** | [`agent-eval-harness`](./agent-eval-harness.md) | planned | PR 1, PR 8, allowlist | **last P1** |
38+
| PR | Plans | Status | Blocked by | Parallel with |
39+
| ----- | ---------------------------------------------------------------------------------------------------------------------------------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------- | --------------------------------- |
40+
| **3** | [`index-lock-and-error-log`](./index-lock-and-error-log.md)[`parse-worker-hardening`](./parse-worker-hardening.md) (stack) | merged | [#129](https://github.com/stainless-code/codemap/pull/129), [#130](https://github.com/stainless-code/codemap/pull/130) | 4, 5 |
41+
| **4** | Recipe half of [`mcp-trace-explore-tools`](./mcp-trace-explore-tools.md) (`call-path`, `symbol-neighborhood` SQL + tests) | merged | [#131](https://github.com/stainless-code/codemap/pull/131) | 3, 5 |
42+
| **5** | [`affected-tests-recipe`](./affected-tests-recipe.md) (+ Phase 2 MCP `affected` in [#133](https://github.com/stainless-code/codemap/pull/133)) | merged | [#132](https://github.com/stainless-code/codemap/pull/132), [#133](https://github.com/stainless-code/codemap/pull/133) | 3, 4 |
43+
| **6** | MCP half of trace (`trace` / `explore` / `node` tools) + update instructions | planned | PR 1, PR 4 ||
44+
| **7** | [`field-qualified-search`](./field-qualified-search.md) | planned | PR 1 | 4, 5 if `mcp-server.ts` untouched |
45+
| **8** | [`agents-init-mcp-wiring`](./agents-init-mcp-wiring.md) | planned | PR 1 | 3–5 |
46+
| **9** | [`agent-eval-harness`](./agent-eval-harness.md) | planned | PR 1, PR 8, allowlist | **last P1** |
4747

4848
**Parallelization constraints**
4949

docs/roadmap.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ Prioritized agent & indexing ops queue (2026-05). Index: [`plans/agent-surface-a
6767

6868
- [ ] **MCP trace / explore / node** — recipe twins + thin MCP composers. Plan: [`plans/mcp-trace-explore-tools.md`](./plans/mcp-trace-explore-tools.md). Effort: M.
6969
- [ ] **Agents init MCP wiring**`agents init --mcp` + permissions. Plan: [`plans/agents-init-mcp-wiring.md`](./plans/agents-init-mcp-wiring.md). Effort: M.
70-
- [ ] **Affected tests recipe** — dep-graph test selection + stdin. Plan: [`plans/affected-tests-recipe.md`](./plans/affected-tests-recipe.md). Effort: M.
70+
- [x] **Affected tests recipe** — dep-graph test selection + stdin + MCP `affected` tool. Plan: [`plans/affected-tests-recipe.md`](./plans/affected-tests-recipe.md). Shipped #132 + #133.
7171
- [ ] **Index lock + error log** — cross-process lock, `unlock`, `errors.log`. Plan: [`plans/index-lock-and-error-log.md`](./plans/index-lock-and-error-log.md). Effort: M.
7272
- [ ] **Parse worker hardening** — per-file timeout + worker recycle. Plan: [`plans/parse-worker-hardening.md`](./plans/parse-worker-hardening.md). Effort: M.
7373
- [ ] **Field-qualified search**`kind:` / `path:` / `name:` → SQL. Plan: [`plans/field-qualified-search.md`](./plans/field-qualified-search.md). Effort: M.

0 commit comments

Comments
 (0)