wogiflow 1.0.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 (221) hide show
  1. package/.workflow/agents/reviewer.md +81 -0
  2. package/.workflow/agents/security.md +94 -0
  3. package/.workflow/agents/story-writer.md +58 -0
  4. package/.workflow/bridges/base-bridge.js +395 -0
  5. package/.workflow/bridges/claude-bridge.js +434 -0
  6. package/.workflow/bridges/index.js +130 -0
  7. package/.workflow/lib/assumption-detector.js +481 -0
  8. package/.workflow/lib/config-substitution.js +371 -0
  9. package/.workflow/lib/failure-categories.js +478 -0
  10. package/.workflow/state/app-map.md.template +15 -0
  11. package/.workflow/state/architecture.md.template +24 -0
  12. package/.workflow/state/component-index.json.template +5 -0
  13. package/.workflow/state/decisions.md.template +15 -0
  14. package/.workflow/state/feedback-patterns.md.template +9 -0
  15. package/.workflow/state/knowledge-sync.json.template +6 -0
  16. package/.workflow/state/progress.md.template +14 -0
  17. package/.workflow/state/ready.json.template +7 -0
  18. package/.workflow/state/request-log.md.template +14 -0
  19. package/.workflow/state/session-state.json.template +11 -0
  20. package/.workflow/state/stack.md.template +33 -0
  21. package/.workflow/state/testing.md.template +36 -0
  22. package/.workflow/templates/claude-md.hbs +257 -0
  23. package/.workflow/templates/correction-report.md +67 -0
  24. package/.workflow/templates/gemini-md.hbs +52 -0
  25. package/README.md +1802 -0
  26. package/bin/flow +205 -0
  27. package/lib/index.js +33 -0
  28. package/lib/installer.js +467 -0
  29. package/lib/release-channel.js +269 -0
  30. package/lib/skill-registry.js +526 -0
  31. package/lib/upgrader.js +401 -0
  32. package/lib/utils.js +305 -0
  33. package/package.json +64 -0
  34. package/scripts/flow +985 -0
  35. package/scripts/flow-adaptive-learning.js +1259 -0
  36. package/scripts/flow-aggregate.js +488 -0
  37. package/scripts/flow-archive +133 -0
  38. package/scripts/flow-auto-context.js +1015 -0
  39. package/scripts/flow-auto-learn.js +615 -0
  40. package/scripts/flow-bridge.js +223 -0
  41. package/scripts/flow-browser-suggest.js +316 -0
  42. package/scripts/flow-bug.js +247 -0
  43. package/scripts/flow-cascade.js +711 -0
  44. package/scripts/flow-changelog +85 -0
  45. package/scripts/flow-checkpoint.js +483 -0
  46. package/scripts/flow-cli.js +403 -0
  47. package/scripts/flow-code-intelligence.js +760 -0
  48. package/scripts/flow-complexity.js +502 -0
  49. package/scripts/flow-config-set.js +152 -0
  50. package/scripts/flow-constants.js +157 -0
  51. package/scripts/flow-context +152 -0
  52. package/scripts/flow-context-init.js +482 -0
  53. package/scripts/flow-context-monitor.js +384 -0
  54. package/scripts/flow-context-scoring.js +886 -0
  55. package/scripts/flow-correct.js +458 -0
  56. package/scripts/flow-damage-control.js +985 -0
  57. package/scripts/flow-deps +101 -0
  58. package/scripts/flow-diff.js +700 -0
  59. package/scripts/flow-done +151 -0
  60. package/scripts/flow-done.js +489 -0
  61. package/scripts/flow-durable-session.js +1541 -0
  62. package/scripts/flow-entropy-monitor.js +345 -0
  63. package/scripts/flow-export-profile +349 -0
  64. package/scripts/flow-export-scanner.js +1046 -0
  65. package/scripts/flow-figma-confirm.js +400 -0
  66. package/scripts/flow-figma-extract.js +496 -0
  67. package/scripts/flow-figma-generate.js +683 -0
  68. package/scripts/flow-figma-index.js +909 -0
  69. package/scripts/flow-figma-match.js +617 -0
  70. package/scripts/flow-figma-mcp-server.js +518 -0
  71. package/scripts/flow-figma-pipeline.js +414 -0
  72. package/scripts/flow-file-ops.js +301 -0
  73. package/scripts/flow-gate-confidence.js +825 -0
  74. package/scripts/flow-guided-edit.js +659 -0
  75. package/scripts/flow-health +185 -0
  76. package/scripts/flow-health.js +413 -0
  77. package/scripts/flow-hooks.js +556 -0
  78. package/scripts/flow-http-client.js +249 -0
  79. package/scripts/flow-hybrid-detect.js +167 -0
  80. package/scripts/flow-hybrid-interactive.js +591 -0
  81. package/scripts/flow-hybrid-test.js +152 -0
  82. package/scripts/flow-import-profile +439 -0
  83. package/scripts/flow-init +253 -0
  84. package/scripts/flow-instruction-richness.js +827 -0
  85. package/scripts/flow-jira-integration.js +579 -0
  86. package/scripts/flow-knowledge-router.js +522 -0
  87. package/scripts/flow-knowledge-sync.js +589 -0
  88. package/scripts/flow-linear-integration.js +631 -0
  89. package/scripts/flow-links.js +774 -0
  90. package/scripts/flow-log-manager.js +559 -0
  91. package/scripts/flow-loop-enforcer.js +1246 -0
  92. package/scripts/flow-loop-retry-learning.js +630 -0
  93. package/scripts/flow-lsp.js +923 -0
  94. package/scripts/flow-map-index +348 -0
  95. package/scripts/flow-map-sync +201 -0
  96. package/scripts/flow-memory-blocks.js +668 -0
  97. package/scripts/flow-memory-compactor.js +350 -0
  98. package/scripts/flow-memory-db.js +1110 -0
  99. package/scripts/flow-memory-sync.js +484 -0
  100. package/scripts/flow-metrics.js +353 -0
  101. package/scripts/flow-migrate-ids.js +370 -0
  102. package/scripts/flow-model-adapter.js +802 -0
  103. package/scripts/flow-model-router.js +884 -0
  104. package/scripts/flow-models.js +1231 -0
  105. package/scripts/flow-morning.js +517 -0
  106. package/scripts/flow-multi-approach.js +660 -0
  107. package/scripts/flow-new-feature +86 -0
  108. package/scripts/flow-onboard +1042 -0
  109. package/scripts/flow-orchestrate-llm.js +459 -0
  110. package/scripts/flow-orchestrate.js +3592 -0
  111. package/scripts/flow-output.js +123 -0
  112. package/scripts/flow-parallel-detector.js +399 -0
  113. package/scripts/flow-parallel-dispatch.js +987 -0
  114. package/scripts/flow-parallel.js +428 -0
  115. package/scripts/flow-pattern-enforcer.js +600 -0
  116. package/scripts/flow-prd-manager.js +282 -0
  117. package/scripts/flow-progress.js +323 -0
  118. package/scripts/flow-project-analyzer.js +975 -0
  119. package/scripts/flow-prompt-composer.js +487 -0
  120. package/scripts/flow-providers.js +1381 -0
  121. package/scripts/flow-queue.js +308 -0
  122. package/scripts/flow-ready +82 -0
  123. package/scripts/flow-ready.js +189 -0
  124. package/scripts/flow-regression.js +396 -0
  125. package/scripts/flow-response-parser.js +450 -0
  126. package/scripts/flow-resume.js +284 -0
  127. package/scripts/flow-rules-sync.js +439 -0
  128. package/scripts/flow-run-trace.js +718 -0
  129. package/scripts/flow-safety.js +587 -0
  130. package/scripts/flow-search +104 -0
  131. package/scripts/flow-security.js +481 -0
  132. package/scripts/flow-session-end +106 -0
  133. package/scripts/flow-session-end.js +437 -0
  134. package/scripts/flow-session-state.js +671 -0
  135. package/scripts/flow-setup-hooks +216 -0
  136. package/scripts/flow-setup-hooks.js +377 -0
  137. package/scripts/flow-skill-create.js +329 -0
  138. package/scripts/flow-skill-creator.js +572 -0
  139. package/scripts/flow-skill-generator.js +1046 -0
  140. package/scripts/flow-skill-learn.js +880 -0
  141. package/scripts/flow-skill-matcher.js +578 -0
  142. package/scripts/flow-spec-generator.js +820 -0
  143. package/scripts/flow-stack-wizard.js +895 -0
  144. package/scripts/flow-standup +162 -0
  145. package/scripts/flow-start +74 -0
  146. package/scripts/flow-start.js +235 -0
  147. package/scripts/flow-status +110 -0
  148. package/scripts/flow-status.js +301 -0
  149. package/scripts/flow-step-browser.js +83 -0
  150. package/scripts/flow-step-changelog.js +217 -0
  151. package/scripts/flow-step-comments.js +306 -0
  152. package/scripts/flow-step-complexity.js +234 -0
  153. package/scripts/flow-step-coverage.js +218 -0
  154. package/scripts/flow-step-knowledge.js +193 -0
  155. package/scripts/flow-step-pr-tests.js +364 -0
  156. package/scripts/flow-step-regression.js +89 -0
  157. package/scripts/flow-step-review.js +516 -0
  158. package/scripts/flow-step-security.js +162 -0
  159. package/scripts/flow-step-silent-failures.js +290 -0
  160. package/scripts/flow-step-simplifier.js +346 -0
  161. package/scripts/flow-story +105 -0
  162. package/scripts/flow-story.js +500 -0
  163. package/scripts/flow-suspend.js +252 -0
  164. package/scripts/flow-sync-daemon.js +654 -0
  165. package/scripts/flow-task-analyzer.js +606 -0
  166. package/scripts/flow-team-dashboard.js +748 -0
  167. package/scripts/flow-team-sync.js +752 -0
  168. package/scripts/flow-team.js +977 -0
  169. package/scripts/flow-tech-options.js +528 -0
  170. package/scripts/flow-templates.js +812 -0
  171. package/scripts/flow-tiered-learning.js +728 -0
  172. package/scripts/flow-trace +204 -0
  173. package/scripts/flow-transcript-chunking.js +1106 -0
  174. package/scripts/flow-transcript-digest.js +7918 -0
  175. package/scripts/flow-transcript-language.js +465 -0
  176. package/scripts/flow-transcript-parsing.js +1085 -0
  177. package/scripts/flow-transcript-stories.js +2194 -0
  178. package/scripts/flow-update-map +224 -0
  179. package/scripts/flow-utils.js +2242 -0
  180. package/scripts/flow-verification.js +644 -0
  181. package/scripts/flow-verify.js +1177 -0
  182. package/scripts/flow-voice-input.js +638 -0
  183. package/scripts/flow-watch +168 -0
  184. package/scripts/flow-workflow-steps.js +521 -0
  185. package/scripts/flow-workflow.js +1029 -0
  186. package/scripts/flow-worktree.js +489 -0
  187. package/scripts/hooks/adapters/base-adapter.js +102 -0
  188. package/scripts/hooks/adapters/claude-code.js +359 -0
  189. package/scripts/hooks/adapters/index.js +79 -0
  190. package/scripts/hooks/core/component-check.js +341 -0
  191. package/scripts/hooks/core/index.js +35 -0
  192. package/scripts/hooks/core/loop-check.js +241 -0
  193. package/scripts/hooks/core/session-context.js +294 -0
  194. package/scripts/hooks/core/task-gate.js +177 -0
  195. package/scripts/hooks/core/validation.js +230 -0
  196. package/scripts/hooks/entry/claude-code/post-tool-use.js +65 -0
  197. package/scripts/hooks/entry/claude-code/pre-tool-use.js +89 -0
  198. package/scripts/hooks/entry/claude-code/session-end.js +87 -0
  199. package/scripts/hooks/entry/claude-code/session-start.js +46 -0
  200. package/scripts/hooks/entry/claude-code/stop.js +43 -0
  201. package/scripts/postinstall.js +139 -0
  202. package/templates/browser-test-flow.json +56 -0
  203. package/templates/bug-report.md +43 -0
  204. package/templates/component-detail.md +42 -0
  205. package/templates/component.stories.tsx +49 -0
  206. package/templates/context/constraints.md +83 -0
  207. package/templates/context/conventions.md +177 -0
  208. package/templates/context/stack.md +60 -0
  209. package/templates/correction-report.md +90 -0
  210. package/templates/feature-proposal.md +35 -0
  211. package/templates/hybrid/_base.md +254 -0
  212. package/templates/hybrid/_patterns.md +45 -0
  213. package/templates/hybrid/create-component.md +127 -0
  214. package/templates/hybrid/create-file.md +56 -0
  215. package/templates/hybrid/create-hook.md +145 -0
  216. package/templates/hybrid/create-service.md +70 -0
  217. package/templates/hybrid/fix-bug.md +33 -0
  218. package/templates/hybrid/modify-file.md +55 -0
  219. package/templates/story.md +68 -0
  220. package/templates/task.json +56 -0
  221. package/templates/trace.md +69 -0
@@ -0,0 +1,748 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Wogi Flow - Team Observability Dashboard
5
+ *
6
+ * Local web dashboard for viewing task progress, execution history,
7
+ * and team-wide metrics.
8
+ *
9
+ * Part of Phase 6: Team & Integrations
10
+ *
11
+ * Usage:
12
+ * flow team dashboard # Start dashboard on port 3850
13
+ * flow team dashboard --port 8080 # Custom port
14
+ * flow team dashboard --open # Auto-open in browser
15
+ */
16
+
17
+ const http = require('http');
18
+ const fs = require('fs');
19
+ const path = require('path');
20
+ const { execSync, execFile } = require('child_process');
21
+ const {
22
+ PROJECT_ROOT,
23
+ STATE_DIR,
24
+ parseFlags,
25
+ color,
26
+ info,
27
+ warn,
28
+ error,
29
+ fileExists,
30
+ safeJsonParse,
31
+ getConfig
32
+ } = require('./flow-utils');
33
+
34
+ // ============================================================
35
+ // Constants
36
+ // ============================================================
37
+
38
+ const DEFAULT_PORT = 3850;
39
+ const MIN_PORT = 1;
40
+ const MAX_PORT = 65535;
41
+ const READY_PATH = path.join(STATE_DIR, 'ready.json');
42
+ const REQUEST_LOG_PATH = path.join(STATE_DIR, 'request-log.md');
43
+ const PROGRESS_PATH = path.join(STATE_DIR, 'progress.md');
44
+ const RUN_HISTORY_PATH = path.join(STATE_DIR, 'run-history.json');
45
+ const TEAM_STATE_PATH = path.join(STATE_DIR, 'team-state.json');
46
+
47
+ // ============================================================
48
+ // Data Loaders
49
+ // ============================================================
50
+
51
+ /**
52
+ * Load ready.json for task status
53
+ */
54
+ function loadTaskStatus() {
55
+ const data = safeJsonParse(READY_PATH);
56
+ if (!data) {
57
+ return {
58
+ ready: [],
59
+ inProgress: [],
60
+ blocked: [],
61
+ recentlyCompleted: []
62
+ };
63
+ }
64
+ return data;
65
+ }
66
+
67
+ /**
68
+ * Load run history for execution tracking
69
+ */
70
+ function loadRunHistory() {
71
+ const data = safeJsonParse(RUN_HISTORY_PATH);
72
+ if (!data) {
73
+ return { runs: [] };
74
+ }
75
+ return data;
76
+ }
77
+
78
+ /**
79
+ * Load team state
80
+ */
81
+ function loadTeamState() {
82
+ const data = safeJsonParse(TEAM_STATE_PATH);
83
+ if (!data) {
84
+ return { loggedIn: false };
85
+ }
86
+ return data;
87
+ }
88
+
89
+ /**
90
+ * Parse request-log.md for recent activity
91
+ */
92
+ function parseRequestLog() {
93
+ if (!fileExists(REQUEST_LOG_PATH)) {
94
+ return [];
95
+ }
96
+
97
+ try {
98
+ const content = fs.readFileSync(REQUEST_LOG_PATH, 'utf-8');
99
+ const entries = [];
100
+ // Use [\s\S] instead of . to match newlines in multiline fields
101
+ const entryPattern = /### R-(\d+)\s*\|\s*([^\n]+)\n\*\*Type\*\*:\s*([^\n]+)\n\*\*Tags\*\*:\s*([^\n]+)\n\*\*Request\*\*:\s*"([^"]+)"\n\*\*Result\*\*:\s*([\s\S]+?)\n\*\*Files\*\*:\s*([\s\S]+?)(?=\n###|\n*$)/g;
102
+
103
+ let match;
104
+ while ((match = entryPattern.exec(content)) !== null) {
105
+ // Validate all capture groups exist before creating entry
106
+ if (match[1] && match[2] && match[3] && match[4] && match[5]) {
107
+ entries.push({
108
+ id: `R-${match[1]}`,
109
+ timestamp: match[2].trim(),
110
+ type: match[3].trim(),
111
+ tags: match[4].trim().split(/\s+/).filter(t => t.startsWith('#')),
112
+ request: match[5].trim(),
113
+ result: (match[6] || '').trim(),
114
+ files: (match[7] || '').trim()
115
+ });
116
+ }
117
+ }
118
+
119
+ return entries.slice(-50); // Last 50 entries
120
+ } catch (err) {
121
+ return [];
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Get project statistics
127
+ */
128
+ function getProjectStats() {
129
+ const tasks = loadTaskStatus();
130
+ const runHistory = loadRunHistory();
131
+ const config = getConfig();
132
+
133
+ const totalCompleted = tasks.recentlyCompleted?.length || 0;
134
+ const totalRuns = runHistory.runs?.length || 0;
135
+ // Use optional chaining on filter result to prevent null reference errors
136
+ const successfulRuns = runHistory.runs?.filter(r => r.status === 'success')?.length || 0;
137
+
138
+ return {
139
+ tasksReady: tasks.ready?.length || 0,
140
+ tasksInProgress: tasks.inProgress?.length || 0,
141
+ tasksBlocked: tasks.blocked?.length || 0,
142
+ tasksCompleted: totalCompleted,
143
+ totalRuns,
144
+ successRate: totalRuns > 0 ? ((successfulRuns / totalRuns) * 100).toFixed(1) : 0,
145
+ projectName: config?.projectName || path.basename(PROJECT_ROOT),
146
+ lastUpdated: tasks.lastUpdated || new Date().toISOString()
147
+ };
148
+ }
149
+
150
+ /**
151
+ * Get git branch and recent commits
152
+ */
153
+ function getGitInfo() {
154
+ try {
155
+ const branch = execSync('git branch --show-current', { encoding: 'utf-8', cwd: PROJECT_ROOT }).trim();
156
+ const commits = execSync('git log --oneline -10', { encoding: 'utf-8', cwd: PROJECT_ROOT })
157
+ .trim()
158
+ .split('\n')
159
+ .map(line => {
160
+ const [hash, ...msgParts] = line.split(' ');
161
+ return { hash, message: msgParts.join(' ') };
162
+ });
163
+
164
+ return { branch, commits };
165
+ } catch (err) {
166
+ return { branch: 'unknown', commits: [] };
167
+ }
168
+ }
169
+
170
+ // ============================================================
171
+ // Dashboard HTML
172
+ // ============================================================
173
+
174
+ function getDashboardHTML() {
175
+ return `<!DOCTYPE html>
176
+ <html lang="en">
177
+ <head>
178
+ <meta charset="UTF-8">
179
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
180
+ <title>Wogi Flow Dashboard</title>
181
+ <style>
182
+ :root {
183
+ --bg-primary: #0d1117;
184
+ --bg-secondary: #161b22;
185
+ --bg-tertiary: #21262d;
186
+ --text-primary: #c9d1d9;
187
+ --text-secondary: #8b949e;
188
+ --border: #30363d;
189
+ --accent-green: #3fb950;
190
+ --accent-yellow: #d29922;
191
+ --accent-red: #f85149;
192
+ --accent-blue: #58a6ff;
193
+ --accent-purple: #a371f7;
194
+ }
195
+ * { box-sizing: border-box; margin: 0; padding: 0; }
196
+ body {
197
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
198
+ background: var(--bg-primary);
199
+ color: var(--text-primary);
200
+ line-height: 1.5;
201
+ }
202
+ .container { max-width: 1400px; margin: 0 auto; padding: 20px; }
203
+ header {
204
+ display: flex;
205
+ justify-content: space-between;
206
+ align-items: center;
207
+ padding: 16px 0;
208
+ border-bottom: 1px solid var(--border);
209
+ margin-bottom: 24px;
210
+ }
211
+ h1 { font-size: 24px; font-weight: 600; }
212
+ .badge {
213
+ display: inline-block;
214
+ padding: 4px 12px;
215
+ border-radius: 20px;
216
+ font-size: 12px;
217
+ font-weight: 500;
218
+ }
219
+ .badge-success { background: rgba(63, 185, 80, 0.2); color: var(--accent-green); }
220
+ .badge-warning { background: rgba(210, 153, 34, 0.2); color: var(--accent-yellow); }
221
+ .badge-danger { background: rgba(248, 81, 73, 0.2); color: var(--accent-red); }
222
+ .badge-info { background: rgba(88, 166, 255, 0.2); color: var(--accent-blue); }
223
+
224
+ .stats-grid {
225
+ display: grid;
226
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
227
+ gap: 16px;
228
+ margin-bottom: 24px;
229
+ }
230
+ .stat-card {
231
+ background: var(--bg-secondary);
232
+ border: 1px solid var(--border);
233
+ border-radius: 8px;
234
+ padding: 16px;
235
+ }
236
+ .stat-card h3 {
237
+ font-size: 12px;
238
+ text-transform: uppercase;
239
+ color: var(--text-secondary);
240
+ margin-bottom: 8px;
241
+ }
242
+ .stat-card .value {
243
+ font-size: 32px;
244
+ font-weight: 600;
245
+ }
246
+ .stat-card .subtext {
247
+ font-size: 12px;
248
+ color: var(--text-secondary);
249
+ margin-top: 4px;
250
+ }
251
+
252
+ .section {
253
+ background: var(--bg-secondary);
254
+ border: 1px solid var(--border);
255
+ border-radius: 8px;
256
+ margin-bottom: 24px;
257
+ }
258
+ .section-header {
259
+ padding: 16px;
260
+ border-bottom: 1px solid var(--border);
261
+ display: flex;
262
+ justify-content: space-between;
263
+ align-items: center;
264
+ }
265
+ .section-header h2 {
266
+ font-size: 16px;
267
+ font-weight: 600;
268
+ }
269
+ .section-content { padding: 16px; }
270
+
271
+ .task-list { list-style: none; }
272
+ .task-item {
273
+ display: flex;
274
+ align-items: center;
275
+ padding: 12px 0;
276
+ border-bottom: 1px solid var(--border);
277
+ }
278
+ .task-item:last-child { border-bottom: none; }
279
+ .task-status {
280
+ width: 10px;
281
+ height: 10px;
282
+ border-radius: 50%;
283
+ margin-right: 12px;
284
+ }
285
+ .task-status.ready { background: var(--accent-blue); }
286
+ .task-status.in-progress { background: var(--accent-yellow); }
287
+ .task-status.blocked { background: var(--accent-red); }
288
+ .task-status.completed { background: var(--accent-green); }
289
+ .task-info { flex: 1; }
290
+ .task-title { font-weight: 500; }
291
+ .task-meta { font-size: 12px; color: var(--text-secondary); }
292
+
293
+ .log-entry {
294
+ padding: 12px;
295
+ border-bottom: 1px solid var(--border);
296
+ font-size: 14px;
297
+ }
298
+ .log-entry:last-child { border-bottom: none; }
299
+ .log-entry:hover { background: var(--bg-tertiary); }
300
+ .log-id { color: var(--accent-purple); font-weight: 500; }
301
+ .log-time { color: var(--text-secondary); font-size: 12px; }
302
+ .log-request { margin-top: 4px; }
303
+ .log-tags { margin-top: 4px; }
304
+ .log-tags .tag {
305
+ display: inline-block;
306
+ padding: 2px 8px;
307
+ background: var(--bg-tertiary);
308
+ border-radius: 4px;
309
+ font-size: 11px;
310
+ margin-right: 4px;
311
+ }
312
+
313
+ .git-info {
314
+ display: flex;
315
+ align-items: center;
316
+ gap: 16px;
317
+ }
318
+ .branch-name {
319
+ display: flex;
320
+ align-items: center;
321
+ gap: 6px;
322
+ color: var(--accent-purple);
323
+ }
324
+ .commit-list {
325
+ max-height: 300px;
326
+ overflow-y: auto;
327
+ }
328
+ .commit-item {
329
+ padding: 8px 0;
330
+ border-bottom: 1px solid var(--border);
331
+ font-size: 13px;
332
+ }
333
+ .commit-hash {
334
+ color: var(--accent-blue);
335
+ font-family: monospace;
336
+ }
337
+
338
+ .refresh-btn {
339
+ background: var(--bg-tertiary);
340
+ border: 1px solid var(--border);
341
+ color: var(--text-primary);
342
+ padding: 8px 16px;
343
+ border-radius: 6px;
344
+ cursor: pointer;
345
+ font-size: 14px;
346
+ }
347
+ .refresh-btn:hover { background: var(--border); }
348
+
349
+ .two-col {
350
+ display: grid;
351
+ grid-template-columns: 1fr 1fr;
352
+ gap: 24px;
353
+ }
354
+ @media (max-width: 900px) {
355
+ .two-col { grid-template-columns: 1fr; }
356
+ }
357
+
358
+ .empty-state {
359
+ text-align: center;
360
+ padding: 40px;
361
+ color: var(--text-secondary);
362
+ }
363
+
364
+ #loading {
365
+ position: fixed;
366
+ top: 0;
367
+ left: 0;
368
+ right: 0;
369
+ height: 3px;
370
+ background: var(--accent-blue);
371
+ display: none;
372
+ }
373
+ #loading.active { display: block; animation: loading 1s infinite; }
374
+ @keyframes loading {
375
+ 0% { transform: scaleX(0); transform-origin: left; }
376
+ 50% { transform: scaleX(1); transform-origin: left; }
377
+ 51% { transform-origin: right; }
378
+ 100% { transform: scaleX(0); transform-origin: right; }
379
+ }
380
+ </style>
381
+ </head>
382
+ <body>
383
+ <div id="loading"></div>
384
+ <div class="container">
385
+ <header>
386
+ <div>
387
+ <h1>Wogi Flow Dashboard</h1>
388
+ <div id="project-name" style="color: var(--text-secondary); font-size: 14px;"></div>
389
+ </div>
390
+ <div style="display: flex; align-items: center; gap: 16px;">
391
+ <div class="git-info">
392
+ <span class="branch-name" id="branch-name"></span>
393
+ </div>
394
+ <button class="refresh-btn" onclick="loadData()">Refresh</button>
395
+ </div>
396
+ </header>
397
+
398
+ <div class="stats-grid" id="stats-grid"></div>
399
+
400
+ <div class="two-col">
401
+ <div class="section">
402
+ <div class="section-header">
403
+ <h2>Tasks</h2>
404
+ <span id="task-count" class="badge badge-info"></span>
405
+ </div>
406
+ <div class="section-content">
407
+ <ul class="task-list" id="task-list"></ul>
408
+ </div>
409
+ </div>
410
+
411
+ <div class="section">
412
+ <div class="section-header">
413
+ <h2>Recent Activity</h2>
414
+ </div>
415
+ <div class="section-content">
416
+ <div id="activity-log"></div>
417
+ </div>
418
+ </div>
419
+ </div>
420
+
421
+ <div class="section">
422
+ <div class="section-header">
423
+ <h2>Recent Commits</h2>
424
+ </div>
425
+ <div class="section-content">
426
+ <div class="commit-list" id="commit-list"></div>
427
+ </div>
428
+ </div>
429
+ </div>
430
+
431
+ <script>
432
+ async function loadData() {
433
+ document.getElementById('loading').classList.add('active');
434
+
435
+ try {
436
+ const [stats, tasks, logs, git] = await Promise.all([
437
+ fetch('/api/stats').then(r => r.json()),
438
+ fetch('/api/tasks').then(r => r.json()),
439
+ fetch('/api/logs').then(r => r.json()),
440
+ fetch('/api/git').then(r => r.json())
441
+ ]);
442
+
443
+ renderStats(stats);
444
+ renderTasks(tasks);
445
+ renderLogs(logs);
446
+ renderGit(git);
447
+
448
+ document.getElementById('project-name').textContent = stats.projectName;
449
+
450
+ } catch (err) {
451
+ console.error('Failed to load data:', e);
452
+ }
453
+
454
+ document.getElementById('loading').classList.remove('active');
455
+ }
456
+
457
+ function renderStats(stats) {
458
+ const grid = document.getElementById('stats-grid');
459
+ grid.innerHTML = \`
460
+ <div class="stat-card">
461
+ <h3>Ready</h3>
462
+ <div class="value" style="color: var(--accent-blue)">\${stats.tasksReady}</div>
463
+ <div class="subtext">tasks waiting</div>
464
+ </div>
465
+ <div class="stat-card">
466
+ <h3>In Progress</h3>
467
+ <div class="value" style="color: var(--accent-yellow)">\${stats.tasksInProgress}</div>
468
+ <div class="subtext">currently active</div>
469
+ </div>
470
+ <div class="stat-card">
471
+ <h3>Completed</h3>
472
+ <div class="value" style="color: var(--accent-green)">\${stats.tasksCompleted}</div>
473
+ <div class="subtext">recently done</div>
474
+ </div>
475
+ <div class="stat-card">
476
+ <h3>Success Rate</h3>
477
+ <div class="value">\${stats.successRate}%</div>
478
+ <div class="subtext">from \${stats.totalRuns} runs</div>
479
+ </div>
480
+ \`;
481
+ }
482
+
483
+ function renderTasks(tasks) {
484
+ const list = document.getElementById('task-list');
485
+ const allTasks = [
486
+ ...(tasks.inProgress || []).map(t => ({ ...t, status: 'in-progress' })),
487
+ ...(tasks.ready || []).map(t => ({ ...t, status: 'ready' })),
488
+ ...(tasks.blocked || []).map(t => ({ ...t, status: 'blocked' })),
489
+ ...(tasks.recentlyCompleted || []).slice(0, 5).map(t => ({ ...t, status: 'completed' }))
490
+ ];
491
+
492
+ document.getElementById('task-count').textContent = allTasks.length + ' tasks';
493
+
494
+ if (allTasks.length === 0) {
495
+ list.innerHTML = '<div class="empty-state">No tasks found</div>';
496
+ return;
497
+ }
498
+
499
+ list.innerHTML = allTasks.map(task => \`
500
+ <li class="task-item">
501
+ <div class="task-status \${task.status}"></div>
502
+ <div class="task-info">
503
+ <div class="task-title">\${task.title || task.id}</div>
504
+ <div class="task-meta">\${task.id} • \${task.type || 'task'} • \${task.priority || 'P2'}</div>
505
+ </div>
506
+ </li>
507
+ \`).join('');
508
+ }
509
+
510
+ function renderLogs(logs) {
511
+ const container = document.getElementById('activity-log');
512
+
513
+ if (logs.length === 0) {
514
+ container.innerHTML = '<div class="empty-state">No recent activity</div>';
515
+ return;
516
+ }
517
+
518
+ container.innerHTML = logs.slice(0, 10).map(log => \`
519
+ <div class="log-entry">
520
+ <div>
521
+ <span class="log-id">\${log.id}</span>
522
+ <span class="log-time">\${log.timestamp}</span>
523
+ </div>
524
+ <div class="log-request">\${log.request}</div>
525
+ <div class="log-tags">
526
+ \${log.tags.map(t => \`<span class="tag">\${t}</span>\`).join('')}
527
+ </div>
528
+ </div>
529
+ \`).join('');
530
+ }
531
+
532
+ function renderGit(git) {
533
+ document.getElementById('branch-name').innerHTML = \`
534
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
535
+ <path d="M11.75 2.5a.75.75 0 100 1.5.75.75 0 000-1.5zm-2.25.75a2.25 2.25 0 113 2.122V6A2.5 2.5 0 0110 8.5H6a1 1 0 00-1 1v1.128a2.251 2.251 0 11-1.5 0V5.372a2.25 2.25 0 111.5 0v1.836A2.492 2.492 0 016 7h4a1 1 0 001-1v-.628A2.25 2.25 0 019.5 3.25zM4.25 12a.75.75 0 100 1.5.75.75 0 000-1.5zM3.5 3.25a.75.75 0 111.5 0 .75.75 0 01-1.5 0z"/>
536
+ </svg>
537
+ \${git.branch}
538
+ \`;
539
+
540
+ const commitList = document.getElementById('commit-list');
541
+ commitList.innerHTML = git.commits.map(c => \`
542
+ <div class="commit-item">
543
+ <span class="commit-hash">\${c.hash}</span>
544
+ \${c.message}
545
+ </div>
546
+ \`).join('');
547
+ }
548
+
549
+ // Initial load and auto-refresh
550
+ loadData();
551
+ setInterval(loadData, 30000);
552
+ </script>
553
+ </body>
554
+ </html>`;
555
+ }
556
+
557
+ // ============================================================
558
+ // HTTP Server
559
+ // ============================================================
560
+
561
+ function startServer(port) {
562
+ const server = http.createServer((req, res) => {
563
+ // CORS headers - restricted to localhost only to prevent data exposure
564
+ const origin = req.headers.origin;
565
+ const allowedOrigins = [`http://localhost:${port}`, `http://127.0.0.1:${port}`];
566
+ if (origin && allowedOrigins.includes(origin)) {
567
+ res.setHeader('Access-Control-Allow-Origin', origin);
568
+ }
569
+ res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
570
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
571
+
572
+ if (req.method === 'OPTIONS') {
573
+ res.writeHead(200);
574
+ res.end();
575
+ return;
576
+ }
577
+
578
+ // Route handling
579
+ const url = new URL(req.url, `http://localhost:${port}`);
580
+ const pathname = url.pathname;
581
+
582
+ try {
583
+ if (pathname === '/' || pathname === '/index.html') {
584
+ res.writeHead(200, { 'Content-Type': 'text/html' });
585
+ res.end(getDashboardHTML());
586
+ return;
587
+ }
588
+
589
+ if (pathname === '/api/stats') {
590
+ res.writeHead(200, { 'Content-Type': 'application/json' });
591
+ res.end(JSON.stringify(getProjectStats()));
592
+ return;
593
+ }
594
+
595
+ if (pathname === '/api/tasks') {
596
+ res.writeHead(200, { 'Content-Type': 'application/json' });
597
+ res.end(JSON.stringify(loadTaskStatus()));
598
+ return;
599
+ }
600
+
601
+ if (pathname === '/api/logs') {
602
+ res.writeHead(200, { 'Content-Type': 'application/json' });
603
+ res.end(JSON.stringify(parseRequestLog()));
604
+ return;
605
+ }
606
+
607
+ if (pathname === '/api/git') {
608
+ res.writeHead(200, { 'Content-Type': 'application/json' });
609
+ res.end(JSON.stringify(getGitInfo()));
610
+ return;
611
+ }
612
+
613
+ if (pathname === '/api/runs') {
614
+ res.writeHead(200, { 'Content-Type': 'application/json' });
615
+ res.end(JSON.stringify(loadRunHistory()));
616
+ return;
617
+ }
618
+
619
+ if (pathname === '/api/team') {
620
+ res.writeHead(200, { 'Content-Type': 'application/json' });
621
+ res.end(JSON.stringify(loadTeamState()));
622
+ return;
623
+ }
624
+
625
+ if (pathname === '/health') {
626
+ res.writeHead(200, { 'Content-Type': 'application/json' });
627
+ res.end(JSON.stringify({ status: 'ok', uptime: process.uptime() }));
628
+ return;
629
+ }
630
+
631
+ // 404
632
+ res.writeHead(404, { 'Content-Type': 'application/json' });
633
+ res.end(JSON.stringify({ error: 'Not found' }));
634
+
635
+ } catch (err) {
636
+ res.writeHead(500, { 'Content-Type': 'application/json' });
637
+ res.end(JSON.stringify({ error: err.message }));
638
+ }
639
+ });
640
+
641
+ server.listen(port, () => {
642
+ console.log(`
643
+ ${color('cyan', '╔══════════════════════════════════════════════════════════════╗')}
644
+ ${color('cyan', '║')} ${color('white', 'Wogi Flow - Team Observability Dashboard')} ${color('cyan', '║')}
645
+ ${color('cyan', '╚══════════════════════════════════════════════════════════════╝')}
646
+
647
+ Dashboard: ${color('green', `http://localhost:${port}`)}
648
+
649
+ API Endpoints:
650
+ GET /api/stats Project statistics
651
+ GET /api/tasks Task status (ready, in-progress, blocked)
652
+ GET /api/logs Recent activity from request-log
653
+ GET /api/git Git branch and recent commits
654
+ GET /api/runs Run history
655
+ GET /api/team Team state
656
+ GET /health Health check
657
+
658
+ Press Ctrl+C to stop the server.
659
+ `);
660
+ });
661
+
662
+ return server;
663
+ }
664
+
665
+ // ============================================================
666
+ // CLI
667
+ // ============================================================
668
+
669
+ function showHelp() {
670
+ console.log(`
671
+ Wogi Flow - Team Observability Dashboard
672
+
673
+ Start a local web dashboard for viewing task progress and history.
674
+
675
+ Usage:
676
+ flow team dashboard Start dashboard (port ${DEFAULT_PORT})
677
+ flow team dashboard --port 8080 Custom port
678
+ flow team dashboard --open Auto-open in browser
679
+
680
+ Options:
681
+ --port <number> Port to listen on (default: ${DEFAULT_PORT})
682
+ --open Open dashboard in default browser
683
+ --help, -h Show this help
684
+ `);
685
+ }
686
+
687
+ async function main() {
688
+ const { flags } = parseFlags(process.argv.slice(2));
689
+
690
+ if (flags.help || flags.h) {
691
+ showHelp();
692
+ process.exit(0);
693
+ }
694
+
695
+ // Validate port is a number within valid range
696
+ const portStr = flags.port;
697
+ let port = DEFAULT_PORT;
698
+ if (portStr !== undefined) {
699
+ if (!/^\d+$/.test(portStr)) {
700
+ error('Invalid port: must be a number');
701
+ process.exit(1);
702
+ }
703
+ port = parseInt(portStr, 10);
704
+ if (port < MIN_PORT || port > MAX_PORT) {
705
+ error(`Invalid port: must be between ${MIN_PORT} and ${MAX_PORT}`);
706
+ process.exit(1);
707
+ }
708
+ }
709
+
710
+ startServer(port);
711
+
712
+ // Auto-open browser if requested - use execFile to prevent shell injection
713
+ if (flags.open) {
714
+ const url = `http://localhost:${port}`;
715
+ try {
716
+ const cmd = process.platform === 'darwin' ? 'open' :
717
+ process.platform === 'win32' ? 'cmd' : 'xdg-open';
718
+ const args = process.platform === 'win32' ? ['/c', 'start', url] : [url];
719
+ execFile(cmd, args, (err) => {
720
+ if (err) {
721
+ info(`Open ${url} in your browser`);
722
+ }
723
+ });
724
+ } catch (err) {
725
+ info(`Open ${url} in your browser`);
726
+ }
727
+ }
728
+ }
729
+
730
+ // ============================================================
731
+ // Exports
732
+ // ============================================================
733
+
734
+ module.exports = {
735
+ startServer,
736
+ loadTaskStatus,
737
+ loadRunHistory,
738
+ parseRequestLog,
739
+ getProjectStats,
740
+ getGitInfo
741
+ };
742
+
743
+ if (require.main === module) {
744
+ main().catch(err => {
745
+ error(err.message);
746
+ process.exit(1);
747
+ });
748
+ }