* 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>
99 lines
5.2 KiB
JavaScript
99 lines
5.2 KiB
JavaScript
'use strict';
|
||
/**
|
||
* Tests for the imperative embedding adapter (ADR-1239 Phase C-1, AC2 / #1680).
|
||
*
|
||
* Pins:
|
||
* 1. KIND — `kind: 'imperative'`, satisfies the same HostIntegrationInterface
|
||
* as the declarative adapter (both bind one engine).
|
||
* 2. REGISTRY COMPOSITION — the adapter composes loadRegistry({includeInstalled:
|
||
* true}) and exposes the result as `.registry` (first-party ∪ installed, so
|
||
* an in-process host gets identical trust semantics to the CLI).
|
||
* 3. DELEGATION — install/uninstall delegate in-process to install-engine
|
||
* (byte-identity link, same as the declarative adapter).
|
||
* 4. FAIL-CLOSED CONSTRUCTION — missing/invalid runtime throws.
|
||
*
|
||
* Behavioral tests only; delegation + loadRegistry verified via module-ref
|
||
* monkeypatch (Node module cache shares the one module object).
|
||
*/
|
||
|
||
const { test } = require('node:test');
|
||
const assert = require('node:assert/strict');
|
||
const { createImperativeAdapter } = require('../gsd-core/bin/lib/adapter-imperative.cjs');
|
||
const installEngine = require('../gsd-core/bin/lib/install-engine.cjs');
|
||
const capabilityLoader = require('../gsd-core/bin/lib/capability-loader.cjs');
|
||
const registry = require('../gsd-core/bin/lib/capability-registry.cjs');
|
||
|
||
const RUNTIMES = Object.keys(registry.runtimes);
|
||
|
||
test('imperative adapter: kind === "imperative" + runtime echoed + registry present, for all 15 runtimes', () => {
|
||
for (const r of RUNTIMES) {
|
||
const adapter = createImperativeAdapter({ runtime: r });
|
||
assert.strictEqual(adapter.kind, 'imperative', `${r}: kind must be 'imperative'`);
|
||
assert.strictEqual(adapter.runtime, r, `${r}: adapter must echo the runtime id`);
|
||
assert.ok(adapter.registry && typeof adapter.registry === 'object', `${r}: registry must be present (composed loadRegistry result)`);
|
||
assert.strictEqual(typeof adapter.install, 'function', `${r}: install must be a function`);
|
||
assert.strictEqual(typeof adapter.uninstall, 'function', `${r}: uninstall must be a function`);
|
||
}
|
||
});
|
||
|
||
test('imperative adapter: loadRegistry composed with includeInstalled:true (host gets CLI-equivalent trust semantics)', () => {
|
||
// Restore-able spy on capabilityLoader.loadRegistry (module-ref, shared via Node cache).
|
||
const original = capabilityLoader.loadRegistry;
|
||
const calls = [];
|
||
capabilityLoader.loadRegistry = function (opts) {
|
||
calls.push(opts);
|
||
// Return a minimal stand-in registry so the factory does not crash.
|
||
return { _spy: true, runtimes: registry.runtimes };
|
||
};
|
||
try {
|
||
const adapter = createImperativeAdapter({ runtime: 'opencode' });
|
||
assert.ok(calls.length >= 1, 'loadRegistry must be invoked once during construction');
|
||
assert.strictEqual(calls[0].includeInstalled, true, 'loadRegistry must be called with includeInstalled:true (compose first-party ∪ installed)');
|
||
assert.strictEqual(adapter.registry._spy, true, 'adapter.registry must expose the composed loadRegistry result');
|
||
} finally {
|
||
capabilityLoader.loadRegistry = original;
|
||
}
|
||
});
|
||
|
||
test('imperative adapter: loadOptions forwarded to loadRegistry (cwd/gsdHome/hostVersion pass-through)', () => {
|
||
const original = capabilityLoader.loadRegistry;
|
||
let captured = null;
|
||
capabilityLoader.loadRegistry = function (opts) { captured = opts; return { runtimes: registry.runtimes }; };
|
||
try {
|
||
createImperativeAdapter({ runtime: 'codex' }, { loadOptions: { cwd: '/tmp/proj', hostVersion: '1.7.0' } });
|
||
assert.strictEqual(captured.includeInstalled, true, 'includeInstalled default preserved');
|
||
assert.strictEqual(captured.cwd, '/tmp/proj', 'loadOptions.cwd forwarded');
|
||
assert.strictEqual(captured.hostVersion, '1.7.0', 'loadOptions.hostVersion forwarded');
|
||
} finally {
|
||
capabilityLoader.loadRegistry = original;
|
||
}
|
||
});
|
||
|
||
test('imperative adapter.install/uninstall delegate in-process to install-engine with exact args', () => {
|
||
const origInstall = installEngine.installRuntimeArtifacts;
|
||
const origUninstall = installEngine.uninstallRuntimeArtifacts;
|
||
try {
|
||
for (const r of RUNTIMES) {
|
||
const adapter = createImperativeAdapter({ runtime: r });
|
||
let installArgs = null;
|
||
let uninstallArgs = null;
|
||
installEngine.installRuntimeArtifacts = function (...a) { installArgs = a; return undefined; };
|
||
installEngine.uninstallRuntimeArtifacts = function (...a) { uninstallArgs = a; return undefined; };
|
||
adapter.install({ configDir: '/tmp/imp/' + r, scope: 'global', resolvedProfile: { p: 1 } });
|
||
adapter.uninstall({ configDir: '/tmp/imp/' + r, scope: 'local' });
|
||
assert.deepStrictEqual(installArgs, [r, '/tmp/imp/' + r, 'global', { p: 1 }, undefined], `${r}: install delegation args`);
|
||
assert.deepStrictEqual(uninstallArgs, [r, '/tmp/imp/' + r, 'local'], `${r}: uninstall delegation args`);
|
||
}
|
||
} finally {
|
||
installEngine.installRuntimeArtifacts = origInstall;
|
||
installEngine.uninstallRuntimeArtifacts = origUninstall;
|
||
}
|
||
});
|
||
|
||
test('createImperativeAdapter: throws on missing/invalid runtime (fail-closed construction)', () => {
|
||
for (const bad of ['', undefined, null]) {
|
||
assert.throws(() => createImperativeAdapter({ runtime: bad }), TypeError, `runtime=${JSON.stringify(bad)} must throw`);
|
||
}
|
||
assert.throws(() => createImperativeAdapter({}), TypeError, 'missing runtime must throw');
|
||
});
|