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
package/lib/upgrader.js
ADDED
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Wogi Flow Upgrader
|
|
5
|
+
*
|
|
6
|
+
* Handles project upgrades with `flow upgrade`.
|
|
7
|
+
* Migrates projects from older versions to the current version,
|
|
8
|
+
* preserving user data while updating configuration and scripts.
|
|
9
|
+
*
|
|
10
|
+
* @module lib/upgrader
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
|
|
16
|
+
// Shared utilities
|
|
17
|
+
const { findProjectRoot, copyDir, safeReadJson } = require('./utils');
|
|
18
|
+
|
|
19
|
+
// Package info
|
|
20
|
+
const packageJson = require('../package.json');
|
|
21
|
+
const CURRENT_VERSION = packageJson.version;
|
|
22
|
+
const PACKAGE_ROOT = path.resolve(__dirname, '..');
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Parse command line arguments
|
|
26
|
+
* @param {string[]} args - Command line arguments
|
|
27
|
+
* @returns {Object} Parsed options
|
|
28
|
+
*/
|
|
29
|
+
function parseArgs(args) {
|
|
30
|
+
const options = {
|
|
31
|
+
force: false,
|
|
32
|
+
dryRun: false,
|
|
33
|
+
help: false
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
for (let i = 0; i < args.length; i++) {
|
|
37
|
+
const arg = args[i];
|
|
38
|
+
|
|
39
|
+
if (arg === '--force' || arg === '-f') {
|
|
40
|
+
options.force = true;
|
|
41
|
+
} else if (arg === '--dry-run' || arg === '-n') {
|
|
42
|
+
options.dryRun = true;
|
|
43
|
+
} else if (arg === '--help' || arg === '-h') {
|
|
44
|
+
options.help = true;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return options;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Show help message
|
|
53
|
+
*/
|
|
54
|
+
function showHelp() {
|
|
55
|
+
console.log(`
|
|
56
|
+
Usage: flow upgrade [options]
|
|
57
|
+
|
|
58
|
+
Upgrade Wogi Flow in the current project.
|
|
59
|
+
|
|
60
|
+
Options:
|
|
61
|
+
--force, -f Force upgrade even if versions match
|
|
62
|
+
--dry-run, -n Show what would be changed without making changes
|
|
63
|
+
--help, -h Show this help message
|
|
64
|
+
|
|
65
|
+
The upgrade process:
|
|
66
|
+
1. Backs up current configuration
|
|
67
|
+
2. Updates scripts to latest version
|
|
68
|
+
3. Migrates configuration if needed
|
|
69
|
+
4. Updates templates and agents
|
|
70
|
+
5. Preserves user state files
|
|
71
|
+
|
|
72
|
+
Examples:
|
|
73
|
+
flow upgrade # Upgrade current project
|
|
74
|
+
flow upgrade --dry-run # Preview changes
|
|
75
|
+
flow upgrade --force # Force re-upgrade
|
|
76
|
+
`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// findProjectRoot is imported from ./utils
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Get current project version
|
|
83
|
+
* @param {string} projectRoot - Project root directory
|
|
84
|
+
* @returns {string|null} Version string or null
|
|
85
|
+
*/
|
|
86
|
+
function getProjectVersion(projectRoot) {
|
|
87
|
+
const configPath = path.join(projectRoot, '.workflow', 'config.json');
|
|
88
|
+
const config = safeReadJson(configPath);
|
|
89
|
+
return config?.version || null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Compare versions
|
|
94
|
+
* @param {string} v1 - First version
|
|
95
|
+
* @param {string} v2 - Second version
|
|
96
|
+
* @returns {number} -1 if v1 < v2, 0 if equal, 1 if v1 > v2
|
|
97
|
+
*/
|
|
98
|
+
function compareVersions(v1, v2) {
|
|
99
|
+
const parts1 = v1.split('.').map(Number);
|
|
100
|
+
const parts2 = v2.split('.').map(Number);
|
|
101
|
+
|
|
102
|
+
for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
|
|
103
|
+
const p1 = parts1[i] || 0;
|
|
104
|
+
const p2 = parts2[i] || 0;
|
|
105
|
+
if (p1 < p2) return -1;
|
|
106
|
+
if (p1 > p2) return 1;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return 0;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Create a backup of the current configuration
|
|
114
|
+
* @param {string} projectRoot - Project root directory
|
|
115
|
+
* @param {boolean} dryRun - If true, only show what would be done
|
|
116
|
+
* @returns {string|null} Backup directory path or null
|
|
117
|
+
*/
|
|
118
|
+
function createBackup(projectRoot, dryRun) {
|
|
119
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
120
|
+
const backupDir = path.join(projectRoot, '.workflow', 'backups', timestamp);
|
|
121
|
+
|
|
122
|
+
if (dryRun) {
|
|
123
|
+
console.log(` Would create backup at: ${backupDir}`);
|
|
124
|
+
return backupDir;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
fs.mkdirSync(backupDir, { recursive: true });
|
|
128
|
+
|
|
129
|
+
// Backup config.json
|
|
130
|
+
const configPath = path.join(projectRoot, '.workflow', 'config.json');
|
|
131
|
+
if (fs.existsSync(configPath)) {
|
|
132
|
+
fs.copyFileSync(configPath, path.join(backupDir, 'config.json'));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
console.log(` Created backup at: ${backupDir}`);
|
|
136
|
+
return backupDir;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// copyDir is imported from ./utils (with dryRun support)
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Update scripts directory
|
|
143
|
+
* @param {string} projectRoot - Project root directory
|
|
144
|
+
* @param {boolean} dryRun - If true, only show what would be done
|
|
145
|
+
*/
|
|
146
|
+
function updateScripts(projectRoot, dryRun) {
|
|
147
|
+
const packageScripts = path.join(PACKAGE_ROOT, 'scripts');
|
|
148
|
+
const projectScripts = path.join(projectRoot, 'scripts');
|
|
149
|
+
|
|
150
|
+
if (!fs.existsSync(packageScripts)) {
|
|
151
|
+
console.log(' Warning: Package scripts not found');
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (dryRun) {
|
|
156
|
+
console.log(` Would update: scripts/`);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Copy all scripts
|
|
161
|
+
copyDir(packageScripts, projectScripts, false);
|
|
162
|
+
|
|
163
|
+
// Make flow script executable
|
|
164
|
+
const flowScript = path.join(projectScripts, 'flow');
|
|
165
|
+
if (fs.existsSync(flowScript)) {
|
|
166
|
+
fs.chmodSync(flowScript, '755');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
console.log(' Updated scripts/');
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Update templates
|
|
174
|
+
* @param {string} projectRoot - Project root directory
|
|
175
|
+
* @param {boolean} dryRun - If true, only show what would be done
|
|
176
|
+
*/
|
|
177
|
+
function updateTemplates(projectRoot, dryRun) {
|
|
178
|
+
const packageTemplates = path.join(PACKAGE_ROOT, '.workflow', 'templates');
|
|
179
|
+
const projectTemplates = path.join(projectRoot, '.workflow', 'templates');
|
|
180
|
+
|
|
181
|
+
if (fs.existsSync(packageTemplates)) {
|
|
182
|
+
if (dryRun) {
|
|
183
|
+
console.log(' Would update: .workflow/templates/');
|
|
184
|
+
} else {
|
|
185
|
+
copyDir(packageTemplates, projectTemplates, false);
|
|
186
|
+
console.log(' Updated .workflow/templates/');
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Update agents
|
|
193
|
+
* @param {string} projectRoot - Project root directory
|
|
194
|
+
* @param {boolean} dryRun - If true, only show what would be done
|
|
195
|
+
*/
|
|
196
|
+
function updateAgents(projectRoot, dryRun) {
|
|
197
|
+
const packageAgents = path.join(PACKAGE_ROOT, '.workflow', 'agents');
|
|
198
|
+
const projectAgents = path.join(projectRoot, '.workflow', 'agents');
|
|
199
|
+
|
|
200
|
+
if (fs.existsSync(packageAgents)) {
|
|
201
|
+
if (dryRun) {
|
|
202
|
+
console.log(' Would update: .workflow/agents/');
|
|
203
|
+
} else {
|
|
204
|
+
copyDir(packageAgents, projectAgents, false);
|
|
205
|
+
console.log(' Updated .workflow/agents/');
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Update bridges
|
|
212
|
+
* @param {string} projectRoot - Project root directory
|
|
213
|
+
* @param {boolean} dryRun - If true, only show what would be done
|
|
214
|
+
*/
|
|
215
|
+
function updateBridges(projectRoot, dryRun) {
|
|
216
|
+
const packageBridges = path.join(PACKAGE_ROOT, '.workflow', 'bridges');
|
|
217
|
+
const projectBridges = path.join(projectRoot, '.workflow', 'bridges');
|
|
218
|
+
|
|
219
|
+
if (fs.existsSync(packageBridges)) {
|
|
220
|
+
if (dryRun) {
|
|
221
|
+
console.log(' Would update: .workflow/bridges/');
|
|
222
|
+
} else {
|
|
223
|
+
copyDir(packageBridges, projectBridges, false);
|
|
224
|
+
console.log(' Updated .workflow/bridges/');
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Update configuration version
|
|
231
|
+
* @param {string} projectRoot - Project root directory
|
|
232
|
+
* @param {boolean} dryRun - If true, only show what would be done
|
|
233
|
+
*/
|
|
234
|
+
function updateConfigVersion(projectRoot, dryRun) {
|
|
235
|
+
const configPath = path.join(projectRoot, '.workflow', 'config.json');
|
|
236
|
+
|
|
237
|
+
const config = safeReadJson(configPath);
|
|
238
|
+
if (!config) {
|
|
239
|
+
console.log(' Warning: config.json not found or invalid');
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (dryRun) {
|
|
244
|
+
console.log(` Would update version in config.json to ${CURRENT_VERSION}`);
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
try {
|
|
249
|
+
config.version = CURRENT_VERSION;
|
|
250
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
251
|
+
console.log(` Updated config.json version to ${CURRENT_VERSION}`);
|
|
252
|
+
} catch (err) {
|
|
253
|
+
console.log(` Warning: Could not update config.json: ${err.message}`);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Run version-specific migrations
|
|
259
|
+
* @param {string} projectRoot - Project root directory
|
|
260
|
+
* @param {string} fromVersion - Current version
|
|
261
|
+
* @param {string} toVersion - Target version
|
|
262
|
+
* @param {boolean} dryRun - If true, only show what would be done
|
|
263
|
+
*/
|
|
264
|
+
function runMigrations(projectRoot, fromVersion, toVersion, dryRun) {
|
|
265
|
+
// Define migrations for specific version ranges
|
|
266
|
+
const migrations = [
|
|
267
|
+
{
|
|
268
|
+
from: '1.0.0',
|
|
269
|
+
to: '1.5.0',
|
|
270
|
+
name: 'Add state directory',
|
|
271
|
+
run: (root) => {
|
|
272
|
+
const stateDir = path.join(root, '.workflow', 'state');
|
|
273
|
+
if (!fs.existsSync(stateDir)) {
|
|
274
|
+
fs.mkdirSync(stateDir, { recursive: true });
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
from: '1.5.0',
|
|
280
|
+
to: '1.9.0',
|
|
281
|
+
name: 'Add models directory',
|
|
282
|
+
run: (root) => {
|
|
283
|
+
const modelsDir = path.join(root, '.workflow', 'models');
|
|
284
|
+
if (!fs.existsSync(modelsDir)) {
|
|
285
|
+
fs.mkdirSync(modelsDir, { recursive: true });
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
// Add more migrations as needed
|
|
290
|
+
];
|
|
291
|
+
|
|
292
|
+
// Find applicable migrations
|
|
293
|
+
const applicable = migrations.filter(m => {
|
|
294
|
+
return compareVersions(fromVersion, m.from) >= 0 &&
|
|
295
|
+
compareVersions(fromVersion, m.to) < 0 &&
|
|
296
|
+
compareVersions(toVersion, m.to) >= 0;
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
if (applicable.length === 0) {
|
|
300
|
+
console.log(' No migrations needed');
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
console.log(` Running ${applicable.length} migration(s):`);
|
|
305
|
+
|
|
306
|
+
for (const migration of applicable) {
|
|
307
|
+
if (dryRun) {
|
|
308
|
+
console.log(` Would run: ${migration.name}`);
|
|
309
|
+
} else {
|
|
310
|
+
try {
|
|
311
|
+
migration.run(projectRoot);
|
|
312
|
+
console.log(` ✓ ${migration.name}`);
|
|
313
|
+
} catch (err) {
|
|
314
|
+
console.log(` ✗ ${migration.name}: ${err.message}`);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Main upgrade function
|
|
322
|
+
* @param {string[]} args - Command line arguments
|
|
323
|
+
*/
|
|
324
|
+
async function upgrade(args) {
|
|
325
|
+
const options = parseArgs(args);
|
|
326
|
+
|
|
327
|
+
if (options.help) {
|
|
328
|
+
showHelp();
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const projectRoot = findProjectRoot();
|
|
333
|
+
|
|
334
|
+
if (!projectRoot) {
|
|
335
|
+
console.error('Error: Not in a Wogi Flow project');
|
|
336
|
+
console.error('Use `flow init` to initialize a new project');
|
|
337
|
+
process.exit(1);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const currentVersion = getProjectVersion(projectRoot);
|
|
341
|
+
|
|
342
|
+
if (!currentVersion) {
|
|
343
|
+
console.error('Error: Could not determine project version');
|
|
344
|
+
console.error('The .workflow/config.json may be missing or invalid');
|
|
345
|
+
process.exit(1);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
console.log('\n🔄 Wogi Flow Upgrader\n');
|
|
349
|
+
console.log(` Current version: ${currentVersion}`);
|
|
350
|
+
console.log(` Target version: ${CURRENT_VERSION}`);
|
|
351
|
+
|
|
352
|
+
if (options.dryRun) {
|
|
353
|
+
console.log('\n (Dry run - no changes will be made)\n');
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Check if upgrade is needed
|
|
357
|
+
const comparison = compareVersions(currentVersion, CURRENT_VERSION);
|
|
358
|
+
|
|
359
|
+
if (comparison === 0 && !options.force) {
|
|
360
|
+
console.log('\n✓ Project is already at the latest version');
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if (comparison > 0) {
|
|
365
|
+
console.log('\n⚠ Project version is newer than package version');
|
|
366
|
+
console.log(' This may indicate a development version.');
|
|
367
|
+
if (!options.force) {
|
|
368
|
+
console.log(' Use --force to downgrade');
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
console.log('\nUpgrading...\n');
|
|
374
|
+
|
|
375
|
+
// Create backup
|
|
376
|
+
createBackup(projectRoot, options.dryRun);
|
|
377
|
+
|
|
378
|
+
// Run migrations
|
|
379
|
+
runMigrations(projectRoot, currentVersion, CURRENT_VERSION, options.dryRun);
|
|
380
|
+
|
|
381
|
+
// Update components
|
|
382
|
+
updateScripts(projectRoot, options.dryRun);
|
|
383
|
+
updateTemplates(projectRoot, options.dryRun);
|
|
384
|
+
updateAgents(projectRoot, options.dryRun);
|
|
385
|
+
updateBridges(projectRoot, options.dryRun);
|
|
386
|
+
|
|
387
|
+
// Update version in config
|
|
388
|
+
updateConfigVersion(projectRoot, options.dryRun);
|
|
389
|
+
|
|
390
|
+
if (options.dryRun) {
|
|
391
|
+
console.log('\n✓ Dry run complete - no changes made');
|
|
392
|
+
} else {
|
|
393
|
+
console.log('\n✅ Upgrade complete!\n');
|
|
394
|
+
console.log('Next steps:');
|
|
395
|
+
console.log(' 1. Review changes in .workflow/');
|
|
396
|
+
console.log(' 2. Run `./scripts/flow health` to verify installation');
|
|
397
|
+
console.log(' 3. Commit the upgraded files');
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
module.exports = { upgrade };
|
package/lib/utils.js
ADDED
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Wogi Flow Shared Utilities
|
|
5
|
+
*
|
|
6
|
+
* Common utility functions used across lib modules.
|
|
7
|
+
* Extracted to reduce duplication and standardize behavior.
|
|
8
|
+
*
|
|
9
|
+
* @module lib/utils
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
const https = require('https');
|
|
15
|
+
|
|
16
|
+
// Constants
|
|
17
|
+
const MAX_REDIRECTS = 5;
|
|
18
|
+
const REQUEST_TIMEOUT = 10000; // 10 seconds
|
|
19
|
+
const MAX_RESPONSE_SIZE = 10 * 1024 * 1024; // 10MB
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Find the project root by looking for .workflow directory
|
|
23
|
+
* @returns {string|null} Project root path or null if not in a project
|
|
24
|
+
*/
|
|
25
|
+
function findProjectRoot() {
|
|
26
|
+
let dir = process.cwd();
|
|
27
|
+
const root = path.parse(dir).root;
|
|
28
|
+
|
|
29
|
+
while (dir !== root) {
|
|
30
|
+
if (fs.existsSync(path.join(dir, '.workflow'))) {
|
|
31
|
+
return dir;
|
|
32
|
+
}
|
|
33
|
+
dir = path.dirname(dir);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Safely parse JSON with prototype pollution protection
|
|
41
|
+
* @param {string} content - JSON string to parse
|
|
42
|
+
* @param {*} defaultValue - Default value if parsing fails
|
|
43
|
+
* @returns {Object} Parsed object or default value
|
|
44
|
+
*/
|
|
45
|
+
function safeJsonParse(content, defaultValue = null) {
|
|
46
|
+
try {
|
|
47
|
+
const parsed = JSON.parse(content);
|
|
48
|
+
|
|
49
|
+
// Protect against prototype pollution
|
|
50
|
+
// Only check for __proto__ and prototype as own properties (not inherited)
|
|
51
|
+
if (parsed && typeof parsed === 'object') {
|
|
52
|
+
if (Object.prototype.hasOwnProperty.call(parsed, '__proto__') ||
|
|
53
|
+
Object.prototype.hasOwnProperty.call(parsed, 'prototype')) {
|
|
54
|
+
console.warn('Warning: Potentially malicious JSON detected');
|
|
55
|
+
return defaultValue;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return parsed;
|
|
60
|
+
} catch (err) {
|
|
61
|
+
return defaultValue;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Safely read and parse JSON file
|
|
67
|
+
* @param {string} filePath - Path to JSON file
|
|
68
|
+
* @param {*} defaultValue - Default value if reading/parsing fails
|
|
69
|
+
* @returns {Object} Parsed object or default value
|
|
70
|
+
*/
|
|
71
|
+
function safeReadJson(filePath, defaultValue = null) {
|
|
72
|
+
try {
|
|
73
|
+
if (!fs.existsSync(filePath)) {
|
|
74
|
+
return defaultValue;
|
|
75
|
+
}
|
|
76
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
77
|
+
return safeJsonParse(content, defaultValue);
|
|
78
|
+
} catch (err) {
|
|
79
|
+
return defaultValue;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Make an HTTPS GET request with safety limits
|
|
85
|
+
* @param {string} url - URL to fetch
|
|
86
|
+
* @param {Object} options - Options
|
|
87
|
+
* @param {number} options.timeout - Request timeout in ms
|
|
88
|
+
* @param {number} options.maxRedirects - Maximum redirects to follow
|
|
89
|
+
* @param {number} options.redirectCount - Current redirect count (internal)
|
|
90
|
+
* @returns {Promise<string>} Response body
|
|
91
|
+
*/
|
|
92
|
+
function httpsGet(url, options = {}) {
|
|
93
|
+
const timeout = options.timeout || REQUEST_TIMEOUT;
|
|
94
|
+
const maxRedirects = options.maxRedirects || MAX_REDIRECTS;
|
|
95
|
+
const redirectCount = options.redirectCount || 0;
|
|
96
|
+
|
|
97
|
+
return new Promise((resolve, reject) => {
|
|
98
|
+
// Validate URL
|
|
99
|
+
let parsedUrl;
|
|
100
|
+
try {
|
|
101
|
+
parsedUrl = new URL(url);
|
|
102
|
+
if (parsedUrl.protocol !== 'https:') {
|
|
103
|
+
reject(new Error('Only HTTPS URLs are allowed'));
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
} catch (err) {
|
|
107
|
+
reject(new Error(`Invalid URL: ${url}`));
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const req = https.get(url, { timeout }, (res) => {
|
|
112
|
+
// Handle redirects
|
|
113
|
+
if (res.statusCode === 301 || res.statusCode === 302) {
|
|
114
|
+
if (redirectCount >= maxRedirects) {
|
|
115
|
+
reject(new Error('Too many redirects'));
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const location = res.headers.location;
|
|
120
|
+
if (!location) {
|
|
121
|
+
reject(new Error('Redirect without location header'));
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Validate redirect URL (same-origin or absolute HTTPS)
|
|
126
|
+
let redirectUrl;
|
|
127
|
+
try {
|
|
128
|
+
redirectUrl = new URL(location, url);
|
|
129
|
+
if (redirectUrl.protocol !== 'https:') {
|
|
130
|
+
reject(new Error('Redirect to non-HTTPS URL not allowed'));
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
} catch (err) {
|
|
134
|
+
reject(new Error(`Invalid redirect URL: ${location}`));
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return httpsGet(redirectUrl.href, {
|
|
139
|
+
...options,
|
|
140
|
+
redirectCount: redirectCount + 1
|
|
141
|
+
}).then(resolve).catch(reject);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (res.statusCode !== 200) {
|
|
145
|
+
reject(new Error(`HTTP ${res.statusCode}`));
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
let data = '';
|
|
150
|
+
let size = 0;
|
|
151
|
+
|
|
152
|
+
res.on('data', chunk => {
|
|
153
|
+
size += chunk.length;
|
|
154
|
+
if (size > MAX_RESPONSE_SIZE) {
|
|
155
|
+
req.destroy();
|
|
156
|
+
reject(new Error('Response too large'));
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
data += chunk;
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
res.on('end', () => resolve(data));
|
|
163
|
+
res.on('error', reject);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
req.on('timeout', () => {
|
|
167
|
+
req.destroy();
|
|
168
|
+
reject(new Error('Request timeout'));
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
req.on('error', reject);
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Copy directory recursively with optional dry-run
|
|
177
|
+
* @param {string} src - Source directory
|
|
178
|
+
* @param {string} dest - Destination directory
|
|
179
|
+
* @param {boolean} dryRun - If true, only show what would be done
|
|
180
|
+
*/
|
|
181
|
+
function copyDir(src, dest, dryRun = false) {
|
|
182
|
+
if (!fs.existsSync(src)) return;
|
|
183
|
+
|
|
184
|
+
if (dryRun) {
|
|
185
|
+
console.log(` Would copy: ${src} -> ${dest}`);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
try {
|
|
190
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
191
|
+
|
|
192
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
193
|
+
|
|
194
|
+
for (const entry of entries) {
|
|
195
|
+
const srcPath = path.join(src, entry.name);
|
|
196
|
+
const destPath = path.join(dest, entry.name);
|
|
197
|
+
|
|
198
|
+
if (entry.isDirectory()) {
|
|
199
|
+
copyDir(srcPath, destPath, dryRun);
|
|
200
|
+
} else {
|
|
201
|
+
fs.copyFileSync(srcPath, destPath);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
} catch (err) {
|
|
205
|
+
console.error(` Error copying ${src}: ${err.message}`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Parse CLI arguments with bounds checking
|
|
211
|
+
* @param {string[]} args - Command line arguments
|
|
212
|
+
* @param {Object} spec - Argument specification { flags: { '--flag': 'key' }, withValue: ['--flag'] }
|
|
213
|
+
* @returns {Object} Parsed options
|
|
214
|
+
*/
|
|
215
|
+
function parseArgs(args, spec = {}) {
|
|
216
|
+
const options = {};
|
|
217
|
+
const flags = spec.flags || {};
|
|
218
|
+
const withValue = new Set(spec.withValue || []);
|
|
219
|
+
|
|
220
|
+
for (let i = 0; i < args.length; i++) {
|
|
221
|
+
const arg = args[i];
|
|
222
|
+
|
|
223
|
+
if (flags[arg]) {
|
|
224
|
+
const key = flags[arg];
|
|
225
|
+
if (withValue.has(arg)) {
|
|
226
|
+
// Bounds check before accessing next argument
|
|
227
|
+
if (i + 1 >= args.length) {
|
|
228
|
+
console.error(`Error: ${arg} requires a value`);
|
|
229
|
+
options._error = true;
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
options[key] = args[++i];
|
|
233
|
+
} else {
|
|
234
|
+
options[key] = true;
|
|
235
|
+
}
|
|
236
|
+
} else if (!arg.startsWith('-')) {
|
|
237
|
+
// Positional argument
|
|
238
|
+
if (!options._positional) options._positional = [];
|
|
239
|
+
options._positional.push(arg);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return options;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Validate a path is within a base directory (prevents path traversal)
|
|
248
|
+
* @param {string} basePath - Base directory path
|
|
249
|
+
* @param {string} targetPath - Target path to validate
|
|
250
|
+
* @returns {string|null} Resolved path if valid, null if traversal detected
|
|
251
|
+
*/
|
|
252
|
+
function validatePath(basePath, targetPath) {
|
|
253
|
+
const resolved = path.resolve(basePath, targetPath);
|
|
254
|
+
const normalizedBase = path.resolve(basePath);
|
|
255
|
+
|
|
256
|
+
if (!resolved.startsWith(normalizedBase + path.sep) && resolved !== normalizedBase) {
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return resolved;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Safely write file with path validation
|
|
265
|
+
* @param {string} basePath - Base directory (file must be within this)
|
|
266
|
+
* @param {string} relativePath - Relative path within base
|
|
267
|
+
* @param {string} content - File content
|
|
268
|
+
* @returns {boolean} True if written successfully
|
|
269
|
+
*/
|
|
270
|
+
function safeWriteFile(basePath, relativePath, content) {
|
|
271
|
+
// Validate path to prevent traversal
|
|
272
|
+
const safePath = validatePath(basePath, relativePath);
|
|
273
|
+
if (!safePath) {
|
|
274
|
+
console.error(`Error: Invalid path '${relativePath}' (path traversal detected)`);
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
try {
|
|
279
|
+
// Ensure parent directory exists
|
|
280
|
+
const dir = path.dirname(safePath);
|
|
281
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
282
|
+
|
|
283
|
+
fs.writeFileSync(safePath, content);
|
|
284
|
+
return true;
|
|
285
|
+
} catch (err) {
|
|
286
|
+
console.error(`Error writing file: ${err.message}`);
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
module.exports = {
|
|
292
|
+
findProjectRoot,
|
|
293
|
+
safeJsonParse,
|
|
294
|
+
safeReadJson,
|
|
295
|
+
httpsGet,
|
|
296
|
+
copyDir,
|
|
297
|
+
parseArgs,
|
|
298
|
+
validatePath,
|
|
299
|
+
safeWriteFile,
|
|
300
|
+
|
|
301
|
+
// Constants
|
|
302
|
+
MAX_REDIRECTS,
|
|
303
|
+
REQUEST_TIMEOUT,
|
|
304
|
+
MAX_RESPONSE_SIZE
|
|
305
|
+
};
|