project-graph-mcp 1.3.0 → 2.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.
Files changed (113) hide show
  1. package/README.md +223 -17
  2. package/{AGENT_ROLE.md → docs/examples/AGENT_ROLE.md} +87 -30
  3. package/{AGENT_ROLE_MINIMAL.md → docs/examples/AGENT_ROLE_MINIMAL.md} +23 -8
  4. package/package.json +12 -8
  5. package/src/.project-graph-cache.json +1 -0
  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/ai-context.js +7 -0
  23. package/src/compact/compact.js +18 -0
  24. package/src/compact/compress.js +13 -0
  25. package/src/compact/ctx-to-jsdoc.js +29 -0
  26. package/src/compact/doc-dialect.js +30 -0
  27. package/src/compact/expand.js +37 -0
  28. package/src/compact/framework-references.js +5 -0
  29. package/src/compact/instructions.js +3 -0
  30. package/src/compact/mode-config.js +8 -0
  31. package/src/compact/validate-pipeline.js +9 -0
  32. package/src/core/event-bus.js +9 -0
  33. package/src/core/filters.js +14 -0
  34. package/src/core/graph-builder.js +12 -0
  35. package/src/core/parser.js +31 -0
  36. package/src/core/workspace.js +8 -0
  37. package/src/lang/lang-go.js +17 -0
  38. package/src/lang/lang-python.js +12 -0
  39. package/src/lang/lang-sql.js +23 -0
  40. package/src/lang/lang-typescript.js +9 -0
  41. package/src/lang/lang-utils.js +4 -0
  42. package/src/mcp/mcp-server.js +17 -0
  43. package/src/mcp/tool-defs.js +3 -0
  44. package/src/mcp/tools.js +25 -0
  45. package/src/network/backend-lifecycle.js +19 -0
  46. package/src/network/backend.js +5 -0
  47. package/src/network/local-gateway.js +23 -0
  48. package/src/network/mdns.js +13 -0
  49. package/src/network/server.js +10 -0
  50. package/src/network/web-server.js +34 -0
  51. package/vendor/terser.mjs +49 -0
  52. package/web/.project-graph-cache.json +1 -0
  53. package/web/app.js +16 -0
  54. package/web/components/code-block.js +3 -0
  55. package/web/components/quick-open.js +5 -0
  56. package/web/dashboard-state.js +3 -0
  57. package/web/dashboard.html +27 -0
  58. package/web/dashboard.js +8 -0
  59. package/web/highlight.js +13 -0
  60. package/web/index.html +35 -0
  61. package/web/panels/ActionBoard/ActionBoard.css.js +1 -0
  62. package/web/panels/ActionBoard/ActionBoard.js +4 -0
  63. package/web/panels/ActionBoard/ActionBoard.tpl.js +1 -0
  64. package/web/panels/EventItem/EventItem.css.js +1 -0
  65. package/web/panels/EventItem/EventItem.js +4 -0
  66. package/web/panels/EventItem/EventItem.tpl.js +1 -0
  67. package/web/panels/ProjectItem/ProjectItem.css.js +1 -0
  68. package/web/panels/ProjectItem/ProjectItem.js +5 -0
  69. package/web/panels/ProjectItem/ProjectItem.tpl.js +1 -0
  70. package/web/panels/ProjectList/ProjectList.css.js +1 -0
  71. package/web/panels/ProjectList/ProjectList.js +4 -0
  72. package/web/panels/ProjectList/ProjectList.tpl.js +1 -0
  73. package/web/panels/SettingsPanel/.project-graph-cache.json +1 -0
  74. package/web/panels/SettingsPanel/SettingsPanel.css.js +1 -0
  75. package/web/panels/SettingsPanel/SettingsPanel.js +7 -0
  76. package/web/panels/SettingsPanel/SettingsPanel.tpl.js +1 -0
  77. package/web/panels/code-viewer.js +5 -0
  78. package/web/panels/ctx-panel.js +4 -0
  79. package/web/panels/dep-graph.js +6 -0
  80. package/web/panels/file-tree.js +188 -0
  81. package/web/panels/health-panel.js +3 -0
  82. package/web/panels/live-monitor.js +3 -0
  83. package/web/state.js +17 -0
  84. package/web/style.css +157 -0
  85. package/references/symbiote-3x.md +0 -834
  86. package/src/cli-handlers.js +0 -140
  87. package/src/cli.js +0 -83
  88. package/src/complexity.js +0 -223
  89. package/src/custom-rules.js +0 -583
  90. package/src/db-analysis.js +0 -194
  91. package/src/dead-code.js +0 -468
  92. package/src/filters.js +0 -227
  93. package/src/framework-references.js +0 -177
  94. package/src/full-analysis.js +0 -174
  95. package/src/graph-builder.js +0 -299
  96. package/src/instructions.js +0 -175
  97. package/src/jsdoc-generator.js +0 -214
  98. package/src/lang-go.js +0 -285
  99. package/src/lang-python.js +0 -197
  100. package/src/lang-sql.js +0 -309
  101. package/src/lang-typescript.js +0 -190
  102. package/src/lang-utils.js +0 -124
  103. package/src/large-files.js +0 -162
  104. package/src/mcp-server.js +0 -468
  105. package/src/outdated-patterns.js +0 -295
  106. package/src/parser.js +0 -452
  107. package/src/server.js +0 -28
  108. package/src/similar-functions.js +0 -278
  109. package/src/test-annotations.js +0 -301
  110. package/src/tool-defs.js +0 -525
  111. package/src/tools.js +0 -470
  112. package/src/undocumented.js +0 -260
  113. package/src/workspace.js +0 -70
@@ -1,299 +0,0 @@
1
- /**
2
- * Graph Builder - Creates minified project graph from parsed data
3
- */
4
-
5
- /**
6
- * @typedef {Object} GraphNode
7
- * @property {string} t - type (class/func)
8
- * @property {string} [x] - extends
9
- * @property {string[]} [m] - methods
10
- * @property {string[]} [$] - properties (init$)
11
- * @property {string[]} [i] - imports
12
- * @property {string[]} [→] - calls (outgoing)
13
- * @property {string[]} [←] - usedBy (incoming)
14
- * @property {string} [f] - source file path
15
- * @property {boolean} [e] - exported flag (functions)
16
- */
17
-
18
- /**
19
- * @typedef {Object} Graph
20
- * @property {number} v - version
21
- * @property {Object<string, string>} legend - minified name → full name
22
- * @property {Object<string, string>} reverseLegend - full name → minified
23
- * @property {Object} stats - { files, classes, functions, tables }
24
- * @property {Object<string, GraphNode>} nodes
25
- * @property {Array<[string, string, string]>} edges - [from, type, to] where type is →, R→, or W→
26
- * @property {string[]} orphans
27
- * @property {Object<string, string[]>} duplicates
28
- * @property {string[]} files - list of parsed file paths
29
- */
30
-
31
- /**
32
- * Create minified legend from names
33
- * Strategy: Use camelCase initials + suffix if collision
34
- * @param {string[]} names
35
- * @returns {Object<string, string>}
36
- */
37
- export function minifyLegend(names) {
38
- const legend = {};
39
- const used = new Set();
40
-
41
- for (const name of names) {
42
- let short = createShortName(name);
43
- let suffix = 1;
44
-
45
- while (used.has(short)) {
46
- short = createShortName(name) + suffix;
47
- suffix++;
48
- }
49
-
50
- used.add(short);
51
- legend[name] = short;
52
- }
53
-
54
- return legend;
55
- }
56
-
57
- /**
58
- * Create short name from full name
59
- * SymNode → SN, togglePin → tP, autoArrange → aA
60
- * @param {string} name
61
- * @returns {string}
62
- */
63
- function createShortName(name) {
64
- // For PascalCase: extract uppercase letters
65
- const upperOnly = name.replace(/[a-z]/g, '');
66
- if (upperOnly.length >= 2) {
67
- return upperOnly.slice(0, 3);
68
- }
69
-
70
- // For camelCase: first letter + next uppercase
71
- const firstUpper = name.match(/[A-Z]/g);
72
- if (firstUpper && firstUpper.length > 0) {
73
- return name[0].toLowerCase() + firstUpper[0];
74
- }
75
-
76
- // Fallback: first 2 letters
77
- return name.slice(0, 2);
78
- }
79
-
80
- /**
81
- * Build graph from parsed project data
82
- * @param {import('./parser.js').ParseResult} parsed
83
- * @returns {Graph}
84
- */
85
- export function buildGraph(parsed) {
86
- // Collect all names for legend
87
- const classes = parsed.classes || [];
88
- const functions = parsed.functions || [];
89
-
90
- const allNames = [
91
- ...classes.map(c => c.name),
92
- ...functions.map(f => f.name),
93
- ...classes.flatMap(c => c.methods || []),
94
- ];
95
-
96
- const legend = minifyLegend([...new Set(allNames)]);
97
- const reverseLegend = Object.fromEntries(
98
- Object.entries(legend).map(([k, v]) => [v, k])
99
- );
100
-
101
- const graph = {
102
- v: 1,
103
- legend,
104
- reverseLegend,
105
- stats: {
106
- files: (parsed.files || []).length,
107
- classes: classes.length,
108
- functions: functions.length,
109
- tables: (parsed.tables || []).length,
110
- },
111
- nodes: {},
112
- edges: [],
113
- orphans: [],
114
- duplicates: {},
115
- files: parsed.files || [],
116
- };
117
-
118
- // Build class nodes
119
- for (const cls of classes) {
120
- const shortName = legend[cls.name];
121
- graph.nodes[shortName] = {
122
- t: 'C',
123
- x: cls.extends || undefined,
124
- m: (cls.methods || []).map(m => legend[m] || m),
125
- $: (cls.properties || []).length ? cls.properties : undefined,
126
- i: cls.imports?.length ? cls.imports : undefined,
127
- f: cls.file || undefined,
128
- };
129
-
130
- // Build edges from calls
131
- for (const call of cls.calls || []) {
132
- if (call.includes('.')) {
133
- // Class.method() pattern
134
- const [target, method] = call.split('.');
135
- if (legend[target]) {
136
- const edge = [shortName, '→', `${legend[target]}.${legend[method] || method}`];
137
- graph.edges.push(edge);
138
- }
139
- } else {
140
- // Standalone function call
141
- if (legend[call]) {
142
- const edge = [shortName, '→', legend[call]];
143
- graph.edges.push(edge);
144
- }
145
- }
146
- }
147
- }
148
-
149
- // Build function nodes
150
- for (const func of functions) {
151
- const shortName = legend[func.name];
152
- graph.nodes[shortName] = {
153
- t: 'F',
154
- e: func.exported,
155
- f: func.file || undefined,
156
- };
157
-
158
- // Build DB edges from function SQL reads/writes
159
- for (const table of func.dbReads || []) {
160
- graph.edges.push([shortName, 'R→', table]);
161
- }
162
- for (const table of func.dbWrites || []) {
163
- graph.edges.push([shortName, 'W→', table]);
164
- }
165
- }
166
-
167
- // Build DB edges from class SQL reads/writes
168
- for (const cls of classes) {
169
- const shortName = legend[cls.name];
170
- for (const table of cls.dbReads || []) {
171
- graph.edges.push([shortName, 'R→', table]);
172
- }
173
- for (const table of cls.dbWrites || []) {
174
- graph.edges.push([shortName, 'W→', table]);
175
- }
176
- }
177
-
178
- // Build table nodes from parsed SQL files
179
- for (const table of parsed.tables || []) {
180
- graph.nodes[table.name] = {
181
- t: 'T',
182
- cols: table.columns.map(c => c.name),
183
- f: table.file || undefined,
184
- };
185
- }
186
-
187
- // Detect orphans (nodes with no incoming edges)
188
- const hasIncoming = new Set();
189
- for (const edge of graph.edges) {
190
- const target = edge[2].split('.')[0];
191
- hasIncoming.add(target);
192
- }
193
-
194
- for (const name of Object.keys(graph.nodes)) {
195
- if (!hasIncoming.has(name) && graph.nodes[name].t === 'F' && !graph.nodes[name].e) {
196
- graph.orphans.push(reverseLegend[name]);
197
- }
198
- }
199
-
200
- // Detect duplicates (same method name in multiple classes)
201
- const methodLocations = Object.create(null);
202
- for (const cls of classes) {
203
- for (const method of cls.methods || []) {
204
- if (!methodLocations[method]) {
205
- methodLocations[method] = [];
206
- }
207
- methodLocations[method].push(`${cls.name}:${cls.line}`);
208
- }
209
- }
210
-
211
- for (const [method, locations] of Object.entries(methodLocations)) {
212
- if (locations.length > 1) {
213
- graph.duplicates[method] = locations;
214
- }
215
- }
216
-
217
- return graph;
218
- }
219
-
220
- /**
221
- * Create compact skeleton (minimal tokens)
222
- * @param {Graph} graph
223
- * @returns {Object}
224
- */
225
- export function createSkeleton(graph) {
226
- const legend = {};
227
- const nodes = {};
228
-
229
- // Build class nodes with file path
230
- // graph.legend = {fullName → shortName}
231
- for (const [full, short] of Object.entries(graph.legend)) {
232
- const node = graph.nodes[short];
233
- if (!node) continue;
234
-
235
- if (node.t === 'C') {
236
- // Skip empty classes (0 methods, 0 props)
237
- const methodCount = node.m?.length || 0;
238
- const propCount = node.$?.length || 0;
239
- if (methodCount === 0 && propCount === 0) continue;
240
-
241
- legend[short] = full;
242
- const entry = { m: methodCount };
243
- if (propCount > 0) entry.$ = propCount;
244
- if (node.f) entry.f = node.f;
245
- nodes[short] = entry;
246
- }
247
- // Skip Table nodes (T) — they only appear in dedicated DB tools
248
- }
249
-
250
- // Build exported functions grouped by file: { "file.js": ["shortName1", ...] }
251
- // Also add function names to legend
252
- const exportsByFile = {};
253
- for (const [full, short] of Object.entries(graph.legend)) {
254
- const node = graph.nodes[short];
255
- if (node?.t === 'F' && node.e) {
256
- legend[short] = full;
257
- const file = node.f || '?';
258
- if (!exportsByFile[file]) exportsByFile[file] = [];
259
- exportsByFile[file].push(short);
260
- }
261
- }
262
-
263
- // Build file tree grouped by directory (only files not covered by n/X)
264
- const coveredFiles = new Set();
265
- for (const v of Object.values(nodes)) {
266
- if (v.f) coveredFiles.add(v.f);
267
- }
268
- for (const file of Object.keys(exportsByFile)) {
269
- coveredFiles.add(file);
270
- }
271
-
272
- const fileTree = {};
273
- for (const filePath of graph.files || []) {
274
- if (coveredFiles.has(filePath)) continue;
275
- const lastSlash = filePath.lastIndexOf('/');
276
- const dir = lastSlash >= 0 ? filePath.slice(0, lastSlash + 1) : './';
277
- const file = lastSlash >= 0 ? filePath.slice(lastSlash + 1) : filePath;
278
- if (!fileTree[dir]) fileTree[dir] = [];
279
- fileTree[dir].push(file);
280
- }
281
-
282
- const result = {
283
- v: graph.v,
284
- L: legend,
285
- s: graph.stats,
286
- n: nodes,
287
- X: exportsByFile,
288
- e: graph.edges.length,
289
- o: graph.orphans.length,
290
- d: Object.keys(graph.duplicates).length,
291
- };
292
-
293
- // Only add uncovered files if there are any
294
- if (Object.keys(fileTree).length > 0) {
295
- result.f = fileTree;
296
- }
297
-
298
- return result;
299
- }
@@ -1,175 +0,0 @@
1
- /**
2
- * Project Guidelines and Instructions for AI Agents
3
- */
4
-
5
- export const AGENT_INSTRUCTIONS = `
6
- # 🤖 Project Guidelines for AI Agents
7
-
8
- ## 1. Architecture Standards (Symbiote.js)
9
- - **Component Structure**: Always use Triple-File Partitioning for components:
10
- - \`MyComponent.js\`: Class logic (extends Symbiote)
11
- - \`MyComponent.tpl.js\`: HTML template (export template)
12
- - \`MyComponent.css.js\`: CSS styles (export rootStyles/shadowStyles)
13
- - **State Management**: Use \`this.init$\` for local state and \`this.sub()\` for reactivity.
14
- - **Directives**: Use \`itemize\` for lists, \`js-d-kit\` for static generation.
15
-
16
- ## 2. Test Annotations (@test/@expect)
17
- Universal verification checklist system. Works for **any** test type.
18
-
19
- ### Syntax
20
- \`\`\`javascript
21
- /**
22
- * Method description
23
- *
24
- * @test {type}: {description}
25
- * @expect {type}: {description}
26
- */
27
- async myMethod() { ... }
28
- \`\`\`
29
-
30
- ### @test Types by Category
31
-
32
- #### 🌐 Browser / UI
33
- | Type | Description | Example |
34
- |------|-------------|---------|
35
- | \`click\` | Click element | \`@test click: Click submit button\` |
36
- | \`key\` | Keyboard input | \`@test key: Press Enter\` |
37
- | \`drag\` | Drag and drop | \`@test drag: Drag item to list\` |
38
- | \`type\` | Text input | \`@test type: Enter email in field\` |
39
- | \`scroll\` | Scroll action | \`@test scroll: Scroll to bottom\` |
40
- | \`hover\` | Mouse hover | \`@test hover: Hover over menu\` |
41
-
42
- #### 🔌 API / Function
43
- | Type | Description | Example |
44
- |------|-------------|---------|
45
- | \`request\` | HTTP request | \`@test request: POST /api/users\` |
46
- | \`call\` | Function call | \`@test call: Call with valid params\` |
47
- | \`invoke\` | Method invoke | \`@test invoke: Trigger event\` |
48
- | \`mock\` | Mock setup | \`@test mock: Mock external service\` |
49
-
50
- #### 💻 CLI / Process
51
- | Type | Description | Example |
52
- |------|-------------|---------|
53
- | \`run\` | Run command | \`@test run: Run with --help flag\` |
54
- | \`exec\` | Execute script | \`@test exec: Execute build script\` |
55
- | \`spawn\` | Spawn process | \`@test spawn: Start server\` |
56
- | \`input\` | Stdin input | \`@test input: Enter password\` |
57
-
58
- #### 🔗 Integration / System
59
- | Type | Description | Example |
60
- |------|-------------|---------|
61
- | \`setup\` | Test setup | \`@test setup: Create test database\` |
62
- | \`action\` | Main action | \`@test action: Run migration\` |
63
- | \`teardown\` | Cleanup | \`@test teardown: Remove temp files\` |
64
- | \`wait\` | Wait condition | \`@test wait: Wait for DB connection\` |
65
-
66
- ### @expect Types by Category
67
-
68
- #### 🌐 Browser / UI
69
- | Type | Description | Example |
70
- |------|-------------|---------|
71
- | \`attr\` | Attribute check | \`@expect attr: disabled attribute set\` |
72
- | \`visual\` | Visual change | \`@expect visual: Button turns green\` |
73
- | \`element\` | Element exists | \`@expect element: Modal appears\` |
74
- | \`text\` | Text content | \`@expect text: Shows "Success"\` |
75
-
76
- #### 🔌 API / Function
77
- | Type | Description | Example |
78
- |------|-------------|---------|
79
- | \`status\` | HTTP status | \`@expect status: 201 Created\` |
80
- | \`body\` | Response body | \`@expect body: Contains user ID\` |
81
- | \`headers\` | Response headers | \`@expect headers: Content-Type JSON\` |
82
- | \`error\` | Error thrown | \`@expect error: Throws ValidationError\` |
83
-
84
- #### 💻 CLI / Process
85
- | Type | Description | Example |
86
- |------|-------------|---------|
87
- | \`output\` | Stdout content | \`@expect output: Prints version\` |
88
- | \`exitcode\` | Exit code | \`@expect exitcode: Returns 0\` |
89
- | \`file\` | File created | \`@expect file: Creates config.json\` |
90
- | \`stderr\` | Stderr content | \`@expect stderr: No errors\` |
91
-
92
- #### 🔗 Integration / System
93
- | Type | Description | Example |
94
- |------|-------------|---------|
95
- | \`state\` | State change | \`@expect state: User logged in\` |
96
- | \`log\` | Log entry | \`@expect log: Info message logged\` |
97
- | \`event\` | Event fired | \`@expect event: 'updated' emitted\` |
98
- | \`db\` | Database change | \`@expect db: Row inserted\` |
99
-
100
- ### Full Example
101
- \`\`\`javascript
102
- /**
103
- * Create new user via API
104
- *
105
- * @test request: POST /api/users with valid data
106
- * @test call: Validate email format
107
- *
108
- * @expect status: 201 Created
109
- * @expect body: Contains user ID and email
110
- * @expect db: User row created in database
111
- * @expect event: 'user.created' event emitted
112
- */
113
- async createUser(data) {
114
- // ...
115
- }
116
- \`\`\`
117
-
118
- ## 3. General Coding Rules
119
- - **ESM Only**: Use \`import\` / \`export\`. No \`require\`.
120
- - **No Dependencies**: Avoid adding new npm packages unless critical.
121
- - **Comments**: Write clear JSDoc for all public methods.
122
- - **Async/Await**: Prefer async/await over promises.
123
-
124
- ## 4. MCP Tools Usage
125
- - **Graph**: Use \`get_skeleton\` first to map the codebase.
126
- - **Deep Dive**: Use \`expand\` to read class details.
127
- - **Tests**: Use \`get_pending_tests\` to see what needs verification.
128
- - **Guidelines**: Use \`get_agent_instructions\` to refresh these rules.
129
-
130
- ## 5. Custom Rules System
131
- Configurable code analysis with auto-detection.
132
-
133
- ### Available Tools
134
- - \`get_custom_rules\`: List all rulesets and their rules
135
- - \`set_custom_rule\`: Add or update a rule in a ruleset
136
- - \`check_custom_rules\`: Run analysis (auto-detects applicable rulesets)
137
-
138
- ### Auto-Detection
139
- Rulesets are applied automatically based on:
140
- 1. \`package.json\` dependencies
141
- 2. Import patterns in source code
142
- 3. Code patterns (e.g., \`extends Symbiote\`)
143
-
144
- ### Creating New Rules
145
- Use \`set_custom_rule\` to add framework-specific rules:
146
- \`\`\`json
147
- {
148
- "ruleSet": "my-framework-2x",
149
- "rule": {
150
- "id": "my-rule-id",
151
- "name": "Rule Name",
152
- "description": "What this rule checks",
153
- "pattern": "badPattern",
154
- "patternType": "string",
155
- "replacement": "Use goodPattern instead",
156
- "severity": "warning",
157
- "filePattern": "*.js",
158
- "docs": "https://docs.example.com/rule"
159
- }
160
- }
161
- \`\`\`
162
-
163
- ### Severity Levels
164
- - \`error\`: Critical issues that must be fixed
165
- - \`warning\`: Important but not blocking
166
- - \`info\`: Suggestions and best practices
167
- `;
168
-
169
- /**
170
- * Get agent instructions
171
- * @returns {string}
172
- */
173
- export function getInstructions() {
174
- return AGENT_INSTRUCTIONS;
175
- }
@@ -1,214 +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
- * @param {boolean} [options.includeTests=true] - Include @test/@expect placeholders
26
- * @returns {JSDocTemplate[]}
27
- */
28
- export function generateJSDoc(filePath, options = {}) {
29
- const includeTests = options.includeTests !== false;
30
- const results = [];
31
-
32
- const code = readFileSync(filePath, 'utf-8');
33
- const relPath = relative(getWorkspaceRoot(), filePath);
34
-
35
- let ast;
36
- try {
37
- ast = parse(code, { ecmaVersion: 'latest', sourceType: 'module', locations: true });
38
- } catch (e) {
39
- return results;
40
- }
41
-
42
- // Check if line already has JSDoc
43
- const hasJSDocAt = (line) => {
44
- const lines = code.split('\n');
45
- // Look backwards from function line for JSDoc closing */
46
- for (let i = line - 2; i >= Math.max(0, line - 15); i--) {
47
- const trimmed = lines[i]?.trim();
48
- if (!trimmed) continue; // Skip empty lines
49
- // Found JSDoc end - look for start
50
- if (trimmed === '*/' || trimmed.endsWith('*/')) {
51
- // Now look for /** opening above
52
- for (let j = i - 1; j >= Math.max(0, i - 20); j--) {
53
- const upper = lines[j]?.trim();
54
- if (upper?.startsWith('/**')) return true;
55
- // If we hit something non-JSDoc, stop
56
- if (upper && !upper.startsWith('*')) break;
57
- }
58
- return false;
59
- }
60
- // If we hit code, stop
61
- if (!trimmed.startsWith('*') && !trimmed.startsWith('//')) break;
62
- }
63
- return false;
64
- };
65
-
66
- walk.simple(ast, {
67
- FunctionDeclaration(node) {
68
- if (!node.id) return;
69
- if (hasJSDocAt(node.loc.start.line)) return;
70
-
71
- const jsdoc = buildJSDoc({
72
- name: node.id.name,
73
- params: node.params,
74
- async: node.async,
75
- includeTests,
76
- });
77
-
78
- results.push({
79
- name: node.id.name,
80
- type: 'function',
81
- file: relPath,
82
- line: node.loc.start.line,
83
- jsdoc,
84
- });
85
- },
86
-
87
- ClassDeclaration(node) {
88
- if (!node.id) return;
89
-
90
- // Check methods
91
- for (const element of node.body.body) {
92
- if (element.type === 'MethodDefinition') {
93
- const methodName = element.key.name || element.key.value;
94
-
95
- // Skip constructor, getters, setters, private
96
- if (element.kind !== 'method') continue;
97
- if (methodName.startsWith('_')) continue;
98
- if (hasJSDocAt(element.loc.start.line)) continue;
99
-
100
- const funcNode = element.value;
101
- const jsdoc = buildJSDoc({
102
- name: methodName,
103
- params: funcNode.params,
104
- async: funcNode.async,
105
- includeTests,
106
- });
107
-
108
- results.push({
109
- name: `${node.id.name}.${methodName}`,
110
- type: 'method',
111
- file: relPath,
112
- line: element.loc.start.line,
113
- jsdoc,
114
- });
115
- }
116
- }
117
- },
118
- });
119
-
120
- return results;
121
- }
122
-
123
- /**
124
- * Build JSDoc string from function info
125
- * @param {Object} info
126
- * @param {string} info.name
127
- * @param {Array} info.params
128
- * @param {boolean} info.async
129
- * @param {boolean} info.includeTests
130
- * @returns {string}
131
- */
132
- function buildJSDoc(info) {
133
- const lines = ['/**'];
134
-
135
- // Description placeholder
136
- lines.push(` * TODO: Add description for ${info.name}`);
137
-
138
- // Parameters
139
- for (const param of info.params) {
140
- const paramName = extractParamName(param);
141
- const paramType = inferParamType(param);
142
- lines.push(` * @param {${paramType}} ${paramName}`);
143
- }
144
-
145
- // Return type
146
- lines.push(` * @returns {${info.async ? 'Promise<*>' : '*'}}`);
147
-
148
- // Test annotations (Agentic Verification)
149
- if (info.includeTests) {
150
- lines.push(` * @test TODO: describe test scenario`);
151
- lines.push(` * @expect TODO: expected result`);
152
- }
153
-
154
- lines.push(' */');
155
- return lines.join('\n');
156
- }
157
-
158
- /**
159
- * Extract parameter name from AST node
160
- * @param {Object} param
161
- * @returns {string}
162
- */
163
- function extractParamName(param) {
164
- if (param.type === 'Identifier') {
165
- return param.name;
166
- }
167
- if (param.type === 'AssignmentPattern' && param.left.type === 'Identifier') {
168
- return `[${param.left.name}]`; // Optional param
169
- }
170
- if (param.type === 'RestElement' && param.argument.type === 'Identifier') {
171
- return `...${param.argument.name}`;
172
- }
173
- if (param.type === 'ObjectPattern') {
174
- return 'options';
175
- }
176
- if (param.type === 'ArrayPattern') {
177
- return 'args';
178
- }
179
- return 'param';
180
- }
181
-
182
- /**
183
- * Infer parameter type from AST
184
- * @param {Object} param
185
- * @returns {string}
186
- */
187
- function inferParamType(param) {
188
- if (param.type === 'AssignmentPattern') {
189
- const defaultVal = param.right;
190
- if (defaultVal.type === 'Literal') {
191
- if (typeof defaultVal.value === 'string') return 'string';
192
- if (typeof defaultVal.value === 'number') return 'number';
193
- if (typeof defaultVal.value === 'boolean') return 'boolean';
194
- }
195
- if (defaultVal.type === 'ArrayExpression') return 'Array';
196
- if (defaultVal.type === 'ObjectExpression') return 'Object';
197
- }
198
- if (param.type === 'RestElement') return 'Array';
199
- if (param.type === 'ObjectPattern') return 'Object';
200
- if (param.type === 'ArrayPattern') return 'Array';
201
- return '*';
202
- }
203
-
204
- /**
205
- * Generate JSDoc for specific function by name
206
- * @param {string} filePath
207
- * @param {string} functionName
208
- * @param {Object} [options]
209
- * @returns {JSDocTemplate|null}
210
- */
211
- export function generateJSDocFor(filePath, functionName, options = {}) {
212
- const results = generateJSDoc(filePath, options);
213
- return results.find(r => r.name === functionName || r.name.endsWith(`.${functionName}`)) || null;
214
- }