wogiflow 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (221) hide show
  1. package/.workflow/agents/reviewer.md +81 -0
  2. package/.workflow/agents/security.md +94 -0
  3. package/.workflow/agents/story-writer.md +58 -0
  4. package/.workflow/bridges/base-bridge.js +395 -0
  5. package/.workflow/bridges/claude-bridge.js +434 -0
  6. package/.workflow/bridges/index.js +130 -0
  7. package/.workflow/lib/assumption-detector.js +481 -0
  8. package/.workflow/lib/config-substitution.js +371 -0
  9. package/.workflow/lib/failure-categories.js +478 -0
  10. package/.workflow/state/app-map.md.template +15 -0
  11. package/.workflow/state/architecture.md.template +24 -0
  12. package/.workflow/state/component-index.json.template +5 -0
  13. package/.workflow/state/decisions.md.template +15 -0
  14. package/.workflow/state/feedback-patterns.md.template +9 -0
  15. package/.workflow/state/knowledge-sync.json.template +6 -0
  16. package/.workflow/state/progress.md.template +14 -0
  17. package/.workflow/state/ready.json.template +7 -0
  18. package/.workflow/state/request-log.md.template +14 -0
  19. package/.workflow/state/session-state.json.template +11 -0
  20. package/.workflow/state/stack.md.template +33 -0
  21. package/.workflow/state/testing.md.template +36 -0
  22. package/.workflow/templates/claude-md.hbs +257 -0
  23. package/.workflow/templates/correction-report.md +67 -0
  24. package/.workflow/templates/gemini-md.hbs +52 -0
  25. package/README.md +1802 -0
  26. package/bin/flow +205 -0
  27. package/lib/index.js +33 -0
  28. package/lib/installer.js +467 -0
  29. package/lib/release-channel.js +269 -0
  30. package/lib/skill-registry.js +526 -0
  31. package/lib/upgrader.js +401 -0
  32. package/lib/utils.js +305 -0
  33. package/package.json +64 -0
  34. package/scripts/flow +985 -0
  35. package/scripts/flow-adaptive-learning.js +1259 -0
  36. package/scripts/flow-aggregate.js +488 -0
  37. package/scripts/flow-archive +133 -0
  38. package/scripts/flow-auto-context.js +1015 -0
  39. package/scripts/flow-auto-learn.js +615 -0
  40. package/scripts/flow-bridge.js +223 -0
  41. package/scripts/flow-browser-suggest.js +316 -0
  42. package/scripts/flow-bug.js +247 -0
  43. package/scripts/flow-cascade.js +711 -0
  44. package/scripts/flow-changelog +85 -0
  45. package/scripts/flow-checkpoint.js +483 -0
  46. package/scripts/flow-cli.js +403 -0
  47. package/scripts/flow-code-intelligence.js +760 -0
  48. package/scripts/flow-complexity.js +502 -0
  49. package/scripts/flow-config-set.js +152 -0
  50. package/scripts/flow-constants.js +157 -0
  51. package/scripts/flow-context +152 -0
  52. package/scripts/flow-context-init.js +482 -0
  53. package/scripts/flow-context-monitor.js +384 -0
  54. package/scripts/flow-context-scoring.js +886 -0
  55. package/scripts/flow-correct.js +458 -0
  56. package/scripts/flow-damage-control.js +985 -0
  57. package/scripts/flow-deps +101 -0
  58. package/scripts/flow-diff.js +700 -0
  59. package/scripts/flow-done +151 -0
  60. package/scripts/flow-done.js +489 -0
  61. package/scripts/flow-durable-session.js +1541 -0
  62. package/scripts/flow-entropy-monitor.js +345 -0
  63. package/scripts/flow-export-profile +349 -0
  64. package/scripts/flow-export-scanner.js +1046 -0
  65. package/scripts/flow-figma-confirm.js +400 -0
  66. package/scripts/flow-figma-extract.js +496 -0
  67. package/scripts/flow-figma-generate.js +683 -0
  68. package/scripts/flow-figma-index.js +909 -0
  69. package/scripts/flow-figma-match.js +617 -0
  70. package/scripts/flow-figma-mcp-server.js +518 -0
  71. package/scripts/flow-figma-pipeline.js +414 -0
  72. package/scripts/flow-file-ops.js +301 -0
  73. package/scripts/flow-gate-confidence.js +825 -0
  74. package/scripts/flow-guided-edit.js +659 -0
  75. package/scripts/flow-health +185 -0
  76. package/scripts/flow-health.js +413 -0
  77. package/scripts/flow-hooks.js +556 -0
  78. package/scripts/flow-http-client.js +249 -0
  79. package/scripts/flow-hybrid-detect.js +167 -0
  80. package/scripts/flow-hybrid-interactive.js +591 -0
  81. package/scripts/flow-hybrid-test.js +152 -0
  82. package/scripts/flow-import-profile +439 -0
  83. package/scripts/flow-init +253 -0
  84. package/scripts/flow-instruction-richness.js +827 -0
  85. package/scripts/flow-jira-integration.js +579 -0
  86. package/scripts/flow-knowledge-router.js +522 -0
  87. package/scripts/flow-knowledge-sync.js +589 -0
  88. package/scripts/flow-linear-integration.js +631 -0
  89. package/scripts/flow-links.js +774 -0
  90. package/scripts/flow-log-manager.js +559 -0
  91. package/scripts/flow-loop-enforcer.js +1246 -0
  92. package/scripts/flow-loop-retry-learning.js +630 -0
  93. package/scripts/flow-lsp.js +923 -0
  94. package/scripts/flow-map-index +348 -0
  95. package/scripts/flow-map-sync +201 -0
  96. package/scripts/flow-memory-blocks.js +668 -0
  97. package/scripts/flow-memory-compactor.js +350 -0
  98. package/scripts/flow-memory-db.js +1110 -0
  99. package/scripts/flow-memory-sync.js +484 -0
  100. package/scripts/flow-metrics.js +353 -0
  101. package/scripts/flow-migrate-ids.js +370 -0
  102. package/scripts/flow-model-adapter.js +802 -0
  103. package/scripts/flow-model-router.js +884 -0
  104. package/scripts/flow-models.js +1231 -0
  105. package/scripts/flow-morning.js +517 -0
  106. package/scripts/flow-multi-approach.js +660 -0
  107. package/scripts/flow-new-feature +86 -0
  108. package/scripts/flow-onboard +1042 -0
  109. package/scripts/flow-orchestrate-llm.js +459 -0
  110. package/scripts/flow-orchestrate.js +3592 -0
  111. package/scripts/flow-output.js +123 -0
  112. package/scripts/flow-parallel-detector.js +399 -0
  113. package/scripts/flow-parallel-dispatch.js +987 -0
  114. package/scripts/flow-parallel.js +428 -0
  115. package/scripts/flow-pattern-enforcer.js +600 -0
  116. package/scripts/flow-prd-manager.js +282 -0
  117. package/scripts/flow-progress.js +323 -0
  118. package/scripts/flow-project-analyzer.js +975 -0
  119. package/scripts/flow-prompt-composer.js +487 -0
  120. package/scripts/flow-providers.js +1381 -0
  121. package/scripts/flow-queue.js +308 -0
  122. package/scripts/flow-ready +82 -0
  123. package/scripts/flow-ready.js +189 -0
  124. package/scripts/flow-regression.js +396 -0
  125. package/scripts/flow-response-parser.js +450 -0
  126. package/scripts/flow-resume.js +284 -0
  127. package/scripts/flow-rules-sync.js +439 -0
  128. package/scripts/flow-run-trace.js +718 -0
  129. package/scripts/flow-safety.js +587 -0
  130. package/scripts/flow-search +104 -0
  131. package/scripts/flow-security.js +481 -0
  132. package/scripts/flow-session-end +106 -0
  133. package/scripts/flow-session-end.js +437 -0
  134. package/scripts/flow-session-state.js +671 -0
  135. package/scripts/flow-setup-hooks +216 -0
  136. package/scripts/flow-setup-hooks.js +377 -0
  137. package/scripts/flow-skill-create.js +329 -0
  138. package/scripts/flow-skill-creator.js +572 -0
  139. package/scripts/flow-skill-generator.js +1046 -0
  140. package/scripts/flow-skill-learn.js +880 -0
  141. package/scripts/flow-skill-matcher.js +578 -0
  142. package/scripts/flow-spec-generator.js +820 -0
  143. package/scripts/flow-stack-wizard.js +895 -0
  144. package/scripts/flow-standup +162 -0
  145. package/scripts/flow-start +74 -0
  146. package/scripts/flow-start.js +235 -0
  147. package/scripts/flow-status +110 -0
  148. package/scripts/flow-status.js +301 -0
  149. package/scripts/flow-step-browser.js +83 -0
  150. package/scripts/flow-step-changelog.js +217 -0
  151. package/scripts/flow-step-comments.js +306 -0
  152. package/scripts/flow-step-complexity.js +234 -0
  153. package/scripts/flow-step-coverage.js +218 -0
  154. package/scripts/flow-step-knowledge.js +193 -0
  155. package/scripts/flow-step-pr-tests.js +364 -0
  156. package/scripts/flow-step-regression.js +89 -0
  157. package/scripts/flow-step-review.js +516 -0
  158. package/scripts/flow-step-security.js +162 -0
  159. package/scripts/flow-step-silent-failures.js +290 -0
  160. package/scripts/flow-step-simplifier.js +346 -0
  161. package/scripts/flow-story +105 -0
  162. package/scripts/flow-story.js +500 -0
  163. package/scripts/flow-suspend.js +252 -0
  164. package/scripts/flow-sync-daemon.js +654 -0
  165. package/scripts/flow-task-analyzer.js +606 -0
  166. package/scripts/flow-team-dashboard.js +748 -0
  167. package/scripts/flow-team-sync.js +752 -0
  168. package/scripts/flow-team.js +977 -0
  169. package/scripts/flow-tech-options.js +528 -0
  170. package/scripts/flow-templates.js +812 -0
  171. package/scripts/flow-tiered-learning.js +728 -0
  172. package/scripts/flow-trace +204 -0
  173. package/scripts/flow-transcript-chunking.js +1106 -0
  174. package/scripts/flow-transcript-digest.js +7918 -0
  175. package/scripts/flow-transcript-language.js +465 -0
  176. package/scripts/flow-transcript-parsing.js +1085 -0
  177. package/scripts/flow-transcript-stories.js +2194 -0
  178. package/scripts/flow-update-map +224 -0
  179. package/scripts/flow-utils.js +2242 -0
  180. package/scripts/flow-verification.js +644 -0
  181. package/scripts/flow-verify.js +1177 -0
  182. package/scripts/flow-voice-input.js +638 -0
  183. package/scripts/flow-watch +168 -0
  184. package/scripts/flow-workflow-steps.js +521 -0
  185. package/scripts/flow-workflow.js +1029 -0
  186. package/scripts/flow-worktree.js +489 -0
  187. package/scripts/hooks/adapters/base-adapter.js +102 -0
  188. package/scripts/hooks/adapters/claude-code.js +359 -0
  189. package/scripts/hooks/adapters/index.js +79 -0
  190. package/scripts/hooks/core/component-check.js +341 -0
  191. package/scripts/hooks/core/index.js +35 -0
  192. package/scripts/hooks/core/loop-check.js +241 -0
  193. package/scripts/hooks/core/session-context.js +294 -0
  194. package/scripts/hooks/core/task-gate.js +177 -0
  195. package/scripts/hooks/core/validation.js +230 -0
  196. package/scripts/hooks/entry/claude-code/post-tool-use.js +65 -0
  197. package/scripts/hooks/entry/claude-code/pre-tool-use.js +89 -0
  198. package/scripts/hooks/entry/claude-code/session-end.js +87 -0
  199. package/scripts/hooks/entry/claude-code/session-start.js +46 -0
  200. package/scripts/hooks/entry/claude-code/stop.js +43 -0
  201. package/scripts/postinstall.js +139 -0
  202. package/templates/browser-test-flow.json +56 -0
  203. package/templates/bug-report.md +43 -0
  204. package/templates/component-detail.md +42 -0
  205. package/templates/component.stories.tsx +49 -0
  206. package/templates/context/constraints.md +83 -0
  207. package/templates/context/conventions.md +177 -0
  208. package/templates/context/stack.md +60 -0
  209. package/templates/correction-report.md +90 -0
  210. package/templates/feature-proposal.md +35 -0
  211. package/templates/hybrid/_base.md +254 -0
  212. package/templates/hybrid/_patterns.md +45 -0
  213. package/templates/hybrid/create-component.md +127 -0
  214. package/templates/hybrid/create-file.md +56 -0
  215. package/templates/hybrid/create-hook.md +145 -0
  216. package/templates/hybrid/create-service.md +70 -0
  217. package/templates/hybrid/fix-bug.md +33 -0
  218. package/templates/hybrid/modify-file.md +55 -0
  219. package/templates/story.md +68 -0
  220. package/templates/task.json +56 -0
  221. package/templates/trace.md +69 -0
@@ -0,0 +1,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
+ };