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.
- package/README.md +85 -239
- package/bin/mcp-server.js +12 -1
- package/dashboard/dist/assets/{icons-Cdq22n2i.js → icons-eQ_rWCus.js} +97 -102
- package/dashboard/dist/assets/index-B3Nnkdxi.js +133 -0
- package/dashboard/dist/assets/index-BFNDAqh3.css +1 -0
- package/dashboard/dist/index.html +3 -3
- package/lib/cli/SetupService.js +9 -12
- package/lib/cli/UpgradeService.js +15 -17
- package/lib/core/AstAnalyzer.js +2 -2
- package/lib/core/ast/ensure-grammars.js +2 -0
- package/lib/core/ast/index.js +8 -0
- package/lib/core/ast/lang-rust.js +695 -0
- package/lib/core/discovery/PythonDiscoverer.js +3 -0
- package/lib/core/discovery/RustDiscoverer.js +467 -0
- package/lib/core/discovery/index.js +3 -0
- package/lib/core/enhancement/django-enhancement.js +169 -3
- package/lib/core/enhancement/fastapi-enhancement.js +149 -3
- package/lib/core/enhancement/go-grpc-enhancement.js +4 -0
- package/lib/core/enhancement/go-web-enhancement.js +6 -0
- package/lib/core/enhancement/index.js +5 -0
- package/lib/core/enhancement/langchain-enhancement.js +233 -0
- package/lib/core/enhancement/ml-enhancement.js +265 -0
- package/lib/core/enhancement/nextjs-enhancement.js +219 -0
- package/lib/core/enhancement/node-server-enhancement.js +178 -4
- package/lib/core/enhancement/react-enhancement.js +165 -4
- package/lib/core/enhancement/rust-tokio-enhancement.js +231 -0
- package/lib/core/enhancement/rust-web-enhancement.js +256 -0
- package/lib/core/enhancement/spring-enhancement.js +2 -0
- package/lib/core/enhancement/vue-enhancement.js +143 -2
- package/lib/external/ai/AiProvider.js +45 -6
- package/lib/external/mcp/handlers/bootstrap/skills.js +2 -0
- package/lib/external/mcp/handlers/bootstrap.js +33 -9
- package/lib/external/mcp/handlers/guard.js +42 -0
- package/lib/http/routes/candidates.js +7 -1
- package/lib/service/chat/ChatAgent.js +1 -0
- package/lib/service/chat/tools.js +5 -1
- package/lib/service/guard/ComplianceReporter.js +20 -7
- package/lib/service/guard/GuardCheckEngine.js +156 -5
- package/lib/service/guard/SourceFileCollector.js +15 -0
- package/package.json +28 -6
- package/scripts/install-vscode-copilot.js +32 -97
- package/scripts/setup-mcp-config.js +18 -36
- package/skills/autosnippet-coldstart/SKILL.md +4 -2
- package/skills/autosnippet-concepts/SKILL.md +5 -3
- package/skills/autosnippet-reference-rust/SKILL.md +401 -0
- package/skills/autosnippet-structure/SKILL.md +1 -1
- package/templates/recipes-setup/README.md +2 -2
- package/templates/recipes-setup/_template.md +1 -1
- package/dashboard/dist/assets/index-ClkyPkDX.js +0 -133
- 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
|
-
|
|
404
|
+
guardEngine = new GuardCheckEngine(db);
|
|
404
405
|
const guardFiles = allFiles.map((f) => ({ path: f.path, content: f.content }));
|
|
405
|
-
guardAudit =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|
|
@@ -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
|
|
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
|
|
286
|
-
const
|
|
287
|
-
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
827
|
-
|
|
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])
|
|
955
|
+
if (/\.then\s*\(/.test(lines[i])) {
|
|
831
956
|
thenLines.push(i);
|
|
832
957
|
}
|
|
833
958
|
}
|
|
834
|
-
if (thenLines.length > 0
|
|
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.
|
|
4
|
-
"description": "
|
|
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
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"
|
|
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
|
},
|