From bb041da93e07af6e9b3732e916dde9217ba464f4 Mon Sep 17 00:00:00 2001 From: Thierry Kwiatkowski Date: Mon, 5 Oct 2026 17:52:41 +0200 Subject: [PATCH 1/6] feat(cli): add Antigravity CLI as a flat host for skills Teams moved from Gemini CLI to Antigravity CLI (agy) have no AIDD install. This adds an isolated `antigravity` flat profile that writes AIDD skills under `.agents/skills/`, from setup and from `translate --to antigravity --as flat`. agy 1.2.14 discovers a skill only directly under `.agents/skills/`: a probe at `.agents/skills///SKILL.md` logs no expansion, one at `.agents/skills//SKILL.md` logs `expanded slash command`. A flat profile can therefore declare a `single-level` skill layout, `skills/-/`, which the flat install now recognises; every other tool keeps the `nested` default. Hooks and agents stay unsupported until their own phases. The golden snapshot gains only the `antigravity:flat` cell. Refs #511 Co-Authored-By: Claude Opus 5.5 --- .github/workflows/ci.yml | 3 +- .../backlog-link.json | 5 + .../phase-2.md | 117 +++++++++++ .../phase-3.md | 90 +++++++++ .../phase-4.md | 82 ++++++++ .../phase-5.md | 79 ++++++++ .../2026_10_02_antigravity-flat-host/plan.md | 45 +++++ cli/aidd_docs/memory/codebase-map.md | 1 + cli/mutation-scopes.json | 5 + cli/package.json | 3 +- cli/scripts/check-bundle-size.mjs | 2 + cli/scripts/smoke-tools.sh | 2 +- .../built-tree-materialization-translator.ts | 15 +- .../domain/capabilities/plugins-capability.ts | 8 + .../profiles/antigravity/antigravity-paths.ts | 5 + .../domain/profiles/antigravity/build.ts | 33 ++++ .../domain/profiles/antigravity/profile.ts | 51 +++++ .../translate/domain/content-translator.ts | 9 +- cli/src/kernel/tool.ts | 10 +- cli/src/presentation/commands/translate.ts | 2 +- cli/src/presentation/prompts/menu-use-case.ts | 6 +- cli/src/runtime/assets/asset-loader.ts | 1 + cli/src/runtime/wiring/framework.ts | 1 + cli/src/runtime/wiring/tools.ts | 1 + cli/src/runtime/wiring/translate.ts | 1 + .../tool-addition-cost.arch.test.ts | 12 +- .../clean-native-cache.integration.test.ts | 1 + ...list-installed-rules-use-case.unit.test.ts | 1 + .../uninstall-use-case.unit.test.ts | 2 +- .../plugin-asset-translation.unit.test.ts | 1 + ...ild-hooks-support-declaration.unit.test.ts | 3 + ...plugin-root-token-declaration.unit.test.ts | 1 + .../profiles/antigravity/build.unit.test.ts | 33 ++++ .../profiles/antigravity/profile.unit.test.ts | 42 ++++ .../domain/registry-conformance.unit.test.ts | 8 +- .../tools/domain/tool-config.unit.test.ts | 4 +- ...egistry-reader-adapter.integration.test.ts | 1 + .../plugin-content-translator.unit.test.ts | 7 + ...clean-antigravity-foreign-file.e2e.test.ts | 48 +++++ .../golden/framework-build-golden.e2e.test.ts | 14 +- .../snapshots/framework-build/golden.json | 184 ++++++++++++++++++ cli/tests/golden/snapshots/help/surface.json | 2 +- .../golden/snapshots/phase0/snapshot.json | 2 +- cli/tests/helpers/ports/build-unit-deps.ts | 1 + .../translate-wiring.integration.test.ts | 3 +- .../interactive-menu-use-case.unit.test.ts | 4 +- ...e-real-user-scope-tool-list-parity.test.js | 2 +- 47 files changed, 919 insertions(+), 34 deletions(-) create mode 100644 aidd_docs/tasks/2026_10/2026_10_02_antigravity-flat-host/backlog-link.json create mode 100644 aidd_docs/tasks/2026_10/2026_10_02_antigravity-flat-host/phase-2.md create mode 100644 aidd_docs/tasks/2026_10/2026_10_02_antigravity-flat-host/phase-3.md create mode 100644 aidd_docs/tasks/2026_10/2026_10_02_antigravity-flat-host/phase-4.md create mode 100644 aidd_docs/tasks/2026_10/2026_10_02_antigravity-flat-host/phase-5.md create mode 100644 aidd_docs/tasks/2026_10/2026_10_02_antigravity-flat-host/plan.md create mode 100644 cli/src/contexts/tools/domain/profiles/antigravity/antigravity-paths.ts create mode 100644 cli/src/contexts/tools/domain/profiles/antigravity/build.ts create mode 100644 cli/src/contexts/tools/domain/profiles/antigravity/profile.ts create mode 100644 cli/tests/contexts/tools/domain/profiles/antigravity/build.unit.test.ts create mode 100644 cli/tests/contexts/tools/domain/profiles/antigravity/profile.unit.test.ts create mode 100644 cli/tests/e2e/clean-antigravity-foreign-file.e2e.test.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f6c4fd865..07278c741 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -178,7 +178,7 @@ jobs: contents: write strategy: fail-fast: false - # Four marketplace plus six flat — opencode and kilo are flat-only. Mirrors the CLI golden + # Four marketplace plus seven flat — opencode, kilo and antigravity are flat-only. Mirrors the CLI golden # snapshot matrix. matrix: include: @@ -192,6 +192,7 @@ jobs: - { tool: codex, mode: flat } - { tool: opencode, mode: flat } - { tool: kilo, mode: flat } + - { tool: antigravity, mode: flat } steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 diff --git a/aidd_docs/tasks/2026_10/2026_10_02_antigravity-flat-host/backlog-link.json b/aidd_docs/tasks/2026_10/2026_10_02_antigravity-flat-host/backlog-link.json new file mode 100644 index 000000000..6f955dffc --- /dev/null +++ b/aidd_docs/tasks/2026_10/2026_10_02_antigravity-flat-host/backlog-link.json @@ -0,0 +1,5 @@ +{ + "backlog": "ai-driven-dev/framework#511", + "written_at": "2026-10-02T16:30:00Z", + "written_by": "aidd-dev:01-plan" +} diff --git a/aidd_docs/tasks/2026_10/2026_10_02_antigravity-flat-host/phase-2.md b/aidd_docs/tasks/2026_10/2026_10_02_antigravity-flat-host/phase-2.md new file mode 100644 index 000000000..3739aca29 --- /dev/null +++ b/aidd_docs/tasks/2026_10/2026_10_02_antigravity-flat-host/phase-2.md @@ -0,0 +1,117 @@ +--- +status: done +--- + + + +# Instruction: `antigravity` profile with skills, registered everywhere, golden cell + +## Architecture projection + +> Tree of the final files. ✅ create · ✏️ modify · ❌ delete + +```txt +cli/ +├── src/kernel/tool.ts ✏️ AiToolId union and AI_TOOL_IDS +├── src/contexts/framework/application/framework/translator/built-tree-materialization-translator.ts ✏️ recognise a skill at the profile's declared flat layout, not only `skills//…` +├── src/contexts/tools/domain/profiles/antigravity/ +│ ├── antigravity-paths.ts ✅ `.agents/` directory and skills dir constants +│ ├── profile.ts ✅ AiTool, flat plugins, no hooks yet, registerTool +│ └── build.ts ✅ flat contract: skills only, agents/hooks/mcp/rules/commands unsupported +├── src/runtime/wiring/{tools,framework,translate}.ts ✏️ side-effect profile import +├── src/runtime/assets/asset-loader.ts ✏️ CONFIG_ASSETS entry (Record) +├── src/presentation/commands/translate.ts ✏️ `--to` help string +├── src/presentation/prompts/menu-use-case.ts ✏️ example strings +├── scripts/smoke-tools.sh ✏️ AI_TOOLS +├── scripts/check-bundle-size.mjs ✏️ only if the budget breaks, with a registry line +├── mutation-scopes.json, package.json ✏️ `tools-antigravity` scope and script +├── tests/architecture/tool-addition-cost.arch.test.ts ✏️ BASELINE counts +1 for the files above +├── tests/contexts/tools/domain/profiles/antigravity/ +│ ├── profile.unit.test.ts ✅ paths, capabilities, telemetry reason +│ └── build.unit.test.ts ✅ skill paths, link rewriting +├── tests/golden/framework-build-golden.e2e.test.ts ✏️ FLAT_TARGETS + cell count +├── tests/golden/snapshots/framework-build/golden.json ✏️ new `antigravity:flat` cell only +├── tests/golden/snapshots/{help/surface,phase0/snapshot}.json, tests/fixtures/cli-owns-read/expected-envelope.json ✏️ regenerated +└── tests/** enumerating tool ids (tool-config, registry-conformance, build-hooks-support-declaration, asset-loader, interactive-menu, translate-wiring, uninstall-use-case, build-unit-deps, telemetry-route-supply, plugin-root-token-declaration, metrics-contract, cost-report-envelope, tool-attribution, host-marketplace-registry-reader-adapter) ✏️ +.github/workflows/ci.yml ✏️ `{ tool: antigravity, mode: flat }` row, comment count +scripts/__tests__/smoke-real-user-scope-tool-list-parity.test.js ✏️ flat-only list +docs/MAINTAINERS.md ✏️ archive count +``` + +## User Journey + +```mermaid +flowchart TD + A[aidd setup --ai antigravity] --> B[skills written to .agents/skills/aidd-*/SKILL.md] + B --> C[manifest records them under antigravity] + D[aidd translate src --to antigravity --as flat] --> E[same .agents/skills tree in --out] + C --> F[aidd clean antigravity removes only its recorded files] +``` + +## Test Scope + +```mermaid +--- +title: Test scope +--- +journey + section Setup + fixture framework source with one plugin skill => source ready: 5: system + section Happy path + aidd translate fixture --to antigravity --as flat => .agents/skills/-/SKILL.md written: 5: cli + aidd setup --ai antigravity => skills under .agents/skills recorded in manifest: 5: cli + golden e2e => antigravity:flat cell matches, other cells unchanged: 5: cli + section Edge case - foreign file + user file .agents/skills/mine/SKILL.md => clean antigravity => file untouched: 5: cli + section Teardown + remove temp project => baseline restored: 5: system +``` + +## Tasks to do + +### `1)` Profile, paths and flat contract + +> Skills only; every other artifact `supported: false` until its phase. + +1. `antigravity-paths.ts`: `ANTIGRAVITY_DIRECTORY = ".agents/"`, skills dir. +2. `build.ts`: skills `fullTree`, `rewriteSkillName: true`, path `.agents/skills/-/`, one directory level (same shape as Codex flat). Measured 2026-10-05 on `agy` 1.2.14: `.agents/skills/flat-probe/SKILL.md` logs `expanded slash command "flat-probe" (skill)`; `.agents/skills/aidd-probe/nested-probe/SKILL.md` logs no expansion. Never `//`. +3. Install side: setup reads the built flat tree back through `belongsToPlugin` in `built-tree-materialization-translator.ts`, which accepts only `skills//…` and so installs 0 files from the one-level layout. Let the profile declare its flat skill layout and have the materializer recognise it; test first (red: setup installs 0 skills), keep every existing tool's behaviour unchanged. +4. `profile.ts`: `SkillsCapability` on `.agents/skills/`, `PluginsCapability` `mode: "flat"`, `acceptsHooks: false` with a reason pointing to phase 3; `telemetryLocalRead` unsupported with the canonical reason string `registry-conformance` expects; no `distributionProbes.marketplace`. +5. Unit tests first: profile and build, red then green. + +### `2)` Register the id everywhere the arch test demands + +> Kilo review lesson: a static id is not a runtime registration. + +1. `tool.ts`, three wiring files, asset loader, translate help, menu strings. +2. Raise each `tool-addition-cost` BASELINE count by one; nothing else in that file. +3. Update every test enumeration listed in the projection; regenerate help and phase0 snapshots and the cli-owns-read envelope. + +### `3)` Golden cell and CI matrix + +> Only the new cell may move. + +1. Add `antigravity` to `FLAT_TARGETS`, update the cell-count title, run with `UPDATE_FRAMEWORK_GOLDEN=1`. +2. `git diff` the golden file: every changed key is `antigravity:flat`. +3. `ci.yml` matrix row, `MAINTAINERS.md` archive count, root parity test list, `smoke-tools.sh`, mutation scope and script. + +### `4)` Ownership under a shared `.agents/` + +> The Codex-shared case is a known bug outside #511; prove only that AIDD never touches a foreign file. + +1. Integration test: setup antigravity, add a user file under `.agents/skills/`, clean antigravity, user file intact. + +### `5)` Full suite and size + +1. `cd cli && pnpm test`, `pnpm exec lefthook run pre-commit`, bundle size check; raise the budget only with a registry line. +2. Diff size: if over 600 lines, move task 4 or the test-enumeration edits that are pure lists into a follow-up commit, never the golden cell. + +## Test acceptance criteria + +| Task | Acceptance criteria | +| ---- | ------------------- | +| 1 | `translate --to antigravity --as flat` writes `.agents/skills/-/SKILL.md`, one level deep, and no other artifact; `aidd setup --ai antigravity` installs the same skills; no other tool's setup output changes | +| 2 | `aidd setup --ai antigravity` succeeds, `aidd --help` and the menu list it, the arch tests pass | +| 3 | `golden.json` gains exactly one key, `antigravity:flat`; no existing cell changes; `ci.yml` builds the new archive | +| 4 | Cleaning Antigravity leaves a user file under `.agents/` in place | +| 5 | The full CLI suite and pre-commit pass on the branch | diff --git a/aidd_docs/tasks/2026_10/2026_10_02_antigravity-flat-host/phase-3.md b/aidd_docs/tasks/2026_10/2026_10_02_antigravity-flat-host/phase-3.md new file mode 100644 index 000000000..ddaf5696f --- /dev/null +++ b/aidd_docs/tasks/2026_10/2026_10_02_antigravity-flat-host/phase-3.md @@ -0,0 +1,90 @@ +--- +status: pending +--- + + + +# Instruction: Memory hook in `.agents/hooks.json` + +## Architecture projection + +> Tree of the final files. ✅ create · ✏️ modify · ❌ delete + +```txt +cli/ +├── src/contexts/tools/domain/profiles/antigravity/ +│ ├── antigravity-hooks.ts ✅ named-hook shape: merge AIDD's `aidd-memory` key into `.agents/hooks.json`, strip it on uninstall, convert Claude-shaped flat hooks +│ ├── profile.ts ✏️ HooksCapability on `.agents/hooks.json` +│ └── build.ts ✏️ hooks artifact: scripts under `.agents/hooks//`, hooksMerge into `.agents/hooks.json` +├── tests/contexts/tools/domain/profiles/antigravity/ +│ └── antigravity-hooks.unit.test.ts ✅ merge, idempotence, foreign keys kept, invalid JSON, strip +├── tests/contexts/tools/domain/build-hooks-support-declaration.unit.test.ts ✏️ antigravity now supports hooks +└── tests/golden/snapshots/framework-build/golden.json ✏️ `antigravity:flat` cell only +``` + +## User Journey + +```mermaid +flowchart TD + A[aidd setup --ai antigravity] --> B[.agents/hooks.json gains key aidd-memory with SessionStart command] + B --> C[agy new session in trusted folder] + C --> D[log: loaded N named hooks; memory refresh runs at repo root] + E[aidd clean antigravity] --> F[aidd-memory key removed, other named hooks kept, file deleted only if empty] +``` + +## Test Scope + +```mermaid +--- +title: Test scope +--- +journey + section Setup + agy 1.2.14 logged in, probe hook printing cwd and workspacePaths => measured cwd recorded: 5: system + section Happy path + aidd setup --ai antigravity on empty project => .agents/hooks.json has aidd-memory SessionStart entry: 5: cli + second setup => file byte-identical: 5: cli + translate --to antigravity --as flat => same hooks.json shape and scripts under .agents/hooks/: 5: cli + section Edge case - user hooks present + .agents/hooks.json with user key lint => setup then clean => lint key unchanged: 5: cli + section Edge case - invalid JSON + unparsable .agents/hooks.json => setup => error names the file, file untouched: 1: cli + section Teardown + remove temp project => baseline restored: 5: system +``` + +## Tasks to do + +### `1)` Measure the hook `cwd` on 1.2.14 + +> #890 measured `cwd = /.agents` on 1.2.5; `update_memory.js` silently no-ops when `aidd_docs/` is not in `cwd`. + +1. Local probe: a `SessionStart` hook writing `pwd` and its stdin payload to a marker; read the marker, not the turn. +2. Pick the command from the result: if `cwd` is `.agents`, the command must reach the repo root (e.g. `node ../.aidd/scripts/update_memory.cjs`, or a root resolved from `workspacePaths[0]`); record the evidence in the PR. + +### `2)` Named-hook merge, test first + +> Shape `{ "": { "SessionStart": [ { "hooks": [ { "type": "command", "command": "…", "timeout": 30 } ] } ] } }`, no wrapper. + +1. Tests: empty file, existing foreign keys, re-run idempotent, invalid JSON fails loud (Codex's silent `catch { parsed = {} }` is not copied), strip leaves foreign keys. +2. Implement in `antigravity-hooks.ts`; mutate the foreign-key preservation and watch its test fail. + +### `3)` Wire setup and translate + +1. `profile.ts`: `HooksCapability` targeting `.agents/hooks.json` with the merge and strip; `acceptsHooks` stays false for plugin flat install so the hook is not written twice. +2. `build.ts`: hooks `hooksBundle`, scripts to `.agents/hooks//`, `hooksMerge` + `hooksMergeDest` converting the rewritten Claude-shaped JSON to one named key per plugin. +3. Confirm the flat-rewritten script path resolves from the measured `cwd`. + +### `4)` Golden and suite + +1. Regenerate golden; only `antigravity:flat` moves. +2. `cd cli && pnpm test`, pre-commit. + +## Test acceptance criteria + +| Task | Acceptance criteria | +| ---- | ------------------- | +| 1 | The PR quotes the measured `cwd` and the marker line from a real `agy` session | +| 2 | Foreign named hooks survive setup and clean; a second setup changes nothing; invalid JSON stops with the file named | +| 3 | After setup, a new `agy` session logs `loaded … named hooks` and the memory refresh marker appears at repo root | +| 4 | Only the `antigravity:flat` golden cell changes; full suite passes | diff --git a/aidd_docs/tasks/2026_10/2026_10_02_antigravity-flat-host/phase-4.md b/aidd_docs/tasks/2026_10/2026_10_02_antigravity-flat-host/phase-4.md new file mode 100644 index 000000000..62fa96668 --- /dev/null +++ b/aidd_docs/tasks/2026_10/2026_10_02_antigravity-flat-host/phase-4.md @@ -0,0 +1,82 @@ +--- +status: pending +--- + + + +# Instruction: Portable agents under `.agents/agents//agent.md` + +## Architecture projection + +> Tree of the final files. ✅ create · ✏️ modify · ❌ delete + +```txt +cli/ +├── src/contexts/tools/domain/formats/ +│ └── portable-agent.ts ✅ rebuild frontmatter: name, description, model: inherit, tools as YAML list; drop everything else +├── src/contexts/tools/domain/profiles/antigravity/ +│ ├── antigravity-paths.ts ✏️ agents dir +│ ├── profile.ts ✏️ AgentsCapability, nested `/agent.md` +│ └── build.ts ✏️ agents artifact, link rewriting relative to the nested path +├── tests/contexts/tools/domain/formats/ +│ └── portable-agent.unit.test.ts ✅ model mapping, tools string → list, Claude-only keys dropped, empty description rejected +├── tests/contexts/tools/domain/profiles/antigravity/build.unit.test.ts ✏️ agent path and body +└── tests/golden/snapshots/framework-build/golden.json ✏️ `antigravity:flat` cell only +``` + +## User Journey + +```mermaid +flowchart TD + A[source agent plugins/aidd-dev/agents/executor.md, model: opus, tools: a, b] --> B[aidd setup / translate antigravity] + B --> C[.agents/agents/aidd-dev-executor/agent.md] + C --> D[frontmatter: name, description, model: inherit, tools list] + D --> E[agy /agents lists aidd-dev-executor] +``` + +## Test Scope + +```mermaid +--- +title: Test scope +--- +journey + section Setup + fixture agent with model opus, comma tools and a Claude-only key => source ready: 5: system + section Happy path + translate --to antigravity --as flat => .agents/agents/-/agent.md written: 5: cli + read frontmatter => name, description, model inherit, tools YAML list, nothing else: 5: cli + aidd setup --ai antigravity => same file recorded in manifest: 5: cli + section Edge case - no tools + source agent without tools => translate => no tools key emitted: 5: cli + section Edge case - empty description + source agent with blank description => translate => build fails naming the agent: 1: cli + section Edge case - flat shape + any build => no .agents/agents/.md file exists: 5: cli +``` + +## Tasks to do + +### `1)` Portable frontmatter, test first + +> One builder #916 can reuse for Codex flat and Kimi. + +1. Tests for the four rules in #916, then implement `portable-agent.ts`. +2. Mutate `model: inherit` back to the source value; watch the model test fail. + +### `2)` Antigravity agents in setup and translate + +1. `AgentsCapability` and flat `agents` artifact writing `.agents/agents/-/agent.md`, body links rewritten from that nested path. +2. Unit test the path and body; regenerate golden, only `antigravity:flat` moves. + +### `3)` Live check + +1. Local `agy --add-dir --output-format json -p "/agents"` lists a generated agent; quote the line in the PR, with an invented-name negative control. + +## Test acceptance criteria + +| Task | Acceptance criteria | +| ---- | ------------------- | +| 1 | A source agent with `model: opus` and `tools: "a, b"` yields `model: inherit` and `tools: [a, b]`, and no Claude-only key | +| 2 | Agents land only at `.agents/agents//agent.md`; only the `antigravity:flat` golden cell changes; full suite passes | +| 3 | `agy /agents` lists the generated agent and not the invented one | diff --git a/aidd_docs/tasks/2026_10/2026_10_02_antigravity-flat-host/phase-5.md b/aidd_docs/tasks/2026_10/2026_10_02_antigravity-flat-host/phase-5.md new file mode 100644 index 000000000..c6448f792 --- /dev/null +++ b/aidd_docs/tasks/2026_10/2026_10_02_antigravity-flat-host/phase-5.md @@ -0,0 +1,79 @@ +--- +status: pending +--- + + + +# Instruction: Docs, AIDD skill references and local runtime evidence + +## Architecture projection + +> Tree of the final files. ✅ create · ✏️ modify · ❌ delete + +```txt +README.md ✏️ compatibility line, tool table, install section +cli/README.md ✏️ tool and config-path rows +docs/FAQ.md ✏️ tool lists +cli/aidd_docs/memory/codebase-map.md ✏️ profiles/antigravity entry +plugins/aidd-context/hooks/update_memory.js ✏️ TOOL_FILES antigravity → AGENTS.md +plugins/aidd-context/skills/02-project-memory/references/tools.md ✏️ mirror of TOOL_FILES +plugins/aidd-context/skills/{04..08}-*/references/tool-paths.md, tool-detect.md ✏️ `.agents/` paths, detection +plugins/aidd-context/skills/11-explore/…/ai-mapping.md, 00-onboard/…/detection.md ✏️ antigravity row +cli/scripts/smoke-antigravity.sh ✅ local only: setup, then assert `expanded slash command`, `loaded … named hooks`, `/agents` line from logs and markers, never the turn's end +``` + +## User Journey + +```mermaid +flowchart TD + A[developer reads README] --> B[aidd setup --ai antigravity] + B --> C[agy in the project] + C --> D[/aidd skill expands, memory refreshes, agents listed] + E[maintainer runs smoke-antigravity.sh locally] --> F[three log assertions pass or name the missing line] +``` + +## Test Scope + +```mermaid +--- +title: Test scope +--- +journey + section Setup + agy 1.2.14 logged in, empty temp project with aidd_docs => ready: 5: system + section Happy path + smoke-antigravity.sh => expanded slash command line found for one AIDD skill: 5: cli + smoke-antigravity.sh => loaded named hooks line and memory marker found: 5: cli + smoke-antigravity.sh => /agents lists a generated agent: 5: cli + section Edge case - agy missing + no agy on PATH => smoke-antigravity.sh => exits non-zero naming agy: 1: cli + section Teardown + kill hung agy turn, remove temp project => baseline restored: 5: system +``` + +## Tasks to do + +### `1)` Docs and references + +1. README tables, FAQ, CLI README, codebase map. +2. `TOOL_FILES` and its mirror `tools.md`; the generate skills' path and detection references. +3. `pnpm exec lefthook run pre-commit` (doc duplication, links, paths named in prose). + +### `2)` Local runtime smoke + +> Print mode hangs after answering: bound each turn with a timeout and read the log, as the spike did. + +1. Script asserting the three log lines; documented as local-only with why (login required) in `docs/MAINTAINERS.md`. +2. Run it; paste the three shortest lines, date and `agy --version` into the PR. + +### `3)` Close the loop on #511 + +1. Tick #511's criteria with links to each phase's PR; file follow-ups named in the plan's decisions if not already filed (native plugin mode, telemetry host, migration guide). + +## Test acceptance criteria + +| Task | Acceptance criteria | +| ---- | ------------------- | +| 1 | README and FAQ list Antigravity; `update_memory.js` refreshes `AGENTS.md` for `antigravity`; pre-commit passes | +| 2 | On a logged-in machine the script reports all three lines; without `agy` it fails naming it | +| 3 | Every #511 criterion points to the PR that proves it | diff --git a/aidd_docs/tasks/2026_10/2026_10_02_antigravity-flat-host/plan.md b/aidd_docs/tasks/2026_10/2026_10_02_antigravity-flat-host/plan.md new file mode 100644 index 000000000..066fde357 --- /dev/null +++ b/aidd_docs/tasks/2026_10/2026_10_02_antigravity-flat-host/plan.md @@ -0,0 +1,45 @@ +--- +objective: "`aidd setup --ai antigravity` and `aidd translate --to antigravity --as flat` write AIDD skills, the memory hook and agents under `.agents/`, which `agy` loads, without touching files AIDD did not write." +status: in-progress +--- + + + +# Plan: Antigravity CLI as a flat host + +## Overview + +| Field | Value | +| ---------- | ----- | +| **Goal** | An isolated `antigravity` flat profile, modelled on Kilo, in project scope, shipped as four PRs under 600 lines each | +| **Source** | ai-driven-dev/framework#511, spike `aidd_docs/backlog/spikes/antigravity-supported-surfaces.md`, Kilo PR #745 and its review | + +## Phases + +| # | Phase | File | +| --- | ----- | ---- | +| 2 | `antigravity` profile with skills, registered everywhere, golden cell | [`phase-2.md`](./phase-2.md) | +| 3 | Memory hook in `.agents/hooks.json` | [`phase-3.md`](./phase-3.md) | +| 4 | Portable agents under `.agents/agents//agent.md` | [`phase-4.md`](./phase-4.md) | +| 5 | Docs, AIDD skill references and local runtime evidence | [`phase-5.md`](./phase-5.md) | + +## Resources + +| Source | Verified | +| ------ | -------- | +| https://antigravity.google/docs/hooks | `hooks.json` is keyed by hook name, no `hooks` wrapper; events `PreToolUse`, `PostToolUse`, `PreInvocation`, `PostInvocation`, `Stop`; `SessionStart` works though undocumented (spike, #890) | +| https://antigravity.google/docs/subagents?tab=cli | agents at `.agents/agents/.md` or `/agent.md`; `name`, `description` required; `model` in `inherit`/`flash`/`pro`; `tools` a YAML list | +| Issue #890 | hooks only from `hooks.json`; hook process `cwd` is `/.agents` on 1.2.5 | +| Issue #916 comments | `agy` silently drops an agent with `model: sonnet`/`opus` or a comma-separated `tools`, while `plugin validate` passes | +| Spike, 2026-10-02 | print-mode turns hang after answering: runtime evidence reads `cli-*.log` lines and markers, never the turn's end; a turn needs a login, so no CI runtime smoke | + +## Decisions + +| Decision | Why | +| -------- | --- | +| The shared plugin-path uninstall bug stays out of #511; phases are numbered 2 to 5 | Codex and Antigravity both write `.agents/skills/`, and uninstalling one deletes plugin files the other still records (probe `uninstall-tools-shared-plugin-path.probe.unit.test.ts` fails). Product owner's call on 2026-10-05: track it separately, ship #511 with it as a known limitation | +| Setup writes the memory hook through a `HooksCapability` on `.agents/hooks.json`, like Codex, not through a JS bridge like Kilo | `agy` reads a JSON file natively; a bridge only exists where the host has no hooks file | +| The hook command is chosen from a measured `cwd` on `agy` 1.2.14, not assumed | `update_memory.js` exits 0 in silence when `aidd_docs/` is absent from `cwd`, so a wrong `cwd` looks like success | +| The portable agent frontmatter builder lives in `tools/domain/formats/`, not in the profile | it is a cross-host shape (#916 reuses it for Codex flat and Kimi); only the Antigravity paths stay in `profiles/antigravity/` | +| No CI runtime smoke; CI gets the golden cell and the `build-per-tool` matrix row only | a turn needs a Google login and print mode hangs; the Kilo review's "real binary in the gate" lesson cannot hold here, so the PR says so instead of shipping a skipped test | +| Native plugin mode, telemetry host, duplicate-agent-name rejection stay out | #511 out of scope; duplicate `name` rejection belongs to #916 | diff --git a/cli/aidd_docs/memory/codebase-map.md b/cli/aidd_docs/memory/codebase-map.md index 86da7d334..752a48546 100644 --- a/cli/aidd_docs/memory/codebase-map.md +++ b/cli/aidd_docs/memory/codebase-map.md @@ -42,6 +42,7 @@ src/ │ │ │ ├── models/ │ │ │ ├── ports/ │ │ │ └── profiles/ # one directory per tool +│ │ │ ├── antigravity/ │ │ │ ├── claude/ │ │ │ ├── codex/ │ │ │ ├── copilot/ diff --git a/cli/mutation-scopes.json b/cli/mutation-scopes.json index 502606351..75e894b89 100644 --- a/cli/mutation-scopes.json +++ b/cli/mutation-scopes.json @@ -9,6 +9,7 @@ "tools": { "mutate": [ "src/contexts/tools/**/*.ts", + "!src/contexts/tools/domain/profiles/antigravity/**/*.ts", "!src/contexts/tools/domain/profiles/claude/**/*.ts", "!src/contexts/tools/domain/profiles/codex/**/*.ts", "!src/contexts/tools/domain/profiles/copilot/**/*.ts", @@ -67,6 +68,10 @@ "mutate": "src/contexts/tools/domain/profiles/opencode/**/*.ts", "break": 94 }, + "tools-antigravity": { + "mutate": "src/contexts/tools/domain/profiles/antigravity/**/*.ts", + "break": 82 + }, "tools-kilo": { "mutate": "src/contexts/tools/domain/profiles/kilo/**/*.ts", "break": 64 diff --git a/cli/package.json b/cli/package.json index 50bb7d104..84c125748 100644 --- a/cli/package.json +++ b/cli/package.json @@ -36,7 +36,7 @@ "node": ">=22.12" }, "packageManager": "pnpm@12.16.1", - "bundleBudgetKB": 712, + "bundleBudgetKB": 716, "scripts": { "build": "tsup && node scripts/check-bundle-size.mjs", "build:check-size": "node scripts/check-bundle-size.mjs", @@ -64,6 +64,7 @@ "test:mutation": "node scripts/run-mutation.mjs", "test:mutation:kernel": "node scripts/run-mutation.mjs kernel", "test:mutation:tools": "node scripts/run-mutation.mjs tools", + "test:mutation:tools-antigravity": "node scripts/run-mutation.mjs tools-antigravity", "test:mutation:tools-claude": "node scripts/run-mutation.mjs tools-claude", "test:mutation:tools-codex": "node scripts/run-mutation.mjs tools-codex", "test:mutation:tools-copilot": "node scripts/run-mutation.mjs tools-copilot", diff --git a/cli/scripts/check-bundle-size.mjs b/cli/scripts/check-bundle-size.mjs index 13322a0d0..023359b4f 100644 --- a/cli/scripts/check-bundle-size.mjs +++ b/cli/scripts/check-bundle-size.mjs @@ -39,6 +39,8 @@ const pkg = JSON.parse(readFileSync(resolve(root, "package.json"), "utf8")); // 0.1% measured headroom, with no added runtime dependency. // 689 KB: 675.2 KB, that recipe validation merged into the deterministic token measurement // (+21.9 KB over the 653.3 KB above); 2.0% measured headroom. +// 716 KB: 708.1 KB, Antigravity's flat profile (+2.1 KB over next's 706.0 KB); 1.1% measured +// headroom. // 756 KB: 755.6 KB, patched esbuild 0.28.2 emits TypeScript parameter-property fields // correctly (+5,008 bytes over 0.27.3); unchanged application and minification settings. // 695 KB: 680.6 KB, that esbuild merged into the deterministic token measurement (+5.3 KB over diff --git a/cli/scripts/smoke-tools.sh b/cli/scripts/smoke-tools.sh index 63afd9111..5a02c9a38 100755 --- a/cli/scripts/smoke-tools.sh +++ b/cli/scripts/smoke-tools.sh @@ -17,7 +17,7 @@ ROOT="$(cd "$(dirname "$0")/.." && pwd)" CLI="$ROOT/dist/cli.js" FRAMEWORK_FIXTURE="$ROOT/tests/fixtures/framework" -AI_TOOLS=(claude cursor copilot codex opencode kilo) +AI_TOOLS=(claude cursor copilot codex opencode kilo antigravity) IDE_TOOLS=(vscode) # Canonical leaf-command surface. Coverage = exercised / total. diff --git a/cli/src/contexts/framework/application/framework/translator/built-tree-materialization-translator.ts b/cli/src/contexts/framework/application/framework/translator/built-tree-materialization-translator.ts index 94a5eec9c..673366f9f 100644 --- a/cli/src/contexts/framework/application/framework/translator/built-tree-materialization-translator.ts +++ b/cli/src/contexts/framework/application/framework/translator/built-tree-materialization-translator.ts @@ -151,11 +151,10 @@ export class BuiltTreeMaterializationTranslator implements PluginTranslator { } // Flat build emits the whole marketplace into one workspace. Agents are namespaced by - // `-`; skills instead nest the whole subtree under `skills//`, since a - // skill's own script can `require()` a sibling by relative path, which only keeps resolving while - // nothing under that subtree is renamed. Hooks land under `flatHooksDir//`, except the - // one script that is the loader's own runtime module, renamed to the plugin's name in the - // loader's directory — so hook paths are matched by path, never by naming convention. + // `-`; skills keep their subtree intact, since a skill's script can `require()` a + // sibling by relative path, under `skills//` or, `single-level`, `skills/-/`. + // Hooks land under `flatHooksDir//`, except the loader's own runtime module, renamed to + // the plugin's name in the loader's directory — so hook paths are matched by path, not by name. private async readFlatFiles( builtDir: string, dist: PluginDistribution, @@ -182,7 +181,11 @@ export class BuiltTreeMaterializationTranslator implements PluginTranslator { if (segments[0] !== toolDirectory || segments.length < 3) return false; // `skills/` nests the whole plugin under one exactly-named segment; every other flat section // hyphen-prefixes the leaf segment. - if (segments[1] === "skills") return segments[2] === name; + if (segments[1] === "skills") { + return resolvePluginsCapability(toolId)?.flatSkillLayout === "single-level" + ? segments[2].startsWith(`${name}-`) + : segments[2] === name; + } return segments[2].startsWith(`${name}-`); } diff --git a/cli/src/contexts/tools/domain/capabilities/plugins-capability.ts b/cli/src/contexts/tools/domain/capabilities/plugins-capability.ts index fe9532422..80a22c5b0 100644 --- a/cli/src/contexts/tools/domain/capabilities/plugins-capability.ts +++ b/cli/src/contexts/tools/domain/capabilities/plugins-capability.ts @@ -127,9 +127,13 @@ export type FlatHooksSupport = } | { acceptsHooks: false; hooksUnsupportedReason: string }; +/** Flat skills go under `skills//`, or `skills/-/` for a shallow-scan tool. */ +export type FlatSkillLayout = "nested" | "single-level"; + export type FlatPluginsParams = { mode: "flat"; flatNamespacePrefix: string; + flatSkillLayout?: FlatSkillLayout; } & FlatHooksSupport; export interface UnsupportedPluginsParams { @@ -180,6 +184,7 @@ export class PluginsCapability { readonly flatHooksLoaderEntry: FlatHooksLoaderEntry | null; /** See {@link FlatHooksBridge}, or `null` when this capability declares none. */ readonly flatHooksBridge: FlatHooksBridge | null; + readonly flatSkillLayout: FlatSkillLayout; readonly marketplaceSettings: MarketplaceSettings | null; readonly nativeActivation: NativeActivation | null; readonly translationMode: PluginTranslationMode | null; @@ -209,6 +214,7 @@ export class PluginsCapability { this.flatHooksDir = null; this.flatHooksLoaderEntry = null; this.flatHooksBridge = null; + this.flatSkillLayout = "nested"; this.marketplaceSettings = params.marketplaceSettings ?? null; this.nativeActivation = params.nativeActivation ?? null; this._userPluginsDir = params.userPluginsDir; @@ -223,6 +229,7 @@ export class PluginsCapability { ? (params.flatHooksLoaderEntry ?? null) : null; this.flatHooksBridge = params.acceptsHooks ? (params.flatHooksBridge ?? null) : null; + this.flatSkillLayout = params.flatSkillLayout ?? "nested"; this.hooksTrustNotice = null; this.pluginRootToken = null; this.acceptsMcp = false; @@ -243,6 +250,7 @@ export class PluginsCapability { this.flatHooksDir = null; this.flatHooksLoaderEntry = null; this.flatHooksBridge = null; + this.flatSkillLayout = "nested"; this.hooksTrustNotice = null; this.pluginRootToken = null; this.acceptsMcp = false; diff --git a/cli/src/contexts/tools/domain/profiles/antigravity/antigravity-paths.ts b/cli/src/contexts/tools/domain/profiles/antigravity/antigravity-paths.ts new file mode 100644 index 000000000..e2d651fdc --- /dev/null +++ b/cli/src/contexts/tools/domain/profiles/antigravity/antigravity-paths.ts @@ -0,0 +1,5 @@ +/** Paths Antigravity CLI scans in a project. Kept apart from the profile so the build contract + * and the setup capability share one declaration. */ + +export const ANTIGRAVITY_DIRECTORY = ".agents/"; +export const ANTIGRAVITY_SKILLS_DIR = `${ANTIGRAVITY_DIRECTORY}skills/`; diff --git a/cli/src/contexts/tools/domain/profiles/antigravity/build.ts b/cli/src/contexts/tools/domain/profiles/antigravity/build.ts new file mode 100644 index 000000000..8dabe531e --- /dev/null +++ b/cli/src/contexts/tools/domain/profiles/antigravity/build.ts @@ -0,0 +1,33 @@ +/** Antigravity's project distribution is flat and carries skills only. A skill sits one level + * under `.agents/skills/`, since `agy` expands none nested deeper. */ + +import { genericFlatSkillPath } from "../../../../../kernel/materialization/flat-paths.js"; +import type { ToolBuildContract } from "../../build-contract.js"; +import { ANTIGRAVITY_SKILLS_DIR } from "./antigravity-paths.js"; + +function antigravityFlatSkillPath(plugin: string, rel: string): string { + return genericFlatSkillPath(ANTIGRAVITY_SKILLS_DIR, plugin, rel.replace(/^skills\//, "")); +} + +export function buildAntigravityFlatContract(): ToolBuildContract { + return { + manifestFileRelative: null, + synthesizeManifest: null, + manifestSchemaName: null, + artifacts: { + skills: { + supported: true, + source: { kind: "fullTree", srcDir: "skills" }, + path: antigravityFlatSkillPath, + rewriteSkillName: true, + }, + agents: { supported: false }, + mcp: { supported: false }, + hooks: { supported: false }, + rules: { supported: false }, + commands: { supported: false }, + }, + buildMarketplaceCatalog: null, + buildMarketplaceEntry: null, + }; +} diff --git a/cli/src/contexts/tools/domain/profiles/antigravity/profile.ts b/cli/src/contexts/tools/domain/profiles/antigravity/profile.ts new file mode 100644 index 000000000..8f9ff4571 --- /dev/null +++ b/cli/src/contexts/tools/domain/profiles/antigravity/profile.ts @@ -0,0 +1,51 @@ +import { PluginsCapability } from "../../capabilities/plugins-capability.js"; +import { SkillsCapability } from "../../capabilities/skills-capability.js"; +import type { AiTool, HasPlugins, HasSkills } from "../../contracts.js"; +import { registerTool } from "../../registry.js"; +import { ANTIGRAVITY_DIRECTORY } from "./antigravity-paths.js"; +import { buildAntigravityFlatContract } from "./build.js"; + +const TOOL_SUFFIX = ".antigravity.md"; +const SKILLS_PREFIX = "aidd-"; + +function skillName(fileName: string): string { + const parts = fileName.split("/"); + if (parts.length > 1) return parts[0] as string; + const base = parts[0] as string; + return base.endsWith(TOOL_SUFFIX) + ? base.slice(0, -TOOL_SUFFIX.length) + : base.replace(/\.md$/, ""); +} + +export const antigravity: AiTool = { + kind: "ai", + toolId: "antigravity", + directory: ANTIGRAVITY_DIRECTORY, + toolSuffix: TOOL_SUFFIX, + displayName: "Antigravity CLI", + signalDir: null, + buildContracts: { flat: buildAntigravityFlatContract }, + + capabilities: { + skills: new SkillsCapability({ + toolSuffix: TOOL_SUFFIX, + prefix: SKILLS_PREFIX, + buildInstallPath: (fileName) => + `${ANTIGRAVITY_DIRECTORY}skills/${SKILLS_PREFIX}${skillName(fileName)}/SKILL.md`, + convertFrontmatter: (fm) => fm, + }), + plugins: new PluginsCapability({ + mode: "flat", + flatNamespacePrefix: "aidd-", + flatSkillLayout: "single-level", + acceptsHooks: false, + hooksUnsupportedReason: "Antigravity CLI hooks are not wired yet.", + }), + }, + + rewriteContent(content: string): string { + return content; + }, +}; + +registerTool(antigravity); diff --git a/cli/src/contexts/translate/domain/content-translator.ts b/cli/src/contexts/translate/domain/content-translator.ts index bc8525d4c..71eb027a1 100644 --- a/cli/src/contexts/translate/domain/content-translator.ts +++ b/cli/src/contexts/translate/domain/content-translator.ts @@ -254,7 +254,7 @@ export class PluginContentTranslator { const { flatNamespacePrefix } = tool.capabilities.plugins; if (flatNamespacePrefix === null) return { files: [], skipped: [] }; const result: InstallationFile[] = []; - for (const file of dist.components.commands) { + for (const file of hasCommands(tool) ? dist.components.commands : []) { result.push(this.flatCommandFile(file, dist.manifest.name, tool, flatNamespacePrefix)); } for (const section of ["agents", "rules", "skills"] as const) { @@ -358,7 +358,12 @@ export class PluginContentTranslator { ); } } - return this.makeFile(`${tool.directory}${section}/${pluginName}/${fileName}`, content); + const singleLevel = + section === "skills" && tool.capabilities.plugins.flatSkillLayout === "single-level"; + const path = singleLevel + ? `${tool.directory}skills/${pluginName}-${fileName}` + : `${tool.directory}${section}/${pluginName}/${fileName}`; + return this.makeFile(path, content); } private makeFile(relativePath: string, content: string): InstallationFile { diff --git a/cli/src/kernel/tool.ts b/cli/src/kernel/tool.ts index 6f8b968f5..ed8f3d32c 100644 --- a/cli/src/kernel/tool.ts +++ b/cli/src/kernel/tool.ts @@ -1,6 +1,13 @@ import { UnknownAiToolIdError } from "./errors.js"; -export type AiToolId = "claude" | "cursor" | "copilot" | "opencode" | "kilo" | "codex"; +export type AiToolId = + | "claude" + | "cursor" + | "copilot" + | "opencode" + | "kilo" + | "codex" + | "antigravity"; export type IdeToolId = "vscode"; export type ToolId = AiToolId | IdeToolId; export type ToolCategory = "ai" | "ide"; @@ -12,6 +19,7 @@ export const AI_TOOL_IDS: readonly AiToolId[] = [ "opencode", "kilo", "codex", + "antigravity", ]; export const IDE_TOOL_IDS: readonly IdeToolId[] = ["vscode"]; export const VALID_TOOL_IDS: readonly ToolId[] = [...AI_TOOL_IDS, ...IDE_TOOL_IDS]; diff --git a/cli/src/presentation/commands/translate.ts b/cli/src/presentation/commands/translate.ts index 79830cce0..960a08d4d 100644 --- a/cli/src/presentation/commands/translate.ts +++ b/cli/src/presentation/commands/translate.ts @@ -72,7 +72,7 @@ export function registerTranslateCommand(program: Command): void { .argument("", "Path to the source framework directory") .requiredOption( "--to ", - "Conversion target (claude, cursor, copilot, codex, opencode, kilo)" + "Conversion target (claude, cursor, copilot, codex, opencode, kilo, antigravity)" ) .requiredOption("--out ", "Output directory (marketplace dist or project root)") .option("--as ", "Output layout", "marketplace") diff --git a/cli/src/presentation/prompts/menu-use-case.ts b/cli/src/presentation/prompts/menu-use-case.ts index b58ae19d3..f758c48e5 100644 --- a/cli/src/presentation/prompts/menu-use-case.ts +++ b/cli/src/presentation/prompts/menu-use-case.ts @@ -44,7 +44,8 @@ const INSTALLED_NODES: MenuNode[] = [ value: "doctor-tool", description: "Scope the report to a single AI or IDE tool", command: ["doctor", "--tool"], - inputPrompt: "Tool (e.g. claude, cursor, copilot, codex, opencode, kilo, vscode)", + inputPrompt: + "Tool (e.g. claude, cursor, copilot, codex, opencode, kilo, antigravity, vscode)", }, { name: "Plugins", @@ -64,7 +65,8 @@ const INSTALLED_NODES: MenuNode[] = [ value: "framework-install", description: "Add a tool to this project", command: ["framework", "install", "--tool"], - inputPrompt: "Tool (e.g. claude, cursor, copilot, codex, opencode, kilo, vscode)", + inputPrompt: + "Tool (e.g. claude, cursor, copilot, codex, opencode, kilo, antigravity, vscode)", }, { name: "Remove", diff --git a/cli/src/runtime/assets/asset-loader.ts b/cli/src/runtime/assets/asset-loader.ts index 65e75bb4d..c74352871 100644 --- a/cli/src/runtime/assets/asset-loader.ts +++ b/cli/src/runtime/assets/asset-loader.ts @@ -29,6 +29,7 @@ const CONFIG_ASSETS: Readonly { }) ).rejects.toThrow( new InputRequiredError( - "At least one tool ID is required. Valid tools: claude, cursor, copilot, opencode, kilo, codex, vscode" + "At least one tool ID is required. Valid tools: claude, cursor, copilot, opencode, kilo, codex, antigravity, vscode" ) ); }); diff --git a/cli/tests/contexts/framework/domain/plugin-asset-translation.unit.test.ts b/cli/tests/contexts/framework/domain/plugin-asset-translation.unit.test.ts index 764502e9d..1afd74c61 100644 --- a/cli/tests/contexts/framework/domain/plugin-asset-translation.unit.test.ts +++ b/cli/tests/contexts/framework/domain/plugin-asset-translation.unit.test.ts @@ -6,6 +6,7 @@ import "../../../../src/contexts/tools/domain/profiles/claude/profile.js"; import "../../../../src/contexts/tools/domain/profiles/codex/profile.js"; import "../../../../src/contexts/tools/domain/profiles/copilot/profile.js"; import "../../../../src/contexts/tools/domain/profiles/cursor/profile.js"; +import "../../../../src/contexts/tools/domain/profiles/antigravity/profile.js"; import "../../../../src/contexts/tools/domain/profiles/kilo/profile.js"; import "../../../../src/contexts/tools/domain/profiles/opencode/profile.js"; import { claude } from "../../../../src/contexts/tools/domain/profiles/claude/profile.js"; diff --git a/cli/tests/contexts/tools/domain/build-hooks-support-declaration.unit.test.ts b/cli/tests/contexts/tools/domain/build-hooks-support-declaration.unit.test.ts index 73557fb4a..a89e2807e 100644 --- a/cli/tests/contexts/tools/domain/build-hooks-support-declaration.unit.test.ts +++ b/cli/tests/contexts/tools/domain/build-hooks-support-declaration.unit.test.ts @@ -3,6 +3,8 @@ import type { ArtifactContract, ToolBuildContract, } from "../../../../src/contexts/tools/domain/build-contract.js"; +import { buildAntigravityFlatContract } from "../../../../src/contexts/tools/domain/profiles/antigravity/build.js"; +import { antigravity } from "../../../../src/contexts/tools/domain/profiles/antigravity/profile.js"; import { buildClaudeFlatContract } from "../../../../src/contexts/tools/domain/profiles/claude/build.js"; import { claude } from "../../../../src/contexts/tools/domain/profiles/claude/profile.js"; import { buildCodexFlatContract } from "../../../../src/contexts/tools/domain/profiles/codex/build.js"; @@ -31,6 +33,7 @@ const FLAT_CONTRACTS: ReadonlyArray<[HooksDeclaringTool, () => ToolBuildContract [copilot, buildCopilotFlatContract], [codex, buildCodexFlatContract], [kilo, buildKiloFlatContract], + [antigravity, buildAntigravityFlatContract], [opencode, buildOpencodeFlatContract], ]; diff --git a/cli/tests/contexts/tools/domain/plugin-root-token-declaration.unit.test.ts b/cli/tests/contexts/tools/domain/plugin-root-token-declaration.unit.test.ts index 468a9a00d..5cb74aa3a 100644 --- a/cli/tests/contexts/tools/domain/plugin-root-token-declaration.unit.test.ts +++ b/cli/tests/contexts/tools/domain/plugin-root-token-declaration.unit.test.ts @@ -9,6 +9,7 @@ import "../../../../src/contexts/tools/domain/profiles/claude/profile.js"; import "../../../../src/contexts/tools/domain/profiles/codex/profile.js"; import "../../../../src/contexts/tools/domain/profiles/copilot/profile.js"; import "../../../../src/contexts/tools/domain/profiles/cursor/profile.js"; +import "../../../../src/contexts/tools/domain/profiles/antigravity/profile.js"; import "../../../../src/contexts/tools/domain/profiles/kilo/profile.js"; import "../../../../src/contexts/tools/domain/profiles/opencode/profile.js"; import type { PluginsCapability } from "../../../../src/contexts/tools/domain/capabilities/plugins-capability.js"; diff --git a/cli/tests/contexts/tools/domain/profiles/antigravity/build.unit.test.ts b/cli/tests/contexts/tools/domain/profiles/antigravity/build.unit.test.ts new file mode 100644 index 000000000..a62d41abc --- /dev/null +++ b/cli/tests/contexts/tools/domain/profiles/antigravity/build.unit.test.ts @@ -0,0 +1,33 @@ +import { describe, expect, it } from "vitest"; +import type { ArtifactContract } from "../../../../../../src/contexts/tools/domain/build-contract.js"; +import { buildAntigravityFlatContract } from "../../../../../../src/contexts/tools/domain/profiles/antigravity/build.js"; + +function supported(artifact: ArtifactContract): Extract { + if (!artifact.supported) throw new Error("artifact is declared unsupported"); + return artifact; +} + +describe("buildAntigravityFlatContract", () => { + it("sits a skill one level under .agents/skills, the plugin hyphenated onto its name", () => { + const skills = supported(buildAntigravityFlatContract().artifacts.skills); + expect(skills.path("aidd-dev", "skills/01-plan/SKILL.md")).toBe( + ".agents/skills/aidd-dev-01-plan/SKILL.md" + ); + expect(skills.rewriteSkillName).toBe(true); + expect(skills.source).toEqual({ kind: "fullTree", srcDir: "skills" }); + }); + + it.each(["agents", "hooks", "mcp", "rules", "commands"] as const)( + "declares %s unsupported", + (kind) => { + expect(buildAntigravityFlatContract().artifacts[kind].supported).toBe(false); + } + ); + + it("writes no manifest, catalog or config artifact", () => { + const contract = buildAntigravityFlatContract(); + expect(contract.manifestFileRelative).toBeNull(); + expect(contract.buildMarketplaceCatalog).toBeNull(); + expect(contract.emitConfigArtifact).toBeUndefined(); + }); +}); diff --git a/cli/tests/contexts/tools/domain/profiles/antigravity/profile.unit.test.ts b/cli/tests/contexts/tools/domain/profiles/antigravity/profile.unit.test.ts new file mode 100644 index 000000000..b449c1ef8 --- /dev/null +++ b/cli/tests/contexts/tools/domain/profiles/antigravity/profile.unit.test.ts @@ -0,0 +1,42 @@ +import { describe, expect, it } from "vitest"; +import { antigravity } from "../../../../../../src/contexts/tools/domain/profiles/antigravity/profile.js"; + +describe("antigravity", () => { + it("is named for the person reading it and declares a flat build only", () => { + expect(antigravity.displayName).toBe("Antigravity CLI"); + expect(antigravity.directory).toBe(".agents/"); + expect(antigravity.buildContracts?.flat).toBeDefined(); + expect(antigravity.buildContracts?.marketplace).toBeUndefined(); + expect(antigravity.distributionProbes).toBeUndefined(); + }); + + it("installs skills under .agents/skills with the aidd- namespace", () => { + expect(antigravity.capabilities.skills.buildInstallPath("01-plan/SKILL.antigravity.md")).toBe( + ".agents/skills/aidd-01-plan/SKILL.md" + ); + expect(antigravity.capabilities.skills.accepts(".agents/skills/aidd-01-plan/SKILL.md")).toBe( + true + ); + expect(antigravity.capabilities.skills.accepts(".kilo/skills/x/SKILL.md")).toBe(false); + }); + + it.each([ + ["plan.antigravity.md", ".agents/skills/aidd-plan/SKILL.md"], + ["plan.md", ".agents/skills/aidd-plan/SKILL.md"], + ])("names a skill from the flat file %s, dropping its suffix", (fileName, expected) => { + expect(antigravity.capabilities.skills.buildInstallPath(fileName)).toBe(expected); + }); + + it("is a flat host that accepts no hooks until the hooks phase", () => { + expect(antigravity.capabilities.plugins).toMatchObject({ + mode: "flat", + flatNamespacePrefix: "aidd-", + flatSkillLayout: "single-level", + acceptsHooks: false, + }); + expect(antigravity.capabilities.plugins.hooksUnsupportedReason).toBe( + "Antigravity CLI hooks are not wired yet." + ); + expect(Object.keys(antigravity.capabilities).sort()).toEqual(["plugins", "skills"]); + }); +}); diff --git a/cli/tests/contexts/tools/domain/registry-conformance.unit.test.ts b/cli/tests/contexts/tools/domain/registry-conformance.unit.test.ts index b37d89642..b39873e59 100644 --- a/cli/tests/contexts/tools/domain/registry-conformance.unit.test.ts +++ b/cli/tests/contexts/tools/domain/registry-conformance.unit.test.ts @@ -6,6 +6,7 @@ import "../../../../src/contexts/tools/domain/profiles/claude/profile.js"; import "../../../../src/contexts/tools/domain/profiles/codex/profile.js"; import "../../../../src/contexts/tools/domain/profiles/copilot/profile.js"; import "../../../../src/contexts/tools/domain/profiles/cursor/profile.js"; +import "../../../../src/contexts/tools/domain/profiles/antigravity/profile.js"; import "../../../../src/contexts/tools/domain/profiles/kilo/profile.js"; import "../../../../src/contexts/tools/domain/profiles/opencode/profile.js"; import "../../../../src/contexts/tools/domain/profiles/vscode/profile.js"; @@ -40,6 +41,8 @@ const registeredAiTools: [string, AiTool][] = [ isAiTool(config) ? [[id as string, config] as [string, AiTool]] : [] ); +const TOOLS_WITHOUT_A_NATIVE_MARKETPLACE: ReadonlySet = new Set(["antigravity"]); + describe("AiTool contract conformance", () => { it("the registry actually contains tools (guards against a no-op suite)", () => { expect(registeredAiTools.length).toBeGreaterThan(0); @@ -90,7 +93,7 @@ describe("AiTool contract conformance", () => { it("is ingestible when it declares a plugins capability", () => { const declaresPlugins = "plugins" in (tool.capabilities as object); - if (!declaresPlugins) return; + if (!declaresPlugins || TOOLS_WITHOUT_A_NATIVE_MARKETPLACE.has(toolId)) return; expect( marketplaceProbes().some((probe) => probe.format === toolId), `${toolId} declares a plugins capability but its profile declares no marketplace probe — its native marketplace would never be detected` @@ -289,7 +292,8 @@ describe("projectHooksFileOf()", () => { /** Pinned as a table rather than described, so a tool whose install path moves — or a sixth * tool added with rules — fails here instead of drifting away from the installer quietly. */ describe("every tool says where its own installed rules live", () => { - const EXPECTED: Readonly> = { + const EXPECTED: Readonly> = { + antigravity: null, claude: { directory: ".claude/rules/", extension: ".md" }, codex: { directory: ".codex/rules/", extension: ".md" }, copilot: { directory: ".github/instructions/", extension: ".instructions.md" }, diff --git a/cli/tests/contexts/tools/domain/tool-config.unit.test.ts b/cli/tests/contexts/tools/domain/tool-config.unit.test.ts index d56a8eea9..b289395e1 100644 --- a/cli/tests/contexts/tools/domain/tool-config.unit.test.ts +++ b/cli/tests/contexts/tools/domain/tool-config.unit.test.ts @@ -23,7 +23,7 @@ const makeStubConfig = (toolId: AiToolId, toolSuffix: string): AiTool = }); describe("VALID_TOOL_IDS", () => { - it("contains exactly claude, cursor, copilot, opencode, kilo, codex, vscode", () => { + it("contains exactly claude, cursor, copilot, opencode, kilo, codex, antigravity, vscode", () => { expect(VALID_TOOL_IDS).toEqual([ "claude", "cursor", @@ -31,6 +31,7 @@ describe("VALID_TOOL_IDS", () => { "opencode", "kilo", "codex", + "antigravity", "vscode", ]); }); @@ -65,6 +66,7 @@ describe("toolIdsForCategory()", () => { "opencode", "kilo", "codex", + "antigravity", ]); }); diff --git a/cli/tests/contexts/tools/infrastructure/host-marketplace-registry-reader-adapter.integration.test.ts b/cli/tests/contexts/tools/infrastructure/host-marketplace-registry-reader-adapter.integration.test.ts index ceeaaa307..9f155bb1f 100644 --- a/cli/tests/contexts/tools/infrastructure/host-marketplace-registry-reader-adapter.integration.test.ts +++ b/cli/tests/contexts/tools/infrastructure/host-marketplace-registry-reader-adapter.integration.test.ts @@ -8,6 +8,7 @@ import "../../../../src/contexts/tools/domain/profiles/claude/profile.js"; import "../../../../src/contexts/tools/domain/profiles/codex/profile.js"; import "../../../../src/contexts/tools/domain/profiles/copilot/profile.js"; import "../../../../src/contexts/tools/domain/profiles/cursor/profile.js"; +import "../../../../src/contexts/tools/domain/profiles/antigravity/profile.js"; import "../../../../src/contexts/tools/domain/profiles/kilo/profile.js"; import "../../../../src/contexts/tools/domain/profiles/opencode/profile.js"; import { nativeActivationOf } from "../../../../src/contexts/tools/domain/registry.js"; diff --git a/cli/tests/contexts/translate/domain/plugin-content-translator.unit.test.ts b/cli/tests/contexts/translate/domain/plugin-content-translator.unit.test.ts index aa9886cbc..89ed0f79b 100644 --- a/cli/tests/contexts/translate/domain/plugin-content-translator.unit.test.ts +++ b/cli/tests/contexts/translate/domain/plugin-content-translator.unit.test.ts @@ -1,5 +1,6 @@ import { describe, expect, it } from "vitest"; import { PluginsCapability } from "../../../../src/contexts/tools/domain/capabilities/plugins-capability.js"; +import { antigravity } from "../../../../src/contexts/tools/domain/profiles/antigravity/profile.js"; import { claude } from "../../../../src/contexts/tools/domain/profiles/claude/profile.js"; import { codex } from "../../../../src/contexts/tools/domain/profiles/codex/profile.js"; import { parseToml } from "../../../../src/contexts/tools/domain/profiles/codex/toml.js"; @@ -308,6 +309,12 @@ describe("PluginContentTranslator.translate()", () => { }); }); + describe("antigravity target (flat mode, one-level skills)", () => { + it("emits a skill as .agents/skills/-/, never nested under the plugin", () => { + expect(pathsFor(antigravity)).toEqual([".agents/skills/sample-plugin-hello/SKILL.md"]); + }); + }); + describe("vscode (IDE tool)", () => { it("returns empty array", () => { expect(translator.translate(makeDist(), vscodeToolConfig)).toEqual([]); diff --git a/cli/tests/e2e/clean-antigravity-foreign-file.e2e.test.ts b/cli/tests/e2e/clean-antigravity-foreign-file.e2e.test.ts new file mode 100644 index 000000000..3f94b5576 --- /dev/null +++ b/cli/tests/e2e/clean-antigravity-foreign-file.e2e.test.ts @@ -0,0 +1,48 @@ +import { existsSync } from "node:fs"; +import { mkdir, readFile, writeFile } from "node:fs/promises"; +import { join, resolve } from "node:path"; +import { describe, expect, it } from "vitest"; +import { createTestEnv, runCli } from "./helpers.js"; + +const FRAMEWORK_REAL_PATH = resolve(process.cwd(), "tests/fixtures/framework-real"); +const OWN_SKILL = "aidd-vcs-01-commit/SKILL.md"; +const FOREIGN_SKILL = "# mine\n"; + +describe("E2E: antigravity under a .agents/ it does not own", () => { + it("clean removes the skills it wrote and leaves a user skill beside them", async () => { + const test = await createTestEnv("clean-antigravity-foreign"); + try { + const skills = join(test.projectDir, ".agents", "skills"); + const foreign = join(skills, "mine", "SKILL.md"); + await mkdir(join(skills, "mine"), { recursive: true }); + await writeFile(foreign, FOREIGN_SKILL); + + const setup = await runCli( + [ + "setup", + "--source", + "local", + "--path", + FRAMEWORK_REAL_PATH, + "--ai", + "antigravity", + "--plugins", + "aidd-vcs", + "--yes", + ], + test.projectDir, + test.fakeHome + ); + expect(setup.exitCode).toBe(0); + expect(existsSync(join(skills, OWN_SKILL))).toBe(true); + + const clean = await runCli(["clean", "--force"], test.projectDir, test.fakeHome); + + expect(clean.exitCode).toBe(0); + expect(existsSync(join(skills, OWN_SKILL))).toBe(false); + expect(await readFile(foreign, "utf-8")).toBe(FOREIGN_SKILL); + } finally { + await test.cleanup(); + } + }); +}); diff --git a/cli/tests/golden/framework-build-golden.e2e.test.ts b/cli/tests/golden/framework-build-golden.e2e.test.ts index 189931fe2..801dbc497 100644 --- a/cli/tests/golden/framework-build-golden.e2e.test.ts +++ b/cli/tests/golden/framework-build-golden.e2e.test.ts @@ -21,7 +21,15 @@ type TargetSnapshot = Record; // rel-path → sha256 type GoldenSnapshot = Record; // key → files const MARKETPLACE_TARGETS = ["copilot", "codex", "claude", "cursor"] as const; -const FLAT_TARGETS = ["claude", "cursor", "copilot", "codex", "opencode", "kilo"] as const; +const FLAT_TARGETS = [ + "claude", + "cursor", + "copilot", + "codex", + "opencode", + "kilo", + "antigravity", +] as const; const FROZEN_CELLS = new Set([ ...MARKETPLACE_TARGETS, @@ -112,7 +120,7 @@ const captures = twoCaptures("fb-golden-det", (env) => captureAllCells(env.projectDir, env.fakeHome, env.tempDir) ); -describe.concurrent("Framework build golden — 10-cell matrix", () => { +describe.concurrent("Framework build golden — 11-cell matrix", () => { afterAll(captures.cleanup); // Two full 9-cell builds measured just past the 60s default on a Windows runner, not a @@ -129,7 +137,7 @@ describe.concurrent("Framework build golden — 10-cell matrix", () => { } }, 120_000); - it("every one of the 10 cells is byte-identical to its stored baseline", async () => { + it("every one of the 11 cells is byte-identical to its stored baseline", async () => { const captured = await captures.first(); if (process.env.UPDATE_FRAMEWORK_GOLDEN === "1") { diff --git a/cli/tests/golden/snapshots/framework-build/golden.json b/cli/tests/golden/snapshots/framework-build/golden.json index f6306566d..463ccc0b0 100644 --- a/cli/tests/golden/snapshots/framework-build/golden.json +++ b/cli/tests/golden/snapshots/framework-build/golden.json @@ -1946,5 +1946,189 @@ ".kilo/agents/aidd-dev-implementer.md": "ec7bd170333d20033679ffdab1976a810067c415dd98b985154f907e2bce78b3", ".kilo/agents/aidd-dev-planner.md": "c832e2830e5148d9cd44daa408636e3e93b914570ea579ee6fc40ec562143495", ".kilo/agents/aidd-dev-reviewer.md": "58b25536395be2be05f14a6213b667011dd16f94fca689cedfc46cda7438e2b9" + }, + "antigravity:flat": { + ".agents/skills/aidd-vcs-04-issue-create/SKILL.md": "3983ed1c71124786fdf737ec5447637a31b1cc146eff43003978cec50c2ddc67", + ".agents/skills/aidd-vcs-04-issue-create/evals/scenarios.json": "d113c62aae4867e425737948c39c9f687f56c3d37234eca345a2f65f1a234c30", + ".agents/skills/aidd-vcs-04-issue-create/assets/CONTRIBUTING.md": "1372c7512c02c26e21643ae523a98d7c90ff92c03633532a8c417b4ca8d9e75f", + ".agents/skills/aidd-vcs-04-issue-create/assets/issue-template.md": "66b4ef6090208512205fce0bb16edbbcf0ffb19009eae76dc317d738c7a10cdc", + ".agents/skills/aidd-vcs-04-issue-create/actions/01-issue-create.md": "e3f60414fa3ce2d78583e5cb118dad8df956927aa86e2f6e802031a7d742d7ed", + ".agents/skills/aidd-vcs-03-release-tag/SKILL.md": "0052405cdf2deeb07b123e8f94ee47a2b4ecea6031641315ce71fc6cff3faabb", + ".agents/skills/aidd-vcs-03-release-tag/evals/scenarios.json": "fdba5d61f815b956512f84baf712cde92be651593282b43ac173b2459ee9eca3", + ".agents/skills/aidd-vcs-03-release-tag/assets/release-template.md": "bce05178ae5ea7da6c356849ba69eebd249ba344c94a3c571d7d40be9d4e9e27", + ".agents/skills/aidd-vcs-03-release-tag/actions/01-release-tag.md": "b2f6c6cce5f7c83f83e4b2fd84c7aae3888bdfcd29f1be3d3e2bc362b18eae6b", + ".agents/skills/aidd-vcs-02-pull-request/SKILL.md": "80c8d4f2dc28e4d1b67d418c12a865ba74ffc8b57720eb05bdf1e1fca3b82a6a", + ".agents/skills/aidd-vcs-02-pull-request/evals/scenarios.json": "63f63e6b13038672fbe325509deb32a9b25a50a996b687c001dfc74815a0dccd", + ".agents/skills/aidd-vcs-02-pull-request/assets/CONTRIBUTING.md": "1372c7512c02c26e21643ae523a98d7c90ff92c03633532a8c417b4ca8d9e75f", + ".agents/skills/aidd-vcs-02-pull-request/assets/README.md": "fb774bb7e5a19a39b21619879ee5045d7b3a0952be3f4b61aea524b93e9c9086", + ".agents/skills/aidd-vcs-02-pull-request/assets/branch.md": "92880244478c3e48c4f105e3ab2f9c09850778f84f1e44108ff8e912ccb0bea3", + ".agents/skills/aidd-vcs-02-pull-request/assets/pull_request.md": "66939eaae42c729f3b05f8dcc2546a1e4227441d9d156d692fc7ced89d07a11f", + ".agents/skills/aidd-vcs-02-pull-request/actions/01-pull-request.md": "3c3753569b5f336e6c53ac8312ba6f9538ccc711e16fb7847ca1ebf04250a790", + ".agents/skills/aidd-vcs-01-commit/SKILL.md": "1825b3994a89c0394271c3437a772e74892e4bfb5fac9c2dbc90739544404585", + ".agents/skills/aidd-vcs-01-commit/evals/scenarios.json": "bdefba21f63f7ef737ff3431077278cc45b722e85ba4b66e3a4adf4d34e0b2ff", + ".agents/skills/aidd-vcs-01-commit/assets/commit-template.md": "b3c392c5c3faecc903bf80eb2bd287d2a02e42d4c3896490a5745d66fca60bf2", + ".agents/skills/aidd-vcs-01-commit/actions/01-commit.md": "06ded7f8c23e950b3b1a76b0bb89c219d1ca161cb8f9a02e04e8083ad7061dc0", + ".agents/skills/aidd-refine-03-condense/SKILL.md": "2d6db1071f826db1cf030fb8a34aeaf0adc79884a29750efa9fe378ab12d8593", + ".agents/skills/aidd-refine-03-condense/references/intensity-levels.md": "8e6aa26fc675a2d30dbb4df13e68d0ce6d2844c88bafd39e0ea3f2945e3276f1", + ".agents/skills/aidd-refine-03-condense/evals/scenarios.json": "c03016b5e98c5a9a8b6680035bf8ee0f2a75edc5ce33e33cf1e1430cd91f55ec", + ".agents/skills/aidd-refine-03-condense/actions/01-condense.md": "bffb26d5a306bc90f40614b29ca21e9a2151dc5ba82880c857e29c39486a16fd", + ".agents/skills/aidd-refine-02-challenge/SKILL.md": "0e05299821bb3e41834cd8ff97ffa23f48342974b4e20c1e9ea59ce83255eed8", + ".agents/skills/aidd-refine-02-challenge/references/confidence-rubric.md": "714f1bfd0c33f2adf911c93e9bb2f113be9fd29bad07d83e54be293fc44de823", + ".agents/skills/aidd-refine-02-challenge/evals/scenarios.json": "332403843b5b5d99a9dcb3464f298ca3b7274edbd1b5c337407a4dd977bc19a5", + ".agents/skills/aidd-refine-02-challenge/actions/01-challenge.md": "6f47920ccec8682708789e07583e32698695f1274bf883757ab13fca6f3e76c0", + ".agents/skills/aidd-refine-01-brainstorm/SKILL.md": "e29675b330838d6bd843f40c227fd64ae46e9162c3c3b5db4d8695d9f6b38f17", + ".agents/skills/aidd-refine-01-brainstorm/references/ambiguity-detection.md": "c869338af0d8e0bdde5915000ce44980fef750cc40d070d4055bcba4c1334e0f", + ".agents/skills/aidd-refine-01-brainstorm/evals/scenarios.json": "c6858f3b6b8efa666e2c7f21f4325f5e57297e3fdfc475b836f3ce5de9d635bf", + ".agents/skills/aidd-refine-01-brainstorm/assets/question-templates.md": "bd744429e69f26caf37359e2b1d1c38dc4faba1a2c54dfaa99bfde48da0dc3c8", + ".agents/skills/aidd-refine-01-brainstorm/actions/01-capture-request.md": "bc133963be4eeb68d6653a65f88f2e1820d711d6d7bf74b79a6225fff7b8e6c8", + ".agents/skills/aidd-refine-01-brainstorm/actions/02-ask-probing-questions.md": "d8a25b7e2c0861bc4bce878e7d10dcda279855fa0db06d1997ad84f3e5682d54", + ".agents/skills/aidd-refine-01-brainstorm/actions/03-integrate-answers.md": "b6f2c34f4f03cca48e83ed6fff5936e8ddbecae5fef1f597e99fd764e13f32cd", + ".agents/skills/aidd-refine-01-brainstorm/actions/04-refine-and-validate.md": "4a6368a3e189c3022f133050f996eda3d9195281a3e4bbabdd120f5b8b414c3c", + ".agents/skills/aidd-refine-01-brainstorm/actions/05-confirm-approval.md": "3c6c8184910a210bb6698c2c010aebec88a28313b31bbed0ef9c1189530cd6fe", + ".agents/skills/aidd-pm-05-spec/SKILL.md": "32a14c6e9b275630363a0cca29ebd3c6c35271fa38be9eb79cc0e41e8e6e478b", + ".agents/skills/aidd-pm-05-spec/assets/spec-template.md": "2f7e446c4ec58d05a471212e9ab3ba64395bdd059943620d02bf1bf5c98777c2", + ".agents/skills/aidd-pm-05-spec/assets/spec-validator.yml": "2358c6f0baa5656ddcff9410149cf8261d54199d8722f77769f76ca0d7c8cf18", + ".agents/skills/aidd-pm-04-clarity/SKILL.md": "5f50dcfdf1c96f1ce71eed10dd791e1a2945eb9e761e8d42beca3519370ba55b", + ".agents/skills/aidd-pm-03-prd/SKILL.md": "b3ac6ea89c9642651135cb9f7dea8bdf47fb3bc4ea777216d2f65b5459dd0c6b", + ".agents/skills/aidd-pm-03-prd/assets/prd-template.md": "af01423720e8c9527ff12a2e9ad1de39c6e63c6569359be08defd0a059aabea9", + ".agents/skills/aidd-pm-03-prd/assets/task-template.md": "c0069ed2ecce4742629dccd6be709d4e5a71f3db3821310c381c054aeb479289", + ".agents/skills/aidd-pm-03-prd/actions/01-prd.md": "c0da6597f8c465ad50a17a07543152f53de1fbccc28cd906763d439004084a22", + ".agents/skills/aidd-pm-02-user-stories-create/SKILL.md": "d95bad3cfd000c1e8dba905a152384320a177f5891bdaa218819ffe55f6ad710", + ".agents/skills/aidd-pm-02-user-stories-create/assets/user-story-template.md": "59ecd98d00057313414a74a94c19c0f2d167957e1080b880ee68a69a31948320", + ".agents/skills/aidd-pm-02-user-stories-create/actions/01-create-user-stories.md": "6fad8968a1e787c197764c4a8c09b17c521ae5ea2286a5082addc717bd72b1e6", + ".agents/skills/aidd-pm-01-ticket-info/SKILL.md": "26e0491f8527a073f6685ffe7c3e15a22f230a22837aea92694d34a7a2f2f29d", + ".agents/skills/aidd-pm-01-ticket-info/actions/01-ticket-info.md": "f075ea6ff626534dbe0826d06b67fae93b6488ec4c5c7a3d857f1d05f73d0630", + ".agents/skills/aidd-dev-08-for-sure/SKILL.md": "07a94d0a975a8b6f4e2be89b3fd9d68b9b71bd9843836c76e1aa074685cc4376", + ".agents/skills/aidd-dev-08-for-sure/actions/01-init-tracking.md": "3ce6bb19135e5d31c4d2ad1c2151c1563886a611f2399b47624368268a79dcdf", + ".agents/skills/aidd-dev-08-for-sure/actions/02-auto-accept.md": "235818118c772c0f499ea0668bfbc7bd00c2aae34e6bbd8b13b912fddfa79705", + ".agents/skills/aidd-dev-08-for-sure/actions/03-autonomous-loop.md": "b19d4520ad716def3c2a4611c37725cba9af713cd4bcb668c237e874e1e43843", + ".agents/skills/aidd-dev-07-debug/SKILL.md": "860f4137ca48a43470d52f76ae8e631204e5fd7dd82a661905d40bd583bf56fc", + ".agents/skills/aidd-dev-07-debug/references/mermaid-conventions.md": "85826285744909dd4c4706b82f0dbeff4f88a8f191cb22d538aa12c3c96365eb", + ".agents/skills/aidd-dev-07-debug/assets/task-template.md": "c0069ed2ecce4742629dccd6be709d4e5a71f3db3821310c381c054aeb479289", + ".agents/skills/aidd-dev-07-debug/actions/01-reproduce.md": "7e902e5aadc9162b444b341deb33f2a683000c1cc4722ad856496f9d740ec17d", + ".agents/skills/aidd-dev-07-debug/actions/02-debug.md": "dc3900ce6fb76074b88034d7b7316f4051d78a1828caa3acef9786e8ea582b18", + ".agents/skills/aidd-dev-07-debug/actions/03-reflect-issue.md": "3d5f18634618737870da4c783c7525cee25e09d942faee80125dd7536978ce12", + ".agents/skills/aidd-dev-06-refactor/SKILL.md": "6fbf8c363728a11288c58f6967d36845aa4709a9a69a62bd6c6d9ca6d39135ba", + ".agents/skills/aidd-dev-06-refactor/actions/01-performance.md": "1f7d500967de58875aeb14b732a1af377b17edff8654f4365b3f29743debb3c7", + ".agents/skills/aidd-dev-06-refactor/actions/02-security.md": "db55b81ab824d81b765189296deb1ce31fdb134edac186baf22a2c3bb1cde938", + ".agents/skills/aidd-dev-05-test/SKILL.md": "131c810499c0673ace51a2385640dc533203ca89560f72a381bef4a2abf9d388", + ".agents/skills/aidd-dev-05-test/actions/01-test.md": "f6db9653cd29729c51653df7afe473fae4ae45bb126a694a367bfd84fa1785a4", + ".agents/skills/aidd-dev-05-test/actions/02-test-journey.md": "1eeb85da69abc3f1fc3e5671e83962f44fcb5686d9ca5972eeb2ba4cab31306d", + ".agents/skills/aidd-dev-04-review/SKILL.md": "87b2de872bd37fa19b12f458f6c30d60ca65de8ce2988dc63d0811cdec4720cc", + ".agents/skills/aidd-dev-04-review/assets/code-review-template.md": "e270c4b6b8c69e4fbbbcc08c2f91cc9a09fc8ad155b6f52cec3e3d34a262324d", + ".agents/skills/aidd-dev-04-review/assets/review-functional-template.md": "a84b48347caf4d09d84994b07329e4d05c8053d45630b37d47ca3ccb386e3146", + ".agents/skills/aidd-dev-04-review/assets/review-template.md": "b0ad0ab703e4d9ed960bd324bc37efe5f682cbcd40ed6293791d9074f174201c", + ".agents/skills/aidd-dev-04-review/actions/01-review-code.md": "8d5e4fc6c9243a83025961ae40146f1d96f321d9ff4d93ab74c370bbc53c18ed", + ".agents/skills/aidd-dev-04-review/actions/02-review-functional.md": "9e5d837715f5610a42f1c294b0fa71277538e2629dc47cf09675487dcfa98324", + ".agents/skills/aidd-dev-03-audit/SKILL.md": "da84a82a3446283034f26ad5a335d7f8a48ecd501c254af2f4ddaccd09cd4e0f", + ".agents/skills/aidd-dev-03-audit/actions/01-audit.md": "0371ae3d0c9a383f8b90657f3381717f323742bad922df2c1c508f6db013267e", + ".agents/skills/aidd-dev-02-assert/SKILL.md": "a75c5fe87f257fe476129d8eb5b89267df6563924e5c84bb4ea6c1d7215b14ad", + ".agents/skills/aidd-dev-02-assert/assets/task-template.md": "c0069ed2ecce4742629dccd6be709d4e5a71f3db3821310c381c054aeb479289", + ".agents/skills/aidd-dev-02-assert/actions/01-assert.md": "b8a9680e7cf956f2e1ff7720f755586e2523d6951c35ec686ded659f7fa3a4b1", + ".agents/skills/aidd-dev-02-assert/actions/02-assert-architecture.md": "e5d7effa39f33c045e4d605ed909d46ddf7562e2a0199eb2e39fcc8f3fc92620", + ".agents/skills/aidd-dev-02-assert/actions/03-assert-frontend.md": "ed9751b32580c7fd3fbe02a8205cbb5d85a8e7bc38f982b5130b810950bbd091", + ".agents/skills/aidd-dev-01-plan/SKILL.md": "272aaa80349b438a1536c78e91d5c55c1ef26b65f942caa1c47edc062e0591c9", + ".agents/skills/aidd-dev-01-plan/references/mermaid-conventions.md": "85826285744909dd4c4706b82f0dbeff4f88a8f191cb22d538aa12c3c96365eb", + ".agents/skills/aidd-dev-01-plan/assets/master-plan-template.md": "f793f2bc8fad34f056e824def49527552f50cf12e43f0929731d64278376b822", + ".agents/skills/aidd-dev-01-plan/assets/plan-template.md": "cf462e831d994230c811c71eaa72a8f9880536ba3b657b70342083dee00ab254", + ".agents/skills/aidd-dev-01-plan/assets/tech-choice-template.md": "c9f16f9b598aded5953127b4bf2768b95d7abda0b75d937e0b31dfee9317c40a", + ".agents/skills/aidd-dev-01-plan/actions/01-plan.md": "c480fc1cffe39f117003b5aeea23e10f808952a0eeea067dbc0ab44c0af992ff", + ".agents/skills/aidd-dev-01-plan/actions/02-components-behavior.md": "cacb673334d8947c3c252feaaf0ef50c269c06c5cf8f51b45663cbd42402e094", + ".agents/skills/aidd-dev-01-plan/actions/03-image-extract-details.md": "6f6cddd0893888c71b0aed5ecfe48217afe0eef10feec6415db433bfcc69f8e3", + ".agents/skills/aidd-dev-00-sdlc/SKILL.md": "34964c02e338adbf393c25abc4f5862e28452f874c7640f85c950e170130c17c", + ".agents/skills/aidd-context-06-discovery/SKILL.md": "45cf7373cddfe1614b6edba32b36a9411d9136b8eab29a3ae9b43dab7ee4e912", + ".agents/skills/aidd-context-06-discovery/actions/01-find-skill.md": "600caf7822017bfe75d40773e1673ada784227b98f5b88d4ba98a57822fa0dcf", + ".agents/skills/aidd-context-05-learn/SKILL.md": "9ca8b41c8a8fac66d943ad1658ae7a2f374373d13a8a2edd66e1f7fd058694de", + ".agents/skills/aidd-context-05-learn/assets/adr-template.md": "1f9feb18109b178226885ab7edabc3d1c4dd2a77dd1fa7345be856643107ac16", + ".agents/skills/aidd-context-05-learn/assets/decision-template.md": "1e6229157fd0a07c090ce0bd159336a13554975e146966b306bf25665a179938", + ".agents/skills/aidd-context-05-learn/actions/01-learn.md": "92400bfcc2cdfe9f0bad8f2feadd7d28dce882240fe58971d25dfa880521d8bb", + ".agents/skills/aidd-context-04-mermaid/SKILL.md": "d25ac37dc7e065a1bf6eafc5a49dfaef14af85cb947e26095d6b376a32c3b416", + ".agents/skills/aidd-context-04-mermaid/references/mermaid-conventions.md": "85826285744909dd4c4706b82f0dbeff4f88a8f191cb22d538aa12c3c96365eb", + ".agents/skills/aidd-context-04-mermaid/actions/01-mermaid.md": "f70b18f429701d0c3b46a4c6e75153c732ee9e06a16aa01efec05e810ac7a6dd", + ".agents/skills/aidd-context-03-context-generate/SKILL.md": "e0f9b6c4a2eadc331c5611e1cc18c74d10633e94180bf56a6fee5aed6f984f07", + ".agents/skills/aidd-context-03-context-generate/references/agents-coordination.md": "eaaa31ed554a53f7870151307853e1add7e3ddd1de8e442b8d5e7c5698ab0098", + ".agents/skills/aidd-context-03-context-generate/references/ai-mapping.md": "4ad192b5ae53c7ceadff165a1cef8c3f24949498375bedc0cc359194dea3117e", + ".agents/skills/aidd-context-03-context-generate/references/naming-conventions.md": "fd39d00b1449f0770ee89aad9f6e84d21e1fee2efc0ff547a240b0e18f648cd8", + ".agents/skills/aidd-context-03-context-generate/references/rule-structure.md": "c8ce6eea6dbaae5309a77237d702db193a7748db5c581d0918f17db3989e414f", + ".agents/skills/aidd-context-03-context-generate/references/rule-writing.md": "e4cddd88dca3162d576a1b616c419f5ff2f3b7681a09a569da68358f3713c9e6", + ".agents/skills/aidd-context-03-context-generate/references/skill-structure.md": "2b6114ff8aa30f1a5d7bfd3bf0d7d87e429cb8d65ba13b369e6cdc9c8bec501c", + ".agents/skills/aidd-context-03-context-generate/evals/scenarios.json": "db355f13f2ca3499f912f39ada136472f9bc993846564aeaeb6eab371d485520", + ".agents/skills/aidd-context-03-context-generate/assets/skills/action-template.md": "8bd80e05dcbb10e24adb668778fd9dfb058c92193c52c6012e9f5cc11305103c", + ".agents/skills/aidd-context-03-context-generate/assets/skills/evals-template.md": "c0468fe9869895b42f7a59b48f7613ca4ef3b1af93acb7c41dc063559964ff27", + ".agents/skills/aidd-context-03-context-generate/assets/skills/skill-template.md": "f30bc5a47574cc85dc0daddfa1abd0d7f8bd8a39e201fbb3df5784506a61b13a", + ".agents/skills/aidd-context-03-context-generate/assets/rules/rule-template.md": "200ec56d8ed43acd51b94df783f7de9236b00a6e5398a98a30640d02edb76ba1", + ".agents/skills/aidd-context-03-context-generate/assets/agents/agent-template.md": "476802721ac4947d741b4bd17626860c4914037992a2ebf0ef282553f819aa25", + ".agents/skills/aidd-context-03-context-generate/actions/skills/01-capture-intent.md": "850c9b7dab101eba66284950eb3b8b612aca3ad40a1b13fcdc5ac181d1f72069", + ".agents/skills/aidd-context-03-context-generate/actions/skills/02-design-evals.md": "fc51fb2c85a7462fe27586ddc3cc5c2699214dcd059c28f7ab8daf26a6479ced", + ".agents/skills/aidd-context-03-context-generate/actions/skills/03-decompose-actions.md": "8bf7ff1ac05d968fc7a3e9b6aa5de3df3be04b07e96517cbe06268baec4151fc", + ".agents/skills/aidd-context-03-context-generate/actions/skills/04-draft-skill.md": "08946a56b720408bee9c63cd7141e6c869e6818610546f8a42628fe6d2e12514", + ".agents/skills/aidd-context-03-context-generate/actions/skills/05-write-actions.md": "948ac436af5e9c9e897b8c4e44b12a278943bff2b93afc10b4ddaee587d58cc5", + ".agents/skills/aidd-context-03-context-generate/actions/skills/06-validate.md": "658c1077b03c461bb0c3fa17db047e4d2fbc3874f4d4fbd2e3dc5df606aa03ee", + ".agents/skills/aidd-context-03-context-generate/actions/rules/01-generate-rules.md": "855b9adf65b0b0cc31ca15c68b8ac37da6a816c9a6416fbc3aea424cffa238c3", + ".agents/skills/aidd-context-03-context-generate/actions/agents/01-generate-agent.md": "3dce30335eba1d60c5a62d43184d133eed621bbc344e9277ab0f05ead443e0ba", + ".agents/skills/aidd-context-02-project-init/SKILL.md": "4bd40beb8290018b6fa58b651d3681e13465b39b84c3e4c0c39964aefcc52002", + ".agents/skills/aidd-context-02-project-init/references/mapping-ai-context-file.md": "cf251454634037d4affd1d27cee2593c555dfe29305b521d3bed4564f4cad273", + ".agents/skills/aidd-context-02-project-init/assets/AGENTS.md": "8b3d349220e9f96b45dedf316eeb2b3418666d96d0a4f64d315cadfc9ef337ae", + ".agents/skills/aidd-context-02-project-init/assets/GUIDELINES.md": "bf41995402b46268ba53ceec41be00c455004603f69035349b6ff3bef14cc18a", + ".agents/skills/aidd-context-02-project-init/assets/README.md": "575086cda27a386806a28c1bb4865cb5f974dc3fe6e39ca8bdd2c19764cf2059", + ".agents/skills/aidd-context-02-project-init/assets/golden-principles.md": "e83c582a9e1477a42531734deed90061f4e004ea28e1b1616bf714070856c888", + ".agents/skills/aidd-context-02-project-init/assets/templates/workflows/README.md": "33d50760dfde22feb8878cf3e24d399a748eef8ed2df630f465d673d9f125a53", + ".agents/skills/aidd-context-02-project-init/assets/templates/stack/README.md": "8dbccfc7d47424411b7725374247b50973a962fff777b272a23109b7e0f2a099", + ".agents/skills/aidd-context-02-project-init/assets/templates/memory/README.md": "66e6275cd7e7187ca520ad8f2f3c57ebecf967742901e00085c139fd9eea5892", + ".agents/skills/aidd-context-02-project-init/assets/templates/memory/architecture.md": "f00c02db4135a43e99f5bdd7888f40f290e39dd4a65bb1539eca9a06c9873427", + ".agents/skills/aidd-context-02-project-init/assets/templates/memory/codebase-map.md": "5d1d184bc70c93e0073cb64312f4fa8d50fcf465b0da982f37bde25d624a5386", + ".agents/skills/aidd-context-02-project-init/assets/templates/memory/coding-assertions.md": "2e3d7b16f85e8af3cf9971c3b0c23409653e8d693c969ff11892c5a4f10bea90", + ".agents/skills/aidd-context-02-project-init/assets/templates/memory/deployment.md": "361e80ebbddda1f0497856530e7a5a35cd2c8ae56df2a6ef64268657de333d28", + ".agents/skills/aidd-context-02-project-init/assets/templates/memory/project-brief.md": "0240ee41e05c6091bd136b7c3b18f603731128941c987449917886b191e38e90", + ".agents/skills/aidd-context-02-project-init/assets/templates/memory/testing.md": "760e58855270ee9210b20987b3f01b537b25f6108f8d164e2a88000f4cb278b7", + ".agents/skills/aidd-context-02-project-init/assets/templates/memory/vcs.md": "3eccc51898b8c0c17245a737c9fa540f30706676ea01ff771d6898f3861060f1", + ".agents/skills/aidd-context-02-project-init/assets/templates/memory/frontend/browsing.md": "f8ab234ba45571979a5dbc2181859a8f6859cd56c9d86371560516b3323dd8a1", + ".agents/skills/aidd-context-02-project-init/assets/templates/memory/frontend/design.md": "edcaa8ab125acd64d330a32abac102fa3d0b37b45ef03a9552c71c885c1ea847", + ".agents/skills/aidd-context-02-project-init/assets/templates/memory/frontend/forms.md": "662dd1b62a9d3fef587cf38f1c2f6fe3de2d0cb226a9ec50dd7b576bf824115b", + ".agents/skills/aidd-context-02-project-init/assets/templates/memory/backend/api-docs.md": "09b9633c1d8c3b5a11bf0d171abf148bf159836ae96223a78a52b9e56ffb52e8", + ".agents/skills/aidd-context-02-project-init/assets/templates/memory/backend/backend-communication.md": "87d548e12bed0fa2a2d7601fe1c36d810f40051abd713cc3d4462582319babde", + ".agents/skills/aidd-context-02-project-init/assets/templates/memory/backend/database.md": "899ceb107598c8b9a1adc3ff7cf8b6217e4582553f5c27f3e22b9e7fb0f555b9", + ".agents/skills/aidd-context-02-project-init/actions/01-init-context-file.md": "33c907084418ec22f8c22381fbefecfbf3d88c250781c852059bf611be0f2ae2", + ".agents/skills/aidd-context-02-project-init/actions/02-scaffold-docs.md": "c07f175692241d477b029441a2a984c2fff6de0041ff6f47a6851d73900a1ac3", + ".agents/skills/aidd-context-02-project-init/actions/03-generate-memory.md": "f7cde767985a1d7ca8c2d74c1ffcc11eac2232a97e26b46573533eefcdd5ac96", + ".agents/skills/aidd-context-02-project-init/actions/04-review-memory.md": "211ad0ad0d7c4925acbd8a41f775a16b09de51b85d9f3fc6f972170ac63fd353", + ".agents/skills/aidd-context-02-project-init/actions/05-init-rules-skeleton.md": "12924f4535c083e114a5a4a4ef148e4aa2c8c4c436d23c8f50e152f869e3be2c", + ".agents/skills/aidd-context-02-project-init/actions/06-sync-memory.md": "3f6825c8d230cea72f729aee15cf6136b613b7a9b07ddec237b91fe5ad3be59b", + ".agents/skills/aidd-context-01-bootstrap/SKILL.md": "be11990b997d32ff2acb7c49eba7d63899ffbe161827e4c25d330debace71805", + ".agents/skills/aidd-context-01-bootstrap/references/stack-heuristics.md": "17f7c0df7b19090f42c26ddf70df1049897b3973d9c1bf8df358b87a1cc9985a", + ".agents/skills/aidd-context-01-bootstrap/evals/scenarios.json": "c5998a91c584d561618dbabe66f21bd8f446260731778de4f467a70a6da21784", + ".agents/skills/aidd-context-01-bootstrap/assets/checklist.md": "64b84a7712ca78bc1901d2c78c183310336a4abe1b5890a689c3cf166b026d21", + ".agents/skills/aidd-context-01-bootstrap/assets/install-template.md": "cd67115a7b11fddea0258810b9cee1d35c3e12840f515c5364fa769cad9d6a60", + ".agents/skills/aidd-context-01-bootstrap/actions/01-gather-needs.md": "b3251034285434b06775ab4a59ffe27259bb23cb97ac65927c946a4846ea946d", + ".agents/skills/aidd-context-01-bootstrap/actions/02-propose-candidates.md": "57376071d9d0b011c5f19ba218a3ef28c3bf1fba1220a87260806a2d79e341d6", + ".agents/skills/aidd-context-01-bootstrap/actions/03-audit-candidates.md": "def30caedeca0babb4500b2081fe46bdcaf72ee911e4ad94ae860b068c2ee0e0", + ".agents/skills/aidd-context-01-bootstrap/actions/04-pick-and-design.md": "1444ede4a16e6df368792d7f214e0bb61912621a9c266205e031ec73151606e7", + ".agents/skills/aidd-context-01-bootstrap/actions/05-write-install-md.md": "c94afb152683cc29f10d2d4451632265b7fdc763a8a41b69f8ddc2c00b81dfd0", + ".agents/skills/aidd-async-dev-03-review/SKILL.md": "12577d34b7e03ac89407075bbeb998925e9d1043620913767d390d1153151e39", + ".agents/skills/aidd-async-dev-03-review/references/stop-conditions.md": "47280ec7ebb0bd2d25bb7bf7dd716f5ef41a0ad8163653fa139981b5283e7ba0", + ".agents/skills/aidd-async-dev-03-review/evals/scenarios.json": "562d462eedcb589487585a6840d3077a8cce760ea4ecd5e416a8a01b8472d995", + ".agents/skills/aidd-async-dev-03-review/actions/skills/01-collect-comments.md": "a7494fcd9ec705cf76d36ed9b7ce59b2d9ed6e82ce64084e2ed935d52ab57527", + ".agents/skills/aidd-async-dev-03-review/actions/skills/02-detect-stop.md": "b5d40992028d2e0a1d59f31dd699237005ebb51d9c70a648aca7f757f7449659", + ".agents/skills/aidd-async-dev-03-review/actions/skills/03-fix-iteration.md": "a3133409542ed6fc8e9b814658be68504e646cc44ed58ca749251c6b6dd45f25", + ".agents/skills/aidd-async-dev-03-review/actions/skills/04-finalize.md": "bf01c103e3940fda6fd07092bd35f44993f24cdd25ec7ff5c52004b20a37cb17", + ".agents/skills/aidd-async-dev-02-run/SKILL.md": "63fa98e5a2e3baaa8a4b1e55fa894f4d98fe47e4dbdc0ae5be2c6c5d7f894e01", + ".agents/skills/aidd-async-dev-02-run/evals/scenarios.json": "cf78e4c46ac988912a7de98b1a1143c439d3c744bef234d7294f17b4836a84d0", + ".agents/skills/aidd-async-dev-02-run/actions/skills/01-poll-ready.md": "38b0767d080c60149fc4dd1b04395c72b6cda204bd2152d2f9be54db814626fd", + ".agents/skills/aidd-async-dev-02-run/actions/skills/02-resolve-deps.md": "9ede89ff3396287bde2ed9fed6e8068a8411b9190dc446ec392badaf31a52a5f", + ".agents/skills/aidd-async-dev-02-run/actions/skills/03-acquire-lock.md": "0a9b54f4bc9ebdb653932b57bd9d61e9ee7248d86104bc95841617beb4eeeee5", + ".agents/skills/aidd-async-dev-02-run/actions/skills/04-check-sdlc.md": "1934b9f0fb5824736349070ceca67105892ef7e4f319ca95ae955f564e821633", + ".agents/skills/aidd-async-dev-02-run/actions/skills/05-delegate-sdlc.md": "3142a7d2ee15d1871fecc1b4977ff3fb43b9a343f25913a54cc943d9fab31a7c", + ".agents/skills/aidd-async-dev-02-run/actions/skills/06-write-audit.md": "3cfbabf8f6c6e267994afcf189d17234eac4c3bb35ab68408776f1acb644b6ba", + ".agents/skills/aidd-async-dev-02-run/actions/skills/07-emit-webhook.md": "4c8fe76bdf265386247a636dafc9365946114afa54c479ef13abfd929d7af3db", + ".agents/skills/aidd-async-dev-01-setup/SKILL.md": "ce55273c1d1109267f8725edfeb654df9ad4c84e9055998b0e29f9817c6befa3", + ".agents/skills/aidd-async-dev-01-setup/references/auth-modes.md": "4122f895e4b210fce0e8f4cac81a79f6711eca56edac02ac43235359a77c2f62", + ".agents/skills/aidd-async-dev-01-setup/evals/scenarios.json": "79b49984a0999a3f34348bedbf8e7fe815ebdddf825701fabfedbe39f7a1ede2", + ".agents/skills/aidd-async-dev-01-setup/assets/config-template.json": "d47d1d93014157d525f48f3c96626472d940fafffc0deb106af2371db97b4e83", + ".agents/skills/aidd-async-dev-01-setup/assets/workflow-template.yml": "63be55459862cf9c1f9943ac48dde6f1304a0fd3c3231b608e2217f8ee785d3f", + ".agents/skills/aidd-async-dev-01-setup/actions/skills/01-detect-context.md": "83f3ba3022dd75fa2597a872a55e8f75173762f932ed39ae73d8181c0b1b4536", + ".agents/skills/aidd-async-dev-01-setup/actions/skills/02-ask-config.md": "986335d1e86a633d0083fe4488aae58266a82ec35d0cdb0b74f0bbcea70e6f4e", + ".agents/skills/aidd-async-dev-01-setup/actions/skills/03-generate-workflow.md": "11f7ec6c03284d0524179f71337691301a6362cf77bf3aa666fe41686b4df40b", + ".agents/skills/aidd-async-dev-01-setup/actions/skills/04-write-config.md": "eb7ecb812e8bdaaeba2e56c71c77bf8c14fce0a2c44311ff7985444617635dd5", + ".agents/skills/aidd-async-dev-01-setup/actions/skills/05-bootstrap-labels.md": "f53177ce1c58767f1bdfcfa3e72f7d4cc5e3d4fd782c35c3998815317be108b1" } } diff --git a/cli/tests/golden/snapshots/help/surface.json b/cli/tests/golden/snapshots/help/surface.json index 449726e32..d34a66563 100644 --- a/cli/tests/golden/snapshots/help/surface.json +++ b/cli/tests/golden/snapshots/help/surface.json @@ -182,7 +182,7 @@ { "invocation": "aidd translate", "exitCode": 0, - "help": "Usage: aidd translate [options] \n\nConvert an arbitrary source into a target-native plugin tree — records nothing\n(see `sync` for the manifest-driven, tracked version)\n\nArguments:\n source Path to the source framework directory\n\nOptions:\n --to Conversion target (claude, cursor, copilot, codex,\n opencode, kilo)\n --out Output directory (marketplace dist or project root)\n --as Output layout (default: \"marketplace\")\n --force Overwrite existing files at canonical paths under\n --out\n -h, --help display help for command" + "help": "Usage: aidd translate [options] \n\nConvert an arbitrary source into a target-native plugin tree — records nothing\n(see `sync` for the manifest-driven, tracked version)\n\nArguments:\n source Path to the source framework directory\n\nOptions:\n --to Conversion target (claude, cursor, copilot, codex,\n opencode, kilo, antigravity)\n --out Output directory (marketplace dist or project root)\n --as Output layout (default: \"marketplace\")\n --force Overwrite existing files at canonical paths under\n --out\n -h, --help display help for command" }, { "invocation": "aidd update", diff --git a/cli/tests/golden/snapshots/phase0/snapshot.json b/cli/tests/golden/snapshots/phase0/snapshot.json index 325cc6a4e..a82f58127 100644 --- a/cli/tests/golden/snapshots/phase0/snapshot.json +++ b/cli/tests/golden/snapshots/phase0/snapshot.json @@ -800,7 +800,7 @@ "command": "[errors] framework install --tool not-a-tool", "exitCode": 1, "stdout": "", - "stderr": "Error: Unknown tool: not-a-tool. Valid tools: claude, cursor, copilot, opencode, kilo, codex, vscode\n", + "stderr": "Error: Unknown tool: not-a-tool. Valid tools: claude, cursor, copilot, opencode, kilo, codex, antigravity, vscode\n", "filesWritten": [], "manifest": null }, diff --git a/cli/tests/helpers/ports/build-unit-deps.ts b/cli/tests/helpers/ports/build-unit-deps.ts index 266d86bf3..5e0ad8e4b 100644 --- a/cli/tests/helpers/ports/build-unit-deps.ts +++ b/cli/tests/helpers/ports/build-unit-deps.ts @@ -4,6 +4,7 @@ import "../../../src/contexts/tools/domain/profiles/claude/profile.js"; import "../../../src/contexts/tools/domain/profiles/codex/profile.js"; import "../../../src/contexts/tools/domain/profiles/copilot/profile.js"; import "../../../src/contexts/tools/domain/profiles/cursor/profile.js"; +import "../../../src/contexts/tools/domain/profiles/antigravity/profile.js"; import "../../../src/contexts/tools/domain/profiles/kilo/profile.js"; import "../../../src/contexts/tools/domain/profiles/opencode/profile.js"; import "../../../src/contexts/tools/domain/profiles/vscode/profile.js"; diff --git a/cli/tests/presentation/commands/translate-wiring.integration.test.ts b/cli/tests/presentation/commands/translate-wiring.integration.test.ts index 5fb23b017..8d593c36e 100644 --- a/cli/tests/presentation/commands/translate-wiring.integration.test.ts +++ b/cli/tests/presentation/commands/translate-wiring.integration.test.ts @@ -5,6 +5,7 @@ import "../../../src/contexts/tools/domain/profiles/claude/profile.js"; import "../../../src/contexts/tools/domain/profiles/codex/profile.js"; import "../../../src/contexts/tools/domain/profiles/copilot/profile.js"; import "../../../src/contexts/tools/domain/profiles/cursor/profile.js"; +import "../../../src/contexts/tools/domain/profiles/antigravity/profile.js"; import "../../../src/contexts/tools/domain/profiles/kilo/profile.js"; import "../../../src/contexts/tools/domain/profiles/opencode/profile.js"; import "../../../src/contexts/tools/domain/profiles/vscode/profile.js"; @@ -211,7 +212,7 @@ describe("aidd translate — the help surface", () => { ).toEqual([ [ "--to ", - "Conversion target (claude, cursor, copilot, codex, opencode, kilo)", + "Conversion target (claude, cursor, copilot, codex, opencode, kilo, antigravity)", undefined, ], ["--out ", "Output directory (marketplace dist or project root)", undefined], diff --git a/cli/tests/presentation/prompts/interactive-menu-use-case.unit.test.ts b/cli/tests/presentation/prompts/interactive-menu-use-case.unit.test.ts index fa4015ae1..96e5fd9ad 100644 --- a/cli/tests/presentation/prompts/interactive-menu-use-case.unit.test.ts +++ b/cli/tests/presentation/prompts/interactive-menu-use-case.unit.test.ts @@ -524,11 +524,11 @@ describe("interactive menu — the command each pick hands over", () => { it.each([ [ ["inspect", "doctor-tool"], - "Tool (e.g. claude, cursor, copilot, codex, opencode, kilo, vscode)", + "Tool (e.g. claude, cursor, copilot, codex, opencode, kilo, antigravity, vscode)", ], [ ["manage-tools", "framework-install"], - "Tool (e.g. claude, cursor, copilot, codex, opencode, kilo, vscode)", + "Tool (e.g. claude, cursor, copilot, codex, opencode, kilo, antigravity, vscode)", ], [["manage-tools", "framework-remove"], "Tool to remove"], [["manage-tools", "framework-update-one"], "Tool to update"], diff --git a/scripts/__tests__/smoke-real-user-scope-tool-list-parity.test.js b/scripts/__tests__/smoke-real-user-scope-tool-list-parity.test.js index 93ffd51bb..2b88d1147 100644 --- a/scripts/__tests__/smoke-real-user-scope-tool-list-parity.test.js +++ b/scripts/__tests__/smoke-real-user-scope-tool-list-parity.test.js @@ -47,7 +47,7 @@ describe("smoke-real.sh drives --scope user with the tools the profiles support" it("leaves out AI tools that declare neither, which setup --scope user refuses", () => { const unsupported = aiToolIds().filter((id) => !declaresUserScopeActivation(id)); - assert.deepEqual([...unsupported].sort(), ["kilo", "opencode"]); + assert.deepEqual([...unsupported].sort(), ["antigravity", "kilo", "opencode"]); for (const toolId of unsupported) { assert.ok( !scriptUserAiList().includes(toolId), From 4d97954984b2dd360027babaea4fab5d9922d29e Mon Sep 17 00:00:00 2001 From: Thierry Kwiatkowski Date: Mon, 5 Oct 2026 18:12:05 +0200 Subject: [PATCH 2/6] test(cli): check antigravity's .agents/ in the setup smoke The setup smoke asserts a `./` directory per AI tool, but Antigravity writes under the shared `.agents/` root, so `pnpm smoke` failed with "antigravity dir missing after --ai all". Refs #511 Co-Authored-By: Claude Opus 5.5 --- cli/scripts/smoke-tools.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cli/scripts/smoke-tools.sh b/cli/scripts/smoke-tools.sh index 5a02c9a38..761b2523f 100755 --- a/cli/scripts/smoke-tools.sh +++ b/cli/scripts/smoke-tools.sh @@ -334,6 +334,9 @@ if true; then [[ ! -d "$BASE/.github" ]] \ && ok "copilot declarative settings absent without native source proof" \ || bad "copilot declarative settings written without native source proof" + elif [[ "$t" == antigravity ]]; then + # Antigravity reads the shared `.agents/` root, not a directory named after itself. + [[ -d "$BASE/.agents/skills" ]] && ok "$t dir present" || bad "$t dir missing after --ai all" else [[ -d "$BASE/.${t}" ]] && ok "$t dir present" || bad "$t dir missing after --ai all" fi From d763de3ece51f7cd4b7062c83935ab020db166f5 Mon Sep 17 00:00:00 2001 From: Baptiste LAFOURCADE Date: Tue, 3 Nov 2026 06:37:24 +0100 Subject: [PATCH 3/6] fix(cli): synchronize the Antigravity support record after rebase Keep generated compatibility tables derived from the declared host contract and record the current bundle measurement. Refs #511 --- README.md | 3 ++- cli/scripts/check-bundle-size.mjs | 4 ++-- .../tool-support-surface.integration.test.ts | 1 + .../golden/snapshots/tool-support/surface.json | 17 +++++++++++++++++ docs/COMPATIBILITY.md | 1 + 5 files changed, 23 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 87e15899c..3ea77aa35 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,8 @@ https://github.com/user-attachments/assets/92474f4c-053a-471d-9c62-16560b7a2a09 GitHub Copilot✅✅✅ OpenCode✅—✅ Kilo Code✅—✅ -Codex✅✅✅ +Codex✅✅✅ +Antigravity CLI✅—✅ diff --git a/cli/scripts/check-bundle-size.mjs b/cli/scripts/check-bundle-size.mjs index 023359b4f..3d4588934 100644 --- a/cli/scripts/check-bundle-size.mjs +++ b/cli/scripts/check-bundle-size.mjs @@ -39,8 +39,8 @@ const pkg = JSON.parse(readFileSync(resolve(root, "package.json"), "utf8")); // 0.1% measured headroom, with no added runtime dependency. // 689 KB: 675.2 KB, that recipe validation merged into the deterministic token measurement // (+21.9 KB over the 653.3 KB above); 2.0% measured headroom. -// 716 KB: 708.1 KB, Antigravity's flat profile (+2.1 KB over next's 706.0 KB); 1.1% measured -// headroom. +// 716 KB: 712.9 KB, Antigravity's flat profile rebased on next's 710.8 KB (+2.1 KB); +// 0.4% measured headroom. // 756 KB: 755.6 KB, patched esbuild 0.28.2 emits TypeScript parameter-property fields // correctly (+5,008 bytes over 0.27.3); unchanged application and minification settings. // 695 KB: 680.6 KB, that esbuild merged into the deterministic token measurement (+5.3 KB over diff --git a/cli/tests/contexts/tools/domain/tool-support-surface.integration.test.ts b/cli/tests/contexts/tools/domain/tool-support-surface.integration.test.ts index af01bdec3..36b880399 100644 --- a/cli/tests/contexts/tools/domain/tool-support-surface.integration.test.ts +++ b/cli/tests/contexts/tools/domain/tool-support-surface.integration.test.ts @@ -4,6 +4,7 @@ import { mkdir, readFile, writeFile } from "node:fs/promises"; import { dirname, join } from "node:path"; import { describe, expect, it } from "vitest"; +import "../../../../src/contexts/tools/domain/profiles/antigravity/profile.js"; import "../../../../src/contexts/tools/domain/profiles/claude/profile.js"; import "../../../../src/contexts/tools/domain/profiles/codex/profile.js"; import "../../../../src/contexts/tools/domain/profiles/copilot/profile.js"; diff --git a/cli/tests/golden/snapshots/tool-support/surface.json b/cli/tests/golden/snapshots/tool-support/surface.json index 20a3b9100..d31103957 100644 --- a/cli/tests/golden/snapshots/tool-support/surface.json +++ b/cli/tests/golden/snapshots/tool-support/surface.json @@ -132,5 +132,22 @@ "commands": "no" } } + }, + { + "id": "antigravity", + "name": "Antigravity CLI", + "directory": ".agents/", + "pluginMode": "flat", + "nativeCli": null, + "builds": { + "flat": { + "skills": "yes", + "agents": "no", + "mcp": "no", + "hooks": "no", + "rules": "no", + "commands": "no" + } + } } ] diff --git a/docs/COMPATIBILITY.md b/docs/COMPATIBILITY.md index 4398c0a28..83c4f1af7 100644 --- a/docs/COMPATIBILITY.md +++ b/docs/COMPATIBILITY.md @@ -12,6 +12,7 @@ What each AI tool receives from the framework. | OpenCode | `.opencode/` | ❌ | ✅ | ✅ | ✅ | ❌ | Some events | | Kilo Code | `.kilo/` | ❌ | ✅ | ✅ | ✅ | ❌ | Some events | | Codex | `.codex/` | ✅ | ✅ | ✅ | ✅ | Marketplace only | ✅ | +| Antigravity CLI | `.agents/` | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ | From 184c833f980b7d3993432eac5c87c8c27f3d2b57 Mon Sep 17 00:00:00 2001 From: Thierry Kwiatkowski Date: Mon, 5 Oct 2026 22:31:23 +0200 Subject: [PATCH 4/6] feat(cli): merge plugin hooks into antigravity's .agents/hooks.json Antigravity now receives the aidd-context memory hook. Setup and plugin install merge each plugin's hooks into .agents/hooks.json under one key named after the plugin, copy its scripts under .agents/hooks//, and clean removes that key alone, deleting the file only when empty. The project-hooks route Cursor used is generalised: a tool declares a ProjectHooksFormat beside hooksDestination "project", now allowed in flat mode too, and the installer and uninstaller read only that. Cursor's behaviour is unchanged. An unparsable project hooks file now stops the install with the file named. agy 1.2.17 rejects Claude's nested { hooks: [...] } groups and runs a workspace hook from /.agents through a shell, so each command is prefixed with "cd .. && "; events other than SessionStart are skipped with a warning. Co-Authored-By: Claude Opus 5.5 --- .../phase-3.md | 63 ++++--- .../2026_10_02_antigravity-flat-host/plan.md | 7 +- .../framework/application/clean-use-case.ts | 27 ++- .../built-tree-materialization-translator.ts | 27 +-- .../mode-b-flat-materialization-translator.ts | 7 +- .../framework/translator/plugin-translator.ts | 4 +- .../translator/project-hooks-materializer.ts | 70 +++++--- .../application/plugin/plugin-helpers.ts | 7 +- .../restore/restore-all-plugins-use-case.ts | 10 +- .../application/restore/restore-use-case.ts | 1 + .../shared/apply-plugin-files-use-case.ts | 8 +- .../shared/remove-project-hooks.ts | 144 +++++++++------ .../contexts/tools/domain/build-contract.ts | 3 +- .../domain/capabilities/plugins-capability.ts | 44 +++-- .../formats/cursor-hooks-project-merge.ts | 21 +++ .../domain/formats/project-hooks-format.ts | 17 ++ .../profiles/antigravity/antigravity-hooks.ts | 77 ++++++++ .../profiles/antigravity/antigravity-paths.ts | 2 + .../domain/profiles/antigravity/build.ts | 13 +- .../domain/profiles/antigravity/profile.ts | 14 +- .../tools/domain/profiles/cursor/profile.ts | 2 + cli/src/contexts/tools/domain/registry.ts | 17 ++ .../strategies/flat-build-strategy.ts | 12 +- .../context-boundary.arch.test.ts | 1 + .../application/clean-use-case.unit.test.ts | 10 +- ...-flat-materialization-adapter.unit.test.ts | 63 +++++++ .../project-hooks-materializer.unit.test.ts | 125 +++++++++++++ .../shared/remove-project-hooks.unit.test.ts | 75 +++++++- .../plugins-capability.unit.test.ts | 28 +++ .../antigravity-hooks.unit.test.ts | 137 ++++++++++++++ .../profiles/antigravity/build.unit.test.ts | 39 +++- .../profiles/antigravity/profile.unit.test.ts | 12 +- .../domain/profiles/claude/build.unit.test.ts | 4 +- .../domain/profiles/codex/build.unit.test.ts | 2 +- .../domain/registry-conformance.unit.test.ts | 4 + .../tools/domain/registry.unit.test.ts | 2 + .../e2e/antigravity-edited-hooks.e2e.test.ts | 168 ++++++++++++++++++ .../snapshots/framework-build/golden.json | 4 +- 38 files changed, 1089 insertions(+), 182 deletions(-) create mode 100644 cli/src/contexts/tools/domain/formats/project-hooks-format.ts create mode 100644 cli/src/contexts/tools/domain/profiles/antigravity/antigravity-hooks.ts create mode 100644 cli/tests/contexts/tools/domain/profiles/antigravity/antigravity-hooks.unit.test.ts create mode 100644 cli/tests/e2e/antigravity-edited-hooks.e2e.test.ts diff --git a/aidd_docs/tasks/2026_10/2026_10_02_antigravity-flat-host/phase-3.md b/aidd_docs/tasks/2026_10/2026_10_02_antigravity-flat-host/phase-3.md index ddaf5696f..9ecbafac5 100644 --- a/aidd_docs/tasks/2026_10/2026_10_02_antigravity-flat-host/phase-3.md +++ b/aidd_docs/tasks/2026_10/2026_10_02_antigravity-flat-host/phase-3.md @@ -1,5 +1,5 @@ --- -status: pending +status: done --- @@ -12,13 +12,26 @@ status: pending ```txt cli/ -├── src/contexts/tools/domain/profiles/antigravity/ -│ ├── antigravity-hooks.ts ✅ named-hook shape: merge AIDD's `aidd-memory` key into `.agents/hooks.json`, strip it on uninstall, convert Claude-shaped flat hooks -│ ├── profile.ts ✏️ HooksCapability on `.agents/hooks.json` -│ └── build.ts ✏️ hooks artifact: scripts under `.agents/hooks//`, hooksMerge into `.agents/hooks.json` +├── src/contexts/tools/domain/ +│ ├── formats/ +│ │ ├── project-hooks-format.ts ✅ per-tool project hooks file strategy: merge, unmerge, contributed entries, empty check, script path and dir +│ │ └── cursor-hooks-project-merge.ts ✏️ Cursor's functions exposed as one `ProjectHooksFormat`, behaviour unchanged +│ ├── capabilities/plugins-capability.ts ✏️ `hooksDestination: "project"` + `projectHooksRelativePath` + `projectHooksFormat` allowed on flat params too +│ ├── build-contract.ts ✏️ `hooksMerge` receives the plugin name +│ └── profiles/ +│ ├── cursor/profile.ts ✏️ declares Cursor's `ProjectHooksFormat` +│ └── antigravity/ +│ ├── antigravity-hooks.ts ✅ named-hook shape: one `` key, handlers under `SessionStart`, `cd .. && ` prefix, other events skipped with a warning +│ ├── profile.ts ✏️ flat `acceptsHooks: true`, `hooksDestination: "project"` on `.agents/hooks.json` +│ └── build.ts ✏️ hooks artifact: scripts under `.agents/hooks//`, hooksMerge into `.agents/hooks.json` +├── src/contexts/framework/application/ +│ ├── framework/translator/project-hooks-materializer.ts ✏️ reads the tool's `ProjectHooksFormat`; invalid JSON names the file; messages name the tool +│ └── shared/remove-project-hooks.ts ✏️ same, no Cursor import left +├── src/contexts/translate/application/strategies/flat-build-strategy.ts ✏️ passes the plugin name to `hooksMerge` ├── tests/contexts/tools/domain/profiles/antigravity/ -│ └── antigravity-hooks.unit.test.ts ✅ merge, idempotence, foreign keys kept, invalid JSON, strip -├── tests/contexts/tools/domain/build-hooks-support-declaration.unit.test.ts ✏️ antigravity now supports hooks +│ └── antigravity-hooks.unit.test.ts ✅ merge, idempotence, foreign keys kept, invalid JSON, unmerge, empty check, unmapped event warning +├── tests/ (Cursor project-hooks suites) ✏️ only messages that named Cursor generically, if any; behaviour assertions untouched +├── tests/ (antigravity profile, build, build-hooks-support-declaration, registry-conformance) ✏️ antigravity now delivers hooks to `.agents/hooks.json` └── tests/golden/snapshots/framework-build/golden.json ✏️ `antigravity:flat` cell only ``` @@ -26,10 +39,10 @@ cli/ ```mermaid flowchart TD - A[aidd setup --ai antigravity] --> B[.agents/hooks.json gains key aidd-memory with SessionStart command] + A[aidd setup --ai antigravity] --> B[.agents/hooks.json gains key aidd-context with SessionStart command, script under .agents/hooks/aidd-context/] B --> C[agy new session in trusted folder] C --> D[log: loaded N named hooks; memory refresh runs at repo root] - E[aidd clean antigravity] --> F[aidd-memory key removed, other named hooks kept, file deleted only if empty] + E[aidd clean antigravity] --> F[aidd-context key and its scripts removed, other named hooks kept, file deleted only if empty] ``` ## Test Scope @@ -40,9 +53,9 @@ title: Test scope --- journey section Setup - agy 1.2.14 logged in, probe hook printing cwd and workspacePaths => measured cwd recorded: 5: system + agy 1.2.17 logged in, interactive session under a pty, probe hook printing cwd and workspacePaths => measured cwd recorded: 5: system section Happy path - aidd setup --ai antigravity on empty project => .agents/hooks.json has aidd-memory SessionStart entry: 5: cli + aidd setup --ai antigravity on empty project => .agents/hooks.json has aidd-context SessionStart entry: 5: cli second setup => file byte-identical: 5: cli translate --to antigravity --as flat => same hooks.json shape and scripts under .agents/hooks/: 5: cli section Edge case - user hooks present @@ -55,25 +68,29 @@ journey ## Tasks to do -### `1)` Measure the hook `cwd` on 1.2.14 +### `1)` Measure the hook `cwd` on 1.2.17 > #890 measured `cwd = /.agents` on 1.2.5; `update_memory.js` silently no-ops when `aidd_docs/` is not in `cwd`. -1. Local probe: a `SessionStart` hook writing `pwd` and its stdin payload to a marker; read the marker, not the turn. -2. Pick the command from the result: if `cwd` is `.agents`, the command must reach the repo root (e.g. `node ../.aidd/scripts/update_memory.cjs`, or a root resolved from `workspacePaths[0]`); record the evidence in the PR. +1. Local probe: a `SessionStart` hook writing `pwd` and its stdin payload to a marker; read the marker, not the turn. On 1.2.17 a print-mode turn never reaches the model and fires no hook at all, so the probe runs interactive under a pty: `script -q /dev/null agy -i "reply OK" --add-dir `, killed after 60s. Without `--add-dir` it loads 0 hooks. +2. Measured on 2026-10-05: `cwd` is `/.agents`, as on 1.2.5; stdin carries `workspacePaths[0]` = ``. A root-relative command needs a `cd .. && ` prefix, and `agy` runs it through a shell: a real session running `cd .. && node ` wrote `AIDD-MEMORY-MARKER cwd= docs=true`. Record the evidence in the PR. -### `2)` Named-hook merge, test first +### `2)` Named-hook format, test first -> Shape `{ "": { "SessionStart": [ { "hooks": [ { "type": "command", "command": "…", "timeout": 30 } ] } ] } }`, no wrapper. +> Shape `{ "": { "SessionStart": [ { "type": "command", "command": "…", "timeout": 30 } ] } }`: no top-level `hooks` wrapper, and handlers sit directly under the event. 1.2.17 rejects the Claude-style nested `[ { "hooks": [ … ] } ]` with `command hook must specify 'command'`; only `PreToolUse` and `PostToolUse` take a `matcher` entry. -1. Tests: empty file, existing foreign keys, re-run idempotent, invalid JSON fails loud (Codex's silent `catch { parsed = {} }` is not copied), strip leaves foreign keys. -2. Implement in `antigravity-hooks.ts`; mutate the foreign-key preservation and watch its test fail. +1. Tests: empty file, existing foreign keys, re-run idempotent, invalid JSON fails loud (Codex's silent `catch { parsed = {} }` is not copied), unmerge leaves foreign keys, empty check, an event other than `SessionStart` skipped with a warning (Claude and `agy` tool names differ; the telemetry host is out of scope). +2. Implement in `antigravity-hooks.ts` as a `ProjectHooksFormat`: the key is the plugin name, each command gets `cd .. && ` and runs `./.agents/hooks//