refactor(#1763): ADR-1235 agent migration — cut over the trivial-converter group to the descriptor path (#1764)
ADR-1235 step 1: route the trivial-converter runtime group (cursor, windsurf, augment, trae, codebuddy) off the inline install() agent loop onto the descriptor-driven installRuntimeArtifacts path. Establishes the converter-context foundation (pre-converter cross-cutting + no agent-stamp). Agent install output is byte-identical for all 16 runtimes (golden-parity, global + verified local). cline deliberately excluded (local rules-only). Closes #1763.
This commit is contained in:
7
.changeset/clever-cats-howl.md
Normal file
7
.changeset/clever-cats-howl.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
type: Changed
|
||||
pr: 1764
|
||||
---
|
||||
**Internal: agent install for cursor/windsurf/augment/trae/codebuddy now flows through the descriptor path** — ADR-1235 step 1 routes the trivial-converter runtime group's agents off the inline install() loop onto the descriptor-driven `installRuntimeArtifacts` path, applying the cross-cutting steps uniformly (pre-converter, no workflow-stamp). Agent output is byte-identical for all 16 runtimes (golden-parity asserted, global + local verified); no user-facing change.
|
||||
|
||||
<!-- docs-exempt: internal refactor, no user-facing surface -->
|
||||
@@ -9268,11 +9268,24 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
||||
agentsSrc = _stageAgents(path.join(src, 'agents'));
|
||||
const agentsDest = path.join(targetDir, 'agents');
|
||||
|
||||
// ADR-1235 §1: runtimes that have been migrated to the descriptor-driven agent
|
||||
// path (installRuntimeArtifacts → convertedAgentsKind). The descriptor path
|
||||
// applies path-rewrite + attribution + converter + normalize via
|
||||
// stageAgentsForRuntimeWithConverter (with agentCtx pre-converter threading) in
|
||||
// createRuntimeArtifactInstallPlan. Their agents are already written ABOVE
|
||||
// (by installRuntimeArtifacts at line 8912), which also performs its own
|
||||
// stale-file prune pass. The inline stale-removal + inline loop both skip them.
|
||||
// Trivial group (cursor/windsurf/augment/trae/codebuddy) cut over together.
|
||||
// cline is excluded: it takes a rules-only local branch and has a local/global
|
||||
// complication that the descriptor-driven path does not handle correctly.
|
||||
const _DESCRIPTOR_AGENTS_RUNTIMES = new Set(['cursor', 'windsurf', 'augment', 'trae', 'codebuddy']);
|
||||
|
||||
// Always remove stale gsd-* agents first so re-installing with
|
||||
// `--minimal` actually shrinks a previously-full install.
|
||||
// For Codex this also covers per-agent `.toml` files alongside the `.md`
|
||||
// sources so a full → minimal switch doesn't leave stale registrations.
|
||||
if (fs.existsSync(agentsDest)) {
|
||||
// Skipped for descriptor-agent runtimes (installRuntimeArtifacts prunes).
|
||||
if (!_DESCRIPTOR_AGENTS_RUNTIMES.has(runtime) && fs.existsSync(agentsDest)) {
|
||||
for (const file of fs.readdirSync(agentsDest)) {
|
||||
if (
|
||||
file.startsWith('gsd-') &&
|
||||
@@ -9285,6 +9298,10 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
||||
|
||||
if (isKimi) {
|
||||
console.log(` ${dim}↳${reset} Kimi custom agent YAML/prompt artifacts were installed via runtime artifact layout`);
|
||||
} else if (_DESCRIPTOR_AGENTS_RUNTIMES.has(runtime)) {
|
||||
// installRuntimeArtifacts already wrote agents + handles stale-file cleanup
|
||||
// via its own prune pass. No further action needed.
|
||||
console.log(` ${dim}↳${reset} Agents installed via descriptor-driven layout (${runtime})`);
|
||||
} else if (isMinimalMode(_effectiveInstallMode)) {
|
||||
// Codex registers agents in `config.toml` via `[agents.gsd-*]` sections.
|
||||
// Without stripping them here, a full → minimal reinstall would leave the
|
||||
|
||||
@@ -36,6 +36,14 @@
|
||||
"nesting": "nested",
|
||||
"recursive": false,
|
||||
"converter": "convertClaudeCommandToAugmentSkill"
|
||||
},
|
||||
{
|
||||
"kind": "agents",
|
||||
"destSubpath": "agents",
|
||||
"prefix": "gsd-",
|
||||
"nesting": "flat",
|
||||
"recursive": false,
|
||||
"converter": "convertClaudeAgentToAugmentAgent"
|
||||
}
|
||||
],
|
||||
"local": [
|
||||
@@ -54,6 +62,14 @@
|
||||
"nesting": "nested",
|
||||
"recursive": false,
|
||||
"converter": "convertClaudeCommandToAugmentSkill"
|
||||
},
|
||||
{
|
||||
"kind": "agents",
|
||||
"destSubpath": "agents",
|
||||
"prefix": "gsd-",
|
||||
"nesting": "flat",
|
||||
"recursive": false,
|
||||
"converter": "convertClaudeAgentToAugmentAgent"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -36,6 +36,14 @@
|
||||
"nesting": "flat",
|
||||
"recursive": false,
|
||||
"converter": "convertClaudeCommandToCodebuddySkill"
|
||||
},
|
||||
{
|
||||
"kind": "agents",
|
||||
"destSubpath": "agents",
|
||||
"prefix": "gsd-",
|
||||
"nesting": "flat",
|
||||
"recursive": false,
|
||||
"converter": "convertClaudeAgentToCodebuddyAgent"
|
||||
}
|
||||
],
|
||||
"local": [
|
||||
@@ -54,6 +62,14 @@
|
||||
"nesting": "flat",
|
||||
"recursive": false,
|
||||
"converter": "convertClaudeCommandToCodebuddySkill"
|
||||
},
|
||||
{
|
||||
"kind": "agents",
|
||||
"destSubpath": "agents",
|
||||
"prefix": "gsd-",
|
||||
"nesting": "flat",
|
||||
"recursive": false,
|
||||
"converter": "convertClaudeAgentToCodebuddyAgent"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -36,6 +36,14 @@
|
||||
"nesting": "flat",
|
||||
"recursive": false,
|
||||
"converter": "convertClaudeCommandToCursorCommand"
|
||||
},
|
||||
{
|
||||
"kind": "agents",
|
||||
"destSubpath": "agents",
|
||||
"prefix": "gsd-",
|
||||
"nesting": "flat",
|
||||
"recursive": false,
|
||||
"converter": "convertClaudeAgentToCursorAgent"
|
||||
}
|
||||
],
|
||||
"local": [
|
||||
@@ -54,6 +62,14 @@
|
||||
"nesting": "flat",
|
||||
"recursive": false,
|
||||
"converter": "convertClaudeCommandToCursorCommand"
|
||||
},
|
||||
{
|
||||
"kind": "agents",
|
||||
"destSubpath": "agents",
|
||||
"prefix": "gsd-",
|
||||
"nesting": "flat",
|
||||
"recursive": false,
|
||||
"converter": "convertClaudeAgentToCursorAgent"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -28,6 +28,14 @@
|
||||
"nesting": "nested",
|
||||
"recursive": false,
|
||||
"converter": "convertClaudeCommandToTraeSkill"
|
||||
},
|
||||
{
|
||||
"kind": "agents",
|
||||
"destSubpath": "agents",
|
||||
"prefix": "gsd-",
|
||||
"nesting": "flat",
|
||||
"recursive": false,
|
||||
"converter": "convertClaudeAgentToTraeAgent"
|
||||
}
|
||||
],
|
||||
"local": [
|
||||
@@ -38,6 +46,14 @@
|
||||
"nesting": "nested",
|
||||
"recursive": false,
|
||||
"converter": "convertClaudeCommandToTraeSkill"
|
||||
},
|
||||
{
|
||||
"kind": "agents",
|
||||
"destSubpath": "agents",
|
||||
"prefix": "gsd-",
|
||||
"nesting": "flat",
|
||||
"recursive": false,
|
||||
"converter": "convertClaudeAgentToTraeAgent"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -21,7 +21,16 @@
|
||||
"localConfigDir": ".windsurf",
|
||||
"configFormat": "none",
|
||||
"artifactLayout": {
|
||||
"global": [],
|
||||
"global": [
|
||||
{
|
||||
"kind": "agents",
|
||||
"destSubpath": "agents",
|
||||
"prefix": "gsd-",
|
||||
"nesting": "flat",
|
||||
"recursive": false,
|
||||
"converter": "convertClaudeAgentToWindsurfAgent"
|
||||
}
|
||||
],
|
||||
"local": [
|
||||
{
|
||||
"kind": "commands",
|
||||
@@ -30,6 +39,14 @@
|
||||
"nesting": "flat",
|
||||
"recursive": false,
|
||||
"converter": "convertClaudeCommandToWindsurfWorkflow"
|
||||
},
|
||||
{
|
||||
"kind": "agents",
|
||||
"destSubpath": "agents",
|
||||
"prefix": "gsd-",
|
||||
"nesting": "flat",
|
||||
"recursive": false,
|
||||
"converter": "convertClaudeAgentToWindsurfAgent"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -213,6 +213,14 @@ const capabilities = {
|
||||
"nesting": "nested",
|
||||
"recursive": false,
|
||||
"converter": "convertClaudeCommandToAugmentSkill"
|
||||
},
|
||||
{
|
||||
"kind": "agents",
|
||||
"destSubpath": "agents",
|
||||
"prefix": "gsd-",
|
||||
"nesting": "flat",
|
||||
"recursive": false,
|
||||
"converter": "convertClaudeAgentToAugmentAgent"
|
||||
}
|
||||
],
|
||||
"local": [
|
||||
@@ -231,6 +239,14 @@ const capabilities = {
|
||||
"nesting": "nested",
|
||||
"recursive": false,
|
||||
"converter": "convertClaudeCommandToAugmentSkill"
|
||||
},
|
||||
{
|
||||
"kind": "agents",
|
||||
"destSubpath": "agents",
|
||||
"prefix": "gsd-",
|
||||
"nesting": "flat",
|
||||
"recursive": false,
|
||||
"converter": "convertClaudeAgentToAugmentAgent"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -506,6 +522,14 @@ const capabilities = {
|
||||
"nesting": "flat",
|
||||
"recursive": false,
|
||||
"converter": "convertClaudeCommandToCodebuddySkill"
|
||||
},
|
||||
{
|
||||
"kind": "agents",
|
||||
"destSubpath": "agents",
|
||||
"prefix": "gsd-",
|
||||
"nesting": "flat",
|
||||
"recursive": false,
|
||||
"converter": "convertClaudeAgentToCodebuddyAgent"
|
||||
}
|
||||
],
|
||||
"local": [
|
||||
@@ -524,6 +548,14 @@ const capabilities = {
|
||||
"nesting": "flat",
|
||||
"recursive": false,
|
||||
"converter": "convertClaudeCommandToCodebuddySkill"
|
||||
},
|
||||
{
|
||||
"kind": "agents",
|
||||
"destSubpath": "agents",
|
||||
"prefix": "gsd-",
|
||||
"nesting": "flat",
|
||||
"recursive": false,
|
||||
"converter": "convertClaudeAgentToCodebuddyAgent"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -735,6 +767,14 @@ const capabilities = {
|
||||
"nesting": "flat",
|
||||
"recursive": false,
|
||||
"converter": "convertClaudeCommandToCursorCommand"
|
||||
},
|
||||
{
|
||||
"kind": "agents",
|
||||
"destSubpath": "agents",
|
||||
"prefix": "gsd-",
|
||||
"nesting": "flat",
|
||||
"recursive": false,
|
||||
"converter": "convertClaudeAgentToCursorAgent"
|
||||
}
|
||||
],
|
||||
"local": [
|
||||
@@ -753,6 +793,14 @@ const capabilities = {
|
||||
"nesting": "flat",
|
||||
"recursive": false,
|
||||
"converter": "convertClaudeCommandToCursorCommand"
|
||||
},
|
||||
{
|
||||
"kind": "agents",
|
||||
"destSubpath": "agents",
|
||||
"prefix": "gsd-",
|
||||
"nesting": "flat",
|
||||
"recursive": false,
|
||||
"converter": "convertClaudeAgentToCursorAgent"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -2107,6 +2155,14 @@ const capabilities = {
|
||||
"nesting": "nested",
|
||||
"recursive": false,
|
||||
"converter": "convertClaudeCommandToTraeSkill"
|
||||
},
|
||||
{
|
||||
"kind": "agents",
|
||||
"destSubpath": "agents",
|
||||
"prefix": "gsd-",
|
||||
"nesting": "flat",
|
||||
"recursive": false,
|
||||
"converter": "convertClaudeAgentToTraeAgent"
|
||||
}
|
||||
],
|
||||
"local": [
|
||||
@@ -2117,6 +2173,14 @@ const capabilities = {
|
||||
"nesting": "nested",
|
||||
"recursive": false,
|
||||
"converter": "convertClaudeCommandToTraeSkill"
|
||||
},
|
||||
{
|
||||
"kind": "agents",
|
||||
"destSubpath": "agents",
|
||||
"prefix": "gsd-",
|
||||
"nesting": "flat",
|
||||
"recursive": false,
|
||||
"converter": "convertClaudeAgentToTraeAgent"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -2265,7 +2329,16 @@ const capabilities = {
|
||||
"localConfigDir": ".windsurf",
|
||||
"configFormat": "none",
|
||||
"artifactLayout": {
|
||||
"global": [],
|
||||
"global": [
|
||||
{
|
||||
"kind": "agents",
|
||||
"destSubpath": "agents",
|
||||
"prefix": "gsd-",
|
||||
"nesting": "flat",
|
||||
"recursive": false,
|
||||
"converter": "convertClaudeAgentToWindsurfAgent"
|
||||
}
|
||||
],
|
||||
"local": [
|
||||
{
|
||||
"kind": "commands",
|
||||
@@ -2274,6 +2347,14 @@ const capabilities = {
|
||||
"nesting": "flat",
|
||||
"recursive": false,
|
||||
"converter": "convertClaudeCommandToWindsurfWorkflow"
|
||||
},
|
||||
{
|
||||
"kind": "agents",
|
||||
"destSubpath": "agents",
|
||||
"prefix": "gsd-",
|
||||
"nesting": "flat",
|
||||
"recursive": false,
|
||||
"converter": "convertClaudeAgentToWindsurfAgent"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -3144,6 +3225,14 @@ const runtimes = {
|
||||
"nesting": "nested",
|
||||
"recursive": false,
|
||||
"converter": "convertClaudeCommandToAugmentSkill"
|
||||
},
|
||||
{
|
||||
"kind": "agents",
|
||||
"destSubpath": "agents",
|
||||
"prefix": "gsd-",
|
||||
"nesting": "flat",
|
||||
"recursive": false,
|
||||
"converter": "convertClaudeAgentToAugmentAgent"
|
||||
}
|
||||
],
|
||||
"local": [
|
||||
@@ -3162,6 +3251,14 @@ const runtimes = {
|
||||
"nesting": "nested",
|
||||
"recursive": false,
|
||||
"converter": "convertClaudeCommandToAugmentSkill"
|
||||
},
|
||||
{
|
||||
"kind": "agents",
|
||||
"destSubpath": "agents",
|
||||
"prefix": "gsd-",
|
||||
"nesting": "flat",
|
||||
"recursive": false,
|
||||
"converter": "convertClaudeAgentToAugmentAgent"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -3376,6 +3473,14 @@ const runtimes = {
|
||||
"nesting": "flat",
|
||||
"recursive": false,
|
||||
"converter": "convertClaudeCommandToCodebuddySkill"
|
||||
},
|
||||
{
|
||||
"kind": "agents",
|
||||
"destSubpath": "agents",
|
||||
"prefix": "gsd-",
|
||||
"nesting": "flat",
|
||||
"recursive": false,
|
||||
"converter": "convertClaudeAgentToCodebuddyAgent"
|
||||
}
|
||||
],
|
||||
"local": [
|
||||
@@ -3394,6 +3499,14 @@ const runtimes = {
|
||||
"nesting": "flat",
|
||||
"recursive": false,
|
||||
"converter": "convertClaudeCommandToCodebuddySkill"
|
||||
},
|
||||
{
|
||||
"kind": "agents",
|
||||
"destSubpath": "agents",
|
||||
"prefix": "gsd-",
|
||||
"nesting": "flat",
|
||||
"recursive": false,
|
||||
"converter": "convertClaudeAgentToCodebuddyAgent"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -3605,6 +3718,14 @@ const runtimes = {
|
||||
"nesting": "flat",
|
||||
"recursive": false,
|
||||
"converter": "convertClaudeCommandToCursorCommand"
|
||||
},
|
||||
{
|
||||
"kind": "agents",
|
||||
"destSubpath": "agents",
|
||||
"prefix": "gsd-",
|
||||
"nesting": "flat",
|
||||
"recursive": false,
|
||||
"converter": "convertClaudeAgentToCursorAgent"
|
||||
}
|
||||
],
|
||||
"local": [
|
||||
@@ -3623,6 +3744,14 @@ const runtimes = {
|
||||
"nesting": "flat",
|
||||
"recursive": false,
|
||||
"converter": "convertClaudeCommandToCursorCommand"
|
||||
},
|
||||
{
|
||||
"kind": "agents",
|
||||
"destSubpath": "agents",
|
||||
"prefix": "gsd-",
|
||||
"nesting": "flat",
|
||||
"recursive": false,
|
||||
"converter": "convertClaudeAgentToCursorAgent"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -4160,6 +4289,14 @@ const runtimes = {
|
||||
"nesting": "nested",
|
||||
"recursive": false,
|
||||
"converter": "convertClaudeCommandToTraeSkill"
|
||||
},
|
||||
{
|
||||
"kind": "agents",
|
||||
"destSubpath": "agents",
|
||||
"prefix": "gsd-",
|
||||
"nesting": "flat",
|
||||
"recursive": false,
|
||||
"converter": "convertClaudeAgentToTraeAgent"
|
||||
}
|
||||
],
|
||||
"local": [
|
||||
@@ -4170,6 +4307,14 @@ const runtimes = {
|
||||
"nesting": "nested",
|
||||
"recursive": false,
|
||||
"converter": "convertClaudeCommandToTraeSkill"
|
||||
},
|
||||
{
|
||||
"kind": "agents",
|
||||
"destSubpath": "agents",
|
||||
"prefix": "gsd-",
|
||||
"nesting": "flat",
|
||||
"recursive": false,
|
||||
"converter": "convertClaudeAgentToTraeAgent"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -4223,7 +4368,16 @@ const runtimes = {
|
||||
"localConfigDir": ".windsurf",
|
||||
"configFormat": "none",
|
||||
"artifactLayout": {
|
||||
"global": [],
|
||||
"global": [
|
||||
{
|
||||
"kind": "agents",
|
||||
"destSubpath": "agents",
|
||||
"prefix": "gsd-",
|
||||
"nesting": "flat",
|
||||
"recursive": false,
|
||||
"converter": "convertClaudeAgentToWindsurfAgent"
|
||||
}
|
||||
],
|
||||
"local": [
|
||||
{
|
||||
"kind": "commands",
|
||||
@@ -4232,6 +4386,14 @@ const runtimes = {
|
||||
"nesting": "flat",
|
||||
"recursive": false,
|
||||
"converter": "convertClaudeCommandToWindsurfWorkflow"
|
||||
},
|
||||
{
|
||||
"kind": "agents",
|
||||
"destSubpath": "agents",
|
||||
"prefix": "gsd-",
|
||||
"nesting": "flat",
|
||||
"recursive": false,
|
||||
"converter": "convertClaudeAgentToWindsurfAgent"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -60,10 +60,34 @@ function createRuntimeArtifactInstallPlan(args) {
|
||||
platform,
|
||||
resolveAttribution,
|
||||
};
|
||||
// ADR-1235 §1: build agentCtx once per plan so agents kind entries can apply
|
||||
// the CORRECT pre-converter cross-cutting (path rewrites → attribution → converter
|
||||
// → normalize). This mirrors the exact per-file order in the inline agent loop
|
||||
// in bin/install.js (lines 9330-9415). agentCtx is passed as the second arg
|
||||
// to kind.stage() for agents kind entries with a converter (convertedAgentsKind).
|
||||
// NO _stampNonClaudeRuntimeDefaults — agents are NOT stamped in the inline loop.
|
||||
const os = _require('node:os');
|
||||
const homedirFn = homedir ?? (() => os.homedir());
|
||||
const resolvedTarget = path.resolve(layout.configDir).replace(/\\/g, '/');
|
||||
const homeDir = homedirFn().replace(/\\/g, '/');
|
||||
const isGlobal = scope === 'global';
|
||||
const isOpencode = layout.runtime === 'opencode';
|
||||
const isWindowsHost = (platform ?? process.platform) === 'win32';
|
||||
const pathPrefix = conversionExports._computePathPrefix({ isGlobal, isOpencode, isWindowsHost, resolvedTarget, homeDir });
|
||||
const attribution = resolveAttribution ? resolveAttribution(layout.runtime) : undefined;
|
||||
const agentCtx = { runtime: layout.runtime, pathPrefix, attribution };
|
||||
for (const kind of layout.kinds) {
|
||||
let stagedDir;
|
||||
try {
|
||||
stagedDir = kind.stage(resolvedProfile);
|
||||
if (kind.kind === 'agents') {
|
||||
// ADR-1235 §1: pass agentCtx so stageAgentsForRuntimeWithConverter applies
|
||||
// the full inline-loop order: pathRewrites → attribution → converter → normalize.
|
||||
// The cross-cutting is now PRE-converter (inside staging), not POST.
|
||||
stagedDir = kind.stage(resolvedProfile, agentCtx);
|
||||
}
|
||||
else {
|
||||
stagedDir = kind.stage(resolvedProfile);
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
return { ok: false, kind: 'stage_failed', message: errorMessage(err), cleanupDirs, failedKind: kind.kind };
|
||||
@@ -78,6 +102,8 @@ function createRuntimeArtifactInstallPlan(args) {
|
||||
const rewrittenDir = rewriteStagedSkillBodies(stagedDir, rewriteOpts);
|
||||
sourceDir = addCleanupDir(cleanupDirs, stagedDir, rewrittenDir);
|
||||
}
|
||||
// agents kind: cross-cutting already applied INSIDE kind.stage() via agentCtx.
|
||||
// No POST-step needed. sourceDir stays as stagedDir.
|
||||
}
|
||||
catch (err) {
|
||||
return { ok: false, kind: 'rewrite_failed', message: errorMessage(err), cleanupDirs, failedKind: kind.kind };
|
||||
|
||||
@@ -11,6 +11,19 @@ import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import os from 'node:os';
|
||||
import { platformWriteSync } from './shell-command-projection.cjs';
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
import conversionModule = require('./runtime-artifact-conversion.cjs');
|
||||
const {
|
||||
applyAgentPathRewrites: _applyAgentPathRewrites,
|
||||
processAttribution: _processAttribution,
|
||||
normalizeAgentBodyForRuntime: _normalizeAgentBodyForRuntime,
|
||||
readGsdCommandNames: _readGsdCommandNames,
|
||||
} = conversionModule as {
|
||||
applyAgentPathRewrites: (content: string, runtime: string, pathPrefix: string) => string;
|
||||
processAttribution: (content: string, attribution: string | null | undefined) => string;
|
||||
normalizeAgentBodyForRuntime: (content: string, runtime: string, cmdNames: string[]) => string;
|
||||
readGsdCommandNames: () => string[];
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Profile definitions
|
||||
@@ -530,6 +543,18 @@ function stageSkillsForRuntimeAsSkills(
|
||||
return stageDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cross-cutting context for descriptor-driven agent staging (ADR-1235 §1).
|
||||
* When present, stageAgentsForRuntimeWithConverter applies the full inline-loop
|
||||
* sequence per agent: pathRewrites → attribution → converter → normalize.
|
||||
* The field names mirror the inline loop's available identifiers.
|
||||
*/
|
||||
interface AgentCtx {
|
||||
runtime: string;
|
||||
pathPrefix: string;
|
||||
attribution: string | null | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stage a converted copy of the agents directory for a given runtime.
|
||||
*
|
||||
@@ -546,24 +571,39 @@ function stageSkillsForRuntimeAsSkills(
|
||||
* For tiered profiles, only agents whose full stem is in `resolvedProfile.agents`
|
||||
* are staged (mirrors `stageAgentsForProfile` behaviour).
|
||||
*
|
||||
* ADR-1235 §1: when `agentCtx` is provided, the per-file order matches the inline
|
||||
* agent loop in bin/install.js exactly:
|
||||
* 1. applyAgentPathRewrites (4 base ~/.claude/ regexes; skipped for copilot/antigravity)
|
||||
* 2. processAttribution (Co-Authored-By policy)
|
||||
* 3. converter (runtime-specific frontmatter/body transform)
|
||||
* 4. normalizeAgentBodyForRuntime (colon→hyphen refs; no-op for trivial group)
|
||||
* When `agentCtx` is absent, only the converter is applied (backward-compat for
|
||||
* the feat-1173 synthetic-descriptor tests and the copilot/antigravity paths
|
||||
* that handle cross-cutting inside their converters).
|
||||
*
|
||||
* @param srcAgentsDir source agents directory (e.g. agents/)
|
||||
* @param resolvedProfile profile filter from resolveProfile()
|
||||
* @param converter (content: string, isGlobal?: boolean) → string per-file
|
||||
* converter; scope-aware converters (copilot/antigravity)
|
||||
* read isGlobal, single-arg converters ignore it (#1173)
|
||||
* @param isGlobal install scope passed through to the converter
|
||||
* @param agentCtx optional cross-cutting context (ADR-1235 §1); when absent,
|
||||
* only the converter is applied (backward compat)
|
||||
*/
|
||||
function stageAgentsForRuntimeWithConverter(
|
||||
srcAgentsDir: string,
|
||||
resolvedProfile: ResolvedProfile,
|
||||
converter: (content: string, isGlobal?: boolean) => string,
|
||||
isGlobal = false,
|
||||
agentCtx?: AgentCtx,
|
||||
): string {
|
||||
if (!fs.existsSync(srcAgentsDir)) return srcAgentsDir;
|
||||
|
||||
const stageDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-profile-runtime-agents-'));
|
||||
try {
|
||||
const entries = fs.readdirSync(srcAgentsDir, { withFileTypes: true });
|
||||
// Resolve cmdNames once per staging call (not per file) for performance.
|
||||
const cmdNames = agentCtx ? _readGsdCommandNames() : [];
|
||||
for (const entry of entries) {
|
||||
if (!entry.isFile()) continue;
|
||||
if (!entry.name.endsWith('.md')) continue;
|
||||
@@ -574,9 +614,22 @@ function stageAgentsForRuntimeWithConverter(
|
||||
continue;
|
||||
}
|
||||
}
|
||||
const content = fs.readFileSync(path.join(srcAgentsDir, entry.name), 'utf8');
|
||||
const converted = converter(content, isGlobal);
|
||||
fs.writeFileSync(path.join(stageDir, entry.name), converted, 'utf8');
|
||||
let content = fs.readFileSync(path.join(srcAgentsDir, entry.name), 'utf8');
|
||||
if (agentCtx) {
|
||||
// ADR-1235 §1: pre-converter cross-cutting (matches inline loop order exactly)
|
||||
// Step 1: path rewrites (4 base ~/.claude/ regexes; skipped for copilot/antigravity)
|
||||
content = _applyAgentPathRewrites(content, agentCtx.runtime, agentCtx.pathPrefix);
|
||||
// Step 2: attribution
|
||||
content = _processAttribution(content, agentCtx.attribution);
|
||||
// Step 3: converter (runtime-specific frontmatter/body transform)
|
||||
content = converter(content, isGlobal);
|
||||
// Step 4: normalize colon→hyphen refs (no-op for trivial group)
|
||||
content = _normalizeAgentBodyForRuntime(content, agentCtx.runtime, cmdNames);
|
||||
} else {
|
||||
// Backward-compat: only apply the converter (no cross-cutting)
|
||||
content = converter(content, isGlobal);
|
||||
}
|
||||
fs.writeFileSync(path.join(stageDir, entry.name), content, 'utf8');
|
||||
}
|
||||
} catch (err) {
|
||||
try { fs.rmSync(stageDir, { recursive: true, force: true }); } catch { /* best-effort */ }
|
||||
|
||||
@@ -2551,6 +2551,60 @@ function rewriteStagedCommandBodies(stagedDir, opts) {
|
||||
return applyRuntimeContentRewritesForCommandsInPlace(stagedDir, runtime, pathPrefix, isGlobal, attribution);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runtimes that use the hyphen-namespace form `/gsd-<cmd>` in agent bodies.
|
||||
* claude/qwen/hermes use hyphen-name:`...` frontmatter; cursor/windsurf/etc
|
||||
* self-convert. Mirrors the `HYPHEN_NAME_AGENT_RUNTIMES` set in bin/install.js.
|
||||
*
|
||||
* @private — export normalizeAgentBodyForRuntime for callers.
|
||||
*/
|
||||
const HYPHEN_NAME_AGENT_RUNTIMES: ReadonlySet<string> = new Set(['claude', 'qwen', 'hermes']);
|
||||
|
||||
/**
|
||||
* Normalize `/gsd:<cmd>` colon refs in the agent body to `/gsd-<cmd>` for
|
||||
* hyphen-`name:` runtimes (claude / qwen / hermes). No-op for all other
|
||||
* runtimes. Mirrors the per-file call in bin/install.js line 9400.
|
||||
*
|
||||
* @param content raw agent file content (post-converter)
|
||||
* @param runtime canonical runtime ID
|
||||
* @param cmdNames gsd command names from readGsdCommandNames()
|
||||
*/
|
||||
function normalizeAgentBodyForRuntime(content: string, runtime: string, cmdNames: string[]): string {
|
||||
if (!HYPHEN_NAME_AGENT_RUNTIMES.has(runtime)) return content;
|
||||
return transformContentToHyphen(content, cmdNames);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the 4 base `~/.claude/` path-prefix rewrites to a single agent content
|
||||
* string. Mirrors the inline agent loop in bin/install.js lines 9330-9340:
|
||||
* ~/\.claude/ → pathPrefix
|
||||
* $HOME/\.claude/ → pathPrefix
|
||||
* ~/\.claude\b → normalizedPathPrefix
|
||||
* $HOME/\.claude\b → normalizedPathPrefix
|
||||
*
|
||||
* Skipped for copilot and antigravity (which do NOT do path rewrites in the
|
||||
* inline loop). NO stamp (_stampNonClaudeRuntimeDefaults) — agents are NOT
|
||||
* stamped in the inline loop.
|
||||
*
|
||||
* ADR-1235 §1: pre-converter cross-cutting for descriptor-driven agent pipeline.
|
||||
* Exported as `applyAgentPathRewrites` for testing and for injection into
|
||||
* stageAgentsForRuntimeWithConverter via agentCtx.
|
||||
*
|
||||
* @param content raw agent file content
|
||||
* @param runtime canonical runtime ID
|
||||
* @param pathPrefix trailing-slash path prefix (e.g. '$HOME/.cursor/')
|
||||
* @returns content with path-prefix rewrites applied (or unchanged for copilot/antigravity)
|
||||
*/
|
||||
function applyAgentPathRewrites(content: string, runtime: string, pathPrefix: string): string {
|
||||
if (runtime === 'copilot' || runtime === 'antigravity') return content;
|
||||
const normalizedPathPrefix = pathPrefix.replace(/\/$/, '');
|
||||
content = content.replace(/~\/\.claude\//g, pathPrefix);
|
||||
content = content.replace(/\$HOME\/\.claude\//g, pathPrefix);
|
||||
content = content.replace(/~\/\.claude\b/g, normalizedPathPrefix);
|
||||
content = content.replace(/\$HOME\/\.claude\b/g, normalizedPathPrefix);
|
||||
return content;
|
||||
}
|
||||
|
||||
// ── End rewrite engine ────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
@@ -2644,6 +2698,9 @@ export = {
|
||||
// High-level wrappers (derive pathPrefix + attribution from opts):
|
||||
rewriteStagedSkillBodies,
|
||||
rewriteStagedCommandBodies,
|
||||
// ADR-1235 §1: descriptor-driven agent cross-cutting
|
||||
applyAgentPathRewrites,
|
||||
normalizeAgentBodyForRuntime,
|
||||
_computePathPrefix: computePathPrefix,
|
||||
_applyRuntimeRewrites,
|
||||
_stampNonClaudeRuntimeDefaults,
|
||||
|
||||
@@ -21,11 +21,17 @@ interface ResolvedProfile {
|
||||
agents?: Set<string>;
|
||||
}
|
||||
|
||||
interface AgentCtx {
|
||||
runtime: string;
|
||||
pathPrefix: string;
|
||||
attribution: string | null | undefined;
|
||||
}
|
||||
|
||||
interface ArtifactKind {
|
||||
kind: ArtifactKindName;
|
||||
destSubpath: string;
|
||||
prefix?: string;
|
||||
stage: (resolvedProfile: ResolvedProfile) => string;
|
||||
stage: (resolvedProfile: ResolvedProfile, agentCtx?: AgentCtx) => string;
|
||||
}
|
||||
|
||||
interface Layout {
|
||||
@@ -49,9 +55,18 @@ interface Dependencies {
|
||||
rewriteStagedCommandBodies?: (stagedDir: string, opts: RewriteOpts) => string | void;
|
||||
}
|
||||
|
||||
interface ComputePathPrefixOpts {
|
||||
isGlobal: boolean;
|
||||
isOpencode: boolean;
|
||||
isWindowsHost: boolean;
|
||||
resolvedTarget: string;
|
||||
homeDir: string;
|
||||
}
|
||||
|
||||
interface RuntimeArtifactConversionExports {
|
||||
rewriteStagedSkillBodies: (stagedDir: string, opts: RewriteOpts) => string | void;
|
||||
rewriteStagedCommandBodies: (stagedDir: string, opts: RewriteOpts) => string | void;
|
||||
_computePathPrefix: (opts: ComputePathPrefixOpts) => string;
|
||||
}
|
||||
|
||||
interface PlanItem {
|
||||
@@ -151,10 +166,34 @@ function createRuntimeArtifactInstallPlan(args: CreateRuntimeArtifactInstallPlan
|
||||
resolveAttribution,
|
||||
};
|
||||
|
||||
// ADR-1235 §1: build agentCtx once per plan so agents kind entries can apply
|
||||
// the CORRECT pre-converter cross-cutting (path rewrites → attribution → converter
|
||||
// → normalize). This mirrors the exact per-file order in the inline agent loop
|
||||
// in bin/install.js (lines 9330-9415). agentCtx is passed as the second arg
|
||||
// to kind.stage() for agents kind entries with a converter (convertedAgentsKind).
|
||||
// NO _stampNonClaudeRuntimeDefaults — agents are NOT stamped in the inline loop.
|
||||
const os = _require('node:os') as typeof import('node:os');
|
||||
const homedirFn: () => string = homedir ?? (() => os.homedir());
|
||||
const resolvedTarget = path.resolve(layout.configDir).replace(/\\/g, '/');
|
||||
const homeDir = homedirFn().replace(/\\/g, '/');
|
||||
const isGlobal = scope === 'global';
|
||||
const isOpencode = layout.runtime === 'opencode';
|
||||
const isWindowsHost = (platform ?? process.platform) === 'win32';
|
||||
const pathPrefix = conversionExports._computePathPrefix({ isGlobal, isOpencode, isWindowsHost, resolvedTarget, homeDir });
|
||||
const attribution = resolveAttribution ? resolveAttribution(layout.runtime) : undefined;
|
||||
const agentCtx: AgentCtx = { runtime: layout.runtime, pathPrefix, attribution };
|
||||
|
||||
for (const kind of layout.kinds) {
|
||||
let stagedDir: string;
|
||||
try {
|
||||
stagedDir = kind.stage(resolvedProfile);
|
||||
if (kind.kind === 'agents') {
|
||||
// ADR-1235 §1: pass agentCtx so stageAgentsForRuntimeWithConverter applies
|
||||
// the full inline-loop order: pathRewrites → attribution → converter → normalize.
|
||||
// The cross-cutting is now PRE-converter (inside staging), not POST.
|
||||
stagedDir = kind.stage(resolvedProfile, agentCtx);
|
||||
} else {
|
||||
stagedDir = kind.stage(resolvedProfile);
|
||||
}
|
||||
} catch (err) {
|
||||
return { ok: false, kind: 'stage_failed', message: errorMessage(err), cleanupDirs, failedKind: kind.kind };
|
||||
}
|
||||
@@ -168,6 +207,8 @@ function createRuntimeArtifactInstallPlan(args: CreateRuntimeArtifactInstallPlan
|
||||
const rewrittenDir = rewriteStagedSkillBodies(stagedDir, rewriteOpts);
|
||||
sourceDir = addCleanupDir(cleanupDirs, stagedDir, rewrittenDir);
|
||||
}
|
||||
// agents kind: cross-cutting already applied INSIDE kind.stage() via agentCtx.
|
||||
// No POST-step needed. sourceDir stays as stagedDir.
|
||||
} catch (err) {
|
||||
return { ok: false, kind: 'rewrite_failed', message: errorMessage(err), cleanupDirs, failedKind: kind.kind };
|
||||
}
|
||||
|
||||
@@ -54,11 +54,25 @@ interface ResolvedProfile {
|
||||
agents: Set<string>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cross-cutting context for descriptor-driven agent staging (ADR-1235 §1).
|
||||
* Passed as the optional second arg to ArtifactKind.stage() for agents kind
|
||||
* entries so that stageAgentsForRuntimeWithConverter can apply the exact
|
||||
* inline-loop transform order: pathRewrites → attribution → converter → normalize.
|
||||
*/
|
||||
interface AgentCtx {
|
||||
runtime: string;
|
||||
pathPrefix: string;
|
||||
attribution: string | null | undefined;
|
||||
}
|
||||
|
||||
interface ArtifactKind {
|
||||
kind: KimiArtifactKindName;
|
||||
destSubpath: string;
|
||||
prefix: string;
|
||||
stage: (resolvedProfile: ResolvedProfile) => string;
|
||||
/** For agents kind with a converter, accepts an optional AgentCtx as the second
|
||||
* arg so cross-cutting can be applied pre-converter (ADR-1235 §1). */
|
||||
stage: (resolvedProfile: ResolvedProfile, agentCtx?: AgentCtx) => string;
|
||||
}
|
||||
|
||||
interface Layout {
|
||||
@@ -207,17 +221,21 @@ function convertedAgentsKind(
|
||||
kind: 'agents',
|
||||
destSubpath,
|
||||
prefix,
|
||||
stage: (resolved) => {
|
||||
stage: (resolved, agentCtx) => {
|
||||
// isGlobal is threaded so scope-aware agent converters (copilot, antigravity)
|
||||
// choose global-home vs workspace-relative paths; converters that only take
|
||||
// (content) ignore the extra positional arg. Mirrors skillsKind's scope
|
||||
// threading (#1173).
|
||||
const converter = conversionExports[converterName] as (content: string, isGlobal?: boolean) => string;
|
||||
// ADR-1235 §1: when agentCtx is provided (by createRuntimeArtifactInstallPlan
|
||||
// for descriptor-driven runtimes), thread it through so stageAgentsForRuntimeWithConverter
|
||||
// can apply the full pre-converter + post-converter sequence in the correct order.
|
||||
return stageAgentsForRuntimeWithConverter(
|
||||
findAgentsSourceRoot(configDir),
|
||||
resolved,
|
||||
converter,
|
||||
scope === 'global',
|
||||
agentCtx,
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -56,11 +56,11 @@ const RESOLVED_CORE = resolveProfile({ modes: ['core'], manifest: MANIFEST });
|
||||
// ─── Layout contract ─────────────────────────────────────────────────────────
|
||||
|
||||
describe('enh-789 — codebuddy layout has commands + skills kinds', () => {
|
||||
test('resolveRuntimeArtifactLayout codebuddy returns 2 kinds', () => {
|
||||
test('resolveRuntimeArtifactLayout codebuddy returns 3 kinds (ADR-1235 §1 agents cutover)', () => {
|
||||
const layout = resolveRuntimeArtifactLayout('codebuddy', '/tmp/fake-codebuddy-dir');
|
||||
assert.strictEqual(layout.kinds.length, 2, 'codebuddy must have exactly 2 artifact kinds');
|
||||
assert.strictEqual(layout.kinds.length, 3, 'codebuddy must have exactly 3 artifact kinds (commands + skills + agents)');
|
||||
const kindNames = layout.kinds.map(k => k.kind).sort();
|
||||
assert.deepStrictEqual(kindNames, ['commands', 'skills']);
|
||||
assert.deepStrictEqual(kindNames, ['agents', 'commands', 'skills']);
|
||||
});
|
||||
|
||||
test('codebuddy commands kind targets commands/ with gsd- prefix', () => {
|
||||
|
||||
@@ -31,12 +31,12 @@ const RESOLVED_CORE = resolveProfile({ modes: ['core'], manifest: MANIFEST });
|
||||
|
||||
// ─── Layout contract ─────────────────────────────────────────────────────────
|
||||
|
||||
describe('enh-790 — augment layout has commands + skills kinds', () => {
|
||||
test('resolveRuntimeArtifactLayout augment returns 2 kinds', () => {
|
||||
describe('enh-790 — augment layout has commands + skills + agents kinds', () => {
|
||||
test('resolveRuntimeArtifactLayout augment returns 3 kinds', () => {
|
||||
const layout = resolveRuntimeArtifactLayout('augment', '/tmp/fake-augment-dir');
|
||||
assert.strictEqual(layout.kinds.length, 2, 'augment must have exactly 2 artifact kinds');
|
||||
assert.strictEqual(layout.kinds.length, 3, 'augment must have exactly 3 artifact kinds');
|
||||
const kindNames = layout.kinds.map(k => k.kind).sort();
|
||||
assert.deepStrictEqual(kindNames, ['commands', 'skills']);
|
||||
assert.deepStrictEqual(kindNames, ['agents', 'commands', 'skills']);
|
||||
});
|
||||
|
||||
test('augment commands kind targets commands/ with gsd- prefix', () => {
|
||||
|
||||
@@ -44,6 +44,8 @@ const FAKE_DIR = '/tmp/fake-config-dir-dd';
|
||||
// ── STEP-0 golden (captured from switch BEFORE edits) ────────────────────────
|
||||
// Format: { kind, destSubpath, prefix } for each entry in kinds[].
|
||||
// 'function' means we assert typeof kind.stage === 'function'.
|
||||
// ADR-1235 step 1 (#1763): cursor, windsurf, augment, trae, codebuddy each gained
|
||||
// an `agents` kind (appended last). Goldens consciously updated post-cutover.
|
||||
|
||||
const GOLDEN = {
|
||||
// ── claude ──────────────────────────────────────────────────────────────────
|
||||
@@ -58,13 +60,16 @@ const GOLDEN = {
|
||||
// ── cursor ───────────────────────────────────────────────────────────────────
|
||||
// Old switch: BOTH scopes returned [skills, commands] (no scope branch).
|
||||
// 5b backfill: local == global.
|
||||
// ADR-1235 step 1 (#1763): agents kind added.
|
||||
'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 ───────────────────────────────────────────────────────────────────
|
||||
@@ -104,29 +109,39 @@ const GOLDEN = {
|
||||
],
|
||||
|
||||
// ── windsurf ─────────────────────────────────────────────────────────────────
|
||||
'windsurf/global': [],
|
||||
// ADR-1235 step 1 (#1763): agents kind added to both scopes.
|
||||
'windsurf/global': [
|
||||
{ kind: 'agents', destSubpath: 'agents', prefix: 'gsd-' },
|
||||
],
|
||||
'windsurf/local': [
|
||||
{ kind: 'commands', destSubpath: 'workflows', prefix: 'gsd-' },
|
||||
{ kind: 'agents', destSubpath: 'agents', prefix: 'gsd-' },
|
||||
],
|
||||
|
||||
// ── augment ──────────────────────────────────────────────────────────────────
|
||||
// Old switch: no scope branch → local == global. 5b backfill restores this.
|
||||
// ADR-1235 step 1 (#1763): agents kind added.
|
||||
'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.
|
||||
// ADR-1235 step 1 (#1763): agents kind added.
|
||||
'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 ─────────────────────────────────────────────────────────────────────
|
||||
@@ -149,13 +164,16 @@ const GOLDEN = {
|
||||
|
||||
// ── codebuddy ────────────────────────────────────────────────────────────────
|
||||
// Old switch: no scope branch → local == global. 5b backfill restores this.
|
||||
// ADR-1235 step 1 (#1763): agents kind added.
|
||||
'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 ────────────────────────────────────────────────────────────────────
|
||||
@@ -360,13 +378,15 @@ 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 2 kinds (commands + skills)', () => {
|
||||
test('augment global returns 3 kinds (commands + skills + agents)', () => {
|
||||
const layout = resolveRuntimeArtifactLayout('augment', FAKE_DIR, 'global');
|
||||
assert.strictEqual(layout.kinds.length, 2);
|
||||
assert.strictEqual(layout.kinds.length, 3);
|
||||
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', () => {
|
||||
@@ -378,10 +398,11 @@ describe('resolveRuntimeArtifactLayout — multi-kind runtimes non-vacuous (desc
|
||||
assert.strictEqual(layout.kinds[1].prefix, 'gsd');
|
||||
});
|
||||
|
||||
test('codebuddy global returns commands then skills', () => {
|
||||
test('codebuddy global returns commands then skills then agents', () => {
|
||||
const layout = resolveRuntimeArtifactLayout('codebuddy', FAKE_DIR, 'global');
|
||||
assert.strictEqual(layout.kinds.length, 2);
|
||||
assert.strictEqual(layout.kinds.length, 3);
|
||||
assert.strictEqual(layout.kinds[0].kind, 'commands');
|
||||
assert.strictEqual(layout.kinds[1].kind, 'skills');
|
||||
assert.strictEqual(layout.kinds[2].kind, 'agents');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -61,11 +61,11 @@ describe('resolveRuntimeArtifactLayout — claude global', () => {
|
||||
});
|
||||
|
||||
describe('resolveRuntimeArtifactLayout — cursor', () => {
|
||||
test('returns correct layout for cursor — skills + commands kinds (#785)', () => {
|
||||
test('returns correct layout for cursor — skills + commands + agents kinds (#785, ADR-1235)', () => {
|
||||
const layout = resolveRuntimeArtifactLayout('cursor', FAKE_DIR);
|
||||
assert.strictEqual(layout.runtime, 'cursor');
|
||||
assert.strictEqual(layout.configDir, FAKE_DIR);
|
||||
assert.strictEqual(layout.kinds.length, 2);
|
||||
assert.strictEqual(layout.kinds.length, 3);
|
||||
|
||||
const skillsKind = layout.kinds.find(k => k.kind === 'skills');
|
||||
assert.ok(skillsKind, 'must have a skills kind');
|
||||
@@ -78,6 +78,12 @@ describe('resolveRuntimeArtifactLayout — cursor', () => {
|
||||
assert.strictEqual(commandsKind.destSubpath, 'commands');
|
||||
assert.strictEqual(commandsKind.prefix, 'gsd-');
|
||||
assert.strictEqual(typeof commandsKind.stage, 'function');
|
||||
|
||||
const agentsKind = layout.kinds.find(k => k.kind === 'agents');
|
||||
assert.ok(agentsKind, 'must have an agents kind (ADR-1235 §1 descriptor cutover)');
|
||||
assert.strictEqual(agentsKind.destSubpath, 'agents');
|
||||
assert.strictEqual(agentsKind.prefix, 'gsd-');
|
||||
assert.strictEqual(typeof agentsKind.stage, 'function');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -134,54 +140,84 @@ describe('resolveRuntimeArtifactLayout — antigravity', () => {
|
||||
});
|
||||
|
||||
describe('resolveRuntimeArtifactLayout — windsurf', () => {
|
||||
test('returns local workflow layout for windsurf', () => {
|
||||
test('returns local workflow + agents layout for windsurf (ADR-1235)', () => {
|
||||
const layout = resolveRuntimeArtifactLayout('windsurf', FAKE_DIR, 'local');
|
||||
assert.strictEqual(layout.runtime, 'windsurf');
|
||||
assert.strictEqual(layout.configDir, FAKE_DIR);
|
||||
assert.strictEqual(layout.kinds.length, 1);
|
||||
assert.strictEqual(layout.kinds[0].kind, 'commands');
|
||||
assert.strictEqual(layout.kinds[0].destSubpath, 'workflows');
|
||||
assert.strictEqual(layout.kinds[0].prefix, 'gsd-');
|
||||
assert.strictEqual(typeof layout.kinds[0].stage, 'function');
|
||||
assert.strictEqual(layout.kinds.length, 2);
|
||||
|
||||
const commandsKind = layout.kinds.find(k => k.kind === 'commands');
|
||||
assert.ok(commandsKind, 'must have a commands/workflows kind');
|
||||
assert.strictEqual(commandsKind.destSubpath, 'workflows');
|
||||
assert.strictEqual(commandsKind.prefix, 'gsd-');
|
||||
assert.strictEqual(typeof commandsKind.stage, 'function');
|
||||
|
||||
const agentsKind = layout.kinds.find(k => k.kind === 'agents');
|
||||
assert.ok(agentsKind, 'must have an agents kind (ADR-1235 §1 descriptor cutover)');
|
||||
assert.strictEqual(agentsKind.destSubpath, 'agents');
|
||||
assert.strictEqual(agentsKind.prefix, 'gsd-');
|
||||
assert.strictEqual(typeof agentsKind.stage, 'function');
|
||||
});
|
||||
|
||||
test('returns empty global layout for windsurf', () => {
|
||||
test('returns agents-only global layout for windsurf (ADR-1235)', () => {
|
||||
const layout = resolveRuntimeArtifactLayout('windsurf', FAKE_DIR, 'global');
|
||||
assert.strictEqual(layout.runtime, 'windsurf');
|
||||
assert.strictEqual(layout.configDir, FAKE_DIR);
|
||||
assert.strictEqual(layout.kinds.length, 0);
|
||||
assert.strictEqual(layout.kinds.length, 1);
|
||||
|
||||
const agentsKind = layout.kinds.find(k => k.kind === 'agents');
|
||||
assert.ok(agentsKind, 'global windsurf must have agents kind (ADR-1235 §1)');
|
||||
assert.strictEqual(agentsKind.destSubpath, 'agents');
|
||||
assert.strictEqual(agentsKind.prefix, 'gsd-');
|
||||
assert.strictEqual(typeof agentsKind.stage, 'function');
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolveRuntimeArtifactLayout — augment', () => {
|
||||
test('returns correct layout for augment (commands + skills)', () => {
|
||||
test('returns correct layout for augment (commands + skills + agents — ADR-1235)', () => {
|
||||
const layout = resolveRuntimeArtifactLayout('augment', FAKE_DIR);
|
||||
assert.strictEqual(layout.runtime, 'augment');
|
||||
assert.strictEqual(layout.configDir, FAKE_DIR);
|
||||
assert.strictEqual(layout.kinds.length, 2);
|
||||
// commands kind first
|
||||
assert.strictEqual(layout.kinds[0].kind, 'commands');
|
||||
assert.strictEqual(layout.kinds[0].destSubpath, 'commands');
|
||||
assert.strictEqual(layout.kinds[0].prefix, 'gsd-');
|
||||
assert.strictEqual(typeof layout.kinds[0].stage, 'function');
|
||||
// skills kind second
|
||||
assert.strictEqual(layout.kinds[1].kind, 'skills');
|
||||
assert.strictEqual(layout.kinds[1].destSubpath, 'skills');
|
||||
assert.strictEqual(layout.kinds[1].prefix, 'gsd-');
|
||||
assert.strictEqual(typeof layout.kinds[1].stage, 'function');
|
||||
assert.strictEqual(layout.kinds.length, 3);
|
||||
|
||||
const commandsKind = layout.kinds.find(k => k.kind === 'commands');
|
||||
assert.ok(commandsKind, 'must have a commands kind');
|
||||
assert.strictEqual(commandsKind.destSubpath, 'commands');
|
||||
assert.strictEqual(commandsKind.prefix, 'gsd-');
|
||||
assert.strictEqual(typeof commandsKind.stage, 'function');
|
||||
|
||||
const skillsKind = layout.kinds.find(k => k.kind === 'skills');
|
||||
assert.ok(skillsKind, 'must have a skills kind');
|
||||
assert.strictEqual(skillsKind.destSubpath, 'skills');
|
||||
assert.strictEqual(skillsKind.prefix, 'gsd-');
|
||||
assert.strictEqual(typeof skillsKind.stage, 'function');
|
||||
|
||||
const agentsKind = layout.kinds.find(k => k.kind === 'agents');
|
||||
assert.ok(agentsKind, 'must have an agents kind (ADR-1235 §1 descriptor cutover)');
|
||||
assert.strictEqual(agentsKind.destSubpath, 'agents');
|
||||
assert.strictEqual(agentsKind.prefix, 'gsd-');
|
||||
assert.strictEqual(typeof agentsKind.stage, 'function');
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolveRuntimeArtifactLayout — trae', () => {
|
||||
test('returns correct layout for trae', () => {
|
||||
test('returns correct layout for trae (skills + agents — ADR-1235)', () => {
|
||||
const layout = resolveRuntimeArtifactLayout('trae', FAKE_DIR);
|
||||
assert.strictEqual(layout.runtime, 'trae');
|
||||
assert.strictEqual(layout.configDir, FAKE_DIR);
|
||||
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');
|
||||
assert.strictEqual(layout.kinds.length, 2);
|
||||
|
||||
const skillsKind = layout.kinds.find(k => k.kind === 'skills');
|
||||
assert.ok(skillsKind, 'must have a skills kind');
|
||||
assert.strictEqual(skillsKind.destSubpath, 'skills');
|
||||
assert.strictEqual(skillsKind.prefix, 'gsd-');
|
||||
assert.strictEqual(typeof skillsKind.stage, 'function');
|
||||
|
||||
const agentsKind = layout.kinds.find(k => k.kind === 'agents');
|
||||
assert.ok(agentsKind, 'must have an agents kind (ADR-1235 §1 descriptor cutover)');
|
||||
assert.strictEqual(agentsKind.destSubpath, 'agents');
|
||||
assert.strictEqual(agentsKind.prefix, 'gsd-');
|
||||
assert.strictEqual(typeof agentsKind.stage, 'function');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -234,21 +270,29 @@ describe('resolveRuntimeArtifactLayout — hermes', () => {
|
||||
});
|
||||
|
||||
describe('resolveRuntimeArtifactLayout — codebuddy', () => {
|
||||
test('returns correct layout for codebuddy (commands + skills — #789)', () => {
|
||||
test('returns correct layout for codebuddy (commands + skills + agents — #789, ADR-1235)', () => {
|
||||
const layout = resolveRuntimeArtifactLayout('codebuddy', FAKE_DIR);
|
||||
assert.strictEqual(layout.runtime, 'codebuddy');
|
||||
assert.strictEqual(layout.configDir, FAKE_DIR);
|
||||
assert.strictEqual(layout.kinds.length, 2);
|
||||
// commands kind first
|
||||
assert.strictEqual(layout.kinds[0].kind, 'commands');
|
||||
assert.strictEqual(layout.kinds[0].destSubpath, 'commands');
|
||||
assert.strictEqual(layout.kinds[0].prefix, 'gsd-');
|
||||
assert.strictEqual(typeof layout.kinds[0].stage, 'function');
|
||||
// skills kind second
|
||||
assert.strictEqual(layout.kinds[1].kind, 'skills');
|
||||
assert.strictEqual(layout.kinds[1].destSubpath, 'skills');
|
||||
assert.strictEqual(layout.kinds[1].prefix, 'gsd-');
|
||||
assert.strictEqual(typeof layout.kinds[1].stage, 'function');
|
||||
assert.strictEqual(layout.kinds.length, 3);
|
||||
|
||||
const commandsKind = layout.kinds.find(k => k.kind === 'commands');
|
||||
assert.ok(commandsKind, 'must have a commands kind');
|
||||
assert.strictEqual(commandsKind.destSubpath, 'commands');
|
||||
assert.strictEqual(commandsKind.prefix, 'gsd-');
|
||||
assert.strictEqual(typeof commandsKind.stage, 'function');
|
||||
|
||||
const skillsKind = layout.kinds.find(k => k.kind === 'skills');
|
||||
assert.ok(skillsKind, 'must have a skills kind');
|
||||
assert.strictEqual(skillsKind.destSubpath, 'skills');
|
||||
assert.strictEqual(skillsKind.prefix, 'gsd-');
|
||||
assert.strictEqual(typeof skillsKind.stage, 'function');
|
||||
|
||||
const agentsKind = layout.kinds.find(k => k.kind === 'agents');
|
||||
assert.ok(agentsKind, 'must have an agents kind (ADR-1235 §1 descriptor cutover)');
|
||||
assert.strictEqual(agentsKind.destSubpath, 'agents');
|
||||
assert.strictEqual(agentsKind.prefix, 'gsd-');
|
||||
assert.strictEqual(typeof agentsKind.stage, 'function');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user