Skip to content

[luv-277] feat: add Gemini CLI integration (beta)#277

Merged
NiveditJain merged 3 commits intomainfrom
luv-277
May 4, 2026
Merged

[luv-277] feat: add Gemini CLI integration (beta)#277
NiveditJain merged 3 commits intomainfrom
luv-277

Conversation

@NiveditJain
Copy link
Copy Markdown
Member

@NiveditJain NiveditJain commented May 3, 2026

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-cli v0.40.1.

  • --cli gemini writes 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.
  • All 11 documented Gemini events install: 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.
  • Tool-name canonicalization via GEMINI_TOOL_MAP translates 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.
  • Response-shape adapter in policy-evaluator.ts emits Gemini's flat {decision: "deny", reason} deny shape (preferred per Gemini's "Golden Rule" exit-0 contract), {hookSpecificOutput: {hookEventName, additionalContext}} for context injection on BeforeAgent / AfterTool / SessionStart, and {decision: "block", reason} on AfterAgent for force-retry semantics matching Claude's exit-2-from-Stop "do this before stopping" pattern. The agent-emitted hookEventName round-trips through a new SessionMetadata.rawHookEventName field captured before canonicalization, and the deny-message displayTool is event-aware so non-tool events emit "Blocked prompt"/"Blocked session start"/etc. instead of the misleading "Blocked unknown tool".
  • Path-protection (isAgentInternalPath + isAgentSettingsFile) covers ~/.gemini/ and .gemini/settings.json.
  • Frontend / dashboard parity: lib/cli-registry.ts adds a Gemini CLI entry with a sky-blue badge; lib/projects.ts merges Gemini projects into /projects via new lib/gemini-projects.ts (scans ~/.gemini/tmp/<basename>/chats/session-<ts>-<uuid-prefix>.jsonl, recovers cwd from sibling .project_root text marker, names rows by encodeFolderName(cwd) so cross-CLI merge unions on the same key); new lib/gemini-sessions.ts parses the JSONL transcript schema with a permissive timestamp regex and a bounded 4 KB readFirstLineSync for header-only validation; app/project/[name]/page.tsx, app/project/[name]/session/[sessionId]/page.tsx, and app/policies/hooks-client.tsx extend their per-CLI fallback chains.
  • Telemetry: adding "gemini" to IntegrationType automatically tags all six existing PostHog events (hooks_installed, hooks_removed, hook_policy_triggered, custom_hook_error, custom_hooks_loaded, convention_policies_loaded) with cli: "gemini". Activity-store entries get integration: "gemini". Handler tests prove canonicalized tool names (e.g. "Bash", not raw "run_shell_command") reach telemetry.
  • Dogfood: ships this repo's own .gemini/settings.json so contributors using gemini get hooks active automatically. Uses $GEMINI_PROJECT_DIR for resolver stability (Gemini also sets $CLAUDE_PROJECT_DIR as a back-compat alias).
  • README restructured into two centred logo rows + isolated "+ more coming soon" line so the seventh logo doesn't crowd the layout. Light/dark Gemini SVGs at assets/logos/gemini-{light,dark}.svg.

End-to-end live verification

Verified against gemini-cli v0.40.1 by dropping .gemini/settings.json into this repo and running gemini --skip-trust --yolo -p "say hi". Confirmed:

  1. Gemini picked up the dogfood .gemini/settings.json and called the failproofai binary on every event including BeforeAgent, AfterAgent, and SessionEnd.
  2. The bun $GEMINI_PROJECT_DIR/bin/failproofai.mjs --hook ... --cli gemini command form works as expected.
  3. The require-commit-before-stop builtin policy fired on AfterAgent, returning {"decision": "block", "reason": "MANDATORY ACTION REQUIRED ..."}.
  4. Gemini interpreted that block decision and actually retried — attempting to commit before stopping, exactly as the Stop-policy semantics require.

Test plan

  • bun run test:run1432/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 with cli: "gemini", activity-store integration: "gemini" round-trip, and Gemini aggregation (Gemini-only inclusion, cross-CLI merge by encoded slug, reject-fallback).
  • bun run test:e2e285/285 e2e tests pass (4 pre-existing skipped, gated by piPresent / opencode availability), including a new __tests__/e2e/hooks/gemini-integration.e2e.test.ts with 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).
  • Live gemini v0.40.1 integration test — dogfood .gemini/settings.json picked up; require-commit-before-stop fired and Gemini retried.
  • Regression coverage — every existing CLI's tests (Claude / Codex / Copilot / Cursor / OpenCode / Pi) still passes unchanged, including settings-file shape snapshots.
  • CodeRabbit review feedback addressed — all 6 inline comments fixed in commit a977cd9 with reply on each thread (CR1 raw hookEventName + event-specific deny message, CR2 Gemini aggregation tests, CR3 PR-suffix in CHANGELOG, CR4 critical project name encoding, CR5 looser SESSION_FILE_RE, CR6 bounded first-line read).

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 under lib/ and app/, 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

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 3, 2026

📝 Walkthrough

Walkthrough

Adds 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.

Changes

Gemini CLI Integration

Layer / File(s) Summary
Type System & Constants
src/hooks/types.ts
Adds "gemini" to INTEGRATION_TYPES; exports GEMINI_HOOK_SCOPES, GEMINI_HOOK_EVENT_TYPES, GEMINI_EVENT_MAP, GEMINI_TOOL_MAP; adds rawHookEventName?: string to SessionMetadata.
Integration Registry
src/hooks/integrations.ts
Adds exported gemini: Integration with settings-path resolution (user/project/local), buildHookEntry, idempotent writeHookEntries, removeHooksFromFile, hooksInstalledInSettings, and detectInstalled; registers in INTEGRATIONS.
Hook Configuration
.gemini/settings.json
New settings file with 11 failproofai-marked hook entries running bun $GEMINI_PROJECT_DIR/bin/failproofai.mjs --hook <Event> --cli gemini and timeout: 60000.
Handler Canonicalization
src/hooks/handler.ts
Adds GEMINI_EVENT_MAP mapping in canonicalizeEventType, introduces canonicalizeToolName using GEMINI_TOOL_MAP, mutates parsed.tool_name before evaluation/persistence, and preserves original event name in rawHookEventName.
Policy Evaluation & Response
src/hooks/policy-evaluator.ts
Implements Gemini-specific output shaping: deny returns Gemini JSON (`{ decision: "deny"
Built-in Policy Protections
src/hooks/builtin-policies.ts
Treats ~/.gemini as agent-internal path and recognizes Gemini settings.json and ~/.gemini/hooks/ as protected settings files.
Project/Session Discovery
lib/gemini-projects.ts, lib/gemini-sessions.ts, lib/projects.ts
New libs to scan Gemini tmp sessions, parse JSONL transcripts into LogEntry[], expose getGeminiProjects, getGeminiSessionsForCwd, getGeminiSessionsByEncodedName, getGeminiSessionLog, and cached variants; integrates Gemini into getProjectFolders().
CLI Argument Parsing & Install Flow
bin/failproofai.mjs, src/hooks/install-prompt.ts
Adds gemini to --cli help/examples and VALID_CLIS for install/uninstall; updates uninstall warning listing CLIs including gemini.
Frontend / Registry
lib/cli-registry.ts, app/*
Registers gemini in KNOWN_CLI_IDS and CLI_ENTRIES; updates project/session pages, session loader fallback, SessionCell fallback segment, and CLI dropdowns to include Gemini.
Tests: Handler, Integrations & Evaluator
__tests__/hooks/*
Adds unit tests for Gemini event/tool canonicalization, telemetry/activity persistence, integration settings behavior, GEMINI_EVENT_MAP/GEMINI_TOOL_MAP exhaustiveness, and Gemini-specific evaluator outputs (deny/instruct/allow/Stop semantics).
Tests: E2E & Helpers
__tests__/e2e/*
Adds E2E suite exercising install→fire→decide flows, Gemini payload factories, runHook cli union extension, and Gemini-specific assertion helpers; includes robustness tests (oversized stdin, malformed input).
Docs & Changelog
CLAUDE.md, docs/*, README.md, CHANGELOG.md
Documents Gemini hook locations, env vars, mapping behavior, decision/response shapes; updates README/docs examples and CHANGELOG entry.

Sequence Diagram

sequenceDiagram
    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
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐇 I hopped into Gemini's den,
Mapped events and tools again.
Hooks aligned and tests in tow,
Policies speak what Gemini should know.
A tiny rabbit, cheering "Go!"

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: adding Gemini CLI integration as a new feature, marked as beta. It is concise and directly relates to the primary objective of the changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed PR description is comprehensive and complete, covering all required sections: summary, type of change context, test results, verification steps, and file listing with commit details.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 win

Avoid "unknown tool" in Gemini deny messages for non-tool events.

blockedMessage is reused for every Gemini non-Stop deny, including UserPromptSubmit/session events. On those paths ctx.toolName is 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

📥 Commits

Reviewing files that changed from the base of the PR and between 3e24c4c and 223feb0.

📒 Files selected for processing (12)
  • .gemini/settings.json
  • __tests__/hooks/handler.test.ts
  • __tests__/hooks/integrations.test.ts
  • __tests__/hooks/policy-evaluator.test.ts
  • bin/failproofai.mjs
  • src/hooks/builtin-policies.ts
  • src/hooks/handler.ts
  • src/hooks/install-prompt.ts
  • src/hooks/integrations.ts
  • src/hooks/policy-evaluator.ts
  • src/hooks/resolve-permission-mode.ts
  • src/hooks/types.ts

Comment thread src/hooks/policy-evaluator.ts Outdated
…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>
@NiveditJain NiveditJain changed the title feat: add Gemini CLI integration to hooks system [luv-277] feat: add Gemini CLI integration (beta) May 3, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 {} and runHook still sends valid JSON, so this case does not test parse-failure fallback. Please either send truly invalid stdin bytes (bypassing runHook serialization) 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

📥 Commits

Reviewing files that changed from the base of the PR and between 223feb0 and 2e43d26.

⛔ Files ignored due to path filters (2)
  • assets/logos/gemini-dark.svg is excluded by !**/*.svg
  • assets/logos/gemini-light.svg is excluded by !**/*.svg
📒 Files selected for processing (19)
  • CHANGELOG.md
  • CLAUDE.md
  • README.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.ts
  • app/policies/hooks-client.tsx
  • app/project/[name]/page.tsx
  • app/project/[name]/session/[sessionId]/page.tsx
  • docs/configuration.mdx
  • docs/dashboard.mdx
  • docs/getting-started.mdx
  • lib/cli-registry.ts
  • lib/gemini-projects.ts
  • lib/gemini-sessions.ts
  • lib/projects.ts
✅ Files skipped from review due to trivial changes (1)
  • README.md

Comment thread __tests__/lib/projects.test.ts
Comment thread CHANGELOG.md Outdated
Comment thread lib/gemini-projects.ts Outdated
Comment thread lib/gemini-sessions.ts
Comment thread lib/gemini-sessions.ts Outdated
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>
@NiveditJain NiveditJain merged commit dfc8d26 into main May 4, 2026
9 checks passed
NiveditJain added a commit that referenced this pull request May 5, 2026
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants