34bb8ed5f640cfe306c7cd11b5480ea42e90bb5c
9 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
94872662e9 |
feat(#1082): complete phase 5 — descriptor-drive all install surfaces + materialize the InstallPlan — ADR-857/1016/58 (#1080)
* 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> |
||
|
|
58ed55683e |
feat(#1049): phase 5d — drive artifactLayout from the runtime descriptor (retire the 128-LOC switch) (#1053)
resolveRuntimeArtifactLayout now builds Layout from
registry.runtimes[id].runtime.artifactLayout[scope] — a loop dispatching each
ArtifactKind through the SAME 5 builders (commandsKind/agentsKind/skillsKind/
convertedCommandsKind/kimiAgentsKind, unchanged) by (kind, converter, nesting) —
replacing the hardcoded switch(runtime). Equivalence-preserving for all 16 runtimes
× {global, local} (Codex-verified, no divergence). -43 LOC; bin/install.js + the
converters + the install loop untouched. getInstallExports()[converterName]
resolution, configDir threading, scope default, unknown-runtime guard all preserved.
Driving the local scope surfaced a 5a gap: the old switch had no scope branch for 13
runtimes (cursor/gemini/codex/copilot/antigravity/windsurf/augment/trae/qwen/hermes/
codebuddy/opencode/kilo) → local == global for them, but 5a authored local:[].
Backfilled local=global for those 13 (descriptor-faithful; a fall-through shim would
wrongly give cline/kimi local=global). claude/cline/kimi scope-gating untouched.
validateArtifactKindEntry tightened: destSubpath/prefix/nesting/converter required
(ConverterName enum still open — 5e). New 39-case deep-equal golden equivalence test.
Closes #1049
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
|
||
|
|
fbd62cd84f |
feat(#1035): phase 5a — author 16 role:runtime capability descriptors (registry-only) (#1039)
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>
|
||
|
|
9ed8c7d574 |
feat(#1026): §5.6/ui-phase cutover — first gate dispatch (plan:pre step + blocking gate) (#1028)
Replace plan-phase.md §5.6 (a 6-branch inline UI gate) with a capability-driven
loop.render-hooks plan:pre dispatch — the FIRST gate dispatch in any workflow.
A step (ui-phase, when:workflow.ui_phase) + a new blocking gate
(when:workflow.ui_safety_gate). New ui.plan-gate check verb returns
{frontend, hasUiSpec, block}; the dispatch runs it unconditionally then fires
the active step (pipeline) or halts on the active blocking gate (manual). The
gate-handling (run check.query; halt if blocking+block) is the reusable
phase-6 template for blocking-gate cutovers.
Config semantics fixed per #1022 + maintainer call: ui_phase gates plan-time
UI-SPEC generation, ui_safety_gate gates the planning block. Common case + all
ui_phase=false cases are equivalence-preserving; the one intended change is
{ui_phase:true, ui_safety_gate:false} now auto-generating in pipelines.
Review found it broken twice (non-generic dispatch, phase-lookup divergence,
then the step-only check nested in a gate loop) — fixed; final Codex pass
verified all 8 (ui_phase,ui_safety_gate)x{pipeline,manual} cases correct.
gsd-ui-phase skill + autonomous §3a.5 untouched (§3a.5 deferred).
getRoadmapPhaseWithFallback mirrors cmdRoadmapGetPhase for lookup parity.
Closes #1026
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
|
||
|
|
caca4d255c |
feat(#985): intel command cutover — commands-only capability, last first-party family (ADR-857 phase 4d-impl-4) (#988)
* feat(#985): intel command cutover — commands-only capability, last first-party family (ADR-857 phase 4d-impl-4) Migrate the intel CLI command family from a hardcoded gsd-tools.cjs case arm to a registry-dispatched Capability (commandFamilies mechanism, #961), mirroring the graphify (#972) and audit (#984) cutovers. New src/intel-command-router.cts exports routeIntelCommand reproducing all 9 subcommands verbatim (incl. the status timeAgo non-raw post-processing), lazily requiring intel.cjs inside the route fn. capabilities/intel/capability.json declares the intel command family; commands-only (skills:[]), declares the existing intel.enabled gate (default false — behavior unchanged). Behavior-CHANGING (dispatch path) but equivalence-proven: CLI output identical; existing intel.test.cjs passes unchanged. Completes the first-party command- family cutover sequence (graphify/audit/intel). Closes #985 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(#985): compute expected planningDir via path.join in intel cutover unit tests (Windows CI) The intel-command-cutover unit-mock assertions hardcoded a POSIX `/.planning` expectation while the router builds it with path.join(cwd, '.planning') → backslashes on Windows, so the planningDir-arg assertions (query/status/diff/ snapshot/validate/update/api-surface) failed only on windows-latest CI. Compute the expectation with path.join (cross-platform); production router unchanged. 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> |
||
|
|
9a03539c2d |
feat(#981): audit-uat + audit-open command cutover — commands-only capability (ADR-857 phase 4d-impl-3) (#984)
Migrate the audit-uat + audit-open CLI commands from hardcoded gsd-tools.cjs case arms to a registry-dispatched Capability (commandFamilies mechanism, #961), mirroring the graphify cutover (#972). New src/audit-command-router.cts exports routeAuditUat/routeAuditOpen, each lazily requiring only its backing module (uat.cjs/audit.cjs) inside the route fn — matching the old per-case lazy loads. capabilities/audit/capability.json declares the two command families; commands-only (skills:[]), no config gate, audit_review cluster untouched. Behavior-CHANGING (dispatch path) but equivalence-proven: CLI output identical; existing audit regression tests pass unchanged. Closes #981 Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
77bfd943dd |
feat(#972): graphify command cutover — first capability owning a command family (ADR-857 phase 4d-impl-2) (#975)
Migrate graphify into a Capability that owns its `graphify` command family, dispatched via the registry (#961 mechanism) instead of a hardcoded case. graphify is now an enable/disable plug-in. - src/graphify-command-router.cts: routeGraphifyCommand (standard route*Command), reproduces the removed case EXACTLY (query +--budget, status, diff, build, hidden build snapshot, usage/unknown errors); injectable _graphify test seam. - capabilities/graphify/capability.json: role feature, tier:full, skills:[graphify], config:{graphify.enabled default false}, commands:[{family:graphify, module, router:routeGraphifyCommand}]. - removed case 'graphify' from gsd-tools.cjs; graphify now flows default -> dispatchCapabilityCommand -> commandFamilies.graphify -> router. - regenerated registry (commandFamilies/bySkill/configSchema/profileMembership/ capabilityClusters for graphify); tier:full keeps 4c install/surface a no-op. Equivalence-proven: 8 recording-mock unit tests assert the exact fn+args per subcommand (budget, snapshot-vs-build); 9 subprocess tests assert distinguishing output shapes; existing graphify tests pass unchanged through the new path. Surfaced (not silently accepted): the pre-existing --budget-no-value NaN no-op quirk, preserved for equivalence, filed separately. Closes #972 Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
dc139b38e0 |
feat(#949): install/surface consume derived profiles/clusters (ADR-857 phase 4c) (#954)
Make install + surface read the registry's derived profileMembership/ capabilityClusters so a capability's tier drives what installs + surfaces. resolveProfile (when given the registry) unions capability skills for the profiles its tier implies before the requires: closure; resolveSurface merges capabilityClusters into the cluster map. bin/install.js, /gsd:surface, and the capability-state resolver all thread the registry. Shipped as a proven no-op: the UI capability is reconciled to tier:full (its skills were full-only in the hand-authored profiles), so it contributes only to the full profile (already the '*' sentinel) and core/standard are unchanged. Equivalence tests prove resolveProfile/resolveSurface/listSurface/staging/ capability-state are identical with vs without the registry; the core-alias staging path is verified equivalent (empty manifest → raw PROFILES.core). Closes #949 Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
ad754ca6cd |
feat(#896): Capability Registry generator + UI pilot (ADR-857 phase 3a-impl) (#902)
* feat(#896): Capability Registry generator + UI pilot (ADR-857 phase 3a-impl) First phase-3 code: the Capability Registry generation pipeline, built against the ADR-894 contract and NOT wired into the live loop (registry-only, per the staged-cutover design). - capabilities/ui/capability.json — the UI pilot (ADR-894 worked example): 2 skills, 2 agents, 3 config keys, 2 steps + 1 gate, with `when` activation. - scripts/gen-capability-registry.cjs — --write/--check generator. Hand-rolled schema validation (envelope + role-typed feature/runtime bodies + typed steps/contributions/gates + when + gate-check variants); cross-capability invariants (single ownership; requires exist+acyclic+tier-monotone; config-key ownership exclusive, collision-vs-central as a pending-migration warning); hooks validated against an inline LOOP_HOST_CONTRACT (3a-impl-2 swaps its source to the generated-from-workflows contract); GLOBAL point-ordered consumes-satisfiability; materialized byLoopPoint ordering (produces/consumes topo-sort); emits gsd-core/bin/lib/capability-registry.cjs (role-partitioned indexes + requiresClosure). Prototype-pollution guards (Object.create(null) + inline literal key checks) + fragment.path traversal guard. - gsd-core/bin/lib/capability-registry.cjs — committed generated artifact (mirrors package-identity.cjs: script-generated, tracked, linted, regenerated on build, drift-tested), wired via the new `gen:capability-registry` build step. - tests/capability-registry.test.cjs — 72 tests: schema + invariant + hook + ordering + adversarial (path-traversal, proto-pollution, runtime body, self-consume, cycles, collisions) + committed-file staleness guard. New-CLI-module checklist (INVENTORY 97->98, MANIFEST, ARCHITECTURE), CONTEXT.md "Capability Registry" un-[Planned]'d. Nothing wired into install/surface/loop. Gates: lint, code-review (4 bugs fixed), security-review (path-traversal + prototype-pollution fixed), codex adversarial-review ×3 (8+ findings fixed, confirmed sound), clean-build docker 13190 pass / 0 fail. Closes #896 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(#896): CRLF-agnostic --check for capability-registry staleness (Windows) The committed capability-registry.cjs staleness guard failed on Windows CI only: git checks out the committed .cjs as CRLF (autocrlf, no .gitattributes) while the generator emits LF, so the byte-for-byte --check comparison mismatched. Normalize line endings on both sides of the --check comparison (no .gitattributes change, no change to the LF the generator writes). Adds a regression test simulating the Windows CRLF checkout. --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |