From d51f3a699cd9ccc74dc0667e8009df12f3bc7304 Mon Sep 17 00:00:00 2001 From: Rhuan Barreto Date: Thu, 12 Mar 2026 19:49:24 +0100 Subject: [PATCH 1/4] refactor: remove MCP server, port functionality to CLI commands MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the MCP server (`archgate mcp`) with direct CLI commands. Skills now invoke `archgate review-context`, `archgate session-context claude-code`, and `archgate session-context cursor` via shell instead of MCP tool calls. New files: - src/helpers/session-context.ts — business logic extracted from MCP tools - src/commands/review-context.ts — replaces MCP review_context tool - src/commands/session-context/ — replaces MCP session context tools Removed: - src/mcp/ (server, resources, 6 tools) - src/commands/mcp.ts - @modelcontextprotocol/sdk dependency - .mcp.json, .cursor/mcp.json - All MCP tests (10 files) Updated: - Editor settings (claude-settings.ts, cursor-settings.ts) — no MCP config - 7 ADRs — removed MCP references - CLAUDE.md — updated command table Co-Authored-By: Claude Opus 4.6 --- .archgate/adrs/ARCH-001-command-structure.md | 2 +- .archgate/adrs/ARCH-002-error-handling.md | 58 +---- .archgate/adrs/ARCH-004-no-barrel-files.md | 2 +- .archgate/adrs/ARCH-005-testing-standards.md | 46 ++-- .archgate/adrs/ARCH-006-dependency-policy.md | 1 - .archgate/adrs/GEN-001-documentation-site.md | 10 +- .archgate/adrs/GEN-002-docs-i18n.md | 2 +- .claude/settings.local.json | 3 - .cursor/mcp.json | 8 - .mcp.json | 8 - CLAUDE.md | 25 +- package.json | 1 - src/cli.ts | 6 +- src/commands/mcp.ts | 15 -- src/commands/review-context.ts | 52 ++++ src/commands/session-context/claude-code.ts | 28 ++ src/commands/session-context/cursor.ts | 30 +++ src/commands/session-context/index.ts | 12 + src/engine/loader.ts | 2 +- src/helpers/adr-writer.ts | 2 +- src/helpers/claude-settings.ts | 19 +- src/helpers/cursor-settings.ts | 71 +----- src/helpers/init-project.ts | 2 +- src/helpers/session-context.ts | 241 ++++++++++++++++++ src/mcp/resources.ts | 84 ------ src/mcp/server.ts | 25 -- src/mcp/tools/check.ts | 61 ----- src/mcp/tools/claude-code-session-context.ts | 180 ------------- src/mcp/tools/cursor-session-context.ts | 218 ---------------- src/mcp/tools/index.ts | 14 - src/mcp/tools/list-adrs.ts | 71 ------ src/mcp/tools/no-project.ts | 18 -- src/mcp/tools/review-context.ts | 57 ----- tests/commands/mcp.test.ts | 19 -- tests/commands/review-context.test.ts | 43 ++++ tests/commands/session-context.test.ts | 62 +++++ .../session-context/claude-code.test.ts | 27 ++ tests/commands/session-context/cursor.test.ts | 35 +++ tests/helpers/claude-settings.test.ts | 45 +--- tests/helpers/cursor-settings.test.ts | 152 ++--------- tests/helpers/init-project.test.ts | 16 +- tests/helpers/session-context.test.ts | 51 ++++ tests/mcp/resources.test.ts | 32 --- tests/mcp/server.test.ts | 49 ---- tests/mcp/tools.test.ts | 38 --- tests/mcp/tools/check.test.ts | 33 --- .../tools/claude-code-session-context.test.ts | 35 --- .../mcp/tools/cursor-session-context.test.ts | 35 --- tests/mcp/tools/list-adrs.test.ts | 32 --- tests/mcp/tools/no-project.test.ts | 30 --- tests/mcp/tools/review-context.test.ts | 33 --- 51 files changed, 669 insertions(+), 1472 deletions(-) delete mode 100644 .cursor/mcp.json delete mode 100644 .mcp.json delete mode 100644 src/commands/mcp.ts create mode 100644 src/commands/review-context.ts create mode 100644 src/commands/session-context/claude-code.ts create mode 100644 src/commands/session-context/cursor.ts create mode 100644 src/commands/session-context/index.ts create mode 100644 src/helpers/session-context.ts delete mode 100644 src/mcp/resources.ts delete mode 100644 src/mcp/server.ts delete mode 100644 src/mcp/tools/check.ts delete mode 100644 src/mcp/tools/claude-code-session-context.ts delete mode 100644 src/mcp/tools/cursor-session-context.ts delete mode 100644 src/mcp/tools/index.ts delete mode 100644 src/mcp/tools/list-adrs.ts delete mode 100644 src/mcp/tools/no-project.ts delete mode 100644 src/mcp/tools/review-context.ts delete mode 100644 tests/commands/mcp.test.ts create mode 100644 tests/commands/review-context.test.ts create mode 100644 tests/commands/session-context.test.ts create mode 100644 tests/commands/session-context/claude-code.test.ts create mode 100644 tests/commands/session-context/cursor.test.ts create mode 100644 tests/helpers/session-context.test.ts delete mode 100644 tests/mcp/resources.test.ts delete mode 100644 tests/mcp/server.test.ts delete mode 100644 tests/mcp/tools.test.ts delete mode 100644 tests/mcp/tools/check.test.ts delete mode 100644 tests/mcp/tools/claude-code-session-context.test.ts delete mode 100644 tests/mcp/tools/cursor-session-context.test.ts delete mode 100644 tests/mcp/tools/list-adrs.test.ts delete mode 100644 tests/mcp/tools/no-project.test.ts delete mode 100644 tests/mcp/tools/review-context.test.ts diff --git a/.archgate/adrs/ARCH-001-command-structure.md b/.archgate/adrs/ARCH-001-command-structure.md index 0fae3ccb..f5adecc1 100644 --- a/.archgate/adrs/ARCH-001-command-structure.md +++ b/.archgate/adrs/ARCH-001-command-structure.md @@ -8,7 +8,7 @@ files: ["src/commands/**/*.ts"] ## Context -The CLI needs a consistent pattern for defining and registering commands. As the command surface grows (init, check, adr, mcp, upgrade, clean), the registration mechanism must scale without introducing hidden coupling or making the dependency graph opaque. +The CLI needs a consistent pattern for defining and registering commands. As the command surface grows (init, check, adr, review-context, session-context, upgrade, clean), the registration mechanism must scale without introducing hidden coupling or making the dependency graph opaque. **Alternatives considered:** diff --git a/.archgate/adrs/ARCH-002-error-handling.md b/.archgate/adrs/ARCH-002-error-handling.md index 673458a7..c44b0390 100644 --- a/.archgate/adrs/ARCH-002-error-handling.md +++ b/.archgate/adrs/ARCH-002-error-handling.md @@ -47,9 +47,7 @@ Use three exit codes with clear semantics: - Let unexpected errors crash naturally (exit code 2) - Provide actionable suggestions in error messages - 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., `claude_code_session_context` reads from `~/.claude/projects/` and uses `process.cwd()` as its path key when no project is found +- **Commands that don't require `.archgate/` SHOULD fall back to `process.cwd()`** when `findProjectRoot()` returns null — e.g., `session-context` reads from `~/.claude/projects/` and uses `process.cwd()` as its path key when no project is found ### Don't @@ -58,9 +56,6 @@ Use three exit codes with clear semantics: - Don't use `console.error()` directly — use `logError()` for consistent formatting - Don't exit with code 0 when an operation fails - Don't use exit codes other than 0, 1, or 2 -- **Don't call `process.exit()` inside MCP tool handlers** — the MCP server is a long-lived process shared with the AI agent; calling `process.exit()` kills the agent's MCP connection and prevents any recovery -- **Don't guard MCP server startup with a fatal precondition check** — never call `process.exit(1)` before `startStdioServer()` for expected missing state such as "no project found"; instead pass `null` and let tools degrade gracefully -- **Don't throw unhandled exceptions from MCP tool handlers** — always catch errors inside the handler and return structured JSON with an `error` field; uncaught exceptions break the MCP transport protocol and produce unreadable output ## Implementation Pattern @@ -111,55 +106,6 @@ try { } ``` -### MCP Tool Pattern - -MCP tools run inside a long-lived server process. They MUST NOT call `process.exit()` or throw unhandled exceptions. Missing prerequisites (e.g., no project found) are communicated via structured JSON responses so the AI agent can recover. - -```typescript -// GOOD: MCP server starts regardless of project state -// src/commands/mcp.ts -export function registerMcpCommand(program: Command) { - program.command("mcp").action(async () => { - // findProjectRoot() returns string | null — pass directly, never exit here - await startStdioServer(findProjectRoot()); - }); -} - -// GOOD: MCP tool returns guidance when projectRoot is null -// src/mcp/tools/check.ts -async ({ adrId, staged }) => { - if (projectRoot === null) { - return noProjectResponse(); // { error, message, action: "invoke @archgate:onboard" } - } - // ... normal tool logic -}; - -// GOOD: tool that doesn't need .archgate/ falls back to cwd -// src/mcp/tools/claude-code-session-context.ts -const encodedPath = encodeProjectPath(projectRoot ?? process.cwd()); -``` - -```typescript -// BAD: blocking MCP startup with process.exit -export function registerMcpCommand(program: Command) { - program.command("mcp").action(async () => { - const root = findProjectRoot(); - if (!root) { - logError("No archgate project found."); - process.exit(1); // WRONG — kills the agent's MCP connection - } - await startStdioServer(root); - }); -} - -// BAD: throwing from an MCP tool handler -async ({ adrId }) => { - if (projectRoot === null) { - throw new Error("No project found"); // WRONG — breaks the MCP transport - } -}; -``` - ## Consequences ### Positive @@ -194,8 +140,6 @@ Code reviewers MUST verify: 1. Error messages include actionable suggestions where possible 2. Expected failures exit with code 1, not code 2 3. No try-catch blocks that swallow errors without logging or re-throwing -4. MCP tool handlers do not call `process.exit()` — failures return `{ error, message, action }` JSON -5. The `mcp` command does not guard `startStdioServer()` with a `process.exit()` on missing project ## References diff --git a/.archgate/adrs/ARCH-004-no-barrel-files.md b/.archgate/adrs/ARCH-004-no-barrel-files.md index 2ff8641d..9a3b8250 100644 --- a/.archgate/adrs/ARCH-004-no-barrel-files.md +++ b/.archgate/adrs/ARCH-004-no-barrel-files.md @@ -42,7 +42,7 @@ A barrel file is defined as an `index.ts` file that: Files named `index.ts` that contain actual logic are **not** barrel files and are permitted. Examples of permitted `index.ts` files: - `src/commands/adr/index.ts` — defines `registerAdrCommand()` with command group composition logic -- `src/mcp/tools/index.ts` — defines `registerTools()` with MCP tool registration logic +- `src/commands/session-context/index.ts` — defines `registerSessionContextCommand()` with subcommand composition logic ## Do's and Don'ts diff --git a/.archgate/adrs/ARCH-005-testing-standards.md b/.archgate/adrs/ARCH-005-testing-standards.md index 485ac560..c237ba16 100644 --- a/.archgate/adrs/ARCH-005-testing-standards.md +++ b/.archgate/adrs/ARCH-005-testing-standards.md @@ -51,7 +51,7 @@ Use Bun's built-in test runner (`bun test`) for all tests. Test files go in `tes - Don't test private implementation details — test the public API of each module - Don't depend on network access in unit tests - Don't leave temp files after test runs -- **Don't leave external SDK instances open after tests** — instances from libraries such as `@modelcontextprotocol/sdk` hold internal references (e.g., `AjvJsonSchemaValidator` backed by `ajv`) that keep Bun's event loop alive on Linux, causing `bun test` to hang indefinitely after all tests complete even though every test passes. Always call the cleanup method in `afterEach`. +- **Don't leave external SDK instances open after tests** — instances from external libraries may hold internal references that keep Bun's event loop alive on Linux, causing `bun test` to hang indefinitely after all tests complete even though every test passes. Always call the cleanup method in `afterEach`. - **Don't rely on globally-configured git identity in temp git repos** — always set `user.email` and `user.name` locally in any repo that makes commits. Omitting this works locally (where developers have global git config) but fails silently in CI, producing a cryptic `ShellPromise` error with no indication that git identity is the cause. - Don't skip tests without a tracking issue - Don't import test utilities from `node:test` — use Bun's built-in `bun:test` module @@ -105,33 +105,19 @@ it("returns both staged and unstaged changes", async () => { }); ``` -### Good Example — External SDK Cleanup +### Good Example — Temp Directory Cleanup ```typescript -// tests/mcp/resources.test.ts +// tests/helpers/session-context.test.ts import { describe, expect, test, beforeEach, afterEach } from "bun:test"; import { mkdtempSync, rmSync, mkdirSync } from "node:fs"; -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { registerResources } from "../../src/mcp/resources"; - -describe("registerResources", () => { - let tempDir: string; - let server: McpServer; - - // GOOD: lifecycle managed in beforeEach/afterEach, not inside test bodies - beforeEach(() => { - tempDir = mkdtempSync(join(tmpdir(), "archgate-mcp-res-test-")); - mkdirSync(join(tempDir, ".archgate", "adrs"), { recursive: true }); - server = new McpServer({ name: "test", version: "0.0.0" }); - }); - - afterEach(async () => { - await server.close(); // GOOD: releases internal validator references - rmSync(tempDir, { recursive: true, force: true }); - }); +import { join } from "node:path"; +import { tmpdir } from "node:os"; +import { encodeProjectPath } from "../../src/helpers/session-context"; - test("does not throw when registering resources", () => { - expect(() => registerResources(server, tempDir)).not.toThrow(); +describe("encodeProjectPath", () => { + test("replaces forward slashes with dashes", () => { + expect(encodeProjectPath("/home/user/project")).toBe("-home-user-project"); }); }); ``` @@ -156,11 +142,11 @@ it("writes output", async () => { // No cleanup — file persists after test }); -// BAD: SDK instance created inside test body — no guaranteed cleanup path -it("registers resources", () => { - const server = new McpServer({ name: "test", version: "0.0.0" }); - // server.close() never called — event loop held open on Linux - expect(() => registerResources(server, tempDir)).not.toThrow(); +// BAD: external resource created inside test body — no guaranteed cleanup path +it("processes data", () => { + const tempDir = mkdtempSync(join(tmpdir(), "test-")); + // tempDir never cleaned up — leaks files after test run + expect(processData(tempDir)).toBeTruthy(); }); // BAD: git commit without local identity — works locally, fails in CI @@ -202,8 +188,8 @@ globalThis.fetch = (() => - **Mitigation:** The project pins a specific Bun version via `.prototools` (currently 1.3.8). Test runner API changes are caught during controlled Bun upgrades with full test suite validation. - **Coverage reporting gaps** — `bun test --coverage` may not report accurate coverage for all code paths, especially for dynamically imported modules. - **Mitigation:** Coverage is a guideline (80% target), not a hard gate. Critical modules (engine, formats) are tested thoroughly regardless of coverage numbers. -- **Third-party SDK event loop retention** — External SDK instances that hold internal resource references (e.g., `AjvJsonSchemaValidator` inside `@modelcontextprotocol/sdk`) keep Bun's event loop alive on Linux after all tests complete, causing `bun test` to hang indefinitely. This does not surface on macOS (event loop drains normally there), making it a Linux-CI-only failure that is hard to reproduce locally. - - **Mitigation:** Always manage SDK lifecycle in `beforeEach`/`afterEach` and call the cleanup method (`close()`, `destroy()`, `disconnect()`) in `afterEach`. Add `timeout-minutes` to CI jobs as a safety net — the `code-pull-request.yml` job is set to 10 minutes to cap any future regressions. +- **Third-party SDK event loop retention** — External SDK instances that hold internal resource references may keep Bun's event loop alive on Linux after all tests complete, causing `bun test` to hang indefinitely. This does not surface on macOS (event loop drains normally there), making it a Linux-CI-only failure that is hard to reproduce locally. + - **Mitigation:** Always manage external resource lifecycle in `beforeEach`/`afterEach` and call the cleanup method (`close()`, `destroy()`, `disconnect()`) in `afterEach`. Add `timeout-minutes` to CI jobs as a safety net — the `code-pull-request.yml` job is set to 10 minutes to cap any future regressions. ## Compliance and Enforcement diff --git a/.archgate/adrs/ARCH-006-dependency-policy.md b/.archgate/adrs/ARCH-006-dependency-policy.md index 3a86e669..87e36e44 100644 --- a/.archgate/adrs/ARCH-006-dependency-policy.md +++ b/.archgate/adrs/ARCH-006-dependency-policy.md @@ -28,7 +28,6 @@ Keep production dependencies minimal. Prefer Bun built-ins over external package | ----------------------------- | ------------------- | ------------------------------------------------------------------ | | `@commander-js/extra-typings` | CLI framework | Bun has no built-in CLI argument parsing with subcommand support | | `inquirer` | Interactive prompts | Bun has no built-in interactive prompt library | -| `@modelcontextprotocol/sdk` | MCP server | Protocol-specific SDK; no built-in MCP support | | `zod` | Schema validation | Used for ADR frontmatter validation; no built-in schema validation | **Adding a new dependency requires:** diff --git a/.archgate/adrs/GEN-001-documentation-site.md b/.archgate/adrs/GEN-001-documentation-site.md index e91f7b2b..19893e69 100644 --- a/.archgate/adrs/GEN-001-documentation-site.md +++ b/.archgate/adrs/GEN-001-documentation-site.md @@ -7,7 +7,7 @@ rules: false ## Context -The Archgate CLI needs a public documentation site for users, contributors, and AI agents. A README and inline code comments are insufficient for a project with multiple commands, an MCP server, a rule API, and editor integrations. Without a dedicated docs site: +The Archgate CLI needs a public documentation site for users, contributors, and AI agents. A README and inline code comments are insufficient for a project with multiple commands, a rule API, and editor integrations. Without a dedicated docs site: 1. **Discoverability is poor** — New users cannot browse guides, reference pages, or examples without reading source code 2. **Onboarding is slow** — Contributors must reverse-engineer conventions from existing code rather than reading a structured guide @@ -16,7 +16,7 @@ The Archgate CLI needs a public documentation site for users, contributors, and **Alternatives considered:** -- **README-only documentation** — Keeping all documentation in `README.md` is simple and requires no build tooling. However, README files become unwieldy beyond 500 lines, lack navigation, and cannot provide the structured multi-page experience users expect from a CLI tool. The Archgate README would need to cover 9 CLI commands, 5 MCP tools, a full Rule API, 3 editor integrations, and multiple guides — far too much for a single file. +- **README-only documentation** — Keeping all documentation in `README.md` is simple and requires no build tooling. However, README files become unwieldy beyond 500 lines, lack navigation, and cannot provide the structured multi-page experience users expect from a CLI tool. The Archgate README would need to cover 10+ CLI commands, a full Rule API, 3 editor integrations, and multiple guides — far too much for a single file. - **Docusaurus (React-based)** — A mature documentation framework with a large ecosystem. However, Docusaurus is built on React and requires Node.js, adding a heavyweight runtime and dependency tree that conflicts with the project's Bun-first philosophy. Its configuration is more complex than needed for a documentation site of this scope. - **VitePress (Vue-based)** — A fast, Vue-powered documentation generator. While lighter than Docusaurus, it still requires a framework runtime (Vue) and has less flexibility for custom content than Astro. Its Markdown extensions are proprietary rather than standard MDX. - **Starlight (Astro-based)** — An Astro integration purpose-built for documentation sites. It uses standard MDX, runs under Bun via `bunx --bun astro`, produces static HTML with zero client-side JavaScript by default, and provides built-in search (Pagefind), sidebar navigation, and dark mode. Its component-based architecture allows embedding interactive elements without framework lock-in. @@ -116,11 +116,9 @@ docs/ ci-integration.mdx claude-code-plugin.mdx cursor-integration.mdx - mcp-server.mdx pre-commit-hooks.mdx reference/ cli-commands.mdx - mcp-tools.mdx rule-api.mdx adr-schema.mdx examples/ @@ -188,7 +186,7 @@ The resource URI format is `adr://\{id\}`. - **Single source of truth** — All user-facing documentation lives in one structured, navigable site rather than scattered across README, source comments, and planning docs - **Search built-in** — Starlight integrates Pagefind for full-text search across all documentation pages with zero configuration - **Consistent with CLI toolchain** — Built with Bun (`bunx --bun astro`), aligning with the project's Bun-first philosophy established in [ARCH-006](./ARCH-006-dependency-policy.md) -- **AI-friendly structure** — AI agents can reference well-structured MDX pages for accurate code generation; the MCP server guide documents how agents interact with Archgate +- **AI-friendly structure** — AI agents can reference well-structured MDX pages for accurate code generation and understanding of how to interact with Archgate - **Zero client-side JavaScript** — Astro renders static HTML by default; the docs site loads instantly without framework hydration overhead - **Automatic deployment** — The `deploy-docs.yml` workflow deploys on every merge to `main` that touches `docs/`, with no manual steps @@ -202,7 +200,7 @@ The resource URI format is `adr://\{id\}`. - **Astro/Starlight breaking changes** — Major version upgrades to Astro or Starlight may change the Content Layer API, configuration format, or component interfaces. - **Mitigation:** Dependencies are pinned to major versions (`astro@^5`, `@astrojs/starlight@^0.34`). Upgrades are performed explicitly with full build verification. Astro follows semver and publishes migration guides for major releases. -- **Documentation drift from source code** — Reference pages (CLI Commands, Rule API, MCP Tools, ADR Schema) may fall out of sync as the CLI evolves. +- **Documentation drift from source code** — Reference pages (CLI Commands, Rule API, ADR Schema) may fall out of sync as the CLI evolves. - **Mitigation:** The "DO keep reference pages accurate to CLI source code" rule requires docs updates in the same PR that changes CLI APIs. Code reviewers MUST verify this during review. - **GitHub Pages deployment failures** — Build or deployment failures in `deploy-docs.yml` may leave stale documentation live. - **Mitigation:** The workflow uses `workflow_dispatch` for manual re-deployment. Build failures are visible in the Actions tab. The docs build is isolated from CLI CI, so docs failures never block CLI releases. diff --git a/.archgate/adrs/GEN-002-docs-i18n.md b/.archgate/adrs/GEN-002-docs-i18n.md index d0a2c576..cb6d4829 100644 --- a/.archgate/adrs/GEN-002-docs-i18n.md +++ b/.archgate/adrs/GEN-002-docs-i18n.md @@ -102,7 +102,7 @@ The sidebar in `docs/astro.config.mjs` does NOT need per-locale duplication. Sta - **Translation drift** -- Portuguese content may fall behind after significant English rewrites. - **Mitigation:** The same-PR policy catches most drift at review time. The 1:1 parity rule catches structural drift (added/removed pages). Periodic manual audits can catch semantic drift within existing files. -- **Stale technical content** -- Reference pages (CLI Commands, Rule API, MCP Tools) change frequently and translations may lag. +- **Stale technical content** -- Reference pages (CLI Commands, Rule API) change frequently and translations may lag. - **Mitigation:** Reference pages contain mostly code blocks and tables with technical values that remain in English. Only surrounding prose needs translation, reducing the update surface. - **Incorrect translations misleading users** -- Machine-translated or poorly reviewed content may contain errors. - **Mitigation:** The ADR explicitly requires human review for all translations. Code blocks stay in English, eliminating the highest-risk translation errors (wrong commands, wrong API calls). diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 86310cc7..0e1254ea 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -1,10 +1,7 @@ { "agent": "archgate:developer", - "enableAllProjectMcpServers": true, - "enabledMcpjsonServers": ["archgate"], "permissions": { "allow": [ - "mcp__plugin_archgate_archgate__*", "Skill(archgate:architect)", "Skill(archgate:quality-manager)", "Skill(archgate:adr-author)" diff --git a/.cursor/mcp.json b/.cursor/mcp.json deleted file mode 100644 index 0206fe30..00000000 --- a/.cursor/mcp.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "mcpServers": { - "archgate": { - "command": "archgate", - "args": ["mcp"] - } - } -} diff --git a/.mcp.json b/.mcp.json deleted file mode 100644 index 9dfcad1f..00000000 --- a/.mcp.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "mcpServers": { - "plugin:archgate:archgate": { - "command": "bun", - "args": ["run", "src/cli.ts", "mcp"] - } - } -} diff --git a/CLAUDE.md b/CLAUDE.md index 0f57a031..09186db7 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -33,18 +33,19 @@ bun run commit # conventional commit wizard Entry point: `src/cli.ts` (shebang `#!/usr/bin/env bun`). Commands registered via `register*Command(program)`. -| Command | File | Description | -| --------------- | ------------------------ | -------------------------------- | -| `init` | `commands/init.ts` | Initialize `.archgate/` skeleton | -| `check` | `commands/check.ts` | Run ADR compliance checks | -| `adr create` | `commands/adr/create.ts` | Create ADR interactively | -| `adr list` | `commands/adr/list.ts` | List ADRs (`--json`, `--domain`) | -| `adr show ` | `commands/adr/show.ts` | Show ADR by ID | -| `adr update` | `commands/adr/update.ts` | Update ADR by ID | -| `login` | `commands/login.ts` | GitHub auth for editor plugins | -| `mcp` | `commands/mcp.ts` | Start MCP server | -| `upgrade` | `commands/upgrade.ts` | Upgrade CLI via npm | -| `clean` | `commands/clean.ts` | Remove `~/.archgate/` cache | +| Command | File | Description | +| ----------------- | ----------------------------------- | -------------------------------------------- | +| `init` | `commands/init.ts` | Initialize `.archgate/` skeleton | +| `check` | `commands/check.ts` | Run ADR compliance checks | +| `adr create` | `commands/adr/create.ts` | Create ADR interactively | +| `adr list` | `commands/adr/list.ts` | List ADRs (`--json`, `--domain`) | +| `adr show ` | `commands/adr/show.ts` | Show ADR by ID | +| `adr update` | `commands/adr/update.ts` | Update ADR by ID | +| `login` | `commands/login.ts` | GitHub auth for editor plugins | +| `review-context` | `commands/review-context.ts` | Pre-compute review context for changed files | +| `session-context` | `commands/session-context/index.ts` | Read AI editor session transcripts | +| `upgrade` | `commands/upgrade.ts` | Upgrade CLI via npm | +| `clean` | `commands/clean.ts` | Remove `~/.archgate/` cache | ### Key Paths diff --git a/package.json b/package.json index 1389b5cd..7480521d 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,6 @@ "@commitlint/cli": "19.8.1", "@commitlint/config-conventional": "19.8.1", "@commitlint/cz-commitlint": "19.8.1", - "@modelcontextprotocol/sdk": "1.26.0", "@simple-release/npm": "2.3.0", "@types/bun": "1.3.9", "commitizen": "4.3.1", diff --git a/src/cli.ts b/src/cli.ts index b9df07b1..44938ddf 100755 --- a/src/cli.ts +++ b/src/cli.ts @@ -10,7 +10,8 @@ import { registerUpgradeCommand } from "./commands/upgrade"; import { registerCleanCommand } from "./commands/clean"; import { registerCheckCommand } from "./commands/check"; import { registerLoginCommand } from "./commands/login"; -import { registerMcpCommand } from "./commands/mcp"; +import { registerReviewContextCommand } from "./commands/review-context"; +import { registerSessionContextCommand } from "./commands/session-context/index"; import { checkForUpdatesIfNeeded } from "./helpers/update-check"; import { logError } from "./helpers/log"; @@ -39,7 +40,8 @@ async function main() { registerLoginCommand(program); registerAdrCommand(program); registerCheckCommand(program); - registerMcpCommand(program); + registerReviewContextCommand(program); + registerSessionContextCommand(program); registerUpgradeCommand(program); registerCleanCommand(program); diff --git a/src/commands/mcp.ts b/src/commands/mcp.ts deleted file mode 100644 index e077af41..00000000 --- a/src/commands/mcp.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { Command } from "@commander-js/extra-typings"; -import { findProjectRoot } from "../helpers/paths"; -import { startStdioServer } from "../mcp/server"; - -export function registerMcpCommand(program: Command) { - program - .command("mcp") - .description("Start MCP server for AI tool integration") - .action(async () => { - // Pass null when no project is found — the MCP server still starts and - // tools will return an actionable no-project guidance response so the - // agent can invoke @archgate:onboard to initialize governance. - await startStdioServer(findProjectRoot()); - }); -} diff --git a/src/commands/review-context.ts b/src/commands/review-context.ts new file mode 100644 index 00000000..26b932b0 --- /dev/null +++ b/src/commands/review-context.ts @@ -0,0 +1,52 @@ +import type { Command } from "@commander-js/extra-typings"; +import { logError } from "../helpers/log"; +import { findProjectRoot } from "../helpers/paths"; +import { buildReviewContext } from "../engine/context"; +import { AdrFrontmatterSchema } from "../formats/adr"; + +export function registerReviewContextCommand(program: Command) { + program + .command("review-context") + .description( + "Pre-compute review context with ADR briefings for changed files" + ) + .option("--staged", "Only include git-staged files") + .option("--run-checks", "Include ADR compliance check results") + .option( + "--domain ", + "Filter to a single domain (backend, frontend, data, architecture, general)" + ) + .action(async (opts) => { + const projectRoot = findProjectRoot(); + if (!projectRoot) { + logError( + "No archgate project found. Run 'archgate init' to create one." + ); + process.exit(1); + } + + if (opts.domain) { + const result = AdrFrontmatterSchema.shape.domain.safeParse(opts.domain); + if (!result.success) { + logError( + `Invalid domain '${opts.domain}'. Use: backend, frontend, data, architecture, general` + ); + process.exit(1); + } + } + + const context = await buildReviewContext(projectRoot, { + staged: opts.staged, + runChecks: opts.runChecks, + domain: opts.domain as + | "backend" + | "frontend" + | "data" + | "architecture" + | "general" + | undefined, + }); + + console.log(JSON.stringify(context, null, 2)); + }); +} diff --git a/src/commands/session-context/claude-code.ts b/src/commands/session-context/claude-code.ts new file mode 100644 index 00000000..433aa186 --- /dev/null +++ b/src/commands/session-context/claude-code.ts @@ -0,0 +1,28 @@ +import type { Command } from "@commander-js/extra-typings"; +import { findProjectRoot } from "../../helpers/paths"; +import { readClaudeCodeSession } from "../../helpers/session-context"; +import { logError } from "../../helpers/log"; + +export function registerClaudeCodeSessionContextCommand(parent: Command) { + parent + .command("claude-code") + .description("Read Claude Code session transcript for the project") + .option( + "--max-entries ", + "Maximum entries to return (default: 200)", + parseInt + ) + .action(async (opts) => { + const projectRoot = findProjectRoot(); + const result = await readClaudeCodeSession(projectRoot, { + maxEntries: opts.maxEntries, + }); + + if (!result.ok) { + logError(result.error); + process.exit(1); + } + + console.log(JSON.stringify(result.data, null, 2)); + }); +} diff --git a/src/commands/session-context/cursor.ts b/src/commands/session-context/cursor.ts new file mode 100644 index 00000000..35d934ed --- /dev/null +++ b/src/commands/session-context/cursor.ts @@ -0,0 +1,30 @@ +import type { Command } from "@commander-js/extra-typings"; +import { findProjectRoot } from "../../helpers/paths"; +import { readCursorSession } from "../../helpers/session-context"; +import { logError } from "../../helpers/log"; + +export function registerCursorSessionContextCommand(parent: Command) { + parent + .command("cursor") + .description("Read Cursor agent session transcript for the project") + .option( + "--max-entries ", + "Maximum entries to return (default: 200)", + parseInt + ) + .option("--session-id ", "Specific session UUID to read") + .action(async (opts) => { + const projectRoot = findProjectRoot(); + const result = await readCursorSession(projectRoot, { + maxEntries: opts.maxEntries, + sessionId: opts.sessionId, + }); + + if (!result.ok) { + logError(result.error); + process.exit(1); + } + + console.log(JSON.stringify(result.data, null, 2)); + }); +} diff --git a/src/commands/session-context/index.ts b/src/commands/session-context/index.ts new file mode 100644 index 00000000..dccb7bff --- /dev/null +++ b/src/commands/session-context/index.ts @@ -0,0 +1,12 @@ +import type { Command } from "@commander-js/extra-typings"; +import { registerClaudeCodeSessionContextCommand } from "./claude-code"; +import { registerCursorSessionContextCommand } from "./cursor"; + +export function registerSessionContextCommand(program: Command) { + const sessionContext = program + .command("session-context") + .description("Read AI editor session transcripts"); + + registerClaudeCodeSessionContextCommand(sessionContext); + registerCursorSessionContextCommand(sessionContext); +} diff --git a/src/engine/loader.ts b/src/engine/loader.ts index c266cd10..8ceeca08 100644 --- a/src/engine/loader.ts +++ b/src/engine/loader.ts @@ -86,7 +86,7 @@ export async function loadRuleAdrs( try { // Cache-bust: Bun caches import() per-process, so append a timestamp - // to force re-reading from disk on every call (critical for MCP server). + // to force re-reading from disk on every call (critical for repeated invocations). // Use file:// URL to handle Windows backslash paths in import(). const rulesUrl = `${pathToFileURL(rulesFile).href}?t=${Date.now()}`; // oxlint-disable-next-line no-await-in-loop -- dynamic import must be sequential diff --git a/src/helpers/adr-writer.ts b/src/helpers/adr-writer.ts index 61cd7f46..978304b2 100644 --- a/src/helpers/adr-writer.ts +++ b/src/helpers/adr-writer.ts @@ -70,7 +70,7 @@ export interface CreateAdrResult { } /** - * Create an ADR file on disk. Shared by CLI command and MCP tool. + * Create an ADR file on disk. */ export async function createAdrFile( adrsDir: string, diff --git a/src/helpers/claude-settings.ts b/src/helpers/claude-settings.ts index c623cc97..a2fc694e 100644 --- a/src/helpers/claude-settings.ts +++ b/src/helpers/claude-settings.ts @@ -7,11 +7,8 @@ import { existsSync, mkdirSync } from "node:fs"; */ export const ARCHGATE_CLAUDE_SETTINGS = { agent: "archgate:developer", - enableAllProjectMcpServers: true, - enabledMcpjsonServers: ["archgate"], permissions: { allow: [ - "mcp__plugin_archgate_archgate__*", "Skill(archgate:architect)", "Skill(archgate:quality-manager)", "Skill(archgate:adr-author)", @@ -31,8 +28,8 @@ function dedup(arr: string[]): string[] { /** * Pure, additive merge of archgate settings into existing Claude settings. * - * - Scalar keys (`agent`, `enableAllProjectMcpServers`): set only if absent - * - Array keys (`enabledMcpjsonServers`, `permissions.allow`): append with dedup + * - Scalar keys (`agent`): set only if absent + * - Array keys (`permissions.allow`): append with dedup * - All existing user settings are preserved (unknown keys pass through) */ export function mergeClaudeSettings( @@ -45,18 +42,6 @@ export function mergeClaudeSettings( if (!("agent" in merged)) { merged.agent = archgate.agent; } - if (!("enableAllProjectMcpServers" in merged)) { - merged.enableAllProjectMcpServers = archgate.enableAllProjectMcpServers; - } - - // Array: append with dedup - const existingMcpServers = Array.isArray(merged.enabledMcpjsonServers) - ? (merged.enabledMcpjsonServers as string[]) - : []; - merged.enabledMcpjsonServers = dedup([ - ...existingMcpServers, - ...archgate.enabledMcpjsonServers, - ]); // Nested permissions object: merge allow array with dedup, preserve deny const existingPermissions = diff --git a/src/helpers/cursor-settings.ts b/src/helpers/cursor-settings.ts index ef2314f7..65c3211e 100644 --- a/src/helpers/cursor-settings.ts +++ b/src/helpers/cursor-settings.ts @@ -1,19 +1,6 @@ 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. @@ -30,78 +17,39 @@ 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 +- Run \`archgate review-context\` 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 +- Run \`archgate check --staged\` 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 +- \`archgate adr list\` — List all active ADRs with metadata +- \`archgate check --staged\` — Run automated compliance checks +- \`archgate 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. +Architectural decisions are enforced, not suggested. If \`archgate 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. + * Writes `.cursor/rules/archgate-governance.mdc` with always-on governance rule. * - * @returns Absolute path to the MCP config file. + * @returns Absolute path to the rules 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 }); @@ -110,8 +58,7 @@ export async function configureCursorSettings( mkdirSync(rulesDir, { recursive: true }); } - await Bun.write(mcpConfigPath, JSON.stringify(merged, null, 2) + "\n"); await Bun.write(rulePath, ARCHGATE_CURSOR_RULE); - return mcpConfigPath; + return rulePath; } diff --git a/src/helpers/init-project.ts b/src/helpers/init-project.ts index 4187c77e..4c752428 100644 --- a/src/helpers/init-project.ts +++ b/src/helpers/init-project.ts @@ -30,7 +30,7 @@ export interface InitResult { } /** - * Initialize an archgate governance directory. Shared by CLI command and MCP tool. + * Initialize an archgate governance directory. * Idempotent — safe to run multiple times. Existing files are overwritten, * directories are created only if missing, and editor settings are merged additively. */ diff --git a/src/helpers/session-context.ts b/src/helpers/session-context.ts new file mode 100644 index 00000000..b373988b --- /dev/null +++ b/src/helpers/session-context.ts @@ -0,0 +1,241 @@ +import { readdirSync, statSync } from "node:fs"; +import { join, basename } from "node:path"; +import { homedir } from "node:os"; + +/** Encode a project root path into the directory name used by Claude/Cursor. */ +export function encodeProjectPath(projectRoot: string): string { + return projectRoot.replaceAll("/", "-"); +} + +const RELEVANT_TYPES = new Set(["user", "assistant"]); +const RELEVANT_ROLES = new Set(["user", "assistant"]); + +interface TranscriptEntry { + type?: string; + role?: string; + message?: { role?: string; content?: unknown }; + [key: string]: unknown; +} + +export interface ClaudeSessionSummary { + sessionFile: string; + totalEntries: number; + relevantEntries: number; + transcript: Array<{ type: string; role?: string; contentPreview: string }>; +} + +export interface CursorSessionSummary { + sessionId: string; + sessionFile: string; + totalEntries: number; + relevantEntries: number; + transcript: Array<{ role: string; contentPreview: string }>; +} + +type ClaudeSessionResult = + | { ok: true; data: ClaudeSessionSummary } + | { ok: false; error: string; path?: string }; + +type CursorSessionResult = + | { ok: true; data: CursorSessionSummary } + | { ok: false; error: string; path?: string; available?: string[] }; + +/** Extract a concise content preview from a transcript entry. */ +function getContentPreview(entry: TranscriptEntry): string { + const content = entry.message?.content; + if (typeof content === "string") { + return content.length > 500 ? content.slice(0, 500) + "..." : content; + } + if (Array.isArray(content)) { + const parts: string[] = []; + for (const block of content) { + if (typeof block !== "object" || block === null) continue; + const b = block as Record; + if (b.type === "text" && typeof b.text === "string") { + const text = b.text as string; + parts.push(text.length > 300 ? text.slice(0, 300) + "..." : text); + } else if (b.type === "tool_use") { + parts.push(`[tool_use: ${b.name}]`); + } else if (b.type === "tool_result") { + parts.push( + `[tool_result: ${String(b.tool_use_id ?? "").slice(0, 20)}]` + ); + } + } + return parts.join(" | "); + } + return ""; +} + +export interface ReadSessionOptions { + maxEntries?: number; +} + +export interface ReadCursorSessionOptions extends ReadSessionOptions { + sessionId?: string; +} + +/** + * Read the most recent Claude Code session transcript for a project. + * Falls back to cwd when no project root is found. + */ +export async function readClaudeCodeSession( + projectRoot: string | null, + options?: ReadSessionOptions +): Promise { + const limit = options?.maxEntries ?? 200; + const encodedPath = encodeProjectPath(projectRoot ?? process.cwd()); + const projectsDir = join(homedir(), ".claude", "projects", encodedPath); + + let files: string[]; + try { + files = readdirSync(projectsDir) + .filter((f) => f.endsWith(".jsonl")) + .map((f) => ({ name: f, mtime: statSync(join(projectsDir, f)).mtimeMs })) + .sort((a, b) => b.mtime - a.mtime) + .map((f) => f.name); + } catch { + return { ok: false, error: "No session files found", path: projectsDir }; + } + + if (files.length === 0) { + return { + ok: false, + error: "No JSONL session files found", + path: projectsDir, + }; + } + + const sessionFile = join(projectsDir, files[0]); + let entries: TranscriptEntry[]; + try { + const raw = await Bun.file(sessionFile).text(); + entries = Bun.JSONL.parse(raw) as TranscriptEntry[]; + } catch { + return { + ok: false, + error: "Failed to read session file", + path: sessionFile, + }; + } + + const relevant: ClaudeSessionSummary["transcript"] = []; + for (const entry of entries) { + if (!RELEVANT_TYPES.has(entry.type ?? "")) continue; + relevant.push({ + type: entry.type!, + role: entry.message?.role, + contentPreview: getContentPreview(entry), + }); + } + + const trimmed = relevant.length > limit ? relevant.slice(-limit) : relevant; + return { + ok: true, + data: { + sessionFile: basename(sessionFile), + totalEntries: entries.length, + relevantEntries: relevant.length, + transcript: trimmed, + }, + }; +} + +/** + * Read a Cursor agent session transcript for a project. + * Falls back to cwd when no project root is found. + */ +export async function readCursorSession( + projectRoot: string | null, + options?: ReadCursorSessionOptions +): Promise { + const limit = options?.maxEntries ?? 200; + const encodedPath = encodeProjectPath(projectRoot ?? process.cwd()); + const transcriptsDir = join( + homedir(), + ".cursor", + "projects", + encodedPath, + "agent-transcripts" + ); + + let sessionDirs: Array<{ name: string; mtime: number }>; + try { + sessionDirs = readdirSync(transcriptsDir) + .map((name) => { + const fullPath = join(transcriptsDir, name); + try { + const stat = statSync(fullPath); + return stat.isDirectory() ? { name, mtime: stat.mtimeMs } : null; + } catch { + return null; + } + }) + .filter((d): d is { name: string; mtime: number } => d !== null) + .sort((a, b) => b.mtime - a.mtime); + } catch { + return { + ok: false, + error: "No Cursor agent-transcripts directory found", + path: transcriptsDir, + }; + } + + if (sessionDirs.length === 0) { + return { + ok: false, + error: "No session directories found", + path: transcriptsDir, + }; + } + + const targetDir = options?.sessionId + ? sessionDirs.find((d) => d.name === options.sessionId) + : sessionDirs[0]; + + if (!targetDir) { + return { + ok: false, + error: `Session not found: ${options?.sessionId}`, + available: sessionDirs.map((d) => d.name), + }; + } + + const sessionFile = join( + transcriptsDir, + targetDir.name, + `${targetDir.name}.jsonl` + ); + let entries: TranscriptEntry[]; + try { + const raw = await Bun.file(sessionFile).text(); + entries = Bun.JSONL.parse(raw) as TranscriptEntry[]; + } catch { + return { + ok: false, + error: "Failed to read session file", + path: sessionFile, + }; + } + + const relevant: CursorSessionSummary["transcript"] = []; + for (const entry of entries) { + if (!RELEVANT_ROLES.has(entry.role ?? "")) continue; + relevant.push({ + role: entry.role!, + contentPreview: getContentPreview(entry), + }); + } + + const trimmed = relevant.length > limit ? relevant.slice(-limit) : relevant; + return { + ok: true, + data: { + sessionId: targetDir.name, + sessionFile: basename(sessionFile), + totalEntries: entries.length, + relevantEntries: relevant.length, + transcript: trimmed, + }, + }; +} diff --git a/src/mcp/resources.ts b/src/mcp/resources.ts deleted file mode 100644 index ddd6f814..00000000 --- a/src/mcp/resources.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { - type McpServer, - ResourceTemplate, -} from "@modelcontextprotocol/sdk/server/mcp.js"; -import { readdirSync } from "node:fs"; -import { join } from "node:path"; -import { parseAdr } from "../formats/adr"; - -export function registerResources( - server: McpServer, - projectRoot: string | null -) { - const adrsDir = - projectRoot === null ? null : join(projectRoot, ".archgate", "adrs"); - - // Resource template: adr://{id} — returns full ADR markdown - server.registerResource( - "adr", - new ResourceTemplate("adr://{id}", { list: undefined }), - { description: "Get full ADR markdown by ID" }, - async (uri, variables) => { - const requestedId = variables.id as string; - - if (adrsDir === null) { - return { - contents: [ - { - uri: uri.href, - mimeType: "text/plain", - text: "No archgate project found. Invoke the @archgate:onboard skill to initialize governance.", - }, - ], - }; - } - - let files: string[]; - try { - files = readdirSync(adrsDir).filter((f) => f.endsWith(".md")); - } catch { - return { - contents: [ - { - uri: uri.href, - mimeType: "text/plain", - text: `ADR directory not found`, - }, - ], - }; - } - - for (const file of files) { - try { - const filePath = join(adrsDir, file); - // oxlint-disable-next-line no-await-in-loop -- sequential file search - const content = await Bun.file(filePath).text(); - const adr = parseAdr(content, filePath); - if (adr.frontmatter.id === requestedId) { - return { - contents: [ - { - uri: uri.href, - mimeType: "text/markdown", - text: content, - }, - ], - }; - } - } catch { - // Skip unparseable - } - } - - return { - contents: [ - { - uri: uri.href, - mimeType: "text/plain", - text: `ADR ${requestedId} not found`, - }, - ], - }; - } - ); -} diff --git a/src/mcp/server.ts b/src/mcp/server.ts deleted file mode 100644 index 411f7611..00000000 --- a/src/mcp/server.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; -import { registerTools } from "./tools/index"; -import { registerResources } from "./resources"; -import packageJson from "../../package.json"; - -export function createMcpServer(projectRoot: string | null): McpServer { - const server = new McpServer({ - name: "archgate", - version: packageJson.version, - }); - - registerTools(server, projectRoot); - registerResources(server, projectRoot); - - return server; -} - -export async function startStdioServer( - projectRoot: string | null -): Promise { - const server = createMcpServer(projectRoot); - const transport = new StdioServerTransport(); - await server.connect(transport); -} diff --git a/src/mcp/tools/check.ts b/src/mcp/tools/check.ts deleted file mode 100644 index c7d76e42..00000000 --- a/src/mcp/tools/check.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { z } from "zod"; -import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { loadRuleAdrs } from "../../engine/loader"; -import { runChecks } from "../../engine/runner"; -import { buildSummary } from "../../engine/reporter"; -import { noProjectResponse } from "./no-project"; - -export function registerCheckTool( - server: McpServer, - projectRoot: string | null -) { - server.registerTool( - "check", - { - description: "Run ADR compliance checks against the codebase", - inputSchema: { - adrId: z - .string() - .optional() - .describe("Only check a specific ADR by ID"), - staged: z.boolean().optional().describe("Only check git-staged files"), - }, - }, - async ({ adrId, staged }) => { - if (projectRoot === null) { - return noProjectResponse(); - } - - const loadedAdrs = await loadRuleAdrs(projectRoot, adrId); - - if (loadedAdrs.length === 0) { - return { - content: [ - { - type: "text" as const, - text: JSON.stringify({ - pass: true, - total: 0, - results: [], - message: "No rules to check", - }), - }, - ], - }; - } - - const result = await runChecks(projectRoot, loadedAdrs, { - staged, - }); - const summary = buildSummary(result, { - maxViolationsPerRule: 20, - }); - - return { - content: [ - { type: "text" as const, text: JSON.stringify(summary, null, 2) }, - ], - }; - } - ); -} diff --git a/src/mcp/tools/claude-code-session-context.ts b/src/mcp/tools/claude-code-session-context.ts deleted file mode 100644 index 99398b36..00000000 --- a/src/mcp/tools/claude-code-session-context.ts +++ /dev/null @@ -1,180 +0,0 @@ -import { z } from "zod"; -import { readdirSync, statSync } from "node:fs"; -import { join, basename } from "node:path"; -import { homedir } from "node:os"; -import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; - -/** Encode a project root path into the Claude projects directory name. */ -function encodeProjectPath(projectRoot: string): string { - return projectRoot.replaceAll("/", "-"); -} - -/** Types we care about from the JSONL transcript. */ -const RELEVANT_TYPES = new Set(["user", "assistant"]); - -interface TranscriptEntry { - type: string; - message?: { - role?: string; - content?: unknown; - }; - [key: string]: unknown; -} - -interface SessionSummary { - sessionFile: string; - totalEntries: number; - relevantEntries: number; - transcript: Array<{ - type: string; - role?: string; - contentPreview: string; - }>; -} - -/** Extract a concise content preview from a transcript entry. */ -function getContentPreview(entry: TranscriptEntry): string { - const content = entry.message?.content; - if (typeof content === "string") { - return content.length > 500 ? content.slice(0, 500) + "..." : content; - } - if (Array.isArray(content)) { - const parts: string[] = []; - for (const block of content) { - if (typeof block !== "object" || block === null) continue; - const b = block as Record; - if (b.type === "text" && typeof b.text === "string") { - const text = b.text as string; - parts.push(text.length > 300 ? text.slice(0, 300) + "..." : text); - } else if (b.type === "tool_use") { - parts.push(`[tool_use: ${b.name}]`); - } else if (b.type === "tool_result") { - parts.push( - `[tool_result: ${String(b.tool_use_id ?? "").slice(0, 20)}]` - ); - } - } - return parts.join(" | "); - } - return ""; -} - -export function registerClaudeCodeSessionContextTool( - server: McpServer, - projectRoot: string | null -) { - server.registerTool( - "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.", - inputSchema: { - maxEntries: z - .number() - .optional() - .describe( - "Maximum number of relevant entries to return (default: 200). Returns the most recent entries." - ), - }, - }, - async ({ maxEntries }) => { - const limit = maxEntries ?? 200; - // Fall back to cwd when no project root found — session files are keyed - // by the directory the user is working in, not by archgate project state. - const encodedPath = encodeProjectPath(projectRoot ?? process.cwd()); - const projectsDir = join(homedir(), ".claude", "projects", encodedPath); - - // Find all JSONL files sorted by modification time (most recent first) - let files: string[]; - try { - files = readdirSync(projectsDir) - .filter((f) => f.endsWith(".jsonl")) - .map((f) => ({ - name: f, - mtime: statSync(join(projectsDir, f)).mtimeMs, - })) - .sort((a, b) => b.mtime - a.mtime) - .map((f) => f.name); - } catch { - return { - content: [ - { - type: "text" as const, - text: JSON.stringify({ - error: "No session files found", - path: projectsDir, - }), - }, - ], - }; - } - - if (files.length === 0) { - return { - content: [ - { - type: "text" as const, - text: JSON.stringify({ - error: "No JSONL session files found", - path: projectsDir, - }), - }, - ], - }; - } - - // Read and parse the most recent session file using Bun's native JSONL parser - const sessionFile = join(projectsDir, files[0]); - let entries: TranscriptEntry[]; - try { - const raw = await Bun.file(sessionFile).text(); - entries = Bun.JSONL.parse(raw) as TranscriptEntry[]; - } catch { - return { - content: [ - { - type: "text" as const, - text: JSON.stringify({ - error: "Failed to read session file", - file: sessionFile, - }), - }, - ], - }; - } - - // Filter for relevant types - const relevant: Array<{ - type: string; - role?: string; - contentPreview: string; - }> = []; - - for (const entry of entries) { - if (!RELEVANT_TYPES.has(entry.type)) continue; - relevant.push({ - type: entry.type, - role: entry.message?.role, - contentPreview: getContentPreview(entry), - }); - } - - // Return the most recent entries up to the limit - const trimmed = - relevant.length > limit ? relevant.slice(-limit) : relevant; - - const summary: SessionSummary = { - sessionFile: basename(sessionFile), - totalEntries: entries.length, - relevantEntries: relevant.length, - transcript: trimmed, - }; - - return { - content: [ - { type: "text" as const, text: JSON.stringify(summary, null, 2) }, - ], - }; - } - ); -} diff --git a/src/mcp/tools/cursor-session-context.ts b/src/mcp/tools/cursor-session-context.ts deleted file mode 100644 index 7241a975..00000000 --- a/src/mcp/tools/cursor-session-context.ts +++ /dev/null @@ -1,218 +0,0 @@ -import { z } from "zod"; -import { readdirSync, statSync } from "node:fs"; -import { join, basename } from "node:path"; -import { homedir } from "node:os"; -import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; - -/** Encode a project root path into the Cursor projects directory name. */ -function encodeProjectPath(projectRoot: string): string { - return projectRoot.replaceAll("/", "-"); -} - -/** Roles we care about from the Cursor transcript. */ -const RELEVANT_ROLES = new Set(["user", "assistant"]); - -interface ContentBlock { - type: string; - text?: string; - [key: string]: unknown; -} - -interface TranscriptEntry { - role: string; - message?: { - content?: unknown; - }; - [key: string]: unknown; -} - -interface SessionSummary { - sessionId: string; - sessionFile: string; - totalEntries: number; - relevantEntries: number; - transcript: Array<{ - role: string; - contentPreview: string; - }>; -} - -/** Extract a concise content preview from a transcript entry. */ -function getContentPreview(entry: TranscriptEntry): string { - const content = entry.message?.content; - if (typeof content === "string") { - return content.length > 500 ? content.slice(0, 500) + "..." : content; - } - if (Array.isArray(content)) { - const parts: string[] = []; - for (const block of content as ContentBlock[]) { - if (typeof block !== "object" || block === null) continue; - if (block.type === "text" && typeof block.text === "string") { - const text = block.text; - parts.push(text.length > 300 ? text.slice(0, 300) + "..." : text); - } else if (block.type === "tool_use") { - parts.push(`[tool_use: ${block.name}]`); - } else if (block.type === "tool_result") { - parts.push( - `[tool_result: ${String(block.tool_use_id ?? "").slice(0, 20)}]` - ); - } - } - return parts.join(" | "); - } - return ""; -} - -export function registerCursorSessionContextTool( - server: McpServer, - projectRoot: string | null -) { - server.registerTool( - "cursor_session_context", - { - description: - "Read Cursor agent session transcripts for the project. Returns filtered entries (user + assistant messages) from Cursor's agent-transcripts JSONL files. Use this to access context from Cursor agent conversations.", - inputSchema: { - maxEntries: z - .number() - .optional() - .describe( - "Maximum number of relevant entries to return (default: 200). Returns the most recent entries." - ), - sessionId: z - .string() - .optional() - .describe( - "Specific session UUID to read. If omitted, reads the most recent session." - ), - }, - }, - async ({ maxEntries, sessionId }) => { - const limit = maxEntries ?? 200; - const encodedPath = encodeProjectPath(projectRoot ?? process.cwd()); - const transcriptsDir = join( - homedir(), - ".cursor", - "projects", - encodedPath, - "agent-transcripts" - ); - - // Find all session directories sorted by modification time (most recent first) - let sessionDirs: Array<{ name: string; mtime: number }>; - try { - sessionDirs = readdirSync(transcriptsDir) - .map((name) => { - const fullPath = join(transcriptsDir, name); - try { - const stat = statSync(fullPath); - return stat.isDirectory() ? { name, mtime: stat.mtimeMs } : null; - } catch { - return null; - } - }) - .filter((d): d is { name: string; mtime: number } => d !== null) - .sort((a, b) => b.mtime - a.mtime); - } catch { - return { - content: [ - { - type: "text" as const, - text: JSON.stringify({ - error: "No Cursor agent-transcripts directory found", - path: transcriptsDir, - }), - }, - ], - }; - } - - if (sessionDirs.length === 0) { - return { - content: [ - { - type: "text" as const, - text: JSON.stringify({ - error: "No session directories found", - path: transcriptsDir, - }), - }, - ], - }; - } - - // Pick the target session - const targetDir = sessionId - ? sessionDirs.find((d) => d.name === sessionId) - : sessionDirs[0]; - - if (!targetDir) { - return { - content: [ - { - type: "text" as const, - text: JSON.stringify({ - error: `Session not found: ${sessionId}`, - available: sessionDirs.map((d) => d.name), - }), - }, - ], - }; - } - - // Read the JSONL file inside the session directory (/.jsonl) - const sessionFile = join( - transcriptsDir, - targetDir.name, - `${targetDir.name}.jsonl` - ); - - let entries: TranscriptEntry[]; - try { - const raw = await Bun.file(sessionFile).text(); - entries = Bun.JSONL.parse(raw) as TranscriptEntry[]; - } catch { - return { - content: [ - { - type: "text" as const, - text: JSON.stringify({ - error: "Failed to read session file", - file: sessionFile, - }), - }, - ], - }; - } - - // Filter for relevant roles - const relevant: Array<{ role: string; contentPreview: string }> = []; - - for (const entry of entries) { - if (!RELEVANT_ROLES.has(entry.role)) continue; - relevant.push({ - role: entry.role, - contentPreview: getContentPreview(entry), - }); - } - - // Return the most recent entries up to the limit - const trimmed = - relevant.length > limit ? relevant.slice(-limit) : relevant; - - const summary: SessionSummary = { - sessionId: targetDir.name, - sessionFile: basename(sessionFile), - totalEntries: entries.length, - relevantEntries: relevant.length, - transcript: trimmed, - }; - - return { - content: [ - { type: "text" as const, text: JSON.stringify(summary, null, 2) }, - ], - }; - } - ); -} diff --git a/src/mcp/tools/index.ts b/src/mcp/tools/index.ts deleted file mode 100644 index 9702bb1c..00000000 --- a/src/mcp/tools/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { registerCheckTool } from "./check"; -import { registerListAdrsTool } from "./list-adrs"; -import { registerReviewContextTool } from "./review-context"; -import { registerClaudeCodeSessionContextTool } from "./claude-code-session-context"; -import { registerCursorSessionContextTool } from "./cursor-session-context"; - -export function registerTools(server: McpServer, projectRoot: string | null) { - registerCheckTool(server, projectRoot); - registerListAdrsTool(server, projectRoot); - registerReviewContextTool(server, projectRoot); - registerClaudeCodeSessionContextTool(server, projectRoot); - registerCursorSessionContextTool(server, projectRoot); -} diff --git a/src/mcp/tools/list-adrs.ts b/src/mcp/tools/list-adrs.ts deleted file mode 100644 index 439d02e7..00000000 --- a/src/mcp/tools/list-adrs.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { z } from "zod"; -import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { readdirSync } from "node:fs"; -import { join } from "node:path"; -import { parseAdr } from "../../formats/adr"; -import { noProjectResponse } from "./no-project"; - -export function registerListAdrsTool( - server: McpServer, - projectRoot: string | null -) { - server.registerTool( - "list_adrs", - { - description: "List all ADRs in the project", - inputSchema: { - domain: z - .string() - .optional() - .describe( - "Filter by domain (backend, frontend, data, architecture, general)" - ), - }, - }, - async ({ domain }) => { - if (projectRoot === null) { - return noProjectResponse(); - } - - const adrsDir = join(projectRoot, ".archgate", "adrs"); - const adrs: Array<{ - id: string; - title: string; - domain: string; - rules: boolean; - }> = []; - - let files: string[]; - try { - files = readdirSync(adrsDir).filter((f) => f.endsWith(".md")); - } catch { - return { - content: [{ type: "text" as const, text: JSON.stringify([]) }], - }; - } - - for (const file of files) { - try { - // oxlint-disable-next-line no-await-in-loop -- sequential file discovery - const content = await Bun.file(join(adrsDir, file)).text(); - const adr = parseAdr(content, join(adrsDir, file)); - if (domain && adr.frontmatter.domain !== domain) continue; - adrs.push({ - id: adr.frontmatter.id, - title: adr.frontmatter.title, - domain: adr.frontmatter.domain, - rules: adr.frontmatter.rules, - }); - } catch { - // Skip unparseable - } - } - - return { - content: [ - { type: "text" as const, text: JSON.stringify(adrs, null, 2) }, - ], - }; - } - ); -} diff --git a/src/mcp/tools/no-project.ts b/src/mcp/tools/no-project.ts deleted file mode 100644 index 32387dac..00000000 --- a/src/mcp/tools/no-project.ts +++ /dev/null @@ -1,18 +0,0 @@ -/** Shared MCP tool response returned when no archgate project is found. */ -export function noProjectResponse() { - return { - content: [ - { - type: "text" as const, - text: JSON.stringify({ - error: "no_project", - message: - "No archgate project found in this directory or any parent directory.", - action: - "Invoke the @archgate:onboard skill to initialize archgate governance in this project. " + - "It will run 'archgate init', explore the codebase, interview you, and create the initial set of Architecture Decision Records.", - }), - }, - ], - }; -} diff --git a/src/mcp/tools/review-context.ts b/src/mcp/tools/review-context.ts deleted file mode 100644 index 3ad9e8de..00000000 --- a/src/mcp/tools/review-context.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { z } from "zod"; -import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { buildReviewContext } from "../../engine/context"; -import { AdrFrontmatterSchema } from "../../formats/adr"; -import { noProjectResponse } from "./no-project"; - -export function registerReviewContextTool( - server: McpServer, - projectRoot: string | null -) { - server.registerTool( - "review_context", - { - description: - "Pre-compute review context for architecture validation. Returns changed files grouped by domain with applicable ADR briefings (Decision + Do's/Don'ts sections only) and optional check results. Use this instead of manually calling list_adrs + reading each ADR file.", - inputSchema: { - staged: z - .boolean() - .optional() - .describe( - "When true, only include git-staged files. When false (default), include all changed files (staged + unstaged)." - ), - runChecks: z - .boolean() - .optional() - .describe( - "When true, run automated ADR compliance checks and include results in the response." - ), - domain: AdrFrontmatterSchema.shape.domain - .optional() - .describe( - "Filter results to a single domain (backend, frontend, data, architecture, general). When set, only briefings for that domain are returned." - ), - }, - }, - async ({ staged, runChecks, domain }) => { - if (projectRoot === null) { - return noProjectResponse(); - } - - const context = await buildReviewContext(projectRoot, { - staged: staged ?? false, - runChecks: runChecks ?? false, - domain: domain ?? undefined, - }); - - return { - content: [ - { - type: "text" as const, - text: JSON.stringify(context, null, 2), - }, - ], - }; - } - ); -} diff --git a/tests/commands/mcp.test.ts b/tests/commands/mcp.test.ts deleted file mode 100644 index ab9ee9b8..00000000 --- a/tests/commands/mcp.test.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { describe, expect, test } from "bun:test"; -import { Command } from "@commander-js/extra-typings"; -import { registerMcpCommand } from "../../src/commands/mcp"; - -describe("registerMcpCommand", () => { - test("registers 'mcp' as a subcommand", () => { - const program = new Command(); - registerMcpCommand(program); - const sub = program.commands.find((c) => c.name() === "mcp"); - expect(sub).toBeDefined(); - }); - - test("has a description", () => { - const program = new Command(); - registerMcpCommand(program); - const sub = program.commands.find((c) => c.name() === "mcp")!; - expect(sub.description()).toBeTruthy(); - }); -}); diff --git a/tests/commands/review-context.test.ts b/tests/commands/review-context.test.ts new file mode 100644 index 00000000..30f282a3 --- /dev/null +++ b/tests/commands/review-context.test.ts @@ -0,0 +1,43 @@ +import { describe, expect, test } from "bun:test"; +import { Command } from "@commander-js/extra-typings"; +import { registerReviewContextCommand } from "../../src/commands/review-context"; + +describe("registerReviewContextCommand", () => { + test("registers 'review-context' as a subcommand", () => { + const program = new Command(); + registerReviewContextCommand(program); + const sub = program.commands.find((c) => c.name() === "review-context"); + expect(sub).toBeDefined(); + }); + + test("has a description", () => { + const program = new Command(); + registerReviewContextCommand(program); + const sub = program.commands.find((c) => c.name() === "review-context")!; + expect(sub.description()).toBeTruthy(); + }); + + test("has --staged option", () => { + const program = new Command(); + registerReviewContextCommand(program); + const sub = program.commands.find((c) => c.name() === "review-context")!; + const opts = sub.options.map((o) => o.long); + expect(opts).toContain("--staged"); + }); + + test("has --run-checks option", () => { + const program = new Command(); + registerReviewContextCommand(program); + const sub = program.commands.find((c) => c.name() === "review-context")!; + const opts = sub.options.map((o) => o.long); + expect(opts).toContain("--run-checks"); + }); + + test("has --domain option", () => { + const program = new Command(); + registerReviewContextCommand(program); + const sub = program.commands.find((c) => c.name() === "review-context")!; + const opts = sub.options.map((o) => o.long); + expect(opts).toContain("--domain"); + }); +}); diff --git a/tests/commands/session-context.test.ts b/tests/commands/session-context.test.ts new file mode 100644 index 00000000..03daa2ac --- /dev/null +++ b/tests/commands/session-context.test.ts @@ -0,0 +1,62 @@ +import { describe, expect, test } from "bun:test"; +import { Command } from "@commander-js/extra-typings"; +import { registerSessionContextCommand } from "../../src/commands/session-context/index"; + +describe("registerSessionContextCommand", () => { + test("registers 'session-context' as a subcommand", () => { + const program = new Command(); + registerSessionContextCommand(program); + const sub = program.commands.find((c) => c.name() === "session-context"); + expect(sub).toBeDefined(); + }); + + test("has a description", () => { + const program = new Command(); + registerSessionContextCommand(program); + const sub = program.commands.find((c) => c.name() === "session-context")!; + expect(sub.description()).toBeTruthy(); + }); + + test("registers 'claude-code' subcommand", () => { + const program = new Command(); + registerSessionContextCommand(program); + const parent = program.commands.find( + (c) => c.name() === "session-context" + )!; + const sub = parent.commands.find((c) => c.name() === "claude-code"); + expect(sub).toBeDefined(); + }); + + test("registers 'cursor' subcommand", () => { + const program = new Command(); + registerSessionContextCommand(program); + const parent = program.commands.find( + (c) => c.name() === "session-context" + )!; + const sub = parent.commands.find((c) => c.name() === "cursor"); + expect(sub).toBeDefined(); + }); + + test("claude-code subcommand has --max-entries option", () => { + const program = new Command(); + registerSessionContextCommand(program); + const parent = program.commands.find( + (c) => c.name() === "session-context" + )!; + const sub = parent.commands.find((c) => c.name() === "claude-code")!; + const opts = sub.options.map((o) => o.long); + expect(opts).toContain("--max-entries"); + }); + + test("cursor subcommand has --max-entries and --session-id options", () => { + const program = new Command(); + registerSessionContextCommand(program); + const parent = program.commands.find( + (c) => c.name() === "session-context" + )!; + const sub = parent.commands.find((c) => c.name() === "cursor")!; + const opts = sub.options.map((o) => o.long); + expect(opts).toContain("--max-entries"); + expect(opts).toContain("--session-id"); + }); +}); diff --git a/tests/commands/session-context/claude-code.test.ts b/tests/commands/session-context/claude-code.test.ts new file mode 100644 index 00000000..d9075d7c --- /dev/null +++ b/tests/commands/session-context/claude-code.test.ts @@ -0,0 +1,27 @@ +import { describe, expect, test } from "bun:test"; +import { Command } from "@commander-js/extra-typings"; +import { registerClaudeCodeSessionContextCommand } from "../../../src/commands/session-context/claude-code"; + +describe("registerClaudeCodeSessionContextCommand", () => { + test("registers 'claude-code' as a subcommand", () => { + const parent = new Command("session-context"); + registerClaudeCodeSessionContextCommand(parent); + const sub = parent.commands.find((c) => c.name() === "claude-code"); + expect(sub).toBeDefined(); + }); + + test("has a description", () => { + const parent = new Command("session-context"); + registerClaudeCodeSessionContextCommand(parent); + const sub = parent.commands.find((c) => c.name() === "claude-code")!; + expect(sub.description()).toBeTruthy(); + }); + + test("accepts --max-entries option", () => { + const parent = new Command("session-context"); + registerClaudeCodeSessionContextCommand(parent); + const sub = parent.commands.find((c) => c.name() === "claude-code")!; + const opt = sub.options.find((o) => o.long === "--max-entries"); + expect(opt).toBeDefined(); + }); +}); diff --git a/tests/commands/session-context/cursor.test.ts b/tests/commands/session-context/cursor.test.ts new file mode 100644 index 00000000..73b2e849 --- /dev/null +++ b/tests/commands/session-context/cursor.test.ts @@ -0,0 +1,35 @@ +import { describe, expect, test } from "bun:test"; +import { Command } from "@commander-js/extra-typings"; +import { registerCursorSessionContextCommand } from "../../../src/commands/session-context/cursor"; + +describe("registerCursorSessionContextCommand", () => { + test("registers 'cursor' as a subcommand", () => { + const parent = new Command("session-context"); + registerCursorSessionContextCommand(parent); + const sub = parent.commands.find((c) => c.name() === "cursor"); + expect(sub).toBeDefined(); + }); + + test("has a description", () => { + const parent = new Command("session-context"); + registerCursorSessionContextCommand(parent); + const sub = parent.commands.find((c) => c.name() === "cursor")!; + expect(sub.description()).toBeTruthy(); + }); + + test("accepts --max-entries option", () => { + const parent = new Command("session-context"); + registerCursorSessionContextCommand(parent); + const sub = parent.commands.find((c) => c.name() === "cursor")!; + const opt = sub.options.find((o) => o.long === "--max-entries"); + expect(opt).toBeDefined(); + }); + + test("accepts --session-id option", () => { + const parent = new Command("session-context"); + registerCursorSessionContextCommand(parent); + const sub = parent.commands.find((c) => c.name() === "cursor")!; + const opt = sub.options.find((o) => o.long === "--session-id"); + expect(opt).toBeDefined(); + }); +}); diff --git a/tests/helpers/claude-settings.test.ts b/tests/helpers/claude-settings.test.ts index 6d86e5b8..1f65ceba 100644 --- a/tests/helpers/claude-settings.test.ts +++ b/tests/helpers/claude-settings.test.ts @@ -13,11 +13,8 @@ describe("mergeClaudeSettings", () => { const result = mergeClaudeSettings({}, ARCHGATE_CLAUDE_SETTINGS); expect(result.agent).toBe("archgate:developer"); - expect(result.enableAllProjectMcpServers).toBe(true); - expect(result.enabledMcpjsonServers).toEqual(["archgate"]); expect(result.permissions).toEqual({ allow: [ - "mcp__plugin_archgate_archgate__*", "Skill(archgate:architect)", "Skill(archgate:quality-manager)", "Skill(archgate:adr-author)", @@ -34,32 +31,11 @@ describe("mergeClaudeSettings", () => { expect(result.agent).toBe("custom-agent"); }); - test("preserves existing enableAllProjectMcpServers (does not overwrite)", () => { - const result = mergeClaudeSettings( - { enableAllProjectMcpServers: false }, - ARCHGATE_CLAUDE_SETTINGS - ); - - expect(result.enableAllProjectMcpServers).toBe(false); - }); - - test("appends enabledMcpjsonServers with dedup", () => { - const result = mergeClaudeSettings( - { enabledMcpjsonServers: ["existing-server", "archgate"] }, - ARCHGATE_CLAUDE_SETTINGS - ); - - expect(result.enabledMcpjsonServers).toEqual([ - "existing-server", - "archgate", - ]); - }); - test("appends permissions.allow with dedup", () => { const result = mergeClaudeSettings( { permissions: { - allow: ["Bash(git *)", "mcp__plugin_archgate_archgate__*"], + allow: ["Bash(git *)", "Skill(archgate:architect)"], }, }, ARCHGATE_CLAUDE_SETTINGS @@ -68,7 +44,6 @@ describe("mergeClaudeSettings", () => { const permissions = result.permissions as Record; expect(permissions.allow).toEqual([ "Bash(git *)", - "mcp__plugin_archgate_archgate__*", "Skill(archgate:architect)", "Skill(archgate:quality-manager)", "Skill(archgate:adr-author)", @@ -104,15 +79,6 @@ describe("mergeClaudeSettings", () => { expect(result.anotherKey).toBe(42); }); - test("handles non-array enabledMcpjsonServers gracefully", () => { - const result = mergeClaudeSettings( - { enabledMcpjsonServers: "not-an-array" }, - ARCHGATE_CLAUDE_SETTINGS - ); - - expect(result.enabledMcpjsonServers).toEqual(["archgate"]); - }); - test("handles non-object permissions gracefully", () => { const result = mergeClaudeSettings( { permissions: "invalid" }, @@ -145,8 +111,7 @@ describe("configureClaudeSettings", () => { const content = JSON.parse(await Bun.file(settingsPath).text()); expect(content.agent).toBe("archgate:developer"); - expect(content.enableAllProjectMcpServers).toBe(true); - expect(content.enabledMcpjsonServers).toEqual(["archgate"]); + expect(content.permissions.allow).toContain("Skill(archgate:architect)"); }); test("merges into existing file without overwriting user entries", async () => { @@ -179,11 +144,7 @@ describe("configureClaudeSettings", () => { expect(content.permissions.deny).toEqual(["Bash(rm *)"]); // Allow permissions appended expect(content.permissions.allow).toContain("Bash(git *)"); - expect(content.permissions.allow).toContain( - "mcp__plugin_archgate_archgate__*" - ); - // Archgate MCP server added - expect(content.enabledMcpjsonServers).toContain("archgate"); + expect(content.permissions.allow).toContain("Skill(archgate:architect)"); }); test("returns correct absolute path", async () => { diff --git a/tests/helpers/cursor-settings.test.ts b/tests/helpers/cursor-settings.test.ts index e7e5720a..26f16aea 100644 --- a/tests/helpers/cursor-settings.test.ts +++ b/tests/helpers/cursor-settings.test.ts @@ -1,91 +1,23 @@ import { describe, expect, test, beforeEach, afterEach } from "bun:test"; -import { mkdtempSync, rmSync, existsSync, mkdirSync } from "node:fs"; +import { mkdtempSync, rmSync, existsSync } 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"], - }); +describe("ARCHGATE_CURSOR_RULE", () => { + test("references CLI commands instead of MCP tools", () => { + expect(ARCHGATE_CURSOR_RULE).toContain("archgate review-context"); + expect(ARCHGATE_CURSOR_RULE).toContain("archgate check --staged"); + expect(ARCHGATE_CURSOR_RULE).toContain("archgate adr list"); + expect(ARCHGATE_CURSOR_RULE).not.toContain("MCP tool"); + expect(ARCHGATE_CURSOR_RULE).not.toContain("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"], - }, - }); + test("has alwaysApply frontmatter", () => { + expect(ARCHGATE_CURSOR_RULE).toContain("alwaysApply: true"); }); }); @@ -100,70 +32,32 @@ describe("configureCursorSettings", () => { rmSync(tempDir, { recursive: true, force: true }); }); - test("creates .cursor/ dir, mcp.json, and rules file when nothing exists", async () => { - const mcpConfigPath = await configureCursorSettings(tempDir); + test("creates .cursor/rules/ dir and governance rule file", async () => { + const rulePath = 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"], - }); + expect(existsSync(join(tempDir, ".cursor", "rules"))).toBe(true); + expect(existsSync(rulePath)).toBe(true); }); - test("writes the governance rule file with alwaysApply", async () => { - await configureCursorSettings(tempDir); + test("writes the governance rule file with correct content", async () => { + const rulePath = 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) - ); - + test("does not create mcp.json", async () => { 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"], - }); + expect(existsSync(join(tempDir, ".cursor", "mcp.json"))).toBe(false); }); - test("returns correct absolute path to mcp.json", async () => { - const mcpConfigPath = await configureCursorSettings(tempDir); + test("returns correct absolute path to rules file", async () => { + const rulePath = await configureCursorSettings(tempDir); - expect(mcpConfigPath).toBe(join(tempDir, ".cursor", "mcp.json")); + expect(rulePath).toBe( + join(tempDir, ".cursor", "rules", "archgate-governance.mdc") + ); }); }); diff --git a/tests/helpers/init-project.test.ts b/tests/helpers/init-project.test.ts index c3928bcf..f97f9428 100644 --- a/tests/helpers/init-project.test.ts +++ b/tests/helpers/init-project.test.ts @@ -69,12 +69,6 @@ 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, @@ -84,13 +78,16 @@ describe("initProject", () => { ); expect(existsSync(rulePath)).toBe(true); + // MCP config should NOT exist (MCP removed) + expect(existsSync(join(tempDir, ".cursor", "mcp.json"))).toBe(false); + // 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); + // Result should point to cursor rule file + expect(result.editorSettingsPath).toBe(rulePath); }); test("skips example ADR when ADRs already exist", async () => { @@ -117,7 +114,8 @@ describe("initProject", () => { const content = JSON.parse(await Bun.file(settingsPath).text()); expect(content.agent).toBe("archgate:developer"); - expect(content.enabledMcpjsonServers).toContain("archgate"); + // MCP settings should not be present (MCP removed) + expect(content.enabledMcpjsonServers).toBeUndefined(); }); test("includes editorSettingsPath in result", async () => { diff --git a/tests/helpers/session-context.test.ts b/tests/helpers/session-context.test.ts new file mode 100644 index 00000000..abe58d95 --- /dev/null +++ b/tests/helpers/session-context.test.ts @@ -0,0 +1,51 @@ +import { describe, expect, test } from "bun:test"; +import { + encodeProjectPath, + readClaudeCodeSession, + readCursorSession, +} from "../../src/helpers/session-context"; + +describe("encodeProjectPath", () => { + test("replaces forward slashes with dashes", () => { + expect(encodeProjectPath("/home/user/project")).toBe("-home-user-project"); + }); + + test("handles paths without slashes", () => { + expect(encodeProjectPath("project")).toBe("project"); + }); + + test("handles empty string", () => { + expect(encodeProjectPath("")).toBe(""); + }); + + test("replaces multiple consecutive slashes", () => { + expect(encodeProjectPath("/a//b")).toBe("-a--b"); + }); +}); + +describe("readClaudeCodeSession", () => { + test("returns error when no session files found", async () => { + const result = await readClaudeCodeSession("/nonexistent/path"); + expect(result.ok).toBe(false); + if (!result.ok) { + expect(result.error).toContain("No session files found"); + } + }); + + test("returns error for non-existent project dir", async () => { + const result = await readClaudeCodeSession("/definitely/not/a/real/path"); + expect(result.ok).toBe(false); + }); +}); + +describe("readCursorSession", () => { + test("returns error when no transcripts directory found", async () => { + const result = await readCursorSession("/nonexistent/path"); + expect(result.ok).toBe(false); + if (!result.ok) { + expect(result.error).toContain( + "No Cursor agent-transcripts directory found" + ); + } + }); +}); diff --git a/tests/mcp/resources.test.ts b/tests/mcp/resources.test.ts deleted file mode 100644 index 85a2840f..00000000 --- a/tests/mcp/resources.test.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { describe, expect, test, beforeEach, afterEach } from "bun:test"; -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 { registerResources } from "../../src/mcp/resources"; - -describe("registerResources", () => { - let tempDir: string; - let server: McpServer; - - beforeEach(() => { - tempDir = mkdtempSync(join(tmpdir(), "archgate-mcp-res-test-")); - mkdirSync(join(tempDir, ".archgate", "adrs"), { recursive: true }); - server = new McpServer({ name: "test", version: "0.0.0" }); - }); - - afterEach(async () => { - await server.close(); - rmSync(tempDir, { recursive: true, force: true }); - }); - - test("does not throw when registering resources", () => { - expect(() => registerResources(server, tempDir)).not.toThrow(); - }); - - test("registers the adr resource template", () => { - registerResources(server, tempDir); - // If registration succeeded without throwing, the resource is registered - expect(true).toBe(true); - }); -}); diff --git a/tests/mcp/server.test.ts b/tests/mcp/server.test.ts deleted file mode 100644 index 80d22291..00000000 --- a/tests/mcp/server.test.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { describe, expect, test, beforeEach, afterEach } from "bun:test"; -import { mkdtempSync, rmSync, mkdirSync } from "node:fs"; -import { join } from "node:path"; -import { tmpdir } from "node:os"; -import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { createMcpServer } from "../../src/mcp/server"; - -describe("createMcpServer", () => { - let tempDir: string; - let server: McpServer; - - beforeEach(() => { - tempDir = mkdtempSync(join(tmpdir(), "archgate-mcp-server-test-")); - mkdirSync(join(tempDir, ".archgate", "adrs"), { recursive: true }); - server = createMcpServer(tempDir); - }); - - afterEach(async () => { - await server.close(); - rmSync(tempDir, { recursive: true, force: true }); - }); - - test("returns an McpServer instance", () => { - expect(server).toBeDefined(); - expect(typeof server.connect).toBe("function"); - }); - - test("server has tool and resource registration methods", () => { - expect(typeof server.registerTool).toBe("function"); - expect(typeof server.registerResource).toBe("function"); - }); -}); - -describe("createMcpServer with null projectRoot", () => { - let server: McpServer; - - beforeEach(() => { - server = createMcpServer(null); - }); - - afterEach(async () => { - await server.close(); - }); - - test("starts successfully without a project root", () => { - expect(server).toBeDefined(); - expect(typeof server.connect).toBe("function"); - }); -}); diff --git a/tests/mcp/tools.test.ts b/tests/mcp/tools.test.ts deleted file mode 100644 index 54aadde3..00000000 --- a/tests/mcp/tools.test.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { describe, expect, test, beforeEach, afterEach, spyOn } from "bun:test"; -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 { registerTools } from "../../src/mcp/tools/index"; - -describe("registerTools", () => { - let tempDir: string; - let server: McpServer; - - beforeEach(() => { - tempDir = mkdtempSync(join(tmpdir(), "archgate-mcp-tools-test-")); - mkdirSync(join(tempDir, ".archgate", "adrs"), { recursive: true }); - server = new McpServer({ name: "test", version: "0.0.0" }); - }); - - afterEach(async () => { - await server.close(); - rmSync(tempDir, { recursive: true, force: true }); - }); - - test("does not throw when registering tools", () => { - expect(() => registerTools(server, tempDir)).not.toThrow(); - }); - - test("registers all expected tools", () => { - const registerSpy = spyOn(server, "registerTool"); - registerTools(server, tempDir); - // The tools module registers 5 tools: check, list_adrs, review_context, claude_code_session_context, cursor_session_context - expect(registerSpy).toHaveBeenCalledTimes(5); - registerSpy.mockRestore(); - }); - - test("does not throw when projectRoot is null", () => { - expect(() => registerTools(server, null)).not.toThrow(); - }); -}); diff --git a/tests/mcp/tools/check.test.ts b/tests/mcp/tools/check.test.ts deleted file mode 100644 index f15a9821..00000000 --- a/tests/mcp/tools/check.test.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { describe, expect, test, beforeEach, afterEach, spyOn } from "bun:test"; -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 { registerCheckTool } from "../../../src/mcp/tools/check"; - -describe("registerCheckTool", () => { - let tempDir: string; - let server: McpServer; - - beforeEach(() => { - tempDir = mkdtempSync(join(tmpdir(), "archgate-mcp-check-test-")); - mkdirSync(join(tempDir, ".archgate", "adrs"), { recursive: true }); - server = new McpServer({ name: "test", version: "0.0.0" }); - }); - - afterEach(async () => { - await server.close(); - rmSync(tempDir, { recursive: true, force: true }); - }); - - test("does not throw when registering", () => { - expect(() => registerCheckTool(server, tempDir)).not.toThrow(); - }); - - test("registers exactly one tool", () => { - const registerSpy = spyOn(server, "registerTool"); - registerCheckTool(server, tempDir); - expect(registerSpy).toHaveBeenCalledTimes(1); - registerSpy.mockRestore(); - }); -}); diff --git a/tests/mcp/tools/claude-code-session-context.test.ts b/tests/mcp/tools/claude-code-session-context.test.ts deleted file mode 100644 index aa4d6ae4..00000000 --- a/tests/mcp/tools/claude-code-session-context.test.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { describe, expect, test, beforeEach, afterEach, spyOn } from "bun:test"; -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 { registerClaudeCodeSessionContextTool } from "../../../src/mcp/tools/claude-code-session-context"; - -describe("registerClaudeCodeSessionContextTool", () => { - let tempDir: string; - let server: McpServer; - - beforeEach(() => { - tempDir = mkdtempSync(join(tmpdir(), "archgate-mcp-session-context-test-")); - mkdirSync(join(tempDir, ".archgate", "adrs"), { recursive: true }); - server = new McpServer({ name: "test", version: "0.0.0" }); - }); - - afterEach(async () => { - await server.close(); - rmSync(tempDir, { recursive: true, force: true }); - }); - - test("does not throw when registering", () => { - expect(() => - registerClaudeCodeSessionContextTool(server, tempDir) - ).not.toThrow(); - }); - - test("registers exactly one tool", () => { - const registerSpy = spyOn(server, "registerTool"); - registerClaudeCodeSessionContextTool(server, tempDir); - expect(registerSpy).toHaveBeenCalledTimes(1); - registerSpy.mockRestore(); - }); -}); diff --git a/tests/mcp/tools/cursor-session-context.test.ts b/tests/mcp/tools/cursor-session-context.test.ts deleted file mode 100644 index 67522b1b..00000000 --- a/tests/mcp/tools/cursor-session-context.test.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { describe, expect, test, beforeEach, afterEach, spyOn } from "bun:test"; -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 { registerCursorSessionContextTool } from "../../../src/mcp/tools/cursor-session-context"; - -describe("registerCursorSessionContextTool", () => { - let tempDir: string; - let server: McpServer; - - beforeEach(() => { - tempDir = mkdtempSync(join(tmpdir(), "archgate-mcp-cursor-session-test-")); - mkdirSync(join(tempDir, ".archgate", "adrs"), { recursive: true }); - server = new McpServer({ name: "test", version: "0.0.0" }); - }); - - afterEach(async () => { - await server.close(); - rmSync(tempDir, { recursive: true, force: true }); - }); - - test("does not throw when registering", () => { - expect(() => - registerCursorSessionContextTool(server, tempDir) - ).not.toThrow(); - }); - - test("registers exactly one tool", () => { - const registerSpy = spyOn(server, "registerTool"); - registerCursorSessionContextTool(server, tempDir); - expect(registerSpy).toHaveBeenCalledTimes(1); - registerSpy.mockRestore(); - }); -}); diff --git a/tests/mcp/tools/list-adrs.test.ts b/tests/mcp/tools/list-adrs.test.ts deleted file mode 100644 index 115794f2..00000000 --- a/tests/mcp/tools/list-adrs.test.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { describe, expect, test, beforeEach, afterEach, spyOn } from "bun:test"; -import { mkdtempSync, rmSync } from "node:fs"; -import { join } from "node:path"; -import { tmpdir } from "node:os"; -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { registerListAdrsTool } from "../../../src/mcp/tools/list-adrs"; - -describe("registerListAdrsTool", () => { - let tempDir: string; - let server: McpServer; - - beforeEach(() => { - tempDir = mkdtempSync(join(tmpdir(), "archgate-mcp-list-adrs-test-")); - server = new McpServer({ name: "test", version: "0.0.0" }); - }); - - afterEach(async () => { - await server.close(); - rmSync(tempDir, { recursive: true, force: true }); - }); - - test("does not throw when registering", () => { - expect(() => registerListAdrsTool(server, tempDir)).not.toThrow(); - }); - - test("registers exactly one tool", () => { - const registerSpy = spyOn(server, "registerTool"); - registerListAdrsTool(server, tempDir); - expect(registerSpy).toHaveBeenCalledTimes(1); - registerSpy.mockRestore(); - }); -}); diff --git a/tests/mcp/tools/no-project.test.ts b/tests/mcp/tools/no-project.test.ts deleted file mode 100644 index 83b9f26b..00000000 --- a/tests/mcp/tools/no-project.test.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { describe, expect, test } from "bun:test"; -import { noProjectResponse } from "../../../src/mcp/tools/no-project"; - -describe("noProjectResponse", () => { - test("returns a content array with one text entry", () => { - const response = noProjectResponse(); - expect(response.content).toHaveLength(1); - expect(response.content[0].type).toBe("text"); - }); - - test("parsed JSON contains error, message, and action fields", () => { - const response = noProjectResponse(); - const parsed = JSON.parse(response.content[0].text) as Record< - string, - string - >; - expect(parsed.error).toBe("no_project"); - expect(typeof parsed.message).toBe("string"); - expect(typeof parsed.action).toBe("string"); - }); - - test("action mentions @archgate:onboard skill", () => { - const response = noProjectResponse(); - const parsed = JSON.parse(response.content[0].text) as Record< - string, - string - >; - expect(parsed.action).toContain("@archgate:onboard"); - }); -}); diff --git a/tests/mcp/tools/review-context.test.ts b/tests/mcp/tools/review-context.test.ts deleted file mode 100644 index 23805375..00000000 --- a/tests/mcp/tools/review-context.test.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { describe, expect, test, beforeEach, afterEach, spyOn } from "bun:test"; -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 { registerReviewContextTool } from "../../../src/mcp/tools/review-context"; - -describe("registerReviewContextTool", () => { - let tempDir: string; - let server: McpServer; - - beforeEach(() => { - tempDir = mkdtempSync(join(tmpdir(), "archgate-mcp-review-context-test-")); - mkdirSync(join(tempDir, ".archgate", "adrs"), { recursive: true }); - server = new McpServer({ name: "test", version: "0.0.0" }); - }); - - afterEach(async () => { - await server.close(); - rmSync(tempDir, { recursive: true, force: true }); - }); - - test("does not throw when registering", () => { - expect(() => registerReviewContextTool(server, tempDir)).not.toThrow(); - }); - - test("registers exactly one tool", () => { - const registerSpy = spyOn(server, "registerTool"); - registerReviewContextTool(server, tempDir); - expect(registerSpy).toHaveBeenCalledTimes(1); - registerSpy.mockRestore(); - }); -}); From aa044ff60182ffffded31be22835bbc19f1caf34 Mon Sep 17 00:00:00 2001 From: Rhuan Barreto Date: Thu, 12 Mar 2026 20:35:05 +0100 Subject: [PATCH 2/4] ci: add workflow_dispatch trigger to Validate workflow Allows manual triggering of validation, useful when automated PR events are not fired (e.g., release PRs created by GitHub Actions). Commitlint step is skipped on manual dispatch since no PR title is available. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/code-pull-request.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/code-pull-request.yml b/.github/workflows/code-pull-request.yml index 7662fc9b..944e3dca 100644 --- a/.github/workflows/code-pull-request.yml +++ b/.github/workflows/code-pull-request.yml @@ -5,6 +5,7 @@ on: types: [opened, edited, synchronize, reopened] branches: - main + workflow_dispatch: permissions: contents: read @@ -18,7 +19,7 @@ jobs: name: Validate Code runs-on: ubuntu-latest timeout-minutes: 10 - if: github.event.pull_request.draft == false + if: github.event_name == 'workflow_dispatch' || github.event.pull_request.draft == false steps: - name: Checkout code uses: actions/checkout@v4 @@ -38,6 +39,7 @@ jobs: - name: Install dependencies run: bun install --frozen-lockfile - name: Validate commit messages + if: github.event_name == 'pull_request' run: echo "${{ github.event.pull_request.title }}" | bun run commitlint - name: Validate id: validate From b041c2a8805aec8bb158ef54445ed0284a15276b Mon Sep 17 00:00:00 2001 From: rhuanbarreto <283004+rhuanbarreto@users.noreply.github.com> Date: Thu, 12 Mar 2026 19:35:40 +0000 Subject: [PATCH 3/4] Apply automatic changes --- bun.lock | 165 +------------------------------------------------------ 1 file changed, 2 insertions(+), 163 deletions(-) diff --git a/bun.lock b/bun.lock index f8271a23..41d4d65c 100644 --- a/bun.lock +++ b/bun.lock @@ -8,7 +8,6 @@ "@commitlint/cli": "19.8.1", "@commitlint/config-conventional": "19.8.1", "@commitlint/cz-commitlint": "19.8.1", - "@modelcontextprotocol/sdk": "1.26.0", "@simple-release/npm": "2.3.0", "@types/bun": "1.3.9", "commitizen": "4.3.1", @@ -74,8 +73,6 @@ "@conventional-changelog/git-client": ["@conventional-changelog/git-client@2.5.1", "", { "dependencies": { "@simple-libs/child-process-utils": "^1.0.0", "@simple-libs/stream-utils": "^1.1.0", "semver": "^7.5.2" }, "peerDependencies": { "conventional-commits-filter": "^5.0.0", "conventional-commits-parser": "^6.1.0" }, "optionalPeers": ["conventional-commits-filter", "conventional-commits-parser"] }, "sha512-lAw7iA5oTPWOLjiweb7DlGEMDEvzqzLLa6aWOly2FSZ64IwLE8T458rC+o+WvI31Doz6joM7X2DoNog7mX8r4A=="], - "@hono/node-server": ["@hono/node-server@1.19.9", "", { "peerDependencies": { "hono": "^4" } }, "sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw=="], - "@inquirer/checkbox": ["@inquirer/checkbox@4.2.2", "", { "dependencies": { "@inquirer/core": "^10.2.0", "@inquirer/figures": "^1.0.13", "@inquirer/type": "^3.0.8", "ansi-escapes": "^4.3.2", "yoctocolors-cjs": "^2.1.2" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-E+KExNurKcUJJdxmjglTl141EwxWyAHplvsYJQgSwXf8qiNWkTxTuCCqmhFEmbIXd4zLaGMfQFJ6WrZ7fSeV3g=="], "@inquirer/confirm": ["@inquirer/confirm@5.1.16", "", { "dependencies": { "@inquirer/core": "^10.2.0", "@inquirer/type": "^3.0.8" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-j1a5VstaK5KQy8Mu8cHmuQvN1Zc62TbLhjJxwHvKPPKEoowSF6h/0UdOpA9DNdWZ+9Inq73+puRq1df6OJ8Sag=="], @@ -106,8 +103,6 @@ "@inquirer/type": ["@inquirer/type@3.0.8", "", { "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-lg9Whz8onIHRthWaN1Q9EGLa/0LFJjyM8mEUbL1eTi6yMGvBf8gvyDLtxSXztQsxMvhxxNpJYrwa1YHdq+w4Jw=="], - "@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.26.0", "", { "dependencies": { "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.2.1", "express-rate-limit": "^8.2.1", "hono": "^4.11.4", "jose": "^6.1.3", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", "zod-to-json-schema": "^3.25.1" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-Y5RmPncpiDtTXDbLKswIJzTqu2hyBKxTNsgKqKclDbhIgg1wgtf1fRuvxgTnRfcnxtvvgbIEcqUOzZrJ6iSReg=="], - "@oxlint/darwin-arm64": ["@oxlint/darwin-arm64@1.14.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-rcTw0QWeOc6IeVp+Up7WtcwdS9l4j7TOq4tihF0Ud/fl+VUVdvDCPuZ9QTnLXJhwMXiyQRWdxRyI6XBwf80ncQ=="], "@oxlint/darwin-x64": ["@oxlint/darwin-x64@1.14.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-TWFSEmyl2/DN4HoXNwQl0y/y3EXFJDctfv5MiDtVOV1GJKX80cGSIxMxXb08Q3CCWqteqEijmfSMo5TG8X1H/A=="], @@ -144,12 +139,8 @@ "JSONStream": ["JSONStream@1.3.5", "", { "dependencies": { "jsonparse": "^1.2.0", "through": ">=2.2.7 <3" }, "bin": { "JSONStream": "./bin.js" } }, "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ=="], - "accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="], - "ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="], - "ajv-formats": ["ajv-formats@3.0.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="], - "ansi-escapes": ["ansi-escapes@4.3.2", "", { "dependencies": { "type-fest": "^0.21.3" } }, "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ=="], "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], @@ -168,8 +159,6 @@ "bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="], - "body-parser": ["body-parser@2.2.2", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.3", "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.1", "raw-body": "^3.0.1", "type-is": "^2.0.1" } }, "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA=="], - "brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], @@ -178,14 +167,8 @@ "bun-types": ["bun-types@1.3.9", "", { "dependencies": { "@types/node": "*" } }, "sha512-+UBWWOakIP4Tswh0Bt0QD0alpTY8cb5hvgiYeWCMet9YukHbzuruIEeXC2D7nMJPB12kbh8C7XJykSexEqGKJg=="], - "bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="], - "cachedir": ["cachedir@2.3.0", "", {}, "sha512-A+Fezp4zxnit6FanDmv9EqXNAi3vt9DWp51/71UEhXukb7QUuvtv9344h91dyAxuTLoSYJFU299qzR3tzwPAhw=="], - "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], - - "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="], - "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], "chalk": ["chalk@5.6.0", "", {}, "sha512-46QrSQFyVSEyYAgQ22hQ+zDa60YHA4fBstHmtSApj1Y5vKtG27fWowW03jCk5KcbXEWPZUIR894aARCA/G1kfQ=="], @@ -214,10 +197,6 @@ "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], - "content-disposition": ["content-disposition@1.0.1", "", {}, "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q=="], - - "content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="], - "conventional-changelog": ["conventional-changelog@7.1.1", "", { "dependencies": { "@conventional-changelog/git-client": "^2.5.1", "@types/normalize-package-data": "^2.4.4", "conventional-changelog-preset-loader": "^5.0.0", "conventional-changelog-writer": "^8.2.0", "conventional-commits-parser": "^6.2.0", "fd-package-json": "^2.0.0", "meow": "^13.0.0", "normalize-package-data": "^7.0.0" }, "bin": { "conventional-changelog": "dist/cli/index.js" } }, "sha512-rlqa8Lgh8YzT3Akruk05DR79j5gN9NCglHtJZwpi6vxVeaoagz+84UAtKQj/sT+RsfGaZkt3cdFCjcN6yjr5sw=="], "conventional-changelog-angular": ["conventional-changelog-angular@8.0.0", "", { "dependencies": { "compare-func": "^2.0.0" } }, "sha512-CLf+zr6St0wIxos4bmaKHRXWAcsCXrJU6F4VdNDrGRK3B8LDLKoX3zuMV5GhtbGkVR/LohZ6MT6im43vZLSjmA=="], @@ -236,72 +215,36 @@ "conventional-recommended-bump": ["conventional-recommended-bump@11.2.0", "", { "dependencies": { "@conventional-changelog/git-client": "^2.5.1", "conventional-changelog-preset-loader": "^5.0.0", "conventional-commits-filter": "^5.0.0", "conventional-commits-parser": "^6.1.0", "meow": "^13.0.0" }, "bin": { "conventional-recommended-bump": "dist/cli/index.js" } }, "sha512-lqIdmw330QdMBgfL0e6+6q5OMKyIpy4OZNmepit6FS3GldhkG+70drZjuZ0A5NFpze5j85dlYs3GabQXl6sMHw=="], - "cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], - - "cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="], - - "cors": ["cors@2.8.6", "", { "dependencies": { "object-assign": "^4", "vary": "^1" } }, "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw=="], - "cosmiconfig": ["cosmiconfig@9.0.0", "", { "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", "parse-json": "^5.2.0" }, "peerDependencies": { "typescript": ">=4.9.5" }, "optionalPeers": ["typescript"] }, "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg=="], "cosmiconfig-typescript-loader": ["cosmiconfig-typescript-loader@6.1.0", "", { "dependencies": { "jiti": "^2.4.1" }, "peerDependencies": { "@types/node": "*", "cosmiconfig": ">=9", "typescript": ">=5" } }, "sha512-tJ1w35ZRUiM5FeTzT7DtYWAFFv37ZLqSRkGi2oeCK1gPhvaWjkAtfXvLmvE1pRfxxp9aQo6ba/Pvg1dKj05D4g=="], - "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], - "cz-conventional-changelog": ["cz-conventional-changelog@3.3.0", "", { "dependencies": { "chalk": "^2.4.1", "commitizen": "^4.0.3", "conventional-commit-types": "^3.0.0", "lodash.map": "^4.5.1", "longest": "^2.0.1", "word-wrap": "^1.0.3" }, "optionalDependencies": { "@commitlint/load": ">6.1.1" } }, "sha512-U466fIzU5U22eES5lTNiNbZ+d8dfcHcssH4o7QsdWaCcRs/feIPCxKYSWkYBNs5mny7MvEfwpTLWjvbm94hecw=="], "dargs": ["dargs@8.1.0", "", {}, "sha512-wAV9QHOsNbwnWdNW2FYvE1P56wtgSbM+3SZcdGiWQILwVjACCXDCI3Ai8QlCjMDB8YK5zySiXZYBiwGmNY3lnw=="], - "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], - "dedent": ["dedent@0.7.0", "", {}, "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA=="], "defaults": ["defaults@1.0.4", "", { "dependencies": { "clone": "^1.0.2" } }, "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A=="], - "depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="], - "detect-file": ["detect-file@1.0.0", "", {}, "sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q=="], "detect-indent": ["detect-indent@6.1.0", "", {}, "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA=="], "dot-prop": ["dot-prop@5.3.0", "", { "dependencies": { "is-obj": "^2.0.0" } }, "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q=="], - "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], - - "ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="], - "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], - "encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="], - "env-paths": ["env-paths@2.2.1", "", {}, "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="], "error-ex": ["error-ex@1.3.2", "", { "dependencies": { "is-arrayish": "^0.2.1" } }, "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g=="], - "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], - - "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], - - "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], - "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], - "escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="], - "escape-string-regexp": ["escape-string-regexp@1.0.5", "", {}, "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="], - "etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="], - - "eventsource": ["eventsource@3.0.7", "", { "dependencies": { "eventsource-parser": "^3.0.1" } }, "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA=="], - - "eventsource-parser": ["eventsource-parser@3.0.6", "", {}, "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg=="], - "expand-tilde": ["expand-tilde@2.0.2", "", { "dependencies": { "homedir-polyfill": "^1.0.1" } }, "sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw=="], - "express": ["express@5.2.1", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="], - - "express-rate-limit": ["express-rate-limit@8.2.1", "", { "dependencies": { "ip-address": "10.0.1" }, "peerDependencies": { "express": ">= 4.11" } }, "sha512-PCZEIEIxqwhzw4KF0n7QF4QqruVTcF73O5kFKUnGOyjbCCgizBBiFaYpd/fnBLUMPw/BWw9OsiN7GgrNYr7j6g=="], - "external-editor": ["external-editor@3.1.0", "", { "dependencies": { "chardet": "^0.7.0", "iconv-lite": "^0.4.24", "tmp": "^0.0.33" } }, "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew=="], "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], @@ -314,8 +257,6 @@ "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], - "finalhandler": ["finalhandler@2.1.1", "", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA=="], - "find-node-modules": ["find-node-modules@2.1.3", "", { "dependencies": { "findup-sync": "^4.0.0", "merge": "^2.1.1" } }, "sha512-UC2I2+nx1ZuOBclWVNdcnbDR5dlrOdVb7xNjmT/lHE+LsgztWks3dG7boJ37yTS/venXw84B/mAW9uHVoC5QRg=="], "find-root": ["find-root@1.1.0", "", {}, "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng=="], @@ -324,22 +265,12 @@ "findup-sync": ["findup-sync@4.0.0", "", { "dependencies": { "detect-file": "^1.0.0", "is-glob": "^4.0.0", "micromatch": "^4.0.2", "resolve-dir": "^1.0.1" } }, "sha512-6jvvn/12IC4quLBL1KNokxC7wWTvYncaVUYSoxWw7YykPLuRrnv4qdHcSOywOI5RpkOVGeQRtWM8/q+G6W6qfQ=="], - "forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="], - - "fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="], - "fs-extra": ["fs-extra@9.1.0", "", { "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ=="], "fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="], - "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], - "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], - "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], - - "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], - "git-raw-commits": ["git-raw-commits@4.0.0", "", { "dependencies": { "dargs": "^8.0.0", "meow": "^12.0.1", "split2": "^4.0.0" }, "bin": { "git-raw-commits": "cli.mjs" } }, "sha512-ICsMM1Wk8xSGMowkOmPrzo2Fgmfo4bMHLNX6ytHjajRJUqvHOw/TFapQ+QG75c3X/tTDDhOSRPGC52dDbNM8FQ=="], "glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], @@ -350,27 +281,17 @@ "global-prefix": ["global-prefix@1.0.2", "", { "dependencies": { "expand-tilde": "^2.0.2", "homedir-polyfill": "^1.0.1", "ini": "^1.3.4", "is-windows": "^1.0.1", "which": "^1.2.14" } }, "sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg=="], - "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], - "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], "handlebars": ["handlebars@4.7.8", "", { "dependencies": { "minimist": "^1.2.5", "neo-async": "^2.6.2", "source-map": "^0.6.1", "wordwrap": "^1.0.0" }, "optionalDependencies": { "uglify-js": "^3.1.4" }, "bin": { "handlebars": "bin/handlebars" } }, "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ=="], "has-flag": ["has-flag@3.0.0", "", {}, "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw=="], - "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], - - "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], - "homedir-polyfill": ["homedir-polyfill@1.0.3", "", { "dependencies": { "parse-passwd": "^1.0.0" } }, "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA=="], - "hono": ["hono@4.12.1", "", {}, "sha512-hi9afu8g0lfJVLolxElAZGANCTTl6bewIdsRNhaywfP9K8BPf++F2z6OLrYGIinUwpRKzbZHMhPwvc0ZEpAwGw=="], - "hosted-git-info": ["hosted-git-info@8.1.0", "", { "dependencies": { "lru-cache": "^10.0.1" } }, "sha512-Rw/B2DNQaPBICNXEm8balFz9a6WpZrkCGpcWFpy7nCj+NyhSdqXipmfvtmWt9xGfp0wZnBxB+iVpLmQMYt47Tw=="], - "http-errors": ["http-errors@2.0.1", "", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="], - - "iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="], + "iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="], "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], @@ -386,10 +307,6 @@ "inquirer": ["inquirer@12.9.4", "", { "dependencies": { "@inquirer/core": "^10.2.0", "@inquirer/prompts": "^7.8.4", "@inquirer/type": "^3.0.8", "ansi-escapes": "^4.3.2", "mute-stream": "^2.0.0", "run-async": "^4.0.5", "rxjs": "^7.8.2" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-5bV3LOgLtMAiJq1QpaUddfRrvaX59wiMYppS7z2jNRSQ64acI0yqx7WMxWhgymenSXOyD657g9tlsTjqGYM8sg=="], - "ip-address": ["ip-address@10.0.1", "", {}, "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA=="], - - "ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="], - "is-arrayish": ["is-arrayish@0.2.1", "", {}, "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="], "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], @@ -404,8 +321,6 @@ "is-obj": ["is-obj@2.0.0", "", {}, "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w=="], - "is-promise": ["is-promise@4.0.0", "", {}, "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="], - "is-text-path": ["is-text-path@2.0.0", "", { "dependencies": { "text-extensions": "^2.0.0" } }, "sha512-+oDTluR6WEjdXEJMnC2z6A4FRwFoYuvShVVEGsS7ewc0UTi2QtAKMDJuL4BDEVt+5T7MjFo12RP8ghOM75oKJw=="], "is-unicode-supported": ["is-unicode-supported@0.1.0", "", {}, "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw=="], @@ -418,8 +333,6 @@ "jiti": ["jiti@2.5.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w=="], - "jose": ["jose@6.1.3", "", {}, "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ=="], - "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], "js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="], @@ -428,8 +341,6 @@ "json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], - "json-schema-typed": ["json-schema-typed@8.0.2", "", {}, "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA=="], - "jsonfile": ["jsonfile@6.2.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="], "jsonparse": ["jsonparse@1.3.1", "", {}, "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg=="], @@ -466,44 +377,24 @@ "lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], - "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], - - "media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="], - "meow": ["meow@13.2.0", "", {}, "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA=="], "merge": ["merge@2.1.1", "", {}, "sha512-jz+Cfrg9GWOZbQAnDQ4hlVnQky+341Yk5ru8bZSe6sIDTCIg8n9i/u7hSQGSVOF3C7lH6mGtqjkiT9G4wFLL0w=="], - "merge-descriptors": ["merge-descriptors@2.0.0", "", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="], - "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], - "mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], - - "mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="], - "mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="], "minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], "minimist": ["minimist@1.2.7", "", {}, "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g=="], - "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], - "mute-stream": ["mute-stream@2.0.0", "", {}, "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA=="], - "negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], - "neo-async": ["neo-async@2.6.2", "", {}, "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="], "normalize-package-data": ["normalize-package-data@7.0.1", "", { "dependencies": { "hosted-git-info": "^8.0.0", "semver": "^7.3.5", "validate-npm-package-license": "^3.0.4" } }, "sha512-linxNAT6M0ebEYZOx2tO6vBEFsVgnPpv+AVjk0wJHfaUIbq31Jm3T6vvZaarnOeWDh8ShnwXuaAyM7WT3RzErA=="], - "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], - - "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="], - - "on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="], - "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], "onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="], @@ -524,32 +415,16 @@ "parse-passwd": ["parse-passwd@1.0.0", "", {}, "sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q=="], - "parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="], - "path-exists": ["path-exists@5.0.0", "", {}, "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ=="], "path-is-absolute": ["path-is-absolute@1.0.1", "", {}, "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="], - "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], - - "path-to-regexp": ["path-to-regexp@8.3.0", "", {}, "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA=="], - "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], "picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], - "pkce-challenge": ["pkce-challenge@5.0.1", "", {}, "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ=="], - "prettier": ["prettier@3.6.2", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ=="], - "proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="], - - "qs": ["qs@6.15.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ=="], - - "range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="], - - "raw-body": ["raw-body@3.0.2", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" } }, "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA=="], - "readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="], @@ -562,8 +437,6 @@ "restore-cursor": ["restore-cursor@3.1.0", "", { "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" } }, "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA=="], - "router": ["router@2.2.0", "", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="], - "run-async": ["run-async@4.0.6", "", {}, "sha512-IoDlSLTs3Yq593mb3ZoKWKXMNu3UpObxhgA/Xuid5p4bbfi2jdY1Hj0m1K+0/tEuQTxIGMhQDqGjKb7RuxGpAQ=="], "rxjs": ["rxjs@7.8.2", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA=="], @@ -574,24 +447,6 @@ "semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="], - "send": ["send@1.2.1", "", { "dependencies": { "debug": "^4.4.3", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.1", "mime-types": "^3.0.2", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.2" } }, "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ=="], - - "serve-static": ["serve-static@2.2.1", "", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw=="], - - "setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="], - - "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], - - "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], - - "side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="], - - "side-channel-list": ["side-channel-list@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" } }, "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA=="], - - "side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="], - - "side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="], - "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], @@ -606,8 +461,6 @@ "split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="], - "statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="], - "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], @@ -630,14 +483,10 @@ "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], - "toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="], - "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], "type-fest": ["type-fest@0.21.3", "", {}, "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w=="], - "type-is": ["type-is@2.0.1", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="], - "typescript": ["typescript@5.9.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A=="], "uglify-js": ["uglify-js@3.19.3", "", { "bin": { "uglifyjs": "bin/uglifyjs" } }, "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ=="], @@ -648,19 +497,15 @@ "universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="], - "unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="], - "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], "validate-npm-package-license": ["validate-npm-package-license@3.0.4", "", { "dependencies": { "spdx-correct": "^3.0.0", "spdx-expression-parse": "^3.0.0" } }, "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew=="], - "vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="], - "walk-up-path": ["walk-up-path@4.0.0", "", {}, "sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A=="], "wcwidth": ["wcwidth@1.0.1", "", { "dependencies": { "defaults": "^1.0.3" } }, "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg=="], - "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + "which": ["which@1.3.1", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "which": "./bin/which" } }, "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ=="], "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], @@ -682,8 +527,6 @@ "zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], - "zod-to-json-schema": ["zod-to-json-schema@3.25.1", "", { "peerDependencies": { "zod": "^3.25 || ^4" } }, "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA=="], - "@commitlint/parse/conventional-changelog-angular": ["conventional-changelog-angular@7.0.0", "", { "dependencies": { "compare-func": "^2.0.0" } }, "sha512-ROjNchA9LgfNMTTFSIWPzebCwOGFdgkEq45EnvvrmSLvCtAw0HSmrCs7/ty+wAeYUZyNay0YMUNYFTRL72PkBQ=="], "@commitlint/parse/conventional-commits-parser": ["conventional-commits-parser@5.0.0", "", { "dependencies": { "JSONStream": "^1.3.5", "is-text-path": "^2.0.0", "meow": "^12.0.1", "split2": "^4.0.0" }, "bin": { "conventional-commits-parser": "cli.mjs" } }, "sha512-ZPMl0ZJbw74iS9LuX9YIAiW8pfM5p3yh2o/NbXHbkFuZzY5jvdi5jFycEOkmBW5H5I7nA+D6f3UcsCLP2vvSEA=="], @@ -708,14 +551,10 @@ "cz-conventional-changelog/chalk": ["chalk@2.4.2", "", { "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" } }, "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ=="], - "external-editor/iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="], - "git-raw-commits/meow": ["meow@12.1.1", "", {}, "sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw=="], "global-prefix/ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="], - "global-prefix/which": ["which@1.3.1", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "which": "./bin/which" } }, "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ=="], - "handlebars/minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], "import-fresh/resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], From 2592e80d126971cb9f7827ee44b6e6c3efca0185 Mon Sep 17 00:00:00 2001 From: Rhuan Barreto Date: Thu, 12 Mar 2026 20:37:08 +0100 Subject: [PATCH 4/4] ci: trigger validation from release workflow via workflow_call Add workflow_call trigger to the Validate workflow so the release workflow can invoke it after creating the release PR. This ensures release PRs get validated even when GitHub's cascading event restrictions prevent the pull_request trigger from firing. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/code-pull-request.yml | 3 ++- .github/workflows/release.yml | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/code-pull-request.yml b/.github/workflows/code-pull-request.yml index 944e3dca..7a94ef2d 100644 --- a/.github/workflows/code-pull-request.yml +++ b/.github/workflows/code-pull-request.yml @@ -6,6 +6,7 @@ on: branches: - main workflow_dispatch: + workflow_call: permissions: contents: read @@ -19,7 +20,7 @@ jobs: name: Validate Code runs-on: ubuntu-latest timeout-minutes: 10 - if: github.event_name == 'workflow_dispatch' || github.event.pull_request.draft == false + if: github.event_name != 'pull_request' || github.event.pull_request.draft == false steps: - name: Checkout code uses: actions/checkout@v4 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2dc296db..bb751b99 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -83,6 +83,10 @@ jobs: workflow: pull-request github-token: ${{ github.token }} branch: release + validate-release-pr: + name: Validate release PR + needs: pull-request + uses: ./.github/workflows/code-pull-request.yml release: runs-on: ubuntu-latest timeout-minutes: 10