autosnippet 3.0.0 → 3.0.2
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 +230 -324
- package/bin/api-server.js +1 -1
- package/bin/cli.js +204 -244
- package/bin/mcp-server.js +5 -3
- package/config/knowledge-base.config.js +132 -132
- package/dashboard/dist/assets/{icons-CEfgGaZi.js → icons-Cdq22n2i.js} +95 -100
- package/dashboard/dist/assets/index-ClkyPkDX.js +133 -0
- package/dashboard/dist/assets/index-t4QrJwv1.css +1 -0
- package/dashboard/dist/index.html +3 -3
- package/lib/bootstrap.js +8 -8
- package/lib/cli/AiScanService.js +86 -40
- package/lib/cli/KnowledgeSyncService.js +113 -74
- package/lib/cli/SetupService.js +439 -277
- package/lib/cli/UpgradeService.js +63 -100
- package/lib/core/AstAnalyzer.js +276 -597
- package/lib/core/ast/ProjectGraph.js +101 -40
- package/lib/core/ast/ensure-grammars.js +232 -0
- package/lib/core/ast/index.js +115 -0
- package/lib/core/ast/lang-dart.js +661 -0
- package/lib/core/ast/lang-go.js +530 -0
- package/lib/core/ast/lang-java.js +435 -0
- package/lib/core/ast/lang-javascript.js +272 -0
- package/lib/core/ast/lang-kotlin.js +423 -0
- package/lib/core/ast/lang-objc.js +388 -0
- package/lib/core/ast/lang-python.js +371 -0
- package/lib/core/ast/lang-swift.js +337 -0
- package/lib/core/ast/lang-typescript.js +503 -0
- package/lib/core/capability/CapabilityProbe.js +18 -9
- package/lib/core/constitution/Constitution.js +2 -3
- package/lib/core/constitution/ConstitutionValidator.js +65 -24
- package/lib/core/discovery/DartDiscoverer.js +534 -0
- package/lib/core/discovery/DiscovererRegistry.js +83 -0
- package/lib/core/discovery/GenericDiscoverer.js +225 -0
- package/lib/core/discovery/GoDiscoverer.js +541 -0
- package/lib/core/discovery/JvmDiscoverer.js +506 -0
- package/lib/core/discovery/NodeDiscoverer.js +466 -0
- package/lib/core/discovery/ProjectDiscoverer.js +93 -0
- package/lib/core/discovery/PythonDiscoverer.js +338 -0
- package/lib/core/discovery/SpmDiscoverer.js +5 -0
- package/lib/core/discovery/index.js +53 -0
- package/lib/core/enhancement/EnhancementPack.js +71 -0
- package/lib/core/enhancement/EnhancementRegistry.js +47 -0
- package/lib/core/enhancement/android-enhancement.js +102 -0
- package/lib/core/enhancement/django-enhancement.js +70 -0
- package/lib/core/enhancement/fastapi-enhancement.js +63 -0
- package/lib/core/enhancement/go-grpc-enhancement.js +152 -0
- package/lib/core/enhancement/go-web-enhancement.js +201 -0
- package/lib/core/enhancement/index.js +65 -0
- package/lib/core/enhancement/node-server-enhancement.js +88 -0
- package/lib/core/enhancement/react-enhancement.js +86 -0
- package/lib/core/enhancement/spring-enhancement.js +112 -0
- package/lib/core/enhancement/vue-enhancement.js +96 -0
- package/lib/core/gateway/Gateway.js +8 -9
- package/lib/core/gateway/GatewayActionRegistry.js +1 -1
- package/lib/core/permission/PermissionManager.js +12 -8
- package/lib/domain/index.js +13 -9
- package/lib/domain/knowledge/KnowledgeEntry.js +111 -101
- package/lib/domain/knowledge/KnowledgeRepository.js +0 -1
- package/lib/domain/knowledge/Lifecycle.js +22 -22
- package/lib/domain/knowledge/index.js +9 -12
- package/lib/domain/knowledge/values/Constraints.js +31 -21
- package/lib/domain/knowledge/values/Content.js +21 -13
- package/lib/domain/knowledge/values/Quality.js +31 -18
- package/lib/domain/knowledge/values/Reasoning.js +20 -12
- package/lib/domain/knowledge/values/Relations.js +37 -25
- package/lib/domain/knowledge/values/Stats.js +18 -12
- package/lib/domain/knowledge/values/index.js +4 -3
- package/lib/domain/snippet/Snippet.js +35 -10
- package/lib/external/ai/AiFactory.js +48 -16
- package/lib/external/ai/AiProvider.js +184 -90
- package/lib/external/ai/providers/ClaudeProvider.js +25 -12
- package/lib/external/ai/providers/GoogleGeminiProvider.js +59 -30
- package/lib/external/ai/providers/MockProvider.js +9 -3
- package/lib/external/ai/providers/OpenAiProvider.js +51 -29
- package/lib/external/mcp/McpServer.js +66 -36
- package/lib/external/mcp/errorHandler.js +23 -11
- package/lib/external/mcp/handlers/LanguageExtensions.js +138 -53
- package/lib/external/mcp/handlers/TargetClassifier.js +52 -16
- package/lib/external/mcp/handlers/bootstrap/pipeline/BootstrapSnapshot.js +81 -20
- package/lib/external/mcp/handlers/bootstrap/pipeline/EpisodicMemory.js +71 -42
- package/lib/external/mcp/handlers/bootstrap/pipeline/IncrementalBootstrap.js +9 -17
- package/lib/external/mcp/handlers/bootstrap/pipeline/ToolResultCache.js +14 -9
- package/lib/external/mcp/handlers/bootstrap/pipeline/dimension-context.js +15 -7
- package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +352 -153
- package/lib/external/mcp/handlers/bootstrap/pipeline/tier-scheduler.js +52 -12
- package/lib/external/mcp/handlers/bootstrap/skills.js +143 -39
- package/lib/external/mcp/handlers/bootstrap.js +691 -168
- package/lib/external/mcp/handlers/browse.js +66 -22
- package/lib/external/mcp/handlers/candidate.js +118 -35
- package/lib/external/mcp/handlers/consolidated.js +49 -17
- package/lib/external/mcp/handlers/guard.js +104 -39
- package/lib/external/mcp/handlers/knowledge.js +60 -36
- package/lib/external/mcp/handlers/search.js +43 -14
- package/lib/external/mcp/handlers/skill.js +120 -45
- package/lib/external/mcp/handlers/structure.js +240 -86
- package/lib/external/mcp/handlers/system.js +42 -12
- package/lib/external/mcp/handlers/wiki.js +58 -33
- package/lib/external/mcp/tools.js +306 -123
- package/lib/http/HttpServer.js +72 -47
- package/lib/http/middleware/RateLimiter.js +5 -3
- package/lib/http/middleware/errorHandler.js +6 -1
- package/lib/http/middleware/requestLogger.js +14 -3
- package/lib/http/middleware/roleResolver.js +30 -23
- package/lib/http/routes/ai.js +387 -265
- package/lib/http/routes/auth.js +81 -61
- package/lib/http/routes/candidates.js +430 -320
- package/lib/http/routes/commands.js +289 -189
- package/lib/http/routes/extract.js +158 -125
- package/lib/http/routes/guardRules.js +309 -217
- package/lib/http/routes/knowledge.js +213 -154
- package/lib/http/routes/modules.js +578 -0
- package/lib/http/routes/monitoring.js +6 -6
- package/lib/http/routes/recipes.js +104 -93
- package/lib/http/routes/search.js +361 -305
- package/lib/http/routes/skills.js +145 -98
- package/lib/http/routes/snippets.js +42 -30
- package/lib/http/routes/spm.js +3 -405
- package/lib/http/routes/violations.js +113 -93
- package/lib/http/routes/wiki.js +211 -170
- package/lib/http/utils/routeHelpers.js +3 -1
- package/lib/http/utils/sse-sessions.js +16 -6
- package/lib/http/utils/sse.js +15 -5
- package/lib/infrastructure/audit/AuditLogger.js +5 -2
- package/lib/infrastructure/audit/AuditStore.js +10 -7
- package/lib/infrastructure/cache/CacheService.js +3 -1
- package/lib/infrastructure/cache/GraphCache.js +8 -4
- package/lib/infrastructure/cache/UnifiedCacheAdapter.js +1 -1
- package/lib/infrastructure/config/ConfigLoader.js +9 -5
- package/lib/infrastructure/config/Defaults.js +30 -10
- package/lib/infrastructure/config/Paths.js +28 -8
- package/lib/infrastructure/config/TriggerSymbol.js +22 -10
- package/lib/infrastructure/database/DatabaseConnection.js +15 -10
- package/lib/infrastructure/database/migrations/001_initial_schema.js +0 -1
- package/lib/infrastructure/external/ClipboardManager.js +6 -2
- package/lib/infrastructure/external/NativeUi.js +50 -43
- package/lib/infrastructure/external/OpenBrowser.js +14 -17
- package/lib/infrastructure/external/XcodeAutomation.js +14 -258
- package/lib/infrastructure/logging/Logger.js +46 -30
- package/lib/infrastructure/monitoring/ErrorTracker.js +7 -5
- package/lib/infrastructure/monitoring/PerformanceMonitor.js +12 -4
- package/lib/infrastructure/paths/HeaderResolver.js +25 -9
- package/lib/infrastructure/paths/PathFinder.js +34 -12
- package/lib/infrastructure/plugin/PluginManager.js +26 -8
- package/lib/infrastructure/realtime/RealtimeService.js +2 -2
- package/lib/infrastructure/vector/Chunker.js +22 -7
- package/lib/infrastructure/vector/IndexingPipeline.js +46 -22
- package/lib/infrastructure/vector/JsonVectorAdapter.js +90 -53
- package/lib/infrastructure/vector/VectorStore.js +28 -10
- package/lib/injection/ServiceContainer.js +247 -93
- package/lib/platform/ios/index.js +63 -0
- package/lib/platform/ios/routes/spm.js +437 -0
- package/lib/platform/ios/snippet/PlaceholderConverter.js +55 -0
- package/lib/platform/ios/snippet/XcodeCodec.js +112 -0
- package/lib/{service → platform/ios}/spm/DependencyGraph.js +41 -17
- package/lib/{service → platform/ios}/spm/PackageSwiftParser.js +41 -14
- package/lib/{service → platform/ios}/spm/PolicyEngine.js +9 -4
- package/lib/platform/ios/spm/SpmDiscoverer.js +122 -0
- package/lib/{service → platform/ios}/spm/SpmService.js +385 -127
- package/lib/{service/automation → platform/ios/xcode}/SaveEventFilter.js +8 -7
- package/lib/platform/ios/xcode/XcodeAutomation.js +350 -0
- package/lib/{service/automation → platform/ios/xcode}/XcodeIntegration.js +325 -145
- package/lib/repository/base/BaseRepository.js +7 -9
- package/lib/repository/knowledge/KnowledgeRepository.impl.js +98 -75
- package/lib/repository/token/TokenUsageStore.js +4 -2
- package/lib/service/automation/ActionPipeline.js +1 -1
- package/lib/service/automation/AutomationOrchestrator.js +8 -4
- package/lib/service/automation/ContextCollector.js +7 -5
- package/lib/service/automation/DirectiveDetector.js +23 -16
- package/lib/service/automation/FileWatcher.js +112 -56
- package/lib/service/automation/TriggerResolver.js +6 -4
- package/lib/service/automation/handlers/AlinkHandler.js +24 -12
- package/lib/service/automation/handlers/CreateHandler.js +19 -20
- package/lib/service/automation/handlers/DraftHandler.js +14 -8
- package/lib/service/automation/handlers/GuardHandler.js +93 -63
- package/lib/service/automation/handlers/HeaderHandler.js +1 -6
- package/lib/service/automation/handlers/SearchHandler.js +155 -88
- package/lib/service/bootstrap/BootstrapTaskManager.js +77 -35
- package/lib/service/candidate/SimilarityService.js +25 -9
- package/lib/service/chat/AnalystAgent.js +50 -24
- package/lib/service/chat/CandidateGuardrail.js +143 -17
- package/lib/service/chat/ChatAgent.js +759 -243
- package/lib/service/chat/ContextWindow.js +116 -71
- package/lib/service/chat/ConversationStore.js +77 -36
- package/lib/service/chat/EpisodicConsolidator.js +47 -23
- package/lib/service/chat/HandoffProtocol.js +98 -22
- package/lib/service/chat/Memory.js +34 -14
- package/lib/service/chat/ProducerAgent.js +40 -20
- package/lib/service/chat/ProjectSemanticMemory.js +109 -78
- package/lib/service/chat/ReasoningLayer.js +148 -70
- package/lib/service/chat/ReasoningTrace.js +44 -32
- package/lib/service/chat/TaskPipeline.js +39 -19
- package/lib/service/chat/ToolRegistry.js +48 -29
- package/lib/service/chat/WorkingMemory.js +44 -18
- package/lib/service/chat/tools.js +1096 -494
- package/lib/service/context/RecipeExtractor.js +132 -51
- package/lib/service/cursor/CursorDeliveryPipeline.js +82 -37
- package/lib/service/cursor/KnowledgeCompressor.js +25 -22
- package/lib/service/cursor/RulesGenerator.js +13 -7
- package/lib/service/cursor/SkillsSyncer.js +77 -27
- package/lib/service/cursor/TokenBudget.js +2 -2
- package/lib/service/cursor/TopicClassifier.js +54 -20
- package/lib/service/guard/ComplianceReporter.js +55 -43
- package/lib/service/guard/ExclusionManager.js +67 -29
- package/lib/service/guard/GuardCheckEngine.js +381 -86
- package/lib/service/guard/GuardFeedbackLoop.js +22 -10
- package/lib/service/guard/GuardService.js +29 -19
- package/lib/service/guard/RuleLearner.js +55 -23
- package/lib/service/guard/SourceFileCollector.js +27 -20
- package/lib/service/guard/ViolationsStore.js +43 -38
- package/lib/service/knowledge/CodeEntityGraph.js +147 -82
- package/lib/service/knowledge/ConfidenceRouter.js +12 -10
- package/lib/service/knowledge/KnowledgeFileWriter.js +147 -56
- package/lib/service/knowledge/KnowledgeGraphService.js +81 -34
- package/lib/service/knowledge/KnowledgeService.js +222 -112
- package/lib/service/module/ModuleService.js +969 -0
- package/lib/service/quality/FeedbackCollector.js +27 -15
- package/lib/service/quality/QualityScorer.js +78 -24
- package/lib/service/recipe/RecipeCandidateValidator.js +110 -44
- package/lib/service/recipe/RecipeParser.js +78 -45
- package/lib/service/search/CoarseRanker.js +43 -28
- package/lib/service/search/CrossEncoderReranker.js +32 -21
- package/lib/service/search/InvertedIndex.js +21 -7
- package/lib/service/search/MultiSignalRanker.js +90 -28
- package/lib/service/search/RetrievalFunnel.js +45 -24
- package/lib/service/search/SearchEngine.js +255 -103
- package/lib/service/skills/EventAggregator.js +32 -15
- package/lib/service/skills/SignalCollector.js +140 -64
- package/lib/service/skills/SkillAdvisor.js +79 -42
- package/lib/service/skills/SkillHooks.js +16 -14
- package/lib/service/snippet/PlaceholderConverter.js +5 -0
- package/lib/service/snippet/SnippetFactory.js +116 -99
- package/lib/service/snippet/SnippetInstaller.js +234 -62
- package/lib/service/snippet/codecs/SnippetCodec.js +67 -0
- package/lib/service/snippet/codecs/VSCodeCodec.js +102 -0
- package/lib/service/snippet/codecs/XcodeCodec.js +5 -0
- package/lib/service/wiki/WikiGenerator.js +637 -263
- package/lib/shared/DimensionCopyRegistry.js +472 -0
- package/lib/shared/LanguageService.js +399 -0
- package/lib/shared/PathGuard.js +45 -28
- package/lib/shared/RecipeReadinessChecker.js +72 -12
- package/lib/shared/constants.js +41 -41
- package/lib/shared/errors/BaseError.js +2 -2
- package/lib/shared/errors/index.js +4 -4
- package/lib/shared/similarity.js +25 -8
- package/lib/shared/token-utils.js +6 -2
- package/lib/shared/utils/common.js +12 -4
- package/package.json +49 -13
- package/scripts/bench-real-projects.mjs +256 -0
- package/scripts/build-native-ui.js +30 -30
- package/scripts/clear-old-vector-index.js +5 -35
- package/scripts/clear-vector-cache.js +7 -37
- package/scripts/collect-test-project-stats.mjs +160 -0
- package/scripts/diagnose-mcp.js +41 -32
- package/scripts/ensure-parse-package.js +6 -9
- package/scripts/generate-recipe-drafts.js +116 -77
- package/scripts/init-db.js +3 -20
- package/scripts/init-snippets.js +305 -0
- package/scripts/init-vector-db.js +173 -170
- package/scripts/install-cursor-skill.js +148 -104
- package/scripts/install-full.js +8 -21
- package/scripts/install-vscode-copilot.js +146 -145
- package/scripts/migrate-md-to-knowledge.mjs +139 -151
- package/scripts/postinstall-safe.js +5 -17
- package/scripts/recipe-audit.js +106 -82
- package/scripts/release.js +283 -323
- package/scripts/setup-mcp-config.js +60 -52
- package/scripts/verify-context-api.js +20 -20
- package/skills/autosnippet-analysis/SKILL.md +10 -6
- package/skills/autosnippet-candidates/SKILL.md +27 -26
- package/skills/autosnippet-coldstart/SKILL.md +555 -38
- package/skills/autosnippet-concepts/SKILL.md +349 -337
- package/skills/autosnippet-create/SKILL.md +5 -5
- package/skills/autosnippet-reference-dart/SKILL.md +543 -0
- package/skills/autosnippet-reference-go/SKILL.md +539 -0
- package/skills/autosnippet-reference-java/SKILL.md +534 -0
- package/skills/autosnippet-reference-jsts/SKILL.md +41 -9
- package/skills/autosnippet-reference-kotlin/SKILL.md +526 -0
- package/skills/autosnippet-reference-objc/SKILL.md +29 -6
- package/skills/autosnippet-reference-python/SKILL.md +800 -0
- package/skills/autosnippet-reference-swift/SKILL.md +70 -14
- package/skills/autosnippet-structure/SKILL.md +4 -4
- package/templates/cursor-rules/autosnippet-conventions.mdc +2 -2
- package/templates/recipes-setup/README.md +2 -2
- package/templates/recipes-setup/_template.md +1 -1
- package/dashboard/dist/assets/index-Bun3ld_J.css +0 -1
- package/dashboard/dist/assets/index-_Sk_Dmg3.js +0 -143
- package/resources/asd-entry/main.swift +0 -159
- package/scripts/build-asd-entry.js +0 -51
- package/scripts/init-xcode-snippets.js +0 -311
- package/template.json +0 -39
|
@@ -1,24 +1,38 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* GuardCheckEngine - Guard 规则检查引擎
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* 从 V1 guard/ios 迁移,适配 V2 架构
|
|
5
5
|
* 支持: 正则模式匹配 + AST 语义规则 + code-level 检查 + 多维度审计
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import Logger from '../../infrastructure/logging/Logger.js';
|
|
9
8
|
import * as AstAnalyzerModule from '../../core/AstAnalyzer.js';
|
|
9
|
+
import Logger from '../../infrastructure/logging/Logger.js';
|
|
10
|
+
import { LanguageService } from '../../shared/LanguageService.js';
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
|
-
*
|
|
13
|
-
*
|
|
13
|
+
* 内置默认规则集 — 多语言基础规则
|
|
14
|
+
*
|
|
15
|
+
* 每条规则包含:
|
|
16
|
+
* - message: 违反时的中文提示
|
|
17
|
+
* - severity: 'error' | 'warning' | 'info'
|
|
18
|
+
* - pattern: 行级正则(不跨行)
|
|
19
|
+
* - languages: 适用语言数组
|
|
20
|
+
* - dimension: 'file' | 'target' | 'project'
|
|
21
|
+
* - category: 规则分类 (安全 / 性能 / 风格 / 正确性)
|
|
22
|
+
* - fixSuggestion?: 修复建议
|
|
14
23
|
*/
|
|
15
24
|
const BUILT_IN_RULES = {
|
|
25
|
+
// ══════════════════════════════════════════════════════════
|
|
26
|
+
// ObjC / Swift — iOS 核心规则
|
|
27
|
+
// ══════════════════════════════════════════════════════════
|
|
28
|
+
|
|
16
29
|
'no-main-thread-sync': {
|
|
17
30
|
message: '禁止在主线程上使用 dispatch_sync(main),易死锁',
|
|
18
31
|
severity: 'error',
|
|
19
32
|
pattern: 'dispatch_sync\\s*\\([^)]*main',
|
|
20
33
|
languages: ['objc', 'swift'],
|
|
21
34
|
dimension: 'file',
|
|
35
|
+
category: 'correctness',
|
|
22
36
|
},
|
|
23
37
|
'main-thread-sync-swift': {
|
|
24
38
|
message: '禁止在主线程上使用 DispatchQueue.main.sync,易死锁',
|
|
@@ -26,13 +40,16 @@ const BUILT_IN_RULES = {
|
|
|
26
40
|
pattern: 'DispatchQueue\\.main\\.sync',
|
|
27
41
|
languages: ['swift'],
|
|
28
42
|
dimension: 'file',
|
|
43
|
+
category: 'correctness',
|
|
29
44
|
},
|
|
30
45
|
'objc-dealloc-async': {
|
|
31
46
|
message: 'dealloc 内禁止使用 dispatch_async/dispatch_after/postNotification 等',
|
|
32
47
|
severity: 'error',
|
|
33
|
-
pattern:
|
|
48
|
+
pattern:
|
|
49
|
+
'(dealloc.*(dispatch_async|dispatch_after|postNotification|performSelector.*afterDelay))',
|
|
34
50
|
languages: ['objc'],
|
|
35
51
|
dimension: 'file',
|
|
52
|
+
category: 'correctness',
|
|
36
53
|
},
|
|
37
54
|
'objc-block-retain-cycle': {
|
|
38
55
|
message: 'block 内直接使用 self 可能循环引用,建议 weakSelf',
|
|
@@ -40,6 +57,8 @@ const BUILT_IN_RULES = {
|
|
|
40
57
|
pattern: '\\^\\s*[({][^}]*\\bself\\b',
|
|
41
58
|
languages: ['objc'],
|
|
42
59
|
dimension: 'file',
|
|
60
|
+
category: 'correctness',
|
|
61
|
+
fixSuggestion: '声明 __weak typeof(self) weakSelf = self; 后在 block 内使用 weakSelf',
|
|
43
62
|
},
|
|
44
63
|
'objc-assign-object': {
|
|
45
64
|
message: 'assign 用于对象类型会产生悬垂指针,建议改为 weak 或 strong',
|
|
@@ -47,6 +66,7 @@ const BUILT_IN_RULES = {
|
|
|
47
66
|
pattern: '@property\\s*\\([^)]*\\bassign\\b[^)]*\\)[^;]*(\\*|id\\s*<|\\bid\\s+)',
|
|
48
67
|
languages: ['objc'],
|
|
49
68
|
dimension: 'file',
|
|
69
|
+
category: 'correctness',
|
|
50
70
|
},
|
|
51
71
|
'swift-force-cast': {
|
|
52
72
|
message: '强制类型转换 as! 在失败时崩溃,建议 as? 或 guard let',
|
|
@@ -54,6 +74,8 @@ const BUILT_IN_RULES = {
|
|
|
54
74
|
pattern: 'as\\s*!',
|
|
55
75
|
languages: ['swift'],
|
|
56
76
|
dimension: 'file',
|
|
77
|
+
category: 'safety',
|
|
78
|
+
fixSuggestion: '使用 as? 配合 guard let / if let 进行安全转换',
|
|
57
79
|
},
|
|
58
80
|
'swift-force-try': {
|
|
59
81
|
message: 'try! 在异常时崩溃,建议 do-catch 或 try?',
|
|
@@ -61,13 +83,16 @@ const BUILT_IN_RULES = {
|
|
|
61
83
|
pattern: 'try\\s*!',
|
|
62
84
|
languages: ['swift'],
|
|
63
85
|
dimension: 'file',
|
|
86
|
+
category: 'safety',
|
|
64
87
|
},
|
|
65
88
|
'objc-timer-retain-cycle': {
|
|
66
|
-
message:
|
|
89
|
+
message:
|
|
90
|
+
'NSTimer 以 self 为 target 会强引用 self,需在 dealloc 前 invalidate 或使用 block 形式',
|
|
67
91
|
severity: 'warning',
|
|
68
92
|
pattern: '(scheduledTimerWithTimeInterval|timerWithTimeInterval)[^;]*target\\s*:\\s*self',
|
|
69
93
|
languages: ['objc'],
|
|
70
94
|
dimension: 'file',
|
|
95
|
+
category: 'correctness',
|
|
71
96
|
},
|
|
72
97
|
'objc-possible-main-thread-blocking': {
|
|
73
98
|
message: 'sleep/usleep 可能造成主线程阻塞',
|
|
@@ -75,9 +100,12 @@ const BUILT_IN_RULES = {
|
|
|
75
100
|
pattern: '\\b(sleep|usleep)\\s*\\(',
|
|
76
101
|
languages: ['objc'],
|
|
77
102
|
dimension: 'file',
|
|
103
|
+
category: 'performance',
|
|
78
104
|
},
|
|
79
105
|
|
|
80
|
-
//
|
|
106
|
+
// ══════════════════════════════════════════════════════════
|
|
107
|
+
// JavaScript / TypeScript
|
|
108
|
+
// ══════════════════════════════════════════════════════════
|
|
81
109
|
|
|
82
110
|
'js-no-eval': {
|
|
83
111
|
message: 'eval() 存在安全风险和性能问题,应避免使用',
|
|
@@ -85,6 +113,7 @@ const BUILT_IN_RULES = {
|
|
|
85
113
|
pattern: '\\beval\\s*\\(',
|
|
86
114
|
languages: ['javascript', 'typescript'],
|
|
87
115
|
dimension: 'file',
|
|
116
|
+
category: 'safety',
|
|
88
117
|
},
|
|
89
118
|
'js-no-var': {
|
|
90
119
|
message: '使用 let/const 替代 var,避免变量提升问题',
|
|
@@ -92,6 +121,7 @@ const BUILT_IN_RULES = {
|
|
|
92
121
|
pattern: '\\bvar\\s+\\w+',
|
|
93
122
|
languages: ['javascript', 'typescript'],
|
|
94
123
|
dimension: 'file',
|
|
124
|
+
category: 'style',
|
|
95
125
|
},
|
|
96
126
|
'js-no-console-log': {
|
|
97
127
|
message: '生产代码应移除 console.log,使用专用日志库',
|
|
@@ -99,6 +129,7 @@ const BUILT_IN_RULES = {
|
|
|
99
129
|
pattern: 'console\\.log\\s*\\(',
|
|
100
130
|
languages: ['javascript', 'typescript'],
|
|
101
131
|
dimension: 'file',
|
|
132
|
+
category: 'style',
|
|
102
133
|
},
|
|
103
134
|
'js-no-debugger': {
|
|
104
135
|
message: '生产代码中不应包含 debugger 语句',
|
|
@@ -106,13 +137,15 @@ const BUILT_IN_RULES = {
|
|
|
106
137
|
pattern: '\\bdebugger\\b',
|
|
107
138
|
languages: ['javascript', 'typescript'],
|
|
108
139
|
dimension: 'file',
|
|
140
|
+
category: 'style',
|
|
109
141
|
},
|
|
110
|
-
'
|
|
111
|
-
message: '
|
|
142
|
+
'js-no-alert': {
|
|
143
|
+
message: '生产代码中不应使用 alert(),影响用户体验',
|
|
112
144
|
severity: 'warning',
|
|
113
|
-
pattern: '
|
|
114
|
-
languages: ['typescript'],
|
|
145
|
+
pattern: '\\balert\\s*\\(',
|
|
146
|
+
languages: ['javascript', 'typescript'],
|
|
115
147
|
dimension: 'file',
|
|
148
|
+
category: 'style',
|
|
116
149
|
},
|
|
117
150
|
'ts-no-non-null-assertion': {
|
|
118
151
|
message: '非空断言 ! 可能掩盖 null/undefined 错误',
|
|
@@ -120,9 +153,12 @@ const BUILT_IN_RULES = {
|
|
|
120
153
|
pattern: '\\w+!\\.',
|
|
121
154
|
languages: ['typescript'],
|
|
122
155
|
dimension: 'file',
|
|
156
|
+
category: 'safety',
|
|
123
157
|
},
|
|
124
158
|
|
|
125
|
-
//
|
|
159
|
+
// ══════════════════════════════════════════════════════════
|
|
160
|
+
// Python
|
|
161
|
+
// ══════════════════════════════════════════════════════════
|
|
126
162
|
|
|
127
163
|
'py-no-bare-except': {
|
|
128
164
|
message: '裸 except: 会捕获所有异常(含 SystemExit),应指定异常类型',
|
|
@@ -130,6 +166,7 @@ const BUILT_IN_RULES = {
|
|
|
130
166
|
pattern: 'except\\s*:',
|
|
131
167
|
languages: ['python'],
|
|
132
168
|
dimension: 'file',
|
|
169
|
+
category: 'correctness',
|
|
133
170
|
},
|
|
134
171
|
'py-no-exec': {
|
|
135
172
|
message: 'exec() 存在安全风险,应避免使用',
|
|
@@ -137,16 +174,37 @@ const BUILT_IN_RULES = {
|
|
|
137
174
|
pattern: '\\bexec\\s*\\(',
|
|
138
175
|
languages: ['python'],
|
|
139
176
|
dimension: 'file',
|
|
177
|
+
category: 'safety',
|
|
140
178
|
},
|
|
141
179
|
'py-no-mutable-default': {
|
|
142
180
|
message: '函数默认参数使用可变对象(list/dict/set)会导致共享状态 bug',
|
|
143
181
|
severity: 'warning',
|
|
144
|
-
pattern: 'def\\s+\\w+\\s*\\([^)]*=\\s
|
|
182
|
+
pattern: 'def\\s+\\w+\\s*\\([^)]*=\\s*(?:\\[\\]|\\{\\}|set\\(\\))',
|
|
183
|
+
languages: ['python'],
|
|
184
|
+
dimension: 'file',
|
|
185
|
+
category: 'correctness',
|
|
186
|
+
},
|
|
187
|
+
'py-no-star-import': {
|
|
188
|
+
message: 'from module import * 导致命名空间污染,应显式导入',
|
|
189
|
+
severity: 'warning',
|
|
190
|
+
pattern: 'from\\s+\\S+\\s+import\\s+\\*',
|
|
145
191
|
languages: ['python'],
|
|
146
192
|
dimension: 'file',
|
|
193
|
+
category: 'style',
|
|
194
|
+
},
|
|
195
|
+
'py-no-assert-in-prod': {
|
|
196
|
+
message: 'assert 在 -O 模式下会被移除,不应用于生产逻辑校验',
|
|
197
|
+
severity: 'info',
|
|
198
|
+
pattern: '^\\s*assert\\s+',
|
|
199
|
+
languages: ['python'],
|
|
200
|
+
dimension: 'file',
|
|
201
|
+
category: 'correctness',
|
|
202
|
+
excludePaths: /(?:^|[\/\\])tests?[\/\\]|[\/\\]test_[^\/\\]*\.py$|_test\.py$/,
|
|
147
203
|
},
|
|
148
204
|
|
|
149
|
-
//
|
|
205
|
+
// ══════════════════════════════════════════════════════════
|
|
206
|
+
// Java / Kotlin
|
|
207
|
+
// ══════════════════════════════════════════════════════════
|
|
150
208
|
|
|
151
209
|
'java-no-system-exit': {
|
|
152
210
|
message: 'System.exit() 直接终止 JVM,应抛异常或返回状态码',
|
|
@@ -154,6 +212,7 @@ const BUILT_IN_RULES = {
|
|
|
154
212
|
pattern: 'System\\.exit\\s*\\(',
|
|
155
213
|
languages: ['java', 'kotlin'],
|
|
156
214
|
dimension: 'file',
|
|
215
|
+
category: 'correctness',
|
|
157
216
|
},
|
|
158
217
|
'java-no-raw-type': {
|
|
159
218
|
message: '使用泛型集合替代原始类型 (如 List<String> 替代 List)',
|
|
@@ -161,9 +220,37 @@ const BUILT_IN_RULES = {
|
|
|
161
220
|
pattern: '(List|Map|Set|Collection|Iterable)\\s+\\w+\\s*[=;]',
|
|
162
221
|
languages: ['java'],
|
|
163
222
|
dimension: 'file',
|
|
223
|
+
category: 'style',
|
|
224
|
+
},
|
|
225
|
+
'java-no-empty-catch': {
|
|
226
|
+
message: '空 catch 块会静默吞掉异常,至少应记录日志',
|
|
227
|
+
severity: 'warning',
|
|
228
|
+
pattern: 'catch\\s*\\([^)]+\\)\\s*\\{\\s*\\}',
|
|
229
|
+
languages: ['java', 'kotlin'],
|
|
230
|
+
dimension: 'file',
|
|
231
|
+
category: 'correctness',
|
|
232
|
+
},
|
|
233
|
+
'java-no-thread-stop': {
|
|
234
|
+
message: 'Thread.stop() 已废弃且不安全,使用 interrupt() 协作式终止',
|
|
235
|
+
severity: 'error',
|
|
236
|
+
pattern: '\\.stop\\s*\\(\\)',
|
|
237
|
+
languages: ['java'],
|
|
238
|
+
dimension: 'file',
|
|
239
|
+
category: 'safety',
|
|
240
|
+
},
|
|
241
|
+
'kotlin-no-force-unwrap': {
|
|
242
|
+
message: '!! 非空断言在值为 null 时抛 NPE,应使用 ?. 或 ?: 安全访问',
|
|
243
|
+
severity: 'warning',
|
|
244
|
+
pattern: '\\w+!!',
|
|
245
|
+
languages: ['kotlin'],
|
|
246
|
+
dimension: 'file',
|
|
247
|
+
category: 'safety',
|
|
248
|
+
fixSuggestion: '使用 ?. 安全调用或 ?: 提供默认值',
|
|
164
249
|
},
|
|
165
250
|
|
|
166
|
-
//
|
|
251
|
+
// ══════════════════════════════════════════════════════════
|
|
252
|
+
// Go
|
|
253
|
+
// ══════════════════════════════════════════════════════════
|
|
167
254
|
|
|
168
255
|
'go-no-panic': {
|
|
169
256
|
message: 'panic 应仅用于不可恢复错误,库代码应返回 error',
|
|
@@ -171,52 +258,120 @@ const BUILT_IN_RULES = {
|
|
|
171
258
|
pattern: '\\bpanic\\s*\\(',
|
|
172
259
|
languages: ['go'],
|
|
173
260
|
dimension: 'file',
|
|
261
|
+
category: 'correctness',
|
|
174
262
|
},
|
|
175
263
|
'go-no-err-ignored': {
|
|
176
264
|
message: '错误值不应用 _ 忽略,应处理或明确标注',
|
|
177
265
|
severity: 'warning',
|
|
178
|
-
pattern: '\\
|
|
266
|
+
pattern: '\\w+\\s*,\\s*_\\s*:?=\\s*\\w|_\\s*=\\s*\\w+\\.[A-Z]\\w*\\(',
|
|
267
|
+
languages: ['go'],
|
|
268
|
+
dimension: 'file',
|
|
269
|
+
category: 'correctness',
|
|
270
|
+
},
|
|
271
|
+
'go-no-init-abuse': {
|
|
272
|
+
message: 'init() 函数副作用难以追踪,避免在 init 中执行复杂逻辑',
|
|
273
|
+
severity: 'info',
|
|
274
|
+
pattern: 'func\\s+init\\s*\\(\\s*\\)',
|
|
179
275
|
languages: ['go'],
|
|
180
276
|
dimension: 'file',
|
|
277
|
+
category: 'style',
|
|
278
|
+
},
|
|
279
|
+
'go-no-global-var': {
|
|
280
|
+
message: '全局可变变量导致并发安全问题,考虑使用依赖注入',
|
|
281
|
+
severity: 'info',
|
|
282
|
+
pattern: '^var\\s+\\w+\\s+',
|
|
283
|
+
languages: ['go'],
|
|
284
|
+
dimension: 'file',
|
|
285
|
+
category: 'style',
|
|
181
286
|
},
|
|
182
287
|
|
|
183
|
-
//
|
|
288
|
+
// ══════════════════════════════════════════════════════════
|
|
289
|
+
// Dart (Flutter)
|
|
290
|
+
// ══════════════════════════════════════════════════════════
|
|
291
|
+
|
|
292
|
+
'dart-no-print': {
|
|
293
|
+
message: '生产代码应使用 logger 替代 print(),便于日志分级和关闭',
|
|
294
|
+
severity: 'info',
|
|
295
|
+
pattern: '\\bprint\\s*\\(',
|
|
296
|
+
languages: ['dart'],
|
|
297
|
+
dimension: 'file',
|
|
298
|
+
category: 'style',
|
|
299
|
+
},
|
|
300
|
+
'dart-avoid-dynamic': {
|
|
301
|
+
message: '避免使用 dynamic 类型,使用具体类型或泛型提升类型安全',
|
|
302
|
+
severity: 'warning',
|
|
303
|
+
pattern: '\\bdynamic\\b',
|
|
304
|
+
languages: ['dart'],
|
|
305
|
+
dimension: 'file',
|
|
306
|
+
category: 'style',
|
|
307
|
+
},
|
|
308
|
+
'dart-no-set-state-after-dispose': {
|
|
309
|
+
message: 'setState 调用前应检查 mounted 状态,避免 disposed 后调用',
|
|
310
|
+
severity: 'warning',
|
|
311
|
+
pattern: 'setState\\s*\\(',
|
|
312
|
+
languages: ['dart'],
|
|
313
|
+
dimension: 'file',
|
|
314
|
+
category: 'correctness',
|
|
315
|
+
fixSuggestion: '使用 if (mounted) setState(...) 守卫',
|
|
316
|
+
},
|
|
184
317
|
|
|
185
|
-
'
|
|
186
|
-
message: '
|
|
318
|
+
'dart-avoid-bang-operator': {
|
|
319
|
+
message: '避免使用 ! 空断言操作符,优先使用 ?? 默认值或 ?. 安全调用',
|
|
187
320
|
severity: 'warning',
|
|
188
|
-
pattern: '
|
|
189
|
-
languages: ['
|
|
321
|
+
pattern: '\\w+!\\.',
|
|
322
|
+
languages: ['dart'],
|
|
323
|
+
dimension: 'file',
|
|
324
|
+
category: 'correctness',
|
|
325
|
+
fixSuggestion: '使用 ?. 安全调用或 ?? 提供默认值',
|
|
326
|
+
},
|
|
327
|
+
'dart-prefer-const-constructor': {
|
|
328
|
+
message: '当所有字段均为 final 时,构造函数应声明为 const 以优化 Widget 重建',
|
|
329
|
+
severity: 'info',
|
|
330
|
+
pattern: '(?<!const\\s)\\bnew\\s+\\w+\\(',
|
|
331
|
+
languages: ['dart'],
|
|
190
332
|
dimension: 'file',
|
|
333
|
+
category: 'performance',
|
|
334
|
+
fixSuggestion: '移除 new 关键字,并在 Widget 构造调用前加 const',
|
|
191
335
|
},
|
|
192
|
-
'
|
|
193
|
-
message: '
|
|
336
|
+
'dart-no-relative-import': {
|
|
337
|
+
message: 'lib/ 目录内应使用 package: 形式的绝对导入,避免相对路径导入',
|
|
194
338
|
severity: 'info',
|
|
195
|
-
pattern:
|
|
196
|
-
languages: ['
|
|
339
|
+
pattern: "import\\s+['\"]\\.\\.?/",
|
|
340
|
+
languages: ['dart'],
|
|
341
|
+
dimension: 'file',
|
|
342
|
+
category: 'style',
|
|
343
|
+
},
|
|
344
|
+
'dart-dispose-controller': {
|
|
345
|
+
message: 'TextEditingController/AnimationController 等须在 dispose() 中释放',
|
|
346
|
+
severity: 'warning',
|
|
347
|
+
pattern: '(?:TextEditingController|AnimationController|ScrollController|FocusNode|TabController)\\(',
|
|
348
|
+
languages: ['dart'],
|
|
197
349
|
dimension: 'file',
|
|
350
|
+
category: 'correctness',
|
|
351
|
+
fixSuggestion: '在 State.dispose() 中调用 controller.dispose()',
|
|
198
352
|
},
|
|
353
|
+
'dart-no-build-context-across-async': {
|
|
354
|
+
message: 'BuildContext 不应跨越 async gap 使用,可能导致引用已卸载的 Widget',
|
|
355
|
+
severity: 'warning',
|
|
356
|
+
pattern: 'await\\s+.*\\n.*context\\.',
|
|
357
|
+
languages: ['dart'],
|
|
358
|
+
dimension: 'file',
|
|
359
|
+
category: 'correctness',
|
|
360
|
+
fixSuggestion: '在 await 前缓存所需数据,或在 await 后检查 mounted',
|
|
361
|
+
},
|
|
362
|
+
|
|
199
363
|
};
|
|
200
364
|
|
|
201
365
|
/**
|
|
202
366
|
* 从文件扩展名推断语言
|
|
203
367
|
*/
|
|
204
368
|
export function detectLanguage(filePath) {
|
|
205
|
-
if (!filePath)
|
|
206
|
-
|
|
207
|
-
switch (ext) {
|
|
208
|
-
case 'swift': return 'swift';
|
|
209
|
-
case 'm': case 'mm': case 'h': return 'objc';
|
|
210
|
-
case 'js': case 'mjs': case 'cjs': return 'javascript';
|
|
211
|
-
case 'ts': case 'tsx': return 'typescript';
|
|
212
|
-
case 'py': return 'python';
|
|
213
|
-
case 'java': return 'java';
|
|
214
|
-
case 'kt': case 'kts': return 'kotlin';
|
|
215
|
-
case 'rb': return 'ruby';
|
|
216
|
-
case 'go': return 'go';
|
|
217
|
-
case 'rs': return 'rust';
|
|
218
|
-
default: return 'unknown';
|
|
369
|
+
if (!filePath) {
|
|
370
|
+
return 'unknown';
|
|
219
371
|
}
|
|
372
|
+
const lang = LanguageService.inferLang(filePath);
|
|
373
|
+
// 向后兼容: objectivec → objc
|
|
374
|
+
return lang === 'objectivec' ? 'objc' : lang;
|
|
220
375
|
}
|
|
221
376
|
|
|
222
377
|
/**
|
|
@@ -246,13 +401,17 @@ export class GuardCheckEngine {
|
|
|
246
401
|
if (!this._customRulesCache || now - this._cacheTime > this._cacheTTL) {
|
|
247
402
|
let rows = [];
|
|
248
403
|
try {
|
|
249
|
-
rows = this.db
|
|
250
|
-
|
|
404
|
+
rows = this.db
|
|
405
|
+
.prepare(
|
|
406
|
+
`SELECT id, title, description, language, scope, constraints
|
|
251
407
|
FROM knowledge_entries
|
|
252
408
|
WHERE (kind = 'rule' OR knowledgeType = 'boundary-constraint')
|
|
253
409
|
AND lifecycle = 'active'`
|
|
254
|
-
|
|
255
|
-
|
|
410
|
+
)
|
|
411
|
+
.all();
|
|
412
|
+
} catch {
|
|
413
|
+
/* table may not exist */
|
|
414
|
+
}
|
|
256
415
|
|
|
257
416
|
const regexRules = [];
|
|
258
417
|
const astRules = [];
|
|
@@ -262,7 +421,9 @@ export class GuardCheckEngine {
|
|
|
262
421
|
try {
|
|
263
422
|
const constraints = JSON.parse(r.constraints || '{}');
|
|
264
423
|
guards = constraints.guards || [];
|
|
265
|
-
} catch {
|
|
424
|
+
} catch {
|
|
425
|
+
/* ignore */
|
|
426
|
+
}
|
|
266
427
|
|
|
267
428
|
for (const g of guards) {
|
|
268
429
|
const ruleType = g.type || 'regex';
|
|
@@ -295,7 +456,7 @@ export class GuardCheckEngine {
|
|
|
295
456
|
}
|
|
296
457
|
|
|
297
458
|
// 合并内置规则(不覆盖同名数据库规则)
|
|
298
|
-
const existingIds = new Set(rules.map(r => r.id || r.name));
|
|
459
|
+
const existingIds = new Set(rules.map((r) => r.id || r.name));
|
|
299
460
|
for (const [ruleId, rule] of Object.entries(this._builtInRules)) {
|
|
300
461
|
if (!existingIds.has(ruleId)) {
|
|
301
462
|
rules.push({
|
|
@@ -306,23 +467,25 @@ export class GuardCheckEngine {
|
|
|
306
467
|
languages: rule.languages,
|
|
307
468
|
severity: rule.severity,
|
|
308
469
|
dimension: rule.dimension || 'file',
|
|
470
|
+
category: rule.category || '',
|
|
309
471
|
source: 'built-in',
|
|
310
472
|
type: 'regex',
|
|
311
473
|
fixSuggestion: rule.fixSuggestion || null,
|
|
474
|
+
...(rule.excludePaths ? { excludePaths: rule.excludePaths } : {}),
|
|
312
475
|
});
|
|
313
476
|
}
|
|
314
477
|
}
|
|
315
478
|
|
|
316
479
|
// 按语言过滤
|
|
317
480
|
if (language) {
|
|
318
|
-
rules = rules.filter(r => !r.languages?.length || r.languages.includes(language));
|
|
481
|
+
rules = rules.filter((r) => !r.languages?.length || r.languages.includes(language));
|
|
319
482
|
}
|
|
320
483
|
|
|
321
484
|
// 合并 AST 规则(供外部调用者使用,如 GuardFeedbackLoop.查找 fixSuggestion)
|
|
322
485
|
if (this._astRulesCache?.length) {
|
|
323
486
|
let astRules = this._astRulesCache;
|
|
324
487
|
if (language) {
|
|
325
|
-
astRules = astRules.filter(r => !r.languages?.length || r.languages.includes(language));
|
|
488
|
+
astRules = astRules.filter((r) => !r.languages?.length || r.languages.includes(language));
|
|
326
489
|
}
|
|
327
490
|
rules.push(...astRules);
|
|
328
491
|
}
|
|
@@ -338,25 +501,40 @@ export class GuardCheckEngine {
|
|
|
338
501
|
* @returns {Array<{ruleId, message, severity, line, snippet, dimension?, fixSuggestion?}>}
|
|
339
502
|
*/
|
|
340
503
|
checkCode(code, language, options = {}) {
|
|
341
|
-
const { scope = null } = options;
|
|
504
|
+
const { scope = null, filePath = '' } = options;
|
|
342
505
|
const violations = [];
|
|
343
506
|
|
|
344
507
|
// 获取匹配语言的规则
|
|
345
508
|
let rules = this.getRules(language);
|
|
346
509
|
|
|
510
|
+
// 按 excludePaths 过滤(测试文件排除等)
|
|
511
|
+
if (filePath) {
|
|
512
|
+
rules = rules.filter((r) => {
|
|
513
|
+
if (!r.excludePaths) return true;
|
|
514
|
+
const re = r.excludePaths instanceof RegExp ? r.excludePaths : new RegExp(r.excludePaths);
|
|
515
|
+
return !re.test(filePath);
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
|
|
347
519
|
// 如果有 scope,按层级过滤:project ⊇ target ⊇ file
|
|
348
520
|
// project 范围包含所有维度的规则;target 包含 file+target;file 仅匹配 file
|
|
349
521
|
if (scope) {
|
|
350
|
-
const SCOPE_HIERARCHY = {
|
|
522
|
+
const SCOPE_HIERARCHY = {
|
|
523
|
+
project: ['file', 'target', 'project'],
|
|
524
|
+
target: ['file', 'target'],
|
|
525
|
+
file: ['file'],
|
|
526
|
+
};
|
|
351
527
|
const allowedDimensions = SCOPE_HIERARCHY[scope] || [scope];
|
|
352
|
-
rules = rules.filter(r => !r.dimension || allowedDimensions.includes(r.dimension));
|
|
528
|
+
rules = rules.filter((r) => !r.dimension || allowedDimensions.includes(r.dimension));
|
|
353
529
|
}
|
|
354
530
|
|
|
355
531
|
const lines = (code || '').split(/\r?\n/);
|
|
356
532
|
|
|
357
533
|
for (const rule of rules) {
|
|
358
534
|
// 跳过空模式或特殊标记 (?!) — 由 code-level 检查接管
|
|
359
|
-
if (!rule.pattern || rule.pattern === '(?!)')
|
|
535
|
+
if (!rule.pattern || rule.pattern === '(?!)') {
|
|
536
|
+
continue;
|
|
537
|
+
}
|
|
360
538
|
|
|
361
539
|
let re;
|
|
362
540
|
try {
|
|
@@ -391,7 +569,7 @@ export class GuardCheckEngine {
|
|
|
391
569
|
this.trackGuardHits(violations);
|
|
392
570
|
|
|
393
571
|
// ── Reasoning Enrichment: 推理信息跟随数据流动 ──
|
|
394
|
-
return violations.map(v => ({
|
|
572
|
+
return violations.map((v) => ({
|
|
395
573
|
...v,
|
|
396
574
|
reasoning: {
|
|
397
575
|
whatViolated: v.ruleId,
|
|
@@ -410,16 +588,23 @@ export class GuardCheckEngine {
|
|
|
410
588
|
* @returns {Array} violations
|
|
411
589
|
*/
|
|
412
590
|
_runAstRuleChecks(code, language) {
|
|
413
|
-
// AST
|
|
414
|
-
const
|
|
415
|
-
|
|
416
|
-
|
|
591
|
+
// AST 语言标准化 — 通过 LanguageService 判断是否为已知编程语言
|
|
592
|
+
const astLang = LanguageService.isKnownLang(language)
|
|
593
|
+
? language
|
|
594
|
+
: language === 'objc'
|
|
595
|
+
? 'objectivec'
|
|
596
|
+
: language;
|
|
597
|
+
if (!LanguageService.isKnownLang(astLang)) {
|
|
598
|
+
return [];
|
|
599
|
+
}
|
|
417
600
|
|
|
418
601
|
// 获取缓存中的 AST 规则
|
|
419
|
-
const astRules = (this._astRulesCache || []).filter(
|
|
420
|
-
!r.languages?.length || r.languages.includes(language)
|
|
602
|
+
const astRules = (this._astRulesCache || []).filter(
|
|
603
|
+
(r) => !r.languages?.length || r.languages.includes(language)
|
|
421
604
|
);
|
|
422
|
-
if (astRules.length === 0)
|
|
605
|
+
if (astRules.length === 0) {
|
|
606
|
+
return [];
|
|
607
|
+
}
|
|
423
608
|
|
|
424
609
|
// 延迟加载 AstAnalyzer
|
|
425
610
|
let AstAnalyzer;
|
|
@@ -427,7 +612,9 @@ export class GuardCheckEngine {
|
|
|
427
612
|
// 使用 dynamic import 会是 async,这里用 require 风格同步加载
|
|
428
613
|
// AstAnalyzer 作为 ESM 模块,在 constructor 时已被引入
|
|
429
614
|
AstAnalyzer = this._getAstAnalyzer();
|
|
430
|
-
if (!AstAnalyzer || !AstAnalyzer.isAvailable())
|
|
615
|
+
if (!AstAnalyzer || !AstAnalyzer.isAvailable()) {
|
|
616
|
+
return [];
|
|
617
|
+
}
|
|
431
618
|
} catch {
|
|
432
619
|
this.logger.debug('AstAnalyzer not available, skipping AST rules');
|
|
433
620
|
return [];
|
|
@@ -437,14 +624,18 @@ export class GuardCheckEngine {
|
|
|
437
624
|
|
|
438
625
|
for (const rule of astRules) {
|
|
439
626
|
const { astQuery } = rule;
|
|
440
|
-
if (!astQuery?.queryType)
|
|
627
|
+
if (!astQuery?.queryType) {
|
|
628
|
+
continue;
|
|
629
|
+
}
|
|
441
630
|
|
|
442
631
|
try {
|
|
443
632
|
switch (astQuery.queryType) {
|
|
444
633
|
case 'mustCallThrough': {
|
|
445
634
|
// 检查某 API 是否只在指定 wrapper 类中调用
|
|
446
635
|
const { targetAPI, wrapperClass } = astQuery.params || {};
|
|
447
|
-
if (!targetAPI || !wrapperClass)
|
|
636
|
+
if (!targetAPI || !wrapperClass) {
|
|
637
|
+
break;
|
|
638
|
+
}
|
|
448
639
|
|
|
449
640
|
const calls = AstAnalyzer.findCallExpressions(code, astLang, targetAPI);
|
|
450
641
|
for (const call of calls) {
|
|
@@ -466,7 +657,9 @@ export class GuardCheckEngine {
|
|
|
466
657
|
case 'mustNotUseInContext': {
|
|
467
658
|
// 在特定上下文中禁止使用某模式
|
|
468
659
|
const { pattern: textPattern, forbiddenContext } = astQuery.params || {};
|
|
469
|
-
if (!textPattern || !forbiddenContext)
|
|
660
|
+
if (!textPattern || !forbiddenContext) {
|
|
661
|
+
break;
|
|
662
|
+
}
|
|
470
663
|
|
|
471
664
|
const matches = AstAnalyzer.findPatternInContext(code, astLang, textPattern, {
|
|
472
665
|
forbiddenContext,
|
|
@@ -488,9 +681,16 @@ export class GuardCheckEngine {
|
|
|
488
681
|
case 'mustConformToProtocol': {
|
|
489
682
|
// 检查类是否实现了指定协议
|
|
490
683
|
const { className, protocolName } = astQuery.params || {};
|
|
491
|
-
if (!className || !protocolName)
|
|
684
|
+
if (!className || !protocolName) {
|
|
685
|
+
break;
|
|
686
|
+
}
|
|
492
687
|
|
|
493
|
-
const result = AstAnalyzer.checkProtocolConformance(
|
|
688
|
+
const result = AstAnalyzer.checkProtocolConformance(
|
|
689
|
+
code,
|
|
690
|
+
astLang,
|
|
691
|
+
className,
|
|
692
|
+
protocolName
|
|
693
|
+
);
|
|
494
694
|
if (result.classFound && !result.conforms) {
|
|
495
695
|
violations.push({
|
|
496
696
|
ruleId: rule.id,
|
|
@@ -528,7 +728,9 @@ export class GuardCheckEngine {
|
|
|
528
728
|
* @param {Array<{ruleId: string}>} violations
|
|
529
729
|
*/
|
|
530
730
|
trackGuardHits(violations) {
|
|
531
|
-
if (!violations?.length || !this.db)
|
|
731
|
+
if (!violations?.length || !this.db) {
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
532
734
|
|
|
533
735
|
try {
|
|
534
736
|
// 收集来自数据库规则的 ruleId → 命中次数
|
|
@@ -547,13 +749,17 @@ export class GuardCheckEngine {
|
|
|
547
749
|
updatedAt = ?
|
|
548
750
|
WHERE id = ?`
|
|
549
751
|
);
|
|
550
|
-
} catch {
|
|
752
|
+
} catch {
|
|
753
|
+
/* table may not exist */
|
|
754
|
+
}
|
|
551
755
|
const now = Math.floor(Date.now() / 1000);
|
|
552
756
|
|
|
553
757
|
for (const [ruleId, count] of hitMap) {
|
|
554
758
|
try {
|
|
555
759
|
updateStmt.run(count, now, ruleId);
|
|
556
|
-
} catch {
|
|
760
|
+
} catch {
|
|
761
|
+
/* 非 Recipe 规则(内置规则)忽略 */
|
|
762
|
+
}
|
|
557
763
|
}
|
|
558
764
|
} catch (err) {
|
|
559
765
|
this.logger.debug('trackGuardHits failed', { error: err.message });
|
|
@@ -561,15 +767,17 @@ export class GuardCheckEngine {
|
|
|
561
767
|
}
|
|
562
768
|
|
|
563
769
|
/**
|
|
564
|
-
* 代码级别检查 -
|
|
770
|
+
* 代码级别检查 - 需要上下文理解的检查(跨行 / 配对检查)
|
|
771
|
+
* 按语言分发到各自的检查逻辑
|
|
565
772
|
*/
|
|
566
773
|
_runCodeLevelChecks(code, language, lines) {
|
|
567
774
|
const violations = [];
|
|
568
775
|
|
|
776
|
+
// ── ObjC ──
|
|
569
777
|
if (language === 'objc') {
|
|
570
778
|
// KVO 观察者未移除检查
|
|
571
779
|
if (code.includes('addObserver') && !code.includes('removeObserver')) {
|
|
572
|
-
const lineIdx = lines.findIndex(l => /addObserver/.test(l));
|
|
780
|
+
const lineIdx = lines.findIndex((l) => /addObserver/.test(l));
|
|
573
781
|
violations.push({
|
|
574
782
|
ruleId: 'objc-kvo-missing-remove',
|
|
575
783
|
message: '存在 addObserver 未发现配对 removeObserver,请在 dealloc 或合适时机移除',
|
|
@@ -586,13 +794,19 @@ export class GuardCheckEngine {
|
|
|
586
794
|
for (let i = 0; i < lines.length; i++) {
|
|
587
795
|
categoryRegex.lastIndex = 0;
|
|
588
796
|
const m = categoryRegex.exec(lines[i]);
|
|
589
|
-
if (!m)
|
|
797
|
+
if (!m) {
|
|
798
|
+
continue;
|
|
799
|
+
}
|
|
590
800
|
const key = `${m[1]}(${m[2]})`;
|
|
591
|
-
if (!categories[key])
|
|
801
|
+
if (!categories[key]) {
|
|
802
|
+
categories[key] = [];
|
|
803
|
+
}
|
|
592
804
|
categories[key].push({ line: i + 1, snippet: lines[i].trim().slice(0, 120) });
|
|
593
805
|
}
|
|
594
806
|
for (const [key, occs] of Object.entries(categories)) {
|
|
595
|
-
if (occs.length <= 1)
|
|
807
|
+
if (occs.length <= 1) {
|
|
808
|
+
continue;
|
|
809
|
+
}
|
|
596
810
|
for (let j = 1; j < occs.length; j++) {
|
|
597
811
|
violations.push({
|
|
598
812
|
ruleId: 'objc-duplicate-category',
|
|
@@ -606,6 +820,78 @@ export class GuardCheckEngine {
|
|
|
606
820
|
}
|
|
607
821
|
}
|
|
608
822
|
|
|
823
|
+
// ── JavaScript / TypeScript ──
|
|
824
|
+
if (language === 'javascript' || language === 'typescript') {
|
|
825
|
+
// Promise 未处理 rejection 检查
|
|
826
|
+
if (code.includes('.then(') && !code.includes('.catch(') && !code.includes('.then(') === false) {
|
|
827
|
+
// 简化: 检查 new Promise 或 .then() 链没有 .catch()
|
|
828
|
+
const thenLines = [];
|
|
829
|
+
for (let i = 0; i < lines.length; i++) {
|
|
830
|
+
if (/\.then\s*\(/.test(lines[i]) && !/\.catch\s*\(/.test(code)) {
|
|
831
|
+
thenLines.push(i);
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
if (thenLines.length > 0 && !code.includes('.catch(')) {
|
|
835
|
+
violations.push({
|
|
836
|
+
ruleId: 'js-unhandled-promise',
|
|
837
|
+
message: 'Promise 链缺少 .catch() 错误处理,未捕获的 rejection 可能导致静默失败',
|
|
838
|
+
severity: 'warning',
|
|
839
|
+
line: thenLines[0] + 1,
|
|
840
|
+
snippet: lines[thenLines[0]].trim().slice(0, 120),
|
|
841
|
+
dimension: 'file',
|
|
842
|
+
});
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
// ── Go ──
|
|
848
|
+
if (language === 'go') {
|
|
849
|
+
// defer 在循环内检查 — defer 在函数结束时才执行,循环内 defer 可能资源泄露
|
|
850
|
+
let inLoop = false;
|
|
851
|
+
for (let i = 0; i < lines.length; i++) {
|
|
852
|
+
const trimmed = lines[i].trim();
|
|
853
|
+
if (/^for\s/.test(trimmed) || /^for\s*\{/.test(trimmed)) {
|
|
854
|
+
inLoop = true;
|
|
855
|
+
}
|
|
856
|
+
if (inLoop && /^\s*defer\s/.test(lines[i])) {
|
|
857
|
+
violations.push({
|
|
858
|
+
ruleId: 'go-defer-in-loop',
|
|
859
|
+
message: 'defer 在循环内会延迟到函数返回时才执行,可能导致资源泄露或大量堆积',
|
|
860
|
+
severity: 'warning',
|
|
861
|
+
line: i + 1,
|
|
862
|
+
snippet: lines[i].trim().slice(0, 120),
|
|
863
|
+
dimension: 'file',
|
|
864
|
+
fixSuggestion: '将循环体提取到独立函数中,或手动调用 Close()',
|
|
865
|
+
});
|
|
866
|
+
}
|
|
867
|
+
// 简化: 遇到 } 且缩进回到顶层,认为循环结束
|
|
868
|
+
if (inLoop && trimmed === '}' && (lines[i].match(/^\t/) || lines[i].match(/^}/))) {
|
|
869
|
+
inLoop = false;
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
// ── Python ──
|
|
875
|
+
if (language === 'python') {
|
|
876
|
+
// 文件中同时存在 tab 和 space 缩进
|
|
877
|
+
let hasTab = false;
|
|
878
|
+
let hasSpace = false;
|
|
879
|
+
for (let i = 0; i < Math.min(lines.length, 200); i++) {
|
|
880
|
+
if (/^\t/.test(lines[i])) hasTab = true;
|
|
881
|
+
if (/^ {2,}/.test(lines[i]) && !/^\t/.test(lines[i])) hasSpace = true;
|
|
882
|
+
}
|
|
883
|
+
if (hasTab && hasSpace) {
|
|
884
|
+
violations.push({
|
|
885
|
+
ruleId: 'py-mixed-indentation',
|
|
886
|
+
message: '文件混用 tab 和 space 缩进,Python 对此敏感,请统一使用 space',
|
|
887
|
+
severity: 'warning',
|
|
888
|
+
line: 1,
|
|
889
|
+
snippet: '',
|
|
890
|
+
dimension: 'file',
|
|
891
|
+
});
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
|
|
609
895
|
return violations;
|
|
610
896
|
}
|
|
611
897
|
|
|
@@ -624,8 +910,8 @@ export class GuardCheckEngine {
|
|
|
624
910
|
violations,
|
|
625
911
|
summary: {
|
|
626
912
|
total: violations.length,
|
|
627
|
-
errors: violations.filter(v => v.severity === 'error').length,
|
|
628
|
-
warnings: violations.filter(v => v.severity === 'warning').length,
|
|
913
|
+
errors: violations.filter((v) => v.severity === 'error').length,
|
|
914
|
+
warnings: violations.filter((v) => v.severity === 'warning').length,
|
|
629
915
|
},
|
|
630
916
|
};
|
|
631
917
|
}
|
|
@@ -651,7 +937,7 @@ export class GuardCheckEngine {
|
|
|
651
937
|
// ── 跨文件检查 ──
|
|
652
938
|
const crossFileViolations = this._runCrossFileChecks(files);
|
|
653
939
|
totalViolations += crossFileViolations.length;
|
|
654
|
-
totalErrors += crossFileViolations.filter(v => v.severity === 'error').length;
|
|
940
|
+
totalErrors += crossFileViolations.filter((v) => v.severity === 'error').length;
|
|
655
941
|
|
|
656
942
|
return {
|
|
657
943
|
files: results,
|
|
@@ -660,7 +946,7 @@ export class GuardCheckEngine {
|
|
|
660
946
|
filesChecked: results.length,
|
|
661
947
|
totalViolations,
|
|
662
948
|
totalErrors,
|
|
663
|
-
filesWithViolations: results.filter(r => r.summary.total > 0).length,
|
|
949
|
+
filesWithViolations: results.filter((r) => r.summary.total > 0).length,
|
|
664
950
|
},
|
|
665
951
|
};
|
|
666
952
|
}
|
|
@@ -680,7 +966,9 @@ export class GuardCheckEngine {
|
|
|
680
966
|
|
|
681
967
|
for (const { path: filePath, content } of files) {
|
|
682
968
|
const ext = filePath.split('.').pop()?.toLowerCase();
|
|
683
|
-
if (ext !== 'm' && ext !== 'mm' && ext !== 'h')
|
|
969
|
+
if (ext !== 'm' && ext !== 'mm' && ext !== 'h') {
|
|
970
|
+
continue;
|
|
971
|
+
}
|
|
684
972
|
|
|
685
973
|
const lines = content.split(/\r?\n/);
|
|
686
974
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -688,7 +976,9 @@ export class GuardCheckEngine {
|
|
|
688
976
|
let m;
|
|
689
977
|
while ((m = categoryRegex.exec(lines[i])) !== null) {
|
|
690
978
|
const key = `${m[1]}(${m[2]})`;
|
|
691
|
-
if (!categoryMap.has(key))
|
|
979
|
+
if (!categoryMap.has(key)) {
|
|
980
|
+
categoryMap.set(key, []);
|
|
981
|
+
}
|
|
692
982
|
categoryMap.get(key).push({
|
|
693
983
|
filePath,
|
|
694
984
|
line: i + 1,
|
|
@@ -701,11 +991,13 @@ export class GuardCheckEngine {
|
|
|
701
991
|
// .h 和 .m 成对出现是正常的(声明 + 实现),只有同类型文件重名才是问题
|
|
702
992
|
// 或者超过 2 处声明就一定有问题
|
|
703
993
|
for (const [key, locations] of categoryMap) {
|
|
704
|
-
if (locations.length <= 1)
|
|
994
|
+
if (locations.length <= 1) {
|
|
995
|
+
continue;
|
|
996
|
+
}
|
|
705
997
|
|
|
706
998
|
// 按文件扩展名分组: .h 和 .m/.mm 各一个是合法的
|
|
707
|
-
const hFiles = locations.filter(l => l.filePath.endsWith('.h'));
|
|
708
|
-
const mFiles = locations.filter(l => !l.filePath.endsWith('.h'));
|
|
999
|
+
const hFiles = locations.filter((l) => l.filePath.endsWith('.h'));
|
|
1000
|
+
const mFiles = locations.filter((l) => !l.filePath.endsWith('.h'));
|
|
709
1001
|
|
|
710
1002
|
// 同类型文件中有多个声明 → 重名冲突
|
|
711
1003
|
const hasDuplicateH = hFiles.length > 1;
|
|
@@ -715,10 +1007,13 @@ export class GuardCheckEngine {
|
|
|
715
1007
|
|
|
716
1008
|
if (hasDuplicateH || hasDuplicateM || tooMany) {
|
|
717
1009
|
// 收集冲突的那些位置
|
|
718
|
-
const conflictLocations = tooMany
|
|
719
|
-
|
|
720
|
-
: hasDuplicateH
|
|
721
|
-
|
|
1010
|
+
const conflictLocations = tooMany
|
|
1011
|
+
? locations
|
|
1012
|
+
: hasDuplicateH && hasDuplicateM
|
|
1013
|
+
? locations
|
|
1014
|
+
: hasDuplicateH
|
|
1015
|
+
? hFiles
|
|
1016
|
+
: mFiles;
|
|
722
1017
|
|
|
723
1018
|
violations.push({
|
|
724
1019
|
ruleId: 'objc-cross-file-duplicate-category',
|