autosnippet 3.0.13 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/bin/api-server.js +2 -0
  2. package/bin/cli.js +24 -19
  3. package/config/default.json +1 -1
  4. package/lib/bootstrap.js +4 -4
  5. package/lib/cli/SetupService.js +29 -29
  6. package/lib/cli/UpgradeService.js +3 -2
  7. package/lib/core/AstAnalyzer.js +1 -1
  8. package/lib/core/ast/ensure-grammars.js +1 -1
  9. package/lib/core/ast/index.js +62 -11
  10. package/lib/core/ast/lang-dart.js +27 -21
  11. package/lib/core/ast/lang-go.js +6 -20
  12. package/lib/core/ast/lang-rust.js +53 -28
  13. package/lib/core/ast/parser-init.js +9 -5
  14. package/lib/core/discovery/DartDiscoverer.js +4 -10
  15. package/lib/core/discovery/GoDiscoverer.js +45 -25
  16. package/lib/core/discovery/NodeDiscoverer.js +1 -3
  17. package/lib/core/discovery/PythonDiscoverer.js +7 -1
  18. package/lib/core/discovery/RustDiscoverer.js +111 -38
  19. package/lib/core/discovery/index.js +2 -2
  20. package/lib/core/enhancement/django-enhancement.js +10 -4
  21. package/lib/core/enhancement/fastapi-enhancement.js +16 -9
  22. package/lib/core/enhancement/go-grpc-enhancement.js +2 -1
  23. package/lib/core/enhancement/go-web-enhancement.js +3 -6
  24. package/lib/core/enhancement/ml-enhancement.js +6 -3
  25. package/lib/core/enhancement/nextjs-enhancement.js +17 -7
  26. package/lib/core/enhancement/node-server-enhancement.js +4 -2
  27. package/lib/core/enhancement/react-enhancement.js +6 -3
  28. package/lib/core/enhancement/rust-tokio-enhancement.js +6 -2
  29. package/lib/core/enhancement/rust-web-enhancement.js +13 -7
  30. package/lib/core/enhancement/vue-enhancement.js +10 -5
  31. package/lib/external/ai/AiFactory.js +3 -1
  32. package/lib/external/ai/AiProvider.js +3 -1
  33. package/lib/external/mcp/McpServer.js +2 -0
  34. package/lib/external/mcp/handlers/bootstrap/base-dimensions.js +1 -2
  35. package/lib/external/mcp/handlers/bootstrap/pipeline/checkpoint.js +7 -1
  36. package/lib/external/mcp/handlers/bootstrap/pipeline/noAiFallback.js +55 -26
  37. package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +8 -8
  38. package/lib/external/mcp/handlers/bootstrap/refine.js +3 -1
  39. package/lib/external/mcp/handlers/bootstrap.js +4 -10
  40. package/lib/external/mcp/handlers/browse.js +6 -2
  41. package/lib/external/mcp/handlers/guard.js +6 -2
  42. package/lib/external/mcp/handlers/skill.js +6 -2
  43. package/lib/http/HttpServer.js +1 -1
  44. package/lib/http/routes/candidates.js +3 -1
  45. package/lib/http/routes/extract.js +4 -5
  46. package/lib/http/routes/guardRules.js +1 -1
  47. package/lib/http/routes/modules.js +9 -3
  48. package/lib/http/routes/skills.js +54 -6
  49. package/lib/http/routes/violations.js +4 -3
  50. package/lib/infrastructure/external/ClipboardManager.js +24 -7
  51. package/lib/infrastructure/external/NativeUi.js +3 -1
  52. package/lib/infrastructure/external/OpenBrowser.js +1 -0
  53. package/lib/infrastructure/external/XcodeAutomation.js +5 -5
  54. package/lib/infrastructure/vector/IndexingPipeline.js +14 -5
  55. package/lib/injection/ServiceContainer.js +34 -11
  56. package/lib/platform/ios/index.js +20 -25
  57. package/lib/platform/ios/routes/spm.js +6 -3
  58. package/lib/platform/ios/snippet/PlaceholderConverter.js +6 -2
  59. package/lib/platform/ios/snippet/XcodeCodec.js +4 -2
  60. package/lib/platform/ios/spm/SpmDiscoverer.js +1 -1
  61. package/lib/platform/ios/spm/SpmService.js +3 -1
  62. package/lib/platform/ios/xcode/XcodeIntegration.js +10 -12
  63. package/lib/platform/ios/xcode/XcodeWriteUtils.js +6 -1
  64. package/lib/service/automation/FileWatcher.js +1 -3
  65. package/lib/service/automation/handlers/CreateHandler.js +3 -5
  66. package/lib/service/automation/handlers/GuardHandler.js +11 -32
  67. package/lib/service/automation/handlers/SearchHandler.js +9 -9
  68. package/lib/service/chat/CandidateGuardrail.js +11 -6
  69. package/lib/service/chat/ChatAgent.js +31 -22
  70. package/lib/service/chat/HandoffProtocol.js +5 -2
  71. package/lib/service/chat/tools/composite.js +3 -2
  72. package/lib/service/chat/tools/index.js +60 -71
  73. package/lib/service/chat/tools/infrastructure.js +9 -4
  74. package/lib/service/chat/tools/lifecycle.js +22 -5
  75. package/lib/service/chat/tools/project-access.js +5 -9
  76. package/lib/service/chat/tools.js +1 -2
  77. package/lib/service/cursor/AgentInstructionsGenerator.js +33 -15
  78. package/lib/service/cursor/CursorDeliveryPipeline.js +2 -1
  79. package/lib/service/cursor/KnowledgeCompressor.js +16 -7
  80. package/lib/service/guard/ComplianceReporter.js +5 -2
  81. package/lib/service/guard/GuardCheckEngine.js +53 -26
  82. package/lib/service/guard/GuardCodeChecks.js +217 -188
  83. package/lib/service/guard/GuardCrossFileChecks.js +203 -184
  84. package/lib/service/guard/GuardPatternUtils.js +17 -10
  85. package/lib/service/module/ModuleService.js +180 -56
  86. package/lib/service/recipe/RecipeCandidateValidator.js +11 -8
  87. package/lib/service/snippet/SnippetFactory.js +3 -3
  88. package/lib/service/snippet/SnippetInstaller.js +35 -11
  89. package/lib/service/snippet/codecs/VSCodeCodec.js +2 -2
  90. package/lib/service/wiki/WikiGenerator.js +54 -36
  91. package/lib/service/wiki/WikiRenderers.js +105 -80
  92. package/lib/service/wiki/WikiUtils.js +217 -80
  93. package/lib/shared/LanguageService.js +111 -53
  94. package/lib/shared/PathGuard.js +0 -8
  95. package/package.json +3 -9
  96. package/scripts/bench-real-projects.mjs +29 -29
  97. package/scripts/generate-recipe-drafts.js +17 -27
  98. package/scripts/init-snippets.js +43 -24
  99. package/scripts/install-vscode-copilot.js +3 -19
  100. 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 (existsSync(join(projectRoot, 'rust-toolchain.toml')) ||
55
- existsSync(join(projectRoot, 'rust-toolchain'))) {
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)) return null;
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)) return [];
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) return [];
207
+ if (!workspaceBlock) {
208
+ return [];
209
+ }
202
210
 
203
211
  const membersLine = workspaceBlock[1].match(/members\s*=\s*\[([\s\S]*?)\]/);
204
- if (!membersLine) return [];
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)) continue;
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 { /* skip */ }
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)) return;
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 { /* skip */ }
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)) return;
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 { /* skip */ }
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)) return null;
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)) return 'actix-web';
331
- if (/\baxum\b/.test(content)) return 'axum';
332
- if (/\brocket\b/.test(content)) return 'rocket';
333
- if (/\bwarp\b/.test(content)) return 'warp';
334
- if (/\btokio\b/.test(content) && /\bhyper\b/.test(content)) return 'hyper';
335
- if (/\btokio\b/.test(content)) return 'tokio';
336
- if (/\basync-std\b/.test(content)) return 'async-std';
337
- if (/\btauri\b/.test(content)) return 'tauri';
338
- if (/\bbevy\b/.test(content)) return 'bevy';
339
- if (/\bclap\b/.test(content)) return 'clap-cli';
340
- if (/\bserde\b/.test(content)) return 'serde';
341
- } catch { /* skip */ }
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)) return;
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('[')) continue;
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 { /* skip */ }
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)) return;
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) return;
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 { /* skip */ }
487
+ } catch {
488
+ /* skip */
489
+ }
429
490
 
430
491
  walk(subDir, subRel, depth + 1);
431
492
  }
432
- } catch { /* skip */ }
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) return;
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('.')) continue;
512
+ if (entry.name.startsWith('.')) {
513
+ continue;
514
+ }
448
515
 
449
516
  if (entry.isDirectory()) {
450
- if (EXCLUDE_DIRS.has(entry.name)) continue;
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 { /* unreadable */ }
531
+ } catch {
532
+ /* unreadable */
533
+ }
463
534
  }
464
535
  }
465
- } catch { /* permission error */ }
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: 'QuerySet 在循环中可能触发 N+1 查询 — 使用 select_related() / prefetch_related() 优化',
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: 'Django 约定: CharField/TextField 不应使用 null=True — 使用 blank=True + default="" 代替',
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: /class\s+\w+\((?:models\.)?Model\):\s*\n(?:(?!\s*def\s+__str__)[\s\S])*?(?=\nclass\s|\Z)/,
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 (cls.superclass && /Serializer$|ModelSerializer|HyperlinkedModelSerializer/.test(cls.superclass)) {
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: 'Route 装饰器(@app.get/post/put/delete)、APIRouter 分组、Pydantic Model/Schema、DI (Depends())、路由参数声明',
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: 'async 路由中不应使用阻塞操作 (time.sleep/open/subprocess.run) — 使用 asyncio 版本或 run_in_executor',
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: /@(?:app|router)\.(?:get|post|put|delete|patch)\s*\([^)]*\)\s*\n\s*(?:async\s+)?def\s+\w+\([^)]*\)(?!\s*->)/,
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: '避免 bare except — 使用 except Exception 或具体异常类,否则会吞掉 KeyboardInterrupt/SystemExit',
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 (m.decorators?.some((d) => /@(?:app|router)\.(?:get|post|put|delete|patch|options|head)/.test(d))) {
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
- nameLower.includes('db') ||
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) => imp.includes('/pb') || imp.includes('/proto') || imp.includes('google.golang.org/grpc')
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: /def\s+(?:evaluate|validate|test|predict|inference)\s*\([^)]*\)[\s\S]*?(?!torch\.no_grad|@torch\.no_grad)model\s*\(/,
101
- message: '推理/评估函数应使用 @torch.no_grad() 或 with torch.no_grad() — 减少内存消耗并加速',
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: '设置随机种子时建议同时设置 torch.manual_seed / torch.cuda.manual_seed_all / np.random.seed / random.seed 保证完全可复现',
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: /['"]use server['"]\s*;?\s*\n[\s\S]*?(?:useState|useEffect|useRef|useContext)\s*\(/,
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: /(?:^(?!.*['"]use client['"])[\s\S]*?)\b(?:window|document|localStorage|sessionStorage)\b/,
102
- message: 'Server Component 中不能访问 window/document 等浏览器 API — 添加 "use client" 或检查运行环境',
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: /['"]use client['"][\s\S]*?export\s+(?:const\s+metadata|async\s+function\s+generateMetadata)/,
111
- message: 'metadata / generateMetadata 只能在 Server Component 中导出 — 不能与 "use client" 共存',
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: 'Next.js 15 默认不缓存 fetch,明确指定 { cache: "force-cache" } 或 { next: { revalidate: N } } 以启用缓存',
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 (m.isExported && !m.className && /^(?:GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)$/.test(m.name)) {
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: 'Promise 链应包含 .catch() 处理或使用 async/await + try/catch — 未处理的 rejection 会导致进程崩溃',
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: /@(?:Get|Post|Put|Delete|Patch)\s*\([^)]*\)\s*\n\s*(?:async\s+)?\w+\([^)]*\)\s*(?::\s*any)?/,
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: '组件目录结构约定(文件组织、barrel export、props/state 命名规范、forwardRef 使用、组件拆分粒度)',
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: /useEffect\s*\(\s*\(\)\s*=>\s*\{[^}]*(?:addEventListener|setInterval|setTimeout|subscribe)[^}]*\}\s*,/,
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: /(?:^|\n)\s*(?:const|let)\s+\[.*?,\s*set\w+\]\s*=\s*useState[\s\S]*?\n\s*set\w+\(/m,
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') continue;
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') continue;
162
+ if (cls.kind !== 'struct') {
163
+ continue;
164
+ }
161
165
  const nameLower = cls.name.toLowerCase();
162
166
  if (
163
167
  nameLower.includes('sender') ||