[luv-277] feat: add Gemini CLI integration (beta)#277
Conversation
📝 WalkthroughWalkthroughAdds a Gemini CLI integration: registers Gemini hook entries, canonicalizes Gemini event and tool identifiers, implements Gemini-specific policy response shapes, discovers/parses Gemini session transcripts, updates CLI/install flows, and adds tests/docs across handler, evaluator, integrations, lib, and frontend layers. ChangesGemini CLI Integration
Sequence DiagramsequenceDiagram
actor Gemini as Gemini CLI
participant Handler as Hook Handler
participant Canonical as Canonical Maps
participant Evaluator as Policy Evaluator
participant Persister as Activity / Telemetry / Response
Gemini->>Handler: POST hook payload (e.g., BeforeTool, run_shell_command)
Handler->>Canonical: Lookup event via GEMINI_EVENT_MAP
Handler->>Canonical: Lookup tool via GEMINI_TOOL_MAP
Canonical-->>Handler: Return canonical event/tool (e.g., PreToolUse, Bash)
Handler->>Evaluator: evaluatePolicies() with canonical names
Evaluator->>Evaluator: Evaluate policies -> decision (deny/instruct/allow)
Evaluator->>Persister: Emit formatted stdout/stderr + decision payload
Persister->>Gemini: Return stdout/stderr and exit code
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/hooks/policy-evaluator.ts (1)
121-123:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winAvoid
"unknown tool"in Gemini deny messages for non-tool events.
blockedMessageis reused for every Gemini non-Stop deny, includingUserPromptSubmit/session events. On those pathsctx.toolNameis usually absent, so the user sees"Blocked unknown tool by failproofai...", which is misleading. Please keep the tool-specific wording for tool events only and use the raw policy reason or event-specific copy for non-tool Gemini events.Also applies to: 183-190
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/hooks/policy-evaluator.ts`:
- Around line 371-380: The code falls back to the canonical eventType when
session.hookEventName is missing, causing the raw Gemini hook arg to be lost;
update evaluatePolicies / SessionMetadata handling so the raw hook arg is
preserved (e.g., store the original CLI hook name into
SessionMetadata.hookEventName when handler parses arguments, or add a reverse
map from canonical eventType back to raw hook names) and use that preserved raw
value instead of eventType when computing hookEventName in the supportsContext
branch (see supportsContext, hookEventName, evaluatePolicies, SessionMetadata).
Also change the shared blockedMessage construction (blockedMessage /
ctx.toolName usage) to produce event-specific text for non-tool events (e.g.,
check eventType and use a prompt/ session-specific label instead of ctx.toolName
?? "unknown tool"), and add a regression test covering the missing
session.hookEventName path to ensure the raw hook name is emitted and deny
messages are event-appropriate.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: e6b6628f-fed5-49f8-8b5f-3c8716765e27
📒 Files selected for processing (12)
.gemini/settings.json__tests__/hooks/handler.test.ts__tests__/hooks/integrations.test.ts__tests__/hooks/policy-evaluator.test.tsbin/failproofai.mjssrc/hooks/builtin-policies.tssrc/hooks/handler.tssrc/hooks/install-prompt.tssrc/hooks/integrations.tssrc/hooks/policy-evaluator.tssrc/hooks/resolve-permission-mode.tssrc/hooks/types.ts
…e2e tests Follow-up to the foundation commit 223feb0. Lands the parts needed to ship Gemini CLI as a fully integrated 7th agent: * README — restructure the supported-CLIs logo block into two centred rows so the Gemini logo doesn't crowd the line, and add `+ more coming soon` on its own row. Light/dark Gemini SVGs at `assets/logos/gemini-{light,dark}.svg`. * CLAUDE.md — add a "Gemini hooks (.gemini/settings.json)" section with the per-event capability matrix and tool-name canonicalization map. * CHANGELOG — Features bullet covering the 11 events, GEMINI_TOOL_MAP coverage, flat `{decision:"deny",reason}` shape, and `{decision:"block"}` on AfterAgent. * docs/{getting-started,configuration,dashboard}.mdx — extend the CLI lists. * Dashboard wiring — `lib/cli-registry.ts` (sky-blue badge), `lib/projects.ts` (merge Gemini projects), new `lib/gemini-projects.ts` + `lib/gemini-sessions.ts` (scan `~/.gemini/tmp/<basename>/chats/session-<ts>-<uuid-prefix>.jsonl`, recover cwd from sibling `.project_root` text marker), and updated session viewer + activity-tab fallback chains in `app/`. * Tests — extend `__tests__/lib/{cli-registry,projects}.test.ts` and `__tests__/components/project-list.test.tsx` for parity, and add a new `__tests__/e2e/hooks/gemini-integration.e2e.test.ts` (18 cases) covering tool-name canonicalization, event-name canonicalization, response-shape correctness, edge cases (huge stdin, malformed JSON), and per-tool fixture fan-out via new `GeminiPayloads` + `assertGemini{Deny,StopBlock,Instruct}` helpers. Verified end-to-end against gemini-cli v0.40.1 — Gemini picks up the dogfood `.gemini/settings.json`, fires hooks for every event, receives `{decision:"block"}` from `require-commit-before-stop` on AfterAgent and actually retries (the policy fired and triggered Gemini's force-retry path). All quality gates green: 1424/1424 unit tests, 289/289 e2e (including 18 new Gemini-specific), lint clean, typecheck clean, build clean. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (1)
__tests__/e2e/hooks/gemini-integration.e2e.test.ts (1)
329-339: ⚡ Quick win“Malformed stdin JSON” test is not actually malformed.
At Line 338, the payload is
{}andrunHookstill sends valid JSON, so this case does not test parse-failure fallback. Please either send truly invalid stdin bytes (bypassingrunHookserialization) or rename the test to reflect missing-field behavior.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@__tests__/e2e/hooks/gemini-integration.e2e.test.ts` around lines 329 - 339, The test "Malformed stdin JSON falls open to allow (no crash)" is not sending malformed bytes because runHook serializes the payload (the {} at line where runHook is called), so update the test to either (A) actually send invalid stdin by bypassing runHook serialization — create the Gemini env with createGeminiEnv(), call the underlying hook runner or child process API directly to write invalid bytes (e.g., a partial/malformed JSON string) to stdin for the "BeforeTool" hook, ensuring writeConfig is still used to install policies; or (B) if you intend to test missing-field behavior, rename the test and assertion to reflect "empty payload/missing fields falls open" and keep using runHook with {} — change the test title and any related expectations accordingly (refer to runHook, createGeminiEnv, writeConfig, and the "BeforeTool" invocation).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@__tests__/lib/projects.test.ts`:
- Around line 43-45: Add explicit unit tests for Gemini behavior in the test
suite: write tests that (1) mock getGeminiProjects (vi.fn) to return a non-empty
list and assert getProjectFolders() includes those Gemini-only entries, (2) mock
both getLocalProjects and getGeminiProjects with distinct items and assert
getProjectFolders() merges them in the expected order (verify merge-order
behavior), and (3) mock getGeminiProjects to throw/reject and assert
getProjectFolders() falls back to/returns projects from the other providers;
reference the mocked symbol getGeminiProjects and the function under test
getProjectFolders to locate where to add these assertions.
In `@CHANGELOG.md`:
- Line 6: Append the PR suffix " (`#277`)" to the two Unreleased entries that
describe the Gemini CLI integration and the other related entry; specifically
update the line starting with "Add Gemini CLI integration (beta) across hooks,
activity dashboard, session viewer, and `/projects` listing." and the other
Unreleased entry referenced in the comment so both end with " (`#277`)" to match
the required `- <short description> (#<PR number>)` changelog format.
In `@lib/gemini-projects.ts`:
- Around line 118-126: The ProjectFolder "name" currently uses the basename
(variable basename) which breaks routing; change it to the encoded full cwd slug
used by downstream lookups (i.e., the same encoding that
getGeminiSessionsByEncodedName expects) so keys match; update the mapping that
builds folders (the byCwd -> ProjectFolder creation) to set name to the encoded
cwd (use the project's existing cwd-encoding helper/encoder used elsewhere)
instead of basename so Gemini-only links and cross-CLI merges resolve correctly.
In `@lib/gemini-sessions.ts`:
- Around line 91-93: The current try block uses readFileSync(candidate, "utf-8")
to load the entire JSONL just to get the first line, which wastes memory and
duplicates work because getGeminiSessionLog() reads the file again; replace that
sync full-file read with a streaming or bounded read that only reads up to the
first newline (e.g., use fs.createReadStream or fs.open+fs.read with a small
buffer) to extract the first line/header, and then call getGeminiSessionLog() as
before; update the code around the variables candidate and firstLine in
lib/gemini-sessions.ts so only a small prefix is read and the function remains
synchronous/async-consistent with the surrounding try block.
- Around line 40-42: SESSION_FILE_RE in lib/gemini-sessions.ts is too strict
about the timestamp (only matches HH-MM) so findGeminiTranscript() can miss
files when seconds are present; update SESSION_FILE_RE to allow an optional
seconds component and accept either '-' or ':' as the separator between minutes
and seconds (e.g. use T\d{2}[-:]\d{2}(?:[-:]\d{2})? in the pattern) so filenames
with or without seconds will match, keeping the existing sessionId validation as
the primary safety check.
---
Nitpick comments:
In `@__tests__/e2e/hooks/gemini-integration.e2e.test.ts`:
- Around line 329-339: The test "Malformed stdin JSON falls open to allow (no
crash)" is not sending malformed bytes because runHook serializes the payload
(the {} at line where runHook is called), so update the test to either (A)
actually send invalid stdin by bypassing runHook serialization — create the
Gemini env with createGeminiEnv(), call the underlying hook runner or child
process API directly to write invalid bytes (e.g., a partial/malformed JSON
string) to stdin for the "BeforeTool" hook, ensuring writeConfig is still used
to install policies; or (B) if you intend to test missing-field behavior, rename
the test and assertion to reflect "empty payload/missing fields falls open" and
keep using runHook with {} — change the test title and any related expectations
accordingly (refer to runHook, createGeminiEnv, writeConfig, and the
"BeforeTool" invocation).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 7d6a8a22-6e69-4e58-83d0-6483063e5b3f
⛔ Files ignored due to path filters (2)
assets/logos/gemini-dark.svgis excluded by!**/*.svgassets/logos/gemini-light.svgis excluded by!**/*.svg
📒 Files selected for processing (19)
CHANGELOG.mdCLAUDE.mdREADME.md__tests__/components/project-list.test.tsx__tests__/e2e/helpers/hook-runner.ts__tests__/e2e/helpers/payloads.ts__tests__/e2e/hooks/gemini-integration.e2e.test.ts__tests__/lib/cli-registry.test.ts__tests__/lib/projects.test.tsapp/policies/hooks-client.tsxapp/project/[name]/page.tsxapp/project/[name]/session/[sessionId]/page.tsxdocs/configuration.mdxdocs/dashboard.mdxdocs/getting-started.mdxlib/cli-registry.tslib/gemini-projects.tslib/gemini-sessions.tslib/projects.ts
✅ Files skipped from review due to trivial changes (1)
- README.md
Six review items, all fixed: * policy-evaluator.ts: preserve the raw CLI `--hook` arg via a new `SessionMetadata.rawHookEventName` field captured in handler.ts before canonicalization, so Gemini's `hookSpecificOutput.hookEventName` round-trips correctly even when stdin omits `hook_event_name`. Also branch the deny message construction on event type so non-tool events (UserPromptSubmit, SessionStart/End, Stop) emit `Blocked prompt|session start|...` instead of the misleading `Blocked unknown tool`. Adds 5 regression tests covering both invariants. (CR1) * projects.test.ts: add three Gemini-specific tests mirroring the existing Pi/Cursor/OpenCode patterns — Gemini-only project inclusion, cross-CLI merge by encoded slug, and reject-fallback when getGeminiProjects throws. (CR2) * CHANGELOG.md: append `(#277)` PR-number suffix to the two new Unreleased entries (Features + Docs) per the project changelog convention. (CR3) * gemini-projects.ts: change ProjectFolder.name from `basename(cwd)` to `encodeFolderName(cwd)` so cross-CLI merge in mergeProjectFolders unions by the same key, and Gemini-only project links resolve through getGeminiSessionsByEncodedName. (CR4 — critical routing bug) * gemini-sessions.ts: loosen SESSION_FILE_RE from a fixed `YYYY-MM-DDTHH-MM-<8hex>` pattern to `<arbitrary timestamp>-<8hex>` so forward-compatible filename shapes (Gemini docs include seconds, may evolve further) still match. The first-line `sessionId` validation in findGeminiTranscript is the load-bearing safety check, so the more permissive regex is safe. (CR5) * gemini-sessions.ts: replace `readFileSync(candidate, "utf-8")` in findGeminiTranscript's first-line check with a new bounded `readFirstLineSync` helper (openSync + readSync of 4 KB) so large transcripts don't get pulled into memory just to inspect the metadata header. (CR6) All gates green: 1432/1432 unit tests, 285/285 e2e (4 pre-existing skipped), lint clean, typecheck clean. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ion-bumps policy (#285) * [luv-cut-0.0.10-beta.0] chore: cut 0.0.10-beta.0 release Bumps package.json from 0.0.9-beta.3 to 0.0.10-beta.0 and rolls the ## Unreleased changelog section into ## 0.0.10-beta.0 — 2026-05-04. Why 0.0.10-beta.0 and not 0.0.9-beta.3: 0.0.9 is already published as `latest` on npm. Per semver, 0.0.9-beta.3 < 0.0.9 — publishing it would point the `beta` dist-tag at a version semver-older than the released 0.0.9, while shipping *more* features than 0.0.9 ever had. The next pre-release after a shipped 0.0.9 must live in the 0.0.10 line. Why the version had drifted to 0.0.13-beta.1 before #284 reset it: PRs #266 (OpenCode) and #267 (Pi) each speculatively bumped package.json in their feature branches even though no release was being cut. When unified into #270, the bumps stacked (0.0.10-beta.1 → .2 → 0.0.11-beta.1 → 0.0.12-beta.1 → 0.0.13-beta.1). Going forward, feature PRs should leave package.json alone — only release-cut PRs touch the version. Adds since v0.0.9: Features: - Add Gemini CLI integration (beta) (#277) - Add OpenCode (sst/opencode) integration (beta) (#270) - Add Pi (pi-coding-agent) integration (beta) (#270) - Add GitHub Copilot CLI integration (beta) (#236) - Add Cursor Agent CLI integration (beta) (#245) - Project page lists Copilot and Cursor sessions (#245) Fixes: - Pi integration: cache sessionId in shim (#284) - Cursor integration: support cursor-agent 2026-04+ layout (#283) - block-read-outside-cwd: deny message for all 6 CLIs (#270) - require-ci-green-before-stop: scope to current HEAD (#266) - failproofai policies --uninstall: correct selector wording (#236) - README: replace broken Copilot and Cursor logos (#236, #257) - Auto-translated MDX: sanitize JSX attribute quotes (#247) Docs: - README: drop "more coming soon" tagline (#281) - README: add Gemini, Pi, Cursor to supported-CLIs list (#277, #264, #245) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * chore: add block-version-bumps custom policy Prevents the kind of drift that caused this very release. PRs #266 (OpenCode) and #267 (Pi) each speculatively bumped package.json in their feature branches, and when unified into #270 the bumps stacked all the way to 0.0.13-beta.1. PR #284 then over-corrected to 0.0.9-beta.3 — older than the already-published 0.0.9. The policy lives at .failproofai/policies/block-version-bumps.mjs (auto-loaded by failproofai's project-scope hooks). It blocks: - Edit/Write/MultiEdit on package.json that touches the "version" key - Bash: npm|yarn|pnpm|bun (pm) version <args> - Bash: sed|awk|jq mutating package.json referencing "version" Allowed when on a `luv-cut-*` branch — the established release-cut branch convention. Branch detection is a best-effort `git rev-parse` that fails open (returns false) so a missing/unusable git tree never blocks a legitimate edit. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * chore: address CodeRabbit review on block-version-bumps Three valid findings, all fixed: 1. sed/awk/jq detection (line 26): regex required `package.json` to appear before `version`, missing forms like `jq '.version="x"' package.json`. Switched to two non-consuming lookaheads so either ordering matches within a shell segment. 2. Value-only Edit/MultiEdit bypass (lines 74-84): an agent could issue `Edit { old_string: '"0.0.9-beta.3"', new_string: '"0.0.10-beta.0"' }` — neither string contains the literal `"version"` key, so the previous check let it through. Added STANDALONE_SEMVER_VALUE_RE plus an editTouchesVersion() helper that catches a value-only swap when both sides are bare semver-quoted strings that differ. The anchors (^ / $) and leading-digit requirement intentionally exclude range-prefixed dep entries (`"^1.2.3"`) and key-prefixed ones (`"react": "18.2.0"`), so dep-version Edits aren't false-positive. 3. Loose cut-branch match (line 36): `^luv-cut-/` allowed any suffix (e.g. `luv-cut-feature`). Tightened to require a semver-shape suffix: `^luv-cut-\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$`. Verified via 16 regex test cases (sed orderings, dep edits with keys, range prefixes, cut branch shapes). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Summary
Adds Gemini CLI as the seventh supported agent CLI, joining Claude Code, OpenAI Codex, GitHub Copilot, Cursor Agent, OpenCode, and Pi. Verified end-to-end against
gemini-cliv0.40.1.--cli geminiwrites Claude-shape hook entries into~/.gemini/settings.json(user) or<cwd>/.gemini/settings.json(project) using Gemini's{matcher, hooks: [{type, command, timeout}]}matcher-wrapper schema.SessionStart,SessionEnd,BeforeAgent,AfterAgent,BeforeModel,AfterModel,BeforeToolSelection,BeforeTool,AfterTool,PreCompress,Notification. Three of those (BeforeModel/AfterModel/BeforeToolSelection) lack a Claude canonical equivalent — the binary still records activity for them so future policies can opt in.GEMINI_TOOL_MAPtranslates Gemini's snake_case names (run_shell_command,read_file,read_many_files,write_file,replace,glob,grep_search,list_directory,web_fetch,google_web_search,write_todos,save_memory,ask_user) to Claude PascalCase (Bash,Read,Write,Edit,Glob,Grep,LS,WebFetch,WebSearch,TodoWrite,Memory,AskUser) so existing builtin policies (block-sudo, block-rm-rf, sanitize-api-keys, …) fire unchanged on Gemini sessions. MCP tool names (mcp_<server>_<tool>) and Skills tool names pass through unchanged.policy-evaluator.tsemits Gemini's flat{decision: "deny", reason}deny shape (preferred per Gemini's "Golden Rule" exit-0 contract),{hookSpecificOutput: {hookEventName, additionalContext}}for context injection onBeforeAgent/AfterTool/SessionStart, and{decision: "block", reason}onAfterAgentfor force-retry semantics matching Claude's exit-2-from-Stop "do this before stopping" pattern. The agent-emittedhookEventNameround-trips through a newSessionMetadata.rawHookEventNamefield captured before canonicalization, and the deny-messagedisplayToolis event-aware so non-tool events emit "Blocked prompt"/"Blocked session start"/etc. instead of the misleading "Blocked unknown tool".isAgentInternalPath+isAgentSettingsFile) covers~/.gemini/and.gemini/settings.json.lib/cli-registry.tsadds aGemini CLIentry with a sky-blue badge;lib/projects.tsmerges Gemini projects into/projectsvia newlib/gemini-projects.ts(scans~/.gemini/tmp/<basename>/chats/session-<ts>-<uuid-prefix>.jsonl, recovers cwd from sibling.project_roottext marker, names rows byencodeFolderName(cwd)so cross-CLI merge unions on the same key); newlib/gemini-sessions.tsparses the JSONL transcript schema with a permissive timestamp regex and a bounded 4 KBreadFirstLineSyncfor header-only validation;app/project/[name]/page.tsx,app/project/[name]/session/[sessionId]/page.tsx, andapp/policies/hooks-client.tsxextend their per-CLI fallback chains."gemini"toIntegrationTypeautomatically tags all six existing PostHog events (hooks_installed,hooks_removed,hook_policy_triggered,custom_hook_error,custom_hooks_loaded,convention_policies_loaded) withcli: "gemini". Activity-store entries getintegration: "gemini". Handler tests prove canonicalized tool names (e.g."Bash", not raw"run_shell_command") reach telemetry..gemini/settings.jsonso contributors usinggeminiget hooks active automatically. Uses$GEMINI_PROJECT_DIRfor resolver stability (Gemini also sets$CLAUDE_PROJECT_DIRas a back-compat alias).assets/logos/gemini-{light,dark}.svg.End-to-end live verification
Verified against
gemini-cliv0.40.1 by dropping.gemini/settings.jsoninto this repo and runninggemini --skip-trust --yolo -p "say hi". Confirmed:.gemini/settings.jsonand called the failproofai binary on every event includingBeforeAgent,AfterAgent, andSessionEnd.bun $GEMINI_PROJECT_DIR/bin/failproofai.mjs --hook ... --cli geminicommand form works as expected.require-commit-before-stopbuiltin policy fired onAfterAgent, returning{"decision": "block", "reason": "MANDATORY ACTION REQUIRED ..."}.blockdecision and actually retried — attempting to commit before stopping, exactly as the Stop-policy semantics require.Test plan
bun run test:run— 1432/1432 unit tests pass (was 1424; CR follow-up added 5 evaluator regression tests + 3 projects-aggregation tests), including new Gemini-specific cases for: install/uninstall round-trip, idempotency, marker preservation, scope-path resolution, event-name canonicalization (all 11 events), tool-name canonicalization (every entry in GEMINI_TOOL_MAP + unknown-name passthrough), deny / instruct / allow-with-context shapes, raw-hookEventName fallback, event-appropriate deny messages, telemetry tagging withcli: "gemini", activity-storeintegration: "gemini"round-trip, and Gemini aggregation (Gemini-only inclusion, cross-CLI merge by encoded slug, reject-fallback).bun run test:e2e— 285/285 e2e tests pass (4 pre-existing skipped, gated by piPresent / opencode availability), including a new__tests__/e2e/hooks/gemini-integration.e2e.test.tswith 18 cases covering the four Gemini-distinguishing invariants (tool-name canonicalization, event-name canonicalization, response-shape correctness, edge cases like 1MB+ stdin and malformed JSON).bun run lint— clean (1 pre-existing warning unrelated to Gemini).bunx tsc --noEmit— clean.bun run build— clean (Next.js + dist bundle).geminiv0.40.1 integration test — dogfood.gemini/settings.jsonpicked up;require-commit-before-stopfired and Gemini retried.Files
Three commits:
223feb0— foundation: types, integrations, handler, policy-evaluator, builtin-policies, install-prompt, resolve-permission-mode, bin/failproofai.mjs, dogfood.gemini/settings.json, in-place test extensions.2e43d26— polish: README two-row layout, CHANGELOG, CLAUDE.md section + capability matrix, docs/*.mdx, dashboard wiring underlib/andapp/, light/dark Gemini SVGs, new__tests__/e2e/hooks/gemini-integration.e2e.test.ts.a977cd9— CodeRabbit review follow-ups: raw hookEventName fallback + event-aware deny message, encodeFolderName for project name, looser session filename regex, bounded header read, three Gemini aggregation tests, CHANGELOG PR-suffix.🤖 Generated with Claude Code