From eff0b2484697b5628102ad1f1156b01941d052de Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 24 Feb 2026 16:50:04 +0000 Subject: [PATCH 1/2] feat(init): add --editor cursor option for Cursor IDE integration Add support for configuring Cursor settings alongside existing Claude Code settings when running `archgate init`. The --editor flag accepts "claude" (default) or "cursor". Cursor integration creates: - .cursor/mcp.json with archgate MCP server registration - .cursor/rules/archgate-governance.mdc with alwaysApply governance rule Also adds docs/06-cursor-integration-plan.md covering the full Cursor integration strategy across phases. https://claude.ai/code/session_01LjZoCqwLTqSF3vaHyJmQPZ --- docs/06-cursor-integration-plan.md | 239 ++++++++++++++++++++++++++ src/commands/init.ts | 28 ++- src/helpers/cursor-settings.ts | 117 +++++++++++++ src/helpers/init-project.ts | 24 ++- tests/helpers/cursor-settings.test.ts | 169 ++++++++++++++++++ tests/helpers/init-project.test.ts | 31 +++- 6 files changed, 598 insertions(+), 10 deletions(-) create mode 100644 docs/06-cursor-integration-plan.md create mode 100644 src/helpers/cursor-settings.ts create mode 100644 tests/helpers/cursor-settings.test.ts diff --git a/docs/06-cursor-integration-plan.md b/docs/06-cursor-integration-plan.md new file mode 100644 index 00000000..37466bca --- /dev/null +++ b/docs/06-cursor-integration-plan.md @@ -0,0 +1,239 @@ +# Archgate Cursor Integration Plan + +## Context + +Archgate's AI integration was built as a Claude Code plugin. With Cursor 2.5 (February 2026) shipping a full plugin system and marketplace, Cursor now supports the same primitives needed for deep agent governance — MCP servers, rules, skills, subagents, hooks, and slash commands. + +The MCP server (`archgate mcp`) already works with any MCP-compatible client, including Cursor. This plan covers the additional integration work needed to give Cursor users the same governed-agent experience that Claude Code users get today. + +--- + +## Current State + +### What already works in Cursor (zero changes needed) + +| Component | How it works | +| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | +| MCP server | `archgate mcp` exposes `check`, `list_adrs`, `review_context`, `claude_code_session_context` tools via stdio — any MCP client can call them | +| ADR resources | `adr://{id}` resource template works with any MCP client | +| Check engine | `archgate check` is editor-agnostic | +| CI integration | `archgate check --staged` / `--ci` is editor-agnostic | + +### What requires Cursor-specific work + +| Component | Claude Code equivalent | Cursor equivalent | Status | +| ---------------- | ----------------------------- | -------------------------------------------- | ------- | +| Editor settings | `.claude/settings.local.json` | `.cursor/mcp.json` + `.cursor/rules/*.mdc` | Planned | +| Agent prompt | `agents/developer.md` | `.cursor/agents/*.md` (subagent) | Planned | +| Skills | `skills/*/SKILL.md` | `.cursor/skills/*/SKILL.md` (same spec) | Planned | +| Slash commands | N/A (skills serve this role) | `.cursor/commands/*.md` | Planned | +| Init scaffolding | `archgate init` → `.claude/` | `archgate init --editor cursor` → `.cursor/` | Planned | +| Hooks | N/A | `.cursor/hooks.json` | Future | +| Marketplace | Git-based distribution | Cursor Marketplace plugin | Future | + +--- + +## Architecture + +### Primitive Mapping + +Cursor and Claude Code share the MCP protocol and the Agent Skills specification (agentskills.io). The differences are in how settings, rules, and agent prompts are configured. + +``` + ┌─────────────────────────────────┐ + │ archgate CLI │ + │ (editor-agnostic core) │ + ├─────────────────────────────────┤ + │ .archgate/adrs/ ← ADRs │ + │ .archgate/lint/ ← linter rules│ + │ src/engine/ ← check engine│ + │ src/mcp/ ← MCP server │ + └──────────┬──────────────────────┘ + │ + ┌──────────────┼──────────────┐ + │ │ │ + ┌─────▼─────┐ ┌─────▼─────┐ ┌─────▼─────┐ + │ Claude Code│ │ Cursor │ │ Future │ + │ Plugin │ │ Plugin │ │ Editors │ + ├───────────┤ ├───────────┤ ├───────────┤ + │ .claude/ │ │ .cursor/ │ │ .editor/ │ + │ settings │ │ mcp.json │ │ config │ + │ agents/ │ │ agents/ │ │ │ + │ skills/ │ │ skills/ │ │ │ + └───────────┘ └───────────┘ └───────────┘ +``` + +### Editor Abstraction in `archgate init` + +The `init` command gains an `--editor` flag to scaffold editor-specific settings: + +``` +archgate init # default: configures Claude Code +archgate init --editor claude # explicit: configures Claude Code +archgate init --editor cursor # configures Cursor +``` + +Both paths create the same `.archgate/` governance structure. The only difference is which editor settings are generated. + +--- + +## Implementation + +### Phase 1: `archgate init --editor cursor` (this PR) + +#### 1.1 New helper: `src/helpers/cursor-settings.ts` + +Creates Cursor-specific configuration files when `archgate init --editor cursor` is run. + +**Files created:** + +1. **`.cursor/mcp.json`** — Registers the archgate MCP server: + +```json +{ + "mcpServers": { + "archgate": { + "command": "archgate", + "args": ["mcp"] + } + } +} +``` + +2. **`.cursor/rules/archgate-governance.mdc`** — Always-on governance rule: + +```markdown +--- +description: Archgate ADR governance — enforces architecture decision records +globs: +alwaysApply: true +--- + +# Archgate Governance + +This project uses Archgate to enforce Architecture Decision Records (ADRs). + +## Before writing code + +- Use the `review_context` MCP tool to get applicable ADR briefings for changed files +- Review the Decision and Do's/Don'ts sections of each applicable ADR + +## After writing code + +- Run the `check` MCP tool to validate compliance with all ADR rules +- Fix any violations before considering work complete + +## ADR commands + +- `list_adrs` — List all active ADRs with metadata +- `check` — Run automated compliance checks (use `staged: true` for pre-commit) +- `review_context` — Get changed files grouped by domain with ADR briefings + +## Key principle + +Architectural decisions are enforced, not suggested. If `check` reports violations, they must be fixed. +``` + +#### 1.2 Updated `src/helpers/init-project.ts` + +Adds an `editor` option to `initProject()`: + +```typescript +export interface InitOptions { + editor?: "claude" | "cursor"; +} +``` + +- `editor: "claude"` (default) — calls `configureClaudeSettings()` +- `editor: "cursor"` — calls `configureCursorSettings()` + +#### 1.3 Updated `src/commands/init.ts` + +Adds `--editor ` option: + +```typescript +.option("--editor ", "editor integration to configure", "claude") +``` + +#### 1.4 Tests + +- `tests/helpers/cursor-settings.test.ts` — unit tests for `configureCursorSettings()` and `mergeCursorMcpConfig()` +- Update `tests/helpers/init-project.test.ts` — test `editor: "cursor"` option + +### Phase 2: Skills and Subagents (future) + +The Claude Code plugin skills (`architect`, `quality-manager`, `adr-author`) follow the open Agent Skills specification (agentskills.io). These should work in Cursor with minimal changes. + +| Skill | Source | Cursor path | +| ----------------- | ---------------------------------------- | ----------------------------------------- | +| `architect` | `plugin/skills/architect/SKILL.md` | `.cursor/skills/architect/SKILL.md` | +| `quality-manager` | `plugin/skills/quality-manager/SKILL.md` | `.cursor/skills/quality-manager/SKILL.md` | +| `adr-author` | `plugin/skills/adr-author/SKILL.md` | `.cursor/skills/adr-author/SKILL.md` | + +The developer agent (`plugin/agents/developer.md`) would become a Cursor subagent at `.cursor/agents/archgate-developer.md`. + +### Phase 3: Hooks (future) + +Cursor hooks enable automatic governance enforcement: + +```json +{ + "hooks": { + "afterFileEdit": { + "command": "archgate check --staged --json", + "description": "Run ADR compliance check after file edits" + } + } +} +``` + +### Phase 4: Cursor Marketplace (future) + +Package as a Cursor plugin (`.cursor-plugin/`) with: + +``` +archgate-cursor-plugin/ +├── plugin.json # Marketplace metadata +├── marketplace.json # Listing info +├── mcp.json # archgate MCP server +├── hooks.json # afterFileEdit auto-check +├── rules/ +│ └── archgate-governance.mdc # alwaysApply: true +├── agents/ +│ └── archgate-developer.md # Governance subagent +├── commands/ +│ ├── archgate-check.md # /archgate-check +│ └── archgate-review.md # /archgate-review +└── skills/ + ├── architect/SKILL.md + ├── quality-manager/SKILL.md + └── adr-author/SKILL.md +``` + +Installable via `/add-plugin` in Cursor. + +--- + +## MCP Tool Compatibility + +The `session_context` tool has been renamed to `claude_code_session_context` to signal that it reads Claude Code session transcripts (from `~/.claude/projects/`). This tool is Claude Code-specific and will not function in Cursor. + +The remaining three tools are fully editor-agnostic: + +| Tool | Editor-agnostic? | Notes | +| ----------------------------- | :--------------: | --------------------------------------- | +| `check` | Yes | Runs ADR compliance checks | +| `list_adrs` | Yes | Lists ADRs with metadata | +| `review_context` | Yes | Changed files + ADR briefings | +| `claude_code_session_context` | No | Reads `~/.claude/projects/` JSONL files | + +A future `cursor_session_context` tool could be added if Cursor exposes session transcript data. + +--- + +## Open Questions + +1. **Skills portability** — The Agent Skills spec (agentskills.io) is shared across Claude Code, Cursor, Copilot. How much adaptation is needed for the skill prompts to work well with Cursor's agent? Likely minimal, but needs testing. +2. **Hook granularity** — Cursor's `afterFileEdit` hook fires on every edit. Running `archgate check` after every edit may be noisy. Should we batch or debounce? +3. **Marketplace publishing** — Cursor Marketplace requires review. Timeline and requirements TBD. +4. **Subagent model selection** — Cursor subagents can specify which model to use. Should the archgate subagent enforce a specific model, or defer to the user's default? diff --git a/src/commands/init.ts b/src/commands/init.ts index 4d17cb14..2fb6b664 100644 --- a/src/commands/init.ts +++ b/src/commands/init.ts @@ -1,18 +1,40 @@ import type { Command } from "@commander-js/extra-typings"; import { logError } from "../helpers/log"; import { initProject } from "../helpers/init-project"; +import type { EditorTarget } from "../helpers/init-project"; + +const VALID_EDITORS = ["claude", "cursor"] as const; export function registerInitCommand(program: Command) { program .command("init") .description("Initialize Archgate governance in the current project") - .action(async () => { + .option( + "--editor ", + "editor integration to configure (claude, cursor)", + "claude" + ) + .action(async (opts) => { try { - const result = await initProject(process.cwd()); + const editor = opts.editor as string; + if (!VALID_EDITORS.includes(editor as EditorTarget)) { + logError( + `Unknown editor "${editor}". Supported: ${VALID_EDITORS.join(", ")}` + ); + process.exit(1); + } + + const result = await initProject(process.cwd(), { + editor: editor as EditorTarget, + }); console.log(`Initialized Archgate governance in ${result.projectRoot}`); console.log(` adrs/ - architecture decision records`); console.log(` lint/ - linter-specific rules`); - console.log(` .claude/ - Claude Code settings configured`); + if (editor === "cursor") { + console.log(` .cursor/ - Cursor settings configured`); + } else { + console.log(` .claude/ - Claude Code settings configured`); + } } catch (err) { logError(err instanceof Error ? err.message : String(err)); process.exit(1); diff --git a/src/helpers/cursor-settings.ts b/src/helpers/cursor-settings.ts new file mode 100644 index 00000000..ef2314f7 --- /dev/null +++ b/src/helpers/cursor-settings.ts @@ -0,0 +1,117 @@ +import { join } from "node:path"; +import { existsSync, mkdirSync } from "node:fs"; + +/** + * MCP server configuration that archgate injects into .cursor/mcp.json. + * Follows the same structure Cursor uses for MCP server registration. + */ +export const ARCHGATE_CURSOR_MCP_CONFIG = { + mcpServers: { + archgate: { + command: "archgate", + args: ["mcp"], + }, + }, +} as const; + +/** + * Content for .cursor/rules/archgate-governance.mdc. + * Uses alwaysApply: true so the agent always has governance context. + */ +export const ARCHGATE_CURSOR_RULE = `--- +description: Archgate ADR governance — enforces architecture decision records +globs: +alwaysApply: true +--- + +# Archgate Governance + +This project uses Archgate to enforce Architecture Decision Records (ADRs). + +## Before writing code + +- Use the \`review_context\` MCP tool to get applicable ADR briefings for changed files +- Review the Decision and Do's/Don'ts sections of each applicable ADR + +## After writing code + +- Run the \`check\` MCP tool to validate compliance with all ADR rules +- Fix any violations before considering work complete + +## ADR commands + +- \`list_adrs\` — List all active ADRs with metadata +- \`check\` — Run automated compliance checks (use \`staged: true\` for pre-commit) +- \`review_context\` — Get changed files grouped by domain with ADR briefings + +## Key principle + +Architectural decisions are enforced, not suggested. If \`check\` reports violations, they must be fixed. +`; + +type CursorMcpConfig = Record; + +/** + * Pure, additive merge of archgate MCP server config into existing Cursor MCP config. + * + * - Preserves all existing MCP server entries + * - Adds the archgate server only if not already present + */ +export function mergeCursorMcpConfig( + existing: CursorMcpConfig, + archgate: typeof ARCHGATE_CURSOR_MCP_CONFIG +): CursorMcpConfig { + const existingServers = + typeof existing.mcpServers === "object" && + existing.mcpServers !== null && + !Array.isArray(existing.mcpServers) + ? (existing.mcpServers as Record) + : {}; + + return { + ...existing, + mcpServers: { + ...existingServers, + ...archgate.mcpServers, + }, + }; +} + +/** + * Configure Cursor settings for archgate integration. + * + * Creates/updates `.cursor/mcp.json` with archgate MCP server and + * writes `.cursor/rules/archgate-governance.mdc` with always-on governance rule. + * + * @returns Absolute path to the MCP config file. + */ +export async function configureCursorSettings( + projectRoot: string +): Promise { + const cursorDir = join(projectRoot, ".cursor"); + const mcpConfigPath = join(cursorDir, "mcp.json"); + const rulesDir = join(cursorDir, "rules"); + const rulePath = join(rulesDir, "archgate-governance.mdc"); + + // Read existing MCP config or start with empty object + let existing: CursorMcpConfig = {}; + if (existsSync(mcpConfigPath)) { + const content = await Bun.file(mcpConfigPath).text(); + existing = JSON.parse(content) as CursorMcpConfig; + } + + const merged = mergeCursorMcpConfig(existing, ARCHGATE_CURSOR_MCP_CONFIG); + + // Ensure directories exist + if (!existsSync(cursorDir)) { + mkdirSync(cursorDir, { recursive: true }); + } + if (!existsSync(rulesDir)) { + mkdirSync(rulesDir, { recursive: true }); + } + + await Bun.write(mcpConfigPath, JSON.stringify(merged, null, 2) + "\n"); + await Bun.write(rulePath, ARCHGATE_CURSOR_RULE); + + return mcpConfigPath; +} diff --git a/src/helpers/init-project.ts b/src/helpers/init-project.ts index 4f413192..6e616163 100644 --- a/src/helpers/init-project.ts +++ b/src/helpers/init-project.ts @@ -3,20 +3,30 @@ import { existsSync, readdirSync } from "node:fs"; import { createPathIfNotExists, projectPaths } from "./paths"; import { generateExampleAdr } from "./adr-templates"; import { configureClaudeSettings } from "./claude-settings"; +import { configureCursorSettings } from "./cursor-settings"; + +export type EditorTarget = "claude" | "cursor"; + +export interface InitOptions { + editor?: EditorTarget; +} export interface InitResult { projectRoot: string; adrsDir: string; lintDir: string; - claudeSettingsPath: string; + editorSettingsPath: string; } /** * Initialize an archgate governance directory. Shared by CLI command and MCP tool. * Idempotent — safe to run multiple times. Existing files are overwritten, - * directories are created only if missing, and Claude settings are merged additively. + * directories are created only if missing, and editor settings are merged additively. */ -export async function initProject(projectRoot: string): Promise { +export async function initProject( + projectRoot: string, + options?: InitOptions +): Promise { const paths = projectPaths(projectRoot); createPathIfNotExists(paths.adrsDir); @@ -64,12 +74,16 @@ Archgate standardizes \`.archgate/lint/\` as the location for linter rules that ` ); - const claudeSettingsPath = await configureClaudeSettings(projectRoot); + const editor = options?.editor ?? "claude"; + const editorSettingsPath = + editor === "cursor" + ? await configureCursorSettings(projectRoot) + : await configureClaudeSettings(projectRoot); return { projectRoot, adrsDir: paths.adrsDir, lintDir: paths.lintDir, - claudeSettingsPath, + editorSettingsPath, }; } diff --git a/tests/helpers/cursor-settings.test.ts b/tests/helpers/cursor-settings.test.ts new file mode 100644 index 00000000..e7e5720a --- /dev/null +++ b/tests/helpers/cursor-settings.test.ts @@ -0,0 +1,169 @@ +import { describe, expect, test, beforeEach, afterEach } from "bun:test"; +import { mkdtempSync, rmSync, existsSync, mkdirSync } from "node:fs"; +import { join } from "node:path"; +import { tmpdir } from "node:os"; +import { + ARCHGATE_CURSOR_MCP_CONFIG, + ARCHGATE_CURSOR_RULE, + mergeCursorMcpConfig, + configureCursorSettings, +} from "../../src/helpers/cursor-settings"; + +describe("mergeCursorMcpConfig", () => { + test("sets archgate server when existing config is empty", () => { + const result = mergeCursorMcpConfig({}, ARCHGATE_CURSOR_MCP_CONFIG); + + expect(result.mcpServers).toEqual({ + archgate: { + command: "archgate", + args: ["mcp"], + }, + }); + }); + + test("preserves existing MCP servers", () => { + const result = mergeCursorMcpConfig( + { + mcpServers: { + "other-server": { + command: "other", + args: ["start"], + }, + }, + }, + ARCHGATE_CURSOR_MCP_CONFIG + ); + + const servers = result.mcpServers as Record; + expect(servers["other-server"]).toEqual({ + command: "other", + args: ["start"], + }); + expect(servers.archgate).toEqual({ + command: "archgate", + args: ["mcp"], + }); + }); + + test("overwrites existing archgate server entry", () => { + const result = mergeCursorMcpConfig( + { + mcpServers: { + archgate: { + command: "old-command", + args: ["old"], + }, + }, + }, + ARCHGATE_CURSOR_MCP_CONFIG + ); + + const servers = result.mcpServers as Record; + expect(servers.archgate).toEqual({ + command: "archgate", + args: ["mcp"], + }); + }); + + test("preserves unknown top-level keys", () => { + const result = mergeCursorMcpConfig( + { customKey: "value" }, + ARCHGATE_CURSOR_MCP_CONFIG + ); + + expect(result.customKey).toBe("value"); + }); + + test("handles non-object mcpServers gracefully", () => { + const result = mergeCursorMcpConfig( + { mcpServers: "invalid" }, + ARCHGATE_CURSOR_MCP_CONFIG + ); + + expect(result.mcpServers).toEqual({ + archgate: { + command: "archgate", + args: ["mcp"], + }, + }); + }); +}); + +describe("configureCursorSettings", () => { + let tempDir: string; + + beforeEach(() => { + tempDir = mkdtempSync(join(tmpdir(), "archgate-cursor-settings-test-")); + }); + + afterEach(() => { + rmSync(tempDir, { recursive: true, force: true }); + }); + + test("creates .cursor/ dir, mcp.json, and rules file when nothing exists", async () => { + const mcpConfigPath = await configureCursorSettings(tempDir); + + expect(existsSync(join(tempDir, ".cursor"))).toBe(true); + expect(existsSync(mcpConfigPath)).toBe(true); + expect( + existsSync(join(tempDir, ".cursor", "rules", "archgate-governance.mdc")) + ).toBe(true); + + const mcpContent = JSON.parse(await Bun.file(mcpConfigPath).text()); + expect(mcpContent.mcpServers.archgate).toEqual({ + command: "archgate", + args: ["mcp"], + }); + }); + + test("writes the governance rule file with alwaysApply", async () => { + await configureCursorSettings(tempDir); + + const rulePath = join( + tempDir, + ".cursor", + "rules", + "archgate-governance.mdc" + ); + const content = await Bun.file(rulePath).text(); + expect(content).toContain("alwaysApply: true"); + expect(content).toContain("review_context"); + expect(content).toContain("check"); + expect(content).toBe(ARCHGATE_CURSOR_RULE); + }); + + test("merges into existing mcp.json without overwriting other servers", async () => { + const cursorDir = join(tempDir, ".cursor"); + mkdirSync(cursorDir, { recursive: true }); + + const existingConfig = { + mcpServers: { + "my-server": { command: "my-cmd", args: [] }, + }, + }; + await Bun.write( + join(cursorDir, "mcp.json"), + JSON.stringify(existingConfig, null, 2) + ); + + await configureCursorSettings(tempDir); + + const content = JSON.parse( + await Bun.file(join(cursorDir, "mcp.json")).text() + ); + expect(content.mcpServers["my-server"]).toEqual({ + command: "my-cmd", + args: [], + }); + expect(content.mcpServers.archgate).toEqual({ + command: "archgate", + args: ["mcp"], + }); + }); + + test("returns correct absolute path to mcp.json", async () => { + const mcpConfigPath = await configureCursorSettings(tempDir); + + expect(mcpConfigPath).toBe(join(tempDir, ".cursor", "mcp.json")); + }); +}); diff --git a/tests/helpers/init-project.test.ts b/tests/helpers/init-project.test.ts index c4a70be7..c3928bcf 100644 --- a/tests/helpers/init-project.test.ts +++ b/tests/helpers/init-project.test.ts @@ -66,6 +66,33 @@ describe("initProject", () => { ); }); + test("configures Cursor settings when editor is cursor", async () => { + const result = await initProject(tempDir, { editor: "cursor" }); + + // Cursor MCP config should exist + const mcpConfigPath = join(tempDir, ".cursor", "mcp.json"); + expect(existsSync(mcpConfigPath)).toBe(true); + const mcpContent = JSON.parse(await Bun.file(mcpConfigPath).text()); + expect(mcpContent.mcpServers.archgate).toBeDefined(); + + // Cursor rule should exist + const rulePath = join( + tempDir, + ".cursor", + "rules", + "archgate-governance.mdc" + ); + expect(existsSync(rulePath)).toBe(true); + + // Claude settings should NOT exist + expect(existsSync(join(tempDir, ".claude", "settings.local.json"))).toBe( + false + ); + + // Result should point to cursor config + expect(result.editorSettingsPath).toBe(mcpConfigPath); + }); + test("skips example ADR when ADRs already exist", async () => { // Pre-create .archgate/adrs/ with an existing ADR const adrsDir = join(tempDir, ".archgate", "adrs"); @@ -93,9 +120,9 @@ describe("initProject", () => { expect(content.enabledMcpjsonServers).toContain("archgate"); }); - test("includes claudeSettingsPath in result", async () => { + test("includes editorSettingsPath in result", async () => { const result = await initProject(tempDir); - expect(result.claudeSettingsPath).toBe( + expect(result.editorSettingsPath).toBe( join(tempDir, ".claude", "settings.local.json") ); }); From 0ec6e7c83d820986309d0aa65a85a9bcbfe501d5 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 24 Feb 2026 16:57:22 +0000 Subject: [PATCH 2/2] refactor(mcp): rename session_context to claude_code_session_context MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Make the Claude Code-specific nature of this tool explicit in its name. The tool reads session transcripts from ~/.claude/projects/ which only exists in Claude Code — renaming prevents confusion when the MCP server is used by other editors like Cursor. Renames across source, tests, and all documentation (README, CONTRIBUTING, ADRs, strategic/tactical/technical plans, agent memory). https://claude.ai/code/session_01LjZoCqwLTqSF3vaHyJmQPZ --- .archgate/adrs/ARCH-002-error-handling.md | 4 ++-- .../agent-memory/archgate-developer/MEMORY.md | 2 +- CONTRIBUTING.md | 2 +- README.md | 12 +++++------ docs/01-strategic-plan.md | 2 +- docs/02-tactical-plan.md | 6 +++--- docs/03-technical-plan.md | 20 +++++++++---------- ...text.ts => claude-code-session-context.ts} | 4 ++-- src/mcp/tools/index.ts | 4 ++-- tests/mcp/tools.test.ts | 2 +- ...ts => claude-code-session-context.test.ts} | 10 ++++++---- 11 files changed, 35 insertions(+), 33 deletions(-) rename src/mcp/tools/{session-context.ts => claude-code-session-context.ts} (98%) rename tests/mcp/tools/{session-context.test.ts => claude-code-session-context.test.ts} (74%) diff --git a/.archgate/adrs/ARCH-002-error-handling.md b/.archgate/adrs/ARCH-002-error-handling.md index 9d74435e..673458a7 100644 --- a/.archgate/adrs/ARCH-002-error-handling.md +++ b/.archgate/adrs/ARCH-002-error-handling.md @@ -49,7 +49,7 @@ Use three exit codes with clear semantics: - Write errors to stderr (via `logError()`), not stdout - **MCP tools MUST return structured JSON guidance when prerequisites are missing** — use `noProjectResponse()` from `src/mcp/tools/no-project.ts`, which returns `{ error, message, action }` where `action` directs the AI agent to the recovery step (e.g., "Invoke the `@archgate:onboard` skill") - **The MCP server MUST start even when no project is found** — `startStdioServer()` and `createMcpServer()` accept `string | null` for `projectRoot`; the `mcp` command passes `findProjectRoot()` directly (which returns `null`) rather than guarding with `process.exit(1)` -- **MCP tools that don't depend on `.archgate/` MUST fall back to `process.cwd()`** when `projectRoot` is null — e.g., `session_context` reads from `~/.claude/projects/` and uses `process.cwd()` as its path key when no project is found +- **MCP tools that don't depend on `.archgate/` MUST fall back to `process.cwd()`** when `projectRoot` is null — e.g., `claude_code_session_context` reads from `~/.claude/projects/` and uses `process.cwd()` as its path key when no project is found ### Don't @@ -135,7 +135,7 @@ async ({ adrId, staged }) => { }; // GOOD: tool that doesn't need .archgate/ falls back to cwd -// src/mcp/tools/session-context.ts +// src/mcp/tools/claude-code-session-context.ts const encodedPath = encodeProjectPath(projectRoot ?? process.cwd()); ``` diff --git a/.claude/agent-memory/archgate-developer/MEMORY.md b/.claude/agent-memory/archgate-developer/MEMORY.md index d1f2aeeb..a40f27cf 100644 --- a/.claude/agent-memory/archgate-developer/MEMORY.md +++ b/.claude/agent-memory/archgate-developer/MEMORY.md @@ -59,7 +59,7 @@ Skipping steps 2 or 3 is a workflow violation. The user should NEVER have to inv ## MCP Tools Structure -- MCP tools live in `src/mcp/tools/` — one file per tool (check, list-adrs, review-context, session-context) +- MCP tools live in `src/mcp/tools/` — one file per tool (check, list-adrs, review-context, claude-code-session-context) - ADR CRUD moved to CLI: `archgate adr create`, `archgate adr update`, `archgate init` (no longer MCP tools) - `src/mcp/tools/index.ts` composes all registrations via `registerTools()` (contains real logic, not a barrel per ARCH-004) - `src/mcp/server.ts` imports from `./tools/index` diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 94723370..e1b9f770 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -102,7 +102,7 @@ src/ ├── check.ts # check tool ├── list-adrs.ts # list_adrs tool ├── review-context.ts # review_context tool - └── session-context.ts # session_context tool + └── claude-code-session-context.ts # claude_code_session_context tool tests/ # Mirrors src/ structure .archgate/adrs/ # Self-governance ADRs ``` diff --git a/README.md b/README.md index d86fd394..8fdca1ef 100644 --- a/README.md +++ b/README.md @@ -185,12 +185,12 @@ archgate mcp Exposes four tools to MCP-compatible clients (Claude Code, Cursor, etc.): -| Tool | Description | -| ----------------- | ----------------------------------------------------------------- | -| `check` | Run ADR compliance checks, optionally on staged files only | -| `list_adrs` | List all ADRs with metadata | -| `review_context` | Get changed files grouped by domain with applicable ADR briefings | -| `session_context` | Read the current Claude Code session transcript | +| Tool | Description | +| ----------------------------- | ----------------------------------------------------------------- | +| `check` | Run ADR compliance checks, optionally on staged files only | +| `list_adrs` | List all ADRs with metadata | +| `review_context` | Get changed files grouped by domain with applicable ADR briefings | +| `claude_code_session_context` | Read the current Claude Code session transcript | Also exposes `adr://{id}` resources for reading individual ADRs by ID. diff --git a/docs/01-strategic-plan.md b/docs/01-strategic-plan.md index 023d0127..ec309642 100644 --- a/docs/01-strategic-plan.md +++ b/docs/01-strategic-plan.md @@ -73,7 +73,7 @@ The governance loop above describes _what_ happens, but not _who drives it_. In - The `plugin/` directory ships with the CLI and provides Claude Code integration - Plugin skills (`@architect`, `@quality-manager`, `@adr-author`) define governance roles - A developer agent (`agents/developer.md`) orchestrates the full governance workflow -- MCP tools (`check`, `list_adrs`, `review_context`, `session_context`) connect Claude Code to the archgate CLI +- MCP tools (`check`, `list_adrs`, `review_context`, `claude_code_session_context`) connect Claude Code to the archgate CLI - The `review_context` MCP tool pre-computes domain-grouped ADR briefings for efficient AI review The critical insight: **the MCP tools and CLI commands are passive capabilities**. The _workflow_ — the ordering, gates, and roles — lives in the plugin's agent and skills. Without the plugin, the tools exist but nothing tells the AI _when_ and _how_ to use them. diff --git a/docs/02-tactical-plan.md b/docs/02-tactical-plan.md index e0ce429c..36c041f9 100644 --- a/docs/02-tactical-plan.md +++ b/docs/02-tactical-plan.md @@ -90,7 +90,7 @@ The CLI has evolved from a v0.1.0 scaffolding tool to a full governance engine. - [x] **T2.1** Implement `archgate mcp` command - `src/mcp/server.ts` + `src/mcp/tools/` + `src/mcp/resources.ts` - - Tools: `check`, `list_adrs`, `review_context`, `session_context` + - Tools: `check`, `list_adrs`, `review_context`, `claude_code_session_context` - Resources: `adr://{id}` for full ADR markdown - Stdio transport via `startStdioServer()` - `src/engine/context.ts` provides shared review context logic (section extraction, file-to-ADR matching) @@ -134,7 +134,7 @@ The CLI has evolved from a v0.1.0 scaffolding tool to a full governance engine. - [x] **T2.5.4** Rich review context via MCP - `review_context` MCP tool — pre-computes domain-grouped ADR briefings (Decision + Do's/Don'ts only) - - `session_context` MCP tool — extracts Claude Code session transcripts for context recovery + - `claude_code_session_context` MCP tool — extracts Claude Code session transcripts for context recovery - `src/engine/context.ts` — shared context building logic (section extraction, file-to-ADR matching) - [x] **T2.5.5** Shared helper modules @@ -205,7 +205,7 @@ Phase 1 (Check Engine) ✅ T1.7 Self-dogfood ✅ Phase 2 (AI Integration) ✅ PIVOTED - T2.1 MCP server (check, list_adrs, review_context, session_context) ✅ + T2.1 MCP server (check, list_adrs, review_context, claude_code_session_context) ✅ T2.2 Review ── PIVOTED to plugin @architect skill T2.3 Capture ── PIVOTED to plugin @quality-manager skill T2.4 Skill generation ── REMOVED (replaced by review_context MCP tool) diff --git a/docs/03-technical-plan.md b/docs/03-technical-plan.md index 68980a36..cbeb0d86 100644 --- a/docs/03-technical-plan.md +++ b/docs/03-technical-plan.md @@ -24,7 +24,7 @@ The CLI is developed using its own governance model. The ADR format, check engin | Choice | Technology | Rationale | | -------------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| Integration Protocol | **MCP (Model Context Protocol)** | The `archgate mcp` server exposes tools (check, list_adrs, review_context, session_context) and resources (adr://{id}) to any MCP-compatible client. Uses `@modelcontextprotocol/sdk`. | +| Integration Protocol | **MCP (Model Context Protocol)** | The `archgate mcp` server exposes tools (check, list_adrs, review_context, claude_code_session_context) and resources (adr://{id}) to any MCP-compatible client. Uses `@modelcontextprotocol/sdk`. | | AI Features | **Claude Code Plugin** | Role-based skills (architect, quality-manager, adr-author) and developer agent delivered as a Claude Code plugin (`plugin/`). Claude Code handles all AI interactions — no direct Anthropic API calls. | | Multi-LLM (future) | **MCP-based** | Any MCP-compatible AI client can use archgate MCP tools. Not locked to a single provider. | @@ -108,7 +108,7 @@ archgate/cli/ │ │ ├── check.ts │ │ ├── list-adrs.ts │ │ ├── review-context.ts -│ │ └── session-context.ts +│ │ └── claude-code-session-context.ts │ ├── formats/ # Zod schemas + parsers (single source of truth) │ │ ├── adr.ts # ADR frontmatter schema, parsing, validation │ │ └── rules.ts # Rule file schema + defineRules() @@ -264,12 +264,12 @@ archgate check ### Tools -| Tool | Description | Input | -| ----------------- | ------------------------------------------------------------------------------ | ----------------------- | -| `check` | Run ADR compliance checks | `adrId?`, `staged?` | -| `list_adrs` | List all active ADRs with metadata | `domain?` | -| `review_context` | Pre-compute review context: changed files grouped by domain with ADR briefings | `staged?`, `runChecks?` | -| `session_context` | Read Claude Code session transcript for context recovery | `maxEntries?` | +| Tool | Description | Input | +| ----------------------------- | ------------------------------------------------------------------------------ | ----------------------- | +| `check` | Run ADR compliance checks | `adrId?`, `staged?` | +| `list_adrs` | List all active ADRs with metadata | `domain?` | +| `review_context` | Pre-compute review context: changed files grouped by domain with ADR briefings | `staged?`, `runChecks?` | +| `claude_code_session_context` | Read Claude Code session transcript for context recovery | `maxEntries?` | ### Resources @@ -305,7 +305,7 @@ The developer agent is the entry point. It enforces a strict workflow: 4. **VALIDATE** — Runs `check` MCP tool + invokes `@architect` skill for structural compliance 5. **CAPTURE** — Invokes `@quality-manager` skill to capture learnings and propose new ADRs -The MCP tools (`check`, `list_adrs`, `review_context`, `session_context`) provide the data layer. The skills provide the judgment layer. The agent provides the orchestration layer. +The MCP tools (`check`, `list_adrs`, `review_context`, `claude_code_session_context`) provide the data layer. The skills provide the judgment layer. The agent provides the orchestration layer. --- @@ -391,6 +391,6 @@ All migration steps have been executed: - [x] Added: `src/engine/context.ts` (review context building for AI tools) - [x] Added: `src/helpers/adr-writer.ts`, `claude-settings.ts`, `init-project.ts` (shared logic) - [x] Added: `src/commands/adr/update.ts` (complete ADR lifecycle) -- [x] Added: `src/mcp/tools/` directory with `review-context.ts`, `session-context.ts` (rich AI context) +- [x] Added: `src/mcp/tools/` directory with `review-context.ts`, `claude-code-session-context.ts` (rich AI context) - [x] Added: `.archgate/` self-governance directory with 6 ADRs + rules - [x] Added: `tests/` with comprehensive test coverage (30+ test files) diff --git a/src/mcp/tools/session-context.ts b/src/mcp/tools/claude-code-session-context.ts similarity index 98% rename from src/mcp/tools/session-context.ts rename to src/mcp/tools/claude-code-session-context.ts index 855f70ab..99398b36 100644 --- a/src/mcp/tools/session-context.ts +++ b/src/mcp/tools/claude-code-session-context.ts @@ -59,12 +59,12 @@ function getContentPreview(entry: TranscriptEntry): string { return ""; } -export function registerSessionContextTool( +export function registerClaudeCodeSessionContextTool( server: McpServer, projectRoot: string | null ) { server.registerTool( - "session_context", + "claude_code_session_context", { description: "Read the current Claude Code session transcript for the project. Returns filtered entries (user + assistant messages only) from the most recent session JSONL file. Use this to recover session context that may have been compacted from the conversation.", diff --git a/src/mcp/tools/index.ts b/src/mcp/tools/index.ts index 3bb4590c..7333065e 100644 --- a/src/mcp/tools/index.ts +++ b/src/mcp/tools/index.ts @@ -2,11 +2,11 @@ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { registerCheckTool } from "./check"; import { registerListAdrsTool } from "./list-adrs"; import { registerReviewContextTool } from "./review-context"; -import { registerSessionContextTool } from "./session-context"; +import { registerClaudeCodeSessionContextTool } from "./claude-code-session-context"; export function registerTools(server: McpServer, projectRoot: string | null) { registerCheckTool(server, projectRoot); registerListAdrsTool(server, projectRoot); registerReviewContextTool(server, projectRoot); - registerSessionContextTool(server, projectRoot); + registerClaudeCodeSessionContextTool(server, projectRoot); } diff --git a/tests/mcp/tools.test.ts b/tests/mcp/tools.test.ts index dcccb429..ab766e89 100644 --- a/tests/mcp/tools.test.ts +++ b/tests/mcp/tools.test.ts @@ -27,7 +27,7 @@ describe("registerTools", () => { test("registers all expected tools", () => { const registerSpy = spyOn(server, "registerTool"); registerTools(server, tempDir); - // The tools module registers 4 tools: check, list_adrs, review_context, session_context + // The tools module registers 4 tools: check, list_adrs, review_context, claude_code_session_context expect(registerSpy).toHaveBeenCalledTimes(4); registerSpy.mockRestore(); }); diff --git a/tests/mcp/tools/session-context.test.ts b/tests/mcp/tools/claude-code-session-context.test.ts similarity index 74% rename from tests/mcp/tools/session-context.test.ts rename to tests/mcp/tools/claude-code-session-context.test.ts index 220f8f0a..aa4d6ae4 100644 --- a/tests/mcp/tools/session-context.test.ts +++ b/tests/mcp/tools/claude-code-session-context.test.ts @@ -3,9 +3,9 @@ import { mkdtempSync, rmSync, mkdirSync } from "node:fs"; import { join } from "node:path"; import { tmpdir } from "node:os"; import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { registerSessionContextTool } from "../../../src/mcp/tools/session-context"; +import { registerClaudeCodeSessionContextTool } from "../../../src/mcp/tools/claude-code-session-context"; -describe("registerSessionContextTool", () => { +describe("registerClaudeCodeSessionContextTool", () => { let tempDir: string; let server: McpServer; @@ -21,12 +21,14 @@ describe("registerSessionContextTool", () => { }); test("does not throw when registering", () => { - expect(() => registerSessionContextTool(server, tempDir)).not.toThrow(); + expect(() => + registerClaudeCodeSessionContextTool(server, tempDir) + ).not.toThrow(); }); test("registers exactly one tool", () => { const registerSpy = spyOn(server, "registerTool"); - registerSessionContextTool(server, tempDir); + registerClaudeCodeSessionContextTool(server, tempDir); expect(registerSpy).toHaveBeenCalledTimes(1); registerSpy.mockRestore(); });