project-graph-mcp 1.3.0 → 1.5.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/AGENT_ROLE.md +87 -30
- package/AGENT_ROLE_MINIMAL.md +23 -8
- package/README.md +100 -14
- package/package.json +1 -1
- package/src/.project-graph-cache.json +1 -0
- package/src/ai-context.js +113 -0
- package/src/analysis-cache.js +155 -0
- package/src/cli-handlers.js +131 -0
- package/src/cli.js +14 -2
- package/src/compact.js +207 -0
- package/src/complexity.js +21 -7
- package/src/compress.js +319 -0
- package/src/ctx-to-jsdoc.js +514 -0
- package/src/custom-rules.js +1 -0
- package/src/doc-dialect.js +716 -0
- package/src/full-analysis.js +307 -11
- package/src/instructions.js +3 -105
- package/src/jsdoc-checker.js +351 -0
- package/src/jsdoc-generator.js +0 -11
- package/src/large-files.js +1 -0
- package/src/mcp-server.js +208 -1
- package/src/mode-config.js +127 -0
- package/src/outdated-patterns.js +1 -0
- package/src/parser.js +223 -13
- package/src/similar-functions.js +1 -0
- package/src/test-annotations.js +203 -181
- package/src/tool-defs.js +270 -2
- package/src/tools.js +1 -1
- package/src/type-checker.js +188 -0
- package/src/undocumented.js +11 -12
- package/src/workspace.js +1 -1
- package/vendor/terser.mjs +49 -0
package/src/full-analysis.js
CHANGED
|
@@ -1,15 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Full Analysis - Comprehensive Code Health Report
|
|
3
3
|
* Runs all analysis tools and generates a health score
|
|
4
|
+
*
|
|
5
|
+
* Uses incremental caching for per-file metrics (complexity, undocumented, jsdocConsistency).
|
|
6
|
+
* Cross-file metrics (dead code, similarity) always run dynamically.
|
|
4
7
|
*/
|
|
5
8
|
|
|
9
|
+
import { readFileSync, readdirSync, statSync } from 'fs';
|
|
10
|
+
import { join, relative, resolve } from 'path';
|
|
6
11
|
import { getDeadCode } from './dead-code.js';
|
|
7
|
-
import {
|
|
12
|
+
import { checkUndocumentedFile } from './undocumented.js';
|
|
8
13
|
import { getSimilarFunctions } from './similar-functions.js';
|
|
9
|
-
import {
|
|
14
|
+
import { analyzeComplexityFile } from './complexity.js';
|
|
10
15
|
import { getLargeFiles } from './large-files.js';
|
|
11
16
|
import { getOutdatedPatterns } from './outdated-patterns.js';
|
|
12
17
|
import { getTableUsage } from './db-analysis.js';
|
|
18
|
+
import { checkJSDocFile } from './jsdoc-checker.js';
|
|
19
|
+
import { readCache, writeCache, computeContentHash, isCacheValid } from './analysis-cache.js';
|
|
20
|
+
import { shouldExcludeDir, shouldExcludeFile, parseGitignore } from './filters.js';
|
|
21
|
+
import { getWorkspaceRoot } from './workspace.js';
|
|
13
22
|
|
|
14
23
|
/**
|
|
15
24
|
* @typedef {Object} AnalysisResult
|
|
@@ -75,10 +84,21 @@ function calculateHealthScore(results) {
|
|
|
75
84
|
const warningPatterns = results.outdated.stats?.bySeverity?.warning || 0;
|
|
76
85
|
const outdatedPenalty = Math.min(errorPatterns * 3 + warningPatterns * 1, 10);
|
|
77
86
|
score -= outdatedPenalty;
|
|
78
|
-
if (results.outdated.redundantDeps
|
|
87
|
+
if (results.outdated.redundantDeps?.length > 0) {
|
|
79
88
|
topIssues.push(`${results.outdated.redundantDeps.length} redundant npm dependencies`);
|
|
80
89
|
}
|
|
81
90
|
|
|
91
|
+
// JSDoc consistency penalty: -2 per error, -1 per warning (max -15)
|
|
92
|
+
if (results.jsdocConsistency) {
|
|
93
|
+
const jsdocErrors = results.jsdocConsistency.errors || 0;
|
|
94
|
+
const jsdocWarnings = results.jsdocConsistency.warnings || 0;
|
|
95
|
+
const jsdocPenalty = Math.min(jsdocErrors * 2 + jsdocWarnings * 1, 15);
|
|
96
|
+
score -= jsdocPenalty;
|
|
97
|
+
if (jsdocErrors > 0) {
|
|
98
|
+
topIssues.push(`${jsdocErrors} JSDoc consistency errors`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
82
102
|
// Clamp score
|
|
83
103
|
score = Math.max(0, Math.min(100, Math.round(score)));
|
|
84
104
|
|
|
@@ -92,8 +112,158 @@ function calculateHealthScore(results) {
|
|
|
92
112
|
return { score, rating, topIssues: topIssues.slice(0, 5) };
|
|
93
113
|
}
|
|
94
114
|
|
|
115
|
+
/**
|
|
116
|
+
* Find all JS files in directory
|
|
117
|
+
* @param {string} dir
|
|
118
|
+
* @param {string} rootDir
|
|
119
|
+
* @returns {string[]}
|
|
120
|
+
*/
|
|
121
|
+
function findJSFiles(dir, rootDir = dir) {
|
|
122
|
+
if (dir === rootDir) parseGitignore(rootDir);
|
|
123
|
+
const files = [];
|
|
124
|
+
try {
|
|
125
|
+
for (const entry of readdirSync(dir)) {
|
|
126
|
+
const fullPath = join(dir, entry);
|
|
127
|
+
const relativePath = relative(rootDir, fullPath);
|
|
128
|
+
const stat = statSync(fullPath);
|
|
129
|
+
if (stat.isDirectory()) {
|
|
130
|
+
if (!shouldExcludeDir(entry, relativePath)) {
|
|
131
|
+
files.push(...findJSFiles(fullPath, rootDir));
|
|
132
|
+
}
|
|
133
|
+
} else if (entry.endsWith('.js') && !entry.endsWith('.css.js') && !entry.endsWith('.tpl.js')) {
|
|
134
|
+
if (!shouldExcludeFile(entry, relativePath)) {
|
|
135
|
+
files.push(fullPath);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
} catch (e) { /* dir not found */ }
|
|
140
|
+
return files;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Run cacheable per-file analyses with cache support
|
|
145
|
+
* Returns aggregated complexity, undocumented, and jsdoc results
|
|
146
|
+
* @param {string} dir
|
|
147
|
+
* @param {string} contextDir
|
|
148
|
+
* @returns {{ complexity: Object[], undocumented: Object[], jsdocIssues: Object[], cacheStats: { hits: number, misses: number } }}
|
|
149
|
+
*/
|
|
150
|
+
function runCacheableAnalyses(dir, contextDir) {
|
|
151
|
+
const resolvedDir = resolve(dir);
|
|
152
|
+
const wsRoot = getWorkspaceRoot();
|
|
153
|
+
const files = findJSFiles(dir);
|
|
154
|
+
|
|
155
|
+
const allComplexity = [];
|
|
156
|
+
const allUndocumented = [];
|
|
157
|
+
const allJsdocIssues = [];
|
|
158
|
+
let cacheHits = 0;
|
|
159
|
+
let cacheMisses = 0;
|
|
160
|
+
|
|
161
|
+
for (const file of files) {
|
|
162
|
+
const relPath = relative(resolvedDir, file);
|
|
163
|
+
// Cache key: workspace-relative (src/parser.js), matches graph paths
|
|
164
|
+
const cacheKey = relative(wsRoot, file);
|
|
165
|
+
let code;
|
|
166
|
+
try {
|
|
167
|
+
code = readFileSync(file, 'utf-8');
|
|
168
|
+
} catch (e) {
|
|
169
|
+
continue; // File deleted between findJSFiles and read
|
|
170
|
+
}
|
|
171
|
+
const contentHash = computeContentHash(code);
|
|
172
|
+
|
|
173
|
+
// Check cache (key: workspace-relative)
|
|
174
|
+
const cached = readCache(contextDir, cacheKey);
|
|
175
|
+
|
|
176
|
+
if (cached && isCacheValid(cached, cached.sig, contentHash, 'content')) {
|
|
177
|
+
// Cache hit — use cached results
|
|
178
|
+
cacheHits++;
|
|
179
|
+
if (cached.complexity) allComplexity.push(...cached.complexity);
|
|
180
|
+
if (cached.undocumented) allUndocumented.push(...cached.undocumented);
|
|
181
|
+
if (cached.jsdocIssues) allJsdocIssues.push(...cached.jsdocIssues);
|
|
182
|
+
} else {
|
|
183
|
+
// Cache miss — compute fresh
|
|
184
|
+
cacheMisses++;
|
|
185
|
+
const complexity = analyzeComplexityFile(code, relPath);
|
|
186
|
+
const undocumented = checkUndocumentedFile(code, relPath, 'tests');
|
|
187
|
+
const jsdocIssues = checkJSDocFile(code, relPath);
|
|
188
|
+
|
|
189
|
+
allComplexity.push(...complexity);
|
|
190
|
+
allUndocumented.push(...undocumented);
|
|
191
|
+
allJsdocIssues.push(...jsdocIssues);
|
|
192
|
+
|
|
193
|
+
// Save to cache (key: workspace-relative)
|
|
194
|
+
writeCache(contextDir, cacheKey, {
|
|
195
|
+
sig: cached?.sig || contentHash,
|
|
196
|
+
contentHash,
|
|
197
|
+
complexity,
|
|
198
|
+
undocumented,
|
|
199
|
+
jsdocIssues,
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
complexity: allComplexity,
|
|
206
|
+
undocumented: allUndocumented,
|
|
207
|
+
jsdocIssues: allJsdocIssues,
|
|
208
|
+
cacheStats: { hits: cacheHits, misses: cacheMisses },
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Aggregate complexity items into summary format
|
|
214
|
+
* @param {Object[]} items
|
|
215
|
+
* @param {number} minComplexity
|
|
216
|
+
* @returns {Object}
|
|
217
|
+
*/
|
|
218
|
+
function aggregateComplexity(items, minComplexity = 5) {
|
|
219
|
+
let filtered = items.filter(i => i.complexity >= minComplexity);
|
|
220
|
+
filtered.sort((a, b) => b.complexity - a.complexity);
|
|
221
|
+
|
|
222
|
+
const stats = {
|
|
223
|
+
low: filtered.filter(i => i.rating === 'low').length,
|
|
224
|
+
moderate: filtered.filter(i => i.rating === 'moderate').length,
|
|
225
|
+
high: filtered.filter(i => i.rating === 'high').length,
|
|
226
|
+
critical: filtered.filter(i => i.rating === 'critical').length,
|
|
227
|
+
average: filtered.length > 0
|
|
228
|
+
? Math.round(filtered.reduce((s, i) => s + i.complexity, 0) / filtered.length * 10) / 10
|
|
229
|
+
: 0,
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
return { total: filtered.length, stats, items: filtered.slice(0, 30) };
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Aggregate undocumented items into summary format
|
|
237
|
+
* @param {Object[]} items
|
|
238
|
+
* @returns {Object}
|
|
239
|
+
*/
|
|
240
|
+
function aggregateUndocumented(items) {
|
|
241
|
+
const byType = {
|
|
242
|
+
class: items.filter(i => i.type === 'class').length,
|
|
243
|
+
function: items.filter(i => i.type === 'function').length,
|
|
244
|
+
method: items.filter(i => i.type === 'method').length,
|
|
245
|
+
};
|
|
246
|
+
return { total: items.length, byType, items: items.slice(0, 20) };
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Aggregate JSDoc issues into summary format
|
|
251
|
+
* @param {Object[]} issues
|
|
252
|
+
* @returns {{ issues: Object[], summary: Object }}
|
|
253
|
+
*/
|
|
254
|
+
function aggregateJSDoc(issues) {
|
|
255
|
+
const errors = issues.filter(i => i.severity === 'error').length;
|
|
256
|
+
const warnings = issues.filter(i => i.severity === 'warning').length;
|
|
257
|
+
const byFile = {};
|
|
258
|
+
for (const issue of issues) {
|
|
259
|
+
byFile[issue.file] = (byFile[issue.file] || 0) + 1;
|
|
260
|
+
}
|
|
261
|
+
return { issues, summary: { total: issues.length, errors, warnings, byFile } };
|
|
262
|
+
}
|
|
263
|
+
|
|
95
264
|
/**
|
|
96
265
|
* Run full analysis on directory
|
|
266
|
+
* Uses incremental cache for per-file metrics; cross-file metrics always recompute.
|
|
97
267
|
* @param {string} dir
|
|
98
268
|
* @param {Object} [options]
|
|
99
269
|
* @param {boolean} [options.includeItems=false] - Include individual items
|
|
@@ -101,15 +271,21 @@ function calculateHealthScore(results) {
|
|
|
101
271
|
*/
|
|
102
272
|
export async function getFullAnalysis(dir, options = {}) {
|
|
103
273
|
const includeItems = options.includeItems || false;
|
|
274
|
+
const resolvedDir = resolve(dir);
|
|
275
|
+
const contextDir = join(getWorkspaceRoot(), '.context');
|
|
276
|
+
|
|
277
|
+
// Run cacheable per-file analyses (complexity, undocumented, jsdoc)
|
|
278
|
+
const cached = runCacheableAnalyses(dir, contextDir);
|
|
279
|
+
const complexity = aggregateComplexity(cached.complexity);
|
|
280
|
+
const undocumented = aggregateUndocumented(cached.undocumented);
|
|
281
|
+
const jsdocCheck = aggregateJSDoc(cached.jsdocIssues);
|
|
104
282
|
|
|
105
|
-
// Run
|
|
106
|
-
const [deadCode,
|
|
107
|
-
getDeadCode(dir),
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
getLargeFiles(dir),
|
|
112
|
-
getOutdatedPatterns(dir),
|
|
283
|
+
// Run cross-file analyses (always dynamic — NOT cacheable per-file)
|
|
284
|
+
const [deadCode, similar, largeFiles, outdated, dbUsage] = await Promise.all([
|
|
285
|
+
getDeadCode(dir).catch(() => ({ total: 0, byType: {}, items: [] })),
|
|
286
|
+
getSimilarFunctions(dir, { threshold: 70 }).catch(() => ({ total: 0, pairs: [] })),
|
|
287
|
+
getLargeFiles(dir).catch(() => ({ total: 0, stats: {}, items: [] })),
|
|
288
|
+
getOutdatedPatterns(dir).catch(() => ({ codePatterns: [], redundantDeps: [], stats: { totalPatterns: 0, bySeverity: {}, byPattern: {}, redundantDeps: 0 } })),
|
|
113
289
|
getTableUsage(dir).catch(() => ({ tables: [], totalTables: 0, totalQueries: 0 })),
|
|
114
290
|
]);
|
|
115
291
|
|
|
@@ -121,6 +297,7 @@ export async function getFullAnalysis(dir, options = {}) {
|
|
|
121
297
|
complexity,
|
|
122
298
|
largeFiles,
|
|
123
299
|
outdated,
|
|
300
|
+
jsdocConsistency: jsdocCheck.summary,
|
|
124
301
|
});
|
|
125
302
|
|
|
126
303
|
// Build result
|
|
@@ -154,6 +331,13 @@ export async function getFullAnalysis(dir, options = {}) {
|
|
|
154
331
|
redundantDeps: outdated.redundantDeps,
|
|
155
332
|
...(includeItems && { codePatterns: outdated.codePatterns.slice(0, 10) }),
|
|
156
333
|
},
|
|
334
|
+
jsdocConsistency: {
|
|
335
|
+
total: jsdocCheck.summary.total,
|
|
336
|
+
errors: jsdocCheck.summary.errors,
|
|
337
|
+
warnings: jsdocCheck.summary.warnings,
|
|
338
|
+
...(includeItems && { issues: jsdocCheck.issues.slice(0, 10) }),
|
|
339
|
+
},
|
|
340
|
+
cache: cached.cacheStats,
|
|
157
341
|
overall,
|
|
158
342
|
};
|
|
159
343
|
|
|
@@ -172,3 +356,115 @@ export async function getFullAnalysis(dir, options = {}) {
|
|
|
172
356
|
|
|
173
357
|
return result;
|
|
174
358
|
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Quick health check — runs only cached per-file metrics, skips cross-file.
|
|
362
|
+
* @param {string} dir - Path to scan
|
|
363
|
+
* @returns {{healthScore: number, complexity: number, undocumented: number, jsdocIssues: number}}
|
|
364
|
+
*/
|
|
365
|
+
export function getAnalysisSummaryOnly(dir) {
|
|
366
|
+
const contextDir = join(getWorkspaceRoot(), '.context');
|
|
367
|
+
const cached = runCacheableAnalyses(dir, contextDir);
|
|
368
|
+
const complexity = aggregateComplexity(cached.complexity);
|
|
369
|
+
const undocumented = aggregateUndocumented(cached.undocumented);
|
|
370
|
+
const jsdocCheck = aggregateJSDoc(cached.jsdocIssues);
|
|
371
|
+
|
|
372
|
+
// Reuse the same health score formula as getFullAnalysis
|
|
373
|
+
const overall = calculateHealthScore({
|
|
374
|
+
deadCode: { total: 0 },
|
|
375
|
+
undocumented,
|
|
376
|
+
similar: { total: 0 },
|
|
377
|
+
complexity,
|
|
378
|
+
largeFiles: { total: 0 },
|
|
379
|
+
outdated: { stats: { totalPatterns: 0 } },
|
|
380
|
+
jsdocConsistency: jsdocCheck.summary,
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
return {
|
|
384
|
+
healthScore: overall.score,
|
|
385
|
+
grade: overall.rating,
|
|
386
|
+
complexity: complexity.total,
|
|
387
|
+
undocumented: undocumented.total,
|
|
388
|
+
jsdocIssues: jsdocCheck.summary.total,
|
|
389
|
+
cache: cached.cacheStats,
|
|
390
|
+
note: 'Partial score — cross-file analyses skipped for speed. Run get_full_analysis for complete health check.',
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Streaming analysis — yields results as each sub-analysis completes.
|
|
396
|
+
* Useful for large codebases where waiting for all analyses is too slow.
|
|
397
|
+
* @param {string} dir - Path to scan
|
|
398
|
+
* @param {Object} [options]
|
|
399
|
+
* @param {boolean} [options.includeItems=false]
|
|
400
|
+
* @returns {AsyncGenerator<{type: string, data: Object}>}
|
|
401
|
+
*/
|
|
402
|
+
export async function* getFullAnalysisStreaming(dir, options = {}) {
|
|
403
|
+
const includeItems = options.includeItems || false;
|
|
404
|
+
const contextDir = join(getWorkspaceRoot(), '.context');
|
|
405
|
+
|
|
406
|
+
// Phase 1: Cached per-file analyses (fast)
|
|
407
|
+
const cached = runCacheableAnalyses(dir, contextDir);
|
|
408
|
+
|
|
409
|
+
const complexity = aggregateComplexity(cached.complexity);
|
|
410
|
+
yield { type: 'complexity', data: {
|
|
411
|
+
total: complexity.total,
|
|
412
|
+
stats: complexity.stats,
|
|
413
|
+
...(includeItems && { items: complexity.items.slice(0, 10) }),
|
|
414
|
+
}};
|
|
415
|
+
|
|
416
|
+
const undocumented = aggregateUndocumented(cached.undocumented);
|
|
417
|
+
yield { type: 'undocumented', data: {
|
|
418
|
+
total: undocumented.total,
|
|
419
|
+
byType: undocumented.byType,
|
|
420
|
+
...(includeItems && { items: undocumented.items.slice(0, 10) }),
|
|
421
|
+
}};
|
|
422
|
+
|
|
423
|
+
const jsdocCheck = aggregateJSDoc(cached.jsdocIssues);
|
|
424
|
+
yield { type: 'jsdocConsistency', data: {
|
|
425
|
+
total: jsdocCheck.summary.total,
|
|
426
|
+
errors: jsdocCheck.summary.errors,
|
|
427
|
+
warnings: jsdocCheck.summary.warnings,
|
|
428
|
+
...(includeItems && { issues: jsdocCheck.issues.slice(0, 10) }),
|
|
429
|
+
}};
|
|
430
|
+
|
|
431
|
+
yield { type: 'cache', data: cached.cacheStats };
|
|
432
|
+
|
|
433
|
+
// Phase 2: Cross-file analyses (slow, one at a time)
|
|
434
|
+
try {
|
|
435
|
+
const deadCode = await getDeadCode(dir);
|
|
436
|
+
yield { type: 'deadCode', data: {
|
|
437
|
+
total: deadCode.total,
|
|
438
|
+
byType: deadCode.byType,
|
|
439
|
+
...(includeItems && { items: deadCode.items.slice(0, 10) }),
|
|
440
|
+
}};
|
|
441
|
+
} catch { yield { type: 'deadCode', data: { total: 0, byType: {}, error: 'analysis failed' } }; }
|
|
442
|
+
|
|
443
|
+
try {
|
|
444
|
+
const similar = await getSimilarFunctions(dir, { threshold: 70 });
|
|
445
|
+
yield { type: 'similar', data: {
|
|
446
|
+
total: similar.total,
|
|
447
|
+
...(includeItems && { pairs: similar.pairs.slice(0, 5) }),
|
|
448
|
+
}};
|
|
449
|
+
} catch { yield { type: 'similar', data: { total: 0, error: 'analysis failed' } }; }
|
|
450
|
+
|
|
451
|
+
try {
|
|
452
|
+
const largeFiles = await getLargeFiles(dir);
|
|
453
|
+
yield { type: 'largeFiles', data: {
|
|
454
|
+
total: largeFiles.total,
|
|
455
|
+
stats: largeFiles.stats,
|
|
456
|
+
...(includeItems && { items: largeFiles.items.slice(0, 10) }),
|
|
457
|
+
}};
|
|
458
|
+
} catch { yield { type: 'largeFiles', data: { total: 0, error: 'analysis failed' } }; }
|
|
459
|
+
|
|
460
|
+
try {
|
|
461
|
+
const outdated = await getOutdatedPatterns(dir);
|
|
462
|
+
yield { type: 'outdated', data: {
|
|
463
|
+
totalPatterns: outdated.stats.totalPatterns,
|
|
464
|
+
redundantDeps: outdated.redundantDeps,
|
|
465
|
+
...(includeItems && { codePatterns: outdated.codePatterns.slice(0, 10) }),
|
|
466
|
+
}};
|
|
467
|
+
} catch { yield { type: 'outdated', data: { totalPatterns: 0, error: 'analysis failed' } }; }
|
|
468
|
+
|
|
469
|
+
yield { type: 'done', data: { phases: 2, timestamp: new Date().toISOString() } };
|
|
470
|
+
}
|
package/src/instructions.js
CHANGED
|
@@ -13,121 +13,19 @@ export const AGENT_INSTRUCTIONS = `
|
|
|
13
13
|
- **State Management**: Use \`this.init$\` for local state and \`this.sub()\` for reactivity.
|
|
14
14
|
- **Directives**: Use \`itemize\` for lists, \`js-d-kit\` for static generation.
|
|
15
15
|
|
|
16
|
-
## 2.
|
|
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
|
|
16
|
+
## 2. General Coding Rules
|
|
119
17
|
- **ESM Only**: Use \`import\` / \`export\`. No \`require\`.
|
|
120
18
|
- **No Dependencies**: Avoid adding new npm packages unless critical.
|
|
121
19
|
- **Comments**: Write clear JSDoc for all public methods.
|
|
122
20
|
- **Async/Await**: Prefer async/await over promises.
|
|
123
21
|
|
|
124
|
-
##
|
|
22
|
+
## 3. MCP Tools Usage
|
|
125
23
|
- **Graph**: Use \`get_skeleton\` first to map the codebase.
|
|
126
24
|
- **Deep Dive**: Use \`expand\` to read class details.
|
|
127
25
|
- **Tests**: Use \`get_pending_tests\` to see what needs verification.
|
|
128
26
|
- **Guidelines**: Use \`get_agent_instructions\` to refresh these rules.
|
|
129
27
|
|
|
130
|
-
##
|
|
28
|
+
## 4. Custom Rules System
|
|
131
29
|
Configurable code analysis with auto-detection.
|
|
132
30
|
|
|
133
31
|
### Available Tools
|