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:
@@ -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 | - |
|
||||
|
||||
|
||||
187
.planning/phases/11-architect-agent/11-01-PLAN.md
Normal file
187
.planning/phases/11-architect-agent/11-01-PLAN.md
Normal 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>
|
||||
146
.planning/phases/11-architect-agent/11-02-PLAN.md
Normal file
146
.planning/phases/11-architect-agent/11-02-PLAN.md
Normal 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>
|
||||
161
.planning/phases/11-architect-agent/11-03-PLAN.md
Normal file
161
.planning/phases/11-architect-agent/11-03-PLAN.md
Normal 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>
|
||||
271
.planning/phases/11-architect-agent/11-04-PLAN.md
Normal file
271
.planning/phases/11-architect-agent/11-04-PLAN.md
Normal 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>
|
||||
205
.planning/phases/11-architect-agent/11-05-PLAN.md
Normal file
205
.planning/phases/11-architect-agent/11-05-PLAN.md
Normal 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>
|
||||
240
.planning/phases/11-architect-agent/11-06-PLAN.md
Normal file
240
.planning/phases/11-architect-agent/11-06-PLAN.md
Normal 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>
|
||||
350
.planning/phases/11-architect-agent/11-07-PLAN.md
Normal file
350
.planning/phases/11-architect-agent/11-07-PLAN.md
Normal 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>
|
||||
361
.planning/phases/11-architect-agent/11-08-PLAN.md
Normal file
361
.planning/phases/11-architect-agent/11-08-PLAN.md
Normal 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>
|
||||
Reference in New Issue
Block a user