Repository navigation
Conversation
Antigravity CLI (`agy` / `antigravity`) — Google DeepMind's coding agent and the successor to `gemini-cli` — becomes a first-class target. Both `agy` and `gemini` are accepted as aliases for the id (`normalizeToolId`). Paths, project / user scope: - skills `.agents/skills` / `~/.gemini/antigravity/skills` - rules `.agents/rules` / `~/.gemini/config/rules` - agents `.agents/agents` / `~/.gemini/antigravity-cli/agents` - mcp `.agents/mcp_config.json` / `~/.gemini/config/mcp_config.json` - hooks `hooks.json` / `~/.gemini/antigravity-cli/hooks.json` - instr. `GEMINI.md` / `~/.gemini/config/GEMINI.md` The install probe is explicit: a project's `.agents/` tree is shared with the central Agent Skills entry, so Antigravity counts as installed by `~/.gemini/antigravity-cli/`, `~/.gemini/config/`, an `agy` / `antigravity` binary on PATH, or an explicit `--agent antigravity` entry — never by the shared project root alone. New `src/antigravity-hooks.ts`: Antigravity's `hooks.json` is keyed by hook NAME and carries the events inside each block, a shape the settings renderer cannot express. teamai owns exactly one top-level key, `teamai-hooks`, and deep-merges it, so a user's own blocks (`plan-readonly-guard`, `dangerous-commands-guard`, ...) survive every inject and removal. It is wired into `reconcileHooksToAllTools` and reported by `hooks list`. Subagents are Markdown with YAML frontmatter (rendered, and reversed back into the canonical spec); MCP uses the Claude `mcpServers` shape; rules are written as plain `.md` copies, since Antigravity has no rule format of its own. Also fixes a Windows-only hazard in an upstream test: `gitlab-clone-realspawn.test.ts` plants a `#!/bin/bash` fake `git` through a `:`-separated PATH, which does not take effect on Windows, so the real git ran against `https://gitlab.com/org/repo.git` and Git for Windows' credential helper (the system-level `credential.helper=manager`) opened a browser login window — the test then hung for 120s. It is skipped on Windows now. `instruction-targets.test.ts`'s `mockRejectedValueOnce`, which rejected whichever file happened to be written first, now rejects only the file under test, so its assertion no longer depends on how many tools are installed.
… tree The first cut asked `isToolInstalledForConfig` for Antigravity's machine-level roots only (`~/.gemini/antigravity-cli`, `~/.gemini/config`, an `agy` / `antigravity` binary on PATH, or an explicit `--agent antigravity`) and replaced the generic path probe with that answer. `instruction-targets` asks whether a tool is installed through the paths a project actually has, and its Tencent#945 suite creates exactly those before asserting delivery — so on any host without `~/.gemini` (i.e. CI) Antigravity counted as not installed and five `instruction-targets` cases failed. A Windows box that happens to have Antigravity installed masks this, which is how it reached the branch. Falling back to the generic probe is not the answer either: Antigravity's project skills live in the `.agents/` tree that the central Agent Skills entry also uses, so the generic root probe reports it installed for every project that merely has `.agents/` — which hands Antigravity's MCP targets to teams that do not run it and breaks four Tencent#882 MCP-reconciliation assertions. The probe now accepts the machine-level roots OR Antigravity's OWN files in the scope's base dir (`hooks.json`, `GEMINI.md`, `.gemini`) — never the shared `.agents/`. Verified on a clean-home Linux host (no `~/.gemini`, no `agy` on PATH): `instruction-targets` 241/241, `mcp-reconcile` 249/249, `antigravity` 14/14; and the full suite's failure set is identical to the pristine base commit (3 pre-existing `dry-run-load-path` 15s timeouts on both sides).
Verification on Linux (added after the initial push)The author's Windows box cannot run this suite green (symlink privileges,
The failure sets are identical — both sides fail only three Two defects this gate caught, both fixed in
|
|
Findings
Testing
|
…ed by it isToolInstalledForConfig runs once per MCP target per reconciliation (mcp-reconcile.ts), and Antigravity's probe reached a synchronous isOnPath walk of PATH inside that loop. Measured on WSL: PATH holds 58 entries, most on a 9p mount where one statSync costs ~1.6ms, so a single uncached call is ~116 blocking stats (~185ms). That made mcp-reconcile and pi-mcp ~4-5x slower and pushed several of their cases past the 15s test timeout under --coverage. The PATH answer only changes when PATH does, so cache it per PATH/PATHEXT value (a stubbed PATH still gets a fresh scan), and check the cheap signals first so the scan is only reached when nothing else matches. Result is unchanged: the signals are OR'd.
Aligning with
|
| tree | --coverage result |
tests phase | duration |
|---|---|---|---|
pristine 2cbf556 |
3 failed / 8441 passed (400 files) | 1202.9s | 355.7s |
2c4ea45 |
5 failed / 8461 passed (401 files) | 2591.1s (2.15x) | 905.8s |
The two extra failures were Test timed out in 15000ms in mcp-reconcile.test.ts and pi-mcp.test.ts. They were not assertion regressions — they were a real performance regression this branch introduced, which the --coverage gate surfaced as timeouts:
- Running just those two files, 3x per tree: both trees 254/254 green — nothing was asserted differently.
- Alternating A/B (P→F→P→F) with a constant pure-CPU control: the pattern was fast/slow/fast/slow — 144.9s / 114.6s on the base vs 782.7s / 456.3s on the branch — while the CPU control was identical every round. So the cost was I/O, not CPU, and it was the branch.
- Root cause, measured:
isOnPathwalks PATH with synchronousstatSync. On this host PATH has 58 entries, most on a 9p mount, where one stat costs ~1.6ms — so a singleisOnPath('agy') || isOnPath('antigravity')is ~116 blocking stats (~185ms).isToolInstalledForConfigruns once per MCP target per reconciliation (mcp-reconcile.ts), so the Antigravity probe sat inside that loop.
Fix in 7a8afce
- cache the PATH result per PATH/PATHEXT value (a stubbed PATH still gets a fresh scan), and
- check the cheap signals first, so the expensive scan is only reached when nothing else matches.
The signals are OR'd, so the boolean result is unchanged; tsc --noEmit exits 0.
Re-verification with the same command on the same host:
| before | after 7a8afce |
|
|---|---|---|
two-file A/B (--coverage) |
branch 456–783s | base 114.97s vs branch 118.61s (1.03x) |
full --coverage |
branch had 2 extra failures | 3 failed / 8463 passed — failure set identical to base |
full --coverage duration |
905.8s | 220.9s |
So all four ci.yml steps (npm run lint, npx tsc --noEmit, npx vitest run --coverage, npm run build) pass locally on the platform CI uses, with zero difference from the base commit; the only failures on either side are the three pre-existing dry-run-load-path timeouts. macOS and node 20/24 are left to CI.
The e2e suite was re-run after the fix too, with a pristine control, over the three files that cover what this change touches (instruction targets, hook injection, and the Trae #904 tool-addition e2e): base 3 files / 45 passed in 90.7s, this branch 3 files / 45 passed in 100.8s — it was 449.4s before the fix.
|
Findings
No earlier finding is resolved in the current head. The PR description includes representative real-CLI and E2E records, so it satisfies the runtime-change testing requirement. |
Resolve the conflict in src/resources/base.ts: upstream Tencent#1017 added toolGateRoot to the installed-tool gate. The Antigravity probe stays ahead of the generic call — Antigravity is not in RELOCATABLE_TOOLS, so a recorded toolRoots entry does not apply to it - and the generic call now uses upstream's toolGateRoot form. Switch the Antigravity hooks install gate in hooks.ts to the same toolGateRoot shape upstream adopted, which is what upstream's import list expects (toolInstallRoot is no longer imported there).
|
Findings
No earlier finding is resolved in the current diff. The PR description includes representative real-CLI and E2E records, so it satisfies the runtime-change testing requirement. |
Re-verified against the new base (
|
| tree | Test Files | Tests | failing set | e2e |
|---|---|---|---|---|
base 26ca277 |
1 failed / 400 passed / 1 skipped (402) | 2 failed / 8592 passed / 33 skipped (8627) | 2 dry-run-load-path timeouts |
3 files / 45 passed |
this branch 387d5eb |
1 failed / 401 passed / 1 skipped (403) | 2 failed / 8614 passed / 33 skipped (8649) | identical (the same 2) | 3 files / 45 passed |
Zero difference from the base; passed went +22 (the 14 new Antigravity cases and 8 new parameterized instruction-targets cases), and the duration is the same order (306.4s vs 282.1s). tsc --noEmit, npm run lint and npm run build all exit 0.
(One Windows-only note, not introduced here: builtin-agents.test.ts > the deployed recall agent marks every recall it runs… fails on Windows after upstream #1033's recall wording change, and passes on both trees on Linux.)
CI and Code Erosion still need a maintainer to approve the run; the PR is mergeable again.
|
Thanks for the hook-dispatch check. When you have a moment, could you also try this with the real Antigravity CLI ( |
Makes Google DeepMind's Antigravity CLI (
agy/antigravity) — the successor togemini-cli— a first-class teamai target, joining the existing 17+ tools.agyandgeminiare accepted as aliases for the tool id.What it delivers
.agents/skills~/.gemini/antigravity/skills.agents/rules~/.gemini/config/rules.agents/agents~/.gemini/antigravity-cli/agents.agents/mcp_config.json~/.gemini/config/mcp_config.jsonhooks.json~/.gemini/antigravity-cli/hooks.jsonGEMINI.md~/.gemini/config/GEMINI.mdToolName/ALL_SUPPORTED_TOOLSgainantigravity; a newnormalizeToolIdmapsagyandgeminionto it, applied innormalizeAgentList, so--agent agy/--agent geminireach the one Antigravity target instead of a phantom tool..agents/tree is shared with the central Agent Skills entry, so the generictoolInstallRootprobe would report Antigravity "installed" for anyone with a.agents/directory. It counts as installed by~/.gemini/antigravity-cli/,~/.gemini/config/, anagy/antigravitybinary onPATH, an explicit--agent antigravityentry, or Antigravity's own files in the scope's base dir (hooks.json,GEMINI.md,.gemini) — never by the shared.agents/root alone. Two earlier versions of this probe were wrong in opposite directions (machine-level roots only, which broke the [bug] Team members with different roles overwrite each other's blocks in shared root AGENTS.md #945 "a tool's own paths mean installed" contract on a host without~/.gemini; then a fallback that accepted the shared.agents/root, which broke [feat] keep project MCP configs with resolved tokens out of git #882); both were only visible when the suite was run on Linux, which is why it is run there.src/antigravity-hooks.ts). Antigravity'shooks.jsonis keyed by hook name and carries the events (PreToolUse,PostToolUse,PreInvocation,PostInvocation,Stop) inside each block — a shape the settings renderer (Claude's flat matcher array) cannot express. teamai owns exactly one top-level key,teamai-hooks, deep-merged, so a user's own blocks (plan-readonly-guard,dangerous-commands-guard, …) survive every inject and removal;hooks remove/uninstalldeletes only that key. Injection is idempotent (a second pass writes nothing) and creates nohooks.jsonwhere the user has none. Wired fromreconcileHooksToAllToolsand reported byhooks list.reverseByToolre-keys the extras toantigravity).mcpServersshape (CLAUDE_TOOLS)..mdcopies — Antigravity has no rule format of its own — and the instruction blocks go to itsclaudemd(GEMINI.md).Tests
New
src/__tests__/antigravity.test.ts— 14 cases, all passing (pnpm exec vitest run src/__tests__/antigravity.test.ts→ exit 0): scope paths, registry, alias normalization, install probe (each root, the--agentbootstrap, and the shared-.agents/non-misdetection), MCP format, verbatim rules, subagent render/reverse round trip, and the hooks adapter (event mapping, unknown-event drop, merge-only-our-key, immutability, remove, malformed-JSON read, idempotent inject, clean uninstall, and injection throughreconcileHooksToAllTools).The gate
ci.ymlruns isnpx vitest run --coverage, so the whole suite was compared with exactly that command on the platform CI uses (WSL Ubuntu 24.04, node v22.22.2, pnpm 11.23.0, 8 cores).mainadvanced 10 commits while this PR was open, soupstream/mainis merged in (merge commit387d5eb, merge rather than force-push) and the comparison was re-run against that new base:26ca277(upstream/main)387d5ebThe failure sets are identical — both sides fail only the same
dry-run-load-path.test.tscases withTest timed out in 15000ms(pre-existing, load-dependent; 2 on this run, a third appears on a loaded host).tsc --noEmit,oxlintandtsupall exit 0 on both trees.gitlab-clone-realspawn.test.tspasses on Linux, so its newskipIf(win32)does not hide a real failure.On Windows, where the suite cannot go green at all (symlink privileges,
chmod, POSIX path separators), a 35-file focused set (every test file touching tool registration — 1427 cases) was compared before/after the change withgit stash: failure set item-by-item identical (71 failures), all pre-existing platform failures; passed 1331 → 1353.Real-CLI verification
On Windows (git 2.53.0 / node v24.14.0), against the built
dist/index.js:The e2e suite (it spawns the real CLI) was then run on Linux with a pristine control, over the three files that cover what this change touches — instruction targets (
toolPaths/claudemd), hook injection (the new adapter's routing), and the newest tool-addition e2e (Trae #904) as a regression proxy:26ca277387d5ebAGENTS.mdasks for one representative real-CLI run rather than the full provider × agent matrix; these three are that run. (The same e2e file on Windows shows 8 failures — output-string assertions on messages the CLI prints with Windows paths, none involving Antigravity — and they are green on both trees on Linux.)Caveats (stated, not hidden)
AGENTS.mdasks for a worktree; this was done directly onfeat/support-antigravity-cliin a clone instead.docs/guide/providers.mddocuments Git providers, not AI tools, anddocs/guide/getting-started.mdcarries only an illustrative diagram, so neither lists a tool set to update.Also fixed
src/resources/base.ts— the install probe cost a full synchronous PATH walk per MCP target.isOnPathscans withstatSync, andisToolInstalledForConfigruns once per tool per reconciliation (mcp-reconcile.ts). On this host PATH has 58 entries, most on a 9p mount at ~1.6ms per stat, so one probe call was ~116 blocking stats (~185ms) — enough to makemcp-reconcile/pi-mcp~4.5x slower and push several cases past the 15s timeout under--coverage. The PATH answer is now cached per PATH/PATHEXT value (a stubbed PATH still rescans) and the cheap signals are checked first; the signals are OR'd, so the result is unchanged. Those two files went 456–783s → 118.6s (base: 115.0s) and the full--coveragerun 905.8s → 220.9s, with the failure set back to the base's.src/__tests__/gitlab-clone-realspawn.test.ts— Windows-only hazard. It plants a#!/bin/bashfakegitthrough a:-separatedPATH, which does not take effect on Windows, so the real git ran against the defaulthttps://gitlab.com/org/repo.git; the 401 made Git for Windows' credential helper (credential.helper=manager, system level) open a browser login window and the test hung for 120 s (observed in the run log). It is nowdescribe.skipIf(process.platform === 'win32'). A side-effect on any machine, but a real network call and a credential prompt are exactly what a unit test must not cause.src/__tests__/instruction-targets.test.ts— itsmockRejectedValueOncerejected whichever file happened to be written first; adding a target shifted which file that was. It now rejects only the file under test, so the assertion no longer depends on how many tools are installed.