* test(#3336): fold the installer & runtime surface issue-* cluster — Wave 4 Folds 10 legacy issue-*.test.cjs regression files (79 test() blocks) into their module's main suite, per H3 (#3315) of the test-hygiene epic (#3053). First of 4 issue-* waves (following the 3 fix-* waves, all merged). - 1 file with no prior target coverage: renamed (git mv) into legacy-cleanup.test.cjs (sole comprehensive suite for that module). - 9 files merged into 6 pre-existing suites: golden-parity-single-source, runtime-artifact-layout-surface, codex-config (4 sources merged jointly in one pass per the issue's own instruction, to catch overlap between the 4 sources themselves, not just against the pre-existing target — zero overlap found, all 20 blocks additive), runtime-config-adapter-registry (1 of 10 source blocks dropped as a proven subset of existing coverage), cline-install, install.test.cjs. Incidental fixes required to keep this wave's own ratchets green: - Fixed a stale ADR doc reference (docs/adr/1235) to a folded-away filename. - scripts/lint-allow-test-rule-refs: pruned 4 stale allowlist entries for renamed/merged-away files, cited 2 previously-uncited allow-test-rule comments that surfaced as "new" only because their file path changed, added 1 fresh allowlist entry for a pre-existing uncited comment that predates this PR, and tightened the exemption-file ceiling 309 -> 305 to match the real post-fold high-water mark. Zero net test-coverage loss. No production code changed. * test(#3336): fix orthogonal-review findings — Wave 4 fold Standards-axis review + Memtrace graph pass found real issues in the just-folded suites, all fixed here: - Standardized the fold-wrapper convention (block-scoped __foldDescribe) across golden-parity-single-source.test.cjs, runtime-artifact-layout- surface.test.cjs, runtime-config-adapter-registry.test.cjs, and cline-install.test.cjs to match the pattern already used by codex-config.test.cjs and install.test.cjs in this same wave (and by earlier folds elsewhere in the epic) — repeats the exact inconsistency Wave 3 (#3335) already fixed once in this epic. - Fixed a stale allowlist entry's alphabetical position (cosmetic, not tool-gated, caught by review anyway). - Fixed two stale test-filename references in PRODUCTION code comments (src/capability-writer.cts, src/runtime-config-adapter-registry.cts) caught by lint-removed-but-needed — a class of stale reference this wave's fold agents didn't check for, since they were scoped to docs/ and gsd-core/references/ only, not src/. First fix attempt wrongly edited the gitignored gsd-core/bin/lib/*.cjs BUILD OUTPUT instead of the tracked .cts source; caught and corrected before commit. - Fixed one remaining stale doc reference in docs/adr/1235 (a prior partial fix in this same wave missed it). No test() count changed in any file. No production code BEHAVIOR changed — comment-only fixes in src/. --------- Co-authored-by: sim <sim@local>
185 lines
7.9 KiB
TypeScript
185 lines
7.9 KiB
TypeScript
'use strict';
|
|
|
|
/**
|
|
* Runtime config adapter registry — dispatch table for install-phase config
|
|
* mutations (issue #60), replacing inline `runtime === '...'` branching in
|
|
* bin/install.js.
|
|
*
|
|
* ADR-857 phase 5g drive 2: The hand-kept REGISTRY const has been retired.
|
|
* Values are now read directly from the capability-registry.cjs descriptor
|
|
* (capabilities/<id>/capability.json runtime block) so a single source of
|
|
* truth drives all surfaces.
|
|
*
|
|
* 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.
|
|
* 'cursor-hooks-json' → early-return after writing .cursor/hooks.json (issue #777).
|
|
* '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 / kimi (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.
|
|
* 'antigravity' → writes BOTH shared settings.json permissions.allow AND a
|
|
* standalone mcp_config.json MCP companion profile (#2096
|
|
* Phase B Upgrades 1+2).
|
|
* null → no dedicated permission writer.
|
|
*/
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
const { runtimes } = require('./capability-registry.cjs') as { runtimes: Record<string, { runtime: Record<string, unknown> | undefined }> };
|
|
|
|
/** Valid sandboxTier enum values — mirrors the gen-capability-registry validator vocabulary. */
|
|
const VALID_SANDBOX_TIERS = new Set(['none', 'codex-agent-sandbox']);
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Types
|
|
// ---------------------------------------------------------------------------
|
|
|
|
type ConfigInstallSurface =
|
|
| 'settings-json'
|
|
| 'codex-toml'
|
|
| 'copilot-instructions'
|
|
| 'cline-rules'
|
|
| 'cursor-hooks-json'
|
|
| 'profile-marker-only'
|
|
// #2103 — Marketplace/VSIX-distributed hosts (e.g. VS Code) with no CLI
|
|
// install surface at all. Never dispatched through install()/finishInstall()
|
|
// (see the ALLOWED_CONFIG_RUNTIMES filter below, which excludes it).
|
|
| 'none';
|
|
|
|
type FinishPermissionWriter = 'opencode' | 'kilo' | 'antigravity' | null;
|
|
|
|
type HooksSurface =
|
|
| 'settings-json'
|
|
| 'codex-hooks-json'
|
|
| 'cursor-hooks-json'
|
|
| 'cline-rules'
|
|
| 'copilot-inline'
|
|
| 'kimi-hooks-toml'
|
|
| 'windsurf-hooks-json'
|
|
| 'none';
|
|
|
|
interface RuntimeConfigIntent {
|
|
runtime: string;
|
|
installSurface: ConfigInstallSurface;
|
|
writesSharedSettings: boolean;
|
|
finishPermissionWriter: FinishPermissionWriter;
|
|
}
|
|
|
|
/**
|
|
* The full install plan for a runtime: config-intent axes PLUS the three
|
|
* hook axes that install() reads from the capability descriptor.
|
|
* ADR-857 phase 5g capstone — single seam for all install-level descriptor reads.
|
|
*/
|
|
interface InstallPlan extends RuntimeConfigIntent {
|
|
/** Hook event dialect: 'claude' | 'gemini' | undefined */
|
|
hookEvents: string | undefined;
|
|
/** Extended hook event names registered beyond the core tool events (may be empty). */
|
|
extendedHookEvents: string[];
|
|
/** Which surface owns the hook registration for this runtime. */
|
|
hooksSurface: HooksSurface;
|
|
/** Runtime sandbox tier ('none' | 'codex-agent-sandbox'); gates per-agent sandbox_mode emission. */
|
|
sandboxTier: string;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Exports
|
|
// ---------------------------------------------------------------------------
|
|
|
|
type RuntimeDescriptorMap = Record<string, { runtime: Record<string, unknown> | undefined }>;
|
|
|
|
/**
|
|
* The complete set of 16 supported runtimes for config-adapter dispatch.
|
|
*
|
|
* Excludes runtimes whose installSurface is 'none' (#2103 — e.g. VS Code): a
|
|
* 'none' installSurface means the runtime has NO CLI install surface at all
|
|
* (Marketplace/VSIX-distributed, never dispatched through
|
|
* install()/finishInstall()), so it is not a "config-adapter runtime" by
|
|
* definition. This keeps this set in lockstep with bin/install.js's
|
|
* `allRuntimes` (see the folded:issue-57-runtime-install-no-drift describe
|
|
* block in tests/runtime-config-adapter-registry.test.cjs) without needing a
|
|
* separate hand-kept exclusion list.
|
|
*/
|
|
const ALLOWED_CONFIG_RUNTIMES: ReadonlySet<string> = new Set(
|
|
Object.entries(runtimes)
|
|
.filter(([, cap]) => cap && cap.runtime && typeof cap.runtime['installSurface'] === 'string' && cap.runtime['installSurface'] !== 'none')
|
|
.map(([id]) => id),
|
|
);
|
|
|
|
/** All valid installSurface values. */
|
|
const INSTALL_SURFACES: ReadonlyArray<ConfigInstallSurface> = Object.freeze([
|
|
'settings-json',
|
|
'codex-toml',
|
|
'copilot-instructions',
|
|
'cline-rules',
|
|
'cursor-hooks-json',
|
|
'profile-marker-only',
|
|
'none',
|
|
]);
|
|
|
|
/**
|
|
* 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 {
|
|
const entry = runtimes[runtime]?.runtime;
|
|
if (!entry) throw new TypeError(`Unknown runtime for config adapter: ${runtime}`);
|
|
const permissionWriter = entry['permissionWriter'];
|
|
return {
|
|
runtime,
|
|
installSurface: entry['installSurface'] as ConfigInstallSurface,
|
|
writesSharedSettings: entry['writesSharedSettings'] as boolean,
|
|
finishPermissionWriter: permissionWriter == null ? null : permissionWriter as FinishPermissionWriter,
|
|
};
|
|
}
|
|
|
|
function resolveInstallPlanFromRuntimes(runtimeDescriptors: RuntimeDescriptorMap, runtime: string): InstallPlan {
|
|
const desc = runtimeDescriptors[runtime]?.runtime;
|
|
if (!desc) throw new TypeError(`Unknown runtime for install plan: ${runtime}`);
|
|
if (desc['hooksSurface'] == null) {
|
|
throw new TypeError(`runtime.hooksSurface is required for install plan: ${runtime}`);
|
|
}
|
|
const sandboxTier = desc['sandboxTier'];
|
|
if (typeof sandboxTier !== 'string' || !VALID_SANDBOX_TIERS.has(sandboxTier)) {
|
|
throw new TypeError(`Runtime '${runtime}' has a missing or invalid sandboxTier descriptor axis: ${JSON.stringify(sandboxTier)}`);
|
|
}
|
|
const permissionWriter = desc['permissionWriter'];
|
|
return {
|
|
runtime,
|
|
installSurface: desc['installSurface'] as ConfigInstallSurface,
|
|
writesSharedSettings: desc['writesSharedSettings'] as boolean,
|
|
finishPermissionWriter: permissionWriter == null ? null : permissionWriter as FinishPermissionWriter,
|
|
hookEvents: desc['hookEvents'] as string | undefined,
|
|
extendedHookEvents: Array.isArray(desc['extendedHookEvents']) ? [...desc['extendedHookEvents'] as string[]] : [],
|
|
hooksSurface: desc['hooksSurface'] as HooksSurface,
|
|
sandboxTier,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Resolve the complete install plan for a given runtime.
|
|
*
|
|
* Composes the config-intent axes from resolveRuntimeConfigIntent PLUS the
|
|
* three hook axes (hookEvents / extendedHookEvents / hooksSurface) that
|
|
* install() previously read scattered from the capability registry.
|
|
*
|
|
* ADR-857 phase 5g capstone — single typed seam for all install-level
|
|
* descriptor reads. Returns a fresh object each call.
|
|
*
|
|
* @throws {TypeError} if runtime is not a known supported runtime.
|
|
*/
|
|
function resolveInstallPlan(runtime: string): InstallPlan {
|
|
return resolveInstallPlanFromRuntimes(runtimes, runtime);
|
|
}
|
|
|
|
export = { resolveRuntimeConfigIntent, resolveInstallPlan, resolveInstallPlanFromRuntimes, ALLOWED_CONFIG_RUNTIMES, INSTALL_SURFACES };
|