wogiflow 2.2.0 → 2.3.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/.claude/commands/wogi-onboard.md +30 -8
- package/.workflow/models/registry.json +1 -1
- package/package.json +1 -1
- package/scripts/flow-api-index.js +128 -63
- package/scripts/flow-function-index.js +65 -63
- package/scripts/flow-pattern-extractor.js +1 -1
- package/scripts/flow-scanner-base.js +200 -7
- package/scripts/flow-skill-generator.js +1 -0
- package/scripts/flow-template-extractor.js +1 -1
- package/scripts/registries/component-registry.js +141 -4
- package/.claude/rules/_internal/README.md +0 -64
- package/.claude/rules/_internal/document-structure.md +0 -77
- package/.claude/rules/_internal/dual-repo-management.md +0 -174
- package/.claude/rules/_internal/feature-refactoring-cleanup.md +0 -87
- package/.claude/rules/_internal/github-releases.md +0 -71
- package/.claude/rules/_internal/model-management.md +0 -35
- package/.claude/rules/_internal/self-maintenance.md +0 -87
- package/.claude/rules/architecture/component-reuse.md +0 -38
- package/.claude/rules/code-style/naming-conventions.md +0 -52
- package/.claude/rules/operations/git-workflows.md +0 -92
- package/.claude/rules/operations/scratch-directory.md +0 -54
- package/.claude/rules/security/security-patterns.md +0 -176
- package/.claude/skills/figma-analyzer/knowledge/learnings.md +0 -11
- package/.workflow/specs/architecture.md.template +0 -24
- package/.workflow/specs/stack.md.template +0 -33
- package/.workflow/specs/testing.md.template +0 -36
|
@@ -530,14 +530,36 @@ Display:
|
|
|
530
530
|
|
|
531
531
|
Display: ` Data-fetching hooks... ✓ react-query, 80 useGet* hooks`
|
|
532
532
|
|
|
533
|
-
15. **
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
533
|
+
15. **Run registry-manager scan (comprehensive registry population):**
|
|
534
|
+
|
|
535
|
+
**CRITICAL**: This step replaces manual AI-driven app-map population. The registry
|
|
536
|
+
manager runs ALL active scanners (components, functions, APIs, schemas, services)
|
|
537
|
+
with recursive directory traversal and glob-based discovery. This ensures no
|
|
538
|
+
subdirectory components, co-located hooks, or separated-export API functions are missed.
|
|
539
|
+
|
|
540
|
+
```javascript
|
|
541
|
+
// Run the full registry scan — this handles recursion, glob patterns, and all export patterns
|
|
542
|
+
const { execSync } = require('child_process');
|
|
543
|
+
try {
|
|
544
|
+
execSync('node node_modules/wogiflow/scripts/flow-registry-manager.js scan', {
|
|
545
|
+
cwd: projectRoot,
|
|
546
|
+
stdio: 'inherit',
|
|
547
|
+
timeout: 60000
|
|
548
|
+
});
|
|
549
|
+
} catch (err) {
|
|
550
|
+
console.warn('Registry manager scan failed, falling back to individual scanners:', err.message);
|
|
551
|
+
// Individual scanners from steps 13-14 already ran as fallback
|
|
552
|
+
}
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
The component scanner generates `app-map.md` from scan results (grouped by category/directory).
|
|
556
|
+
The function scanner discovers co-located hooks via glob patterns (`src/**/hooks`, etc.).
|
|
557
|
+
The API scanner handles all export patterns including separated `const + export default`.
|
|
558
|
+
|
|
559
|
+
Display: ` Registry scan... ✓ All registries populated (components, functions, APIs)`
|
|
560
|
+
|
|
561
|
+
**NOTE**: If the registry manager scan succeeds, it supersedes the individual scanner runs
|
|
562
|
+
from steps 13-14. The scanners are idempotent — running them twice just refreshes the same data.
|
|
541
563
|
|
|
542
564
|
16. **Extract file templates:**
|
|
543
565
|
```javascript
|
package/package.json
CHANGED
|
@@ -45,6 +45,13 @@ const DEFAULT_CONFIG = {
|
|
|
45
45
|
'src/mutations'
|
|
46
46
|
],
|
|
47
47
|
|
|
48
|
+
// Glob patterns discover co-located API files (any project structure)
|
|
49
|
+
globPatterns: [
|
|
50
|
+
'src/**/queries',
|
|
51
|
+
'src/**/mutations',
|
|
52
|
+
'src/**/api'
|
|
53
|
+
],
|
|
54
|
+
|
|
48
55
|
filePatterns: ['**/*.ts', '**/*.js', '**/*.tsx', '**/*.jsx'],
|
|
49
56
|
|
|
50
57
|
excludePatterns: [
|
|
@@ -75,6 +82,7 @@ class APIScanner extends BaseScanner {
|
|
|
75
82
|
super({
|
|
76
83
|
configKey: 'apiRegistry',
|
|
77
84
|
directories: DEFAULT_CONFIG.directories,
|
|
85
|
+
globPatterns: DEFAULT_CONFIG.globPatterns,
|
|
78
86
|
filePatterns: DEFAULT_CONFIG.filePatterns,
|
|
79
87
|
excludePatterns: DEFAULT_CONFIG.excludePatterns,
|
|
80
88
|
...config
|
|
@@ -131,36 +139,27 @@ class APIScanner extends BaseScanner {
|
|
|
131
139
|
}
|
|
132
140
|
|
|
133
141
|
/**
|
|
134
|
-
* Parse file with Babel AST
|
|
142
|
+
* Parse file with Babel AST using shared two-pass approach from BaseScanner.
|
|
143
|
+
* Handles every JS/TS export pattern by design.
|
|
135
144
|
*/
|
|
136
145
|
parseWithBabel(content, filePath, service) {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
sourceType: 'module',
|
|
140
|
-
plugins: ['typescript', 'jsx', 'decorators-legacy']
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
this.traverse(ast, {
|
|
144
|
-
ExportNamedDeclaration: (nodePath) => {
|
|
145
|
-
const declaration = nodePath.node.declaration;
|
|
146
|
-
if (!declaration) return;
|
|
147
|
-
|
|
148
|
-
if (declaration.type === 'FunctionDeclaration' && declaration.id) {
|
|
149
|
-
this.extractAPIFunction(declaration, filePath, service, content);
|
|
150
|
-
} else if (declaration.type === 'VariableDeclaration') {
|
|
151
|
-
for (const decl of declaration.declarations) {
|
|
152
|
-
if (decl.init &&
|
|
153
|
-
(decl.init.type === 'ArrowFunctionExpression' ||
|
|
154
|
-
decl.init.type === 'FunctionExpression')) {
|
|
155
|
-
this.extractAPIFunctionFromVariable(decl, filePath, service, content);
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
});
|
|
161
|
-
} catch (err) {
|
|
162
|
-
// Fall back to regex if babel fails
|
|
146
|
+
const result = this.collectExportedDeclarations(content);
|
|
147
|
+
if (!result) {
|
|
163
148
|
this.parseWithRegex(content, filePath, service);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const { declarations, exported } = result;
|
|
153
|
+
|
|
154
|
+
for (const [name, _exportInfo] of exported) {
|
|
155
|
+
const declInfo = declarations.get(name);
|
|
156
|
+
if (!declInfo) continue;
|
|
157
|
+
|
|
158
|
+
if (declInfo.kind === 'func') {
|
|
159
|
+
this.extractAPIFunction(declInfo.node, filePath, service, content);
|
|
160
|
+
} else {
|
|
161
|
+
this.extractAPIFunctionFromVariable(declInfo.node, filePath, service, content);
|
|
162
|
+
}
|
|
164
163
|
}
|
|
165
164
|
}
|
|
166
165
|
|
|
@@ -181,7 +180,7 @@ class APIScanner extends BaseScanner {
|
|
|
181
180
|
name,
|
|
182
181
|
params,
|
|
183
182
|
method: isAPIFunction.method || this.inferMethodFromName(name),
|
|
184
|
-
endpoint: isAPIFunction.endpoint,
|
|
183
|
+
endpoint: isAPIFunction.endpoint || 'dynamic',
|
|
185
184
|
description: jsdoc.description || '',
|
|
186
185
|
file: filePath,
|
|
187
186
|
service,
|
|
@@ -210,7 +209,7 @@ class APIScanner extends BaseScanner {
|
|
|
210
209
|
name,
|
|
211
210
|
params,
|
|
212
211
|
method: isAPIFunction.method || this.inferMethodFromName(name),
|
|
213
|
-
endpoint: isAPIFunction.endpoint,
|
|
212
|
+
endpoint: isAPIFunction.endpoint || 'dynamic',
|
|
214
213
|
description: jsdoc.description || '',
|
|
215
214
|
file: filePath,
|
|
216
215
|
service,
|
|
@@ -228,13 +227,15 @@ class APIScanner extends BaseScanner {
|
|
|
228
227
|
/^(post|create|add|save|submit)/i,
|
|
229
228
|
/^(put|update|modify|patch)/i,
|
|
230
229
|
/^(delete|remove|destroy)/i,
|
|
230
|
+
/^use(Get|Fetch|Load|Create|Update|Delete|Post|Put|Patch|Remove|Query|Mutation)/,
|
|
231
231
|
/(api|endpoint|request|mutation|query)$/i
|
|
232
232
|
];
|
|
233
233
|
|
|
234
234
|
const nameMatches = apiNamePatterns.some(p => p.test(name));
|
|
235
235
|
|
|
236
|
-
// Check function body for HTTP calls
|
|
237
|
-
|
|
236
|
+
// Check function body for HTTP calls — use full body including nested scopes
|
|
237
|
+
// Find the matching closing brace to capture nested arrow functions
|
|
238
|
+
const funcBody = this._extractFullBody(content, startPos, endPos);
|
|
238
239
|
const httpPatterns = [
|
|
239
240
|
/fetch\s*\(/,
|
|
240
241
|
/axios\./,
|
|
@@ -247,7 +248,9 @@ class APIScanner extends BaseScanner {
|
|
|
247
248
|
/apiClient/i,
|
|
248
249
|
/useSWR/,
|
|
249
250
|
/useQuery/,
|
|
250
|
-
/useMutation
|
|
251
|
+
/useMutation/,
|
|
252
|
+
/useInfiniteQuery/,
|
|
253
|
+
/useSuspenseQuery/
|
|
251
254
|
];
|
|
252
255
|
|
|
253
256
|
const bodyMatches = httpPatterns.some(p => p.test(funcBody));
|
|
@@ -280,6 +283,68 @@ class APIScanner extends BaseScanner {
|
|
|
280
283
|
};
|
|
281
284
|
}
|
|
282
285
|
|
|
286
|
+
/**
|
|
287
|
+
* Extract the full function body content, including nested scopes.
|
|
288
|
+
* Falls back to substring if brace matching fails.
|
|
289
|
+
* @param {string} content - Full file content
|
|
290
|
+
* @param {number} startPos - Start position of the function
|
|
291
|
+
* @param {number} endPos - End position from AST (may be too narrow)
|
|
292
|
+
* @returns {string} Function body content
|
|
293
|
+
*/
|
|
294
|
+
_extractFullBody(content, startPos, endPos) {
|
|
295
|
+
const MAX_SCAN = 50000; // Cap at 50KB to avoid stalls on large files
|
|
296
|
+
const braceStart = content.indexOf('{', startPos);
|
|
297
|
+
if (braceStart === -1 || braceStart > endPos + 100) {
|
|
298
|
+
return content.substring(startPos, Math.min(endPos + 500, content.length));
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Match braces with string/comment awareness
|
|
302
|
+
let depth = 0;
|
|
303
|
+
let i = braceStart;
|
|
304
|
+
const limit = Math.min(content.length, braceStart + MAX_SCAN);
|
|
305
|
+
while (i < limit) {
|
|
306
|
+
const ch = content[i];
|
|
307
|
+
|
|
308
|
+
// Skip single-line comments
|
|
309
|
+
if (ch === '/' && content[i + 1] === '/') {
|
|
310
|
+
i = content.indexOf('\n', i + 2);
|
|
311
|
+
if (i === -1) break;
|
|
312
|
+
i++;
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
// Skip multi-line comments
|
|
316
|
+
if (ch === '/' && content[i + 1] === '*') {
|
|
317
|
+
i = content.indexOf('*/', i + 2);
|
|
318
|
+
if (i === -1) break;
|
|
319
|
+
i += 2;
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
// Skip string literals
|
|
323
|
+
if (ch === "'" || ch === '"' || ch === '`') {
|
|
324
|
+
i++;
|
|
325
|
+
while (i < limit) {
|
|
326
|
+
if (content[i] === '\\') { i += 2; continue; }
|
|
327
|
+
if (content[i] === ch) break;
|
|
328
|
+
i++;
|
|
329
|
+
}
|
|
330
|
+
i++;
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (ch === '{') depth++;
|
|
335
|
+
else if (ch === '}') {
|
|
336
|
+
depth--;
|
|
337
|
+
if (depth === 0) {
|
|
338
|
+
return content.substring(startPos, i + 1);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
i++;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Fallback to AST bounds with buffer
|
|
345
|
+
return content.substring(startPos, Math.min(endPos + 200, content.length));
|
|
346
|
+
}
|
|
347
|
+
|
|
283
348
|
/**
|
|
284
349
|
* Infer HTTP method from function name
|
|
285
350
|
*/
|
|
@@ -294,56 +359,55 @@ class APIScanner extends BaseScanner {
|
|
|
294
359
|
}
|
|
295
360
|
|
|
296
361
|
/**
|
|
297
|
-
* Parse with regex (fallback)
|
|
362
|
+
* Parse with regex (fallback, two-pass approach)
|
|
298
363
|
*/
|
|
299
364
|
parseWithRegex(content, filePath, service) {
|
|
300
|
-
//
|
|
301
|
-
const funcRegex = /export\s+(async\s+)?function\s+(\w+)\s*\(([^)]*)\)/g;
|
|
365
|
+
// Two-pass regex: find declarations, find exports, intersect
|
|
302
366
|
let match;
|
|
303
367
|
|
|
304
|
-
|
|
305
|
-
|
|
368
|
+
// Pass 1: Find all function-like declarations
|
|
369
|
+
const declarations = new Map(); // name -> { line, paramsStr }
|
|
306
370
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
this.addClientFunction({
|
|
315
|
-
name,
|
|
316
|
-
params: this.parseParamsFromString(paramsStr),
|
|
317
|
-
method: this.inferMethodFromName(name),
|
|
318
|
-
endpoint: null,
|
|
319
|
-
description: jsdoc,
|
|
320
|
-
file: filePath,
|
|
321
|
-
service,
|
|
322
|
-
line: this.getLineNumber(content, match.index)
|
|
371
|
+
const funcRegex = /(?:export\s+(?:default\s+)?)?(?:async\s+)?function\s+(\w+)\s*\(([^)]*)\)/g;
|
|
372
|
+
while ((match = funcRegex.exec(content)) !== null) {
|
|
373
|
+
declarations.set(match[1], {
|
|
374
|
+
line: this.getLineNumber(content, match.index),
|
|
375
|
+
paramsStr: match[2],
|
|
376
|
+
pos: match.index
|
|
323
377
|
});
|
|
324
378
|
}
|
|
325
379
|
|
|
326
|
-
|
|
327
|
-
const arrowRegex = /export\s+const\s+(\w+)\s*=\s*(?:async\s*)?\([^)]*\)\s*(?::\s*[^=]+)?\s*=>/g;
|
|
328
|
-
|
|
380
|
+
const arrowRegex = /(?:export\s+)?const\s+(\w+)\s*=\s*(?:async\s*)?\([^)]*\)\s*(?::\s*[^=]+)?\s*=>/g;
|
|
329
381
|
while ((match = arrowRegex.exec(content)) !== null) {
|
|
330
|
-
|
|
382
|
+
if (!declarations.has(match[1])) {
|
|
383
|
+
declarations.set(match[1], {
|
|
384
|
+
line: this.getLineNumber(content, match.index),
|
|
385
|
+
paramsStr: '',
|
|
386
|
+
pos: match.index
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Pass 2: Use shared export-collection from BaseScanner
|
|
392
|
+
const exported = this.collectExportedNamesRegex(content);
|
|
331
393
|
|
|
332
|
-
|
|
394
|
+
// Intersect: register exported declarations that look like API functions
|
|
395
|
+
for (const [name, info] of declarations) {
|
|
396
|
+
if (!exported.has(name)) continue;
|
|
397
|
+
if (!this.isLikelyAPIFunctionFromName(name) && !this.hasHTTPCall(content, info.pos)) {
|
|
333
398
|
continue;
|
|
334
399
|
}
|
|
335
400
|
|
|
336
|
-
const jsdoc = this.extractJSDocBefore(content,
|
|
337
|
-
|
|
401
|
+
const jsdoc = this.extractJSDocBefore(content, info.pos);
|
|
338
402
|
this.addClientFunction({
|
|
339
403
|
name,
|
|
340
|
-
params: [],
|
|
404
|
+
params: info.paramsStr ? this.parseParamsFromString(info.paramsStr) : [],
|
|
341
405
|
method: this.inferMethodFromName(name),
|
|
342
|
-
endpoint:
|
|
406
|
+
endpoint: 'dynamic',
|
|
343
407
|
description: jsdoc,
|
|
344
408
|
file: filePath,
|
|
345
409
|
service,
|
|
346
|
-
line:
|
|
410
|
+
line: info.line
|
|
347
411
|
});
|
|
348
412
|
}
|
|
349
413
|
}
|
|
@@ -353,6 +417,7 @@ class APIScanner extends BaseScanner {
|
|
|
353
417
|
*/
|
|
354
418
|
isLikelyAPIFunctionFromName(name) {
|
|
355
419
|
return /^(get|fetch|load|post|create|put|update|patch|delete|remove|query|mutation)/i.test(name) ||
|
|
420
|
+
/^use(Get|Fetch|Load|Create|Update|Delete|Post|Put|Patch|Remove|Query|Mutation)/i.test(name) ||
|
|
356
421
|
/(api|endpoint|request)$/i.test(name);
|
|
357
422
|
}
|
|
358
423
|
|
|
@@ -38,6 +38,7 @@ const DEFAULT_CONFIG = {
|
|
|
38
38
|
'src/utils',
|
|
39
39
|
'src/lib',
|
|
40
40
|
'src/helpers',
|
|
41
|
+
'src/services',
|
|
41
42
|
'utils',
|
|
42
43
|
'lib',
|
|
43
44
|
'helpers',
|
|
@@ -45,6 +46,13 @@ const DEFAULT_CONFIG = {
|
|
|
45
46
|
'shared'
|
|
46
47
|
],
|
|
47
48
|
|
|
49
|
+
// Glob patterns discover co-located directories (any project structure)
|
|
50
|
+
globPatterns: [
|
|
51
|
+
'src/**/hooks',
|
|
52
|
+
'src/**/helpers',
|
|
53
|
+
'src/**/utils'
|
|
54
|
+
],
|
|
55
|
+
|
|
48
56
|
filePatterns: ['**/*.ts', '**/*.js', '**/*.tsx', '**/*.jsx'],
|
|
49
57
|
|
|
50
58
|
excludePatterns: [
|
|
@@ -70,6 +78,7 @@ class FunctionScanner extends BaseScanner {
|
|
|
70
78
|
super({
|
|
71
79
|
configKey: 'functionRegistry',
|
|
72
80
|
directories: DEFAULT_CONFIG.directories,
|
|
81
|
+
globPatterns: DEFAULT_CONFIG.globPatterns,
|
|
73
82
|
filePatterns: DEFAULT_CONFIG.filePatterns,
|
|
74
83
|
excludePatterns: DEFAULT_CONFIG.excludePatterns,
|
|
75
84
|
...config
|
|
@@ -113,42 +122,27 @@ class FunctionScanner extends BaseScanner {
|
|
|
113
122
|
}
|
|
114
123
|
|
|
115
124
|
/**
|
|
116
|
-
* Parse file with Babel AST
|
|
125
|
+
* Parse file with Babel AST using shared two-pass approach from BaseScanner.
|
|
126
|
+
* Handles every JS/TS export pattern by design — no whack-a-mole.
|
|
117
127
|
*/
|
|
118
128
|
parseWithBabel(content, filePath, category) {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
sourceType: 'module',
|
|
122
|
-
plugins: ['typescript', 'jsx', 'decorators-legacy']
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
this.traverse(ast, {
|
|
126
|
-
ExportNamedDeclaration: (nodePath) => {
|
|
127
|
-
const declaration = nodePath.node.declaration;
|
|
128
|
-
if (!declaration) return;
|
|
129
|
-
|
|
130
|
-
if (declaration.type === 'FunctionDeclaration' && declaration.id) {
|
|
131
|
-
this.extractFunction(declaration, filePath, category, content);
|
|
132
|
-
} else if (declaration.type === 'VariableDeclaration') {
|
|
133
|
-
for (const decl of declaration.declarations) {
|
|
134
|
-
if (decl.init &&
|
|
135
|
-
(decl.init.type === 'ArrowFunctionExpression' ||
|
|
136
|
-
decl.init.type === 'FunctionExpression')) {
|
|
137
|
-
this.extractFunctionFromVariable(decl, filePath, category, content);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
},
|
|
142
|
-
ExportDefaultDeclaration: (nodePath) => {
|
|
143
|
-
const declaration = nodePath.node.declaration;
|
|
144
|
-
if (declaration.type === 'FunctionDeclaration' && declaration.id) {
|
|
145
|
-
this.extractFunction(declaration, filePath, category, content, true);
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
});
|
|
149
|
-
} catch (err) {
|
|
150
|
-
// Fall back to regex if babel fails
|
|
129
|
+
const result = this.collectExportedDeclarations(content);
|
|
130
|
+
if (!result) {
|
|
151
131
|
this.parseWithRegex(content, filePath, category);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const { declarations, exported } = result;
|
|
136
|
+
|
|
137
|
+
for (const [name, exportInfo] of exported) {
|
|
138
|
+
const declInfo = declarations.get(name);
|
|
139
|
+
if (!declInfo) continue;
|
|
140
|
+
|
|
141
|
+
if (declInfo.kind === 'func') {
|
|
142
|
+
this.extractFunction(declInfo.node, filePath, category, content, exportInfo.isDefault);
|
|
143
|
+
} else {
|
|
144
|
+
this.extractFunctionFromVariable(declInfo.node, filePath, category, content);
|
|
145
|
+
}
|
|
152
146
|
}
|
|
153
147
|
}
|
|
154
148
|
|
|
@@ -223,44 +217,52 @@ class FunctionScanner extends BaseScanner {
|
|
|
223
217
|
* Parse file with regex (fallback)
|
|
224
218
|
*/
|
|
225
219
|
parseWithRegex(content, filePath, category) {
|
|
226
|
-
//
|
|
227
|
-
const
|
|
220
|
+
// Pass 1: Find all function-like declarations
|
|
221
|
+
const declarations = new Map();
|
|
228
222
|
let match;
|
|
229
223
|
|
|
224
|
+
const functionRegex = /(?:export\s+(?:default\s+)?)?(?:(async)\s+)?function\s+(\w+)\s*(?:<[^>]*>)?\s*\(([^)]*)\)(?:\s*:\s*([^\s{]+))?\s*\{/g;
|
|
230
225
|
while ((match = functionRegex.exec(content)) !== null) {
|
|
231
|
-
const [, isAsync, name,
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
this.addFunction({
|
|
236
|
-
name,
|
|
237
|
-
params,
|
|
226
|
+
const [, isAsync, name, paramsStr, returnType] = match;
|
|
227
|
+
declarations.set(name, {
|
|
228
|
+
line: this.getLineNumber(content, match.index),
|
|
229
|
+
params: this.parseParamsFromString(paramsStr),
|
|
238
230
|
returnType: returnType || (isAsync ? 'Promise<any>' : null),
|
|
239
|
-
|
|
240
|
-
file: filePath,
|
|
241
|
-
category,
|
|
242
|
-
isDefault: false,
|
|
243
|
-
line: this.getLineNumber(content, match.index)
|
|
231
|
+
jsdoc: this.extractJSDocBefore(content, match.index)
|
|
244
232
|
});
|
|
245
233
|
}
|
|
246
234
|
|
|
247
|
-
//
|
|
248
|
-
const arrowRegex = /export\s+const\s+(\w+)\s*(?::\s*[^=]+)?\s*=\s*(?:async\s*)?\([^)]
|
|
249
|
-
|
|
235
|
+
// Const arrow functions — capture params group (finding-005 fix)
|
|
236
|
+
const arrowRegex = /(?:export\s+)?const\s+(\w+)\s*(?::\s*[^=]+)?\s*=\s*(?:async\s*)?\(([^)]*)\)\s*(?::\s*([^\s=>]+))?\s*=>/g;
|
|
250
237
|
while ((match = arrowRegex.exec(content)) !== null) {
|
|
251
|
-
const [, name, returnType] = match;
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
238
|
+
const [, name, paramsStr, returnType] = match;
|
|
239
|
+
if (!declarations.has(name)) {
|
|
240
|
+
declarations.set(name, {
|
|
241
|
+
line: this.getLineNumber(content, match.index),
|
|
242
|
+
params: paramsStr ? this.parseParamsFromString(paramsStr) : [],
|
|
243
|
+
returnType,
|
|
244
|
+
jsdoc: this.extractJSDocBefore(content, match.index)
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Pass 2: Use shared export-collection from BaseScanner
|
|
250
|
+
const exported = this.collectExportedNamesRegex(content);
|
|
251
|
+
|
|
252
|
+
// Intersect: register all exported declarations
|
|
253
|
+
for (const [name, info] of declarations) {
|
|
254
|
+
if (exported.has(name)) {
|
|
255
|
+
this.addFunction({
|
|
256
|
+
name,
|
|
257
|
+
params: info.params,
|
|
258
|
+
returnType: info.returnType,
|
|
259
|
+
description: info.jsdoc,
|
|
260
|
+
file: filePath,
|
|
261
|
+
category,
|
|
262
|
+
isDefault: false,
|
|
263
|
+
line: info.line
|
|
264
|
+
});
|
|
265
|
+
}
|
|
264
266
|
}
|
|
265
267
|
}
|
|
266
268
|
|
|
@@ -92,7 +92,7 @@ const IGNORE_PATTERNS = [
|
|
|
92
92
|
];
|
|
93
93
|
|
|
94
94
|
// Colors for CLI output
|
|
95
|
-
const { colors: c } = require('./flow-output');
|
|
95
|
+
const { colors: c, getTodayDate } = require('./flow-output');
|
|
96
96
|
|
|
97
97
|
// ============================================================================
|
|
98
98
|
// Utility Functions
|