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,502 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Wogi Flow - Task Complexity Assessment
|
|
5
|
+
*
|
|
6
|
+
* Analyzes task complexity to guide context richness for the local LLM.
|
|
7
|
+
*
|
|
8
|
+
* Key insight: Local LLM tokens are FREE. The goal is to give the LLM
|
|
9
|
+
* everything it needs for 90%+ success rate. Failed executions cost more
|
|
10
|
+
* (in Claude retry tokens) than generous upfront context.
|
|
11
|
+
*
|
|
12
|
+
* This module helps Claude estimate the MINIMUM context needed for success,
|
|
13
|
+
* not impose artificial limits on token usage.
|
|
14
|
+
*
|
|
15
|
+
* Usage:
|
|
16
|
+
* const { assessTaskComplexity } = require('./flow-complexity');
|
|
17
|
+
* const complexity = assessTaskComplexity(task);
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
// ============================================================
|
|
21
|
+
// Complexity Levels (Guidance, NOT Hard Limits)
|
|
22
|
+
// ============================================================
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* These are GUIDANCE for Claude on how much context to include.
|
|
26
|
+
* There are NO hard token limits - local LLM tokens are free.
|
|
27
|
+
*
|
|
28
|
+
* The suggestedTokens is a minimum estimate for 90%+ success rate.
|
|
29
|
+
* Claude should include MORE context if there's any doubt.
|
|
30
|
+
*/
|
|
31
|
+
const COMPLEXITY_LEVELS = {
|
|
32
|
+
small: {
|
|
33
|
+
suggestedTokens: 2000,
|
|
34
|
+
description: 'Single file, simple change, no new dependencies',
|
|
35
|
+
contextNeeds: 'Basic imports list, target file only'
|
|
36
|
+
},
|
|
37
|
+
medium: {
|
|
38
|
+
suggestedTokens: 4000,
|
|
39
|
+
description: 'Multi-file changes, moderate complexity, some boilerplate',
|
|
40
|
+
contextNeeds: 'Full imports, props/types for related components, patterns'
|
|
41
|
+
},
|
|
42
|
+
large: {
|
|
43
|
+
suggestedTokens: 6000,
|
|
44
|
+
description: 'Many files, complex logic, tests, error handling',
|
|
45
|
+
contextNeeds: 'All imports with usage examples, related code snippets, full types'
|
|
46
|
+
},
|
|
47
|
+
xl: {
|
|
48
|
+
suggestedTokens: 10000,
|
|
49
|
+
description: 'Architectural changes, many dependencies, extensive boilerplate',
|
|
50
|
+
contextNeeds: 'Everything available - full context, all examples, comprehensive types'
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// Keep TOKEN_BUDGETS for backward compatibility but without hard limits
|
|
55
|
+
const TOKEN_BUDGETS = {
|
|
56
|
+
small: { default: 2000, description: COMPLEXITY_LEVELS.small.description },
|
|
57
|
+
medium: { default: 4000, description: COMPLEXITY_LEVELS.medium.description },
|
|
58
|
+
large: { default: 6000, description: COMPLEXITY_LEVELS.large.description },
|
|
59
|
+
xl: { default: 10000, description: COMPLEXITY_LEVELS.xl.description }
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// ============================================================
|
|
63
|
+
// Complexity Keywords and Weights
|
|
64
|
+
// ============================================================
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Keywords that indicate higher complexity
|
|
68
|
+
* Each keyword adds to the complexity score
|
|
69
|
+
*/
|
|
70
|
+
const COMPLEXITY_KEYWORDS = {
|
|
71
|
+
// High complexity (weight: 3)
|
|
72
|
+
high: [
|
|
73
|
+
'refactor', 'migrate', 'authentication', 'authorization', 'auth',
|
|
74
|
+
'security', 'encryption', 'database', 'migration', 'schema',
|
|
75
|
+
'integration', 'api', 'websocket', 'realtime', 'cache', 'caching',
|
|
76
|
+
'payment', 'stripe', 'checkout', 'transaction'
|
|
77
|
+
],
|
|
78
|
+
// Medium complexity (weight: 2)
|
|
79
|
+
medium: [
|
|
80
|
+
'validation', 'form', 'crud', 'create', 'update', 'delete',
|
|
81
|
+
'filter', 'sort', 'pagination', 'search', 'modal', 'dialog',
|
|
82
|
+
'notification', 'toast', 'error handling', 'loading', 'state',
|
|
83
|
+
'hook', 'context', 'provider', 'service'
|
|
84
|
+
],
|
|
85
|
+
// Low complexity (weight: 1)
|
|
86
|
+
low: [
|
|
87
|
+
'style', 'css', 'color', 'margin', 'padding', 'layout',
|
|
88
|
+
'text', 'label', 'button', 'icon', 'image', 'link',
|
|
89
|
+
'import', 'export', 'rename', 'move', 'typo', 'fix'
|
|
90
|
+
]
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Keywords that indicate simple/small tasks
|
|
95
|
+
*/
|
|
96
|
+
const SIMPLICITY_KEYWORDS = [
|
|
97
|
+
'simple', 'quick', 'small', 'minor', 'typo', 'rename',
|
|
98
|
+
'comment', 'log', 'console', 'debug', 'cleanup', 'remove unused'
|
|
99
|
+
];
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Keywords indicating test requirements
|
|
103
|
+
*/
|
|
104
|
+
const TEST_KEYWORDS = [
|
|
105
|
+
'test', 'spec', 'unit test', 'integration test', 'e2e',
|
|
106
|
+
'coverage', 'mock', 'stub', 'fixture'
|
|
107
|
+
];
|
|
108
|
+
|
|
109
|
+
// ============================================================
|
|
110
|
+
// Complexity Assessment Functions
|
|
111
|
+
// ============================================================
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Counts file references in task description
|
|
115
|
+
* @param {string} text - Task description or criteria
|
|
116
|
+
* @returns {Object} - File count metrics
|
|
117
|
+
*/
|
|
118
|
+
function countFileReferences(text) {
|
|
119
|
+
if (!text) return { total: 0, create: 0, modify: 0 };
|
|
120
|
+
|
|
121
|
+
const lowerText = text.toLowerCase();
|
|
122
|
+
|
|
123
|
+
// Count explicit file mentions
|
|
124
|
+
const filePatterns = [
|
|
125
|
+
/\b\w+\.(tsx?|jsx?|ts|js|css|scss|json|md)\b/gi,
|
|
126
|
+
/create\s+(?:a\s+)?(?:new\s+)?(\w+)\s+(?:file|component|service|hook)/gi,
|
|
127
|
+
/modify\s+(?:the\s+)?(\w+)/gi,
|
|
128
|
+
/update\s+(?:the\s+)?(\w+)/gi,
|
|
129
|
+
/add\s+to\s+(\w+)/gi
|
|
130
|
+
];
|
|
131
|
+
|
|
132
|
+
let fileCount = 0;
|
|
133
|
+
for (const pattern of filePatterns) {
|
|
134
|
+
const matches = text.match(pattern);
|
|
135
|
+
if (matches) fileCount += matches.length;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Estimate create vs modify
|
|
139
|
+
const createKeywords = ['create', 'new', 'add', 'implement', 'build'];
|
|
140
|
+
const modifyKeywords = ['update', 'modify', 'change', 'fix', 'edit', 'refactor'];
|
|
141
|
+
|
|
142
|
+
const hasCreate = createKeywords.some(k => lowerText.includes(k));
|
|
143
|
+
const hasModify = modifyKeywords.some(k => lowerText.includes(k));
|
|
144
|
+
|
|
145
|
+
// Minimum 1 file if task exists
|
|
146
|
+
const total = Math.max(1, fileCount);
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
total,
|
|
150
|
+
create: hasCreate ? Math.ceil(total * 0.6) : 0,
|
|
151
|
+
modify: hasModify ? Math.ceil(total * 0.4) : total
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Detects complexity keywords in text
|
|
157
|
+
* @param {string} text - Task description
|
|
158
|
+
* @returns {Object} - Detected keywords by weight
|
|
159
|
+
*/
|
|
160
|
+
function detectComplexityKeywords(text) {
|
|
161
|
+
if (!text) return { high: [], medium: [], low: [], simplicity: [] };
|
|
162
|
+
|
|
163
|
+
const lowerText = text.toLowerCase();
|
|
164
|
+
|
|
165
|
+
const detected = {
|
|
166
|
+
high: COMPLEXITY_KEYWORDS.high.filter(k => lowerText.includes(k)),
|
|
167
|
+
medium: COMPLEXITY_KEYWORDS.medium.filter(k => lowerText.includes(k)),
|
|
168
|
+
low: COMPLEXITY_KEYWORDS.low.filter(k => lowerText.includes(k)),
|
|
169
|
+
simplicity: SIMPLICITY_KEYWORDS.filter(k => lowerText.includes(k))
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
return detected;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Checks if tests are required
|
|
177
|
+
* @param {string} text - Task description
|
|
178
|
+
* @returns {boolean}
|
|
179
|
+
*/
|
|
180
|
+
function requiresTests(text) {
|
|
181
|
+
if (!text) return false;
|
|
182
|
+
const lowerText = text.toLowerCase();
|
|
183
|
+
return TEST_KEYWORDS.some(k => lowerText.includes(k));
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Counts acceptance criteria/scenarios
|
|
188
|
+
* @param {Object} task - Task object
|
|
189
|
+
* @returns {number}
|
|
190
|
+
*/
|
|
191
|
+
function countAcceptanceCriteria(task) {
|
|
192
|
+
let count = 0;
|
|
193
|
+
|
|
194
|
+
// Check for scenarios in task
|
|
195
|
+
if (task.scenarios) {
|
|
196
|
+
count += Array.isArray(task.scenarios) ? task.scenarios.length : 1;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Check for acceptance criteria
|
|
200
|
+
if (task.acceptanceCriteria) {
|
|
201
|
+
if (Array.isArray(task.acceptanceCriteria)) {
|
|
202
|
+
count += task.acceptanceCriteria.length;
|
|
203
|
+
} else if (typeof task.acceptanceCriteria === 'string') {
|
|
204
|
+
// Count Given/When/Then blocks
|
|
205
|
+
const givenMatches = task.acceptanceCriteria.match(/given\b/gi);
|
|
206
|
+
count += givenMatches ? givenMatches.length : 1;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Check description for scenario patterns
|
|
211
|
+
const description = task.description || task.title || '';
|
|
212
|
+
const scenarioMatches = description.match(/scenario\s*\d*:|given\s|when\s|then\s/gi);
|
|
213
|
+
if (scenarioMatches) {
|
|
214
|
+
count += Math.ceil(scenarioMatches.length / 3); // Given/When/Then = 1 scenario
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return count || 1; // Minimum 1
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Calculates complexity score and estimates tokens
|
|
222
|
+
* @param {Object} factors - Complexity factors
|
|
223
|
+
* @returns {Object} - Score and token estimate
|
|
224
|
+
*/
|
|
225
|
+
function calculateComplexityScore(factors) {
|
|
226
|
+
let score = 0;
|
|
227
|
+
const breakdown = [];
|
|
228
|
+
|
|
229
|
+
// Base score
|
|
230
|
+
score += 1000;
|
|
231
|
+
breakdown.push('Base: 1000');
|
|
232
|
+
|
|
233
|
+
// File-based scoring
|
|
234
|
+
// Note: These are conservative estimates. TypeScript files with types,
|
|
235
|
+
// imports, and boilerplate can easily be 1000+ tokens each.
|
|
236
|
+
// Prefer overestimating since local LLM tokens are free.
|
|
237
|
+
if (factors.fileCount.create > 0) {
|
|
238
|
+
const createTokens = factors.fileCount.create * 1200; // Increased from 800
|
|
239
|
+
score += createTokens;
|
|
240
|
+
breakdown.push(`Create ${factors.fileCount.create} files: +${createTokens}`);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (factors.fileCount.modify > 0) {
|
|
244
|
+
const modifyTokens = factors.fileCount.modify * 600; // Increased from 400
|
|
245
|
+
score += modifyTokens;
|
|
246
|
+
breakdown.push(`Modify ${factors.fileCount.modify} files: +${modifyTokens}`);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Keyword-based scoring
|
|
250
|
+
if (factors.keywords.high.length > 0) {
|
|
251
|
+
const highTokens = factors.keywords.high.length * 500;
|
|
252
|
+
score += highTokens;
|
|
253
|
+
breakdown.push(`High complexity keywords (${factors.keywords.high.join(', ')}): +${highTokens}`);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (factors.keywords.medium.length > 0) {
|
|
257
|
+
const medTokens = factors.keywords.medium.length * 300;
|
|
258
|
+
score += medTokens;
|
|
259
|
+
breakdown.push(`Medium complexity keywords: +${medTokens}`);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Tests
|
|
263
|
+
if (factors.hasTests) {
|
|
264
|
+
score += 1000;
|
|
265
|
+
breakdown.push('Tests required: +1000');
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Acceptance criteria complexity
|
|
269
|
+
if (factors.acceptanceCriteriaCount > 3) {
|
|
270
|
+
const acTokens = (factors.acceptanceCriteriaCount - 3) * 200;
|
|
271
|
+
score += acTokens;
|
|
272
|
+
breakdown.push(`Extra acceptance criteria: +${acTokens}`);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Simplicity discount
|
|
276
|
+
if (factors.keywords.simplicity.length > 0 && score > 1500) {
|
|
277
|
+
const discount = Math.min(500, factors.keywords.simplicity.length * 200);
|
|
278
|
+
score -= discount;
|
|
279
|
+
breakdown.push(`Simplicity discount: -${discount}`);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return { score, breakdown };
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Determines complexity level from score
|
|
287
|
+
* Uses soft thresholds - these guide context richness, not limit tokens
|
|
288
|
+
* @param {number} score - Calculated score
|
|
289
|
+
* @returns {string} - Complexity level
|
|
290
|
+
*/
|
|
291
|
+
function scoreToLevel(score) {
|
|
292
|
+
if (score <= 2000) return 'small';
|
|
293
|
+
if (score <= 4000) return 'medium';
|
|
294
|
+
if (score <= 7000) return 'large';
|
|
295
|
+
return 'xl';
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Main function: Assess task complexity
|
|
300
|
+
*
|
|
301
|
+
* Returns guidance for Claude on how much context to include.
|
|
302
|
+
* The estimatedTokens is a MINIMUM for 90%+ success - Claude should
|
|
303
|
+
* include more if there's any doubt. Local LLM tokens are free!
|
|
304
|
+
*
|
|
305
|
+
* @param {Object} task - Task object with description, title, acceptanceCriteria, etc.
|
|
306
|
+
* @returns {Object} - Complexity assessment with guidance (not limits)
|
|
307
|
+
*/
|
|
308
|
+
function assessTaskComplexity(task) {
|
|
309
|
+
// Handle string input (just a description)
|
|
310
|
+
if (typeof task === 'string') {
|
|
311
|
+
task = { description: task };
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Combine all text sources for analysis
|
|
315
|
+
const textSources = [
|
|
316
|
+
task.title,
|
|
317
|
+
task.description,
|
|
318
|
+
task.summary,
|
|
319
|
+
Array.isArray(task.acceptanceCriteria) ? task.acceptanceCriteria.join(' ') : task.acceptanceCriteria,
|
|
320
|
+
task.technicalNotes,
|
|
321
|
+
task.notes
|
|
322
|
+
].filter(Boolean).join(' ');
|
|
323
|
+
|
|
324
|
+
// Gather factors
|
|
325
|
+
const factors = {
|
|
326
|
+
fileCount: countFileReferences(textSources),
|
|
327
|
+
keywords: detectComplexityKeywords(textSources),
|
|
328
|
+
hasTests: requiresTests(textSources),
|
|
329
|
+
acceptanceCriteriaCount: countAcceptanceCriteria(task),
|
|
330
|
+
hasDependencies: /depend|require|need|import from|install/i.test(textSources)
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
// Calculate score
|
|
334
|
+
const { score, breakdown } = calculateComplexityScore(factors);
|
|
335
|
+
|
|
336
|
+
// Determine level (guidance, not limit)
|
|
337
|
+
const level = scoreToLevel(score);
|
|
338
|
+
const complexityInfo = COMPLEXITY_LEVELS[level];
|
|
339
|
+
|
|
340
|
+
// Generate reasoning
|
|
341
|
+
const reasoning = generateReasoning(level, factors, breakdown);
|
|
342
|
+
|
|
343
|
+
// Estimated tokens is a MINIMUM - no upper limit, local LLM is free
|
|
344
|
+
// Add buffer for safety (better to include too much than too little)
|
|
345
|
+
const estimatedTokens = Math.max(score, complexityInfo.suggestedTokens);
|
|
346
|
+
|
|
347
|
+
return {
|
|
348
|
+
level,
|
|
349
|
+
estimatedTokens,
|
|
350
|
+
reasoning,
|
|
351
|
+
// What context the LLM needs for this complexity level
|
|
352
|
+
contextNeeds: complexityInfo.contextNeeds,
|
|
353
|
+
factors: {
|
|
354
|
+
fileCount: factors.fileCount.total,
|
|
355
|
+
filesToCreate: factors.fileCount.create,
|
|
356
|
+
filesToModify: factors.fileCount.modify,
|
|
357
|
+
hasTests: factors.hasTests,
|
|
358
|
+
hasDependencies: factors.hasDependencies,
|
|
359
|
+
acceptanceCriteriaCount: factors.acceptanceCriteriaCount,
|
|
360
|
+
complexityKeywords: [...factors.keywords.high, ...factors.keywords.medium],
|
|
361
|
+
simplicityKeywords: factors.keywords.simplicity
|
|
362
|
+
},
|
|
363
|
+
// Keep for backward compatibility
|
|
364
|
+
budget: TOKEN_BUDGETS[level],
|
|
365
|
+
scoreBreakdown: breakdown
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Generates human-readable reasoning
|
|
371
|
+
*/
|
|
372
|
+
function generateReasoning(level, factors, breakdown) {
|
|
373
|
+
const parts = [];
|
|
374
|
+
|
|
375
|
+
// File summary
|
|
376
|
+
if (factors.fileCount.create > 0 && factors.fileCount.modify > 0) {
|
|
377
|
+
parts.push(`Creating ${factors.fileCount.create} and modifying ${factors.fileCount.modify} files`);
|
|
378
|
+
} else if (factors.fileCount.create > 0) {
|
|
379
|
+
parts.push(`Creating ${factors.fileCount.create} new file(s)`);
|
|
380
|
+
} else if (factors.fileCount.modify > 0) {
|
|
381
|
+
parts.push(`Modifying ${factors.fileCount.modify} file(s)`);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Complexity keywords
|
|
385
|
+
if (factors.keywords.high.length > 0) {
|
|
386
|
+
parts.push(`High complexity: ${factors.keywords.high.slice(0, 3).join(', ')}`);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Tests
|
|
390
|
+
if (factors.hasTests) {
|
|
391
|
+
parts.push('Tests required');
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Dependencies
|
|
395
|
+
if (factors.hasDependencies) {
|
|
396
|
+
parts.push('Has dependencies');
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
return parts.length > 0 ? parts.join('. ') + '.' : TOKEN_BUDGETS[level].description;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Gets the suggested token minimum for a level
|
|
404
|
+
*/
|
|
405
|
+
function getDefaultTokens(level) {
|
|
406
|
+
return COMPLEXITY_LEVELS[level]?.suggestedTokens || COMPLEXITY_LEVELS.medium.suggestedTokens;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Ensures tokens meet minimum threshold
|
|
411
|
+
* Note: No upper limit - local LLM tokens are free!
|
|
412
|
+
*/
|
|
413
|
+
function clampTokens(tokens, minTokens = 1000) {
|
|
414
|
+
// Only enforce minimum - no maximum, local LLM is free
|
|
415
|
+
return Math.max(minTokens, tokens);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// ============================================================
|
|
419
|
+
// Exports
|
|
420
|
+
// ============================================================
|
|
421
|
+
|
|
422
|
+
module.exports = {
|
|
423
|
+
assessTaskComplexity,
|
|
424
|
+
// New exports
|
|
425
|
+
COMPLEXITY_LEVELS,
|
|
426
|
+
// Legacy exports (for backward compatibility)
|
|
427
|
+
TOKEN_BUDGETS,
|
|
428
|
+
COMPLEXITY_KEYWORDS,
|
|
429
|
+
SIMPLICITY_KEYWORDS,
|
|
430
|
+
TEST_KEYWORDS,
|
|
431
|
+
getDefaultTokens,
|
|
432
|
+
clampTokens,
|
|
433
|
+
// Expose internal functions for testing
|
|
434
|
+
countFileReferences,
|
|
435
|
+
detectComplexityKeywords,
|
|
436
|
+
requiresTests,
|
|
437
|
+
countAcceptanceCriteria,
|
|
438
|
+
calculateComplexityScore,
|
|
439
|
+
scoreToLevel
|
|
440
|
+
};
|
|
441
|
+
|
|
442
|
+
// ============================================================
|
|
443
|
+
// CLI for testing
|
|
444
|
+
// ============================================================
|
|
445
|
+
|
|
446
|
+
if (require.main === module) {
|
|
447
|
+
const args = process.argv.slice(2);
|
|
448
|
+
|
|
449
|
+
if (args.length === 0) {
|
|
450
|
+
console.log(`
|
|
451
|
+
Usage: node flow-complexity.js "<task description>"
|
|
452
|
+
|
|
453
|
+
Examples:
|
|
454
|
+
node flow-complexity.js "Add a console.log to the login function"
|
|
455
|
+
node flow-complexity.js "Create a new UserProfile component with tests"
|
|
456
|
+
node flow-complexity.js "Implement role-based access control for all API endpoints"
|
|
457
|
+
`);
|
|
458
|
+
process.exit(0);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
const taskDescription = args.join(' ');
|
|
462
|
+
const result = assessTaskComplexity(taskDescription);
|
|
463
|
+
|
|
464
|
+
console.log('\n═══════════════════════════════════════════════════════════');
|
|
465
|
+
console.log(' TASK COMPLEXITY ASSESSMENT (No Limits!)');
|
|
466
|
+
console.log('═══════════════════════════════════════════════════════════\n');
|
|
467
|
+
|
|
468
|
+
console.log(`Task: "${taskDescription}"\n`);
|
|
469
|
+
|
|
470
|
+
console.log(`Level: ${result.level.toUpperCase()}`);
|
|
471
|
+
console.log(`Minimum Tokens for 90%+ Success: ${result.estimatedTokens.toLocaleString()}`);
|
|
472
|
+
console.log(`Context Needs: ${result.contextNeeds}`);
|
|
473
|
+
console.log(`\nReasoning: ${result.reasoning}`);
|
|
474
|
+
|
|
475
|
+
console.log('\n───────────────────────────────────────────────────────────');
|
|
476
|
+
console.log(' FACTORS');
|
|
477
|
+
console.log('───────────────────────────────────────────────────────────\n');
|
|
478
|
+
|
|
479
|
+
console.log(`Files to create: ${result.factors.filesToCreate}`);
|
|
480
|
+
console.log(`Files to modify: ${result.factors.filesToModify}`);
|
|
481
|
+
console.log(`Has tests: ${result.factors.hasTests}`);
|
|
482
|
+
console.log(`Has dependencies: ${result.factors.hasDependencies}`);
|
|
483
|
+
console.log(`Acceptance criteria: ${result.factors.acceptanceCriteriaCount}`);
|
|
484
|
+
|
|
485
|
+
if (result.factors.complexityKeywords.length > 0) {
|
|
486
|
+
console.log(`Complexity keywords: ${result.factors.complexityKeywords.join(', ')}`);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
if (result.factors.simplicityKeywords.length > 0) {
|
|
490
|
+
console.log(`Simplicity keywords: ${result.factors.simplicityKeywords.join(', ')}`);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
console.log('\n───────────────────────────────────────────────────────────');
|
|
494
|
+
console.log(' SCORE BREAKDOWN');
|
|
495
|
+
console.log('───────────────────────────────────────────────────────────\n');
|
|
496
|
+
|
|
497
|
+
for (const item of result.scoreBreakdown) {
|
|
498
|
+
console.log(` ${item}`);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
console.log('\n═══════════════════════════════════════════════════════════\n');
|
|
502
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Wogi Flow - Config Set
|
|
5
|
+
*
|
|
6
|
+
* Sets a configuration value in config.json with proper cache invalidation.
|
|
7
|
+
* Replaces jq calls in the main flow script to ensure cache consistency.
|
|
8
|
+
*
|
|
9
|
+
* Usage: node flow-config-set.js <key.path> <value>
|
|
10
|
+
*
|
|
11
|
+
* Examples:
|
|
12
|
+
* node flow-config-set.js parallel.enabled true
|
|
13
|
+
* node flow-config-set.js hybrid.model "qwen3"
|
|
14
|
+
* node flow-config-set.js worktree.autoCleanupHours 48
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const {
|
|
18
|
+
PATHS,
|
|
19
|
+
readJson,
|
|
20
|
+
withLock,
|
|
21
|
+
invalidateConfigCache,
|
|
22
|
+
color,
|
|
23
|
+
success,
|
|
24
|
+
error
|
|
25
|
+
} = require('./flow-utils');
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Parse a value string to the appropriate type
|
|
29
|
+
*/
|
|
30
|
+
function parseValue(valueStr) {
|
|
31
|
+
// Boolean
|
|
32
|
+
if (valueStr === 'true') return true;
|
|
33
|
+
if (valueStr === 'false') return false;
|
|
34
|
+
|
|
35
|
+
// Null
|
|
36
|
+
if (valueStr === 'null') return null;
|
|
37
|
+
|
|
38
|
+
// Number
|
|
39
|
+
if (/^-?\d+(\.\d+)?$/.test(valueStr)) {
|
|
40
|
+
return parseFloat(valueStr);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// JSON object or array
|
|
44
|
+
if ((valueStr.startsWith('{') && valueStr.endsWith('}')) ||
|
|
45
|
+
(valueStr.startsWith('[') && valueStr.endsWith(']'))) {
|
|
46
|
+
try {
|
|
47
|
+
return JSON.parse(valueStr);
|
|
48
|
+
} catch {
|
|
49
|
+
// Not valid JSON, treat as string
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// String (remove quotes if present)
|
|
54
|
+
if ((valueStr.startsWith('"') && valueStr.endsWith('"')) ||
|
|
55
|
+
(valueStr.startsWith("'") && valueStr.endsWith("'"))) {
|
|
56
|
+
return valueStr.slice(1, -1);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return valueStr;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Set a nested value in an object using dot notation
|
|
64
|
+
*/
|
|
65
|
+
function setNestedValue(obj, path, value) {
|
|
66
|
+
const keys = path.split('.');
|
|
67
|
+
let current = obj;
|
|
68
|
+
|
|
69
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
70
|
+
const key = keys[i];
|
|
71
|
+
if (!(key in current) || typeof current[key] !== 'object') {
|
|
72
|
+
current[key] = {};
|
|
73
|
+
}
|
|
74
|
+
current = current[key];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const lastKey = keys[keys.length - 1];
|
|
78
|
+
current[lastKey] = value;
|
|
79
|
+
|
|
80
|
+
return obj;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Get a nested value from an object using dot notation
|
|
85
|
+
*/
|
|
86
|
+
function getNestedValue(obj, path) {
|
|
87
|
+
return path.split('.').reduce((o, k) => o?.[k], obj);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function main() {
|
|
91
|
+
const keyPath = process.argv[2];
|
|
92
|
+
const valueStr = process.argv[3];
|
|
93
|
+
|
|
94
|
+
if (!keyPath) {
|
|
95
|
+
console.log('Usage: flow-config-set <key.path> [value]');
|
|
96
|
+
console.log('');
|
|
97
|
+
console.log('Examples:');
|
|
98
|
+
console.log(' flow-config-set parallel.enabled true');
|
|
99
|
+
console.log(' flow-config-set hybrid.model "qwen3"');
|
|
100
|
+
console.log(' flow-config-set worktree.autoCleanupHours 48');
|
|
101
|
+
console.log('');
|
|
102
|
+
console.log('If no value is provided, prints the current value.');
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Load config (read-only, no lock needed)
|
|
107
|
+
const config = readJson(PATHS.config, {});
|
|
108
|
+
|
|
109
|
+
// If no value provided, just print current value
|
|
110
|
+
if (valueStr === undefined) {
|
|
111
|
+
const currentValue = getNestedValue(config, keyPath);
|
|
112
|
+
if (currentValue === undefined) {
|
|
113
|
+
console.log(color('yellow', `${keyPath} is not set`));
|
|
114
|
+
} else {
|
|
115
|
+
console.log(JSON.stringify(currentValue, null, 2));
|
|
116
|
+
}
|
|
117
|
+
process.exit(0);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Parse the value
|
|
121
|
+
const value = parseValue(valueStr);
|
|
122
|
+
const oldValue = getNestedValue(config, keyPath);
|
|
123
|
+
|
|
124
|
+
// Use file lock to prevent race conditions during write
|
|
125
|
+
try {
|
|
126
|
+
await withLock(PATHS.config, () => {
|
|
127
|
+
// Re-read config after acquiring lock (may have changed)
|
|
128
|
+
const freshConfig = readJson(PATHS.config, {});
|
|
129
|
+
setNestedValue(freshConfig, keyPath, value);
|
|
130
|
+
|
|
131
|
+
// Write config using fs directly (withLock handles the file)
|
|
132
|
+
const fs = require('fs');
|
|
133
|
+
fs.writeFileSync(PATHS.config, JSON.stringify(freshConfig, null, 2));
|
|
134
|
+
invalidateConfigCache();
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// Output result
|
|
138
|
+
if (oldValue === undefined) {
|
|
139
|
+
success(`Set ${keyPath} = ${JSON.stringify(value)}`);
|
|
140
|
+
} else {
|
|
141
|
+
success(`Changed ${keyPath}: ${JSON.stringify(oldValue)} → ${JSON.stringify(value)}`);
|
|
142
|
+
}
|
|
143
|
+
} catch (lockError) {
|
|
144
|
+
error(`Failed to update config: ${lockError.message}`);
|
|
145
|
+
process.exit(1);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
main().catch(err => {
|
|
150
|
+
error(`Unexpected error: ${err.message}`);
|
|
151
|
+
process.exit(1);
|
|
152
|
+
});
|