autosnippet 3.0.1 → 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 +655 -260
- 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
|
@@ -30,16 +30,20 @@ export class GuardFeedbackLoop {
|
|
|
30
30
|
* @returns {Array<{ ruleId: string, filePath: string, fixRecipeId: string }>} 已修复且有 Recipe 关联的列表
|
|
31
31
|
*/
|
|
32
32
|
detectFixedViolations(currentResult, filePath) {
|
|
33
|
-
if (!this.violationsStore)
|
|
33
|
+
if (!this.violationsStore) {
|
|
34
|
+
return [];
|
|
35
|
+
}
|
|
34
36
|
|
|
35
37
|
try {
|
|
36
38
|
const previousRuns = this.violationsStore.getRunsByFile(filePath);
|
|
37
|
-
if (previousRuns.length === 0)
|
|
39
|
+
if (previousRuns.length === 0) {
|
|
40
|
+
return [];
|
|
41
|
+
}
|
|
38
42
|
|
|
39
43
|
// 取最近一次运行结果
|
|
40
44
|
const lastRun = previousRuns[previousRuns.length - 1];
|
|
41
|
-
const lastRuleIds = new Set((lastRun.violations || []).map(v => v.ruleId));
|
|
42
|
-
const currentRuleIds = new Set((currentResult.violations || []).map(v => v.ruleId));
|
|
45
|
+
const lastRuleIds = new Set((lastRun.violations || []).map((v) => v.ruleId));
|
|
46
|
+
const currentRuleIds = new Set((currentResult.violations || []).map((v) => v.ruleId));
|
|
43
47
|
|
|
44
48
|
const fixed = [];
|
|
45
49
|
for (const ruleId of lastRuleIds) {
|
|
@@ -64,7 +68,9 @@ export class GuardFeedbackLoop {
|
|
|
64
68
|
* @param {Array<{ ruleId: string, filePath: string, fixRecipeId: string }>} fixedList
|
|
65
69
|
*/
|
|
66
70
|
autoConfirmUsage(fixedList) {
|
|
67
|
-
if (!this.feedbackCollector || !fixedList?.length)
|
|
71
|
+
if (!this.feedbackCollector || !fixedList?.length) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
68
74
|
|
|
69
75
|
for (const { ruleId, fixRecipeId, filePath } of fixedList) {
|
|
70
76
|
try {
|
|
@@ -74,7 +80,9 @@ export class GuardFeedbackLoop {
|
|
|
74
80
|
ruleId,
|
|
75
81
|
filePath,
|
|
76
82
|
});
|
|
77
|
-
this.logger.info(
|
|
83
|
+
this.logger.info(
|
|
84
|
+
`[GuardFeedbackLoop] Auto-confirmed usage: recipe=${fixRecipeId} from fixing rule=${ruleId}`
|
|
85
|
+
);
|
|
78
86
|
} catch (err) {
|
|
79
87
|
this.logger.debug(`[GuardFeedbackLoop] autoConfirmUsage error: ${err.message}`);
|
|
80
88
|
}
|
|
@@ -91,7 +99,9 @@ export class GuardFeedbackLoop {
|
|
|
91
99
|
const fixed = this.detectFixedViolations(currentResult, filePath);
|
|
92
100
|
if (fixed.length > 0) {
|
|
93
101
|
this.autoConfirmUsage(fixed);
|
|
94
|
-
this.logger.info(
|
|
102
|
+
this.logger.info(
|
|
103
|
+
`[GuardFeedbackLoop] Detected ${fixed.length} fixed violations in ${filePath}`
|
|
104
|
+
);
|
|
95
105
|
}
|
|
96
106
|
return fixed;
|
|
97
107
|
}
|
|
@@ -101,7 +111,7 @@ export class GuardFeedbackLoop {
|
|
|
101
111
|
*/
|
|
102
112
|
_findFixRecipe(ruleId, violations) {
|
|
103
113
|
// 先从 violation 本身的 fixSuggestion 查找
|
|
104
|
-
for (const v of
|
|
114
|
+
for (const v of violations || []) {
|
|
105
115
|
if (v.ruleId === ruleId && v.fixSuggestion) {
|
|
106
116
|
return v.fixSuggestion.replace(/^recipe:/, '');
|
|
107
117
|
}
|
|
@@ -111,11 +121,13 @@ export class GuardFeedbackLoop {
|
|
|
111
121
|
if (this.guardCheckEngine) {
|
|
112
122
|
try {
|
|
113
123
|
const rules = this.guardCheckEngine.getRules();
|
|
114
|
-
const rule = rules.find(r => r.id === ruleId);
|
|
124
|
+
const rule = rules.find((r) => r.id === ruleId);
|
|
115
125
|
if (rule?.fixSuggestion) {
|
|
116
126
|
return rule.fixSuggestion.replace(/^recipe:/, '');
|
|
117
127
|
}
|
|
118
|
-
} catch {
|
|
128
|
+
} catch {
|
|
129
|
+
/* ignore */
|
|
130
|
+
}
|
|
119
131
|
}
|
|
120
132
|
|
|
121
133
|
return null;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
1
2
|
import Logger from '../../infrastructure/logging/Logger.js';
|
|
2
|
-
import {
|
|
3
|
+
import { ConflictError, NotFoundError, ValidationError } from '../../shared/errors/index.js';
|
|
3
4
|
import { unixNow } from '../../shared/utils/common.js';
|
|
4
|
-
import { v4 as uuidv4 } from 'uuid';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* GuardService
|
|
@@ -44,14 +44,16 @@ export class GuardService {
|
|
|
44
44
|
boundaries: [],
|
|
45
45
|
preconditions: [],
|
|
46
46
|
sideEffects: [],
|
|
47
|
-
guards: [
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
47
|
+
guards: [
|
|
48
|
+
{
|
|
49
|
+
...(data.pattern ? { pattern: data.pattern } : {}),
|
|
50
|
+
severity: data.severity || 'warning',
|
|
51
|
+
message: data.description || '',
|
|
52
|
+
type: data.type || 'regex',
|
|
53
|
+
...(data.astQuery ? { astQuery: data.astQuery } : {}),
|
|
54
|
+
...(data.fixSuggestion ? { fixSuggestion: data.fixSuggestion } : {}),
|
|
55
|
+
},
|
|
56
|
+
],
|
|
55
57
|
},
|
|
56
58
|
tags: data.languages || [],
|
|
57
59
|
lifecycle: 'active',
|
|
@@ -82,7 +84,9 @@ export class GuardService {
|
|
|
82
84
|
async enableRule(ruleId, context) {
|
|
83
85
|
try {
|
|
84
86
|
const entry = await this.knowledgeRepository.findById(ruleId);
|
|
85
|
-
if (!entry)
|
|
87
|
+
if (!entry) {
|
|
88
|
+
throw new NotFoundError('Guard rule not found', 'knowledge_entry', ruleId);
|
|
89
|
+
}
|
|
86
90
|
if (entry.lifecycle === 'active') {
|
|
87
91
|
throw new ConflictError('Rule is already enabled', 'Cannot enable an already enabled rule');
|
|
88
92
|
}
|
|
@@ -111,9 +115,14 @@ export class GuardService {
|
|
|
111
115
|
async disableRule(ruleId, reason, context) {
|
|
112
116
|
try {
|
|
113
117
|
const entry = await this.knowledgeRepository.findById(ruleId);
|
|
114
|
-
if (!entry)
|
|
118
|
+
if (!entry) {
|
|
119
|
+
throw new NotFoundError('Guard rule not found', 'knowledge_entry', ruleId);
|
|
120
|
+
}
|
|
115
121
|
if (entry.lifecycle === 'deprecated') {
|
|
116
|
-
throw new ConflictError(
|
|
122
|
+
throw new ConflictError(
|
|
123
|
+
'Rule is already disabled',
|
|
124
|
+
'Cannot disable an already disabled rule'
|
|
125
|
+
);
|
|
117
126
|
}
|
|
118
127
|
|
|
119
128
|
if (!reason || reason.trim().length === 0) {
|
|
@@ -158,9 +167,7 @@ export class GuardService {
|
|
|
158
167
|
|
|
159
168
|
// 按语言过滤
|
|
160
169
|
if (language) {
|
|
161
|
-
guardEntries = guardEntries.filter(
|
|
162
|
-
e => !e.language || e.language === language
|
|
163
|
-
);
|
|
170
|
+
guardEntries = guardEntries.filter((e) => !e.language || e.language === language);
|
|
164
171
|
}
|
|
165
172
|
|
|
166
173
|
const matches = [];
|
|
@@ -176,7 +183,7 @@ export class GuardService {
|
|
|
176
183
|
ruleName: entry.title,
|
|
177
184
|
severity: guard.severity || 'warning',
|
|
178
185
|
message: guard.message || '',
|
|
179
|
-
matches: codeMatches.map(m => ({
|
|
186
|
+
matches: codeMatches.map((m) => ({
|
|
180
187
|
match: m[0],
|
|
181
188
|
index: m.index,
|
|
182
189
|
line: code.substring(0, m.index).split('\\n').length,
|
|
@@ -185,7 +192,10 @@ export class GuardService {
|
|
|
185
192
|
});
|
|
186
193
|
}
|
|
187
194
|
} catch (e) {
|
|
188
|
-
this.logger.warn('Error matching guard pattern', {
|
|
195
|
+
this.logger.warn('Error matching guard pattern', {
|
|
196
|
+
entryId: entry.id,
|
|
197
|
+
error: e.message,
|
|
198
|
+
});
|
|
189
199
|
}
|
|
190
200
|
}
|
|
191
201
|
}
|
|
@@ -221,7 +231,7 @@ export class GuardService {
|
|
|
221
231
|
const { page = 1, pageSize = 20 } = pagination;
|
|
222
232
|
const result = await this.knowledgeRepository.search(keyword, { page, pageSize });
|
|
223
233
|
result.data = (result.data || []).filter(
|
|
224
|
-
r => r.kind === 'rule' && r.knowledgeType === 'boundary-constraint'
|
|
234
|
+
(r) => r.kind === 'rule' && r.knowledgeType === 'boundary-constraint'
|
|
225
235
|
);
|
|
226
236
|
result.total = result.data.length;
|
|
227
237
|
return result;
|
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
* 持久化到 AutoSnippet/guard-learner.json(Git 友好)
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
7
|
+
import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
8
|
+
import { dirname, join } from 'node:path';
|
|
9
9
|
import Logger from '../../infrastructure/logging/Logger.js';
|
|
10
|
-
import pathGuard from '../../shared/PathGuard.js';
|
|
11
10
|
import { RULE_LEARNER } from '../../shared/constants.js';
|
|
11
|
+
import pathGuard from '../../shared/PathGuard.js';
|
|
12
12
|
|
|
13
13
|
const PROBLEMATIC_THRESHOLD = {
|
|
14
14
|
falsePositiveRate: RULE_LEARNER.PROBLEMATIC_FALSE_POSITIVE_RATE,
|
|
@@ -17,7 +17,7 @@ const PROBLEMATIC_THRESHOLD = {
|
|
|
17
17
|
|
|
18
18
|
export class RuleLearner {
|
|
19
19
|
#learnerPath;
|
|
20
|
-
#data;
|
|
20
|
+
#data; // { ruleStats: { [ruleId]: { triggers, correct, falsePositive, falseNegative } } }
|
|
21
21
|
|
|
22
22
|
constructor(projectRoot, options = {}) {
|
|
23
23
|
const kbDir = options.knowledgeBaseDir || 'AutoSnippet';
|
|
@@ -37,7 +37,9 @@ export class RuleLearner {
|
|
|
37
37
|
stat.triggers++;
|
|
38
38
|
const now = new Date().toISOString();
|
|
39
39
|
stat.lastTriggered = now;
|
|
40
|
-
if (!stat.firstTriggered)
|
|
40
|
+
if (!stat.firstTriggered) {
|
|
41
|
+
stat.firstTriggered = now;
|
|
42
|
+
}
|
|
41
43
|
this.#save();
|
|
42
44
|
}
|
|
43
45
|
|
|
@@ -48,9 +50,13 @@ export class RuleLearner {
|
|
|
48
50
|
*/
|
|
49
51
|
recordFeedback(ruleId, feedbackType) {
|
|
50
52
|
const stat = this.#ensureStat(ruleId);
|
|
51
|
-
if (feedbackType === 'correct')
|
|
52
|
-
|
|
53
|
-
else if (feedbackType === '
|
|
53
|
+
if (feedbackType === 'correct') {
|
|
54
|
+
stat.correct++;
|
|
55
|
+
} else if (feedbackType === 'falsePositive') {
|
|
56
|
+
stat.falsePositive++;
|
|
57
|
+
} else if (feedbackType === 'falseNegative') {
|
|
58
|
+
stat.falseNegative++;
|
|
59
|
+
}
|
|
54
60
|
stat.lastFeedback = new Date().toISOString();
|
|
55
61
|
this.#save();
|
|
56
62
|
}
|
|
@@ -70,9 +76,9 @@ export class RuleLearner {
|
|
|
70
76
|
const fp = stat.falsePositive;
|
|
71
77
|
const fn = stat.falseNegative;
|
|
72
78
|
|
|
73
|
-
const precision =
|
|
74
|
-
const recall =
|
|
75
|
-
const f1 =
|
|
79
|
+
const precision = tp + fp > 0 ? tp / (tp + fp) : 1;
|
|
80
|
+
const recall = tp + fn > 0 ? tp / (tp + fn) : 1;
|
|
81
|
+
const f1 = precision + recall > 0 ? (2 * precision * recall) / (precision + recall) : 0;
|
|
76
82
|
const falsePositiveRate = stat.triggers > 0 ? fp / stat.triggers : 0;
|
|
77
83
|
|
|
78
84
|
return { precision, recall, f1, triggers: stat.triggers, falsePositiveRate };
|
|
@@ -85,7 +91,9 @@ export class RuleLearner {
|
|
|
85
91
|
getProblematicRules() {
|
|
86
92
|
const results = [];
|
|
87
93
|
for (const [ruleId, stat] of Object.entries(this.#data.ruleStats)) {
|
|
88
|
-
if (stat.triggers < PROBLEMATIC_THRESHOLD.minTriggers)
|
|
94
|
+
if (stat.triggers < PROBLEMATIC_THRESHOLD.minTriggers) {
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
89
97
|
|
|
90
98
|
const metrics = this.getMetrics(ruleId);
|
|
91
99
|
if (metrics.falsePositiveRate >= PROBLEMATIC_THRESHOLD.falsePositiveRate) {
|
|
@@ -163,7 +171,10 @@ export class RuleLearner {
|
|
|
163
171
|
// 策略 2: 高触发 + 高精度内置规则 → 建议创建项目定制版
|
|
164
172
|
const allStats = this.getAllStats();
|
|
165
173
|
for (const [ruleId, stat] of Object.entries(allStats)) {
|
|
166
|
-
if (
|
|
174
|
+
if (
|
|
175
|
+
stat.triggers > RULE_LEARNER.HIGH_TRIGGER_COUNT &&
|
|
176
|
+
(stat.metrics?.precision ?? 1) > RULE_LEARNER.HIGH_PRECISION
|
|
177
|
+
) {
|
|
167
178
|
suggestions.push({
|
|
168
179
|
type: 'specialize',
|
|
169
180
|
ruleId,
|
|
@@ -177,14 +188,18 @@ export class RuleLearner {
|
|
|
177
188
|
// 策略 3: 长期无触发的规则 → 可能不适用
|
|
178
189
|
for (const [ruleId, stat] of Object.entries(allStats)) {
|
|
179
190
|
if (stat.triggers === 0 && stat.lastTriggered) {
|
|
180
|
-
const daysSinceLastTrigger =
|
|
191
|
+
const daysSinceLastTrigger =
|
|
192
|
+
(Date.now() - new Date(stat.lastTriggered).getTime()) / 86400000;
|
|
181
193
|
if (daysSinceLastTrigger > RULE_LEARNER.UNUSED_DAYS_THRESHOLD) {
|
|
182
194
|
suggestions.push({
|
|
183
195
|
type: 'review_unused',
|
|
184
196
|
ruleId,
|
|
185
197
|
message: `规则 ${ruleId} 超过 ${RULE_LEARNER.UNUSED_DAYS_THRESHOLD} 天未触发,建议审查是否仍需保留`,
|
|
186
198
|
confidence: RULE_LEARNER.CONFIDENCE_REVIEW,
|
|
187
|
-
evidence: {
|
|
199
|
+
evidence: {
|
|
200
|
+
daysSinceLastTrigger: Math.round(daysSinceLastTrigger),
|
|
201
|
+
triggers: stat.triggers,
|
|
202
|
+
},
|
|
188
203
|
});
|
|
189
204
|
}
|
|
190
205
|
}
|
|
@@ -201,10 +216,14 @@ export class RuleLearner {
|
|
|
201
216
|
*/
|
|
202
217
|
trackRuleEffectiveness(ruleId) {
|
|
203
218
|
const stat = this.#data.ruleStats[ruleId];
|
|
204
|
-
if (!stat)
|
|
219
|
+
if (!stat) {
|
|
220
|
+
return { status: 'no_data', triggers: 0, precision: 1, recommendation: 'monitor' };
|
|
221
|
+
}
|
|
205
222
|
|
|
206
223
|
const firstTriggered = stat.firstTriggered || stat.lastTriggered;
|
|
207
|
-
if (!firstTriggered)
|
|
224
|
+
if (!firstTriggered) {
|
|
225
|
+
return { status: 'no_triggers', triggers: 0, precision: 1, recommendation: 'monitor' };
|
|
226
|
+
}
|
|
208
227
|
|
|
209
228
|
const daysSinceFirstTrigger = (Date.now() - new Date(firstTriggered).getTime()) / 86400000;
|
|
210
229
|
|
|
@@ -222,7 +241,10 @@ export class RuleLearner {
|
|
|
222
241
|
const metrics = this.getMetrics(ruleId);
|
|
223
242
|
|
|
224
243
|
// 14 天后判定
|
|
225
|
-
if (
|
|
244
|
+
if (
|
|
245
|
+
metrics.precision < RULE_LEARNER.LOW_PRECISION &&
|
|
246
|
+
stat.triggers >= PROBLEMATIC_THRESHOLD.minTriggers
|
|
247
|
+
) {
|
|
226
248
|
return {
|
|
227
249
|
status: 'ineffective',
|
|
228
250
|
triggers: stat.triggers,
|
|
@@ -263,17 +285,23 @@ export class RuleLearner {
|
|
|
263
285
|
if (existsSync(this.#learnerPath)) {
|
|
264
286
|
return JSON.parse(readFileSync(this.#learnerPath, 'utf-8'));
|
|
265
287
|
}
|
|
266
|
-
} catch {
|
|
288
|
+
} catch {
|
|
289
|
+
/* silent */
|
|
290
|
+
}
|
|
267
291
|
return { ruleStats: {} };
|
|
268
292
|
}
|
|
269
293
|
|
|
270
294
|
#save() {
|
|
271
295
|
try {
|
|
272
296
|
const dir = dirname(this.#learnerPath);
|
|
273
|
-
if (!existsSync(dir))
|
|
297
|
+
if (!existsSync(dir)) {
|
|
298
|
+
mkdirSync(dir, { recursive: true });
|
|
299
|
+
}
|
|
274
300
|
writeFileSync(this.#learnerPath, JSON.stringify(this.#data, null, 2));
|
|
275
301
|
} catch (err) {
|
|
276
|
-
Logger.getInstance().warn('RuleLearner: failed to persist learner data', {
|
|
302
|
+
Logger.getInstance().warn('RuleLearner: failed to persist learner data', {
|
|
303
|
+
error: err.message,
|
|
304
|
+
});
|
|
277
305
|
}
|
|
278
306
|
}
|
|
279
307
|
|
|
@@ -283,10 +311,14 @@ export class RuleLearner {
|
|
|
283
311
|
if (existsSync(oldPath) && !existsSync(this.#learnerPath)) {
|
|
284
312
|
const content = readFileSync(oldPath, 'utf-8');
|
|
285
313
|
const dir = dirname(this.#learnerPath);
|
|
286
|
-
if (!existsSync(dir))
|
|
314
|
+
if (!existsSync(dir)) {
|
|
315
|
+
mkdirSync(dir, { recursive: true });
|
|
316
|
+
}
|
|
287
317
|
writeFileSync(this.#learnerPath, content);
|
|
288
318
|
unlinkSync(oldPath);
|
|
289
319
|
}
|
|
290
|
-
} catch {
|
|
320
|
+
} catch {
|
|
321
|
+
/* 迁移失败不阻断启动 */
|
|
322
|
+
}
|
|
291
323
|
}
|
|
292
324
|
}
|
|
@@ -1,25 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* SourceFileCollector — 递归收集项目源文件
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* 从 GuardHandler 提取的公共工具,供 ComplianceReporter / guard:ci / guard:staged 复用
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { join, extname } from 'node:path';
|
|
8
7
|
import { readdir, readFile } from 'node:fs/promises';
|
|
8
|
+
import { extname, join } from 'node:path';
|
|
9
|
+
import { LanguageService } from '../../shared/LanguageService.js';
|
|
9
10
|
|
|
10
|
-
/** 支持审计的源文件扩展名 */
|
|
11
|
-
export const SOURCE_EXTS =
|
|
12
|
-
'.m', '.mm', '.h', '.swift',
|
|
13
|
-
'.c', '.cpp', '.cc', '.cxx', '.hpp',
|
|
14
|
-
'.js', '.ts', '.jsx', '.tsx',
|
|
15
|
-
'.java', '.kt', '.py', '.rb', '.go', '.rs',
|
|
16
|
-
]);
|
|
11
|
+
/** 支持审计的源文件扩展名 — 委托给 LanguageService */
|
|
12
|
+
export const SOURCE_EXTS = LanguageService.sourceExts;
|
|
17
13
|
|
|
18
14
|
/** 跳过的目录 */
|
|
19
15
|
const SKIP_DIRS = new Set([
|
|
20
|
-
'node_modules',
|
|
21
|
-
'
|
|
22
|
-
'
|
|
16
|
+
'node_modules',
|
|
17
|
+
'.git',
|
|
18
|
+
'build',
|
|
19
|
+
'DerivedData',
|
|
20
|
+
'Pods',
|
|
21
|
+
'.build',
|
|
22
|
+
'vendor',
|
|
23
|
+
'dist',
|
|
24
|
+
'.next',
|
|
25
|
+
'Carthage',
|
|
26
|
+
'xcuserdata',
|
|
27
|
+
'__pycache__',
|
|
23
28
|
]);
|
|
24
29
|
|
|
25
30
|
/**
|
|
@@ -32,16 +37,14 @@ const SKIP_DIRS = new Set([
|
|
|
32
37
|
* @returns {Promise<string[]>} 文件路径列表
|
|
33
38
|
*/
|
|
34
39
|
export async function collectSourceFiles(dir, options = {}) {
|
|
35
|
-
const {
|
|
36
|
-
extensions = SOURCE_EXTS,
|
|
37
|
-
skipDirs = SKIP_DIRS,
|
|
38
|
-
maxFiles = Infinity,
|
|
39
|
-
} = options;
|
|
40
|
+
const { extensions = SOURCE_EXTS, skipDirs = SKIP_DIRS, maxFiles = Infinity } = options;
|
|
40
41
|
|
|
41
42
|
const files = [];
|
|
42
43
|
|
|
43
44
|
async function walk(currentDir) {
|
|
44
|
-
if (files.length >= maxFiles)
|
|
45
|
+
if (files.length >= maxFiles) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
45
48
|
|
|
46
49
|
let entries;
|
|
47
50
|
try {
|
|
@@ -51,8 +54,12 @@ export async function collectSourceFiles(dir, options = {}) {
|
|
|
51
54
|
}
|
|
52
55
|
|
|
53
56
|
for (const entry of entries) {
|
|
54
|
-
if (files.length >= maxFiles)
|
|
55
|
-
|
|
57
|
+
if (files.length >= maxFiles) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (entry.name.startsWith('.') && entry.name !== '.') {
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
56
63
|
|
|
57
64
|
const fullPath = join(currentDir, entry.name);
|
|
58
65
|
if (entry.isDirectory()) {
|
|
@@ -27,25 +27,29 @@ export class ViolationsStore {
|
|
|
27
27
|
const id = `run_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
28
28
|
const now = Math.floor(Date.now() / 1000);
|
|
29
29
|
|
|
30
|
-
this.#db
|
|
30
|
+
this.#db
|
|
31
|
+
.prepare(`
|
|
31
32
|
INSERT INTO guard_violations (id, file_path, triggered_at, violation_count, summary, violations_json, created_at)
|
|
32
33
|
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
33
|
-
`)
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
34
|
+
`)
|
|
35
|
+
.run(
|
|
36
|
+
id,
|
|
37
|
+
run.filePath || '',
|
|
38
|
+
new Date().toISOString(),
|
|
39
|
+
(run.violations || []).length,
|
|
40
|
+
run.summary || '',
|
|
41
|
+
JSON.stringify(run.violations || []),
|
|
42
|
+
now
|
|
43
|
+
);
|
|
42
44
|
|
|
43
45
|
// 超限截断:保留最新 MAX_RUNS 条
|
|
44
|
-
this.#db
|
|
46
|
+
this.#db
|
|
47
|
+
.prepare(`
|
|
45
48
|
DELETE FROM guard_violations WHERE id NOT IN (
|
|
46
49
|
SELECT id FROM guard_violations ORDER BY created_at DESC LIMIT ?
|
|
47
50
|
)
|
|
48
|
-
`)
|
|
51
|
+
`)
|
|
52
|
+
.run(MAX_RUNS);
|
|
49
53
|
|
|
50
54
|
return id;
|
|
51
55
|
}
|
|
@@ -56,50 +60,49 @@ export class ViolationsStore {
|
|
|
56
60
|
* 获取所有运行记录(最新在后)
|
|
57
61
|
*/
|
|
58
62
|
getRuns() {
|
|
59
|
-
const rows = this.#db.prepare(
|
|
60
|
-
|
|
61
|
-
).all();
|
|
62
|
-
return rows.map(r => this.#rowToRun(r));
|
|
63
|
+
const rows = this.#db.prepare('SELECT * FROM guard_violations ORDER BY created_at ASC').all();
|
|
64
|
+
return rows.map((r) => this.#rowToRun(r));
|
|
63
65
|
}
|
|
64
66
|
|
|
65
67
|
/**
|
|
66
68
|
* 按文件路径查询历史
|
|
67
69
|
*/
|
|
68
70
|
getRunsByFile(filePath) {
|
|
69
|
-
const rows = this.#db
|
|
70
|
-
'SELECT * FROM guard_violations WHERE file_path = ? ORDER BY created_at ASC'
|
|
71
|
-
|
|
72
|
-
return rows.map(r => this.#rowToRun(r));
|
|
71
|
+
const rows = this.#db
|
|
72
|
+
.prepare('SELECT * FROM guard_violations WHERE file_path = ? ORDER BY created_at ASC')
|
|
73
|
+
.all(filePath);
|
|
74
|
+
return rows.map((r) => this.#rowToRun(r));
|
|
73
75
|
}
|
|
74
76
|
|
|
75
77
|
/**
|
|
76
78
|
* 获取最近 N 条记录
|
|
77
79
|
*/
|
|
78
80
|
getRecentRuns(n = 20) {
|
|
79
|
-
const rows = this.#db
|
|
80
|
-
'SELECT * FROM guard_violations ORDER BY created_at DESC, rowid DESC LIMIT ?'
|
|
81
|
-
|
|
82
|
-
return rows.reverse().map(r => this.#rowToRun(r));
|
|
81
|
+
const rows = this.#db
|
|
82
|
+
.prepare('SELECT * FROM guard_violations ORDER BY created_at DESC, rowid DESC LIMIT ?')
|
|
83
|
+
.all(n);
|
|
84
|
+
return rows.reverse().map((r) => this.#rowToRun(r));
|
|
83
85
|
}
|
|
84
86
|
|
|
85
87
|
/**
|
|
86
88
|
* 获取统计汇总
|
|
87
89
|
*/
|
|
88
90
|
getStats() {
|
|
89
|
-
const row = this.#db
|
|
91
|
+
const row = this.#db
|
|
92
|
+
.prepare(`
|
|
90
93
|
SELECT
|
|
91
94
|
COUNT(*) AS totalRuns,
|
|
92
95
|
COALESCE(SUM(violation_count), 0) AS totalViolations,
|
|
93
96
|
MAX(triggered_at) AS lastRunAt
|
|
94
97
|
FROM guard_violations
|
|
95
|
-
`)
|
|
98
|
+
`)
|
|
99
|
+
.get();
|
|
96
100
|
|
|
97
101
|
return {
|
|
98
102
|
totalRuns: row.totalRuns,
|
|
99
103
|
totalViolations: row.totalViolations,
|
|
100
|
-
averageViolationsPerRun:
|
|
101
|
-
? (row.totalViolations / row.totalRuns).toFixed(2)
|
|
102
|
-
: 0,
|
|
104
|
+
averageViolationsPerRun:
|
|
105
|
+
row.totalRuns > 0 ? (row.totalViolations / row.totalRuns).toFixed(2) : 0,
|
|
103
106
|
lastRunAt: row.lastRunAt || null,
|
|
104
107
|
};
|
|
105
108
|
}
|
|
@@ -111,7 +114,8 @@ export class ViolationsStore {
|
|
|
111
114
|
*/
|
|
112
115
|
getStatsByRule() {
|
|
113
116
|
try {
|
|
114
|
-
return this.#db
|
|
117
|
+
return this.#db
|
|
118
|
+
.prepare(`
|
|
115
119
|
SELECT
|
|
116
120
|
json_extract(j.value, '$.ruleId') AS ruleId,
|
|
117
121
|
json_extract(j.value, '$.severity') AS severity,
|
|
@@ -120,7 +124,8 @@ export class ViolationsStore {
|
|
|
120
124
|
WHERE json_extract(j.value, '$.ruleId') IS NOT NULL
|
|
121
125
|
GROUP BY ruleId, severity
|
|
122
126
|
ORDER BY count DESC
|
|
123
|
-
`)
|
|
127
|
+
`)
|
|
128
|
+
.all();
|
|
124
129
|
} catch {
|
|
125
130
|
return [];
|
|
126
131
|
}
|
|
@@ -137,8 +142,8 @@ export class ViolationsStore {
|
|
|
137
142
|
return {
|
|
138
143
|
errorsChange: 0,
|
|
139
144
|
warningsChange: 0,
|
|
140
|
-
latestErrors: latest.filter(v => v.severity === 'error').length,
|
|
141
|
-
latestWarnings: latest.filter(v => v.severity === 'warning').length,
|
|
145
|
+
latestErrors: latest.filter((v) => v.severity === 'error').length,
|
|
146
|
+
latestWarnings: latest.filter((v) => v.severity === 'warning').length,
|
|
142
147
|
previousErrors: 0,
|
|
143
148
|
previousWarnings: 0,
|
|
144
149
|
hasHistory: false,
|
|
@@ -146,10 +151,10 @@ export class ViolationsStore {
|
|
|
146
151
|
}
|
|
147
152
|
|
|
148
153
|
const [prev, latest] = recent;
|
|
149
|
-
const latestErrors = latest.violations.filter(v => v.severity === 'error').length;
|
|
150
|
-
const latestWarnings = latest.violations.filter(v => v.severity === 'warning').length;
|
|
151
|
-
const previousErrors = prev.violations.filter(v => v.severity === 'error').length;
|
|
152
|
-
const previousWarnings = prev.violations.filter(v => v.severity === 'warning').length;
|
|
154
|
+
const latestErrors = latest.violations.filter((v) => v.severity === 'error').length;
|
|
155
|
+
const latestWarnings = latest.violations.filter((v) => v.severity === 'warning').length;
|
|
156
|
+
const previousErrors = prev.violations.filter((v) => v.severity === 'error').length;
|
|
157
|
+
const previousWarnings = prev.violations.filter((v) => v.severity === 'warning').length;
|
|
153
158
|
|
|
154
159
|
return {
|
|
155
160
|
errorsChange: latestErrors - previousErrors,
|
|
@@ -211,7 +216,7 @@ export class ViolationsStore {
|
|
|
211
216
|
const total = this.#db.prepare(countSql).get(...countParams).c;
|
|
212
217
|
|
|
213
218
|
return {
|
|
214
|
-
data: rows.map(r => this.#rowToRun(r)),
|
|
219
|
+
data: rows.map((r) => this.#rowToRun(r)),
|
|
215
220
|
pagination: { page, limit, total, pages: Math.ceil(total / limit) },
|
|
216
221
|
};
|
|
217
222
|
}
|