@@ -16,6 +16,7 @@ const { output, error, loadConfig } = require('./core.cjs');
|
||||
const { platformReadSync: safeReadFile, platformWriteSync, platformEnsureDir } = require('./shell-command-projection.cjs');
|
||||
const { getGlobalSkillDir } = require('./runtime-homes.cjs');
|
||||
const { formatGsdSlash, resolveRuntime } = require('./runtime-slash.cjs');
|
||||
const { resolveRuntimeNameFromCandidates } = require('./runtime-name-policy.cjs');
|
||||
|
||||
// ─── Constants ────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -811,9 +812,16 @@ function cmdGenerateDevPreferences(cwd, options, raw) {
|
||||
let effectiveRuntime = 'claude';
|
||||
try {
|
||||
const config = loadConfig(cwd);
|
||||
effectiveRuntime = process.env.GSD_RUNTIME || config.runtime || 'claude';
|
||||
effectiveRuntime = resolveRuntimeNameFromCandidates(
|
||||
process.env.GSD_RUNTIME,
|
||||
config.runtime,
|
||||
'claude'
|
||||
) || 'claude';
|
||||
} catch {
|
||||
effectiveRuntime = process.env.GSD_RUNTIME || 'claude';
|
||||
effectiveRuntime = resolveRuntimeNameFromCandidates(
|
||||
process.env.GSD_RUNTIME,
|
||||
'claude'
|
||||
) || 'claude';
|
||||
}
|
||||
const skillDir = getGlobalSkillDir(effectiveRuntime, 'gsd-dev-preferences');
|
||||
if (!skillDir) {
|
||||
@@ -1003,7 +1011,10 @@ function cmdGenerateClaudeMd(cwd, options, raw) {
|
||||
// #3163: When runtime is codex, override the output target to AGENTS.md
|
||||
// regardless of claude_md_path, so Codex projects never write to CLAUDE.md.
|
||||
// GSD_RUNTIME env var takes precedence over config.runtime, mirroring detectRuntime().
|
||||
const effectiveRuntime = process.env.GSD_RUNTIME || config.runtime || null;
|
||||
const effectiveRuntime = resolveRuntimeNameFromCandidates(
|
||||
process.env.GSD_RUNTIME,
|
||||
config.runtime
|
||||
);
|
||||
if (!options.output && effectiveRuntime === 'codex') {
|
||||
configClaudeMdPath = './AGENTS.md';
|
||||
}
|
||||
|
||||
@@ -57,6 +57,27 @@ function canonicalizeRuntimeName(value) {
|
||||
return aliasToCanonical.get(normalizeRuntimeToken(value)) || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve runtime from a precedence list of candidate values.
|
||||
*
|
||||
* - First non-empty string candidate wins.
|
||||
* - Known aliases are canonicalized (codex-cli -> codex).
|
||||
* - Unknown values are normalized and returned (future-runtime tolerance).
|
||||
*
|
||||
* @param {...string} candidates
|
||||
* @returns {string|null}
|
||||
*/
|
||||
function resolveRuntimeNameFromCandidates(...candidates) {
|
||||
for (const candidate of candidates) {
|
||||
if (typeof candidate !== 'string') continue;
|
||||
const normalized = normalizeRuntimeToken(candidate);
|
||||
if (!normalized) continue;
|
||||
return canonicalizeRuntimeName(normalized) || normalized;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
canonicalizeRuntimeName,
|
||||
resolveRuntimeNameFromCandidates,
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
const { canonicalizeRuntimeName } = require('./runtime-name-policy.cjs');
|
||||
const { canonicalizeRuntimeName, resolveRuntimeNameFromCandidates } = require('./runtime-name-policy.cjs');
|
||||
|
||||
/**
|
||||
* runtime-slash.cjs — single source of truth for emitting GSD slash-command
|
||||
@@ -69,10 +69,8 @@ function formatGsdSlash(commandName, runtime) {
|
||||
* @returns {string}
|
||||
*/
|
||||
function resolveRuntime(projectDir) {
|
||||
if (process.env.GSD_RUNTIME) {
|
||||
const rawRuntime = String(process.env.GSD_RUNTIME).toLowerCase();
|
||||
return canonicalizeRuntimeName(rawRuntime) || rawRuntime;
|
||||
}
|
||||
const envRuntime = resolveRuntimeNameFromCandidates(process.env.GSD_RUNTIME);
|
||||
if (envRuntime) return envRuntime;
|
||||
if (projectDir) {
|
||||
try {
|
||||
// Read config.json directly (not via loadConfig). loadConfig has a side
|
||||
@@ -87,8 +85,8 @@ function resolveRuntime(projectDir) {
|
||||
const raw = fs.readFileSync(configPath, 'utf-8');
|
||||
const parsed = JSON.parse(raw);
|
||||
if (parsed && typeof parsed === 'object' && parsed.runtime) {
|
||||
const rawRuntime = String(parsed.runtime).toLowerCase();
|
||||
return canonicalizeRuntimeName(rawRuntime) || rawRuntime;
|
||||
const configRuntime = resolveRuntimeNameFromCandidates(parsed.runtime);
|
||||
if (configRuntime) return configRuntime;
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
|
||||
@@ -174,6 +174,16 @@ describe('generate-claude-md command', () => {
|
||||
assert.ok(content.includes('.codex/skills/'));
|
||||
assert.ok(!content.includes('get-shit-done/skills'));
|
||||
});
|
||||
|
||||
test('codex runtime aliases default output to AGENTS.md', () => {
|
||||
const result = runGsdTools(
|
||||
['generate-claude-md', '--auto', '--raw'],
|
||||
tmpDir,
|
||||
{ GSD_RUNTIME: 'codex-cli' }
|
||||
);
|
||||
assert.ok(result.success, `Failed: ${result.error}`);
|
||||
assert.ok(fs.existsSync(path.join(tmpDir, 'AGENTS.md')), 'AGENTS.md should be generated for codex aliases');
|
||||
});
|
||||
});
|
||||
|
||||
// ─── generate-dev-preferences ─────────────────────────────────────────────────
|
||||
@@ -238,6 +248,27 @@ describe('generate-dev-preferences command', () => {
|
||||
assert.ok(fs.existsSync(out.command_path), 'runtime-aware output should be written');
|
||||
});
|
||||
|
||||
test('canonicalizes codex runtime aliases for skills output path', () => {
|
||||
const analysis = {
|
||||
profile_version: '1.0',
|
||||
dimensions: {
|
||||
communication_style: { rating: 'terse-direct', confidence: 'HIGH' },
|
||||
},
|
||||
};
|
||||
const analysisPath = path.join(tmpDir, 'analysis.json');
|
||||
const codexHome = path.join(tmpDir, 'codex-home');
|
||||
fs.writeFileSync(analysisPath, JSON.stringify(analysis));
|
||||
|
||||
const result = runGsdTools(
|
||||
['generate-dev-preferences', '--analysis', analysisPath, '--raw'],
|
||||
tmpDir,
|
||||
{ CODEX_HOME: codexHome, GSD_RUNTIME: 'codex-app' }
|
||||
);
|
||||
assert.ok(result.success, `Failed: ${result.error}`);
|
||||
const out = JSON.parse(result.output);
|
||||
assert.strictEqual(out.command_path, path.join(codexHome, 'skills', 'gsd-dev-preferences', 'SKILL.md'));
|
||||
});
|
||||
|
||||
test('errors for cline unless --output is supplied', () => {
|
||||
const analysis = {
|
||||
profile_version: '1.0',
|
||||
|
||||
Reference in New Issue
Block a user