Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/runtime-test-isolation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@stainless-code/codemap": patch
---

`createCodemap()` and the CLI now reject invalid project config at load time. A second `createCodemap()` with a different project root in the same process throws (audit `--base` worktree reindex is exempt).
4 changes: 2 additions & 2 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -234,13 +234,13 @@ The npm package exports **`createCodemap`**, **`Codemap`** (`query`, `index`), *
2. **`await cm.index({ mode, files?, quiet? })`** — same pipeline as the CLI (incremental / full / targeted).
3. **`cm.query(sql)`** — read-only SQL against `.codemap/index.db` (opens the DB per call).

**Constraint:** `initCodemap` is global to the process; only one active indexed project at a time.
**Constraint:** One project root per process — a second `initCodemap` / `createCodemap` with a different `root` throws. Audit `--base` worktree reindex brackets temporary root swaps (internal swap guard). Re-init on the same root is allowed.

### User config

Optional **`<state-dir>/config.{ts,js,json}`** (default `.codemap/config.*`; default export: object or async factory). **`--config <path>`** overrides with an explicit file (absolute or relative to cwd). Example shape: [`codemap.config.example.json`](../codemap.config.example.json). **Self-healing (D11):** `<state-dir>/.gitignore` is reconciled to canonical on every codemap boot via **`ensureStateGitignore`** (`src/application/state-dir.ts`); JSON config is reconciled via **`ensureStateConfig`** (`src/application/state-config.ts` — prunes unknown keys with a warning, sorts alphabetically, write-only-on-drift). TS/JS configs are validate-only at load time. Bumping the canonical `STATE_GITIGNORE_BODY` constant or the Zod schema IS the migration — every consumer's project repairs itself on next boot. Single attachment point: **`src/cli/bootstrap-codemap.ts`** runs the reconcilers before `loadUserConfig`.

**Validation:** **`codemapUserConfigSchema`** ([Zod](https://zod.dev)) — strict object (unknown keys are rejected). **`defineConfig({ ... })`**, **`parseCodemapUserConfig`**, and **`resolveCodemapConfig`** (CLI and merged `createCodemap({ config })`) all go through the same schema. Invalid config throws **`TypeError`** with a short path/message list.
**Validation:** **`codemapUserConfigSchema`** ([Zod](https://zod.dev)) — strict object (unknown keys are rejected). **`defineConfig({ ... })`**, **`parseCodemapUserConfig`**, and **`resolveCodemapConfig`** (CLI and merged `createCodemap({ config })`) all go through the same schema; **`createCodemap`** and the CLI load path validate config files at load time. Invalid config throws **`TypeError`** with a short path/message list.

**Exports:** `codemapUserConfigSchema`, `parseCodemapUserConfig`, `defineConfig`, and **`CodemapUserConfig`** (inferred type) from the package entry — see **`src/config.ts`** / **`dist/index.d.mts`**.

Expand Down
75 changes: 0 additions & 75 deletions docs/plans/runtime-test-isolation.md

This file was deleted.

33 changes: 17 additions & 16 deletions docs/plans/security-hardening-orchestrator.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@

## PR schedule

| PR | Plan | Status | Blocks |
| ----- | --------------------------------------------------------------- | ----------------------------------------------------------------------------------- | ----------------------------------- |
| **1** | lifted → [`architecture.md`](../architecture.md) (plan retired) | **merged** ([#180](https://github.com/stainless-code/codemap/pull/180) · `a5caca8`) | — |
| **2** | lifted → [`architecture.md`](../architecture.md) (plan retired) | **PR open** (`fix/impact-inpath-homonyms`) | — |
| **3** | [`runtime-test-isolation.md`](./runtime-test-isolation.md) | **pending** | PR **1** merged (PR **2** optional) |
| PR | Plan | Status | Blocks |
| ----- | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | ------ |
| **1** | lifted → [`architecture.md`](../architecture.md) (plan retired) | **merged** ([#180](https://github.com/stainless-code/codemap/pull/180) · `a5caca8`) | — |
| **2** | lifted → [`architecture.md`](../architecture.md) (plan retired) | **merged** ([#181](https://github.com/stainless-code/codemap/pull/181) · `aae172f`) | — |
| **3** | lifted → [`architecture.md`](../architecture.md) (plan retired) | **PR open** ([#182](https://github.com/stainless-code/codemap/pull/182) · `fix/runtime-test-isolation`) | — |

| — | — | **deferred** | golden `schema.test.ts` + path guards |
| — | — | **skip** | atomic `ensureStateConfig` writes |
Expand Down Expand Up @@ -64,17 +64,18 @@ Evaluated 2026-06 against [roadmap § Floors](../roadmap.md#floors-v1-product-sh

## Session log

| Date | Event | Notes |
| ---------- | ----------- | ---------------------------------------------------------------------------- |
| 2026-06-10 | Triage | ROI on 7 slices; 3-PR program adopted. |
| 2026-06-10 | PR 1 impl | PR **1** committed on `fix/security-hardening-wave1`; harden pass in flight. |
| 2026-06-05 | PR 1 harden | `/harden-pr full` — plan retired; contracts in architecture/glossary. |
| 2026-06-05 | PR 1 merge | [#180](https://github.com/stainless-code/codemap/pull/180) → `a5caca8`. |
| 2026-06-05 | PR 2 start | `fix/impact-inpath-homonyms` — `inPath` + homonym walks in impact-engine. |
| 2026-06-05 | PR 2 harden | `/harden-pr full` — plan retired; CLI/MCP/docs parity. |
| — | PR 2 merge | _PR URL · merge SHA_ |
| — | PR 3 start | _from `main`_ |
| — | PR 3 merge | _fill · close orchestrator_ |
| Date | Event | Notes |
| ---------- | ----------- | ----------------------------------------------------------------------------- |
| 2026-06-10 | Triage | ROI on 7 slices; 3-PR program adopted. |
| 2026-06-10 | PR 1 impl | PR **1** committed on `fix/security-hardening-wave1`; harden pass in flight. |
| 2026-06-05 | PR 1 harden | `/harden-pr full` — plan retired; contracts in architecture/glossary. |
| 2026-06-05 | PR 1 merge | [#180](https://github.com/stainless-code/codemap/pull/180) → `a5caca8`. |
| 2026-06-05 | PR 2 start | `fix/impact-inpath-homonyms` — `inPath` + homonym walks in impact-engine. |
| 2026-06-05 | PR 2 harden | `/harden-pr full` — plan retired; CLI/MCP/docs parity. |
| 2026-06-05 | PR 2 merge | [#181](https://github.com/stainless-code/codemap/pull/181) → `aae172f`. |
| 2026-06-05 | PR 3 start | `fix/runtime-test-isolation` — root guards + test teardown + config validate. |
| 2026-06-05 | PR 3 harden | `/harden-pr full` — plan retired; swap finally fix + API/config tests. |
| — | PR 3 merge | _fill · close orchestrator_ |

---

Expand Down
2 changes: 1 addition & 1 deletion docs/roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ Predicate-as-API only — enrich row shape and audit deltas; no standalone pass/
- [ ] **`organize-imports` diff-shape recipe** — deterministic single-file import sort/group; `imports.line_number` + `source` substrate sufficient. Review-first (`auto_fixable: false`). Effort: S.
- [ ] **`codemap-to-tsmorph` Path B adapter** — separate package experiment: `query_recipe` discovery → `ts-morph` / `jscodeshift` transforms for AST-shape edits codemap's substring executor defers (see [architecture § Rejected apply-path alternatives](./architecture.md#apply--input-modes-transport-and-policy)). Not an in-tree AST writer (Path A rejected). Effort: M.
- [ ] **Apply write-safety hardening** — close apply TOCTOU: SHA-256 `hashContent` at phase-1 read, recheck disk hash immediately before phase-2 write (`file content changed` conflict); `fsync` temp file before `rename`; skip files with mixed CRLF/LF (`mixed line endings`). Preserves all-or-nothing on any conflict. Plan: [`plans/apply-write-safety.md`](./plans/apply-write-safety.md). Effort: L.
- [ ] **Read-surface hardening (3 PRs)** — query/HTTP/validate safety, `impact` `inPath` homonyms, runtime guards + test teardown. **Orchestrator:** [`plans/security-hardening-orchestrator.md`](./plans/security-hardening-orchestrator.md). PR1 ([#180](https://github.com/stainless-code/codemap/pull/180), lifted to [architecture](./architecture.md)) · PR2 (`impact` homonyms, lifted to [architecture](./architecture.md)) · Plans: [PR3](./plans/runtime-test-isolation.md). Effort: S–M.
- [ ] **Read-surface hardening (3 PRs)** — query/HTTP/validate safety, `impact` `inPath` homonyms, runtime guards + test teardown. **Orchestrator:** [`plans/security-hardening-orchestrator.md`](./plans/security-hardening-orchestrator.md). PR1 ([#180](https://github.com/stainless-code/codemap/pull/180), lifted to [architecture](./architecture.md)) · PR2 ([#181](https://github.com/stainless-code/codemap/pull/181), lifted to [architecture](./architecture.md)) · PR3 ([#182](https://github.com/stainless-code/codemap/pull/182), lifted to [architecture](./architecture.md)). Effort: S–M.
- [ ] **`history` table** (deferred — revisit-triggered) — temporal queries: "when did symbol X get `@deprecated`?", "coverage trend over last 50 commits", "files that became dead this week". `audit --base <ref>` covers the most-common temporal question (PR-scoped diff) without schema growth, so the table earns its place only when bigger questions emerge. Two shapes (per-commit snapshots ~N × DB size; append-only event log heavier CTE walks); both pay an N-reindexes backfill cost (~30s per reindex). **Revisit triggers:** two consumers ship `jq`-based "audit-runs-over-time" workflows, OR `query_baselines` evolution becomes a recurring agent need.
- [ ] **`codemap audit` verdict + thresholds** (v1.x) — `verdict: "pass" | "warn" | "fail"` driven by an `audit.deltas[<key>].{added_max, action}` field on the config object (`.codemap/config.{ts,js,json}`). Triggers: two consumers ship `jq`-based threshold scripts with similar shapes, OR one consumer asks with a concrete config sketch. Until then, raw deltas + consumer-side `jq` is the CI exit-code idiom. **Likely accelerant:** the Marketplace Action (next item) shipping is the most plausible path to firing the trigger — once `- uses: stainless-code/codemap@v1` is the dominant CI path, real `jq` threshold scripts will surface.
- [ ] **GitHub Marketplace Action — publish + listing finish** — core Action implementation is in-tree: root `action.yml`, `query --ci`, `audit --format sarif` / `--ci`, package-manager detection, dogfood smoke, and opt-in `pr-comment` summary renderer have shipped. Remaining work is the release/listing slice: `MARKETPLACE.md`, `v1.0.0` / floating `v1` tags, Marketplace setup, sacrificial-repo smoke, and making `action-smoke` blocking once the Action tag exists. Action version stream is independent of CLI version (`package.json` currently drives CLI/npm version; Action publishes at its own `v1.0.0`). Plan: [`plans/github-marketplace-action.md`](./plans/github-marketplace-action.md). Effort: S.
Expand Down
3 changes: 3 additions & 0 deletions scripts/agent-eval/parse-agent-log.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { join } from "node:path";

import { resolveCodemapConfig } from "../../src/config";
import { initCodemap } from "../../src/runtime";
import { installCodemapTestTeardown } from "../../src/test-helpers/runtime-reset";
import { resolveGoldenQuery } from "../query-golden/resolve-golden-query";
import { compareLogArms, summarizeLogComparison } from "./compare-live-logs";
import { runLiveMcpArm } from "./live-mcp-arm";
Expand Down Expand Up @@ -43,6 +44,8 @@ import {
traditionalToolSequence,
} from "./traditional-probe";

installCodemapTestTeardown();

const sampleLog = join(
import.meta.dir,
"../../fixtures/agent-eval/sample-cursor-log.json",
Expand Down
23 changes: 23 additions & 0 deletions src/api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import { tmpdir } from "node:os";
import { join } from "node:path";

import { createCodemap } from "./api";
import { installCodemapTestTeardown } from "./test-helpers/runtime-reset";

installCodemapTestTeardown();

describe("createCodemap", () => {
test("query runs against the index database", async () => {
Expand All @@ -13,4 +16,24 @@ describe("createCodemap", () => {
const rows = cm.query("SELECT 1 as ok") as { ok: number }[];
expect(rows[0]?.ok).toBe(1);
});

test("throws when switching to a different root in the same process", async () => {
const rootA = mkdtempSync(join(tmpdir(), "codemap-api-a-"));
const rootB = mkdtempSync(join(tmpdir(), "codemap-api-b-"));
writeFileSync(join(rootA, "package.json"), "{}");
writeFileSync(join(rootB, "package.json"), "{}");
await createCodemap({ root: rootA });
await expect(createCodemap({ root: rootB })).rejects.toThrow(
/cannot switch project root/,
);
});

test("throws when config file is invalid at load", async () => {
const root = mkdtempSync(join(tmpdir(), "codemap-api-bad-"));
const configPath = join(root, "bad.json");
writeFileSync(configPath, JSON.stringify({ include: [1, 2] }));
await expect(
createCodemap({ root, configFile: configPath }),
).rejects.toThrow(/include/);
});
});
7 changes: 6 additions & 1 deletion src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,12 @@ export interface CodemapInitOptions {
* and returns a {@link Codemap} handle.
*
* @remarks
* Only one Codemap project per process: `initCodemap` is global; the last `createCodemap()` wins.
* One project root per process: a second `createCodemap()` with a different `root` throws.
* Re-initializing the same root is allowed. Audit `--base` worktree reindex is the only
* production path that may temporarily switch roots.
*
* Invalid project config (unknown keys, wrong types) throws at load time via the same
* schema as {@link parseCodemapUserConfig}.
*/
export async function createCodemap(
options: CodemapInitOptions = {},
Expand Down
4 changes: 4 additions & 0 deletions src/application/affected-engine.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";

import { installCodemapTestTeardown } from "../test-helpers/runtime-reset";

installCodemapTestTeardown();

import { resolveCodemapConfig } from "../config";
import { closeDb, createTables, openDb } from "../db";
import { initCodemap } from "../runtime";
Expand Down
Loading
Loading