Containerize Codewalkers with a multi-stage Docker build (Node + Caddy) and add a seed script that populates the database with a demo initiative, 3 phases, 9 tasks, 3 agents with JSONL log output, a root page, review comments, and a git repo with real branch diffs for the review tab.
483 lines
18 KiB
TypeScript
483 lines
18 KiB
TypeScript
/**
|
|
* Preview Seed Script
|
|
*
|
|
* Populates the database with demo data for preview deployments.
|
|
* Run with: CW_DB_PATH=/workspace/.cw/cw.db node /app/apps/server/dist/preview-seed.js
|
|
*
|
|
* Prints the project ID to stdout so the calling shell script
|
|
* can derive the clone path.
|
|
*/
|
|
|
|
import { createDatabase, ensureSchema } from './db/index.js';
|
|
import { createRepositories } from './container.js';
|
|
|
|
const dbPath = process.env.CW_DB_PATH;
|
|
if (!dbPath) {
|
|
console.error('CW_DB_PATH environment variable is required');
|
|
process.exit(1);
|
|
}
|
|
|
|
const db = createDatabase(dbPath);
|
|
ensureSchema(db);
|
|
const repos = createRepositories(db);
|
|
|
|
// ─── Project ───
|
|
|
|
const project = await repos.projectRepository.create({
|
|
name: 'demo-app',
|
|
url: 'file:///workspace/demo-repo.git',
|
|
defaultBranch: 'main',
|
|
});
|
|
|
|
// ─── Initiative ───
|
|
|
|
const initiative = await repos.initiativeRepository.create({
|
|
name: 'Task Manager Redesign',
|
|
status: 'active',
|
|
branch: 'cw/task-manager-redesign',
|
|
executionMode: 'review_per_phase',
|
|
});
|
|
|
|
await repos.projectRepository.setInitiativeProjects(initiative.id, [project.id]);
|
|
|
|
// ─── Phases ───
|
|
|
|
const phase1 = await repos.phaseRepository.create({
|
|
initiativeId: initiative.id,
|
|
name: 'Backend API',
|
|
status: 'completed',
|
|
});
|
|
|
|
const phase2 = await repos.phaseRepository.create({
|
|
initiativeId: initiative.id,
|
|
name: 'UI Overhaul',
|
|
status: 'pending_review',
|
|
});
|
|
|
|
const phase3 = await repos.phaseRepository.create({
|
|
initiativeId: initiative.id,
|
|
name: 'Testing & Polish',
|
|
status: 'in_progress',
|
|
});
|
|
|
|
// ─── Tasks ───
|
|
|
|
// Phase 1: Backend API (all completed)
|
|
const task1a = await repos.taskRepository.create({
|
|
phaseId: phase1.id,
|
|
initiativeId: initiative.id,
|
|
name: 'Research REST vs GraphQL patterns',
|
|
category: 'research',
|
|
status: 'completed',
|
|
description: 'Evaluate REST and GraphQL approaches for the task management API. Consider developer experience, caching, and real-time requirements.',
|
|
order: 0,
|
|
summary: 'Recommended REST with WebSocket subscriptions for real-time. GraphQL adds complexity without clear benefit for this domain.',
|
|
});
|
|
|
|
const task1b = await repos.taskRepository.create({
|
|
phaseId: phase1.id,
|
|
initiativeId: initiative.id,
|
|
name: 'Implement task CRUD endpoints',
|
|
category: 'execute',
|
|
status: 'completed',
|
|
description: 'Build the core CRUD API for tasks with proper validation and error handling.',
|
|
order: 1,
|
|
summary: 'Implemented GET/POST/PUT/DELETE /api/tasks with Zod validation, proper HTTP status codes, and pagination support.',
|
|
});
|
|
|
|
const task1c = await repos.taskRepository.create({
|
|
phaseId: phase1.id,
|
|
initiativeId: initiative.id,
|
|
name: 'Add input validation middleware',
|
|
category: 'execute',
|
|
status: 'completed',
|
|
description: 'Create reusable validation middleware using Zod schemas for all API endpoints.',
|
|
order: 2,
|
|
summary: 'Added validateBody() and validateQuery() middleware with typed error responses.',
|
|
});
|
|
|
|
// Phase 2: UI Overhaul (all completed)
|
|
const task2a = await repos.taskRepository.create({
|
|
phaseId: phase2.id,
|
|
initiativeId: initiative.id,
|
|
name: 'Design component hierarchy',
|
|
category: 'plan',
|
|
status: 'completed',
|
|
description: 'Plan the component architecture for the redesigned UI, focusing on reusability and maintainability.',
|
|
order: 0,
|
|
summary: 'Designed 3-level hierarchy: Layout (Header, Sidebar) → Views (TaskList, TaskDetail) → Primitives (StatusBadge, PriorityTag).',
|
|
});
|
|
|
|
const task2b = await repos.taskRepository.create({
|
|
phaseId: phase2.id,
|
|
initiativeId: initiative.id,
|
|
name: 'Implement responsive header',
|
|
category: 'execute',
|
|
status: 'completed',
|
|
description: 'Build the responsive header component with search functionality and notification bell.',
|
|
order: 1,
|
|
summary: 'Created Header.tsx with search input, notification dropdown (3 mock items), and user avatar. Fully responsive with mobile hamburger menu.',
|
|
});
|
|
|
|
const task2c = await repos.taskRepository.create({
|
|
phaseId: phase2.id,
|
|
initiativeId: initiative.id,
|
|
name: 'Refactor task list with filters',
|
|
category: 'execute',
|
|
status: 'completed',
|
|
description: 'Rewrite the task list component with status icons, priority badges, and filter controls.',
|
|
order: 2,
|
|
summary: 'Refactored TaskList.tsx with table layout, status icons, priority badges, assignee avatars, and due dates. Added TaskFilters.tsx with status/priority filter buttons.',
|
|
});
|
|
|
|
// Phase 3: Testing & Polish (mixed statuses)
|
|
const task3a = await repos.taskRepository.create({
|
|
phaseId: phase3.id,
|
|
initiativeId: initiative.id,
|
|
name: 'Plan test strategy',
|
|
category: 'plan',
|
|
status: 'completed',
|
|
description: 'Define the testing strategy: unit tests, integration tests, and E2E test coverage targets.',
|
|
order: 0,
|
|
summary: 'Defined 3-tier strategy: Vitest unit tests (80% coverage), API integration tests with supertest, and Playwright E2E for critical flows.',
|
|
});
|
|
|
|
const task3b = await repos.taskRepository.create({
|
|
phaseId: phase3.id,
|
|
initiativeId: initiative.id,
|
|
name: 'Write integration tests',
|
|
category: 'execute',
|
|
status: 'in_progress',
|
|
description: 'Write integration tests for all API endpoints using supertest.',
|
|
order: 1,
|
|
});
|
|
|
|
const task3c = await repos.taskRepository.create({
|
|
phaseId: phase3.id,
|
|
initiativeId: initiative.id,
|
|
name: 'Add error handling',
|
|
category: 'execute',
|
|
status: 'pending',
|
|
description: 'Add global error boundary, API error toasts, and retry logic for failed requests.',
|
|
order: 2,
|
|
});
|
|
|
|
// ─── Agents ───
|
|
|
|
const agent1 = await repos.agentRepository.create({
|
|
name: 'keen-falcon',
|
|
worktreeId: 'worktrees/keen-falcon',
|
|
taskId: task1b.id,
|
|
initiativeId: initiative.id,
|
|
status: 'stopped',
|
|
mode: 'execute',
|
|
provider: 'claude',
|
|
});
|
|
|
|
const agent2 = await repos.agentRepository.create({
|
|
name: 'swift-otter',
|
|
worktreeId: 'worktrees/swift-otter',
|
|
taskId: task2c.id,
|
|
initiativeId: initiative.id,
|
|
status: 'stopped',
|
|
mode: 'execute',
|
|
provider: 'claude',
|
|
});
|
|
|
|
const agent3 = await repos.agentRepository.create({
|
|
name: 'bright-heron',
|
|
worktreeId: 'worktrees/bright-heron',
|
|
taskId: task3b.id,
|
|
initiativeId: initiative.id,
|
|
status: 'running',
|
|
mode: 'execute',
|
|
provider: 'claude',
|
|
});
|
|
|
|
// ─── Agent Log Chunks ───
|
|
|
|
// Agent 1: keen-falcon (completed backend work)
|
|
await repos.logChunkRepository.insertChunk({
|
|
agentId: agent1.id,
|
|
agentName: 'keen-falcon',
|
|
sessionNumber: 1,
|
|
content: [
|
|
JSON.stringify({ type: 'system', session_id: 'session-kf-001' }),
|
|
JSON.stringify({
|
|
type: 'assistant',
|
|
message: {
|
|
content: [
|
|
{ type: 'text', text: "I'll start by reading the existing API structure to understand the current patterns." },
|
|
{ type: 'tool_use', id: 'toolu_kf1', name: 'Read', input: { file_path: '/app/src/lib/api.ts' } },
|
|
],
|
|
},
|
|
}),
|
|
JSON.stringify({
|
|
type: 'user',
|
|
message: { content: [{ type: 'tool_result', content: "const API_URL = '/api';\n\nexport async function fetchTasks(filter: string) {\n const res = await fetch(`${API_URL}/tasks?filter=${filter}`);\n return res.json();\n}" }] },
|
|
tool_use_result: { stdout: "const API_URL = '/api';\n\nexport async function fetchTasks(filter: string) {\n const res = await fetch(`${API_URL}/tasks?filter=${filter}`);\n return res.json();\n}" },
|
|
}),
|
|
JSON.stringify({
|
|
type: 'assistant',
|
|
message: {
|
|
content: [
|
|
{ type: 'text', text: "Now I'll implement the task CRUD endpoints with proper validation. I'll create a structured API client with error handling." },
|
|
{ type: 'tool_use', id: 'toolu_kf2', name: 'Write', input: { file_path: '/app/src/server/routes/tasks.ts', content: '// CRUD endpoints implementation...' } },
|
|
],
|
|
},
|
|
}),
|
|
JSON.stringify({
|
|
type: 'user',
|
|
message: { content: [{ type: 'tool_result', content: 'File written successfully' }] },
|
|
tool_use_result: { stdout: 'File written successfully' },
|
|
}),
|
|
JSON.stringify({
|
|
type: 'assistant',
|
|
message: {
|
|
content: [
|
|
{ type: 'text', text: "Let me also add the Zod validation schemas and middleware." },
|
|
{ type: 'tool_use', id: 'toolu_kf3', name: 'Write', input: { file_path: '/app/src/server/middleware/validate.ts', content: '// Validation middleware...' } },
|
|
],
|
|
},
|
|
}),
|
|
JSON.stringify({
|
|
type: 'user',
|
|
message: { content: [{ type: 'tool_result', content: 'File written successfully' }] },
|
|
tool_use_result: { stdout: 'File written successfully' },
|
|
}),
|
|
JSON.stringify({ type: 'result', is_error: false, total_cost_usd: 0.0847, duration_ms: 32000 }),
|
|
].join('\n'),
|
|
});
|
|
|
|
// Agent 2: swift-otter (completed UI overhaul)
|
|
await repos.logChunkRepository.insertChunk({
|
|
agentId: agent2.id,
|
|
agentName: 'swift-otter',
|
|
sessionNumber: 1,
|
|
content: [
|
|
JSON.stringify({ type: 'system', session_id: 'session-so-001' }),
|
|
JSON.stringify({
|
|
type: 'assistant',
|
|
message: {
|
|
content: [
|
|
{ type: 'text', text: "I'll start by reading the current TaskList component to understand what needs to be refactored." },
|
|
{ type: 'tool_use', id: 'toolu_so1', name: 'Read', input: { file_path: '/app/src/components/TaskList.tsx' } },
|
|
],
|
|
},
|
|
}),
|
|
JSON.stringify({
|
|
type: 'user',
|
|
message: {
|
|
content: [{
|
|
type: 'tool_result',
|
|
content: "import { useTasks } from '../hooks/useTasks';\n\nexport function TaskList({ filter }) {\n const { tasks, loading } = useTasks(filter);\n if (loading) return <div>Loading...</div>;\n return (\n <ul className=\"task-list\">\n {tasks.map(task => (\n <li key={task.id}>\n <span>{task.title}</span>\n <span className=\"status\">{task.status}</span>\n </li>\n ))}\n </ul>\n );\n}",
|
|
}],
|
|
},
|
|
tool_use_result: { stdout: "import { useTasks } from '../hooks/useTasks';\n..." },
|
|
}),
|
|
JSON.stringify({
|
|
type: 'assistant',
|
|
message: {
|
|
content: [
|
|
{ type: 'text', text: "The current TaskList is very basic — just a plain unordered list with no visual hierarchy. I'll refactor it into a proper table with status icons, priority badges, assignee avatars, and a loading skeleton." },
|
|
{
|
|
type: 'tool_use', id: 'toolu_so2', name: 'Edit',
|
|
input: {
|
|
file_path: '/app/src/components/TaskList.tsx',
|
|
old_string: "import { useTasks } from '../hooks/useTasks';",
|
|
new_string: "import { useTasks } from '../hooks/useTasks';\n\ninterface Task {\n id: string;\n title: string;\n status: 'active' | 'completed' | 'blocked';\n priority: 'high' | 'medium' | 'low';\n assignee?: string;\n}",
|
|
},
|
|
},
|
|
],
|
|
},
|
|
}),
|
|
JSON.stringify({
|
|
type: 'user',
|
|
message: { content: [{ type: 'tool_result', content: 'File edited successfully' }] },
|
|
tool_use_result: { stdout: 'File edited successfully' },
|
|
}),
|
|
JSON.stringify({
|
|
type: 'assistant',
|
|
message: {
|
|
content: [
|
|
{ type: 'text', text: "Now I'll create the TaskFilters component for status and priority filtering." },
|
|
{ type: 'tool_use', id: 'toolu_so3', name: 'Write', input: { file_path: '/app/src/components/TaskFilters.tsx', content: '// TaskFilters component...' } },
|
|
],
|
|
},
|
|
}),
|
|
JSON.stringify({
|
|
type: 'user',
|
|
message: { content: [{ type: 'tool_result', content: 'File written successfully' }] },
|
|
tool_use_result: { stdout: 'File written successfully' },
|
|
}),
|
|
JSON.stringify({
|
|
type: 'assistant',
|
|
message: {
|
|
content: [
|
|
{ type: 'text', text: "Finally, let me refactor the API client to be type-safe with proper error handling." },
|
|
{ type: 'tool_use', id: 'toolu_so4', name: 'Edit', input: { file_path: '/app/src/lib/api.ts', old_string: "const API_URL = '/api';", new_string: 'class ApiClient {\n private baseUrl: string;\n constructor(baseUrl = \'/api\') { this.baseUrl = baseUrl; }' } },
|
|
],
|
|
},
|
|
}),
|
|
JSON.stringify({
|
|
type: 'user',
|
|
message: { content: [{ type: 'tool_result', content: 'File edited successfully' }] },
|
|
tool_use_result: { stdout: 'File edited successfully' },
|
|
}),
|
|
JSON.stringify({ type: 'result', is_error: false, total_cost_usd: 0.1234, duration_ms: 45000 }),
|
|
].join('\n'),
|
|
});
|
|
|
|
// Agent 3: bright-heron (in-progress, no result line)
|
|
await repos.logChunkRepository.insertChunk({
|
|
agentId: agent3.id,
|
|
agentName: 'bright-heron',
|
|
sessionNumber: 1,
|
|
content: [
|
|
JSON.stringify({ type: 'system', session_id: 'session-bh-001' }),
|
|
JSON.stringify({
|
|
type: 'assistant',
|
|
message: {
|
|
content: [
|
|
{ type: 'text', text: "I'll start writing integration tests for the task CRUD endpoints. Let me first check the existing test setup." },
|
|
{ type: 'tool_use', id: 'toolu_bh1', name: 'Bash', input: { command: 'ls src/test/', description: 'Check existing test files' } },
|
|
],
|
|
},
|
|
}),
|
|
JSON.stringify({
|
|
type: 'user',
|
|
message: { content: [{ type: 'tool_result', content: 'setup.ts\nfixtures/' }] },
|
|
tool_use_result: { stdout: 'setup.ts\nfixtures/' },
|
|
}),
|
|
JSON.stringify({
|
|
type: 'assistant',
|
|
message: {
|
|
content: [
|
|
{ type: 'text', text: "Good, there's a test setup file and fixtures directory. Let me read the setup to understand the test configuration." },
|
|
{ type: 'tool_use', id: 'toolu_bh2', name: 'Read', input: { file_path: '/app/src/test/setup.ts' } },
|
|
],
|
|
},
|
|
}),
|
|
JSON.stringify({
|
|
type: 'user',
|
|
message: { content: [{ type: 'tool_result', content: "import { beforeAll, afterAll } from 'vitest';\nimport { createTestServer } from './fixtures/server';\n\nlet server;\nbeforeAll(async () => { server = await createTestServer(); });\nafterAll(() => server?.close());" }] },
|
|
tool_use_result: { stdout: "import { beforeAll, afterAll } from 'vitest';\n..." },
|
|
}),
|
|
JSON.stringify({
|
|
type: 'assistant',
|
|
message: {
|
|
content: [
|
|
{ type: 'text', text: "Now I'll write the integration tests for the tasks API. Starting with the CRUD operations..." },
|
|
{ type: 'tool_use', id: 'toolu_bh3', name: 'Write', input: { file_path: '/app/src/test/tasks.test.ts', content: '// Integration tests...' } },
|
|
],
|
|
},
|
|
}),
|
|
JSON.stringify({
|
|
type: 'user',
|
|
message: { content: [{ type: 'tool_result', content: 'File written successfully' }] },
|
|
tool_use_result: { stdout: 'File written successfully' },
|
|
}),
|
|
].join('\n'),
|
|
});
|
|
|
|
// ─── Root Page ───
|
|
|
|
const pageContent = {
|
|
type: 'doc',
|
|
content: [
|
|
{
|
|
type: 'heading',
|
|
attrs: { level: 1 },
|
|
content: [{ type: 'text', text: 'Task Manager Redesign' }],
|
|
},
|
|
{
|
|
type: 'paragraph',
|
|
content: [
|
|
{
|
|
type: 'text',
|
|
text: 'A comprehensive redesign of the task management application, focusing on improved user experience, type safety, and modern UI patterns.',
|
|
},
|
|
],
|
|
},
|
|
{
|
|
type: 'heading',
|
|
attrs: { level: 2 },
|
|
content: [{ type: 'text', text: 'Goals' }],
|
|
},
|
|
{
|
|
type: 'bulletList',
|
|
content: [
|
|
{
|
|
type: 'listItem',
|
|
content: [{ type: 'paragraph', content: [{ type: 'text', text: 'Responsive layout with mobile-first design' }] }],
|
|
},
|
|
{
|
|
type: 'listItem',
|
|
content: [{ type: 'paragraph', content: [{ type: 'text', text: 'Type-safe API client with proper error handling' }] }],
|
|
},
|
|
{
|
|
type: 'listItem',
|
|
content: [{ type: 'paragraph', content: [{ type: 'text', text: 'Status icons and priority badges for visual clarity' }] }],
|
|
},
|
|
{
|
|
type: 'listItem',
|
|
content: [{ type: 'paragraph', content: [{ type: 'text', text: 'Comprehensive test coverage (unit, integration, E2E)' }] }],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
type: 'heading',
|
|
attrs: { level: 2 },
|
|
content: [{ type: 'text', text: 'Progress' }],
|
|
},
|
|
{
|
|
type: 'paragraph',
|
|
content: [
|
|
{
|
|
type: 'text',
|
|
text: 'Backend API is complete with full CRUD endpoints and validation middleware. The UI overhaul phase is in review — header, task list, and filters have been refactored. Testing phase is actively in progress with integration tests being written.',
|
|
},
|
|
],
|
|
},
|
|
],
|
|
};
|
|
|
|
await repos.pageRepository.create({
|
|
initiativeId: initiative.id,
|
|
title: 'Task Manager Redesign',
|
|
content: JSON.stringify(pageContent),
|
|
sortOrder: 0,
|
|
});
|
|
|
|
// ─── Review Comments (on Phase 2) ───
|
|
|
|
await repos.reviewCommentRepository.create({
|
|
phaseId: phase2.id,
|
|
filePath: 'src/components/TaskList.tsx',
|
|
lineNumber: 12,
|
|
lineType: 'added',
|
|
body: 'Should we memoize the STATUS_ICONS and PRIORITY_COLORS objects since they are static? Moving them outside the component is good, but wrapping the component in React.memo might help with re-renders.',
|
|
author: 'you',
|
|
});
|
|
|
|
await repos.reviewCommentRepository.create({
|
|
phaseId: phase2.id,
|
|
filePath: 'src/components/Header.tsx',
|
|
lineNumber: 8,
|
|
lineType: 'added',
|
|
body: 'The notifications are hardcoded. For the demo this is fine, but we should add a TODO to wire this up to a real notification system.',
|
|
author: 'you',
|
|
});
|
|
|
|
await repos.reviewCommentRepository.create({
|
|
phaseId: phase2.id,
|
|
filePath: 'src/lib/api.ts',
|
|
lineNumber: 25,
|
|
lineType: 'added',
|
|
body: 'Nice error handling pattern. Could we also add request timeout support? Long-running requests should fail gracefully.',
|
|
author: 'you',
|
|
});
|
|
|
|
// Print project ID for the shell script
|
|
process.stdout.write(project.id);
|