Merge branch 'next' into kimi-runtime-support

This commit is contained in:
Colin Johnson
2026-06-08 23:04:37 -04:00
committed by GitHub
117 changed files with 6892 additions and 608 deletions

View File

@@ -362,13 +362,16 @@ Node.js CLI utility (`gsd-tools.cjs`) with domain modules split across `gsd-core
| `milestone.cjs` | Milestone archival, requirements marking |
| `commands.cjs` | Misc commands (slug, timestamp, todos, scaffolding, stats) |
| `model-profiles.cjs` | Model profile resolution table |
| `model-resolver.cjs` | Model and effort resolution policy — resolves model, tier, granularity, effort, and fast-mode for a given agent from project config and model profiles/catalog (extracted from `core.cjs`, ADR-857) |
| `security.cjs` | Path traversal prevention, prompt injection detection, safe JSON parsing, shell argument validation |
| `uat.cjs` | UAT file parsing, verification debt tracking, audit-uat support |
| `docs.cjs` | Docs-update workflow init, Markdown scanning, monorepo detection |
| `workstream.cjs` | Workstream CRUD, migration, session-scoped active pointer |
| `schema-detect.cjs` | Schema-drift detection for ORM patterns (Prisma, Drizzle, etc.) |
| `profile-pipeline.cjs` | User behavioral profiling data pipeline, session file scanning |
| `profile-output.cjs` | Profile rendering, USER-PROFILE.md and dev-preferences.md generation |
| `profile-output.cjs` | Profile rendering, USER-PROFILE.md and dev-preferences.md generation |
| `loop-host-contract.cjs` | Generated Loop Host Contract — 12 loop points, per-step agent roles, and core artifacts; emitted by `scripts/gen-loop-host-contract.cjs` from workflow markers (ADR-894 §3); consumed by `gen-capability-registry.cjs` |
| `capability-registry.cjs` | Generated central Capability Registry — role-partitioned index of all co-located capability declarations; emitted by `scripts/gen-capability-registry.cjs` (ADR-894 §5) |
---

View File

@@ -1,5 +1,5 @@
{
"generated": "2026-06-08",
"generated": "2026-06-09",
"families": {
"agents": [
"gsd-advisor-researcher",
@@ -270,6 +270,7 @@
"agent-command-router.cjs",
"artifacts.cjs",
"audit.cjs",
"capability-registry.cjs",
"check-command-router.cjs",
"cjs-command-router-adapter.cjs",
"cli-exit.cjs",
@@ -306,9 +307,11 @@
"io.cjs",
"learnings.cjs",
"legacy-cleanup.cjs",
"loop-host-contract.cjs",
"milestone.cjs",
"model-catalog.cjs",
"model-profiles.cjs",
"model-resolver.cjs",
"package-identity.cjs",
"package-legitimacy.cjs",
"phase-command-router.cjs",

View File

@@ -370,7 +370,7 @@ The `gsd-planner` agent is decomposed into a core agent plus reference modules t
---
## CLI Modules (96 shipped)
## CLI Modules (99 shipped)
Full listing: `gsd-core/bin/lib/*.cjs`.
@@ -381,6 +381,7 @@ Full listing: `gsd-core/bin/lib/*.cjs`.
| `agent-command-router.cjs` | Thin CJS subcommand router adapter for `gsd-tools agent` |
| `artifacts.cjs` | Canonical artifact registry — known `.planning/` root file names; used by `gsd-health` W019 lint |
| `audit.cjs` | Audit dispatch, audit open sessions, audit storage helpers |
| `capability-registry.cjs` | Generated central Capability Registry — role-partitioned index of all co-located capability declarations (`capabilities/<id>/capability.json`); emitted by `scripts/gen-capability-registry.cjs --write` (ADR-894 §5) |
| `check-command-router.cjs` | Thin CJS subcommand router adapter for `gsd-tools check` |
| `cli-exit.cjs` | `ExitError` class and `runMain()` helper — CLI entrypoints throw `ExitError` instead of calling `process.exit()`; `runMain()` translates the outcome into `process.exitCode` so output flushes cleanly |
| `cjs-command-router-adapter.cjs` | Shared compatibility adapter for manifest-backed CJS command-family routers |
@@ -395,7 +396,7 @@ Full listing: `gsd-core/bin/lib/*.cjs`.
| `config-schema.cjs` | Single source of truth for `VALID_CONFIG_KEYS` and dynamic key patterns; imported by both the validator and the config-schema-docs parity test |
| `config-types.cjs` | TypeScript type definitions for the `model_policy` config block — `ModelPolicyConfig`, `TierEntry`, `RuntimeTiers`; compiled from `src/config-types.cts` at publish time (ADR-457) |
| `config.cjs` | `config.json` read/write, section initialization; imports validator from `config-schema.cjs` |
| `configuration.cjs` | Configuration Module — canonical config loading, legacy-key normalization, defaults merge, and explicit on-disk migration; source of truth for both SDK and CJS consumers |
| `configuration.cjs` | Configuration Module — legacy-key normalization, defaults merge, and explicit on-disk migration; pure normalization primitives consumed by `config-loader.cjs` and `config-schema.cjs` (loadConfig extracted to config-loader per ADR-857 #885) |
| `context-utilization.cjs` | Pure classifier for `gsd-health --context` — turns (tokensUsed, contextWindow) into a `{ percent, state }` triage result against the 60%/70% fracture-point thresholds (#2792) |
| `core-utils.cjs` | Shared low-level utilities — POSIX path normalization, sub-repo/subdirectory scanning, phase file stats, slug/one-liner/plan-id helpers, time-ago (extracted from `core.cjs`, ADR-857) |
| `core.cjs` | Shared utilities and runtime fallbacks; compatibility re-exports for planning-workspace and I/O (`io.cjs`) helpers |
@@ -417,9 +418,11 @@ Full listing: `gsd-core/bin/lib/*.cjs`.
| `io.cjs` | CLI I/O primitives — `output`/`error` emission, JSON-error mode, and large-payload temp-file spillover (extracted from `core.cjs`, ADR-857) |
| `learnings.cjs` | Cross-phase learnings extraction for `/gsd-extract-learnings` |
| `legacy-cleanup.cjs` | Detect and remove leftover get-shit-done-cc artifacts; exports `planLegacyCleanup` (pure scan) and `applyLegacyCleanup` (thin IO applier) that root out stale files from the old package across every GSD-managed runtime config directory (#607) |
| `loop-host-contract.cjs` | Generated Loop Host Contract — 12 loop points, per-step agent roles, and core artifacts for the five-step pipeline (discuss/plan/execute/verify/ship); emitted by `scripts/gen-loop-host-contract.cjs --write` (ADR-894 §3); consumed by `gen-capability-registry.cjs` |
| `milestone.cjs` | Milestone archival, requirements marking |
| `model-catalog.cjs` | CJS adapter over the shared model catalog JSON; exports canonical runtime tier defaults, agent profile maps, alias maps, and routing metadata for all CLI consumers |
| `model-profiles.cjs` | Backward-compatible profile helpers derived from `model-catalog.cjs`; no longer owns its own model table |
| `model-resolver.cjs` | Model/effort resolution policy — resolves model, tier, granularity, effort, and fast-mode for an agent from config + model profiles/catalog (extracted from `core.cjs`, ADR-857) |
| `package-identity.cjs` | Generated single source for GSD's published-package coordinates (npm name, bin name, repo slug, changelog URL, manual-install command), derived from package.json; read by the update worker, `check-latest-version`, and installer (#498) |
| `package-legitimacy.cjs` | Registry-API package legitimacy verdicts (OK/SUS/SLOP) from npm/PyPI/crates, slopcheck optional |
| `phase-command-router.cjs` | Thin CJS subcommand router adapter for `gsd-tools phase` |

View File

@@ -0,0 +1,232 @@
# ADR-894: Capability declaration format + registry generation [Proposed]
- **Status:** Proposed
- **Date:** 2026-06-08 (amended same day across two design grillings — see "Grilling amendments")
- **Issue:** #894
- **Parent:** ADR-857 (Capability system) — resolves its Open question #1
- **Phase:** ADR-857 rollout phase 3a (design-only)
## Context
[ADR-857](857-capability-system.md) decided the **Capability** model: the five-step loop is the privileged host; every other feature is a Capability declared co-located and compiled into a generated central **Capability Registry**, owning its skills, agents, hooks, federated config-key schema, and **Loop Extension Point** registrations. ADR-857 deferred one detail to phase 3:
> *"The exact on-disk shape of a co-located Capability declaration (folder layout, declaration format)."*
The generator, the federated config loader (phase 3b), and the loop seam (phase 3c) all build against that format. This ADR fixes it **as a reviewable design** with no code, reusing the repo's proven co-located-source → generated-central pattern with a `--write`/`--check` drift gate (`scripts/gen-inventory-manifest.cjs`, `scripts/research-profiles.cjs`).
## Decision
### 1. Folder layout
```
capabilities/
<id>/
capability.json # the declaration
# (future) skills/ agents/ hooks/ loop/ — co-located owned artifacts
```
- `<id>` unique, kebab-case, equals the folder name.
- **Migration-staged ownership.** Declarations initially *reference* existing artifact locations by stem; the physical move into `capabilities/<id>/…` is the ADR-857 **phase-6 migration**. Format is identical either way.
- Genuinely shared artifacts (e.g. `gsd-planner`) stay in a core/host home and are **referenced, not owned**.
### 2. The `capability.json` schema
Schema-validated JSON. Common envelope + role-typed body (`role: feature | runtime`).
**Common envelope:**
| Field | Type | Notes |
|---|---|---|
| `id` | string (kebab) | unique; equals folder name |
| `role` | `"feature" \| "runtime"` | discriminator |
| `title`, `description` | string | label + summary |
| `tier` | `"core" \| "standard" \| "full"` | **the source of truth for install-profile + cluster membership** (§4); maps via tier + requires-closure |
| `requires` | string[] | **Capability ids only** (host is implicit). Generator enforces: exist, acyclic, **tier-monotone** (`core` may not require `standard`/`full`; `standard` may not require `full`) |
**`role: "feature"` body** — three typed hook arrays (one per ADR-857 hook kind):
| Field | Type | Notes |
|---|---|---|
| `skills` / `agents` | string[] | owned stems — exactly one owner each across all capabilities |
| `hooks` | `{event, script}[]` | lifecycle hooks |
| `config` | object | federated config-key schema slice |
| `steps` / `contributions` / `gates` | arrays | loop hooks (below) |
```jsonc
// Step — runs at a point as its own unit; order derives from produces/consumes
{ "point": "plan:pre", "ref": { "skill": "ui-phase" }, // {skill:…} | {agent:…}
"produces": ["UI-SPEC.md"], "consumes": ["CONTEXT.md"],
"when": "workflow.ui_phase", // config-level activation (§ below)
"onError": "skip" } // "skip" (default) | "halt"
// Contribution — injects a fragment into a NAMED agent role's prompt
{ "point": "plan:pre", "into": "planner", // into ∈ the step's published agentRoles
"fragment": { "path": "loop/threat-model.md" }, // {path:…} | {inline:"…"}
"when": "workflow.security_enforcement", "onError": "skip" }
// No produces/consumes; multiple contributions into the same agent render as ordered
// labeled blocks (<contribution from="<id>">…) by capability-id (ADR-857 decision 6).
// Gate — checks and optionally blocks
{ "point": "execute:wave:post",
"check": { "query": "ui.safety-gate" }, // {query:…} | {predicate:…} | {agentVerdict:…}
"when": "workflow.ui_safety_gate", "blocking": true, "onError": "halt" }
```
**Hook activation (`when`).** A hook may declare a cheap, **deterministic `when`** over config keys + capability-enablement (e.g. `"workflow.ui_phase"`); `loop.render-hooks` evaluates it to decide whether the hook is active. **Deeper context applicability** ("is this actually a frontend phase?", "are ORM files in scope?") is *not* declared — it stays inside the dispatched skill/agent, which no-ops if inapplicable, exactly where that judgment lives today. This deliberately avoids a phase-context predicate vocabulary that would drift from reality. Consequence: when an entry step self-gates (produces no artifact), its downstream same-capability gate/step must **degrade gracefully** (e.g. `ui.safety-gate` passes when there is no `UI-SPEC.md`) — that is the skill/query's responsibility.
**Gate `check`** is one of:
- `{ query: "<gsd_run query>" }` — deterministic first-party code; **may block**.
- `{ predicate: { kind: "artifact-exists" | "config-equals" | …, … } }` — declarative, no code; **may block**.
- `{ agentVerdict: { ref, prompt } }` — LLM check; **forced `blocking: false` (advisory)** — non-deterministic checks may not halt the loop.
**`role: "runtime"` body** (ADR-857 decision 8 — closed primitive vocabulary; no skills/steps/etc.):
| Field | Notes |
|---|---|
| `runtime.configHome` | config dir |
| `runtime.configFormat` | `settings-json \| toml \| markdown \| markdown-dir \| none` |
| `runtime.artifactLayout` | `{kind, destSubpath, prefix}[]` |
| `runtime.commandStyle` / `hooksSurface` / `sandboxTier` | closed enums (exact sets enumerated in phase 5) |
| `runtime.supportTier` | `1` (Claude/Codex/Antigravity) \| `2` |
### 3. The Loop Host Contract — generated from the workflows
Capability hooks attach to the host, so the host must publish what it exposes (points, agent roles, core artifacts) — otherwise `into: "planner"`, `consumes: ["RESEARCH.md"]`, and `point: "plan:pre"` are unverifiable strings.
**The contract is generated from the workflows, not hand-authored** — so it cannot drift into a lie. The five step workflows carry structured markers; a parser generates the contract from them:
```html
<!-- in gsd-core/workflows/plan-phase.md -->
<loop-point id="plan:pre"/>
<agent-role name="planner"/> <agent-role name="researcher"/> <agent-role name="checker"/>
<loop-artifact produces="PLAN.md" consumes="CONTEXT.md"/>
```
→ generated host contract entry:
```jsonc
{ "step": "plan", "points": ["plan:pre","plan:post"],
"agentRoles": ["researcher","planner","checker"],
"coreArtifacts": { "produces": ["PLAN.md"], "consumes": ["CONTEXT.md"] } }
```
The 12 points (illustrative roles): discuss `pre`/`post` (orchestrator); plan `pre`/`post` (`researcher`/`planner`/`checker`); execute `pre`/`wave:pre`/`wave:post`/`post` (`executor`/`verifier`); verify `pre`/`post` (orchestrator); ship `pre`/`post` (orchestrator).
**Generator validation against the (generated) contract:** every hook `point` ∈ host points; every `contribution.into` ∈ that step's `agentRoles`; every `step.consumes` is satisfiable by `coreArtifacts.produces` or an earlier hook's `produces`; `when` references valid config keys.
### 4. The generators
Two generated artifacts, both following `gen-inventory-manifest`'s `--write`/`--check` + build-wiring + CI drift-test pattern:
1. **`gen-loop-host-contract.cjs`** — parses the workflow markers (§3) → the host contract.
2. **`gen-capability-registry.cjs`** — reads every `capabilities/*/capability.json`, validates each against the JSON-schema (§2), then enforces cross-capability invariants (fail build on violation):
- one owner per skill/agent stem;
- `requires` exist, acyclic, **tier-monotone**;
- hooks valid against the host contract (§3);
- config-key ownership **exclusive AND complete** — a federated key must be owned by exactly one capability *and absent from the central `config-schema`* (presence in both = collision = a mid-flight migration; finish the move);
- emits the registry (§5).
**`tier` is the source of profile/cluster membership.** Install profiles (`core`/`standard`/`full`) and surface clusters are **generated** from capability `tier` + the requires-closure — collapsing ADR-857's dual/triple toggle systems. `/gsd:surface` will operate on capabilities. (This generation lands in the phase-4 install integration; ADR-894 fixes the contract.)
### 5. The generated registry shape
One `capability-registry.cjs`, **role-partitioned indexes**; per-point hook ordering **materialized** (the generator owns ordering; `loop.render-hooks` owns runtime *activation filtering*):
```js
module.exports = {
version: '<schema-version>',
capabilities: { '<id>': {…validated…}, … }, // all roles, by id
bySkill: {…}, byAgent: {…}, // feature-role
byLoopPoint: { 'plan:pre': { // ordering materialized
steps:[…produces/consumes topo-sorted, cap-id tiebreak…],
contributions:[…grouped by into, cap-id order…],
gates:[…as declared…] }, … },
configKeys: { '<key>': '<id>', … },
runtimes: { '<id>': {…descriptor…}, … }, // runtime-role
requiresClosure(id) {…},
};
```
At a point, `loop.render-hooks` reads the materialized order, **filters to the active set** (`when` + enablement), and renders the concrete markdown the orchestrator executes (ADR-857 decision 5).
### Rollout & migration (how this avoids double-execution)
3a-impl builds the registry + host-contract artifacts and the generators **without wiring them into the live loop**. The loop keeps running its currently-inlined features. Each feature's **cutover is one atomic PR** (phase 6) that simultaneously *removes the inlined workflow call* and *activates the capability's hook* — so a feature is never both inlined and hook-fired. No double-run; the registry simply exists, validated, until each feature flips.
### Worked example — the UI capability
```json
{
"id": "ui", "role": "feature", "title": "UI design contracts",
"description": "UI-SPEC design contract + retrospective UI audit for frontend phases.",
"tier": "standard", "requires": [],
"skills": ["ui-phase", "ui-review"],
"agents": ["gsd-ui-checker", "gsd-ui-auditor"],
"hooks": [],
"config": {
"workflow.ui_phase": { "type": "boolean", "default": true, "description": "Enable the UI design-contract gate during planning." },
"workflow.ui_review": { "type": "boolean", "default": true, "description": "Enable the retrospective UI audit." },
"workflow.ui_safety_gate": { "type": "boolean", "default": true, "description": "Block execution on unmet UI-SPEC contracts." }
},
"steps": [
{ "point": "plan:pre", "ref": { "skill": "ui-phase" }, "produces": ["UI-SPEC.md"], "consumes": ["CONTEXT.md"], "when": "workflow.ui_phase", "onError": "skip" },
{ "point": "verify:post", "ref": { "skill": "ui-review" }, "produces": ["UI-REVIEW.md"], "consumes": ["UI-SPEC.md"], "when": "workflow.ui_review", "onError": "skip" }
],
"contributions": [],
"gates": [
{ "point": "execute:wave:post", "check": { "query": "ui.safety-gate" }, "when": "workflow.ui_safety_gate", "blocking": true, "onError": "halt" }
]
}
```
`when` gates each hook on its config key (cheap/deterministic); whether the phase is *actually* frontend work is decided inside `ui-phase` (self-gate). The `plan:pre` step self-skips on a non-frontend phase, producing no `UI-SPEC.md`; the `execute:wave:post` gate's `ui.safety-gate` query passes gracefully when no `UI-SPEC.md` exists. (A `contribution` looks like security's: `{ "point":"plan:pre", "into":"planner", "fragment":{"path":"loop/threat-model.md"}, "when":"workflow.security_enforcement" }`.)
## Grilling amendments
This ADR was stress-tested in two rounds before merge; the format changed materially.
**Round 1 (format):** `loopHooks[]` → three typed arrays (`steps`/`contributions`/`gates`); `contribution.into: <agent-role>`; the Loop Host Contract added; `requires` = capabilities-only + tier-monotone; config federation = atomic move; one registry with role-partitioned indexes.
**Round 2 (operational reality):**
1. **Host contract is generated from workflow markers** (§3), not hand-authored — it can't drift.
2. **Staged cutover** — registry-only until atomic per-feature cutover; no double-run.
3. **`tier` is the source** of profile/cluster membership; profiles + clusters generated; `/gsd:surface` operates on capabilities.
4. **Gate `check`** = query / declarative-predicate / agentVerdict; agentVerdict forced advisory; only deterministic checks may block.
5. **Hook activation `when`** — declarative config-level gating; deeper context applicability self-gates in the skill (no phase-context vocabulary).
6. **`byLoopPoint` ordering materialized** in the registry; resolver filters active + renders. Same-capability hooks must degrade gracefully when an entry step self-gates.
## Consequences
**Positive**
- The format is *enforceable*: hooks validate against a **generated** host contract (no drift), `requires`/`tier`/config invariants are machine-checked, and ordering is materialized + testable.
- `tier`-as-source collapses ADR-857's multiple toggle systems into one generated truth.
- Staged cutover means the whole machinery can land and be validated with zero risk to the live loop until each feature deliberately flips.
**Negative / costs**
- Two new generated artifacts (registry + host contract) and workflow markup to author and keep building.
- The 12 points + agent-role vocabularies + schema are a stability contract — additive-only.
- Config-key migration is atomic-per-feature by design (a half-migrated key fails the gate).
## Alternatives considered
| Decision | Rejected | Why |
|---|---|---|
| Hook shape | one polymorphic `loopHooks[]` | typed arrays let generator/resolver branch on a known shape |
| Contribution target | "inject into the step" | ambiguous for multi-agent steps; `into: <role>` is precise |
| `requires` | capabilities + host steps | host always present → trivially satisfied; capability-only keeps the graph meaningful |
| Registry | two registries / flat map | role-partitioned indexes in one artifact: one generator, role-correct surfaces |
| Config | both-allowed / central-until-cutover | atomic move keeps one source of truth |
| Host contract | hand-authored + drift test / runtime-assert | generate-from-workflows makes drift impossible by construction |
| Profiles | profiles authoritative / tier-default-with-overrides | `tier`-as-source is the ADR-857 unification goal |
| Gate checks | query-only / agentVerdict-blockable | predicates avoid trivial code; non-deterministic blocking gates flap |
| Activation | full `when` over phase context / enablement-only | config-level `when` is cheap+honest; phase context self-gates (no drift-prone vocab) |
| Migration | big-bang / source-flag | atomic per-feature cutover: safe, staged, no coexistence flag to retire |
## Open questions (genuinely deferred to build sub-phases — cheap to decide then)
- JSON-schema `$id`/versioning; where `capability.schema.json`, the workflow markers' schema, and the generated host-contract file live.
- The declarative-predicate vocabulary for gates (`artifact-exists`, `config-equals`, …).
- The exact `commandStyle`/`sandboxTier`/`hooksSurface` enums for `role: runtime` (phase 5, against the 15 runtimes).
- Point/role-set deprecation policy once third-party capabilities exist (additive-only holds until then; a rename/removal needs a major bump + deprecation window — deferred with third-party loading per ADR-857).