enh(#142): centralize runtime alias canonicalization seam (#143)

* fix(#142): canonicalize runtime aliases across cjs and sdk

* fix(#142): satisfy hand-sync and inventory parity gates

* test(#1974): remove record-session lock contention in context monitor spec

* test(config): retry transient config-ensure-section failures

* docs(context): capture PR #143 CI reliability findings

* fix(#142): bump CLI Modules inventory headline to 76 (runtime-name-policy + runtime-slash)

docs/INVENTORY.md had the two new .cjs rows listed but the headline
count stayed at 75; fs count is 76. inventory-counts.test.cjs caught
the drift.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Tom Boucher
2026-05-23 18:39:02 -04:00
committed by GitHub
parent 33ffc647e2
commit 63396dbb16
17 changed files with 420 additions and 47 deletions

View File

@@ -403,10 +403,59 @@ describe('detectRuntime', () => {
expect(detectRuntime()).toBe('codex');
});
it('normalizes Codex env aliases to canonical codex runtime', () => {
process.env.GSD_RUNTIME = 'codex-app';
expect(detectRuntime()).toBe('codex');
process.env.GSD_RUNTIME = 'codex_cli';
expect(detectRuntime()).toBe('codex');
});
it('normalizes common runtime env aliases to canonical runtime IDs', () => {
const cases: Array<[string, Runtime]> = [
['claude-code', 'claude'],
['gemini-cli', 'gemini'],
['opencode-cli', 'opencode'],
['qwen-code', 'qwen'],
['hermes-agent', 'hermes'],
['github-copilot', 'copilot'],
['cursor-nightly', 'cursor'],
['windsurf-next', 'windsurf'],
['augment-code', 'augment'],
['codebuddy-cli', 'codebuddy'],
['cline-cli', 'cline'],
];
for (const [alias, runtime] of cases) {
process.env.GSD_RUNTIME = alias;
expect(detectRuntime()).toBe(runtime);
}
});
it('falls back to config.runtime when GSD_RUNTIME unset', () => {
expect(detectRuntime({ runtime: 'gemini' })).toBe('gemini');
});
it('normalizes Codex config aliases to canonical codex runtime', () => {
expect(detectRuntime({ runtime: 'codex-cli' })).toBe('codex');
expect(detectRuntime({ runtime: 'codex app' })).toBe('codex');
});
it('normalizes common runtime config aliases to canonical runtime IDs', () => {
const cases: Array<[string, Runtime]> = [
['claude-cli', 'claude'],
['gemini-code', 'gemini'],
['open code', 'opencode'],
['hermes_cli', 'hermes'],
['antigravity-agent', 'antigravity'],
['trae-cli', 'trae'],
['kilo-cli', 'kilo'],
];
for (const [alias, runtime] of cases) {
expect(detectRuntime({ runtime: alias })).toBe(runtime);
}
});
it('GSD_RUNTIME wins over config.runtime', () => {
process.env.GSD_RUNTIME = 'codex';
expect(detectRuntime({ runtime: 'gemini' })).toBe('codex');

View File

@@ -24,6 +24,7 @@ import { homedir } from 'node:os';
import { GSDError, ErrorClassification } from '../errors.js';
export { SUPPORTED_RUNTIMES, type Runtime } from '../model-catalog.js';
import { SUPPORTED_RUNTIMES, type Runtime } from '../model-catalog.js';
import { canonicalizeRuntimeName } from '../runtime-name-policy.js';
import { workspacePlanningPaths, resolveWorkspaceContext, type PlanningPaths } from './workspace.js';
export { stateExtractField } from './state-document.js';
import { relPlanningPath, validateWorkstreamName } from '../workstream-utils.js';
@@ -93,14 +94,12 @@ export function getRuntimeConfigDir(runtime: Runtime): string {
* stale env values don't hard-block workflows.
*/
export function detectRuntime(config?: { runtime?: unknown }): Runtime {
const envValue = process.env.GSD_RUNTIME;
if (envValue && (SUPPORTED_RUNTIMES as readonly string[]).includes(envValue)) {
return envValue as Runtime;
}
const configValue = config?.runtime;
if (typeof configValue === 'string' && (SUPPORTED_RUNTIMES as readonly string[]).includes(configValue)) {
return configValue as Runtime;
}
const envRuntime = canonicalizeRuntimeName(process.env.GSD_RUNTIME);
if (envRuntime) return envRuntime;
const configRuntime = canonicalizeRuntimeName(config?.runtime);
if (configRuntime) return configRuntime;
return 'claude';
}