Skip to content

Commit 2c3045d

Browse files
feat(boundaries): config-driven boundary-violations recipe (#72)
* feat(boundaries): config-driven boundary-violations recipe + .codemap/config sweep Ships the next cadence pick from the roadmap (research note § 1.5). - New `boundaries: [{name, from_glob, to_glob, action?}]` field on the Zod user-config schema; `action` defaults to `"deny"`. - New `boundary_rules` table (STRICT, WITHOUT ROWID) with CHECK constraint on action. Schema bump 8 → 9. - New `reconcileBoundaryRules` helper in src/db.ts; called from `runCodemapIndex` after `createSchema` so the table tracks resolved config exactly (config = single source of truth, table = denormalised lookup). - New runtime accessor `getBoundaryRules`. - Bundled `boundary-violations.{sql,md}` recipe joining `dependencies` × `boundary_rules` via SQLite GLOB. Recipe rows alias `from_path` to `file_path` so `--format sarif` / `annotations` light up automatically. - `codemap.config.example.json` gains a sample boundary. Lockstep + sweep: - docs/architecture.md § Schema gains `boundary_rules` subsection. - docs/glossary.md adds boundaries / boundary_rules / boundary-violations. - docs/roadmap.md drops the now-shipped backlog item per Rule 2. - README + agent rule + skill (templates/ + .agents/) document the shape. - Stale `codemap.config.ts` references swept across the codebase to `.codemap/config.{ts,js,json}` per the post-D11 layout (all docs, agent surfaces, JSDoc, CLI help). Historical changesets and the legacy-migration mention in src/config.ts intentionally preserved. Tests: - src/application/boundary-rules.test.ts: schema, reconciler idempotency, CHECK constraint, recipe SQL against synthetic dependency graph. - src/config.test.ts: default-`[]`, default action filling, unknown action rejection. Verification: bun run check (798 tests + golden scenarios) passes. * fix(boundaries): address PR #72 fact-checked review feedback Verified each CodeRabbit thread; applied real bugs (T3, T4) and tightened config-path wording (T1, T2). - T3 (MAJOR bug): full-rebuild path called `reconcileBoundaryRules` BEFORE `indexFiles`, but `indexFiles` runs `dropAll` internally on full rebuild, wiping the just-reconciled `boundary_rules`. Moved the reconciler into a `try/finally` around the existing branching so rules are written AFTER every code path returns. Regression test (`survives a full rebuild (reconciler runs after dropAll)`) confirms the fix. - T4: SQLite `GLOB *` is NOT filesystem-aware — it matches `/` like any other character. The recipe `.md` claim that `src/ui/*` "matches one level only" was wrong. Rewrote the GLOB section: documented that `*` crosses `/`, gave the `[^/]*` recipe for single-segment matches, and clarified there is no `**` in SQLite. - T1: `docs/plans/c9-plugin-layer.md` Q2 broadened from `.codemap/config.ts` to `.codemap/config.{ts,js,json}` to match the supported variants. - T2: `src/api.ts` `configFile` JSDoc broadened from `.ts | .json` to `.codemap/config.{ts,js,json}` so JS callers aren't misled. * fix(boundaries): atomic reconcile + sync schema version doc to 9 Two more fact-checked CodeRabbit points from PR #72: - T6: `reconcileBoundaryRules` was DELETE-then-INSERT without transactional protection. Zod doesn't dedupe `name`, but the schema's `name TEXT PRIMARY KEY` does — a duplicate would throw mid-loop, leaving the table partially populated (DELETE already ran). Wrapped the body in a SAVEPOINT so the prior good state is preserved on any insert failure. SAVEPOINT works inside or outside an open transaction, so callers don't need to coordinate. New regression test `rolls back on duplicate name — preserves prior good state (atomic)` confirms the prior row survives. - T5: `docs/architecture.md` § Schema header still read **8**. Bumped to **9** to match `SCHEMA_VERSION` (the `boundary_rules` row was added in the same PR but the version line was missed).
1 parent edee493 commit 2c3045d

25 files changed

Lines changed: 694 additions & 178 deletions

.agents/rules/codemap.md

Lines changed: 28 additions & 27 deletions
Large diffs are not rendered by default.

.agents/skills/codemap/SKILL.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ Replace placeholders (`'...'`) with your module path, file glob, or symbol name.
4545
- **`--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`.**
4646
- **`--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.
4747
- **Per-row recipe `actions`** — recipes that define an **`actions: [{type, auto_fixable?, description?}]`** template append it to every row in **`--json`** output (recipe-only; ad-hoc SQL never carries actions). Under `--baseline`, actions attach to the **`added`** rows only (the rows the agent should act on). Inspect via **`--recipes-json`**.
48+
- **Boundary violations (config-driven)** — declare `boundaries: [{name, from_glob, to_glob, action?}]` in `.codemap/config.ts` and run `bun src/index.ts query --recipe boundary-violations [--format sarif]`. 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`. See [`docs/architecture.md` § `boundary_rules`](../../../docs/architecture.md#boundary_rules--architecture-boundary-rules-config-derived-strict-without-rowid).
4849
- **Project-local recipes** — drop **`<id>.sql`** (and optional **`<id>.md`** for description body, params, and actions) into **`<state-dir>/recipes/`** (default `<projectRoot>/.codemap/recipes/`) 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: `bun src/index.ts 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. `.codemap/index.db` is gitignored; **`.codemap/recipes/` is NOT** — recipes are git-tracked source code authored for human review.
4950

5051
**Audit (`bun src/index.ts audit`)** — separate top-level command for structural-drift verdicts. Composes B.6 baselines into a per-delta `{head, deltas}` envelope; v1 ships `files` / `dependencies` / `deprecated`. Two snapshot-source shapes:
@@ -510,9 +511,9 @@ bun src/index.ts query --json "SELECT key, value FROM meta"
510511

511512
## Troubleshooting
512513

513-
| Problem | Solution |
514-
| -------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
515-
| Stale results after rebase | Run **`bun src/index.ts --full`** (or **`codemap --full`** when exercising the packaged CLI) |
516-
| Missing file in results | Check exclude / include globs in **`codemap.config.ts`**, **`codemap.config.json`**, or defaults in **`src/index.ts`** |
517-
| `resolved_path` is NULL | Import is an external package (not in project) |
518-
| Resolver errors | Verify `tsconfig.json` paths (or **`tsconfigPath`** in config) when resolving aliases |
514+
| Problem | Solution |
515+
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
516+
| Stale results after rebase | Run **`bun src/index.ts --full`** (or **`codemap --full`** when exercising the packaged CLI) |
517+
| Missing file in results | Check exclude / include globs in **`.codemap/config.ts`**, **`.codemap/config.json`**, or defaults in **`src/index.ts`** |
518+
| `resolved_path` is NULL | Import is an external package (not in project) |
519+
| Resolver errors | Verify `tsconfig.json` paths (or **`tsconfigPath`** in config) when resolving aliases |

.changeset/boundary-violations.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
---
2+
"@stainless-code/codemap": minor
3+
---
4+
5+
feat(boundaries): config-driven architecture-boundary rules + `boundary-violations` recipe
6+
7+
Adds the smallest substrate for first-class architecture boundary checks. Schema bump 8 → 9.
8+
9+
**Configure**
10+
11+
```ts
12+
import { defineConfig } from "@stainless-code/codemap";
13+
14+
export default defineConfig({
15+
boundaries: [
16+
{
17+
name: "ui-cant-touch-server",
18+
from_glob: "src/ui/**",
19+
to_glob: "src/server/**",
20+
},
21+
],
22+
});
23+
```
24+
25+
`action` defaults to `"deny"` (the only shape v1 surfaces); `"allow"` reserves the slot for future whitelist semantics.
26+
27+
**Substrate**
28+
29+
- New config field `boundaries: BoundaryRule[]` on the Zod user-config schema (`src/config.ts`); validated at config-load time.
30+
- New table `boundary_rules(name PK, from_glob, to_glob, action CHECK IN ('deny','allow'))` (`STRICT, WITHOUT ROWID`) — fully derived from config, dropped on `--full` / `SCHEMA_VERSION` rebuilds and re-filled by the next index pass.
31+
- New helper `reconcileBoundaryRules(db, rules)` in `src/db.ts`; called from `runCodemapIndex` after `createSchema` so the table tracks config exactly.
32+
- New runtime accessor `getBoundaryRules()`.
33+
34+
**Recipe**
35+
36+
`templates/recipes/boundary-violations.{sql,md}` joins `dependencies` × `boundary_rules` via SQLite `GLOB` and surfaces violating import edges as locatable rows. `--format sarif` and `--format annotations` light up automatically (the recipe aliases `dependencies.from_path` to `file_path`). Use as a CI gate:
37+
38+
```bash
39+
codemap query --recipe boundary-violations --format sarif > findings.sarif
40+
```
41+
42+
**Lockstep**
43+
44+
- `docs/architecture.md` § Schema gains a `boundary_rules` subsection.
45+
- `docs/glossary.md` adds `boundaries` / `boundary_rules` / `boundary-violations` entry.
46+
- `docs/roadmap.md § Backlog` removes the now-shipped item per Rule 2.
47+
- `templates/agents/rules/codemap.md`, `.agents/rules/codemap.md`, `templates/agents/skills/codemap/SKILL.md`, `.agents/skills/codemap/SKILL.md`, and `README.md` all document the new shape.
48+
49+
**Tests**
50+
51+
`src/application/boundary-rules.test.ts` covers schema creation, idempotent reconciliation, CHECK constraint, and the recipe SQL against a synthetic dependency graph. `src/config.test.ts` covers Zod validation including default-action filling and unknown-action rejection.

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,10 @@ codemap query --json --recipe fan-out-sample
8080
# Parametrised recipes validate params from <id>.md frontmatter before SQL binding.
8181
codemap query --json --recipe find-symbol-by-kind --params kind=function,name_pattern=%Query%
8282
codemap query --recipe rename-preview --params old=usePermissions,new=useAccess,kind=function --format diff
83+
# Architecture-boundary rules (declare in .codemap/config.ts):
84+
# boundaries: [{ name: "ui-cant-touch-server", from_glob: "src/ui/**", to_glob: "src/server/**" }]
85+
# Default action is "deny"; the table is reconciled from config on every index pass.
86+
codemap query --recipe boundary-violations --format sarif > boundary-findings.sarif
8387
# Counts only (skip the rows) — pairs well with --recipe for dashboards / agent context windows
8488
codemap query --json --summary -r deprecated-symbols
8589
# PR-scoped: filter result rows to those touching files changed since <ref>
@@ -128,7 +132,7 @@ codemap query --format mermaid 'SELECT from_path AS "from", to_path AS "to" FROM
128132
codemap query --format diff 'SELECT "README.md" AS file_path, 1 AS line_start, "# Codemap" AS before_pattern, "# Codemap Preview" AS after_pattern'
129133
codemap query --format diff-json 'SELECT "README.md" AS file_path, 1 AS line_start, "# Codemap" AS before_pattern, "# Codemap Preview" AS after_pattern' | jq '.summary'
130134
# --with-fts — opt-in FTS5 virtual table populated at index time. Default OFF (preserves
131-
# .codemap/index.db size); CLI flag wins over codemap.config.ts `fts5` field. Toggle change
135+
# .codemap/index.db size); CLI flag wins over .codemap/config.ts `fts5` field. Toggle change
132136
# auto-detects and forces a full rebuild so `source_fts` stays consistent.
133137
codemap --with-fts --full
134138
codemap query --recipe text-in-deprecated-functions # demonstrates FTS5 ⨯ symbols ⨯ coverage JOIN

codemap.config.example.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
11
{
22
"include": ["src/**/*.{ts,tsx,js,jsx}", "src/**/*.css", "**/*.{md,json}"],
3-
"excludeDirNames": ["node_modules", ".git", "dist", "build", ".output"]
3+
"excludeDirNames": ["node_modules", ".git", "dist", "build", ".output"],
4+
"boundaries": [
5+
{
6+
"name": "ui-cant-touch-server",
7+
"from_glob": "src/ui/*",
8+
"to_glob": "src/server/*"
9+
}
10+
]
411
}

0 commit comments

Comments
 (0)