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,306 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Wogi Flow - Comment Analyzer Step
|
|
5
|
+
*
|
|
6
|
+
* Analyzes code comments for quality issues:
|
|
7
|
+
* - Stale or misleading comments
|
|
8
|
+
* - TODO/FIXME/HACK markers
|
|
9
|
+
* - JSDoc accuracy vs actual signatures
|
|
10
|
+
* - Commented-out code
|
|
11
|
+
* - Missing documentation for public APIs
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
const { getProjectRoot, colors } = require('./flow-utils');
|
|
17
|
+
|
|
18
|
+
const PROJECT_ROOT = getProjectRoot();
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Run comment analysis as a workflow step
|
|
22
|
+
*
|
|
23
|
+
* @param {object} options
|
|
24
|
+
* @param {string[]} options.files - Files modified
|
|
25
|
+
* @param {object} options.stepConfig - Step configuration
|
|
26
|
+
* @param {string} options.mode - Step mode (block/warn/prompt/auto)
|
|
27
|
+
* @returns {object} - { passed: boolean, message: string, details?: object[] }
|
|
28
|
+
*/
|
|
29
|
+
async function run(options = {}) {
|
|
30
|
+
const { files = [], stepConfig = {} } = options;
|
|
31
|
+
const flagTodo = stepConfig.flagTodo !== false;
|
|
32
|
+
const flagFixme = stepConfig.flagFixme !== false;
|
|
33
|
+
const checkJsdoc = stepConfig.checkJsdoc !== false;
|
|
34
|
+
const flagCommentedCode = stepConfig.flagCommentedCode !== false;
|
|
35
|
+
const flagStale = stepConfig.flagStale !== false;
|
|
36
|
+
|
|
37
|
+
// Filter to analyzable files
|
|
38
|
+
const analyzableExtensions = ['.js', '.ts', '.jsx', '.tsx'];
|
|
39
|
+
const analyzableFiles = files.filter(f =>
|
|
40
|
+
analyzableExtensions.some(ext => f.endsWith(ext)) &&
|
|
41
|
+
!f.includes('.d.ts')
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
if (analyzableFiles.length === 0) {
|
|
45
|
+
return { passed: true, message: 'No files to analyze' };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const issues = [];
|
|
49
|
+
|
|
50
|
+
for (const file of analyzableFiles) {
|
|
51
|
+
const filePath = path.join(PROJECT_ROOT, file);
|
|
52
|
+
if (!fs.existsSync(filePath)) continue;
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
56
|
+
const fileIssues = analyzeComments(content, file, {
|
|
57
|
+
flagTodo,
|
|
58
|
+
flagFixme,
|
|
59
|
+
checkJsdoc,
|
|
60
|
+
flagCommentedCode,
|
|
61
|
+
flagStale,
|
|
62
|
+
});
|
|
63
|
+
issues.push(...fileIssues);
|
|
64
|
+
} catch (err) {
|
|
65
|
+
// Skip unreadable files
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (issues.length === 0) {
|
|
70
|
+
return {
|
|
71
|
+
passed: true,
|
|
72
|
+
message: 'Comment analysis passed',
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Report issues
|
|
77
|
+
console.log(colors.yellow + '\n Comment Analysis Issues:' + colors.reset);
|
|
78
|
+
|
|
79
|
+
// Group by type
|
|
80
|
+
const grouped = {};
|
|
81
|
+
for (const issue of issues) {
|
|
82
|
+
if (!grouped[issue.type]) grouped[issue.type] = [];
|
|
83
|
+
grouped[issue.type].push(issue);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
for (const [type, typeIssues] of Object.entries(grouped)) {
|
|
87
|
+
console.log(colors.cyan + ` ${type}:` + colors.reset);
|
|
88
|
+
for (const issue of typeIssues.slice(0, 5)) { // Limit to 5 per type
|
|
89
|
+
console.log(` ${issue.file}:${issue.line}`);
|
|
90
|
+
console.log(` ${issue.message}`);
|
|
91
|
+
}
|
|
92
|
+
if (typeIssues.length > 5) {
|
|
93
|
+
console.log(colors.dim + ` ... and ${typeIssues.length - 5} more` + colors.reset);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const highSeverity = issues.filter(i => i.severity === 'high');
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
passed: highSeverity.length === 0,
|
|
101
|
+
message: `${issues.length} comment issue(s) found (${highSeverity.length} high severity)`,
|
|
102
|
+
details: issues,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Analyze code for comment quality issues
|
|
108
|
+
*/
|
|
109
|
+
function analyzeComments(content, fileName, config) {
|
|
110
|
+
const issues = [];
|
|
111
|
+
const lines = content.split('\n');
|
|
112
|
+
|
|
113
|
+
// Track JSDoc blocks for accuracy checking
|
|
114
|
+
let inJsDoc = false;
|
|
115
|
+
let jsDocContent = [];
|
|
116
|
+
let jsDocStartLine = 0;
|
|
117
|
+
|
|
118
|
+
for (let i = 0; i < lines.length; i++) {
|
|
119
|
+
const line = lines[i];
|
|
120
|
+
const trimmed = line.trim();
|
|
121
|
+
|
|
122
|
+
// Track JSDoc blocks
|
|
123
|
+
if (trimmed.startsWith('/**')) {
|
|
124
|
+
inJsDoc = true;
|
|
125
|
+
jsDocContent = [trimmed];
|
|
126
|
+
jsDocStartLine = i + 1;
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (inJsDoc) {
|
|
131
|
+
jsDocContent.push(trimmed);
|
|
132
|
+
if (trimmed.includes('*/')) {
|
|
133
|
+
inJsDoc = false;
|
|
134
|
+
// Check JSDoc accuracy if the next line is a function
|
|
135
|
+
if (config.checkJsdoc && i + 1 < lines.length) {
|
|
136
|
+
const nextLine = lines[i + 1];
|
|
137
|
+
const jsDocIssues = checkJsDocAccuracy(jsDocContent, nextLine, jsDocStartLine, fileName);
|
|
138
|
+
issues.push(...jsDocIssues);
|
|
139
|
+
}
|
|
140
|
+
jsDocContent = [];
|
|
141
|
+
}
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Check for TODO/FIXME/HACK markers
|
|
146
|
+
if (config.flagTodo || config.flagFixme) {
|
|
147
|
+
const todoMatch = trimmed.match(/\b(TODO|FIXME|XXX|HACK|BUG|UNDONE)\b:?\s*(.*)/i);
|
|
148
|
+
if (todoMatch) {
|
|
149
|
+
const marker = todoMatch[1].toUpperCase();
|
|
150
|
+
const isTodo = marker === 'TODO';
|
|
151
|
+
const isFixme = ['FIXME', 'XXX', 'BUG', 'HACK', 'UNDONE'].includes(marker);
|
|
152
|
+
|
|
153
|
+
if ((isTodo && config.flagTodo) || (isFixme && config.flagFixme)) {
|
|
154
|
+
issues.push({
|
|
155
|
+
file: fileName,
|
|
156
|
+
line: i + 1,
|
|
157
|
+
type: marker,
|
|
158
|
+
severity: isFixme ? 'high' : 'medium',
|
|
159
|
+
message: todoMatch[2] || `Unresolved ${marker} marker`,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Check for commented-out code
|
|
166
|
+
if (config.flagCommentedCode) {
|
|
167
|
+
// Single line comment that looks like code
|
|
168
|
+
if (trimmed.startsWith('//') && !trimmed.startsWith('///')) {
|
|
169
|
+
const commentContent = trimmed.substring(2).trim();
|
|
170
|
+
if (looksLikeCode(commentContent)) {
|
|
171
|
+
issues.push({
|
|
172
|
+
file: fileName,
|
|
173
|
+
line: i + 1,
|
|
174
|
+
type: 'Commented Code',
|
|
175
|
+
severity: 'medium',
|
|
176
|
+
message: 'Commented-out code should be removed',
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Check for potentially stale comments
|
|
183
|
+
if (config.flagStale) {
|
|
184
|
+
// Comment followed by code that contradicts it
|
|
185
|
+
if (trimmed.startsWith('//') && i + 1 < lines.length) {
|
|
186
|
+
const comment = trimmed.substring(2).trim().toLowerCase();
|
|
187
|
+
const nextLine = lines[i + 1].trim().toLowerCase();
|
|
188
|
+
|
|
189
|
+
const stalePatterns = [
|
|
190
|
+
{ comment: /always returns?|never fails?/, code: /throw|error|null|undefined/ },
|
|
191
|
+
{ comment: /deprecated|don't use|do not use/, code: /^\s*(?:export\s+)?(?:function|const|class)/ },
|
|
192
|
+
{ comment: /temporary|temp\s|remove later/, code: /.+/ }, // Any code after "temporary"
|
|
193
|
+
];
|
|
194
|
+
|
|
195
|
+
for (const pattern of stalePatterns) {
|
|
196
|
+
if (pattern.comment.test(comment) && pattern.code.test(nextLine)) {
|
|
197
|
+
issues.push({
|
|
198
|
+
file: fileName,
|
|
199
|
+
line: i + 1,
|
|
200
|
+
type: 'Potentially Stale Comment',
|
|
201
|
+
severity: 'medium',
|
|
202
|
+
message: 'Comment may not match the code below it',
|
|
203
|
+
});
|
|
204
|
+
break;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return issues;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Check if a comment looks like commented-out code
|
|
216
|
+
*/
|
|
217
|
+
function looksLikeCode(text) {
|
|
218
|
+
// Skip very short comments
|
|
219
|
+
if (text.length < 10) return false;
|
|
220
|
+
|
|
221
|
+
// Skip obvious prose
|
|
222
|
+
if (/^[A-Z][a-z]+\s+[a-z]+/.test(text)) return false; // Starts like a sentence
|
|
223
|
+
|
|
224
|
+
// Code patterns
|
|
225
|
+
const codePatterns = [
|
|
226
|
+
/^\s*(?:const|let|var|function|class|if|for|while|return|import|export)\s/,
|
|
227
|
+
/^\s*\w+\s*[=:]\s*[^=]/, // Assignment
|
|
228
|
+
/^\s*\w+\s*\([^)]*\)\s*[;{]?$/, // Function call
|
|
229
|
+
/^\s*\}\s*(?:else|catch|finally)?\s*\{?\s*$/, // Braces
|
|
230
|
+
/^\s*(?:await|async)\s+/, // Async
|
|
231
|
+
/^\s*\w+\.\w+\(/, // Method call
|
|
232
|
+
/^\s*\/\*|\*\/\s*$/, // Multi-line comment markers
|
|
233
|
+
];
|
|
234
|
+
|
|
235
|
+
return codePatterns.some(pattern => pattern.test(text));
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Check JSDoc accuracy against function signature
|
|
240
|
+
*/
|
|
241
|
+
function checkJsDocAccuracy(jsDocLines, functionLine, startLine, fileName) {
|
|
242
|
+
const issues = [];
|
|
243
|
+
const jsDoc = jsDocLines.join('\n');
|
|
244
|
+
|
|
245
|
+
// Extract @param and @returns from JSDoc
|
|
246
|
+
const jsDocParams = [];
|
|
247
|
+
const paramMatches = jsDoc.matchAll(/@param\s+(?:\{[^}]+\}\s+)?(\w+)/g);
|
|
248
|
+
for (const match of paramMatches) {
|
|
249
|
+
jsDocParams.push(match[1]);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const hasReturns = /@returns?/.test(jsDoc);
|
|
253
|
+
|
|
254
|
+
// Parse function signature
|
|
255
|
+
const funcMatch = functionLine.match(/(?:async\s+)?(?:function\s+)?(\w+)?\s*\(([^)]*)\)/);
|
|
256
|
+
if (!funcMatch) return issues;
|
|
257
|
+
|
|
258
|
+
const actualParams = funcMatch[2]
|
|
259
|
+
.split(',')
|
|
260
|
+
.map(p => p.trim().replace(/[:=].*/, '').replace(/^\.\.\./, '').trim())
|
|
261
|
+
.filter(p => p.length > 0);
|
|
262
|
+
|
|
263
|
+
// Check for missing params in JSDoc
|
|
264
|
+
for (const param of actualParams) {
|
|
265
|
+
if (!jsDocParams.includes(param)) {
|
|
266
|
+
issues.push({
|
|
267
|
+
file: fileName,
|
|
268
|
+
line: startLine,
|
|
269
|
+
type: 'Missing @param',
|
|
270
|
+
severity: 'medium',
|
|
271
|
+
message: `JSDoc missing @param for "${param}"`,
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Check for extra params in JSDoc
|
|
277
|
+
for (const param of jsDocParams) {
|
|
278
|
+
if (!actualParams.includes(param)) {
|
|
279
|
+
issues.push({
|
|
280
|
+
file: fileName,
|
|
281
|
+
line: startLine,
|
|
282
|
+
type: 'Extra @param',
|
|
283
|
+
severity: 'medium',
|
|
284
|
+
message: `JSDoc has @param "${param}" but function doesn't have this parameter`,
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Check for returns mismatch
|
|
290
|
+
const hasReturnStatement = /\breturn\s+[^;]/.test(functionLine) ||
|
|
291
|
+
(functionLine.includes('=>') && !functionLine.includes('=> {'));
|
|
292
|
+
|
|
293
|
+
if (hasReturnStatement && !hasReturns) {
|
|
294
|
+
issues.push({
|
|
295
|
+
file: fileName,
|
|
296
|
+
line: startLine,
|
|
297
|
+
type: 'Missing @returns',
|
|
298
|
+
severity: 'low',
|
|
299
|
+
message: 'Function returns a value but JSDoc has no @returns',
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return issues;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
module.exports = { run, analyzeComments };
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Wogi Flow - Code Complexity Check Step
|
|
5
|
+
*
|
|
6
|
+
* Analyzes cyclomatic complexity of modified files.
|
|
7
|
+
* Flags functions that exceed the configured threshold.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const { getProjectRoot, colors } = require('./flow-utils');
|
|
13
|
+
|
|
14
|
+
const PROJECT_ROOT = getProjectRoot();
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Run code complexity check step
|
|
18
|
+
*
|
|
19
|
+
* @param {object} options
|
|
20
|
+
* @param {string[]} options.files - Files modified
|
|
21
|
+
* @param {object} options.stepConfig - Step configuration
|
|
22
|
+
* @param {string} options.mode - Step mode (block/warn/prompt/auto)
|
|
23
|
+
* @returns {object} - { passed: boolean, message: string, details?: object[] }
|
|
24
|
+
*/
|
|
25
|
+
async function run(options = {}) {
|
|
26
|
+
const { files = [], stepConfig = {}, mode } = options;
|
|
27
|
+
const threshold = stepConfig.threshold || 10;
|
|
28
|
+
|
|
29
|
+
// Filter to analyzable files
|
|
30
|
+
const analyzableExtensions = ['.js', '.ts', '.jsx', '.tsx'];
|
|
31
|
+
const analyzableFiles = files.filter(f =>
|
|
32
|
+
analyzableExtensions.some(ext => f.endsWith(ext)) &&
|
|
33
|
+
!f.includes('.test.') &&
|
|
34
|
+
!f.includes('.spec.') &&
|
|
35
|
+
!f.includes('.d.ts')
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
if (analyzableFiles.length === 0) {
|
|
39
|
+
return { passed: true, message: 'No analyzable files modified' };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const complexFunctions = [];
|
|
43
|
+
|
|
44
|
+
for (const file of analyzableFiles) {
|
|
45
|
+
const filePath = path.join(PROJECT_ROOT, file);
|
|
46
|
+
if (!fs.existsSync(filePath)) continue;
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
50
|
+
const functions = analyzeComplexity(content, file);
|
|
51
|
+
|
|
52
|
+
for (const func of functions) {
|
|
53
|
+
if (func.complexity > threshold) {
|
|
54
|
+
complexFunctions.push({
|
|
55
|
+
file,
|
|
56
|
+
function: func.name,
|
|
57
|
+
complexity: func.complexity,
|
|
58
|
+
line: func.line,
|
|
59
|
+
suggestion: getSuggestion(func.complexity, threshold),
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
} catch (err) {
|
|
64
|
+
// Skip files that can't be analyzed
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (complexFunctions.length === 0) {
|
|
69
|
+
return {
|
|
70
|
+
passed: true,
|
|
71
|
+
message: `All functions under complexity threshold (${threshold})`,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Report complex functions
|
|
76
|
+
console.log(colors.yellow + `\n Functions exceeding complexity threshold (${threshold}):` + colors.reset);
|
|
77
|
+
for (const func of complexFunctions) {
|
|
78
|
+
console.log(` ${func.file}:${func.line} - ${func.function} (${func.complexity})`);
|
|
79
|
+
console.log(colors.gray + ` ${func.suggestion}` + colors.reset);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
passed: false,
|
|
84
|
+
message: `${complexFunctions.length} function(s) exceed complexity threshold`,
|
|
85
|
+
details: complexFunctions,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Analyze cyclomatic complexity of functions in code
|
|
91
|
+
*
|
|
92
|
+
* Uses a simplified heuristic approach:
|
|
93
|
+
* - Base complexity: 1
|
|
94
|
+
* - +1 for each: if, else if, for, while, case, catch, &&, ||, ?, ??
|
|
95
|
+
*/
|
|
96
|
+
function analyzeComplexity(content, fileName) {
|
|
97
|
+
const functions = [];
|
|
98
|
+
|
|
99
|
+
// Match function declarations and expressions
|
|
100
|
+
const functionPatterns = [
|
|
101
|
+
// function name() {}
|
|
102
|
+
/function\s+(\w+)\s*\([^)]*\)\s*\{/g,
|
|
103
|
+
// const name = function() {}
|
|
104
|
+
/(?:const|let|var)\s+(\w+)\s*=\s*function\s*\([^)]*\)\s*\{/g,
|
|
105
|
+
// const name = () => {}
|
|
106
|
+
/(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s*)?\([^)]*\)\s*=>/g,
|
|
107
|
+
// const name = async () => {}
|
|
108
|
+
/(?:const|let|var)\s+(\w+)\s*=\s*async\s+\([^)]*\)\s*=>/g,
|
|
109
|
+
// method() {} in class
|
|
110
|
+
/^\s*(?:async\s+)?(\w+)\s*\([^)]*\)\s*\{/gm,
|
|
111
|
+
];
|
|
112
|
+
|
|
113
|
+
// Split content into lines for line number tracking
|
|
114
|
+
const lines = content.split('\n');
|
|
115
|
+
|
|
116
|
+
// Simple approach: find function boundaries and count complexity indicators
|
|
117
|
+
let currentFunction = null;
|
|
118
|
+
let braceCount = 0;
|
|
119
|
+
let functionStart = 0;
|
|
120
|
+
let functionContent = '';
|
|
121
|
+
|
|
122
|
+
for (let i = 0; i < lines.length; i++) {
|
|
123
|
+
const line = lines[i];
|
|
124
|
+
|
|
125
|
+
// Check for function start
|
|
126
|
+
if (!currentFunction) {
|
|
127
|
+
for (const pattern of functionPatterns) {
|
|
128
|
+
pattern.lastIndex = 0;
|
|
129
|
+
const match = pattern.exec(line);
|
|
130
|
+
if (match) {
|
|
131
|
+
currentFunction = match[1] || 'anonymous';
|
|
132
|
+
functionStart = i + 1;
|
|
133
|
+
braceCount = (line.match(/\{/g) || []).length - (line.match(/\}/g) || []).length;
|
|
134
|
+
functionContent = line;
|
|
135
|
+
|
|
136
|
+
// Arrow functions without braces
|
|
137
|
+
if (line.includes('=>') && !line.includes('{')) {
|
|
138
|
+
const complexity = calculateLineComplexity(line);
|
|
139
|
+
functions.push({
|
|
140
|
+
name: currentFunction,
|
|
141
|
+
line: functionStart,
|
|
142
|
+
complexity: 1 + complexity,
|
|
143
|
+
});
|
|
144
|
+
currentFunction = null;
|
|
145
|
+
}
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
} else {
|
|
150
|
+
// Track braces
|
|
151
|
+
braceCount += (line.match(/\{/g) || []).length - (line.match(/\}/g) || []).length;
|
|
152
|
+
functionContent += '\n' + line;
|
|
153
|
+
|
|
154
|
+
// Function ended
|
|
155
|
+
if (braceCount <= 0) {
|
|
156
|
+
const complexity = calculateComplexity(functionContent);
|
|
157
|
+
functions.push({
|
|
158
|
+
name: currentFunction,
|
|
159
|
+
line: functionStart,
|
|
160
|
+
complexity,
|
|
161
|
+
});
|
|
162
|
+
currentFunction = null;
|
|
163
|
+
functionContent = '';
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return functions;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Calculate cyclomatic complexity of a code block
|
|
173
|
+
*/
|
|
174
|
+
function calculateComplexity(code) {
|
|
175
|
+
let complexity = 1; // Base complexity
|
|
176
|
+
|
|
177
|
+
// Decision points
|
|
178
|
+
const patterns = [
|
|
179
|
+
/\bif\s*\(/g, // if statements
|
|
180
|
+
/\belse\s+if\s*\(/g, // else if (already counted in if, subtract)
|
|
181
|
+
/\bfor\s*\(/g, // for loops
|
|
182
|
+
/\bwhile\s*\(/g, // while loops
|
|
183
|
+
/\bcase\s+[^:]+:/g, // switch cases
|
|
184
|
+
/\bcatch\s*\(/g, // catch blocks
|
|
185
|
+
/\?\s*[^:]+:/g, // ternary operators
|
|
186
|
+
/&&/g, // logical AND
|
|
187
|
+
/\|\|/g, // logical OR
|
|
188
|
+
/\?\?/g, // nullish coalescing
|
|
189
|
+
];
|
|
190
|
+
|
|
191
|
+
for (const pattern of patterns) {
|
|
192
|
+
const matches = code.match(pattern);
|
|
193
|
+
if (matches) {
|
|
194
|
+
complexity += matches.length;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Subtract double-counted else if
|
|
199
|
+
const elseIfMatches = code.match(/\belse\s+if\s*\(/g);
|
|
200
|
+
if (elseIfMatches) {
|
|
201
|
+
complexity -= elseIfMatches.length;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return complexity;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Calculate complexity for a single line (arrow functions)
|
|
209
|
+
*/
|
|
210
|
+
function calculateLineComplexity(line) {
|
|
211
|
+
let complexity = 0;
|
|
212
|
+
|
|
213
|
+
if (line.includes('?') && line.includes(':')) complexity += 1;
|
|
214
|
+
complexity += (line.match(/&&/g) || []).length;
|
|
215
|
+
complexity += (line.match(/\|\|/g) || []).length;
|
|
216
|
+
complexity += (line.match(/\?\?/g) || []).length;
|
|
217
|
+
|
|
218
|
+
return complexity;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Get suggestion for reducing complexity
|
|
223
|
+
*/
|
|
224
|
+
function getSuggestion(complexity, threshold) {
|
|
225
|
+
if (complexity > threshold * 2) {
|
|
226
|
+
return 'Consider breaking into multiple smaller functions';
|
|
227
|
+
}
|
|
228
|
+
if (complexity > threshold * 1.5) {
|
|
229
|
+
return 'Consider extracting some logic into helper functions';
|
|
230
|
+
}
|
|
231
|
+
return 'Consider simplifying conditional logic';
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
module.exports = { run, analyzeComplexity, calculateComplexity };
|