Skip to content

Commit 0cf3667

Browse files
committed
harden: CRAP consumer surfaces, docs contract, roadmap
Add coverage_source one-liners to served rule/skill, golden-queries and architecture contracts, roadmap checkbox, wave slice 2.4, and recipe precedence note (measured 0% beats graph tiers).
1 parent 3fc823a commit 0cf3667

8 files changed

Lines changed: 29 additions & 18 deletions

File tree

docs/architecture.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,8 @@ Three **mutually exclusive** CLI entry shapes; all converge on `applyDiffPayload
192192

193193
**Evidence columns (high-judgment recipes):** Some bundled recipes add optional **`reason`** and **`evidence_json`** TEXT columns on each result row — factual detection path for agents, not pass/fail verdicts. Contract: [golden-queries.md § Evidence columns](./golden-queries.md#evidence-columns-high-judgment-recipes).
194194

195+
**Coverage columns (CRAP recipes):** `high-crap-score` adds **`coverage_source`** and **`effective_coverage_pct`** — measured vs graph-estimated undertest signal. Contract: [golden-queries.md § Coverage columns](./golden-queries.md#coverage-columns-crap--enrichment-recipes).
196+
195197
**Recipes wiring:** **`src/application/recipes-loader.ts`** (pure transport-agnostic loader) + **`src/application/query-recipes.ts`** (cache + public API — `getQueryRecipeSql` / `getQueryRecipeActions` / `getQueryRecipeParams` / `listQueryRecipeIds` / `listQueryRecipeCatalog` / `getQueryRecipeCatalogEntry`, shared by CLI + MCP). Recipes live as file pairs: **`<id>.sql`** + optional **`<id>.md`**. The loader reads `templates/recipes/` (bundled, ships in npm package next to `templates/agents/`) and `<state-dir>/recipes/` (project-local — default `.codemap/recipes/`; honors `--state-dir` / `CODEMAP_STATE_DIR`; root-only resolution per the registry plan, no walk-up). Project recipes win on id collision; entries that override a bundled id carry **`shadows: true`** in the catalog so agents reading `codemap://recipes` at session start see when a recipe behaves differently from the documented bundled version. Per-row **`actions`** templates and recipe **`params`** declarations live in YAML frontmatter on each `<id>.md` — uniform shape across bundled + project. Param types are `string | number | boolean`; CLI passes values via repeatable `--params key=value[,key=value]`, MCP / HTTP pass nested `params: {key: value}` to `query_recipe`. Validation runs before SQL binding; missing / unknown / malformed params return the same `{error}` envelope as query failures. Hand-rolled YAML parser is scoped to block-list `actions:` and `params:` only (no `js-yaml` dep). Load-time validation rejects empty SQL and DML / DDL keywords (`INSERT` / `UPDATE` / `DELETE` / `DROP` / `CREATE` / `ALTER` / `ATTACH` / `DETACH` / `REPLACE` / `TRUNCATE` / `VACUUM` / `PRAGMA`) with recipe-aware error messages — defence in depth alongside the runtime `PRAGMA query_only=1` backstop in `query-engine.ts` (PR #35). `<state-dir>/index.db` is gitignored; `<state-dir>/recipes/` is NOT (verified via `git check-ignore`) — recipes are git-tracked source code authored for human review.
196198

197199
**Tool / resource handlers (transport-agnostic):** **`src/application/tool-handlers.ts`** + **`src/application/resource-handlers.ts`** — pure functions that take the args object an MCP tool / resource URI accepts and return a discriminated **`ToolResult`** (`{ok: true, format: 'json'|'sarif'|'annotations'|'mermaid'|'diff'|'diff-json'|'codeclimate'|'badge', payload}` — badge arm also carries `badgeStyle`; `{ok: false, error}`) or a **`ResourcePayload`** (`{mimeType, text}`). MCP and HTTP both wrap the same handlers — MCP translates to `{content: [{type: "text", text}]}`, HTTP translates to `(status, body)` with the right `Content-Type`. Engine layer untouched; transport changes don't ripple into the SQL.

docs/golden-queries.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@ Scenarios live in **`fixtures/golden/scenarios.json`** (Tier A) or optional **`s
7070

7171
Some bundled recipes add optional **`reason`** (TEXT) and **`evidence_json`** (TEXT, JSON array) columns on each row — factual detection path for agents, not engine verdicts. See [plans/evidence-chains-on-recipe-rows.md](./plans/evidence-chains-on-recipe-rows.md). Goldens assert these columns when the recipe ships evidence (`boundary-violations`, `deprecated-symbols`, `unimported-exports`).
7272

73+
### Coverage columns (CRAP / enrichment recipes)
74+
75+
`high-crap-score` adds **`coverage_source`** (`measured` \| `estimated`) and **`effective_coverage_pct`** on each row — measured when `coverage` has a matching symbol row after `ingest-coverage`; otherwise graph-estimated tiers from test reachability. Goldens assert `coverage_source` when the recipe ships coverage semantics (`high-crap-score`); measured override is covered by `scripts/high-crap-score-measured.test.mjs`.
76+
7377
---
7478

7579
## Status

docs/plans/agent-enrichment-wave.md

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,15 @@
3939

4040
## Plan 2 — Graph-estimated CRAP (`graph-estimated-crap.md`)
4141

42-
| Slice | Deliverable | Verify |
43-
| ------------------------- | --------------------------------------------------------------- | ----------------- |
44-
| **2.0 spike** | Reachability CTE on `fixtures/minimal` (script or ad-hoc query) | manual row counts |
45-
| **2.1 recipe** | `high-crap-score.sql` + `.md`; `scenarios.json` | `test:golden` |
46-
| **2.2 measured override** | golden with `ingest-coverage` setup | golden matrix |
47-
| **2.3 cross-link** | `high-complexity-untested.md` points at CRAP when no ingest | doc |
42+
| Slice | Deliverable | Verify |
43+
| ------------------------- | --------------------------------------------------------------- | ---------------------- |
44+
| **2.0 spike** | Reachability CTE on `fixtures/minimal` (script or ad-hoc query) | manual row counts |
45+
| **2.1 recipe** | `high-crap-score.sql` + `.md`; `scenarios.json` | `test:golden` |
46+
| **2.2 measured override** | `scripts/high-crap-score-measured.test.mjs` | `bun run test:scripts` |
47+
| **2.3 cross-link** | `high-complexity-untested.md` points at CRAP when no ingest | doc |
48+
| **2.4 agent surface** | `rule/00-full.md` + `skill/10-recipes-context.md` one-liners | consumer check |
4849

49-
**Grill before 2.1 if spike ambiguous:** Q1 type-only imports in walk (default: value edges only); Q2 recipe id `high-crap-score`.
50+
**Locked:** Q1 value edges only (`dependencies` — type-only omitted at index); Q2 recipe id `high-crap-score`.
5051

5152
---
5253

@@ -91,4 +92,4 @@ Each PR: `harden-pr full` → merge. Do not batch plans 1–4 into one PR.
9192

9293
## Current slice
9394

94-
**Active:** Plan 2 **in flight** on `feat/high-crap-score`slices **2.0–2.3** (`graph-estimated-crap.md`); PR **#C** when complete.
95+
**Active:** Plan 2 complete on `feat/high-crap-score`open **PR #C**, then Plan 3 slice **3.1** (`coverage-deletion-confidence.md`).

docs/plans/graph-estimated-crap.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Graph-estimated CRAP score — plan
22

3-
> **Status:** open · **Priority:** P2 · **Effort:** M (~2 weeks)
3+
> **Status:** shipped (PR #C) · **Priority:** P2 · **Effort:** M (~2 weeks)
44
>
55
> **Motivator:** CRAP ranks **complex and undertested** functions. Codemap has `symbols.complexity` + ingested `coverage`, but `high-complexity-untested` is **misleading without ingest** (`COALESCE(coverage_pct, 0)` treats missing as 0%). Graph-estimated tiers (85/40/0%) from test reachability when measured coverage is absent.
66
>
@@ -42,7 +42,7 @@ recipe high-crap-score (SQL only)
4242
| 40% | 4 | `deeplyNested`, `relay`, … — `complexity-fixture.ts` reachable from test |
4343
| 0% | 39 | `createClient`, `get`, … — not dependency-reachable from tests |
4444

45-
Reachability walk: `test_suites` + `*.test.*` / `*.spec.*` globs → recursive `dependencies` fan-out (value edges only).
45+
Reachability walk: `test_suites` + `*.test.*` / `*.spec.*` globs → recursive `dependencies` fan-out (value edges only — type-only imports never enter `dependencies` at index time).
4646

4747
### Tracer bullet (slice 2.1)
4848

@@ -126,11 +126,11 @@ bun test scripts/query-golden-coverage-matrix.test.mjs # after golden scenario
126126

127127
## Open decisions (impl PR)
128128

129-
| # | Question |
130-
| --- | ------------------------------------------------------------------------------------------------ |
131-
| Q1 | Include type-only imports in reachability walk? (default: value edges only, mirror import graph) |
132-
| Q2 | Recipe id: `high-crap-score` vs `crap-score`? |
133-
| Q3 | Materialised column at index time vs recipe-only — measure CTE cost on self-index first. |
129+
| # | Question | Lock (wave 2026-06) |
130+
| --- | ------------------------------------------------------------------------------------------------ | ------------------------------------- |
131+
| Q1 | Include type-only imports in reachability walk? (default: value edges only, mirror import graph) | **Value edges**`dependencies` only |
132+
| Q2 | Recipe id: `high-crap-score` vs `crap-score`? | **`high-crap-score`** |
133+
| Q3 | Materialised column at index time vs recipe-only — measure CTE cost on self-index first. | **Recipe-only** (defer v2) |
134134

135135
---
136136

docs/roadmap.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ Predicate-as-API only — enrich row shape and audit deltas; no standalone pass/
8989
- [ ] **Audit delta attribution** — on `audit --base <ref>` (and matching MCP/HTTP audit), tag each `added` row with **`attribution: introduced | inherited`** via stable finding keys (`requiredColumns` → deterministic key) diffed against the sha-keyed audit-cache index at the merge base. Per-delta `summary` counts (`added_introduced`, `added_inherited`) optional when `summary: true`. Reuses shipped `audit-worktree` / `git archive` cache — no new verdict primitive ([Moat A](./roadmap.md#moats-load-bearing)). Complements deferred **`codemap audit` verdict + thresholds** (consumer filters `introduced` via `jq`). Plan: [`plans/audit-delta-attribution.md`](./plans/audit-delta-attribution.md). Effort: M.
9090
- [ ] **Evidence chains on recipe rows** — extend high-judgment recipe SQL with standard columns `reason` (short detection code + clause) and optional `evidence_json` (bounded hop array): e.g. `unimported-exports``re_export_chains` summary / unresolved-import blind-spot hint; `boundary-violations` → matched deny rule; `deprecated-symbols` → top caller sites from `calls` / `references`. Phased v1 on three recipes; complements frontmatter `actions[]` — agents cite evidence before `apply` / manual edits ([Moat A](./roadmap.md#moats-load-bearing)). Plan: [`plans/evidence-chains-on-recipe-rows.md`](./plans/evidence-chains-on-recipe-rows.md). Effort: M–L.
9191
- [ ] **Tiered lookup fast paths**`show` / exact-name recipe paths hit covering indexes first; document latency expectations in MCP tool descriptions. FTS and broad scans remain explicit fallbacks. Effort: S–M.
92-
- [ ] **Graph-estimated CRAP recipe** — bundled `high-crap-score`: CRAP = `CC² × (1 - coverage/100)³ + CC` using `symbols.complexity`; **measured** `coverage` when ingested, else **graph-estimated** tiers (85% / 40% / 0% from test-file reachability over `dependencies` / `calls` / `test_suites`). Rows expose `coverage_source: measured | estimated`. Complements `high-complexity-untested` when no coverage file exists. Plan: [`plans/graph-estimated-crap.md`](./plans/graph-estimated-crap.md). Effort: M.
92+
- [x] **Graph-estimated CRAP recipe** — bundled `high-crap-score`: CRAP = `CC² × (1 - coverage/100)³ + CC` using `symbols.complexity`; **measured** `coverage` when ingested, else **graph-estimated** tiers (85% / 40% / 0% from test-file reachability over `dependencies` / `calls` / `test_suites`). Rows expose `coverage_source: measured | estimated`. Complements `high-complexity-untested` when no coverage file exists. Plan: [`plans/graph-estimated-crap.md`](./plans/graph-estimated-crap.md). Effort: M.
9393
- [ ] **Coverage-confirmed dead recipe** — bundled `coverage-confirmed-dead`: JOIN static dead-code predicate (uncalled exports, suppression-aware) with ingested `coverage` — rows carry `confidence: high` when callers = 0 and `coverage_pct = 0`, `medium` when coverage not ingested. Predicate columns only, no verdict primitive ([Moat A](./roadmap.md#moats-load-bearing)). Plan: [`plans/coverage-deletion-confidence.md`](./plans/coverage-deletion-confidence.md). Effort: L–M.
9494

9595
### Distribution & evaluation depth

templates/agent-content/rule/00-full.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ codemap query --recipes-json # canonical list of every bundled + p
2222

2323
**Evidence columns:** Some recipe rows (e.g. `boundary-violations`, `deprecated-symbols`, `unimported-exports`) add **`reason`** and **`evidence_json`** — factual detection path for agents, not pass/fail verdicts.
2424

25+
**Coverage columns:** `high-crap-score` rows add **`coverage_source`** (`measured` \| `estimated`) and **`effective_coverage_pct`** — measured when `ingest-coverage` has a symbol row; else graph tiers 85/40/0% from test reachability (heuristic, not execution).
26+
2527
## Trigger patterns
2628

2729
If the question matches any of these, use the index instead of grepping:
@@ -60,7 +62,8 @@ If the question matches any of these, use the index instead of grepping:
6062
| "Which exports has nobody imported?" | `--recipe unimported-exports` |
6163
| "Which components touch deprecated APIs?" | `--recipe components-touching-deprecated` |
6264
| "What's risky to refactor right now?" | `--recipe refactor-risk-ranking` |
63-
| "What's high-complexity AND undertested?" | `--recipe high-complexity-untested` |
65+
| "What's high-complexity AND undertested?" | `--recipe high-complexity-untested` (needs `ingest-coverage`; without ingest prefer `high-crap-score`) |
66+
| "Complex + undertested without coverage ingest?" | `--recipe high-crap-score` (graph-estimated tiers; `coverage_source: estimated`) |
6467
| "What's cognitively complex (nesting-heavy)?" | `--recipe high-cognitive-complexity` (default `min_score=15`; `--params min_score=20` to tighten) |
6568

6669
## Quick reference queries

templates/agent-content/skill/10-recipes-context.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ Replace placeholders (`'...'`) with your module path, file glob, or symbol name.
1717
- **`--baseline[=<name>]`** — diff the current result against the saved baseline. Output `{baseline:{...}, current_row_count, added: [...], removed: [...]}` (with `--json`) or a two-section terminal dump. Identity = per-row multiset equality (canonical `JSON.stringify` keyed frequency map; duplicates preserved). Pair with `--summary` for `{baseline:{...}, current_row_count, added: N, removed: N}`. **Mutually exclusive with `--group-by`.**
1818
- **`--baselines`** lists saved baselines (no `rows_json` payload); **`--drop-baseline <name>`** deletes one. Both reject every other flag — they're list-only / drop-only operations.
1919
- **Evidence columns** — high-judgment recipes (`boundary-violations`, `deprecated-symbols`, `unimported-exports`, …) may add **`reason`** and **`evidence_json`** on each row — factual detection path; parse before `apply` or deletion.
20+
- **Coverage columns**`high-crap-score` adds **`coverage_source`** (`measured` \| `estimated`) and **`effective_coverage_pct`**; `estimated` is graph reachability, not execution — prefer `ingest-coverage` before CI gates.
2021
- **Per-row recipe `actions`** — recipes that define an **`actions: [{type, auto_fixable?, description?, command?}]`** template append it to every row in **`--json`** output (recipe-only; ad-hoc SQL never carries actions). Rendered **`command`** lines substitute `{{param}}` from bound recipe params — param **names vary by recipe** (`old`/`new` on `rename-preview`; `old_source`/`new_source` on `migrate-import-source`; `symbol`/`replacement` on `migrate-deprecated`; see each `<id>.md` frontmatter). Under `--baseline`, actions attach to the **`added`** rows only (the rows the agent should act on). Inspect via **`--recipes-json`**.
2122
- **Boundary violations (config-driven)** — declare `boundaries: [{name, from_glob, to_glob, action?}]` in `.codemap/config.ts` and run `codemap query --recipe boundary-violations [--format sarif|codeclimate|badge]`. GitLab CI: `--format codeclimate`; README/CI summary: `--format badge`. The `action` field defaults to `"deny"` (the only shape v1 surfaces); rules are reconciled into the `boundary_rules` table on every index pass and joined against `dependencies` via SQLite `GLOB`.
2223
- **Project-local recipes** — drop **`<id>.sql`** (and optional **`<id>.md`** for description body, params, and actions) into **`<state-dir>/recipes/`** (default `.codemap/recipes/`; honors `--state-dir` / `CODEMAP_STATE_DIR`) to make team-internal SQL a first-class CLI verb. `--recipes-json` and the `codemap://recipes` MCP resource list project recipes alongside bundled ones with **`source: "bundled" | "project"`** discriminating them. Project recipes win on id collision; entries that override a bundled id carry **`shadows: true`** so agents reading the catalog at session start know when a recipe behaves differently from the documented bundled version. `<id>.md` supports YAML frontmatter for `params:` and per-row `actions:` — **block-list shape only** (loader's hand-rolled parser; no inline-flow `[{...}]`). Param types: `string | number | boolean`; pass values with `--params key=value[,key=value]` (repeatable; last value wins). Example: `codemap query --json --recipe find-symbol-by-kind --params kind=function,name_pattern=%Query%`. Validation: SQL is rejected at load time if it starts with DML/DDL (DELETE/DROP/UPDATE/etc.); params validate before SQL binding; runtime `PRAGMA query_only=1` is the parser-proof backstop. `<state-dir>/index.db` is gitignored; **`<state-dir>/recipes/` is NOT** — recipes are git-tracked source code authored for human review.

templates/recipes/high-crap-score.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ actions:
1515

1616
Ranks symbols by **CRAP score**`CC² × (1 - effective_coverage/100)³ + CC` where `CC = symbols.complexity`.
1717

18-
**Coverage precedence:** ingested `coverage` rows win (`coverage_source: measured`). Otherwise graph-estimated tiers (`coverage_source: estimated`):
18+
**Coverage precedence:** ingested `coverage` rows win (`coverage_source: measured`) — including **0% measured**, which overrides graph tiers even when tests reference the symbol. Otherwise graph-estimated tiers (`coverage_source: estimated`) via value-only `dependencies` fan-out (type-only imports are excluded at index time):
1919

2020
| Tier | When |
2121
| ------- | --------------------------------------------------------------------------------------------- |

0 commit comments

Comments
 (0)