docs(11): create phase plan

Phase 11: Architect Agent
- 8 plans in 4 waves
- 6 parallel (Wave 1-2), 2 sequential (Wave 3-4)
- Ready for execution
This commit is contained in:
Lukas May
2026-01-31 18:21:39 +01:00
parent 8c93ce24c9
commit 934373bf68
9 changed files with 1931 additions and 3 deletions

View File

@@ -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 | - |

View File

@@ -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
---
<objective>
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.
</objective>
<execution_context>
@~/.claude/get-shit-done/workflows/execute-plan.md
@~/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.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
</context>
<tasks>
<task type="auto">
<name>Task 1: Add AgentMode type and database column</name>
<files>src/agent/types.ts, src/db/schema.ts</files>
<action>
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;
```
</action>
<verify>npm run build passes with no type errors</verify>
<done>AgentMode type exists, database schema has mode column, types updated</done>
</task>
<task type="auto">
<name>Task 2: Create mode-specific output schemas</name>
<files>src/agent/schema.ts</files>
<action>
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).
</action>
<verify>npm run build passes, new schemas are exported</verify>
<done>Three mode-specific output schemas exist with JSON schema versions</done>
</task>
<task type="auto">
<name>Task 3: Update MockAgentManager for mode support</name>
<files>src/agent/mock-manager.ts</files>
<action>
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.
</action>
<verify>npm test passes for mock-manager.test.ts</verify>
<done>MockAgentManager supports all three modes with proper status handling</done>
</task>
</tasks>
<verification>
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
</verification>
<success_criteria>
- 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
</success_criteria>
<output>
After completion, create `.planning/phases/11-architect-agent/11-01-SUMMARY.md`
</output>

View File

@@ -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
---
<objective>
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.
</objective>
<execution_context>
@~/.claude/get-shit-done/workflows/execute-plan.md
@~/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.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
</context>
<tasks>
<task type="auto">
<name>Task 1: Create InitiativeRepository port and adapter</name>
<files>src/db/repositories/initiative-repository.ts, src/db/repositories/drizzle-initiative-repository.ts</files>
<action>
1. Create src/db/repositories/initiative-repository.ts (PORT):
```typescript
import type { Initiative, NewInitiative } from '../schema.js';
export type CreateInitiativeData = Omit<NewInitiative, 'id' | 'createdAt' | 'updatedAt'>;
export type UpdateInitiativeData = Partial<CreateInitiativeData>;
export interface InitiativeRepository {
create(data: CreateInitiativeData): Promise<Initiative>;
findById(id: string): Promise<Initiative | null>;
findAll(): Promise<Initiative[]>;
findByStatus(status: 'active' | 'completed' | 'archived'): Promise<Initiative[]>;
update(id: string, data: UpdateInitiativeData): Promise<Initiative>;
delete(id: string): Promise<void>;
}
```
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
</action>
<verify>npm run build passes</verify>
<done>InitiativeRepository port and Drizzle adapter created</done>
</task>
<task type="auto">
<name>Task 2: Create PhaseRepository port and adapter</name>
<files>src/db/repositories/phase-repository.ts, src/db/repositories/drizzle-phase-repository.ts</files>
<action>
1. Create src/db/repositories/phase-repository.ts (PORT):
```typescript
import type { Phase, NewPhase } from '../schema.js';
export type CreatePhaseData = Omit<NewPhase, 'id' | 'createdAt' | 'updatedAt'>;
export type UpdatePhaseData = Partial<CreatePhaseData>;
export interface PhaseRepository {
create(data: CreatePhaseData): Promise<Phase>;
findById(id: string): Promise<Phase | null>;
findByInitiativeId(initiativeId: string): Promise<Phase[]>;
findByNumber(initiativeId: string, number: number): Promise<Phase | null>;
update(id: string, data: UpdatePhaseData): Promise<Phase>;
delete(id: string): Promise<void>;
getNextNumber(initiativeId: string): Promise<number>;
}
```
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
</action>
<verify>npm run build passes</verify>
<done>PhaseRepository port and Drizzle adapter created</done>
</task>
<task type="auto">
<name>Task 3: Export repositories from index files</name>
<files>src/db/repositories/index.ts, src/db/index.ts</files>
<action>
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.
</action>
<verify>npm run build passes, repositories are importable from src/db</verify>
<done>All repositories exported and importable</done>
</task>
</tasks>
<verification>
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)
</verification>
<success_criteria>
- All tasks completed
- Both repositories have port interfaces and Drizzle adapters
- Pattern matches existing TaskRepository implementation
- TypeScript types are correct
</success_criteria>
<output>
After completion, create `.planning/phases/11-architect-agent/11-02-SUMMARY.md`
</output>

View File

@@ -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
---
<objective>
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.
</objective>
<execution_context>
@~/.claude/get-shit-done/workflows/execute-plan.md
@~/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.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
</context>
<tasks>
<task type="auto">
<name>Task 1: Update AgentRepository for mode field</name>
<files>src/db/repositories/agent-repository.ts, src/db/repositories/drizzle-agent-repository.ts</files>
<action>
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
</action>
<verify>npm run build passes</verify>
<done>AgentRepository handles mode field with 'execute' default</done>
</task>
<task type="auto">
<name>Task 2: Update ClaudeAgentManager for mode-specific schemas</name>
<files>src/agent/manager.ts</files>
<action>
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.
</action>
<verify>npm run build passes</verify>
<done>ClaudeAgentManager spawns agents with mode-specific JSON schemas</done>
</task>
<task type="auto">
<name>Task 3: Add mode to AgentStoppedEvent reasons</name>
<files>src/events/types.ts</files>
<action>
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.
</action>
<verify>npm run build passes, existing tests pass</verify>
<done>AgentStoppedEvent supports mode-specific completion reasons</done>
</task>
</tasks>
<verification>
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)
</verification>
<success_criteria>
- 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)
</success_criteria>
<output>
After completion, create `.planning/phases/11-architect-agent/11-03-SUMMARY.md`
</output>

View File

@@ -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
---
<objective>
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.
</objective>
<execution_context>
@~/.claude/get-shit-done/workflows/execute-plan.md
@~/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.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
</context>
<tasks>
<task type="auto">
<name>Task 1: Add repositories to tRPC context</name>
<files>src/trpc/context.ts</files>
<action>
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.
</action>
<verify>npm run build passes</verify>
<done>Context includes optional initiative and phase repositories</done>
</task>
<task type="auto">
<name>Task 2: Add Initiative tRPC procedures</name>
<files>src/trpc/router.ts</files>
<action>
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);
}),
```
</action>
<verify>npm run build passes</verify>
<done>Initiative CRUD procedures added to router</done>
</task>
<task type="auto">
<name>Task 3: Add Phase tRPC procedures</name>
<files>src/trpc/router.ts</files>
<action>
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;
}),
```
</action>
<verify>npm run build passes</verify>
<done>Phase CRUD procedures added to router including bulk create</done>
</task>
</tasks>
<verification>
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)
</verification>
<success_criteria>
- All tasks completed
- Initiative and Phase CRUD available via tRPC
- Bulk phase creation supports Architect breakdown output
- Type-safe input/output validation
</success_criteria>
<output>
After completion, create `.planning/phases/11-architect-agent/11-04-SUMMARY.md`
</output>

View File

@@ -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
---
<objective>
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.
</objective>
<execution_context>
@~/.claude/get-shit-done/workflows/execute-plan.md
@~/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.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
</context>
<tasks>
<task type="auto">
<name>Task 1: Update spawnAgent to support mode</name>
<files>src/trpc/router.ts</files>
<action>
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
});
}),
```
</action>
<verify>npm run build passes</verify>
<done>spawnAgent procedure accepts mode parameter</done>
</task>
<task type="auto">
<name>Task 2: Add spawnArchitect convenience procedures</name>
<files>src/trpc/router.ts</files>
<action>
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.`;
}
```
</action>
<verify>npm run build passes</verify>
<done>Architect spawn procedures with mode-specific prompts added</done>
</task>
</tasks>
<verification>
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
</verification>
<success_criteria>
- All tasks completed
- Architect workflow has dedicated spawn procedures
- Mode-specific prompts guide agent behavior
- Initiative validation prevents orphaned agents
</success_criteria>
<output>
After completion, create `.planning/phases/11-architect-agent/11-05-SUMMARY.md`
</output>

View File

@@ -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
---
<objective>
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.
</objective>
<execution_context>
@~/.claude/get-shit-done/workflows/execute-plan.md
@~/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.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
</context>
<tasks>
<task type="auto">
<name>Task 1: Add initiative CLI commands</name>
<files>src/cli/index.ts</files>
<action>
Add initiative command group following existing patterns (agent commands):
```typescript
// cw initiative
const initiativeCommand = program
.command('initiative')
.description('Manage initiatives');
// cw initiative create <name>
initiativeCommand
.command('create <name>')
.description('Create a new initiative')
.option('-d, --description <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 <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 <id>
initiativeCommand
.command('get <id>')
.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);
}
});
```
</action>
<verify>npm run build passes, cw initiative --help shows commands</verify>
<done>Initiative CLI commands added (create, list, get)</done>
</task>
<task type="auto">
<name>Task 2: Add architect CLI commands</name>
<files>src/cli/index.ts</files>
<action>
Add architect command group:
```typescript
// cw architect
const architectCommand = program
.command('architect')
.description('Architect agent workflow');
// cw architect discuss <initiative-id>
architectCommand
.command('discuss <initiativeId>')
.description('Start discussion phase for an initiative')
.requiredOption('--name <name>', 'Agent name')
.option('-c, --context <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 <initiative-id>
architectCommand
.command('breakdown <initiativeId>')
.description('Start breakdown phase for an initiative')
.requiredOption('--name <name>', 'Agent name')
.option('-s, --summary <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);
}
});
```
</action>
<verify>npm run build passes, cw architect --help shows commands</verify>
<done>Architect CLI commands added (discuss, breakdown)</done>
</task>
<task type="auto">
<name>Task 3: Add phase list command</name>
<files>src/cli/index.ts</files>
<action>
Add phase listing under initiative or as separate command:
```typescript
// cw initiative phases <initiative-id>
initiativeCommand
.command('phases <initiativeId>')
.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);
}
});
```
</action>
<verify>npm run build passes</verify>
<done>Phase listing command added</done>
</task>
</tasks>
<verification>
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)
</verification>
<success_criteria>
- All tasks completed
- Initiative CRUD available via CLI
- Architect workflow accessible via CLI
- Phase listing works for initiatives
</success_criteria>
<output>
After completion, create `.planning/phases/11-architect-agent/11-06-SUMMARY.md`
</output>

View File

@@ -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
---
<objective>
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.
</objective>
<execution_context>
@~/.claude/get-shit-done/workflows/execute-plan.md
@~/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.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
</context>
<tasks>
<task type="auto">
<name>Task 1: Add MockAgentManager mode tests</name>
<files>src/agent/mock-manager.test.ts</files>
<action>
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');
});
});
```
</action>
<verify>npm test src/agent/mock-manager.test.ts passes</verify>
<done>Mode tests added for MockAgentManager</done>
</task>
<task type="auto">
<name>Task 2: Add InitiativeRepository tests</name>
<files>src/db/repositories/initiative-repository.test.ts</files>
<action>
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<void>;
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.
</action>
<verify>npm test src/db/repositories/initiative-repository.test.ts passes</verify>
<done>Initiative repository tests added</done>
</task>
<task type="auto">
<name>Task 3: Add PhaseRepository tests</name>
<files>src/db/repositories/phase-repository.test.ts</files>
<action>
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<void>;
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');
});
});
});
```
</action>
<verify>npm test src/db/repositories/phase-repository.test.ts passes</verify>
<done>Phase repository tests added</done>
</task>
</tasks>
<verification>
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
</verification>
<success_criteria>
- All tasks completed
- MockAgentManager modes tested
- Initiative repository CRUD tested
- Phase repository CRUD tested
- getNextNumber edge cases tested
</success_criteria>
<output>
After completion, create `.planning/phases/11-architect-agent/11-07-SUMMARY.md`
</output>

View File

@@ -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
---
<objective>
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.
</objective>
<execution_context>
@~/.claude/get-shit-done/workflows/execute-plan.md
@~/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.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
</context>
<tasks>
<task type="auto">
<name>Task 1: Add TestHarness helpers for architect modes</name>
<files>src/test/harness.ts</files>
<action>
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<Initiative | null> {
try {
return await this.caller.getInitiative({ id });
} catch {
return null;
}
}
/**
* Get phases for initiative through tRPC
*/
async getPhases(initiativeId: string): Promise<Phase[]> {
return this.caller.listPhases({ initiativeId });
}
/**
* Create initiative through tRPC
*/
async createInitiative(name: string, description?: string): Promise<Initiative> {
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<Phase[]> {
return this.caller.createPhasesFromBreakdown({ initiativeId, phases });
}
```
Update imports as needed for Initiative and Phase types.
</action>
<verify>npm run build passes</verify>
<done>TestHarness has architect mode helpers</done>
</task>
<task type="auto">
<name>Task 2: Add E2E test for discuss mode</name>
<files>src/test/e2e/architect-workflow.test.ts</files>
<action>
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');
});
});
});
```
</action>
<verify>npm test src/test/e2e/architect-workflow.test.ts passes</verify>
<done>Discuss mode E2E tests added</done>
</task>
<task type="auto">
<name>Task 3: Add E2E test for breakdown mode and phase persistence</name>
<files>src/test/e2e/architect-workflow.test.ts</files>
<action>
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);
});
});
```
</action>
<verify>npm test src/test/e2e/architect-workflow.test.ts passes</verify>
<done>Full architect workflow E2E tests added</done>
</task>
</tasks>
<verification>
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
</verification>
<success_criteria>
- All tasks completed
- TestHarness has architect-specific helpers
- E2E tests cover discuss → breakdown → persist workflow
- All existing tests still pass
</success_criteria>
<output>
After completion, create `.planning/phases/11-architect-agent/11-08-SUMMARY.md`
</output>