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:
@@ -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 */ }
|
||||
|
||||
Reference in New Issue
Block a user