autosnippet 3.0.13 → 3.1.1
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/bin/api-server.js +2 -0
- package/bin/cli.js +24 -19
- package/config/default.json +1 -1
- package/lib/bootstrap.js +4 -4
- package/lib/cli/SetupService.js +29 -29
- package/lib/cli/UpgradeService.js +3 -2
- package/lib/core/AstAnalyzer.js +1 -1
- package/lib/core/ast/ensure-grammars.js +1 -1
- package/lib/core/ast/index.js +62 -11
- package/lib/core/ast/lang-dart.js +27 -21
- package/lib/core/ast/lang-go.js +6 -20
- package/lib/core/ast/lang-rust.js +53 -28
- package/lib/core/ast/parser-init.js +9 -5
- package/lib/core/discovery/DartDiscoverer.js +4 -10
- package/lib/core/discovery/GoDiscoverer.js +45 -25
- package/lib/core/discovery/NodeDiscoverer.js +1 -3
- package/lib/core/discovery/PythonDiscoverer.js +7 -1
- package/lib/core/discovery/RustDiscoverer.js +111 -38
- package/lib/core/discovery/index.js +2 -2
- package/lib/core/enhancement/django-enhancement.js +10 -4
- package/lib/core/enhancement/fastapi-enhancement.js +16 -9
- package/lib/core/enhancement/go-grpc-enhancement.js +2 -1
- package/lib/core/enhancement/go-web-enhancement.js +3 -6
- package/lib/core/enhancement/ml-enhancement.js +6 -3
- package/lib/core/enhancement/nextjs-enhancement.js +17 -7
- package/lib/core/enhancement/node-server-enhancement.js +4 -2
- package/lib/core/enhancement/react-enhancement.js +6 -3
- package/lib/core/enhancement/rust-tokio-enhancement.js +6 -2
- package/lib/core/enhancement/rust-web-enhancement.js +13 -7
- package/lib/core/enhancement/vue-enhancement.js +10 -5
- package/lib/external/ai/AiFactory.js +3 -1
- package/lib/external/ai/AiProvider.js +3 -1
- package/lib/external/mcp/McpServer.js +2 -0
- package/lib/external/mcp/handlers/bootstrap/base-dimensions.js +1 -2
- package/lib/external/mcp/handlers/bootstrap/pipeline/checkpoint.js +7 -1
- package/lib/external/mcp/handlers/bootstrap/pipeline/noAiFallback.js +55 -26
- package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +8 -8
- package/lib/external/mcp/handlers/bootstrap/refine.js +3 -1
- package/lib/external/mcp/handlers/bootstrap.js +4 -10
- package/lib/external/mcp/handlers/browse.js +6 -2
- package/lib/external/mcp/handlers/guard.js +6 -2
- package/lib/external/mcp/handlers/skill.js +6 -2
- package/lib/http/HttpServer.js +1 -1
- package/lib/http/routes/candidates.js +3 -1
- package/lib/http/routes/extract.js +4 -5
- package/lib/http/routes/guardRules.js +1 -1
- package/lib/http/routes/modules.js +9 -3
- package/lib/http/routes/skills.js +54 -6
- package/lib/http/routes/violations.js +4 -3
- package/lib/infrastructure/external/ClipboardManager.js +24 -7
- package/lib/infrastructure/external/NativeUi.js +3 -1
- package/lib/infrastructure/external/OpenBrowser.js +1 -0
- package/lib/infrastructure/external/XcodeAutomation.js +5 -5
- package/lib/infrastructure/vector/IndexingPipeline.js +14 -5
- package/lib/injection/ServiceContainer.js +34 -11
- package/lib/platform/ios/index.js +20 -25
- package/lib/platform/ios/routes/spm.js +6 -3
- package/lib/platform/ios/snippet/PlaceholderConverter.js +6 -2
- package/lib/platform/ios/snippet/XcodeCodec.js +4 -2
- package/lib/platform/ios/spm/SpmDiscoverer.js +1 -1
- package/lib/platform/ios/spm/SpmService.js +3 -1
- package/lib/platform/ios/xcode/XcodeIntegration.js +10 -12
- package/lib/platform/ios/xcode/XcodeWriteUtils.js +6 -1
- package/lib/service/automation/FileWatcher.js +1 -3
- package/lib/service/automation/handlers/CreateHandler.js +3 -5
- package/lib/service/automation/handlers/GuardHandler.js +11 -32
- package/lib/service/automation/handlers/SearchHandler.js +9 -9
- package/lib/service/chat/CandidateGuardrail.js +11 -6
- package/lib/service/chat/ChatAgent.js +31 -22
- package/lib/service/chat/HandoffProtocol.js +5 -2
- package/lib/service/chat/tools/composite.js +3 -2
- package/lib/service/chat/tools/index.js +60 -71
- package/lib/service/chat/tools/infrastructure.js +9 -4
- package/lib/service/chat/tools/lifecycle.js +22 -5
- package/lib/service/chat/tools/project-access.js +5 -9
- package/lib/service/chat/tools.js +1 -2
- package/lib/service/cursor/AgentInstructionsGenerator.js +33 -15
- package/lib/service/cursor/CursorDeliveryPipeline.js +2 -1
- package/lib/service/cursor/KnowledgeCompressor.js +16 -7
- package/lib/service/guard/ComplianceReporter.js +5 -2
- package/lib/service/guard/GuardCheckEngine.js +53 -26
- package/lib/service/guard/GuardCodeChecks.js +217 -188
- package/lib/service/guard/GuardCrossFileChecks.js +203 -184
- package/lib/service/guard/GuardPatternUtils.js +17 -10
- package/lib/service/module/ModuleService.js +180 -56
- package/lib/service/recipe/RecipeCandidateValidator.js +11 -8
- package/lib/service/snippet/SnippetFactory.js +3 -3
- package/lib/service/snippet/SnippetInstaller.js +35 -11
- package/lib/service/snippet/codecs/VSCodeCodec.js +2 -2
- package/lib/service/wiki/WikiGenerator.js +67 -40
- package/lib/service/wiki/WikiRenderers.js +105 -80
- package/lib/service/wiki/WikiUtils.js +217 -80
- package/lib/shared/LanguageService.js +111 -53
- package/lib/shared/PathGuard.js +0 -8
- package/package.json +3 -9
- package/scripts/bench-real-projects.mjs +29 -29
- package/scripts/generate-recipe-drafts.js +17 -27
- package/scripts/init-snippets.js +43 -24
- package/scripts/install-vscode-copilot.js +3 -19
- package/scripts/setup-mcp-config.js +0 -4
|
@@ -51,8 +51,10 @@ export class RustDiscoverer extends ProjectDiscoverer {
|
|
|
51
51
|
}
|
|
52
52
|
reasons.push('Cargo.lock exists');
|
|
53
53
|
}
|
|
54
|
-
if (
|
|
55
|
-
|
|
54
|
+
if (
|
|
55
|
+
existsSync(join(projectRoot, 'rust-toolchain.toml')) ||
|
|
56
|
+
existsSync(join(projectRoot, 'rust-toolchain'))
|
|
57
|
+
) {
|
|
56
58
|
confidence = Math.max(confidence, 0.85);
|
|
57
59
|
reasons.push('rust-toolchain exists');
|
|
58
60
|
}
|
|
@@ -163,7 +165,9 @@ export class RustDiscoverer extends ProjectDiscoverer {
|
|
|
163
165
|
*/
|
|
164
166
|
#parseCargoToml(projectRoot) {
|
|
165
167
|
const cargoPath = join(projectRoot, 'Cargo.toml');
|
|
166
|
-
if (!existsSync(cargoPath))
|
|
168
|
+
if (!existsSync(cargoPath)) {
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
167
171
|
|
|
168
172
|
try {
|
|
169
173
|
const content = readFileSync(cargoPath, 'utf8');
|
|
@@ -191,17 +195,23 @@ export class RustDiscoverer extends ProjectDiscoverer {
|
|
|
191
195
|
*/
|
|
192
196
|
#discoverWorkspaceMembers(projectRoot) {
|
|
193
197
|
const cargoPath = join(projectRoot, 'Cargo.toml');
|
|
194
|
-
if (!existsSync(cargoPath))
|
|
198
|
+
if (!existsSync(cargoPath)) {
|
|
199
|
+
return [];
|
|
200
|
+
}
|
|
195
201
|
|
|
196
202
|
try {
|
|
197
203
|
const content = readFileSync(cargoPath, 'utf8');
|
|
198
204
|
|
|
199
205
|
// [workspace] members = ["crate_a", "crate_b", "crates/*"]
|
|
200
206
|
const workspaceBlock = content.match(/\[workspace\]([\s\S]*?)(?:\n\[|\s*$)/);
|
|
201
|
-
if (!workspaceBlock)
|
|
207
|
+
if (!workspaceBlock) {
|
|
208
|
+
return [];
|
|
209
|
+
}
|
|
202
210
|
|
|
203
211
|
const membersLine = workspaceBlock[1].match(/members\s*=\s*\[([\s\S]*?)\]/);
|
|
204
|
-
if (!membersLine)
|
|
212
|
+
if (!membersLine) {
|
|
213
|
+
return [];
|
|
214
|
+
}
|
|
205
215
|
|
|
206
216
|
const memberPatterns = membersLine[1]
|
|
207
217
|
.split(',')
|
|
@@ -214,7 +224,9 @@ export class RustDiscoverer extends ProjectDiscoverer {
|
|
|
214
224
|
// Glob — 展开
|
|
215
225
|
const prefix = pattern.replace('/*', '');
|
|
216
226
|
const parentDir = join(projectRoot, prefix);
|
|
217
|
-
if (!existsSync(parentDir))
|
|
227
|
+
if (!existsSync(parentDir)) {
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
218
230
|
try {
|
|
219
231
|
const entries = readdirSync(parentDir, { withFileTypes: true });
|
|
220
232
|
for (const entry of entries) {
|
|
@@ -235,7 +247,9 @@ export class RustDiscoverer extends ProjectDiscoverer {
|
|
|
235
247
|
}
|
|
236
248
|
}
|
|
237
249
|
}
|
|
238
|
-
} catch {
|
|
250
|
+
} catch {
|
|
251
|
+
/* skip */
|
|
252
|
+
}
|
|
239
253
|
} else {
|
|
240
254
|
const memberPath = join(projectRoot, pattern);
|
|
241
255
|
if (existsSync(join(memberPath, 'Cargo.toml'))) {
|
|
@@ -265,7 +279,9 @@ export class RustDiscoverer extends ProjectDiscoverer {
|
|
|
265
279
|
*/
|
|
266
280
|
#discoverExamples(projectRoot, framework) {
|
|
267
281
|
const examplesDir = join(projectRoot, 'examples');
|
|
268
|
-
if (!existsSync(examplesDir))
|
|
282
|
+
if (!existsSync(examplesDir)) {
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
269
285
|
|
|
270
286
|
try {
|
|
271
287
|
const entries = readdirSync(examplesDir, { withFileTypes: true });
|
|
@@ -294,7 +310,9 @@ export class RustDiscoverer extends ProjectDiscoverer {
|
|
|
294
310
|
language: 'rust',
|
|
295
311
|
});
|
|
296
312
|
}
|
|
297
|
-
} catch {
|
|
313
|
+
} catch {
|
|
314
|
+
/* skip */
|
|
315
|
+
}
|
|
298
316
|
}
|
|
299
317
|
|
|
300
318
|
/**
|
|
@@ -302,7 +320,9 @@ export class RustDiscoverer extends ProjectDiscoverer {
|
|
|
302
320
|
*/
|
|
303
321
|
#discoverBenches(projectRoot) {
|
|
304
322
|
const benchDir = join(projectRoot, 'benches');
|
|
305
|
-
if (!existsSync(benchDir))
|
|
323
|
+
if (!existsSync(benchDir)) {
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
306
326
|
|
|
307
327
|
try {
|
|
308
328
|
const entries = readdirSync(benchDir);
|
|
@@ -314,7 +334,9 @@ export class RustDiscoverer extends ProjectDiscoverer {
|
|
|
314
334
|
language: 'rust',
|
|
315
335
|
});
|
|
316
336
|
}
|
|
317
|
-
} catch {
|
|
337
|
+
} catch {
|
|
338
|
+
/* skip */
|
|
339
|
+
}
|
|
318
340
|
}
|
|
319
341
|
|
|
320
342
|
/**
|
|
@@ -322,23 +344,49 @@ export class RustDiscoverer extends ProjectDiscoverer {
|
|
|
322
344
|
*/
|
|
323
345
|
#detectFramework(projectRoot) {
|
|
324
346
|
const cargoPath = join(projectRoot, 'Cargo.toml');
|
|
325
|
-
if (!existsSync(cargoPath))
|
|
347
|
+
if (!existsSync(cargoPath)) {
|
|
348
|
+
return null;
|
|
349
|
+
}
|
|
326
350
|
|
|
327
351
|
try {
|
|
328
352
|
const content = readFileSync(cargoPath, 'utf8');
|
|
329
353
|
|
|
330
|
-
if (/\bactix-web\b/.test(content))
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
if (/\
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
if (/\
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
if (/\
|
|
340
|
-
|
|
341
|
-
|
|
354
|
+
if (/\bactix-web\b/.test(content)) {
|
|
355
|
+
return 'actix-web';
|
|
356
|
+
}
|
|
357
|
+
if (/\baxum\b/.test(content)) {
|
|
358
|
+
return 'axum';
|
|
359
|
+
}
|
|
360
|
+
if (/\brocket\b/.test(content)) {
|
|
361
|
+
return 'rocket';
|
|
362
|
+
}
|
|
363
|
+
if (/\bwarp\b/.test(content)) {
|
|
364
|
+
return 'warp';
|
|
365
|
+
}
|
|
366
|
+
if (/\btokio\b/.test(content) && /\bhyper\b/.test(content)) {
|
|
367
|
+
return 'hyper';
|
|
368
|
+
}
|
|
369
|
+
if (/\btokio\b/.test(content)) {
|
|
370
|
+
return 'tokio';
|
|
371
|
+
}
|
|
372
|
+
if (/\basync-std\b/.test(content)) {
|
|
373
|
+
return 'async-std';
|
|
374
|
+
}
|
|
375
|
+
if (/\btauri\b/.test(content)) {
|
|
376
|
+
return 'tauri';
|
|
377
|
+
}
|
|
378
|
+
if (/\bbevy\b/.test(content)) {
|
|
379
|
+
return 'bevy';
|
|
380
|
+
}
|
|
381
|
+
if (/\bclap\b/.test(content)) {
|
|
382
|
+
return 'clap-cli';
|
|
383
|
+
}
|
|
384
|
+
if (/\bserde\b/.test(content)) {
|
|
385
|
+
return 'serde';
|
|
386
|
+
}
|
|
387
|
+
} catch {
|
|
388
|
+
/* skip */
|
|
389
|
+
}
|
|
342
390
|
|
|
343
391
|
return null;
|
|
344
392
|
}
|
|
@@ -348,7 +396,9 @@ export class RustDiscoverer extends ProjectDiscoverer {
|
|
|
348
396
|
*/
|
|
349
397
|
#parseDependencies(projectRoot) {
|
|
350
398
|
const cargoPath = join(projectRoot, 'Cargo.toml');
|
|
351
|
-
if (!existsSync(cargoPath))
|
|
399
|
+
if (!existsSync(cargoPath)) {
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
352
402
|
|
|
353
403
|
const nodeSet = new Set(this.#depGraph.nodes.map((n) => (typeof n === 'string' ? n : n.id)));
|
|
354
404
|
const rootNode =
|
|
@@ -372,7 +422,9 @@ export class RustDiscoverer extends ProjectDiscoverer {
|
|
|
372
422
|
|
|
373
423
|
for (const line of lines) {
|
|
374
424
|
const trimmed = line.trim();
|
|
375
|
-
if (!trimmed || trimmed.startsWith('#') || trimmed.startsWith('['))
|
|
425
|
+
if (!trimmed || trimmed.startsWith('#') || trimmed.startsWith('[')) {
|
|
426
|
+
continue;
|
|
427
|
+
}
|
|
376
428
|
|
|
377
429
|
// dep = "version" 或 dep = { version = "...", ... }
|
|
378
430
|
const depMatch = trimmed.match(/^(\S+)\s*=/);
|
|
@@ -396,7 +448,9 @@ export class RustDiscoverer extends ProjectDiscoverer {
|
|
|
396
448
|
}
|
|
397
449
|
}
|
|
398
450
|
}
|
|
399
|
-
} catch {
|
|
451
|
+
} catch {
|
|
452
|
+
/* skip */
|
|
453
|
+
}
|
|
400
454
|
}
|
|
401
455
|
|
|
402
456
|
/**
|
|
@@ -404,17 +458,22 @@ export class RustDiscoverer extends ProjectDiscoverer {
|
|
|
404
458
|
*/
|
|
405
459
|
#discoverInternalModules(projectRoot) {
|
|
406
460
|
const srcDir = join(projectRoot, 'src');
|
|
407
|
-
if (!existsSync(srcDir))
|
|
461
|
+
if (!existsSync(srcDir)) {
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
408
464
|
|
|
409
465
|
const nodeSet = new Set(this.#depGraph.nodes.map((n) => (typeof n === 'string' ? n : n.id)));
|
|
410
466
|
|
|
411
467
|
const walk = (dir, relPath, depth) => {
|
|
412
|
-
if (depth > 6)
|
|
468
|
+
if (depth > 6) {
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
413
471
|
try {
|
|
414
472
|
const entries = readdirSync(dir, { withFileTypes: true });
|
|
415
473
|
for (const entry of entries) {
|
|
416
|
-
if (!entry.isDirectory() || entry.name.startsWith('.') || EXCLUDE_DIRS.has(entry.name))
|
|
474
|
+
if (!entry.isDirectory() || entry.name.startsWith('.') || EXCLUDE_DIRS.has(entry.name)) {
|
|
417
475
|
continue;
|
|
476
|
+
}
|
|
418
477
|
const subDir = join(dir, entry.name);
|
|
419
478
|
const subRel = relPath ? `${relPath}/${entry.name}` : entry.name;
|
|
420
479
|
|
|
@@ -425,11 +484,15 @@ export class RustDiscoverer extends ProjectDiscoverer {
|
|
|
425
484
|
this.#depGraph.nodes.push({ id: subRel, label: subRel, type: 'internal' });
|
|
426
485
|
nodeSet.add(subRel);
|
|
427
486
|
}
|
|
428
|
-
} catch {
|
|
487
|
+
} catch {
|
|
488
|
+
/* skip */
|
|
489
|
+
}
|
|
429
490
|
|
|
430
491
|
walk(subDir, subRel, depth + 1);
|
|
431
492
|
}
|
|
432
|
-
} catch {
|
|
493
|
+
} catch {
|
|
494
|
+
/* skip */
|
|
495
|
+
}
|
|
433
496
|
};
|
|
434
497
|
|
|
435
498
|
walk(srcDir, '', 0);
|
|
@@ -439,15 +502,21 @@ export class RustDiscoverer extends ProjectDiscoverer {
|
|
|
439
502
|
* 递归收集 .rs 文件
|
|
440
503
|
*/
|
|
441
504
|
#collectRsFiles(dir, rootDir, files, depth = 0) {
|
|
442
|
-
if (depth > 15)
|
|
505
|
+
if (depth > 15) {
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
443
508
|
|
|
444
509
|
try {
|
|
445
510
|
const entries = readdirSync(dir, { withFileTypes: true });
|
|
446
511
|
for (const entry of entries) {
|
|
447
|
-
if (entry.name.startsWith('.'))
|
|
512
|
+
if (entry.name.startsWith('.')) {
|
|
513
|
+
continue;
|
|
514
|
+
}
|
|
448
515
|
|
|
449
516
|
if (entry.isDirectory()) {
|
|
450
|
-
if (EXCLUDE_DIRS.has(entry.name))
|
|
517
|
+
if (EXCLUDE_DIRS.has(entry.name)) {
|
|
518
|
+
continue;
|
|
519
|
+
}
|
|
451
520
|
this.#collectRsFiles(join(dir, entry.name), rootDir, files, depth + 1);
|
|
452
521
|
} else if (entry.isFile() && SOURCE_EXTENSIONS.has(extname(entry.name))) {
|
|
453
522
|
const fullPath = join(dir, entry.name);
|
|
@@ -459,9 +528,13 @@ export class RustDiscoverer extends ProjectDiscoverer {
|
|
|
459
528
|
relativePath: relative(rootDir, fullPath),
|
|
460
529
|
content,
|
|
461
530
|
});
|
|
462
|
-
} catch {
|
|
531
|
+
} catch {
|
|
532
|
+
/* unreadable */
|
|
533
|
+
}
|
|
463
534
|
}
|
|
464
535
|
}
|
|
465
|
-
} catch {
|
|
536
|
+
} catch {
|
|
537
|
+
/* permission error */
|
|
538
|
+
}
|
|
466
539
|
}
|
|
467
540
|
}
|
|
@@ -8,9 +8,9 @@ import { DiscovererRegistry } from './DiscovererRegistry.js';
|
|
|
8
8
|
import { GenericDiscoverer } from './GenericDiscoverer.js';
|
|
9
9
|
import { GoDiscoverer } from './GoDiscoverer.js';
|
|
10
10
|
import { JvmDiscoverer } from './JvmDiscoverer.js';
|
|
11
|
-
import { RustDiscoverer } from './RustDiscoverer.js';
|
|
12
11
|
import { NodeDiscoverer } from './NodeDiscoverer.js';
|
|
13
12
|
import { PythonDiscoverer } from './PythonDiscoverer.js';
|
|
13
|
+
import { RustDiscoverer } from './RustDiscoverer.js';
|
|
14
14
|
import { SpmDiscoverer } from './SpmDiscoverer.js';
|
|
15
15
|
|
|
16
16
|
/** @type {DiscovererRegistry|null} */
|
|
@@ -47,10 +47,10 @@ export { DartDiscoverer } from './DartDiscoverer.js';
|
|
|
47
47
|
export { DiscovererRegistry } from './DiscovererRegistry.js';
|
|
48
48
|
export { GenericDiscoverer } from './GenericDiscoverer.js';
|
|
49
49
|
export { GoDiscoverer } from './GoDiscoverer.js';
|
|
50
|
-
export { RustDiscoverer } from './RustDiscoverer.js';
|
|
51
50
|
export { JvmDiscoverer } from './JvmDiscoverer.js';
|
|
52
51
|
export { NodeDiscoverer } from './NodeDiscoverer.js';
|
|
53
52
|
// Re-exports
|
|
54
53
|
export { ProjectDiscoverer } from './ProjectDiscoverer.js';
|
|
55
54
|
export { PythonDiscoverer } from './PythonDiscoverer.js';
|
|
55
|
+
export { RustDiscoverer } from './RustDiscoverer.js';
|
|
56
56
|
export { SpmDiscoverer } from './SpmDiscoverer.js';
|
|
@@ -81,7 +81,8 @@ class DjangoEnhancement extends EnhancementPack {
|
|
|
81
81
|
severity: 'warning',
|
|
82
82
|
languages: ['python'],
|
|
83
83
|
pattern: /\.objects\.(?:all|filter)\([^)]*\)[\s\S]*?for\s+\w+\s+in/,
|
|
84
|
-
message:
|
|
84
|
+
message:
|
|
85
|
+
'QuerySet 在循环中可能触发 N+1 查询 — 使用 select_related() / prefetch_related() 优化',
|
|
85
86
|
},
|
|
86
87
|
{
|
|
87
88
|
ruleId: 'django-raw-sql',
|
|
@@ -99,7 +100,8 @@ class DjangoEnhancement extends EnhancementPack {
|
|
|
99
100
|
severity: 'info',
|
|
100
101
|
languages: ['python'],
|
|
101
102
|
pattern: /(?:CharField|TextField|SlugField|URLField|EmailField)\s*\([^)]*null\s*=\s*True/,
|
|
102
|
-
message:
|
|
103
|
+
message:
|
|
104
|
+
'Django 约定: CharField/TextField 不应使用 null=True — 使用 blank=True + default="" 代替',
|
|
103
105
|
},
|
|
104
106
|
{
|
|
105
107
|
ruleId: 'django-model-str',
|
|
@@ -107,7 +109,8 @@ class DjangoEnhancement extends EnhancementPack {
|
|
|
107
109
|
dimension: 'file',
|
|
108
110
|
severity: 'info',
|
|
109
111
|
languages: ['python'],
|
|
110
|
-
pattern:
|
|
112
|
+
pattern:
|
|
113
|
+
/class\s+\w+\((?:models\.)?Model\):\s*\n(?:(?!\s*def\s+__str__)[\s\S])*?(?=\nclass\s|Z)/,
|
|
111
114
|
message: 'Django Model 建议实现 __str__ 方法 — 便于 admin 和 shell 调试',
|
|
112
115
|
},
|
|
113
116
|
];
|
|
@@ -142,7 +145,10 @@ class DjangoEnhancement extends EnhancementPack {
|
|
|
142
145
|
|
|
143
146
|
// ── DRF Serializers ──
|
|
144
147
|
for (const cls of astSummary.classes || []) {
|
|
145
|
-
if (
|
|
148
|
+
if (
|
|
149
|
+
cls.superclass &&
|
|
150
|
+
/Serializer$|ModelSerializer|HyperlinkedModelSerializer/.test(cls.superclass)
|
|
151
|
+
) {
|
|
146
152
|
patterns.push({
|
|
147
153
|
type: 'drf-serializer',
|
|
148
154
|
className: cls.name,
|
|
@@ -28,7 +28,8 @@ class FastAPIEnhancement extends EnhancementPack {
|
|
|
28
28
|
{
|
|
29
29
|
id: 'fastapi-route-scan',
|
|
30
30
|
label: 'FastAPI 路由分析',
|
|
31
|
-
guide:
|
|
31
|
+
guide:
|
|
32
|
+
'Route 装饰器(@app.get/post/put/delete)、APIRouter 分组、Pydantic Model/Schema、DI (Depends())、路由参数声明',
|
|
32
33
|
knowledgeTypes: ['architecture', 'code-pattern'],
|
|
33
34
|
skillWorthy: true,
|
|
34
35
|
dualOutput: true,
|
|
@@ -78,7 +79,8 @@ class FastAPIEnhancement extends EnhancementPack {
|
|
|
78
79
|
severity: 'warning',
|
|
79
80
|
languages: ['python'],
|
|
80
81
|
pattern: /async\s+def\s+\w+[\s\S]*?(?:time\.sleep|open\(|os\.read|subprocess\.run)/,
|
|
81
|
-
message:
|
|
82
|
+
message:
|
|
83
|
+
'async 路由中不应使用阻塞操作 (time.sleep/open/subprocess.run) — 使用 asyncio 版本或 run_in_executor',
|
|
82
84
|
},
|
|
83
85
|
{
|
|
84
86
|
ruleId: 'fastapi-no-response-model',
|
|
@@ -86,7 +88,8 @@ class FastAPIEnhancement extends EnhancementPack {
|
|
|
86
88
|
dimension: 'file',
|
|
87
89
|
severity: 'info',
|
|
88
90
|
languages: ['python'],
|
|
89
|
-
pattern:
|
|
91
|
+
pattern:
|
|
92
|
+
/@(?:app|router)\.(?:get|post|put|delete|patch)\s*\([^)]*\)\s*\n\s*(?:async\s+)?def\s+\w+\([^)]*\)(?!\s*->)/,
|
|
90
93
|
message: 'FastAPI 路由建议声明 response_model 或返回类型注解 — 便于自动生成 OpenAPI 文档',
|
|
91
94
|
},
|
|
92
95
|
{
|
|
@@ -96,7 +99,8 @@ class FastAPIEnhancement extends EnhancementPack {
|
|
|
96
99
|
severity: 'warning',
|
|
97
100
|
languages: ['python'],
|
|
98
101
|
pattern: /except\s*:/,
|
|
99
|
-
message:
|
|
102
|
+
message:
|
|
103
|
+
'避免 bare except — 使用 except Exception 或具体异常类,否则会吞掉 KeyboardInterrupt/SystemExit',
|
|
100
104
|
},
|
|
101
105
|
{
|
|
102
106
|
ruleId: 'fastapi-no-star-import',
|
|
@@ -127,7 +131,11 @@ class FastAPIEnhancement extends EnhancementPack {
|
|
|
127
131
|
|
|
128
132
|
// ── FastAPI route handlers ──
|
|
129
133
|
for (const m of astSummary.methods || []) {
|
|
130
|
-
if (
|
|
134
|
+
if (
|
|
135
|
+
m.decorators?.some((d) =>
|
|
136
|
+
/@(?:app|router)\.(?:get|post|put|delete|patch|options|head)/.test(d)
|
|
137
|
+
)
|
|
138
|
+
) {
|
|
131
139
|
patterns.push({
|
|
132
140
|
type: 'fastapi-route',
|
|
133
141
|
methodName: m.name,
|
|
@@ -142,13 +150,12 @@ class FastAPIEnhancement extends EnhancementPack {
|
|
|
142
150
|
if (!m.className) {
|
|
143
151
|
const nameLower = m.name?.toLowerCase() || '';
|
|
144
152
|
if (
|
|
145
|
-
nameLower.startsWith('get_') &&
|
|
146
|
-
|
|
153
|
+
nameLower.startsWith('get_') &&
|
|
154
|
+
(nameLower.includes('db') ||
|
|
147
155
|
nameLower.includes('session') ||
|
|
148
156
|
nameLower.includes('current_user') ||
|
|
149
157
|
nameLower.includes('settings') ||
|
|
150
|
-
nameLower.includes('service')
|
|
151
|
-
)
|
|
158
|
+
nameLower.includes('service'))
|
|
152
159
|
) {
|
|
153
160
|
patterns.push({
|
|
154
161
|
type: 'fastapi-dependency',
|
|
@@ -139,7 +139,8 @@ class GoGrpcEnhancement extends EnhancementPack {
|
|
|
139
139
|
|
|
140
140
|
// ── Protobuf imports ──
|
|
141
141
|
const pbImports = (astSummary.imports || []).filter(
|
|
142
|
-
(imp) =>
|
|
142
|
+
(imp) =>
|
|
143
|
+
imp.includes('/pb') || imp.includes('/proto') || imp.includes('google.golang.org/grpc')
|
|
143
144
|
);
|
|
144
145
|
if (pbImports.length > 0) {
|
|
145
146
|
patterns.push({
|
|
@@ -41,8 +41,7 @@ class GoWebEnhancement extends EnhancementPack {
|
|
|
41
41
|
{
|
|
42
42
|
id: 'go-route-scan',
|
|
43
43
|
label: 'API Route 结构',
|
|
44
|
-
guide:
|
|
45
|
-
'Go 路由结构分析 — 路由分组 (Group)、URI 命名规范、HTTP 方法分布、路由参数与通配符',
|
|
44
|
+
guide: 'Go 路由结构分析 — 路由分组 (Group)、URI 命名规范、HTTP 方法分布、路由参数与通配符',
|
|
46
45
|
knowledgeTypes: ['architecture'],
|
|
47
46
|
skillWorthy: true,
|
|
48
47
|
dualOutput: true,
|
|
@@ -73,8 +72,7 @@ class GoWebEnhancement extends EnhancementPack {
|
|
|
73
72
|
severity: 'warning',
|
|
74
73
|
languages: ['go'],
|
|
75
74
|
pattern: /go\s+func\s*\(/,
|
|
76
|
-
message:
|
|
77
|
-
'HTTP handler 中启动 goroutine 应包含 recover() 防止 panic 导致整个进程崩溃',
|
|
75
|
+
message: 'HTTP handler 中启动 goroutine 应包含 recover() 防止 panic 导致整个进程崩溃',
|
|
78
76
|
},
|
|
79
77
|
{
|
|
80
78
|
ruleId: 'go-context-first-param',
|
|
@@ -82,8 +80,7 @@ class GoWebEnhancement extends EnhancementPack {
|
|
|
82
80
|
dimension: 'file',
|
|
83
81
|
severity: 'info',
|
|
84
82
|
languages: ['go'],
|
|
85
|
-
pattern:
|
|
86
|
-
/func\s+\w+\([^)]*\)\s*\([^)]*context\.Context/,
|
|
83
|
+
pattern: /func\s+\w+\([^)]*\)\s*\([^)]*context\.Context/,
|
|
87
84
|
message: 'context.Context 应作为函数的第一个参数,而非放在返回值或中间位置',
|
|
88
85
|
},
|
|
89
86
|
];
|
|
@@ -97,8 +97,10 @@ class MLEnhancement extends EnhancementPack {
|
|
|
97
97
|
dimension: 'file',
|
|
98
98
|
severity: 'warning',
|
|
99
99
|
languages: ['python'],
|
|
100
|
-
pattern:
|
|
101
|
-
|
|
100
|
+
pattern:
|
|
101
|
+
/def\s+(?:evaluate|validate|test|predict|inference)\s*\([^)]*\)[\s\S]*?(?!torch\.no_grad|@torch\.no_grad)model\s*\(/,
|
|
102
|
+
message:
|
|
103
|
+
'推理/评估函数应使用 @torch.no_grad() 或 with torch.no_grad() — 减少内存消耗并加速',
|
|
102
104
|
},
|
|
103
105
|
{
|
|
104
106
|
ruleId: 'ml-gradient-accumulation-zero',
|
|
@@ -116,7 +118,8 @@ class MLEnhancement extends EnhancementPack {
|
|
|
116
118
|
severity: 'info',
|
|
117
119
|
languages: ['python'],
|
|
118
120
|
pattern: /torch\.manual_seed|random\.seed|np\.random\.seed/,
|
|
119
|
-
message:
|
|
121
|
+
message:
|
|
122
|
+
'设置随机种子时建议同时设置 torch.manual_seed / torch.cuda.manual_seed_all / np.random.seed / random.seed 保证完全可复现',
|
|
120
123
|
},
|
|
121
124
|
];
|
|
122
125
|
}
|
|
@@ -79,7 +79,8 @@ class NextjsEnhancement extends EnhancementPack {
|
|
|
79
79
|
dimension: 'file',
|
|
80
80
|
severity: 'error',
|
|
81
81
|
languages: ['typescript', 'javascript'],
|
|
82
|
-
pattern:
|
|
82
|
+
pattern:
|
|
83
|
+
/['"]use server['"]\s*;?\s*\n[\s\S]*?(?:useState|useEffect|useRef|useContext)\s*\(/,
|
|
83
84
|
message: '"use server" 文件/函数中不能使用 React Hooks — Server Actions 运行在服务端',
|
|
84
85
|
},
|
|
85
86
|
{
|
|
@@ -98,8 +99,10 @@ class NextjsEnhancement extends EnhancementPack {
|
|
|
98
99
|
dimension: 'file',
|
|
99
100
|
severity: 'error',
|
|
100
101
|
languages: ['typescript', 'javascript'],
|
|
101
|
-
pattern:
|
|
102
|
-
|
|
102
|
+
pattern:
|
|
103
|
+
/(?:^(?!.*['"]use client['"])[\s\S]*?)\b(?:window|document|localStorage|sessionStorage)\b/,
|
|
104
|
+
message:
|
|
105
|
+
'Server Component 中不能访问 window/document 等浏览器 API — 添加 "use client" 或检查运行环境',
|
|
103
106
|
},
|
|
104
107
|
{
|
|
105
108
|
ruleId: 'nextjs-metadata-with-use-client',
|
|
@@ -107,8 +110,10 @@ class NextjsEnhancement extends EnhancementPack {
|
|
|
107
110
|
dimension: 'file',
|
|
108
111
|
severity: 'error',
|
|
109
112
|
languages: ['typescript', 'javascript'],
|
|
110
|
-
pattern:
|
|
111
|
-
|
|
113
|
+
pattern:
|
|
114
|
+
/['"]use client['"][\s\S]*?export\s+(?:const\s+metadata|async\s+function\s+generateMetadata)/,
|
|
115
|
+
message:
|
|
116
|
+
'metadata / generateMetadata 只能在 Server Component 中导出 — 不能与 "use client" 共存',
|
|
112
117
|
},
|
|
113
118
|
{
|
|
114
119
|
ruleId: 'nextjs-fetch-no-store',
|
|
@@ -117,7 +122,8 @@ class NextjsEnhancement extends EnhancementPack {
|
|
|
117
122
|
severity: 'info',
|
|
118
123
|
languages: ['typescript', 'javascript'],
|
|
119
124
|
pattern: /fetch\s*\([^)]+\)\s*(?![\s\S]*?(?:cache|revalidate|next))/,
|
|
120
|
-
message:
|
|
125
|
+
message:
|
|
126
|
+
'Next.js 15 默认不缓存 fetch,明确指定 { cache: "force-cache" } 或 { next: { revalidate: N } } 以启用缓存',
|
|
121
127
|
},
|
|
122
128
|
];
|
|
123
129
|
}
|
|
@@ -172,7 +178,11 @@ class NextjsEnhancement extends EnhancementPack {
|
|
|
172
178
|
|
|
173
179
|
// ── Route Handlers (GET/POST/PUT/DELETE/PATCH exports) ──
|
|
174
180
|
for (const m of astSummary.methods || []) {
|
|
175
|
-
if (
|
|
181
|
+
if (
|
|
182
|
+
m.isExported &&
|
|
183
|
+
!m.className &&
|
|
184
|
+
/^(?:GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)$/.test(m.name)
|
|
185
|
+
) {
|
|
176
186
|
patterns.push({
|
|
177
187
|
type: 'nextjs-route-handler',
|
|
178
188
|
methodName: m.name,
|
|
@@ -96,7 +96,8 @@ class NodeServerEnhancement extends EnhancementPack {
|
|
|
96
96
|
severity: 'warning',
|
|
97
97
|
languages: ['typescript', 'javascript'],
|
|
98
98
|
pattern: /\.then\s*\([^)]*\)\s*(?!\.catch)/,
|
|
99
|
-
message:
|
|
99
|
+
message:
|
|
100
|
+
'Promise 链应包含 .catch() 处理或使用 async/await + try/catch — 未处理的 rejection 会导致进程崩溃',
|
|
100
101
|
},
|
|
101
102
|
{
|
|
102
103
|
ruleId: 'nestjs-circular-dependency',
|
|
@@ -113,7 +114,8 @@ class NodeServerEnhancement extends EnhancementPack {
|
|
|
113
114
|
dimension: 'file',
|
|
114
115
|
severity: 'info',
|
|
115
116
|
languages: ['typescript', 'javascript'],
|
|
116
|
-
pattern:
|
|
117
|
+
pattern:
|
|
118
|
+
/@(?:Get|Post|Put|Delete|Patch)\s*\([^)]*\)\s*\n\s*(?:async\s+)?\w+\([^)]*\)\s*(?::\s*any)?/,
|
|
117
119
|
message: 'NestJS controller 方法建议声明返回类型 DTO — 便于 Swagger 文档生成与类型安全',
|
|
118
120
|
},
|
|
119
121
|
{
|
|
@@ -41,7 +41,8 @@ class ReactEnhancement extends EnhancementPack {
|
|
|
41
41
|
{
|
|
42
42
|
id: 'component-structure-scan',
|
|
43
43
|
label: '组件结构约定',
|
|
44
|
-
guide:
|
|
44
|
+
guide:
|
|
45
|
+
'组件目录结构约定(文件组织、barrel export、props/state 命名规范、forwardRef 使用、组件拆分粒度)',
|
|
45
46
|
knowledgeTypes: ['code-standard', 'architecture'],
|
|
46
47
|
skillWorthy: true,
|
|
47
48
|
dualOutput: true,
|
|
@@ -117,7 +118,8 @@ class ReactEnhancement extends EnhancementPack {
|
|
|
117
118
|
dimension: 'file',
|
|
118
119
|
severity: 'info',
|
|
119
120
|
languages: ['typescript', 'javascript'],
|
|
120
|
-
pattern:
|
|
121
|
+
pattern:
|
|
122
|
+
/useEffect\s*\(\s*\(\)\s*=>\s*\{[^}]*(?:addEventListener|setInterval|setTimeout|subscribe)[^}]*\}\s*,/,
|
|
121
123
|
message: 'useEffect 中注册了事件/订阅/定时器但可能缺少清理函数,会导致内存泄漏',
|
|
122
124
|
},
|
|
123
125
|
{
|
|
@@ -126,7 +128,8 @@ class ReactEnhancement extends EnhancementPack {
|
|
|
126
128
|
dimension: 'file',
|
|
127
129
|
severity: 'error',
|
|
128
130
|
languages: ['typescript', 'javascript'],
|
|
129
|
-
pattern:
|
|
131
|
+
pattern:
|
|
132
|
+
/(?:^|\n)\s*(?:const|let)\s+\[.*?,\s*set\w+\]\s*=\s*useState[\s\S]*?\n\s*set\w+\(/m,
|
|
130
133
|
message: '避免在渲染阶段直接调用 setState — 会导致无限重渲染',
|
|
131
134
|
},
|
|
132
135
|
{
|
|
@@ -136,7 +136,9 @@ class RustTokioEnhancement extends EnhancementPack {
|
|
|
136
136
|
|
|
137
137
|
// ── Structs wrapping async primitives ──
|
|
138
138
|
for (const cls of astSummary.classes || []) {
|
|
139
|
-
if (cls.kind !== 'struct')
|
|
139
|
+
if (cls.kind !== 'struct') {
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
140
142
|
const nameLower = cls.name.toLowerCase();
|
|
141
143
|
if (
|
|
142
144
|
nameLower.includes('worker') ||
|
|
@@ -157,7 +159,9 @@ class RustTokioEnhancement extends EnhancementPack {
|
|
|
157
159
|
|
|
158
160
|
// ── Channel / sync wrappers ──
|
|
159
161
|
for (const cls of astSummary.classes || []) {
|
|
160
|
-
if (cls.kind !== 'struct')
|
|
162
|
+
if (cls.kind !== 'struct') {
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
161
165
|
const nameLower = cls.name.toLowerCase();
|
|
162
166
|
if (
|
|
163
167
|
nameLower.includes('sender') ||
|