* 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>
This commit is contained in:
@@ -145,8 +145,8 @@ Projects a pure, typed install plan for a given runtime by composing artifact pl
|
||||
### Capability [Planned]
|
||||
A bundle delivering one optional GSD feature, toggled as a unit at install or after install. Owns its skills, agents, hooks, federated config-key schema (keys + defaults + validation), and loop extension-point registrations, plus a `requires` list of other Capabilities. Declared co-located in the Capability's own folder and compiled into a generated central Capability Registry at build time. The five-step loop (Discuss → Plan → Execute → Verify → Ship) and shared-infrastructure skills (phase, config, help, update, surface, progress) are the privileged host, not Capabilities, in v1 — but host extension points are data so a loop step can become a Capability under a future uniform kernel. Supersedes the implicit feature-scattering across clusters, install-profiles, and config-schema. Generalizes the Skill Surface Budget Module and Runtime Install Policy Module.
|
||||
|
||||
### Capability Registry [Planned]
|
||||
Generated central manifest projecting all co-located Capability declarations into one validated artifact for runtime resolution and for the install, surface, config, and loop-extension adapters. Mirrors the research-profiles / package-identity generation pattern (co-located source → generated central file).
|
||||
### Capability Registry
|
||||
Generated central manifest projecting all co-located Capability declarations into one validated artifact for runtime resolution and for the install, surface, config, and loop-extension adapters. Mirrors the research-profiles / package-identity generation pattern (co-located source → generated central file). Generated by `scripts/gen-capability-registry.cjs` → `gsd-core/bin/lib/capability-registry.cjs` (ADR-894 §5 phase 3a-impl). Role-partitioned indexes: `bySkill`, `byAgent`, `byLoopPoint` (hook ordering materialized), `configKeys`, `runtimes`, `requiresClosure(id)`. Validated against the inline Loop Host Contract (12 points; `gen-loop-host-contract.cjs` to replace the inline constant in phase 3a-impl-2). Run `node scripts/gen-capability-registry.cjs --write` after editing any `capabilities/<id>/capability.json`.
|
||||
|
||||
### Loop Extension Point [Planned]
|
||||
A named, stable site on a host loop step (per-step `pre`/`post` plus per-wave in Execute; ~12 total) where Capabilities register hooks. Three hook kinds: `step` (runs as its own sequenced unit), `contribution` (injects into the core step's prompt/context), and `gate` (checks and optionally blocks via a declared `blocking` flag). Each hook declares the artifacts it produces and consumes; hook order is derived by topological sort of that produces/consumes graph (capability-id tiebreak), which also defines data flow — file-artifact based, surviving `/clear` and fresh executor contexts. Hooks are surfaced by runtime resolution with concrete projection: the workflow calls a query (extending the `init.*` resolution seam) that resolves the active hooks and returns fully-rendered, ordered markdown for the executor. Failure is default-resilient — a non-gate hook that errors is skipped with a warning; a hook may opt into `onError: halt`. Part of the Capability system.
|
||||
|
||||
21
capabilities/ui/capability.json
Normal file
21
capabilities/ui/capability.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"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" }
|
||||
]
|
||||
}
|
||||
@@ -368,7 +368,8 @@ Node.js CLI utility (`gsd-tools.cjs`) with domain modules split across `gsd-core
|
||||
| `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 |
|
||||
| `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) |
|
||||
|
||||
|
||||
---
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -370,7 +370,7 @@ The `gsd-planner` agent is decomposed into a core agent plus reference modules t
|
||||
|
||||
---
|
||||
|
||||
## CLI Modules (97 shipped)
|
||||
## CLI Modules (98 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 |
|
||||
|
||||
241
gsd-core/bin/lib/capability-registry.cjs
Normal file
241
gsd-core/bin/lib/capability-registry.cjs
Normal file
@@ -0,0 +1,241 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* capability-registry.cjs — generated by scripts/gen-capability-registry.cjs
|
||||
* DO NOT EDIT BY HAND. Run: node scripts/gen-capability-registry.cjs --write
|
||||
* ADR-894 §5 — role-partitioned Capability Registry.
|
||||
*/
|
||||
|
||||
const capabilities = {
|
||||
"ui": {
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
const bySkill = {
|
||||
"ui-phase": "ui",
|
||||
"ui-review": "ui"
|
||||
};
|
||||
|
||||
const byAgent = {
|
||||
"gsd-ui-checker": "ui",
|
||||
"gsd-ui-auditor": "ui"
|
||||
};
|
||||
|
||||
const byLoopPoint = {
|
||||
"discuss:pre": {
|
||||
"steps": [],
|
||||
"contributions": [],
|
||||
"gates": []
|
||||
},
|
||||
"discuss:post": {
|
||||
"steps": [],
|
||||
"contributions": [],
|
||||
"gates": []
|
||||
},
|
||||
"plan:pre": {
|
||||
"steps": [
|
||||
{
|
||||
"capId": "ui",
|
||||
"point": "plan:pre",
|
||||
"ref": {
|
||||
"skill": "ui-phase"
|
||||
},
|
||||
"produces": [
|
||||
"UI-SPEC.md"
|
||||
],
|
||||
"consumes": [
|
||||
"CONTEXT.md"
|
||||
],
|
||||
"when": "workflow.ui_phase",
|
||||
"onError": "skip"
|
||||
}
|
||||
],
|
||||
"contributions": [],
|
||||
"gates": []
|
||||
},
|
||||
"plan:post": {
|
||||
"steps": [],
|
||||
"contributions": [],
|
||||
"gates": []
|
||||
},
|
||||
"execute:pre": {
|
||||
"steps": [],
|
||||
"contributions": [],
|
||||
"gates": []
|
||||
},
|
||||
"execute:wave:pre": {
|
||||
"steps": [],
|
||||
"contributions": [],
|
||||
"gates": []
|
||||
},
|
||||
"execute:wave:post": {
|
||||
"steps": [],
|
||||
"contributions": [],
|
||||
"gates": [
|
||||
{
|
||||
"capId": "ui",
|
||||
"point": "execute:wave:post",
|
||||
"check": {
|
||||
"query": "ui.safety-gate"
|
||||
},
|
||||
"when": "workflow.ui_safety_gate",
|
||||
"blocking": true,
|
||||
"onError": "halt"
|
||||
}
|
||||
]
|
||||
},
|
||||
"execute:post": {
|
||||
"steps": [],
|
||||
"contributions": [],
|
||||
"gates": []
|
||||
},
|
||||
"verify:pre": {
|
||||
"steps": [],
|
||||
"contributions": [],
|
||||
"gates": []
|
||||
},
|
||||
"verify:post": {
|
||||
"steps": [
|
||||
{
|
||||
"capId": "ui",
|
||||
"point": "verify:post",
|
||||
"ref": {
|
||||
"skill": "ui-review"
|
||||
},
|
||||
"produces": [
|
||||
"UI-REVIEW.md"
|
||||
],
|
||||
"consumes": [
|
||||
"UI-SPEC.md"
|
||||
],
|
||||
"when": "workflow.ui_review",
|
||||
"onError": "skip"
|
||||
}
|
||||
],
|
||||
"contributions": [],
|
||||
"gates": []
|
||||
},
|
||||
"ship:pre": {
|
||||
"steps": [],
|
||||
"contributions": [],
|
||||
"gates": []
|
||||
},
|
||||
"ship:post": {
|
||||
"steps": [],
|
||||
"contributions": [],
|
||||
"gates": []
|
||||
}
|
||||
};
|
||||
|
||||
const configKeys = {
|
||||
"workflow.ui_phase": "ui",
|
||||
"workflow.ui_review": "ui",
|
||||
"workflow.ui_safety_gate": "ui"
|
||||
};
|
||||
|
||||
const runtimes = {};
|
||||
|
||||
const _requiresGraph = {
|
||||
"ui": []
|
||||
};
|
||||
|
||||
function requiresClosure(id) {
|
||||
const visited = new Set();
|
||||
const queue = [id];
|
||||
while (queue.length > 0) {
|
||||
const current = queue.shift();
|
||||
const reqs = _requiresGraph[current] || [];
|
||||
for (const req of reqs) {
|
||||
if (!visited.has(req)) {
|
||||
visited.add(req);
|
||||
queue.push(req);
|
||||
}
|
||||
}
|
||||
}
|
||||
return visited;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
version: '1',
|
||||
capabilities,
|
||||
bySkill,
|
||||
byAgent,
|
||||
byLoopPoint,
|
||||
configKeys,
|
||||
runtimes,
|
||||
requiresClosure,
|
||||
};
|
||||
@@ -77,10 +77,11 @@
|
||||
"check:alias-drift": "node scripts/check-alias-drift.cjs",
|
||||
"check:identity-drift": "node scripts/lint-package-identity-drift.cjs",
|
||||
"check:integrity": "node scripts/check-npm-integrity.cjs",
|
||||
"build": "npm run generate:identity && npm run build:lib && npm run build:hooks",
|
||||
"build": "npm run generate:identity && npm run build:lib && npm run gen:capability-registry && npm run build:hooks",
|
||||
"build:hooks": "node scripts/build-hooks.js",
|
||||
"build:lib": "tsc -p tsconfig.build.json",
|
||||
"generate:identity": "node scripts/generate-package-identity.cjs",
|
||||
"gen:capability-registry": "node scripts/gen-capability-registry.cjs --write",
|
||||
"prepack": "npm run build:lib",
|
||||
"prepare": "npm run build:lib",
|
||||
"version": "node scripts/sync-manifest-versions.cjs --stage",
|
||||
|
||||
1346
scripts/gen-capability-registry.cjs
Normal file
1346
scripts/gen-capability-registry.cjs
Normal file
File diff suppressed because it is too large
Load Diff
1242
tests/capability-registry.test.cjs
Normal file
1242
tests/capability-registry.test.cjs
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user