Extends `dispatchKindEntry` in `runtime-artifact-layout.cts` to route agents-kind entries through a converter when the descriptor carries a non-null `converter` field. Adds `stageAgentsForRuntimeWithConverter` to `install-profiles.cts`, expands `VALID_CONVERTER_NAMES` with the 9 agent converter names, and adds a fail-first behavioral test suite (9 tests) proving the new wiring end-to-end. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -530,6 +530,59 @@ function stageSkillsForRuntimeAsSkills(
|
||||
return stageDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stage a converted copy of the agents directory for a given runtime.
|
||||
*
|
||||
* Analogous to `stageCommandsForRuntimeFlat` but for agent `.md` files. Each
|
||||
* source `.md` is passed through `converter` and written as a flat `${name}.md`
|
||||
* file in the staging directory. Agent filenames are kept verbatim (no prefix
|
||||
* added here — the prefix is already embedded in agent stems, e.g. `gsd-planner.md`).
|
||||
*
|
||||
* This is used by the descriptor-driven `dispatchKindEntry` when an `agents` kind
|
||||
* entry carries a non-null converter (ADR-457 / #1173). When `converter` is null,
|
||||
* `agentsKind` falls back to the existing raw-copy path (`stageAgentsForProfile`).
|
||||
*
|
||||
* For the `full` profile (`skills === '*'`), all `.md` files are staged.
|
||||
* For tiered profiles, only agents whose full stem is in `resolvedProfile.agents`
|
||||
* are staged (mirrors `stageAgentsForProfile` behaviour).
|
||||
*
|
||||
* @param srcAgentsDir source agents directory (e.g. agents/)
|
||||
* @param resolvedProfile profile filter from resolveProfile()
|
||||
* @param converter (content: string) → string pure per-file converter
|
||||
*/
|
||||
function stageAgentsForRuntimeWithConverter(
|
||||
srcAgentsDir: string,
|
||||
resolvedProfile: ResolvedProfile,
|
||||
converter: (content: string) => string,
|
||||
): 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 });
|
||||
for (const entry of entries) {
|
||||
if (!entry.isFile()) continue;
|
||||
if (!entry.name.endsWith('.md')) continue;
|
||||
// For tiered profiles, gate by agent stem (full filename without extension).
|
||||
if (resolvedProfile.skills !== '*') {
|
||||
const stem = entry.name.slice(0, -3);
|
||||
if (!(resolvedProfile.agents instanceof Set && resolvedProfile.agents.has(stem))) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
const content = fs.readFileSync(path.join(srcAgentsDir, entry.name), 'utf8');
|
||||
const converted = converter(content);
|
||||
fs.writeFileSync(path.join(stageDir, entry.name), converted, 'utf8');
|
||||
}
|
||||
} catch (err) {
|
||||
try { fs.rmSync(stageDir, { recursive: true, force: true }); } catch { /* best-effort */ }
|
||||
throw err;
|
||||
}
|
||||
STAGED_DIRS.add(stageDir);
|
||||
ensureExitCleanup();
|
||||
return stageDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stage converted command files as flat `.md` files.
|
||||
*
|
||||
@@ -757,6 +810,7 @@ export = {
|
||||
mostRestrictiveProfile,
|
||||
stageSkillsForProfile,
|
||||
stageAgentsForProfile,
|
||||
stageAgentsForRuntimeWithConverter,
|
||||
stageSkillsForRuntimeAsSkills,
|
||||
stageCommandsForRuntimeFlat,
|
||||
STAGED_DIRS,
|
||||
|
||||
@@ -21,6 +21,7 @@ import installProfiles = require('./install-profiles.cjs');
|
||||
const {
|
||||
stageSkillsForProfile,
|
||||
stageAgentsForProfile,
|
||||
stageAgentsForRuntimeWithConverter,
|
||||
stageSkillsForRuntimeAsSkills,
|
||||
stageCommandsForRuntimeFlat,
|
||||
} = installProfiles;
|
||||
@@ -192,6 +193,41 @@ function agentsKind(destSubpath: string, prefix: string, configDir: string): Art
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a converted-agents kind descriptor for runtimes whose agent `.md` files
|
||||
* need runtime-specific frontmatter/body conversion (e.g. Copilot, Cursor, Codex).
|
||||
*
|
||||
* Unlike `agentsKind` (which raw-copies source files), this kind applies
|
||||
* `converterName` from Runtime Artifact Conversion exports to each agent file
|
||||
* during staging, writing flat `${name}.md` files to the staged directory.
|
||||
*
|
||||
* Agent filenames are preserved verbatim (the prefix is already embedded in the
|
||||
* agent stem — e.g. `gsd-planner.md`).
|
||||
*
|
||||
* Mirrors the `convertedCommandsKind` pattern (#785).
|
||||
*
|
||||
* @param destSubpath destination subpath within configDir (e.g. 'agents')
|
||||
* @param prefix filename prefix (informational; not applied here)
|
||||
* @param converterName name of converter function in Runtime Artifact Conversion exports
|
||||
* @param configDir runtime config dir (for .gsd-source marker resolution)
|
||||
*/
|
||||
function convertedAgentsKind(
|
||||
destSubpath: string,
|
||||
prefix: string,
|
||||
converterName: string,
|
||||
configDir: string,
|
||||
): ArtifactKind {
|
||||
return {
|
||||
kind: 'agents',
|
||||
destSubpath,
|
||||
prefix,
|
||||
stage: (resolved) => {
|
||||
const converter = conversionExports[converterName] as (content: string) => string;
|
||||
return stageAgentsForRuntimeWithConverter(findAgentsSourceRoot(configDir), resolved, converter);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function kimiAgentsKind(destSubpath: string, prefix: string, configDir: string): ArtifactKind {
|
||||
return {
|
||||
kind: 'kimi-agents',
|
||||
@@ -401,7 +437,10 @@ function dispatchKindEntry(entry: ArtifactKindDescriptor, runtime: string, confi
|
||||
return convertedCommandsKind(destSubpath, prefix, converter, configDir);
|
||||
|
||||
case 'agents':
|
||||
return agentsKind(destSubpath, prefix, configDir);
|
||||
if (converter == null) {
|
||||
return agentsKind(destSubpath, prefix, configDir);
|
||||
}
|
||||
return convertedAgentsKind(destSubpath, prefix, converter, configDir);
|
||||
|
||||
case 'skills':
|
||||
if (converter == null) {
|
||||
|
||||
Reference in New Issue
Block a user