Promotes the verify-time enforcement decision that accreted as four chronological addenda on ADR-550 (#1259/#1279/#1346/#1278) into a single first-class architecture-of-record. ADR-550 keeps the spec-phase contract; this ADR owns the test-tier enforcement producer (locate -> machine-prove-fail-first -> run -> dispose) in src/prohibition-enforcement.cts.
Verified against live src/*.cts: the green AND-gate lives in runProhibitionEnforcement, the empty-file vacuity guard is isNonVacuousNodeTestPass (red-side is isNonVacuousNodeTestRed), and the ADR-857 pointer cites the real 'Verification substrate vs. plug-in tier' section / decision #6.
Closes#1606.
Appends an 'Alternatives considered (recall / representation / packaging side)' addendum to ADR-550: the two NEW spec-phase-side rejections (the withdrawn LLM requirement classifier #652, and a deterministic prohibition-recall engine) plus cross-references to the two already-decided ones (no polarity field on truths — D3; deferred single dispatcher CLI — D7e). Enforcement-side alternatives stay in ADR-1606.
Closes#1607.
* feat(#1684): add negotiated host-integration interface module
ADR-1239 Phase A: a pure, additive, no-I/O module exposing PROTOCOL_VERSION, the 8-axis HOST_INTEGRATION_AXES closed vocabulary, the UNDOCUMENTED fail-closed sentinel, negotiateHostCapabilities (effective subset of host-declared and engine-known), a typed degradation ladder, and host-capability profiles.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(#1684): validate and document host-integration axes (16 runtimes)
Extend validateRuntimeBody to validate the 8 hostIntegration axes (closed enums + undocumented sentinel + dispatch struct + reserved-key guards) and the widened runtime vocabulary; author a documentation-sourced hostIntegration block in all 16 runtime descriptors; regenerate the registry. Every per-CLI value is documented (cited) or the explicit undocumented sentinel.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs(#1684): add host-integration capability matrix and adr amendment
New per-CLI, per-axis citation reference (value/source/evidence for all 16 CLIs); ADR-1239 Phase-A-implemented amendment; CONTEXT.md glossary seam entry.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#1684): harden dispatch negotiation edge cases
Code-review hardening: treat NaN/Infinity maxDepth as missing (fail-closed, +warning); reset nested/background when namedDispatch collapses to false (struct consistency); SAFE_DEFAULTS dispatch floor to read-only; warn on non-finite protocolVersion; symmetric undocumented warnings for dispatch fields. Pure module — no consumers; behaviour fail-closed throughout.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore(#1684): register host-integration.cjs in lint-ignore and inventory
New tsc-generated bin/lib artifact: add to the eslint ignore list (ADR-457 — lint the .cts source), regenerate docs/INVENTORY-MANIFEST.json, and add the docs/INVENTORY.md CLI-modules row. Fixes the 3 gsd-test failures (551-eslint-bin-lib-coverage x2 + inventory-manifest-sync).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs(#1684): add changeset fragment for host-integration interface
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs(#1684): add how-to for sourcing a host's integration axes
Diataxis how-to guide for adding/updating a host's runtime.hostIntegration axes from authoritative docs, the undocumented-sentinel rule, validation, and extending the closed vocabulary. Completes the Step-5 doc quadrants (reference + explanation + how-to). Indexed in docs/README.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Append a dated amendment to ADR-1508 (append-only convention) recording that
the Runtime Artifact Conversion Module decision is implemented on next: the
content-rewrite engine + walkers + computePathPrefix live behind the deep seam
rewriteStagedSkillBodies/rewriteStagedCommandBodies, the getInstallExports relay
and GSD_TEST_MODE require are deleted, and CONTEXT.md marks the module SHIPPED.
Status stays Accepted (no Implemented status per docs/adr/README.md). Two
deferred follow-ups are tracked as sub-issues of the epic (neither a blocker):
- #1675 dedup convertClaudeToAugmentMarkdown family
- #1676 fast-check property test ($HOME-collapse + idempotency)
Delivery verified by a Codex (gpt-5.4, high) read-only review against the
epic's stated deliverables, cross-checked against the indexed code graph.
Closes#1507
Phase 0 of the Dynamic Context Management epic (#1671): the ADR (documentation) plus a non-shipping reference example under examples/dynamic-context-management/ — the Option-E predicate fact-store parser/selector, a self-contained --check/--write/--select index generator, a sample index, and a runnable demo.
The example is intentionally excluded from the build (src/->bin/lib/), the npm package files[], the installer, and the CI test suite (tests/) — nothing here is compiled into or installed with GSD. Production implementation lands in a later phase.
Resolves#1672
Refs #1671
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#1577): isolate WebFetch/WebSearch ingress + opt-in injection blocking
Split A of #1573 (security-critical). Scans WebFetch/WebSearch output (the
largest untrusted channel) in gsd-read-injection-scanner; shared
untrusted-input-boundary reference @-included by the 8 ingest agents
(randomized per-wrap delimiters, in-prompt self-scan guard, task-anchoring);
opt-in security.injection_blocking (default advisory — non-breaking).
arXiv: 2506.05739 (PPA), 2507.15219 (PromptArmor), 2504.20472 (Referencing), 2503.00061 (defense-in-depth).
* fix(#1577): address review — honest blocking docs, config key, ADR, property test, revert localized
- A1: rewrote the opt-in-blocking doc + Security changeset honestly — the PostToolUse hook is a
circuit-breaker (halts the agent's next step), NOT a redactor; it does not scrub content already
in the transcript. The prompt-level data/instruction boundary is the primary control.
- A2: registered security.injection_blocking in the config schema + defaults manifests (default
false) + an e2e config-roundtrip test; the dotted setter writes the nested shape the hook reads.
- A3: reverted the 4 hand-edited localized security-model.md (canonical EN only, per convention).
- A5: ADR-1577 (untrusted-input boundary + opt-in blocking; redaction-vs-circuit-breaker rationale).
- A6: property test — scanner never crashes / only emits valid JSON on unicode/large/malformed input.
- Also: inventory (untrusted-input-boundary.md) + agent-size baseline (8 ingest agents) +
drift-guard matcher update (Read -> Read|WebFetch|WebSearch). A7 (content<20 early-exit) left as
the noted pre-existing follow-up.
* fix(#1577): allowlist untrusted-input-boundary.md in injection-scan CI gate
The new reference quotes injection phrases ('ignore previous instructions',
'you are now…') as examples agents must NOT comply with, tripping the repo's
own prompt-injection-scan.sh diff gate (the standalone 'security' CI job, red
on HEAD). Allowlist it alongside the other security docs (security-model.md,
TEST-EXAMPLES.md) that legitimately demonstrate injection patterns. The JS
scanner test doesn't scan references/, so only the shell gate needed it.
Verified: scan --diff origin/next -> 0 findings; scanner JS test 15/15.
* fix(#1577): cover AC #2's gsd-ui-researcher + gsd-assumptions-analyzer
trek-e Major 1: the @-included set dropped two AC #2 agents. Restore them so
no named web-ingress agent is uncovered, keeping the two justified additions
(gsd-ai-researcher, gsd-domain-researcher). Final set = AC's 8 + 2 = 10.
- gsd-ui-researcher carries the full WebSearch/WebFetch + MCP-fetch toolset.
- gsd-assumptions-analyzer reads 5-15 codebase source files (external/source-
document ingress per the boundary), though it has no web tools.
INGEST_AGENTS in the isolation test now asserts all 10; size baselines
regenerated (+60 bytes each, both well under the DEFAULT cap); changeset
reworded 8 -> 10.
Verified: untrusted-input-isolation 14/14; agent-size-budget 39/39.
* docs(#1577): document security.injection_blocking + boundary seam
trek-e Major 2 + Minor:
- docs/CONFIGURATION.md: add the top-level security.injection_blocking key to
the Full Schema and a Security Settings subsection, distinguishing it from
the workflow.security_* namespace; honest circuit-breaker-not-redactor
framing matching ADR-1577 / security-model.
- CONTEXT.md: add the 'Untrusted-input boundary' seam glossary entry.
Verified: lint:docs ok; config-field-docs + contributor-standards green.
* test(#1577): make read-injection property test git-text, not binary
trek-e nit (and more): the file embedded a raw U+FFFF AND a raw NUL byte as
degenerate-edge inputs. The NUL is what actually made git classify it binary
(git binary = NUL in first 8K). Replace both with text-safe escapes that keep
the identical runtime values: '\\x00' and String.fromCodePoint(0xFFFF). File
now diffs/blames line-by-line.
Verified: property test 2/2; no NUL/raw-noncharacter bytes remain.
* docs(#1577): align untrusted boundary docs
Name all 10 ingress agents in INVENTORY/security-model and allowlist the intentional read-injection property corpus for the prompt-injection scanner.
* docs(#1577): align ADR ingest agent count
Update ADR-1577 from 8 to 10 ingest agents so it matches the actual boundary include set and the rest of the docs.
---------
Co-authored-by: Tom Boucher <trekkie@nomorestars.com>
* fix(#1634): honor capability hook matcher and node-prefix command
Capability hook install (applyCapabilitySharedEdits) wrote each settings.json
hook entry with no `matcher`, so a tool-scoped hook fired on every tool (a
fail-closed guard could then block the whole session), and emitted a bare
single-quoted script path so a .js-family hook from a git/tarball source
without +x failed with Permission denied on every matching call.
- Pass through an optional declared `matcher` (entry-level sibling of `hooks`);
absent => omitted (match-all), so existing shipped capabilities are unchanged.
- Validate `matcher` in the declaration (non-empty string, no control chars).
- Emit `node <quoted-path>` for .js/.cjs/.mjs hooks (mirrors first-party);
.sh and others keep the bare quoted path (unchanged).
Root cause: the manifest hook schema (validator rule C4) was {event, script}
only with no matcher, and applyCapabilitySharedEdits never read or wrote one;
the command used shellSingleQuote(absScript) with no node prefix.
Regression tests fail-first on both defects (matcher dropped; bare path) and
pass after the fix; #1460 command assertions updated for the node prefix.
* chore(#1634): backfill changeset pr:1638
* fix(#1634): resolve lint and windows CI failures
- validator: replace the control-character range regex with a char-code loop.
The literal /[\x00-\x1f\x7f]/ tripped ESLint's no-control-regex rule; char
codes are equally precise and lint-clean. Behavior unchanged (still rejects
matchers containing ASCII control characters incl. DEL).
- test: gate the executable-bit precondition on POSIX. Windows fs does not
honor POSIX write modes (a 0o644 write reads back as 0o666), so the
precondition is meaningless there and failed the windows-latest lane. The
node-prefix assertion — the actual fix — is platform-independent and still
runs everywhere.
* docs(#1634): amend ADR-894 for optional lifecycle hook matcher
The `role: "feature"` `hooks[]` entry now carries an optional `matcher`
(settings.json tool-scoping pattern: exact/pipe/wildcard/regex). Document
the field in the §2 schema table and record a Grilling-amendments entry:
the install path projects a declared matcher onto the emitted settings.json
hook entry (absent = match-all, so shipped capabilities are unchanged), and
per-runtime matcher projection (ADR-857 D8) stays a separate concern. This
amendment ships with the fix that introduced the field rather than as a
follow-up.
* docs(#1634): record WINDOWS-POSIX-MODE-BIT-ASSERT defect in CONTEXT.md
Capture the CI failure pattern from #1634/PR #1638 so it is not repeated: a
test that writes a file with a POSIX mode and then asserts statSync().mode
& 0o777 === <octal> passes on macOS/Linux but fails on windows-latest
(Windows fs does not honor POSIX write modes — reads back 0o666). Added as a
machine-greppable DEFECT predicate (symptom/examples/detect/fix-forward/
prevention) next to DEFECT.WINDOWS-TEST-PORTABILITY, with the fix-forward:
gate the mode-bit precondition on process.platform !== 'win32' and keep the
platform-independent behavioral assertion running everywhere.
Two changes to ADR-0174 §5 (Sync dispatch with tight-typed Result<T>):
1. Reconcile the documented Result type to the as-built code. The
original §5 text planned 'Unknown' / 'BadArgs' / 'ValidationFailed'
/ 'NotImplemented' / 'HandlerFailed'; the SDK retirement migration
kept the ADR-0012 names (UnknownCommand / InvalidArgs / HandlerFailure)
and never added the planned ValidationFailed or NotImplemented
variants. HandlerRefusal was added during implementation but never
back-filled into this ADR. The ADR now documents what consumers
actually depend on.
2. Add the optional exitReason?: string field on the InvalidArgs variant
(and update the makeInvalidArgs factory signature). This carries an
ERROR_REASON enum value separately from the existing reason
explanation text, so capability routers migrating from direct
error(msg, ERROR_REASON.USAGE) calls to makeInvalidArgs(...) Results
preserve ERROR_REASON granularity through the Hub Result →
error(msg, exitReason) translation. The field is additive and
backward-compatible.
Also tightens the amendment requirement: 'Adding a new variant OR
adding a field to an existing variant requires amending this ADR.'
Phase 0 of parent #1641. No code changes; pure ADR amendment.
Add a non-blocking, warn-only codebase-drift gate at plan:pre so a stale
STRUCTURE.md is surfaced before /gsd:plan-phase spawns the planner, instead
of being discovered mid-execution by the existing execute:wave:post gate.
Gated on a dedicated workflow.plan_drift_precheck toggle (default true),
independent of schema_drift_gate. Never blocks planning, never spawns the
mapper agent at plan time.
Review feedback (#1595):
- Use a documented conventional-commit type (feat, not enhance) per
CONTRIBUTING.md / gsd-validate-commit.sh.
- Normalize the plan_drift_precheck command references to the colon prose
form (/gsd:plan-phase, /gsd:map-codebase) to match plan-phase.md §5.65;
registry regenerated from capability.json.
- Make the test temp dirs hermetic: drain mkdtemp dirs in an after() hook
via the helpers.cleanup() budget (local/no-raw-rmsync-in-tests-compliant).
Closes#1592
Claude-Session: https://claude.ai/code/session_016JBiXEAofvB3prJim29XMS
Phase B-provide of epic #1258. Adds a build-generated skills/ dir +
a skills manifest field so plugin-installed GSD exposes gsd-core:<skill>
the native Claude Code way. Closes the gap where plugin-only installs
lacked the skill surface because bin/install.js never ran.
- scripts/gen-plugin-skills.cjs: build step converting commands/gsd/*.md
to skills/gsd-<stem>/SKILL.md via convertClaudeCommandToClaudeSkill
- .claude-plugin/plugin.json: add "skills": "./skills/"
- package.json: add skills to files, gen:plugin-skills to build chain
- tests/issue-766-plugin-manifest.test.cjs: Section H conformance
(manifest field + dir + frontmatter + count parity) + C2 skills symlink
- docs/adr/766-*.md: dated amendment adding skills surface row
- .changeset/rapid-bears-hum.md: type Added
- skills/: 69 generated gsd-<stem>/SKILL.md files (build-committed)
Closes#1596
Phase A of epic #1258. Adds the single authoritative ADR documenting
the per-runtime skill mapping + converter transform-contract catalog
that ADR-3660 (layout) and ADR-1508 (module ownership) each carry a
third of. Ships a companion reference matrix projecting all 16
runtimes from their capability descriptors.
- docs/adr/1593-skill-mapping-converter-methodology.md (new ADR)
- docs/reference/skill-mapping-matrix.md (new reference page)
- docs/adr/README.md (index row + status fixes: 3660, 1016 Proposed->Accepted)
- docs/adr/1016-runtime-capability-descriptor.md (header Proposed->Accepted;
the ConverterName enum is already code-enforced per ADR-857 phase 5e)
Doc-only. No code, no behavior change. Closes#1593.
Follow-up hygiene from the #1507 epic review (release blocker fixed in #1537):
- path-replacement.test.cjs: delete the hand-reimplemented computePathPrefix copy
(ADR-1508 said to); route all cases (incl. Windows + outside-home) through the
real _computePathPrefix so the test can no longer drift from the function.
- enh-1511: add an isWindowsHost no-op tripwire characterization test.
- enh-1511: add a deterministic (fs-method monkeypatch, root/OS-independent)
error-path test for applyRuntimeContentRewritesForCommandsInPlace — asserts the
temp dir is rm'd on a read failure with no orphaned gsd-cmd-rewrites-* leak.
- runtime-artifact-conversion.cts: @internal note on rewriteStagedCommandBodies
(deep-seam companion to rewriteStagedSkillBodies; no production caller today).
- ADR-1508 + CONTEXT.md: qualify the "single owner" claim with the deliberate
opencode/kilo applyOpencodeFamilyPathPrefix pre-conversion carve-out (#784).
No user-facing behavior change (tests + internal docs + one code comment).
Refs #1507.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Adopt maintainer-recommended option (a): keep the convertedAgentsKind /
stageAgentsForRuntimeWithConverter scope-threading plumbing, but DEFER the
8 runtimes' capability.json `agents`-kind declarations to a follow-up that
first ships the ADR-1235 §0 byte-for-byte parity harness.
The declarations were a live regression: the second `layout.kinds` consumer,
applySurface / `/gsd:surface` / `--materialize` (src/surface.cts), does not
mirror the legacy agent pipeline (copilot `.agent.md` rename, path-prefix
rewrite + attribution, stale cleanup), so a `/gsd:surface` toggle deleted
installed copilot `gsd-*.agent.md` and path-unrewrote the other 7 runtimes.
trek-e + davesienkowski both flagged this.
- Revert the agents-kind entries from the 8 capability.json files and
regenerate capability-registry.cjs (now matches next; 0 converted agents
kinds declared).
- Revert the declaration-driven kind-count test bumps
(runtime-artifact-layout, descriptor-drive, bug-782, enh-789, enh-790).
- Keep the synthetic-descriptor seam tests for convertedAgentsKind dispatch;
add a synthetic scope-threading test so the kept isGlobal plumbing stays
covered without depending on real declarations.
- Update the convertedAgentsKind doc comment to state declarations are
deferred pending the ADR-1235 §0 parity harness.
- Move ADR-1235 to Accepted.
- Reword the changeset to plumbing-only (docs-exempt now honest: no runtime
declares the kind, legacy loop authoritative, installed output unchanged).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The #1279 node-test machine-proof confirmed a known-bad subject drives the
negative test RED, but could not distinguish a genuine content-violation from a
deceptive test that reds merely because GSD_PROHIB_SUBJECT is set. Add an
optional fifth flat scalar `check_clean_fixture` (-> CheckDescriptor.cleanFixture)
threading a KNOWN-CLEAN control subject through projectProhibitions +
descriptorFromProjection. When present, the prover also runs the check against
the clean subject and requires GREEN, so fail-first is proven only when the check
is RED on the violation AND GREEN on the clean subject (content-dependent).
Opt-in and additive: absent a clean fixture the prover behaves exactly as
post-#1314 (no control, documented residual), preserving the zero-authoring
compose path; the lint-rule kind needs no analog (its subject IS the linted
file, no env indirection). Coverage: RED-first deceptive case, positive,
missing-clean fail-closed, round-trip read-back/emit, fast-check property
extended to the 5th scalar, and an end-to-end COMPOSE capstone (honest vs
deceptive). Docs: ADR-550 dated addendum, prohibition-probe reference,
spec-phase + verify-phase workflows.
Closes#1346
Claude-Session: https://claude.ai/code/session_01GsPRb8zvpcT7Eat6vZw8PX
Phase 0 of epic #1507. Records the decision to make the Runtime Artifact
Conversion Module the single owner of per-runtime content rewriting, flip the
dependency direction to installer/layout -> conversion, and close the
surface.cts -> bin/install.js getInstallExports relay. Doc-only.
Resolves ADR-3660 Initial-Scope deferral; distinct from epic #1258.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0187qgypdy1wkWRpdaf2hRuD
* fix(#1441): antigravity resolver prefers GSD-owned dir over first-existing
resolveConfigHomeFromDescriptor's dot-home-nested probe returned the first
bare-existing candidate, so a CLI user (~/.gemini/antigravity-cli) who also
had the IDE's ~/.gemini/antigravity dir was silently shadowed to the legacy
dir (probed first). Regression from #217 — pre-#217 returned
~/.gemini/antigravity unconditionally.
Add an optional probeMarker (gsd-core/VERSION) to the dot-home-nested
descriptor: a two-pass probe prefers the candidate GSD installed into, then
bare existence, then probe[0]. Behavior is byte-identical when probeMarker is
absent (windsurf etc. unaffected). Adds detectAntigravityDirAmbiguity() for
installer/operator guidance on already-misinstalled users (auto-relocation
ruled out per ADR-0008's single-configDir migration bound).
Regression tests fail before / pass after: coexistence + marker-priority
cases, end-to-end through the registry descriptor.
Claude-Session: https://claude.ai/code/session_01JU2WB23JLE3QTysPXuFJjB
* chore(#1441): add changeset for antigravity resolver fix
Claude-Session: https://claude.ai/code/session_01JU2WB23JLE3QTysPXuFJjB
Narrows P3 of ADR-1411 (Resolution Provenance, epic #1411) based on an
adversarial fit-analysis that showed a single Resolution<T> envelope adopted
by agent-skills, capability-state, and capability-writer fails the deletion
test: configured/reason are meaningless for capability verbs, and
capability-writer's errors[] (operation-not-applied) cannot fold into
warnings[]. The only genuinely shared seam is warnings: string[].
Changes:
- src/resolution.cts: new pure types+builder leaf — exports Resolution<T>
{value, configured, reason, warnings}, makeResolution<T>() builder, and
AgentSkillsValue {block, skills_count}. No other src/ imports.
- src/init.cts: cmdAgentSkills --json IR gains additive value:{block,
skills_count} field (built via makeResolution). All existing flat fields
(agent_type, block, skills_count, warnings, configured, reason, source,
degraded) are retained unchanged for back-compat.
- src/capability-state.cts: doc comment on ResolveCapabilityRuntimeStateResult
naming it the canonical read-verb envelope. No JSON change.
- src/capability-writer.cts: doc comment on SetCapabilityStateResult naming it
the canonical mutation-verb result (warnings=advisory, errors=operation-
not-applied). No JSON change.
- CONTEXT.md: new ### Resolution Convention glossary entry after
### Resolution Provenance.
- docs/adr/1411-resolution-provenance.md: P3 narrowing amendment appended.
- tests/resolution.test.cjs: 9 unit tests for makeResolution (new).
- tests/agent-skills.test.cjs: 2 P3 tests for value.block/value.skills_count
and back-compat of all flat fields.
All 277 tests pass (5 suites). npm run lint clean. All lint checks pass.
Part of #1411
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Decision record for the Resolution Provenance epic (#1411, P0). Establishes that
context resolution (config loading, project-root anchoring, workstream resolution)
must report its provenance rather than fall open silently to defaults — the
resolution-side analog of ADR-227. Binds the Config Loader Module, Project-Root
Resolution Module, and I/O Module. Adds the ADR, the index/seam-map entry, and the
CONTEXT.md glossary term.
Part of #1411Closes#1412
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* refactor(#1373): add markdown-sectionizer seam (ADR-1372 T0)
Establishes the canonical markdown-structure parsing seam per ADR-1372.
No existing parsers are modified; this is the foundational T0 tier only.
- docs/adr/1372-markdown-sectionizer-seam.md: Accepted ADR defining the
seam interface, the tiered migration plan (T0-T7), and the prohibition
enforcement approach (no-adhoc-markdown-parsing ESLint rule in T7).
- src/markdown-sectionizer.cts: Pure module, Node built-ins only.
Exports: stripFencedCode (CommonMark-correct state machine ported from
uat-predicate.cts _stripFencedBlocks, CRLF-safe, unterminatedFence
signal), tokenizeHeadings (ATX headings outside fenced blocks),
collectSections (line-by-line predicate-driven section collection),
collectSection (single named section, levelBounded stop, optional
stripFences), iterateBullets (dash/checkbox/numbered + continuation).
- tests/markdown-sectionizer.test.cjs: 54-test behavioral suite covering
the parser QA matrix (LF/CRLF, Unicode headings, headings-inside-fences,
unterminated fences, nested levels, all bullet markers, continuation
lines, empty/non-string input) plus 4 fast-check property tests
(idempotence, output shape, never-throws, length monotonicity).
- CONTEXT.md: Markdown Sectionizer glossary entry added (PR review gate).
Tests: 54 pass, 0 fail. Existing adr-parser + uat-passed tests: 22 pass.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(#1373): add extractTaggedBlocks + replaceSection to seam; register inventory
- src/markdown-sectionizer.cts: extend Section type with bodyStart/bodyEnd offsets;
add extractTaggedBlocks(content, tagName) (inner text of <tag>…</tag> blocks,
tagName regex-escaped, caller decides fence-stripping) and replaceSection(content,
section, newBody) (pure character-offset splice for read-modify-write callers);
update collectSections/collectSection to populate bodyStart/bodyEnd.
- tests/markdown-sectionizer.test.cjs: add 33 new behavioral tests for
extractTaggedBlocks, replaceSection, and a DEFECT.GENERATIVE-FIX parity guard
that asserts stripFencedCode and uat-predicate's _stripFencedBlocks agree on a
shared 9-item corpus; documents the known 4-space-indent divergence.
- docs/adr/1372-markdown-sectionizer-seam.md: list extractTaggedBlocks and
replaceSection in §"The seam".
- CONTEXT.md: update ### Markdown Sectionizer glossary entry with the two new exports.
- docs/INVENTORY.md: add markdown-sectionizer.cjs row (alphabetically between
loop-resolver and milestone).
- docs/INVENTORY-MANIFEST.json: regenerated via gen-inventory-manifest --write.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(#1373): clear no-unsafe-assignment + unused-var lint in markdown-sectionizer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(#1373): correct section offset/round-trip + CommonMark heading/fence edges; register eslint coverage
FIX 1 (CRITICAL): Enforce content.slice(bodyStart,bodyEnd) === body invariant in both
collectSection and collectSections. bodyEnd is now bodyStart + body.length instead of
the raw stop-line offset, eliminating the trailing-newline overcounting that caused
replaceSection to drop separator newlines (## A\nbody## B gluing bug).
FIX 2 (MED): tokenizeHeadings now accepts ≤3-space indent (CommonMark §4.5) and empty
ATX headings (## / ## ), text=''. 4-space indent correctly excluded.
FIX 3 (MED): collectSection gains stopAtLevel option — stops at the next heading whose
level ≤ stopAtLevel, independent of the opener's level. Enables state.cts ## sections
that also stop at ### without abusing levelBounded.
FIX 4 (MED): Backtick fence opener info string must not contain a backtick (CommonMark).
Applied in both stripFencedCode and tokenizeHeadings fence state machines. Tilde fences
unaffected.
FIX 5 (LOW): "byte offset" → "character (string-index) offset" in HeadingToken / Section
doc comments.
FIX 6 (LOW): extractTaggedBlocks doc comment documents nested-tag non-support; test locks
the non-greedy close-at-first-</tag> behavior.
FIX 7: Add gsd-core/bin/lib/markdown-sectionizer.cjs to eslint.config.mjs ignores so
tests/551-eslint-bin-lib-coverage.test.cjs passes (3/3).
Tests: 107 pass / 0 fail (was 87; +20 new tests for FIX 1–4, 6).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(#1373): gitignore tsc-built markdown-sectionizer.cjs (ADR-457 build-at-publish)
The seam's compiled artifact must be a build-at-publish output like every other
src/*.cts->bin/lib/*.cjs module (decisions, core, state, ...), not a committed
file. Add it to the ADR-457 ignore list and untrack it; build:lib/CI regenerate it.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Delivers option (a) from the #1314 maintainer review: thread a fourth flat
scalar check_violation_fixture through the projection so a prohibition authored
at spec-phase machine-proves fail-first and greens through the deterministic
path alone — zero hand-authoring at verify time.
- src/probe-core.cts: Prohibition gains check_violation_fixture?; projectProhibitions
emits it (both kinds) ONLY for a well-formed descriptor and ONLY when non-empty
(blank/absent -> projects absent -> producer hard-gates, never a partial green).
- src/prohibition-enforcement.cts: descriptorFromProjection reads it back into
violationFixture via the same numeric-coercion-safe scalar() normalizer.
- Tests (RED-first, proven non-vacuous by reverting both src edits): CHK-02(#1346)
projection emit, CHK-08(#1346) read-back, CHK-03(D) example round-trip, the
fast-check round-trip property extended to the 4th scalar (the contract trek-e
blocked #1301 on), and a real-subprocess COMPOSE capstone greening end-to-end
through project -> descriptorFromProjection -> default prover+runner.
- Docs flipped from 'hard-gates until #1346' to 'composes end-to-end': verify-phase.md,
prohibition-probe.md, spec-phase.md authoring, ADR-550 addendum, changeset.
#1346 now tracks only the node-test causation residual.
190 affected-suite tests green; eslint + tsc clean; size baseline regenerated.
Addresses the #1314 maintainer review (trek-e):
- Major 1 (fail-OPEN): defaultProveFailFirst's node-test branch only guarded
`if (!fixture)`. A missing/typo'd/stale violationFixture made GSD_PROHIB_SUBJECT
point at a missing file; an honest negative test threw ENOENT *inside its
callback* (a failing test named distinctly from the file), which
isNonVacuousNodeTestRed accepted as proof -> a green forged from a setup crash.
Now requires fs.existsSync(path.resolve(cwd, fixture)) before spawning, symmetric
with the lint-rule path's file-result guard. Regression test pins it (RED without
the guard); a second test pins cwd-relative fixture resolution.
- Major 2 (misleading prose): the #1278 projection carries no violationFixture, so
the deterministic-locate path always hard-gates (fail-closed) until a
check_violation_fixture scalar is threaded through. verify-phase.md and
prohibition-probe.md no longer read as if the projected path produces greens;
the ADR-550 addendum records both items. Tracked as follow-up #1346.
- Documented residual: existence is necessary but not sufficient (a red caused by
the env being set vs the subject's content); recorded as a constraint, in #1346.
- Nit: stale 'NOT attested fail-first' comment -> 'NOT machine-proven fail-first'.
64 tests pass; eslint + tsc clean; changeset valid.
* test(1278): RED-first descriptor parity + fail-closed guards + CHK-07 byte-stability (wave 1)
- CHK-03 (RED): extend PROB-14 parity in prohibition-probe.schema.test.cjs to carry the flat
check_kind/check_target/check_rule scalars through project->write->parseMustHavesBlock; the
non-droppable check_kind-presence assertion is the load-bearing RED trigger (fails because
projectProhibitions strips check_* on the current build).
- CHK-07 (GREEN forward-guard): probe-core.test.cjs pins descriptor-less byte-stability +
dispositionForProhibition fail-closed policy, with a t.todo marker forward-locking plan 01-02.
- CHK-06 (RED): prohibition-enforcement.test.cjs asserts descriptorFromProjection export +
fail-closed on absent/partial/unknown descriptors via the projection adapter (RED until 01-03).
- No src/*.cts or .cjs edits; no new test files; lint-test-file-count clean.
* feat(1278): add optional flat-scalar check descriptor fields to Prohibition interface (wave 2)
- check_kind?/check_target?/check_rule? mirror CheckDescriptor.kind/target/rule (minus caller-attested failFirst, #1279)
- optional so existing Prohibition consumers compile unchanged
* feat(1278): project check descriptor as flat scalars in projectProhibitions (wave 2)
- emit check_kind/check_target (+ check_rule only for lint-rule with a rule) when descriptor well-formed
- under-specified/descriptor-less items project byte-identically (CHK-07); flat scalars ride existing parseMustHavesBlock continuation-KV path (no parser rewrite)
- add CHK-02 probe-core unit cases pinning the projection
- turns CHK-03 parity test GREEN; dispositionForProhibition untouched
* feat(1278): descriptorFromProjection read-back adapter feeds fail-closed locate (wave 3)
- Add descriptorFromProjection(projected) -> CheckDescriptor | null to
src/prohibition-enforcement.cts: renames the projected flat scalars
check_kind/check_target/check_rule -> {kind,target,rule?}, or null when
the descriptor is absent/non-object (no check_kind key).
- failFirst is NEVER sourced from the projection (stays caller-attested; #1279).
- rule is set only when check_rule is a non-empty string; the adapter does NOT
re-validate kind/target/rule — an under-specified descriptor reconstructs to
one the EXISTING runProhibitionEnforcement LOCATE guard rejects (located:false,
never green). The merged #1259 guard stays the single source of fail-closed truth.
- Turns the RED CHK-06 fail-closed tests (plan 01-01) GREEN end-to-end; CHK-03 /
CHK-07 stay green. CheckDescriptor type, locate guard, dispositionForProhibition,
and parseMustHavesBlock are unchanged (additive +36/-0).
* feat(1278): verify-phase locates prohibition check from projected descriptor (wave 3)
- request.check kind/target/rule sourced from projected check_kind/check_target/check_rule via descriptorFromProjection, not verifier invention (CHK-05)
- replaces the #1278 author-supplied / tracked-follow-up note with the delivered deterministic-locate behavior
- preserves fail-closed routing: absent/partial descriptor -> never green, hard-gate in both modes
- failFirst stays a verify-time caller attestation; #1279 bounds the remaining fail-first proof
* feat(1278): spec-phase captures wired-check descriptor on test-tier resolution (wave 3)
- Step 5.6 'Keep it' / verification: test path captures check_kind/check_target/check_rule, projected onto must_haves.prohibitions for verify-phase deterministic locate (CHK-04)
- SOFT capture: a test-tier prohibition without a descriptor is still allowed (no hard authoring block); stays fail-closed/flagged downstream
- --auto captures only an unambiguous descriptor, never fabricates a check path
- failFirst NOT captured at spec-phase (verify-time attestation; #1279)
- PROB-06 soft-gate + text-mode (PROB-09) behavior unchanged
* chore(1278): re-baseline workflow size for grown verify-phase + spec-phase prose (wave 3)
- spec-phase.md 28438 -> 30343 (+1905), verify-phase.md 35362 -> 36498 (+1136)
- regenerated via npm run size:baseline (no hand-picked numbers); growth is the #1278 deterministic-locate + descriptor-capture prose
- workflow-size-budget guard green (122/122)
* docs(1278): ratify optional check descriptor in dated ADR-550 addendum + type:Changed changeset
- Append dated 2026-06-15 ADR-550 addendum ratifying the D3 prohibition-item
shape extension (optional flat-scalar check_kind/check_target/check_rule)
- Document flat-scalar rationale, deterministic projection/read-back,
fail-closed on partial/invalid/absent, #1279/policy out-of-scope
- Add .changeset/1278-prohibition-check-descriptor.md (type: Changed)
* docs(1278): document optional check descriptor in prohibition-probe reference + FEATURES
- Add 'Optional wired-check descriptor (deterministic locate, #1278)' section
to the prohibition-probe reference (flat-scalar keys, projection/read-back,
fail-closed + backward-compat, failFirst stays attested)
- Add deterministic prohibition-check descriptor source entry to FEATURES.md
- No CONTEXT.md glossary change: descriptor reuses existing wired-check /
verification:test vocabulary, no new glossary term introduced
* fix(1278): pass packaging gates — changeset pr field + retired slash-form fix
- Add required pr: 1278 to changeset (lint:changeset MISSING_PR hard requirement;
plan's 'omit if unknown' was inaccurate — issue number per #1259 convention,
updated to real PR number when opened) [Rule 3 - blocking]
- Fix retired /gsd-spec-phase -> /gsd:spec-phase at verify-phase.md:83 (wave-3
prose; caught by slash-namespace invariant #3443/bug-2543, blocked CHK-09
full-suite-green) [Rule 1 - bug]
- size:baseline + INVENTORY manifest verified in-sync post-build (no diff)
* docs(1278): add check descriptor + descriptorFromProjection to CONTEXT.md prohibition glossary
* fix(1278): harden descriptorFromProjection round-trip (numeric-coercion + stray-rule) per review
- MD-01/LW-01: narrow projected scalars to primitives + String()-coerce, so a
numeric-looking check_target (parseMustHavesBlock coerces ^\d+$ to number)
reconstructs as a string and locates instead of silently un-locating; no
as-string type-lie, satisfies no-base-to-string.
- LW-02: attach rule only for the lint-rule kind (drop a stray node-test rule).
- LW-03: document the optional check_* keys in the reference Output schema.
RED->GREEN tests added in prohibition-enforcement.test.cjs.
* chore(1278): set changeset pr to 1301
* test(1278): add fast-check property for the check-descriptor round-trip + fail-closed (trek-e review)
RULESET.TESTS.property-based-testing: the projectProhibitions -> render ->
parseMustHavesBlock -> descriptorFromProjection chain is a bijective/transformation
contract. Adds 2 fc properties to tests/probe-core.property.test.cjs (no new file;
ratchet stays at 2 for probe-core):
- well-formed descriptors survive the round-trip across the full string domain
incl. the numeric-coercion case (target/rule reconstruct as strings);
- under-specified/invalid descriptors (absent / target-less / rule-less /
unknown-kind) are always fail-closed (never green, flagged, unlocated).
Stability is asserted at the descriptorFromProjection layer (the raw parse step is
intentionally lossy for numeric scalars; the shared parser is unchanged).
---------
Co-authored-by: Tom Boucher <trekkie@nomorestars.com>
Maintainer CHANGES_REQUESTED (reviewed e08667e5, pre-portability-fix):
- B1: lint-rule no longer greens an unparseable target (eslintHasFatalError -> fail
closed on any fatal/parse error) or an inline-suppressed violation (eslintJsonHasRule
now scans suppressedMessages too). RED-first + real-runner repros.
- B2: both child spawns get a bounded timeout (30s node / 60s eslint) + 16MiB maxBuffer;
timeout fails closed. Injectable timeoutMs (positive-only — 0/negative can't disable
the bound) enables a fast 1.5s hang test.
- M1: scoped verify-phase.md — the check descriptor is author-supplied for now; filed
#1278 for deterministic auto-locate of the descriptor (the locate half).
- M2: added tests/prohibition-enforcement.property.test.cjs (fast-check fail-closed
invariants; within the <=2-file budget).
- m1: tapTestNames excludes # SKIP/# TODO; parseNodeTestSummary tracks # cancelled;
isNonVacuousNodeTestPass requires cancelled===0.
- m2: scoped the determinism claim to the decision/parse layer (real runner is env-dependent).
- m3: filed #1279 for machine-proven fail-first (violation-fixture probe).
- n1: -- before target in both arg builders (option-injection). n2: dropped dead token.
- B3 (Windows npx) was already fixed in 2af76306 (pushed ~65s after the review).
Verified node 22 + 24; size baseline regenerated for the verify-phase note.
Adversarial pre-submission review found the injected-runCheck tests masked a
non-functional real runner. Fixes:
- BL-01 (false green on vacuous test): the node-test runner now parses the TAP
summary and requires a NON-VACUOUS pass (>=1 test, >=1 pass, 0 fail) AND a
reported test named distinctly from the file — node --test counts an empty file
as one passing test, so counts alone could not catch it.
- SF-01 (lint anchor never greened): the lint-rule runner now runs the project
eslint as --format json and filters by ruleId, so plugin rules (local/*) load
via the flat config — bare --rule cannot load a plugin. local/no-source-grep
now genuinely greens (covered by a real, non-injected test).
- BL-02 (tautological fail-first): the runner no longer echoes the caller's
failFirst as if confirmed. failFirst is documented as caller-ATTESTED; the
producer requires attestation + a genuine non-vacuous pass. Machine-proven
fail-first (needs a violation fixture) is flagged as a tracked follow-up in
ADR-550, the changeset, FEATURES, the reference doc, and verify-phase.
- SF-02: added real-runner end-to-end tests (no injected runCheck) + pure,
exported parse/filter helpers (parseNodeTestSummary, tapTestNames,
eslintJsonHasRule, eslintFileResultCount) so the shipping branches are
mutation-pinned.
- NIT-01/02: LOCATE guard rejects empty-string rule and unknown kinds.
- Hardening: spawn checks with NODE_TEST_CONTEXT/NODE_OPTIONS scrubbed so an
ambient test-runner context cannot corrupt a verify-time result.
- Docs reconciled to the shipped behavior (no 'confirms fail-first' overclaim).
Adds the spec-time prohibition probe (spec-phase Step 5.6) — the second adapter of the probe-core resolution model. Surfaces unwritten must-NOT constraints as negative SPEC acceptance criteria with test/judgment verification tiers; fail-closed at verify time. Per ADR-550. Closes#644.
Records the design gate for moving agent conversion off the inline bin/install.js loop onto the descriptor path (ADR-3660): the two-(really three-)path problem, the 10 parity behaviors the descriptor agents path must gain (verified against code — the issue's 7 plus Qwen/Hermes branding, the Codex TOML sidecar, and stale-agent cleanup), an AgentConverterContext contract to fix the leaky (content)=>string converter signature, and an incremental per-runtime cutover gated on byte-for-byte golden parity (full + minimal mode). Status: Proposed. Codex-reviewed for technical accuracy.
Closes#1235
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* docs(#1239): ADR for GSD as an embeddable orchestration engine
Records the design to invert GSD from a standalone installer that projects
onto a host into an embeddable orchestration engine a host loads as a plugin,
driven through a negotiated host-integration interface. Unifies ADR-1016
projection (the declarative adapter) with imperative embedding behind one
contract; grounded in a 9-host capability survey.
Closes#1239
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(#1239): de-slash illustrative command placeholders (docs-parity gate)
Replace illustrative /gsd:x /gsd-x /gsd.x placeholders with namespace-prefix
wording so the docs-parity live-registry check does not parse them as
non-live commands.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(#1213): Capability State Writer — write-side inverse of the resolver
Adds src/capability-writer.cts (setCapabilityState + cmdCapabilitySet) and the
`gsd-tools capability set` subcommand: the write-side inverse of the capability
resolver (ADR-1213). One desired capability state projects onto the substrates —
`enabled` drives the runtime surface (canonical on/off), `gates` drive federated
config keys (hook granularity), install profile is a read-only floor — then
re-resolves and reports divergence (assert-and-report), so "off means off" holds
as a write-time invariant. Adds batched setConfigValues; routes gsd:settings
capability hook-gates through the writer. Docs: CLI-TOOLS reference, how-to,
ADR-1213, CONTEXT.md term.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore(#1213): add changeset for Capability State Writer (#1225)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Proposes a runtime-gated, default-off BETA capability (post ADR-857) that
adopts Claude Code's Workflow tool — the engine behind /effort ultracode — as
an optional parallel-execution backend for the GSD loop, and folds the existing
ultraplan plan-offload under the same capability. Restores wave parallelism +
plan-checker + verifier on Claude Code that #853 currently forces inline.
Design-only ADR draft accompanying feature request #1143; implementation is
blocked on #857 being released.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#1151): drive codex sandbox_mode emission from runtime descriptor sandboxTier axis
The sandboxTier runtime-capability axis was cosmetic: declared and validated
on all 16 descriptors but read by nothing. The codex per-agent sandbox_mode
line was emitted unconditionally from the hardcoded CODEX_AGENT_SANDBOX map, so
the descriptor field drove no behaviour (ADR-857 audit finding F10; the
"rides along in 5e/5g" promise in ADR-1016 §8 never landed).
Make the axis load-bearing:
- resolveInstallPlan projects sandboxTier as a 7th InstallPlan axis and fails
loud (throws) on a missing/invalid value rather than coercing to 'none'.
- installCodexConfig / generateCodexAgentToml gate sandbox_mode emission on
sandboxTier !== 'none'.
- The per-agent CODEX_AGENT_SANDBOX map is kept: it is GSD agent policy, not a
runtime-descriptor property (different layer). Full removal of that map is
tracked under #1138 (phase-6 descriptor-residue removal).
For codex (sandboxTier === 'codex-agent-sandbox') the emitted TOML is
byte-identical to before; for 'none' runtimes sandbox_mode is omitted.
Adds leaf, projection, and installCodexConfig threading-seam regression tests;
updates the enh-1082 InstallPlan golden master with sandboxTier for all 16
runtimes. Confirmed hypothesis: schema-first vocabulary closure outran consumer
wiring, with no conformance gate to catch the orphaned axis.
Closes#1151
Refs #857, #1138
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore(#1151): stamp changeset with PR number 1152
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Classify edge-probe/prohibition-probe predicate-generation as core
verification substrate (not an off-by-default Feature Capability),
settled before ADR-857 phase 6 (Migrate) freezes the core/plug-in line.
Prompted by @davesienkowski's boundary analysis on #857.
- ADR-857: amendment note + top-level decision carve-out + Loop Extension
Points exemption + new "Verification substrate vs. plug-in tier"
subsection + 2 Alternatives rows + phase-6 rollout exception + Open
Questions resolution.
- ADR-550: cross-reference pinning the core-substrate classification;
ties "exogenous grading" to Decision 4 (judgment-tier) and Decision 5
(test the contract, not the classifier).
- CONTEXT.md: glossary — Probe Core / Edge Probe reclassified (probe-core
on the contract side, adapters as the generator) + new "Verification
substrate (predicate boundary)" term.
Decomposition: the verifier<->predicate contract is core/non-toggleable;
the generator (probe adapters) is core-default but independently
versionable. Docs-only; no user-facing change.
Closes#1120
Refs #857
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* ci(#1104): sync next package.json version to the last published release
next rested on a -dev stream per ADR-660 (1.3.1-dev.0) — a never-published
placeholder that leaked to source/dev installs. Make every release type write
its exact published version back to next:
- finalize/hotfix (push main): auto-backmerge sets next's version to main's
released version, folded into the existing back-merge PR (+ pinned setup-node).
- rc (no main push): the rc job opens + admin-merges a sync PR after publish.
Shared, fail-closed scripts/sync-next-version.cjs stamps package.json + the
runtime manifests via the npm version hook and refuses any non-release version.
Amends ADR-660 (supersedes the -dev stream decision).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* ci(#1104): harden next-version sync against post-publish failure modes
Review hardening (Codex + code-review gates) on the #1104 sync helper and
its workflow callers:
- release.yml rc Sync step: continue-on-error so a post-publish sync hiccup
cannot fail an already-published release (npm immutability would block re-run).
- auto-backmerge.yml inline sync: set -euo pipefail + validate VERSION before
any shell use (closes a ${VERSION}-in-commit-message injection vector); git
add -u instead of -A.
- sync-next-version.cjs: reuse an existing open PR instead of failing gh pr
create on rc re-runs; regex-parse the PR number and fail loud; discriminate
the git diff --cached --quiet exit code (only status 1 == has-diff, else
rethrow); git add -u to avoid sweeping runner artifacts into next; tolerate
already-merged on admin merge.
- tests: +2 (existing-PR reuse, non-diff rethrow); 14/14 pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* feat(spec-phase): spec-completeness edge-probe (#550) — relocated to gsd-core/
Rebased onto current next and relocated the whole feature from get-shit-done/ to
gsd-core/ per #615 (trek-e re-review #4, option 1). The artifact now builds to
gsd-core/bin/lib/edge-probe.cjs; all hard-coded path strings (tests, workflow
@-refs, run-tests.cjs sentinel, eslint ADR-457 ignore, .gitignore) updated.
Content conflicts in .gitignore / eslint.config.mjs / run-tests.cjs resolved.
Feature: Step 5.5 edge-completeness probe walks each SPEC requirement against a
closed 8-category edge taxonomy, proposes applicable candidate edges, and resolves
each (covered/dismissed/backstop/unresolved). covered/backstop criteria are lifted
by plan-phase into must_haves.truths, extending the goal-backward verifier's reach
to boundary edges no requirement was written for. Engine authored as strict TS
(src/edge-probe.cts, ADR-457), compiled to the gitignored gsd-core/bin/lib/edge-probe.cjs.
Folds in every prior review round on PR #584:
- RR-01..03: plan-phase resolves the phase *-SPEC.md and injects {SPEC_PATH} into the
planner; must_haves<->Edge-Coverage quality_gate; held-out planner-contract test.
- RR-04/11: Step 5.5 invokes the compiled engine at runtime (npm --prefix-pinned,
source-checkout-gated build fallback) instead of LLM re-derivation; the engine
capture is exit-checked and the report JSON-validated before use (fail closed).
- RR-05..10: all six fixtures embedded + count-equality; backstop/covered require a
resolution; Array.isArray(shapes); duplicate-resolution rejection; CLI JSON exit(2);
per-artifact build sentinel.
- Authored-shape validation: invalid (non-empty) shapes fail closed (VALID_SHAPES).
- Adversarial-review hardening: orphan/typo resolution rejection, requirement input
validation (id/text/shapes, duplicate id, non-array), zero-applicable guard.
Full suite 0 failures; npm run lint 0 errors; edge-probe suite 72/72.
* test(#550): RED — status×verification re-cut + probe-core engine specs
Re-cut the edge-probe resolution model onto two orthogonal axes per
ADR-550 Decision 7 (trek-e #644 comments 2026-06-03 14:36 + 14:44):
status: resolved | dismissed | unresolved (lifecycle, shared)
verification: explicit | backstop | null (only when resolved)
- tests/probe-core.test.cjs (new): behavioral specs for the generic engine
to be extracted — validateResolution(r, validators), validateRequirement,
analyzeCoverage(items, resolutions?, validators), byVerification rollup,
runProbeCli I/O scaffold (injected-io unit tests).
- tests/edge-probe.test.cjs: covered→{resolved,explicit}, backstop→
{resolved,backstop}; coverage gains byVerification.{explicit,backstop};
proposeEdges items gain verification:null.
- 6 fixtures re-genned + re-embedded in edge-probe.md; coverage.resolved
COUNT preserved on every fixture (closed set = resolved+dismissed; doc
line: 'adjacency=covered + ordering=dismissed' -> 2). edge-probe.md prose
rewritten to the two-axis model.
Fails as expected: probe-core.cjs has no source yet; edge-probe still
emits the old covered/backstop enum (27/61 edge specs red).
* feat(#550): extract probe-core seam + refactor edge-probe onto it (ADR-550 D7)
Extract the generic resolution model into src/probe-core.cts (the shared
seam the prohibition probe #644 is born on) and refactor edge-probe.cts
into its first adapter.
probe-core owns (probe-agnostic):
- the status×verification re-cut: status: resolved|dismissed|unresolved ×
verification: <probe-defined>|null
- validateResolution(r, validators) / validateRequirement (generic id+text)
- analyzeCoverage(items, resolutions?, validators) over ALREADY-PROPOSED
items[] (core never assumes propose is deterministic — edge resolves via
LLM, #644 proposes via LLM), with merge / dup-reject / orphan-reject
- byVerification rollup; coverage.resolved = closed set (resolved+dismissed),
count-preserved from the pre-re-cut engine
- runProbeCli I/O scaffold (injected io; one bin per probe)
- hybrid typing: generic params + injected runtime validators
{categories, verification, requiredFieldsByVerification} (ADR-550 #5)
edge-probe keeps ONLY the edge cluster: Shape/SHAPE_CUES/VALID_SHAPES/
classifyShape/TAXONOMY/applicableCategories/proposeEdges + EDGE_VALIDATORS
{explicit,backstop}; delegates merge/rollup/CLI to probe-core. Every shipped
#584 guarantee preserved (fail-closed shapes, orphan/dup rejection, input
validation, CLI exit 2). 104/104 edge+probe-core+docs+contract specs green.
* chore(#550): register probe-core.cjs artifact in ledgers + inventory
New gitignored build artifact gsd-core/bin/lib/probe-core.cjs (compiled
from src/probe-core.cts) needs registering in every artifact ledger:
- .gitignore + eslint.config.mjs ADR-457 ignore: lint the .cts source,
never the emitted .cjs.
- scripts/run-tests.cjs per-artifact build sentinel: build if probe-core.cjs
is missing on a clean checkout.
- docs/INVENTORY.md: CLI Modules 83 -> 84, new probe-core.cjs row, and the
edge-probe.cjs row updated to reflect it is now the first probe-core adapter.
- docs/INVENTORY-MANIFEST.json: regenerated (gen-inventory-manifest.cjs --write).
probe-core.test.cjs is a single test file (under the 2-file cap), so no
lint-test-file-count allowlist entry is needed.
* docs(adr-550): spec-phase probe pattern + prohibition contract [Accepted]
trek-e's final ADR-550 body, verbatim (open-gsd/gsd-core#644 comment
2026-06-03T15:23Z), Accepted by both maintainer and #550 author. Lands on
PR #584 alongside the probe-core extraction (Decision 7) it governs, so the
contract and its first implementation arrive together.
Decisions: probe packaging (3 layers); recall->precision protocol;
prohibition home = SPEC acceptance criteria + optional must_haves.prohibitions:
(truths untouched, no polarity); tiered verification test|judgment
(judgment = mode-dependent soft-gate-with-flags, never silent pass / never
hard-halt); CI tests the contract not the classifier; secure-phase ownership
seam; and Decision 7 — probe-core seam + status×verification re-cut (7a-7e),
which this PR implements.
* feat(#550): fail-closed probe-core across full status×verification + runProbeCli structural guard
Re-review #5 (trek-e) seam-hardening on the generic probe-core contract #644 inherits:
- validateResolution now enforces the 'verification is null unless resolved'
invariant for EVERY status (not just resolved): a dismissed/unresolved
resolution carrying a verification tier is rejected instead of merging verbatim.
- An unresolved resolution carrying a resolution/reason payload is rejected
(was silently dropped into the unresolved count).
- runProbeCli structurally validates the report an adapter returns before writing
it (was: any malformed object stringified as green output) — fails closed → exit 2.
- coverage.resolved kept count-preserved (closed set) per the blessed migration
contract; a new test locks that an all-dismissed run is NOT affirmatively covered
(byVerification is the honest gate).
Tests: probe-core 37/37; full edge-probe suite 113/113; full suite 1816/1816; lint 0.
* docs(adr-550): annotate Decision 5 #584/#644 scope + correct 7a coverage.resolved semantics
Re-review #5 (trek-e) clarity edits:
- Decision 5: annotate that only contract item (a) ships on #584 (the edge
adapter's parse+validate test); (b)–(d) are #644 scope, matching Consequences.
- Decision 7a: correct the 'coverage.resolved is preserved (status === resolved)'
parenthetical — the blessed/implemented semantics are count-preserved = the
CLOSED set (resolved + dismissed = applicable − unresolved), with byVerification
carrying the per-tier resolved-status breakdown. The old parenthetical
contradicted the shipped count.
* test(#550): cover runProbeCli structural-guard numeric-count branch
Second-pass coverage audit found the 'coverage object present but counts
non-numeric' branch of isValidReport (built probe-core.cjs:60-61) unexercised —
the {nope:true} malformed test fails earlier at the items[] check. Add a report
with well-formed items[] + a coverage object carrying non-numeric counts so the
numeric branch is hit. No source change; closes the line gap.
* fix(#550): reject edge requirement with missing/empty text when no shapes override (M2)
The edge adapter's `text` is the classification signal and a required field, but
core `validateRequirement` left it optional, so a `{ id }` requirement classified to
zero shapes -> zero edges -> was silently DROPPED from coverage with no signal -- the
exact fail-open this feature exists to eliminate. Reject missing/empty text unless an
authored `shapes` override (incl. `[]`) opts out of prose classification.
* fix(#550): validate verbatim items in analyzeCoverage shared seam (m1)
A proposed item with no matching author resolution is rolled up VERBATIM, but its own
status/fields were never validated -- an item carrying an out-of-enum status (the dropped
"covered") or `dismissed` without a reason would be counted closed. The edge adapter only
proposes `unresolved` items, but the prohibition adapter (#644) proposes LLM-generated
items that arrive populated. An Item is structurally a superset of a Resolution, so reuse
validateResolution to fail closed. ADR-550 Decision 5 hardens this shared seam.
* fix(#550): move edge-coverage lift instruction to runtime planner surface (M1)
templates/planner-subagent-prompt.md is loaded by nothing at runtime (no @-import in
agents/gsd-planner.md; plan-phase.md spawns the planner from its own inline
<planning_context>), so the precise covered/backstop -> must_haves.truths lift instruction
this PR added there never reached the planner -- and the RR-02 contract test asserted it in
that dead file, giving false green. Move the instruction into plan-phase.md's runtime
<downstream_consumer> block (where the rest of the wire already lives), revert the dead-template
edit, and retarget RR-02 to the loaded surface with a guard against re-orphaning.
* test(#550): lock machine<->SPEC vocabulary mapping against drift (m2)
The machine contract uses orthogonal status x verification; the SPEC table renders a flat
covered/dismissed/backstop/unresolved. The migration map (ADR-550 Decision 7a) was prose-only
with no test, so the layers could silently drift. The SPEC table is LLM-rendered (no JS
renderer to round-trip), so pin the canonical bijection as code AND ground it in every doc
surface that renders the vocabulary (ADR migration clause, spec.md legend, reference mapping
table) -- a rename or remap now fails the suite.
* docs(#550): add how-to for resolving edge-coverage findings (B1)
Feature shipped reference coverage (FEATURES.md, COMMANDS.md, references/edge-probe.md) but
no how-to -- reference-only does not satisfy the Diataxis docs standard for a user-facing
capability. Add a single-mode how-to (imperative, goal-directed) walking each resolution
state (specify/dismiss/backstop/defer), the soft gate, and --auto, with taxonomy/concepts
linked out to the reference. Register it in the docs/how-to index.
* docs(#550): add Probe Core + Edge Probe glossary entries to CONTEXT.md (N1)
trek-e re-review #7 N1 (Major): adding probe-core/edge-probe as src/*.cts-derived
seam modules (ADR-550 Decision 7) requires CONTEXT.md Domain-terms glossary entries
per the maintainer-enforced new-seam gate. Adds '### Probe Core Module' and
'### Edge Probe Module' with exports, generated source paths, and the ADR-550 seam
contract, placed beside the Research Module feature-seam entries.
* test(#550): add fast-check property suite for probe-core analyzeCoverage (N2)
trek-e re-review #7 N2 (RULESET.TESTS.property-based-testing): analyzeCoverage is a
transformation/rollup module, the class the property-testing predicate covers, and
fast-check is already a dependency with an established *.property.test.cjs pattern.
Adds 5 properties over the algebraic invariants: closed-set identity
(applicable === resolved + unresolved), byVerification sums ≤ resolved, per-tier
recount + resolved-status-only counting, rollup determinism, and stable orphan
rejection. 200 runs/seed 42 via helpers/fast-check-setup.cjs.
* test(#550): align allow-test-rule tokens to canonical runtime-contract-is-the-product (N3)
trek-e re-review #7 N3 (Nit): the // allow-test-rule: tokens (source-text-is-the-product,
docs-parity) differed from CONTEXT.md's canonical exemption category
'runtime-contract-is-the-product' (RULESET.TESTS.no-source-grep.exemption, CONTEXT.md:240).
All three tests assert deployed runtime-contract surfaces (spec-phase.md Step 5.5, the
plan-phase.md planner prompt, the rendered reference/SPEC/ADR vocabulary), so the canonical
category fits; each now carries a one-line justification per the ruleset format. Free-text
reason — lint behavior unchanged.
* test(#550): re-baseline plan-phase + spec-phase byte sizes for edge-probe
Rebased onto next (e4f0910d), which replaced the line-based tier-max
size ratchet (#597) with the byte-based per-file baseline guard (#1074).
The edge-probe feature legitimately grows two workflows:
- spec-phase.md 15131 -> 23094 (+7963): Step 5.5 Edge-Completeness Probe
- plan-phase.md 93135 -> 94253 (+1118): covered/backstop edge lift into
must_haves.truths (the live <downstream_consumer> block)
Both remain under their tier hard caps (plan-phase XL 98304, ~4KB
headroom; spec-phase DEFAULT 40960). Growth is real inline workflow
content the feature requires at that step — not eager @-import proxy
gaming. Drops the obsolete line-based XL_BUDGET 93000->94000 bump
(superseded by the byte baseline) via rebase.
* chore(#550): reconcile INVENTORY headline counts after rebase onto next
Rebase onto current next dropped the prior reconcile commit (stale counts
refs 68 / CLI 107). Current next + the edge-probe additions yield:
- References (68 -> 69 shipped): + gsd-core/references/edge-probe.md
- CLI Modules (107 -> 109 shipped): + edge-probe.cjs + probe-core.cjs
Rows for all three already present; only the headline counts were stale.
Caught by tests/inventory-counts.test.cjs (CI ubuntu-24 leg).
* feat(#1077): phase 5f-2 — drive the hookEvents dialect (PostToolUse/AfterTool) from the descriptor
postToolEvent (bin/install.js) and preToolEvent (applySettingsJsonHooks in
runtime-hooks-surface.cts) now select the event-name dialect from
registry.runtimes[id].runtime.hookEvents instead of the hardcoded
(runtime === 'gemini' || runtime === 'antigravity') check: hookEvents === 'gemini'
→ AfterTool/BeforeTool; else → PostToolUse/PreToolUse. hookEvents threaded into the
applySettingsJsonHooks opts bag. Equivalence-preserving (Codex-verified): hookEvents
'gemini' is exactly {gemini, antigravity}, 'claude' the rest; undefined → claude
dialect (matches the old else).
The per-event SET guards (isQwen||claude → SubagentStop/Stop/PreCompact;
runtime==='claude' → FileChanged; isGemini → Gemini agent-events) stay HARDCODED —
hookEvents (2-value) is too coarse to drive them (the event set differs within
hookEvents='claude'); per-event-set drive tracked in #1076.
Registry-parity test (enh-1077): asserts BOTH post-tool (AfterTool/PostToolUse) AND
pre-tool (BeforeTool/PreToolUse) dialects are a pure function of hookEvents, for
gemini/antigravity/claude/augment — non-vacuous (catches a broken hookEvents thread).
Closes#1077
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(#1077): build hooks/dist in before() so dialect-drive test passes in scoped CI
hooks/dist is gitignored and absent in scoped/windows CI jobs that do not
pre-run build:hooks. Without it, install() finds no hook files and all
AfterTool/BeforeTool/PostToolUse/PreToolUse event arrays come back empty,
failing every hook-presence assertion. Added an idempotent ensureHooksDist()
called in a top-level before() — mirrors the pattern from
bug-376-claude-js-hook-gsd-rewriter.test.cjs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(#1055): add installSurface/writesSharedSettings/permissionWriter/extendedHookEvents to runtime descriptors
Purely additive: four new fields on all 16 runtime capability.json descriptors,
validator extended with three new closed-vocab sets, registry regenerated.
Test fixtures (VALID_RUNTIME_CAP and makeRuntimeCap) updated to include the new
required fields so all 255 capability-registry tests continue to pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(#1076): drive per-event hook guards from extendedHookEvents descriptor
Replace hardcoded runtime-name checks (isQwen||runtime==='claude',
runtime==='claude', isGemini) in applySettingsJsonHooks with a single
descriptor-driven extendedEvents array derived from the new opts field.
Remove isQwen and isGemini derivations (no remaining uses after the three
guard blocks are migrated). Wire extendedHookEvents from the capability
registry in bin/install.js call site. Add behavioral regression test
(enh-1076-extended-hook-events-drive.test.cjs) confirming the drive is
purely descriptor-based and runtime-name-agnostic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(#1055): drive resolveRuntimeConfigIntent from the runtime descriptor; retire hand-kept REGISTRY
- Rewrites src/runtime-config-adapter-registry.cts to require capability-registry.cjs
and read installSurface / writesSharedSettings / permissionWriter from
runtimes[id].runtime; deletes the hand-kept REGISTRY const (ADR-857 phase 5g drive 2).
- ALLOWED_CONFIG_RUNTIMES is now derived from descriptor entries that have installSurface.
- Fixes the configFormat parity gate in scripts/gen-capability-registry.cjs to read
installSurface directly from capMap descriptor bodies, breaking the require cycle
(adapter now requires the generated registry; gen-script must not require the adapter).
- Adds golden-master test tests/enh-1055-config-intent-descriptor-drive.test.cjs (41 tests)
pinning all 16 runtimes' return shapes and the TypeError-on-unknown contract.
- Updates scripts/lint-test-file-count.allowlist.json (config module, +1 file).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(#1076): make hooksSurface descriptor load-bearing for the settings-json hook-skip
- Adds hooksSurface?: string to ApplySettingsJsonHooksOpts and destructuring in
applySettingsJsonHooks (src/runtime-hooks-surface.cts).
- Replaces the hardcoded !isOpencode && !isKilo hook-skip guard with
hooksSurface !== 'none'; removes the now-unused isOpencode/isKilo derivations
(ADR-857 phase 5g drive 3).
- Passes hooksSurface from the runtime descriptor at the applySettingsJsonHooks
call site in bin/install.js using the established
_capabilityRegistry?.runtimes?.[runtime]?.runtime?.hooksSurface idiom.
- Extends tests/enh-1076-extended-hook-events-drive.test.cjs with two new suites
proving: (a) hooksSurface:'none' writes no hooks regardless of runtime name;
(b) hooksSurface:'settings-json' writes hooks even for 'opencode' (previously
hardcoded to skip).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs: record installSurface/writesSharedSettings/permissionWriter/extendedHookEvents descriptor axes in ADR-1016
Add Decision 7a documenting the four axes added in the 5f-completion pass,
update axis counts from "six" to "twelve", note 5f-completion drives as done
in Decision 8's ladder, update Out of scope to reflect #1055/#1076 are done
and 5g (InstallPlan capstone) remains the only open phase.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#1055): parity gate must fire on configFormat↔installSurface mismatch (read installSurface at the descriptor level)
The test fixture makeRuntimeCapMap did not include installSurface in the runtime object,
so the gate's typeof r.installSurface !== 'string' guard always skipped the entry and never threw.
Added installSurface as an optional third parameter to makeRuntimeCapMap and passed the correct
installSurface values ('settings-json' for claude, 'codex-toml' for codex) to the two THROWS tests.
The gate implementation already reads r.installSurface correctly from the descriptor level.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(#1076): add installSurface↔hooksSurface + extendedHookEvents↔hookEvents consistency gates with rejection tests
GATE A: INSTALL_SURFACE_TO_ALLOWED_HOOKS_SURFACES map in validateRuntimeBody enforces that a
runtime's hooksSurface is valid for its installSurface (e.g. profile-marker-only only allows none,
codex-toml only allows codex-hooks-json). Derived from the 16 real runtime descriptors.
GATE B: validateRuntimeBody checks that if extendedHookEvents contains Gemini agent-events
(BeforeAgent/AfterAgent/BeforeModel), hookEvents must be 'gemini'; if it contains Claude-family
events (SubagentStop/Stop/PreCompact/FileChanged), hookEvents must be 'claude'.
Added 10 rejection tests in suite 27 covering each gate + each new field validator.
All 16 real runtimes satisfy both gates (verified before coding).
Exports: INSTALL_SURFACE_TO_ALLOWED_HOOKS_SURFACES, VALID_INSTALL_SURFACES,
VALID_EXTENDED_HOOK_EVENTS, VALID_PERMISSION_WRITERS, validateRuntimeBody.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(#1076): strengthen hooksSurface-drive assertions; defensive hooksSurface fallback; drop vacuous dup
1. bin/install.js: add explicit literal fallback for hooksSurface when the committed
capability registry fails to load (opencode/kilo → 'none', all others → 'settings-json').
The descriptor is always the source of truth in normal operation.
2. enh-1076 Suite 7: change SessionStart assertion from key-presence (hasOwnProperty)
to at least-one-command (hasHooksFor), so the test fails if hooks are initialized-but-empty.
ensureHooksDist() in before() guarantees hook files exist.
3. enh-1055 Test 2: remove vacuous duplicate suite that re-asserted intent.runtime === row.runtime
already fully covered by Test 1's deepStrictEqual over all four fields.
4. capability-registry.test.cjs: fix stale comments in the grok-skip test that claimed the
parity gate uses the adapter registry; gate reads purely from the descriptor (installSurface
absent → typeof r.installSurface !== 'string' → soft-skip).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(#1082): materialize the InstallPlan — collect install-level descriptor axes into resolveInstallPlan; route install()/finishInstall() through it (ADR-58/5g)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs: record 5g InstallPlan materialization (ADR-58 Accepted, ADR-1016 phase-5 complete)
ADR-1016 Decision 8 step 7 updated to DONE: resolveInstallPlan(runtime) in
runtime-config-adapter-registry collects install-level descriptor axes into
the typed InstallPlan consumed by install()/finishInstall(). Out-of-scope
section updated: 5g capstone is complete, phase 5 fully materialized.
ADR-1016 line ~20 updated: InstallPlan IS now materialized (both halves).
ADR-58 Implementation note added (2026-06-11): realized in
runtime-config-adapter-registry (co-located with adapter-selection).
CONTEXT.md Runtime Config Adapter Registry entry extended to document
resolveInstallPlan and both-halves realization.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(#1082): update install drift guard to the resolveInstallPlan seam (5g)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Author capabilities/<runtime>/capability.json for all 16 runtimes (role:runtime),
populating the registry's runtimes index ({} -> 16). Each carries the 6 ADR-1016
axes (configHome structured, configFormat, artifactLayout structured, commandStyle,
hooksSurface + hookEvents, sandboxTier, supportTier), extracted from the live
modules (runtime-homes/runtime-slash/runtime-artifact-layout/runtime-config-adapter
+ CODEX_AGENT_SANDBOX). validateRuntimeBody tightened to enforce the closed
vocabularies + structured configHome/artifactLayout (rejects old string configHome;
env required; skillsHome recursively validated).
Registry-only: bin/install.js + src/*.cts untouched, zero behavior change. The 5b-5f
drive steps consume these one axis at a time (ADR-1016 §8).
Codex accuracy review caught + fixed: opencode/kilo register ZERO lifecycle hooks
(install.js skips the whole block) -> hooksSurface:none (configFormat stays
settings-json); kimi probe selects on <root>/skills existence -> probeExists:"skills".
ArtifactKind required-field strictness + ConverterName closing deferred to 5d/5e.
ADR-1016 (Proposed) amended to match (Decisions 1 + 5).
Closes#1035
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Maintainer governance decision (waiving a standalone ADR for Qoder, PR #1021):
registering a runtime that reuses the existing profile-marker-only install
surface + a single skills kind is an enhancement governed by ADR-3660 via
addendum. Records the addendum-vs-new-ADR qualifying criteria, the normative
agent-frontmatter contract (name+description only — the sibling converter
shape), and Qoder as the first runtime logged under this path.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>