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 ( |
|
Real Thanks for the pointer -- I ran it against the real Environment: Windows 11, 1.
|
| hook command | result |
|---|---|
teamai hook-dispatch session-start --tool antigravity |
[hook-dispatch] session-start/antigravity background: 18311ms total, 2 handlers, slowest pull=18311ms | pull=18311ms(ok) |
cmd /c teamai hook-dispatch prompt-submit --tool antigravity |
ran on each invocation: prompt-submit/antigravity foreground: 92ms total, 5 handlers ... |
So a Windows form for agy should avoid the quoted git-bash wrapper (and the POSIX 2>/dev/null), like the ZCode/DSH adapters already do for their platform-specific runners.
3. SessionStart is a real agy event, so it does not need to ride PreInvocation
antigravity-hooks.ts maps SessionStart -> PreInvocation with the comment "Antigravity has no session-start event of its own". 1.3.4 documents SessionStart ("once when a new conversation session starts", flat handler list) and it fires: in one session a SessionStart probe fired once while a PreInvocation probe fired once per model call. As written, the full session-start dispatch -- including the pull -- is repeated before every invocation.
What does work (verified, same run)
init --scope user --agent antigravitycloned the repo, registered the member, and delivered the team skill and rule:~/.gemini/antigravity/skills/agy-e2e-marker-skill/SKILL.md,~/.gemini/config/rules/agy-e2e-rule.md.teamai pullafter a second skill landed on the team repo:Team repo: 1 file(s) changed->Synced 2 skills (1 new, 1 updated)/new: agy-e2e-marker-skill-2->Synced 1 rule(s), file present on disk.teamai hooks injectmerged into an existing, hand-written~/.gemini/config/hooks.jsonand kept both of the user's own named blocks (dangerous-commands-guard,plan-readonly-guard) -- the one-key ownership contract holds for this adapter.
Two more paths worth checking (same root cause, not benchmarked yet)
- User-scope
skills: the delivered skill lands in~/.gemini/antigravity/skills, but on this machine that directory is the Antigravity 2.0 app's skill set (antigravity/is its data dir per agy's docs, while the CLI's isantigravity-cli/). If the CLI's global root is~/.gemini/config/, it should be~/.gemini/config/skills(andagentslikewise). - Project scope: the docs place
hooks.jsoninside a customization root, i.e.<project>/.agents/hooks.json, not<project>/hooks.json.
Happy to push a follow-up for 1-3 and re-run this exact loop to show the corrected block executing inside agy if that's useful.
…on Windows Verified against the real Antigravity CLI (`agy` 1.3.4, Windows) rather than its documentation alone; the previous paths and command form left every lifecycle hook inert, so session start never pulled and no team resource was delivered at session start. * Roots: the CLI discovers customizations in a project's `.agents/` and, for the machine, `~/.gemini/config/`. `~/.gemini/antigravity-cli/` is its application data (logs, conversations, built-in skills) and `~/.gemini/antigravity/` belongs to Antigravity 2.0 - a hooks file or skill written there is loaded-but-ignored or not discovered at all. User-scope hooks therefore move to `~/.gemini/config/hooks.json`, skills to `~/.gemini/config/skills`, subagents to `~/.gemini/config/agents`, and the project hooks file to `.agents/hooks.json` (a project's root `hooks.json` is never executed). * Events: agy fires a `SessionStart` of its own, so the session-start dispatch rides it instead of `PreInvocation`, which fires before every model call. * Windows: agy hands each hook command to `cmd /c`, where the POSIX `"<git-bash>" -lc "…"` wrapper's quotes arrive backslash-escaped and the command never starts (`exit status 1` on every invocation). On Windows the adapter now writes the bare `teamai hook-dispatch …` command. * The hooks install gate is the settings file itself, so the project path change keeps "a project with no hooks file gets none written". Verified end to end in the real CLI: session-start, prompt-submit and stop all run with no hook failures, a pulled skill is discovered from the corrected skills root, and a project-scope `.agents/hooks.json` handler executes while a root `hooks.json` one does not.
|
Fix pushed: The verification above turned up three things; all three are fixed in 1. Roots.
Both old locations are ones agy loads but never runs (its app-data hooks file) or never discovers at all (skills under 2. Events. 3. Windows command. New Verification after the fixThe installed block (user scope, merged into a "teamai-hooks": {
"enabled": true,
"SessionStart": [ { "command": "teamai hook-dispatch session-start --tool antigravity", "type": "command" } ],
"PreInvocation": [ { "command": "teamai hook-dispatch prompt-submit --tool antigravity", "type": "command" } ],
"Stop": [ { "command": "teamai hook-dispatch stop --tool antigravity", "type": "command" } ],
"PostToolUse": [ ... ]
}Real agy session (
Still open (not touched by this commit)
Two things I hit that look like separate issues, not this PR: The branch is still behind |
|
Findings
Earlier Findings Testing |
A rule file without frontmatter `trigger` is rejected by the CLI -- it logs `Invalid rule trigger: CORTEX_MEMORY_TRIGGER_UNSPECIFIED` and the rule never reaches the model -- so the verbatim `.md` copy this adapter wrote delivered nothing. Verified against agy 1.3.4 with the same rule twice: verbatim it was dropped (and logged), with `trigger: always_on` the model followed it. * New `ANTIGRAVITY_RULE_FORMAT`: a team rule renders as `---\ntrigger: always_on\n---` plus the team body, and a rule that declares a trigger of its own keeps that value. * The verbatim render stays in `previousRenders`, so copies an earlier teamai wrote are still recognized as teamai's.
|
Follow-up: team rules never reached the model -- fixed in Chasing the open item from my last comment (does a delivered rule actually reach the agent?) turned up a second real defect, now fixed and verified in the real CLI. Root cause. agy parses a rule file's frontmatter and requires a Evidence.
Fix ( Tests. Open items after this (the rules channel is now off the list):
|
|
Findings
Earlier Findings Testing |
Brings the branch up to `27a026f` so the PR is mergeable again. Conflict resolutions: * `src/resources/agent-format.ts`: keep both the new `muse` tool and `antigravity` in `ToolName`, `ALL_SUPPORTED_TOOLS` and `tool_extras`. * `src/resources/base.ts`: keep Antigravity's own install probe (it must not use the `..agents/`-based generic probe) and upstream's new `HOME_INSTALL_ROOTS` check; Antigravity is not in that table, so the order of the two checks does not change either answer. * Docs: upstream rewrote `docs/guide/advanced.md` into several pages (`dashboard`, `diagnostics`, `distribution`, `http-contract`, `reporting`, `skill`, `workflow`) and removed it, and rewrote `member-guide.md` and `sharing.md`. Resolved to upstream's structure, since the page our Antigravity section lived on no longer exists, and the tool's own documentation is carried by the README matrix (an `Antigravity CLI` row, added to all five language versions) plus a row in `docs/guide/team-culture.md` and its zh-CN twin for where the culture blocks land.
|
Merged Head is now
Re-verified on the merged base with the real CLI (agy 1.3.4, Windows):
One upstream behaviour change worth noting for anyone following along: |
|
Findings
Earlier Findings Testing |
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.