diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 50cb564..b28d6cc 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -161,10 +161,17 @@ Plans: **Goal**: Agent modes for concept refinement (questioning) and phase breakdown (persisting to ROADMAP.md) **Depends on**: Phase 10 **Research**: Unlikely (internal workflow patterns) -**Plans**: TBD +**Plans**: 8 Plans: -- [ ] 11-01: TBD +- [ ] 11-01: Agent Mode Schema Extension +- [ ] 11-02: Initiative & Phase Repositories +- [ ] 11-03: ClaudeAgentManager Mode Support +- [ ] 11-04: Initiative & Phase tRPC Procedures +- [ ] 11-05: Architect Spawn Procedures +- [ ] 11-06: CLI Commands +- [ ] 11-07: Unit Tests +- [ ] 11-08: E2E Tests #### Phase 12: Phase-Task Decomposition **Goal**: Agents break phases into individual tasks with ability to ask questions during breakdown @@ -204,7 +211,7 @@ Phases execute in numeric order: 1 → 1.1 → 2 → 3 → 4 → 5 → 6 → 7 | 8.1. Agent Output Schema | v1.1 | 2/2 | Complete | 2026-01-31 | | 9. Extended Scenarios | v1.1 | 2/2 | Complete | 2026-01-31 | | 10. Multi-Question Schema | v1.2 | 4/4 | Complete | 2026-01-31 | -| 11. Architect Agent | v1.2 | 0/? | Not started | - | +| 11. Architect Agent | v1.2 | 0/8 | Planned | - | | 12. Phase-Task Decomposition | v1.2 | 0/? | Not started | - | | 13. Real Claude E2E Tests | v1.2 | 0/? | Not started | - | diff --git a/.planning/phases/11-architect-agent/11-01-PLAN.md b/.planning/phases/11-architect-agent/11-01-PLAN.md new file mode 100644 index 0000000..591017c --- /dev/null +++ b/.planning/phases/11-architect-agent/11-01-PLAN.md @@ -0,0 +1,187 @@ +--- +phase: 11-architect-agent +plan: 01 +type: execute +wave: 1 +depends_on: [] +files_modified: + - src/agent/schema.ts + - src/agent/types.ts + - src/db/schema.ts + - src/agent/mock-manager.ts +autonomous: true +--- + + +Add agent mode/role support to track what type of work an agent is performing. + +Purpose: Enable different agent behaviors (discuss, breakdown, execute) with mode-specific output schemas so the system knows how to interpret agent output. + +Output: Extended agent schema with mode field, mode-specific output schemas, updated types. + + + +@~/.claude/get-shit-done/workflows/execute-plan.md +@~/.claude/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/STATE.md + +# Prior phase context +@.planning/phases/10-multi-question-schema/10-01-SUMMARY.md + +# Source files to modify +@src/agent/schema.ts +@src/agent/types.ts +@src/db/schema.ts +@src/agent/mock-manager.ts + + + + + + Task 1: Add AgentMode type and database column + src/agent/types.ts, src/db/schema.ts + +1. In src/agent/types.ts, add AgentMode type: + ```typescript + export type AgentMode = 'execute' | 'discuss' | 'breakdown'; + ``` + +2. In src/db/schema.ts, add mode column to agents table: + ```typescript + mode: text('mode', { enum: ['execute', 'discuss', 'breakdown'] }) + .notNull() + .default('execute'), + ``` + +3. Update SpawnAgentOptions in types.ts to include optional mode: + ```typescript + /** Agent operation mode (defaults to 'execute') */ + mode?: AgentMode; + ``` + +4. Update AgentInfo in types.ts to include mode: + ```typescript + /** Current operation mode */ + mode: AgentMode; + ``` + + npm run build passes with no type errors + AgentMode type exists, database schema has mode column, types updated + + + + Task 2: Create mode-specific output schemas + src/agent/schema.ts + +Add mode-specific output schemas following the existing discriminated union pattern: + +1. Keep existing agentOutputSchema as 'execute' mode schema (already handles done/questions/error) + +2. Add discussOutputSchema for discussion mode: + ```typescript + export const discussOutputSchema = z.discriminatedUnion('status', [ + z.object({ + status: z.literal('questions'), + questions: z.array(questionItemSchema), + context: z.string().optional(), // Summary of what's been discussed so far + }), + z.object({ + status: z.literal('context_complete'), + decisions: z.array(z.object({ + topic: z.string(), + decision: z.string(), + reason: z.string(), + })), + summary: z.string(), + }), + z.object({ + status: z.literal('unrecoverable_error'), + error: z.string(), + }), + ]); + ``` + +3. Add breakdownOutputSchema for breakdown mode: + ```typescript + export const breakdownOutputSchema = z.discriminatedUnion('status', [ + z.object({ + status: z.literal('questions'), + questions: z.array(questionItemSchema), + }), + z.object({ + status: z.literal('breakdown_complete'), + phases: z.array(z.object({ + number: z.number(), + name: z.string(), + description: z.string(), + dependencies: z.array(z.number()).optional(), // Phase numbers this depends on + })), + }), + z.object({ + status: z.literal('unrecoverable_error'), + error: z.string(), + }), + ]); + ``` + +4. Export all schemas and types. + +5. Create JSON schema versions for --json-schema flag (discussOutputJsonSchema, breakdownOutputJsonSchema). + + npm run build passes, new schemas are exported + Three mode-specific output schemas exist with JSON schema versions + + + + Task 3: Update MockAgentManager for mode support + src/agent/mock-manager.ts + +1. Update MockAgentScenario to support mode-specific scenarios: + - Add 'discuss' and 'breakdown' status types to the union + - For discuss mode: 'questions' or 'context_complete' + - For breakdown mode: 'questions' or 'breakdown_complete' + +2. Update spawn() to accept mode in SpawnAgentOptions (defaults to 'execute') + +3. Store mode in agent record + +4. Update toAgentInfo() to include mode field + +5. Ensure handleCompletion properly handles mode-specific statuses: + - 'context_complete' → agent:stopped with reason 'context_complete' + - 'breakdown_complete' → agent:stopped with reason 'breakdown_complete' + +Note: Keep backwards compatible with existing tests by defaulting to 'execute' mode. + + npm test passes for mock-manager.test.ts + MockAgentManager supports all three modes with proper status handling + + + + + +Before declaring plan complete: +- [ ] npm run build succeeds without errors +- [ ] npm test passes (all existing tests still work) +- [ ] AgentMode type is exported from src/agent/types.ts +- [ ] All three output schemas are exported from src/agent/schema.ts +- [ ] MockAgentManager handles mode parameter + + + + +- All tasks completed +- Agent mode tracking works end-to-end +- Mode-specific output schemas are validated by Zod +- Existing tests pass (backwards compatible) +- No TypeScript errors + + + +After completion, create `.planning/phases/11-architect-agent/11-01-SUMMARY.md` + diff --git a/.planning/phases/11-architect-agent/11-02-PLAN.md b/.planning/phases/11-architect-agent/11-02-PLAN.md new file mode 100644 index 0000000..548fc83 --- /dev/null +++ b/.planning/phases/11-architect-agent/11-02-PLAN.md @@ -0,0 +1,146 @@ +--- +phase: 11-architect-agent +plan: 02 +type: execute +wave: 1 +depends_on: [] +files_modified: + - src/db/repositories/initiative-repository.ts + - src/db/repositories/phase-repository.ts + - src/db/repositories/index.ts + - src/db/index.ts +autonomous: true +--- + + +Create Initiative and Phase repositories following the hexagonal port/adapter pattern. + +Purpose: Enable CRUD operations for initiatives and phases, which are required for the Architect agent to persist its breakdown work. + +Output: InitiativeRepository and PhaseRepository ports with Drizzle adapters. + + + +@~/.claude/get-shit-done/workflows/execute-plan.md +@~/.claude/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/STATE.md + +# Repository pattern reference +@src/db/repositories/task-repository.ts +@src/db/repositories/drizzle-task-repository.ts + +# Schema reference +@src/db/schema.ts + + + + + + Task 1: Create InitiativeRepository port and adapter + src/db/repositories/initiative-repository.ts, src/db/repositories/drizzle-initiative-repository.ts + +1. Create src/db/repositories/initiative-repository.ts (PORT): + ```typescript + import type { Initiative, NewInitiative } from '../schema.js'; + + export type CreateInitiativeData = Omit; + export type UpdateInitiativeData = Partial; + + export interface InitiativeRepository { + create(data: CreateInitiativeData): Promise; + findById(id: string): Promise; + findAll(): Promise; + findByStatus(status: 'active' | 'completed' | 'archived'): Promise; + update(id: string, data: UpdateInitiativeData): Promise; + delete(id: string): Promise; + } + ``` + +2. Create src/db/repositories/drizzle-initiative-repository.ts (ADAPTER): + - Follow DrizzleTaskRepository pattern exactly + - Use randomUUID() for id generation + - Set createdAt/updatedAt to new Date() on create + - Update updatedAt on update + - Fetch after insert to get defaults + + npm run build passes + InitiativeRepository port and Drizzle adapter created + + + + Task 2: Create PhaseRepository port and adapter + src/db/repositories/phase-repository.ts, src/db/repositories/drizzle-phase-repository.ts + +1. Create src/db/repositories/phase-repository.ts (PORT): + ```typescript + import type { Phase, NewPhase } from '../schema.js'; + + export type CreatePhaseData = Omit; + export type UpdatePhaseData = Partial; + + export interface PhaseRepository { + create(data: CreatePhaseData): Promise; + findById(id: string): Promise; + findByInitiativeId(initiativeId: string): Promise; + findByNumber(initiativeId: string, number: number): Promise; + update(id: string, data: UpdatePhaseData): Promise; + delete(id: string): Promise; + getNextNumber(initiativeId: string): Promise; + } + ``` + +2. Create src/db/repositories/drizzle-phase-repository.ts (ADAPTER): + - Follow DrizzleTaskRepository pattern + - findByInitiativeId returns phases ordered by number ASC + - getNextNumber returns MAX(number) + 1, or 1 if no phases exist + - Foreign key to initiative enforced by database + + npm run build passes + PhaseRepository port and Drizzle adapter created + + + + Task 3: Export repositories from index files + src/db/repositories/index.ts, src/db/index.ts + +1. In src/db/repositories/index.ts, add exports: + ```typescript + export type { InitiativeRepository, CreateInitiativeData, UpdateInitiativeData } from './initiative-repository.js'; + export { DrizzleInitiativeRepository } from './drizzle-initiative-repository.js'; + + export type { PhaseRepository, CreatePhaseData, UpdatePhaseData } from './phase-repository.js'; + export { DrizzlePhaseRepository } from './drizzle-phase-repository.js'; + ``` + +2. In src/db/index.ts, re-export the new repositories if not already done via index.ts. + + npm run build passes, repositories are importable from src/db + All repositories exported and importable + + + + + +Before declaring plan complete: +- [ ] npm run build succeeds without errors +- [ ] npm test passes +- [ ] InitiativeRepository and PhaseRepository are importable from src/db +- [ ] Adapters follow existing patterns (randomUUID, timestamps, fetch after insert) + + + + +- All tasks completed +- Both repositories have port interfaces and Drizzle adapters +- Pattern matches existing TaskRepository implementation +- TypeScript types are correct + + + +After completion, create `.planning/phases/11-architect-agent/11-02-SUMMARY.md` + diff --git a/.planning/phases/11-architect-agent/11-03-PLAN.md b/.planning/phases/11-architect-agent/11-03-PLAN.md new file mode 100644 index 0000000..618c6be --- /dev/null +++ b/.planning/phases/11-architect-agent/11-03-PLAN.md @@ -0,0 +1,161 @@ +--- +phase: 11-architect-agent +plan: 03 +type: execute +wave: 2 +depends_on: ["11-01", "11-02"] +files_modified: + - src/agent/manager.ts + - src/db/repositories/agent-repository.ts + - src/db/repositories/drizzle-agent-repository.ts +autonomous: true +--- + + +Update ClaudeAgentManager and AgentRepository to support agent modes. + +Purpose: Enable spawning agents in different modes (execute, discuss, breakdown) with mode-specific JSON schemas passed to Claude CLI. + +Output: Mode-aware agent spawning with correct output schema per mode. + + + +@~/.claude/get-shit-done/workflows/execute-plan.md +@~/.claude/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/STATE.md + +# Depends on Plan 01 for mode schemas +@.planning/phases/11-architect-agent/11-01-SUMMARY.md + +# Source files to modify +@src/agent/manager.ts +@src/agent/schema.ts +@src/db/repositories/agent-repository.ts +@src/db/repositories/drizzle-agent-repository.ts + + + + + + Task 1: Update AgentRepository for mode field + src/db/repositories/agent-repository.ts, src/db/repositories/drizzle-agent-repository.ts + +1. In agent-repository.ts, update CreateAgentData to include mode: + ```typescript + export type CreateAgentData = { + name: string; + taskId: string | null; + sessionId: string | null; + worktreeId: string; + status: AgentStatus; + mode?: AgentMode; // Defaults to 'execute' if not provided + }; + ``` + +2. Import AgentMode from '../agent/types.js' + +3. In drizzle-agent-repository.ts: + - Import AgentMode type + - Update create() to handle mode field (default to 'execute' if not provided) + - Ensure toAgentRecord() includes mode in return type + + npm run build passes + AgentRepository handles mode field with 'execute' default + + + + Task 2: Update ClaudeAgentManager for mode-specific schemas + src/agent/manager.ts + +1. Import all output JSON schemas: + ```typescript + import { + agentOutputSchema, + agentOutputJsonSchema, + discussOutputJsonSchema, + breakdownOutputJsonSchema, + discussOutputSchema, + breakdownOutputSchema, + } from './schema.js'; + ``` + +2. Add helper function to get JSON schema by mode: + ```typescript + private getJsonSchemaForMode(mode: AgentMode): object { + switch (mode) { + case 'discuss': return discussOutputJsonSchema; + case 'breakdown': return breakdownOutputJsonSchema; + case 'execute': + default: return agentOutputJsonSchema; + } + } + ``` + +3. Update spawn() to: + - Extract mode from options (default to 'execute') + - Pass mode to repository.create() + - Use getJsonSchemaForMode(mode) when constructing CLI args + +4. Update handleAgentCompletion() to parse output based on agent mode: + - Fetch agent to get its mode + - Use appropriate schema (discussOutputSchema, breakdownOutputSchema, or agentOutputSchema) + - Handle mode-specific statuses: + - 'context_complete' → emit agent:stopped with reason 'context_complete' + - 'breakdown_complete' → emit agent:stopped with reason 'breakdown_complete' + +5. Update toAgentInfo() to include mode field from agent record. + + npm run build passes + ClaudeAgentManager spawns agents with mode-specific JSON schemas + + + + Task 3: Add mode to AgentStoppedEvent reasons + src/events/types.ts + +1. Update AgentStoppedEvent payload to support new stop reasons: + ```typescript + export interface AgentStoppedEvent extends BaseEvent { + type: 'agent:stopped'; + payload: { + agentId: string; + name: string; + taskId: string; + reason: 'task_complete' | 'user_requested' | 'context_complete' | 'breakdown_complete'; + }; + } + ``` + +Note: This expands the union type - existing code using 'task_complete' and 'user_requested' continues to work. + + npm run build passes, existing tests pass + AgentStoppedEvent supports mode-specific completion reasons + + + + + +Before declaring plan complete: +- [ ] npm run build succeeds without errors +- [ ] npm test passes (all existing tests still work) +- [ ] Agent can be spawned with mode='discuss' and receives discuss JSON schema +- [ ] Agent can be spawned with mode='breakdown' and receives breakdown JSON schema +- [ ] Default mode='execute' works as before (backwards compatible) + + + + +- All tasks completed +- Mode-specific JSON schemas passed to Claude CLI +- Mode stored in database and returned in AgentInfo +- Existing tests pass (backwards compatible with execute mode) + + + +After completion, create `.planning/phases/11-architect-agent/11-03-SUMMARY.md` + diff --git a/.planning/phases/11-architect-agent/11-04-PLAN.md b/.planning/phases/11-architect-agent/11-04-PLAN.md new file mode 100644 index 0000000..634186a --- /dev/null +++ b/.planning/phases/11-architect-agent/11-04-PLAN.md @@ -0,0 +1,271 @@ +--- +phase: 11-architect-agent +plan: 04 +type: execute +wave: 2 +depends_on: ["11-02"] +files_modified: + - src/trpc/router.ts + - src/trpc/context.ts +autonomous: true +--- + + +Add Initiative and Phase tRPC procedures for CRUD operations. + +Purpose: Enable CLI and future UI to create, read, update, and delete initiatives and phases through type-safe RPC. + +Output: tRPC procedures for initiative and phase management. + + + +@~/.claude/get-shit-done/workflows/execute-plan.md +@~/.claude/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/STATE.md + +# Depends on Plan 02 for repositories +@.planning/phases/11-architect-agent/11-02-SUMMARY.md + +# Source files +@src/trpc/router.ts +@src/trpc/context.ts + + + + + + Task 1: Add repositories to tRPC context + src/trpc/context.ts + +1. Import InitiativeRepository and PhaseRepository types: + ```typescript + import type { InitiativeRepository } from '../db/repositories/initiative-repository.js'; + import type { PhaseRepository } from '../db/repositories/phase-repository.js'; + ``` + +2. Add to TRPCContext interface: + ```typescript + initiativeRepository?: InitiativeRepository; + phaseRepository?: PhaseRepository; + ``` + +3. Add to CreateContextOptions if needed for wiring. + + npm run build passes + Context includes optional initiative and phase repositories + + + + Task 2: Add Initiative tRPC procedures + src/trpc/router.ts + +1. Add helper function: + ```typescript + function requireInitiativeRepository(ctx: TRPCContext): InitiativeRepository { + if (!ctx.initiativeRepository) { + throw new TRPCError({ + code: 'INTERNAL_SERVER_ERROR', + message: 'Initiative repository not available', + }); + } + return ctx.initiativeRepository; + } + ``` + +2. Add Initiative procedures to appRouter: + ```typescript + // Create initiative + createInitiative: publicProcedure + .input(z.object({ + name: z.string().min(1), + description: z.string().optional(), + })) + .mutation(async ({ ctx, input }) => { + const repo = requireInitiativeRepository(ctx); + return repo.create({ + name: input.name, + description: input.description ?? null, + status: 'active', + }); + }), + + // List all initiatives + listInitiatives: publicProcedure + .input(z.object({ + status: z.enum(['active', 'completed', 'archived']).optional(), + }).optional()) + .query(async ({ ctx, input }) => { + const repo = requireInitiativeRepository(ctx); + if (input?.status) { + return repo.findByStatus(input.status); + } + return repo.findAll(); + }), + + // Get initiative by ID + getInitiative: publicProcedure + .input(z.object({ id: z.string().min(1) })) + .query(async ({ ctx, input }) => { + const repo = requireInitiativeRepository(ctx); + const initiative = await repo.findById(input.id); + if (!initiative) { + throw new TRPCError({ + code: 'NOT_FOUND', + message: `Initiative '${input.id}' not found`, + }); + } + return initiative; + }), + + // Update initiative + updateInitiative: publicProcedure + .input(z.object({ + id: z.string().min(1), + name: z.string().min(1).optional(), + description: z.string().optional(), + status: z.enum(['active', 'completed', 'archived']).optional(), + })) + .mutation(async ({ ctx, input }) => { + const repo = requireInitiativeRepository(ctx); + const { id, ...data } = input; + return repo.update(id, data); + }), + ``` + + npm run build passes + Initiative CRUD procedures added to router + + + + Task 3: Add Phase tRPC procedures + src/trpc/router.ts + +1. Add helper function: + ```typescript + function requirePhaseRepository(ctx: TRPCContext): PhaseRepository { + if (!ctx.phaseRepository) { + throw new TRPCError({ + code: 'INTERNAL_SERVER_ERROR', + message: 'Phase repository not available', + }); + } + return ctx.phaseRepository; + } + ``` + +2. Add Phase procedures to appRouter: + ```typescript + // Create phase (auto-assigns next number) + createPhase: publicProcedure + .input(z.object({ + initiativeId: z.string().min(1), + name: z.string().min(1), + description: z.string().optional(), + })) + .mutation(async ({ ctx, input }) => { + const repo = requirePhaseRepository(ctx); + const nextNumber = await repo.getNextNumber(input.initiativeId); + return repo.create({ + initiativeId: input.initiativeId, + number: nextNumber, + name: input.name, + description: input.description ?? null, + status: 'pending', + }); + }), + + // List phases for initiative + listPhases: publicProcedure + .input(z.object({ initiativeId: z.string().min(1) })) + .query(async ({ ctx, input }) => { + const repo = requirePhaseRepository(ctx); + return repo.findByInitiativeId(input.initiativeId); + }), + + // Get phase by ID + getPhase: publicProcedure + .input(z.object({ id: z.string().min(1) })) + .query(async ({ ctx, input }) => { + const repo = requirePhaseRepository(ctx); + const phase = await repo.findById(input.id); + if (!phase) { + throw new TRPCError({ + code: 'NOT_FOUND', + message: `Phase '${input.id}' not found`, + }); + } + return phase; + }), + + // Update phase + updatePhase: publicProcedure + .input(z.object({ + id: z.string().min(1), + name: z.string().min(1).optional(), + description: z.string().optional(), + status: z.enum(['pending', 'in_progress', 'completed']).optional(), + })) + .mutation(async ({ ctx, input }) => { + const repo = requirePhaseRepository(ctx); + const { id, ...data } = input; + return repo.update(id, data); + }), + + // Create multiple phases at once (from breakdown) + createPhasesFromBreakdown: publicProcedure + .input(z.object({ + initiativeId: z.string().min(1), + phases: z.array(z.object({ + number: z.number(), + name: z.string().min(1), + description: z.string(), + })), + })) + .mutation(async ({ ctx, input }) => { + const repo = requirePhaseRepository(ctx); + const created: Phase[] = []; + for (const p of input.phases) { + const phase = await repo.create({ + initiativeId: input.initiativeId, + number: p.number, + name: p.name, + description: p.description, + status: 'pending', + }); + created.push(phase); + } + return created; + }), + ``` + + npm run build passes + Phase CRUD procedures added to router including bulk create + + + + + +Before declaring plan complete: +- [ ] npm run build succeeds without errors +- [ ] npm test passes +- [ ] Initiative procedures: createInitiative, listInitiatives, getInitiative, updateInitiative +- [ ] Phase procedures: createPhase, listPhases, getPhase, updatePhase, createPhasesFromBreakdown +- [ ] All procedures follow existing patterns (error handling, optional repos) + + + + +- All tasks completed +- Initiative and Phase CRUD available via tRPC +- Bulk phase creation supports Architect breakdown output +- Type-safe input/output validation + + + +After completion, create `.planning/phases/11-architect-agent/11-04-SUMMARY.md` + diff --git a/.planning/phases/11-architect-agent/11-05-PLAN.md b/.planning/phases/11-architect-agent/11-05-PLAN.md new file mode 100644 index 0000000..f87325a --- /dev/null +++ b/.planning/phases/11-architect-agent/11-05-PLAN.md @@ -0,0 +1,205 @@ +--- +phase: 11-architect-agent +plan: 05 +type: execute +wave: 3 +depends_on: ["11-03", "11-04"] +files_modified: + - src/trpc/router.ts +autonomous: true +--- + + +Add Architect-specific tRPC procedures for spawning agents in discuss/breakdown modes. + +Purpose: Enable spawning architect agents that use mode-specific prompts and output schemas, with convenience procedures for the discuss→breakdown workflow. + +Output: spawnArchitect procedure and mode-aware agent spawning. + + + +@~/.claude/get-shit-done/workflows/execute-plan.md +@~/.claude/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/STATE.md + +# Depends on prior plans +@.planning/phases/11-architect-agent/11-03-SUMMARY.md +@.planning/phases/11-architect-agent/11-04-SUMMARY.md + +# Source files +@src/trpc/router.ts +@src/agent/types.ts + + + + + + Task 1: Update spawnAgent to support mode + src/trpc/router.ts + +1. Update spawnAgentInputSchema to include mode: + ```typescript + export const spawnAgentInputSchema = z.object({ + name: z.string().min(1), + taskId: z.string().min(1), + prompt: z.string().min(1), + cwd: z.string().optional(), + mode: z.enum(['execute', 'discuss', 'breakdown']).optional(), // defaults to 'execute' + }); + ``` + +2. Update spawnAgent procedure to pass mode to agentManager.spawn(): + ```typescript + spawnAgent: publicProcedure + .input(spawnAgentInputSchema) + .mutation(async ({ ctx, input }) => { + const agentManager = requireAgentManager(ctx); + return agentManager.spawn({ + name: input.name, + taskId: input.taskId, + prompt: input.prompt, + cwd: input.cwd, + mode: input.mode, // undefined defaults to 'execute' in AgentManager + }); + }), + ``` + + npm run build passes + spawnAgent procedure accepts mode parameter + + + + Task 2: Add spawnArchitect convenience procedures + src/trpc/router.ts + +Add architect-specific spawn procedures: + +```typescript +// Spawn architect in discuss mode +spawnArchitectDiscuss: publicProcedure + .input(z.object({ + name: z.string().min(1), + initiativeId: z.string().min(1), + context: z.string().optional(), // Initial context about what to discuss + })) + .mutation(async ({ ctx, input }) => { + const agentManager = requireAgentManager(ctx); + const initiativeRepo = requireInitiativeRepository(ctx); + + // Verify initiative exists + const initiative = await initiativeRepo.findById(input.initiativeId); + if (!initiative) { + throw new TRPCError({ + code: 'NOT_FOUND', + message: `Initiative '${input.initiativeId}' not found`, + }); + } + + const prompt = buildDiscussPrompt(initiative, input.context); + + return agentManager.spawn({ + name: input.name, + taskId: input.initiativeId, // Use initiative ID as task reference + prompt, + mode: 'discuss', + }); + }), + +// Spawn architect in breakdown mode +spawnArchitectBreakdown: publicProcedure + .input(z.object({ + name: z.string().min(1), + initiativeId: z.string().min(1), + contextSummary: z.string().optional(), // Summary from discuss phase + })) + .mutation(async ({ ctx, input }) => { + const agentManager = requireAgentManager(ctx); + const initiativeRepo = requireInitiativeRepository(ctx); + + const initiative = await initiativeRepo.findById(input.initiativeId); + if (!initiative) { + throw new TRPCError({ + code: 'NOT_FOUND', + message: `Initiative '${input.initiativeId}' not found`, + }); + } + + const prompt = buildBreakdownPrompt(initiative, input.contextSummary); + + return agentManager.spawn({ + name: input.name, + taskId: input.initiativeId, + prompt, + mode: 'breakdown', + }); + }), +``` + +Add helper functions above the router definition: +```typescript +function buildDiscussPrompt(initiative: Initiative, context?: string): string { + return `You are an Architect agent in discuss mode. + +Initiative: ${initiative.name} +${initiative.description ? `Description: ${initiative.description}` : ''} +${context ? `\nContext: ${context}` : ''} + +Your task is to ask clarifying questions to understand the requirements better. +Ask questions about: +- User journeys and workflows +- Technical constraints and preferences +- Edge cases and error handling +- Integration points + +When you have enough information, output status: "context_complete" with your decisions.`; +} + +function buildBreakdownPrompt(initiative: Initiative, contextSummary?: string): string { + return `You are an Architect agent in breakdown mode. + +Initiative: ${initiative.name} +${initiative.description ? `Description: ${initiative.description}` : ''} +${contextSummary ? `\nContext from discussion:\n${contextSummary}` : ''} + +Your task is to break this initiative into phases. +Each phase should be: +- A coherent unit of work +- Deliverable independently +- Clear dependencies on prior phases + +Output status: "breakdown_complete" with an array of phases.`; +} +``` + + npm run build passes + Architect spawn procedures with mode-specific prompts added + + + + + +Before declaring plan complete: +- [ ] npm run build succeeds without errors +- [ ] npm test passes +- [ ] spawnAgent accepts mode parameter +- [ ] spawnArchitectDiscuss spawns agent in discuss mode +- [ ] spawnArchitectBreakdown spawns agent in breakdown mode +- [ ] Both architect procedures validate initiative exists + + + + +- All tasks completed +- Architect workflow has dedicated spawn procedures +- Mode-specific prompts guide agent behavior +- Initiative validation prevents orphaned agents + + + +After completion, create `.planning/phases/11-architect-agent/11-05-SUMMARY.md` + diff --git a/.planning/phases/11-architect-agent/11-06-PLAN.md b/.planning/phases/11-architect-agent/11-06-PLAN.md new file mode 100644 index 0000000..dd1fe2a --- /dev/null +++ b/.planning/phases/11-architect-agent/11-06-PLAN.md @@ -0,0 +1,240 @@ +--- +phase: 11-architect-agent +plan: 06 +type: execute +wave: 3 +depends_on: ["11-04", "11-05"] +files_modified: + - src/cli/index.ts +autonomous: true +--- + + +Add CLI commands for initiative management and architect workflow. + +Purpose: Enable users to create initiatives, list them, and spawn architect agents via the CLI. + +Output: CLI commands for `cw initiative` and `cw architect` command groups. + + + +@~/.claude/get-shit-done/workflows/execute-plan.md +@~/.claude/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/STATE.md + +# Depends on tRPC procedures +@.planning/phases/11-architect-agent/11-04-SUMMARY.md +@.planning/phases/11-architect-agent/11-05-SUMMARY.md + +# CLI reference +@src/cli/index.ts + + + + + + Task 1: Add initiative CLI commands + src/cli/index.ts + +Add initiative command group following existing patterns (agent commands): + +```typescript +// cw initiative +const initiativeCommand = program + .command('initiative') + .description('Manage initiatives'); + +// cw initiative create +initiativeCommand + .command('create ') + .description('Create a new initiative') + .option('-d, --description ', 'Initiative description') + .action(async (name: string, options: { description?: string }) => { + const client = createClient(); + try { + const initiative = await client.createInitiative.mutate({ + name, + description: options.description, + }); + console.log(`Created initiative: ${initiative.id}`); + console.log(` Name: ${initiative.name}`); + if (initiative.description) { + console.log(` Description: ${initiative.description}`); + } + } catch (error) { + handleError(error); + } + }); + +// cw initiative list +initiativeCommand + .command('list') + .description('List all initiatives') + .option('-s, --status ', 'Filter by status (active, completed, archived)') + .action(async (options: { status?: 'active' | 'completed' | 'archived' }) => { + const client = createClient(); + try { + const initiatives = await client.listInitiatives.query( + options.status ? { status: options.status } : undefined + ); + if (initiatives.length === 0) { + console.log('No initiatives found'); + return; + } + for (const init of initiatives) { + console.log(`${init.id} ${init.status.padEnd(10)} ${init.name}`); + } + } catch (error) { + handleError(error); + } + }); + +// cw initiative get +initiativeCommand + .command('get ') + .description('Get initiative details') + .action(async (id: string) => { + const client = createClient(); + try { + const initiative = await client.getInitiative.query({ id }); + console.log(`ID: ${initiative.id}`); + console.log(`Name: ${initiative.name}`); + console.log(`Status: ${initiative.status}`); + if (initiative.description) { + console.log(`Description: ${initiative.description}`); + } + console.log(`Created: ${initiative.createdAt.toISOString()}`); + } catch (error) { + handleError(error); + } + }); +``` + + npm run build passes, cw initiative --help shows commands + Initiative CLI commands added (create, list, get) + + + + Task 2: Add architect CLI commands + src/cli/index.ts + +Add architect command group: + +```typescript +// cw architect +const architectCommand = program + .command('architect') + .description('Architect agent workflow'); + +// cw architect discuss +architectCommand + .command('discuss ') + .description('Start discussion phase for an initiative') + .requiredOption('--name ', 'Agent name') + .option('-c, --context ', 'Initial context') + .action(async (initiativeId: string, options: { name: string; context?: string }) => { + const client = createClient(); + try { + const agent = await client.spawnArchitectDiscuss.mutate({ + name: options.name, + initiativeId, + context: options.context, + }); + console.log(`Started architect agent in discuss mode`); + console.log(` Agent: ${agent.name} (${agent.id})`); + console.log(` Mode: ${agent.mode}`); + console.log(` Initiative: ${initiativeId}`); + } catch (error) { + handleError(error); + } + }); + +// cw architect breakdown +architectCommand + .command('breakdown ') + .description('Start breakdown phase for an initiative') + .requiredOption('--name ', 'Agent name') + .option('-s, --summary ', 'Context summary from discuss phase') + .action(async (initiativeId: string, options: { name: string; summary?: string }) => { + const client = createClient(); + try { + const agent = await client.spawnArchitectBreakdown.mutate({ + name: options.name, + initiativeId, + contextSummary: options.summary, + }); + console.log(`Started architect agent in breakdown mode`); + console.log(` Agent: ${agent.name} (${agent.id})`); + console.log(` Mode: ${agent.mode}`); + console.log(` Initiative: ${initiativeId}`); + } catch (error) { + handleError(error); + } + }); +``` + + npm run build passes, cw architect --help shows commands + Architect CLI commands added (discuss, breakdown) + + + + Task 3: Add phase list command + src/cli/index.ts + +Add phase listing under initiative or as separate command: + +```typescript +// cw initiative phases +initiativeCommand + .command('phases ') + .description('List phases for an initiative') + .action(async (initiativeId: string) => { + const client = createClient(); + try { + const phases = await client.listPhases.query({ initiativeId }); + if (phases.length === 0) { + console.log('No phases found'); + return; + } + for (const phase of phases) { + console.log(`${phase.number.toString().padStart(2)}. ${phase.name} [${phase.status}]`); + if (phase.description) { + console.log(` ${phase.description}`); + } + } + } catch (error) { + handleError(error); + } + }); +``` + + npm run build passes + Phase listing command added + + + + + +Before declaring plan complete: +- [ ] npm run build succeeds without errors +- [ ] cw initiative --help shows: create, list, get, phases +- [ ] cw architect --help shows: discuss, breakdown +- [ ] Commands follow existing CLI patterns (error handling, output format) + + + + +- All tasks completed +- Initiative CRUD available via CLI +- Architect workflow accessible via CLI +- Phase listing works for initiatives + + + +After completion, create `.planning/phases/11-architect-agent/11-06-SUMMARY.md` + diff --git a/.planning/phases/11-architect-agent/11-07-PLAN.md b/.planning/phases/11-architect-agent/11-07-PLAN.md new file mode 100644 index 0000000..9ce9dd9 --- /dev/null +++ b/.planning/phases/11-architect-agent/11-07-PLAN.md @@ -0,0 +1,350 @@ +--- +phase: 11-architect-agent +plan: 07 +type: execute +wave: 4 +depends_on: ["11-01", "11-02", "11-03"] +files_modified: + - src/agent/mock-manager.test.ts + - src/db/repositories/initiative-repository.test.ts + - src/db/repositories/phase-repository.test.ts +autonomous: true +--- + + +Add unit tests for mode schemas, MockAgentManager modes, and new repositories. + +Purpose: Ensure mode-specific functionality works correctly before E2E testing. + +Output: Test coverage for agent modes, initiative repository, and phase repository. + + + +@~/.claude/get-shit-done/workflows/execute-plan.md +@~/.claude/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/STATE.md + +# Test patterns +@src/agent/mock-manager.test.ts +@src/db/repositories/drizzle-task-repository.test.ts + + + + + + Task 1: Add MockAgentManager mode tests + src/agent/mock-manager.test.ts + +Add test suite for mode functionality: + +```typescript +describe('agent modes', () => { + it('should spawn agent with default execute mode', async () => { + const agent = await manager.spawn({ + name: 'exec-agent', + taskId: 't1', + prompt: 'test', + }); + expect(agent.mode).toBe('execute'); + }); + + it('should spawn agent in discuss mode', async () => { + manager.setScenario('discuss-agent', { + status: 'context_complete', + delay: 0, + decisions: [{ topic: 'Auth', decision: 'JWT', reason: 'Standard' }], + summary: 'Auth discussion complete', + }); + + const agent = await manager.spawn({ + name: 'discuss-agent', + taskId: 't1', + prompt: 'discuss auth', + mode: 'discuss', + }); + + expect(agent.mode).toBe('discuss'); + }); + + it('should spawn agent in breakdown mode', async () => { + manager.setScenario('breakdown-agent', { + status: 'breakdown_complete', + delay: 0, + phases: [ + { number: 1, name: 'Foundation', description: 'Core setup' }, + { number: 2, name: 'Features', description: 'Main features' }, + ], + }); + + const agent = await manager.spawn({ + name: 'breakdown-agent', + taskId: 't1', + prompt: 'breakdown work', + mode: 'breakdown', + }); + + expect(agent.mode).toBe('breakdown'); + }); + + it('should emit stopped event with context_complete reason', async () => { + manager.setScenario('discuss-done', { + status: 'context_complete', + delay: 0, + decisions: [], + summary: 'Done', + }); + + await manager.spawn({ + name: 'discuss-done', + taskId: 't1', + prompt: 'test', + mode: 'discuss', + }); + await vi.runAllTimersAsync(); + + const stopped = eventBus.emittedEvents.find(e => e.type === 'agent:stopped'); + expect(stopped?.payload.reason).toBe('context_complete'); + }); + + it('should emit stopped event with breakdown_complete reason', async () => { + manager.setScenario('breakdown-done', { + status: 'breakdown_complete', + delay: 0, + phases: [], + }); + + await manager.spawn({ + name: 'breakdown-done', + taskId: 't1', + prompt: 'test', + mode: 'breakdown', + }); + await vi.runAllTimersAsync(); + + const stopped = eventBus.emittedEvents.find(e => e.type === 'agent:stopped'); + expect(stopped?.payload.reason).toBe('breakdown_complete'); + }); +}); +``` + + npm test src/agent/mock-manager.test.ts passes + Mode tests added for MockAgentManager + + + + Task 2: Add InitiativeRepository tests + src/db/repositories/initiative-repository.test.ts + +Create test file following DrizzleTaskRepository test patterns: + +```typescript +import { describe, it, expect, beforeEach, afterEach } from 'vitest'; +import { DrizzleInitiativeRepository } from './drizzle-initiative-repository.js'; +import { createTestDatabase, cleanupTestDatabase } from '../test-utils.js'; + +describe('DrizzleInitiativeRepository', () => { + let repository: DrizzleInitiativeRepository; + let cleanup: () => Promise; + + beforeEach(async () => { + const { db, cleanup: c } = await createTestDatabase(); + cleanup = c; + repository = new DrizzleInitiativeRepository(db); + }); + + afterEach(async () => { + await cleanup(); + }); + + describe('create', () => { + it('should create initiative with generated id', async () => { + const initiative = await repository.create({ + name: 'Test Initiative', + description: 'Test description', + status: 'active', + }); + + expect(initiative.id).toBeDefined(); + expect(initiative.name).toBe('Test Initiative'); + expect(initiative.status).toBe('active'); + }); + + it('should set timestamps on create', async () => { + const initiative = await repository.create({ + name: 'Test', + status: 'active', + }); + + expect(initiative.createdAt).toBeInstanceOf(Date); + expect(initiative.updatedAt).toBeInstanceOf(Date); + }); + }); + + describe('findById', () => { + it('should find existing initiative', async () => { + const created = await repository.create({ name: 'Find Me', status: 'active' }); + const found = await repository.findById(created.id); + expect(found).not.toBeNull(); + expect(found?.name).toBe('Find Me'); + }); + + it('should return null for non-existent id', async () => { + const found = await repository.findById('non-existent'); + expect(found).toBeNull(); + }); + }); + + describe('findByStatus', () => { + it('should filter by status', async () => { + await repository.create({ name: 'Active 1', status: 'active' }); + await repository.create({ name: 'Active 2', status: 'active' }); + await repository.create({ name: 'Completed', status: 'completed' }); + + const active = await repository.findByStatus('active'); + expect(active).toHaveLength(2); + + const completed = await repository.findByStatus('completed'); + expect(completed).toHaveLength(1); + }); + }); + + describe('update', () => { + it('should update initiative fields', async () => { + const created = await repository.create({ name: 'Original', status: 'active' }); + const updated = await repository.update(created.id, { name: 'Updated' }); + + expect(updated.name).toBe('Updated'); + expect(updated.updatedAt.getTime()).toBeGreaterThan(created.updatedAt.getTime()); + }); + }); +}); +``` + +Note: If createTestDatabase doesn't exist, create a simple test utility or use in-memory SQLite. + + npm test src/db/repositories/initiative-repository.test.ts passes + Initiative repository tests added + + + + Task 3: Add PhaseRepository tests + src/db/repositories/phase-repository.test.ts + +Create test file: + +```typescript +import { describe, it, expect, beforeEach, afterEach } from 'vitest'; +import { DrizzlePhaseRepository } from './drizzle-phase-repository.js'; +import { DrizzleInitiativeRepository } from './drizzle-initiative-repository.js'; +import { createTestDatabase, cleanupTestDatabase } from '../test-utils.js'; + +describe('DrizzlePhaseRepository', () => { + let phaseRepo: DrizzlePhaseRepository; + let initRepo: DrizzleInitiativeRepository; + let initiativeId: string; + let cleanup: () => Promise; + + beforeEach(async () => { + const { db, cleanup: c } = await createTestDatabase(); + cleanup = c; + phaseRepo = new DrizzlePhaseRepository(db); + initRepo = new DrizzleInitiativeRepository(db); + + // Create parent initiative + const init = await initRepo.create({ name: 'Test Initiative', status: 'active' }); + initiativeId = init.id; + }); + + afterEach(async () => { + await cleanup(); + }); + + describe('create', () => { + it('should create phase with generated id', async () => { + const phase = await phaseRepo.create({ + initiativeId, + number: 1, + name: 'Phase 1', + description: 'First phase', + status: 'pending', + }); + + expect(phase.id).toBeDefined(); + expect(phase.number).toBe(1); + expect(phase.name).toBe('Phase 1'); + }); + }); + + describe('findByInitiativeId', () => { + it('should return phases ordered by number', async () => { + await phaseRepo.create({ initiativeId, number: 3, name: 'Phase 3', status: 'pending' }); + await phaseRepo.create({ initiativeId, number: 1, name: 'Phase 1', status: 'pending' }); + await phaseRepo.create({ initiativeId, number: 2, name: 'Phase 2', status: 'pending' }); + + const phases = await phaseRepo.findByInitiativeId(initiativeId); + + expect(phases).toHaveLength(3); + expect(phases[0].number).toBe(1); + expect(phases[1].number).toBe(2); + expect(phases[2].number).toBe(3); + }); + }); + + describe('getNextNumber', () => { + it('should return 1 for empty initiative', async () => { + const next = await phaseRepo.getNextNumber(initiativeId); + expect(next).toBe(1); + }); + + it('should return max + 1', async () => { + await phaseRepo.create({ initiativeId, number: 1, name: 'P1', status: 'pending' }); + await phaseRepo.create({ initiativeId, number: 2, name: 'P2', status: 'pending' }); + + const next = await phaseRepo.getNextNumber(initiativeId); + expect(next).toBe(3); + }); + }); + + describe('findByNumber', () => { + it('should find phase by initiative and number', async () => { + await phaseRepo.create({ initiativeId, number: 1, name: 'Phase 1', status: 'pending' }); + + const found = await phaseRepo.findByNumber(initiativeId, 1); + expect(found).not.toBeNull(); + expect(found?.name).toBe('Phase 1'); + }); + }); +}); +``` + + npm test src/db/repositories/phase-repository.test.ts passes + Phase repository tests added + + + + + +Before declaring plan complete: +- [ ] npm test passes for all new test files +- [ ] Mode tests cover execute, discuss, breakdown +- [ ] Repository tests cover CRUD operations +- [ ] All existing tests still pass + + + + +- All tasks completed +- MockAgentManager modes tested +- Initiative repository CRUD tested +- Phase repository CRUD tested +- getNextNumber edge cases tested + + + +After completion, create `.planning/phases/11-architect-agent/11-07-SUMMARY.md` + diff --git a/.planning/phases/11-architect-agent/11-08-PLAN.md b/.planning/phases/11-architect-agent/11-08-PLAN.md new file mode 100644 index 0000000..cc9a407 --- /dev/null +++ b/.planning/phases/11-architect-agent/11-08-PLAN.md @@ -0,0 +1,361 @@ +--- +phase: 11-architect-agent +plan: 08 +type: execute +wave: 4 +depends_on: ["11-05", "11-06", "11-07"] +files_modified: + - src/test/harness.ts + - src/test/e2e/architect-workflow.test.ts +autonomous: true +--- + + +Add TestHarness support for architect modes and E2E tests for the discuss→breakdown workflow. + +Purpose: Validate the complete architect workflow from discussion through phase creation. + +Output: E2E tests proving architect mode switching and phase persistence work. + + + +@~/.claude/get-shit-done/workflows/execute-plan.md +@~/.claude/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/STATE.md + +# Test infrastructure +@src/test/harness.ts +@src/test/e2e/recovery-scenarios.test.ts + +# Prior plan context +@.planning/phases/11-architect-agent/11-07-SUMMARY.md + + + + + + Task 1: Add TestHarness helpers for architect modes + src/test/harness.ts + +Add convenience methods for architect mode scenarios: + +```typescript +/** + * Set up scenario where architect completes discussion with decisions + */ +setArchitectDiscussComplete( + agentName: string, + decisions: Array<{ topic: string; decision: string; reason: string }>, + summary: string +): void { + this.mockAgentManager.setScenario(agentName, { + status: 'context_complete', + delay: 0, + decisions, + summary, + }); +} + +/** + * Set up scenario where architect needs more questions in discuss mode + */ +setArchitectDiscussQuestions( + agentName: string, + questions: Array<{ id: string; question: string; options?: Array<{ label: string }> }> +): void { + this.mockAgentManager.setScenario(agentName, { + status: 'questions', + delay: 0, + questions, + }); +} + +/** + * Set up scenario where architect completes breakdown with phases + */ +setArchitectBreakdownComplete( + agentName: string, + phases: Array<{ number: number; name: string; description: string }> +): void { + this.mockAgentManager.setScenario(agentName, { + status: 'breakdown_complete', + delay: 0, + phases, + }); +} + +/** + * Get initiative by ID through tRPC + */ +async getInitiative(id: string): Promise { + try { + return await this.caller.getInitiative({ id }); + } catch { + return null; + } +} + +/** + * Get phases for initiative through tRPC + */ +async getPhases(initiativeId: string): Promise { + return this.caller.listPhases({ initiativeId }); +} + +/** + * Create initiative through tRPC + */ +async createInitiative(name: string, description?: string): Promise { + return this.caller.createInitiative({ name, description }); +} + +/** + * Create phases from breakdown output through tRPC + */ +async createPhasesFromBreakdown( + initiativeId: string, + phases: Array<{ number: number; name: string; description: string }> +): Promise { + return this.caller.createPhasesFromBreakdown({ initiativeId, phases }); +} +``` + +Update imports as needed for Initiative and Phase types. + + npm run build passes + TestHarness has architect mode helpers + + + + Task 2: Add E2E test for discuss mode + src/test/e2e/architect-workflow.test.ts + +Create new E2E test file: + +```typescript +import { describe, it, expect, beforeEach, afterEach } from 'vitest'; +import { TestHarness } from '../harness.js'; + +describe('Architect Workflow E2E', () => { + let harness: TestHarness; + + beforeEach(async () => { + harness = await TestHarness.create(); + }); + + afterEach(async () => { + await harness.cleanup(); + }); + + describe('discuss mode', () => { + it('should spawn architect in discuss mode and complete with decisions', async () => { + // Create initiative + const initiative = await harness.createInitiative('Auth System', 'User authentication'); + + // Set up discuss completion scenario + harness.setArchitectDiscussComplete('auth-discuss', [ + { topic: 'Auth Method', decision: 'JWT', reason: 'Stateless, scalable' }, + { topic: 'Token Storage', decision: 'httpOnly cookie', reason: 'XSS protection' }, + ], 'Auth approach decided'); + + // Spawn architect in discuss mode + const agent = await harness.caller.spawnArchitectDiscuss({ + name: 'auth-discuss', + initiativeId: initiative.id, + }); + + expect(agent.mode).toBe('discuss'); + + // Wait for completion + await harness.advanceTimers(); + + // Verify agent stopped with context_complete + const events = harness.getEmittedEvents('agent:stopped'); + expect(events).toHaveLength(1); + expect(events[0].payload.reason).toBe('context_complete'); + }); + + it('should pause on questions and resume with answers', async () => { + const initiative = await harness.createInitiative('Auth System'); + + // First, agent asks questions + harness.setArchitectDiscussQuestions('auth-discuss', [ + { id: 'q1', question: 'JWT or Session?', options: [{ label: 'JWT' }, { label: 'Session' }] }, + { id: 'q2', question: 'OAuth providers?' }, + ]); + + const agent = await harness.caller.spawnArchitectDiscuss({ + name: 'auth-discuss', + initiativeId: initiative.id, + }); + + await harness.advanceTimers(); + + // Agent should be waiting + const waitingAgent = await harness.caller.getAgent({ name: 'auth-discuss' }); + expect(waitingAgent?.status).toBe('waiting_for_input'); + + // Get pending questions + const pending = await harness.mockAgentManager.getPendingQuestions(agent.id); + expect(pending?.questions).toHaveLength(2); + + // Now set up completion scenario for after resume + harness.setArchitectDiscussComplete('auth-discuss', [ + { topic: 'Auth', decision: 'JWT', reason: 'User chose' }, + ], 'Complete'); + + // Resume with answers + await harness.caller.resumeAgent({ + name: 'auth-discuss', + answers: { q1: 'JWT', q2: 'Google, GitHub' }, + }); + + await harness.advanceTimers(); + + // Should complete + const finalAgent = await harness.caller.getAgent({ name: 'auth-discuss' }); + expect(finalAgent?.status).toBe('idle'); + }); + }); +}); +``` + + npm test src/test/e2e/architect-workflow.test.ts passes + Discuss mode E2E tests added + + + + Task 3: Add E2E test for breakdown mode and phase persistence + src/test/e2e/architect-workflow.test.ts + +Add breakdown tests to the same file: + +```typescript +describe('breakdown mode', () => { + it('should spawn architect in breakdown mode and create phases', async () => { + const initiative = await harness.createInitiative('Auth System'); + + // Set up breakdown completion + harness.setArchitectBreakdownComplete('auth-breakdown', [ + { number: 1, name: 'Database Setup', description: 'User table and auth schema' }, + { number: 2, name: 'JWT Implementation', description: 'Token generation and validation' }, + { number: 3, name: 'Protected Routes', description: 'Middleware and route guards' }, + ]); + + const agent = await harness.caller.spawnArchitectBreakdown({ + name: 'auth-breakdown', + initiativeId: initiative.id, + }); + + expect(agent.mode).toBe('breakdown'); + + await harness.advanceTimers(); + + // Verify stopped with breakdown_complete + const events = harness.getEmittedEvents('agent:stopped'); + expect(events).toHaveLength(1); + expect(events[0].payload.reason).toBe('breakdown_complete'); + }); + + it('should persist phases from breakdown output', async () => { + const initiative = await harness.createInitiative('Auth System'); + + const phasesData = [ + { number: 1, name: 'Foundation', description: 'Core setup' }, + { number: 2, name: 'Features', description: 'Main features' }, + ]; + + // Persist phases (simulating what would happen after breakdown) + const created = await harness.createPhasesFromBreakdown(initiative.id, phasesData); + + expect(created).toHaveLength(2); + expect(created[0].number).toBe(1); + expect(created[1].number).toBe(2); + + // Verify retrieval + const phases = await harness.getPhases(initiative.id); + expect(phases).toHaveLength(2); + expect(phases[0].name).toBe('Foundation'); + expect(phases[1].name).toBe('Features'); + }); +}); + +describe('full workflow', () => { + it('should complete discuss → breakdown → phases workflow', async () => { + // 1. Create initiative + const initiative = await harness.createInitiative('Full Workflow Test'); + + // 2. Discuss phase + harness.setArchitectDiscussComplete('discuss-agent', [ + { topic: 'Scope', decision: 'MVP only', reason: 'Time constraint' }, + ], 'Scope defined'); + + await harness.caller.spawnArchitectDiscuss({ + name: 'discuss-agent', + initiativeId: initiative.id, + }); + await harness.advanceTimers(); + + // 3. Breakdown phase + harness.setArchitectBreakdownComplete('breakdown-agent', [ + { number: 1, name: 'Core', description: 'Core functionality' }, + { number: 2, name: 'Polish', description: 'UI and UX' }, + ]); + + await harness.caller.spawnArchitectBreakdown({ + name: 'breakdown-agent', + initiativeId: initiative.id, + contextSummary: 'MVP scope defined', + }); + await harness.advanceTimers(); + + // 4. Persist phases + await harness.createPhasesFromBreakdown(initiative.id, [ + { number: 1, name: 'Core', description: 'Core functionality' }, + { number: 2, name: 'Polish', description: 'UI and UX' }, + ]); + + // 5. Verify final state + const phases = await harness.getPhases(initiative.id); + expect(phases).toHaveLength(2); + + // Both agents should be idle + const agents = await harness.caller.listAgents(); + expect(agents.filter(a => a.status === 'idle')).toHaveLength(2); + }); +}); +``` + + npm test src/test/e2e/architect-workflow.test.ts passes + Full architect workflow E2E tests added + + + + + +Before declaring plan complete: +- [ ] npm run build succeeds +- [ ] npm test passes all tests including new E2E tests +- [ ] Discuss mode completion tested +- [ ] Discuss Q&A flow tested +- [ ] Breakdown mode completion tested +- [ ] Phase persistence tested +- [ ] Full workflow tested + + + + +- All tasks completed +- TestHarness has architect-specific helpers +- E2E tests cover discuss → breakdown → persist workflow +- All existing tests still pass + + + +After completion, create `.planning/phases/11-architect-agent/11-08-SUMMARY.md` +