Skip to content

v3.17.0

Choose a tag to compare

@mrbro-bot mrbro-bot released this 10 Sep 23:38
· 161 commits to main since this release
449934b

What's new

This release adds an operator-facing cleanup path for ce:review artifacts, turns on noUncheckedIndexedAccess repo-wide, and converts a hand-measured eval-scanner coverage claim into a test that re-runs itself.

Features

Review artifacts can now be cleaned up, and are ignored before they are written. Review runs retain private source excerpts indefinitely, with no supported way to age them out. The new ce:review-cleanup skill (#965) closes that gap with a deliberately conservative shape: a required age cutoff, a read-only preview, and a separate deletion approval bound to the previewed selection and that fixed cutoff. Before removal it rechecks directory identity, ancestry, and subtree metadata, rejects unsafe traversal, and reports partial failures instead of an all-clear. Complementing it, writable review runs now prepare .context/.gitignore with /systematic/ce-review/ before artifacts exist, preserving existing bytes; missing Git or ambiguous verification blocks persistence, and report-only mode remains no-write. Both ship as independent Node helpers alongside their owning skills, with strict argument handling and read-only usage help.

The safety boundaries are stated rather than implied. Age means the latest filesystem modification within a run, not proof that nothing is writing to it — the offline acknowledgment is an operator precondition, not a liveness check, and the preview token detects stale state without authenticating approval. There is no automatic expiry and no new source-redaction mode. Ignore rules do not protect tracked files, force-adds, or copies made elsewhere, and deletion is irreversible with no rollback or backup.

Documentation

ARCHITECTURE.md and STRUCTURE.md caught up with three shipped changes (#960). Review artifact validation now documents the containment default and the --allow-outside-artifact-root escape hatch. Named model profiles gained a cross-cutting entry covering resolveActiveProfile, resolveProfileSelector, and the trust boundary: profiles sits in PROJECT_PROTECTED_FIELDS alongside workflow_guard, so a project systematic.json may select a profile but a profiles map it defines is ignored with a warning rather than merged. The host-contract gate is recorded too — the host-contract job, scripts/host-contract-guard.ts, and scripts/lib/opencode-pin.ts — including that it is a required predecessor of release and that evidence gathered at one pinned OpenCode version is not evidence at another. Every claim was checked against the code it describes.

A new learning records what counts as evidence for a "nothing changed" claim (#961). Two changes shipped the same defect an hour apart: one asserted no runtime behavior changed while reassigning an array OpenCode owns, and one claimed a project overlay could override profile-supplied routing when the code rejects rather than strips. Both were confident claims about the far side of a boundary, and both survived multi-persona review — eleven personas returned zero findings on the array conversion, because every existing test read the value back off the same object and so passed under either semantics. The rule the doc records: a no-change claim is scoped to a boundary, neither the diff nor a passing suite is evidence for it, and if the contract is real you pin it with a test that fails under the wrong semantics. It is indexed by symptom rather than by lesson, so the tags carry the identifiers actually involved.

Three shipped plans in docs/plans/ are marked completed (#957). Each unit was verified against the current tree rather than against the plan's own prose, and half the reported drift turned out not to be drift — a program document and two genuinely incomplete plans were correctly left active, with reasons stated. content-integrity.ts now catches one narrow shape of this problem: a plan marked status: active whose units are all ticked fails the gate. Notably, it does not catch the drift this change fixed, since "shipped but nobody ticked a box" is indistinguishable from "not started" using frontmatter and checkboxes alone.

Tests

The eval fixture scanner re-proves its own coverage on every run (#958). The scanner's blind spots had been measured by hand and quoted as a number at a commit — a technique nobody could re-run, so nothing re-checked it when the scanner or the files it reads changed. probeScannerBlindSpots now plants a version literal at every line position of every allowlisted fixture file and asserts the scanner reports it, across all four historical injection shapes, so a regression confined to one shape or one scanner code path cannot hide behind the others passing. Non-detections inside comments or inside an already-open template literal are classified as legitimate per shape, derived from the scanner's own tokenizer output rather than a second hand-written walk over the source. The probe is itself proven: reconstructing each of the three prior scanner designs makes the sweep fail and name the positions each would have missed, and a deliberately crippled stub scanner produces a real miss. The stated limit is that the sweep re-proves detection only for the shapes and positions present in today's files.

Code Refactoring

noUncheckedIndexedAccess is on, and all 132 surfaced sites are fixed (#959). z.record(z.string(), V) infers Record<string, V>, so parsed.categories.anything type-checked clean and threw at runtime — a trap that applied to every indexed read in the repo, not just the config schema. The flag now lives in the base tsconfig.json, inherited by the scripts and tests configs, with all sites fixed in the same commit because typecheck:all is a required gate and a partial fix would land red. One site changed observably and was corrected: an interim output.system = output.system.map(...) in applyBootstrapContent reassigned an array that a sibling hook captures by reference, which would have silently dropped injected bootstrap content; the loop now mutates in place and a test pins array identity across the call. Every other site was checked individually and is type-only, with four regression tests pinning the narrowings that landed on live branches. Consumers are unaffected — dist/lib/config-schema.d.ts is byte-identical with and without the flag.

The parsed config shape is inferred instead of erased (#955). createSystematicConfigSchema declared its return type as z.ZodObject<z.core.$ZodLooseShape>, which threw away the shape the schema actually parses; callers got a loose object, and the config test compensated with a hand-maintained ParsedSystematicConfig mirror plus a cast helper that nothing kept in sync. Removing the annotations lets the shape infer from the builders and deletes the mirror. Inference degrades gracefully here — record-keyed fields infer as Record<string, T> rather than expanding to literal keys, and typecheck:all stays clean at roughly the same speed. Ordinary toEqual assertions now fail to compile when the schema gains an unaccounted required field, though an added .optional() field with no default still drifts silently, and SystematicConfigParsed's docstring records that scope. Because annotating a Zod schema builder erases its inferred shape, AGENTS.md and .github/copilot-instructions.md now record an explicit exception to the repo's explicit-return-type convention.

Also in this release

  • deps: update bfra-me/.github action to v4.27.0 (#966)
  • deps: update github/codeql-action action to v4.38.0 (#963)
  • dev: update @types/node to v26.5.0 (#967)
  • dev: update typebox to v1.3.27 and v1.3.28 (#956, #962)

Compare

v3.16.5...v3.17.0