feat(#2096): migrate Antigravity onto EoS declarative adapter + permission-writer + MCP companion (ADR-1239)

Fold all antigravity literal branches into descriptor-driven reads:
getConfigDirFromHome (→ configHome.kind 'dot-home-nested'), projectLocalHookPrefix
(→ hostBehaviors.hookPathStyle 'raw'), applyAgentPathRewrites (→ noPathRewrite),
getProjectInstructionFile (→ projectInstructionFile 'GEMINI.md'); removed the dead
inline convertClaudeAgentToAntigravityAgent branch + dead isAntigravity
destructures (antigravity is already on the descriptor-agents path). subagentToolkit
flipped undocumented→full (Context7: antigravity.google/docs/cli/features);
namedDispatch/nested/maxDepth/backgroundDispatch stay undocumented. Byte-identical
golden parity for all 16 runtimes.

UPGRADE 1 (permission-writer): permissionWriter 'antigravity' + configureAntigravityPermissions
merges a scoped permissions.allow block (GSD's own tree + hooks) into Antigravity's
settings.json — non-destructive, idempotent, symmetric uninstall. Added to
VALID_PERMISSION_WRITERS + the FinishPermissionWriter union.
UPGRADE 2 (MCP companion): configureAntigravityMcpConfig writes mcp_config.json
registering the gsd-core companion MCP server (Gemini-successor mcpServers schema,
best-effort — raw schema unpublished). Both writers dispatch from finishInstall.
settings.json is golden-excluded (HOOK_CONFIG_FILES); mcp_config.json (portable,
no absolute paths) is golden-tracked → only antigravity.json changes.

Tests: declarative-reference-antigravity extended (source-grep guard across 4
modules, fail-closed for the 4 undocumented sub-axes, validator acceptance) +
antigravity-upgrades (permission-writer + mcp_config live-install, idempotency,
user-preservation). Matrix + ADR-1016 + capability-manifest + CONTEXT.md +
connect-gsd-mcp-server docs updated; changeset (Changed).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Tom Boucher
2026-07-11 02:27:57 -04:00
parent e3a8c063b8
commit 5695522d5f
19 changed files with 800 additions and 48 deletions

View File

@@ -2743,9 +2743,11 @@ function normalizeAgentBodyForRuntime(content: string, runtime: string, cmdNames
* ~/\.claude\b → normalizedPathPrefix
* $HOME/\.claude\b → normalizedPathPrefix
*
* Skipped for copilot and antigravity (which do NOT do path rewrites in the
* inline loop). NO stamp (_stampNonClaudeRuntimeDefaults) — agents are NOT
* stamped in the inline loop.
* Skipped for copilot (hardcoded — #2099 will fold it) and for any runtime
* that declares `hostBehaviors.noPathRewrite` (descriptor-driven, ADR-1239 /
* #2096 — folds the prior hardcoded `runtime === 'antigravity'` literal;
* Antigravity does NOT do path rewrites in the inline loop). NO stamp
* (_stampNonClaudeRuntimeDefaults) — agents are NOT stamped in the inline loop.
*
* ADR-1235 §1: pre-converter cross-cutting for descriptor-driven agent pipeline.
* Exported as `applyAgentPathRewrites` for testing and for injection into
@@ -2754,10 +2756,10 @@ function normalizeAgentBodyForRuntime(content: string, runtime: string, cmdNames
* @param content raw agent file content
* @param runtime canonical runtime ID
* @param pathPrefix trailing-slash path prefix (e.g. '$HOME/.cursor/')
* @returns content with path-prefix rewrites applied (or unchanged for copilot/antigravity)
* @returns content with path-prefix rewrites applied (or unchanged for copilot / noPathRewrite runtimes)
*/
function applyAgentPathRewrites(content: string, runtime: string, pathPrefix: string): string {
if (runtime === 'copilot' || runtime === 'antigravity') return content;
if (runtime === 'copilot' || _hostBehaviors(runtime).noPathRewrite === true) return content;
const normalizedPathPrefix = pathPrefix.replace(/\/$/, '');
content = content.replace(/~\/\.claude\//g, pathPrefix);
content = content.replace(/\$HOME\/\.claude\//g, pathPrefix);

View File

@@ -22,9 +22,12 @@
* 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.
* null → no dedicated permission 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
@@ -45,7 +48,7 @@ type ConfigInstallSurface =
| 'cursor-hooks-json'
| 'profile-marker-only';
type FinishPermissionWriter = 'opencode' | 'kilo' | null;
type FinishPermissionWriter = 'opencode' | 'kilo' | 'antigravity' | null;
type HooksSurface =
| 'settings-json'

View File

@@ -120,13 +120,26 @@ export function resolveRuntimeNameFromCandidates(...candidates: unknown[]): stri
* Aliases are normalized via `canonicalizeRuntimeName` first, so inputs like
* `codex-cli` resolve to `codex` → `AGENTS.md`. Replaces the prior codex-only
* override in profile-output.cjs (#3163) which left AGENTS-native runtimes
* (opencode/kilo/kimi) incorrectly emitting `.claude/CLAUDE.md`. Pure: no I/O.
* (opencode/kilo/kimi) incorrectly emitting `.claude/CLAUDE.md`. Pure: no I/O
* (the lazy `require` below reads a static generated module, not the disk).
*
* Descriptor-driven (ADR-1239 / #2096): antigravity's `GEMINI.md` is folded
* from a hardcoded `canonical === 'antigravity'` literal into a read of
* `runtime.hostBehaviors.projectInstructionFile`. claude/copilot stay
* hardcoded (out of scope here) mirroring `getDirName` below, which already
* lazy-`require`s `capability-registry.cjs` inside the function body to
* avoid a circular dependency at module load.
*/
export function getProjectInstructionFile(runtime: unknown): string {
const canonical = canonicalizeRuntimeName(runtime);
if (canonical === 'claude') return '.claude/CLAUDE.md';
if (canonical === 'copilot') return '.github/copilot-instructions.md';
if (canonical === 'antigravity') return 'GEMINI.md';
// eslint-disable-next-line @typescript-eslint/no-require-imports
const { runtimes } = require('./capability-registry.cjs') as {
runtimes: Record<string, { runtime?: { hostBehaviors?: { projectInstructionFile?: string } } } | undefined>;
};
const declared = canonical ? runtimes[canonical]?.runtime?.hostBehaviors?.projectInstructionFile : undefined;
if (typeof declared === 'string' && declared.length > 0) return declared;
// codex, opencode, kilo, kimi, AND unknown/future runtimes all default to
// root AGENTS.md (the safe cross-agent instruction file).
return 'AGENTS.md';

View File

@@ -98,9 +98,17 @@ export function formatManagedHookScriptToken(scriptPath: string, opts: { platfor
return JSON.stringify(scriptPath.replace(/\\/g, '/'));
}
export function projectLocalHookPrefix({ runtime = 'claude', dirName }: { runtime?: string; dirName?: string | null }): string | undefined | null {
export function projectLocalHookPrefix({ runtime: _runtime = 'claude', dirName, hookPathStyle }: { runtime?: string; dirName?: string | null; hookPathStyle?: string | null }): string | undefined | null {
if (!dirName) return dirName;
return (runtime === 'antigravity')
// Descriptor-driven (ADR-1239 / #2096): folded from a hardcoded
// `runtime === 'antigravity'` literal into the runtime's declared
// `hostBehaviors.hookPathStyle`. Runtimes that always run project hooks
// with the project dir as cwd (Antigravity today) declare 'raw' and get
// the bare dirName; every other runtime keeps the $CLAUDE_PROJECT_DIR-
// anchored prefix. `runtime` itself is now unused here but stays in the
// signature for call-site/back-compat parity (kept `_`-prefixed to
// satisfy no-unused-vars).
return (hookPathStyle === 'raw')
? dirName
: `"$CLAUDE_PROJECT_DIR"/${dirName}`;
}