diff --git a/.agents/lessons.md b/.agents/lessons.md index fae1b79..d9722cd 100644 --- a/.agents/lessons.md +++ b/.agents/lessons.md @@ -13,5 +13,5 @@ Each entry is a single bullet: `- **** — `. Newest entries at t - **backticks inside SQL or help-text template literals** — never put a literal backtick inside a `` `...` `` template-literal string. `db.ts` SQL DDL strings (multi-line CREATE TABLE templates) and `printQueryCmdHelp()` (multi-line help text) are both `` `...` `` template literals; an inner backtick — typically a Markdown-style code-fence around a flag like `` `--full` `` — terminates the literal early and the parser blows up several lines later with cryptic "expected `,` or `)`" errors. **Use plain prose in those strings** (`--full` not `` `--full` ``), or escape (`` \` ``) if you really need the character. Hit twice (B.7 + B.6 PR #30); the lesson is general — applies to any TS template literal that gets pasted prose later, not just SQL / help text. - **STOP-before-Grep applies to symbol lookups too** — `Grep` for symbol names like `printQueryResult`, `getCurrentCommit`, `dropAll` violates the [`codemap` rule](rules/codemap.md). The codemap query `SELECT file_path, line_start FROM symbols WHERE name = ''` answers it faster and without scanning. Reach for `Grep` only when the question is content-shaped (regex over file bodies, finding pattern usages inside function bodies, etc.) — not when it's "where is X defined / who calls X / what does file Y export." This was a PR #30 self-correction. - **PR / issue / comment bodies always go through a temp file** — never pass markdown bodies via shell heredoc to `gh pr create --body "$(cat <<'EOF'…)"` / `gh pr edit --body …` / `gh pr comment --body …` / `gh issue create --body …` / `gh api` `--field body=…`. Backticks inside the heredoc (every code span and code fence) get shell-escaped to `\`` and render literally on GitHub — every recipe id, file path, flag, SQL fragment, and code fence in the rendered body comes out as `\`coverage\``instead of`coverage`. Pattern: write the body to a temp file (`Write`to`/tmp/pr--body.md`), pass `--body-file /tmp/pr--body.md`, then delete the temp file. Cost is one extra tool call; saves redoing every PR body that has more than a few backticks. Hit on PR #57 — final body was a wall of `\`` artifacts until rewritten via temp file. -- **Never commit absolute local user paths** — no `/Users//…`, `/home//…`, `~/…`, or `file:///` URIs in any tracked doc, code, comment, or PR body. Reasons: (1) leaks the maintainer's directory structure / username to public mirrors; (2) every other contributor's paths differ — the reference is dead on their machine; (3) a `git clone` of someone else's machine isn't a fact we can cite as a "source for deep-dives" — public upstream URLs are. Pattern: cite `https://github.com//` (with optional `/tree//`) for upstream sources; use repo-relative paths (`docs/foo.md`, `src/bar.ts`) for in-tree references. Hit on PR #58 first draft — referenced the local fallow clone path in the research note before the user caught it. +- **Never commit absolute local user paths** — no `/Users//…`, `/home//…`, `~/…`, or `file:///` URIs in any tracked doc, code, comment, or PR body. Reasons: (1) leaks the maintainer's directory structure / username to public mirrors; (2) every other contributor's paths differ — the reference is dead on their machine; (3) a `git clone` of someone else's machine isn't a fact we can cite as a "source for deep-dives" — public upstream URLs are. Pattern: cite `https://github.com//` (with optional `/tree//`) for upstream sources; use repo-relative paths (`docs/foo.md`, `src/bar.ts`) for in-tree references. Hit on PR #58 first draft — referenced a local peer-repo clone path in a research note before the user caught it. - **Prescriptive research notes pin every concrete claim before recommending a ship sequence** — when a research/plan-shape doc proposes work (effort estimates, capability inventories, "we already do X" framing), every concrete claim needs a `file:line` / `codemap query` / `rg` / `--recipes-json` reference a reviewer can re-run. Reasoning-from-substrate intuition without pinning ships errors: "the AST walker already counts nodes" / "fan-in detects orphans" / "the `re_export_source` column doesn't exist" — all real errors caught on PR #58 by triangulating against the codebase. Don't ship a peer / parallel "descriptive baseline" doc to triangulate against (Rule 1 violation — it duplicates `architecture.md` / `db.ts` / `--recipes-json`); instead, either (a) pin claims in the prescriptive doc itself, or (b) self-audit by re-running every claim against the canonical home before committing. Either path beats the "dual descriptive + prescriptive doc" pattern on docs-governance grounds. diff --git a/.agents/rules/codemap.md b/.agents/rules/codemap.md index 8416c40..7a29e83 100644 --- a/.agents/rules/codemap.md +++ b/.agents/rules/codemap.md @@ -12,36 +12,38 @@ A local database (default **`.codemap/index.db`**) indexes structure: symbols, i ## CLI (this repository) -| Context | Incremental index | Query | -| ------------------------------ | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **Default** — from this clone | `bun src/index.ts` | `bun src/index.ts query --json ""` | -| Same entry | `bun run dev` | (same as first row) | -| Query (ASCII table — optional) | — | `bun src/index.ts query ""` | -| Recipe | — | `bun src/index.ts query --json --recipe fan-out` (see **`bun src/index.ts query --help`**) | -| Parametrised recipe | — | `bun src/index.ts query --json --recipe find-symbol-by-kind --params kind=function,name_pattern=%Query%` — params declared in recipe `.md` frontmatter and validated before SQL binding. | -| Boundary violations | — | `bun src/index.ts query --json --recipe boundary-violations` — joins `dependencies` × `boundary_rules` (config-driven) via SQLite `GLOB`. `.codemap/config.ts` `boundaries: [{name, from_glob, to_glob, action?}]`; default `action: "deny"`. SARIF / annotations work via the `file_path` alias. | -| Rename preview | — | `bun src/index.ts query --recipe rename-preview --params old=usePermissions,new=useAccess,kind=function --format diff` — read-only unified diff; codemap never writes files. | -| Recipe catalog / SQL | — | `bun src/index.ts query --recipes-json` · `bun src/index.ts query --print-sql fan-out` | -| Counts only | — | `bun src/index.ts query --json --summary -r deprecated-symbols` | -| PR-scoped rows | — | `bun src/index.ts query --json --changed-since origin/main -r fan-out` | -| Bucket by owner / dir / pkg | — | `bun src/index.ts query --json --group-by directory -r fan-in` | -| Save / diff a baseline | — | `bun src/index.ts query --save-baseline -r visibility-tags` then `… --json --baseline -r visibility-tags` | -| List / drop baselines | — | `bun src/index.ts query --baselines` · `bun src/index.ts query --drop-baseline ` | -| Per-delta audit | — | `bun src/index.ts audit --json --baseline base` (auto-resolves `base-files` / `base-dependencies` / `base-deprecated`) | -| Audit vs git ref | — | `bun src/index.ts audit --base origin/main --json` — worktree+reindex against any committish; sub-100ms second run via sha-keyed cache. Mutually exclusive with `--baseline`; per-delta overrides compose. Add `--format sarif` to emit SARIF 2.1.0 directly (one rule per delta key; severity `warning`). | -| MCP server (for agent hosts) | — | `bun src/index.ts mcp [--no-watch] [--debounce ]` — JSON-RPC on stdio; one tool per CLI verb. Watcher default-ON since 2026-05. See **MCP** section below. | -| HTTP server (for non-MCP) | — | `bun src/index.ts serve [--host 127.0.0.1] [--port 7878] [--token ] [--no-watch] [--debounce ]` — same tool taxonomy over POST /tool/{name}. Watcher default-ON since 2026-05. | -| Watch mode (live reindex) | — | `bun src/index.ts watch [--debounce 250] [--quiet]` — standalone long-running process; debounced reindex on file changes. `mcp` / `serve` boot the watcher in-process by default — pass `--no-watch` (or `CODEMAP_WATCH=0`) to opt out. | -| Targeted read (metadata) | — | `bun src/index.ts show [--kind ] [--in ] [--json]` — file:line + signature | -| Targeted read (source text) | — | `bun src/index.ts snippet [--kind ] [--in ] [--json]` — same lookup + source from disk + stale flag | -| Impact (blast-radius walker) | — | `bun src/index.ts impact [--direction up\|down\|both] [--depth N] [--via ] [--limit N] [--summary] [--json]` — replaces hand-composed `WITH RECURSIVE` queries | -| Coverage ingest | — | `bun src/index.ts ingest-coverage [--json]` — Istanbul (`coverage-final.json`) or LCOV (`lcov.info`); format auto-detected. Joinable to `symbols` for "untested AND dead" queries. | -| SARIF / GH annotations | — | `bun src/index.ts query --recipe deprecated-symbols --format sarif` · `… --format annotations` | -| `--ci` aggregate flag | — | `bun src/index.ts query -r deprecated-symbols --ci` (or `audit --base origin/main --ci`) — aliases `--format sarif` + non-zero exit when findings/additions surfaced + suppresses the no-locatable-rows stderr warning. Mutually exclusive with `--json` / `--format `. | -| PR-comment renderer | — | `bun src/index.ts pr-comment ` (or `-` for stdin) — renders an audit JSON envelope or SARIF doc as a markdown PR-summary comment. Pipe to `gh pr comment -F -`. Useful for private repos without GHAS, aggregate audit deltas, or bot-context seeding. | -| Mermaid graph (≤50 edges) | — | `bun src/index.ts query --format mermaid 'SELECT from_path AS "from", to_path AS "to" FROM dependencies LIMIT 50'` — recipes / SQL must alias columns to `{from, to, label?, kind?}`; rejects unbounded inputs. | -| Diff preview | — | `bun src/index.ts query --format diff ''` — read-only unified diff; `--format diff-json` returns structured hunks for agents. | -| FTS5 full-text (opt-in) | `--with-fts` | `bun src/index.ts --with-fts --full` enables `source_fts` virtual table; `query --recipe text-in-deprecated-functions` demos JOINs. | +| Context | Incremental index | Query | +| ------------------------------ | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Default** — from this clone | `bun src/index.ts` | `bun src/index.ts query --json ""` | +| Same entry | `bun run dev` | (same as first row) | +| Query (ASCII table — optional) | — | `bun src/index.ts query ""` | +| Recipe | — | `bun src/index.ts query --json --recipe fan-out` (see **`bun src/index.ts query --help`**) | +| Outcome alias | — | `bun src/index.ts dead-code` · `deprecated` · `boundaries` · `hotspots` · `coverage-gaps` — thin wrappers over `query --recipe `; every `query` flag passes through. See `bun src/index.ts --help` for the wrapped recipe id. Capped at 5 to avoid sprawl. | +| Suppressions (opt-in) | — | `// codemap-ignore-next-line ` and `// codemap-ignore-file ` (also `#`, `--`, `\n", + "doc.md", + ), + ).toEqual([ + { file_path: "doc.md", line_number: 0, recipe_id: "unused-type-members" }, + ]); + expect( + extractSuppressions("/* codemap-ignore-file fan-in */\n", "f.css"), + ).toEqual([{ file_path: "f.css", line_number: 0, recipe_id: "fan-in" }]); + }); + + it("returns empty when no suppression markers", () => { + expect( + extractSuppressions("const x = 1;\n// regular TODO: y\n", "f.ts"), + ).toEqual([]); + }); + + it("ignores prose mentions inside multi-line doc comments (no false positives)", () => { + // ` * ` continuation lines aren't leaders, so directive in prose is ignored. + const src = [ + "/**", + " * Document mentions codemap-ignore-file boundaries in prose;", + " * NOT a real suppression because the line leader is `* `, not `//`.", + " */", + "export const x = 1;", + ].join("\n"); + expect(extractSuppressions(src, "f.ts")).toEqual([]); + }); +}); diff --git a/src/markers.ts b/src/markers.ts index da4cf56..10c3b3f 100644 --- a/src/markers.ts +++ b/src/markers.ts @@ -1,7 +1,13 @@ -import type { MarkerRow } from "./db"; +import type { MarkerRow, SuppressionRow } from "./db"; const MARKER_RE = /\b(TODO|FIXME|HACK|NOTE)[\s:]+(.+)/g; +// Leader must start the line (modulo whitespace) so the directive never +// matches inside a string literal — both this clone's tests and recipe docs +// embed the phrase legitimately. +const SUPPRESS_RE = + /(?:^|\n)\s*(?:\/\/|#|--|