refactor(#1173): defer agents-kind descriptor declarations (option a)

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>
This commit is contained in:
Behruz Nassre Esfahani
2026-06-21 11:29:50 -07:00
parent 9e1ab856ae
commit 2b6107d46f
18 changed files with 78 additions and 537 deletions

View File

@@ -2,6 +2,6 @@
type: Changed
pr: 1438
---
**Descriptor-driven install path now applies per-runtime agent conversion** for copilot/antigravity/cursor/windsurf/augment/trae/codebuddy/cline — their extracted agent converters (from #1099) are wired into the descriptor's `agents` kind, with install scope threaded for the scope-aware copilot/antigravity converters. Internal install-path parity step (ADR-1235 cutover); the legacy install loop remains authoritative so installed output is unchanged. (#1173)
**Thread `isGlobal` install scope through the descriptor-driven `convertedAgentsKind` / `stageAgentsForRuntimeWithConverter` plumbing** — a prerequisite for the ADR-1235 agent-conversion cutover. No runtime declares a converted `agents` kind yet; the `capability.json` wiring is deferred to a follow-up that first ships the ADR-1235 §0 byte-for-byte parity harness (so the `/gsd:surface` / `--materialize` consumer can mirror the legacy agent pipeline before the kind goes live). The legacy `bin/install.js` agent loop remains authoritative, so installed agent output is unchanged. (#1173)
<!-- docs-exempt: internal install-path wiring (ADR-1235 cutover); the legacy loop remains authoritative so installed agent output is unchanged — no user-facing command/config/behavior surface. -->
<!-- docs-exempt: internal install-path plumbing only (ADR-1235 cutover prerequisite); no runtime declares the converted agents kind, the legacy loop remains authoritative, and installed agent output is unchanged — no user-facing command/config/behavior surface. -->

View File

@@ -34,14 +34,6 @@
"nesting": "nested",
"recursive": false,
"converter": "convertClaudeCommandToAntigravitySkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToAntigravityAgent"
}
],
"local": [
@@ -52,14 +44,6 @@
"nesting": "nested",
"recursive": false,
"converter": "convertClaudeCommandToAntigravitySkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToAntigravityAgent"
}
]
},

View File

@@ -35,14 +35,6 @@
"nesting": "nested",
"recursive": false,
"converter": "convertClaudeCommandToAugmentSkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToAugmentAgent"
}
],
"local": [
@@ -61,14 +53,6 @@
"nesting": "nested",
"recursive": false,
"converter": "convertClaudeCommandToAugmentSkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToAugmentAgent"
}
]
},

View File

@@ -27,14 +27,6 @@
"nesting": "nested",
"recursive": false,
"converter": "convertClaudeCommandToClineSkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToClineAgent"
}
],
"local": []

View File

@@ -35,14 +35,6 @@
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeCommandToCodebuddySkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToCodebuddyAgent"
}
],
"local": [
@@ -61,14 +53,6 @@
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeCommandToCodebuddySkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToCodebuddyAgent"
}
]
},

View File

@@ -28,14 +28,6 @@
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeCommandToCopilotSkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToCopilotAgent"
}
],
"local": [
@@ -46,14 +38,6 @@
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeCommandToCopilotSkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToCopilotAgent"
}
]
},

View File

@@ -35,14 +35,6 @@
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeCommandToCursorCommand"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToCursorAgent"
}
],
"local": [
@@ -61,14 +53,6 @@
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeCommandToCursorCommand"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToCursorAgent"
}
]
},

View File

@@ -27,14 +27,6 @@
"nesting": "nested",
"recursive": false,
"converter": "convertClaudeCommandToTraeSkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToTraeAgent"
}
],
"local": [
@@ -45,14 +37,6 @@
"nesting": "nested",
"recursive": false,
"converter": "convertClaudeCommandToTraeSkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToTraeAgent"
}
]
},

View File

@@ -28,14 +28,6 @@
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeCommandToWindsurfSkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToWindsurfAgent"
}
],
"local": [
@@ -46,14 +38,6 @@
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeCommandToWindsurfSkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToWindsurfAgent"
}
]
},

View File

@@ -1,6 +1,6 @@
# ADR-1235: Migrate agent conversion to the descriptor-driven install path
- **Status:** Proposed
- **Status:** Accepted
- **Date:** 2026-06-14
- **Issue:** #1235
- **Builds on:** [ADR-3660](3660-runtime-artifact-layout-module.md) (runtime artifact layout), [ADR-457](457-generated-cjs-single-source.md) (the `src/*.cts` build-at-publish tree the converters live in), [ADR-1016](1016-runtime-capability-descriptor.md) (runtime capability descriptor)

View File

@@ -96,14 +96,6 @@ const capabilities = {
"nesting": "nested",
"recursive": false,
"converter": "convertClaudeCommandToAntigravitySkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToAntigravityAgent"
}
],
"local": [
@@ -114,14 +106,6 @@ const capabilities = {
"nesting": "nested",
"recursive": false,
"converter": "convertClaudeCommandToAntigravitySkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToAntigravityAgent"
}
]
},
@@ -210,14 +194,6 @@ const capabilities = {
"nesting": "nested",
"recursive": false,
"converter": "convertClaudeCommandToAugmentSkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToAugmentAgent"
}
],
"local": [
@@ -236,14 +212,6 @@ const capabilities = {
"nesting": "nested",
"recursive": false,
"converter": "convertClaudeCommandToAugmentSkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToAugmentAgent"
}
]
},
@@ -353,14 +321,6 @@ const capabilities = {
"nesting": "nested",
"recursive": false,
"converter": "convertClaudeCommandToClineSkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToClineAgent"
}
],
"local": []
@@ -473,14 +433,6 @@ const capabilities = {
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeCommandToCodebuddySkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToCodebuddyAgent"
}
],
"local": [
@@ -499,14 +451,6 @@ const capabilities = {
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeCommandToCodebuddySkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToCodebuddyAgent"
}
]
},
@@ -604,14 +548,6 @@ const capabilities = {
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeCommandToCopilotSkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToCopilotAgent"
}
],
"local": [
@@ -622,14 +558,6 @@ const capabilities = {
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeCommandToCopilotSkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToCopilotAgent"
}
]
},
@@ -680,14 +608,6 @@ const capabilities = {
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeCommandToCursorCommand"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToCursorAgent"
}
],
"local": [
@@ -706,14 +626,6 @@ const capabilities = {
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeCommandToCursorCommand"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToCursorAgent"
}
]
},
@@ -1928,14 +1840,6 @@ const capabilities = {
"nesting": "nested",
"recursive": false,
"converter": "convertClaudeCommandToTraeSkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToTraeAgent"
}
],
"local": [
@@ -1946,14 +1850,6 @@ const capabilities = {
"nesting": "nested",
"recursive": false,
"converter": "convertClaudeCommandToTraeSkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToTraeAgent"
}
]
},
@@ -2092,14 +1988,6 @@ const capabilities = {
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeCommandToWindsurfSkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToWindsurfAgent"
}
],
"local": [
@@ -2110,14 +1998,6 @@ const capabilities = {
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeCommandToWindsurfSkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToWindsurfAgent"
}
]
},
@@ -2874,14 +2754,6 @@ const runtimes = {
"nesting": "nested",
"recursive": false,
"converter": "convertClaudeCommandToAntigravitySkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToAntigravityAgent"
}
],
"local": [
@@ -2892,14 +2764,6 @@ const runtimes = {
"nesting": "nested",
"recursive": false,
"converter": "convertClaudeCommandToAntigravitySkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToAntigravityAgent"
}
]
},
@@ -2951,14 +2815,6 @@ const runtimes = {
"nesting": "nested",
"recursive": false,
"converter": "convertClaudeCommandToAugmentSkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToAugmentAgent"
}
],
"local": [
@@ -2977,14 +2833,6 @@ const runtimes = {
"nesting": "nested",
"recursive": false,
"converter": "convertClaudeCommandToAugmentSkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToAugmentAgent"
}
]
},
@@ -3094,14 +2942,6 @@ const runtimes = {
"nesting": "nested",
"recursive": false,
"converter": "convertClaudeCommandToClineSkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToClineAgent"
}
],
"local": []
@@ -3153,14 +2993,6 @@ const runtimes = {
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeCommandToCodebuddySkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToCodebuddyAgent"
}
],
"local": [
@@ -3179,14 +3011,6 @@ const runtimes = {
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeCommandToCodebuddySkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToCodebuddyAgent"
}
]
},
@@ -3284,14 +3108,6 @@ const runtimes = {
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeCommandToCopilotSkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToCopilotAgent"
}
],
"local": [
@@ -3302,14 +3118,6 @@ const runtimes = {
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeCommandToCopilotSkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToCopilotAgent"
}
]
},
@@ -3360,14 +3168,6 @@ const runtimes = {
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeCommandToCursorCommand"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToCursorAgent"
}
],
"local": [
@@ -3386,14 +3186,6 @@ const runtimes = {
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeCommandToCursorCommand"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToCursorAgent"
}
]
},
@@ -3805,14 +3597,6 @@ const runtimes = {
"nesting": "nested",
"recursive": false,
"converter": "convertClaudeCommandToTraeSkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToTraeAgent"
}
],
"local": [
@@ -3823,14 +3607,6 @@ const runtimes = {
"nesting": "nested",
"recursive": false,
"converter": "convertClaudeCommandToTraeSkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToTraeAgent"
}
]
},
@@ -3874,14 +3650,6 @@ const runtimes = {
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeCommandToWindsurfSkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToWindsurfAgent"
}
],
"local": [
@@ -3892,14 +3660,6 @@ const runtimes = {
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeCommandToWindsurfSkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToWindsurfAgent"
}
]
},

View File

@@ -175,15 +175,19 @@ function agentsKind(destSubpath: string, prefix: string, configDir: string): Art
* Agent filenames are preserved verbatim (the prefix is already embedded in the
* agent stem — e.g. `gsd-planner.md`).
*
* #1173 SCOPE: this wires the per-runtime agent CONVERTER (frontmatter/body +
* isGlobal scope) into the descriptor path. The remaining byte-for-byte parity
* behaviors of the legacy `bin/install.js` agent loop — Copilot's `.agent.md`
* filename rename, the cross-cutting path-prefix rewrite + attribution, and the
* config-reading steps (claude effort, opencode model override) — are NOT applied
* here yet; they are tracked by the ADR-1235 cutover (later steps) and remain
* provided by the legacy loop, which runs after `installRuntimeArtifacts` and is
* authoritative for the real install. So this kind is correct for converter
* coverage but not yet a full standalone replacement for these runtimes.
* #1173 SCOPE — plumbing only (declarations deferred): this provides the
* converter dispatch + `isGlobal` scope threading for the descriptor's `agents`
* kind, but NO runtime currently declares a converted `agents` kind in its
* `capability.json`. The descriptor declarations for the 8 non-Claude runtimes
* (copilot/antigravity/cursor/windsurf/augment/trae/codebuddy/cline) are
* DEFERRED to a follow-up that first ships the ADR-1235 §0 byte-for-byte parity
* harness, because the second `layout.kinds` consumer — `applySurface` /
* `/gsd:surface` / `--materialize` (`src/surface.cts`) — does not yet mirror the
* legacy agent pipeline (Copilot's `.agent.md` filename rename, the cross-cutting
* path-prefix rewrite + attribution, stale-file cleanup, config-reading steps),
* so declaring the kind now would regress the surface path. Until then the legacy
* `bin/install.js` agent loop remains authoritative for the real install, and
* this `convertedAgentsKind` is exercised only by synthetic-descriptor seam tests.
*
* Mirrors the `convertedCommandsKind` pattern (#785).
*

View File

@@ -630,14 +630,11 @@ describe('resolveRuntimeArtifactLayout — cline scope-aware (Fix 2)', () => {
assert.strictEqual(layout.kinds.length, 0, 'cline local must have 0 kinds');
});
test('cline global: kinds.length === 2 (skills + agents)', () => {
// #1173: cline global gained an agents kind (descriptor-driven agent conversion);
// cline local stays empty (0 kinds) — agents was wired for global only.
test('cline global: kinds.length === 1 (skills kind)', () => {
const { resolveRuntimeArtifactLayout } = require('../gsd-core/bin/lib/runtime-artifact-layout.cjs');
const layout = resolveRuntimeArtifactLayout('cline', '/tmp/x', 'global');
assert.strictEqual(layout.kinds.length, 2, 'cline global must have skills + agents kinds');
assert.strictEqual(layout.kinds.length, 1, 'cline global must have 1 skills kind');
assert.strictEqual(layout.kinds[0].kind, 'skills');
assert.strictEqual(layout.kinds[1].kind, 'agents');
});
test('installRuntimeArtifacts cline local: no skills/ dir created', (t) => {

View File

@@ -53,12 +53,11 @@ const RESOLVED_CORE = resolveProfile({ modes: ['core'], manifest: MANIFEST });
// ─── Layout contract ─────────────────────────────────────────────────────────
describe('enh-789 — codebuddy layout has commands + skills kinds', () => {
test('resolveRuntimeArtifactLayout codebuddy returns 3 kinds', () => {
// #1173: codebuddy gained an agents kind (descriptor-driven per-runtime agent conversion).
test('resolveRuntimeArtifactLayout codebuddy returns 2 kinds', () => {
const layout = resolveRuntimeArtifactLayout('codebuddy', '/tmp/fake-codebuddy-dir');
assert.strictEqual(layout.kinds.length, 3, 'codebuddy must have exactly 3 artifact kinds');
assert.strictEqual(layout.kinds.length, 2, 'codebuddy must have exactly 2 artifact kinds');
const kindNames = layout.kinds.map(k => k.kind).sort();
assert.deepStrictEqual(kindNames, ['agents', 'commands', 'skills']);
assert.deepStrictEqual(kindNames, ['commands', 'skills']);
});
test('codebuddy commands kind targets commands/ with gsd- prefix', () => {

View File

@@ -32,12 +32,11 @@ const RESOLVED_CORE = resolveProfile({ modes: ['core'], manifest: MANIFEST });
// ─── Layout contract ─────────────────────────────────────────────────────────
describe('enh-790 — augment layout has commands + skills kinds', () => {
test('resolveRuntimeArtifactLayout augment returns 3 kinds', () => {
// #1173: augment gained an agents kind (descriptor-driven per-runtime agent conversion).
test('resolveRuntimeArtifactLayout augment returns 2 kinds', () => {
const layout = resolveRuntimeArtifactLayout('augment', '/tmp/fake-augment-dir');
assert.strictEqual(layout.kinds.length, 3, 'augment must have exactly 3 artifact kinds');
assert.strictEqual(layout.kinds.length, 2, 'augment must have exactly 2 artifact kinds');
const kindNames = layout.kinds.map(k => k.kind).sort();
assert.deepStrictEqual(kindNames, ['agents', 'commands', 'skills']);
assert.deepStrictEqual(kindNames, ['commands', 'skills']);
});
test('augment commands kind targets commands/ with gsd- prefix', () => {

View File

@@ -285,6 +285,48 @@ describe('feat-1173: dispatchKindEntry agents converter wiring', () => {
const stagedContent = fs.readFileSync(path.join(stagedDir, 'gsd-planner.md'), 'utf8');
assert.strictEqual(stagedContent, CLAUDE_AGENT_SOURCE, 'converter=null must raw-copy the agent content');
});
test('scope threads isGlobal to a scope-aware converter (global vs local differ)', (t) => {
// The plumbing kept by #1173 (option a): convertedAgentsKind / dispatchKindEntry
// pass the install scope to the converter as isGlobal. A scope-aware converter
// (copilot) must therefore produce different output for global vs local. This
// proves the thread is live via a synthetic descriptor — no real runtime
// declares a converted agents kind yet (declarations deferred to the ADR-1235
// §0 parity follow-up).
const fixtureRoot = makeFixtureRoot([{ name: 'gsd-planner.md', content: CLAUDE_AGENT_SOURCE }]);
t.after(() => {
cleanup(fixtureRoot);
cleanupStagedSkills();
});
const agentsEntry = {
kind: 'agents',
destSubpath: 'agents',
prefix: 'gsd-',
nesting: 'flat',
recursive: false,
converter: 'convertClaudeAgentToCopilotAgent',
};
const registry = {
runtimes: { testruntime: { runtime: { artifactLayout: { global: [agentsEntry], local: [agentsEntry] } } } },
};
const profile = { name: 'full', skills: '*', agents: new Set() };
const stageFor = (scope) => {
const layout = resolveRuntimeArtifactLayoutFromRegistry(registry, 'testruntime', fixtureRoot, scope);
const agentKind = layout.kinds.find((k) => k.kind === 'agents');
assert.ok(agentKind, `${scope} layout must include an agents kind`);
return fs.readFileSync(path.join(agentKind.stage(profile), 'gsd-planner.md'), 'utf8');
};
const globalOut = stageFor('global');
const localOut = stageFor('local');
assert.notStrictEqual(
globalOut,
localOut,
'scope-aware converter output must differ by scope — proves isGlobal is threaded from the descriptor scope',
);
});
});
// ─── real registry: claude agents kind has converter=null ────────────────────
@@ -298,75 +340,3 @@ describe('feat-1173: real registry claude agents kind has converter=null (backwa
assert.strictEqual(agentsEntry.converter, null, 'claude agents entry must have converter=null');
});
});
// ─── feat-1173: real-registry wiring for the 8 runtimes ───────────────────────
// The synthetic-descriptor tests above prove the dispatch SEAM exists. These
// prove the actual deliverable: each of the 8 runtimes' capability.json now
// declares the correct agent converter, the descriptor path APPLIES it (not a
// raw copy), and the install scope is threaded so scope-aware converters
// (copilot/antigravity) choose global- vs workspace-relative paths. These fail
// on pristine `next`, where these runtimes have no agents kind (silent raw copy).
describe('feat-1173: real-registry agent converter wiring (8 runtimes)', () => {
const conv = require(path.join(ROOT, 'gsd-core', 'bin', 'lib', 'runtime-artifact-conversion.cjs'));
const layout = require(path.join(ROOT, 'gsd-core', 'bin', 'lib', 'runtime-artifact-layout.cjs'));
const registry = require(path.join(ROOT, 'gsd-core', 'bin', 'lib', 'capability-registry.cjs'));
// runtime → its agent converter + the scopes whose descriptor carries an agents kind.
// cline is global-only (its local artifactLayout is empty), so it wires global only.
const WIRED = [
{ runtime: 'copilot', converter: 'convertClaudeAgentToCopilotAgent', scopeAware: true, scopes: ['global', 'local'] },
{ runtime: 'antigravity', converter: 'convertClaudeAgentToAntigravityAgent', scopeAware: true, scopes: ['global', 'local'] },
{ runtime: 'cursor', converter: 'convertClaudeAgentToCursorAgent', scopeAware: false, scopes: ['global', 'local'] },
{ runtime: 'windsurf', converter: 'convertClaudeAgentToWindsurfAgent', scopeAware: false, scopes: ['global', 'local'] },
{ runtime: 'augment', converter: 'convertClaudeAgentToAugmentAgent', scopeAware: false, scopes: ['global', 'local'] },
{ runtime: 'trae', converter: 'convertClaudeAgentToTraeAgent', scopeAware: false, scopes: ['global', 'local'] },
{ runtime: 'codebuddy', converter: 'convertClaudeAgentToCodebuddyAgent', scopeAware: false, scopes: ['global', 'local'] },
{ runtime: 'cline', converter: 'convertClaudeAgentToClineAgent', scopeAware: false, scopes: ['global'] },
];
for (const { runtime, converter, scopeAware, scopes } of WIRED) {
test(`${runtime}: capability descriptor declares ${converter} for ${scopes.join('+')}`, () => {
const al = registry.runtimes[runtime].runtime.artifactLayout;
for (const scope of scopes) {
const entry = (al[scope] || []).find((e) => e.kind === 'agents');
assert.ok(entry, `${runtime} ${scope} must declare an agents kind`);
assert.strictEqual(entry.converter, converter, `${runtime} ${scope} agents converter`);
}
if (!scopes.includes('local')) {
assert.ok(!(al.local || []).some((e) => e.kind === 'agents'),
`${runtime} local must NOT declare an agents kind (global-only runtime)`);
}
});
test(`${runtime}: descriptor staging applies ${converter} with scope threading`, (t) => {
const fixtureRoot = makeFixtureRoot([{ name: 'gsd-planner.md', content: CLAUDE_AGENT_SOURCE }]);
t.after(() => { cleanup(fixtureRoot); cleanupStagedSkills(); });
const profile = { name: 'full', skills: '*', agents: new Set() };
for (const scope of scopes) {
const lay = layout.resolveRuntimeArtifactLayout(runtime, fixtureRoot, scope);
const agentsKind = lay.kinds.find((k) => k.kind === 'agents');
assert.ok(agentsKind, `${runtime} ${scope} layout must include an agents kind`);
const stagedDir = agentsKind.stage(profile);
const staged = fs.readFileSync(path.join(stagedDir, 'gsd-planner.md'), 'utf8');
// Conversion actually happened (guards against the raw-copy regression).
assert.notStrictEqual(staged, CLAUDE_AGENT_SOURCE,
`${runtime} ${scope}: descriptor must convert, not raw-copy`);
// Routed to the correct converter, with isGlobal threaded from the scope.
const expected = conv[converter](CLAUDE_AGENT_SOURCE, scope === 'global');
assert.strictEqual(staged, expected,
`${runtime} ${scope}: staged must equal ${converter}(src, isGlobal=${scope === 'global'})`);
}
// Scope-aware converters must differ by scope — proves the isGlobal thread is
// real (a broken/constant thread would make global and local identical).
if (scopeAware) {
assert.notStrictEqual(
conv[converter](CLAUDE_AGENT_SOURCE, true),
conv[converter](CLAUDE_AGENT_SOURCE, false),
`${runtime}: global vs local conversion must differ (scope threading observable)`);
}
});
}
});

View File

@@ -46,13 +46,6 @@ const FAKE_DIR = '/tmp/fake-config-dir-dd';
// 'function' means we assert typeof kind.stage === 'function'.
const GOLDEN = {
// #1173: these 8 runtimes (copilot/antigravity/cursor/windsurf/augment/trae/
// codebuddy/cline) gained an `agents` kind so the descriptor-driven path applies
// their per-runtime agent converter. This INTENTIONALLY extends the layout beyond
// the old switch() (which emitted no agents branch for them — their agents were
// converted only by the legacy bin/install.js loop). Not an equivalence regression
// of the ADR-857 descriptor migration; a sanctioned #1173 (ADR-1235 agent-conversion
// cutover) extension. cline stays global-only (empty local).
// ── claude ──────────────────────────────────────────────────────────────────
'claude/global': [
{ kind: 'skills', destSubpath: 'skills', prefix: 'gsd-' },
@@ -68,12 +61,10 @@ const GOLDEN = {
'cursor/global': [
{ kind: 'skills', destSubpath: 'skills', prefix: 'gsd-' },
{ kind: 'commands', destSubpath: 'commands', prefix: 'gsd-' },
{ kind: 'agents', destSubpath: 'agents', prefix: 'gsd-' },
],
'cursor/local': [
{ kind: 'skills', destSubpath: 'skills', prefix: 'gsd-' },
{ kind: 'commands', destSubpath: 'commands', prefix: 'gsd-' },
{ kind: 'agents', destSubpath: 'agents', prefix: 'gsd-' },
],
// ── gemini ───────────────────────────────────────────────────────────────────
@@ -98,33 +89,27 @@ const GOLDEN = {
// Old switch: no scope branch → local == global. 5b backfill restores this.
'copilot/global': [
{ kind: 'skills', destSubpath: 'skills', prefix: 'gsd-' },
{ kind: 'agents', destSubpath: 'agents', prefix: 'gsd-' },
],
'copilot/local': [
{ kind: 'skills', destSubpath: 'skills', prefix: 'gsd-' },
{ kind: 'agents', destSubpath: 'agents', prefix: 'gsd-' },
],
// ── antigravity ──────────────────────────────────────────────────────────────
// Old switch: no scope branch → local == global. 5b backfill restores this.
'antigravity/global': [
{ kind: 'skills', destSubpath: 'skills', prefix: 'gsd-' },
{ kind: 'agents', destSubpath: 'agents', prefix: 'gsd-' },
],
'antigravity/local': [
{ kind: 'skills', destSubpath: 'skills', prefix: 'gsd-' },
{ kind: 'agents', destSubpath: 'agents', prefix: 'gsd-' },
],
// ── windsurf ─────────────────────────────────────────────────────────────────
// Old switch: no scope branch → local == global. 5b backfill restores this.
'windsurf/global': [
{ kind: 'skills', destSubpath: 'skills', prefix: 'gsd-' },
{ kind: 'agents', destSubpath: 'agents', prefix: 'gsd-' },
],
'windsurf/local': [
{ kind: 'skills', destSubpath: 'skills', prefix: 'gsd-' },
{ kind: 'agents', destSubpath: 'agents', prefix: 'gsd-' },
],
// ── augment ──────────────────────────────────────────────────────────────────
@@ -132,23 +117,19 @@ const GOLDEN = {
'augment/global': [
{ kind: 'commands', destSubpath: 'commands', prefix: 'gsd-' },
{ kind: 'skills', destSubpath: 'skills', prefix: 'gsd-' },
{ kind: 'agents', destSubpath: 'agents', prefix: 'gsd-' },
],
'augment/local': [
{ kind: 'commands', destSubpath: 'commands', prefix: 'gsd-' },
{ kind: 'skills', destSubpath: 'skills', prefix: 'gsd-' },
{ kind: 'agents', destSubpath: 'agents', prefix: 'gsd-' },
],
// ── trae ─────────────────────────────────────────────────────────────────────
// Old switch: no scope branch → local == global. 5b backfill restores this.
'trae/global': [
{ kind: 'skills', destSubpath: 'skills', prefix: 'gsd-' },
{ kind: 'agents', destSubpath: 'agents', prefix: 'gsd-' },
],
'trae/local': [
{ kind: 'skills', destSubpath: 'skills', prefix: 'gsd-' },
{ kind: 'agents', destSubpath: 'agents', prefix: 'gsd-' },
],
// ── qwen ─────────────────────────────────────────────────────────────────────
@@ -174,19 +155,16 @@ const GOLDEN = {
'codebuddy/global': [
{ kind: 'commands', destSubpath: 'commands', prefix: 'gsd-' },
{ kind: 'skills', destSubpath: 'skills', prefix: 'gsd-' },
{ kind: 'agents', destSubpath: 'agents', prefix: 'gsd-' },
],
'codebuddy/local': [
{ kind: 'commands', destSubpath: 'commands', prefix: 'gsd-' },
{ kind: 'skills', destSubpath: 'skills', prefix: 'gsd-' },
{ kind: 'agents', destSubpath: 'agents', prefix: 'gsd-' },
],
// ── cline ────────────────────────────────────────────────────────────────────
// Old switch: scope='global' → [skills]; scope='local' → []. Matches descriptor.
'cline/global': [
{ kind: 'skills', destSubpath: 'skills', prefix: 'gsd-' },
{ kind: 'agents', destSubpath: 'agents', prefix: 'gsd-' },
],
'cline/local': [],
@@ -385,16 +363,13 @@ describe('resolveRuntimeArtifactLayout — scope defaults to global (descriptor-
// ── Non-vacuous check: verify at least one multi-kind runtime ─────────────────
describe('resolveRuntimeArtifactLayout — multi-kind runtimes non-vacuous (descriptor-driven)', () => {
test('augment global returns 3 kinds (commands + skills + agents)', () => {
// #1173: augment gained an agents kind (per-runtime converter) after commands+skills.
test('augment global returns 2 kinds (commands + skills)', () => {
const layout = resolveRuntimeArtifactLayout('augment', FAKE_DIR, 'global');
assert.strictEqual(layout.kinds.length, 3);
assert.strictEqual(layout.kinds.length, 2);
assert.strictEqual(layout.kinds[0].kind, 'commands');
assert.strictEqual(layout.kinds[1].kind, 'skills');
assert.strictEqual(layout.kinds[2].kind, 'agents');
assert.strictEqual(typeof layout.kinds[0].stage, 'function');
assert.strictEqual(typeof layout.kinds[1].stage, 'function');
assert.strictEqual(typeof layout.kinds[2].stage, 'function');
});
test('kimi global returns skills then kimi-agents', () => {
@@ -406,12 +381,10 @@ describe('resolveRuntimeArtifactLayout — multi-kind runtimes non-vacuous (desc
assert.strictEqual(layout.kinds[1].prefix, 'gsd');
});
test('codebuddy global returns commands then skills then agents', () => {
// #1173: codebuddy gained an agents kind (per-runtime converter) after commands+skills.
test('codebuddy global returns commands then skills', () => {
const layout = resolveRuntimeArtifactLayout('codebuddy', FAKE_DIR, 'global');
assert.strictEqual(layout.kinds.length, 3);
assert.strictEqual(layout.kinds.length, 2);
assert.strictEqual(layout.kinds[0].kind, 'commands');
assert.strictEqual(layout.kinds[1].kind, 'skills');
assert.strictEqual(layout.kinds[2].kind, 'agents');
});
});

View File

@@ -65,7 +65,7 @@ describe('resolveRuntimeArtifactLayout — cursor', () => {
const layout = resolveRuntimeArtifactLayout('cursor', FAKE_DIR);
assert.strictEqual(layout.runtime, 'cursor');
assert.strictEqual(layout.configDir, FAKE_DIR);
assert.strictEqual(layout.kinds.length, 3);
assert.strictEqual(layout.kinds.length, 2);
const skillsKind = layout.kinds.find(k => k.kind === 'skills');
assert.ok(skillsKind, 'must have a skills kind');
@@ -78,12 +78,6 @@ describe('resolveRuntimeArtifactLayout — cursor', () => {
assert.strictEqual(commandsKind.destSubpath, 'commands');
assert.strictEqual(commandsKind.prefix, 'gsd-');
assert.strictEqual(typeof commandsKind.stage, 'function');
// #1173: agents kind appended (descriptor now applies per-runtime agent conversion).
const agentsKind = layout.kinds.find(k => k.kind === 'agents');
assert.ok(agentsKind, 'must have an agents kind (#1173 descriptor-driven agent conversion)');
assert.strictEqual(agentsKind.destSubpath, 'agents');
assert.strictEqual(agentsKind.prefix, 'gsd-');
assert.strictEqual(typeof agentsKind.stage, 'function');
});
});
@@ -118,16 +112,11 @@ describe('resolveRuntimeArtifactLayout — copilot', () => {
const layout = resolveRuntimeArtifactLayout('copilot', FAKE_DIR);
assert.strictEqual(layout.runtime, 'copilot');
assert.strictEqual(layout.configDir, FAKE_DIR);
assert.strictEqual(layout.kinds.length, 2);
assert.strictEqual(layout.kinds.length, 1);
assert.strictEqual(layout.kinds[0].kind, 'skills');
assert.strictEqual(layout.kinds[0].destSubpath, 'skills');
assert.strictEqual(layout.kinds[0].prefix, 'gsd-');
assert.strictEqual(typeof layout.kinds[0].stage, 'function');
// #1173: agents kind appended (descriptor now applies per-runtime agent conversion).
assert.strictEqual(layout.kinds[1].kind, 'agents');
assert.strictEqual(layout.kinds[1].destSubpath, 'agents');
assert.strictEqual(layout.kinds[1].prefix, 'gsd-');
assert.strictEqual(typeof layout.kinds[1].stage, 'function');
});
});
@@ -136,16 +125,11 @@ describe('resolveRuntimeArtifactLayout — antigravity', () => {
const layout = resolveRuntimeArtifactLayout('antigravity', FAKE_DIR);
assert.strictEqual(layout.runtime, 'antigravity');
assert.strictEqual(layout.configDir, FAKE_DIR);
assert.strictEqual(layout.kinds.length, 2);
assert.strictEqual(layout.kinds.length, 1);
assert.strictEqual(layout.kinds[0].kind, 'skills');
assert.strictEqual(layout.kinds[0].destSubpath, 'skills');
assert.strictEqual(layout.kinds[0].prefix, 'gsd-');
assert.strictEqual(typeof layout.kinds[0].stage, 'function');
// #1173: agents kind appended (descriptor now applies per-runtime agent conversion).
assert.strictEqual(layout.kinds[1].kind, 'agents');
assert.strictEqual(layout.kinds[1].destSubpath, 'agents');
assert.strictEqual(layout.kinds[1].prefix, 'gsd-');
assert.strictEqual(typeof layout.kinds[1].stage, 'function');
});
});
@@ -154,16 +138,11 @@ describe('resolveRuntimeArtifactLayout — windsurf', () => {
const layout = resolveRuntimeArtifactLayout('windsurf', FAKE_DIR);
assert.strictEqual(layout.runtime, 'windsurf');
assert.strictEqual(layout.configDir, FAKE_DIR);
assert.strictEqual(layout.kinds.length, 2);
assert.strictEqual(layout.kinds.length, 1);
assert.strictEqual(layout.kinds[0].kind, 'skills');
assert.strictEqual(layout.kinds[0].destSubpath, 'skills');
assert.strictEqual(layout.kinds[0].prefix, 'gsd-');
assert.strictEqual(typeof layout.kinds[0].stage, 'function');
// #1173: agents kind appended (descriptor now applies per-runtime agent conversion).
assert.strictEqual(layout.kinds[1].kind, 'agents');
assert.strictEqual(layout.kinds[1].destSubpath, 'agents');
assert.strictEqual(layout.kinds[1].prefix, 'gsd-');
assert.strictEqual(typeof layout.kinds[1].stage, 'function');
});
});
@@ -172,7 +151,7 @@ describe('resolveRuntimeArtifactLayout — augment', () => {
const layout = resolveRuntimeArtifactLayout('augment', FAKE_DIR);
assert.strictEqual(layout.runtime, 'augment');
assert.strictEqual(layout.configDir, FAKE_DIR);
assert.strictEqual(layout.kinds.length, 3);
assert.strictEqual(layout.kinds.length, 2);
// commands kind first
assert.strictEqual(layout.kinds[0].kind, 'commands');
assert.strictEqual(layout.kinds[0].destSubpath, 'commands');
@@ -183,11 +162,6 @@ describe('resolveRuntimeArtifactLayout — augment', () => {
assert.strictEqual(layout.kinds[1].destSubpath, 'skills');
assert.strictEqual(layout.kinds[1].prefix, 'gsd-');
assert.strictEqual(typeof layout.kinds[1].stage, 'function');
// #1173: agents kind appended (descriptor now applies per-runtime agent conversion).
assert.strictEqual(layout.kinds[2].kind, 'agents');
assert.strictEqual(layout.kinds[2].destSubpath, 'agents');
assert.strictEqual(layout.kinds[2].prefix, 'gsd-');
assert.strictEqual(typeof layout.kinds[2].stage, 'function');
});
});
@@ -196,16 +170,11 @@ describe('resolveRuntimeArtifactLayout — trae', () => {
const layout = resolveRuntimeArtifactLayout('trae', FAKE_DIR);
assert.strictEqual(layout.runtime, 'trae');
assert.strictEqual(layout.configDir, FAKE_DIR);
assert.strictEqual(layout.kinds.length, 2);
assert.strictEqual(layout.kinds.length, 1);
assert.strictEqual(layout.kinds[0].kind, 'skills');
assert.strictEqual(layout.kinds[0].destSubpath, 'skills');
assert.strictEqual(layout.kinds[0].prefix, 'gsd-');
assert.strictEqual(typeof layout.kinds[0].stage, 'function');
// #1173: agents kind appended (descriptor now applies per-runtime agent conversion).
assert.strictEqual(layout.kinds[1].kind, 'agents');
assert.strictEqual(layout.kinds[1].destSubpath, 'agents');
assert.strictEqual(layout.kinds[1].prefix, 'gsd-');
assert.strictEqual(typeof layout.kinds[1].stage, 'function');
});
});
@@ -262,7 +231,7 @@ describe('resolveRuntimeArtifactLayout — codebuddy', () => {
const layout = resolveRuntimeArtifactLayout('codebuddy', FAKE_DIR);
assert.strictEqual(layout.runtime, 'codebuddy');
assert.strictEqual(layout.configDir, FAKE_DIR);
assert.strictEqual(layout.kinds.length, 3);
assert.strictEqual(layout.kinds.length, 2);
// commands kind first
assert.strictEqual(layout.kinds[0].kind, 'commands');
assert.strictEqual(layout.kinds[0].destSubpath, 'commands');
@@ -273,11 +242,6 @@ describe('resolveRuntimeArtifactLayout — codebuddy', () => {
assert.strictEqual(layout.kinds[1].destSubpath, 'skills');
assert.strictEqual(layout.kinds[1].prefix, 'gsd-');
assert.strictEqual(typeof layout.kinds[1].stage, 'function');
// #1173: agents kind appended (descriptor now applies per-runtime agent conversion).
assert.strictEqual(layout.kinds[2].kind, 'agents');
assert.strictEqual(layout.kinds[2].destSubpath, 'agents');
assert.strictEqual(layout.kinds[2].prefix, 'gsd-');
assert.strictEqual(typeof layout.kinds[2].stage, 'function');
});
});
@@ -286,16 +250,11 @@ describe('resolveRuntimeArtifactLayout — cline', () => {
const layout = resolveRuntimeArtifactLayout('cline', FAKE_DIR, 'global');
assert.strictEqual(layout.runtime, 'cline');
assert.strictEqual(layout.configDir, FAKE_DIR);
assert.strictEqual(layout.kinds.length, 2);
assert.strictEqual(layout.kinds.length, 1);
assert.strictEqual(layout.kinds[0].kind, 'skills');
assert.strictEqual(layout.kinds[0].destSubpath, 'skills');
assert.strictEqual(layout.kinds[0].prefix, 'gsd-');
assert.strictEqual(typeof layout.kinds[0].stage, 'function');
// #1173: agents kind appended (descriptor now applies per-runtime agent conversion).
assert.strictEqual(layout.kinds[1].kind, 'agents');
assert.strictEqual(layout.kinds[1].destSubpath, 'agents');
assert.strictEqual(layout.kinds[1].prefix, 'gsd-');
assert.strictEqual(typeof layout.kinds[1].stage, 'function');
});
test('cline local: no skills kinds (global-only, #782)', () => {