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,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
|
+
}
|