From a673db806d95fcfafb98d18f2a76e8824efac824 Mon Sep 17 00:00:00 2001 From: NagyVikt Date: Fri, 15 May 2026 20:39:07 +0200 Subject: [PATCH] Add Cursor and Gemini claim-before-edit smoke tests --- .../.openspec.yaml | 2 + .../proposal.md | 24 +++++++ .../spec.md | 18 ++++++ .../tasks.md | 36 +++++++++++ .../test/cursor-claim-before-edit.test.ts | 61 ++++++++++++++++++ .../test/gemini-cli-claim-before-edit.test.ts | 64 +++++++++++++++++++ 6 files changed, 205 insertions(+) create mode 100644 openspec/changes/agent-codex-per-runtime-claim-before-edit-smoke-for-2026-05-15-20-34/.openspec.yaml create mode 100644 openspec/changes/agent-codex-per-runtime-claim-before-edit-smoke-for-2026-05-15-20-34/proposal.md create mode 100644 openspec/changes/agent-codex-per-runtime-claim-before-edit-smoke-for-2026-05-15-20-34/specs/per-runtime-claim-before-edit-smoke-for-cursor-and-gemini-cli/spec.md create mode 100644 openspec/changes/agent-codex-per-runtime-claim-before-edit-smoke-for-2026-05-15-20-34/tasks.md create mode 100644 packages/installers/test/cursor-claim-before-edit.test.ts create mode 100644 packages/installers/test/gemini-cli-claim-before-edit.test.ts diff --git a/openspec/changes/agent-codex-per-runtime-claim-before-edit-smoke-for-2026-05-15-20-34/.openspec.yaml b/openspec/changes/agent-codex-per-runtime-claim-before-edit-smoke-for-2026-05-15-20-34/.openspec.yaml new file mode 100644 index 0000000..9f70866 --- /dev/null +++ b/openspec/changes/agent-codex-per-runtime-claim-before-edit-smoke-for-2026-05-15-20-34/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-05-15 diff --git a/openspec/changes/agent-codex-per-runtime-claim-before-edit-smoke-for-2026-05-15-20-34/proposal.md b/openspec/changes/agent-codex-per-runtime-claim-before-edit-smoke-for-2026-05-15-20-34/proposal.md new file mode 100644 index 0000000..82c890a --- /dev/null +++ b/openspec/changes/agent-codex-per-runtime-claim-before-edit-smoke-for-2026-05-15-20-34/proposal.md @@ -0,0 +1,24 @@ +## Why + +Claim-before-edit health depends on knowing which runtimes can emit lifecycle +hooks and which are MCP-only. Cursor and Gemini CLI currently install the +Colony MCP server, but they do not have native PreToolUse/PostToolUse hook +wiring in the installer. Add explicit per-runtime smoke coverage so future +installer work does not accidentally claim hook support or regress the MCP +namespace required for manual claims. + +## What Changes + +- Add Cursor installer smoke coverage in + `packages/installers/test/cursor-claim-before-edit.test.ts`. +- Add Gemini CLI installer smoke coverage in + `packages/installers/test/gemini-cli-claim-before-edit.test.ts`. +- Assert each runtime installs `mcpServers.colony`, removes stale `cavemem`, + preserves unrelated settings, and does not write claim-before-edit lifecycle + hook commands. + +## Impact + +- Test-only change for `@colony/installers`. +- The tests document current runtime support boundaries: Cursor and Gemini CLI + remain MCP-only until a real lifecycle hook bridge is added. diff --git a/openspec/changes/agent-codex-per-runtime-claim-before-edit-smoke-for-2026-05-15-20-34/specs/per-runtime-claim-before-edit-smoke-for-cursor-and-gemini-cli/spec.md b/openspec/changes/agent-codex-per-runtime-claim-before-edit-smoke-for-2026-05-15-20-34/specs/per-runtime-claim-before-edit-smoke-for-cursor-and-gemini-cli/spec.md new file mode 100644 index 0000000..dc9de06 --- /dev/null +++ b/openspec/changes/agent-codex-per-runtime-claim-before-edit-smoke-for-2026-05-15-20-34/specs/per-runtime-claim-before-edit-smoke-for-cursor-and-gemini-cli/spec.md @@ -0,0 +1,18 @@ +## ADDED Requirements + +### Requirement: Cursor and Gemini CLI claim-before-edit installer smoke coverage +The system SHALL keep per-runtime installer smoke coverage for Cursor and +Gemini CLI that verifies their Colony MCP namespace setup while documenting +that these installers do not yet provide claim-before-edit lifecycle hooks. + +#### Scenario: Cursor installer remains MCP-only for claim-before-edit +- **WHEN** the Cursor installer is run +- **THEN** the Cursor MCP config includes `mcpServers.colony` +- **AND** stale `cavemem` entries are removed +- **AND** no `pre-tool-use` or `claim-before-edit` lifecycle hook command is written. + +#### Scenario: Gemini CLI installer remains MCP-only for claim-before-edit +- **WHEN** the Gemini CLI installer is run +- **THEN** the Gemini settings include `mcpServers.colony` +- **AND** stale `cavemem` entries are removed +- **AND** no `pre-tool-use` or `claim-before-edit` lifecycle hook command is written. diff --git a/openspec/changes/agent-codex-per-runtime-claim-before-edit-smoke-for-2026-05-15-20-34/tasks.md b/openspec/changes/agent-codex-per-runtime-claim-before-edit-smoke-for-2026-05-15-20-34/tasks.md new file mode 100644 index 0000000..43b80e0 --- /dev/null +++ b/openspec/changes/agent-codex-per-runtime-claim-before-edit-smoke-for-2026-05-15-20-34/tasks.md @@ -0,0 +1,36 @@ +## Definition of Done + +This change is complete only when **all** of the following are true: + +- Every checkbox below is checked. +- The agent branch reaches `MERGED` state on `origin` and the PR URL + state are recorded in the completion handoff. +- If any step blocks (test failure, conflict, ambiguous result), append a `BLOCKED:` line under section 4 explaining the blocker and **STOP**. Do not tick remaining cleanup boxes; do not silently skip the cleanup pipeline. + +## Handoff + +- Handoff: change=`agent-codex-per-runtime-claim-before-edit-smoke-for-2026-05-15-20-34`; branch=`agent/codex/per-runtime-claim-before-edit-smoke-for-2026-05-15-20-34`; scope=`packages/installers/test/*claim-before-edit.test.ts`; action=`finish PR handoff after verification`. +- Copy prompt: Continue `agent-codex-per-runtime-claim-before-edit-smoke-for-2026-05-15-20-34` on branch `agent/codex/per-runtime-claim-before-edit-smoke-for-2026-05-15-20-34`. Work inside the existing sandbox, review `openspec/changes/agent-codex-per-runtime-claim-before-edit-smoke-for-2026-05-15-20-34/tasks.md`, continue from the current state instead of creating a new sandbox, and when the work is done run `gx branch finish --branch agent/codex/per-runtime-claim-before-edit-smoke-for-2026-05-15-20-34 --base main --via-pr --cleanup`. + +## 1. Specification + +- [x] 1.1 Finalize proposal scope and acceptance criteria for `agent-codex-per-runtime-claim-before-edit-smoke-for-2026-05-15-20-34`. +- [x] 1.2 Define normative requirements in `specs/per-runtime-claim-before-edit-smoke-for-cursor-and-gemini-cli/spec.md`. + +## 2. Implementation + +- [x] 2.1 Implement scoped behavior changes. +- [x] 2.2 Add/update focused regression coverage. + +## 3. Verification + +- [x] 3.1 Run targeted project verification commands. + - `pnpm --filter @colony/installers test -- cursor-claim-before-edit.test.ts gemini-cli-claim-before-edit.test.ts` + - `pnpm --filter @colony/installers typecheck` +- [x] 3.2 Run `openspec validate agent-codex-per-runtime-claim-before-edit-smoke-for-2026-05-15-20-34 --type change --strict`. +- [x] 3.3 Run `openspec validate --specs`. + +## 4. Cleanup (mandatory; run before claiming completion) + +- [ ] 4.1 Run the cleanup pipeline: `gx branch finish --branch agent// --base dev --via-pr --wait-for-merge --cleanup`. This handles commit -> push -> PR create -> merge wait -> worktree prune in one invocation. +- [ ] 4.2 Record the PR URL and final merge state (`MERGED`) in the completion handoff. +- [ ] 4.3 Confirm the sandbox worktree is gone (`git worktree list` no longer shows the agent path; `git branch -a` shows no surviving local/remote refs for the branch). diff --git a/packages/installers/test/cursor-claim-before-edit.test.ts b/packages/installers/test/cursor-claim-before-edit.test.ts new file mode 100644 index 0000000..0f65e45 --- /dev/null +++ b/packages/installers/test/cursor-claim-before-edit.test.ts @@ -0,0 +1,61 @@ +import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { dirname, join } from 'node:path'; +import { afterEach, beforeEach, describe, expect, it } from 'vitest'; +import { cursor } from '../src/cursor.js'; +import type { InstallContext } from '../src/types.js'; + +let home: string; +let originalHome: string | undefined; +let ctx: InstallContext; + +beforeEach(() => { + home = mkdtempSync(join(tmpdir(), 'colony-cursor-claim-')); + originalHome = process.env.HOME; + process.env.HOME = home; + ctx = { + ideConfigDir: home, + cliPath: '/fake/bin/colony.js', + nodeBin: '/fake/bin/node', + dataDir: join(home, '.colony'), + }; +}); + +afterEach(() => { + if (originalHome === undefined) delete process.env.HOME; + else process.env.HOME = originalHome; + rmSync(home, { recursive: true, force: true }); +}); + +describe('cursor claim-before-edit installer smoke', () => { + it('installs the Colony MCP server without pretending Cursor has lifecycle hooks', async () => { + const configPath = join(home, '.cursor', 'mcp.json'); + mkdirSync(dirname(configPath), { recursive: true }); + writeFileSync( + configPath, + JSON.stringify({ + mcpServers: { + other: { command: '/other/bin' }, + cavemem: { command: '/old/bin', args: ['old-mcp'] }, + }, + }), + ); + + await cursor.install(ctx); + + expect(existsSync(configPath)).toBe(true); + const installed = JSON.parse(readFileSync(configPath, 'utf8')) as { + hooks?: unknown; + mcpServers: Record; + }; + expect(installed.mcpServers.other).toEqual({ command: '/other/bin' }); + expect(installed.mcpServers.colony).toEqual({ + command: ctx.nodeBin, + args: [ctx.cliPath, 'mcp'], + }); + expect(installed.mcpServers.cavemem).toBeUndefined(); + expect(installed.hooks).toBeUndefined(); + expect(JSON.stringify(installed)).not.toContain('pre-tool-use'); + expect(JSON.stringify(installed)).not.toContain('claim-before-edit'); + }); +}); diff --git a/packages/installers/test/gemini-cli-claim-before-edit.test.ts b/packages/installers/test/gemini-cli-claim-before-edit.test.ts new file mode 100644 index 0000000..2116d3e --- /dev/null +++ b/packages/installers/test/gemini-cli-claim-before-edit.test.ts @@ -0,0 +1,64 @@ +import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { dirname, join } from 'node:path'; +import { afterEach, beforeEach, describe, expect, it } from 'vitest'; +import { geminiCli } from '../src/gemini-cli.js'; +import type { InstallContext } from '../src/types.js'; + +let home: string; +let originalHome: string | undefined; +let ctx: InstallContext; + +beforeEach(() => { + home = mkdtempSync(join(tmpdir(), 'colony-gemini-claim-')); + originalHome = process.env.HOME; + process.env.HOME = home; + ctx = { + ideConfigDir: home, + cliPath: '/fake/bin/colony.js', + nodeBin: '/fake/bin/node', + dataDir: join(home, '.colony'), + }; +}); + +afterEach(() => { + if (originalHome === undefined) delete process.env.HOME; + else process.env.HOME = originalHome; + rmSync(home, { recursive: true, force: true }); +}); + +describe('gemini-cli claim-before-edit installer smoke', () => { + it('installs the Colony MCP server without pretending Gemini CLI has lifecycle hooks', async () => { + const settingsPath = join(home, '.gemini', 'settings.json'); + mkdirSync(dirname(settingsPath), { recursive: true }); + writeFileSync( + settingsPath, + JSON.stringify({ + contextFiles: ['GEMINI.md'], + mcpServers: { + other: { command: '/other/bin' }, + cavemem: { command: '/old/bin', args: ['old-mcp'] }, + }, + }), + ); + + await geminiCli.install(ctx); + + expect(existsSync(settingsPath)).toBe(true); + const installed = JSON.parse(readFileSync(settingsPath, 'utf8')) as { + contextFiles?: string[]; + hooks?: unknown; + mcpServers: Record; + }; + expect(installed.contextFiles).toEqual(['GEMINI.md']); + expect(installed.mcpServers.other).toEqual({ command: '/other/bin' }); + expect(installed.mcpServers.colony).toEqual({ + command: ctx.nodeBin, + args: [ctx.cliPath, 'mcp'], + }); + expect(installed.mcpServers.cavemem).toBeUndefined(); + expect(installed.hooks).toBeUndefined(); + expect(JSON.stringify(installed)).not.toContain('pre-tool-use'); + expect(JSON.stringify(installed)).not.toContain('claim-before-edit'); + }); +});