# 9plan MCP Server: Implementation Design Document **Version**: 1.2.0 **Date**: January 2026 **Status**: Draft --- ## Table of Contents 1. [Executive Summary](#1-executive-summary) 2. [Architecture Overview](#2-architecture-overview) 3. [Technology Stack](#3-technology-stack) 4. [Project Structure](#4-project-structure) 5. [Data Model](#5-data-model) 6. [Tool Specifications](#6-tool-specifications) 7. [Prompt Specifications](#7-prompt-specifications) 8. [File Formats](#8-file-formats) 9. [Logging](#9-logging) 10. [Error Handling](#10-error-handling) 11. [Configuration](#11-configuration) 12. [Dependency Injection & Testability](#12-dependency-injection--testability) 13. [Implementation Phases](#13-implementation-phases) 14. [Known Limitations](#14-known-limitations) --- ## 1. Executive Summary ### 1.1 Purpose 9plan is an MCP server that provides a **session-scoped work queue** for long time-horizon agent task sequencing. It enables AI agents to decompose complex tasks into discrete, self-contained plans that can be executed across conversation boundaries—even surviving context compaction. ### 1.2 Core Value Proposition | Challenge | 9plan Solution | |-----------|----------------| | Agents lose context during long tasks | Persistent sessions with three-word identifiers for easy resumption | | Complex tasks require decomposition | Structured plans with explicit inputs/outputs for dependency tracking | | Work ordering is ambiguous | Single queue with front/back positioning semantics | | Progress is lost on interruption | Plan files serve as checkpoints; notes preserve state | ### 1.3 Design Principles 1. **Self-Contained Plans**: Each plan contains enough context for an agent to execute without asking clarifying questions 2. **Semantic Dependencies**: Cross-plan dependencies are resolved by searching history, not by ID references 3. **Single Queue Discipline**: No priority tiers or backlogs—one queue that must eventually empty 4. **Hybrid Storage**: SQLite for structural integrity, flat files for agent-accessible content 5. **Session Breadcrumbs**: Tool responses include session names to aid context recovery --- ## 2. Architecture Overview ### 2.1 High-Level Architecture ``` ┌─────────────────────────────────────────────────────────────────┐ │ MCP Client │ │ (Claude, Cursor, VS Code, etc.) │ └─────────────────────────┬───────────────────────────────────────┘ │ stdio / HTTP ▼ ┌─────────────────────────────────────────────────────────────────┐ │ 9plan MCP Server │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────────┐ │ │ │ Tools │ │ Prompts │ │ Server Instructions │ │ │ │ (12 total) │ │ (bootstrap) │ │ (plan format spec) │ │ │ └──────┬──────┘ └─────────────┘ └─────────────────────────┘ │ │ │ │ │ ┌──────▼──────────────────────────────────────────────────┐ │ │ │ Core Services │ │ │ │ ┌────────────┐ ┌────────────┐ ┌────────────────────┐ │ │ │ │ │ Session │ │ Queue │ │ History │ │ │ │ │ │ Manager │ │ Manager │ │ Search (FTS5) │ │ │ │ │ └────────────┘ └────────────┘ └────────────────────┘ │ │ │ └──────────────────────────────────────────────────────────┘ │ │ │ │ │ ┌───────────────────────────▼──────────────────────────────┐ │ │ │ Storage Layer │ │ │ │ SQLite (session.db) + Plan Files (plans/*.txt) │ │ │ └───────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────┘ ``` ### 2.2 Plan Lifecycle State Machine ``` ┌──────────────────┐ │ Created │ │ (queued) │ └────────┬─────────┘ │ 9plan_queue_pull ▼ ┌──────────────────┐ ┌──────────│ Active │──────────┐ │ └──────────────────┘ │ │ │ │ 9plan_plan_defer 9plan_plan_complete 9plan_plan_discard │ │ │ ▼ ▼ ▼ ┌──────────────┐ ┌──────────────────┐ ┌──────────────┐ │ Queued │ │ Completed │ │ Discarded │ │ (deferred) │ │ (in history) │ │ (deleted) │ └──────────────┘ └──────────────────┘ └──────────────┘ ``` ### 2.3 Key Architectural Decisions | Decision | Rationale | |----------|-----------| | **SQLite + flat files** | SQLite protects queue integrity; flat files allow agent filesystem access | | **stdio transport** | Most MCP clients spawn servers via stdio; HTTP can be added later | | **Single queue (no priorities)** | Prevents agents from abandoning low-priority work | | **Three-word session names** | Human-memorable, easy to communicate | | **Alphanumeric plan IDs** | Short (5 chars), collision-resistant | | **FTS5 for history search** | Built into SQLite, semantic search over completed plans | --- ## 3. Technology Stack ### 3.1 Core Dependencies | Component | Technology | Version | Rationale | |-----------|------------|---------|-----------| | **Runtime** | Node.js | ≥22.5.0 | Required for built-in `node:sqlite` | | **MCP Framework** | @modelcontextprotocol/sdk | ^1.22.0 | Official SDK | | **MCP Transport** | @karashiiro/mcp | ^1.0.0 | Handles stdio + HTTP with sessions | | **Schema Validation** | Zod | ^3.25.0 | Runtime validation, env validation | | **Database** | node:sqlite | built-in | Zero deps, FTS5 support | | **Session Names** | unique-names-generator | ^4.7.1 | Pre-built dictionaries | | **Plan IDs** | nanoid | ^5.1.6 | Cryptographically secure, custom alphabets | | **App Directories** | env-paths | ^3.0.0 | Cross-platform standard directories | | **Logging** | pino | ^9.0.0 | Ultra-fast structured JSON logging | ### 3.2 Optional Dependencies (HTTP Transport) | Component | Technology | Version | |-----------|------------|---------| | **Web Framework** | hono | ^4.0.0 | | **Node Adapter** | @hono/node-server | ^1.0.0 | ### 3.3 Development Dependencies | Component | Technology | |-----------|------------| | **Build** | TypeScript ^5.8.0 | | **Dev Runner** | tsx | | **Testing** | Vitest | | **Log Formatting** | pino-pretty | ### 3.4 package.json ```json { "name": "9plan-mcp-server", "version": "1.0.0", "type": "module", "main": "dist/index.js", "scripts": { "build": "tsc", "dev": "tsx watch src/index.ts", "start": "node dist/index.js", "test": "vitest" }, "dependencies": { "@karashiiro/mcp": "^1.0.0", "@modelcontextprotocol/sdk": "^1.22.0", "env-paths": "^3.0.0", "nanoid": "^5.1.6", "pino": "^9.0.0", "unique-names-generator": "^4.7.1", "zod": "^3.25.0" }, "optionalDependencies": { "hono": "^4.0.0", "@hono/node-server": "^1.0.0" }, "devDependencies": { "@types/node": "^22.0.0", "pino-pretty": "^11.0.0", "tsx": "^4.0.0", "typescript": "^5.8.0", "vitest": "^3.0.0" }, "engines": { "node": ">=22.5.0" } } ``` --- ## 4. Project Structure ``` 9plan-mcp-server/ ├── src/ │ ├── index.ts # Entry point, transport setup │ ├── server.ts # McpServer factory, tool/prompt registration │ ├── types.ts # Core TypeScript interfaces │ ├── env.ts # Environment config with Zod │ ├── logger.ts # Pino logger configuration │ │ │ ├── tools/ │ │ ├── index.ts # Tool registration aggregator │ │ ├── session-create.ts │ │ ├── session-resume.ts │ │ ├── queue-add.ts │ │ ├── queue-pull.ts │ │ ├── plan-defer.ts │ │ ├── plan-complete.ts │ │ ├── plan-discard.ts │ │ ├── history-search.ts │ │ ├── history-get.ts │ │ ├── admin-validate.ts # Invariant checking │ │ ├── admin-sessions.ts # List all sessions │ │ └── admin-state.ts # Detailed state dump │ │ │ ├── prompts/ │ │ └── bootstrap.ts │ │ │ ├── db/ │ │ ├── schema.sql │ │ └── session-store.ts │ │ │ ├── generators/ │ │ ├── session-name.ts # Uses unique-names-generator │ │ └── plan-id.ts # Uses nanoid │ │ │ └── files/ │ └── plan-files.ts │ ├── tests/ │ ├── unit/ │ │ ├── session-store.test.ts │ │ ├── plan-files.test.ts │ │ └── generators.test.ts │ └── integration/ │ └── workflow.test.ts │ ├── package.json ├── tsconfig.json ├── vitest.config.ts └── README.md ``` --- ## 5. Data Model ### 5.1 SQLite Schema ```sql PRAGMA foreign_keys = ON; CREATE TABLE IF NOT EXISTS sessions ( name TEXT PRIMARY KEY, task_description TEXT, created_at TEXT DEFAULT (datetime('now')) ) STRICT; CREATE TABLE IF NOT EXISTS plans ( id TEXT PRIMARY KEY, session_name TEXT NOT NULL REFERENCES sessions(name) ON DELETE CASCADE, status TEXT NOT NULL CHECK (status IN ('queued', 'active', 'completed', 'discarded')), queue_position INTEGER, goal TEXT NOT NULL, context TEXT, inputs TEXT, outputs TEXT, approach TEXT, success_criteria TEXT, notes TEXT, outcome TEXT, created_at TEXT DEFAULT (datetime('now')), completed_at TEXT ) STRICT; CREATE INDEX IF NOT EXISTS idx_plans_queue ON plans(session_name, status, queue_position) WHERE status = 'queued'; CREATE INDEX IF NOT EXISTS idx_plans_active ON plans(session_name, status) WHERE status = 'active'; -- FTS5 for history search CREATE VIRTUAL TABLE IF NOT EXISTS plans_fts USING fts5( id, goal, context, inputs, outputs, outcome, content='plans', content_rowid='rowid' ); -- Triggers to keep FTS in sync CREATE TRIGGER IF NOT EXISTS plans_ai AFTER INSERT ON plans BEGIN INSERT INTO plans_fts(rowid, id, goal, context, inputs, outputs, outcome) VALUES (new.rowid, new.id, new.goal, new.context, new.inputs, new.outputs, new.outcome); END; CREATE TRIGGER IF NOT EXISTS plans_ad AFTER DELETE ON plans BEGIN INSERT INTO plans_fts(plans_fts, rowid, id, goal, context, inputs, outputs, outcome) VALUES ('delete', old.rowid, old.id, old.goal, old.context, old.inputs, old.outputs, old.outcome); END; CREATE TRIGGER IF NOT EXISTS plans_au AFTER UPDATE ON plans BEGIN INSERT INTO plans_fts(plans_fts, rowid, id, goal, context, inputs, outputs, outcome) VALUES ('delete', old.rowid, old.id, old.goal, old.context, old.inputs, old.outputs, old.outcome); INSERT INTO plans_fts(rowid, id, goal, context, inputs, outputs, outcome) VALUES (new.rowid, new.id, new.goal, new.context, new.inputs, new.outputs, new.outcome); END; ``` ### 5.2 TypeScript Interfaces ```typescript export type PlanStatus = 'queued' | 'active' | 'completed' | 'discarded'; export interface Session { name: string; taskDescription: string | null; createdAt: string; } export interface Plan { id: string; sessionName: string; status: PlanStatus; queuePosition: number | null; goal: string; context: string | null; inputs: string | null; outputs: string | null; approach: string | null; successCriteria: string | null; notes: string | null; outcome: string | null; createdAt: string; completedAt: string | null; } export interface QueuedPlan { id: string; goal: string; queuePosition: number; } export interface SessionState { sessionName: string; sessionPath: string; taskDescription: string | null; queue: QueuedPlan[]; activePlan: { id: string; filePath: string } | null; completedCount: number; } export interface HistoryMatch { id: string; goal: string; outcome: string | null; relevanceScore: number; } ``` --- ## 6. Tool Specifications All tools use the prefix `9plan_` with snake_case. ### 6.1 Session Tools #### `9plan_session_create` Creates a new session with a randomly-generated three-word identifier. **Input**: `{ task_description?: string }` **Output**: `{ session_name: string, session_path: string }` **Behavior**: 1. Generate unique three-word name 2. Create session directory with `session.db` and `plans/` 3. Initialize SQLite with schema 4. Return session name and path --- #### `9plan_session_resume` Loads an existing session by name. **Input**: `{ session_name: string }` (format: `word-word-word`) **Output**: Full session state including queue, active plan, and completed count. --- ### 6.2 Queue Tools #### `9plan_queue_add` Adds a new plan to the queue. **Input**: ```typescript { context: string, // Where this plan fits in overall task goal: string, // What this plan accomplishes approach: string, // How to accomplish the goal success_criteria: string, inputs?: string, // Dependencies from other plans outputs?: string, // What this produces position?: 'front' | 'back' // Default: 'back' } ``` **Output**: `{ plan_id: string, plan_path: string, queue_position: number }` **Position Semantics**: - `front`: Blocking work that must complete first - `back`: Work that should happen eventually --- #### `9plan_queue_pull` Removes the front plan from queue and marks it active. **Input**: None **Output**: `{ plan_id: string, plan_path: string, goal: string }` **Errors**: - `PLAN_ALREADY_ACTIVE`: Must complete/defer/discard current plan first - `QUEUE_EMPTY`: Task complete! 🎉 --- ### 6.3 Plan Lifecycle Tools #### `9plan_plan_defer` Returns active plan to queue without completing it. **Input**: `{ reason: string, position?: 'front' | 'back' }` **Behavior**: Appends reason to Notes section with timestamp, returns to queue. **Use Cases**: - **Blocked**: Can't proceed → `position: 'front'` - **Decomposition**: Breaking into subplans → `position: 'back'` --- #### `9plan_plan_complete` Marks active plan as done. **Input**: `{ outcome: string }` — Summary including outputs produced **Behavior**: 1. Store outcome in database 2. Index into FTS5 for history search 3. Delete plan file 4. Return queue status --- #### `9plan_plan_discard` Abandons active plan without completing. **Input**: `{ reason: string }` **Behavior**: Marks as discarded (NOT in searchable history), deletes file. **Use When**: Plan is obsolete, unnecessary, or misconceived. --- ### 6.4 History Tools #### `9plan_history_search` Searches completed plans for outputs matching a query. **Input**: `{ query: string, max_results?: number }` **Output**: Array of matches with `{ id, goal, outcome, relevance_rank }` Uses FTS5 MATCH with BM25 ranking across goal, context, inputs, outputs, outcome. --- #### `9plan_history_get` Retrieves a specific completed plan by ID. **Input**: `{ plan_id: string }` (5 alphanumeric chars) **Output**: Full plan content including outcome. --- ### 6.5 Admin Tools Admin tools support validation and debugging. Prefixed with `admin_` to distinguish from core workflow tools. #### `9plan_admin_validate` Checks session state against all invariants. **Input**: None (uses current session) **Output**: ```typescript { valid: boolean, invariants: { single_active_plan: boolean, queue_order_preserved: boolean, files_match_database: boolean, no_orphaned_files: boolean }, issues: string[] } ``` **Use Case**: Verify session consistency during debugging or validation testing. --- #### `9plan_admin_sessions` Lists all sessions on the system. **Input**: None **Output**: ```typescript { sessions: Array<{ name: string, created: string, task_description: string | null, queue_length: number, completed_count: number, has_active_plan: boolean }> } ``` **Use Case**: Find existing sessions, clean up old ones, verify session creation. --- #### `9plan_admin_state` Returns detailed state dump for current session. **Input**: None (uses current session) **Output**: ```typescript { session_name: string, task_description: string | null, queue: Array<{ id: string, goal: string, position: number }>, active_plan: { id: string, goal: string, file_path: string } | null, completed_count: number, plan_files: string[], file_db_match: boolean } ``` **Use Case**: Detailed inspection during validation or debugging. --- ## 7. Prompt Specifications ### `bootstrap` Guides initial session setup and planning decomposition. **Arguments**: `{ task?: string }` **Content**: ``` You are helping the user set up a new 9plan session for a complex task. WORKFLOW: 1. CLARIFY: Ask questions to understand scope, components, dependencies 2. CREATE SESSION: Use 9plan_session_create with task description 3. DECOMPOSE: Break into discrete, self-contained plans 4. CAPTURE DEPENDENCIES: Specify inputs/outputs for each plan 5. ENQUEUE: Add plans with 9plan_queue_add (respect dependency order) 6. START WORK: Pull first plan with 9plan_queue_pull PLAN QUALITY CHECKLIST: □ Goal is specific and measurable □ Approach has concrete steps □ Success criteria are observable □ Inputs reference other plans by description □ Outputs are specific enough to search for NOTE: Adding A, B, C at 'front' gives [C, B, A] — add in reverse order if needed. ``` --- ## 8. File Formats ### 8.1 Plan File Format **Location**: `~/.9plan/sessions/{session-name}/plans/{plan-id}.txt` ``` # Context {Where this plan fits in the overall task} # Goal {What this plan accomplishes} # Inputs {Dependencies - format: "- description: source"} # Outputs {What this produces - format: "- description: details"} # Approach {Detailed, actionable steps} # Success Criteria {Observable completion conditions} # Notes {Working scratchpad - updated during execution} ``` --- ## 9. Logging ### 9.1 Logger Setup ```typescript // src/logger.ts import pino from 'pino'; import { env } from './env.js'; export const logger = process.env.NODE_ENV === 'development' ? pino({ level: env.NINEPLAN_LOG_LEVEL, transport: { target: 'pino-pretty', options: { colorize: true, translateTime: 'SYS:standard' }, }, }) : pino({ level: env.NINEPLAN_LOG_LEVEL }); export type Logger = pino.Logger; ``` ### 9.2 Session-Scoped Child Loggers ```typescript export class SessionStore { private log: Logger; constructor(sessionName: string) { this.log = logger.child({ session: sessionName }); } addPlan(plan: Plan): void { this.log.info({ planId: plan.id, goal: plan.goal }, 'Plan added'); } } ``` ### 9.3 Log Levels | Level | Usage | |-------|-------| | `debug` | SQL queries, queue state | | `info` | Session created, plan pulled/completed | | `warn` | Collision retry, missing optional field | | `error` | DB error, file write failure | --- ## 10. Error Handling ### 10.1 Error Categories | Category | Description | |----------|-------------| | `SESSION_NOT_FOUND` | Session does not exist | | `PLAN_NOT_FOUND` | Plan ID does not exist | | `NO_ACTIVE_PLAN` | Operation requires active plan | | `PLAN_ALREADY_ACTIVE` | Cannot pull; another plan is active | | `QUEUE_EMPTY` | Queue is empty (task complete) | | `VALIDATION_ERROR` | Input validation failed | | `FILE_SYSTEM_ERROR` | Filesystem operation failed | | `DATABASE_ERROR` | SQLite operation failed | ### 10.2 Error Response Format ``` [Session: {name}] Error: {category} {message} Suggested action: {suggestion} ``` --- ## 11. Configuration ### 11.1 Environment Variables ```typescript // src/env.ts import { z } from 'zod'; import envPaths from 'env-paths'; import { join } from 'node:path'; const paths = envPaths('9plan', { suffix: '' }); const envSchema = z.object({ NINEPLAN_SESSIONS_PATH: z.string().default(join(paths.data, 'sessions')), NINEPLAN_LOG_LEVEL: z.enum(['debug', 'info', 'warn', 'error']).default('info'), NINEPLAN_TRANSPORT: z.enum(['stdio', 'http']).default('stdio'), NINEPLAN_HTTP_PORT: z.coerce.number().default(8080), NINEPLAN_HTTP_HOST: z.string().default('127.0.0.1'), }); export const env = envSchema.parse(process.env); ``` ### 11.2 Entry Point ```typescript // src/index.ts import { serveStdio } from "@karashiiro/mcp/stdio"; import { serveHttp } from "@karashiiro/mcp/http"; import { createServer } from "./server.js"; import { logger } from "./logger.js"; import { env } from "./env.js"; if (env.NINEPLAN_TRANSPORT === "http") { const handle = await serveHttp(createServer, { port: env.NINEPLAN_HTTP_PORT, host: env.NINEPLAN_HTTP_HOST, endpoint: "/mcp", }); logger.info({ port: env.NINEPLAN_HTTP_PORT }, '9plan running (HTTP)'); process.on("SIGINT", async () => { await handle.close(); process.exit(0); }); } else { logger.info('9plan running (stdio)'); await serveStdio(createServer); } ``` ### 11.3 MCP Client Configuration **Claude Desktop** (`~/Library/Application Support/Claude/claude_desktop_config.json`): ```json { "mcpServers": { "9plan": { "command": "node", "args": ["/path/to/9plan-mcp-server/dist/index.js"] } } } ``` --- ## 12. Dependency Injection & Testability We use **constructor injection** for testability without a DI framework. This keeps the codebase simple while allowing full mockability in tests. ### 12.1 The Pattern All classes accept their dependencies via an options object in the constructor: ```typescript // src/types.ts - Dependency interfaces export interface SessionStoreDeps { logger: Logger; generatePlanId: () => string; planFiles: PlanFileHandler; } export interface PlanFileHandlerDeps { logger: Logger; } ``` ### 12.2 Implementation Example ```typescript // src/db/session-store.ts import type { Logger } from '../logger.js'; import type { Plan, SessionStoreDeps } from '../types.js'; export class SessionStore { private log: Logger; private generatePlanId: () => string; private planFiles: PlanFileHandler; constructor( private sessionName: string, private sessionPath: string, deps: SessionStoreDeps, ) { this.log = deps.logger.child({ session: sessionName }); this.generatePlanId = deps.generatePlanId; this.planFiles = deps.planFiles; } addPlan(input: PlanInput): Plan { const id = this.generatePlanId(); this.log.info({ planId: id, goal: input.goal }, 'Adding plan'); // ... implementation } } ``` ### 12.3 Production Wiring ```typescript // src/container.ts - Simple factory functions, no framework needed import { logger } from './logger.js'; import { SessionStore } from './db/session-store.js'; import { PlanFileHandler } from './files/plan-files.js'; import { generatePlanId } from './generators/plan-id.js'; export function createPlanFileHandler(sessionPath: string): PlanFileHandler { return new PlanFileHandler(sessionPath, { logger }); } export function createSessionStore( sessionName: string, sessionPath: string, ): SessionStore { return new SessionStore(sessionName, sessionPath, { logger, generatePlanId, planFiles: createPlanFileHandler(sessionPath), }); } ``` ### 12.4 Testing with Mocks ```typescript // tests/unit/session-store.test.ts import { describe, it, expect, vi, beforeEach } from 'vitest'; import { SessionStore } from '../../src/db/session-store.js'; describe('SessionStore', () => { // Mock factory - creates fresh mocks for each test const createMocks = () => ({ logger: { info: vi.fn(), error: vi.fn(), warn: vi.fn(), debug: vi.fn(), child: vi.fn(() => ({ info: vi.fn(), error: vi.fn(), warn: vi.fn(), debug: vi.fn(), })), }, generatePlanId: vi.fn(() => 'test1'), planFiles: { write: vi.fn(), read: vi.fn(), delete: vi.fn(), exists: vi.fn(() => false), }, }); it('should generate ID and log when adding plan', () => { const mocks = createMocks(); const store = new SessionStore('test-session', '/tmp/test', mocks); store.addPlan({ goal: 'Test the thing', context: 'Testing context', approach: 'Test approach', successCriteria: 'Tests pass', }); expect(mocks.generatePlanId).toHaveBeenCalled(); expect(mocks.logger.child).toHaveBeenCalledWith({ session: 'test-session' }); }); it('should write plan file when adding plan', () => { const mocks = createMocks(); const store = new SessionStore('test-session', '/tmp/test', mocks); store.addPlan({ goal: 'Test the thing', context: 'Testing context', approach: 'Test approach', successCriteria: 'Tests pass', }); expect(mocks.planFiles.write).toHaveBeenCalled(); }); it('should throw when plan file write fails', () => { const mocks = createMocks(); mocks.planFiles.write.mockImplementation(() => { throw new Error('Disk full'); }); const store = new SessionStore('test-session', '/tmp/test', mocks); expect(() => store.addPlan({ goal: 'Test', context: 'Context', approach: 'Approach', successCriteria: 'Criteria', })).toThrow('Disk full'); }); }); ``` ### 12.5 Testing Tools Tools receive their dependencies through the session store: ```typescript // tests/unit/tools/queue-add.test.ts import { describe, it, expect, vi } from 'vitest'; import { handleQueueAdd } from '../../../src/tools/queue-add.js'; describe('9plan_queue_add', () => { it('should return plan ID and path on success', async () => { const mockStore = { addPlan: vi.fn(() => ({ id: 'abc12', queuePosition: 1, })), getSessionPath: vi.fn(() => '/tmp/sessions/test-session'), }; const result = await handleQueueAdd(mockStore, { goal: 'Do something', context: 'Context here', approach: 'Step by step', success_criteria: 'It works', }); expect(result.plan_id).toBe('abc12'); expect(result.plan_path).toContain('abc12.txt'); }); }); ``` ### 12.6 Why Not a DI Container? | Aspect | Manual Injection | DI Container (inversify/tsyringe) | |--------|------------------|-----------------------------------| | **Testability** | ✅ Full | ✅ Full | | **Lines of setup** | ~20 | ~80+ | | **Extra dependencies** | None | reflect-metadata, decorators | | **Decorator noise** | None | `@injectable()` on every class | | **Type safety** | ✅ Native TS | Requires Symbol tokens | | **Learning curve** | None | Moderate | For a project with <10 injectable services, manual wiring is simpler and equally testable. --- ## 13. Implementation Phases ### Phase 1: Foundation - [ ] Project scaffolding (package.json, tsconfig.json) - [ ] Install dependencies - [ ] Logger setup - [ ] TypeScript interfaces (including dep interfaces) - [ ] SQLite schema - [ ] Session name generator (unique-names-generator) - [ ] Plan ID generator (nanoid) - [ ] Entry point with transport ### Phase 2: Session Tools - [ ] `9plan_session_create` - [ ] `9plan_session_resume` - [ ] Session directory management - [ ] Database initialization - [ ] Container/factory functions ### Phase 3: Queue Tools - [ ] `9plan_queue_add` - [ ] `9plan_queue_pull` - [ ] Plan file creation/reading - [ ] Queue position management - [ ] Unit tests for queue operations ### Phase 4: Plan Lifecycle Tools - [ ] `9plan_plan_defer` - [ ] `9plan_plan_complete` - [ ] `9plan_plan_discard` - [ ] Notes accumulation - [ ] FTS5 indexing - [ ] Unit tests for lifecycle ### Phase 5: History Tools - [ ] `9plan_history_search` - [ ] `9plan_history_get` - [ ] FTS5 query implementation - [ ] Unit tests for search ### Phase 6: Admin Tools - [ ] `9plan_admin_validate` - [ ] `9plan_admin_sessions` - [ ] `9plan_admin_state` - [ ] Admin tool unit tests ### Phase 7: Polish - [ ] `bootstrap` prompt - [ ] Server instructions - [ ] Error messages - [ ] Integration tests - [ ] Documentation ### Phase 8: Agent Validation Infrastructure - [ ] `validation/README.md` - Human overview - [ ] `validation/outer-claude-guide.md` - Instructions for validation agent - [ ] `validation/scenarios/*.yaml` - Validation scenario files - [ ] `validation/sandbox/notekeeper/` - Test project scaffold - [ ] `.mcp.json` - MCP configuration for testing --- ## 14. Known Limitations ### Front-Insertion Order Reversal Adding A, B, C at front gives `[C, B, A, ...]`. Add subplans in reverse order to achieve correct sequence. ### Context Compaction Mid-Plan If compaction occurs while executing a plan, the agent loses session awareness. Session name is included in all tool responses to aid recovery. ### Single Agent Per Session No concurrent access support—designed for single-agent workflows. --- ## Appendix: Server Instructions The MCP server's `instructions` field: ``` 9plan manages session-scoped work queues for agent task sequencing. QUICK START: 1. Create session: 9plan_session_create 2. Add plans: 9plan_queue_add ('front' for blocking, 'back' for eventual) 3. Get work: 9plan_queue_pull 4. Execute the plan (read file for full context) 5. Complete: 9plan_plan_complete (or defer/discard) 6. Repeat until queue is empty PLAN FILE FORMAT: # Context, # Goal, # Inputs, # Outputs, # Approach, # Success Criteria, # Notes DEPENDENCY RESOLUTION: - Use 9plan_history_search to find outputs from completed plans - Inputs reference plans by description, not ID SESSION RECOVERY: - All responses include [Session: name] prefix - Resume with: 9plan_session_resume ``` --- *End of Implementation Design Document*