Skip to content
Draft
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
32 changes: 30 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,42 @@ All notable changes to this project are documented here. The format is based on

## [Unreleased]

### Added

- **The experimental engineering record has a v1 contract, stable snapshot identities, and an isolated store.** Record types, identifiers, digest rules, validators, and negative fixtures define how changes, attempts, proofs, reviews, and acceptance requests are represented. Snapshot coverage is included in identity; unreadable relevant files block acceptance, and excluded secret files are disclosed without recording their digests. The file-backed store keeps change and slice projections under revision compare-and-swap, serializes concurrent writers, and keeps finalized attempt history immutable; `.codecarto/engineering/` is excluded from the copied template and npm package. Contract corrections separate claims from observations, enforce request TTL and presentation disclosure, and add a class-sweep field for review objections. An unprotected storage boundary cannot classify acceptance as verified (#414, #415, #417, #420, #425, #426).

- **Repository-local changes can be briefed and planned without a library.** New change-brief and change-plan templates and the planning primitive reject floating references, unknown scenarios, empty proof obligations, and dependency cycles; uncovered scenarios and stale findings remain visible, and readiness is reported separately from plan validity. No external reference is required (#431).

- **Engineering proofs and gates keep reported claims separate from observed results.** Proof ingestion binds a result to an attempt, candidate, scenario, and declared obligation, derives authority from its collector and attestation, and retains failed or stale proofs without treating them as discharged obligations. The acceptance gate checks candidate freshness, proof coverage, blocking review objections, dependencies, and the host's ability to obtain a human decision; it reports actionable blockers or `needs-human-acceptance` rather than granting approval (#432, #433).

- **The experimental `codecarto_change` MCP tool exposes engineering records and a supervised Traverse next-step procedure.** A host can create, plan, inspect, and record a change; Traverse chooses a bounded, resumable next action from stored records and declared host capabilities. The host still executes checks and edits, while the server refuses self-approval and treats proof submitted in a tool call as claimed. The packaged Traverse skill is available before an analysis pipeline completes (#434, #436).

- **Planning artifacts now connect slices to acceptance scenarios and observable verification routes.** Reimplementation specifications and project plans give scenarios stable IDs and tiers, then name each slice's deliverable, dependencies, scenarios, and proof obligations. The lifter checks those links before forming an engineering plan. Architecture and porting reports assess agent addressability with evidence; specification slices name a test, run, or manual procedure route, and a missing route is reported as a gap. Matching skill, template, and pipeline criteria ship together (#438, #439).

- **An end-to-end engineering test covers a first change, negative controls, and a second change in the same store.** It exercises the real record primitives against synthetic fixtures, including resumed attempts, stale candidates, missing proof, and preservation of the first change's history. This is test coverage, not a live-host F01 run or human acceptance (#440).

- **A Claude Code hook can collect host-observed tool results for engineering proof ingestion.** The hook writes Bash tool observations to an inbox, and the reader matches commands to declared obligations and ingests them with provenance derived from the configured observation path. The MCP `ingest_observations` action currently uses an unprotected path, so its results remain caller-attested; the protected-hook adversarial checks are still outstanding (#446).

- **The MCP change tool now exposes the attempt lifecycle and an acceptance request adapter.** `start_attempt` and `capture_candidate` observe the server's working tree, while `record_review` binds a review to the stored candidate; a running attempt may update only its lifecycle binding and becomes immutable after finalization. `request_acceptance` stores a request, checks the gate and tree freshness, and can ask through client elicitation only for a verified host/client integration. The shipped registry is empty, so a live client would receive `needs-human-acceptance` and no approval would be minted; the supported path is covered by a scripted test client, not a live human decision (#445, #447).

### Changed

- **The MCP server runs on SDK v2 and serves the `2026-07-28` revision alongside the 2025 era.** `@modelcontextprotocol/sdk` ^1.29.0 is replaced by `@modelcontextprotocol/server` + `/core` 2.0.0 (the `latest` line; the v1 package is no longer in the tree, and 80 transitive packages the v1 line dragged in — express, hono, ajv, cors, … — go with it). The codemod rewrote the imports and `McpError`/`ErrorCode` → `ProtocolError`/`ProtocolErrorCode` (the four JSON-RPC codes the server emits, -32600/-32601/-32602/-32603, are unchanged) and `setRequestHandler(Schema, …)` → `setRequestHandler("tools/list" | "tools/call", …)`; the two-handler dispatch and the 22 tools are untouched. The stdio entry is now the SDK's `serveStdio`, which owns the era decision per connection: a client that opens with `initialize` is pinned to a 2025-era instance and served exactly as before (every revision the v1 line accepted — `2025-11-25`, `2025-06-18`, `2025-03-26`, `2024-11-05`, `2024-10-07` — is still accepted and echoed; `@modelcontextprotocol/server-legacy` is SSE + OAuth and is not needed); a client that probes with `server/discover` is pinned to a `2026-07-28` instance, on which the SDK itself answers the probe (`supportedVersions: ["2026-07-28"]`, capabilities, instructions), stamps `resultType: "complete"` and `serverInfo` onto every result, and emits the caching hints — `tools/list` is declared `ttlMs: 86400000, cacheScope: "public"` because the inventory is a module constant. A 2025-era response never carries any of the 2026 vocabulary. `server.json`'s `$schema` stays at `2025-12-11`: it is the only registry schema published (`…/schemas/2026-07-28/server.schema.json` is 404 and the 2026-07-28 spec's registry docs reference `2025-12-11`). `tests/mcp-protocol-2026-07-28.test.mjs` drives the shipped binary over stdio in both eras, and `scripts/smoke-mcp.mjs` now runs 12 checks: the original nine through `initialize`, then three through the v2 client's `versionNegotiation: { mode: "auto" }` probe. One wire-visible detail changed: `ProtocolError.message` no longer carries the v1 `MCP error <code>: ` prefix (the code is in `error.code`; four tests that matched the prefix now match the message body and keep asserting the code). Not yet done: a real round trip through Claude Code, Codex and Hermes per CONTRIBUTING's surface verification — an SDK-era swap is exactly the change that step exists to catch, and it is the pre-merge requirement for this entry (#185).
- **The Windows CI test job is required.** The `test-windows` result now gates PRs after the earlier Windows failures were fixed (#427).

- **Fixture git commands are isolated from the developer's git environment.** Tests neutralize inherited configuration, repository-location overrides, template hooks, editors, and other git program overrides so local settings cannot redirect or run fixture operations (#421, #442).

- **The distribution test packs a disposable copy of the template.** It still proves that engineering records cannot enter the npm tarball, while the test suite no longer plants a synthetic record in the live `.codecarto/` directory where concurrent tests may be copying it (#441).

- **Ten test expectations no longer assume POSIX paths.** The `windows-latest` job's first run reported ten failures; two were product defects (#393, #394) and the other eight were expectations written for `/`-separated paths: an `outputDir`'s last segment taken with `split("/")`, which does not split a backslash path; four comparisons against a raw `library.path` or `source_repo` line, where a path containing backslashes is correctly emitted as a quoted YAML scalar with those backslashes escaped; a path interpolated into a `RegExp` source, where backslashes read as escapes and `\b` becomes a word boundary; a refusal message matched against a `/`-rooted pattern; and a joined path compared against a `/`-joined literal. Each now compares a parsed value, a `basename`, a `path.join` on both sides, or a literal prefix — and one `doesNotMatch` in the same family, which a quoted backslash path would have satisfied vacuously rather than failing, became a parsed-value comparison too. Fixing those eight exposed two more of the same kind — a third raw `library.path` comparison and a second `/`-rooted `source_repo` pattern — because a test stops at its first failing assertion, so the run could only report the first one in each: the issue's list of eight was what was visible, not the whole set. A sweep for every instance of these shapes across `tests/` (raw-line comparisons, a path interpolated into a pattern, `split("/")` on a path) finds no others; what remains is writes that feed the parser, URLs, which are always `/`-separated, and patterns that already escape their input. No product code changed, and `continue-on-error` stays on the `test-windows` job until a run reports it green (#395).
- **The MCP server runs on SDK v2 and serves the `2026-07-28` revision alongside the 2025 era.** `@modelcontextprotocol/sdk` ^1.29.0 is replaced by `@modelcontextprotocol/server` + `/core` 2.0.0 (the `latest` line; the v1 package is no longer in the tree, and 80 transitive packages the v1 line dragged in — express, hono, ajv, cors, … — go with it). The codemod rewrote the imports and `McpError`/`ErrorCode` → `ProtocolError`/`ProtocolErrorCode` (the four JSON-RPC codes the server emits, -32600/-32601/-32602/-32603, are unchanged) and `setRequestHandler(Schema, …)` → `setRequestHandler("tools/list" | "tools/call", …)`; the two-handler dispatch and the tools already present at that migration are untouched. The stdio entry is now the SDK's `serveStdio`, which owns the era decision per connection: a client that opens with `initialize` is pinned to a 2025-era instance and served exactly as before (every revision the v1 line accepted — `2025-11-25`, `2025-06-18`, `2025-03-26`, `2024-11-05`, `2024-10-07` — is still accepted and echoed; `@modelcontextprotocol/server-legacy` is SSE + OAuth and is not needed); a client that probes with `server/discover` is pinned to a `2026-07-28` instance, on which the SDK itself answers the probe (`supportedVersions: ["2026-07-28"]`, capabilities, instructions), stamps `resultType: "complete"` and `serverInfo` onto every result, and emits the caching hints — `tools/list` is declared `ttlMs: 86400000, cacheScope: "public"` because the inventory is a module constant. A 2025-era response never carries any of the 2026 vocabulary. `server.json`'s `$schema` stays at `2025-12-11`: it is the only registry schema published (`…/schemas/2026-07-28/server.schema.json` is 404 and the 2026-07-28 spec's registry docs reference `2025-12-11`). `tests/mcp-protocol-2026-07-28.test.mjs` drives the shipped binary over stdio in both eras, and `scripts/smoke-mcp.mjs` now runs 12 checks: the original nine through `initialize`, then three through the v2 client's `versionNegotiation: { mode: "auto" }` probe. One wire-visible detail changed: `ProtocolError.message` no longer carries the v1 `MCP error <code>: ` prefix (the code is in `error.code`; four tests that matched the prefix now match the message body and keep asserting the code). Not yet done: a real round trip through Claude Code, Codex and Hermes per CONTRIBUTING's surface verification — an SDK-era swap is exactly the change that step exists to catch, and that surface verification remains open (#185, #443).

- **Ten test expectations no longer assume POSIX paths.** The `windows-latest` job's first run reported ten failures; two were product defects (#393, #394) and the other eight were expectations written for `/`-separated paths: an `outputDir`'s last segment taken with `split("/")`, which does not split a backslash path; four comparisons against a raw `library.path` or `source_repo` line, where a path containing backslashes is correctly emitted as a quoted YAML scalar with those backslashes escaped; a path interpolated into a `RegExp` source, where backslashes read as escapes and `\b` becomes a word boundary; a refusal message matched against a `/`-rooted pattern; and a joined path compared against a `/`-joined literal. Each now compares a parsed value, a `basename`, a `path.join` on both sides, or a literal prefix — and one `doesNotMatch` in the same family, which a quoted backslash path would have satisfied vacuously rather than failing, became a parsed-value comparison too. Fixing those eight exposed two more of the same kind — a third raw `library.path` comparison and a second `/`-rooted `source_repo` pattern — because a test stops at its first failing assertion, so the run could only report the first one in each: the issue's list of eight was what was visible, not the whole set. A sweep for every instance of these shapes across `tests/` (raw-line comparisons, a path interpolated into a pattern, `split("/")` on a path) finds no others; what remains is writes that feed the parser, URLs, which are always `/`-separated, and patterns that already escape their input. No product code changed in this change; the subsequent Windows CI update made `test-windows` required (#395, #427).

### Fixed

- **Windows stale-lock reaping chooses one winner.** Contending processes now arbitrate a stale ticket with an exclusive marker, retry transient claims, and clean up failed waiters so they cannot both reap the same lock ticket (#435, #444).

- **A committed `node_modules` symlink no longer breaks clean checkouts.** The tracked link was removed and the ignore rule keeps local dependencies out of git (#429).

- **A containment root that does not exist yet is canonicalized the way its target is.** `isWithinPathResolved` resolved the target's existing prefix through symlinks but the root through `realpath` alone, which throws on a `.codecarto/` that is not there yet and fell back to the root as spelled. Wherever an ancestor needed expanding the two operands then disagreed — the 8.3 short name the Windows runner puts in `%TEMP%` (`C:\Users\RUNNER~1\…`), macOS' `/var` → `/private/var` — and a write to `.codecarto/findings/…`, the one place a phase session may write, was refused while that directory did not exist yet. Both operands now resolve through `resolveExistingPrefix`, and the primitive takes the base a relative operand resolves against. The Pi orchestrator hook and the phase hook call that single primitive instead of each hand-rolling the resolve-canonicalize-compare sequence one of them had drifted from, and the MCP `spec_path` containment check inherits the fix. What containment refuses is unchanged. Confirmed by the `test-windows` job's first run (#394).

- **Windows: a rename that loses a race retries instead of failing the write.** `atomicWriteFile` writes a uniquely named sibling temp file and renames it over the destination; on Windows that rename fails with `EPERM` while another writer holds or is replacing the same file, so two concurrent writers — two MCP hosts, Pi and MCP, the usage log appending as a phase completes — lost a write with an error rather than serializing. Every canonical write in the framework goes through that function (`status.yaml`, the usage log, Broad-Side `state.json`, the library index). The rename now retries on `EPERM`, `EBUSY` and `EACCES` within a bounded, jittered budget and still propagates any other error on the first attempt; the temp-file cleanup is unchanged. Confirmed by the `test-windows` job's first run (#393).
Expand Down
Loading