From 683c7508fc4c23e38a43dc3629004284dd229e37 Mon Sep 17 00:00:00 2001 From: Thierry Kwiatkowski Date: Mon, 5 Oct 2026 22:31:23 +0200 Subject: [PATCH] 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 +- .../translator/project-hooks-materializer.ts | 50 ++++--- .../shared/remove-project-hooks.ts | 95 ++++++------ .../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 | 15 +- .../tools/domain/profiles/cursor/profile.ts | 2 + cli/src/contexts/tools/domain/registry.ts | 17 +++ .../strategies/flat-build-strategy.ts | 2 +- .../context-boundary.arch.test.ts | 1 + .../project-hooks-materializer.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 | 10 +- .../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 + .../snapshots/framework-build/golden.json | 4 +- 26 files changed, 603 insertions(+), 131 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 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//