Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-05-15
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
@@ -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/<your-name>/<branch-slug> --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).
61 changes: 61 additions & 0 deletions packages/installers/test/cursor-claim-before-edit.test.ts
Original file line number Diff line number Diff line change
@@ -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<string, { command: string; args?: string[] }>;
};
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');
});
});
64 changes: 64 additions & 0 deletions packages/installers/test/gemini-cli-claim-before-edit.test.ts
Original file line number Diff line number Diff line change
@@ -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<string, { command: string; args?: string[] }>;
};
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');
});
});
Loading