* 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>
93 lines
3.9 KiB
JavaScript
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)})`);
|
|
}
|
|
});
|