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,105 @@
1
+ #!/bin/bash
2
+
3
+ # Wogi Flow - Create Story
4
+ # Generate a detailed story template
5
+
6
+ set -e
7
+
8
+ WORKFLOW_DIR=".workflow"
9
+
10
+ # Colors
11
+ GREEN='\033[0;32m'
12
+ CYAN='\033[0;36m'
13
+ NC='\033[0m'
14
+
15
+ if [ -z "$1" ]; then
16
+ echo "Usage: flow story <title> [feature-name]"
17
+ echo ""
18
+ echo "Creates a detailed story with acceptance criteria template."
19
+ echo ""
20
+ echo "Examples:"
21
+ echo " flow story \"Add forgot password link\""
22
+ echo " flow story \"User profile page\" user-management"
23
+ exit 1
24
+ fi
25
+
26
+ TITLE="$1"
27
+ FEATURE="${2:-general}"
28
+ FEATURE_DIR="$WORKFLOW_DIR/changes/$FEATURE"
29
+
30
+ # Create feature dir if needed
31
+ mkdir -p "$FEATURE_DIR"
32
+
33
+ # Find next task number
34
+ if [ -f "$FEATURE_DIR/tasks.json" ]; then
35
+ LAST_NUM=$(grep -oE '"id": "TASK-[0-9]+"' "$FEATURE_DIR/tasks.json" | grep -oE '[0-9]+' | sort -n | tail -1 || echo "0")
36
+ else
37
+ LAST_NUM=0
38
+ fi
39
+ NEXT_NUM=$((LAST_NUM + 1))
40
+ TASK_ID=$(printf "TASK-%03d" $NEXT_NUM)
41
+
42
+ STORY_FILE="$FEATURE_DIR/$TASK_ID.md"
43
+
44
+ cat > "$STORY_FILE" << EOF
45
+ # [$TASK_ID] $TITLE
46
+
47
+ ## User Story
48
+ **As a** [user type]
49
+ **I want** [action/capability]
50
+ **So that** [benefit/value]
51
+
52
+ ## Description
53
+ [2-4 sentences explaining the context, what needs to be built, and why it matters.]
54
+
55
+ ## Acceptance Criteria
56
+
57
+ ### Scenario 1: Happy path
58
+ **Given** [initial context/state]
59
+ **When** [action taken]
60
+ **Then** [expected outcome]
61
+ **And** [additional outcome if needed]
62
+
63
+ ### Scenario 2: Alternative path
64
+ **Given** [context]
65
+ **When** [action]
66
+ **Then** [outcome]
67
+
68
+ ### Scenario 3: Error handling
69
+ **Given** [context]
70
+ **When** [invalid action or error condition]
71
+ **Then** [error handling behavior]
72
+
73
+ ## Technical Notes
74
+ - **Components**:
75
+ - Use existing: [check app-map.md]
76
+ - Create new: [add to app-map after]
77
+ - **API**: [endpoints if any]
78
+ - **State**: [state management notes]
79
+ - **Constraints**: [technical limitations]
80
+
81
+ ## Test Strategy
82
+ - [ ] Unit: [what to test]
83
+ - [ ] Integration: [what to test]
84
+ - [ ] E2E: [user flow to verify]
85
+
86
+ ## Dependencies
87
+ - None
88
+
89
+ ## Complexity
90
+ [Low / Medium / High] - [justification]
91
+
92
+ ## Out of Scope
93
+ - [What this does NOT include]
94
+ EOF
95
+
96
+ echo -e "${GREEN}✓ Created story: $TASK_ID${NC}"
97
+ echo -e " ${CYAN}$STORY_FILE${NC}"
98
+ echo ""
99
+ echo "Title: $TITLE"
100
+ echo "Feature: $FEATURE"
101
+ echo ""
102
+ echo "Next steps:"
103
+ echo " 1. Fill in the story details"
104
+ echo " 2. Check app-map.md for existing components"
105
+ echo " 3. Add to ready.json when ready to implement"
@@ -0,0 +1,500 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Wogi Flow - Story Creation with Deep Decomposition
5
+ *
6
+ * Creates detailed stories with acceptance criteria.
7
+ * Supports --deep flag for automatic decomposition into sub-tasks.
8
+ *
9
+ * Usage:
10
+ * flow story "Add login form" # Create standard story
11
+ * flow story "Add login form" --deep # Create with decomposition
12
+ * flow story "Add login form" auth-feature # Specify feature folder
13
+ */
14
+
15
+ const fs = require('fs');
16
+ const path = require('path');
17
+ const {
18
+ getProjectRoot,
19
+ colors,
20
+ getConfig,
21
+ getConfigValue,
22
+ generateTaskId,
23
+ parseFlags,
24
+ outputJson,
25
+ withLock
26
+ } = require('./flow-utils');
27
+
28
+ const PROJECT_ROOT = getProjectRoot();
29
+ const WORKFLOW_DIR = path.join(PROJECT_ROOT, '.workflow');
30
+ const CHANGES_DIR = path.join(WORKFLOW_DIR, 'changes');
31
+ const STATE_DIR = path.join(WORKFLOW_DIR, 'state');
32
+ const READY_PATH = path.join(STATE_DIR, 'ready.json');
33
+
34
+ function log(color, ...args) {
35
+ console.log(colors[color] + args.join(' ') + colors.reset);
36
+ }
37
+
38
+ /**
39
+ * Generate a task ID for a story
40
+ * Uses hash-based IDs (wf-XXXXXXXX format)
41
+ */
42
+ function getTaskId(title) {
43
+ return generateTaskId(title);
44
+ }
45
+
46
+ /**
47
+ * Generate a sub-task ID
48
+ * Sub-tasks use parent ID with numeric suffix: wf-a1b2c3d4-01, wf-a1b2c3d4-02, etc.
49
+ */
50
+ function getSubTaskId(parentId, subNum) {
51
+ return `${parentId}-${String(subNum).padStart(2, '0')}`;
52
+ }
53
+
54
+ /**
55
+ * Generate story template content
56
+ */
57
+ function generateStoryTemplate(taskId, title) {
58
+ return `# [${taskId}] ${title}
59
+
60
+ ## User Story
61
+ **As a** [user type]
62
+ **I want** [action/capability]
63
+ **So that** [benefit/value]
64
+
65
+ ## Description
66
+ [2-4 sentences explaining the context, what needs to be built, and why it matters.]
67
+
68
+ ## Acceptance Criteria
69
+
70
+ ### Scenario 1: Happy path
71
+ **Given** [initial context/state]
72
+ **When** [action taken]
73
+ **Then** [expected outcome]
74
+ **And** [additional outcome if needed]
75
+
76
+ ### Scenario 2: Alternative path
77
+ **Given** [context]
78
+ **When** [action]
79
+ **Then** [outcome]
80
+
81
+ ### Scenario 3: Error handling
82
+ **Given** [context]
83
+ **When** [invalid action or error condition]
84
+ **Then** [error handling behavior]
85
+
86
+ ## Technical Notes
87
+ - **Components**:
88
+ - Use existing: [check app-map.md]
89
+ - Create new: [add to app-map after]
90
+ - **API**: [endpoints if any]
91
+ - **State**: [state management notes]
92
+ - **Constraints**: [technical limitations]
93
+
94
+ ## Test Strategy
95
+ - [ ] Unit: [what to test]
96
+ - [ ] Integration: [what to test]
97
+ - [ ] E2E: [user flow to verify]
98
+
99
+ ## Dependencies
100
+ - None
101
+
102
+ ## Complexity
103
+ [Low / Medium / High] - [justification]
104
+
105
+ ## Out of Scope
106
+ - [What this does NOT include]
107
+ `;
108
+ }
109
+
110
+ /**
111
+ * Generate sub-task template
112
+ */
113
+ function generateSubTaskTemplate(parentId, subNum, objective, doneCriteria, deps = []) {
114
+ const subTaskId = getSubTaskId(parentId, subNum);
115
+ const depStr = deps.length > 0
116
+ ? deps.map(d => `- ${d}`).join('\n')
117
+ : '- None (can start immediately)';
118
+
119
+ return {
120
+ id: subTaskId,
121
+ content: `# [${subTaskId}] ${objective}
122
+
123
+ ## Objective
124
+ ${objective}
125
+
126
+ ## Done Criteria
127
+ ${doneCriteria.map(c => `- [ ] ${c}`).join('\n')}
128
+
129
+ ## Dependencies
130
+ ${depStr}
131
+
132
+ ## Scope
133
+ S - Single focused objective
134
+
135
+ ## Parent
136
+ Part of [${parentId}]
137
+ `
138
+ };
139
+ }
140
+
141
+ /**
142
+ * Analyze title and suggest decomposition
143
+ */
144
+ function analyzeForDecomposition(title) {
145
+ const titleLower = title.toLowerCase();
146
+
147
+ // Common patterns that suggest complexity
148
+ const complexityIndicators = {
149
+ auth: ['login', 'logout', 'register', 'signup', 'authentication', 'password', 'session'],
150
+ form: ['form', 'input', 'validation', 'submit'],
151
+ crud: ['create', 'read', 'update', 'delete', 'edit', 'list', 'view'],
152
+ ui: ['component', 'modal', 'dialog', 'dropdown', 'table', 'grid', 'card'],
153
+ api: ['api', 'endpoint', 'fetch', 'request', 'integration'],
154
+ state: ['state', 'store', 'context', 'redux', 'zustand']
155
+ };
156
+
157
+ const detectedPatterns = [];
158
+ for (const [pattern, keywords] of Object.entries(complexityIndicators)) {
159
+ if (keywords.some(kw => titleLower.includes(kw))) {
160
+ detectedPatterns.push(pattern);
161
+ }
162
+ }
163
+
164
+ // Suggest sub-tasks based on patterns
165
+ const suggestedSubTasks = [];
166
+
167
+ if (detectedPatterns.includes('auth')) {
168
+ suggestedSubTasks.push(
169
+ { objective: 'Create UI layout and structure', criteria: ['Layout renders correctly', 'Responsive design works'] },
170
+ { objective: 'Add form inputs with validation', criteria: ['Inputs accept user data', 'Validation feedback shows'] },
171
+ { objective: 'Implement API integration', criteria: ['API calls work', 'Errors handled'] },
172
+ { objective: 'Handle success flow', criteria: ['Success redirects work', 'State updates correctly'] },
173
+ { objective: 'Handle error states', criteria: ['Error messages display', 'User can retry'] },
174
+ { objective: 'Add loading states', criteria: ['Loading indicator shows', 'UI disabled during load'] }
175
+ );
176
+ } else if (detectedPatterns.includes('form')) {
177
+ suggestedSubTasks.push(
178
+ { objective: 'Create form layout', criteria: ['Form renders correctly', 'Labels and inputs aligned'] },
179
+ { objective: 'Add input validation', criteria: ['Validation rules work', 'Error messages show'] },
180
+ { objective: 'Implement form submission', criteria: ['Submit triggers correctly', 'Data sent properly'] },
181
+ { objective: 'Handle submission states', criteria: ['Loading state works', 'Success/error handled'] }
182
+ );
183
+ } else if (detectedPatterns.includes('crud')) {
184
+ suggestedSubTasks.push(
185
+ { objective: 'Create list/display view', criteria: ['Data displays correctly', 'Empty state handled'] },
186
+ { objective: 'Add create functionality', criteria: ['Create form works', 'New items appear'] },
187
+ { objective: 'Add edit functionality', criteria: ['Edit form populates', 'Changes save correctly'] },
188
+ { objective: 'Add delete functionality', criteria: ['Delete confirmation works', 'Items removed correctly'] }
189
+ );
190
+ } else if (detectedPatterns.includes('ui')) {
191
+ suggestedSubTasks.push(
192
+ { objective: 'Create component structure', criteria: ['Component renders', 'Props typed correctly'] },
193
+ { objective: 'Add styling and variants', criteria: ['Styles applied', 'Variants work'] },
194
+ { objective: 'Add interactivity', criteria: ['Events handled', 'State updates'] },
195
+ { objective: 'Handle edge cases', criteria: ['Empty state works', 'Error state works', 'Loading state works'] }
196
+ );
197
+ }
198
+
199
+ return {
200
+ patterns: detectedPatterns,
201
+ suggestedSubTasks,
202
+ shouldDecompose: suggestedSubTasks.length >= 3
203
+ };
204
+ }
205
+
206
+ /**
207
+ * Sanitize feature name to prevent path traversal and invalid characters
208
+ * @param {string} feature - The feature name to sanitize
209
+ * @returns {string} Sanitized feature name
210
+ */
211
+ function sanitizeFeatureName(feature) {
212
+ if (!feature || typeof feature !== 'string') {
213
+ return 'general';
214
+ }
215
+
216
+ // Remove path traversal attempts and normalize
217
+ let sanitized = feature
218
+ .replace(/\.\./g, '') // Remove ..
219
+ .replace(/[\/\\]/g, '-') // Replace slashes with dashes
220
+ .replace(/[<>:"|?*\x00-\x1f]/g, '') // Remove invalid filename chars
221
+ .replace(/^[.\s]+|[.\s]+$/g, '') // Remove leading/trailing dots and spaces
222
+ .trim();
223
+
224
+ // If empty after sanitization, use default
225
+ if (!sanitized) {
226
+ return 'general';
227
+ }
228
+
229
+ // Limit length
230
+ if (sanitized.length > 100) {
231
+ sanitized = sanitized.substring(0, 100);
232
+ }
233
+
234
+ return sanitized;
235
+ }
236
+
237
+ /**
238
+ * Validate that a path stays within the allowed directory
239
+ * @param {string} targetPath - The path to validate
240
+ * @param {string} allowedDir - The directory that must contain the path
241
+ * @returns {boolean} True if valid
242
+ */
243
+ function isPathWithinDir(targetPath, allowedDir) {
244
+ const resolved = path.resolve(targetPath);
245
+ const resolvedAllowed = path.resolve(allowedDir);
246
+ return resolved.startsWith(resolvedAllowed + path.sep) || resolved === resolvedAllowed;
247
+ }
248
+
249
+ /**
250
+ * Create story with optional deep decomposition
251
+ */
252
+ async function createStory(title, feature, options = {}) {
253
+ // Input validation
254
+ if (!title || typeof title !== 'string' || title.trim().length === 0) {
255
+ throw new Error('Title is required and must be a non-empty string');
256
+ }
257
+
258
+ // Sanitize feature name to prevent path traversal
259
+ const sanitizedFeature = sanitizeFeatureName(feature);
260
+
261
+ const config = getConfig();
262
+ const decompositionConfig = config.storyDecomposition || {};
263
+
264
+ // Get priority from options or config
265
+ const defaultPriority = getConfigValue('priorities.defaultPriority', 'P2');
266
+ const priority = options.priority || defaultPriority;
267
+
268
+ // Build and validate feature directory path
269
+ const featureDir = path.join(CHANGES_DIR, sanitizedFeature);
270
+
271
+ // Ensure the path stays within CHANGES_DIR (defense in depth)
272
+ if (!isPathWithinDir(featureDir, CHANGES_DIR)) {
273
+ throw new Error(`Invalid feature name: path traversal detected`);
274
+ }
275
+
276
+ fs.mkdirSync(featureDir, { recursive: true });
277
+
278
+ // Generate hash-based task ID
279
+ const taskId = getTaskId(title);
280
+
281
+ // Create main story file
282
+ const storyContent = generateStoryTemplate(taskId, title);
283
+ const storyFile = path.join(featureDir, `${taskId}.md`);
284
+ fs.writeFileSync(storyFile, storyContent);
285
+
286
+ const result = {
287
+ taskId,
288
+ title,
289
+ feature: sanitizedFeature,
290
+ priority,
291
+ storyFile,
292
+ subTasks: []
293
+ };
294
+
295
+ // Check if decomposition needed
296
+ const analysis = analyzeForDecomposition(title);
297
+ const shouldDecompose = options.deep ||
298
+ (decompositionConfig.autoDecompose && analysis.shouldDecompose);
299
+
300
+ const shouldSuggest = !options.deep &&
301
+ !decompositionConfig.autoDecompose &&
302
+ decompositionConfig.autoDetect &&
303
+ analysis.shouldDecompose;
304
+
305
+ if (shouldSuggest) {
306
+ result.decompositionSuggested = true;
307
+ result.suggestedCount = analysis.suggestedSubTasks.length;
308
+ result.patterns = analysis.patterns;
309
+ }
310
+
311
+ if (shouldDecompose && analysis.suggestedSubTasks.length > 0) {
312
+ // Create sub-task files
313
+ let subNum = 1;
314
+ const subTaskIds = [];
315
+
316
+ for (const sub of analysis.suggestedSubTasks) {
317
+ const deps = subNum > 1 ? [`${taskId}-${String(subNum - 1).padStart(2, '0')}`] : [];
318
+ const subTask = generateSubTaskTemplate(taskId, subNum, sub.objective, sub.criteria, deps);
319
+
320
+ const subTaskFile = path.join(featureDir, `${subTask.id}.md`);
321
+ fs.writeFileSync(subTaskFile, subTask.content);
322
+
323
+ subTaskIds.push(subTask.id);
324
+ result.subTasks.push({
325
+ id: subTask.id,
326
+ objective: sub.objective,
327
+ file: subTaskFile
328
+ });
329
+ subNum++;
330
+ }
331
+
332
+ // Update ready.json with parent and sub-tasks (with file locking)
333
+ if (fs.existsSync(READY_PATH)) {
334
+ try {
335
+ await withLock(READY_PATH, async () => {
336
+ const ready = JSON.parse(fs.readFileSync(READY_PATH, 'utf8'));
337
+ ready.ready = ready.ready || [];
338
+
339
+ // Add parent task with new format
340
+ ready.ready.push({
341
+ id: taskId,
342
+ title,
343
+ type: 'parent',
344
+ subTasks: subTaskIds,
345
+ status: 'ready',
346
+ priority,
347
+ createdAt: new Date().toISOString()
348
+ });
349
+
350
+ // Add sub-tasks with new format
351
+ for (let i = 0; i < result.subTasks.length; i++) {
352
+ const sub = result.subTasks[i];
353
+ ready.ready.push({
354
+ id: sub.id,
355
+ title: sub.objective,
356
+ type: 'sub-task',
357
+ parent: taskId,
358
+ status: 'ready',
359
+ priority,
360
+ dependencies: i > 0 ? [result.subTasks[i - 1].id] : [],
361
+ createdAt: new Date().toISOString()
362
+ });
363
+ }
364
+
365
+ ready.lastUpdated = new Date().toISOString();
366
+ fs.writeFileSync(READY_PATH, JSON.stringify(ready, null, 2));
367
+ });
368
+ result.addedToReady = true;
369
+ } catch (err) {
370
+ result.addedToReady = false;
371
+ result.readyError = err.message;
372
+ }
373
+ }
374
+
375
+ result.decomposed = true;
376
+ }
377
+
378
+ return result;
379
+ }
380
+
381
+ // CLI handling
382
+ if (require.main === module) {
383
+ (async () => {
384
+ const { flags, positional } = parseFlags(process.argv.slice(2));
385
+
386
+ if (flags.help || positional.length === 0) {
387
+ console.log(`
388
+ Wogi Flow - Story Creation
389
+
390
+ Usage:
391
+ flow story "<title>" Create standard story
392
+ flow story "<title>" --deep Create with decomposition
393
+ flow story "<title>" <feature> Specify feature folder
394
+ flow story "<title>" --priority P1 Set priority (P0-P4)
395
+ flow story "<title>" <feature> --deep --json All options
396
+
397
+ Options:
398
+ --deep Automatically decompose into sub-tasks
399
+ --priority <P> Priority P0-P4 (default: from config, usually P2)
400
+ --json Output JSON instead of human-readable
401
+
402
+ Configuration (config.json):
403
+ "storyDecomposition": {
404
+ "autoDetect": true, // Suggest decomposition when beneficial
405
+ "autoDecompose": false, // Auto-decompose without asking
406
+ }
407
+ "priorities": {
408
+ "defaultPriority": "P2", // Default priority for new stories
409
+ }
410
+
411
+ Examples:
412
+ flow story "Add user login"
413
+ flow story "Add user login" --deep
414
+ flow story "Add user login" --priority P1
415
+ flow story "Add user login" authentication
416
+ flow story "Add user login" authentication --deep --json
417
+ `);
418
+ process.exit(0);
419
+ }
420
+
421
+ if (positional.length === 0) {
422
+ log('red', 'Error: Title is required');
423
+ process.exit(1);
424
+ }
425
+
426
+ const title = positional[0];
427
+ const feature = positional[1] || 'general';
428
+
429
+ // Validate priority if provided
430
+ let priority = flags.priority;
431
+ if (priority && !/^P[0-4]$/.test(priority)) {
432
+ log('yellow', `Warning: Invalid priority "${priority}", using default`);
433
+ priority = undefined;
434
+ }
435
+
436
+ // Create story
437
+ const result = await createStory(title, feature, {
438
+ deep: flags.deep,
439
+ priority
440
+ });
441
+
442
+ // JSON output
443
+ if (flags.json) {
444
+ outputJson({
445
+ success: true,
446
+ ...result
447
+ });
448
+ // outputJson exits, so this won't run
449
+ }
450
+
451
+ // Human-readable output
452
+ console.log('');
453
+ log('green', `✓ Created story: ${result.taskId}`);
454
+ log('cyan', ` ${result.storyFile}`);
455
+ console.log('');
456
+ log('white', `Title: ${result.title}`);
457
+ log('white', `Feature: ${result.feature}`);
458
+ log('white', `Priority: ${result.priority}`);
459
+
460
+ if (result.decomposed) {
461
+ console.log('');
462
+ log('cyan', `📋 Decomposed into ${result.subTasks.length} sub-tasks:`);
463
+ result.subTasks.forEach(sub => {
464
+ log('dim', ` ${sub.id}: ${sub.objective}`);
465
+ });
466
+ if (result.addedToReady) {
467
+ console.log('');
468
+ log('green', '✓ Added parent and sub-tasks to ready.json');
469
+ }
470
+ } else if (result.decompositionSuggested) {
471
+ console.log('');
472
+ log('yellow', `💡 This looks like a complex story (${result.patterns.join(', ')})`);
473
+ log('yellow', ` Consider using --deep to decompose into ~${result.suggestedCount} sub-tasks`);
474
+ log('dim', ` Run: flow story "${title}" ${feature} --deep`);
475
+ }
476
+
477
+ console.log('');
478
+ log('dim', 'Next steps:');
479
+ log('dim', ' 1. Fill in the story details');
480
+ log('dim', ' 2. Check app-map.md for existing components');
481
+ if (!result.decomposed) {
482
+ log('dim', ' 3. Add to ready.json when ready to implement');
483
+ } else {
484
+ log('dim', ' 3. Start with: /wogi-start ' + result.subTasks[0].id);
485
+ }
486
+ })().catch(e => {
487
+ log('red', `Error: ${err.message}`);
488
+ process.exit(1);
489
+ });
490
+ }
491
+
492
+ // Export for use by other modules
493
+ module.exports = {
494
+ createStory,
495
+ analyzeForDecomposition,
496
+ generateStoryTemplate,
497
+ generateSubTaskTemplate,
498
+ getTaskId,
499
+ getSubTaskId
500
+ };