opencastle 0.27.3 → 0.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/bin/cli.mjs +13 -5
  2. package/dist/cli/convoy/engine.js +2 -2
  3. package/dist/cli/convoy/engine.js.map +1 -1
  4. package/dist/cli/convoy/engine.test.js +1 -1
  5. package/dist/cli/convoy/engine.test.js.map +1 -1
  6. package/dist/cli/convoy/issues.js +3 -3
  7. package/dist/cli/convoy/issues.js.map +1 -1
  8. package/dist/cli/convoy/issues.test.js +4 -3
  9. package/dist/cli/convoy/issues.test.js.map +1 -1
  10. package/dist/cli/pipeline.d.ts +3 -0
  11. package/dist/cli/pipeline.d.ts.map +1 -0
  12. package/dist/cli/pipeline.js +305 -0
  13. package/dist/cli/pipeline.js.map +1 -0
  14. package/dist/cli/plan.d.ts +37 -0
  15. package/dist/cli/plan.d.ts.map +1 -1
  16. package/dist/cli/plan.js +321 -161
  17. package/dist/cli/plan.js.map +1 -1
  18. package/dist/cli/validate.d.ts +3 -0
  19. package/dist/cli/validate.d.ts.map +1 -0
  20. package/dist/cli/validate.js +60 -0
  21. package/dist/cli/validate.js.map +1 -0
  22. package/package.json +5 -4
  23. package/src/cli/convoy/engine.test.ts +1 -1
  24. package/src/cli/convoy/engine.ts +2 -2
  25. package/src/cli/convoy/issues.test.ts +3 -2
  26. package/src/cli/convoy/issues.ts +3 -3
  27. package/src/cli/pipeline.ts +343 -0
  28. package/src/cli/plan.ts +357 -153
  29. package/src/cli/validate.ts +65 -0
  30. package/src/dashboard/dist/data/convoy-list.json +54 -9
  31. package/src/dashboard/dist/data/convoys/demo-api-v2.json +177 -0
  32. package/src/dashboard/dist/data/convoys/demo-auth-revamp.json +239 -0
  33. package/src/dashboard/dist/data/convoys/demo-dashboard-ui.json +328 -0
  34. package/src/dashboard/dist/data/convoys/demo-data-pipeline.json +187 -0
  35. package/src/dashboard/dist/data/convoys/demo-deploy-ci.json +153 -0
  36. package/src/dashboard/dist/data/convoys/demo-docs-update.json +154 -0
  37. package/src/dashboard/dist/data/convoys/demo-perf-opt.json +227 -0
  38. package/src/dashboard/dist/data/events.ndjson +115 -0
  39. package/src/dashboard/dist/data/overall-stats.json +56 -13
  40. package/src/dashboard/dist/data/pipelines.ndjson +5285 -0
  41. package/src/dashboard/dist/index.html +39 -16
  42. package/src/dashboard/node_modules/.vite/deps/_metadata.json +6 -6
  43. package/src/dashboard/public/data/convoy-list.json +54 -9
  44. package/src/dashboard/public/data/convoys/demo-api-v2.json +177 -0
  45. package/src/dashboard/public/data/convoys/demo-auth-revamp.json +239 -0
  46. package/src/dashboard/public/data/convoys/demo-dashboard-ui.json +328 -0
  47. package/src/dashboard/public/data/convoys/demo-data-pipeline.json +187 -0
  48. package/src/dashboard/public/data/convoys/demo-deploy-ci.json +153 -0
  49. package/src/dashboard/public/data/convoys/demo-docs-update.json +154 -0
  50. package/src/dashboard/public/data/convoys/demo-perf-opt.json +227 -0
  51. package/src/dashboard/public/data/events.ndjson +115 -0
  52. package/src/dashboard/public/data/overall-stats.json +56 -13
  53. package/src/dashboard/public/data/pipelines.ndjson +5285 -0
  54. package/src/dashboard/scripts/etl.ts +24 -3
  55. package/src/dashboard/scripts/generate-demo-db.ts +482 -115
  56. package/src/dashboard/src/pages/index.astro +46 -23
  57. package/src/orchestrator/prompts/fix-convoy.prompt.md +79 -0
  58. package/src/orchestrator/prompts/generate-convoy.prompt.md +53 -58
  59. package/src/orchestrator/prompts/generate-prd.prompt.md +120 -0
  60. package/src/orchestrator/prompts/validate-convoy.prompt.md +89 -0
  61. package/src/orchestrator/prompts/validate-prd.prompt.md +83 -0
@@ -1,140 +1,507 @@
1
1
  import { resolve, dirname } from 'node:path'
2
- import { mkdirSync } from 'node:fs'
2
+ import { mkdirSync, writeFileSync } from 'node:fs'
3
3
  import { fileURLToPath } from 'node:url'
4
4
  import { createConvoyStore } from '../../cli/convoy/store.js'
5
5
 
6
- // Fixed timestamps ensure the demo JSON committed to the repo stays stable
7
- const C1_START = '2026-01-15T10:00:00.000Z'
8
- const T1A_START = '2026-01-15T10:00:01.000Z'
9
- const T1A_END = '2026-01-15T10:00:15.000Z'
10
- const T1B_START = '2026-01-15T10:00:16.000Z'
11
- const T1B_END = '2026-01-15T10:00:32.000Z'
12
- const C1_END = '2026-01-15T10:00:33.000Z' // convoy-1 total: 33s
13
- const C2_START = '2026-01-15T10:05:00.000Z'
14
- const T2A_START = '2026-01-15T10:05:01.000Z'
15
-
16
- export async function createDemoDb(outPath: string): Promise<void> {
6
+ // ---------------------------------------------------------------------------
7
+ // Helpers
8
+ // ---------------------------------------------------------------------------
9
+
10
+ function iso(base: string, offsetMs = 0): string {
11
+ return new Date(new Date(base).getTime() + offsetMs).toISOString()
12
+ }
13
+ function min(m: number): number { return m * 60_000 }
14
+ function sec(s: number): number { return s * 1_000 }
15
+
16
+ // ---------------------------------------------------------------------------
17
+ // Demo timestamps – spread over 40 days (2026-02-01 → 2026-03-12)
18
+ // ---------------------------------------------------------------------------
19
+
20
+ const DAYS: string[] = []
21
+ for (let d = 0; d < 40; d++) {
22
+ const dt = new Date('2026-02-01T00:00:00.000Z')
23
+ dt.setUTCDate(dt.getUTCDate() + d)
24
+ DAYS.push(dt.toISOString().slice(0, 10))
25
+ }
26
+
27
+ function dayTs(dayIdx: number, hour = 10, minute = 0): string {
28
+ return `${DAYS[dayIdx]}T${String(hour).padStart(2, '0')}:${String(minute).padStart(2, '0')}:00.000Z`
29
+ }
30
+
31
+ // ---------------------------------------------------------------------------
32
+ // Public API
33
+ // ---------------------------------------------------------------------------
34
+
35
+ export async function createDemoDb(outPath: string, eventsOutPath?: string): Promise<void> {
17
36
  const dbPath = resolve(process.cwd(), outPath)
18
37
  mkdirSync(dirname(dbPath), { recursive: true })
19
38
  const store = createConvoyStore(dbPath)
20
39
 
21
- try {
22
- // --- Convoy 1: completed ---
23
- store.insertConvoy({
24
- id: 'demo-convoy-1',
25
- name: 'Demo Convoy Alpha',
26
- spec_hash: 'h1',
27
- status: 'done',
28
- branch: 'main',
29
- created_at: C1_START,
30
- spec_yaml: 'name: demo-alpha',
31
- })
32
- store.updateConvoyStatus('demo-convoy-1', 'done', {
33
- started_at: C1_START,
34
- finished_at: C1_END,
35
- total_tokens: 8432,
36
- total_cost_usd: 0.84,
37
- })
40
+ // ── Convoy 1: Auth Revamp – DONE ─────────────────────────────────────
41
+ const C1 = dayTs(2, 9)
42
+ store.insertConvoy({ id: 'demo-auth-revamp', name: 'Auth System Revamp', spec_hash: 'h1', status: 'done', branch: 'feat/auth-v2', created_at: C1, spec_yaml: 'name: auth-revamp' })
43
+ store.updateConvoyStatus('demo-auth-revamp', 'done', { started_at: C1, finished_at: iso(C1, min(47)), total_tokens: 42850, total_cost_usd: 4.28 })
44
+ const authTasks = [
45
+ { id: 'auth-t1', phase: 1, prompt: 'Design OAuth2 token refresh architecture', agent: 'Architect', status: 'done' as const, retries: 0, tokens: 8400, cost: 0.84, start: iso(C1, sec(5)), end: iso(C1, min(9)) },
46
+ { id: 'auth-t2', phase: 2, prompt: 'Implement JWT middleware with refresh rotation', agent: 'Developer', status: 'done' as const, retries: 1, tokens: 12600, cost: 1.26, start: iso(C1, min(10)), end: iso(C1, min(24)) },
47
+ { id: 'auth-t3', phase: 2, prompt: 'Add RLS policies for session tokens', agent: 'Security Expert', status: 'done' as const, retries: 0, tokens: 9200, cost: 0.92, start: iso(C1, min(10)), end: iso(C1, min(20)) },
48
+ { id: 'auth-t4', phase: 3, prompt: 'Write auth integration tests', agent: 'Testing Expert', status: 'done' as const, retries: 0, tokens: 8900, cost: 0.89, start: iso(C1, min(25)), end: iso(C1, min(37)) },
49
+ { id: 'auth-t5', phase: 4, prompt: 'QA gate – security review', agent: 'Reviewer', status: 'done' as const, retries: 0, tokens: 3750, cost: 0.37, start: iso(C1, min(38)), end: iso(C1, min(46)) },
50
+ ]
51
+ for (const t of authTasks) {
52
+ store.insertTask({ id: t.id, convoy_id: 'demo-auth-revamp', phase: t.phase, prompt: t.prompt, agent: t.agent, adapter: 'vscode', model: t.agent === 'Architect' ? 'claude-opus-4-6' : 'claude-sonnet-4-6', timeout_ms: 120000, status: t.status, retries: t.retries, max_retries: 3, files: null, depends_on: null, gates: null, outputs: JSON.stringify({ result: 'done' }), inputs: null })
53
+ store.updateTaskStatus(t.id, 'demo-auth-revamp', t.status, { started_at: t.start, finished_at: t.end, total_tokens: t.tokens, cost_usd: t.cost })
54
+ store.insertEvent({ convoy_id: 'demo-auth-revamp', task_id: t.id, worker_id: null, type: 'task_started', data: null, created_at: t.start })
55
+ store.insertEvent({ convoy_id: 'demo-auth-revamp', task_id: t.id, worker_id: null, type: 'task_done', data: null, created_at: t.end })
56
+ }
38
57
 
39
- store.insertTask({
40
- id: 'task-1-a',
41
- convoy_id: 'demo-convoy-1',
42
- phase: 1,
43
- prompt: 'Implement authentication middleware',
44
- agent: 'developer',
45
- adapter: 'noop',
46
- model: 'claude-sonnet-4-6',
47
- timeout_ms: 60000,
48
- status: 'done',
49
- retries: 0,
50
- max_retries: 3,
51
- files: null,
52
- depends_on: null,
53
- gates: null,
54
- outputs: JSON.stringify({ result: 'done' }),
55
- inputs: null,
56
- })
57
- store.updateTaskStatus('task-1-a', 'demo-convoy-1', 'done', {
58
- started_at: T1A_START,
59
- finished_at: T1A_END,
60
- total_tokens: 4200,
61
- cost_usd: 0.42,
62
- })
58
+ // ── Convoy 2: Dashboard UI – DONE ────────────────────────────────────
59
+ const C2 = dayTs(6, 14)
60
+ store.insertConvoy({ id: 'demo-dashboard-ui', name: 'Observability Dashboard UI', spec_hash: 'h2', status: 'done', branch: 'feat/dashboard-v2', created_at: C2, spec_yaml: 'name: dashboard-ui' })
61
+ store.updateConvoyStatus('demo-dashboard-ui', 'done', { started_at: C2, finished_at: iso(C2, min(98)), total_tokens: 78400, total_cost_usd: 7.84 })
62
+ const uiTasks = [
63
+ { id: 'ui-t1', phase: 1, prompt: 'Design dark-theme component system', agent: 'UI/UX Expert', status: 'done' as const, retries: 0, tokens: 14200, cost: 1.42, start: iso(C2, sec(5)), end: iso(C2, min(19)) },
64
+ { id: 'ui-t2', phase: 1, prompt: 'Implement KPI card components', agent: 'Developer', status: 'done' as const, retries: 0, tokens: 11800, cost: 1.18, start: iso(C2, sec(5)), end: iso(C2, min(16)) },
65
+ { id: 'ui-t3', phase: 2, prompt: 'Build SVG donut charts and bar charts', agent: 'Developer', status: 'done' as const, retries: 1, tokens: 13500, cost: 1.35, start: iso(C2, min(20)), end: iso(C2, min(44)) },
66
+ { id: 'ui-t4', phase: 2, prompt: 'Write dashboard CSS animations', agent: 'UI/UX Expert', status: 'done' as const, retries: 0, tokens: 9400, cost: 0.94, start: iso(C2, min(20)), end: iso(C2, min(38)) },
67
+ { id: 'ui-t5', phase: 3, prompt: 'Accessibility audit and ARIA labels', agent: 'UI/UX Expert', status: 'done' as const, retries: 0, tokens: 8700, cost: 0.87, start: iso(C2, min(45)), end: iso(C2, min(58)) },
68
+ { id: 'ui-t6', phase: 3, prompt: 'Cross-browser visual regression tests', agent: 'Testing Expert', status: 'done' as const, retries: 0, tokens: 11200, cost: 1.12, start: iso(C2, min(45)), end: iso(C2, min(62)) },
69
+ { id: 'ui-t7', phase: 4, prompt: 'QA panel – design review', agent: 'Reviewer', status: 'done' as const, retries: 0, tokens: 9600, cost: 0.96, start: iso(C2, min(63)), end: iso(C2, min(97)) },
70
+ ]
71
+ for (const t of uiTasks) {
72
+ store.insertTask({ id: t.id, convoy_id: 'demo-dashboard-ui', phase: t.phase, prompt: t.prompt, agent: t.agent, adapter: 'vscode', model: t.agent === 'UI/UX Expert' ? 'claude-opus-4-6' : 'claude-sonnet-4-6', timeout_ms: 120000, status: t.status, retries: t.retries, max_retries: 3, files: null, depends_on: null, gates: null, outputs: JSON.stringify({ result: 'done' }), inputs: null })
73
+ store.updateTaskStatus(t.id, 'demo-dashboard-ui', t.status, { started_at: t.start, finished_at: t.end, total_tokens: t.tokens, cost_usd: t.cost })
74
+ store.insertEvent({ convoy_id: 'demo-dashboard-ui', task_id: t.id, worker_id: null, type: 'task_started', data: null, created_at: t.start })
75
+ store.insertEvent({ convoy_id: 'demo-dashboard-ui', task_id: t.id, worker_id: null, type: 'task_done', data: null, created_at: t.end })
76
+ }
77
+
78
+ // ── Convoy 3: API v2 – GATE_FAILED ──────────────────────────────────
79
+ const C3 = dayTs(11, 16)
80
+ store.insertConvoy({ id: 'demo-api-v2', name: 'REST API v2 Migration', spec_hash: 'h3', status: 'gate_failed', branch: 'feat/api-v2', created_at: C3, spec_yaml: 'name: api-v2' })
81
+ store.updateConvoyStatus('demo-api-v2', 'gate_failed', { started_at: C3, finished_at: iso(C3, min(28)), total_tokens: 24600, total_cost_usd: 2.46 })
82
+ const apiTasks = [
83
+ { id: 'api-t1', phase: 1, prompt: 'Design RESTful v2 route contracts', agent: 'API Designer', status: 'done' as const, eventType: 'task_done', retries: 0, tokens: 7200, cost: 0.72, start: iso(C3, sec(5)), end: iso(C3, min(11)) },
84
+ { id: 'api-t2', phase: 2, prompt: 'Implement rate limiting middleware', agent: 'Developer', status: 'done' as const, eventType: 'task_done', retries: 2, tokens: 11400, cost: 1.14, start: iso(C3, min(12)), end: iso(C3, min(23)) },
85
+ { id: 'api-t3', phase: 3, prompt: 'Security gate – injection vulnerability scan', agent: 'Security Expert', status: 'gate_failed' as const, eventType: 'task_gate_failed', retries: 0, tokens: 6000, cost: 0.60, start: iso(C3, min(24)), end: iso(C3, min(27)) },
86
+ ]
87
+ for (const t of apiTasks) {
88
+ store.insertTask({ id: t.id, convoy_id: 'demo-api-v2', phase: t.phase, prompt: t.prompt, agent: t.agent, adapter: 'vscode', model: 'claude-sonnet-4-6', timeout_ms: 120000, status: t.status, retries: t.retries, max_retries: 3, files: null, depends_on: null, gates: null, outputs: t.status === 'gate_failed' ? JSON.stringify({ gate_failure: 'SQL injection risk detected in query builder' }) : JSON.stringify({ result: 'done' }), inputs: null })
89
+ store.updateTaskStatus(t.id, 'demo-api-v2', t.status, { started_at: t.start, finished_at: t.end, total_tokens: t.tokens, cost_usd: t.cost })
90
+ store.insertEvent({ convoy_id: 'demo-api-v2', task_id: t.id, worker_id: null, type: 'task_started', data: null, created_at: t.start })
91
+ store.insertEvent({ convoy_id: 'demo-api-v2', task_id: t.id, worker_id: null, type: t.eventType, data: t.status === 'gate_failed' ? JSON.stringify({ reason: 'SQL injection risk' }) : null, created_at: t.end })
92
+ }
93
+
94
+ // ── Convoy 4: Performance Optimization – DONE ────────────────────────
95
+ const C4 = dayTs(16, 10)
96
+ store.insertConvoy({ id: 'demo-perf-opt', name: 'Frontend Performance Boost', spec_hash: 'h4', status: 'done', branch: 'perf/core-web-vitals', created_at: C4, spec_yaml: 'name: perf-opt' })
97
+ store.updateConvoyStatus('demo-perf-opt', 'done', { started_at: C4, finished_at: iso(C4, min(62)), total_tokens: 37200, total_cost_usd: 3.72 })
98
+ const perfTasks = [
99
+ { id: 'perf-t1', phase: 1, prompt: 'Profile bundle and identify bottlenecks', agent: 'Performance Expert', status: 'done' as const, retries: 0, tokens: 8900, cost: 0.89, start: iso(C4, sec(5)), end: iso(C4, min(13)) },
100
+ { id: 'perf-t2', phase: 2, prompt: 'Code-split heavy chart library', agent: 'Developer', status: 'done' as const, retries: 0, tokens: 11200, cost: 1.12, start: iso(C4, min(14)), end: iso(C4, min(30)) },
101
+ { id: 'perf-t3', phase: 2, prompt: 'Implement image lazy-loading and AVIF conversion', agent: 'Developer', status: 'done' as const, retries: 0, tokens: 9600, cost: 0.96, start: iso(C4, min(14)), end: iso(C4, min(27)) },
102
+ { id: 'perf-t4', phase: 3, prompt: 'Validate Core Web Vitals improvements', agent: 'Performance Expert', status: 'done' as const, retries: 0, tokens: 7500, cost: 0.75, start: iso(C4, min(31)), end: iso(C4, min(44)) },
103
+ ]
104
+ for (const t of perfTasks) {
105
+ store.insertTask({ id: t.id, convoy_id: 'demo-perf-opt', phase: t.phase, prompt: t.prompt, agent: t.agent, adapter: 'vscode', model: 'claude-sonnet-4-6', timeout_ms: 120000, status: t.status, retries: t.retries, max_retries: 3, files: null, depends_on: null, gates: null, outputs: JSON.stringify({ result: 'done' }), inputs: null })
106
+ store.updateTaskStatus(t.id, 'demo-perf-opt', t.status, { started_at: t.start, finished_at: t.end, total_tokens: t.tokens, cost_usd: t.cost })
107
+ store.insertEvent({ convoy_id: 'demo-perf-opt', task_id: t.id, worker_id: null, type: 'task_started', data: null, created_at: t.start })
108
+ store.insertEvent({ convoy_id: 'demo-perf-opt', task_id: t.id, worker_id: null, type: 'task_done', data: null, created_at: t.end })
109
+ }
110
+
111
+ // ── Convoy 5: Data Pipeline – DONE ────────────────────────────────────
112
+ const C5 = dayTs(21, 13)
113
+ store.insertConvoy({ id: 'demo-data-pipeline', name: 'Analytics ETL Pipeline', spec_hash: 'h5', status: 'done', branch: 'feat/etl-v2', created_at: C5, spec_yaml: 'name: data-pipeline' })
114
+ store.updateConvoyStatus('demo-data-pipeline', 'done', { started_at: C5, finished_at: iso(C5, min(38)), total_tokens: 28900, total_cost_usd: 2.89 })
115
+ const etlTasks = [
116
+ { id: 'etl-t1', phase: 1, prompt: 'Design ndjson processing schema', agent: 'Data Expert', status: 'done' as const, retries: 0, tokens: 7800, cost: 0.78, start: iso(C5, sec(5)), end: iso(C5, min(11)) },
117
+ { id: 'etl-t2', phase: 2, prompt: 'Implement incremental ETL with deduplication', agent: 'Data Expert', status: 'done' as const, retries: 1, tokens: 12400, cost: 1.24, start: iso(C5, min(12)), end: iso(C5, min(30)) },
118
+ { id: 'etl-t3', phase: 3, prompt: 'Write ETL test suite', agent: 'Testing Expert', status: 'done' as const, retries: 0, tokens: 8700, cost: 0.87, start: iso(C5, min(31)), end: iso(C5, min(37)) },
119
+ ]
120
+ for (const t of etlTasks) {
121
+ store.insertTask({ id: t.id, convoy_id: 'demo-data-pipeline', phase: t.phase, prompt: t.prompt, agent: t.agent, adapter: 'vscode', model: 'claude-sonnet-4-6', timeout_ms: 120000, status: t.status, retries: t.retries, max_retries: 3, files: null, depends_on: null, gates: null, outputs: JSON.stringify({ result: 'done' }), inputs: null })
122
+ store.updateTaskStatus(t.id, 'demo-data-pipeline', t.status, { started_at: t.start, finished_at: t.end, total_tokens: t.tokens, cost_usd: t.cost })
123
+ store.insertEvent({ convoy_id: 'demo-data-pipeline', task_id: t.id, worker_id: null, type: 'task_started', data: null, created_at: t.start })
124
+ store.insertEvent({ convoy_id: 'demo-data-pipeline', task_id: t.id, worker_id: null, type: 'task_done', data: null, created_at: t.end })
125
+ }
126
+
127
+ // ── Convoy 6: CI/CD Deployment – RUNNING ────────────────────────────
128
+ const C6 = dayTs(38, 8)
129
+ store.insertConvoy({ id: 'demo-deploy-ci', name: 'CI/CD Pipeline Setup', spec_hash: 'h6', status: 'running', branch: 'feat/ci-cd', created_at: C6, spec_yaml: 'name: deploy-ci' })
130
+ store.updateConvoyStatus('demo-deploy-ci', 'running', { started_at: C6 })
131
+ const ciTasks = [
132
+ { id: 'ci-t1', phase: 1, prompt: 'Design GitHub Actions workflow matrix', agent: 'DevOps Expert', status: 'done' as const, running: false, retries: 0, tokens: 6400, cost: 0.64, start: iso(C6, sec(5)), end: iso(C6, min(14)) },
133
+ { id: 'ci-t2', phase: 2, prompt: 'Configure nx affected build caching', agent: 'DevOps Expert', status: 'running' as const, running: true, retries: 0, tokens: 0, cost: 0, start: iso(C6, min(15)), end: '' },
134
+ { id: 'ci-t3', phase: 2, prompt: 'Set up staging environment deployment', agent: 'DevOps Expert', status: 'pending' as const, running: false, retries: 0, tokens: 0, cost: 0, start: '', end: '' },
135
+ ]
136
+ for (const t of ciTasks) {
137
+ store.insertTask({ id: t.id, convoy_id: 'demo-deploy-ci', phase: t.phase, prompt: t.prompt, agent: t.agent, adapter: 'vscode', model: 'claude-sonnet-4-6', timeout_ms: 120000, status: t.status, retries: t.retries, max_retries: 3, files: null, depends_on: null, gates: null, outputs: t.status === 'done' ? JSON.stringify({ result: 'done' }) : null, inputs: null })
138
+ if (t.status === 'done') {
139
+ store.updateTaskStatus(t.id, 'demo-deploy-ci', t.status, { started_at: t.start, finished_at: t.end, total_tokens: t.tokens, cost_usd: t.cost })
140
+ store.insertEvent({ convoy_id: 'demo-deploy-ci', task_id: t.id, worker_id: null, type: 'task_started', data: null, created_at: t.start })
141
+ store.insertEvent({ convoy_id: 'demo-deploy-ci', task_id: t.id, worker_id: null, type: 'task_done', data: null, created_at: t.end })
142
+ } else if (t.running) {
143
+ store.updateTaskStatus(t.id, 'demo-deploy-ci', t.status, { started_at: t.start })
144
+ store.insertEvent({ convoy_id: 'demo-deploy-ci', task_id: t.id, worker_id: null, type: 'task_started', data: null, created_at: t.start })
145
+ }
146
+ }
147
+
148
+ // ── Convoy 7: Docs Update – DONE ────────────────────────────────────
149
+ const C7 = dayTs(27, 15)
150
+ store.insertConvoy({ id: 'demo-docs-update', name: 'Documentation Refresh', spec_hash: 'h7', status: 'done', branch: 'docs/update-march', created_at: C7, spec_yaml: 'name: docs-update' })
151
+ store.updateConvoyStatus('demo-docs-update', 'done', { started_at: C7, finished_at: iso(C7, min(22)), total_tokens: 14800, total_cost_usd: 1.48 })
152
+ const docTasks = [
153
+ { id: 'docs-t1', phase: 1, prompt: 'Update README and ARCHITECTURE docs', agent: 'Documentation Writer', status: 'done' as const, retries: 0, tokens: 8200, cost: 0.82, start: iso(C7, sec(5)), end: iso(C7, min(14)) },
154
+ { id: 'docs-t2', phase: 2, prompt: 'Generate API reference from source', agent: 'Documentation Writer', status: 'done' as const, retries: 0, tokens: 6600, cost: 0.66, start: iso(C7, min(15)), end: iso(C7, min(21)) },
155
+ ]
156
+ for (const t of docTasks) {
157
+ store.insertTask({ id: t.id, convoy_id: 'demo-docs-update', phase: t.phase, prompt: t.prompt, agent: t.agent, adapter: 'vscode', model: 'claude-haiku-3-5', timeout_ms: 120000, status: t.status, retries: t.retries, max_retries: 3, files: null, depends_on: null, gates: null, outputs: JSON.stringify({ result: 'done' }), inputs: null })
158
+ store.updateTaskStatus(t.id, 'demo-docs-update', t.status, { started_at: t.start, finished_at: t.end, total_tokens: t.tokens, cost_usd: t.cost })
159
+ store.insertEvent({ convoy_id: 'demo-docs-update', task_id: t.id, worker_id: null, type: 'task_started', data: null, created_at: t.start })
160
+ store.insertEvent({ convoy_id: 'demo-docs-update', task_id: t.id, worker_id: null, type: 'task_done', data: null, created_at: t.end })
161
+ }
162
+
163
+ // ── Review data ──────────────────────────────────────────────────────
164
+ // Auth Revamp: fast-review pass on QA gate task
165
+ store.updateTaskReview('auth-t5', 'demo-auth-revamp', {
166
+ review_level: 'fast', review_verdict: 'pass',
167
+ review_tokens: 1850, review_model: 'claude-haiku-3-5', panel_attempts: 0,
168
+ })
169
+ // Dashboard UI: panel review (2 attempts — first blocked, then passed)
170
+ store.updateTaskReview('ui-t7', 'demo-dashboard-ui', {
171
+ review_level: 'panel', review_verdict: 'pass',
172
+ review_tokens: 4800, review_model: 'claude-opus-4-6', panel_attempts: 2,
173
+ })
174
+ // API v2: deep security gate review blocked (gate failure)
175
+ store.updateTaskReview('api-t3', 'demo-api-v2', {
176
+ review_level: 'deep', review_verdict: 'block',
177
+ review_tokens: 2100, review_model: 'claude-sonnet-4-6', panel_attempts: 1,
178
+ })
179
+ // Perf Opt: add reviewer task + fast review pass
180
+ store.insertTask({ id: 'perf-t5', convoy_id: 'demo-perf-opt', phase: 4, prompt: 'Fast review – performance changes', agent: 'Reviewer', adapter: 'vscode', model: 'claude-haiku-3-5', timeout_ms: 60000, status: 'done', retries: 0, max_retries: 3, files: null, depends_on: null, gates: null })
181
+ store.updateTaskStatus('perf-t5', 'demo-perf-opt', 'done', { started_at: iso(C4, min(45)), finished_at: iso(C4, min(52)), total_tokens: 1200, cost_usd: 0.12 })
182
+ store.updateTaskReview('perf-t5', 'demo-perf-opt', {
183
+ review_level: 'fast', review_verdict: 'pass',
184
+ review_tokens: 1200, review_model: 'claude-haiku-3-5', panel_attempts: 0,
185
+ })
186
+ // Data Pipeline: add reviewer task + fast review pass
187
+ store.insertTask({ id: 'etl-t4', convoy_id: 'demo-data-pipeline', phase: 4, prompt: 'Fast review – ETL pipeline', agent: 'Reviewer', adapter: 'vscode', model: 'claude-haiku-3-5', timeout_ms: 60000, status: 'done', retries: 0, max_retries: 3, files: null, depends_on: null, gates: null })
188
+ store.updateTaskStatus('etl-t4', 'demo-data-pipeline', 'done', { started_at: iso(C5, min(32)), finished_at: iso(C5, min(37)), total_tokens: 900, cost_usd: 0.09 })
189
+ store.updateTaskReview('etl-t4', 'demo-data-pipeline', {
190
+ review_level: 'fast', review_verdict: 'pass',
191
+ review_tokens: 900, review_model: 'claude-haiku-3-5', panel_attempts: 0,
192
+ })
193
+ // Docs Update: add reviewer task + fast review pass
194
+ store.insertTask({ id: 'docs-t3', convoy_id: 'demo-docs-update', phase: 3, prompt: 'Fast review – documentation changes', agent: 'Reviewer', adapter: 'vscode', model: 'claude-haiku-3-5', timeout_ms: 60000, status: 'done', retries: 0, max_retries: 3, files: null, depends_on: null, gates: null })
195
+ store.updateTaskStatus('docs-t3', 'demo-docs-update', 'done', { started_at: iso(C7, min(17)), finished_at: iso(C7, min(21)), total_tokens: 800, cost_usd: 0.08 })
196
+ store.updateTaskReview('docs-t3', 'demo-docs-update', {
197
+ review_level: 'fast', review_verdict: 'pass',
198
+ review_tokens: 800, review_model: 'claude-haiku-3-5', panel_attempts: 0,
199
+ })
200
+
201
+ // ── Drift data ────────────────────────────────────────────────────────
202
+ store.updateTaskDrift('auth-t2', 'demo-auth-revamp', { drift_score: 0.15, drift_retried: 1 })
203
+ store.updateTaskDrift('ui-t3', 'demo-dashboard-ui', { drift_score: 0.28, drift_retried: 1 })
204
+ store.updateTaskDrift('etl-t2', 'demo-data-pipeline', { drift_score: 0.12, drift_retried: 0 })
205
+
206
+ // ── Secret leak event (drives drift section banner in deploy-ci) ──────
207
+ store.insertEvent({
208
+ convoy_id: 'demo-deploy-ci', task_id: 'ci-t1', worker_id: null,
209
+ type: 'secret_leak_prevented',
210
+ data: JSON.stringify({ secret_type: 'API_KEY', file: '.env.staging', masked: true }),
211
+ created_at: iso(C6, min(8)),
212
+ })
63
213
 
64
- store.insertTask({
65
- id: 'task-1-b',
66
- convoy_id: 'demo-convoy-1',
67
- phase: 2,
68
- prompt: 'Write unit tests for auth middleware',
69
- agent: 'developer',
70
- adapter: 'noop',
71
- model: 'claude-sonnet-4-6',
72
- timeout_ms: 60000,
73
- status: 'done',
74
- retries: 1,
75
- max_retries: 3,
76
- files: null,
77
- depends_on: null,
78
- gates: null,
79
- outputs: JSON.stringify({ tests: 12 }),
80
- inputs: null,
214
+ // ── DLQ entries ───────────────────────────────────────────────────────
215
+ store.insertDlqEntry({
216
+ id: 'dlq-api-1', convoy_id: 'demo-api-v2', task_id: 'api-t3',
217
+ agent: 'Security Expert', failure_type: 'gate_failed',
218
+ error_output: 'SQL injection risk detected in query builder at src/db/query.ts:45. Parameterized queries required.',
219
+ attempts: 1, tokens_spent: 6000, escalation_task_id: null,
220
+ resolved: 0, resolution: null,
221
+ created_at: iso(C3, min(27)), resolved_at: null,
222
+ })
223
+ store.insertDlqEntry({
224
+ id: 'dlq-ci-1', convoy_id: 'demo-deploy-ci', task_id: 'ci-t2',
225
+ agent: 'DevOps Expert', failure_type: 'timeout',
226
+ error_output: 'Task exceeded max execution time (120s) during nx cache configuration. Cache key computation timed out.',
227
+ attempts: 2, tokens_spent: 3200, escalation_task_id: null,
228
+ resolved: 0, resolution: null,
229
+ created_at: iso(C6, min(35)), resolved_at: null,
230
+ })
231
+ store.insertDlqEntry({
232
+ id: 'dlq-ui-1', convoy_id: 'demo-dashboard-ui', task_id: 'ui-t7',
233
+ agent: 'Reviewer', failure_type: 'review_blocked',
234
+ error_output: 'Panel review blocked: accessibility issues in chart components – 2 major findings (missing ARIA labels, insufficient contrast ratio).',
235
+ attempts: 1, tokens_spent: 2400, escalation_task_id: null,
236
+ resolved: 1, resolution: 'Re-reviewed after accessibility fixes applied in ui-t5',
237
+ created_at: iso(C2, min(65)), resolved_at: iso(C2, min(97)),
238
+ })
239
+
240
+ // ── Artifacts ────────────────────────────────────────────────────────
241
+ const demoArtifacts: Array<{ convoyId: string; taskId: string; name: string; type: 'file' | 'summary' | 'json' }> = [
242
+ { convoyId: 'demo-auth-revamp', taskId: 'auth-t2', name: 'libs/auth/src/jwt-middleware.ts', type: 'file' },
243
+ { convoyId: 'demo-auth-revamp', taskId: 'auth-t3', name: 'libs/auth/src/rls-policies.sql', type: 'file' },
244
+ { convoyId: 'demo-auth-revamp', taskId: 'auth-t4', name: 'tests/auth/integration.test.ts', type: 'file' },
245
+ { convoyId: 'demo-auth-revamp', taskId: 'auth-t5', name: 'reports/auth-review-summary.md', type: 'summary' },
246
+ { convoyId: 'demo-dashboard-ui', taskId: 'ui-t1', name: 'src/components/design-tokens.ts', type: 'file' },
247
+ { convoyId: 'demo-dashboard-ui', taskId: 'ui-t2', name: 'src/components/KpiCard.tsx', type: 'file' },
248
+ { convoyId: 'demo-dashboard-ui', taskId: 'ui-t3', name: 'src/components/DonutChart.tsx', type: 'file' },
249
+ { convoyId: 'demo-dashboard-ui', taskId: 'ui-t4', name: 'src/styles/animations.css', type: 'file' },
250
+ { convoyId: 'demo-dashboard-ui', taskId: 'ui-t6', name: 'reports/visual-regression.json', type: 'json' },
251
+ { convoyId: 'demo-dashboard-ui', taskId: 'ui-t7', name: 'reports/panel-review-dashboard.md', type: 'summary' },
252
+ { convoyId: 'demo-api-v2', taskId: 'api-t1', name: 'docs/api-v2-contract.json', type: 'json' },
253
+ { convoyId: 'demo-api-v2', taskId: 'api-t2', name: 'src/api/rate-limiter.ts', type: 'file' },
254
+ { convoyId: 'demo-api-v2', taskId: 'api-t3', name: 'reports/security-gate-failure.md', type: 'summary' },
255
+ { convoyId: 'demo-perf-opt', taskId: 'perf-t1', name: 'reports/bundle-analysis.json', type: 'json' },
256
+ { convoyId: 'demo-perf-opt', taskId: 'perf-t2', name: 'src/charts/index.ts', type: 'file' },
257
+ { convoyId: 'demo-perf-opt', taskId: 'perf-t3', name: 'src/utils/image-loader.ts', type: 'file' },
258
+ { convoyId: 'demo-perf-opt', taskId: 'perf-t4', name: 'reports/web-vitals-improvement.md', type: 'summary' },
259
+ { convoyId: 'demo-data-pipeline', taskId: 'etl-t1', name: 'src/etl/schema.ts', type: 'file' },
260
+ { convoyId: 'demo-data-pipeline', taskId: 'etl-t2', name: 'src/etl/pipeline.ts', type: 'file' },
261
+ { convoyId: 'demo-data-pipeline', taskId: 'etl-t3', name: 'tests/etl/pipeline.test.ts', type: 'file' },
262
+ { convoyId: 'demo-docs-update', taskId: 'docs-t1', name: 'docs/README.md', type: 'file' },
263
+ { convoyId: 'demo-docs-update', taskId: 'docs-t1', name: 'docs/ARCHITECTURE.md', type: 'file' },
264
+ { convoyId: 'demo-docs-update', taskId: 'docs-t2', name: 'docs/api-reference.json', type: 'json' },
265
+ { convoyId: 'demo-deploy-ci', taskId: 'ci-t1', name: '.github/workflows/ci.yml', type: 'file' },
266
+ ]
267
+ for (const a of demoArtifacts) {
268
+ store.insertArtifact({
269
+ id: `artifact-${a.convoyId}-${a.name.replace(/[^a-z0-9]/gi, '-').slice(0, 40)}`,
270
+ convoy_id: a.convoyId, task_id: a.taskId,
271
+ name: a.name, type: a.type,
272
+ content: `Demo artifact: ${a.name}`,
273
+ created_at: new Date().toISOString(),
81
274
  })
82
- store.updateTaskStatus('task-1-b', 'demo-convoy-1', 'done', {
83
- started_at: T1B_START,
84
- finished_at: T1B_END,
85
- total_tokens: 4232,
86
- cost_usd: 0.42,
275
+ }
276
+
277
+ store.close()
278
+ console.log(`Created demo convoy DB at ${dbPath}`)
279
+
280
+ // ── Generate demo observability events.ndjson ──────────────────────────
281
+ if (!eventsOutPath) return
282
+ const eventsPath = resolve(process.cwd(), eventsOutPath)
283
+ mkdirSync(dirname(eventsPath), { recursive: true })
284
+ const lines: string[] = []
285
+
286
+ function emit(record: Record<string, unknown>) {
287
+ lines.push(JSON.stringify(record))
288
+ }
289
+
290
+ // ── Session records (62 entries, spanning 40 days) ───────────────────
291
+ type Outcome = 'success' | 'partial' | 'failed'
292
+ const sessions: Array<{
293
+ dayIdx: number; hour: number; agent: string; model: string
294
+ task: string; outcome: Outcome; duration_min: number
295
+ files_changed: number; retries: number; convoy_id?: string; tracker_issue?: string
296
+ lessons_added?: string[]; discoveries?: string[]
297
+ }> = [
298
+ // Day 0-2: Auth planning
299
+ { dayIdx: 0, hour: 9, agent: 'Team Lead (OpenCastle)', model: 'claude-opus-4-6', task: 'Plan auth system revamp', outcome: 'success', duration_min: 8, files_changed: 2, retries: 0, convoy_id: 'demo-auth-revamp', tracker_issue: 'TASK-01' },
300
+ { dayIdx: 1, hour: 10, agent: 'Architect', model: 'claude-opus-4-6', task: 'Design OAuth2 token refresh architecture', outcome: 'success', duration_min: 9, files_changed: 3, retries: 0, convoy_id: 'demo-auth-revamp', tracker_issue: 'TASK-02' },
301
+ { dayIdx: 2, hour: 9, agent: 'Team Lead (OpenCastle)', model: 'claude-opus-4-6', task: 'Orchestrate auth revamp convoy', outcome: 'success', duration_min: 47, files_changed: 0, retries: 0, convoy_id: 'demo-auth-revamp', tracker_issue: 'TASK-03' },
302
+ { dayIdx: 2, hour: 9, agent: 'Developer', model: 'claude-sonnet-4-6', task: 'Implement JWT middleware with refresh rotation', outcome: 'success', duration_min: 14, files_changed: 6, retries: 1, convoy_id: 'demo-auth-revamp', tracker_issue: 'TASK-04', lessons_added: ['Always invalidate old tokens before issuing new ones'] },
303
+ { dayIdx: 2, hour: 9, agent: 'Security Expert', model: 'claude-sonnet-4-6', task: 'Add RLS policies for session tokens', outcome: 'success', duration_min: 10, files_changed: 4, retries: 0, convoy_id: 'demo-auth-revamp', tracker_issue: 'TASK-05' },
304
+ { dayIdx: 2, hour: 10, agent: 'Testing Expert', model: 'claude-sonnet-4-6', task: 'Write auth integration tests', outcome: 'success', duration_min: 12, files_changed: 5, retries: 0, convoy_id: 'demo-auth-revamp', tracker_issue: 'TASK-06' },
305
+ { dayIdx: 2, hour: 11, agent: 'Reviewer', model: 'claude-sonnet-4-6', task: 'QA review – auth middleware', outcome: 'success', duration_min: 8, files_changed: 0, retries: 0, convoy_id: 'demo-auth-revamp', tracker_issue: 'TASK-07' },
306
+ // Day 3-5
307
+ { dayIdx: 3, hour: 14, agent: 'Developer', model: 'claude-sonnet-4-6', task: 'Fix token expiry edge case', outcome: 'success', duration_min: 6, files_changed: 2, retries: 0, tracker_issue: 'TASK-08' },
308
+ { dayIdx: 4, hour: 11, agent: 'Security Expert', model: 'claude-sonnet-4-6', task: 'Audit CSRF protection mechanisms', outcome: 'partial', duration_min: 15, files_changed: 3, retries: 1, tracker_issue: 'TASK-09', discoveries: ['Rate limiting missing on /api/tokens'] },
309
+ { dayIdx: 5, hour: 10, agent: 'Team Lead (OpenCastle)', model: 'claude-opus-4-6', task: 'Plan dashboard UI redesign', outcome: 'success', duration_min: 6, files_changed: 1, retries: 0, convoy_id: 'demo-dashboard-ui', tracker_issue: 'TASK-10' },
310
+ // Day 6-8: Dashboard UI
311
+ { dayIdx: 6, hour: 14, agent: 'Team Lead (OpenCastle)', model: 'claude-opus-4-6', task: 'Orchestrate dashboard UI convoy', outcome: 'success', duration_min: 98, files_changed: 0, retries: 0, convoy_id: 'demo-dashboard-ui', tracker_issue: 'TASK-11' },
312
+ { dayIdx: 6, hour: 14, agent: 'UI/UX Expert', model: 'claude-opus-4-6', task: 'Design dark-theme component system', outcome: 'success', duration_min: 19, files_changed: 8, retries: 0, convoy_id: 'demo-dashboard-ui', tracker_issue: 'TASK-12' },
313
+ { dayIdx: 6, hour: 14, agent: 'Developer', model: 'claude-sonnet-4-6', task: 'Implement KPI card components', outcome: 'success', duration_min: 16, files_changed: 7, retries: 0, convoy_id: 'demo-dashboard-ui', tracker_issue: 'TASK-13' },
314
+ { dayIdx: 6, hour: 15, agent: 'Developer', model: 'claude-sonnet-4-6', task: 'Build SVG donut charts and bar charts', outcome: 'success', duration_min: 24, files_changed: 5, retries: 1, convoy_id: 'demo-dashboard-ui', tracker_issue: 'TASK-14', lessons_added: ['SVG stroke-dashoffset must be negated for CCW donut segments'] },
315
+ { dayIdx: 6, hour: 15, agent: 'UI/UX Expert', model: 'claude-opus-4-6', task: 'Write dashboard CSS animations', outcome: 'success', duration_min: 18, files_changed: 3, retries: 0, convoy_id: 'demo-dashboard-ui', tracker_issue: 'TASK-15' },
316
+ { dayIdx: 7, hour: 9, agent: 'UI/UX Expert', model: 'claude-opus-4-6', task: 'Accessibility audit and ARIA labels', outcome: 'success', duration_min: 13, files_changed: 6, retries: 0, convoy_id: 'demo-dashboard-ui', tracker_issue: 'TASK-16' },
317
+ { dayIdx: 7, hour: 9, agent: 'Testing Expert', model: 'claude-sonnet-4-6', task: 'Cross-browser visual regression tests', outcome: 'success', duration_min: 17, files_changed: 4, retries: 0, convoy_id: 'demo-dashboard-ui', tracker_issue: 'TASK-17' },
318
+ { dayIdx: 7, hour: 11, agent: 'Reviewer', model: 'claude-sonnet-4-6', task: 'Panel review – dashboard UI', outcome: 'success', duration_min: 34, files_changed: 0, retries: 0, convoy_id: 'demo-dashboard-ui', tracker_issue: 'TASK-18' },
319
+ { dayIdx: 8, hour: 10, agent: 'Copywriter', model: 'claude-sonnet-4-6', task: 'Write dashboard empty state copy', outcome: 'success', duration_min: 5, files_changed: 1, retries: 0, tracker_issue: 'TASK-19' },
320
+ // Day 9-13: API work
321
+ { dayIdx: 9, hour: 10, agent: 'Team Lead (OpenCastle)', model: 'claude-opus-4-6', task: 'Plan REST API v2 migration', outcome: 'success', duration_min: 7, files_changed: 1, retries: 0, convoy_id: 'demo-api-v2', tracker_issue: 'TASK-20' },
322
+ { dayIdx: 10, hour: 11, agent: 'API Designer', model: 'claude-sonnet-4-6', task: 'Design RESTful v2 route contracts', outcome: 'success', duration_min: 11, files_changed: 4, retries: 0, convoy_id: 'demo-api-v2', tracker_issue: 'TASK-21' },
323
+ { dayIdx: 11, hour: 16, agent: 'Team Lead (OpenCastle)', model: 'claude-opus-4-6', task: 'Orchestrate API v2 convoy', outcome: 'failed', duration_min: 28, files_changed: 0, retries: 0, convoy_id: 'demo-api-v2', tracker_issue: 'TASK-22' },
324
+ { dayIdx: 11, hour: 16, agent: 'Developer', model: 'claude-sonnet-4-6', task: 'Implement rate limiting middleware', outcome: 'partial', duration_min: 11, files_changed: 5, retries: 2, convoy_id: 'demo-api-v2', tracker_issue: 'TASK-23' },
325
+ { dayIdx: 11, hour: 17, agent: 'Security Expert', model: 'claude-sonnet-4-6', task: 'Security gate – injection vulnerability scan', outcome: 'failed', duration_min: 3, files_changed: 0, retries: 0, convoy_id: 'demo-api-v2', tracker_issue: 'TASK-24', discoveries: ['SQL injection risk in query builder', 'Missing input sanitization on user endpoint'] },
326
+ { dayIdx: 12, hour: 9, agent: 'Developer', model: 'claude-sonnet-4-6', task: 'Patch SQL injection in query builder', outcome: 'success', duration_min: 8, files_changed: 3, retries: 0, tracker_issue: 'TASK-25' },
327
+ { dayIdx: 13, hour: 14, agent: 'Security Expert', model: 'claude-sonnet-4-6', task: 'Re-audit query builder after patch', outcome: 'success', duration_min: 7, files_changed: 1, retries: 0, tracker_issue: 'TASK-26' },
328
+ // Day 14-18: Performance
329
+ { dayIdx: 14, hour: 10, agent: 'Team Lead (OpenCastle)', model: 'claude-opus-4-6', task: 'Plan performance optimization sprint', outcome: 'success', duration_min: 5, files_changed: 1, retries: 0, convoy_id: 'demo-perf-opt', tracker_issue: 'TASK-27' },
330
+ { dayIdx: 15, hour: 11, agent: 'Performance Expert', model: 'claude-sonnet-4-6', task: 'Profile bundle and identify bottlenecks', outcome: 'success', duration_min: 13, files_changed: 2, retries: 0, convoy_id: 'demo-perf-opt', tracker_issue: 'TASK-28' },
331
+ { dayIdx: 16, hour: 10, agent: 'Team Lead (OpenCastle)', model: 'claude-opus-4-6', task: 'Orchestrate performance optimization convoy', outcome: 'success', duration_min: 62, files_changed: 0, retries: 0, convoy_id: 'demo-perf-opt', tracker_issue: 'TASK-29' },
332
+ { dayIdx: 16, hour: 10, agent: 'Developer', model: 'claude-sonnet-4-6', task: 'Code-split heavy chart library', outcome: 'success', duration_min: 16, files_changed: 8, retries: 0, convoy_id: 'demo-perf-opt', tracker_issue: 'TASK-30' },
333
+ { dayIdx: 16, hour: 10, agent: 'Developer', model: 'claude-sonnet-4-6', task: 'Implement image lazy-loading and AVIF conversion', outcome: 'success', duration_min: 13, files_changed: 5, retries: 0, convoy_id: 'demo-perf-opt', tracker_issue: 'TASK-31' },
334
+ { dayIdx: 16, hour: 11, agent: 'Performance Expert', model: 'claude-sonnet-4-6', task: 'Validate Core Web Vitals improvements', outcome: 'success', duration_min: 13, files_changed: 0, retries: 0, convoy_id: 'demo-perf-opt', tracker_issue: 'TASK-32' },
335
+ { dayIdx: 17, hour: 14, agent: 'Reviewer', model: 'claude-haiku-3-5', task: 'Fast review – performance PR', outcome: 'success', duration_min: 6, files_changed: 0, retries: 0, tracker_issue: 'TASK-33' },
336
+ { dayIdx: 18, hour: 9, agent: 'Developer', model: 'claude-sonnet-4-6', task: 'Fix LCP regression on mobile', outcome: 'success', duration_min: 9, files_changed: 3, retries: 1, tracker_issue: 'TASK-34', lessons_added: ['Always check mobile viewport when optimizing LCP'] },
337
+ // Day 19-23: Data pipeline
338
+ { dayIdx: 19, hour: 13, agent: 'Team Lead (OpenCastle)', model: 'claude-opus-4-6', task: 'Plan analytics ETL pipeline', outcome: 'success', duration_min: 6, files_changed: 1, retries: 0, convoy_id: 'demo-data-pipeline', tracker_issue: 'TASK-35' },
339
+ { dayIdx: 20, hour: 11, agent: 'Data Expert', model: 'claude-sonnet-4-6', task: 'Design ndjson processing schema', outcome: 'success', duration_min: 11, files_changed: 3, retries: 0, convoy_id: 'demo-data-pipeline', tracker_issue: 'TASK-36' },
340
+ { dayIdx: 21, hour: 13, agent: 'Team Lead (OpenCastle)', model: 'claude-opus-4-6', task: 'Orchestrate data pipeline convoy', outcome: 'success', duration_min: 38, files_changed: 0, retries: 0, convoy_id: 'demo-data-pipeline', tracker_issue: 'TASK-37' },
341
+ { dayIdx: 21, hour: 13, agent: 'Data Expert', model: 'claude-sonnet-4-6', task: 'Implement incremental ETL with deduplication', outcome: 'success', duration_min: 18, files_changed: 7, retries: 1, convoy_id: 'demo-data-pipeline', tracker_issue: 'TASK-38' },
342
+ { dayIdx: 21, hour: 14, agent: 'Testing Expert', model: 'claude-haiku-3-5', task: 'Write ETL test suite', outcome: 'success', duration_min: 6, files_changed: 4, retries: 0, convoy_id: 'demo-data-pipeline', tracker_issue: 'TASK-39' },
343
+ { dayIdx: 22, hour: 15, agent: 'Reviewer', model: 'claude-haiku-3-5', task: 'Fast review – data pipeline', outcome: 'success', duration_min: 5, files_changed: 0, retries: 0, tracker_issue: 'TASK-40' },
344
+ { dayIdx: 23, hour: 10, agent: 'Documentation Writer', model: 'claude-haiku-3-5', task: 'Document ETL schema and configuration', outcome: 'success', duration_min: 8, files_changed: 2, retries: 0, tracker_issue: 'TASK-41' },
345
+ // Day 24-29: Docs
346
+ { dayIdx: 25, hour: 10, agent: 'Team Lead (OpenCastle)', model: 'claude-opus-4-6', task: 'Plan documentation refresh', outcome: 'success', duration_min: 4, files_changed: 0, retries: 0, convoy_id: 'demo-docs-update', tracker_issue: 'TASK-42' },
347
+ { dayIdx: 27, hour: 15, agent: 'Team Lead (OpenCastle)', model: 'claude-opus-4-6', task: 'Orchestrate documentation update convoy', outcome: 'success', duration_min: 22, files_changed: 0, retries: 0, convoy_id: 'demo-docs-update', tracker_issue: 'TASK-43' },
348
+ { dayIdx: 27, hour: 15, agent: 'Documentation Writer', model: 'claude-haiku-3-5', task: 'Update README and ARCHITECTURE docs', outcome: 'success', duration_min: 14, files_changed: 5, retries: 0, convoy_id: 'demo-docs-update', tracker_issue: 'TASK-44' },
349
+ { dayIdx: 27, hour: 16, agent: 'Documentation Writer', model: 'claude-haiku-3-5', task: 'Generate API reference from source', outcome: 'success', duration_min: 6, files_changed: 3, retries: 0, convoy_id: 'demo-docs-update', tracker_issue: 'TASK-45' },
350
+ { dayIdx: 28, hour: 11, agent: 'Copywriter', model: 'claude-haiku-3-5', task: 'Update marketing copy for v2 features', outcome: 'success', duration_min: 7, files_changed: 2, retries: 0, tracker_issue: 'TASK-46' },
351
+ { dayIdx: 29, hour: 14, agent: 'SEO Specialist', model: 'claude-haiku-3-5', task: 'Add structured data and meta tags', outcome: 'success', duration_min: 9, files_changed: 4, retries: 0, tracker_issue: 'TASK-47' },
352
+ // Day 30-35: CI/CD
353
+ { dayIdx: 30, hour: 9, agent: 'Team Lead (OpenCastle)', model: 'claude-opus-4-6', task: 'Plan CI/CD pipeline setup', outcome: 'success', duration_min: 5, files_changed: 1, retries: 0, convoy_id: 'demo-deploy-ci', tracker_issue: 'TASK-48' },
354
+ { dayIdx: 31, hour: 10, agent: 'DevOps Expert', model: 'claude-sonnet-4-6', task: 'Design GitHub Actions workflow matrix', outcome: 'success', duration_min: 12, files_changed: 4, retries: 0, convoy_id: 'demo-deploy-ci', tracker_issue: 'TASK-49' },
355
+ { dayIdx: 32, hour: 11, agent: 'DevOps Expert', model: 'claude-sonnet-4-6', task: 'Configure secret rotation policy', outcome: 'partial', duration_min: 14, files_changed: 3, retries: 1, tracker_issue: 'TASK-50' },
356
+ { dayIdx: 33, hour: 15, agent: 'Security Expert', model: 'claude-sonnet-4-6', task: 'Review deployment security headers', outcome: 'success', duration_min: 11, files_changed: 2, retries: 0, tracker_issue: 'TASK-51' },
357
+ { dayIdx: 34, hour: 11, agent: 'Developer', model: 'claude-sonnet-4-6', task: 'Add smoke test after deployment', outcome: 'success', duration_min: 8, files_changed: 3, retries: 0, tracker_issue: 'TASK-52' },
358
+ { dayIdx: 35, hour: 10, agent: 'Reviewer', model: 'claude-haiku-3-5', task: 'Review CI config for security issues', outcome: 'success', duration_min: 7, files_changed: 0, retries: 0, tracker_issue: 'TASK-53' },
359
+ // Day 36-39: misc
360
+ { dayIdx: 36, hour: 9, agent: 'Developer', model: 'claude-sonnet-4-6', task: 'Resolve TypeScript strict mode errors', outcome: 'success', duration_min: 11, files_changed: 9, retries: 0, tracker_issue: 'TASK-54' },
361
+ { dayIdx: 36, hour: 14, agent: 'Testing Expert', model: 'claude-sonnet-4-6', task: 'Increase test coverage to 95%', outcome: 'success', duration_min: 19, files_changed: 12, retries: 1, tracker_issue: 'TASK-55', lessons_added: ['Mock SQLite in unit tests to avoid file system issues'] },
362
+ { dayIdx: 37, hour: 10, agent: 'Developer', model: 'claude-sonnet-4-6', task: 'Implement watch mode with cron triggers', outcome: 'success', duration_min: 28, files_changed: 6, retries: 0, tracker_issue: 'TASK-56' },
363
+ { dayIdx: 37, hour: 15, agent: 'UI/UX Expert', model: 'claude-opus-4-6', task: 'Refine responsive breakpoints', outcome: 'success', duration_min: 14, files_changed: 4, retries: 0, tracker_issue: 'TASK-57' },
364
+ { dayIdx: 38, hour: 8, agent: 'Team Lead (OpenCastle)', model: 'claude-opus-4-6', task: 'Orchestrate CI/CD deployment convoy', outcome: 'partial', duration_min: 20, files_changed: 0, retries: 0, convoy_id: 'demo-deploy-ci', tracker_issue: 'TASK-58' },
365
+ { dayIdx: 38, hour: 8, agent: 'DevOps Expert', model: 'claude-sonnet-4-6', task: 'Configure nx affected build caching', outcome: 'partial', duration_min: 18, files_changed: 5, retries: 0, convoy_id: 'demo-deploy-ci', tracker_issue: 'TASK-59' },
366
+ { dayIdx: 39, hour: 9, agent: 'Developer', model: 'claude-sonnet-4-6', task: 'Add agents CLI command for identity management', outcome: 'success', duration_min: 16, files_changed: 7, retries: 0, tracker_issue: 'TASK-60' },
367
+ { dayIdx: 39, hour: 11, agent: 'Reviewer', model: 'claude-haiku-3-5', task: 'Fast review – agents CLI', outcome: 'success', duration_min: 5, files_changed: 0, retries: 0, tracker_issue: 'TASK-61' },
368
+ ]
369
+
370
+ for (const s of sessions) {
371
+ emit({
372
+ type: 'session',
373
+ timestamp: dayTs(s.dayIdx, s.hour, (s.dayIdx * 7 + s.hour * 3) % 50),
374
+ agent: s.agent, model: s.model, task: s.task, outcome: s.outcome,
375
+ duration_min: s.duration_min, files_changed: s.files_changed, retries: s.retries,
376
+ ...(s.convoy_id ? { convoy_id: s.convoy_id } : {}),
377
+ ...(s.tracker_issue ? { tracker_issue: s.tracker_issue } : {}),
378
+ ...(s.lessons_added ? { lessons_added: s.lessons_added } : {}),
379
+ ...(s.discoveries ? { discoveries: s.discoveries } : {}),
87
380
  })
381
+ }
382
+
383
+ // ── Delegation records ────────────────────────────────────────────────
384
+ const delegations: Array<{
385
+ dayIdx: number; hour: number; agent: string; task: string
386
+ outcome: string; tier: string; mechanism: string; phase: number
387
+ convoy_id?: string; model: string
388
+ }> = [
389
+ { dayIdx: 1, hour: 10, agent: 'Architect', task: 'OAuth2 architecture review', outcome: 'success', tier: 'premium', mechanism: 'sub-agent', phase: 1, convoy_id: 'demo-auth-revamp', model: 'claude-opus-4-6' },
390
+ { dayIdx: 2, hour: 9, agent: 'Developer', task: 'Implement JWT middleware', outcome: 'success', tier: 'standard', mechanism: 'background', phase: 2, convoy_id: 'demo-auth-revamp', model: 'claude-sonnet-4-6' },
391
+ { dayIdx: 2, hour: 9, agent: 'Security Expert', task: 'RLS policies for session tokens', outcome: 'success', tier: 'standard', mechanism: 'background', phase: 2, convoy_id: 'demo-auth-revamp', model: 'claude-sonnet-4-6' },
392
+ { dayIdx: 2, hour: 10, agent: 'Testing Expert', task: 'Auth integration tests', outcome: 'success', tier: 'standard', mechanism: 'background', phase: 3, convoy_id: 'demo-auth-revamp', model: 'claude-sonnet-4-6' },
393
+ { dayIdx: 2, hour: 11, agent: 'Reviewer', task: 'Auth QA gate', outcome: 'success', tier: 'economy', mechanism: 'sub-agent', phase: 4, convoy_id: 'demo-auth-revamp', model: 'claude-haiku-3-5' },
394
+ { dayIdx: 4, hour: 11, agent: 'Security Expert', task: 'CSRF audit', outcome: 'partial', tier: 'standard', mechanism: 'sub-agent', phase: 2, model: 'claude-sonnet-4-6' },
395
+ { dayIdx: 6, hour: 14, agent: 'UI/UX Expert', task: 'Component system design', outcome: 'success', tier: 'premium', mechanism: 'sub-agent', phase: 1, convoy_id: 'demo-dashboard-ui', model: 'claude-opus-4-6' },
396
+ { dayIdx: 6, hour: 14, agent: 'Developer', task: 'KPI card implementation', outcome: 'success', tier: 'standard', mechanism: 'background', phase: 1, convoy_id: 'demo-dashboard-ui', model: 'claude-sonnet-4-6' },
397
+ { dayIdx: 6, hour: 15, agent: 'Developer', task: 'SVG chart components', outcome: 'success', tier: 'standard', mechanism: 'background', phase: 2, convoy_id: 'demo-dashboard-ui', model: 'claude-sonnet-4-6' },
398
+ { dayIdx: 6, hour: 15, agent: 'UI/UX Expert', task: 'CSS animation system', outcome: 'success', tier: 'premium', mechanism: 'background', phase: 2, convoy_id: 'demo-dashboard-ui', model: 'claude-opus-4-6' },
399
+ { dayIdx: 7, hour: 9, agent: 'UI/UX Expert', task: 'Accessibility audit', outcome: 'success', tier: 'premium', mechanism: 'sub-agent', phase: 3, convoy_id: 'demo-dashboard-ui', model: 'claude-opus-4-6' },
400
+ { dayIdx: 7, hour: 9, agent: 'Testing Expert', task: 'Visual regression tests', outcome: 'success', tier: 'standard', mechanism: 'background', phase: 3, convoy_id: 'demo-dashboard-ui', model: 'claude-sonnet-4-6' },
401
+ { dayIdx: 7, hour: 11, agent: 'Reviewer', task: 'Panel review – dashboard', outcome: 'success', tier: 'economy', mechanism: 'sub-agent', phase: 4, convoy_id: 'demo-dashboard-ui', model: 'claude-haiku-3-5' },
402
+ { dayIdx: 10, hour: 11, agent: 'API Designer', task: 'REST v2 route design', outcome: 'success', tier: 'standard', mechanism: 'sub-agent', phase: 1, convoy_id: 'demo-api-v2', model: 'claude-sonnet-4-6' },
403
+ { dayIdx: 11, hour: 16, agent: 'Developer', task: 'Rate limiting middleware', outcome: 'partial', tier: 'standard', mechanism: 'background', phase: 2, convoy_id: 'demo-api-v2', model: 'claude-sonnet-4-6' },
404
+ { dayIdx: 11, hour: 17, agent: 'Security Expert', task: 'Injection vulnerability scan', outcome: 'failed', tier: 'standard', mechanism: 'sub-agent', phase: 3, convoy_id: 'demo-api-v2', model: 'claude-sonnet-4-6' },
405
+ { dayIdx: 12, hour: 9, agent: 'Developer', task: 'Patch SQL injection', outcome: 'success', tier: 'standard', mechanism: 'sub-agent', phase: 2, model: 'claude-sonnet-4-6' },
406
+ { dayIdx: 15, hour: 11, agent: 'Performance Expert', task: 'Bundle profiling', outcome: 'success', tier: 'standard', mechanism: 'sub-agent', phase: 1, convoy_id: 'demo-perf-opt', model: 'claude-sonnet-4-6' },
407
+ { dayIdx: 16, hour: 10, agent: 'Developer', task: 'Code-split chart library', outcome: 'success', tier: 'standard', mechanism: 'background', phase: 2, convoy_id: 'demo-perf-opt', model: 'claude-sonnet-4-6' },
408
+ { dayIdx: 16, hour: 10, agent: 'Developer', task: 'Image lazy-loading', outcome: 'success', tier: 'standard', mechanism: 'background', phase: 2, convoy_id: 'demo-perf-opt', model: 'claude-sonnet-4-6' },
409
+ { dayIdx: 16, hour: 11, agent: 'Performance Expert', task: 'Core Web Vitals validation', outcome: 'success', tier: 'standard', mechanism: 'sub-agent', phase: 3, convoy_id: 'demo-perf-opt', model: 'claude-sonnet-4-6' },
410
+ { dayIdx: 17, hour: 14, agent: 'Reviewer', task: 'Performance PR review', outcome: 'success', tier: 'utility', mechanism: 'sub-agent', phase: 4, model: 'claude-haiku-3-5' },
411
+ { dayIdx: 20, hour: 11, agent: 'Data Expert', task: 'ndjson schema design', outcome: 'success', tier: 'standard', mechanism: 'sub-agent', phase: 1, convoy_id: 'demo-data-pipeline', model: 'claude-sonnet-4-6' },
412
+ { dayIdx: 21, hour: 13, agent: 'Data Expert', task: 'ETL implementation', outcome: 'success', tier: 'standard', mechanism: 'background', phase: 2, convoy_id: 'demo-data-pipeline', model: 'claude-sonnet-4-6' },
413
+ { dayIdx: 21, hour: 14, agent: 'Testing Expert', task: 'ETL test suite', outcome: 'success', tier: 'economy', mechanism: 'background', phase: 3, convoy_id: 'demo-data-pipeline', model: 'claude-haiku-3-5' },
414
+ { dayIdx: 27, hour: 15, agent: 'Documentation Writer', task: 'README and ARCHITECTURE update', outcome: 'success', tier: 'economy', mechanism: 'sub-agent', phase: 1, convoy_id: 'demo-docs-update', model: 'claude-haiku-3-5' },
415
+ { dayIdx: 27, hour: 16, agent: 'Documentation Writer', task: 'API reference generation', outcome: 'success', tier: 'economy', mechanism: 'sub-agent', phase: 2, convoy_id: 'demo-docs-update', model: 'claude-haiku-3-5' },
416
+ { dayIdx: 31, hour: 10, agent: 'DevOps Expert', task: 'GitHub Actions workflow', outcome: 'success', tier: 'standard', mechanism: 'sub-agent', phase: 1, convoy_id: 'demo-deploy-ci', model: 'claude-sonnet-4-6' },
417
+ { dayIdx: 33, hour: 15, agent: 'Security Expert', task: 'Deployment security headers review', outcome: 'success', tier: 'standard', mechanism: 'sub-agent', phase: 2, model: 'claude-sonnet-4-6' },
418
+ { dayIdx: 35, hour: 10, agent: 'Reviewer', task: 'CI config security review', outcome: 'success', tier: 'utility', mechanism: 'sub-agent', phase: 3, model: 'claude-haiku-3-5' },
419
+ { dayIdx: 36, hour: 9, agent: 'Developer', task: 'TypeScript strict mode fixes', outcome: 'success', tier: 'standard', mechanism: 'sub-agent', phase: 2, model: 'claude-sonnet-4-6' },
420
+ { dayIdx: 36, hour: 14, agent: 'Testing Expert', task: 'Increase test coverage', outcome: 'success', tier: 'standard', mechanism: 'background', phase: 3, model: 'claude-sonnet-4-6' },
421
+ { dayIdx: 37, hour: 10, agent: 'Developer', task: 'Watch mode implementation', outcome: 'success', tier: 'standard', mechanism: 'background', phase: 2, model: 'claude-sonnet-4-6' },
422
+ { dayIdx: 38, hour: 8, agent: 'DevOps Expert', task: 'nx build caching config', outcome: 'partial', tier: 'standard', mechanism: 'background', phase: 2, convoy_id: 'demo-deploy-ci', model: 'claude-sonnet-4-6' },
423
+ { dayIdx: 39, hour: 9, agent: 'Developer', task: 'Agents CLI command', outcome: 'success', tier: 'standard', mechanism: 'sub-agent', phase: 2, model: 'claude-sonnet-4-6' },
424
+ { dayIdx: 39, hour: 11, agent: 'Reviewer', task: 'Agents CLI fast review', outcome: 'success', tier: 'utility', mechanism: 'sub-agent', phase: 4, model: 'claude-haiku-3-5' },
425
+ ]
88
426
 
89
- store.insertEvent({ convoy_id: 'demo-convoy-1', task_id: 'task-1-a', worker_id: null, type: 'task_started', data: null, created_at: T1A_START })
90
- store.insertEvent({ convoy_id: 'demo-convoy-1', task_id: 'task-1-a', worker_id: null, type: 'task_done', data: null, created_at: T1A_END })
91
- store.insertEvent({ convoy_id: 'demo-convoy-1', task_id: 'task-1-b', worker_id: null, type: 'task_started', data: null, created_at: T1B_START })
92
- store.insertEvent({ convoy_id: 'demo-convoy-1', task_id: 'task-1-b', worker_id: null, type: 'task_done', data: null, created_at: T1B_END })
93
-
94
- // --- Convoy 2: still running ---
95
- store.insertConvoy({
96
- id: 'demo-convoy-2',
97
- name: 'Demo Convoy Beta',
98
- spec_hash: 'h2',
99
- status: 'running',
100
- branch: 'feature/x',
101
- created_at: C2_START,
102
- spec_yaml: 'name: demo-beta',
427
+ for (const d of delegations) {
428
+ emit({
429
+ type: 'delegation',
430
+ timestamp: dayTs(d.dayIdx, d.hour, (d.dayIdx * 5 + d.hour) % 55 + 2),
431
+ agent: d.agent, task: d.task, outcome: d.outcome,
432
+ tier: d.tier, mechanism: d.mechanism, phase: d.phase, model: d.model,
433
+ ...(d.convoy_id ? { convoy_id: d.convoy_id } : {}),
103
434
  })
104
- store.updateConvoyStatus('demo-convoy-2', 'running', { started_at: C2_START })
105
-
106
- store.insertTask({
107
- id: 'task-2-a',
108
- convoy_id: 'demo-convoy-2',
109
- phase: 1,
110
- prompt: 'Refactor database layer',
111
- agent: 'developer',
112
- adapter: 'noop',
113
- model: 'claude-sonnet-4-6',
114
- timeout_ms: 60000,
115
- status: 'running',
116
- retries: 0,
117
- max_retries: 3,
118
- files: null,
119
- depends_on: null,
120
- gates: null,
121
- outputs: null,
122
- inputs: null,
435
+ }
436
+
437
+ // ── Panel records ───────────────────────────────────────────────────
438
+ const panels = [
439
+ { dayIdx: 2, hour: 11, key: 'auth-security-panel', verdict: 'pass', pass: 3, block: 0, mustFix: 0, shouldFix: 1, attempt: 1, convoyId: 'demo-auth-revamp' },
440
+ { dayIdx: 7, hour: 10, key: 'dashboard-ui-panel', verdict: 'block', pass: 2, block: 1, mustFix: 0, shouldFix: 3, attempt: 1, convoyId: 'demo-dashboard-ui' },
441
+ { dayIdx: 7, hour: 11, key: 'dashboard-ui-panel', verdict: 'pass', pass: 3, block: 0, mustFix: 0, shouldFix: 2, attempt: 2, convoyId: 'demo-dashboard-ui' },
442
+ { dayIdx: 11, hour: 17, key: 'api-security-panel', verdict: 'block', pass: 1, block: 2, mustFix: 2, shouldFix: 1, attempt: 1, convoyId: 'demo-api-v2' },
443
+ { dayIdx: 13, hour: 15, key: 'api-security-panel-retry', verdict: 'pass', pass: 3, block: 0, mustFix: 0, shouldFix: 1, attempt: 2, convoyId: 'demo-api-v2' },
444
+ { dayIdx: 16, hour: 12, key: 'perf-quality-panel', verdict: 'pass', pass: 3, block: 0, mustFix: 0, shouldFix: 0, attempt: 1, convoyId: 'demo-perf-opt' },
445
+ { dayIdx: 21, hour: 15, key: 'etl-review-panel', verdict: 'pass', pass: 2, block: 1, mustFix: 0, shouldFix: 2, attempt: 1, convoyId: 'demo-data-pipeline' },
446
+ { dayIdx: 27, hour: 16, key: 'docs-panel', verdict: 'pass', pass: 3, block: 0, mustFix: 0, shouldFix: 1, attempt: 1, convoyId: 'demo-docs-update' },
447
+ { dayIdx: 33, hour: 16, key: 'ci-security-panel', verdict: 'pass', pass: 2, block: 1, mustFix: 1, shouldFix: 0, attempt: 1 },
448
+ { dayIdx: 38, hour: 13, key: 'deploy-final-panel', verdict: 'block', pass: 1, block: 2, mustFix: 1, shouldFix: 2, attempt: 1, convoyId: 'demo-deploy-ci' },
449
+ ]
450
+
451
+ for (const p of panels) {
452
+ emit({
453
+ type: 'panel',
454
+ timestamp: dayTs(p.dayIdx, p.hour, 15),
455
+ panel_key: p.key, verdict: p.verdict,
456
+ pass_count: p.pass, block_count: p.block,
457
+ must_fix: p.mustFix, should_fix: p.shouldFix,
458
+ reviewer_model: 'claude-opus-4-6',
459
+ attempt: p.attempt, artifacts_count: p.verdict === 'pass' ? p.pass + 1 : 0,
460
+ ...(p.convoyId ? { convoy_id: p.convoyId } : {}),
123
461
  })
124
- store.updateTaskStatus('task-2-a', 'demo-convoy-2', 'running', { started_at: T2A_START })
462
+ }
125
463
 
126
- store.insertEvent({ convoy_id: 'demo-convoy-2', task_id: 'task-2-a', worker_id: null, type: 'task_started', data: null, created_at: T2A_START })
464
+ // ── Review records ──────────────────────────────────────────────────
465
+ const reviews = [
466
+ { dayIdx: 2, hour: 11, agent: 'Reviewer', verdict: 'pass', critical: 0, major: 1, minor: 2, confidence: 'high', attempt: 1, escalated: false, issue: 'TASK-07', convoyId: 'demo-auth-revamp' },
467
+ { dayIdx: 7, hour: 10, agent: 'Reviewer', verdict: 'block', critical: 0, major: 2, minor: 3, confidence: 'medium', attempt: 1, escalated: true, issue: 'TASK-18', convoyId: 'demo-dashboard-ui' },
468
+ { dayIdx: 7, hour: 11, agent: 'Reviewer', verdict: 'pass', critical: 0, major: 0, minor: 2, confidence: 'high', attempt: 2, escalated: false, issue: 'TASK-18', convoyId: 'demo-dashboard-ui' },
469
+ { dayIdx: 11, hour: 17, agent: 'Reviewer', verdict: 'block', critical: 2, major: 1, minor: 0, confidence: 'high', attempt: 1, escalated: true, issue: 'TASK-24', convoyId: 'demo-api-v2' },
470
+ { dayIdx: 17, hour: 14, agent: 'Reviewer', verdict: 'pass', critical: 0, major: 0, minor: 1, confidence: 'high', attempt: 1, escalated: false, issue: 'TASK-33' },
471
+ { dayIdx: 22, hour: 15, agent: 'Reviewer', verdict: 'pass', critical: 0, major: 0, minor: 0, confidence: 'high', attempt: 1, escalated: false, issue: 'TASK-40', convoyId: 'demo-data-pipeline' },
472
+ { dayIdx: 35, hour: 10, agent: 'Reviewer', verdict: 'pass', critical: 0, major: 1, minor: 1, confidence: 'medium', attempt: 1, escalated: false, issue: 'TASK-53' },
473
+ { dayIdx: 39, hour: 11, agent: 'Reviewer', verdict: 'pass', critical: 0, major: 0, minor: 1, confidence: 'high', attempt: 1, escalated: false, issue: 'TASK-61' },
474
+ ]
127
475
 
128
- console.log(`Created demo convoy DB at ${dbPath}`)
129
- } finally {
130
- store.close()
476
+ for (const r of reviews) {
477
+ emit({
478
+ type: 'review',
479
+ timestamp: dayTs(r.dayIdx, r.hour, 25),
480
+ agent: r.agent, verdict: r.verdict,
481
+ issues_critical: r.critical, issues_major: r.major, issues_minor: r.minor,
482
+ confidence: r.confidence, attempt: r.attempt, escalated: r.escalated,
483
+ tracker_issue: r.issue,
484
+ ...(r.convoyId ? { convoy_id: r.convoyId } : {}),
485
+ })
131
486
  }
487
+
488
+ writeFileSync(eventsPath, lines.join('\n') + '\n', 'utf8')
489
+ console.log(`Generated demo events at ${eventsPath} (${lines.length} records)`)
132
490
  }
133
491
 
134
492
  // CLI entry (ESM-safe)
135
493
  const __filename = fileURLToPath(import.meta.url)
136
494
  if (process.argv[1] != null && resolve(process.argv[1]) === __filename) {
137
495
  const outArgIndex = process.argv.indexOf('--out')
138
- const out = outArgIndex >= 0 && process.argv[outArgIndex + 1] ? process.argv[outArgIndex + 1] : '.opencastle/convoy-demo.db'
139
- createDemoDb(out).catch(err => { console.error('Failed to create demo DB:', (err as Error).message); process.exit(1) })
496
+ const out = outArgIndex >= 0 && process.argv[outArgIndex + 1]
497
+ ? process.argv[outArgIndex + 1]
498
+ : '.opencastle/convoy-demo.db'
499
+ const eventsArgIndex = process.argv.indexOf('--events-out')
500
+ const eventsOut = eventsArgIndex >= 0 && process.argv[eventsArgIndex + 1]
501
+ ? process.argv[eventsArgIndex + 1]
502
+ : '.opencastle/convoy-demo.events.ndjson'
503
+ createDemoDb(out, eventsOut).catch(err => {
504
+ console.error('Failed to create demo DB:', (err as Error).message)
505
+ process.exit(1)
506
+ })
140
507
  }