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.
- package/.workflow/agents/reviewer.md +81 -0
- package/.workflow/agents/security.md +94 -0
- package/.workflow/agents/story-writer.md +58 -0
- package/.workflow/bridges/base-bridge.js +395 -0
- package/.workflow/bridges/claude-bridge.js +434 -0
- package/.workflow/bridges/index.js +130 -0
- package/.workflow/lib/assumption-detector.js +481 -0
- package/.workflow/lib/config-substitution.js +371 -0
- package/.workflow/lib/failure-categories.js +478 -0
- package/.workflow/state/app-map.md.template +15 -0
- package/.workflow/state/architecture.md.template +24 -0
- package/.workflow/state/component-index.json.template +5 -0
- package/.workflow/state/decisions.md.template +15 -0
- package/.workflow/state/feedback-patterns.md.template +9 -0
- package/.workflow/state/knowledge-sync.json.template +6 -0
- package/.workflow/state/progress.md.template +14 -0
- package/.workflow/state/ready.json.template +7 -0
- package/.workflow/state/request-log.md.template +14 -0
- package/.workflow/state/session-state.json.template +11 -0
- package/.workflow/state/stack.md.template +33 -0
- package/.workflow/state/testing.md.template +36 -0
- package/.workflow/templates/claude-md.hbs +257 -0
- package/.workflow/templates/correction-report.md +67 -0
- package/.workflow/templates/gemini-md.hbs +52 -0
- package/README.md +1802 -0
- package/bin/flow +205 -0
- package/lib/index.js +33 -0
- package/lib/installer.js +467 -0
- package/lib/release-channel.js +269 -0
- package/lib/skill-registry.js +526 -0
- package/lib/upgrader.js +401 -0
- package/lib/utils.js +305 -0
- package/package.json +64 -0
- package/scripts/flow +985 -0
- package/scripts/flow-adaptive-learning.js +1259 -0
- package/scripts/flow-aggregate.js +488 -0
- package/scripts/flow-archive +133 -0
- package/scripts/flow-auto-context.js +1015 -0
- package/scripts/flow-auto-learn.js +615 -0
- package/scripts/flow-bridge.js +223 -0
- package/scripts/flow-browser-suggest.js +316 -0
- package/scripts/flow-bug.js +247 -0
- package/scripts/flow-cascade.js +711 -0
- package/scripts/flow-changelog +85 -0
- package/scripts/flow-checkpoint.js +483 -0
- package/scripts/flow-cli.js +403 -0
- package/scripts/flow-code-intelligence.js +760 -0
- package/scripts/flow-complexity.js +502 -0
- package/scripts/flow-config-set.js +152 -0
- package/scripts/flow-constants.js +157 -0
- package/scripts/flow-context +152 -0
- package/scripts/flow-context-init.js +482 -0
- package/scripts/flow-context-monitor.js +384 -0
- package/scripts/flow-context-scoring.js +886 -0
- package/scripts/flow-correct.js +458 -0
- package/scripts/flow-damage-control.js +985 -0
- package/scripts/flow-deps +101 -0
- package/scripts/flow-diff.js +700 -0
- package/scripts/flow-done +151 -0
- package/scripts/flow-done.js +489 -0
- package/scripts/flow-durable-session.js +1541 -0
- package/scripts/flow-entropy-monitor.js +345 -0
- package/scripts/flow-export-profile +349 -0
- package/scripts/flow-export-scanner.js +1046 -0
- package/scripts/flow-figma-confirm.js +400 -0
- package/scripts/flow-figma-extract.js +496 -0
- package/scripts/flow-figma-generate.js +683 -0
- package/scripts/flow-figma-index.js +909 -0
- package/scripts/flow-figma-match.js +617 -0
- package/scripts/flow-figma-mcp-server.js +518 -0
- package/scripts/flow-figma-pipeline.js +414 -0
- package/scripts/flow-file-ops.js +301 -0
- package/scripts/flow-gate-confidence.js +825 -0
- package/scripts/flow-guided-edit.js +659 -0
- package/scripts/flow-health +185 -0
- package/scripts/flow-health.js +413 -0
- package/scripts/flow-hooks.js +556 -0
- package/scripts/flow-http-client.js +249 -0
- package/scripts/flow-hybrid-detect.js +167 -0
- package/scripts/flow-hybrid-interactive.js +591 -0
- package/scripts/flow-hybrid-test.js +152 -0
- package/scripts/flow-import-profile +439 -0
- package/scripts/flow-init +253 -0
- package/scripts/flow-instruction-richness.js +827 -0
- package/scripts/flow-jira-integration.js +579 -0
- package/scripts/flow-knowledge-router.js +522 -0
- package/scripts/flow-knowledge-sync.js +589 -0
- package/scripts/flow-linear-integration.js +631 -0
- package/scripts/flow-links.js +774 -0
- package/scripts/flow-log-manager.js +559 -0
- package/scripts/flow-loop-enforcer.js +1246 -0
- package/scripts/flow-loop-retry-learning.js +630 -0
- package/scripts/flow-lsp.js +923 -0
- package/scripts/flow-map-index +348 -0
- package/scripts/flow-map-sync +201 -0
- package/scripts/flow-memory-blocks.js +668 -0
- package/scripts/flow-memory-compactor.js +350 -0
- package/scripts/flow-memory-db.js +1110 -0
- package/scripts/flow-memory-sync.js +484 -0
- package/scripts/flow-metrics.js +353 -0
- package/scripts/flow-migrate-ids.js +370 -0
- package/scripts/flow-model-adapter.js +802 -0
- package/scripts/flow-model-router.js +884 -0
- package/scripts/flow-models.js +1231 -0
- package/scripts/flow-morning.js +517 -0
- package/scripts/flow-multi-approach.js +660 -0
- package/scripts/flow-new-feature +86 -0
- package/scripts/flow-onboard +1042 -0
- package/scripts/flow-orchestrate-llm.js +459 -0
- package/scripts/flow-orchestrate.js +3592 -0
- package/scripts/flow-output.js +123 -0
- package/scripts/flow-parallel-detector.js +399 -0
- package/scripts/flow-parallel-dispatch.js +987 -0
- package/scripts/flow-parallel.js +428 -0
- package/scripts/flow-pattern-enforcer.js +600 -0
- package/scripts/flow-prd-manager.js +282 -0
- package/scripts/flow-progress.js +323 -0
- package/scripts/flow-project-analyzer.js +975 -0
- package/scripts/flow-prompt-composer.js +487 -0
- package/scripts/flow-providers.js +1381 -0
- package/scripts/flow-queue.js +308 -0
- package/scripts/flow-ready +82 -0
- package/scripts/flow-ready.js +189 -0
- package/scripts/flow-regression.js +396 -0
- package/scripts/flow-response-parser.js +450 -0
- package/scripts/flow-resume.js +284 -0
- package/scripts/flow-rules-sync.js +439 -0
- package/scripts/flow-run-trace.js +718 -0
- package/scripts/flow-safety.js +587 -0
- package/scripts/flow-search +104 -0
- package/scripts/flow-security.js +481 -0
- package/scripts/flow-session-end +106 -0
- package/scripts/flow-session-end.js +437 -0
- package/scripts/flow-session-state.js +671 -0
- package/scripts/flow-setup-hooks +216 -0
- package/scripts/flow-setup-hooks.js +377 -0
- package/scripts/flow-skill-create.js +329 -0
- package/scripts/flow-skill-creator.js +572 -0
- package/scripts/flow-skill-generator.js +1046 -0
- package/scripts/flow-skill-learn.js +880 -0
- package/scripts/flow-skill-matcher.js +578 -0
- package/scripts/flow-spec-generator.js +820 -0
- package/scripts/flow-stack-wizard.js +895 -0
- package/scripts/flow-standup +162 -0
- package/scripts/flow-start +74 -0
- package/scripts/flow-start.js +235 -0
- package/scripts/flow-status +110 -0
- package/scripts/flow-status.js +301 -0
- package/scripts/flow-step-browser.js +83 -0
- package/scripts/flow-step-changelog.js +217 -0
- package/scripts/flow-step-comments.js +306 -0
- package/scripts/flow-step-complexity.js +234 -0
- package/scripts/flow-step-coverage.js +218 -0
- package/scripts/flow-step-knowledge.js +193 -0
- package/scripts/flow-step-pr-tests.js +364 -0
- package/scripts/flow-step-regression.js +89 -0
- package/scripts/flow-step-review.js +516 -0
- package/scripts/flow-step-security.js +162 -0
- package/scripts/flow-step-silent-failures.js +290 -0
- package/scripts/flow-step-simplifier.js +346 -0
- package/scripts/flow-story +105 -0
- package/scripts/flow-story.js +500 -0
- package/scripts/flow-suspend.js +252 -0
- package/scripts/flow-sync-daemon.js +654 -0
- package/scripts/flow-task-analyzer.js +606 -0
- package/scripts/flow-team-dashboard.js +748 -0
- package/scripts/flow-team-sync.js +752 -0
- package/scripts/flow-team.js +977 -0
- package/scripts/flow-tech-options.js +528 -0
- package/scripts/flow-templates.js +812 -0
- package/scripts/flow-tiered-learning.js +728 -0
- package/scripts/flow-trace +204 -0
- package/scripts/flow-transcript-chunking.js +1106 -0
- package/scripts/flow-transcript-digest.js +7918 -0
- package/scripts/flow-transcript-language.js +465 -0
- package/scripts/flow-transcript-parsing.js +1085 -0
- package/scripts/flow-transcript-stories.js +2194 -0
- package/scripts/flow-update-map +224 -0
- package/scripts/flow-utils.js +2242 -0
- package/scripts/flow-verification.js +644 -0
- package/scripts/flow-verify.js +1177 -0
- package/scripts/flow-voice-input.js +638 -0
- package/scripts/flow-watch +168 -0
- package/scripts/flow-workflow-steps.js +521 -0
- package/scripts/flow-workflow.js +1029 -0
- package/scripts/flow-worktree.js +489 -0
- package/scripts/hooks/adapters/base-adapter.js +102 -0
- package/scripts/hooks/adapters/claude-code.js +359 -0
- package/scripts/hooks/adapters/index.js +79 -0
- package/scripts/hooks/core/component-check.js +341 -0
- package/scripts/hooks/core/index.js +35 -0
- package/scripts/hooks/core/loop-check.js +241 -0
- package/scripts/hooks/core/session-context.js +294 -0
- package/scripts/hooks/core/task-gate.js +177 -0
- package/scripts/hooks/core/validation.js +230 -0
- package/scripts/hooks/entry/claude-code/post-tool-use.js +65 -0
- package/scripts/hooks/entry/claude-code/pre-tool-use.js +89 -0
- package/scripts/hooks/entry/claude-code/session-end.js +87 -0
- package/scripts/hooks/entry/claude-code/session-start.js +46 -0
- package/scripts/hooks/entry/claude-code/stop.js +43 -0
- package/scripts/postinstall.js +139 -0
- package/templates/browser-test-flow.json +56 -0
- package/templates/bug-report.md +43 -0
- package/templates/component-detail.md +42 -0
- package/templates/component.stories.tsx +49 -0
- package/templates/context/constraints.md +83 -0
- package/templates/context/conventions.md +177 -0
- package/templates/context/stack.md +60 -0
- package/templates/correction-report.md +90 -0
- package/templates/feature-proposal.md +35 -0
- package/templates/hybrid/_base.md +254 -0
- package/templates/hybrid/_patterns.md +45 -0
- package/templates/hybrid/create-component.md +127 -0
- package/templates/hybrid/create-file.md +56 -0
- package/templates/hybrid/create-hook.md +145 -0
- package/templates/hybrid/create-service.md +70 -0
- package/templates/hybrid/fix-bug.md +33 -0
- package/templates/hybrid/modify-file.md +55 -0
- package/templates/story.md +68 -0
- package/templates/task.json +56 -0
- 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
|
+
}
|