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,820 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Wogi Flow - Specification Generator (Priority 2: Mandatory Spec Mode)
5
+ *
6
+ * Generates comprehensive specifications BEFORE implementation starts.
7
+ * Follows "spec-first" approach - planning before coding.
8
+ *
9
+ * Key principle: "Quality code starts with quality planning"
10
+ *
11
+ * Usage:
12
+ * const { generateSpec, loadSpec, validateSpec } = require('./flow-spec-generator');
13
+ * const spec = await generateSpec(taskId, taskContext);
14
+ */
15
+
16
+ const fs = require('fs');
17
+ const path = require('path');
18
+ const { getProjectRoot, getConfig, PATHS, colors } = require('./flow-utils');
19
+ const { matchSkills, loadSkillContext } = require('./flow-skill-matcher');
20
+
21
+ const PROJECT_ROOT = getProjectRoot();
22
+
23
+ // ============================================================
24
+ // Spec Generation
25
+ // ============================================================
26
+
27
+ /**
28
+ * Generate a specification for a task
29
+ *
30
+ * @param {string} taskId - Task ID (e.g., wf-abc123)
31
+ * @param {object} taskContext - Task context
32
+ * @param {string} taskContext.title - Task title
33
+ * @param {string} taskContext.description - Task description
34
+ * @param {string} taskContext.userStory - User story (As a... I want... So that...)
35
+ * @param {Array} taskContext.acceptanceCriteria - Acceptance criteria scenarios
36
+ * @param {string} taskContext.type - Task type (feature, bugfix, refactor)
37
+ * @param {string} taskContext.size - Task size (small, medium, large)
38
+ */
39
+ async function generateSpec(taskId, taskContext) {
40
+ const config = getConfig();
41
+ const specConfig = config.specificationMode || {};
42
+
43
+ // Check if spec mode is enabled
44
+ if (!specConfig.enabled) {
45
+ return { skipped: true, reason: 'Specification mode disabled' };
46
+ }
47
+
48
+ // Check if mandatory for this task size
49
+ const taskSize = taskContext.size || 'medium';
50
+ const taskType = taskContext.type || 'feature';
51
+
52
+ if (specConfig.skipFor?.includes(taskSize) || specConfig.skipFor?.includes(taskType)) {
53
+ return { skipped: true, reason: `Skipped for ${taskSize}/${taskType} tasks` };
54
+ }
55
+
56
+ // Generate spec content
57
+ const spec = {
58
+ taskId,
59
+ title: taskContext.title,
60
+ generatedAt: new Date().toISOString(),
61
+ status: 'pending_approval',
62
+ sections: {}
63
+ };
64
+
65
+ // 1. Acceptance Criteria
66
+ if (specConfig.sections?.acceptanceCriteria !== false) {
67
+ spec.sections.acceptanceCriteria = formatAcceptanceCriteria(taskContext.acceptanceCriteria);
68
+ }
69
+
70
+ // 2. Implementation Steps
71
+ if (specConfig.sections?.implementationSteps !== false) {
72
+ spec.sections.implementationSteps = await generateImplementationSteps(taskContext);
73
+ }
74
+
75
+ // 3. Files to Change
76
+ if (specConfig.sections?.filesToChange !== false && specConfig.autoDetectFiles) {
77
+ spec.sections.filesToChange = await detectFilesToChange(taskContext);
78
+ }
79
+
80
+ // 4. Test Strategy
81
+ if (specConfig.sections?.testStrategy !== false) {
82
+ spec.sections.testStrategy = generateTestStrategy(taskContext);
83
+ }
84
+
85
+ // 5. Verification Commands
86
+ if (specConfig.sections?.verificationCommands !== false) {
87
+ spec.sections.verificationCommands = generateVerificationCommands(taskContext);
88
+ }
89
+
90
+ // 6. Matched Skills
91
+ const matchedSkills = matchSkills(taskContext.description || taskContext.title, {
92
+ taskType: taskContext.type
93
+ });
94
+ spec.sections.matchedSkills = matchedSkills.map(s => ({
95
+ name: s.name,
96
+ score: s.score,
97
+ reasons: s.reasons
98
+ }));
99
+
100
+ // 7. Assumptions (Phase 0.5 - Hybrid Assumption Surfacing)
101
+ if (specConfig.sections?.assumptions !== false) {
102
+ try {
103
+ const {
104
+ detectAssumptions,
105
+ getAssumptionsNeedingClarification,
106
+ generateClarificationQuestions
107
+ } = require('../.workflow/lib/assumption-detector');
108
+ const assumptions = detectAssumptions({
109
+ title: taskContext.title,
110
+ description: taskContext.description || taskContext.userStory || '',
111
+ acceptanceCriteria: taskContext.acceptanceCriteria || [],
112
+ context: taskContext
113
+ });
114
+ const needingClarification = getAssumptionsNeedingClarification(assumptions);
115
+ spec.sections.assumptions = {
116
+ detected: assumptions,
117
+ needingClarification,
118
+ clarificationRequired: needingClarification.length > 0,
119
+ // AskUserQuestion-compatible format for Claude to use
120
+ askUserQuestions: needingClarification.length > 0
121
+ ? generateClarificationQuestions(assumptions)
122
+ : []
123
+ };
124
+ } catch (err) {
125
+ // Assumption detector not available
126
+ spec.sections.assumptions = { detected: [], needingClarification: [], error: err.message };
127
+ }
128
+ }
129
+
130
+ // 8. Rollback Plan (optional)
131
+ if (specConfig.sections?.rollbackPlan) {
132
+ spec.sections.rollbackPlan = generateRollbackPlan(taskContext);
133
+ }
134
+
135
+ // Save spec to file
136
+ const specPath = saveSpec(taskId, spec);
137
+ spec.filePath = specPath;
138
+
139
+ return spec;
140
+ }
141
+
142
+ /**
143
+ * Format acceptance criteria into structured scenarios
144
+ */
145
+ function formatAcceptanceCriteria(criteria) {
146
+ if (!criteria || criteria.length === 0) {
147
+ return [];
148
+ }
149
+
150
+ return criteria.map((criterion, index) => {
151
+ // Parse Given/When/Then if it's a string
152
+ if (typeof criterion === 'string') {
153
+ const givenMatch = criterion.match(/Given\s+(.+?)(?=\s+When|$)/i);
154
+ const whenMatch = criterion.match(/When\s+(.+?)(?=\s+Then|$)/i);
155
+ const thenMatch = criterion.match(/Then\s+(.+?)$/i);
156
+
157
+ return {
158
+ id: index + 1,
159
+ scenario: criterion,
160
+ given: givenMatch ? givenMatch[1].trim() : null,
161
+ when: whenMatch ? whenMatch[1].trim() : null,
162
+ then: thenMatch ? thenMatch[1].trim() : null,
163
+ status: 'pending'
164
+ };
165
+ }
166
+
167
+ return {
168
+ id: index + 1,
169
+ ...criterion,
170
+ status: 'pending'
171
+ };
172
+ });
173
+ }
174
+
175
+ /**
176
+ * Generate implementation steps based on task context
177
+ */
178
+ async function generateImplementationSteps(taskContext) {
179
+ const steps = [];
180
+
181
+ // Base steps for all tasks
182
+ steps.push({
183
+ order: 1,
184
+ description: 'Load and review relevant context',
185
+ type: 'preparation',
186
+ status: 'pending'
187
+ });
188
+
189
+ // Add steps based on task type
190
+ const type = taskContext.type || 'feature';
191
+
192
+ if (type === 'feature') {
193
+ steps.push(
194
+ { order: 2, description: 'Create/update necessary data models', type: 'implementation', status: 'pending' },
195
+ { order: 3, description: 'Implement core business logic', type: 'implementation', status: 'pending' },
196
+ { order: 4, description: 'Add API endpoints or UI components', type: 'implementation', status: 'pending' },
197
+ { order: 5, description: 'Write unit tests', type: 'testing', status: 'pending' },
198
+ { order: 6, description: 'Write integration tests', type: 'testing', status: 'pending' }
199
+ );
200
+ } else if (type === 'bugfix') {
201
+ steps.push(
202
+ { order: 2, description: 'Reproduce the bug', type: 'investigation', status: 'pending' },
203
+ { order: 3, description: 'Identify root cause', type: 'investigation', status: 'pending' },
204
+ { order: 4, description: 'Write failing test that captures the bug', type: 'testing', status: 'pending' },
205
+ { order: 5, description: 'Implement fix', type: 'implementation', status: 'pending' },
206
+ { order: 6, description: 'Verify test passes', type: 'verification', status: 'pending' }
207
+ );
208
+ } else if (type === 'refactor') {
209
+ steps.push(
210
+ { order: 2, description: 'Ensure existing tests pass', type: 'verification', status: 'pending' },
211
+ { order: 3, description: 'Refactor code incrementally', type: 'implementation', status: 'pending' },
212
+ { order: 4, description: 'Verify tests still pass after each change', type: 'verification', status: 'pending' },
213
+ { order: 5, description: 'Update documentation if needed', type: 'documentation', status: 'pending' }
214
+ );
215
+ }
216
+
217
+ // Add final verification step
218
+ steps.push({
219
+ order: steps.length + 1,
220
+ description: 'Run all verification commands',
221
+ type: 'verification',
222
+ status: 'pending'
223
+ });
224
+
225
+ return steps;
226
+ }
227
+
228
+ /**
229
+ * Detect files that will likely be changed
230
+ * Uses task description keywords and existing file patterns
231
+ */
232
+ async function detectFilesToChange(taskContext) {
233
+ const files = {
234
+ create: [],
235
+ modify: [],
236
+ delete: []
237
+ };
238
+
239
+ // Extract keywords from description
240
+ const desc = (taskContext.description || taskContext.title || '').toLowerCase();
241
+
242
+ // Check component-index for matching files
243
+ const indexPath = path.join(PATHS.state, 'component-index.json');
244
+ if (fs.existsSync(indexPath)) {
245
+ try {
246
+ const index = JSON.parse(fs.readFileSync(indexPath, 'utf-8'));
247
+ const components = index.components || [];
248
+
249
+ // Find components that match keywords
250
+ for (const comp of components) {
251
+ const name = (comp.name || '').toLowerCase();
252
+ const filePath = comp.path || '';
253
+
254
+ // Simple keyword matching
255
+ const keywords = desc.split(/\s+/).filter(w => w.length > 3);
256
+ for (const keyword of keywords) {
257
+ if (name.includes(keyword) || filePath.toLowerCase().includes(keyword)) {
258
+ files.modify.push({
259
+ path: filePath,
260
+ reason: `matches keyword "${keyword}"`,
261
+ confidence: 'medium'
262
+ });
263
+ break;
264
+ }
265
+ }
266
+ }
267
+ } catch {
268
+ // Ignore errors
269
+ }
270
+ }
271
+
272
+ // Deduplicate
273
+ files.modify = [...new Map(files.modify.map(f => [f.path, f])).values()];
274
+
275
+ return files;
276
+ }
277
+
278
+ /**
279
+ * Generate test strategy based on task context
280
+ */
281
+ function generateTestStrategy(taskContext) {
282
+ const strategy = {
283
+ unitTests: [],
284
+ integrationTests: [],
285
+ e2eTests: []
286
+ };
287
+
288
+ const type = taskContext.type || 'feature';
289
+
290
+ if (type === 'feature') {
291
+ strategy.unitTests.push(
292
+ 'Test core business logic functions',
293
+ 'Test edge cases and error handling',
294
+ 'Test data transformations'
295
+ );
296
+ strategy.integrationTests.push(
297
+ 'Test API endpoints with mock data',
298
+ 'Test database operations',
299
+ 'Test service integrations'
300
+ );
301
+ strategy.e2eTests.push(
302
+ 'Test happy path user flow',
303
+ 'Test error scenarios'
304
+ );
305
+ } else if (type === 'bugfix') {
306
+ strategy.unitTests.push(
307
+ 'Add test case that reproduces the bug',
308
+ 'Add tests for related edge cases'
309
+ );
310
+ strategy.integrationTests.push(
311
+ 'Verify fix doesn\'t break existing functionality'
312
+ );
313
+ } else if (type === 'refactor') {
314
+ strategy.unitTests.push(
315
+ 'Ensure all existing tests still pass',
316
+ 'Update tests if API changes'
317
+ );
318
+ }
319
+
320
+ return strategy;
321
+ }
322
+
323
+ /**
324
+ * Generate verification commands
325
+ */
326
+ function generateVerificationCommands(taskContext) {
327
+ const config = getConfig();
328
+ const commands = [];
329
+
330
+ // Add lint command
331
+ commands.push({
332
+ command: 'npm run lint',
333
+ description: 'Run linter',
334
+ required: true,
335
+ expectedExitCode: 0
336
+ });
337
+
338
+ // Add typecheck command
339
+ commands.push({
340
+ command: 'npm run typecheck',
341
+ description: 'Run type checker',
342
+ required: true,
343
+ expectedExitCode: 0
344
+ });
345
+
346
+ // Add test command
347
+ commands.push({
348
+ command: 'npm test',
349
+ description: 'Run tests',
350
+ required: true,
351
+ expectedExitCode: 0
352
+ });
353
+
354
+ // Add build command for features
355
+ if (taskContext.type === 'feature') {
356
+ commands.push({
357
+ command: 'npm run build',
358
+ description: 'Build project',
359
+ required: false,
360
+ expectedExitCode: 0
361
+ });
362
+ }
363
+
364
+ return commands;
365
+ }
366
+
367
+ /**
368
+ * Generate rollback plan
369
+ */
370
+ function generateRollbackPlan(taskContext) {
371
+ return {
372
+ strategy: 'git-revert',
373
+ steps: [
374
+ 'Identify the commit(s) to revert',
375
+ 'Run git revert <commit-hash>',
376
+ 'Verify tests pass after revert',
377
+ 'Push revert commit'
378
+ ],
379
+ automatedRollback: false
380
+ };
381
+ }
382
+
383
+ // ============================================================
384
+ // Spec File Management
385
+ // ============================================================
386
+
387
+ /**
388
+ * Save spec to file
389
+ */
390
+ function saveSpec(taskId, spec) {
391
+ const config = getConfig();
392
+ const specDir = config.specificationMode?.specDirectory || '.workflow/specs';
393
+ const fullDir = path.join(PROJECT_ROOT, specDir);
394
+
395
+ // Ensure directory exists
396
+ if (!fs.existsSync(fullDir)) {
397
+ fs.mkdirSync(fullDir, { recursive: true });
398
+ }
399
+
400
+ // Generate markdown content
401
+ const content = formatSpecAsMarkdown(spec);
402
+
403
+ // Save file
404
+ const filePath = path.join(fullDir, `${taskId}.md`);
405
+ fs.writeFileSync(filePath, content, 'utf-8');
406
+
407
+ // Also save JSON version for programmatic access
408
+ const jsonPath = path.join(fullDir, `${taskId}.json`);
409
+ fs.writeFileSync(jsonPath, JSON.stringify(spec, null, 2), 'utf-8');
410
+
411
+ return filePath;
412
+ }
413
+
414
+ /**
415
+ * Format spec as markdown
416
+ */
417
+ function formatSpecAsMarkdown(spec) {
418
+ let md = `# Specification: ${spec.title}\n\n`;
419
+ md += `**Task ID:** ${spec.taskId}\n`;
420
+ md += `**Generated:** ${spec.generatedAt}\n`;
421
+ md += `**Status:** ${spec.status}\n\n`;
422
+ md += `---\n\n`;
423
+
424
+ // Acceptance Criteria
425
+ if (spec.sections.acceptanceCriteria?.length > 0) {
426
+ md += `## Acceptance Criteria\n\n`;
427
+ for (const criterion of spec.sections.acceptanceCriteria) {
428
+ md += `### Scenario ${criterion.id}\n`;
429
+ if (criterion.given) md += `**Given** ${criterion.given}\n`;
430
+ if (criterion.when) md += `**When** ${criterion.when}\n`;
431
+ if (criterion.then) md += `**Then** ${criterion.then}\n`;
432
+ md += `**Status:** ${criterion.status}\n\n`;
433
+ }
434
+ }
435
+
436
+ // Implementation Steps
437
+ if (spec.sections.implementationSteps?.length > 0) {
438
+ md += `## Implementation Steps\n\n`;
439
+ for (const step of spec.sections.implementationSteps) {
440
+ const checkbox = step.status === 'completed' ? '[x]' : '[ ]';
441
+ md += `${checkbox} **Step ${step.order}:** ${step.description} _(${step.type})_\n`;
442
+ }
443
+ md += '\n';
444
+ }
445
+
446
+ // Files to Change
447
+ if (spec.sections.filesToChange) {
448
+ md += `## Files to Change\n\n`;
449
+ const files = spec.sections.filesToChange;
450
+
451
+ if (files.create?.length > 0) {
452
+ md += `### Create\n`;
453
+ for (const f of files.create) {
454
+ md += `- \`${f.path}\` - ${f.reason}\n`;
455
+ }
456
+ md += '\n';
457
+ }
458
+
459
+ if (files.modify?.length > 0) {
460
+ md += `### Modify\n`;
461
+ for (const f of files.modify) {
462
+ md += `- \`${f.path}\` - ${f.reason} (${f.confidence})\n`;
463
+ }
464
+ md += '\n';
465
+ }
466
+ }
467
+
468
+ // Test Strategy
469
+ if (spec.sections.testStrategy) {
470
+ md += `## Test Strategy\n\n`;
471
+ const ts = spec.sections.testStrategy;
472
+
473
+ if (ts.unitTests?.length > 0) {
474
+ md += `### Unit Tests\n`;
475
+ for (const t of ts.unitTests) {
476
+ md += `- ${t}\n`;
477
+ }
478
+ md += '\n';
479
+ }
480
+
481
+ if (ts.integrationTests?.length > 0) {
482
+ md += `### Integration Tests\n`;
483
+ for (const t of ts.integrationTests) {
484
+ md += `- ${t}\n`;
485
+ }
486
+ md += '\n';
487
+ }
488
+
489
+ if (ts.e2eTests?.length > 0) {
490
+ md += `### E2E Tests\n`;
491
+ for (const t of ts.e2eTests) {
492
+ md += `- ${t}\n`;
493
+ }
494
+ md += '\n';
495
+ }
496
+ }
497
+
498
+ // Verification Commands
499
+ if (spec.sections.verificationCommands?.length > 0) {
500
+ md += `## Verification Commands\n\n`;
501
+ md += `| Command | Description | Required | Expected Exit |\n`;
502
+ md += `|---------|-------------|----------|---------------|\n`;
503
+ for (const cmd of spec.sections.verificationCommands) {
504
+ md += `| \`${cmd.command}\` | ${cmd.description} | ${cmd.required ? 'Yes' : 'No'} | ${cmd.expectedExitCode} |\n`;
505
+ }
506
+ md += '\n';
507
+ }
508
+
509
+ // Matched Skills
510
+ if (spec.sections.matchedSkills?.length > 0) {
511
+ md += `## Matched Skills\n\n`;
512
+ for (const skill of spec.sections.matchedSkills) {
513
+ md += `- **${skill.name}** (score: ${skill.score})\n`;
514
+ md += ` - ${skill.reasons.slice(0, 3).join(', ')}\n`;
515
+ }
516
+ md += '\n';
517
+ }
518
+
519
+ // Assumptions
520
+ if (spec.sections.assumptions?.detected?.length > 0) {
521
+ md += `## Assumptions\n\n`;
522
+
523
+ // Show clarification warning if needed
524
+ const needClarification = spec.sections.assumptions.needingClarification || [];
525
+ if (needClarification.length > 0) {
526
+ md += `> ⚠️ **${needClarification.length} assumption(s) need clarification** before proceeding.\n\n`;
527
+ md += `> Use \`AskUserQuestion\` tool to clarify these assumptions before implementation.\n\n`;
528
+ }
529
+
530
+ // Format assumptions using assumption-detector if available
531
+ try {
532
+ const { formatAssumptionsForSpec } = require('../.workflow/lib/assumption-detector');
533
+ md += formatAssumptionsForSpec(spec.sections.assumptions.detected);
534
+ } catch {
535
+ // Fallback formatting
536
+ for (const a of spec.sections.assumptions.detected) {
537
+ const confidenceDisplay = Math.round(a.confidence * 100);
538
+ const needsFlag = a.needsClarification ? ' ⚠️' : '';
539
+ md += `- **[${a.id}]** ${a.text} (${confidenceDisplay}% confidence)${needsFlag}\n`;
540
+ if (a.needsClarification && a.clarificationQuestion) {
541
+ md += ` - *Clarify:* ${a.clarificationQuestion}\n`;
542
+ }
543
+ }
544
+ }
545
+
546
+ // Add clarification questions JSON for Claude to use with AskUserQuestion
547
+ if (spec.sections.assumptions.askUserQuestions?.length > 0) {
548
+ md += `\n### Clarification Questions\n\n`;
549
+ md += `The following questions are formatted for \`AskUserQuestion\`:\n\n`;
550
+ md += `\`\`\`json\n`;
551
+ // Format without assumption metadata (just the tool-compatible format)
552
+ const toolQuestions = spec.sections.assumptions.askUserQuestions.map(q => ({
553
+ question: q.question,
554
+ header: q.header,
555
+ options: q.options,
556
+ multiSelect: q.multiSelect
557
+ }));
558
+ md += JSON.stringify(toolQuestions, null, 2);
559
+ md += `\n\`\`\`\n`;
560
+ }
561
+ md += '\n';
562
+ }
563
+
564
+ return md;
565
+ }
566
+
567
+ /**
568
+ * Load existing spec for a task
569
+ */
570
+ function loadSpec(taskId) {
571
+ const config = getConfig();
572
+ const specDir = config.specificationMode?.specDirectory || '.workflow/specs';
573
+ const jsonPath = path.join(PROJECT_ROOT, specDir, `${taskId}.json`);
574
+
575
+ if (!fs.existsSync(jsonPath)) {
576
+ return null;
577
+ }
578
+
579
+ try {
580
+ return JSON.parse(fs.readFileSync(jsonPath, 'utf-8'));
581
+ } catch {
582
+ return null;
583
+ }
584
+ }
585
+
586
+ /**
587
+ * Update spec status
588
+ */
589
+ function updateSpecStatus(taskId, status) {
590
+ const spec = loadSpec(taskId);
591
+ if (!spec) return null;
592
+
593
+ spec.status = status;
594
+ spec.updatedAt = new Date().toISOString();
595
+
596
+ saveSpec(taskId, spec);
597
+ return spec;
598
+ }
599
+
600
+ /**
601
+ * Mark spec step as completed
602
+ */
603
+ function markStepCompleted(taskId, stepOrder) {
604
+ const spec = loadSpec(taskId);
605
+ if (!spec) return null;
606
+
607
+ const step = spec.sections.implementationSteps?.find(s => s.order === stepOrder);
608
+ if (step) {
609
+ step.status = 'completed';
610
+ step.completedAt = new Date().toISOString();
611
+ }
612
+
613
+ spec.updatedAt = new Date().toISOString();
614
+ saveSpec(taskId, spec);
615
+ return spec;
616
+ }
617
+
618
+ /**
619
+ * Validate that spec requirements are met
620
+ */
621
+ function validateSpec(taskId) {
622
+ const spec = loadSpec(taskId);
623
+ if (!spec) {
624
+ return { valid: false, errors: ['Spec not found'] };
625
+ }
626
+
627
+ const errors = [];
628
+ const warnings = [];
629
+
630
+ // Check all acceptance criteria are addressed
631
+ for (const criterion of spec.sections.acceptanceCriteria || []) {
632
+ if (criterion.status !== 'completed') {
633
+ errors.push(`Acceptance criterion ${criterion.id} not completed`);
634
+ }
635
+ }
636
+
637
+ // Check all implementation steps are completed
638
+ for (const step of spec.sections.implementationSteps || []) {
639
+ if (step.status !== 'completed') {
640
+ warnings.push(`Implementation step ${step.order} not completed: ${step.description}`);
641
+ }
642
+ }
643
+
644
+ return {
645
+ valid: errors.length === 0,
646
+ errors,
647
+ warnings
648
+ };
649
+ }
650
+
651
+ // ============================================================
652
+ // CLI
653
+ // ============================================================
654
+
655
+ function showHelp() {
656
+ console.log(`
657
+ Wogi Flow - Specification Generator
658
+
659
+ Generates comprehensive specifications before implementation.
660
+
661
+ Usage:
662
+ flow spec generate <task-id> [options]
663
+ flow spec view <task-id>
664
+ flow spec validate <task-id>
665
+ flow spec approve <task-id>
666
+
667
+ Commands:
668
+ generate Generate a new spec for a task
669
+ view View existing spec
670
+ validate Validate spec completion
671
+ approve Mark spec as approved
672
+
673
+ Options:
674
+ --title <title> Task title
675
+ --description <desc> Task description
676
+ --type <type> Task type (feature, bugfix, refactor)
677
+ --size <size> Task size (small, medium, large)
678
+ --json Output as JSON
679
+ --help, -h Show this help
680
+
681
+ Examples:
682
+ flow spec generate wf-abc123 --title "Add user login" --type feature
683
+ flow spec view wf-abc123
684
+ flow spec validate wf-abc123
685
+ `);
686
+ }
687
+
688
+ async function main() {
689
+ const args = process.argv.slice(2);
690
+
691
+ if (args.includes('--help') || args.includes('-h') || args.length === 0) {
692
+ showHelp();
693
+ process.exit(0);
694
+ }
695
+
696
+ const command = args[0];
697
+ const taskId = args[1];
698
+ const jsonOutput = args.includes('--json');
699
+
700
+ if (!taskId && command !== '--help') {
701
+ console.log(`${colors.red}Error: Task ID required${colors.reset}`);
702
+ process.exit(1);
703
+ }
704
+
705
+ switch (command) {
706
+ case 'generate': {
707
+ // Extract options
708
+ const titleIdx = args.indexOf('--title');
709
+ const descIdx = args.indexOf('--description');
710
+ const typeIdx = args.indexOf('--type');
711
+ const sizeIdx = args.indexOf('--size');
712
+
713
+ const context = {
714
+ title: titleIdx >= 0 ? args[titleIdx + 1] : taskId,
715
+ description: descIdx >= 0 ? args[descIdx + 1] : '',
716
+ type: typeIdx >= 0 ? args[typeIdx + 1] : 'feature',
717
+ size: sizeIdx >= 0 ? args[sizeIdx + 1] : 'medium',
718
+ acceptanceCriteria: []
719
+ };
720
+
721
+ const spec = await generateSpec(taskId, context);
722
+
723
+ if (jsonOutput) {
724
+ console.log(JSON.stringify(spec, null, 2));
725
+ } else {
726
+ if (spec.skipped) {
727
+ console.log(`${colors.yellow}Spec generation skipped: ${spec.reason}${colors.reset}`);
728
+ } else {
729
+ console.log(`${colors.green}✓ Spec generated: ${spec.filePath}${colors.reset}`);
730
+ console.log(`\n${colors.cyan}Sections:${colors.reset}`);
731
+ for (const [name, content] of Object.entries(spec.sections)) {
732
+ const count = Array.isArray(content) ? content.length : Object.keys(content).length;
733
+ console.log(` - ${name}: ${count} items`);
734
+ }
735
+ }
736
+ }
737
+ break;
738
+ }
739
+
740
+ case 'view': {
741
+ const spec = loadSpec(taskId);
742
+ if (!spec) {
743
+ console.log(`${colors.red}Spec not found for ${taskId}${colors.reset}`);
744
+ process.exit(1);
745
+ }
746
+
747
+ if (jsonOutput) {
748
+ console.log(JSON.stringify(spec, null, 2));
749
+ } else {
750
+ console.log(formatSpecAsMarkdown(spec));
751
+ }
752
+ break;
753
+ }
754
+
755
+ case 'validate': {
756
+ const result = validateSpec(taskId);
757
+
758
+ if (jsonOutput) {
759
+ console.log(JSON.stringify(result, null, 2));
760
+ } else {
761
+ if (result.valid) {
762
+ console.log(`${colors.green}✓ Spec validation passed${colors.reset}`);
763
+ } else {
764
+ console.log(`${colors.red}✗ Spec validation failed${colors.reset}`);
765
+ for (const error of result.errors) {
766
+ console.log(` ${colors.red}• ${error}${colors.reset}`);
767
+ }
768
+ }
769
+ if (result.warnings.length > 0) {
770
+ console.log(`\n${colors.yellow}Warnings:${colors.reset}`);
771
+ for (const warning of result.warnings) {
772
+ console.log(` ${colors.yellow}• ${warning}${colors.reset}`);
773
+ }
774
+ }
775
+ }
776
+ break;
777
+ }
778
+
779
+ case 'approve': {
780
+ const spec = updateSpecStatus(taskId, 'approved');
781
+ if (!spec) {
782
+ console.log(`${colors.red}Spec not found for ${taskId}${colors.reset}`);
783
+ process.exit(1);
784
+ }
785
+ console.log(`${colors.green}✓ Spec approved for ${taskId}${colors.reset}`);
786
+ break;
787
+ }
788
+
789
+ default:
790
+ console.log(`${colors.red}Unknown command: ${command}${colors.reset}`);
791
+ showHelp();
792
+ process.exit(1);
793
+ }
794
+ }
795
+
796
+ // ============================================================
797
+ // Exports
798
+ // ============================================================
799
+
800
+ module.exports = {
801
+ generateSpec,
802
+ loadSpec,
803
+ saveSpec,
804
+ updateSpecStatus,
805
+ markStepCompleted,
806
+ validateSpec,
807
+ formatSpecAsMarkdown,
808
+ formatAcceptanceCriteria,
809
+ generateImplementationSteps,
810
+ detectFilesToChange,
811
+ generateTestStrategy,
812
+ generateVerificationCommands
813
+ };
814
+
815
+ if (require.main === module) {
816
+ main().catch(err => {
817
+ console.error(`Error: ${err.message}`);
818
+ process.exit(1);
819
+ });
820
+ }