project-graph-mcp 1.5.0 → 2.1.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.
Files changed (125) hide show
  1. package/README.md +171 -31
  2. package/docs/img/explorer-compact.jpg +0 -0
  3. package/docs/img/explorer-expanded.jpg +0 -0
  4. package/package.json +12 -8
  5. package/src/.project-graph-cache.json +1 -1
  6. package/src/analysis/analysis-cache.js +7 -0
  7. package/src/analysis/complexity.js +14 -0
  8. package/src/analysis/custom-rules.js +36 -0
  9. package/src/analysis/db-analysis.js +9 -0
  10. package/src/analysis/dead-code.js +19 -0
  11. package/src/analysis/full-analysis.js +18 -0
  12. package/src/analysis/jsdoc-checker.js +24 -0
  13. package/src/analysis/jsdoc-generator.js +10 -0
  14. package/src/analysis/large-files.js +11 -0
  15. package/src/analysis/outdated-patterns.js +12 -0
  16. package/src/analysis/similar-functions.js +16 -0
  17. package/src/analysis/test-annotations.js +21 -0
  18. package/src/analysis/type-checker.js +8 -0
  19. package/src/analysis/undocumented.js +14 -0
  20. package/src/cli/cli-handlers.js +4 -0
  21. package/src/cli/cli.js +5 -0
  22. package/src/compact/.project-graph-cache.json +1 -0
  23. package/src/compact/ai-context.js +7 -0
  24. package/src/compact/compact-migrate.js +17 -0
  25. package/src/compact/compact.js +18 -0
  26. package/src/compact/compress.js +14 -0
  27. package/src/compact/ctx-to-jsdoc.js +29 -0
  28. package/src/compact/doc-dialect.js +30 -0
  29. package/src/compact/expand.js +37 -0
  30. package/src/compact/framework-references.js +5 -0
  31. package/src/compact/instructions.js +3 -0
  32. package/src/compact/mode-config.js +8 -0
  33. package/src/compact/validate-pipeline.js +9 -0
  34. package/src/core/event-bus.js +9 -0
  35. package/src/core/filters.js +14 -0
  36. package/src/core/graph-builder.js +12 -0
  37. package/src/core/parser.js +31 -0
  38. package/src/core/workspace.js +8 -0
  39. package/src/lang/lang-go.js +17 -0
  40. package/src/lang/lang-python.js +12 -0
  41. package/src/lang/lang-sql.js +23 -0
  42. package/src/lang/lang-typescript.js +9 -0
  43. package/src/lang/lang-utils.js +4 -0
  44. package/src/mcp/mcp-server.js +17 -0
  45. package/src/mcp/tool-defs.js +3 -0
  46. package/src/mcp/tools.js +25 -0
  47. package/src/network/backend-lifecycle.js +19 -0
  48. package/src/network/backend.js +5 -0
  49. package/src/network/local-gateway.js +23 -0
  50. package/src/network/mdns.js +13 -0
  51. package/src/network/server.js +10 -0
  52. package/src/network/web-server.js +34 -0
  53. package/web/.project-graph-cache.json +1 -0
  54. package/web/app.js +17 -0
  55. package/web/components/code-block.js +3 -0
  56. package/web/components/quick-open.js +5 -0
  57. package/web/dashboard-state.js +3 -0
  58. package/web/dashboard.html +27 -0
  59. package/web/dashboard.js +8 -0
  60. package/web/highlight.js +13 -0
  61. package/web/index.html +35 -0
  62. package/web/panels/ActionBoard/ActionBoard.css.js +1 -0
  63. package/web/panels/ActionBoard/ActionBoard.js +4 -0
  64. package/web/panels/ActionBoard/ActionBoard.tpl.js +1 -0
  65. package/web/panels/EventItem/EventItem.css.js +1 -0
  66. package/web/panels/EventItem/EventItem.js +4 -0
  67. package/web/panels/EventItem/EventItem.tpl.js +1 -0
  68. package/web/panels/ProjectItem/ProjectItem.css.js +1 -0
  69. package/web/panels/ProjectItem/ProjectItem.js +5 -0
  70. package/web/panels/ProjectItem/ProjectItem.tpl.js +1 -0
  71. package/web/panels/ProjectList/ProjectList.css.js +1 -0
  72. package/web/panels/ProjectList/ProjectList.js +4 -0
  73. package/web/panels/ProjectList/ProjectList.tpl.js +1 -0
  74. package/web/panels/SettingsPanel/.project-graph-cache.json +1 -0
  75. package/web/panels/SettingsPanel/SettingsPanel.css.js +1 -0
  76. package/web/panels/SettingsPanel/SettingsPanel.js +7 -0
  77. package/web/panels/SettingsPanel/SettingsPanel.tpl.js +1 -0
  78. package/web/panels/code-viewer.js +5 -0
  79. package/web/panels/ctx-panel.js +4 -0
  80. package/web/panels/dep-graph.js +6 -0
  81. package/web/panels/file-tree.js +188 -0
  82. package/web/panels/health-panel.js +3 -0
  83. package/web/panels/live-monitor.js +3 -0
  84. package/web/state.js +17 -0
  85. package/web/style.css +157 -0
  86. package/references/symbiote-3x.md +0 -834
  87. package/src/ai-context.js +0 -113
  88. package/src/analysis-cache.js +0 -155
  89. package/src/cli-handlers.js +0 -271
  90. package/src/cli.js +0 -95
  91. package/src/compact.js +0 -207
  92. package/src/complexity.js +0 -237
  93. package/src/compress.js +0 -319
  94. package/src/ctx-to-jsdoc.js +0 -514
  95. package/src/custom-rules.js +0 -584
  96. package/src/db-analysis.js +0 -194
  97. package/src/dead-code.js +0 -468
  98. package/src/doc-dialect.js +0 -716
  99. package/src/filters.js +0 -227
  100. package/src/framework-references.js +0 -177
  101. package/src/full-analysis.js +0 -470
  102. package/src/graph-builder.js +0 -299
  103. package/src/instructions.js +0 -73
  104. package/src/jsdoc-checker.js +0 -351
  105. package/src/jsdoc-generator.js +0 -203
  106. package/src/lang-go.js +0 -285
  107. package/src/lang-python.js +0 -197
  108. package/src/lang-sql.js +0 -309
  109. package/src/lang-typescript.js +0 -190
  110. package/src/lang-utils.js +0 -124
  111. package/src/large-files.js +0 -163
  112. package/src/mcp-server.js +0 -675
  113. package/src/mode-config.js +0 -127
  114. package/src/outdated-patterns.js +0 -296
  115. package/src/parser.js +0 -662
  116. package/src/server.js +0 -28
  117. package/src/similar-functions.js +0 -279
  118. package/src/test-annotations.js +0 -323
  119. package/src/tool-defs.js +0 -793
  120. package/src/tools.js +0 -470
  121. package/src/type-checker.js +0 -188
  122. package/src/undocumented.js +0 -259
  123. package/src/workspace.js +0 -70
  124. /package/{AGENT_ROLE.md → docs/examples/AGENT_ROLE.md} +0 -0
  125. /package/{AGENT_ROLE_MINIMAL.md → docs/examples/AGENT_ROLE_MINIMAL.md} +0 -0
@@ -1,351 +0,0 @@
1
- /**
2
- * JSDoc Consistency Checker (AST-based)
3
- * Validates JSDoc annotations against actual function signatures
4
- *
5
- * Checks:
6
- * - Param count mismatch (JSDoc vs AST)
7
- * - Param name mismatch
8
- * - Missing @returns on functions with return statements
9
- * - Type hint inconsistency (default value vs JSDoc type)
10
- */
11
-
12
- import { readFileSync, readdirSync, statSync } from 'fs';
13
- import { join, relative, resolve } from 'path';
14
- import { parse } from '../vendor/acorn.mjs';
15
- import * as walk from '../vendor/walk.mjs';
16
- import { shouldExcludeDir, shouldExcludeFile, parseGitignore } from './filters.js';
17
-
18
- /**
19
- * @typedef {Object} JSDocIssue
20
- * @property {string} file
21
- * @property {number} line
22
- * @property {string} name - Function or method name
23
- * @property {'error'|'warning'} severity
24
- * @property {string} message
25
- */
26
-
27
- /**
28
- * Find all JS files in directory
29
- * @param {string} dir
30
- * @param {string} rootDir
31
- * @returns {string[]}
32
- */
33
- function findJSFiles(dir, rootDir = dir) {
34
- if (dir === rootDir) parseGitignore(rootDir);
35
- const files = [];
36
- try {
37
- for (const entry of readdirSync(dir)) {
38
- const fullPath = join(dir, entry);
39
- const relativePath = relative(rootDir, fullPath);
40
- const stat = statSync(fullPath);
41
- if (stat.isDirectory()) {
42
- if (!shouldExcludeDir(entry, relativePath)) {
43
- files.push(...findJSFiles(fullPath, rootDir));
44
- }
45
- } else if (entry.endsWith('.js') && !entry.endsWith('.css.js') && !entry.endsWith('.tpl.js')) {
46
- if (!shouldExcludeFile(entry, relativePath)) {
47
- files.push(fullPath);
48
- }
49
- }
50
- }
51
- } catch (e) { /* dir not found */ }
52
- return files;
53
- }
54
-
55
- /**
56
- * Extract JSDoc comments with positions
57
- * @param {string} code
58
- * @returns {Array<{text: string, endLine: number, params: Array<{name: string, type: string}>, hasReturns: boolean}>}
59
- */
60
- function extractJSDocComments(code) {
61
- const comments = [];
62
- const regex = /\/\*\*[\s\S]*?\*\//g;
63
- let match;
64
-
65
- while ((match = regex.exec(code)) !== null) {
66
- const text = match[0];
67
- const endLine = code.slice(0, match.index + text.length).split('\n').length;
68
-
69
- // Parse @param tags — handle nested braces in types like {Array<{text: string}>}
70
- const params = [];
71
- const paramStartRegex = /@param\s+\{/g;
72
- let paramStart;
73
- while ((paramStart = paramStartRegex.exec(text)) !== null) {
74
- // Find matching closing brace (balanced)
75
- let depth = 1;
76
- let i = paramStart.index + paramStart[0].length;
77
- while (i < text.length && depth > 0) {
78
- if (text[i] === '{') depth++;
79
- else if (text[i] === '}') depth--;
80
- i++;
81
- }
82
- if (depth !== 0) continue;
83
- const type = text.slice(paramStart.index + paramStart[0].length, i - 1);
84
- // Extract param name after the closing brace
85
- const afterType = text.slice(i);
86
- const nameMatch = afterType.match(/^\s+(\[?\w+(?:\.\w+)*\]?)/);
87
- if (!nameMatch) continue;
88
- let name = nameMatch[1];
89
- // Strip [] from optional params: [opts] → opts
90
- if (name.startsWith('[')) name = name.slice(1);
91
- if (name.endsWith(']')) name = name.slice(0, -1);
92
- // Strip dotted paths: options.includeTests → skip (nested property)
93
- if (name.includes('.')) continue;
94
- params.push({ name, type });
95
- }
96
-
97
- const hasReturns = /@returns?\s/.test(text);
98
-
99
- comments.push({ text, endLine, params, hasReturns });
100
- }
101
-
102
- return comments;
103
- }
104
-
105
- /**
106
- * Find JSDoc comment before a target line
107
- * @param {Array} comments
108
- * @param {number} targetLine
109
- * @returns {Object|null}
110
- */
111
- function findJSDocBefore(comments, targetLine) {
112
- for (const comment of comments) {
113
- const gap = targetLine - comment.endLine;
114
- if (gap >= 0 && gap <= 2) return comment;
115
- }
116
- return null;
117
- }
118
-
119
- /**
120
- * Extract parameter name from AST node
121
- * @param {Object} param
122
- * @returns {string}
123
- */
124
- function extractParamName(param) {
125
- if (param.type === 'Identifier') return param.name;
126
- if (param.type === 'AssignmentPattern' && param.left.type === 'Identifier') return param.left.name;
127
- if (param.type === 'RestElement' && param.argument.type === 'Identifier') return param.argument.name;
128
- if (param.type === 'ObjectPattern') return 'options';
129
- if (param.type === 'ArrayPattern') return 'args';
130
- return 'param';
131
- }
132
-
133
- /**
134
- * Infer expected type from AST default value
135
- * @param {Object} param
136
- * @returns {string|null}
137
- */
138
- function inferTypeFromDefault(param) {
139
- if (param.type !== 'AssignmentPattern') return null;
140
- const def = param.right;
141
- if (def.type === 'Literal') {
142
- if (typeof def.value === 'string') return 'string';
143
- if (typeof def.value === 'number') return 'number';
144
- if (typeof def.value === 'boolean') return 'boolean';
145
- }
146
- if (def.type === 'ArrayExpression') return 'Array';
147
- if (def.type === 'ObjectExpression') return 'Object';
148
- return null;
149
- }
150
-
151
- /**
152
- * Check if function body has return statements with values
153
- * @param {Object} node - Function AST node
154
- * @returns {boolean}
155
- */
156
- function hasReturnValue(node) {
157
- let found = false;
158
- try {
159
- walk.simple(node.body, {
160
- ReturnStatement(ret) {
161
- if (ret.argument) found = true;
162
- },
163
- // Don't recurse into nested functions
164
- FunctionDeclaration() { },
165
- FunctionExpression() { },
166
- ArrowFunctionExpression() { },
167
- });
168
- } catch (e) { /* walk error */ }
169
- return found;
170
- }
171
-
172
- /**
173
- * Validate a function's JSDoc against its AST
174
- * @param {Object} jsdoc - Parsed JSDoc
175
- * @param {Object[]} astParams - AST param nodes
176
- * @param {Object} funcNode - AST function node
177
- * @param {string} name - Function name
178
- * @param {string} file - File path
179
- * @param {number} line - Line number
180
- * @returns {JSDocIssue[]}
181
- */
182
- function validateFunction(jsdoc, astParams, funcNode, name, file, line) {
183
- const issues = [];
184
-
185
- if (!jsdoc) return issues; // No JSDoc = handled by undocumented checker
186
-
187
- const docParams = jsdoc.params;
188
-
189
- // 1. Param count mismatch
190
- if (docParams.length !== astParams.length) {
191
- issues.push({
192
- file, line, name,
193
- severity: 'error',
194
- message: `Param count mismatch: JSDoc has ${docParams.length}, function has ${astParams.length}`,
195
- });
196
- }
197
-
198
- // 2. Param name mismatch
199
- const minLen = Math.min(docParams.length, astParams.length);
200
- for (let i = 0; i < minLen; i++) {
201
- const docName = docParams[i].name;
202
- const astName = extractParamName(astParams[i]);
203
-
204
- if (docName !== astName && astName !== 'options' && astName !== 'args' && astName !== 'param') {
205
- issues.push({
206
- file, line, name,
207
- severity: 'error',
208
- message: `Param name mismatch at position ${i}: JSDoc says "${docName}", code has "${astName}"`,
209
- });
210
- }
211
- }
212
-
213
- // 3. Missing @returns on non-void functions
214
- if (!jsdoc.hasReturns && hasReturnValue(funcNode)) {
215
- issues.push({
216
- file, line, name,
217
- severity: 'warning',
218
- message: 'Function returns a value but JSDoc has no @returns',
219
- });
220
- }
221
-
222
- // 4. Type hint inconsistency
223
- for (let i = 0; i < minLen; i++) {
224
- const docType = docParams[i].type;
225
- const inferredType = inferTypeFromDefault(astParams[i]);
226
-
227
- if (inferredType && docType && docType !== '*') {
228
- let compatible = docType.includes(inferredType);
229
- // Union types like 'a'|'b' are valid strings
230
- if (!compatible && inferredType === 'string' && docType.includes("'") && docType.includes('|')) {
231
- compatible = true;
232
- }
233
- // Type[] shorthand is a valid Array
234
- if (!compatible && inferredType === 'Array' && docType.includes('[]')) {
235
- compatible = true;
236
- }
237
- if (!compatible) {
238
- issues.push({
239
- file, line, name,
240
- severity: 'warning',
241
- message: `Type mismatch for "${docParams[i].name}": JSDoc says {${docType}}, default value suggests {${inferredType}}`,
242
- });
243
- }
244
- }
245
- }
246
-
247
- return issues;
248
- }
249
-
250
- /**
251
- * Check JSDoc consistency for a single file (per-file export for cache integration)
252
- * @param {string} code
253
- * @param {string} filePath
254
- * @returns {JSDocIssue[]}
255
- */
256
- export function checkJSDocFile(code, filePath) {
257
- const issues = [];
258
-
259
- let ast;
260
- try {
261
- ast = parse(code, { ecmaVersion: 'latest', sourceType: 'module', locations: true });
262
- } catch (e) {
263
- return issues;
264
- }
265
-
266
- const comments = extractJSDocComments(code);
267
-
268
- walk.simple(ast, {
269
- FunctionDeclaration(node) {
270
- if (!node.id) return;
271
- const jsdoc = findJSDocBefore(comments, node.loc.start.line);
272
- if (jsdoc) {
273
- issues.push(...validateFunction(jsdoc, node.params, node, node.id.name, filePath, node.loc.start.line));
274
- }
275
- },
276
-
277
- // Exported arrow/const functions
278
- VariableDeclaration(node) {
279
- for (const decl of node.declarations) {
280
- if (!decl.init) continue;
281
- const func = decl.init.type === 'ArrowFunctionExpression' || decl.init.type === 'FunctionExpression'
282
- ? decl.init : null;
283
- if (!func || !decl.id?.name) continue;
284
-
285
- const jsdoc = findJSDocBefore(comments, node.loc.start.line);
286
- if (jsdoc) {
287
- issues.push(...validateFunction(jsdoc, func.params, func, decl.id.name, filePath, node.loc.start.line));
288
- }
289
- }
290
- },
291
-
292
- ClassDeclaration(node) {
293
- const className = node.id?.name || 'Anonymous';
294
- for (const element of node.body.body) {
295
- if (element.type !== 'MethodDefinition') continue;
296
- const methodName = element.key.name || element.key.value;
297
- if (!methodName || methodName === 'constructor') continue;
298
- if (element.kind !== 'method') continue;
299
-
300
- const funcNode = element.value;
301
- const jsdoc = findJSDocBefore(comments, element.loc.start.line);
302
- if (jsdoc) {
303
- issues.push(...validateFunction(jsdoc, funcNode.params, funcNode, `${className}.${methodName}`, filePath, element.loc.start.line));
304
- }
305
- }
306
- },
307
- });
308
-
309
- return issues;
310
- }
311
-
312
- /**
313
- * Check JSDoc consistency across a directory
314
- * @param {string} dir
315
- * @returns {{ issues: JSDocIssue[], summary: { total: number, errors: number, warnings: number, byFile: Object } }}
316
- */
317
- export function checkJSDocConsistency(dir) {
318
- const resolvedDir = resolve(dir);
319
- const files = findJSFiles(dir);
320
- const allIssues = [];
321
-
322
- for (const file of files) {
323
- let content;
324
- try {
325
- content = readFileSync(file, 'utf-8');
326
- } catch (e) {
327
- continue; // File deleted between findJSFiles and read
328
- }
329
- const relPath = relative(resolvedDir, file);
330
- const issues = checkJSDocFile(content, relPath);
331
- allIssues.push(...issues);
332
- }
333
-
334
- const errors = allIssues.filter(i => i.severity === 'error').length;
335
- const warnings = allIssues.filter(i => i.severity === 'warning').length;
336
-
337
- const byFile = {};
338
- for (const issue of allIssues) {
339
- byFile[issue.file] = (byFile[issue.file] || 0) + 1;
340
- }
341
-
342
- return {
343
- issues: allIssues,
344
- summary: {
345
- total: allIssues.length,
346
- errors,
347
- warnings,
348
- byFile,
349
- },
350
- };
351
- }
@@ -1,203 +0,0 @@
1
- /**
2
- * JSDoc Generator
3
- * Auto-generates JSDoc templates from AST analysis
4
- */
5
-
6
- import { readFileSync } from 'fs';
7
- import { relative } from 'path';
8
- import { parse } from '../vendor/acorn.mjs';
9
- import * as walk from '../vendor/walk.mjs';
10
- import { getWorkspaceRoot } from './workspace.js';
11
-
12
- /**
13
- * @typedef {Object} JSDocTemplate
14
- * @property {string} name - Function/method name
15
- * @property {string} type - 'function' | 'method' | 'class'
16
- * @property {string} file
17
- * @property {number} line
18
- * @property {string} jsdoc - Generated JSDoc template
19
- */
20
-
21
- /**
22
- * Generate JSDoc for a single file
23
- * @param {string} filePath - Absolute path to file
24
- * @param {Object} [options]
25
- * @returns {JSDocTemplate[]}
26
- */
27
- export function generateJSDoc(filePath, options = {}) {
28
- const results = [];
29
-
30
- const code = readFileSync(filePath, 'utf-8');
31
- const relPath = relative(getWorkspaceRoot(), filePath);
32
-
33
- let ast;
34
- try {
35
- ast = parse(code, { ecmaVersion: 'latest', sourceType: 'module', locations: true });
36
- } catch (e) {
37
- return results;
38
- }
39
-
40
- // Check if line already has JSDoc
41
- const hasJSDocAt = (line) => {
42
- const lines = code.split('\n');
43
- // Look backwards from function line for JSDoc closing */
44
- for (let i = line - 2; i >= Math.max(0, line - 15); i--) {
45
- const trimmed = lines[i]?.trim();
46
- if (!trimmed) continue; // Skip empty lines
47
- // Found JSDoc end - look for start
48
- if (trimmed === '*/' || trimmed.endsWith('*/')) {
49
- // Now look for /** opening above
50
- for (let j = i - 1; j >= Math.max(0, i - 20); j--) {
51
- const upper = lines[j]?.trim();
52
- if (upper?.startsWith('/**')) return true;
53
- // If we hit something non-JSDoc, stop
54
- if (upper && !upper.startsWith('*')) break;
55
- }
56
- return false;
57
- }
58
- // If we hit code, stop
59
- if (!trimmed.startsWith('*') && !trimmed.startsWith('//')) break;
60
- }
61
- return false;
62
- };
63
-
64
- walk.simple(ast, {
65
- FunctionDeclaration(node) {
66
- if (!node.id) return;
67
- if (hasJSDocAt(node.loc.start.line)) return;
68
-
69
- const jsdoc = buildJSDoc({
70
- name: node.id.name,
71
- params: node.params,
72
- async: node.async,
73
- });
74
-
75
- results.push({
76
- name: node.id.name,
77
- type: 'function',
78
- file: relPath,
79
- line: node.loc.start.line,
80
- jsdoc,
81
- });
82
- },
83
-
84
- ClassDeclaration(node) {
85
- if (!node.id) return;
86
-
87
- // Check methods
88
- for (const element of node.body.body) {
89
- if (element.type === 'MethodDefinition') {
90
- const methodName = element.key.name || element.key.value;
91
-
92
- // Skip constructor, getters, setters, private
93
- if (element.kind !== 'method') continue;
94
- if (methodName.startsWith('_')) continue;
95
- if (hasJSDocAt(element.loc.start.line)) continue;
96
-
97
- const funcNode = element.value;
98
- const jsdoc = buildJSDoc({
99
- name: methodName,
100
- params: funcNode.params,
101
- async: funcNode.async,
102
- });
103
-
104
- results.push({
105
- name: `${node.id.name}.${methodName}`,
106
- type: 'method',
107
- file: relPath,
108
- line: element.loc.start.line,
109
- jsdoc,
110
- });
111
- }
112
- }
113
- },
114
- });
115
-
116
- return results;
117
- }
118
-
119
- /**
120
- * Build JSDoc string from function info
121
- * @param {Object} info
122
- * @param {string} info.name
123
- * @param {Array} info.params
124
- * @param {boolean} info.async
125
- * @returns {string}
126
- */
127
- function buildJSDoc(info) {
128
- const lines = ['/**'];
129
-
130
- // Description placeholder
131
- lines.push(` * TODO: Add description for ${info.name}`);
132
-
133
- // Parameters
134
- for (const param of info.params) {
135
- const paramName = extractParamName(param);
136
- const paramType = inferParamType(param);
137
- lines.push(` * @param {${paramType}} ${paramName}`);
138
- }
139
-
140
- // Return type
141
- lines.push(` * @returns {${info.async ? 'Promise<*>' : '*'}}`);
142
-
143
- lines.push(' */');
144
- return lines.join('\n');
145
- }
146
-
147
- /**
148
- * Extract parameter name from AST node
149
- * @param {Object} param
150
- * @returns {string}
151
- */
152
- function extractParamName(param) {
153
- if (param.type === 'Identifier') {
154
- return param.name;
155
- }
156
- if (param.type === 'AssignmentPattern' && param.left.type === 'Identifier') {
157
- return `[${param.left.name}]`; // Optional param
158
- }
159
- if (param.type === 'RestElement' && param.argument.type === 'Identifier') {
160
- return `...${param.argument.name}`;
161
- }
162
- if (param.type === 'ObjectPattern') {
163
- return 'options';
164
- }
165
- if (param.type === 'ArrayPattern') {
166
- return 'args';
167
- }
168
- return 'param';
169
- }
170
-
171
- /**
172
- * Infer parameter type from AST
173
- * @param {Object} param
174
- * @returns {string}
175
- */
176
- function inferParamType(param) {
177
- if (param.type === 'AssignmentPattern') {
178
- const defaultVal = param.right;
179
- if (defaultVal.type === 'Literal') {
180
- if (typeof defaultVal.value === 'string') return 'string';
181
- if (typeof defaultVal.value === 'number') return 'number';
182
- if (typeof defaultVal.value === 'boolean') return 'boolean';
183
- }
184
- if (defaultVal.type === 'ArrayExpression') return 'Array';
185
- if (defaultVal.type === 'ObjectExpression') return 'Object';
186
- }
187
- if (param.type === 'RestElement') return 'Array';
188
- if (param.type === 'ObjectPattern') return 'Object';
189
- if (param.type === 'ArrayPattern') return 'Array';
190
- return '*';
191
- }
192
-
193
- /**
194
- * Generate JSDoc for specific function by name
195
- * @param {string} filePath
196
- * @param {string} functionName
197
- * @param {Object} [options]
198
- * @returns {JSDocTemplate|null}
199
- */
200
- export function generateJSDocFor(filePath, functionName, options = {}) {
201
- const results = generateJSDoc(filePath, options);
202
- return results.find(r => r.name === functionName || r.name.endsWith(`.${functionName}`)) || null;
203
- }