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.
- package/README.md +223 -17
- package/{AGENT_ROLE.md → docs/examples/AGENT_ROLE.md} +87 -30
- package/{AGENT_ROLE_MINIMAL.md → docs/examples/AGENT_ROLE_MINIMAL.md} +23 -8
- package/package.json +12 -8
- package/src/.project-graph-cache.json +1 -0
- package/src/analysis/analysis-cache.js +7 -0
- package/src/analysis/complexity.js +14 -0
- package/src/analysis/custom-rules.js +36 -0
- package/src/analysis/db-analysis.js +9 -0
- package/src/analysis/dead-code.js +19 -0
- package/src/analysis/full-analysis.js +18 -0
- package/src/analysis/jsdoc-checker.js +24 -0
- package/src/analysis/jsdoc-generator.js +10 -0
- package/src/analysis/large-files.js +11 -0
- package/src/analysis/outdated-patterns.js +12 -0
- package/src/analysis/similar-functions.js +16 -0
- package/src/analysis/test-annotations.js +21 -0
- package/src/analysis/type-checker.js +8 -0
- package/src/analysis/undocumented.js +14 -0
- package/src/cli/cli-handlers.js +4 -0
- package/src/cli/cli.js +5 -0
- package/src/compact/ai-context.js +7 -0
- package/src/compact/compact.js +18 -0
- package/src/compact/compress.js +13 -0
- package/src/compact/ctx-to-jsdoc.js +29 -0
- package/src/compact/doc-dialect.js +30 -0
- package/src/compact/expand.js +37 -0
- package/src/compact/framework-references.js +5 -0
- package/src/compact/instructions.js +3 -0
- package/src/compact/mode-config.js +8 -0
- package/src/compact/validate-pipeline.js +9 -0
- package/src/core/event-bus.js +9 -0
- package/src/core/filters.js +14 -0
- package/src/core/graph-builder.js +12 -0
- package/src/core/parser.js +31 -0
- package/src/core/workspace.js +8 -0
- package/src/lang/lang-go.js +17 -0
- package/src/lang/lang-python.js +12 -0
- package/src/lang/lang-sql.js +23 -0
- package/src/lang/lang-typescript.js +9 -0
- package/src/lang/lang-utils.js +4 -0
- package/src/mcp/mcp-server.js +17 -0
- package/src/mcp/tool-defs.js +3 -0
- package/src/mcp/tools.js +25 -0
- package/src/network/backend-lifecycle.js +19 -0
- package/src/network/backend.js +5 -0
- package/src/network/local-gateway.js +23 -0
- package/src/network/mdns.js +13 -0
- package/src/network/server.js +10 -0
- package/src/network/web-server.js +34 -0
- package/vendor/terser.mjs +49 -0
- package/web/.project-graph-cache.json +1 -0
- package/web/app.js +16 -0
- package/web/components/code-block.js +3 -0
- package/web/components/quick-open.js +5 -0
- package/web/dashboard-state.js +3 -0
- package/web/dashboard.html +27 -0
- package/web/dashboard.js +8 -0
- package/web/highlight.js +13 -0
- package/web/index.html +35 -0
- package/web/panels/ActionBoard/ActionBoard.css.js +1 -0
- package/web/panels/ActionBoard/ActionBoard.js +4 -0
- package/web/panels/ActionBoard/ActionBoard.tpl.js +1 -0
- package/web/panels/EventItem/EventItem.css.js +1 -0
- package/web/panels/EventItem/EventItem.js +4 -0
- package/web/panels/EventItem/EventItem.tpl.js +1 -0
- package/web/panels/ProjectItem/ProjectItem.css.js +1 -0
- package/web/panels/ProjectItem/ProjectItem.js +5 -0
- package/web/panels/ProjectItem/ProjectItem.tpl.js +1 -0
- package/web/panels/ProjectList/ProjectList.css.js +1 -0
- package/web/panels/ProjectList/ProjectList.js +4 -0
- package/web/panels/ProjectList/ProjectList.tpl.js +1 -0
- package/web/panels/SettingsPanel/.project-graph-cache.json +1 -0
- package/web/panels/SettingsPanel/SettingsPanel.css.js +1 -0
- package/web/panels/SettingsPanel/SettingsPanel.js +7 -0
- package/web/panels/SettingsPanel/SettingsPanel.tpl.js +1 -0
- package/web/panels/code-viewer.js +5 -0
- package/web/panels/ctx-panel.js +4 -0
- package/web/panels/dep-graph.js +6 -0
- package/web/panels/file-tree.js +188 -0
- package/web/panels/health-panel.js +3 -0
- package/web/panels/live-monitor.js +3 -0
- package/web/state.js +17 -0
- package/web/style.css +157 -0
- package/references/symbiote-3x.md +0 -834
- package/src/cli-handlers.js +0 -140
- package/src/cli.js +0 -83
- package/src/complexity.js +0 -223
- package/src/custom-rules.js +0 -583
- package/src/db-analysis.js +0 -194
- package/src/dead-code.js +0 -468
- package/src/filters.js +0 -227
- package/src/framework-references.js +0 -177
- package/src/full-analysis.js +0 -174
- package/src/graph-builder.js +0 -299
- package/src/instructions.js +0 -175
- package/src/jsdoc-generator.js +0 -214
- package/src/lang-go.js +0 -285
- package/src/lang-python.js +0 -197
- package/src/lang-sql.js +0 -309
- package/src/lang-typescript.js +0 -190
- package/src/lang-utils.js +0 -124
- package/src/large-files.js +0 -162
- package/src/mcp-server.js +0 -468
- package/src/outdated-patterns.js +0 -295
- package/src/parser.js +0 -452
- package/src/server.js +0 -28
- package/src/similar-functions.js +0 -278
- package/src/test-annotations.js +0 -301
- package/src/tool-defs.js +0 -525
- package/src/tools.js +0 -470
- package/src/undocumented.js +0 -260
- package/src/workspace.js +0 -70
package/src/graph-builder.js
DELETED
|
@@ -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
|
-
}
|
package/src/instructions.js
DELETED
|
@@ -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
|
-
}
|
package/src/jsdoc-generator.js
DELETED
|
@@ -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
|
-
}
|