autosnippet 3.0.2 → 3.0.6

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 (50) hide show
  1. package/README.md +85 -239
  2. package/bin/mcp-server.js +12 -1
  3. package/dashboard/dist/assets/{icons-Cdq22n2i.js → icons-eQ_rWCus.js} +97 -102
  4. package/dashboard/dist/assets/index-B3Nnkdxi.js +133 -0
  5. package/dashboard/dist/assets/index-BFNDAqh3.css +1 -0
  6. package/dashboard/dist/index.html +3 -3
  7. package/lib/cli/SetupService.js +9 -12
  8. package/lib/cli/UpgradeService.js +15 -17
  9. package/lib/core/AstAnalyzer.js +2 -2
  10. package/lib/core/ast/ensure-grammars.js +2 -0
  11. package/lib/core/ast/index.js +8 -0
  12. package/lib/core/ast/lang-rust.js +695 -0
  13. package/lib/core/discovery/PythonDiscoverer.js +3 -0
  14. package/lib/core/discovery/RustDiscoverer.js +467 -0
  15. package/lib/core/discovery/index.js +3 -0
  16. package/lib/core/enhancement/django-enhancement.js +169 -3
  17. package/lib/core/enhancement/fastapi-enhancement.js +149 -3
  18. package/lib/core/enhancement/go-grpc-enhancement.js +4 -0
  19. package/lib/core/enhancement/go-web-enhancement.js +6 -0
  20. package/lib/core/enhancement/index.js +5 -0
  21. package/lib/core/enhancement/langchain-enhancement.js +233 -0
  22. package/lib/core/enhancement/ml-enhancement.js +265 -0
  23. package/lib/core/enhancement/nextjs-enhancement.js +219 -0
  24. package/lib/core/enhancement/node-server-enhancement.js +178 -4
  25. package/lib/core/enhancement/react-enhancement.js +165 -4
  26. package/lib/core/enhancement/rust-tokio-enhancement.js +231 -0
  27. package/lib/core/enhancement/rust-web-enhancement.js +256 -0
  28. package/lib/core/enhancement/spring-enhancement.js +2 -0
  29. package/lib/core/enhancement/vue-enhancement.js +143 -2
  30. package/lib/external/ai/AiProvider.js +45 -6
  31. package/lib/external/mcp/handlers/bootstrap/skills.js +2 -0
  32. package/lib/external/mcp/handlers/bootstrap.js +33 -9
  33. package/lib/external/mcp/handlers/guard.js +42 -0
  34. package/lib/http/routes/candidates.js +7 -1
  35. package/lib/service/chat/ChatAgent.js +1 -0
  36. package/lib/service/chat/tools.js +5 -1
  37. package/lib/service/guard/ComplianceReporter.js +20 -7
  38. package/lib/service/guard/GuardCheckEngine.js +156 -5
  39. package/lib/service/guard/SourceFileCollector.js +15 -0
  40. package/package.json +28 -6
  41. package/scripts/install-vscode-copilot.js +32 -97
  42. package/scripts/setup-mcp-config.js +18 -36
  43. package/skills/autosnippet-coldstart/SKILL.md +4 -2
  44. package/skills/autosnippet-concepts/SKILL.md +5 -3
  45. package/skills/autosnippet-reference-rust/SKILL.md +401 -0
  46. package/skills/autosnippet-structure/SKILL.md +1 -1
  47. package/templates/recipes-setup/README.md +2 -2
  48. package/templates/recipes-setup/_template.md +1 -1
  49. package/dashboard/dist/assets/index-ClkyPkDX.js +0 -133
  50. package/dashboard/dist/assets/index-t4QrJwv1.css +0 -1
@@ -213,6 +213,9 @@ export class AiProvider {
213
213
  ? `\n# Code Structure Analysis (AST)\nThe following is a Tree-sitter AST analysis of the project. Use this structural context to better understand class hierarchies, design patterns, and code quality when extracting recipes:\n\n${options.astContext.substring(0, 3000)}\n`
214
214
  : '';
215
215
 
216
+ // 用户语言偏好 — 控制 AI 输出人类可读字段的语言
217
+ const langInstruction = this._buildLangInstruction(options.lang);
218
+
216
219
  // comprehensive 模式:全量分析整个文件,不跳过任何有意义的方法
217
220
  if (options.comprehensive) {
218
221
  return `# Role
@@ -267,7 +270,7 @@ Each item MUST use the following V3 KnowledgeEntry structure:
267
270
  - aiInsight (string, optional): One-sentence concise insight — the single most important takeaway about this code pattern
268
271
 
269
272
  IMPORTANT: content.pattern must contain the COMPLETE source code. content.markdown must be a meaningful project-specific writeup, NOT just a copy of description. content.rationale must explain WHY this pattern is designed this way. reasoning.whyStandard must explain WHY this pattern matters.
270
-
273
+ ${langInstruction}
271
274
  Return ONLY a JSON array. Do NOT return an empty array.
272
275
 
273
276
  Files Content:
@@ -328,13 +331,49 @@ Each item MUST use the following V3 KnowledgeEntry structure:
328
331
  - aiInsight (string, optional): One-sentence concise insight — the single most important takeaway about this code pattern
329
332
 
330
333
  IMPORTANT: content.pattern must contain the COMPLETE source code. content.markdown must be a meaningful project-specific writeup, NOT just a copy of description. content.rationale must explain WHY this pattern is designed this way. reasoning.whyStandard must explain WHY this pattern matters.
331
-
334
+ ${langInstruction}
332
335
  Return ONLY a JSON array. If no meaningful patterns found, return [].
333
336
 
334
337
  Files Content:
335
338
  ${files}`;
336
339
  }
337
340
 
341
+ /**
342
+ * 根据用户语言偏好生成输出语言指令
343
+ * @param {string} [lang] - 语言代码,如 'zh', 'en'
344
+ * @returns {string} 语言指令段落(为空则返回空字符串)
345
+ */
346
+ _buildLangInstruction(lang) {
347
+ if (!lang || lang === 'en') return '';
348
+ if (lang === 'zh') {
349
+ return `
350
+ # 输出语言要求
351
+ 用户使用中文,请用**中文**书写以下字段的内容:
352
+ - title(标题)
353
+ - description(描述)
354
+ - doClause(做什么)
355
+ - dontClause(不要做什么)
356
+ - whenClause(适用场景)
357
+ - topicHint(分组标签)
358
+ - content.markdown(使用指南)
359
+ - content.rationale(设计原因)
360
+ - reasoning.whyStandard(为什么是最佳实践)
361
+ - aiInsight(核心洞察)
362
+ - constraints 中的 preconditions / sideEffects / boundaries
363
+
364
+ 以下字段保持英文或代码原文,不要翻译:
365
+ - trigger(@快捷方式)
366
+ - content.pattern(源代码)
367
+ - coreCode(代码骨架)
368
+ - headers(import 语句)
369
+ - tags(搜索关键词,可中英混合)
370
+ - kind / knowledgeType / complexity / scope / category / language
371
+ `;
372
+ }
373
+ // 其他语言通用指令
374
+ return `\n# Output Language\nThe user's preferred language is "${lang}". Write all human-readable text fields (title, description, doClause, dontClause, whenClause, topicHint, content.markdown, content.rationale, reasoning.whyStandard, aiInsight, constraints text) in "${lang}". Keep code fields (trigger, content.pattern, coreCode, headers, tags) in their original language.\n`;
375
+ }
376
+
338
377
  /**
339
378
  * 根据文件扩展名检测语言特征,返回提示词适配参数
340
379
  */
@@ -502,8 +541,8 @@ ${files}`;
502
541
  * @param {Array<object>} candidates - 候选对象数组,每项至少含 {code, language, title?}
503
542
  * @returns {Promise<Array<object>>} enriched 候选数组(仅含补全的字段)
504
543
  */
505
- async enrichCandidates(candidates) {
506
- const prompt = this._buildEnrichPrompt(candidates);
544
+ async enrichCandidates(candidates, options = {}) {
545
+ const prompt = this._buildEnrichPrompt(candidates, options);
507
546
  const parsed = await this.chatWithStructuredOutput(prompt, {
508
547
  openChar: '[',
509
548
  closeChar: ']',
@@ -515,7 +554,7 @@ ${files}`;
515
554
  /**
516
555
  * 构建 enrichCandidates 提示词
517
556
  */
518
- _buildEnrichPrompt(candidates) {
557
+ _buildEnrichPrompt(candidates, options = {}) {
519
558
  const items = candidates
520
559
  .map((c, i) => {
521
560
  const existing = [];
@@ -580,7 +619,7 @@ Example:
580
619
  ]
581
620
 
582
621
  Return ONLY a JSON array. No markdown, no explanation.
583
-
622
+ ${this._buildLangInstruction(options.lang)}
584
623
  # Candidates
585
624
 
586
625
  ${items}`;
@@ -26,6 +26,8 @@ const LANG_SKILL_MAP = {
26
26
  java: ['autosnippet-coldstart', 'autosnippet-reference-java'],
27
27
  kotlin: ['autosnippet-coldstart', 'autosnippet-reference-kotlin'],
28
28
  go: ['autosnippet-coldstart', 'autosnippet-reference-go'],
29
+ dart: ['autosnippet-coldstart', 'autosnippet-reference-dart'],
30
+ rust: ['autosnippet-coldstart', 'autosnippet-reference-rust'],
29
31
  };
30
32
 
31
33
  /**
@@ -393,16 +393,17 @@ export async function bootstrapKnowledge(ctx, args) {
393
393
  }
394
394
 
395
395
  // ═══════════════════════════════════════════════════════════
396
- // Phase 3: Guard 规则审计
396
+ // Phase 3: Guard 规则审计(初始 — Enhancement Pack 规则在 Phase 4 后补充注入)
397
397
  // ═══════════════════════════════════════════════════════════
398
398
  let guardAudit = null;
399
+ let guardEngine = null; // 保持引用,供 Enhancement Pack 注入后二次审计
399
400
  if (!skipGuard) {
400
401
  try {
401
402
  const { GuardCheckEngine } = await import('../../../service/guard/GuardCheckEngine.js');
402
403
  const db = ctx.container.get('database');
403
- const engine = new GuardCheckEngine(db);
404
+ guardEngine = new GuardCheckEngine(db);
404
405
  const guardFiles = allFiles.map((f) => ({ path: f.path, content: f.content }));
405
- guardAudit = engine.auditFiles(guardFiles, { scope: 'project' });
406
+ guardAudit = guardEngine.auditFiles(guardFiles, { scope: 'project' });
406
407
 
407
408
  // 写入 ViolationsStore
408
409
  try {
@@ -423,12 +424,7 @@ export async function bootstrapKnowledge(ctx, args) {
423
424
  ctx.logger.warn(`[Bootstrap] Guard audit failed: ${e.message}`);
424
425
  }
425
426
  }
426
- report.phases.guardAudit = {
427
- totalViolations: guardAudit?.summary?.totalViolations || 0,
428
- filesWithViolations: (guardAudit?.files || []).filter((f) => f.violations.length > 0).length,
429
- skipped: skipGuard,
430
- };
431
- report.totals.guardViolations = guardAudit?.summary?.totalViolations || 0;
427
+ // guardAudit report 在 Enhancement Pack 注入后统一更新(见下方)
432
428
  report.totals.graphEdges = depEdgesWritten;
433
429
 
434
430
  const _elapsed = Date.now() - t0;
@@ -767,6 +763,25 @@ export async function bootstrapKnowledge(ctx, args) {
767
763
  } catch (enhErr) {
768
764
  ctx.logger.warn(`[Bootstrap] Enhancement pack loading skipped: ${enhErr.message}`);
769
765
  }
766
+
767
+ // ── Enhancement Pack Guard 规则注入 + 补充审计 ──
768
+ if (enhancementGuardRules.length > 0 && guardEngine) {
769
+ try {
770
+ guardEngine.injectExternalRules(enhancementGuardRules);
771
+ // 补充审计:仅用新注入的 Enhancement Pack 规则重新扫描
772
+ // 避免全量重审,只对已有 guardAudit 结果做增量合并
773
+ const guardFiles = allFiles.map((f) => ({ path: f.path, content: f.content }));
774
+ const enhancedAudit = guardEngine.auditFiles(guardFiles, { scope: 'project' });
775
+ // 用包含 Enhancement Pack 规则的完整结果替换原始审计
776
+ guardAudit = enhancedAudit;
777
+ ctx.logger.info(
778
+ `[Bootstrap] Guard re-audit with ${guardEngine.getExternalRuleCount()} Enhancement Pack rules → ${enhancedAudit.summary.totalViolations} total violations`
779
+ );
780
+ } catch (reAuditErr) {
781
+ ctx.logger.warn(`[Bootstrap] Enhancement Pack guard re-audit failed: ${reAuditErr.message}`);
782
+ }
783
+ }
784
+
770
785
  report.phases.enhancementPacks = {
771
786
  matched: enhancementPackInfo,
772
787
  extraDimensions: enhancementPackInfo.length,
@@ -774,6 +789,15 @@ export async function bootstrapKnowledge(ctx, args) {
774
789
  patterns: enhancementPatterns.length,
775
790
  };
776
791
 
792
+ // ── Guard 审计报告统计(在 Enhancement Pack 注入后更新,确保包含 EP 规则结果)──
793
+ report.phases.guardAudit = {
794
+ totalViolations: guardAudit?.summary?.totalViolations || 0,
795
+ filesWithViolations: (guardAudit?.files || []).filter((f) => f.violations.length > 0).length,
796
+ skipped: skipGuard,
797
+ enhancementRulesInjected: enhancementGuardRules.length,
798
+ };
799
+ report.totals.guardViolations = guardAudit?.summary?.totalViolations || 0;
800
+
777
801
  // 按项目语言画像注入差异化文案(支持多语言项目)
778
802
  const langProfile = LanguageService.detectProfile(langStats);
779
803
  DimensionCopy.applyMulti(activeDimensions, langProfile.primary, langProfile.secondary);
@@ -27,6 +27,10 @@ export async function guardCheck(ctx, args) {
27
27
 
28
28
  const db = ctx.container.get('database');
29
29
  const engine = new GuardCheckEngine(db);
30
+
31
+ // 注入 Enhancement Pack Guard 规则
32
+ await _injectEnhancementGuardRules(engine, ctx);
33
+
30
34
  const language = args.language || detectLanguage(args.filePath || '');
31
35
  const violations = engine.checkCode(args.code, language);
32
36
 
@@ -76,6 +80,9 @@ export async function guardAuditFiles(ctx, args) {
76
80
  const db = ctx.container.get('database');
77
81
  const engine = new GuardCheckEngine(db);
78
82
 
83
+ // 注入 Enhancement Pack Guard 规则
84
+ await _injectEnhancementGuardRules(engine, ctx);
85
+
79
86
  // 补充缺失的 content(从磁盘读取)
80
87
  const filesToAudit = args.files.map((f) => ({
81
88
  path: f.path,
@@ -203,6 +210,9 @@ export async function scanProject(ctx, args) {
203
210
  const db = ctx.container.get('database');
204
211
  const engine = new GuardCheckEngine(db);
205
212
 
213
+ // 注入 Enhancement Pack Guard 规则
214
+ await _injectEnhancementGuardRules(engine, ctx);
215
+
206
216
  const filesToAudit = allFiles.map((f) => {
207
217
  const content = f.content || (fs.existsSync(f.path) ? fs.readFileSync(f.path, 'utf8') : '');
208
218
  return { path: f.path, content };
@@ -265,3 +275,35 @@ export async function scanProject(ctx, args) {
265
275
  meta: { tool: 'autosnippet_bootstrap' },
266
276
  });
267
277
  }
278
+
279
+ // ─── 内部辅助 ─────────────────────────────────────────────
280
+
281
+ /**
282
+ * 将 Enhancement Pack 的 Guard 规则注入 GuardCheckEngine
283
+ * 静默失败 — Enhancement Pack 不可用不应阻断 Guard 审计
284
+ */
285
+ async function _injectEnhancementGuardRules(engine, ctx) {
286
+ try {
287
+ const { initEnhancementRegistry } = await import('../../../core/enhancement/index.js');
288
+ const enhReg = await initEnhancementRegistry();
289
+ // 使用空语言+空框架列表获取所有已注册的 Pack(不过滤)
290
+ // 这里我们注入 ALL 规则,让 GuardCheckEngine 按 languages 字段自行过滤
291
+ const allPacks = enhReg.all();
292
+ const allGuardRules = [];
293
+ for (const pack of allPacks) {
294
+ try {
295
+ const rules = pack.getGuardRules();
296
+ if (rules.length > 0) {
297
+ allGuardRules.push(...rules);
298
+ }
299
+ } catch {
300
+ /* graceful degradation per pack */
301
+ }
302
+ }
303
+ if (allGuardRules.length > 0) {
304
+ engine.injectExternalRules(allGuardRules);
305
+ }
306
+ } catch {
307
+ /* Enhancement registry not available — non-critical */
308
+ }
309
+ }
@@ -72,7 +72,13 @@ router.post(
72
72
  if (aiProvider) {
73
73
  let enriched = [];
74
74
  try {
75
- enriched = await aiProvider.enrichCandidates(candidates);
75
+ // 获取用户语言偏好
76
+ let lang = 'en';
77
+ try {
78
+ const chatAgent = container.get('chatAgent');
79
+ lang = chatAgent?.getLang?.() || 'en';
80
+ } catch { /* chatAgent not available */ }
81
+ enriched = await aiProvider.enrichCandidates(candidates, { lang });
76
82
  } catch (err) {
77
83
  logger.warn('AI enrichCandidates failed', { error: err.message });
78
84
  }
@@ -1999,6 +1999,7 @@ submit_knowledge 的 code 字段必须是「项目特写」— 将技术的基
1999
1999
  logger: this.#logger,
2000
2000
  source: this.#currentSource,
2001
2001
  fileCache: this.#fileCache || null,
2002
+ lang: this.#currentLang || this.#defaultLang || 'en',
2002
2003
  ...extras,
2003
2004
  };
2004
2005
  }
@@ -1390,6 +1390,10 @@ const extractRecipes = {
1390
1390
  if (comprehensive) {
1391
1391
  extractOpts.comprehensive = true;
1392
1392
  }
1393
+ // 传递用户语言偏好,让 AI 输出匹配用户语言
1394
+ if (ctx.lang && ctx.lang !== 'en') {
1395
+ extractOpts.lang = ctx.lang;
1396
+ }
1393
1397
 
1394
1398
  // 首选:使用当前 aiProvider
1395
1399
  let recipes;
@@ -2635,7 +2639,7 @@ const queryAuditLog = {
2635
2639
  const loadSkill = {
2636
2640
  name: 'load_skill',
2637
2641
  description:
2638
- '加载指定的 Agent Skill 文档,获取领域操作指南和最佳实践参考。可用于冷启动指南 (autosnippet-coldstart)、语言参考 (autosnippet-reference-swift/objc/jsts/python/java/kotlin/go) 等。',
2642
+ '加载指定的 Agent Skill 文档,获取领域操作指南和最佳实践参考。可用于冷启动指南 (autosnippet-coldstart)、语言参考 (autosnippet-reference-swift/objc/jsts/python/java/kotlin/go/dart/rust) 等。',
2639
2643
  parameters: {
2640
2644
  type: 'object',
2641
2645
  properties: {
@@ -276,27 +276,40 @@ export class ComplianceReporter {
276
276
  }
277
277
 
278
278
  _printText(report) {
279
- const { qualityGate, topViolations, fileHotspots, trend } = report;
279
+ const { qualityGate, summary, topViolations, fileHotspots, trend } = report;
280
280
 
281
- const _gateIcon =
281
+ const gateIcon =
282
282
  qualityGate.status === 'PASS' ? '✅' : qualityGate.status === 'WARN' ? '⚠️' : '❌';
283
283
 
284
+ const lines = [];
285
+ lines.push(`${gateIcon} Quality Gate: ${qualityGate.status} Score: ${qualityGate.score}/100`);
286
+ lines.push(` Files: ${summary.filesScanned} Errors: ${summary.errors} Warnings: ${summary.warnings} Infos: ${summary.infos || 0}`);
287
+
284
288
  if (trend.hasHistory) {
285
- const _errTrend = trend.errorsChange > 0 ? `+${trend.errorsChange}` : `${trend.errorsChange}`;
286
- const _warnTrend =
287
- trend.warningsChange > 0 ? `+${trend.warningsChange}` : `${trend.warningsChange}`;
289
+ const errTrend = trend.errorsChange > 0 ? `+${trend.errorsChange}` : `${trend.errorsChange}`;
290
+ const warnTrend = trend.warningsChange > 0 ? `+${trend.warningsChange}` : `${trend.warningsChange}`;
291
+ lines.push(` Trend: Errors ${errTrend} Warnings ${warnTrend}`);
288
292
  }
289
293
 
290
294
  if (topViolations.length > 0) {
295
+ lines.push('');
296
+ lines.push('Top Violations:');
291
297
  for (const v of topViolations.slice(0, 10)) {
292
- const _fix = v.fixRecipeId ? ` → 🔧 recipe:${v.fixRecipeId}` : '';
298
+ const fix = v.fixRecipeId ? ` → 🔧 recipe:${v.fixRecipeId}` : '';
299
+ const sev = v.severity === 'error' ? '🔴' : v.severity === 'warning' ? '🟡' : '🔵';
300
+ lines.push(` ${sev} ${v.ruleId} (${v.occurrences} hits, ${v.fileCount} files)${fix}`);
293
301
  }
294
302
  }
295
303
 
296
304
  if (fileHotspots.length > 0) {
297
- for (const _f of fileHotspots.slice(0, 10)) {
305
+ lines.push('');
306
+ lines.push('File Hotspots:');
307
+ for (const f of fileHotspots.slice(0, 10)) {
308
+ lines.push(` ${f.filePath} — ${f.violationCount} violations (${f.errorCount} errors)`);
298
309
  }
299
310
  }
311
+
312
+ this.logger.info(lines.join('\n'));
300
313
  }
301
314
 
302
315
  _printMarkdown(report) {
@@ -360,6 +360,84 @@ const BUILT_IN_RULES = {
360
360
  fixSuggestion: '在 await 前缓存所需数据,或在 await 后检查 mounted',
361
361
  },
362
362
 
363
+ // ══════════════════════════════════════════════════════════
364
+ // Rust
365
+ // ══════════════════════════════════════════════════════════
366
+
367
+ 'rust-no-unwrap': {
368
+ message: '生产代码避免 .unwrap(),None/Err 时会 panic。使用 ? 或 unwrap_or / expect',
369
+ severity: 'warning',
370
+ pattern: '\\.unwrap\\s*\\(\\)',
371
+ languages: ['rust'],
372
+ dimension: 'file',
373
+ category: 'correctness',
374
+ fixSuggestion: '使用 ? 操作符传播错误,或 .unwrap_or_default() / .expect("原因")',
375
+ excludePaths: /(?:^|[\/\\])tests?[\/\\]|[\/\\]test_|_test\.rs$|[\/\\]benches[\/\\]/,
376
+ },
377
+ 'rust-no-expect-without-msg': {
378
+ message: 'expect() 应提供有意义的错误消息,帮助定位 panic 原因',
379
+ severity: 'info',
380
+ pattern: '\\.expect\\s*\\(\\s*""\\s*\\)',
381
+ languages: ['rust'],
382
+ dimension: 'file',
383
+ category: 'style',
384
+ fixSuggestion: '提供描述性消息: .expect("config file should exist")',
385
+ },
386
+ 'rust-unsafe-block': {
387
+ message: 'unsafe 块需要 SAFETY 注释说明前置条件,确保审计可追踪',
388
+ severity: 'warning',
389
+ pattern: 'unsafe\\s*\\{',
390
+ languages: ['rust'],
391
+ dimension: 'file',
392
+ category: 'safety',
393
+ fixSuggestion: '在 unsafe 块前添加 // SAFETY: ... 注释说明安全前提',
394
+ },
395
+ 'rust-no-todo-macro': {
396
+ message: '生产代码不应包含 todo!() / unimplemented!(),运行时会 panic',
397
+ severity: 'warning',
398
+ pattern: '\\b(?:todo|unimplemented)!\\s*\\(',
399
+ languages: ['rust'],
400
+ dimension: 'file',
401
+ category: 'correctness',
402
+ excludePaths: /(?:^|[\/\\])tests?[\/\\]|[\/\\]examples[\/\\]/,
403
+ },
404
+ 'rust-clone-overuse': {
405
+ message: '频繁 .clone() 可能暗示所有权设计问题,考虑使用借用或 Cow',
406
+ severity: 'info',
407
+ pattern: '\\.clone\\s*\\(\\)',
408
+ languages: ['rust'],
409
+ dimension: 'file',
410
+ category: 'performance',
411
+ fixSuggestion: '分析是否可用 &T 借用替代,或使用 Cow<T> 延迟克隆',
412
+ },
413
+ 'rust-no-panic-in-lib': {
414
+ message: 'panic!() 在库代码中应避免使用,返回 Result 让调用方决定如何处理',
415
+ severity: 'warning',
416
+ pattern: '\\bpanic!\\s*\\(',
417
+ languages: ['rust'],
418
+ dimension: 'file',
419
+ category: 'correctness',
420
+ excludePaths: /(?:^|[\/\\])tests?[\/\\]|[\/\\]benches[\/\\]|main\.rs$/,
421
+ },
422
+ 'rust-std-mutex-in-async': {
423
+ message: 'async 代码中不应使用 std::sync::Mutex,MutexGuard 不是 Send',
424
+ severity: 'warning',
425
+ pattern: 'std::sync::Mutex',
426
+ languages: ['rust'],
427
+ dimension: 'file',
428
+ category: 'correctness',
429
+ fixSuggestion: '使用 tokio::sync::Mutex 或 parking_lot::Mutex',
430
+ },
431
+ 'rust-no-string-push-in-loop': {
432
+ message: '循环中 String::push_str/format! 拼接可能导致多次分配,考虑预分配或 join',
433
+ severity: 'info',
434
+ pattern: 'for\\s+.*\\{[\\s\\S]*?(?:push_str|format!)',
435
+ languages: ['rust'],
436
+ dimension: 'file',
437
+ category: 'performance',
438
+ fixSuggestion: '使用 Vec<&str> 收集后 .join(),或 String::with_capacity 预分配',
439
+ },
440
+
363
441
  };
364
442
 
365
443
  /**
@@ -386,6 +464,45 @@ export class GuardCheckEngine {
386
464
  this._astRulesCache = null;
387
465
  this._cacheTime = 0;
388
466
  this._cacheTTL = options.cacheTTL || 60_000; // 1min
467
+ /** @type {Map<string, object>} Enhancement Pack 注入的外部规则 */
468
+ this._externalRules = new Map();
469
+ /** @type {Map<string, RegExp>} 已编译的正则缓存 (pattern string → RegExp) */
470
+ this._regexCache = new Map();
471
+ }
472
+
473
+ /**
474
+ * 注入 Enhancement Pack 外部规则(支持 RegExp 和 string pattern)
475
+ * 与 BUILT_IN_RULES 合并检查,自动跳过 ruleId 重复的规则
476
+ * @param {Array<{ruleId: string, pattern: RegExp|string, severity: string, message: string, category?: string, dimension?: string, languages?: string[], fixSuggestion?: string}>} rules
477
+ */
478
+ injectExternalRules(rules) {
479
+ if (!Array.isArray(rules)) return;
480
+ for (const rule of rules) {
481
+ if (!rule.ruleId) continue;
482
+ // 跳过与 BUILT_IN_RULES 重复的模式(通过比较 pattern 源文本)
483
+ const rulePatternStr = rule.pattern instanceof RegExp ? rule.pattern.source : String(rule.pattern || '');
484
+ const isDuplicate = Object.entries(this._builtInRules).some(([, builtIn]) => {
485
+ return builtIn.pattern === rulePatternStr;
486
+ });
487
+ if (isDuplicate) {
488
+ this.logger.debug(`[GuardCheckEngine] Skipping duplicate external rule: ${rule.ruleId}`);
489
+ continue;
490
+ }
491
+ this._externalRules.set(rule.ruleId, {
492
+ id: rule.ruleId,
493
+ name: rule.ruleId,
494
+ message: rule.message || '',
495
+ pattern: rule.pattern,
496
+ languages: rule.languages || [],
497
+ severity: rule.severity || 'warning',
498
+ dimension: rule.dimension || 'file',
499
+ category: rule.category || '',
500
+ source: 'enhancement-pack',
501
+ type: 'regex',
502
+ fixSuggestion: rule.fixSuggestion || null,
503
+ });
504
+ }
505
+ this.logger.debug(`[GuardCheckEngine] External rules injected: ${this._externalRules.size} active`);
389
506
  }
390
507
 
391
508
  /**
@@ -476,6 +593,14 @@ export class GuardCheckEngine {
476
593
  }
477
594
  }
478
595
 
596
+ // 合并 Enhancement Pack 外部规则(不覆盖已有 ID)
597
+ for (const [ruleId, rule] of this._externalRules) {
598
+ if (!existingIds.has(ruleId)) {
599
+ rules.push(rule);
600
+ existingIds.add(ruleId);
601
+ }
602
+ }
603
+
479
604
  // 按语言过滤
480
605
  if (language) {
481
606
  rules = rules.filter((r) => !r.languages?.length || r.languages.includes(language));
@@ -538,7 +663,7 @@ export class GuardCheckEngine {
538
663
 
539
664
  let re;
540
665
  try {
541
- re = new RegExp(rule.pattern);
666
+ re = this._compilePattern(rule.pattern);
542
667
  } catch {
543
668
  this.logger.debug(`Invalid regex in rule ${rule.id}: ${rule.pattern}`);
544
669
  continue;
@@ -823,15 +948,15 @@ export class GuardCheckEngine {
823
948
  // ── JavaScript / TypeScript ──
824
949
  if (language === 'javascript' || language === 'typescript') {
825
950
  // Promise 未处理 rejection 检查
826
- if (code.includes('.then(') && !code.includes('.catch(') && !code.includes('.then(') === false) {
827
- // 简化: 检查 new Promise 或 .then() 链没有 .catch()
951
+ // 文件中存在 .then() 但没有对应的 .catch() try-catch
952
+ if (code.includes('.then(') && !code.includes('.catch(') && !code.includes('try')) {
828
953
  const thenLines = [];
829
954
  for (let i = 0; i < lines.length; i++) {
830
- if (/\.then\s*\(/.test(lines[i]) && !/\.catch\s*\(/.test(code)) {
955
+ if (/\.then\s*\(/.test(lines[i])) {
831
956
  thenLines.push(i);
832
957
  }
833
958
  }
834
- if (thenLines.length > 0 && !code.includes('.catch(')) {
959
+ if (thenLines.length > 0) {
835
960
  violations.push({
836
961
  ruleId: 'js-unhandled-promise',
837
962
  message: 'Promise 链缺少 .catch() 错误处理,未捕获的 rejection 可能导致静默失败',
@@ -1033,6 +1158,25 @@ export class GuardCheckEngine {
1033
1158
  clearCache() {
1034
1159
  this._customRulesCache = null;
1035
1160
  this._cacheTime = 0;
1161
+ this._regexCache.clear();
1162
+ }
1163
+
1164
+ /**
1165
+ * 编译正则模式(支持 RegExp 对象和 string,带缓存)
1166
+ * @param {RegExp|string} pattern
1167
+ * @returns {RegExp}
1168
+ */
1169
+ _compilePattern(pattern) {
1170
+ if (pattern instanceof RegExp) {
1171
+ return pattern;
1172
+ }
1173
+ const key = String(pattern);
1174
+ let cached = this._regexCache.get(key);
1175
+ if (!cached) {
1176
+ cached = new RegExp(key);
1177
+ this._regexCache.set(key, cached);
1178
+ }
1179
+ return cached;
1036
1180
  }
1037
1181
 
1038
1182
  /**
@@ -1041,6 +1185,13 @@ export class GuardCheckEngine {
1041
1185
  getBuiltInRules() {
1042
1186
  return { ...this._builtInRules };
1043
1187
  }
1188
+
1189
+ /**
1190
+ * 获取已注入的外部规则数量
1191
+ */
1192
+ getExternalRuleCount() {
1193
+ return this._externalRules.size;
1194
+ }
1044
1195
  }
1045
1196
 
1046
1197
  export default GuardCheckEngine;
@@ -25,6 +25,21 @@ const SKIP_DIRS = new Set([
25
25
  'Carthage',
26
26
  'xcuserdata',
27
27
  '__pycache__',
28
+ // Rust
29
+ 'target',
30
+ '.cargo',
31
+ // Python 虚拟环境
32
+ 'venv',
33
+ '.venv',
34
+ 'env',
35
+ // JVM 构建工具
36
+ '.gradle',
37
+ '.mvn',
38
+ // 通用构建/输出目录
39
+ 'out',
40
+ 'coverage',
41
+ '.turbo',
42
+ '.parcel-cache',
28
43
  ]);
29
44
 
30
45
  /**
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "autosnippet",
3
- "version": "3.0.2",
4
- "description": "AutoSnippet - 连接开发者、AI 与项目知识库的工具",
3
+ "version": "3.0.6",
4
+ "description": "Extract code patterns into a knowledge base for AI coding assistants",
5
5
  "type": "module",
6
6
  "main": "lib/bootstrap.js",
7
7
  "engines": {
@@ -43,9 +43,27 @@
43
43
  "asd": "bin/cli.js"
44
44
  },
45
45
  "keywords": [
46
- "Xcode",
47
- "iOS",
48
- "Snippet"
46
+ "snippet",
47
+ "code-snippet",
48
+ "knowledge-base",
49
+ "ai",
50
+ "mcp",
51
+ "copilot",
52
+ "cursor",
53
+ "code-review",
54
+ "guard",
55
+ "ast",
56
+ "tree-sitter",
57
+ "xcode",
58
+ "ios",
59
+ "swift",
60
+ "typescript",
61
+ "javascript",
62
+ "python",
63
+ "rust",
64
+ "kotlin",
65
+ "developer-tools",
66
+ "cli"
49
67
  ],
50
68
  "homepage": "https://github.com/GxFn/AutoSnippet#readme",
51
69
  "repository": {
@@ -116,7 +134,8 @@
116
134
  "tree-sitter-objc": "^3.0.2",
117
135
  "tree-sitter-python": "^0.25.0",
118
136
  "tree-sitter-swift": "^0.7.1",
119
- "tree-sitter-typescript": "^0.23.2"
137
+ "tree-sitter-typescript": "^0.23.2",
138
+ "tree-sitter-rust": "^0.23.2"
120
139
  },
121
140
  "overrides": {
122
141
  "tree-sitter-kotlin": {
@@ -140,6 +159,9 @@
140
159
  "tree-sitter-objc": {
141
160
  "tree-sitter": "$tree-sitter"
142
161
  },
162
+ "tree-sitter-rust": {
163
+ "tree-sitter": "$tree-sitter"
164
+ },
143
165
  "tree-sitter-c": {
144
166
  "tree-sitter": "$tree-sitter"
145
167
  },