Split chat message and group references by task, update intent routing and context budget, distinguish accepted card updates from verified writes, and explain the ambiguous chat --from flag.
The Coverage context was the PR critical path (~17 min end to end):
coverage-current re-ran the whole suite serially (-p 1, ~13 min) and
coverage-baseline re-ran it again at the merge-base (~13 min) although
that profile is a pure function of the base commit.
- coverage-current now owns only the scoped (standard-tier) profile;
full-suite candidate profiles come from a 5-way shard matrix
(app/cli/generators/helpers/remaining) that keeps -p 1 inside each
shard on isolated runners. scripts/ci/test-packages.sh list-coverage
defines the shards and verify proves the union equals the previous
single-run package set exactly once.
- the aggregate Coverage job reassembles the disjoint shard profiles
into coverage.txt before make coverage-gate, failing closed when a
shard file is missing, so gate semantics (100% changed-code +
scope-matched overall non-regression) are byte-compatible.
- coverage-baseline restores the merge-base full-suite profile from an
exact-key cache (merge-base SHA + resolved Go version) written by the
last green main push; any miss falls back to recomputing in the
merge-base worktree. Exact key only - no prefix fallback, a near-miss
profile would compare the candidate against the wrong commit.
- new contract tests pin the shard matrix, the assembly step, the
exact-key cache pair, and the absence of restore-keys; the package
plan test also covers the coverage shard partition.
release_version was interpolated into an awk regex, where '.' matches any
character. Version 1.0.1-beta.1 therefore also admitted
.changes/released/1x0x1-betaX1/, letting the archive drift from the
CHANGELOG version while every other seal assertion still passed and
breaking the documented audit trail.
Compare the archive prefix with index() and split the basename off with
substr(), matching the literal-comparison idiom already used throughout
check-changelog-pr.sh. Only the basename, whose character class is fixed,
stays a pattern.
Both trigger predicates ran the same git diff, which the script already
avoids elsewhere by staging --name-status into $tmp_root/status. Write the
path list once and let each awk predicate read it, matching that idiom.
Git records no diff entry for a directory itself, so adding
.changes/foo/bar.md only surfaced the nested path, which the single-level
trigger regex skipped. The entry validation and the renderer were both
bypassed, letting a nested directory reach main and break every later
fragment render with 'unexpected directory'.
Trigger the top-level tree validation on any .changes change outside
.changes/released/ (which keeps its own immutability and release-seal
checks), and assert .changes itself is still a tree so replacing it with a
blob or symlink cannot empty the child listing unnoticed.
Re-rendering stays keyed on fragment changes so a README-only edit does
not fail on an empty fragment set.
Bind each accepted marker to the exact workflow run attempt, immutable artifact, source comment, and current PR head so a historical successful run cannot authorize a different payload.
The fragment gate only ran validation when the changed path matched the
legal fragment name pattern, so `.changes/Foo.md`, `.changes/notes.txt`
and a symlinked fragment slipped through untouched and then broke the
next PR that added a legal fragment. The trigger now fires on any
top-level `.changes/` change other than README.md and rejects every
entry that is not README.md, released/, or a 100644 blob named
^[a-z0-9][a-z0-9._-]*\.md$.
The renderer had the same hole from the other side: `find -type f`
is false for symlinks, so a symlinked fragment was silently dropped
from the rendered notes, and the `[a-z0-9]*.md` glob only constrained
the first character so `chat reply.md` passed. It now walks every
top-level entry and fails on symlinks, unexpected directories,
non-regular files and illegal names. Both scripts pin LC_ALL=C so the
ASCII ranges cannot match uppercase under a different collation.
Adds regression coverage for illegal names, non-markdown entries,
symlinks and executable modes on both the gate and the renderer.
Address P1 finding: extract_payload now strictly requires the parsed JSON
to be a dict, and validates each field's type and format:
- pr_number: string of digits
- pr_head_sha: 40-char lowercase hex string
- products: alphanumeric with commas/dots/hyphens/underscores only
- run_id: string of digits
- cases_ref: string (may be empty)
validate_run_id also guards against non-string input.
Added tests for: integer/array/string/null JSON, numeric field types,
invalid SHA format, injection in products, missing required fields.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>