wogiflow 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.workflow/agents/reviewer.md +81 -0
- package/.workflow/agents/security.md +94 -0
- package/.workflow/agents/story-writer.md +58 -0
- package/.workflow/bridges/base-bridge.js +395 -0
- package/.workflow/bridges/claude-bridge.js +434 -0
- package/.workflow/bridges/index.js +130 -0
- package/.workflow/lib/assumption-detector.js +481 -0
- package/.workflow/lib/config-substitution.js +371 -0
- package/.workflow/lib/failure-categories.js +478 -0
- package/.workflow/state/app-map.md.template +15 -0
- package/.workflow/state/architecture.md.template +24 -0
- package/.workflow/state/component-index.json.template +5 -0
- package/.workflow/state/decisions.md.template +15 -0
- package/.workflow/state/feedback-patterns.md.template +9 -0
- package/.workflow/state/knowledge-sync.json.template +6 -0
- package/.workflow/state/progress.md.template +14 -0
- package/.workflow/state/ready.json.template +7 -0
- package/.workflow/state/request-log.md.template +14 -0
- package/.workflow/state/session-state.json.template +11 -0
- package/.workflow/state/stack.md.template +33 -0
- package/.workflow/state/testing.md.template +36 -0
- package/.workflow/templates/claude-md.hbs +257 -0
- package/.workflow/templates/correction-report.md +67 -0
- package/.workflow/templates/gemini-md.hbs +52 -0
- package/README.md +1802 -0
- package/bin/flow +205 -0
- package/lib/index.js +33 -0
- package/lib/installer.js +467 -0
- package/lib/release-channel.js +269 -0
- package/lib/skill-registry.js +526 -0
- package/lib/upgrader.js +401 -0
- package/lib/utils.js +305 -0
- package/package.json +64 -0
- package/scripts/flow +985 -0
- package/scripts/flow-adaptive-learning.js +1259 -0
- package/scripts/flow-aggregate.js +488 -0
- package/scripts/flow-archive +133 -0
- package/scripts/flow-auto-context.js +1015 -0
- package/scripts/flow-auto-learn.js +615 -0
- package/scripts/flow-bridge.js +223 -0
- package/scripts/flow-browser-suggest.js +316 -0
- package/scripts/flow-bug.js +247 -0
- package/scripts/flow-cascade.js +711 -0
- package/scripts/flow-changelog +85 -0
- package/scripts/flow-checkpoint.js +483 -0
- package/scripts/flow-cli.js +403 -0
- package/scripts/flow-code-intelligence.js +760 -0
- package/scripts/flow-complexity.js +502 -0
- package/scripts/flow-config-set.js +152 -0
- package/scripts/flow-constants.js +157 -0
- package/scripts/flow-context +152 -0
- package/scripts/flow-context-init.js +482 -0
- package/scripts/flow-context-monitor.js +384 -0
- package/scripts/flow-context-scoring.js +886 -0
- package/scripts/flow-correct.js +458 -0
- package/scripts/flow-damage-control.js +985 -0
- package/scripts/flow-deps +101 -0
- package/scripts/flow-diff.js +700 -0
- package/scripts/flow-done +151 -0
- package/scripts/flow-done.js +489 -0
- package/scripts/flow-durable-session.js +1541 -0
- package/scripts/flow-entropy-monitor.js +345 -0
- package/scripts/flow-export-profile +349 -0
- package/scripts/flow-export-scanner.js +1046 -0
- package/scripts/flow-figma-confirm.js +400 -0
- package/scripts/flow-figma-extract.js +496 -0
- package/scripts/flow-figma-generate.js +683 -0
- package/scripts/flow-figma-index.js +909 -0
- package/scripts/flow-figma-match.js +617 -0
- package/scripts/flow-figma-mcp-server.js +518 -0
- package/scripts/flow-figma-pipeline.js +414 -0
- package/scripts/flow-file-ops.js +301 -0
- package/scripts/flow-gate-confidence.js +825 -0
- package/scripts/flow-guided-edit.js +659 -0
- package/scripts/flow-health +185 -0
- package/scripts/flow-health.js +413 -0
- package/scripts/flow-hooks.js +556 -0
- package/scripts/flow-http-client.js +249 -0
- package/scripts/flow-hybrid-detect.js +167 -0
- package/scripts/flow-hybrid-interactive.js +591 -0
- package/scripts/flow-hybrid-test.js +152 -0
- package/scripts/flow-import-profile +439 -0
- package/scripts/flow-init +253 -0
- package/scripts/flow-instruction-richness.js +827 -0
- package/scripts/flow-jira-integration.js +579 -0
- package/scripts/flow-knowledge-router.js +522 -0
- package/scripts/flow-knowledge-sync.js +589 -0
- package/scripts/flow-linear-integration.js +631 -0
- package/scripts/flow-links.js +774 -0
- package/scripts/flow-log-manager.js +559 -0
- package/scripts/flow-loop-enforcer.js +1246 -0
- package/scripts/flow-loop-retry-learning.js +630 -0
- package/scripts/flow-lsp.js +923 -0
- package/scripts/flow-map-index +348 -0
- package/scripts/flow-map-sync +201 -0
- package/scripts/flow-memory-blocks.js +668 -0
- package/scripts/flow-memory-compactor.js +350 -0
- package/scripts/flow-memory-db.js +1110 -0
- package/scripts/flow-memory-sync.js +484 -0
- package/scripts/flow-metrics.js +353 -0
- package/scripts/flow-migrate-ids.js +370 -0
- package/scripts/flow-model-adapter.js +802 -0
- package/scripts/flow-model-router.js +884 -0
- package/scripts/flow-models.js +1231 -0
- package/scripts/flow-morning.js +517 -0
- package/scripts/flow-multi-approach.js +660 -0
- package/scripts/flow-new-feature +86 -0
- package/scripts/flow-onboard +1042 -0
- package/scripts/flow-orchestrate-llm.js +459 -0
- package/scripts/flow-orchestrate.js +3592 -0
- package/scripts/flow-output.js +123 -0
- package/scripts/flow-parallel-detector.js +399 -0
- package/scripts/flow-parallel-dispatch.js +987 -0
- package/scripts/flow-parallel.js +428 -0
- package/scripts/flow-pattern-enforcer.js +600 -0
- package/scripts/flow-prd-manager.js +282 -0
- package/scripts/flow-progress.js +323 -0
- package/scripts/flow-project-analyzer.js +975 -0
- package/scripts/flow-prompt-composer.js +487 -0
- package/scripts/flow-providers.js +1381 -0
- package/scripts/flow-queue.js +308 -0
- package/scripts/flow-ready +82 -0
- package/scripts/flow-ready.js +189 -0
- package/scripts/flow-regression.js +396 -0
- package/scripts/flow-response-parser.js +450 -0
- package/scripts/flow-resume.js +284 -0
- package/scripts/flow-rules-sync.js +439 -0
- package/scripts/flow-run-trace.js +718 -0
- package/scripts/flow-safety.js +587 -0
- package/scripts/flow-search +104 -0
- package/scripts/flow-security.js +481 -0
- package/scripts/flow-session-end +106 -0
- package/scripts/flow-session-end.js +437 -0
- package/scripts/flow-session-state.js +671 -0
- package/scripts/flow-setup-hooks +216 -0
- package/scripts/flow-setup-hooks.js +377 -0
- package/scripts/flow-skill-create.js +329 -0
- package/scripts/flow-skill-creator.js +572 -0
- package/scripts/flow-skill-generator.js +1046 -0
- package/scripts/flow-skill-learn.js +880 -0
- package/scripts/flow-skill-matcher.js +578 -0
- package/scripts/flow-spec-generator.js +820 -0
- package/scripts/flow-stack-wizard.js +895 -0
- package/scripts/flow-standup +162 -0
- package/scripts/flow-start +74 -0
- package/scripts/flow-start.js +235 -0
- package/scripts/flow-status +110 -0
- package/scripts/flow-status.js +301 -0
- package/scripts/flow-step-browser.js +83 -0
- package/scripts/flow-step-changelog.js +217 -0
- package/scripts/flow-step-comments.js +306 -0
- package/scripts/flow-step-complexity.js +234 -0
- package/scripts/flow-step-coverage.js +218 -0
- package/scripts/flow-step-knowledge.js +193 -0
- package/scripts/flow-step-pr-tests.js +364 -0
- package/scripts/flow-step-regression.js +89 -0
- package/scripts/flow-step-review.js +516 -0
- package/scripts/flow-step-security.js +162 -0
- package/scripts/flow-step-silent-failures.js +290 -0
- package/scripts/flow-step-simplifier.js +346 -0
- package/scripts/flow-story +105 -0
- package/scripts/flow-story.js +500 -0
- package/scripts/flow-suspend.js +252 -0
- package/scripts/flow-sync-daemon.js +654 -0
- package/scripts/flow-task-analyzer.js +606 -0
- package/scripts/flow-team-dashboard.js +748 -0
- package/scripts/flow-team-sync.js +752 -0
- package/scripts/flow-team.js +977 -0
- package/scripts/flow-tech-options.js +528 -0
- package/scripts/flow-templates.js +812 -0
- package/scripts/flow-tiered-learning.js +728 -0
- package/scripts/flow-trace +204 -0
- package/scripts/flow-transcript-chunking.js +1106 -0
- package/scripts/flow-transcript-digest.js +7918 -0
- package/scripts/flow-transcript-language.js +465 -0
- package/scripts/flow-transcript-parsing.js +1085 -0
- package/scripts/flow-transcript-stories.js +2194 -0
- package/scripts/flow-update-map +224 -0
- package/scripts/flow-utils.js +2242 -0
- package/scripts/flow-verification.js +644 -0
- package/scripts/flow-verify.js +1177 -0
- package/scripts/flow-voice-input.js +638 -0
- package/scripts/flow-watch +168 -0
- package/scripts/flow-workflow-steps.js +521 -0
- package/scripts/flow-workflow.js +1029 -0
- package/scripts/flow-worktree.js +489 -0
- package/scripts/hooks/adapters/base-adapter.js +102 -0
- package/scripts/hooks/adapters/claude-code.js +359 -0
- package/scripts/hooks/adapters/index.js +79 -0
- package/scripts/hooks/core/component-check.js +341 -0
- package/scripts/hooks/core/index.js +35 -0
- package/scripts/hooks/core/loop-check.js +241 -0
- package/scripts/hooks/core/session-context.js +294 -0
- package/scripts/hooks/core/task-gate.js +177 -0
- package/scripts/hooks/core/validation.js +230 -0
- package/scripts/hooks/entry/claude-code/post-tool-use.js +65 -0
- package/scripts/hooks/entry/claude-code/pre-tool-use.js +89 -0
- package/scripts/hooks/entry/claude-code/session-end.js +87 -0
- package/scripts/hooks/entry/claude-code/session-start.js +46 -0
- package/scripts/hooks/entry/claude-code/stop.js +43 -0
- package/scripts/postinstall.js +139 -0
- package/templates/browser-test-flow.json +56 -0
- package/templates/bug-report.md +43 -0
- package/templates/component-detail.md +42 -0
- package/templates/component.stories.tsx +49 -0
- package/templates/context/constraints.md +83 -0
- package/templates/context/conventions.md +177 -0
- package/templates/context/stack.md +60 -0
- package/templates/correction-report.md +90 -0
- package/templates/feature-proposal.md +35 -0
- package/templates/hybrid/_base.md +254 -0
- package/templates/hybrid/_patterns.md +45 -0
- package/templates/hybrid/create-component.md +127 -0
- package/templates/hybrid/create-file.md +56 -0
- package/templates/hybrid/create-hook.md +145 -0
- package/templates/hybrid/create-service.md +70 -0
- package/templates/hybrid/fix-bug.md +33 -0
- package/templates/hybrid/modify-file.md +55 -0
- package/templates/story.md +68 -0
- package/templates/task.json +56 -0
- package/templates/trace.md +69 -0
|
@@ -0,0 +1,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
|
+
};
|