Merge pull request #1438 from behruznassre/feat/1173-wire-agent-converters-descriptor
feat(#1173): wire the 8 agent converters into the descriptor-driven install path
This commit is contained in:
7
.changeset/clever-seals-rest.md
Normal file
7
.changeset/clever-seals-rest.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
type: Changed
|
||||
pr: 1438
|
||||
---
|
||||
**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 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. -->
|
||||
@@ -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)
|
||||
|
||||
@@ -548,12 +548,16 @@ function stageSkillsForRuntimeAsSkills(
|
||||
*
|
||||
* @param srcAgentsDir source agents directory (e.g. agents/)
|
||||
* @param resolvedProfile profile filter from resolveProfile()
|
||||
* @param converter (content: string) → string pure per-file converter
|
||||
* @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
|
||||
*/
|
||||
function stageAgentsForRuntimeWithConverter(
|
||||
srcAgentsDir: string,
|
||||
resolvedProfile: ResolvedProfile,
|
||||
converter: (content: string) => string,
|
||||
converter: (content: string, isGlobal?: boolean) => string,
|
||||
isGlobal = false,
|
||||
): string {
|
||||
if (!fs.existsSync(srcAgentsDir)) return srcAgentsDir;
|
||||
|
||||
@@ -571,7 +575,7 @@ function stageAgentsForRuntimeWithConverter(
|
||||
}
|
||||
}
|
||||
const content = fs.readFileSync(path.join(srcAgentsDir, entry.name), 'utf8');
|
||||
const converted = converter(content);
|
||||
const converted = converter(content, isGlobal);
|
||||
fs.writeFileSync(path.join(stageDir, entry.name), converted, 'utf8');
|
||||
}
|
||||
} catch (err) {
|
||||
|
||||
@@ -175,6 +175,20 @@ 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 — 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).
|
||||
*
|
||||
* @param destSubpath destination subpath within configDir (e.g. 'agents')
|
||||
@@ -187,14 +201,24 @@ function convertedAgentsKind(
|
||||
prefix: string,
|
||||
converterName: string,
|
||||
configDir: string,
|
||||
scope: 'local' | 'global' = 'global',
|
||||
): ArtifactKind {
|
||||
return {
|
||||
kind: 'agents',
|
||||
destSubpath,
|
||||
prefix,
|
||||
stage: (resolved) => {
|
||||
const converter = conversionExports[converterName] as (content: string) => string;
|
||||
return stageAgentsForRuntimeWithConverter(findAgentsSourceRoot(configDir), resolved, converter);
|
||||
// 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;
|
||||
return stageAgentsForRuntimeWithConverter(
|
||||
findAgentsSourceRoot(configDir),
|
||||
resolved,
|
||||
converter,
|
||||
scope === 'global',
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -411,7 +435,7 @@ function dispatchKindEntry(entry: ArtifactKindDescriptor, runtime: string, confi
|
||||
if (converter == null) {
|
||||
return agentsKind(destSubpath, prefix, configDir);
|
||||
}
|
||||
return convertedAgentsKind(destSubpath, prefix, converter, configDir);
|
||||
return convertedAgentsKind(destSubpath, prefix, converter, configDir, scope);
|
||||
|
||||
case 'skills':
|
||||
if (converter == null) {
|
||||
|
||||
@@ -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 ────────────────────
|
||||
|
||||
Reference in New Issue
Block a user