Mechanical rename produced by scripts/msd-rename.cjs: gsd/Gsd/GSD -> msd/Msd/MSD
across contents and paths, upstream package/repo coordinates -> @golem15/msd-core
and golem15com/msd-core. Deep links into upstream history, sibling upstream
packages, the GSD-2 import feature, CHANGELOG.md and .changeset/ are kept as-is.
Hand edits on top: MSD block-letter banner and logos, LICENSE copyright line,
package/plugin identity, regenerated lockfile, install-tree fixtures, derived
registries and benchmark baseline; migration checksum baseline re-locked
(MSD keeps its own install state, so no install had applied the old sums);
sort-order and regex-escaped expectations in tests adjusted.
* test(#3909): failing-first suite for the fabricated probe fallbacks
Binds the four fabrication sites found by executing the surfaces (ADR-3889
failure class (c)), each with a positive control so an over-firing fix goes red:
- the blocking api-coverage.verify-pre gate certifying "no external-API
integration" from a zero-byte phase scope
- the assumption-delta query route scanning an unresolvable phase section as
the empty string and reporting it as an examined negative
- both capability fragments' probe fallbacks, which append a fabricated
verdict rather than replacing, and fire on the legitimate exit-1 negative
Verification runs on the remote runner.
Refs #3909
* enhance(#3909): a probe that could not run no longer asserts a verdict
ADR-3889 Phase 5. Four sites turned a failed or unexamined probe into a
confident negative; each now reports what it could not establish.
- check api-coverage.verify-pre: a phase with no plan body and no roadmap
section ran detection over zero bytes and PASSED the blocking seal gate,
certifying "no external-API integration" from input it never read. It now
holds with scope_unavailable. The discriminator is bytes examined, never
signals found, so a phase whose plans are real and simply carry no API
vocabulary passes exactly as before.
- query assumption-delta scan: an unresolvable phase section was scanned as
the empty string and reported as an examined negative. It now returns
{skipped, reason: phase_unresolved}, still at exit 0 — an ADR-2980 degraded
result in the payload, leaving the gsd-tools exit projection to P8.
- both capability fragments: `|| echo '{"detected":false}'` appended rather
than replaced, and fired on the legitimate exit-1 negative, so a correct
answer and an honest skip both arrived as two concatenated objects. They now
keep the probe's own payload and manufacture only an explicit
probe_unavailable skip when the probe produced nothing at all.
Every registered outcome is more restrictive on a blocking gate, so this can
turn a false green red and never a red green.
Docs: FEATURES 156, CONFIGURATION (both keys), references/api-coverage.md
seal-time outcome table, and a new how-to for the reason-code vocabulary.
Verification runs on the remote runner.
Closes#3909
* test(#3909): correct the stale unknown-phase assertion
`unknown phase → detected:false, no throw (graceful)` scanned phase 999
against a two-phase roadmap and asserted `detected === false`. That pinned
the fabrication as intended behavior: the phase does not exist, so the
detector was handed the empty string and its "no core assumption changed"
answer described nothing that was ever read.
It now asserts the skipped-with-reason shape. The graceful-degradation
contract the test was actually protecting — the query succeeds and does not
throw on an unknown phase — is unchanged.
Found by code review, not by the author.
Refs #3909
* docs(#3909): author the FEATURES entry in its generator source
`docs/FEATURES.md` is generated by `scripts/gen-features.cjs` from the
per-feature fragments in `docs/features/`. The API-coverage entry was edited
in the generated file, so the next regeneration silently dropped it.
The text now lives in `docs/features/api-coverage-gate.md` and
`docs/FEATURES.md` is regenerated from it, leaving the shipped file
byte-identical and its content actually derivable.
Caught by `lint:generated-sync`.
Refs #3909
* test(#3909): bind the skip to "not found", and pin the discriminator
The first verification run went red on one case, and the case was wrong
rather than the code.
`getRoadmapPhaseWithFallback` returns `null` for an unknown phase and for a
missing ROADMAP.md, but for a section whose body is whitespace-only it returns
the heading line alone — which is not empty. So a body-less section WAS found,
and reporting `detected:false` over its heading is a real negative, not a
fabrication. The test had assumed the resolver yielded `''` there.
Correcting the test rather than the resolver keeps `skipped` bound to the
distinction the issue asks for — found versus not found — and avoids diverging
`assumption-delta scan` from `roadmap.get-phase`, which the fragment documents
as sharing one resolver.
Also adds the seeded property the test matrix had promised: for any plan body,
the scope read back is whitespace-only exactly when the body was. That pins the
gate's discriminator to bytes examined, so it cannot quietly become "no signals
found", across unicode whitespace and CRLF.
`docs/INVENTORY.md` picks up the reference doc's new seal-time outcome table —
surfaced by the co-change gate, not by a lint failure.
Refs #3909
* chore(#3909): backfill the changeset PR number
Refs #3909
---------
Co-authored-by: sim <sim@local>
* feat(#3907): gates report no-input instead of asserting a verdict they never reached
The three stdin-reading gates bound 2 to a stdin read error only, with no arm for stdin closed at zero bytes - so empty input flowed into the detector, found nothing, and exited 1, which each module's own comment defines as a negative verdict. An unset PHASE_SECTION made the UI gate assert the phase has no UI. Empty and whitespace-only input now exit NO_INPUT, and a read error exits UNAVAILABLE rather than a locally-invented 2, both resolved through the registry and delivered by terminateNow.
The exit code was only half of it: under --json the same input emitted {detected:false}, byte-identical to the fabricated payload #3909 exists to fix, and the blocking coverage gate reads that payload. Empty input now emits the in-tree {skipped:true,reason} form with no detected key at all.
teams-status is excluded: it never reads stdin and has no invented 2, so the four-module framing in the issue and ADR is wrong. The dead root bin/lib/ui-safety-gate.cjs is deleted - no installer reference, no workflow invocation, and the live fallback chains are for other modules. Its removal restores the unit tests to the module that actually ships; they had been asserting the stale copy's two-field shape, which is why it drifted unnoticed.
* fix(#3907): drive gate tests through the process seam, and make removed-but-needed basename-precise
CONTRIBUTING requires every subprocess go through tests/helpers/process-seam.cjs; two of the three gate suites hand-rolled spawnSync while the third, added in the same change, used runNode correctly for the identical injection case. Converted the blocks this change added, leaving pre-existing ones alone.
Deleting one of two files sharing a basename made lint-removed-but-needed report 14 references that were all to the surviving canonical module - the false-positive class its own docstring names. It now matches on the deleted file's full path when a surviving file shares its basename, which is more precise rather than weaker: a genuine full-path reference still fails, and behaviour is unchanged when no basename collides. It immediately caught a docstring on this branch that spelled the deleted path.
* test(#3907): update the one existing assertion that pinned the old empty-stdin verdict
A pre-existing test asserted exit 1 on empty stdin - the defect this phase removes - and was missed because the change added new blocks without auditing existing ones pinning the old contract. Audited the rest: the other three status-1 assertions in that file all feed real input and are the genuine-negative controls that must keep returning 1, so exactly one was stale. The retired 2 is gone from the describe's contract comment too.
* chore(#3907): backfill changeset pr number
---------
Co-authored-by: sim <sim@local>
* fix(#2365): stop the api-coverage detector false-positiving non-API phases
detectApiIntegration fired on any integration verb co-occurring anywhere on a
line with any API noun, treated / as a word boundary (so a first-party Next.js
src/app/api/... route path matched the noun "api"), and read any capitalized
word before API/SDK/REST/GraphQL as a service name behind a fixed stopword
denylist (so threat-model prose like "Resolver-only API" fired). Because the
verify:pre seal gate is BLOCKING, a phase touching no external API could not
reach UAT without fabricating a coverage matrix.
The compound rule now requires the verb and noun to share one clause (sentence
punctuation and table-cell walls end a clause) within a bounded word gap.
Non-prose spans are excluded before matching: fenced code (already), inline
code spans (new stripInlineCode in the markdown-sectionizer seam), and
path-shaped tokens. The <Service> API surface rule requires proper-noun
position — a clause-initial capitalized word is ordinary English and needs
dependency evidence (URL / package reference) on the same line — and rejects
compound modifiers ("Resolver-only", lowercase after the hyphen).
A phase that integrates no external API now has a first-class, reasoned way to
say so: a COVERAGE.md containing "No external API integration: <reason>"
satisfies the gate (declaration + rows is contradictory and blocks). The
true-positive path is pinned by regression tests: every default-vocabulary
positive still fires, including the widest word-gap pairing and the
surface-rule-only shape.
Fixes#2365
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(#2365): tighten api-coverage detector per Codex review (round 2)
Applies the Codex review findings on the initial #2365 fix:
- S-1: a COVERAGE.md "no external API integration" declaration is the human
override for a fallible detector, so it must PASS even when detection still
fires — but the contradiction is now SURFACED in the gate output (overridden
signal count + terms) instead of passing silently.
- S-2: verb/noun pairing is now a term-group nearest-pair merge walk over
precomputed word ordinals (computeWordStarts / minWordGap), not a match×match
cross product — a hostile line repeating one pair thousands of times stays
linear instead of going quadratic.
- FN-4: package-shaped inline-code spans (`stripe-sdk`, `@stripe/stripe-js`)
are kept as noun/dependency evidence rather than being fully masked, so a
genuine dependency reference inside code ticks still corroborates.
- C-1: the <Service> API surface rule now scans every candidate in every
clause; a rejected first candidate no longer shadows a later genuine service.
- Cross-clause binding: a verb may bind a noun in the immediately following
clause only when its own clause names a service object, within a tight gap —
admits "Integrate Stripe, exposing its endpoints …" without re-admitting the
unrelated-clauses false-positive class.
- Internal-descriptor negative evidence ("internal Payments API",
"the internal endpoint") never pairs; URL/scheme matching generalized beyond
http(s).
All 5 acceptance criteria still hold: the three reported false positives are
clean and "integrate the Stripe API" still fires. Built .cjs committed
alongside the .cts. tsc + eslint (incl. no-adhoc-markdown-parsing) +
lint:regression-names clean; affected suites 256/256 green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(#2365): retune api-coverage detector fail-closed per Codex review (round 3)
Codex's second-round review found the round-2 tightening had over-corrected into
FAIL-OPEN false negatives — realistic external-API prose that the BLOCKING seal
gate silently let through (the catastrophic class, since a missed API surface is
worse than a dismissable false positive). Retuned the detector to be explicitly
fail-closed: lean toward detecting, and let the one-line COVERAGE.md "no external
API integration" declaration dismiss the residual false positives.
Fail-open false negatives fixed (all now detect):
- F1 clause-initial `<Service> API` with a plain follower ("Stripe API for
payment processing") — dropped the follower-allowlist / corroboration gate on
clause-initial surfaces; a service that is not a stopword, descriptor, or
compound modifier is a real name from any clause position.
- F2 scheme-less external host ("api.stripe.com/v1") — a dotted host with an
alphabetic final label now contributes its API nouns; a first-party route
path (no dotted host) still does not.
- F3 vendor's first-party SDK ("Integrate Shopify's first-party SDK") — the
compound path no longer filters nouns on "internal"/"first-party" (Codex: the
qualifier can describe the vendor's own API, not the consuming project's).
- F4 long single integration clause — removed the word-gap cap entirely: it
could not separate a 21-word genuine clause from an 18-word internal one, so
the clause boundary is now the whole relationship test.
- F5 lowercase cross-clause service — cross-clause binding no longer requires a
capitalized "service object".
New false positives fixed (all now clean):
- F6 a URL token that swallowed a trailing clause comma, merging two clauses —
trailing clause punctuation is kept literal so the split survives.
- F7 a capitalized internal component authorizing cross-clause binding — the new
gate requires a dependent elaboration, not a new coordinate clause opened by a
conjunction ("…, then document…").
- F8 a protocol name read as a service ("REST API", "GraphQL API") — protocol
and locality descriptors are rejected in the `<Service>` position.
- Finding 9: the inline-code-span scanner was O(n^2) on pathological backtick
runs; rewritten to linear via a per-length run cursor (2 MB: 4.15 s -> ~6 ms),
semantics preserved (148 sectionizer tests unchanged).
Net simplification: the fail-closed model removed the round-2 minWordGap /
groupByTerm / follower / corroboration machinery (350 insertions vs 445
deletions across the touched files). Under fail-closed, three round-2 negative
tests now correctly detect (integration verb + "internal"-qualified noun, and
the distant-same-clause case); none were trek-e acceptance FPs.
Verified: 1491/1491 unit tests pass; tsc + eslint (incl. no-adhoc-markdown-
parsing) + lint:regression-names clean; all 8 review findings reproduced as
regression tests, both directions. Built .cjs committed alongside the .cts.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(#2365): resolve round-3 Codex review findings (fail-closed, round 4)
Codex's round-3 adversarial review found the fail-closed retune had introduced
new holes in both directions. Resolved:
Fail-open false negatives (now detect):
- External host addressing a PATH ("graph.microsoft.com/v1.0/me") is itself an
integration surface and contributes an endpoint noun even when the host names
no vocabulary word. A bare domain link with no path ("https://example.com")
stays a non-signal, so "Integrate … from example.com, document …" is still
clean.
- Locality qualification ("internal", "private") no longer leaks across a
sentence or clause boundary: only plain spaces may separate the descriptor
from the service, so "The cache is private. Stripe API …" now detects.
- Cross-clause binding: the fragile head-word cap (which could not tell a
genuine "Connect … to Stripe payments, exposing its endpoints" from an
unrelated "Integrate … from URL, document …" — both 4 words after the verb)
is replaced by a participial-continuation rule: a verb binds a noun in the
next clause only when that clause begins with an "-ing" elaboration. This
fixes the 4-word-head false negative AND the false positive below at once.
False positives (now clean):
- Cross-clause no longer binds a finite continuation regardless of separator:
"Wire the settings form. Document endpoint props." / "…; document …" /
"…, document …" are separate actions, not elaborations.
Perf (quadratic → linear):
- The trailing-punctuation peel is a backward char scan instead of an
unanchored `[…]+$` regex (16k chars: 156 ms → ~1 ms).
- SERVICE_SURFACE_API_RE bounds the service-name length {1,40} so a hostile
"A-A-…-x" run cannot drive O(n^2) backtracking (16k: 385 ms → ~3 ms).
Consumer fail-open (blocking gate):
- readPhaseScope now distinguishes "no plans" from a plan that EXISTS but is
unreadable. On a read error the gate BLOCKS ("could not read the phase
scope …") instead of silently certifying no-integration from partial scope —
an unreadable plan could be the one describing the integration.
Documented fail-closed tradeoffs, now pinned with tests so they are not
"fixed" back into a fail-open: a clause-initial capitalized common word before
"API" ("Payment API", "Search API") reads as a service name; a long clause
pairs a verb with a distant noun; and a CommonMark inline code span that wraps
a newline is matched within-line only. Codex judged these acceptable because
the COVERAGE.md declaration is a cheap override.
One documented limitation remains out of scope: "Integrate Stripe, and
authenticate requests with its API" (a coordinate finite clause whose noun
refers back by pronoun) needs coreference resolution, beyond a lexical detector.
Verified: 379/379 affected + command-router tests pass (+14 new regression
tests covering every round-3 finding, both directions); tsc + eslint
(no-adhoc-markdown-parsing) + lint:regression-names clean. Built .cjs committed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(#2365): simplify to robust core — remove whack-a-mole heuristics (round 5)
Round-4 review confirmed the detector's two most complex features generate
findings in both directions no matter how they are tuned, because they need a
vendor dictionary + coreference the issue rules out in principle. Per the
operator's "ship the robust core" decision, both are removed and their gaps are
documented rather than chased further:
- Cross-clause binding DELETED (allowsCrossClause / participle rule). It caused
a fail-open on finite continuations ("Integrate Stripe; use its OAuth
endpoints" — missed) and a false positive on "-ing"-SPELLED nouns ("…, billing
endpoint terminology…" — wrongly fired). Detection is now same-clause only.
- URL-path-as-evidence REVERTED. Treating every path-bearing URL as an endpoint
fired on ordinary asset/link URLs ("…/theme.css", "…?next=/x", a docs/repo
link) and recreated routine UI-phase false positives. An external URL is
evidence only when it NAMES an API vocabulary word ("api.stripe.com/v1").
Two fail-open cases are now DOCUMENTED limitations, pinned by tests so a future
maintainer does not re-add the heuristics that caused the false positives above:
a service named only in a clause separate from its API noun, and a bare external
host that names no vocabulary word. Both are cheaply covered by the COVERAGE.md
declaration and rare in real phase prose ("integrate the X API").
Also fixed from the round-4 review:
- Qualification now survives markdown emphasis ("The **internal** Payments API"
stays clean) while still not crossing a sentence/clause boundary.
- readPhaseScope fail-closes on a REAL read failure (EACCES/EIO) enumerating the
phase directory or reading the roadmap fallback — not only per-plan-file
failures; a missing directory/section remains a legitimate no-op. The
declaration-override path surfaces scope_read_error so an incomplete-scope
override stays visible.
- SERVICE_SURFACE_API_RE length-bound comment no longer overclaims.
Net: the detector is same-clause verb+noun + `<Service> API` surface, with
path/code/inline masking and a fail-closed posture. All five acceptance criteria
hold. 1573/1573 unit tests pass; tsc + eslint (no-adhoc-markdown-parsing) +
lint:regression-names clean. Built .cjs committed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(#2365): close roadmap-fallback fail-open + stale JSDoc (round-5 review)
The round-5 sanity review confirmed the detector simplification is sound (all
acceptance positives fire, all required negatives clean) and flagged one real
blocker plus a nit:
- Blocker: readPhaseScope's roadmap fallback could still silently pass an
UNREADABLE roadmap. getRoadmapPhaseWithFallback gated on fs.existsSync(), which
returns false on EACCES/EIO too — so an unreadable ROADMAP.md read as "absent",
no exception reached isRealReadFailure, and the blocking gate certified empty
scope. Fixed at the source: read the roadmap directly and honor the function's
OWN documented contract — null only on ENOENT (genuinely absent), otherwise
throw. Both existing callers already wrap it in try/catch expecting that throw,
and readPhaseScope now fail-closes (blocks) via its roadmap catch. Verified by
a new e2e test (unreadable roadmap fallback → block).
- Nit: the detectApiIntegration JSDoc still described the removed cross-clause
participial binding and "every external hostname counts" — corrected to the
actual same-clause-only behavior and the names-a-vocab-word URL rule.
Verified: full unit suite green; tsc + eslint + lint:regression-names clean.
Built .cjs committed (roadmap.cjs is gitignored/rebuilt, per repo convention).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(#2365): backfill changeset PR number (#2397)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(#2365): sync generated capability-registry + recapture install goldens
CI surfaced two generated-artifact staleness issues (all failing test shards +
lint-tests traced to these, not to a logic defect):
- gsd-core/bin/lib/capability-registry.cjs was stale: the initial fix edited the
ai-integration `api-coverage-plan-pre.md` fragment (added the "No external API
integration" declaration section) but did not regenerate the registry, which
embeds an inline copy of that fragment. Regenerated via
`gen-capability-registry.cjs --write` — the diff is exactly the fragment text
sync. Fixes `lint:generated-sync` and the "committed registry is in sync" +
"registry integration" tests.
- The 18 golden-install-parity fixtures were stale by exactly one hash line each
— `gsd-core/references/api-coverage.md`, which this PR edits and which is a
hashed installed artifact. Recaptured with `UPDATE_GOLDEN=1`; the diff is that
single hash per runtime and nothing else. Fixes the `golden parity — *` tests.
No source or behavior change — generated artifacts only.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(#2365): flip representative-corpus manifest to assert the fixed behavior
The #2371 representative corpus (merged into next after this branch was cut) is a
known-bug tripwire: it asserts each fixture's currentBuggyOutput so the test
fails loudly the moment #2365 is fixed, at which point — per its own contract in
representative-corpus.test.cjs — the fixer removes currentBuggyOutput so the
assertion checks expectedDetected instead.
This is that moment. Removed currentBuggyOutput from the three detector fixtures
(nextjs-route-path, unrelated-verb-noun, threat-model-prose); the corpus now
asserts detected:false, which the fail-closed same-clause detector satisfies.
Notes updated to describe the fix rather than the bug. The #2366 matrix corpus
is left untouched — that tripwire belongs to its own PR (#2374).
Corpus test: 7/7 pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(#2365): skip chmod-000 fail-closed e2e tests on Windows
The three fail-closed gate tests induce an unreadable plan / directory / roadmap
with chmod 000, but Windows does not enforce POSIX mode bits — readFileSync
still succeeds, so the gate never reaches the read-error path and the assertion
fails on the windows-latest CI leg. The fail-closed LOGIC is platform-
independent (readError → block) and is fully exercised on the macOS/Linux legs;
only the method of inducing EACCES is POSIX-specific. Guard the three tests to
skip on win32 as well as root, mirroring golden-install-parity's win32 skip.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(#2365): address trek-e review — glossary, clock-seam, IO injection, bounds
Review response to PR #2397 (trek-e, CHANGES_REQUESTED). Fix logic unchanged;
this closes the test/process-hygiene findings.
Major:
- CONTEXT.md "Markdown Sectionizer" glossary now lists the two exports this fix
relies on, `stripInlineCode` and `scanInlineCodeSpans` (glossary is a PR gate).
- Replaced the banned wall-clock assertion in the "hostile repeated-term line"
test (Clock Seams rule — no elapsed-time asserts) with a deterministic
signal-count assertion, which also directly verifies the term-dedup that keeps
pairing linear (one signal for a 10k-pair line, not thousands).
- Rewrote the three fail-closed read-failure tests: instead of chmod 0o000
(a no-op under root / on Windows, the pattern the repo's IO-failure convention
avoids) they now exercise the newly-exported `readPhaseScope` in-process and
inject the failure by monkeypatching fs.readFileSync/readdirSync to throw,
restoring in finally. Deterministic and platform-independent (no skip needed),
and they add the ENOENT-is-absence case that the chmod tests couldn't express.
Minor:
- Added limit / limit+1 boundary tests for SERVICE_SURFACE_API_RE's {1,40}
service-name bound, QUALIFIER_LOOKBACK's 24-char window, and REASON_MAX_LEN
(200) on the declaration reason.
- Added a fast-check property that fuzzes the tokenizer / clause splitter /
masking (scanLineTokens, splitClauses, collectTermMatches) with adversarial
tokens (slashes, backticks, URLs, clause punctuation) and asserts the detector
is total (never throws), shape-stable, holds detected <=> signals, and is
deterministic.
readPhaseScope is exported for the in-process tests. Verified: 125 detector +
19 gate tests pass; tsc + eslint + generated-sync (glossary/registry) +
lint-regression-test-names + lint-test-file-count clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* fix(release): finalize job calls sync-next-version.cjs to bump next after final release (#2423)
The release pipeline's 'finalize' job shipped X.Y.0 to npm 'latest' and
merged to main, but never bumped 'next' to match. 'scripts/sync-next-version.cjs'
exists exactly for this — its docstring promises to run 'for every release
type (rc / hotfix / final)' — but it was wired only into the 'rc' job
(release.yml:479), not 'finalize'. As a result, after 1.7.0 shipped on
2026-07-15, 'next' stayed at 1.7.0-rc.6 and every npm script banner on
'next' (and feature branches cut from it) reported the stale rc version.
Regression of #1104 — closed incomplete (covered rc only, not final).
This patch:
- adds a 'Sync next branch to the published release' step to the
'finalize' job, mirroring the rc job's pattern at line 479 (uses
VERSION from inputs.version rather than PRE_VERSION from steps.prerelease,
since finalize does not run the prerelease step);
- gates it on !inputs.dry_run + continue-on-error:true (matches rc);
- adds tests/release-finalize-syncs-next-version.test.cjs — a structural
YAML assertion that fails against the pre-fix workflow and passes after.
Failing-first demonstrated during development; the test parses job blocks
by indentation rather than grep so it stays valid as the file grows.
Root-cause diagnosis: scripts/sync-next-version.cjs:14 docstring admits
'used by release.yml's rc job, which has no next-targeting PR of its own'.
release.yml:506-685 (finalize job) had no sync-next-version step before
this patch. Every prior rc.N release has a matching 'chore: sync next
package version to 1.7.0-rc.N' commit; there is no such commit for 1.7.0.
* chore(release): sync next package version to 1.7.0 (#2423)
Replays the canonical 'chore: sync next package version to <v>' commit
that the release pipeline's rc job auto-produces via scripts/sync-next-version.cjs,
for the 1.7.0 final release that shipped on 2026-07-15 (commit dd4c90f82
'chore: finalize v1.7.0' on main). Without this, 'next' (and every feature
branch cut from it) carried 1.7.0-rc.6 indefinitely and reported it in
every npm script banner (e.g. 'lint:ci').
Bumps 43 synchronized manifests via the npm 'version' lifecycle hook
(scripts/sync-manifest-versions.cjs --stage + scripts/gen-capability-registry.cjs
--write), matching the file set of commit 27f69cc48 ('chore: sync next
package version to 1.7.0-rc.6') and commit dd4c90f82 ('chore: finalize
v1.7.0').
This is the immediate Layer-1 repair for #2423. Layer-2 (prevent recurrence)
is the workflow patch in the previous commit; Layer-3 (regression test)
ships with it. Future X.Y.0 final releases will produce this commit
automatically once the workflow fix lands.
* test(release): tighten #2423 dry-run gate assertion to the sync step
Code review of fix/2423 found that test #3 ('gates sync-next-version on
!inputs.dry_run') asserted too loosely: it scanned the entire finalize
block for any '!inputs.dry_run' line, so it would still pass if the
gate were stripped from the sync-next-version step specifically — the
exact regression the test name promises to catch. The finalize job has
multiple steps with their own !inputs.dry_run gates (e.g. Verify
publish), so the loose version masked the very bug it claimed to detect.
Tighten by extracting the specific YAML step block containing
'scripts/sync-next-version.cjs' and asserting the gate appears within
THAT step's lines, not anywhere in the job. Verified the tightened test:
- PASSES against the post-fix workflow (sync step has its own gate)
- FAILS when the sync step's gate is stripped (even when other steps
in finalize retain their own !inputs.dry_run gates) — the exact
regression that previously slipped through
Adds extractStepBlockContaining(jobBlock, marker) helper alongside the
existing extractJobBlock(text, jobName). Reuses the same indentation-
based parsing, so it stays valid as the file grows.
* chore(changeset): backfill pr:2437 in .changeset/sturdy-ibex-jump.md
CLAUDE.md changeset convention: 'Use placeholder pr:0 during initial commit.
Backfill immediately after gh api POST /pulls returns the real number.'
PR #2437 created from branch fix/2423-release-finalize-sync-next-version.
* fix(#2423): add see #2423 to allow-test-rule exemption per ADR-456
CI lint-allow-test-rule-refs failed on PR #2437: ADR-456 requires new
allow-test-rule exemptions added after the ADR's acceptance to include a
tracking issue number in the comment, in the form
// allow-test-rule: <reason> (see #NNN)
The exemption added in commit 976c8b0a2 lacked this ref. Fixed.
Verified locally:
node scripts/lint-allow-test-rule-refs.cjs
→ ok lint-allow-test-rule-refs: 173 grandfathered exemption(s) tracked, no novel untracked offenders
Full API Coverage by Default — Opt Out, Never Opt In. A phase that integrates
an external API/SDK/service can no longer seal without a decided coverage matrix.
- src/api-coverage.cts: deterministic detector (compound verb+noun signal +
<Service> API/SDK surface; stopword-guarded; strips fenced code) + matrix
parse/validate/render with field-length caps.
- check api-coverage.verify-pre: blocking seal-time gate; phase arg resolved as
a token under .planning/phases/ only (traversal-neutralized); validates
COVERAGE.md or blocks iff a strong integration signal is detected and no
matrix exists; fail-closed when phases tree exists but phase unresolvable.
- capabilities/ai-integration: workflow.api_coverage_gate config key (default
true), plan:pre contribution, blocking verify:pre gate. Data-driven.
- gsd-core/workflows/verify-work.md: generic verify:pre gate dispatch.
- Tests: detector FP/FN + matrix validation + fast-check bijection; gate e2e.
Code+security review findings fixed (stopword FP, scope containment, pipe/cap
rejection, prompt-injection message hygiene).
- Regenerated registry/matrix/loop-host-contract/goldens/baseline + docs.
Closes#1562
* feat(#1430): versioned capability manifest + native stamping (ADR-1244 Phase 1)
Make the capability manifest versioned — the data substrate the Capability
Ecosystem (ADR-1244) keys off:
- capability.json gains a REQUIRED semver `version` plus the optional
ecosystem envelope (`engines.gsd`, `compatVersions`, `integrity`,
`provenance`); the build-time conformance validator enforces them via a new
`validateVersionEnvelope()` (exported for the Phase 2 runtime overlay).
- All 32 native capabilities stamped with `version` (= package version,
lockstep) + `engines.gsd`; `sync-manifest-versions.cjs` gains a glob sweep
that keeps them in sync, and the issue-844 regression guard is extended.
- Strict SemVer 2.0.0 grammar blocks metacharacter/space/unicode smuggling in
version strings; range/integrity fields are shape-validated (satisfaction
and the load-time gate are deferred to Phase 2/4).
- Capability rel-paths emitted forward-slash for cross-platform git correctness.
Closes#1430
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs(#1430): add changeset for versioned capability manifest
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>