From eb7407298611b193f4b4ca7f592ae737ccdcb838 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Darko=20Mijic=CC=81?= Date: Sat, 14 Mar 2026 20:07:50 +0100 Subject: [PATCH 1/7] feat: add MCP server with 25 tools for native Claude Code integration Implement a Model Context Protocol (MCP) server that wraps ProcessStateAPI, eliminating 30-90s of subprocess overhead per session by loading the pipeline once and keeping MasterDataset in memory. Architecture (4 modules): - server.ts: Lifecycle, stdio transport, stdout isolation, graceful shutdown - tool-registry.ts: 25 tools with Zod schemas (session-aware, data query, architecture, utility, server management) - pipeline-session.ts: Atomic dataset swap, config auto-detection, rebuild - file-watcher.ts: Chokidar v5 with 500ms debounce, error handling Bug fixes applied during code review: - Fix dp_list filter chaining (filters now narrow cumulatively, not overwrite) - Fix dp_arch_coverage duplicating dp_unannotated (now calls analyzeCoverage) - Add chokidar error event listener to prevent unhandled crashes - Warn on unknown CLI arguments instead of silently ignoring - Use static fs import instead of dynamic import('fs') - Export parseCliArgs and isWatchedFileType for testability Test coverage: 99 new Gherkin tests across 5 Rules covering pipeline session lifecycle, tool registration, file type filtering, CLI argument parsing, and output format validation. Uses MockMcpServer for transport-free testing. --- .../design-reviews/mcp-server-integration.md | 206 +++++ .../specs/mcp-server-integration.feature | 59 +- .../mcp-server-integration/file-watcher.ts | 55 ++ .../pipeline-session.ts | 75 ++ .../stubs/mcp-server-integration/server.ts | 63 ++ .../mcp-server-integration/tool-registry.ts | 60 ++ docs/MCP-SETUP.md | 140 ++++ package.json | 11 +- pnpm-lock.yaml | 705 ++++++++++++++++++ src/cli/mcp-server.ts | 24 + src/mcp/file-watcher.ts | 127 ++++ src/mcp/index.ts | 27 + src/mcp/pipeline-session.ts | 182 +++++ src/mcp/server.ts | 231 ++++++ src/mcp/tool-registry.ts | 678 +++++++++++++++++ tests/features/mcp/mcp-server.feature | 182 +++++ tests/steps/mcp/mcp-server.steps.ts | 437 +++++++++++ tests/support/helpers/mcp-state.ts | 163 ++++ 18 files changed, 3409 insertions(+), 16 deletions(-) create mode 100644 delivery-process/design-reviews/mcp-server-integration.md create mode 100644 delivery-process/stubs/mcp-server-integration/file-watcher.ts create mode 100644 delivery-process/stubs/mcp-server-integration/pipeline-session.ts create mode 100644 delivery-process/stubs/mcp-server-integration/server.ts create mode 100644 delivery-process/stubs/mcp-server-integration/tool-registry.ts create mode 100644 docs/MCP-SETUP.md create mode 100644 src/cli/mcp-server.ts create mode 100644 src/mcp/file-watcher.ts create mode 100644 src/mcp/index.ts create mode 100644 src/mcp/pipeline-session.ts create mode 100644 src/mcp/server.ts create mode 100644 src/mcp/tool-registry.ts create mode 100644 tests/features/mcp/mcp-server.feature create mode 100644 tests/steps/mcp/mcp-server.steps.ts create mode 100644 tests/support/helpers/mcp-state.ts diff --git a/delivery-process/design-reviews/mcp-server-integration.md b/delivery-process/design-reviews/mcp-server-integration.md new file mode 100644 index 00000000..a7d3b1d3 --- /dev/null +++ b/delivery-process/design-reviews/mcp-server-integration.md @@ -0,0 +1,206 @@ +# Design Review: MCPServerIntegration + +**Purpose:** Design review with sequence and component diagrams for the MCP server +**Detail Level:** Design review artifact from sequence annotations + +--- + +**Pattern:** MCPServerIntegration | **Phase:** Phase 46 | **Status:** active | **Orchestrator:** mcp-server | **Steps:** 5 | **Participants:** 4 + +**Source:** `delivery-process/specs/mcp-server-integration.feature` + +--- + +## Annotation Convention + +This design review is generated from the following annotations: + +| Tag | Level | Format | Purpose | +| --------------------- | -------- | ------ | ---------------------------------- | +| sequence-orchestrator | Feature | value | Identifies the coordinator module | +| sequence-step | Rule | number | Explicit execution ordering | +| sequence-module | Rule | csv | Maps Rule to deliverable module(s) | +| sequence-error | Scenario | flag | Marks scenario as error/alt path | + +Description markers: `**Input:**` and `**Output:**` in Rule descriptions define data flow types for sequence diagram call arrows and component diagram edges. + +--- + +## Sequence Diagram — Runtime Interaction Flow + +Generated from: `@libar-docs-sequence-step`, `@libar-docs-sequence-module`, `@libar-docs-sequence-error`, `**Input:**`/`**Output:**` markers, and `@libar-docs-sequence-orchestrator` on the Feature. + +```mermaid +sequenceDiagram + participant Client as "Claude Code / MCP Client" + participant mcp_server as "mcp-server.ts" + participant pipeline_session as "pipeline-session.ts" + participant tool_registry as "tool-registry.ts" + participant file_watcher as "file-watcher.ts" + + Client->>mcp_server: spawn process + + Note over mcp_server: Step 1 — Server starts via stdio transport.
Builds pipeline once during initialization.
No non-MCP output on stdout. + + mcp_server->>+pipeline_session: SessionOptions (input, features, baseDir, watch) + pipeline_session->>pipeline_session: loadConfig() + buildMasterDataset() + createProcessStateAPI() + pipeline_session-->>-mcp_server: PipelineSession (dataset, api, registry, sourceGlobs, buildTimeMs) + + alt No config file and no explicit globs + mcp_server-->>Client: error + mcp_server->>mcp_server: exit(1) + end + + Note over mcp_server: Step 2 — Register all CLI subcommands as MCP tools.
25 tools with dp_ prefix and Zod input schemas. + + mcp_server->>+tool_registry: PipelineSession (dataset, api, registry) + tool_registry->>tool_registry: registerTool() x 25 with Zod schemas + tool_registry-->>-mcp_server: RegisteredTools (25 tools) + + Note over mcp_server: Step 3 — Tool call dispatch.
Pipeline loaded once, all calls read in-memory dataset. + + Client->>mcp_server: tools/call (e.g., dp_overview) + mcp_server->>+tool_registry: dispatch(toolName, args, session) + tool_registry->>tool_registry: handler calls ProcessStateAPI / assembler + tool_registry-->>-mcp_server: ToolCallResult (content, isError) + mcp_server-->>Client: JSON-RPC response + + alt Tool call with missing required parameter + mcp_server-->>Client: MCP error (invalid params) + end + + alt dp_rebuild called + mcp_server->>+pipeline_session: rebuild() + pipeline_session-->>-mcp_server: new PipelineSession (atomic swap) + end + + Note over mcp_server: Step 4 — File watcher (optional, --watch flag).
500ms debounce, auto-rebuild on .ts/.feature changes. + + opt --watch enabled + mcp_server->>+file_watcher: start(globs, baseDir, sessionManager) + file_watcher->>file_watcher: chokidar.watch() with debounce + + file_watcher-->>file_watcher: onChange → debounced rebuild + file_watcher->>+pipeline_session: rebuild() + pipeline_session-->>-file_watcher: new PipelineSession + file_watcher-->>-mcp_server: rebuild complete (logged to stderr) + + alt Rebuild failure during watch + file_watcher->>file_watcher: keep previous dataset, log error + end + end + + Note over mcp_server: Step 5 — Configuration and shutdown.
Auto-detects delivery-process.config.ts.
Supports --input, --features, --base-dir overrides. + + Client->>mcp_server: close connection + mcp_server->>file_watcher: stop() + mcp_server->>mcp_server: server.close() + mcp_server-->>Client: exit(0) +``` + +--- + +## Component Diagram — Types and Data Flow + +Generated from: `@libar-docs-sequence-module` (nodes), `**Input:**`/`**Output:**` (edges and type shapes), deliverables table (locations), and `sequence-step` (grouping). + +```mermaid +graph LR + subgraph phase_1["Phase 1: Initialization"] + pipeline_session["pipeline-session.ts"] + end + + subgraph phase_2["Phase 2: Tool Registration"] + tool_registry["tool-registry.ts"] + end + + subgraph phase_3["Phase 3: Request Loop"] + tool_registry_dispatch["tool-registry.ts (dispatch)"] + end + + subgraph phase_4["Phase 4: File Watching"] + file_watcher["file-watcher.ts"] + end + + subgraph orchestrator["Orchestrator"] + mcp_server["mcp-server.ts"] + end + + subgraph types["Key Types"] + SessionOptions{{"SessionOptions\n-----------\ninput\nfeatures\nbaseDir\nwatch"}} + PipelineSession{{"PipelineSession\n-----------\ndataset\napi\nregistry\nbaseDir\nsourceGlobs\nbuildTimeMs"}} + RegisteredTools{{"RegisteredTools\n-----------\n25 tools\ndp_ prefix\nZod schemas\nhandler functions"}} + ToolCallResult{{"ToolCallResult\n-----------\ncontent\nisError"}} + end + + mcp_server -->|"SessionOptions"| pipeline_session + pipeline_session -->|"PipelineSession"| mcp_server + mcp_server -->|"PipelineSession"| tool_registry + tool_registry -->|"RegisteredTools"| mcp_server + mcp_server -->|"ToolCallRequest"| tool_registry_dispatch + tool_registry_dispatch -->|"ToolCallResult"| mcp_server + mcp_server -->|"globs, sessionManager"| file_watcher + file_watcher -->|"rebuild trigger"| pipeline_session +``` + +--- + +## Key Type Definitions + +| Type | Fields | Produced By | Consumed By | +| ------------------ | --------------------------------------------------------- | ---------------------- | --------------------------------------- | +| `SessionOptions` | input, features, baseDir, watch | CLI arg parser | pipeline-session | +| `PipelineSession` | dataset, api, registry, baseDir, sourceGlobs, buildTimeMs | pipeline-session | tool-registry, file-watcher, mcp-server | +| `RegisteredTools` | 25 tools with dp\_ prefix, Zod schemas, handler functions | tool-registry | mcp-server (via McpServer) | +| `ToolCallResult` | content, isError | tool-registry handlers | mcp-server (→ JSON-RPC response) | +| `FileChangeEvent` | filePath, eventType | chokidar | file-watcher | +| `McpServerOptions` | input, features, baseDir, watch, version | CLI arg parser | mcp-server | + +--- + +## Design Questions + +Verify these design properties against the diagrams above: + +| # | Question | Auto-Check | Diagram | +| ---- | ---------------------------------- | ------------------------------- | --------- | +| DQ-1 | Is the execution ordering correct? | 5 steps in monotonic order | Sequence | +| DQ-2 | Are all interfaces well-defined? | 6 distinct types across 5 steps | Component | +| DQ-3 | Is error handling complete? | 5 error paths identified | Sequence | +| DQ-4 | Is data flow unidirectional? | Review component diagram edges | Component | +| DQ-5 | Does the server isolate stdout? | console.log redirected at load | Sequence | + +--- + +## Design Decisions Summary + +| # | Decision | Rationale | +| ----- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | +| DD-1 | Atomic dataset swap on rebuild | During rebuild, tool calls read from previous PipelineSession. New session replaces it atomically after successful build. | +| DD-2 | Config auto-detection mirrors CLI | Uses same applyProjectSourceDefaults() function, then falls back to filesystem detection. | +| DD-3 | No caching layer | CLI uses dataset-cache.ts for inter-process caching, but in-process memory is sufficient for long-lived server. | +| DD-4 | Text output for session-aware tools | context, overview, scope-validate return formatted text matching CLI output — what Claude Code expects for direct consumption. | +| DD-5 | JSON output for data tools | pattern, list, status return JSON for structured querying. | +| DD-6 | Synchronous handlers where possible | MCP SDK ToolCallback accepts both sync and async returns. Avoids require-await lint violations. | +| DD-7 | dp\_ tool prefix | Per spec invariant. Avoids collision with other MCP servers in multi-server setups. | +| DD-8 | Stdout isolation via console.log redirect | MCP JSON-RPC uses stdout exclusively. console.log → console.error at module load time. | +| DD-9 | Chokidar EventEmitter cast for Node 20 | chokidar v5 typed EventEmitter requires Node 22+ @types/node. Cast to plain EventEmitter. | +| DD-10 | Pipeline failure fatal at startup, non-fatal during watch | Startup with bad config exits immediately. File-watch rebuild failure keeps previous dataset. | + +--- + +## Findings + +Record design observations from reviewing the diagrams above. Each finding should reference which diagram revealed it and its impact on the spec. + +| # | Finding | Diagram Source | Impact on Spec | +| --- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | ------------------------------------------------- | +| F-1 | The tool-registry appears twice in the component diagram (registration + dispatch) because it serves two roles: static registration at startup and dynamic dispatch at runtime. This is correct — a single module handles both. | Component | None — intentional dual-role design | +| F-2 | File watcher triggers rebuild on pipeline-session directly, bypassing mcp-server. This is correct — the watcher has a reference to the sessionManager, so the new dataset is available to the next tool call without orchestrator involvement. | Sequence | None — by-design for simplicity | +| F-3 | No tool for query (the raw ProcessStateAPI method dispatcher). The CLI has a `query` subcommand that calls arbitrary API methods. The MCP server exposes each method as a dedicated tool instead, providing better discoverability and input validation. | Both | DD-4 captures this — no generic query tool needed | + +--- + +## Summary + +The MCPServerIntegration design review covers 5 sequential steps across 4 participants (mcp-server, pipeline-session, tool-registry, file-watcher) with 6 key data types and 5 error paths. The architecture is a thin transport layer — all business logic delegates to existing ProcessStateAPI methods and CLI assembler functions. The 10 design decisions document key trade-offs around stdout isolation, output format selection, rebuild atomicity, and Node.js compatibility. diff --git a/delivery-process/specs/mcp-server-integration.feature b/delivery-process/specs/mcp-server-integration.feature index 6a557fe6..780ecd72 100644 --- a/delivery-process/specs/mcp-server-integration.feature +++ b/delivery-process/specs/mcp-server-integration.feature @@ -1,6 +1,6 @@ @libar-docs @libar-docs-pattern:MCPServerIntegration -@libar-docs-status:roadmap +@libar-docs-status:active @libar-docs-phase:46 @libar-docs-product-area:DataAPI @libar-docs-effort:3d @@ -8,6 +8,7 @@ @libar-docs-depends-on:DataAPICLIErgonomics @libar-docs-see-also:DataAPIPlatformIntegration,DataAPICLIErgonomics @libar-docs-business-value:native-claude-code-tool-integration-with-zero-subprocess-overhead +@libar-docs-sequence-orchestrator:mcp-server Feature: MCP Server Integration **Problem:** @@ -28,13 +29,15 @@ Feature: MCP Server Integration Background: Deliverables Given the following deliverables: | Deliverable | Status | Location | - | MCP server entry point and lifecycle | pending | src/mcp/server.ts | - | Tool registry with JSON Schema generation | pending | src/mcp/tool-registry.ts | - | Pipeline session manager | pending | src/mcp/pipeline-session.ts | - | File watcher with debounced rebuild | pending | src/mcp/file-watcher.ts | - | MCP server bin entry | pending | src/cli/mcp-server.ts | - | MCP configuration documentation | pending | docs/MCP-SETUP.md | - + | MCP server entry point and lifecycle | complete | src/mcp/server.ts | + | Tool registry with JSON Schema generation | complete | src/mcp/tool-registry.ts | + | Pipeline session manager | complete | src/mcp/pipeline-session.ts | + | File watcher with debounced rebuild | complete | src/mcp/file-watcher.ts | + | MCP server bin entry | complete | src/cli/mcp-server.ts | + | MCP configuration documentation | complete | docs/MCP-SETUP.md | + + @libar-docs-sequence-step:1 + @libar-docs-sequence-module:pipeline-session Rule: MCP server starts via stdio transport and manages its own lifecycle **Invariant:** The MCP server communicates over stdio using JSON-RPC. It builds @@ -46,6 +49,10 @@ Feature: MCP Server Integration Any extraneous stdout output corrupts the JSON-RPC stream. Loading the pipeline during initialization ensures the first tool call is fast. + **Input:** SessionOptions -- input, features, baseDir, watch + + **Output:** PipelineSession -- dataset, api, registry, baseDir, sourceGlobs, buildTimeMs + **Verified by:** Server starts and responds to initialize, Server handles shutdown cleanly @@ -63,12 +70,14 @@ Feature: MCP Server Integration Then the file watcher is stopped And the process exits with code 0 - @acceptance-criteria @edge-case + @acceptance-criteria @edge-case @libar-docs-sequence-error Scenario: Server starts with explicit input globs Given the MCP server is started with args "--input src/**/*.ts --features specs/**/*.feature" When the client sends an MCP initialize request Then the pipeline uses the explicit globs instead of config auto-detection + @libar-docs-sequence-step:2 + @libar-docs-sequence-module:tool-registry Rule: ProcessStateAPI methods and CLI subcommands are registered as MCP tools **Invariant:** Every CLI subcommand is registered as an MCP tool with a JSON @@ -79,6 +88,10 @@ Feature: MCP Server Integration description (for LLM tool selection), and JSON Schema for input validation. The "dp_" prefix prevents collisions in multi-server setups. + **Input:** PipelineSession -- dataset, api, registry + + **Output:** RegisteredTools -- 25 tools with dp_ prefix, Zod input schemas, handler functions + **Verified by:** All CLI subcommands appear as MCP tools, Tool schemas validate input parameters @@ -96,12 +109,14 @@ Feature: MCP Server Integration When the client calls "dp_overview" Then the response contains the overview text with progress and phases - @acceptance-criteria @edge-case + @acceptance-criteria @edge-case @libar-docs-sequence-error Scenario: Tool call with missing required parameter returns error Given the MCP server is initialized When the client calls "dp_pattern" without the required "name" parameter Then the response is an MCP error indicating invalid params + @libar-docs-sequence-step:3 + @libar-docs-sequence-module:pipeline-session Rule: MasterDataset is loaded once and reused across all tool invocations **Invariant:** The pipeline runs exactly once during server initialization. All @@ -111,6 +126,10 @@ Feature: MCP Server Integration **Rationale:** The pipeline costs 2-5 seconds. Running it per tool call negates MCP benefits. Pre-computed views provide O(1) access ideal for a query server. + **Input:** ToolCallRequest -- tool name, arguments + + **Output:** ToolCallResult -- content, isError + **Verified by:** Multiple tool calls share one pipeline build, Rebuild refreshes the dataset @@ -128,13 +147,15 @@ Feature: MCP Server Integration Then the pipeline runs again And subsequent tool calls use the new dataset - @acceptance-criteria @edge-case + @acceptance-criteria @edge-case @libar-docs-sequence-error Scenario: Concurrent reads during rebuild use previous dataset Given a rebuild is in progress When a tool call arrives for "dp_status" Then the call uses the previous dataset - And the response metadata indicates rebuild in progress + And the call completes successfully with the previous data + @libar-docs-sequence-step:4 + @libar-docs-sequence-module:file-watcher Rule: Source file changes trigger automatic dataset rebuild with debouncing **Invariant:** When --watch is enabled, changes to source files trigger an @@ -145,6 +166,10 @@ Feature: MCP Server Integration Without auto-rebuild, agents must manually call dp_rebuild. Debouncing prevents redundant rebuilds during rapid-fire saves. + **Input:** FileChangeEvent -- filePath, eventType + + **Output:** PipelineSession -- rebuilt dataset with updated patterns + **Verified by:** File change triggers rebuild, Rapid changes are debounced @@ -161,13 +186,15 @@ Feature: MCP Server Integration When 5 files are modified within 200ms Then the pipeline rebuilds exactly once after the debounce window - @acceptance-criteria @edge-case + @acceptance-criteria @edge-case @libar-docs-sequence-error Scenario: Rebuild failure during watch does not crash server Given the MCP server is running with --watch enabled When a source file change introduces a parse error Then the server continues using the previous valid dataset And an MCP notification indicates rebuild failure + @libar-docs-sequence-step:5 + @libar-docs-sequence-module:mcp-server Rule: MCP server is configurable via standard client configuration **Invariant:** The server works with .mcp.json (Claude Code), claude_desktop_config.json @@ -178,6 +205,10 @@ Feature: MCP Server Integration server must work with sensible defaults (config auto-detection) while supporting explicit overrides for monorepo setups. + **Input:** CLIArgs -- input, features, baseDir, watch, help, version + + **Output:** McpServerOptions -- parsed options merged with config defaults + **Verified by:** Default config auto-detection, Server works when started via npx @@ -195,7 +226,7 @@ Feature: MCP Server Integration Then the server process starts and awaits MCP initialize And no extraneous output appears on stdout - @acceptance-criteria @edge-case + @acceptance-criteria @edge-case @libar-docs-sequence-error Scenario: No config file and no explicit globs Given a directory without delivery-process.config.ts When the MCP server is started without arguments diff --git a/delivery-process/stubs/mcp-server-integration/file-watcher.ts b/delivery-process/stubs/mcp-server-integration/file-watcher.ts new file mode 100644 index 00000000..a345404d --- /dev/null +++ b/delivery-process/stubs/mcp-server-integration/file-watcher.ts @@ -0,0 +1,55 @@ +/** + * @libar-docs + * @libar-docs-status completed + * @libar-docs-implements MCPServerIntegration + * @libar-docs-uses MCPPipelineSession + * @libar-docs-used-by MCPServerImpl + * @libar-docs-target src/mcp/file-watcher.ts + * @libar-docs-since DS-MCP + * + * ## McpFileWatcher — Debounced Source File Watcher + * + * Watches TypeScript and Gherkin source files for changes, triggering + * debounced pipeline rebuilds. Uses chokidar v5 with 500ms default debounce. + * + * ### Design Decisions + * + * DD-1: Chokidar v5 with EventEmitter cast - chokidar v5 uses typed + * EventEmitter which requires Node 22+ @types/node. We cast to + * plain EventEmitter for Node 20 compatibility. + * + * DD-2: 500ms debounce window - balances responsiveness (developer sees + * updated data quickly) with efficiency (avoids rebuilding per keystroke + * during rapid editing). Configurable via debounceMs option. + * + * DD-3: File type filtering - only .ts and .feature file changes trigger + * rebuilds. Changes to .md, .json, or other files are silently ignored + * since they don't affect the MasterDataset. + * + * DD-4: Rebuild failure isolation - if a rebuild fails (e.g., parse error + * in modified file), the watcher logs the error and keeps the previous + * valid dataset. The server never crashes from a file-watch rebuild failure. + * + * DD-5: Synchronous start() - watch() from chokidar returns immediately + * (no async setup needed). The method is synchronous to satisfy the + * require-await lint rule. + * + * ### When to Use + * + * - When starting the MCP server with --watch flag + * - When implementing auto-rebuild on source changes + */ + +export interface FileWatcherOptions { + readonly globs: readonly string[]; + readonly baseDir: string; + readonly debounceMs?: number | undefined; + readonly sessionManager: import('./pipeline-session.js').PipelineSessionManager; + readonly log: (message: string) => void; +} + +export declare class McpFileWatcher { + constructor(options: FileWatcherOptions); + start(): void; + stop(): Promise; +} diff --git a/delivery-process/stubs/mcp-server-integration/pipeline-session.ts b/delivery-process/stubs/mcp-server-integration/pipeline-session.ts new file mode 100644 index 00000000..75feb3fa --- /dev/null +++ b/delivery-process/stubs/mcp-server-integration/pipeline-session.ts @@ -0,0 +1,75 @@ +/** + * @libar-docs + * @libar-docs-status completed + * @libar-docs-implements MCPServerIntegration + * @libar-docs-uses PipelineFactory, ProcessStateAPI, ConfigLoader + * @libar-docs-used-by MCPServerImpl, MCPToolRegistry, MCPFileWatcher + * @libar-docs-target src/mcp/pipeline-session.ts + * @libar-docs-since DS-MCP + * + * ## PipelineSessionManager — In-Memory MasterDataset Lifecycle + * + * Manages the persistent MasterDataset that all MCP tool calls read from. + * Loads config via auto-detection or explicit globs, builds the pipeline once, + * and provides atomic rebuild with concurrent-read safety. + * + * ### Architecture + * + * ``` + * SessionOptions + * | + * v + * PipelineSessionManager.initialize() + * |-- loadConfig() / applyProjectSourceDefaults() + * |-- buildMasterDataset() + * |-- createProcessStateAPI() + * v + * PipelineSession { dataset, api, registry, baseDir, sourceGlobs, buildTimeMs } + * ``` + * + * ### Design Decisions + * + * DD-1: Atomic dataset swap on rebuild - during rebuild, tool calls read from + * the previous PipelineSession. The new session replaces it atomically after + * a successful build. Failed rebuilds are logged, previous dataset stays active. + * + * DD-2: Config auto-detection mirrors the CLI - uses the same + * applyProjectSourceDefaults() from config-loader, then falls back to + * filesystem-based detection (delivery-process.config.ts presence, + * delivery-process/specs/ and delivery-process/stubs/ directories). + * + * DD-3: No caching layer - the CLI uses dataset-cache.ts for inter-process + * caching, but the MCP server keeps the dataset in-process memory. Caching + * would add complexity with no benefit for a long-lived server process. + * + * ### When to Use + * + * - When the MCP server needs to load or reload the pipeline + * - When implementing rebuild triggers (manual or file-watch) + */ + +export interface SessionOptions { + readonly input?: readonly string[] | undefined; + readonly features?: readonly string[] | undefined; + readonly baseDir?: string | undefined; + readonly watch?: boolean | undefined; +} + +export interface PipelineSession { + readonly dataset: import('../../src/generators/pipeline/index.js').RuntimeMasterDataset; + readonly api: import('../../src/api/process-state.js').ProcessStateAPI; + readonly registry: import('../../src/validation-schemas/tag-registry.js').TagRegistry; + readonly baseDir: string; + readonly sourceGlobs: { + readonly input: readonly string[]; + readonly features: readonly string[]; + }; + readonly buildTimeMs: number; +} + +export declare class PipelineSessionManager { + initialize(options?: SessionOptions): Promise; + rebuild(): Promise; + getSession(): PipelineSession; + isRebuilding(): boolean; +} diff --git a/delivery-process/stubs/mcp-server-integration/server.ts b/delivery-process/stubs/mcp-server-integration/server.ts new file mode 100644 index 00000000..24d37e5a --- /dev/null +++ b/delivery-process/stubs/mcp-server-integration/server.ts @@ -0,0 +1,63 @@ +/** + * @libar-docs + * @libar-docs-status completed + * @libar-docs-implements MCPServerIntegration + * @libar-docs-uses MCPPipelineSession, MCPToolRegistry, MCPFileWatcher + * @libar-docs-used-by MCPServerBin + * @libar-docs-target src/mcp/server.ts + * @libar-docs-since DS-MCP + * + * ## MCPServer — Entry Point and Lifecycle Manager + * + * Main entry point for the delivery-process MCP server. Wires together + * the pipeline session, tool registry, file watcher, and stdio transport. + * + * ### Lifecycle (5 phases) + * + * 1. Parse CLI args (--input, --features, --base-dir, --watch) + * 2. Initialize PipelineSessionManager (loads config, builds pipeline) + * 3. Create McpServer and register all tools + * 4. Optionally start file watcher + * 5. Connect via StdioServerTransport (enters JSON-RPC loop) + * + * ### Design Decisions + * + * DD-1: Stdout isolation via console.log redirect - MCP protocol uses JSON-RPC + * over stdout exclusively. console.log is redirected to console.error at module + * load time (before any imports). All application logging uses console.error + * via a log() helper that prefixes messages with [dp-mcp]. + * + * DD-2: Graceful shutdown with cleanup - SIGINT/SIGTERM handlers stop the file + * watcher, close the McpServer, then exit. Prevents dangling watchers and ensures + * the server process terminates cleanly when Claude Code closes the connection. + * + * DD-3: CLI arg parsing without commander.js - matches the project convention + * (manual arg parsing with for loop + switch). Supports --input/-i, --features/-f, + * --base-dir/-b, --watch/-w, --help/-h, --version/-v. + * + * DD-4: Config auto-detection with override - explicit --input/--features override + * config file detection. When no args are provided, the server auto-detects + * delivery-process.config.ts using the same applyProjectSourceDefaults() as CLI. + * + * DD-5: Pipeline failure at startup is fatal - if the pipeline fails during + * initialization (bad config, scan errors), the server exits with code 1. + * Pipeline failures during file-watch rebuilds are non-fatal (previous dataset stays). + * + * ### When to Use + * + * - When starting the MCP server from the CLI bin entry + * - When programmatically creating an MCP server instance + */ + +export interface McpServerOptions { + readonly input?: readonly string[] | undefined; + readonly features?: readonly string[] | undefined; + readonly baseDir?: string | undefined; + readonly watch?: boolean | undefined; + readonly version?: string | undefined; +} + +export declare function startMcpServer( + argv?: string[], + options?: McpServerOptions, +): Promise; diff --git a/delivery-process/stubs/mcp-server-integration/tool-registry.ts b/delivery-process/stubs/mcp-server-integration/tool-registry.ts new file mode 100644 index 00000000..c737b70d --- /dev/null +++ b/delivery-process/stubs/mcp-server-integration/tool-registry.ts @@ -0,0 +1,60 @@ +/** + * @libar-docs + * @libar-docs-status completed + * @libar-docs-implements MCPServerIntegration + * @libar-docs-uses ProcessStateAPI, MCPPipelineSession + * @libar-docs-used-by MCPServerImpl + * @libar-docs-target src/mcp/tool-registry.ts + * @libar-docs-since DS-MCP + * + * ## MCPToolRegistry — Tool Definitions with Zod Schemas + * + * Defines 25 MCP tools mapping to ProcessStateAPI methods and CLI subcommands. + * Each tool has a dp_ prefix, a Zod input schema for parameter validation, + * and a handler that delegates to existing API functions. + * + * ### Tool Categories (5 groups) + * + * | Category | Count | Output Format | Tools | + * |----------|-------|---------------|-------| + * | Session-aware | 6 | Formatted text | overview, context, files, dep_tree, scope_validate, handoff | + * | Data query | 9 | JSON | status, pattern, list, search, rules, tags, sources, stubs, decisions | + * | Architecture | 6 | JSON | arch_context, arch_layer, arch_neighborhood, arch_blocking, arch_dangling, arch_coverage | + * | Utility | 1 | JSON | unannotated | + * | Server mgmt | 3 | Text/JSON | rebuild, config, help | + * + * ### Design Decisions + * + * DD-1: Text output for session-aware tools - context, overview, scope-validate, + * handoff, files, dep-tree return formatted text identical to CLI output. This is + * what Claude Code expects for direct consumption. JSON would require the LLM to + * parse structure it already understands as text. + * + * DD-2: JSON output for data tools - pattern, list, status, stubs, rules return + * JSON for structured querying. Claude Code can extract specific fields. + * + * DD-3: Synchronous handlers where possible - the MCP SDK ToolCallback type + * accepts both sync and async returns. Handlers that don't need await are + * synchronous to avoid require-await lint violations. + * + * DD-4: No output pipeline - the CLI's output-pipeline.ts (namesOnly, count, + * fields modifiers) is replaced by dedicated tool parameters. Each tool has + * explicit namesOnly/count parameters instead of generic modifier flags. + * + * DD-5: dp_ prefix per spec invariant - avoids collision with other MCP servers + * in multi-server Claude Code setups. Matches the spec's Rule 2 invariant. + * + * ### When to Use + * + * - When registering tools on the McpServer instance + * - When adding a new tool to the MCP server + * - When understanding the mapping from CLI subcommands to MCP tools + */ + +import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import type { PipelineSessionManager } from './pipeline-session.js'; + +export declare function registerAllTools( + server: McpServer, + sessionManager: PipelineSessionManager, +): void; diff --git a/docs/MCP-SETUP.md b/docs/MCP-SETUP.md new file mode 100644 index 00000000..f4a94255 --- /dev/null +++ b/docs/MCP-SETUP.md @@ -0,0 +1,140 @@ +# MCP Server Setup + +> delivery-process MCP server exposes ProcessStateAPI as native Claude Code tools with sub-millisecond dispatch. + +## Quick Start + +### Claude Code (`.mcp.json`) + +Add to your project's `.mcp.json`: + +```json +{ + "mcpServers": { + "delivery-process": { + "command": "npx", + "args": ["dp-mcp-server"], + "cwd": "${workspaceFolder}" + } + } +} +``` + +### Claude Desktop (`claude_desktop_config.json`) + +```json +{ + "mcpServers": { + "delivery-process": { + "command": "npx", + "args": ["dp-mcp-server"], + "cwd": "/path/to/your/project" + } + } +} +``` + +### With File Watching + +Auto-rebuild the dataset when source files change: + +```json +{ + "mcpServers": { + "delivery-process": { + "command": "npx", + "args": ["dp-mcp-server", "--watch"], + "cwd": "${workspaceFolder}" + } + } +} +``` + +### With Explicit Globs (Monorepo) + +Override config auto-detection for monorepo setups: + +```json +{ + "mcpServers": { + "delivery-process": { + "command": "npx", + "args": [ + "dp-mcp-server", + "--input", + "packages/core/src/**/*.ts", + "--features", + "packages/core/specs/**/*.feature", + "--base-dir", + "packages/core" + ] + } + } +} +``` + +## How It Works + +The MCP server: + +1. **Loads the pipeline once** — config detection, scanning, extraction, transformation (~1-2s) +2. **Keeps MasterDataset in memory** — all subsequent queries are O(1) lookups +3. **Exposes 25 tools** — each wrapping a ProcessStateAPI method or CLI subcommand +4. **Optionally watches files** — auto-rebuilds on source changes (500ms debounce) + +## Available Tools + +| Tool | Description | +| ---------------------- | ---------------------------------------------------- | +| `dp_overview` | Project health summary (start here) | +| `dp_context` | Session-aware context bundle for a pattern | +| `dp_pattern` | Full pattern metadata | +| `dp_list` | List patterns with filters (status, phase, category) | +| `dp_search` | Fuzzy search patterns by name | +| `dp_status` | Status counts and completion percentage | +| `dp_files` | File reading list for a pattern | +| `dp_dep_tree` | Dependency chain with status | +| `dp_scope_validate` | Pre-flight check for implementation | +| `dp_handoff` | Session-end state for continuity | +| `dp_rules` | Business rules and invariants | +| `dp_tags` | Tag usage report | +| `dp_sources` | Source file inventory | +| `dp_stubs` | Design stubs with resolution status | +| `dp_decisions` | Design decisions from stubs | +| `dp_arch_context` | Bounded contexts with members | +| `dp_arch_layer` | Architecture layers with members | +| `dp_arch_neighborhood` | Pattern uses/used-by/peers | +| `dp_arch_blocking` | Patterns blocked by dependencies | +| `dp_arch_dangling` | Broken pattern references | +| `dp_arch_coverage` | Annotation coverage analysis | +| `dp_unannotated` | Files missing @libar-docs | +| `dp_rebuild` | Force dataset rebuild | +| `dp_config` | Show current configuration | +| `dp_help` | List all tools | + +## CLI Options + +``` +dp-mcp-server [options] + + -i, --input TypeScript source globs (repeatable) + -f, --features Gherkin feature globs (repeatable) + -b, --base-dir Base directory (default: cwd) + -w, --watch Watch source files for changes + -h, --help Show help + -v, --version Show version +``` + +## Troubleshooting + +### Server fails to start + +Check that `delivery-process.config.ts` exists in your project root, or provide explicit `--input` and `--features` globs. + +### Tools return stale data + +Call `dp_rebuild` to force a dataset refresh, or start the server with `--watch` for automatic rebuilds. + +### Config not found in monorepo + +Use `--base-dir` to point to the package root and `--input`/`--features` for explicit glob patterns. diff --git a/package.json b/package.json index 0bb2dc42..0dd852b0 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,11 @@ "types": "./dist/validation/index.d.ts", "import": "./dist/validation/index.js" }, - "./package.json": "./package.json" + "./package.json": "./package.json", + "./mcp": { + "types": "./dist/mcp/index.d.ts", + "import": "./dist/mcp/index.js" + } }, "bin": { "generate-docs": "./dist/cli/generate-docs.js", @@ -65,7 +69,8 @@ "generate-tag-taxonomy": "./dist/cli/generate-tag-taxonomy.js", "validate-patterns": "./dist/cli/validate-patterns.js", "process-api": "./dist/cli/process-api.js", - "lint-steps": "./dist/cli/lint-steps.js" + "lint-steps": "./dist/cli/lint-steps.js", + "dp-mcp-server": "./dist/cli/mcp-server.js" }, "scripts": { "build": "tsc", @@ -181,7 +186,9 @@ "dependencies": { "@cucumber/gherkin": "^29.0.0", "@cucumber/messages": "^25.0.1", + "@modelcontextprotocol/sdk": "^1.27.1", "@typescript-eslint/typescript-estree": "^8.18.0", + "chokidar": "^5.0.0", "glob": "^10.3.10", "zod": "^4.1.11" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index afe30857..84d39ae6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,9 +14,15 @@ importers: '@cucumber/messages': specifier: ^25.0.1 version: 25.0.1 + '@modelcontextprotocol/sdk': + specifier: ^1.27.1 + version: 1.27.1(zod@4.3.6) '@typescript-eslint/typescript-estree': specifier: ^8.18.0 version: 8.53.1(typescript@5.9.3) + chokidar: + specifier: ^5.0.0 + version: 5.0.0 glob: specifier: ^10.3.10 version: 10.5.0 @@ -434,6 +440,12 @@ packages: resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@hono/node-server@1.19.11': + resolution: {integrity: sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g==} + engines: {node: '>=18.14.1'} + peerDependencies: + hono: ^4 + '@humanfs/core@0.19.1': resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} engines: {node: '>=18.18.0'} @@ -484,6 +496,16 @@ packages: version: 0.1.0 hasBin: true + '@modelcontextprotocol/sdk@1.27.1': + resolution: {integrity: sha512-sr6GbP+4edBwFndLbM60gf07z0FQ79gaExpnsjMGePXqFcSSb7t6iscpjk9DhFhwd+mTEQrzNafGP8/iGGFYaA==} + engines: {node: '>=18'} + peerDependencies: + '@cfworker/json-schema': ^4.1.1 + zod: ^3.25 || ^4.0 + peerDependenciesMeta: + '@cfworker/json-schema': + optional: true + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -737,6 +759,10 @@ packages: '@vitest/utils@2.1.9': resolution: {integrity: sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==} + accepts@2.0.0: + resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} + engines: {node: '>= 0.6'} + acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -747,9 +773,20 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + ajv-formats@3.0.1: + resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + ajv@8.18.0: + resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==} + ansi-escapes@7.2.0: resolution: {integrity: sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw==} engines: {node: '>=18'} @@ -784,6 +821,10 @@ packages: resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} engines: {node: 18 || 20 || >=22} + body-parser@2.2.2: + resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==} + engines: {node: '>=18'} + brace-expansion@1.1.12: resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} @@ -798,10 +839,22 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + cac@6.7.14: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} @@ -822,6 +875,10 @@ packages: resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==} engines: {node: '>= 16'} + chokidar@5.0.0: + resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} + engines: {node: '>= 20.19.0'} + class-transformer@0.5.1: resolution: {integrity: sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==} @@ -853,6 +910,26 @@ packages: concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + content-disposition@1.0.1: + resolution: {integrity: sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==} + engines: {node: '>=18'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + + cookie-signature@1.2.2: + resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} + engines: {node: '>=6.6.0'} + + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + + cors@2.8.6: + resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} + engines: {node: '>= 0.10'} + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -873,9 +950,20 @@ packages: deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + emoji-regex@10.6.0: resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} @@ -885,13 +973,29 @@ packages: emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + environment@1.1.0: resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} engines: {node: '>=18'} + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + es-module-lexer@1.7.0: resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + esbuild@0.21.5: resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} engines: {node: '>=12'} @@ -902,6 +1006,9 @@ packages: engines: {node: '>=18'} hasBin: true + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} @@ -957,13 +1064,35 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + eventemitter3@5.0.4: resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} + eventsource-parser@3.0.6: + resolution: {integrity: sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==} + engines: {node: '>=18.0.0'} + + eventsource@3.0.7: + resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==} + engines: {node: '>=18.0.0'} + expect-type@1.3.0: resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} engines: {node: '>=12.0.0'} + express-rate-limit@8.3.1: + resolution: {integrity: sha512-D1dKN+cmyPWuvB+G2SREQDzPY1agpBIcTa9sJxOPMCNeH3gwzhqJRDWCXW3gg0y//+LQ/8j52JbMROWyrKdMdw==} + engines: {node: '>= 16'} + peerDependencies: + express: '>= 4.11' + + express@5.2.1: + resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} + engines: {node: '>= 18'} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -977,6 +1106,9 @@ packages: fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + fast-uri@3.1.0: + resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} + fastq@1.20.1: resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} @@ -997,6 +1129,10 @@ packages: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} + finalhandler@2.1.1: + resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} + engines: {node: '>= 18.0.0'} + find-up@5.0.0: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} @@ -1012,15 +1148,34 @@ packages: resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} engines: {node: '>=14'} + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + + fresh@2.0.0: + resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} + engines: {node: '>= 0.8'} + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + get-east-asian-width@1.4.0: resolution: {integrity: sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==} engines: {node: '>=18'} + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + get-tsconfig@4.13.0: resolution: {integrity: sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==} @@ -1040,18 +1195,42 @@ packages: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} engines: {node: '>=18'} + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + hono@4.12.8: + resolution: {integrity: sha512-VJCEvtrezO1IAR+kqEYnxUOoStaQPGrCmX3j4wDTNOcD1uRPFpGlwQUIW8niPuvHXaTUxeOUl5MMDGrl+tmO9A==} + engines: {node: '>=16.9.0'} + html-escaper@2.0.2: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + http-errors@2.0.1: + resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} + engines: {node: '>= 0.8'} + husky@9.1.7: resolution: {integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==} engines: {node: '>=18'} hasBin: true + iconv-lite@0.7.2: + resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} + engines: {node: '>=0.10.0'} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -1068,6 +1247,17 @@ packages: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ip-address@10.1.0: + resolution: {integrity: sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==} + engines: {node: '>= 12'} + + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -1088,6 +1278,9 @@ packages: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} + is-promise@4.0.0: + resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} + isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} @@ -1110,6 +1303,9 @@ packages: jackspeak@3.4.3: resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + jose@6.2.1: + resolution: {integrity: sha512-jUaKr1yrbfaImV7R2TN/b3IcZzsw38/chqMpo2XJ7i2F8AfM/lA4G1goC3JVEwg0H7UldTmSt3P68nt31W7/mw==} + js-yaml@4.1.1: resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} hasBin: true @@ -1120,6 +1316,12 @@ packages: json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + json-schema-typed@8.0.2: + resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==} + json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} @@ -1166,6 +1368,18 @@ packages: resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} engines: {node: '>=10'} + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + media-typer@1.1.0: + resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} + engines: {node: '>= 0.8'} + + merge-descriptors@2.0.0: + resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} + engines: {node: '>=18'} + merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} @@ -1174,6 +1388,14 @@ packages: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} + mime-db@1.54.0: + resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} + engines: {node: '>= 0.6'} + + mime-types@3.0.2: + resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} + engines: {node: '>=18'} + mimic-function@5.0.1: resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} engines: {node: '>=18'} @@ -1215,6 +1437,25 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + negotiator@1.0.0: + resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} + engines: {node: '>= 0.6'} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + onetime@7.0.0: resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} engines: {node: '>=18'} @@ -1241,6 +1482,10 @@ packages: parsecurrency@1.1.1: resolution: {integrity: sha512-IAw/8PSFgiko70KfZGv63rbEXhmVu+zpb42PvEtgHAm83Mze3eQJHWV1ZoOhPnrYeOyufvv0GS6hZDuQOdBH4Q==} + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + path-browserify@1.0.1: resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} @@ -1256,6 +1501,9 @@ packages: resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} engines: {node: '>=16 || 14 >=14.18'} + path-to-regexp@8.3.0: + resolution: {integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==} + pathe@1.1.2: resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} @@ -1279,6 +1527,10 @@ packages: engines: {node: '>=0.10'} hasBin: true + pkce-challenge@5.0.1: + resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==} + engines: {node: '>=16.20.0'} + postcss@8.5.6: resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} @@ -1292,16 +1544,40 @@ packages: engines: {node: '>=14'} hasBin: true + proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} + qs@6.15.0: + resolution: {integrity: sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==} + engines: {node: '>=0.6'} + queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + + raw-body@3.0.2: + resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} + engines: {node: '>= 0.10'} + + readdirp@5.0.0: + resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} + engines: {node: '>= 20.19.0'} + reflect-metadata@0.2.2: resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==} + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -1325,14 +1601,32 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + router@2.2.0: + resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} + engines: {node: '>= 18'} + run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + semver@7.7.3: resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} engines: {node: '>=10'} hasBin: true + send@1.2.1: + resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} + engines: {node: '>= 18'} + + serve-static@2.2.1: + resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} + engines: {node: '>= 18'} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -1341,6 +1635,22 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} + side-channel-list@1.0.0: + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} @@ -1359,6 +1669,10 @@ packages: stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + std-env@3.10.0: resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} @@ -1428,6 +1742,10 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + ts-api-utils@2.4.0: resolution: {integrity: sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==} engines: {node: '>=18.12'} @@ -1446,6 +1764,10 @@ packages: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} + type-is@2.0.1: + resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} + engines: {node: '>= 0.6'} + typescript-eslint@8.53.1: resolution: {integrity: sha512-gB+EVQfP5RDElh9ittfXlhZJdjSU4jUSTyE2+ia8CYyNvet4ElfaLlAIqDvQV9JPknKx0jQH1racTYe/4LaLSg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1461,6 +1783,10 @@ packages: undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} @@ -1468,6 +1794,10 @@ packages: resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} hasBin: true + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + vite-node@2.1.9: resolution: {integrity: sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==} engines: {node: ^18.0.0 || >=20.0.0} @@ -1555,6 +1885,9 @@ packages: resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} engines: {node: '>=18'} + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + yaml@2.8.2: resolution: {integrity: sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==} engines: {node: '>= 14.6'} @@ -1564,6 +1897,11 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + zod-to-json-schema@3.25.1: + resolution: {integrity: sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==} + peerDependencies: + zod: ^3.25 || ^4 + zod@4.3.6: resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} @@ -1801,6 +2139,10 @@ snapshots: '@eslint/core': 0.17.0 levn: 0.4.1 + '@hono/node-server@1.19.11(hono@4.12.8)': + dependencies: + hono: 4.12.8 + '@humanfs/core@0.19.1': {} '@humanfs/node@0.16.7': @@ -1845,6 +2187,28 @@ snapshots: '@libar-dev/modular-claude-md@https://codeload.github.com/libar-dev/modular-claude-md/tar.gz/3a37c573ae8611f1e0e92c00f565bb0ab45e1263': {} + '@modelcontextprotocol/sdk@1.27.1(zod@4.3.6)': + dependencies: + '@hono/node-server': 1.19.11(hono@4.12.8) + ajv: 8.18.0 + ajv-formats: 3.0.1(ajv@8.18.0) + content-type: 1.0.5 + cors: 2.8.6 + cross-spawn: 7.0.6 + eventsource: 3.0.7 + eventsource-parser: 3.0.6 + express: 5.2.1 + express-rate-limit: 8.3.1(express@5.2.1) + hono: 4.12.8 + jose: 6.2.1 + json-schema-typed: 8.0.2 + pkce-challenge: 5.0.1 + raw-body: 3.0.2 + zod: 4.3.6 + zod-to-json-schema: 3.25.1(zod@4.3.6) + transitivePeerDependencies: + - supports-color + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -2100,12 +2464,21 @@ snapshots: loupe: 3.2.1 tinyrainbow: 1.2.0 + accepts@2.0.0: + dependencies: + mime-types: 3.0.2 + negotiator: 1.0.0 + acorn-jsx@5.3.2(acorn@8.15.0): dependencies: acorn: 8.15.0 acorn@8.15.0: {} + ajv-formats@3.0.1(ajv@8.18.0): + optionalDependencies: + ajv: 8.18.0 + ajv@6.12.6: dependencies: fast-deep-equal: 3.1.3 @@ -2113,6 +2486,13 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 + ajv@8.18.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.0 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + ansi-escapes@7.2.0: dependencies: environment: 1.1.0 @@ -2135,6 +2515,20 @@ snapshots: balanced-match@4.0.4: {} + body-parser@2.2.2: + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 4.4.3 + http-errors: 2.0.1 + iconv-lite: 0.7.2 + on-finished: 2.4.1 + qs: 6.15.0 + raw-body: 3.0.2 + type-is: 2.0.1 + transitivePeerDependencies: + - supports-color + brace-expansion@1.1.12: dependencies: balanced-match: 1.0.2 @@ -2152,8 +2546,20 @@ snapshots: dependencies: fill-range: 7.1.1 + bytes@3.1.2: {} + cac@6.7.14: {} + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + callsites@3.1.0: {} callsites@4.2.0: {} @@ -2173,6 +2579,10 @@ snapshots: check-error@2.1.3: {} + chokidar@5.0.0: + dependencies: + readdirp: 5.0.0 + class-transformer@0.5.1: {} cli-cursor@5.0.0: @@ -2198,6 +2608,19 @@ snapshots: concat-map@0.0.1: {} + content-disposition@1.0.1: {} + + content-type@1.0.5: {} + + cookie-signature@1.2.2: {} + + cookie@0.7.2: {} + + cors@2.8.6: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -2212,18 +2635,38 @@ snapshots: deep-is@0.1.4: {} + depd@2.0.0: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + eastasianwidth@0.2.0: {} + ee-first@1.1.1: {} + emoji-regex@10.6.0: {} emoji-regex@8.0.0: {} emoji-regex@9.2.2: {} + encodeurl@2.0.0: {} + environment@1.1.0: {} + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + es-module-lexer@1.7.0: {} + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + esbuild@0.21.5: optionalDependencies: '@esbuild/aix-ppc64': 0.21.5 @@ -2279,6 +2722,8 @@ snapshots: '@esbuild/win32-ia32': 0.27.2 '@esbuild/win32-x64': 0.27.2 + escape-html@1.0.3: {} + escape-string-regexp@4.0.0: {} eslint-config-prettier@10.1.8(eslint@9.39.2): @@ -2355,10 +2800,56 @@ snapshots: esutils@2.0.3: {} + etag@1.8.1: {} + eventemitter3@5.0.4: {} + eventsource-parser@3.0.6: {} + + eventsource@3.0.7: + dependencies: + eventsource-parser: 3.0.6 + expect-type@1.3.0: {} + express-rate-limit@8.3.1(express@5.2.1): + dependencies: + express: 5.2.1 + ip-address: 10.1.0 + + express@5.2.1: + dependencies: + accepts: 2.0.0 + body-parser: 2.2.2 + content-disposition: 1.0.1 + content-type: 1.0.5 + cookie: 0.7.2 + cookie-signature: 1.2.2 + debug: 4.4.3 + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 2.1.1 + fresh: 2.0.0 + http-errors: 2.0.1 + merge-descriptors: 2.0.0 + mime-types: 3.0.2 + on-finished: 2.4.1 + once: 1.4.0 + parseurl: 1.3.3 + proxy-addr: 2.0.7 + qs: 6.15.0 + range-parser: 1.2.1 + router: 2.2.0 + send: 1.2.1 + serve-static: 2.2.1 + statuses: 2.0.2 + type-is: 2.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + fast-deep-equal@3.1.3: {} fast-glob@3.3.3: @@ -2373,6 +2864,8 @@ snapshots: fast-levenshtein@2.0.6: {} + fast-uri@3.1.0: {} + fastq@1.20.1: dependencies: reusify: 1.1.0 @@ -2389,6 +2882,17 @@ snapshots: dependencies: to-regex-range: 5.0.1 + finalhandler@2.1.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + find-up@5.0.0: dependencies: locate-path: 6.0.0 @@ -2406,11 +2910,35 @@ snapshots: cross-spawn: 7.0.6 signal-exit: 4.1.0 + forwarded@0.2.0: {} + + fresh@2.0.0: {} + fsevents@2.3.3: optional: true + function-bind@1.1.2: {} + get-east-asian-width@1.4.0: {} + 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 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + get-tsconfig@4.13.0: dependencies: resolve-pkg-maps: 1.0.0 @@ -2434,12 +2962,34 @@ snapshots: globals@14.0.0: {} + gopd@1.2.0: {} + has-flag@4.0.0: {} + has-symbols@1.1.0: {} + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + hono@4.12.8: {} + html-escaper@2.0.2: {} + 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 + husky@9.1.7: {} + iconv-lite@0.7.2: + dependencies: + safer-buffer: 2.1.2 + ignore@5.3.2: {} ignore@7.0.5: {} @@ -2451,6 +3001,12 @@ snapshots: imurmurhash@0.1.4: {} + inherits@2.0.4: {} + + ip-address@10.1.0: {} + + ipaddr.js@1.9.1: {} + is-extglob@2.1.1: {} is-fullwidth-code-point@3.0.0: {} @@ -2465,6 +3021,8 @@ snapshots: is-number@7.0.0: {} + is-promise@4.0.0: {} + isexe@2.0.0: {} istanbul-lib-coverage@3.2.2: {} @@ -2494,6 +3052,8 @@ snapshots: optionalDependencies: '@pkgjs/parseargs': 0.11.0 + jose@6.2.1: {} + js-yaml@4.1.1: dependencies: argparse: 2.0.1 @@ -2502,6 +3062,10 @@ snapshots: json-schema-traverse@0.4.1: {} + json-schema-traverse@1.0.0: {} + + json-schema-typed@8.0.2: {} + json-stable-stringify-without-jsonify@1.0.1: {} keyv@4.5.4: @@ -2564,6 +3128,12 @@ snapshots: dependencies: semver: 7.7.3 + math-intrinsics@1.1.0: {} + + media-typer@1.1.0: {} + + merge-descriptors@2.0.0: {} + merge2@1.4.1: {} micromatch@4.0.8: @@ -2571,6 +3141,12 @@ snapshots: braces: 3.0.3 picomatch: 2.3.1 + mime-db@1.54.0: {} + + mime-types@3.0.2: + dependencies: + mime-db: 1.54.0 + mimic-function@5.0.1: {} minimatch@10.1.1: @@ -2601,6 +3177,20 @@ snapshots: natural-compare@1.4.0: {} + negotiator@1.0.0: {} + + object-assign@4.1.1: {} + + object-inspect@1.13.4: {} + + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + onetime@7.0.0: dependencies: mimic-function: 5.0.1 @@ -2630,6 +3220,8 @@ snapshots: parsecurrency@1.1.1: {} + parseurl@1.3.3: {} + path-browserify@1.0.1: {} path-exists@4.0.0: {} @@ -2641,6 +3233,8 @@ snapshots: lru-cache: 10.4.3 minipass: 7.1.2 + path-to-regexp@8.3.0: {} + pathe@1.1.2: {} pathval@2.0.1: {} @@ -2653,6 +3247,8 @@ snapshots: pidtree@0.6.0: {} + pkce-challenge@5.0.1: {} + postcss@8.5.6: dependencies: nanoid: 3.3.11 @@ -2663,12 +3259,34 @@ snapshots: prettier@3.8.1: {} + proxy-addr@2.0.7: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + punycode@2.3.1: {} + qs@6.15.0: + dependencies: + side-channel: 1.1.0 + queue-microtask@1.2.3: {} + range-parser@1.2.1: {} + + raw-body@3.0.2: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.1 + iconv-lite: 0.7.2 + unpipe: 1.0.0 + + readdirp@5.0.0: {} + reflect-metadata@0.2.2: {} + require-from-string@2.0.2: {} + resolve-from@4.0.0: {} resolve-pkg-maps@1.0.0: {} @@ -2713,18 +3331,85 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.56.0 fsevents: 2.3.3 + router@2.2.0: + dependencies: + debug: 4.4.3 + depd: 2.0.0 + is-promise: 4.0.0 + parseurl: 1.3.3 + path-to-regexp: 8.3.0 + transitivePeerDependencies: + - supports-color + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 + safer-buffer@2.1.2: {} + semver@7.7.3: {} + 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 + transitivePeerDependencies: + - supports-color + + serve-static@2.2.1: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 1.2.1 + transitivePeerDependencies: + - supports-color + + setprototypeof@1.2.0: {} + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 shebang-regex@3.0.0: {} + side-channel-list@1.0.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.0 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + siginfo@2.0.0: {} signal-exit@4.1.0: {} @@ -2738,6 +3423,8 @@ snapshots: stackback@0.0.2: {} + statuses@2.0.2: {} + std-env@3.10.0: {} string-argv@0.3.2: {} @@ -2804,6 +3491,8 @@ snapshots: dependencies: is-number: 7.0.0 + toidentifier@1.0.1: {} + ts-api-utils@2.4.0(typescript@5.9.3): dependencies: typescript: 5.9.3 @@ -2824,6 +3513,12 @@ snapshots: dependencies: prelude-ls: 1.2.1 + type-is@2.0.1: + dependencies: + content-type: 1.0.5 + media-typer: 1.1.0 + mime-types: 3.0.2 + typescript-eslint@8.53.1(eslint@9.39.2)(typescript@5.9.3): dependencies: '@typescript-eslint/eslint-plugin': 8.53.1(@typescript-eslint/parser@8.53.1(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2)(typescript@5.9.3) @@ -2839,12 +3534,16 @@ snapshots: undici-types@5.26.5: {} + unpipe@1.0.0: {} + uri-js@4.4.1: dependencies: punycode: 2.3.1 uuid@9.0.1: {} + vary@1.1.2: {} + vite-node@2.1.9(@types/node@20.10.0): dependencies: cac: 6.7.14 @@ -2936,8 +3635,14 @@ snapshots: string-width: 7.2.0 strip-ansi: 7.1.2 + wrappy@1.0.2: {} + yaml@2.8.2: {} yocto-queue@0.1.0: {} + zod-to-json-schema@3.25.1(zod@4.3.6): + dependencies: + zod: 4.3.6 + zod@4.3.6: {} diff --git a/src/cli/mcp-server.ts b/src/cli/mcp-server.ts new file mode 100644 index 00000000..80c6df5a --- /dev/null +++ b/src/cli/mcp-server.ts @@ -0,0 +1,24 @@ +#!/usr/bin/env node +/** + * @libar-docs + * @libar-docs-core + * @libar-docs-pattern MCPServerBin + * @libar-docs-status active + * @libar-docs-arch-role infrastructure + * @libar-docs-arch-context cli + * @libar-docs-arch-layer infrastructure + * @libar-docs-uses MCPServerImpl + * @libar-docs-implements MCPServerIntegration + * + * ## MCP Server CLI Entry Point + * + * Minimal bin entry for the delivery-process MCP server. + * Delegates to startMcpServer() in src/mcp/server.ts. + */ + +import { startMcpServer } from '../mcp/server.js'; + +startMcpServer(process.argv.slice(2)).catch((error: unknown) => { + console.error('Fatal error:', error instanceof Error ? error.message : String(error)); + process.exit(1); +}); diff --git a/src/mcp/file-watcher.ts b/src/mcp/file-watcher.ts new file mode 100644 index 00000000..6cbe2b90 --- /dev/null +++ b/src/mcp/file-watcher.ts @@ -0,0 +1,127 @@ +/** + * @libar-docs + * @libar-docs-core + * @libar-docs-pattern MCPFileWatcher + * @libar-docs-status active + * @libar-docs-arch-role infrastructure + * @libar-docs-arch-context api + * @libar-docs-arch-layer infrastructure + * @libar-docs-implements MCPServerIntegration + * + * ## MCP File Watcher + * + * Watches source file globs and triggers debounced pipeline rebuilds. + * When a TypeScript or Gherkin file changes, the MasterDataset is rebuilt + * so subsequent tool calls reflect the updated annotations. + * + * ### When to Use + * + * - When starting the MCP server with --watch flag + * - When implementing auto-rebuild on source changes + */ + +import type { EventEmitter } from 'events'; +import { watch } from 'chokidar'; +import type { PipelineSessionManager } from './pipeline-session.js'; + +// ============================================================================= +// Types +// ============================================================================= + +export interface FileWatcherOptions { + readonly globs: readonly string[]; + readonly baseDir: string; + readonly debounceMs?: number; + readonly sessionManager: PipelineSessionManager; + readonly log: (message: string) => void; +} + +/** + * Returns true if the file type should trigger a pipeline rebuild. + * Only TypeScript (.ts) and Gherkin (.feature) files are watched. + */ +export function isWatchedFileType(filePath: string): boolean { + return filePath.endsWith('.ts') || filePath.endsWith('.feature'); +} + +// ============================================================================= +// File Watcher +// ============================================================================= + +export class McpFileWatcher { + private watcher: ReturnType | null = null; + private debounceTimer: ReturnType | null = null; + private readonly options: Required> & { + debounceMs: number; + }; + + constructor(options: FileWatcherOptions) { + this.options = { + ...options, + debounceMs: options.debounceMs ?? 500, + }; + } + + start(): void { + this.watcher = watch([...this.options.globs], { + cwd: this.options.baseDir, + ignoreInitial: true, + ignored: ['**/node_modules/**', '**/dist/**', '**/.git/**'], + }); + + // chokidar v5 uses typed EventEmitter which requires Node 22+ @types/node. + // Use EventEmitter.on() via cast to maintain Node 20 compatibility. + const emitter = this.watcher as unknown as EventEmitter; + emitter.on('change', (filePath: string) => this.onFileChange(filePath)); + emitter.on('add', (filePath: string) => this.onFileChange(filePath)); + emitter.on('unlink', (filePath: string) => this.onFileChange(filePath)); + emitter.on('error', (error: Error) => { + this.options.log(`File watcher error: ${error.message}`); + }); + + this.options.log( + `File watcher started (${this.options.globs.length} glob patterns, ${this.options.debounceMs}ms debounce)` + ); + } + + async stop(): Promise { + if (this.debounceTimer !== null) { + clearTimeout(this.debounceTimer); + this.debounceTimer = null; + } + if (this.watcher !== null) { + await this.watcher.close(); + this.watcher = null; + this.options.log('File watcher stopped'); + } + } + + private onFileChange(filePath: string): void { + if (!isWatchedFileType(filePath)) { + return; + } + + if (this.debounceTimer !== null) { + clearTimeout(this.debounceTimer); + } + + this.debounceTimer = setTimeout(() => { + this.debounceTimer = null; + void this.triggerRebuild(filePath); + }, this.options.debounceMs); + } + + private async triggerRebuild(triggerFile: string): Promise { + this.options.log(`Source changed: ${triggerFile} — rebuilding dataset...`); + try { + const session = await this.options.sessionManager.rebuild(); + this.options.log( + `Dataset rebuilt in ${session.buildTimeMs}ms (${session.dataset.patterns.length} patterns)` + ); + } catch (error) { + this.options.log( + `Rebuild failed: ${error instanceof Error ? error.message : String(error)}. Keeping previous dataset.` + ); + } + } +} diff --git a/src/mcp/index.ts b/src/mcp/index.ts new file mode 100644 index 00000000..55fbddb3 --- /dev/null +++ b/src/mcp/index.ts @@ -0,0 +1,27 @@ +/** + * @libar-docs + * @libar-docs-core + * @libar-docs-pattern MCPModule + * @libar-docs-status active + * @libar-docs-arch-role infrastructure + * @libar-docs-arch-context api + * @libar-docs-arch-layer application + * + * ## MCP Module Exports + * + * Public API for the MCP server module. + */ + +export { + startMcpServer, + parseCliArgs, + type McpServerOptions, + type ParsedOptions, +} from './server.js'; +export { + PipelineSessionManager, + type PipelineSession, + type SessionOptions, +} from './pipeline-session.js'; +export { McpFileWatcher, isWatchedFileType, type FileWatcherOptions } from './file-watcher.js'; +export { registerAllTools } from './tool-registry.js'; diff --git a/src/mcp/pipeline-session.ts b/src/mcp/pipeline-session.ts new file mode 100644 index 00000000..02d0272d --- /dev/null +++ b/src/mcp/pipeline-session.ts @@ -0,0 +1,182 @@ +/** + * @libar-docs + * @libar-docs-core + * @libar-docs-pattern MCPPipelineSession + * @libar-docs-status active + * @libar-docs-arch-role service + * @libar-docs-arch-context api + * @libar-docs-arch-layer application + * @libar-docs-uses PipelineFactory, ProcessStateAPI, ConfigLoader + * @libar-docs-implements MCPServerIntegration + * + * ## MCP Pipeline Session Manager + * + * Manages the in-memory MasterDataset lifecycle for the MCP server. + * Loads config, builds the pipeline once, and provides atomic rebuild. + * + * ### When to Use + * + * - When the MCP server needs a persistent ProcessStateAPI instance + * - When rebuilding the dataset after source file changes + */ + +import * as fs from 'node:fs'; +import * as path from 'path'; +import { + buildMasterDataset, + type PipelineResult, + type RuntimeMasterDataset, +} from '../generators/pipeline/index.js'; +import { createProcessStateAPI } from '../api/process-state.js'; +import type { ProcessStateAPI } from '../api/process-state.js'; +import type { TagRegistry } from '../validation-schemas/tag-registry.js'; +import { applyProjectSourceDefaults } from '../config/config-loader.js'; + +// ============================================================================= +// Types +// ============================================================================= + +export interface SessionOptions { + readonly input?: readonly string[] | undefined; + readonly features?: readonly string[] | undefined; + readonly baseDir?: string | undefined; + readonly watch?: boolean | undefined; +} + +export interface PipelineSession { + readonly dataset: RuntimeMasterDataset; + readonly api: ProcessStateAPI; + readonly registry: TagRegistry; + readonly baseDir: string; + readonly sourceGlobs: { readonly input: readonly string[]; readonly features: readonly string[] }; + readonly buildTimeMs: number; +} + +// ============================================================================= +// Pipeline Session Manager +// ============================================================================= + +export class PipelineSessionManager { + private session: PipelineSession | null = null; + private rebuilding = false; + + async initialize(options: SessionOptions = {}): Promise { + const baseDir = path.resolve(options.baseDir ?? process.cwd()); + + // Resolve source globs: explicit args override config auto-detection + const input: string[] = options.input ? [...options.input] : []; + const features: string[] = options.features ? [...options.features] : []; + + if (input.length === 0 || features.length === 0) { + const applied = await applyProjectSourceDefaults({ baseDir, input, features }); + if (!applied) { + // Fall back to convention-based detection + this.applyFallbackDefaults({ baseDir, input, features }); + } + } + + if (input.length === 0) { + throw new Error( + 'No TypeScript source globs found. Provide --input or create delivery-process.config.ts' + ); + } + + const session = await this.buildSession(baseDir, input, features); + this.session = session; + return session; + } + + async rebuild(): Promise { + if (this.session === null) { + throw new Error('Cannot rebuild: session not initialized'); + } + this.rebuilding = true; + try { + const newSession = await this.buildSession( + this.session.baseDir, + [...this.session.sourceGlobs.input], + [...this.session.sourceGlobs.features] + ); + this.session = newSession; + return newSession; + } finally { + this.rebuilding = false; + } + } + + getSession(): PipelineSession { + if (this.session === null) { + throw new Error('Session not initialized. Call initialize() first.'); + } + return this.session; + } + + isRebuilding(): boolean { + return this.rebuilding; + } + + // --------------------------------------------------------------------------- + // Private + // --------------------------------------------------------------------------- + + private async buildSession( + baseDir: string, + input: readonly string[], + features: readonly string[] + ): Promise { + const startMs = Date.now(); + + const result = await buildMasterDataset({ + input, + features, + baseDir, + mergeConflictStrategy: 'fatal', + }); + + if (!result.ok) { + throw new Error(`Pipeline error [${result.error.step}]: ${result.error.message}`); + } + + const pipelineResult: PipelineResult = result.value; + const dataset = pipelineResult.dataset; + const api = createProcessStateAPI(dataset); + const buildTimeMs = Date.now() - startMs; + + return { + dataset, + api, + registry: dataset.tagRegistry, + baseDir, + sourceGlobs: { input, features }, + buildTimeMs, + }; + } + + private applyFallbackDefaults(config: { + baseDir: string; + input: string[]; + features: string[]; + }): void { + if (config.input.length === 0) { + const configPath = path.join(config.baseDir, 'delivery-process.config.ts'); + if (fs.existsSync(configPath)) { + config.input.push('src/**/*.ts'); + const stubsDir = path.join(config.baseDir, 'delivery-process', 'stubs'); + if (fs.existsSync(stubsDir)) { + config.input.push('delivery-process/stubs/**/*.ts'); + } + } + } + + if (config.features.length === 0) { + const specsDir = path.join(config.baseDir, 'delivery-process', 'specs'); + if (fs.existsSync(specsDir)) { + config.features.push('delivery-process/specs/*.feature'); + } + const releasesDir = path.join(config.baseDir, 'delivery-process', 'releases'); + if (fs.existsSync(releasesDir)) { + config.features.push('delivery-process/releases/*.feature'); + } + } + } +} diff --git a/src/mcp/server.ts b/src/mcp/server.ts new file mode 100644 index 00000000..e5042f45 --- /dev/null +++ b/src/mcp/server.ts @@ -0,0 +1,231 @@ +/** + * @libar-docs + * @libar-docs-core + * @libar-docs-pattern MCPServerImpl + * @libar-docs-status active + * @libar-docs-arch-role service + * @libar-docs-arch-context api + * @libar-docs-arch-layer application + * @libar-docs-uses MCPPipelineSession, MCPToolRegistry, MCPFileWatcher + * @libar-docs-implements MCPServerIntegration + * + * ## MCP Server Entry Point + * + * Main entry point for the delivery-process MCP server. + * Initializes the pipeline, registers tools, and connects via stdio transport. + * + * ### Stdout Isolation + * + * CRITICAL: MCP protocol uses JSON-RPC over stdout. All application logging + * must go to stderr. console.log is redirected to console.error at module + * load time to prevent accidental stdout corruption. + * + * ### When to Use + * + * - When starting the MCP server from the CLI bin entry + * - When programmatically creating an MCP server instance + */ + +// ============================================================================= +// Stdout Isolation (MUST be first — before any other imports that might log) +// ============================================================================= + +// Redirect console.log to stderr so only MCP JSON-RPC goes to stdout. +// eslint-disable-next-line no-console +const _originalConsoleLog = console.log; +// eslint-disable-next-line no-console +console.log = console.error; + +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; +import { PipelineSessionManager, type SessionOptions } from './pipeline-session.js'; +import { registerAllTools } from './tool-registry.js'; +import { McpFileWatcher } from './file-watcher.js'; + +// ============================================================================= +// Types +// ============================================================================= + +export interface McpServerOptions extends SessionOptions { + readonly watch?: boolean; + readonly version?: string; +} + +// ============================================================================= +// Server +// ============================================================================= + +function log(message: string): void { + console.error(`[dp-mcp] ${message}`); +} + +export async function startMcpServer( + argv: string[] = [], + options: McpServerOptions = {} +): Promise { + // Parse CLI args + const parsedOptions = parseCliArgs(argv, options); + + // Initialize pipeline session + const sessionManager = new PipelineSessionManager(); + + log('Initializing pipeline...'); + try { + const session = await sessionManager.initialize({ + ...(parsedOptions.input !== undefined ? { input: parsedOptions.input } : {}), + ...(parsedOptions.features !== undefined ? { features: parsedOptions.features } : {}), + ...(parsedOptions.baseDir !== undefined ? { baseDir: parsedOptions.baseDir } : {}), + }); + log(`Pipeline built in ${session.buildTimeMs}ms (${session.dataset.patterns.length} patterns)`); + } catch (error) { + log(`Failed to initialize pipeline: ${error instanceof Error ? error.message : String(error)}`); + process.exit(1); + } + + // Create MCP server + const version = parsedOptions.version ?? '1.0.0'; + const server = new McpServer( + { name: 'delivery-process', version }, + { capabilities: { logging: {} } } + ); + + // Register all tools + registerAllTools(server, sessionManager); + log('Tools registered'); + + // Start file watcher if requested + let fileWatcher: McpFileWatcher | null = null; + if (parsedOptions.watch === true) { + const session = sessionManager.getSession(); + fileWatcher = new McpFileWatcher({ + globs: [...session.sourceGlobs.input, ...session.sourceGlobs.features], + baseDir: session.baseDir, + sessionManager, + log, + }); + fileWatcher.start(); + } + + // Handle shutdown + const cleanup = async (): Promise => { + log('Shutting down...'); + if (fileWatcher !== null) { + await fileWatcher.stop(); + } + await server.close(); + log('Shutdown complete'); + }; + + process.on('SIGINT', () => { + void cleanup().then(() => process.exit(0)); + }); + process.on('SIGTERM', () => { + void cleanup().then(() => process.exit(0)); + }); + + // Connect via stdio transport + const transport = new StdioServerTransport(); + await server.connect(transport); + log('MCP server running on stdio'); +} + +// ============================================================================= +// CLI Arg Parser +// ============================================================================= + +export interface ParsedOptions { + input?: readonly string[] | undefined; + features?: readonly string[] | undefined; + baseDir?: string | undefined; + watch?: boolean | undefined; + version?: string | undefined; +} + +export function parseCliArgs(argv: string[], defaults: McpServerOptions = {}): ParsedOptions { + const result: ParsedOptions = { ...defaults }; + const input: string[] = []; + const features: string[] = []; + + for (let i = 0; i < argv.length; i++) { + const arg = argv[i]; + + switch (arg) { + case '--input': + case '-i': { + const val = argv[++i]; + if (val !== undefined) input.push(val); + break; + } + case '--features': + case '-f': { + const val = argv[++i]; + if (val !== undefined) features.push(val); + break; + } + case '--base-dir': + case '-b': { + result.baseDir = argv[++i]; + break; + } + case '--watch': + case '-w': { + result.watch = true; + break; + } + case '--version': + case '-v': { + console.error('dp-mcp-server v' + (defaults.version ?? '1.0.0')); + process.exit(0); + break; + } + case '--help': + case '-h': { + console.error(HELP_TEXT); + process.exit(0); + break; + } + case '--': { + // Skip pnpm separator + break; + } + default: { + if (typeof arg === 'string' && arg.startsWith('-')) { + console.error(`[dp-mcp] Warning: unknown argument "${arg}"`); + } + break; + } + } + } + + if (input.length > 0) result.input = input; + if (features.length > 0) result.features = features; + + return result; +} + +const HELP_TEXT = ` +dp-mcp-server — MCP server for delivery-process + +Usage: dp-mcp-server [options] + +Options: + -i, --input TypeScript source globs (repeatable) + -f, --features Gherkin feature globs (repeatable) + -b, --base-dir Base directory (default: cwd) + -w, --watch Watch source files for changes + -h, --help Show this help + -v, --version Show version + +The server auto-detects delivery-process.config.ts if no explicit +globs are provided. Configure in Claude Code via .mcp.json: + + { + "mcpServers": { + "delivery-process": { + "command": "npx", + "args": ["dp-mcp-server"], + "cwd": "/path/to/project" + } + } + } +`.trim(); diff --git a/src/mcp/tool-registry.ts b/src/mcp/tool-registry.ts new file mode 100644 index 00000000..f794a39d --- /dev/null +++ b/src/mcp/tool-registry.ts @@ -0,0 +1,678 @@ +/** + * @libar-docs + * @libar-docs-core + * @libar-docs-pattern MCPToolRegistry + * @libar-docs-status active + * @libar-docs-arch-role service + * @libar-docs-arch-context api + * @libar-docs-arch-layer application + * @libar-docs-uses ProcessStateAPI, MCPPipelineSession + * @libar-docs-implements MCPServerIntegration + * + * ## MCP Tool Registry + * + * Defines all MCP tools with Zod input schemas and handler functions. + * Each tool wraps a ProcessStateAPI method or CLI subcommand. + * Tool names use "dp_" prefix to avoid collisions with other MCP servers. + * + * ### When to Use + * + * - When registering tools on the McpServer instance + * - When adding a new tool to the MCP server + */ + +import { z } from 'zod'; +import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import type { PipelineSession, PipelineSessionManager } from './pipeline-session.js'; +import { + assembleContext, + buildDepTree, + buildFileReadingList, + buildOverview, + isValidSessionType, + type SessionType, +} from '../api/context-assembler.js'; +import { + formatContextBundle, + formatDepTree, + formatFileReadingList, + formatOverview, +} from '../api/context-formatter.js'; +import { + computeNeighborhood, + aggregateTagUsage, + buildSourceInventory, +} from '../api/arch-queries.js'; +import { analyzeCoverage, findUnannotatedFiles } from '../api/coverage-analyzer.js'; +import { validateScope, formatScopeValidation, type ScopeType } from '../api/scope-validator.js'; +import { + generateHandoff, + formatHandoff, + type HandoffSessionType, +} from '../api/handoff-generator.js'; +import { queryBusinessRules } from '../api/rules-query.js'; +import { findStubPatterns, resolveStubs, groupStubsByPattern } from '../api/stub-resolver.js'; +import { fuzzyMatchPatterns } from '../api/fuzzy-match.js'; +import { allPatternNames } from '../api/pattern-helpers.js'; +import { summarizePattern, summarizePatterns } from '../api/summarize.js'; + +// ============================================================================= +// Types +// ============================================================================= + +type TextContent = { type: 'text'; text: string }; +type ToolResult = { content: TextContent[]; isError?: boolean }; + +function textResult(text: string): ToolResult { + return { content: [{ type: 'text', text }] }; +} + +function jsonResult(data: unknown): ToolResult { + return textResult(JSON.stringify(data, null, 2)); +} + +function errorResult(message: string): ToolResult { + return { content: [{ type: 'text', text: message }], isError: true }; +} + +// ============================================================================= +// Tool Registration +// ============================================================================= + +export function registerAllTools(server: McpServer, sessionManager: PipelineSessionManager): void { + const getSession = (): PipelineSession => sessionManager.getSession(); + + // --------------------------------------------------------------------------- + // Session-aware tools (text output — matches CLI text output) + // --------------------------------------------------------------------------- + + server.registerTool( + 'dp_overview', + { + title: 'Project Overview', + description: + 'Get project health summary: progress percentage, active phases, blocking chains, and data API commands. Start here to understand project state.', + inputSchema: z.object({}), + }, + () => { + const s = getSession(); + const overview = buildOverview(s.dataset); + return textResult(formatOverview(overview)); + } + ); + + server.registerTool( + 'dp_context', + { + title: 'Pattern Context', + description: + 'Get curated context bundle for a pattern, tailored to a session type (planning/design/implement). Returns spec, dependencies, deliverables, and FSM state.', + inputSchema: z.object({ + name: z.string().describe('Pattern name'), + session: z + .enum(['planning', 'design', 'implement']) + .optional() + .describe('Session type (planning, design, implement)'), + }), + }, + ({ name, session }) => { + const s = getSession(); + const validated = session ?? ''; + const sessionType: SessionType = isValidSessionType(validated) ? validated : 'implement'; + const bundle = assembleContext(s.dataset, s.api, { + patterns: [name], + sessionType, + baseDir: s.baseDir, + }); + return textResult(formatContextBundle(bundle)); + } + ); + + server.registerTool( + 'dp_files', + { + title: 'File Reading List', + description: + 'Get ordered file list for a pattern with implementation paths and roles. Use before reading code.', + inputSchema: z.object({ + name: z.string().describe('Pattern name'), + }), + }, + ({ name }) => { + const s = getSession(); + const fileList = buildFileReadingList(s.dataset, name, true); + return textResult(formatFileReadingList(fileList)); + } + ); + + server.registerTool( + 'dp_dep_tree', + { + title: 'Dependency Tree', + description: + 'Get dependency chain for a pattern showing status of each dependency. Useful for understanding what must be completed first.', + inputSchema: z.object({ + name: z.string().describe('Pattern name'), + maxDepth: z.number().optional().describe('Maximum depth (default: 10)'), + }), + }, + ({ name, maxDepth }) => { + const s = getSession(); + const tree = buildDepTree(s.dataset, { + pattern: name, + maxDepth: maxDepth ?? 10, + includeImplementationDeps: false, + }); + return textResult(formatDepTree(tree)); + } + ); + + server.registerTool( + 'dp_scope_validate', + { + title: 'Scope Validation', + description: + 'Pre-flight check before starting work on a pattern. Validates FSM state, dependencies, deliverables, and design decisions.', + inputSchema: z.object({ + name: z.string().describe('Pattern name'), + session: z.enum(['implement', 'design']).describe('Session type (implement or design)'), + }), + }, + ({ name, session }) => { + const s = getSession(); + const result = validateScope(s.api, s.dataset, { + patternName: name, + scopeType: session as ScopeType, + baseDir: s.baseDir, + }); + return textResult(formatScopeValidation(result)); + } + ); + + server.registerTool( + 'dp_handoff', + { + title: 'Session Handoff', + description: + 'Generate session-end state for continuity. Captures progress, remaining work, and next steps.', + inputSchema: z.object({ + name: z.string().describe('Pattern name'), + session: z + .enum(['planning', 'design', 'implement', 'review']) + .optional() + .describe('Session type'), + }), + }, + ({ name, session }) => { + const s = getSession(); + const handoff = generateHandoff(s.api, s.dataset, { + patternName: name, + sessionType: (session ?? 'implement') as HandoffSessionType, + }); + return textResult(formatHandoff(handoff)); + } + ); + + // --------------------------------------------------------------------------- + // Data query tools (JSON output) + // --------------------------------------------------------------------------- + + server.registerTool( + 'dp_status', + { + title: 'Status Counts', + description: + 'Get pattern counts by status (completed, active, roadmap, deferred) and completion percentage.', + inputSchema: z.object({}), + }, + () => { + const s = getSession(); + return jsonResult({ + counts: s.api.getStatusCounts(), + distribution: s.api.getStatusDistribution(), + }); + } + ); + + server.registerTool( + 'dp_pattern', + { + title: 'Pattern Detail', + description: + 'Get full metadata for a single pattern: status, phase, deliverables, relationships, business rules, and extracted shapes.', + inputSchema: z.object({ + name: z.string().describe('Pattern name (case-insensitive)'), + }), + }, + ({ name }) => { + const s = getSession(); + const pattern = s.api.getPattern(name); + if (pattern === undefined) { + return errorResult(`Pattern "${name}" not found.`); + } + return jsonResult(summarizePattern(pattern)); + } + ); + + server.registerTool( + 'dp_list', + { + title: 'List Patterns', + description: + 'List patterns with optional filters. Supports filtering by status, phase, and category. Use namesOnly for compact output.', + inputSchema: z.object({ + status: z + .enum(['completed', 'active', 'roadmap', 'deferred']) + .optional() + .describe('Filter by status (completed, active, roadmap, deferred)'), + phase: z.number().optional().describe('Filter by phase number'), + category: z.string().optional().describe('Filter by category'), + namesOnly: z.boolean().optional().describe('Return only pattern names'), + count: z.boolean().optional().describe('Return only the count'), + }), + }, + ({ status, phase, category, namesOnly, count }) => { + const s = getSession(); + let patterns = [...s.dataset.patterns]; + + if (status !== undefined) { + patterns = patterns.filter((p) => p.status === status); + } + if (phase !== undefined) { + patterns = patterns.filter((p) => p.phase === phase); + } + if (category !== undefined) { + patterns = patterns.filter((p) => p.category === category); + } + + if (count === true) { + return textResult(`${patterns.length} patterns`); + } + if (namesOnly === true) { + return textResult(patterns.map((p) => p.name).join('\n')); + } + return jsonResult(summarizePatterns(patterns)); + } + ); + + server.registerTool( + 'dp_search', + { + title: 'Search Patterns', + description: + 'Fuzzy search for patterns by name. Returns ranked matches with similarity scores.', + inputSchema: z.object({ + query: z.string().describe('Search query string'), + }), + }, + ({ query }) => { + const s = getSession(); + const names = allPatternNames(s.dataset); + const matches = fuzzyMatchPatterns(query, names); + return jsonResult(matches); + } + ); + + server.registerTool( + 'dp_rules', + { + title: 'Business Rules', + description: + 'Query business rules and invariants extracted from Gherkin Rule: blocks. Filter by pattern name.', + inputSchema: z.object({ + pattern: z.string().optional().describe('Filter rules by pattern name'), + onlyInvariants: z + .boolean() + .optional() + .describe('Return only invariants (skip rationale/scenarios)'), + }), + }, + ({ pattern, onlyInvariants }) => { + const s = getSession(); + const result = queryBusinessRules(s.dataset, { + productArea: null, + patternName: pattern ?? null, + onlyInvariants: onlyInvariants ?? false, + }); + return jsonResult(result); + } + ); + + server.registerTool( + 'dp_tags', + { + title: 'Tag Usage Report', + description: 'Get tag inventory: counts per tag and value across all annotated sources.', + inputSchema: z.object({}), + }, + () => { + const s = getSession(); + return jsonResult(aggregateTagUsage(s.dataset)); + } + ); + + server.registerTool( + 'dp_sources', + { + title: 'Source Inventory', + description: + 'Get file inventory by source type (TypeScript, Gherkin, stubs) with pattern counts.', + inputSchema: z.object({}), + }, + () => { + const s = getSession(); + return jsonResult(buildSourceInventory(s.dataset)); + } + ); + + server.registerTool( + 'dp_stubs', + { + title: 'Design Stubs', + description: + 'Get design stub files grouped by pattern. Shows which stubs have implementations and which are unresolved.', + inputSchema: z.object({ + unresolved: z + .boolean() + .optional() + .describe('Show only unresolved stubs (no implementation yet)'), + }), + }, + ({ unresolved }) => { + const s = getSession(); + const stubs = findStubPatterns(s.dataset); + const resolutions = resolveStubs(stubs, s.baseDir); + + if (unresolved === true) { + const unresolvedOnly = resolutions.filter((r) => !r.targetExists); + return jsonResult({ unresolvedCount: unresolvedOnly.length, stubs: unresolvedOnly }); + } + return jsonResult(groupStubsByPattern(resolutions)); + } + ); + + server.registerTool( + 'dp_decisions', + { + title: 'Design Decisions', + description: 'Extract design decisions (AD-N / DD-N) from pattern descriptions.', + inputSchema: z.object({ + name: z.string().optional().describe('Pattern name (shows all if omitted)'), + }), + }, + ({ name }) => { + const s = getSession(); + // Extract decisions from pattern descriptions (stub descriptions contain AD-N entries) + const stubs = findStubPatterns(s.dataset); + const decisions: Array<{ pattern: string; id: string; description: string }> = []; + + for (const stub of stubs) { + const desc = stub.directive.description; + const regex = /AD-(\d+):\s*(.+?)(?:\n|$)/g; + let match = regex.exec(desc); + while (match !== null) { + const matchedId = match[1] ?? ''; + const matchedDesc = match[2]?.trim() ?? ''; + decisions.push({ + pattern: stub.name, + id: `AD-${matchedId}`, + description: matchedDesc, + }); + match = regex.exec(desc); + } + } + + if (name !== undefined) { + const filtered = decisions.filter((d) => d.pattern.toLowerCase() === name.toLowerCase()); + return jsonResult(filtered); + } + return jsonResult(decisions); + } + ); + + // --------------------------------------------------------------------------- + // Architecture tools + // --------------------------------------------------------------------------- + + server.registerTool( + 'dp_arch_context', + { + title: 'Architecture Contexts', + description: + 'Get bounded contexts with member patterns. Optionally filter to a specific context name.', + inputSchema: z.object({ + name: z.string().optional().describe('Filter to a specific bounded context'), + }), + }, + ({ name }) => { + const s = getSession(); + if (s.dataset.archIndex === undefined) { + return jsonResult([]); + } + const byContext = s.dataset.archIndex.byContext; + if (name !== undefined) { + const patterns = byContext[name] ?? []; + return jsonResult(summarizePatterns(patterns)); + } + const contexts = Object.entries(byContext).map(([context, patterns]) => ({ + context, + count: patterns.length, + patterns: patterns.map((p) => p.name), + })); + return jsonResult(contexts); + } + ); + + server.registerTool( + 'dp_arch_layer', + { + title: 'Architecture Layers', + description: + 'Get architecture layers with member patterns. Optionally filter to a specific layer.', + inputSchema: z.object({ + name: z.string().optional().describe('Filter to a specific architecture layer'), + }), + }, + ({ name }) => { + const s = getSession(); + if (s.dataset.archIndex === undefined) { + return jsonResult([]); + } + const byLayer = s.dataset.archIndex.byLayer; + if (name !== undefined) { + const patterns = byLayer[name] ?? []; + return jsonResult(summarizePatterns(patterns)); + } + const layers = Object.entries(byLayer).map(([layer, patterns]) => ({ + layer, + count: patterns.length, + patterns: patterns.map((p) => p.name), + })); + return jsonResult(layers); + } + ); + + server.registerTool( + 'dp_arch_neighborhood', + { + title: 'Pattern Neighborhood', + description: + 'Get everything a pattern touches: uses, used-by, same-context peers, and dependency status.', + inputSchema: z.object({ + name: z.string().describe('Pattern name'), + }), + }, + ({ name }) => { + const s = getSession(); + return jsonResult(computeNeighborhood(name, s.dataset)); + } + ); + + server.registerTool( + 'dp_arch_blocking', + { + title: 'Blocked Patterns', + description: + 'Find patterns blocked by incomplete dependencies. Shows which dependencies must be completed first.', + inputSchema: z.object({}), + }, + () => { + const s = getSession(); + const overview = buildOverview(s.dataset); + return jsonResult(overview.blocking); + } + ); + + server.registerTool( + 'dp_arch_dangling', + { + title: 'Dangling References', + description: 'Find broken references to nonexistent pattern names in relationship tags.', + inputSchema: z.object({}), + }, + () => { + const s = getSession(); + const allNames = new Set(s.dataset.patterns.map((p) => p.name)); + const dangling: Array<{ pattern: string; tag: string; target: string }> = []; + + for (const p of s.dataset.patterns) { + for (const dep of p.dependsOn ?? []) { + if (!allNames.has(dep)) + dangling.push({ pattern: p.name, tag: 'depends-on', target: dep }); + } + for (const u of p.uses ?? []) { + if (!allNames.has(u)) dangling.push({ pattern: p.name, tag: 'uses', target: u }); + } + for (const ub of p.usedBy ?? []) { + if (!allNames.has(ub)) dangling.push({ pattern: p.name, tag: 'used-by', target: ub }); + } + } + + return jsonResult(dangling); + } + ); + + server.registerTool( + 'dp_arch_coverage', + { + title: 'Annotation Coverage', + description: + 'Analyze annotation coverage across source files. Returns coverage percentage, annotated/total counts, and unused taxonomy values.', + inputSchema: z.object({ + path: z.string().optional().describe('Filter to a specific directory path'), + }), + }, + async ({ path: pathFilter }) => { + const s = getSession(); + const globs = pathFilter !== undefined ? [`${pathFilter}/**/*.ts`] : [...s.sourceGlobs.input]; + const report = await analyzeCoverage(s.dataset, globs, s.baseDir, s.registry); + return jsonResult(report); + } + ); + + server.registerTool( + 'dp_unannotated', + { + title: 'Unannotated Files', + description: + 'Find TypeScript files missing @libar-docs annotations. Optionally filter by directory.', + inputSchema: z.object({ + path: z.string().optional().describe('Filter to a specific directory path'), + }), + }, + async ({ path: pathFilter }) => { + const s = getSession(); + const unannotated = await findUnannotatedFiles( + [...s.sourceGlobs.input], + s.baseDir, + s.registry, + pathFilter + ); + return jsonResult(unannotated); + } + ); + + // --------------------------------------------------------------------------- + // Server management tools + // --------------------------------------------------------------------------- + + server.registerTool( + 'dp_rebuild', + { + title: 'Rebuild Dataset', + description: + 'Force rebuild of the in-memory MasterDataset from current source files. Use after making changes to annotated sources.', + inputSchema: z.object({}), + }, + async () => { + if (sessionManager.isRebuilding()) { + return textResult('Rebuild already in progress.'); + } + const newSession = await sessionManager.rebuild(); + return textResult( + `Dataset rebuilt in ${newSession.buildTimeMs}ms. ${newSession.dataset.patterns.length} patterns loaded.` + ); + } + ); + + server.registerTool( + 'dp_config', + { + title: 'Current Configuration', + description: + 'Show current project configuration: source globs, base directory, and build time.', + inputSchema: z.object({}), + }, + () => { + const s = getSession(); + return jsonResult({ + baseDir: s.baseDir, + sourceGlobs: s.sourceGlobs, + buildTimeMs: s.buildTimeMs, + patternCount: s.dataset.patterns.length, + phaseCount: s.dataset.phaseCount, + categoryCount: s.dataset.categoryCount, + }); + } + ); + + server.registerTool( + 'dp_help', + { + title: 'MCP Tools Help', + description: + 'List all available MCP tools with descriptions. Use this to discover what queries are available.', + inputSchema: z.object({}), + }, + () => { + const tools = [ + 'dp_overview - Project health summary (start here)', + 'dp_context - Session-aware context bundle for a pattern', + 'dp_pattern - Full pattern metadata', + 'dp_list - List patterns with filters', + 'dp_search - Fuzzy search patterns', + 'dp_status - Status counts and completion %', + 'dp_files - File reading list for a pattern', + 'dp_dep_tree - Dependency chain with status', + 'dp_scope_validate - Pre-flight check for implementation', + 'dp_handoff - Session-end state for continuity', + 'dp_rules - Business rules and invariants', + 'dp_tags - Tag usage report', + 'dp_sources - Source file inventory', + 'dp_stubs - Design stubs with resolution status', + 'dp_decisions - Design decisions from stubs', + 'dp_arch_context - Bounded contexts with members', + 'dp_arch_layer - Architecture layers with members', + 'dp_arch_neighborhood - Pattern uses/used-by/peers', + 'dp_arch_blocking - Patterns blocked by dependencies', + 'dp_arch_dangling - Broken pattern references', + 'dp_arch_coverage - Annotation coverage analysis', + 'dp_unannotated - Files missing @libar-docs', + 'dp_rebuild - Force dataset rebuild', + 'dp_config - Show current configuration', + 'dp_help - This help text', + ]; + return textResult(`delivery-process MCP Server — Available Tools\n\n${tools.join('\n')}`); + } + ); +} diff --git a/tests/features/mcp/mcp-server.feature b/tests/features/mcp/mcp-server.feature new file mode 100644 index 00000000..5dfe77d0 --- /dev/null +++ b/tests/features/mcp/mcp-server.feature @@ -0,0 +1,182 @@ +@libar-docs +Feature: MCP Server Integration Tests + + Verifies the MCP-specific layer: tool registration, pipeline session + lifecycle, file watcher behavior, CLI argument parsing, and output + formatting. ProcessStateAPI correctness is tested separately. + + Background: Test infrastructure + Given a test MasterDataset is initialized for MCP + + Rule: Pipeline session manager loads once and supports atomic rebuild + + **Invariant:** The pipeline runs exactly once during initialization. + All subsequent calls read from in-memory MasterDataset. Rebuild + atomically replaces the session. + + **Rationale:** Verifies the core lifecycle contract of PipelineSessionManager + without requiring real file I/O. + + **Verified by:** Session initializes and contains dataset, + getSession throws before initialization, + Rebuild replaces session atomically, + isRebuilding flag lifecycle + + Scenario: Session initializes and contains dataset + Given a PipelineSessionManager initialized with test data + When getSession is called + Then the session contains a MasterDataset with patterns + And the session records build time in milliseconds + + Scenario: getSession throws before initialization + Given a new uninitialized PipelineSessionManager + When getSession is called without initialization + Then it throws an error containing "Session not initialized" + + Scenario: Rebuild replaces session atomically + Given a PipelineSessionManager initialized with test data + When rebuild is called + Then a new session is returned + And the new session has a different build time than the original + + Scenario: isRebuilding flag lifecycle + Given a PipelineSessionManager initialized with test data + Then isRebuilding returns false before rebuild + When rebuild is called + Then isRebuilding returns false after rebuild completes + + Rule: Tool registration creates correctly named tools with schemas + + **Invariant:** Every CLI subcommand is registered as an MCP tool with + dp_ prefix, non-empty description, and Zod input schema. + + **Rationale:** Verifies tool registration correctness via MockMcpServer + that records registerTool calls. + + **Verified by:** All tools registered with dp_ prefix, + Each tool has a non-empty description, + dp_overview returns formatted text, + dp_pattern returns error for unknown pattern, + dp_list filters apply cumulatively + + Scenario: All tools registered with dp_ prefix + Given an McpServer mock with registered tools + Then at least 25 tools are registered + And each tool name starts with "dp_" + + Scenario: Each tool has a non-empty description + Given an McpServer mock with registered tools + Then each registered tool has a non-empty description + + Scenario: dp_overview returns formatted text + Given an McpServer mock with registered tools + When the dp_overview handler is called + Then the result contains text content + And the result is not an error + + Scenario: dp_pattern returns error for unknown pattern + Given an McpServer mock with registered tools + When the dp_pattern handler is called with name "NonExistentPattern" + Then the result is an error + And the error message contains "not found" + + Scenario: dp_list filters apply cumulatively + Given a session with patterns of mixed status and phase + And an McpServer mock with registered tools using that session + When dp_list is called with status "active" and phase 46 + Then only patterns matching both status and phase are returned + + Rule: File watcher filters file types correctly + + **Invariant:** Only .ts and .feature files trigger rebuild. + Other file types are ignored. + + **Rationale:** Verifies the file type guard extracted as isWatchedFileType. + + **Verified by:** TypeScript files trigger rebuild, + Feature files trigger rebuild, + Non-watched file types are ignored + + Scenario: TypeScript files trigger rebuild + When checking if "src/mcp/server.ts" is a watched file type + Then the file is watched + + Scenario: Feature files trigger rebuild + When checking if "specs/mcp-server.feature" is a watched file type + Then the file is watched + + Scenario Outline: Non-watched file types are ignored + When checking if is a watched file type + Then the file is not watched + + Examples: + | file | + | package.json | + | README.md | + | styles.css | + + Rule: CLI argument parser handles all flag variants + + **Invariant:** The parser supports --input, --features, --base-dir, + --watch with short forms and handles -- separator and unknown args. + + **Rationale:** Verifies the exported parseCliArgs function directly. + + **Verified by:** Input flag with long form, + Input flag with short form, + Features flag with long form, + Watch flag enables watching, + Base-dir flag sets directory, + Multiple input globs accumulate, + Double-dash separator is skipped, + Unknown flags produce no crash + + Scenario Outline: CLI flags are parsed correctly + When parseCliArgs is called with "" + Then the parsed result has "