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,718 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Wogi Flow - Run Trace Manager
5
+ *
6
+ * Creates and manages execution traces for each run.
7
+ * Provides both JSON (queryable) and Markdown (readable) outputs.
8
+ *
9
+ * Usage:
10
+ * flow run-trace start <run-name> # Start a new run
11
+ * flow run-trace event <type> <data> # Log an event
12
+ * flow run-trace end [status] # End current run
13
+ * flow run-trace list [--limit N] # List recent runs
14
+ * flow run-trace inspect <run-id> # Show run details
15
+ * flow run-trace diff <run-id> # Show changes from run
16
+ * flow run-trace cleanup # Remove old runs
17
+ */
18
+
19
+ const fs = require('fs');
20
+ const path = require('path');
21
+ const crypto = require('crypto');
22
+ const { getProjectRoot, colors: c } = require('./flow-utils');
23
+
24
+ const PROJECT_ROOT = getProjectRoot();
25
+ const WORKFLOW_DIR = path.join(PROJECT_ROOT, '.workflow');
26
+ const RUNS_DIR = path.join(WORKFLOW_DIR, 'runs');
27
+
28
+ // Event types
29
+ const EVENT_TYPES = {
30
+ RUN_START: 'run_start',
31
+ STEP_START: 'step_start',
32
+ STEP_END: 'step_end',
33
+ FILE_READ: 'file_read',
34
+ FILE_WRITE: 'file_write',
35
+ FILE_DELETE: 'file_delete',
36
+ COMMAND_RUN: 'command_run',
37
+ COMMAND_SUCCESS: 'command_success',
38
+ COMMAND_FAIL: 'command_fail',
39
+ LLM_CALL: 'llm_call',
40
+ LLM_RESPONSE: 'llm_response',
41
+ VALIDATION_START: 'validation_start',
42
+ VALIDATION_PASS: 'validation_pass',
43
+ VALIDATION_FAIL: 'validation_fail',
44
+ CHECKPOINT: 'checkpoint',
45
+ ERROR: 'error',
46
+ WARNING: 'warning',
47
+ RUN_END: 'run_end'
48
+ };
49
+
50
+ /**
51
+ * Generate a unique run ID
52
+ */
53
+ function generateRunId() {
54
+ const now = new Date();
55
+ const timestamp = now.toISOString()
56
+ .slice(0, 19)
57
+ .replace(/[-:T]/g, '')
58
+ .slice(0, 14);
59
+ const shortId = crypto.randomBytes(3).toString('hex');
60
+ return `${timestamp}-${shortId}`;
61
+ }
62
+
63
+ /**
64
+ * Ensure runs directory exists
65
+ */
66
+ function ensureRunsDir() {
67
+ if (!fs.existsSync(RUNS_DIR)) {
68
+ fs.mkdirSync(RUNS_DIR, { recursive: true });
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Get current active run ID
74
+ */
75
+ function getCurrentRunId() {
76
+ const currentFile = path.join(RUNS_DIR, '.current');
77
+ if (fs.existsSync(currentFile)) {
78
+ return fs.readFileSync(currentFile, 'utf-8').trim();
79
+ }
80
+ return null;
81
+ }
82
+
83
+ /**
84
+ * Set current active run
85
+ */
86
+ function setCurrentRun(runId) {
87
+ ensureRunsDir();
88
+ fs.writeFileSync(path.join(RUNS_DIR, '.current'), runId);
89
+ }
90
+
91
+ /**
92
+ * Clear current run
93
+ */
94
+ function clearCurrentRun() {
95
+ const currentFile = path.join(RUNS_DIR, '.current');
96
+ if (fs.existsSync(currentFile)) {
97
+ fs.unlinkSync(currentFile);
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Start a new run
103
+ */
104
+ function startRun(name, metadata = {}) {
105
+ ensureRunsDir();
106
+
107
+ const runId = generateRunId();
108
+ const runDir = path.join(RUNS_DIR, runId);
109
+ fs.mkdirSync(runDir);
110
+ fs.mkdirSync(path.join(runDir, 'artifacts'));
111
+ fs.mkdirSync(path.join(runDir, 'checkpoints'));
112
+
113
+ const manifest = {
114
+ id: runId,
115
+ name: name || 'unnamed',
116
+ startedAt: new Date().toISOString(),
117
+ endedAt: null,
118
+ status: 'running',
119
+ steps: 0,
120
+ filesModified: [],
121
+ filesCreated: [],
122
+ filesDeleted: [],
123
+ commandsRun: [],
124
+ validationResults: [],
125
+ llmCalls: 0,
126
+ totalTokens: { input: 0, output: 0 },
127
+ errors: [],
128
+ warnings: [],
129
+ checkpoints: [],
130
+ ...metadata
131
+ };
132
+
133
+ fs.writeFileSync(
134
+ path.join(runDir, 'manifest.json'),
135
+ JSON.stringify(manifest, null, 2)
136
+ );
137
+
138
+ // Initialize trace log
139
+ logEvent(runId, EVENT_TYPES.RUN_START, { name, metadata });
140
+
141
+ // Update index
142
+ updateIndex(runId, manifest);
143
+
144
+ setCurrentRun(runId);
145
+
146
+ return runId;
147
+ }
148
+
149
+ /**
150
+ * Log an event to the trace
151
+ */
152
+ function logEvent(runId, eventType, data = {}) {
153
+ const runDir = path.join(RUNS_DIR, runId);
154
+ if (!fs.existsSync(runDir)) {
155
+ console.error(`Run not found: ${runId}`);
156
+ return;
157
+ }
158
+
159
+ const tracePath = path.join(runDir, 'trace.jsonl');
160
+
161
+ const event = {
162
+ timestamp: new Date().toISOString(),
163
+ type: eventType,
164
+ data: data
165
+ };
166
+
167
+ fs.appendFileSync(tracePath, JSON.stringify(event) + '\n');
168
+
169
+ // Update manifest counters
170
+ updateManifestFromEvent(runId, eventType, data);
171
+ }
172
+
173
+ /**
174
+ * Update manifest based on event
175
+ */
176
+ function updateManifestFromEvent(runId, eventType, data) {
177
+ const manifestPath = path.join(RUNS_DIR, runId, 'manifest.json');
178
+ if (!fs.existsSync(manifestPath)) return;
179
+
180
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
181
+
182
+ switch (eventType) {
183
+ case EVENT_TYPES.STEP_START:
184
+ manifest.steps++;
185
+ break;
186
+ case EVENT_TYPES.FILE_WRITE:
187
+ if (data.created) {
188
+ if (!manifest.filesCreated.includes(data.path)) {
189
+ manifest.filesCreated.push(data.path);
190
+ }
191
+ } else {
192
+ if (!manifest.filesModified.includes(data.path)) {
193
+ manifest.filesModified.push(data.path);
194
+ }
195
+ }
196
+ break;
197
+ case EVENT_TYPES.FILE_DELETE:
198
+ if (!manifest.filesDeleted.includes(data.path)) {
199
+ manifest.filesDeleted.push(data.path);
200
+ }
201
+ break;
202
+ case EVENT_TYPES.COMMAND_RUN:
203
+ manifest.commandsRun.push({
204
+ command: data.command,
205
+ timestamp: new Date().toISOString()
206
+ });
207
+ break;
208
+ case EVENT_TYPES.LLM_CALL:
209
+ manifest.llmCalls++;
210
+ break;
211
+ case EVENT_TYPES.LLM_RESPONSE:
212
+ if (data.tokens) {
213
+ manifest.totalTokens.input += data.tokens.input || 0;
214
+ manifest.totalTokens.output += data.tokens.output || 0;
215
+ }
216
+ break;
217
+ case EVENT_TYPES.VALIDATION_PASS:
218
+ case EVENT_TYPES.VALIDATION_FAIL:
219
+ manifest.validationResults.push({
220
+ command: data.command,
221
+ passed: eventType === EVENT_TYPES.VALIDATION_PASS,
222
+ timestamp: new Date().toISOString()
223
+ });
224
+ break;
225
+ case EVENT_TYPES.CHECKPOINT:
226
+ manifest.checkpoints.push(data.checkpointId);
227
+ break;
228
+ case EVENT_TYPES.ERROR:
229
+ manifest.errors.push({
230
+ message: data.message,
231
+ timestamp: new Date().toISOString()
232
+ });
233
+ break;
234
+ case EVENT_TYPES.WARNING:
235
+ manifest.warnings.push({
236
+ message: data.message,
237
+ timestamp: new Date().toISOString()
238
+ });
239
+ break;
240
+ }
241
+
242
+ fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
243
+ }
244
+
245
+ /**
246
+ * End the current run
247
+ */
248
+ function endRun(runId, status = 'completed') {
249
+ const runDir = path.join(RUNS_DIR, runId);
250
+ if (!fs.existsSync(runDir)) {
251
+ throw new Error(`Run not found: ${runId}`);
252
+ }
253
+
254
+ const manifestPath = path.join(runDir, 'manifest.json');
255
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
256
+
257
+ manifest.endedAt = new Date().toISOString();
258
+ manifest.status = status;
259
+ manifest.durationMs = new Date(manifest.endedAt) - new Date(manifest.startedAt);
260
+
261
+ fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
262
+
263
+ logEvent(runId, EVENT_TYPES.RUN_END, { status });
264
+
265
+ // Generate human-readable summary
266
+ generateSummary(runId);
267
+
268
+ // Update index
269
+ updateIndex(runId, manifest);
270
+
271
+ clearCurrentRun();
272
+
273
+ return manifest;
274
+ }
275
+
276
+ /**
277
+ * Generate human-readable summary
278
+ */
279
+ function generateSummary(runId) {
280
+ const runDir = path.join(RUNS_DIR, runId);
281
+ const manifest = JSON.parse(
282
+ fs.readFileSync(path.join(runDir, 'manifest.json'), 'utf-8')
283
+ );
284
+ const tracePath = path.join(runDir, 'trace.jsonl');
285
+
286
+ const events = fs.existsSync(tracePath)
287
+ ? fs.readFileSync(tracePath, 'utf-8')
288
+ .split('\n')
289
+ .filter(line => line.trim())
290
+ .map(line => JSON.parse(line))
291
+ : [];
292
+
293
+ const durationSec = manifest.durationMs
294
+ ? Math.round(manifest.durationMs / 1000)
295
+ : 'N/A';
296
+
297
+ let summary = `# Run: ${manifest.name}\n\n`;
298
+ summary += `| Property | Value |\n`;
299
+ summary += `|----------|-------|\n`;
300
+ summary += `| **ID** | ${manifest.id} |\n`;
301
+ summary += `| **Status** | ${manifest.status} |\n`;
302
+ summary += `| **Started** | ${manifest.startedAt} |\n`;
303
+ summary += `| **Duration** | ${durationSec}s |\n`;
304
+ summary += `| **Steps** | ${manifest.steps} |\n`;
305
+ summary += `| **LLM Calls** | ${manifest.llmCalls} |\n`;
306
+ summary += `| **Tokens** | ${manifest.totalTokens.input} in / ${manifest.totalTokens.output} out |\n`;
307
+ summary += `\n`;
308
+
309
+ // Files section
310
+ const totalFiles = manifest.filesCreated.length +
311
+ manifest.filesModified.length +
312
+ manifest.filesDeleted.length;
313
+
314
+ if (totalFiles > 0) {
315
+ summary += `## Files Changed (${totalFiles})\n\n`;
316
+
317
+ if (manifest.filesCreated.length > 0) {
318
+ summary += `### Created\n`;
319
+ for (const file of manifest.filesCreated) {
320
+ summary += `- \`${file}\`\n`;
321
+ }
322
+ summary += '\n';
323
+ }
324
+
325
+ if (manifest.filesModified.length > 0) {
326
+ summary += `### Modified\n`;
327
+ for (const file of manifest.filesModified) {
328
+ summary += `- \`${file}\`\n`;
329
+ }
330
+ summary += '\n';
331
+ }
332
+
333
+ if (manifest.filesDeleted.length > 0) {
334
+ summary += `### Deleted\n`;
335
+ for (const file of manifest.filesDeleted) {
336
+ summary += `- \`${file}\`\n`;
337
+ }
338
+ summary += '\n';
339
+ }
340
+ }
341
+
342
+ // Validation section
343
+ if (manifest.validationResults.length > 0) {
344
+ summary += `## Validation Results\n\n`;
345
+ for (const v of manifest.validationResults) {
346
+ const icon = v.passed ? '✅' : '❌';
347
+ summary += `- ${icon} \`${v.command}\`\n`;
348
+ }
349
+ summary += '\n';
350
+ }
351
+
352
+ // Errors section
353
+ if (manifest.errors.length > 0) {
354
+ summary += `## Errors\n\n`;
355
+ for (const err of manifest.errors) {
356
+ summary += `- ❌ ${err.message}\n`;
357
+ }
358
+ summary += '\n';
359
+ }
360
+
361
+ // Warnings section
362
+ if (manifest.warnings.length > 0) {
363
+ summary += `## Warnings\n\n`;
364
+ for (const warn of manifest.warnings) {
365
+ summary += `- ⚠️ ${warn.message}\n`;
366
+ }
367
+ summary += '\n';
368
+ }
369
+
370
+ // Steps timeline
371
+ summary += `## Timeline\n\n`;
372
+ let stepNum = 0;
373
+ for (const event of events) {
374
+ if (event.type === EVENT_TYPES.STEP_START) {
375
+ stepNum++;
376
+ const time = event.timestamp.slice(11, 19);
377
+ summary += `### ${time} - Step ${stepNum}: ${event.data.name || 'Unnamed'}\n`;
378
+ } else if (event.type === EVENT_TYPES.FILE_WRITE) {
379
+ summary += ` - 📝 ${event.data.created ? 'Created' : 'Modified'}: \`${event.data.path}\`\n`;
380
+ } else if (event.type === EVENT_TYPES.COMMAND_RUN) {
381
+ summary += ` - 🖥️ Command: \`${event.data.command}\`\n`;
382
+ } else if (event.type === EVENT_TYPES.ERROR) {
383
+ summary += ` - ❌ Error: ${event.data.message}\n`;
384
+ }
385
+ }
386
+
387
+ fs.writeFileSync(path.join(runDir, 'summary.md'), summary);
388
+ }
389
+
390
+ /**
391
+ * Update the runs index
392
+ */
393
+ function updateIndex(runId, manifest) {
394
+ const indexPath = path.join(RUNS_DIR, 'index.json');
395
+ let index = { runs: [], lastUpdated: new Date().toISOString() };
396
+
397
+ if (fs.existsSync(indexPath)) {
398
+ try {
399
+ index = JSON.parse(fs.readFileSync(indexPath, 'utf-8'));
400
+ } catch {
401
+ // Reset if corrupt
402
+ }
403
+ }
404
+
405
+ // Remove existing entry if updating
406
+ index.runs = index.runs.filter(r => r.id !== runId);
407
+
408
+ // Add new entry at the start
409
+ index.runs.unshift({
410
+ id: runId,
411
+ name: manifest.name,
412
+ status: manifest.status,
413
+ startedAt: manifest.startedAt,
414
+ endedAt: manifest.endedAt,
415
+ durationMs: manifest.durationMs,
416
+ steps: manifest.steps,
417
+ filesChanged: manifest.filesCreated.length +
418
+ manifest.filesModified.length +
419
+ manifest.filesDeleted.length,
420
+ errors: manifest.errors.length
421
+ });
422
+
423
+ // Load config for retention settings
424
+ let maxRuns = 100;
425
+ const configPath = path.join(WORKFLOW_DIR, 'config.json');
426
+ if (fs.existsSync(configPath)) {
427
+ try {
428
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
429
+ maxRuns = config.traces?.runs?.maxRuns || 100;
430
+ } catch {}
431
+ }
432
+
433
+ // Keep only configured number of runs in index
434
+ index.runs = index.runs.slice(0, maxRuns);
435
+ index.lastUpdated = new Date().toISOString();
436
+
437
+ fs.writeFileSync(indexPath, JSON.stringify(index, null, 2));
438
+ }
439
+
440
+ /**
441
+ * List recent runs
442
+ */
443
+ function listRuns(limit = 10) {
444
+ const indexPath = path.join(RUNS_DIR, 'index.json');
445
+ if (!fs.existsSync(indexPath)) {
446
+ return [];
447
+ }
448
+
449
+ const index = JSON.parse(fs.readFileSync(indexPath, 'utf-8'));
450
+ return index.runs.slice(0, limit);
451
+ }
452
+
453
+ /**
454
+ * Inspect a run
455
+ */
456
+ function inspectRun(runId) {
457
+ const runDir = path.join(RUNS_DIR, runId);
458
+ if (!fs.existsSync(runDir)) {
459
+ throw new Error(`Run not found: ${runId}`);
460
+ }
461
+
462
+ const manifest = JSON.parse(
463
+ fs.readFileSync(path.join(runDir, 'manifest.json'), 'utf-8')
464
+ );
465
+
466
+ const tracePath = path.join(runDir, 'trace.jsonl');
467
+ const events = fs.existsSync(tracePath)
468
+ ? fs.readFileSync(tracePath, 'utf-8')
469
+ .split('\n')
470
+ .filter(line => line.trim())
471
+ .map(line => JSON.parse(line))
472
+ : [];
473
+
474
+ const summaryPath = path.join(runDir, 'summary.md');
475
+ const summary = fs.existsSync(summaryPath)
476
+ ? fs.readFileSync(summaryPath, 'utf-8')
477
+ : null;
478
+
479
+ return { manifest, events, summary };
480
+ }
481
+
482
+ /**
483
+ * Cleanup old runs based on retention policy
484
+ */
485
+ function cleanupRuns() {
486
+ const configPath = path.join(WORKFLOW_DIR, 'config.json');
487
+ let retentionDays = 30;
488
+ let maxRuns = 100;
489
+
490
+ if (fs.existsSync(configPath)) {
491
+ try {
492
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
493
+ retentionDays = config.traces?.runs?.retentionDays || 30;
494
+ maxRuns = config.traces?.runs?.maxRuns || 100;
495
+ } catch {}
496
+ }
497
+
498
+ if (!fs.existsSync(RUNS_DIR)) return { deleted: 0 };
499
+
500
+ const cutoffDate = new Date();
501
+ cutoffDate.setDate(cutoffDate.getDate() - retentionDays);
502
+
503
+ const indexPath = path.join(RUNS_DIR, 'index.json');
504
+ if (!fs.existsSync(indexPath)) return { deleted: 0 };
505
+
506
+ const index = JSON.parse(fs.readFileSync(indexPath, 'utf-8'));
507
+ let deleted = 0;
508
+
509
+ // Delete runs older than retention period or exceeding max
510
+ const runsToKeep = [];
511
+ for (let i = 0; i < index.runs.length; i++) {
512
+ const run = index.runs[i];
513
+ const runDate = new Date(run.startedAt);
514
+
515
+ if (runDate < cutoffDate || i >= maxRuns) {
516
+ // Delete run directory
517
+ const runDir = path.join(RUNS_DIR, run.id);
518
+ if (fs.existsSync(runDir)) {
519
+ fs.rmSync(runDir, { recursive: true });
520
+ deleted++;
521
+ }
522
+ } else {
523
+ runsToKeep.push(run);
524
+ }
525
+ }
526
+
527
+ // Update index
528
+ index.runs = runsToKeep;
529
+ index.lastUpdated = new Date().toISOString();
530
+ fs.writeFileSync(indexPath, JSON.stringify(index, null, 2));
531
+
532
+ return { deleted };
533
+ }
534
+
535
+ /**
536
+ * Format runs for display
537
+ */
538
+ function formatRunsForDisplay(runs) {
539
+ if (runs.length === 0) {
540
+ return `${c.dim}No runs recorded yet.${c.reset}`;
541
+ }
542
+
543
+ let output = `${c.cyan}${c.bold}Recent Runs${c.reset}\n`;
544
+ output += `${'─'.repeat(80)}\n`;
545
+
546
+ for (const run of runs) {
547
+ const statusColor = run.status === 'completed' ? c.green :
548
+ run.status === 'failed' ? c.red :
549
+ run.status === 'running' ? c.yellow : c.dim;
550
+
551
+ const statusIcon = run.status === 'completed' ? '✅' :
552
+ run.status === 'failed' ? '❌' :
553
+ run.status === 'running' ? '🔄' : '⏸️';
554
+
555
+ const duration = run.durationMs
556
+ ? `${Math.round(run.durationMs / 1000)}s`
557
+ : 'running';
558
+
559
+ output += `${statusIcon} ${c.bold}${run.name}${c.reset}`;
560
+ output += ` ${c.dim}(${run.id})${c.reset}\n`;
561
+ output += ` ${statusColor}${run.status}${c.reset}`;
562
+ output += ` | ${duration}`;
563
+ output += ` | ${run.steps} steps`;
564
+ output += ` | ${run.filesChanged} files`;
565
+ if (run.errors > 0) {
566
+ output += ` | ${c.red}${run.errors} errors${c.reset}`;
567
+ }
568
+ output += '\n';
569
+ }
570
+
571
+ return output;
572
+ }
573
+
574
+ // ============================================================
575
+ // Module exports
576
+ // ============================================================
577
+
578
+ module.exports = {
579
+ EVENT_TYPES,
580
+ generateRunId,
581
+ startRun,
582
+ logEvent,
583
+ endRun,
584
+ getCurrentRunId,
585
+ setCurrentRun,
586
+ clearCurrentRun,
587
+ listRuns,
588
+ inspectRun,
589
+ generateSummary,
590
+ cleanupRuns
591
+ };
592
+
593
+ // ============================================================
594
+ // CLI Handler
595
+ // ============================================================
596
+
597
+ if (require.main === module) {
598
+ const args = process.argv.slice(2);
599
+ const command = args[0];
600
+ const jsonOutput = args.includes('--json');
601
+
602
+ try {
603
+ switch (command) {
604
+ case 'start': {
605
+ const name = args[1] || 'unnamed';
606
+ const runId = startRun(name);
607
+ if (jsonOutput) {
608
+ console.log(JSON.stringify({ success: true, runId }));
609
+ } else {
610
+ console.log(`${c.green}✅ Started run: ${runId}${c.reset}`);
611
+ }
612
+ break;
613
+ }
614
+
615
+ case 'event': {
616
+ const currentId = getCurrentRunId();
617
+ if (!currentId) {
618
+ throw new Error('No active run');
619
+ }
620
+ const eventType = args[1];
621
+ const eventData = args[2] ? JSON.parse(args[2]) : {};
622
+ logEvent(currentId, eventType, eventData);
623
+ if (!jsonOutput) {
624
+ console.log(`${c.dim}Event logged: ${eventType}${c.reset}`);
625
+ }
626
+ break;
627
+ }
628
+
629
+ case 'end': {
630
+ const currentId = getCurrentRunId();
631
+ if (!currentId) {
632
+ throw new Error('No active run');
633
+ }
634
+ const status = args[1] || 'completed';
635
+ const manifest = endRun(currentId, status);
636
+ if (jsonOutput) {
637
+ console.log(JSON.stringify({ success: true, manifest }));
638
+ } else {
639
+ console.log(`${c.green}✅ Ended run: ${currentId} (${status})${c.reset}`);
640
+ }
641
+ break;
642
+ }
643
+
644
+ case 'list': {
645
+ const limitIdx = args.indexOf('--limit');
646
+ const limit = limitIdx !== -1 ? parseInt(args[limitIdx + 1]) : 10;
647
+ const runs = listRuns(limit);
648
+ if (jsonOutput) {
649
+ console.log(JSON.stringify(runs, null, 2));
650
+ } else {
651
+ console.log(formatRunsForDisplay(runs));
652
+ }
653
+ break;
654
+ }
655
+
656
+ case 'inspect': {
657
+ const runId = args[1];
658
+ if (!runId) {
659
+ throw new Error('Run ID required');
660
+ }
661
+ const data = inspectRun(runId);
662
+ if (jsonOutput) {
663
+ console.log(JSON.stringify(data, null, 2));
664
+ } else {
665
+ console.log(data.summary || JSON.stringify(data.manifest, null, 2));
666
+ }
667
+ break;
668
+ }
669
+
670
+ case 'current': {
671
+ const currentId = getCurrentRunId();
672
+ if (jsonOutput) {
673
+ console.log(JSON.stringify({ currentRunId: currentId }));
674
+ } else {
675
+ console.log(currentId || 'No active run');
676
+ }
677
+ break;
678
+ }
679
+
680
+ case 'cleanup': {
681
+ const result = cleanupRuns();
682
+ if (jsonOutput) {
683
+ console.log(JSON.stringify(result));
684
+ } else {
685
+ console.log(`${c.green}✅ Cleaned up ${result.deleted} old runs${c.reset}`);
686
+ }
687
+ break;
688
+ }
689
+
690
+ default:
691
+ console.log(`
692
+ ${c.cyan}Wogi Flow - Run Trace Manager${c.reset}
693
+
694
+ ${c.bold}Usage:${c.reset}
695
+ flow run-trace start <name> Start a new run
696
+ flow run-trace event <type> <json> Log an event to current run
697
+ flow run-trace end [status] End current run (completed|failed|aborted)
698
+ flow run-trace list [--limit N] List recent runs
699
+ flow run-trace inspect <run-id> Show run details
700
+ flow run-trace current Show current run ID
701
+ flow run-trace cleanup Remove old runs per retention policy
702
+
703
+ ${c.bold}Options:${c.reset}
704
+ --json Output in JSON format
705
+
706
+ ${c.bold}Event Types:${c.reset}
707
+ ${Object.keys(EVENT_TYPES).map(k => ` ${k}`).join('\n')}
708
+ `);
709
+ }
710
+ } catch (err) {
711
+ if (jsonOutput) {
712
+ console.error(JSON.stringify({ success: false, error: err.message }));
713
+ } else {
714
+ console.error(`${c.red}Error: ${err.message}${c.reset}`);
715
+ }
716
+ process.exit(1);
717
+ }
718
+ }