Files
msd-core/tests/getdirname-registry-derivation.test.cjs
Tom Boucher 8f2ebbe9bf feat(#1928): remove sunset Gemini CLI runtime, redirect to Antigravity (#1996)
* feat(#1928): remove sunset gemini cli runtime, redirect to antigravity

Google sunset Gemini CLI on 2026-06-18; Antigravity CLI is its official successor (already a first-class GSD runtime). Remove the gemini runtime from the enum (16->15), aliases, labels, config-home fragment, install path, converters (convertClaudeToGemini{Markdown,Toml,Agent}, convertSlashCommandsToGeminiMentions), capability descriptor, gemini-extension.json, RULESET.GEMINI.*, and the interactive menu (renumbered, no gap).

--gemini now prints an explicit deprecation notice citing the 2026-06-18 sunset and redirects to --antigravity (no silent alias, per the issue's Hyrum's-Law rejection). Antigravity is preserved throughout: its GEMINI.md contextFileName, .gemini/antigravity config home, the shared convertGeminiToolName/claudeToGeminiTools tool vocabulary, and the 'gemini' hookEvents dialect it declares. GEMINI.md retargeted as Antigravity's context file.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(#1928): backfill changeset PR number (#1996)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(#1928): drop Gemini CLI from issue templates (review nit)

Removes the sunset Gemini CLI runtime from the two GitHub issue-template
runtime lists that the removal PR missed, per @davesienkowski's review nit:
- feature_request.yml: 'Applicable runtimes' checkbox (a user could otherwise
  request a feature for a runtime GSD no longer supports)
- bug_report.yml: 'Runtime' dropdown + the stale ~/.gemini/settings.json
  retrieval-help line

Leaves the post-removal templates fully consistent with the Antigravity redirect.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 13:32:51 -04:00

93 lines
3.9 KiB
JavaScript

'use strict';
/**
* Drift-guard: getDirName must be derived from the capability registry.
* Verifies:
* 1. For every known runtime id, getDirName(id) equals the hardcoded golden
* expected map — a pinned oracle that catches BOTH formula bugs AND
* unintended registry drift (adding/removing a runtime or changing its
* localConfigDir forces a deliberate golden-map update here).
* 2. getDirName('unknown') and getDirName('') fall back to '.claude'.
* 3. Every registry runtime entry has a non-empty dot-dir localConfigDir string —
* cross-check from a different angle than the production derivation formula.
*
* ADR-1239 Phase B (#1679).
* Behavioral tests only: assert on returned values, no source-grep.
*/
const { test } = require('node:test');
const assert = require('node:assert/strict');
const runtimeNamePolicy = require('../gsd-core/bin/lib/runtime-name-policy.cjs');
const registry = require('../gsd-core/bin/lib/capability-registry.cjs');
const { getDirName } = runtimeNamePolicy;
// Golden oracle: hardcoded expected map of all 15 runtime ids to their local config dir.
// A pinned expected value in a TEST is correct — the test IS the oracle (non-circular).
// Only PRODUCTION code should derive dynamically from the registry.
// If this map diverges from getDirName output, either the formula is wrong
// OR the registry changed — both require a deliberate golden-map update here.
const GOLDEN_DIR_MAP = {
claude: '.claude',
copilot: '.github',
opencode: '.opencode',
kilo: '.kilo',
codex: '.codex',
antigravity: '.agents',
cursor: '.cursor',
windsurf: '.windsurf',
augment: '.augment',
trae: '.trae',
qwen: '.qwen',
hermes: '.hermes',
kimi: '.kimi-code',
codebuddy: '.codebuddy',
cline: '.cline',
};
test('getDirName: golden map matches for all 15 known runtime ids', () => {
for (const [id, expected] of Object.entries(GOLDEN_DIR_MAP)) {
const actual = getDirName(id);
assert.strictEqual(
actual,
expected,
`getDirName('${id}') diverged from golden.\n` +
` actual: ${JSON.stringify(actual)}\n` +
` expected: ${JSON.stringify(expected)}`,
);
}
});
test('drift guard: registry runtime id set EXACTLY equals the golden map (adding/removing a runtime forces a golden update)', () => {
// Without this, a newly-added runtime would pass (its value never checked) and
// removing `claude` could pass via the .claude fallback. Pin the set both ways.
const registryIds = Object.keys(registry.runtimes).sort();
const goldenIds = Object.keys(GOLDEN_DIR_MAP).sort();
assert.deepEqual(registryIds, goldenIds,
'registry.runtimes id set must exactly match GOLDEN_DIR_MAP — update the golden map when adding/removing a runtime');
});
test('getDirName fallback: unknown runtime returns ".claude"', () => {
assert.strictEqual(getDirName('unknown'), '.claude',
'getDirName("unknown") must return ".claude" (default fallback)');
});
test('getDirName fallback: empty string returns ".claude"', () => {
assert.strictEqual(getDirName(''), '.claude',
'getDirName("") must return ".claude" (empty-input fallback)');
});
test('registry cross-check: every runtimes[id].runtime.localConfigDir is a non-empty dot-dir string', () => {
for (const [id, entry] of Object.entries(registry.runtimes)) {
if (!entry || typeof entry !== 'object') continue;
const runtimeBlock = entry.runtime;
if (!runtimeBlock || typeof runtimeBlock !== 'object') continue;
const dir = runtimeBlock.localConfigDir;
assert.strictEqual(typeof dir, 'string',
`registry.runtimes['${id}'].runtime.localConfigDir must be a string (got: ${typeof dir})`);
assert.ok(dir.length > 0,
`registry.runtimes['${id}'].runtime.localConfigDir must be non-empty`);
assert.ok(dir.startsWith('.'),
`registry.runtimes['${id}'].runtime.localConfigDir must start with '.' (got: ${JSON.stringify(dir)})`);
}
});