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,760 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Wogi Flow - Code Intelligence (Priority 5: Better Code Understanding)
|
|
5
|
+
*
|
|
6
|
+
* Enhanced code analysis with:
|
|
7
|
+
* - Import/export relationship mapping
|
|
8
|
+
* - Type dependencies
|
|
9
|
+
* - Function call graphs
|
|
10
|
+
* - Semantic code search
|
|
11
|
+
*
|
|
12
|
+
* Uses semantic code analysis for better understanding.
|
|
13
|
+
*
|
|
14
|
+
* Usage:
|
|
15
|
+
* const { analyzeRelationships, findRelatedCode } = require('./flow-code-intelligence');
|
|
16
|
+
* const relationships = await analyzeRelationships('src/components/Button.tsx');
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const fs = require('fs');
|
|
20
|
+
const path = require('path');
|
|
21
|
+
const { getProjectRoot, getConfig, PATHS, colors } = require('./flow-utils');
|
|
22
|
+
const { safeGrep, safeFind, escapeRegex } = require('./flow-security');
|
|
23
|
+
|
|
24
|
+
const PROJECT_ROOT = getProjectRoot();
|
|
25
|
+
|
|
26
|
+
// ============================================================
|
|
27
|
+
// Relationship Analysis
|
|
28
|
+
// ============================================================
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Analyze import/export relationships for a file
|
|
32
|
+
*
|
|
33
|
+
* @param {string} filePath - Path to the file to analyze
|
|
34
|
+
* @returns {object} Relationships object
|
|
35
|
+
*/
|
|
36
|
+
function analyzeRelationships(filePath) {
|
|
37
|
+
const fullPath = path.isAbsolute(filePath)
|
|
38
|
+
? filePath
|
|
39
|
+
: path.join(PROJECT_ROOT, filePath);
|
|
40
|
+
|
|
41
|
+
if (!fs.existsSync(fullPath)) {
|
|
42
|
+
return { error: 'File not found' };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
46
|
+
const relPath = path.relative(PROJECT_ROOT, fullPath);
|
|
47
|
+
|
|
48
|
+
const relationships = {
|
|
49
|
+
file: relPath,
|
|
50
|
+
analyzedAt: new Date().toISOString(),
|
|
51
|
+
imports: extractImports(content, path.dirname(fullPath)),
|
|
52
|
+
exports: extractExports(content),
|
|
53
|
+
dependencies: {
|
|
54
|
+
internal: [],
|
|
55
|
+
external: []
|
|
56
|
+
},
|
|
57
|
+
dependents: [],
|
|
58
|
+
types: extractTypeUsage(content),
|
|
59
|
+
functions: extractFunctions(content)
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// Categorize imports
|
|
63
|
+
for (const imp of relationships.imports) {
|
|
64
|
+
if (imp.source.startsWith('.') || imp.source.startsWith('@/')) {
|
|
65
|
+
relationships.dependencies.internal.push(imp);
|
|
66
|
+
} else {
|
|
67
|
+
relationships.dependencies.external.push(imp);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return relationships;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Extract imports from file content
|
|
76
|
+
*/
|
|
77
|
+
function extractImports(content, fileDir) {
|
|
78
|
+
const imports = [];
|
|
79
|
+
|
|
80
|
+
// ES6 imports
|
|
81
|
+
const importRegex = /import\s+(?:(\{[^}]+\})|(\w+)(?:\s*,\s*\{([^}]+)\})?)\s+from\s+['"]([^'"]+)['"]/g;
|
|
82
|
+
let match;
|
|
83
|
+
|
|
84
|
+
while ((match = importRegex.exec(content)) !== null) {
|
|
85
|
+
const namedImports = match[1] || match[3];
|
|
86
|
+
const defaultImport = match[2];
|
|
87
|
+
const source = match[4];
|
|
88
|
+
|
|
89
|
+
const imp = {
|
|
90
|
+
source,
|
|
91
|
+
default: defaultImport || null,
|
|
92
|
+
named: []
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
if (namedImports) {
|
|
96
|
+
imp.named = namedImports
|
|
97
|
+
.replace(/[{}]/g, '')
|
|
98
|
+
.split(',')
|
|
99
|
+
.map(s => s.trim().split(' as ')[0].trim())
|
|
100
|
+
.filter(Boolean);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
imports.push(imp);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Side-effect imports
|
|
107
|
+
const sideEffectRegex = /import\s+['"]([^'"]+)['"]/g;
|
|
108
|
+
while ((match = sideEffectRegex.exec(content)) !== null) {
|
|
109
|
+
imports.push({
|
|
110
|
+
source: match[1],
|
|
111
|
+
sideEffect: true
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Dynamic imports
|
|
116
|
+
const dynamicRegex = /import\(['"]([^'"]+)['"]\)/g;
|
|
117
|
+
while ((match = dynamicRegex.exec(content)) !== null) {
|
|
118
|
+
imports.push({
|
|
119
|
+
source: match[1],
|
|
120
|
+
dynamic: true
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return imports;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Extract exports from file content
|
|
129
|
+
*/
|
|
130
|
+
function extractExports(content) {
|
|
131
|
+
const exports = {
|
|
132
|
+
default: null,
|
|
133
|
+
named: [],
|
|
134
|
+
types: []
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
// Default export
|
|
138
|
+
const defaultMatch = content.match(/export\s+default\s+(?:function\s+)?(\w+)/);
|
|
139
|
+
if (defaultMatch) {
|
|
140
|
+
exports.default = defaultMatch[1];
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Named exports
|
|
144
|
+
const namedRegex = /export\s+(?:const|let|var|function|class)\s+(\w+)/g;
|
|
145
|
+
let match;
|
|
146
|
+
while ((match = namedRegex.exec(content)) !== null) {
|
|
147
|
+
if (!exports.named.includes(match[1])) {
|
|
148
|
+
exports.named.push(match[1]);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Export block
|
|
153
|
+
const blockRegex = /export\s+\{([^}]+)\}/g;
|
|
154
|
+
while ((match = blockRegex.exec(content)) !== null) {
|
|
155
|
+
const items = match[1].split(',').map(s => s.trim().split(' as ')[0].trim());
|
|
156
|
+
for (const item of items) {
|
|
157
|
+
if (item && !exports.named.includes(item)) {
|
|
158
|
+
exports.named.push(item);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Type exports
|
|
164
|
+
const typeRegex = /export\s+(?:type|interface)\s+(\w+)/g;
|
|
165
|
+
while ((match = typeRegex.exec(content)) !== null) {
|
|
166
|
+
exports.types.push(match[1]);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return exports;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Extract type usage from file content
|
|
174
|
+
*/
|
|
175
|
+
function extractTypeUsage(content) {
|
|
176
|
+
const types = {
|
|
177
|
+
interfaces: [],
|
|
178
|
+
types: [],
|
|
179
|
+
generics: []
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
// Interface definitions
|
|
183
|
+
const interfaceRegex = /interface\s+(\w+)(?:<([^>]+)>)?/g;
|
|
184
|
+
let match;
|
|
185
|
+
while ((match = interfaceRegex.exec(content)) !== null) {
|
|
186
|
+
types.interfaces.push({
|
|
187
|
+
name: match[1],
|
|
188
|
+
generics: match[2] ? match[2].split(',').map(s => s.trim()) : []
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Type definitions
|
|
193
|
+
const typeRegex = /type\s+(\w+)(?:<([^>]+)>)?\s*=/g;
|
|
194
|
+
while ((match = typeRegex.exec(content)) !== null) {
|
|
195
|
+
types.types.push({
|
|
196
|
+
name: match[1],
|
|
197
|
+
generics: match[2] ? match[2].split(',').map(s => s.trim()) : []
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Generic usage
|
|
202
|
+
const genericRegex = /:\s*(\w+)<([^>]+)>/g;
|
|
203
|
+
while ((match = genericRegex.exec(content)) !== null) {
|
|
204
|
+
types.generics.push({
|
|
205
|
+
type: match[1],
|
|
206
|
+
params: match[2].split(',').map(s => s.trim())
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return types;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Extract function definitions
|
|
215
|
+
*/
|
|
216
|
+
function extractFunctions(content) {
|
|
217
|
+
const functions = [];
|
|
218
|
+
|
|
219
|
+
// Regular functions
|
|
220
|
+
const funcRegex = /(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\(([^)]*)\)/g;
|
|
221
|
+
let match;
|
|
222
|
+
while ((match = funcRegex.exec(content)) !== null) {
|
|
223
|
+
functions.push({
|
|
224
|
+
name: match[1],
|
|
225
|
+
params: match[2].split(',').map(s => s.trim().split(':')[0].trim()).filter(Boolean),
|
|
226
|
+
async: content.slice(match.index - 10, match.index).includes('async')
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Arrow functions (const/let)
|
|
231
|
+
const arrowRegex = /(?:export\s+)?(?:const|let)\s+(\w+)\s*=\s*(?:async\s+)?\([^)]*\)\s*(?::\s*[^=]+)?\s*=>/g;
|
|
232
|
+
while ((match = arrowRegex.exec(content)) !== null) {
|
|
233
|
+
functions.push({
|
|
234
|
+
name: match[1],
|
|
235
|
+
type: 'arrow',
|
|
236
|
+
async: content.slice(match.index, match.index + 100).includes('async')
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return functions.slice(0, 20); // Limit to 20
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// ============================================================
|
|
244
|
+
// Dependency Graph
|
|
245
|
+
// ============================================================
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Build dependency graph for a set of files
|
|
249
|
+
*
|
|
250
|
+
* @param {string[]} filePaths - Files to analyze
|
|
251
|
+
* @returns {object} Dependency graph
|
|
252
|
+
*/
|
|
253
|
+
function buildDependencyGraph(filePaths) {
|
|
254
|
+
const graph = {
|
|
255
|
+
nodes: [],
|
|
256
|
+
edges: [],
|
|
257
|
+
clusters: {}
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
const nodeMap = new Map();
|
|
261
|
+
|
|
262
|
+
for (const filePath of filePaths) {
|
|
263
|
+
const relationships = analyzeRelationships(filePath);
|
|
264
|
+
if (relationships.error) continue;
|
|
265
|
+
|
|
266
|
+
const nodeId = relationships.file;
|
|
267
|
+
|
|
268
|
+
// Add node
|
|
269
|
+
if (!nodeMap.has(nodeId)) {
|
|
270
|
+
nodeMap.set(nodeId, {
|
|
271
|
+
id: nodeId,
|
|
272
|
+
exports: relationships.exports,
|
|
273
|
+
category: categorizeFile(nodeId)
|
|
274
|
+
});
|
|
275
|
+
graph.nodes.push(nodeMap.get(nodeId));
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Add edges for internal dependencies
|
|
279
|
+
for (const dep of relationships.dependencies.internal) {
|
|
280
|
+
const resolvedPath = resolveImportPath(dep.source, path.dirname(filePath));
|
|
281
|
+
if (resolvedPath) {
|
|
282
|
+
graph.edges.push({
|
|
283
|
+
from: nodeId,
|
|
284
|
+
to: resolvedPath,
|
|
285
|
+
type: 'import',
|
|
286
|
+
imports: [...(dep.named || []), dep.default].filter(Boolean)
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Group into clusters
|
|
293
|
+
for (const node of graph.nodes) {
|
|
294
|
+
const category = node.category;
|
|
295
|
+
if (!graph.clusters[category]) {
|
|
296
|
+
graph.clusters[category] = [];
|
|
297
|
+
}
|
|
298
|
+
graph.clusters[category].push(node.id);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return graph;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Categorize file based on path
|
|
306
|
+
*/
|
|
307
|
+
function categorizeFile(filePath) {
|
|
308
|
+
const lower = filePath.toLowerCase();
|
|
309
|
+
|
|
310
|
+
if (lower.includes('/components/')) return 'components';
|
|
311
|
+
if (lower.includes('/hooks/') || lower.match(/\/use\w+\./)) return 'hooks';
|
|
312
|
+
if (lower.includes('/services/')) return 'services';
|
|
313
|
+
if (lower.includes('/pages/') || lower.includes('/app/')) return 'pages';
|
|
314
|
+
if (lower.includes('/api/')) return 'api';
|
|
315
|
+
if (lower.includes('/utils/') || lower.includes('/lib/')) return 'utils';
|
|
316
|
+
if (lower.includes('/types/')) return 'types';
|
|
317
|
+
|
|
318
|
+
return 'other';
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Resolve import path to actual file
|
|
323
|
+
*/
|
|
324
|
+
function resolveImportPath(importSource, fromDir) {
|
|
325
|
+
if (!importSource.startsWith('.')) {
|
|
326
|
+
// Handle alias imports like @/
|
|
327
|
+
if (importSource.startsWith('@/')) {
|
|
328
|
+
importSource = importSource.replace('@/', 'src/');
|
|
329
|
+
} else {
|
|
330
|
+
return null; // External package
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const extensions = ['.ts', '.tsx', '.js', '.jsx', '/index.ts', '/index.tsx', '/index.js'];
|
|
335
|
+
|
|
336
|
+
for (const ext of extensions) {
|
|
337
|
+
const candidate = path.join(fromDir, importSource + ext);
|
|
338
|
+
const relPath = path.relative(PROJECT_ROOT, candidate);
|
|
339
|
+
|
|
340
|
+
if (fs.existsSync(path.join(PROJECT_ROOT, relPath))) {
|
|
341
|
+
return relPath;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return null;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// ============================================================
|
|
349
|
+
// Related Code Search
|
|
350
|
+
// ============================================================
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Find code related to a given file or function
|
|
354
|
+
*
|
|
355
|
+
* @param {string} query - File path, function name, or keyword
|
|
356
|
+
* @param {object} options - Search options
|
|
357
|
+
*/
|
|
358
|
+
async function findRelatedCode(query, options = {}) {
|
|
359
|
+
const results = {
|
|
360
|
+
query,
|
|
361
|
+
timestamp: new Date().toISOString(),
|
|
362
|
+
directDependencies: [],
|
|
363
|
+
reverseDependencies: [],
|
|
364
|
+
similarFiles: [],
|
|
365
|
+
relatedFunctions: []
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
// If query is a file path, analyze its relationships
|
|
369
|
+
if (query.includes('/') || query.includes('.')) {
|
|
370
|
+
const relationships = analyzeRelationships(query);
|
|
371
|
+
if (!relationships.error) {
|
|
372
|
+
results.directDependencies = relationships.dependencies.internal
|
|
373
|
+
.map(d => d.source)
|
|
374
|
+
.slice(0, 10);
|
|
375
|
+
|
|
376
|
+
// Find files that import this file
|
|
377
|
+
results.reverseDependencies = await findFilesImporting(query);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Search for keyword in codebase
|
|
382
|
+
results.similarFiles = await searchCodebase(query, options.maxResults || 10);
|
|
383
|
+
|
|
384
|
+
return results;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Find files that import a given file
|
|
389
|
+
*/
|
|
390
|
+
async function findFilesImporting(filePath) {
|
|
391
|
+
const basename = path.basename(filePath, path.extname(filePath));
|
|
392
|
+
|
|
393
|
+
// Use safe grep with escaped pattern to prevent injection
|
|
394
|
+
const pattern = `from.*${escapeRegex(basename)}`;
|
|
395
|
+
return safeGrep(pattern, {
|
|
396
|
+
cwd: PROJECT_ROOT,
|
|
397
|
+
searchDir: 'src/',
|
|
398
|
+
extensions: ['.ts', '.tsx', '.js', '.jsx'],
|
|
399
|
+
maxResults: 20
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Search codebase for keyword
|
|
405
|
+
*/
|
|
406
|
+
async function searchCodebase(keyword, maxResults = 10) {
|
|
407
|
+
// Use safe grep with escaped pattern to prevent injection
|
|
408
|
+
return safeGrep(keyword, {
|
|
409
|
+
cwd: PROJECT_ROOT,
|
|
410
|
+
searchDir: 'src/',
|
|
411
|
+
extensions: ['.ts', '.tsx', '.js', '.jsx'],
|
|
412
|
+
maxResults
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// ============================================================
|
|
417
|
+
// Enhanced Component Index
|
|
418
|
+
// ============================================================
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Generate enhanced component index with relationships
|
|
422
|
+
*/
|
|
423
|
+
async function generateEnhancedIndex() {
|
|
424
|
+
const config = getConfig();
|
|
425
|
+
const indexPath = path.join(PATHS.state, 'component-index.json');
|
|
426
|
+
|
|
427
|
+
// Read existing index
|
|
428
|
+
let existingIndex = { components: [] };
|
|
429
|
+
if (fs.existsSync(indexPath)) {
|
|
430
|
+
try {
|
|
431
|
+
existingIndex = JSON.parse(fs.readFileSync(indexPath, 'utf-8'));
|
|
432
|
+
} catch {
|
|
433
|
+
// Ignore
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// Enhance with relationships
|
|
438
|
+
const enhanced = {
|
|
439
|
+
...existingIndex,
|
|
440
|
+
lastEnhanced: new Date().toISOString(),
|
|
441
|
+
relationships: {},
|
|
442
|
+
dependencyGraph: {
|
|
443
|
+
nodes: [],
|
|
444
|
+
edges: []
|
|
445
|
+
}
|
|
446
|
+
};
|
|
447
|
+
|
|
448
|
+
// Analyze relationships for each component
|
|
449
|
+
const allComponents = [
|
|
450
|
+
...(existingIndex.components || []),
|
|
451
|
+
...(existingIndex.hooks || []),
|
|
452
|
+
...(existingIndex.services || []),
|
|
453
|
+
...(existingIndex.pages || [])
|
|
454
|
+
];
|
|
455
|
+
|
|
456
|
+
const filePaths = allComponents.map(c => c.path).filter(Boolean);
|
|
457
|
+
|
|
458
|
+
// Build dependency graph
|
|
459
|
+
const graph = buildDependencyGraph(filePaths);
|
|
460
|
+
enhanced.dependencyGraph = graph;
|
|
461
|
+
|
|
462
|
+
// Add individual relationship data
|
|
463
|
+
for (const comp of allComponents.slice(0, 50)) { // Limit to 50 for performance
|
|
464
|
+
if (comp.path) {
|
|
465
|
+
const rel = analyzeRelationships(comp.path);
|
|
466
|
+
if (!rel.error) {
|
|
467
|
+
enhanced.relationships[comp.path] = {
|
|
468
|
+
imports: rel.dependencies.internal.map(d => d.source),
|
|
469
|
+
exports: rel.exports.named,
|
|
470
|
+
types: rel.types.interfaces.concat(rel.types.types).map(t => t.name)
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// Save enhanced index
|
|
477
|
+
fs.writeFileSync(indexPath, JSON.stringify(enhanced, null, 2), 'utf-8');
|
|
478
|
+
|
|
479
|
+
return enhanced;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Get smart context for a task based on code intelligence
|
|
484
|
+
*
|
|
485
|
+
* @param {string} taskDescription - Task description
|
|
486
|
+
* @param {object} options - Options
|
|
487
|
+
*/
|
|
488
|
+
async function getSmartContext(taskDescription, options = {}) {
|
|
489
|
+
const config = getConfig();
|
|
490
|
+
const indexPath = path.join(PATHS.state, 'component-index.json');
|
|
491
|
+
|
|
492
|
+
if (!fs.existsSync(indexPath)) {
|
|
493
|
+
return { files: [], reason: 'No component index' };
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
const index = JSON.parse(fs.readFileSync(indexPath, 'utf-8'));
|
|
497
|
+
const desc = taskDescription.toLowerCase();
|
|
498
|
+
|
|
499
|
+
const relevantFiles = [];
|
|
500
|
+
const seen = new Set();
|
|
501
|
+
|
|
502
|
+
// 1. Direct keyword matches
|
|
503
|
+
const allComponents = [
|
|
504
|
+
...(index.components || []),
|
|
505
|
+
...(index.hooks || []),
|
|
506
|
+
...(index.services || []),
|
|
507
|
+
...(index.pages || [])
|
|
508
|
+
];
|
|
509
|
+
|
|
510
|
+
for (const comp of allComponents) {
|
|
511
|
+
const name = (comp.name || '').toLowerCase();
|
|
512
|
+
const filePath = (comp.path || '').toLowerCase();
|
|
513
|
+
|
|
514
|
+
// Check if any word in description matches component
|
|
515
|
+
const words = desc.split(/\s+/).filter(w => w.length > 3);
|
|
516
|
+
for (const word of words) {
|
|
517
|
+
if (name.includes(word) || filePath.includes(word)) {
|
|
518
|
+
if (!seen.has(comp.path)) {
|
|
519
|
+
relevantFiles.push({
|
|
520
|
+
path: comp.path,
|
|
521
|
+
reason: `keyword match: "${word}"`,
|
|
522
|
+
score: 3
|
|
523
|
+
});
|
|
524
|
+
seen.add(comp.path);
|
|
525
|
+
}
|
|
526
|
+
break;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// 2. Follow relationships for matched files
|
|
532
|
+
if (index.relationships && relevantFiles.length > 0) {
|
|
533
|
+
for (const file of relevantFiles.slice(0, 5)) {
|
|
534
|
+
const rel = index.relationships[file.path];
|
|
535
|
+
if (rel?.imports) {
|
|
536
|
+
for (const imp of rel.imports.slice(0, 3)) {
|
|
537
|
+
// Resolve import to actual path
|
|
538
|
+
const resolved = resolveImportPath(imp, path.dirname(file.path));
|
|
539
|
+
if (resolved && !seen.has(resolved)) {
|
|
540
|
+
relevantFiles.push({
|
|
541
|
+
path: resolved,
|
|
542
|
+
reason: `imported by ${file.path}`,
|
|
543
|
+
score: 2
|
|
544
|
+
});
|
|
545
|
+
seen.add(resolved);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// Sort by score
|
|
553
|
+
relevantFiles.sort((a, b) => b.score - a.score);
|
|
554
|
+
|
|
555
|
+
return {
|
|
556
|
+
files: relevantFiles.slice(0, options.maxFiles || 10),
|
|
557
|
+
totalMatches: relevantFiles.length
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// ============================================================
|
|
562
|
+
// CLI
|
|
563
|
+
// ============================================================
|
|
564
|
+
|
|
565
|
+
function showHelp() {
|
|
566
|
+
console.log(`
|
|
567
|
+
Wogi Flow - Code Intelligence
|
|
568
|
+
|
|
569
|
+
Enhanced code analysis with relationships and semantic search.
|
|
570
|
+
|
|
571
|
+
Usage:
|
|
572
|
+
flow code-intel analyze <file>
|
|
573
|
+
flow code-intel graph [directory]
|
|
574
|
+
flow code-intel related <query>
|
|
575
|
+
flow code-intel enhance
|
|
576
|
+
|
|
577
|
+
Commands:
|
|
578
|
+
analyze Analyze relationships for a file
|
|
579
|
+
graph Build dependency graph
|
|
580
|
+
related Find related code
|
|
581
|
+
enhance Enhance component index with relationships
|
|
582
|
+
|
|
583
|
+
Options:
|
|
584
|
+
--json Output as JSON
|
|
585
|
+
--help Show this help
|
|
586
|
+
|
|
587
|
+
Examples:
|
|
588
|
+
flow code-intel analyze src/components/Button.tsx
|
|
589
|
+
flow code-intel related "authentication"
|
|
590
|
+
flow code-intel enhance
|
|
591
|
+
`);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
async function main() {
|
|
595
|
+
const args = process.argv.slice(2);
|
|
596
|
+
|
|
597
|
+
if (args.includes('--help') || args.includes('-h') || args.length === 0) {
|
|
598
|
+
showHelp();
|
|
599
|
+
process.exit(0);
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
const command = args[0];
|
|
603
|
+
const target = args[1];
|
|
604
|
+
const jsonOutput = args.includes('--json');
|
|
605
|
+
|
|
606
|
+
switch (command) {
|
|
607
|
+
case 'analyze': {
|
|
608
|
+
if (!target) {
|
|
609
|
+
console.log(`${colors.red}Error: File path required${colors.reset}`);
|
|
610
|
+
process.exit(1);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
const relationships = analyzeRelationships(target);
|
|
614
|
+
|
|
615
|
+
if (jsonOutput) {
|
|
616
|
+
console.log(JSON.stringify(relationships, null, 2));
|
|
617
|
+
} else {
|
|
618
|
+
console.log(`\n${colors.cyan}File: ${relationships.file}${colors.reset}\n`);
|
|
619
|
+
|
|
620
|
+
console.log(`${colors.bold}Imports:${colors.reset}`);
|
|
621
|
+
for (const imp of relationships.imports.slice(0, 10)) {
|
|
622
|
+
console.log(` ${imp.source}`);
|
|
623
|
+
if (imp.named?.length > 0) {
|
|
624
|
+
console.log(` → ${imp.named.join(', ')}`);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
console.log(`\n${colors.bold}Exports:${colors.reset}`);
|
|
629
|
+
if (relationships.exports.default) {
|
|
630
|
+
console.log(` default: ${relationships.exports.default}`);
|
|
631
|
+
}
|
|
632
|
+
if (relationships.exports.named.length > 0) {
|
|
633
|
+
console.log(` named: ${relationships.exports.named.join(', ')}`);
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
console.log(`\n${colors.bold}Functions:${colors.reset}`);
|
|
637
|
+
for (const func of relationships.functions.slice(0, 10)) {
|
|
638
|
+
console.log(` ${func.async ? 'async ' : ''}${func.name}()`);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
break;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
case 'graph': {
|
|
645
|
+
const dir = target || 'src';
|
|
646
|
+
|
|
647
|
+
// Use safe find to prevent command injection
|
|
648
|
+
const files = safeFind(dir, {
|
|
649
|
+
cwd: PROJECT_ROOT,
|
|
650
|
+
extensions: ['.ts', '.tsx', '.js', '.jsx'],
|
|
651
|
+
maxResults: 100
|
|
652
|
+
});
|
|
653
|
+
|
|
654
|
+
if (files.length === 0) {
|
|
655
|
+
console.log(`${colors.red}Error scanning directory or no files found${colors.reset}`);
|
|
656
|
+
process.exit(1);
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
const graph = buildDependencyGraph(files);
|
|
660
|
+
|
|
661
|
+
if (jsonOutput) {
|
|
662
|
+
console.log(JSON.stringify(graph, null, 2));
|
|
663
|
+
} else {
|
|
664
|
+
console.log(`\n${colors.cyan}Dependency Graph${colors.reset}\n`);
|
|
665
|
+
console.log(`Nodes: ${graph.nodes.length}`);
|
|
666
|
+
console.log(`Edges: ${graph.edges.length}`);
|
|
667
|
+
|
|
668
|
+
console.log(`\n${colors.bold}Clusters:${colors.reset}`);
|
|
669
|
+
for (const [category, items] of Object.entries(graph.clusters)) {
|
|
670
|
+
console.log(` ${category}: ${items.length} files`);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
break;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
case 'related': {
|
|
677
|
+
if (!target) {
|
|
678
|
+
console.log(`${colors.red}Error: Query required${colors.reset}`);
|
|
679
|
+
process.exit(1);
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
const results = await findRelatedCode(target);
|
|
683
|
+
|
|
684
|
+
if (jsonOutput) {
|
|
685
|
+
console.log(JSON.stringify(results, null, 2));
|
|
686
|
+
} else {
|
|
687
|
+
console.log(`\n${colors.cyan}Related to: ${target}${colors.reset}\n`);
|
|
688
|
+
|
|
689
|
+
if (results.directDependencies.length > 0) {
|
|
690
|
+
console.log(`${colors.bold}Direct Dependencies:${colors.reset}`);
|
|
691
|
+
for (const dep of results.directDependencies) {
|
|
692
|
+
console.log(` → ${dep}`);
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
if (results.reverseDependencies.length > 0) {
|
|
697
|
+
console.log(`\n${colors.bold}Imported By:${colors.reset}`);
|
|
698
|
+
for (const dep of results.reverseDependencies) {
|
|
699
|
+
console.log(` ← ${dep}`);
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
if (results.similarFiles.length > 0) {
|
|
704
|
+
console.log(`\n${colors.bold}Similar Files:${colors.reset}`);
|
|
705
|
+
for (const file of results.similarFiles) {
|
|
706
|
+
console.log(` • ${file}`);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
break;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
case 'enhance': {
|
|
714
|
+
console.log(`${colors.cyan}Enhancing component index with relationships...${colors.reset}\n`);
|
|
715
|
+
const enhanced = await generateEnhancedIndex();
|
|
716
|
+
|
|
717
|
+
if (jsonOutput) {
|
|
718
|
+
console.log(JSON.stringify(enhanced, null, 2));
|
|
719
|
+
} else {
|
|
720
|
+
console.log(`${colors.green}✓ Enhanced index generated${colors.reset}`);
|
|
721
|
+
console.log(` Nodes: ${enhanced.dependencyGraph.nodes.length}`);
|
|
722
|
+
console.log(` Edges: ${enhanced.dependencyGraph.edges.length}`);
|
|
723
|
+
console.log(` Relationships: ${Object.keys(enhanced.relationships || {}).length}`);
|
|
724
|
+
}
|
|
725
|
+
break;
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
default:
|
|
729
|
+
console.log(`${colors.red}Unknown command: ${command}${colors.reset}`);
|
|
730
|
+
showHelp();
|
|
731
|
+
process.exit(1);
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
// ============================================================
|
|
736
|
+
// Exports
|
|
737
|
+
// ============================================================
|
|
738
|
+
|
|
739
|
+
module.exports = {
|
|
740
|
+
analyzeRelationships,
|
|
741
|
+
extractImports,
|
|
742
|
+
extractExports,
|
|
743
|
+
extractTypeUsage,
|
|
744
|
+
extractFunctions,
|
|
745
|
+
buildDependencyGraph,
|
|
746
|
+
findRelatedCode,
|
|
747
|
+
findFilesImporting,
|
|
748
|
+
searchCodebase,
|
|
749
|
+
generateEnhancedIndex,
|
|
750
|
+
getSmartContext,
|
|
751
|
+
categorizeFile,
|
|
752
|
+
resolveImportPath
|
|
753
|
+
};
|
|
754
|
+
|
|
755
|
+
if (require.main === module) {
|
|
756
|
+
main().catch(err => {
|
|
757
|
+
console.error(`Error: ${err.message}`);
|
|
758
|
+
process.exit(1);
|
|
759
|
+
});
|
|
760
|
+
}
|