* refactor(#60): make runtime config adapter registry explicit Replace scattered inline `runtime === '...'` config-mutation branching in bin/install.js with an explicit, typed adapter registry. The new src/runtime-config-adapter-registry.cts maps each of the 15 supported runtimes to a config intent { installSurface, writesSharedSettings, finishPermissionWriter }; install()/finishInstall() dispatch by resolved intent instead of runtime-name checks (cursor/windsurf/trae collapse to one profile-marker-only branch). Behavior-preserving: the same config files are written for the same runtimes (opencode still writes both settings.json and its permissions; kilo writes only its permissions; codex minimal-mode and opencode GSD_TEST_MODE guards unchanged). Unknown runtimes fail loudly via TypeError, with an Object.hasOwn barrier so prototype-chain keys (__proto__/constructor) also throw rather than returning a bogus intent. Leads the installer-refactor chain (#58 -> #60 -> #56), building on ADR-58. Closes #60 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * chore(#60): add changeset for runtime config adapter registry Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(#60): register Runtime Config Adapter Registry in CONTEXT.md glossary Per docs/contributor-standards.md, every new Module/seam must get a `### <Name>` entry under the domain glossary. Adds the entry for the runtime-config-adapter-registry seam introduced in this PR (interface, policy boundary, source file, ADR-58 / #60 cross-references). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
110 lines
5.5 KiB
TypeScript
110 lines
5.5 KiB
TypeScript
'use strict';
|
|
|
|
/**
|
|
* Runtime config adapter registry — explicit dispatch table for install-phase
|
|
* config mutations (issue #60), replacing inline `runtime === '...'` branching
|
|
* in bin/install.js.
|
|
*
|
|
* Design notes:
|
|
* - `installSurface` selects which config handler install() runs:
|
|
* 'settings-json' → fall through to the shared settings.json accumulation.
|
|
* 'codex-toml' → early-return after writing codex.toml.
|
|
* 'copilot-instructions' → early-return after writing .github/copilot-instructions.md.
|
|
* 'cline-rules' → early-return after writing .clinerules.
|
|
* 'profile-marker-only' → early-return after writing only the profile marker.
|
|
* - `writesSharedSettings` is the finishInstall writeSettings gate:
|
|
* false for codex / copilot / kilo / cursor / windsurf / trae / cline (legacy exclusion list).
|
|
* true for all other runtimes.
|
|
* - `finishPermissionWriter` names the finishInstall-phase dedicated config writer:
|
|
* 'opencode' → writes BOTH shared settings AND its own permissions file.
|
|
* 'kilo' → writes only its own permissions file.
|
|
* null → no dedicated permission writer.
|
|
*/
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Types
|
|
// ---------------------------------------------------------------------------
|
|
|
|
type ConfigInstallSurface =
|
|
| 'settings-json'
|
|
| 'codex-toml'
|
|
| 'copilot-instructions'
|
|
| 'cline-rules'
|
|
| 'profile-marker-only';
|
|
|
|
type FinishPermissionWriter = 'opencode' | 'kilo' | null;
|
|
|
|
interface RuntimeConfigIntent {
|
|
runtime: string;
|
|
installSurface: ConfigInstallSurface;
|
|
writesSharedSettings: boolean;
|
|
finishPermissionWriter: FinishPermissionWriter;
|
|
}
|
|
|
|
interface RegistryEntry {
|
|
installSurface: ConfigInstallSurface;
|
|
writesSharedSettings: boolean;
|
|
finishPermissionWriter: FinishPermissionWriter;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Registry
|
|
// ---------------------------------------------------------------------------
|
|
|
|
const REGISTRY: Record<string, Readonly<RegistryEntry>> = Object.freeze({
|
|
claude: Object.freeze({ installSurface: 'settings-json', writesSharedSettings: true, finishPermissionWriter: null } as const),
|
|
gemini: Object.freeze({ installSurface: 'settings-json', writesSharedSettings: true, finishPermissionWriter: null } as const),
|
|
antigravity: Object.freeze({ installSurface: 'settings-json', writesSharedSettings: true, finishPermissionWriter: null } as const),
|
|
augment: Object.freeze({ installSurface: 'settings-json', writesSharedSettings: true, finishPermissionWriter: null } as const),
|
|
qwen: Object.freeze({ installSurface: 'settings-json', writesSharedSettings: true, finishPermissionWriter: null } as const),
|
|
hermes: Object.freeze({ installSurface: 'settings-json', writesSharedSettings: true, finishPermissionWriter: null } as const),
|
|
codebuddy: Object.freeze({ installSurface: 'settings-json', writesSharedSettings: true, finishPermissionWriter: null } as const),
|
|
opencode: Object.freeze({ installSurface: 'settings-json', writesSharedSettings: true, finishPermissionWriter: 'opencode' } as const),
|
|
kilo: Object.freeze({ installSurface: 'settings-json', writesSharedSettings: false, finishPermissionWriter: 'kilo' } as const),
|
|
codex: Object.freeze({ installSurface: 'codex-toml', writesSharedSettings: false, finishPermissionWriter: null } as const),
|
|
copilot: Object.freeze({ installSurface: 'copilot-instructions', writesSharedSettings: false, finishPermissionWriter: null } as const),
|
|
cline: Object.freeze({ installSurface: 'cline-rules', writesSharedSettings: false, finishPermissionWriter: null } as const),
|
|
cursor: Object.freeze({ installSurface: 'profile-marker-only', writesSharedSettings: false, finishPermissionWriter: null } as const),
|
|
windsurf: Object.freeze({ installSurface: 'profile-marker-only', writesSharedSettings: false, finishPermissionWriter: null } as const),
|
|
trae: Object.freeze({ installSurface: 'profile-marker-only', writesSharedSettings: false, finishPermissionWriter: null } as const),
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Exports
|
|
// ---------------------------------------------------------------------------
|
|
|
|
/** The complete set of 15 supported runtimes for config-adapter dispatch. */
|
|
const ALLOWED_CONFIG_RUNTIMES: ReadonlySet<string> = new Set(Object.keys(REGISTRY));
|
|
|
|
/** All valid installSurface values. */
|
|
const INSTALL_SURFACES: ReadonlyArray<ConfigInstallSurface> = Object.freeze([
|
|
'settings-json',
|
|
'codex-toml',
|
|
'copilot-instructions',
|
|
'cline-rules',
|
|
'profile-marker-only',
|
|
]);
|
|
|
|
/**
|
|
* Resolve the config adapter intent for a given runtime.
|
|
*
|
|
* Returns a fresh object each call so callers cannot poison the registry by
|
|
* mutating the returned value.
|
|
*
|
|
* @throws {TypeError} if runtime is not a known supported runtime.
|
|
*/
|
|
function resolveRuntimeConfigIntent(runtime: string): RuntimeConfigIntent {
|
|
if (!Object.hasOwn(REGISTRY, runtime)) {
|
|
throw new TypeError(`Unknown runtime for config adapter: ${runtime}`);
|
|
}
|
|
const entry = REGISTRY[runtime];
|
|
return {
|
|
runtime,
|
|
installSurface: entry.installSurface,
|
|
writesSharedSettings: entry.writesSharedSettings,
|
|
finishPermissionWriter: entry.finishPermissionWriter,
|
|
};
|
|
}
|
|
|
|
export = { resolveRuntimeConfigIntent, ALLOWED_CONFIG_RUNTIMES, INSTALL_SURFACES };
|