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,282 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Wogi Flow - PRD Manager
|
|
5
|
+
*
|
|
6
|
+
* Manages Project Requirements Documents (PRDs) with:
|
|
7
|
+
* - Loading and chunking PRD content
|
|
8
|
+
* - Semantic storage for contextual retrieval (via flow-memory-db)
|
|
9
|
+
* - Task-aware context extraction with embeddings
|
|
10
|
+
*
|
|
11
|
+
* Part of v1.8.0 Team Collaboration
|
|
12
|
+
*
|
|
13
|
+
* Usage:
|
|
14
|
+
* ./scripts/flow prd load <file> Load PRD into memory
|
|
15
|
+
* ./scripts/flow prd context <task> Get relevant PRD context for task
|
|
16
|
+
* ./scripts/flow prd list List loaded PRDs
|
|
17
|
+
* ./scripts/flow prd clear Clear PRD memory
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const fs = require('fs');
|
|
21
|
+
const path = require('path');
|
|
22
|
+
const {
|
|
23
|
+
getConfig,
|
|
24
|
+
colors,
|
|
25
|
+
color,
|
|
26
|
+
success,
|
|
27
|
+
warn,
|
|
28
|
+
error,
|
|
29
|
+
info,
|
|
30
|
+
printHeader,
|
|
31
|
+
fileExists,
|
|
32
|
+
readFile
|
|
33
|
+
} = require('./flow-utils');
|
|
34
|
+
|
|
35
|
+
// Use shared memory database
|
|
36
|
+
const memoryDb = require('./flow-memory-db');
|
|
37
|
+
|
|
38
|
+
// ============================================================
|
|
39
|
+
// Constants
|
|
40
|
+
// ============================================================
|
|
41
|
+
|
|
42
|
+
const DEFAULT_MAX_CONTEXT_TOKENS = 2000;
|
|
43
|
+
|
|
44
|
+
// ============================================================
|
|
45
|
+
// PRD Loading
|
|
46
|
+
// ============================================================
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Load a PRD file into storage (using shared database)
|
|
50
|
+
*/
|
|
51
|
+
async function loadPRD(filePath, options = {}) {
|
|
52
|
+
if (!fileExists(filePath)) {
|
|
53
|
+
throw new Error(`File not found: ${filePath}`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const content = readFile(filePath);
|
|
57
|
+
const fileName = path.basename(filePath);
|
|
58
|
+
const prdId = options.id || fileName.replace(/\.[^.]+$/, '');
|
|
59
|
+
|
|
60
|
+
// Use shared database to store PRD with embeddings
|
|
61
|
+
const result = await memoryDb.storePRD({
|
|
62
|
+
content,
|
|
63
|
+
prdId,
|
|
64
|
+
fileName
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
return result;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ============================================================
|
|
71
|
+
// Context Retrieval
|
|
72
|
+
// ============================================================
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Get relevant PRD context for a task (using semantic search)
|
|
76
|
+
*
|
|
77
|
+
* @param {string} taskDescription - The task description or query
|
|
78
|
+
* @param {object} options - Retrieval options
|
|
79
|
+
* @returns {string} Formatted context for the task
|
|
80
|
+
*/
|
|
81
|
+
async function getPRDContext(taskDescription, options = {}) {
|
|
82
|
+
const config = getConfig();
|
|
83
|
+
const maxTokens = options.maxTokens || config.prd?.maxContextTokens || DEFAULT_MAX_CONTEXT_TOKENS;
|
|
84
|
+
|
|
85
|
+
const result = await memoryDb.getPRDContext({
|
|
86
|
+
query: taskDescription,
|
|
87
|
+
maxTokens,
|
|
88
|
+
prdId: options.prdId
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
return result ? result.context : null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* List loaded PRDs
|
|
96
|
+
*/
|
|
97
|
+
async function listPRDs() {
|
|
98
|
+
const prds = await memoryDb.listPRDs();
|
|
99
|
+
return prds.map(p => ({
|
|
100
|
+
id: p.prd_id,
|
|
101
|
+
fileName: p.file_name,
|
|
102
|
+
chunkCount: p.chunk_count,
|
|
103
|
+
loadedAt: p.created_at
|
|
104
|
+
}));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Clear all PRD data
|
|
109
|
+
*/
|
|
110
|
+
async function clearPRDs() {
|
|
111
|
+
await memoryDb.clearPRDs();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Remove a specific PRD
|
|
116
|
+
*/
|
|
117
|
+
async function removePRD(prdId) {
|
|
118
|
+
const result = await memoryDb.deletePRD(prdId);
|
|
119
|
+
return result.deleted;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ============================================================
|
|
123
|
+
// CLI
|
|
124
|
+
// ============================================================
|
|
125
|
+
|
|
126
|
+
function printUsage() {
|
|
127
|
+
console.log(`
|
|
128
|
+
Wogi Flow - PRD Manager
|
|
129
|
+
|
|
130
|
+
Usage: ./scripts/flow prd <command> [args]
|
|
131
|
+
|
|
132
|
+
Commands:
|
|
133
|
+
load <file> Load PRD markdown file into memory
|
|
134
|
+
context <task> Get relevant PRD context for task
|
|
135
|
+
list List loaded PRDs
|
|
136
|
+
remove <prd-id> Remove a PRD from memory
|
|
137
|
+
clear Clear all PRD data
|
|
138
|
+
stats Show PRD memory statistics
|
|
139
|
+
|
|
140
|
+
Examples:
|
|
141
|
+
./scripts/flow prd load docs/PRD.md
|
|
142
|
+
./scripts/flow prd context "implement user login"
|
|
143
|
+
./scripts/flow prd list
|
|
144
|
+
|
|
145
|
+
Configuration (config.json):
|
|
146
|
+
prd.enabled Enable PRD features (default: false)
|
|
147
|
+
prd.maxContextTokens Max tokens for context (default: 2000)
|
|
148
|
+
`);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async function main() {
|
|
152
|
+
const args = process.argv.slice(2);
|
|
153
|
+
const command = args[0];
|
|
154
|
+
|
|
155
|
+
switch (command) {
|
|
156
|
+
case 'load': {
|
|
157
|
+
printHeader('Load PRD');
|
|
158
|
+
|
|
159
|
+
const filePath = args[1];
|
|
160
|
+
if (!filePath) {
|
|
161
|
+
error('Please provide a file path');
|
|
162
|
+
process.exit(1);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
try {
|
|
166
|
+
info('Loading PRD with embeddings (this may take a moment on first run)...');
|
|
167
|
+
const result = await loadPRD(filePath);
|
|
168
|
+
success(`Loaded PRD: ${result.prdId}`);
|
|
169
|
+
console.log(` Chunks: ${result.chunkCount}`);
|
|
170
|
+
console.log(` Sections: ${result.sections.join(', ')}`);
|
|
171
|
+
} catch (err) {
|
|
172
|
+
error(err.message);
|
|
173
|
+
process.exit(1);
|
|
174
|
+
}
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
case 'context': {
|
|
179
|
+
const task = args.slice(1).join(' ');
|
|
180
|
+
if (!task) {
|
|
181
|
+
error('Please provide a task description');
|
|
182
|
+
process.exit(1);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const context = await getPRDContext(task);
|
|
186
|
+
if (!context) {
|
|
187
|
+
warn('No PRD loaded. Run: ./scripts/flow prd load <file>');
|
|
188
|
+
process.exit(0);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
console.log(context);
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
case 'list': {
|
|
196
|
+
printHeader('Loaded PRDs');
|
|
197
|
+
|
|
198
|
+
const prds = await listPRDs();
|
|
199
|
+
if (prds.length === 0) {
|
|
200
|
+
info('No PRDs loaded.');
|
|
201
|
+
info('Load one with: ./scripts/flow prd load <file>');
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
console.log('');
|
|
206
|
+
for (const prd of prds) {
|
|
207
|
+
console.log(` ${color('green', prd.id)}`);
|
|
208
|
+
console.log(` File: ${prd.fileName || 'N/A'}`);
|
|
209
|
+
console.log(` Chunks: ${prd.chunkCount}`);
|
|
210
|
+
console.log(` Loaded: ${prd.loadedAt}`);
|
|
211
|
+
console.log('');
|
|
212
|
+
}
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
case 'remove': {
|
|
217
|
+
const prdId = args[1];
|
|
218
|
+
if (!prdId) {
|
|
219
|
+
error('Please provide a PRD ID');
|
|
220
|
+
process.exit(1);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (await removePRD(prdId)) {
|
|
224
|
+
success(`Removed PRD: ${prdId}`);
|
|
225
|
+
} else {
|
|
226
|
+
error(`PRD not found: ${prdId}`);
|
|
227
|
+
process.exit(1);
|
|
228
|
+
}
|
|
229
|
+
break;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
case 'clear': {
|
|
233
|
+
await clearPRDs();
|
|
234
|
+
success('All PRD data cleared');
|
|
235
|
+
break;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
case 'stats': {
|
|
239
|
+
printHeader('PRD Memory Stats');
|
|
240
|
+
const stats = await memoryDb.getStats();
|
|
241
|
+
console.log('');
|
|
242
|
+
console.log(` PRDs: ${stats.prds.total}`);
|
|
243
|
+
console.log(` Chunks: ${stats.prds.chunks}`);
|
|
244
|
+
console.log(` Facts: ${stats.facts.total}`);
|
|
245
|
+
console.log(` Proposals: ${stats.proposals.total} (${stats.proposals.pending} pending)`);
|
|
246
|
+
console.log('');
|
|
247
|
+
break;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
case '--help':
|
|
251
|
+
case '-h':
|
|
252
|
+
case 'help':
|
|
253
|
+
printUsage();
|
|
254
|
+
break;
|
|
255
|
+
|
|
256
|
+
default:
|
|
257
|
+
if (command) {
|
|
258
|
+
error(`Unknown command: ${command}`);
|
|
259
|
+
}
|
|
260
|
+
printUsage();
|
|
261
|
+
process.exit(command ? 1 : 0);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// ============================================================
|
|
266
|
+
// Exports
|
|
267
|
+
// ============================================================
|
|
268
|
+
|
|
269
|
+
module.exports = {
|
|
270
|
+
loadPRD,
|
|
271
|
+
getPRDContext,
|
|
272
|
+
listPRDs,
|
|
273
|
+
clearPRDs,
|
|
274
|
+
removePRD
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
if (require.main === module) {
|
|
278
|
+
main().catch(e => {
|
|
279
|
+
error(err.message);
|
|
280
|
+
process.exit(1);
|
|
281
|
+
});
|
|
282
|
+
}
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Wogi Flow - Progress Display Utilities
|
|
5
|
+
*
|
|
6
|
+
* Provides visual feedback during hybrid execution.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const readline = require('readline');
|
|
10
|
+
const { colors } = require('./flow-utils');
|
|
11
|
+
|
|
12
|
+
const symbols = {
|
|
13
|
+
success: '✅',
|
|
14
|
+
error: '❌',
|
|
15
|
+
warning: '⚠️',
|
|
16
|
+
info: 'ℹ️',
|
|
17
|
+
pending: '⏳',
|
|
18
|
+
running: '🔄',
|
|
19
|
+
skip: '⏭️',
|
|
20
|
+
parallel: '⚡',
|
|
21
|
+
rollback: '🔙',
|
|
22
|
+
plan: '📋',
|
|
23
|
+
step: '📍',
|
|
24
|
+
file: '📄',
|
|
25
|
+
folder: '📁',
|
|
26
|
+
check: '✓',
|
|
27
|
+
cross: '✗',
|
|
28
|
+
arrow: '→',
|
|
29
|
+
bullet: '•'
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
class ProgressBar {
|
|
33
|
+
constructor(total, width = 30) {
|
|
34
|
+
this.total = total;
|
|
35
|
+
this.current = 0;
|
|
36
|
+
this.width = width;
|
|
37
|
+
this.startTime = Date.now();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
update(current, label = '') {
|
|
41
|
+
this.current = current;
|
|
42
|
+
const percent = Math.floor((current / this.total) * 100);
|
|
43
|
+
const filled = Math.floor((current / this.total) * this.width);
|
|
44
|
+
const empty = this.width - filled;
|
|
45
|
+
|
|
46
|
+
const bar = colors.green + '█'.repeat(filled) + colors.dim + '░'.repeat(empty) + colors.reset;
|
|
47
|
+
const elapsed = ((Date.now() - this.startTime) / 1000).toFixed(1);
|
|
48
|
+
|
|
49
|
+
const line = ` [${bar}] ${percent}% ${label} ${colors.dim}(${elapsed}s)${colors.reset}`;
|
|
50
|
+
|
|
51
|
+
process.stdout.write('\r' + ' '.repeat(80) + '\r');
|
|
52
|
+
process.stdout.write(line);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
complete(label = 'Complete') {
|
|
56
|
+
this.update(this.total, label);
|
|
57
|
+
console.log('');
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
class Spinner {
|
|
62
|
+
constructor(text = 'Loading') {
|
|
63
|
+
this.text = text;
|
|
64
|
+
this.frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
65
|
+
this.frameIndex = 0;
|
|
66
|
+
this.interval = null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
start() {
|
|
70
|
+
this.interval = setInterval(() => {
|
|
71
|
+
const frame = this.frames[this.frameIndex];
|
|
72
|
+
process.stdout.write(`\r${colors.cyan}${frame}${colors.reset} ${this.text}`);
|
|
73
|
+
this.frameIndex = (this.frameIndex + 1) % this.frames.length;
|
|
74
|
+
}, 80);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
stop(finalText = '', success = true) {
|
|
78
|
+
clearInterval(this.interval);
|
|
79
|
+
const symbol = success ? colors.green + symbols.check : colors.red + symbols.cross;
|
|
80
|
+
process.stdout.write(`\r${symbol}${colors.reset} ${finalText || this.text}\n`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
update(text) {
|
|
84
|
+
this.text = text;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
class StepDisplay {
|
|
89
|
+
constructor() {
|
|
90
|
+
this.steps = [];
|
|
91
|
+
this.currentStep = -1;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
setSteps(steps) {
|
|
95
|
+
this.steps = steps.map(s => ({
|
|
96
|
+
id: s.id,
|
|
97
|
+
title: s.title,
|
|
98
|
+
status: 'pending',
|
|
99
|
+
duration: null,
|
|
100
|
+
error: null
|
|
101
|
+
}));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
startStep(stepId) {
|
|
105
|
+
this.currentStep = this.steps.findIndex(s => s.id === stepId);
|
|
106
|
+
if (this.currentStep >= 0) {
|
|
107
|
+
this.steps[this.currentStep].status = 'running';
|
|
108
|
+
this.steps[this.currentStep].startTime = Date.now();
|
|
109
|
+
}
|
|
110
|
+
this.render();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
completeStep(stepId, success = true, error = null) {
|
|
114
|
+
const step = this.steps.find(s => s.id === stepId);
|
|
115
|
+
if (step) {
|
|
116
|
+
step.status = success ? 'success' : 'error';
|
|
117
|
+
step.duration = ((Date.now() - step.startTime) / 1000).toFixed(1);
|
|
118
|
+
step.error = error;
|
|
119
|
+
}
|
|
120
|
+
this.render();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
skipStep(stepId) {
|
|
124
|
+
const step = this.steps.find(s => s.id === stepId);
|
|
125
|
+
if (step) {
|
|
126
|
+
step.status = 'skipped';
|
|
127
|
+
}
|
|
128
|
+
this.render();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
render() {
|
|
132
|
+
if (this.steps.length > 0) {
|
|
133
|
+
process.stdout.write(`${ESC}[${this.steps.length + 2}A`);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
console.log('');
|
|
137
|
+
for (const step of this.steps) {
|
|
138
|
+
let statusIcon, statusColor;
|
|
139
|
+
|
|
140
|
+
switch (step.status) {
|
|
141
|
+
case 'success':
|
|
142
|
+
statusIcon = symbols.success;
|
|
143
|
+
statusColor = colors.green;
|
|
144
|
+
break;
|
|
145
|
+
case 'error':
|
|
146
|
+
statusIcon = symbols.error;
|
|
147
|
+
statusColor = colors.red;
|
|
148
|
+
break;
|
|
149
|
+
case 'running':
|
|
150
|
+
statusIcon = symbols.running;
|
|
151
|
+
statusColor = colors.cyan;
|
|
152
|
+
break;
|
|
153
|
+
case 'skipped':
|
|
154
|
+
statusIcon = symbols.skip;
|
|
155
|
+
statusColor = colors.dim;
|
|
156
|
+
break;
|
|
157
|
+
default:
|
|
158
|
+
statusIcon = symbols.pending;
|
|
159
|
+
statusColor = colors.dim;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const duration = step.duration ? ` ${colors.dim}(${step.duration}s)${colors.reset}` : '';
|
|
163
|
+
console.log(` ${statusIcon} ${statusColor}Step ${step.id}:${colors.reset} ${step.title}${duration}`);
|
|
164
|
+
}
|
|
165
|
+
console.log('');
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function formatBox(title, content, width = 60) {
|
|
170
|
+
const lines = content.split('\n');
|
|
171
|
+
const topBorder = '═'.repeat(width);
|
|
172
|
+
const bottomBorder = '═'.repeat(width);
|
|
173
|
+
|
|
174
|
+
let output = `${colors.cyan}╔${topBorder}╗${colors.reset}\n`;
|
|
175
|
+
|
|
176
|
+
const titlePadding = Math.floor((width - title.length) / 2);
|
|
177
|
+
output += `${colors.cyan}║${colors.reset}${' '.repeat(titlePadding)}${colors.bold}${title}${colors.reset}${' '.repeat(width - titlePadding - title.length)}${colors.cyan}║${colors.reset}\n`;
|
|
178
|
+
output += `${colors.cyan}╠${topBorder}╣${colors.reset}\n`;
|
|
179
|
+
|
|
180
|
+
for (const line of lines) {
|
|
181
|
+
const paddedLine = line.slice(0, width - 2).padEnd(width - 2);
|
|
182
|
+
output += `${colors.cyan}║${colors.reset} ${paddedLine}${colors.cyan}║${colors.reset}\n`;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
output += `${colors.cyan}╚${bottomBorder}╝${colors.reset}`;
|
|
186
|
+
|
|
187
|
+
return output;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function formatPlanSummary(plan) {
|
|
191
|
+
let output = '';
|
|
192
|
+
|
|
193
|
+
output += `\n${colors.cyan}${'═'.repeat(60)}${colors.reset}\n`;
|
|
194
|
+
output += `${colors.cyan}${' '.repeat(20)}EXECUTION PLAN${colors.reset}\n`;
|
|
195
|
+
output += `${colors.cyan}${'═'.repeat(60)}${colors.reset}\n\n`;
|
|
196
|
+
|
|
197
|
+
output += `${symbols.plan} Task: ${colors.bold}${plan.task}${colors.reset}\n`;
|
|
198
|
+
output += `🤖 Executor: ${plan.model || 'Local LLM'}\n`;
|
|
199
|
+
output += `📊 Steps: ${plan.steps.length}\n`;
|
|
200
|
+
output += `💰 Est. savings: ~${(plan.estimatedTokensSaved || 0).toLocaleString()} tokens\n\n`;
|
|
201
|
+
|
|
202
|
+
for (const step of plan.steps) {
|
|
203
|
+
output += `${colors.dim}┌${'─'.repeat(58)}${colors.reset}\n`;
|
|
204
|
+
output += `${colors.dim}│${colors.reset} ${colors.bold}Step ${step.id}:${colors.reset} ${step.title}\n`;
|
|
205
|
+
output += `${colors.dim}│${colors.reset} Type: ${step.type}\n`;
|
|
206
|
+
if (step.params?.path) {
|
|
207
|
+
output += `${colors.dim}│${colors.reset} Path: ${colors.cyan}${step.params.path}${colors.reset}\n`;
|
|
208
|
+
}
|
|
209
|
+
const deps = step.dependsOn?.length > 0 ? `Steps ${step.dependsOn.join(', ')}` : 'None';
|
|
210
|
+
output += `${colors.dim}│${colors.reset} Dependencies: ${deps}\n`;
|
|
211
|
+
}
|
|
212
|
+
output += `${colors.dim}└${'─'.repeat(58)}${colors.reset}\n`;
|
|
213
|
+
|
|
214
|
+
return output;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function formatOptions(options) {
|
|
218
|
+
let output = '\nHow would you like to proceed?\n\n';
|
|
219
|
+
|
|
220
|
+
for (const opt of options) {
|
|
221
|
+
output += ` ${colors.cyan}[${opt.key}]${colors.reset} ${opt.icon} ${opt.label}\n`;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return output + '\nYour choice: ';
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
async function prompt(question) {
|
|
228
|
+
const rl = readline.createInterface({
|
|
229
|
+
input: process.stdin,
|
|
230
|
+
output: process.stdout
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
return new Promise(resolve => {
|
|
234
|
+
rl.question(question, answer => {
|
|
235
|
+
rl.close();
|
|
236
|
+
resolve(answer.trim());
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function formatResults(results) {
|
|
242
|
+
let output = '';
|
|
243
|
+
|
|
244
|
+
output += `\n${colors.cyan}${'═'.repeat(60)}${colors.reset}\n`;
|
|
245
|
+
output += `${colors.cyan}${' '.repeat(18)}EXECUTION SUMMARY${colors.reset}\n`;
|
|
246
|
+
output += `${colors.cyan}${'═'.repeat(60)}${colors.reset}\n\n`;
|
|
247
|
+
|
|
248
|
+
if (results.success) {
|
|
249
|
+
output += `${symbols.success} ${colors.green}Plan executed successfully!${colors.reset}\n`;
|
|
250
|
+
} else {
|
|
251
|
+
output += `${symbols.error} ${colors.red}Plan execution failed${colors.reset}\n`;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const successCount = results.steps?.filter(s => s.success).length || 0;
|
|
255
|
+
const totalCount = results.steps?.length || 0;
|
|
256
|
+
|
|
257
|
+
output += `\nSteps completed: ${successCount}/${totalCount}\n`;
|
|
258
|
+
output += `Tokens saved: ~${(results.tokensSaved || 0).toLocaleString()}\n`;
|
|
259
|
+
|
|
260
|
+
if (results.escalateToCloud?.length > 0) {
|
|
261
|
+
output += `\n${colors.yellow}${symbols.warning} Steps requiring escalation:${colors.reset}\n`;
|
|
262
|
+
for (const step of results.escalateToCloud) {
|
|
263
|
+
output += ` ${symbols.bullet} Step ${step.id}: ${step.title}\n`;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
output += `\n${colors.dim}Results saved to: .workflow/state/hybrid-results.json${colors.reset}\n`;
|
|
268
|
+
|
|
269
|
+
return output;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
module.exports = {
|
|
273
|
+
colors,
|
|
274
|
+
symbols,
|
|
275
|
+
ProgressBar,
|
|
276
|
+
Spinner,
|
|
277
|
+
StepDisplay,
|
|
278
|
+
formatBox,
|
|
279
|
+
formatPlanSummary,
|
|
280
|
+
formatOptions,
|
|
281
|
+
formatResults,
|
|
282
|
+
prompt
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
if (require.main === module) {
|
|
286
|
+
const demo = async () => {
|
|
287
|
+
console.log('\n=== Progress Display Demo ===\n');
|
|
288
|
+
|
|
289
|
+
const spinner = new Spinner('Detecting providers...');
|
|
290
|
+
spinner.start();
|
|
291
|
+
await new Promise(r => setTimeout(r, 2000));
|
|
292
|
+
spinner.stop('Providers detected', true);
|
|
293
|
+
|
|
294
|
+
console.log('\nProgress bar:');
|
|
295
|
+
const bar = new ProgressBar(10);
|
|
296
|
+
for (let i = 0; i <= 10; i++) {
|
|
297
|
+
bar.update(i, `Step ${i}/10`);
|
|
298
|
+
await new Promise(r => setTimeout(r, 200));
|
|
299
|
+
}
|
|
300
|
+
bar.complete('All steps done');
|
|
301
|
+
|
|
302
|
+
const plan = {
|
|
303
|
+
task: 'Add user authentication',
|
|
304
|
+
model: 'nemotron-3-nano',
|
|
305
|
+
estimatedTokensSaved: 12000,
|
|
306
|
+
steps: [
|
|
307
|
+
{ id: 1, type: 'create-service', title: 'Create authService', params: { path: 'src/services/authService.ts' }, dependsOn: [] },
|
|
308
|
+
{ id: 2, type: 'create-hook', title: 'Create useAuth hook', params: { path: 'src/hooks/useAuth.ts' }, dependsOn: [1] }
|
|
309
|
+
]
|
|
310
|
+
};
|
|
311
|
+
console.log(formatPlanSummary(plan));
|
|
312
|
+
|
|
313
|
+
const options = [
|
|
314
|
+
{ key: '1', icon: '✅', label: 'Execute this plan' },
|
|
315
|
+
{ key: '2', icon: '✅', label: 'Execute, skip future reviews' },
|
|
316
|
+
{ key: '3', icon: '✏️', label: 'Modify something' },
|
|
317
|
+
{ key: '4', icon: '❌', label: 'Cancel' }
|
|
318
|
+
];
|
|
319
|
+
console.log(formatOptions(options));
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
demo();
|
|
323
|
+
}
|