autosnippet 3.3.0 → 3.3.3
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/dashboard/dist/assets/icons-BJ2mUBi8.js +1 -0
- package/dashboard/dist/assets/index-B659K9t5.js +128 -0
- package/dashboard/dist/assets/index-NCm40PMD.css +1 -0
- package/dashboard/dist/index.html +3 -3
- package/dist/bin/cli.d.ts +1 -0
- package/dist/bin/cli.js +284 -142
- package/dist/lib/agent/context/ExplorationTracker.d.ts +2 -0
- package/dist/lib/agent/context/ExplorationTracker.js +21 -3
- package/dist/lib/agent/core/ToolExecutionPipeline.d.ts +3 -1
- package/dist/lib/agent/core/ToolExecutionPipeline.js +8 -1
- package/dist/lib/agent/forge/DynamicComposer.d.ts +58 -0
- package/dist/lib/agent/forge/DynamicComposer.js +99 -0
- package/dist/lib/agent/forge/SandboxRunner.d.ts +60 -0
- package/dist/lib/agent/forge/SandboxRunner.js +251 -0
- package/dist/lib/agent/forge/TemporaryToolRegistry.d.ts +76 -0
- package/dist/lib/agent/forge/TemporaryToolRegistry.js +154 -0
- package/dist/lib/agent/forge/ToolForge.d.ts +92 -0
- package/dist/lib/agent/forge/ToolForge.js +239 -0
- package/dist/lib/agent/forge/ToolRequirementAnalyzer.d.ts +44 -0
- package/dist/lib/agent/forge/ToolRequirementAnalyzer.js +119 -0
- package/dist/lib/agent/tools/ToolRegistry.d.ts +2 -0
- package/dist/lib/agent/tools/ToolRegistry.js +4 -0
- package/dist/lib/agent/tools/composite.js +0 -1
- package/dist/lib/agent/tools/index.d.ts +2 -50
- package/dist/lib/agent/tools/index.js +2 -3
- package/dist/lib/agent/tools/lifecycle.d.ts +1 -58
- package/dist/lib/agent/tools/lifecycle.js +2 -75
- package/dist/lib/cli/KnowledgeSyncService.d.ts +26 -0
- package/dist/lib/cli/KnowledgeSyncService.js +33 -1
- package/dist/lib/cli/deploy/FileManifest.d.ts +0 -21
- package/dist/lib/cli/deploy/FileManifest.js +0 -11
- package/dist/lib/domain/knowledge/KnowledgeEntry.d.ts +10 -0
- package/dist/lib/domain/knowledge/KnowledgeEntry.js +2 -0
- package/dist/lib/domain/knowledge/Lifecycle.d.ts +19 -2
- package/dist/lib/domain/knowledge/Lifecycle.js +32 -6
- package/dist/lib/domain/knowledge/UnifiedValidator.d.ts +1 -5
- package/dist/lib/domain/knowledge/UnifiedValidator.js +7 -44
- package/dist/lib/domain/knowledge/values/Stats.d.ts +29 -0
- package/dist/lib/domain/knowledge/values/Stats.js +41 -0
- package/dist/lib/external/mcp/McpServer.d.ts +19 -38
- package/dist/lib/external/mcp/McpServer.js +145 -117
- package/dist/lib/external/mcp/autoApproveInjector.js +0 -2
- package/dist/lib/external/mcp/handlers/bootstrap/MissionBriefingBuilder.d.ts +26 -1
- package/dist/lib/external/mcp/handlers/bootstrap/MissionBriefingBuilder.js +41 -0
- package/dist/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +49 -0
- package/dist/lib/external/mcp/handlers/bootstrap/shared/bootstrap-phases.d.ts +3 -0
- package/dist/lib/external/mcp/handlers/bootstrap/shared/bootstrap-phases.js +27 -0
- package/dist/lib/external/mcp/handlers/bootstrap/skills.js +1 -1
- package/dist/lib/external/mcp/handlers/bootstrap-external.js +1 -0
- package/dist/lib/external/mcp/handlers/bootstrap-internal.js +2 -0
- package/dist/lib/external/mcp/handlers/browse.d.ts +1 -0
- package/dist/lib/external/mcp/handlers/browse.js +2 -1
- package/dist/lib/external/mcp/handlers/consolidated.d.ts +117 -6
- package/dist/lib/external/mcp/handlers/consolidated.js +251 -71
- package/dist/lib/external/mcp/handlers/guard.d.ts +150 -0
- package/dist/lib/external/mcp/handlers/guard.js +239 -5
- package/dist/lib/external/mcp/handlers/knowledge.d.ts +0 -29
- package/dist/lib/external/mcp/handlers/knowledge.js +1 -76
- package/dist/lib/external/mcp/handlers/panorama.d.ts +36 -0
- package/dist/lib/external/mcp/handlers/panorama.js +156 -0
- package/dist/lib/external/mcp/handlers/system.d.ts +2 -54
- package/dist/lib/external/mcp/handlers/system.js +3 -113
- package/dist/lib/external/mcp/handlers/task.d.ts +13 -24
- package/dist/lib/external/mcp/handlers/task.js +218 -557
- package/dist/lib/external/mcp/handlers/types.d.ts +91 -8
- package/dist/lib/external/mcp/handlers/types.js +18 -1
- package/dist/lib/external/mcp/handlers/wiki-external.d.ts +18 -1
- package/dist/lib/external/mcp/handlers/wiki-external.js +16 -1
- package/dist/lib/external/mcp/tools.d.ts +18 -24
- package/dist/lib/external/mcp/tools.js +132 -159
- package/dist/lib/http/HttpServer.js +52 -0
- package/dist/lib/http/middleware/validate.js +7 -3
- package/dist/lib/http/routes/audit.d.ts +8 -0
- package/dist/lib/http/routes/audit.js +51 -0
- package/dist/lib/http/routes/guardReport.d.ts +10 -0
- package/dist/lib/http/routes/guardReport.js +143 -0
- package/dist/lib/http/routes/knowledge.js +32 -1
- package/dist/lib/http/routes/panorama.d.ts +11 -0
- package/dist/lib/http/routes/panorama.js +322 -0
- package/dist/lib/http/routes/signals.d.ts +10 -0
- package/dist/lib/http/routes/signals.js +104 -0
- package/dist/lib/http/routes/task.d.ts +2 -3
- package/dist/lib/http/routes/task.js +17 -347
- package/dist/lib/http/routes/violations.js +1 -1
- package/dist/lib/infrastructure/audit/AuditLogger.d.ts +6 -1
- package/dist/lib/infrastructure/audit/AuditLogger.js +14 -1
- package/dist/lib/infrastructure/database/drizzle/schema.d.ts +202 -504
- package/dist/lib/infrastructure/database/drizzle/schema.js +38 -69
- package/dist/lib/infrastructure/database/migrations/004_evolution_proposals.d.ts +8 -0
- package/dist/lib/infrastructure/database/migrations/004_evolution_proposals.js +43 -0
- package/dist/lib/infrastructure/database/migrations/005_recipe_source_refs.d.ts +9 -0
- package/dist/lib/infrastructure/database/migrations/005_recipe_source_refs.js +24 -0
- package/dist/lib/infrastructure/logging/Logger.d.ts +2 -0
- package/dist/lib/infrastructure/logging/Logger.js +34 -7
- package/dist/lib/infrastructure/monitoring/ErrorTracker.js +3 -1
- package/dist/lib/infrastructure/monitoring/PerformanceMonitor.d.ts +2 -2
- package/dist/lib/infrastructure/monitoring/PerformanceMonitor.js +12 -10
- package/dist/lib/infrastructure/notification/LarkNotifier.d.ts +24 -0
- package/dist/lib/infrastructure/notification/LarkNotifier.js +97 -0
- package/dist/lib/infrastructure/report/ReportStore.d.ts +45 -0
- package/dist/lib/infrastructure/report/ReportStore.js +133 -0
- package/dist/lib/infrastructure/signal/SignalAggregator.d.ts +18 -0
- package/dist/lib/infrastructure/signal/SignalAggregator.js +84 -0
- package/dist/lib/infrastructure/signal/SignalBridge.d.ts +13 -0
- package/dist/lib/infrastructure/signal/SignalBridge.js +20 -0
- package/dist/lib/infrastructure/signal/SignalBus.d.ts +63 -0
- package/dist/lib/infrastructure/signal/SignalBus.js +106 -0
- package/dist/lib/infrastructure/signal/SignalTraceWriter.d.ts +36 -0
- package/dist/lib/infrastructure/signal/SignalTraceWriter.js +130 -0
- package/dist/lib/infrastructure/vector/HnswVectorAdapter.js +18 -2
- package/dist/lib/injection/ServiceContainer.js +8 -0
- package/dist/lib/injection/ServiceMap.d.ts +16 -10
- package/dist/lib/injection/modules/AgentModule.d.ts +1 -1
- package/dist/lib/injection/modules/AgentModule.js +7 -1
- package/dist/lib/injection/modules/AppModule.d.ts +1 -1
- package/dist/lib/injection/modules/AppModule.js +4 -13
- package/dist/lib/injection/modules/GuardModule.js +27 -2
- package/dist/lib/injection/modules/InfraModule.d.ts +0 -1
- package/dist/lib/injection/modules/InfraModule.js +9 -7
- package/dist/lib/injection/modules/KnowledgeModule.d.ts +5 -0
- package/dist/lib/injection/modules/KnowledgeModule.js +131 -0
- package/dist/lib/injection/modules/PanoramaModule.d.ts +18 -0
- package/dist/lib/injection/modules/PanoramaModule.js +76 -0
- package/dist/lib/injection/modules/SignalModule.d.ts +10 -0
- package/dist/lib/injection/modules/SignalModule.js +84 -0
- package/dist/lib/repository/knowledge/KnowledgeRepository.impl.d.ts +1 -0
- package/dist/lib/repository/knowledge/KnowledgeRepository.impl.js +6 -0
- package/dist/lib/service/bootstrap/BootstrapTaskManager.d.ts +3 -1
- package/dist/lib/service/bootstrap/BootstrapTaskManager.js +20 -1
- package/dist/lib/service/bootstrap/UiStartupTasks.d.ts +45 -0
- package/dist/lib/service/bootstrap/UiStartupTasks.js +101 -0
- package/dist/lib/service/delivery/AgentInstructionsGenerator.js +4 -5
- package/dist/lib/service/delivery/CursorDeliveryPipeline.d.ts +3 -1
- package/dist/lib/service/delivery/CursorDeliveryPipeline.js +13 -10
- package/dist/lib/service/delivery/RulesGenerator.js +3 -2
- package/dist/lib/service/evolution/ConsolidationAdvisor.d.ts +114 -0
- package/dist/lib/service/evolution/ConsolidationAdvisor.js +542 -0
- package/dist/lib/service/evolution/ContradictionDetector.d.ts +54 -0
- package/dist/lib/service/evolution/ContradictionDetector.js +253 -0
- package/dist/lib/service/evolution/DecayDetector.d.ts +71 -0
- package/dist/lib/service/evolution/DecayDetector.js +244 -0
- package/dist/lib/service/evolution/EnhancementSuggester.d.ts +38 -0
- package/dist/lib/service/evolution/EnhancementSuggester.js +220 -0
- package/dist/lib/service/evolution/KnowledgeMetabolism.d.ts +82 -0
- package/dist/lib/service/evolution/KnowledgeMetabolism.js +167 -0
- package/dist/lib/service/evolution/RedundancyAnalyzer.d.ts +53 -0
- package/dist/lib/service/evolution/RedundancyAnalyzer.js +210 -0
- package/dist/lib/service/evolution/StagingManager.d.ts +57 -0
- package/dist/lib/service/evolution/StagingManager.js +201 -0
- package/dist/lib/service/guard/ComplianceReporter.d.ts +42 -2
- package/dist/lib/service/guard/ComplianceReporter.js +43 -5
- package/dist/lib/service/guard/CoverageAnalyzer.d.ts +54 -0
- package/dist/lib/service/guard/CoverageAnalyzer.js +149 -0
- package/dist/lib/service/guard/GuardCheckEngine.d.ts +42 -0
- package/dist/lib/service/guard/GuardCheckEngine.js +465 -14
- package/dist/lib/service/guard/GuardFeedbackLoop.d.ts +3 -0
- package/dist/lib/service/guard/GuardFeedbackLoop.js +9 -0
- package/dist/lib/service/guard/ReverseGuard.d.ts +73 -0
- package/dist/lib/service/guard/ReverseGuard.js +256 -0
- package/dist/lib/service/guard/RuleLearner.d.ts +12 -0
- package/dist/lib/service/guard/RuleLearner.js +38 -0
- package/dist/lib/service/guard/UncertaintyCollector.d.ts +83 -0
- package/dist/lib/service/guard/UncertaintyCollector.js +149 -0
- package/dist/lib/service/guard/ViolationsStore.d.ts +1 -0
- package/dist/lib/service/guard/ViolationsStore.js +33 -3
- package/dist/lib/service/knowledge/ConfidenceRouter.d.ts +13 -0
- package/dist/lib/service/knowledge/ConfidenceRouter.js +14 -0
- package/dist/lib/service/knowledge/KnowledgeService.js +22 -4
- package/dist/lib/service/knowledge/SourceRefReconciler.d.ts +68 -0
- package/dist/lib/service/knowledge/SourceRefReconciler.js +309 -0
- package/dist/lib/service/panorama/CouplingAnalyzer.d.ts +27 -0
- package/dist/lib/service/panorama/CouplingAnalyzer.js +192 -0
- package/dist/lib/service/panorama/DimensionAnalyzer.d.ts +28 -0
- package/dist/lib/service/panorama/DimensionAnalyzer.js +320 -0
- package/dist/lib/service/panorama/LayerInferrer.d.ts +19 -0
- package/dist/lib/service/panorama/LayerInferrer.js +182 -0
- package/dist/lib/service/panorama/ModuleDiscoverer.d.ts +24 -0
- package/dist/lib/service/panorama/ModuleDiscoverer.js +185 -0
- package/dist/lib/service/panorama/PanoramaAggregator.d.ts +29 -0
- package/dist/lib/service/panorama/PanoramaAggregator.js +228 -0
- package/dist/lib/service/panorama/PanoramaScanner.d.ts +52 -0
- package/dist/lib/service/panorama/PanoramaScanner.js +188 -0
- package/dist/lib/service/panorama/PanoramaService.d.ts +125 -0
- package/dist/lib/service/panorama/PanoramaService.js +363 -0
- package/dist/lib/service/panorama/PanoramaTypes.d.ts +134 -0
- package/dist/lib/service/panorama/PanoramaTypes.js +6 -0
- package/dist/lib/service/panorama/RoleRefiner.d.ts +48 -0
- package/dist/lib/service/panorama/RoleRefiner.js +535 -0
- package/dist/lib/service/search/BM25Scorer.d.ts +2 -2
- package/dist/lib/service/search/CoarseRanker.d.ts +7 -6
- package/dist/lib/service/search/CoarseRanker.js +11 -10
- package/dist/lib/service/search/FieldWeightedScorer.d.ts +81 -0
- package/dist/lib/service/search/FieldWeightedScorer.js +318 -0
- package/dist/lib/service/search/MultiSignalRanker.d.ts +3 -2
- package/dist/lib/service/search/MultiSignalRanker.js +17 -1
- package/dist/lib/service/search/SearchEngine.d.ts +9 -7
- package/dist/lib/service/search/SearchEngine.js +67 -10
- package/dist/lib/service/search/SearchTypes.d.ts +25 -3
- package/dist/lib/service/search/SearchTypes.js +6 -1
- package/dist/lib/service/signal/HitRecorder.d.ts +68 -0
- package/dist/lib/service/signal/HitRecorder.js +173 -0
- package/dist/lib/service/skills/SignalCollector.d.ts +3 -1
- package/dist/lib/service/skills/SignalCollector.js +31 -1
- package/dist/lib/service/task/IntentExtractor.d.ts +66 -0
- package/dist/lib/service/task/IntentExtractor.js +256 -0
- package/dist/lib/service/task/PrimeSearchPipeline.d.ts +54 -0
- package/dist/lib/service/task/PrimeSearchPipeline.js +113 -0
- package/dist/lib/service/vector/VectorService.d.ts +3 -0
- package/dist/lib/service/vector/VectorService.js +38 -4
- package/dist/lib/shared/schemas/mcp-tools.d.ts +41 -96
- package/dist/lib/shared/schemas/mcp-tools.js +59 -119
- package/dist/scripts/analyze-signals.d.ts +20 -0
- package/dist/scripts/analyze-signals.js +155 -0
- package/dist/scripts/diagnose-mcp.js +1 -1
- package/package.json +1 -1
- package/skills/autosnippet-create/SKILL.md +98 -89
- package/skills/autosnippet-devdocs/SKILL.md +55 -57
- package/templates/claude-code/hooks/autosnippet-session.sh +10 -15
- package/templates/cursor-hooks/hooks/session-start.sh +1 -1
- package/templates/guard-ci.yml +2 -2
- package/templates/instructions/agent-static.md +2 -1
- package/templates/instructions/conventions.md +5 -6
- package/templates/recipes-setup/README.md +1 -2
- package/templates/recipes-setup/_template.md +39 -39
- package/dashboard/dist/assets/icons-BofcEZ3f.js +0 -1
- package/dashboard/dist/assets/index-D0whuycy.css +0 -1
- package/dashboard/dist/assets/index-SiN1GChm.js +0 -128
- package/dist/lib/domain/task/Task.d.ts +0 -140
- package/dist/lib/domain/task/Task.js +0 -254
- package/dist/lib/domain/task/TaskDependency.d.ts +0 -23
- package/dist/lib/domain/task/TaskDependency.js +0 -34
- package/dist/lib/domain/task/TaskIdGenerator.d.ts +0 -40
- package/dist/lib/domain/task/TaskIdGenerator.js +0 -75
- package/dist/lib/domain/task/index.d.ts +0 -4
- package/dist/lib/domain/task/index.js +0 -4
- package/dist/lib/infrastructure/database/migrations/002_add_tasks.d.ts +0 -11
- package/dist/lib/infrastructure/database/migrations/002_add_tasks.js +0 -86
- package/dist/lib/repository/task/TaskRepository.impl.d.ts +0 -171
- package/dist/lib/repository/task/TaskRepository.impl.js +0 -347
- package/dist/lib/service/task/TaskGraphService.d.ts +0 -222
- package/dist/lib/service/task/TaskGraphService.js +0 -597
- package/dist/lib/service/task/TaskKnowledgeBridge.d.ts +0 -95
- package/dist/lib/service/task/TaskKnowledgeBridge.js +0 -298
- package/dist/lib/service/task/TaskReadyEngine.d.ts +0 -84
- package/dist/lib/service/task/TaskReadyEngine.js +0 -115
|
@@ -10,6 +10,7 @@ import { LanguageService } from '../../shared/LanguageService.js';
|
|
|
10
10
|
import { runCodeLevelChecks } from './GuardCodeChecks.js';
|
|
11
11
|
import { runCrossFileChecks } from './GuardCrossFileChecks.js';
|
|
12
12
|
import { buildCommentMask, buildTestBlockMask, clearPatternCache, compilePattern, detectLanguage, } from './GuardPatternUtils.js';
|
|
13
|
+
import { UncertaintyCollector } from './UncertaintyCollector.js';
|
|
13
14
|
/**
|
|
14
15
|
* 内置默认规则集 — 多语言基础规则
|
|
15
16
|
*
|
|
@@ -80,7 +81,7 @@ const BUILT_IN_RULES = {
|
|
|
80
81
|
'dequeueReusableCell.*as\\s*!',
|
|
81
82
|
'dequeueReusableSupplementaryView.*as\\s*!',
|
|
82
83
|
'dequeueReusableHeaderFooterView.*as\\s*!',
|
|
83
|
-
'
|
|
84
|
+
'\\blayer\\s+as\\s*!',
|
|
84
85
|
],
|
|
85
86
|
},
|
|
86
87
|
'swift-force-try': {
|
|
@@ -456,6 +457,11 @@ export class GuardCheckEngine {
|
|
|
456
457
|
_epInjected;
|
|
457
458
|
_externalRules;
|
|
458
459
|
_guardConfig;
|
|
460
|
+
_signalBus;
|
|
461
|
+
/** 上次 guard 信号指纹,用于去重(相同结果不重复发射) */
|
|
462
|
+
_lastGuardSignalKey;
|
|
463
|
+
_lastBlindSpotSignalKey;
|
|
464
|
+
_uncertaintyCollector;
|
|
459
465
|
db;
|
|
460
466
|
logger;
|
|
461
467
|
constructor(db, options = {}) {
|
|
@@ -473,6 +479,10 @@ export class GuardCheckEngine {
|
|
|
473
479
|
this._epInjected = false;
|
|
474
480
|
/** Guard 配置 — 允许禁用特定规则或调整 Code-Level 检查阈值 */
|
|
475
481
|
this._guardConfig = options.guardConfig || {};
|
|
482
|
+
this._signalBus = options.signalBus || null;
|
|
483
|
+
this._lastGuardSignalKey = '';
|
|
484
|
+
this._lastBlindSpotSignalKey = '';
|
|
485
|
+
this._uncertaintyCollector = new UncertaintyCollector();
|
|
476
486
|
}
|
|
477
487
|
/**
|
|
478
488
|
* 注入 Enhancement Pack 外部规则(支持 RegExp 和 string pattern)
|
|
@@ -534,10 +544,10 @@ export class GuardCheckEngine {
|
|
|
534
544
|
let rows = [];
|
|
535
545
|
try {
|
|
536
546
|
rows = this.db
|
|
537
|
-
.prepare(`SELECT id, title, description, language, scope, constraints
|
|
547
|
+
.prepare(`SELECT id, title, description, language, scope, constraints, lifecycle
|
|
538
548
|
FROM knowledge_entries
|
|
539
549
|
WHERE (kind = 'rule' OR knowledgeType = 'boundary-constraint')
|
|
540
|
-
AND lifecycle
|
|
550
|
+
AND lifecycle IN ('active', 'staging', 'evolving', 'decaying')`)
|
|
541
551
|
.all();
|
|
542
552
|
}
|
|
543
553
|
catch {
|
|
@@ -557,12 +567,14 @@ export class GuardCheckEngine {
|
|
|
557
567
|
for (const g of guards) {
|
|
558
568
|
const ruleType = g.type || 'regex';
|
|
559
569
|
const lang = r.language;
|
|
570
|
+
const isDecaying = r.lifecycle === 'decaying';
|
|
571
|
+
const rawSeverity = (g.severity || 'warning');
|
|
560
572
|
const base = {
|
|
561
573
|
id: (g.id || r.id),
|
|
562
574
|
name: (g.name || r.title),
|
|
563
575
|
message: (g.message || r.description || r.title),
|
|
564
576
|
languages: lang ? [lang, LanguageService.toGuardLangId(lang)] : [],
|
|
565
|
-
severity:
|
|
577
|
+
severity: isDecaying && rawSeverity === 'error' ? 'warning' : rawSeverity,
|
|
566
578
|
dimension: (r.scope || 'file'),
|
|
567
579
|
source: 'database',
|
|
568
580
|
fixSuggestion: (g.fixSuggestion || null),
|
|
@@ -607,6 +619,7 @@ export class GuardCheckEngine {
|
|
|
607
619
|
...(rule.excludePaths ? { excludePaths: rule.excludePaths } : {}),
|
|
608
620
|
...(rule.skipComments ? { skipComments: true } : {}),
|
|
609
621
|
...(rule.skipTestBlocks ? { skipTestBlocks: true } : {}),
|
|
622
|
+
...(rule.excludeLinePatterns ? { excludeLinePatterns: rule.excludeLinePatterns } : {}),
|
|
610
623
|
});
|
|
611
624
|
}
|
|
612
625
|
}
|
|
@@ -692,6 +705,8 @@ export class GuardCheckEngine {
|
|
|
692
705
|
}
|
|
693
706
|
catch {
|
|
694
707
|
this.logger.debug(`Invalid regex in rule ${rule.id}: ${rule.pattern}`);
|
|
708
|
+
this._uncertaintyCollector.recordSkip('regex', 'invalid_regex', `Rule ${rule.id}: pattern "${rule.pattern}" failed to compile`, { ruleId: rule.id || rule.name });
|
|
709
|
+
this._uncertaintyCollector.addUncertain(rule.id || rule.name, rule.message, 'regex', 'invalid_regex', `Pattern compilation failed: ${rule.pattern}`);
|
|
695
710
|
continue;
|
|
696
711
|
}
|
|
697
712
|
const shouldSkipComments = !!rule.skipComments;
|
|
@@ -736,8 +751,10 @@ export class GuardCheckEngine {
|
|
|
736
751
|
disabledRules: this._guardConfig.disabledRules,
|
|
737
752
|
codeLevelThresholds: numericThresholds,
|
|
738
753
|
}));
|
|
739
|
-
// AST
|
|
754
|
+
// AST 语义规则检查(Layer 1: 3 查询函数)
|
|
740
755
|
violations.push(...this._runAstRuleChecks(code, language));
|
|
756
|
+
// AST Layer 2: analyzeFile() 深层检查(复杂度、类膨胀、深嵌套)
|
|
757
|
+
violations.push(...this._runAstLayer2Checks(code, language, filePath));
|
|
741
758
|
// 跟踪 Guard 命中次数(回写 Recipe 统计)
|
|
742
759
|
this.trackGuardHits(violations);
|
|
743
760
|
// ── Reasoning Enrichment: 推理信息跟随数据流动 ──
|
|
@@ -780,11 +797,24 @@ export class GuardCheckEngine {
|
|
|
780
797
|
// AstAnalyzer 作为 ESM 模块,在 constructor 时已被引入
|
|
781
798
|
AstAnalyzer = this._getAstAnalyzer();
|
|
782
799
|
if (!AstAnalyzer || !AstAnalyzer.isAvailable()) {
|
|
800
|
+
// AST 不可用 — 记录 uncertain
|
|
801
|
+
for (const rule of astRules) {
|
|
802
|
+
this._uncertaintyCollector.recordSkip('ast', 'ast_unavailable', `AST check skipped: tree-sitter not available for lang "${language}"`, { ruleId: rule.id });
|
|
803
|
+
this._uncertaintyCollector.addUncertain(rule.id, rule.message, 'ast', 'ast_unavailable', `Tree-sitter not available for language "${language}"`);
|
|
804
|
+
}
|
|
805
|
+
this._uncertaintyCollector.recordLayerStats('ast', astRules.length, 0);
|
|
783
806
|
return [];
|
|
784
807
|
}
|
|
785
808
|
}
|
|
786
809
|
catch {
|
|
787
810
|
this.logger.debug('AstAnalyzer not available, skipping AST rules');
|
|
811
|
+
for (const rule of astRules) {
|
|
812
|
+
this._uncertaintyCollector.recordSkip('ast', 'ast_unavailable', `AST module load failed`, {
|
|
813
|
+
ruleId: rule.id,
|
|
814
|
+
});
|
|
815
|
+
this._uncertaintyCollector.addUncertain(rule.id, rule.message, 'ast', 'ast_unavailable', 'AstAnalyzer module failed to load');
|
|
816
|
+
}
|
|
817
|
+
this._uncertaintyCollector.recordLayerStats('ast', astRules.length, 0);
|
|
788
818
|
return [];
|
|
789
819
|
}
|
|
790
820
|
const violations = [];
|
|
@@ -867,6 +897,371 @@ export class GuardCheckEngine {
|
|
|
867
897
|
this.logger.debug(`AST rule ${rule.id} check failed: ${err.message}`);
|
|
868
898
|
}
|
|
869
899
|
}
|
|
900
|
+
// AST 层统计
|
|
901
|
+
this._uncertaintyCollector.recordLayerStats('ast', astRules.length, astRules.length);
|
|
902
|
+
return violations;
|
|
903
|
+
}
|
|
904
|
+
/**
|
|
905
|
+
* AST Layer 2: analyzeFile() 深层检查
|
|
906
|
+
*
|
|
907
|
+
* 利用 AstAnalyzer.analyzeFile() 的完整输出产出 violations:
|
|
908
|
+
*
|
|
909
|
+
* --- 方法度量 ---
|
|
910
|
+
* - ast_class_bloat: 类方法数过多 (>20)
|
|
911
|
+
* - ast_method_complexity: 高圈复杂度 (>15)
|
|
912
|
+
* - ast_method_too_long: 方法行数过长 (>80)
|
|
913
|
+
* - ast_deep_nesting: 方法嵌套过深 (>5)
|
|
914
|
+
*
|
|
915
|
+
* --- 继承图检查 ---
|
|
916
|
+
* - ast_deep_inheritance: 继承链过深 (>4)
|
|
917
|
+
* - ast_wide_protocol_conformance: 单类遵守协议过多 (>5)
|
|
918
|
+
* - ast_missing_super: 子类未调用 super 的关键方法
|
|
919
|
+
*
|
|
920
|
+
* --- 属性规范 ---
|
|
921
|
+
* - ast_assign_object_property: ObjC assign 修饰对象类型属性
|
|
922
|
+
* - ast_missing_nonatomic: ObjC 属性缺少 nonatomic
|
|
923
|
+
* - ast_mutable_public_collection: 公开可变集合属性
|
|
924
|
+
*
|
|
925
|
+
* --- 设计模式/反模式检测 ---
|
|
926
|
+
* - ast_god_class: 方法+属性过多的上帝类 (>30 methods + >15 properties)
|
|
927
|
+
* - ast_singleton_abuse: 过多单例模式
|
|
928
|
+
* - ast_missing_weakify: block 内 self 捕获但未使用 weakify
|
|
929
|
+
*/
|
|
930
|
+
_runAstLayer2Checks(code, language, filePath) {
|
|
931
|
+
const disabled = this._guardConfig.disabledRules || [];
|
|
932
|
+
const allLayer2Rules = [
|
|
933
|
+
'ast_class_bloat',
|
|
934
|
+
'ast_method_complexity',
|
|
935
|
+
'ast_method_too_long',
|
|
936
|
+
'ast_deep_nesting',
|
|
937
|
+
'ast_deep_inheritance',
|
|
938
|
+
'ast_wide_protocol_conformance',
|
|
939
|
+
'ast_missing_super',
|
|
940
|
+
'ast_assign_object_property',
|
|
941
|
+
'ast_missing_nonatomic',
|
|
942
|
+
'ast_mutable_public_collection',
|
|
943
|
+
'ast_god_class',
|
|
944
|
+
'ast_singleton_abuse',
|
|
945
|
+
'ast_missing_weakify',
|
|
946
|
+
];
|
|
947
|
+
const allDisabled = allLayer2Rules.every((id) => disabled.includes(id));
|
|
948
|
+
if (allDisabled) {
|
|
949
|
+
return [];
|
|
950
|
+
}
|
|
951
|
+
// 语言标准化
|
|
952
|
+
const astLang = LanguageService.isKnownLang(language)
|
|
953
|
+
? language
|
|
954
|
+
: language === 'objc'
|
|
955
|
+
? 'objectivec'
|
|
956
|
+
: language;
|
|
957
|
+
if (!LanguageService.isKnownLang(astLang)) {
|
|
958
|
+
return [];
|
|
959
|
+
}
|
|
960
|
+
let AstAnalyzer;
|
|
961
|
+
try {
|
|
962
|
+
AstAnalyzer = this._getAstAnalyzer();
|
|
963
|
+
if (!AstAnalyzer || !AstAnalyzer.isAvailable()) {
|
|
964
|
+
this._uncertaintyCollector.recordSkip('ast', 'ast_unavailable', `AST Layer 2 skipped: tree-sitter not available for "${language}"`);
|
|
965
|
+
return [];
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
catch {
|
|
969
|
+
return [];
|
|
970
|
+
}
|
|
971
|
+
let fileSummary;
|
|
972
|
+
try {
|
|
973
|
+
fileSummary = AstAnalyzer.analyzeFile(code, astLang, { extractCallSites: false });
|
|
974
|
+
}
|
|
975
|
+
catch (err) {
|
|
976
|
+
this.logger.debug(`AST Layer 2 analyzeFile failed: ${err.message}`);
|
|
977
|
+
return [];
|
|
978
|
+
}
|
|
979
|
+
if (!fileSummary) {
|
|
980
|
+
return [];
|
|
981
|
+
}
|
|
982
|
+
const violations = [];
|
|
983
|
+
// — 阈值配置(可通过 codeLevelThresholds 覆盖) —
|
|
984
|
+
const thresholds = this._guardConfig.codeLevelThresholds || {};
|
|
985
|
+
const classBloatLimit = (typeof thresholds['ast_class_bloat'] === 'number' ? thresholds['ast_class_bloat'] : 20);
|
|
986
|
+
const complexityLimit = (typeof thresholds['ast_method_complexity'] === 'number'
|
|
987
|
+
? thresholds['ast_method_complexity']
|
|
988
|
+
: 15);
|
|
989
|
+
const methodLengthLimit = (typeof thresholds['ast_method_too_long'] === 'number' ? thresholds['ast_method_too_long'] : 80);
|
|
990
|
+
const nestingLimit = (typeof thresholds['ast_deep_nesting'] === 'number' ? thresholds['ast_deep_nesting'] : 5);
|
|
991
|
+
const inheritanceDepthLimit = (typeof thresholds['ast_deep_inheritance'] === 'number'
|
|
992
|
+
? thresholds['ast_deep_inheritance']
|
|
993
|
+
: 4);
|
|
994
|
+
const protocolConformanceLimit = (typeof thresholds['ast_wide_protocol_conformance'] === 'number'
|
|
995
|
+
? thresholds['ast_wide_protocol_conformance']
|
|
996
|
+
: 5);
|
|
997
|
+
const godClassMethodLimit = (typeof thresholds['ast_god_class_methods'] === 'number'
|
|
998
|
+
? thresholds['ast_god_class_methods']
|
|
999
|
+
: 30);
|
|
1000
|
+
const godClassPropertyLimit = (typeof thresholds['ast_god_class_properties'] === 'number'
|
|
1001
|
+
? thresholds['ast_god_class_properties']
|
|
1002
|
+
: 15);
|
|
1003
|
+
// ══════════════════════════════════════════════════════════
|
|
1004
|
+
// Section A: 方法度量(原有 4 条规则)
|
|
1005
|
+
// ══════════════════════════════════════════════════════════
|
|
1006
|
+
// 1. Class bloat — 类方法数过多
|
|
1007
|
+
if (!disabled.includes('ast_class_bloat')) {
|
|
1008
|
+
const methodCountByClass = {};
|
|
1009
|
+
for (const m of fileSummary.methods) {
|
|
1010
|
+
if (m.className && m.kind === 'definition') {
|
|
1011
|
+
if (!methodCountByClass[m.className]) {
|
|
1012
|
+
const cls = fileSummary.classes.find((c) => c.name === m.className);
|
|
1013
|
+
methodCountByClass[m.className] = { count: 0, line: cls?.line || 1 };
|
|
1014
|
+
}
|
|
1015
|
+
methodCountByClass[m.className].count++;
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
for (const [className, { count, line }] of Object.entries(methodCountByClass)) {
|
|
1019
|
+
if (count > classBloatLimit) {
|
|
1020
|
+
violations.push({
|
|
1021
|
+
ruleId: 'ast_class_bloat',
|
|
1022
|
+
message: `类 ${className} 有 ${count} 个方法,超过阈值 ${classBloatLimit},建议拆分职责`,
|
|
1023
|
+
severity: 'warning',
|
|
1024
|
+
line,
|
|
1025
|
+
snippet: `class ${className} — ${count} methods`,
|
|
1026
|
+
dimension: 'file',
|
|
1027
|
+
fixSuggestion: '将职责拆分到多个类或使用 Extension/Category 分组',
|
|
1028
|
+
});
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
// 2. Method complexity — 高圈复杂度
|
|
1033
|
+
if (!disabled.includes('ast_method_complexity')) {
|
|
1034
|
+
for (const m of fileSummary.methods) {
|
|
1035
|
+
if (m.complexity && m.complexity > complexityLimit) {
|
|
1036
|
+
violations.push({
|
|
1037
|
+
ruleId: 'ast_method_complexity',
|
|
1038
|
+
message: `方法 ${m.className ? `${m.className}.` : ''}${m.name} 圈复杂度 ${m.complexity},超过阈值 ${complexityLimit}`,
|
|
1039
|
+
severity: 'warning',
|
|
1040
|
+
line: m.line || 1,
|
|
1041
|
+
snippet: `${m.name} — complexity: ${m.complexity}`,
|
|
1042
|
+
dimension: 'file',
|
|
1043
|
+
fixSuggestion: '提取子方法、使用 early return 或策略模式降低复杂度',
|
|
1044
|
+
});
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
// 3. Method too long — 方法行数过长
|
|
1049
|
+
if (!disabled.includes('ast_method_too_long')) {
|
|
1050
|
+
for (const m of fileSummary.methods) {
|
|
1051
|
+
if (m.bodyLines && m.bodyLines > methodLengthLimit) {
|
|
1052
|
+
violations.push({
|
|
1053
|
+
ruleId: 'ast_method_too_long',
|
|
1054
|
+
message: `方法 ${m.className ? `${m.className}.` : ''}${m.name} 有 ${m.bodyLines} 行,超过阈值 ${methodLengthLimit}`,
|
|
1055
|
+
severity: 'warning',
|
|
1056
|
+
line: m.line || 1,
|
|
1057
|
+
snippet: `${m.name} — ${m.bodyLines} lines`,
|
|
1058
|
+
dimension: 'file',
|
|
1059
|
+
fixSuggestion: '将长方法拆分为多个更小的、职责单一的方法',
|
|
1060
|
+
});
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
// 4. Deep nesting — 方法嵌套过深
|
|
1065
|
+
if (!disabled.includes('ast_deep_nesting')) {
|
|
1066
|
+
for (const m of fileSummary.methods) {
|
|
1067
|
+
if (m.nestingDepth && m.nestingDepth > nestingLimit) {
|
|
1068
|
+
violations.push({
|
|
1069
|
+
ruleId: 'ast_deep_nesting',
|
|
1070
|
+
message: `方法 ${m.className ? `${m.className}.` : ''}${m.name} 嵌套深度 ${m.nestingDepth},超过阈值 ${nestingLimit}`,
|
|
1071
|
+
severity: 'warning',
|
|
1072
|
+
line: m.line || 1,
|
|
1073
|
+
snippet: `${m.name} — nesting depth: ${m.nestingDepth}`,
|
|
1074
|
+
dimension: 'file',
|
|
1075
|
+
fixSuggestion: '使用 guard/early return 减少嵌套,或提取内层逻辑为独立方法',
|
|
1076
|
+
});
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
// ══════════════════════════════════════════════════════════
|
|
1081
|
+
// Section B: 继承图检查(inheritanceGraph + classes.protocols)
|
|
1082
|
+
// ══════════════════════════════════════════════════════════
|
|
1083
|
+
// 5. Deep inheritance — 继承链过深
|
|
1084
|
+
if (!disabled.includes('ast_deep_inheritance') && fileSummary.inheritanceGraph?.length > 0) {
|
|
1085
|
+
// 构建父类映射: child → parent
|
|
1086
|
+
const parentMap = {};
|
|
1087
|
+
for (const edge of fileSummary.inheritanceGraph) {
|
|
1088
|
+
if (edge.type === 'extends' || edge.type === 'inherits') {
|
|
1089
|
+
parentMap[edge.from] = edge.to;
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
// 计算每个类的继承深度
|
|
1093
|
+
for (const cls of fileSummary.classes) {
|
|
1094
|
+
let depth = 0;
|
|
1095
|
+
let current = cls.name;
|
|
1096
|
+
const visited = new Set();
|
|
1097
|
+
while (parentMap[current] && !visited.has(current)) {
|
|
1098
|
+
visited.add(current);
|
|
1099
|
+
current = parentMap[current];
|
|
1100
|
+
depth++;
|
|
1101
|
+
}
|
|
1102
|
+
if (depth > inheritanceDepthLimit) {
|
|
1103
|
+
violations.push({
|
|
1104
|
+
ruleId: 'ast_deep_inheritance',
|
|
1105
|
+
message: `类 ${cls.name} 继承链深度 ${depth},超过阈值 ${inheritanceDepthLimit},过深继承增加理解和维护成本`,
|
|
1106
|
+
severity: 'warning',
|
|
1107
|
+
line: cls.line || 1,
|
|
1108
|
+
snippet: `class ${cls.name} — inheritance depth: ${depth}`,
|
|
1109
|
+
dimension: 'file',
|
|
1110
|
+
fixSuggestion: '优先使用组合(Composition)替代继承,或使用协议/接口解耦',
|
|
1111
|
+
});
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
// 6. Wide protocol conformance — 单类遵守协议过多
|
|
1116
|
+
if (!disabled.includes('ast_wide_protocol_conformance')) {
|
|
1117
|
+
for (const cls of fileSummary.classes) {
|
|
1118
|
+
const protocolCount = cls.protocols?.length || 0;
|
|
1119
|
+
if (protocolCount > protocolConformanceLimit) {
|
|
1120
|
+
violations.push({
|
|
1121
|
+
ruleId: 'ast_wide_protocol_conformance',
|
|
1122
|
+
message: `类 ${cls.name} 遵守 ${protocolCount} 个协议,超过阈值 ${protocolConformanceLimit},职责可能过重`,
|
|
1123
|
+
severity: 'warning',
|
|
1124
|
+
line: cls.line || 1,
|
|
1125
|
+
snippet: `class ${cls.name} — ${protocolCount} protocols: ${cls.protocols.slice(0, 5).join(', ')}${protocolCount > 5 ? '...' : ''}`,
|
|
1126
|
+
dimension: 'file',
|
|
1127
|
+
fixSuggestion: '将协议实现拆分到 Extension/Category 中,或拆分类职责',
|
|
1128
|
+
});
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
// ══════════════════════════════════════════════════════════
|
|
1133
|
+
// Section C: 属性规范(properties + attributes)
|
|
1134
|
+
// ══════════════════════════════════════════════════════════
|
|
1135
|
+
const isObjcLike = ['objc', 'objectivec', 'objective-c'].includes(language.toLowerCase());
|
|
1136
|
+
if (isObjcLike && fileSummary.properties?.length > 0) {
|
|
1137
|
+
for (const prop of fileSummary.properties) {
|
|
1138
|
+
const attrs = prop.attributes || [];
|
|
1139
|
+
const attrsLower = attrs.map((a) => a.toLowerCase());
|
|
1140
|
+
// 7. assign 修饰对象类型属性
|
|
1141
|
+
if (!disabled.includes('ast_assign_object_property')) {
|
|
1142
|
+
if (attrsLower.includes('assign') && !attrsLower.includes('readonly')) {
|
|
1143
|
+
// assign 用于对象类型(通过属性名启发:delegate, block, handler 等常为对象)
|
|
1144
|
+
const likelyObject = /delegate|block|handler|callback|completion|dataSource|view|controller|manager|service/i.test(prop.name);
|
|
1145
|
+
if (likelyObject) {
|
|
1146
|
+
violations.push({
|
|
1147
|
+
ruleId: 'ast_assign_object_property',
|
|
1148
|
+
message: `属性 ${prop.className ? `${prop.className}.` : ''}${prop.name} 使用 assign 修饰,疑似对象类型,应改为 weak`,
|
|
1149
|
+
severity: 'warning',
|
|
1150
|
+
line: prop.line || 1,
|
|
1151
|
+
snippet: `@property (assign) ... ${prop.name}`,
|
|
1152
|
+
dimension: 'file',
|
|
1153
|
+
fixSuggestion: '对象类型属性使用 weak(delegate)或 strong/copy,避免悬垂指针',
|
|
1154
|
+
});
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
// 8. 缺少 nonatomic
|
|
1159
|
+
if (!disabled.includes('ast_missing_nonatomic')) {
|
|
1160
|
+
if (!attrsLower.includes('nonatomic') &&
|
|
1161
|
+
!attrsLower.includes('atomic') &&
|
|
1162
|
+
attrs.length > 0) {
|
|
1163
|
+
violations.push({
|
|
1164
|
+
ruleId: 'ast_missing_nonatomic',
|
|
1165
|
+
message: `属性 ${prop.className ? `${prop.className}.` : ''}${prop.name} 缺少 nonatomic,iOS 中应默认使用 nonatomic 提升性能`,
|
|
1166
|
+
severity: 'info',
|
|
1167
|
+
line: prop.line || 1,
|
|
1168
|
+
snippet: `@property (${attrs.join(', ')}) ... ${prop.name}`,
|
|
1169
|
+
dimension: 'file',
|
|
1170
|
+
fixSuggestion: '添加 nonatomic 修饰符:@property (nonatomic, ...) ...',
|
|
1171
|
+
});
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
// 9. 公开可变集合属性
|
|
1175
|
+
if (!disabled.includes('ast_mutable_public_collection')) {
|
|
1176
|
+
const isMutable = /NSMutableArray|NSMutableDictionary|NSMutableSet|NSMutableString|NSMutableData|NSMutableOrderedSet/i.test(`${attrs.join(' ')} ${prop.name}`);
|
|
1177
|
+
if (isMutable && !attrsLower.includes('readonly')) {
|
|
1178
|
+
violations.push({
|
|
1179
|
+
ruleId: 'ast_mutable_public_collection',
|
|
1180
|
+
message: `属性 ${prop.className ? `${prop.className}.` : ''}${prop.name} 暴露可变集合,外部可直接修改内部状态`,
|
|
1181
|
+
severity: 'warning',
|
|
1182
|
+
line: prop.line || 1,
|
|
1183
|
+
snippet: `@property ... NSMutable* ${prop.name}`,
|
|
1184
|
+
dimension: 'file',
|
|
1185
|
+
fixSuggestion: '对外使用 readonly + 不可变类型(NSArray/NSDictionary),内部用 readwrite + 可变类型',
|
|
1186
|
+
});
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
// ══════════════════════════════════════════════════════════
|
|
1192
|
+
// Section D: 设计模式 / 反模式检测(patterns + aggregated metrics)
|
|
1193
|
+
// ══════════════════════════════════════════════════════════
|
|
1194
|
+
// 10. God class — 方法+属性过多的上帝类
|
|
1195
|
+
if (!disabled.includes('ast_god_class')) {
|
|
1196
|
+
// 按类聚合方法数和属性数
|
|
1197
|
+
const classStats = {};
|
|
1198
|
+
for (const m of fileSummary.methods) {
|
|
1199
|
+
if (m.className && m.kind === 'definition') {
|
|
1200
|
+
if (!classStats[m.className]) {
|
|
1201
|
+
const cls = fileSummary.classes.find((c) => c.name === m.className);
|
|
1202
|
+
classStats[m.className] = { methods: 0, properties: 0, line: cls?.line || 1 };
|
|
1203
|
+
}
|
|
1204
|
+
classStats[m.className].methods++;
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
for (const p of fileSummary.properties) {
|
|
1208
|
+
if (p.className) {
|
|
1209
|
+
if (!classStats[p.className]) {
|
|
1210
|
+
const cls = fileSummary.classes.find((c) => c.name === p.className);
|
|
1211
|
+
classStats[p.className] = { methods: 0, properties: 0, line: cls?.line || 1 };
|
|
1212
|
+
}
|
|
1213
|
+
classStats[p.className].properties++;
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
for (const [className, stats] of Object.entries(classStats)) {
|
|
1217
|
+
if (stats.methods > godClassMethodLimit && stats.properties > godClassPropertyLimit) {
|
|
1218
|
+
violations.push({
|
|
1219
|
+
ruleId: 'ast_god_class',
|
|
1220
|
+
message: `类 ${className} 有 ${stats.methods} 个方法和 ${stats.properties} 个属性,疑似上帝类(God Class),职责过重`,
|
|
1221
|
+
severity: 'warning',
|
|
1222
|
+
line: stats.line,
|
|
1223
|
+
snippet: `class ${className} — ${stats.methods} methods, ${stats.properties} properties`,
|
|
1224
|
+
dimension: 'file',
|
|
1225
|
+
fixSuggestion: '遵循单一职责原则(SRP),将类拆分为多个更小的、职责明确的类',
|
|
1226
|
+
});
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
// 11. Singleton abuse — 过多单例模式(文件级别)
|
|
1231
|
+
if (!disabled.includes('ast_singleton_abuse') && fileSummary.patterns?.length > 0) {
|
|
1232
|
+
const singletonPatterns = fileSummary.patterns.filter((p) => p.type === 'singleton');
|
|
1233
|
+
if (singletonPatterns.length > 2) {
|
|
1234
|
+
violations.push({
|
|
1235
|
+
ruleId: 'ast_singleton_abuse',
|
|
1236
|
+
message: `文件中检测到 ${singletonPatterns.length} 个单例模式,过多单例增加耦合和测试难度`,
|
|
1237
|
+
severity: 'info',
|
|
1238
|
+
line: singletonPatterns[0]?.line || 1,
|
|
1239
|
+
snippet: `${singletonPatterns.length} singletons: ${singletonPatterns
|
|
1240
|
+
.map((p) => p.className || p.methodName || 'unknown')
|
|
1241
|
+
.slice(0, 3)
|
|
1242
|
+
.join(', ')}`,
|
|
1243
|
+
dimension: 'file',
|
|
1244
|
+
fixSuggestion: '考虑使用依赖注入(DI)替代单例,提升可测试性和解耦',
|
|
1245
|
+
});
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
// 12. Missing weakify — block 内 self 捕获但未使用 weakify 模式
|
|
1249
|
+
if (!disabled.includes('ast_missing_weakify') &&
|
|
1250
|
+
isObjcLike &&
|
|
1251
|
+
fileSummary.patterns?.length > 0) {
|
|
1252
|
+
const selfCaptures = fileSummary.patterns.filter((p) => p.type === 'block_self_capture' && !p.isWeakRef);
|
|
1253
|
+
for (const cap of selfCaptures) {
|
|
1254
|
+
violations.push({
|
|
1255
|
+
ruleId: 'ast_missing_weakify',
|
|
1256
|
+
message: `${cap.className ? `${cap.className}.` : ''}${cap.methodName || 'block'} 中 block 捕获 self 但未使用 @weakify/@strongify`,
|
|
1257
|
+
severity: 'warning',
|
|
1258
|
+
line: cap.line || 1,
|
|
1259
|
+
snippet: `block captures self without weakify in ${cap.methodName || 'anonymous block'}`,
|
|
1260
|
+
dimension: 'file',
|
|
1261
|
+
fixSuggestion: '使用 @weakify(self) / @strongify(self) 或 __weak typeof(self) weakSelf = self',
|
|
1262
|
+
});
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
870
1265
|
return violations;
|
|
871
1266
|
}
|
|
872
1267
|
/** 获取 AstAnalyzer 模块(静态 import,带可用性检测) */
|
|
@@ -943,15 +1338,20 @@ export class GuardCheckEngine {
|
|
|
943
1338
|
*/
|
|
944
1339
|
auditFile(filePath, code, options = {}) {
|
|
945
1340
|
const language = detectLanguage(filePath);
|
|
1341
|
+
// 每次文件审计前重置 collector(单文件粒度)
|
|
1342
|
+
this._uncertaintyCollector.reset();
|
|
946
1343
|
const violations = this.checkCode(code, language, { ...options, filePath });
|
|
1344
|
+
const report = this._uncertaintyCollector.buildReport();
|
|
947
1345
|
return {
|
|
948
1346
|
filePath,
|
|
949
1347
|
language,
|
|
950
1348
|
violations,
|
|
1349
|
+
uncertainResults: report.uncertainResults,
|
|
951
1350
|
summary: {
|
|
952
1351
|
total: violations.length,
|
|
953
1352
|
errors: violations.filter((v) => v.severity === 'error').length,
|
|
954
1353
|
warnings: violations.filter((v) => v.severity === 'warning').length,
|
|
1354
|
+
uncertain: report.uncertainResults.length,
|
|
955
1355
|
},
|
|
956
1356
|
};
|
|
957
1357
|
}
|
|
@@ -977,16 +1377,67 @@ export class GuardCheckEngine {
|
|
|
977
1377
|
});
|
|
978
1378
|
totalViolations += crossFileViolations.length;
|
|
979
1379
|
totalErrors += crossFileViolations.filter((v) => v.severity === 'error').length;
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
totalErrors,
|
|
987
|
-
filesWithViolations: results.filter((r) => r.summary.total > 0).length,
|
|
988
|
-
},
|
|
1380
|
+
const summary = {
|
|
1381
|
+
filesChecked: results.length,
|
|
1382
|
+
totalViolations,
|
|
1383
|
+
totalErrors,
|
|
1384
|
+
totalUncertain: results.reduce((s, r) => s + r.summary.uncertain, 0),
|
|
1385
|
+
filesWithViolations: results.filter((r) => r.summary.total > 0).length,
|
|
989
1386
|
};
|
|
1387
|
+
// ── Signal emission (去重:相同检查结果不重复发射) ──
|
|
1388
|
+
if (this._signalBus && totalViolations > 0) {
|
|
1389
|
+
const signalKey = `${summary.filesChecked}:${summary.totalViolations}:${summary.totalErrors}:${summary.totalUncertain}:${summary.filesWithViolations}`;
|
|
1390
|
+
if (signalKey !== this._lastGuardSignalKey) {
|
|
1391
|
+
this._lastGuardSignalKey = signalKey;
|
|
1392
|
+
this._signalBus.send('guard', 'GuardCheckEngine', totalErrors > 0 ? 1 : 0.5, {
|
|
1393
|
+
metadata: { ...summary },
|
|
1394
|
+
});
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
// ── 聚合 capability report ──
|
|
1398
|
+
const aggregateCollector = new UncertaintyCollector();
|
|
1399
|
+
for (const r of results) {
|
|
1400
|
+
for (const u of r.uncertainResults) {
|
|
1401
|
+
aggregateCollector.addUncertain(u.ruleId, u.message, u.layer, u.reason, u.detail);
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
const capabilityReport = aggregateCollector.buildReport();
|
|
1405
|
+
// ── guard_blind_spot: uncertain 超阈值时发射 CapabilityRequest 信号(去重) ──
|
|
1406
|
+
if (this._signalBus && capabilityReport.uncertainResults.length > 0) {
|
|
1407
|
+
const uncertainTotal = capabilityReport.uncertainResults.length;
|
|
1408
|
+
const blindSpotThreshold = 5; // 触发阈值
|
|
1409
|
+
if (uncertainTotal >= blindSpotThreshold) {
|
|
1410
|
+
const blindSpotKey = `${uncertainTotal}:${capabilityReport.checkCoverage}`;
|
|
1411
|
+
if (blindSpotKey !== this._lastBlindSpotSignalKey) {
|
|
1412
|
+
this._lastBlindSpotSignalKey = blindSpotKey;
|
|
1413
|
+
// 按 layer 聚合盲区
|
|
1414
|
+
const byLayer = {};
|
|
1415
|
+
for (const u of capabilityReport.uncertainResults) {
|
|
1416
|
+
byLayer[u.layer] = (byLayer[u.layer] || 0) + 1;
|
|
1417
|
+
}
|
|
1418
|
+
this._signalBus.send('guard_blind_spot', 'GuardCheckEngine', uncertainTotal >= 20 ? 1 : 0.5, {
|
|
1419
|
+
metadata: {
|
|
1420
|
+
type: 'CapabilityRequest',
|
|
1421
|
+
uncertainTotal,
|
|
1422
|
+
checkCoverage: capabilityReport.checkCoverage,
|
|
1423
|
+
byLayer,
|
|
1424
|
+
boundaries: capabilityReport.boundaries.map((b) => ({
|
|
1425
|
+
type: b.type,
|
|
1426
|
+
description: b.description,
|
|
1427
|
+
affectedRules: b.affectedRules,
|
|
1428
|
+
suggestedAction: b.suggestedAction,
|
|
1429
|
+
})),
|
|
1430
|
+
suggestedAction: 'Extend Guard capability: add AST support for uncovered languages or implement missing cross-file checks',
|
|
1431
|
+
},
|
|
1432
|
+
});
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
return { files: results, crossFileViolations, summary, capabilityReport };
|
|
1437
|
+
}
|
|
1438
|
+
/** 获取 uncertainty collector(供外部读取单文件 uncertain 状态) */
|
|
1439
|
+
getUncertaintyCollector() {
|
|
1440
|
+
return this._uncertaintyCollector;
|
|
990
1441
|
}
|
|
991
1442
|
/** 清除规则缓存 */
|
|
992
1443
|
clearCache() {
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* 3. 集成到 guardAuditFiles MCP handler 和 GuardHandler (FileWatcher)
|
|
8
8
|
*/
|
|
9
9
|
import Logger from '../../infrastructure/logging/Logger.js';
|
|
10
|
+
import type { SignalBus } from '../../infrastructure/signal/SignalBus.js';
|
|
10
11
|
interface ViolationsStoreLike {
|
|
11
12
|
getRunsByFile(filePath: string): {
|
|
12
13
|
violations: {
|
|
@@ -40,9 +41,11 @@ export declare class GuardFeedbackLoop {
|
|
|
40
41
|
guardCheckEngine: GuardCheckEngineLike | null;
|
|
41
42
|
logger: ReturnType<typeof Logger.getInstance>;
|
|
42
43
|
violationsStore: ViolationsStoreLike | null;
|
|
44
|
+
_signalBus: SignalBus | null;
|
|
43
45
|
/** @param [options.guardCheckEngine] 用于查找规则 */
|
|
44
46
|
constructor(violationsStore: ViolationsStoreLike | null, feedbackCollector: FeedbackCollectorLike | null, options?: {
|
|
45
47
|
guardCheckEngine?: GuardCheckEngineLike;
|
|
48
|
+
signalBus?: SignalBus;
|
|
46
49
|
});
|
|
47
50
|
/**
|
|
48
51
|
* 对比当前和历史 violations,检测已修复的违规
|
|
@@ -12,11 +12,13 @@ export class GuardFeedbackLoop {
|
|
|
12
12
|
guardCheckEngine;
|
|
13
13
|
logger;
|
|
14
14
|
violationsStore;
|
|
15
|
+
_signalBus;
|
|
15
16
|
/** @param [options.guardCheckEngine] 用于查找规则 */
|
|
16
17
|
constructor(violationsStore, feedbackCollector, options = {}) {
|
|
17
18
|
this.violationsStore = violationsStore;
|
|
18
19
|
this.feedbackCollector = feedbackCollector;
|
|
19
20
|
this.guardCheckEngine = options.guardCheckEngine || null;
|
|
21
|
+
this._signalBus = options.signalBus || null;
|
|
20
22
|
this.logger = Logger.getInstance();
|
|
21
23
|
}
|
|
22
24
|
/**
|
|
@@ -72,6 +74,13 @@ export class GuardFeedbackLoop {
|
|
|
72
74
|
filePath,
|
|
73
75
|
});
|
|
74
76
|
this.logger.info(`[GuardFeedbackLoop] Auto-confirmed usage: recipe=${fixRecipeId} from fixing rule=${ruleId}`);
|
|
77
|
+
// ── Signal: usage confirmation ──
|
|
78
|
+
if (this._signalBus) {
|
|
79
|
+
this._signalBus.send('usage', 'GuardFeedbackLoop', 1, {
|
|
80
|
+
target: fixRecipeId,
|
|
81
|
+
metadata: { ruleId, filePath, source: 'guard_fix_detection' },
|
|
82
|
+
});
|
|
83
|
+
}
|
|
75
84
|
}
|
|
76
85
|
catch (err) {
|
|
77
86
|
this.logger.debug(`[GuardFeedbackLoop] autoConfirmUsage error: ${err.message}`);
|