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
|
@@ -82,9 +82,10 @@
|
|
|
82
82
|
import fs from 'node:fs';
|
|
83
83
|
import path from 'node:path';
|
|
84
84
|
import { fileURLToPath } from 'node:url';
|
|
85
|
+
import Logger from '../../infrastructure/logging/Logger.js';
|
|
86
|
+
import { LanguageService } from '../../shared/LanguageService.js';
|
|
85
87
|
import { findSimilarRecipes } from '../candidate/SimilarityService.js';
|
|
86
88
|
import { CandidateGuardrail } from './CandidateGuardrail.js';
|
|
87
|
-
import Logger from '../../infrastructure/logging/Logger.js';
|
|
88
89
|
|
|
89
90
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
90
91
|
|
|
@@ -103,23 +104,37 @@ const PROJECT_SKILLS_DIR = path.resolve(PROJECT_ROOT, '.autosnippet', 'skills');
|
|
|
103
104
|
// ────────────────────────────────────────────────────────────
|
|
104
105
|
|
|
105
106
|
/** 三方库路径识别(与 bootstrap/shared/third-party-filter.js 对齐) */
|
|
106
|
-
const THIRD_PARTY_RE =
|
|
107
|
+
const THIRD_PARTY_RE =
|
|
108
|
+
/(?:^|\/)(?:Pods|Carthage|\.build\/checkouts|vendor|ThirdParty|External|Submodules|DerivedData|include|node_modules|build)\/|(?:^|\/)(?:Masonry|AFNetworking|SDWebImage|MJRefresh|MJExtension|YYKit|YYModel|Lottie|FLEX|IQKeyboardManager|MBProgressHUD|SVProgressHUD|SnapKit|Kingfisher|Alamofire|Moya|ReactiveObjC|ReactiveCocoa|RxSwift|RxCocoa|FMDB|Realm|Mantle|JSONModel|CocoaLumberjack|CocoaAsyncSocket|SocketRocket|GPUImage|FBSDKCore|FBSDKLogin|FlatBuffers|Protobuf|PromiseKit|Charts|Hero)\//i;
|
|
107
109
|
|
|
108
110
|
/** 源码文件扩展名 */
|
|
109
111
|
const SOURCE_EXT_RE = /\.(m|mm|swift|h|c|cpp|js|ts|jsx|tsx|py|rb|java|kt|go|rs)$/i;
|
|
110
112
|
|
|
111
113
|
/** 声明行识别 — 用于对匹配行打分(与 bootstrap/shared/scanner.js 对齐) */
|
|
112
|
-
const DECL_RE =
|
|
114
|
+
const DECL_RE =
|
|
115
|
+
/^\s*(@property\b|@interface\b|@protocol\b|@class\b|@synthesize\b|@dynamic\b|@end\b|NS_ASSUME_NONNULL|#import\b|#include\b|#define\b)/;
|
|
113
116
|
const TYPE_DECL_RE = /^\s*\w[\w<>*\s]+[\s*]+_?\w+\s*;$/;
|
|
114
117
|
|
|
115
118
|
function _scoreSearchLine(line) {
|
|
116
119
|
const t = line.trim();
|
|
117
|
-
if (DECL_RE.test(t))
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
if (
|
|
121
|
-
|
|
122
|
-
|
|
120
|
+
if (DECL_RE.test(t)) {
|
|
121
|
+
return -2;
|
|
122
|
+
}
|
|
123
|
+
if (TYPE_DECL_RE.test(t)) {
|
|
124
|
+
return -1;
|
|
125
|
+
}
|
|
126
|
+
if (/^[-+]\s*\([^)]+\)\s*\w+[^{]*;\s*$/.test(t)) {
|
|
127
|
+
return -1;
|
|
128
|
+
}
|
|
129
|
+
if (/\[.*\w+.*\]/.test(t)) {
|
|
130
|
+
return 2; // ObjC message send
|
|
131
|
+
}
|
|
132
|
+
if (/\w+\s*\(/.test(t)) {
|
|
133
|
+
return 2; // function call
|
|
134
|
+
}
|
|
135
|
+
if (/\^\s*[{(]/.test(t)) {
|
|
136
|
+
return 1; // block literal
|
|
137
|
+
}
|
|
123
138
|
return 0;
|
|
124
139
|
}
|
|
125
140
|
|
|
@@ -133,7 +148,7 @@ async function _getProjectFiles(params, ctx) {
|
|
|
133
148
|
|
|
134
149
|
let extFilter = null;
|
|
135
150
|
if (fileFilter) {
|
|
136
|
-
const exts = fileFilter.split(',').map(e => e.trim().replace(/^\./, ''));
|
|
151
|
+
const exts = fileFilter.split(',').map((e) => e.trim().replace(/^\./, ''));
|
|
137
152
|
extFilter = new RegExp(`\\.(${exts.join('|')})$`, 'i');
|
|
138
153
|
}
|
|
139
154
|
|
|
@@ -142,11 +157,18 @@ async function _getProjectFiles(params, ctx) {
|
|
|
142
157
|
let skippedThirdParty = 0;
|
|
143
158
|
|
|
144
159
|
if (fileCache && Array.isArray(fileCache)) {
|
|
145
|
-
files = fileCache.filter(f => {
|
|
160
|
+
files = fileCache.filter((f) => {
|
|
146
161
|
const p = f.relativePath || f.path || '';
|
|
147
|
-
if (THIRD_PARTY_RE.test(p)) {
|
|
148
|
-
|
|
149
|
-
|
|
162
|
+
if (THIRD_PARTY_RE.test(p)) {
|
|
163
|
+
skippedThirdParty++;
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
if (extFilter && !extFilter.test(p)) {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
if (!SOURCE_EXT_RE.test(p)) {
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
150
172
|
return true;
|
|
151
173
|
});
|
|
152
174
|
} else {
|
|
@@ -158,25 +180,65 @@ async function _getProjectFiles(params, ctx) {
|
|
|
158
180
|
for (const entry of entries) {
|
|
159
181
|
const relPath = relBase ? `${relBase}/${entry.name}` : entry.name;
|
|
160
182
|
const fullPath = path.join(dir, entry.name);
|
|
161
|
-
const isDir =
|
|
162
|
-
|
|
183
|
+
const isDir =
|
|
184
|
+
entry.isDirectory() ||
|
|
185
|
+
(entry.isSymbolicLink() &&
|
|
186
|
+
(() => {
|
|
187
|
+
try {
|
|
188
|
+
return fs.statSync(fullPath).isDirectory();
|
|
189
|
+
} catch {
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
})());
|
|
193
|
+
const isFile =
|
|
194
|
+
entry.isFile() ||
|
|
195
|
+
(entry.isSymbolicLink() &&
|
|
196
|
+
(() => {
|
|
197
|
+
try {
|
|
198
|
+
return fs.statSync(fullPath).isFile();
|
|
199
|
+
} catch {
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
})());
|
|
163
203
|
if (isDir) {
|
|
164
|
-
if (
|
|
165
|
-
|
|
204
|
+
if (
|
|
205
|
+
entry.name.startsWith('.') ||
|
|
206
|
+
entry.name === 'node_modules' ||
|
|
207
|
+
entry.name === 'build'
|
|
208
|
+
) {
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
if (THIRD_PARTY_RE.test(`${relPath}/`)) {
|
|
212
|
+
skippedThirdParty++;
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
166
215
|
walk(fullPath, relPath);
|
|
167
216
|
} else if (isFile) {
|
|
168
|
-
if (THIRD_PARTY_RE.test(relPath)) {
|
|
169
|
-
|
|
170
|
-
|
|
217
|
+
if (THIRD_PARTY_RE.test(relPath)) {
|
|
218
|
+
skippedThirdParty++;
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
if (!SOURCE_EXT_RE.test(entry.name)) {
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
if (extFilter && !extFilter.test(entry.name)) {
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
171
227
|
try {
|
|
172
228
|
const stat = fs.statSync(fullPath);
|
|
173
|
-
if (stat.size > MAX_FILE_SIZE)
|
|
229
|
+
if (stat.size > MAX_FILE_SIZE) {
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
174
232
|
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
175
233
|
files.push({ relativePath: relPath, content, name: entry.name });
|
|
176
|
-
} catch {
|
|
234
|
+
} catch {
|
|
235
|
+
/* skip unreadable files */
|
|
236
|
+
}
|
|
177
237
|
}
|
|
178
238
|
}
|
|
179
|
-
} catch {
|
|
239
|
+
} catch {
|
|
240
|
+
/* skip inaccessible dirs */
|
|
241
|
+
}
|
|
180
242
|
};
|
|
181
243
|
walk(projectRoot);
|
|
182
244
|
}
|
|
@@ -186,26 +248,34 @@ async function _getProjectFiles(params, ctx) {
|
|
|
186
248
|
|
|
187
249
|
const searchProjectCode = {
|
|
188
250
|
name: 'search_project_code',
|
|
189
|
-
description:
|
|
251
|
+
description:
|
|
252
|
+
'在用户项目源码中搜索指定模式。返回匹配的代码片段及上下文。' +
|
|
190
253
|
'自动过滤三方库代码(Pods/Carthage/node_modules),优先返回实际使用行而非声明行。' +
|
|
191
254
|
'适用场景:验证代码模式存在性、查找更多项目示例、理解项目中某个 API 的用法。' +
|
|
192
255
|
'批量搜索:传入 patterns 数组可一次搜索多个关键词(每个关键词独立返回结果),减少工具调用次数。',
|
|
193
256
|
parameters: {
|
|
194
257
|
type: 'object',
|
|
195
258
|
properties: {
|
|
196
|
-
pattern:
|
|
197
|
-
patterns:
|
|
198
|
-
|
|
199
|
-
|
|
259
|
+
pattern: { type: 'string', description: '搜索词或正则表达式(单个搜索时使用)' },
|
|
260
|
+
patterns: {
|
|
261
|
+
type: 'array',
|
|
262
|
+
items: { type: 'string' },
|
|
263
|
+
description:
|
|
264
|
+
'批量搜索:多个搜索词数组,如 ["methodA", "methodB", "classC"]。与 pattern 互斥,优先使用 patterns。',
|
|
265
|
+
},
|
|
266
|
+
isRegex: { type: 'boolean', description: '是否为正则表达式,默认 false' },
|
|
267
|
+
fileFilter: { type: 'string', description: '文件扩展名过滤,如 ".m,.swift"' },
|
|
200
268
|
contextLines: { type: 'number', description: '匹配行前后的上下文行数,默认 3' },
|
|
201
|
-
maxResults:
|
|
269
|
+
maxResults: { type: 'number', description: '每个 pattern 的最大返回结果数,默认 5' },
|
|
202
270
|
},
|
|
203
271
|
required: [],
|
|
204
272
|
},
|
|
205
273
|
handler: async (params, ctx) => {
|
|
206
274
|
// ── 去重缓存初始化 ──
|
|
207
275
|
const state = ctx._sharedState || ctx;
|
|
208
|
-
if (!state._searchCache)
|
|
276
|
+
if (!state._searchCache) {
|
|
277
|
+
state._searchCache = new Map();
|
|
278
|
+
}
|
|
209
279
|
|
|
210
280
|
// ── 批量模式:patterns 数组 ──
|
|
211
281
|
if (Array.isArray(params.patterns) && params.patterns.length > 0) {
|
|
@@ -222,7 +292,7 @@ const searchProjectCode = {
|
|
|
222
292
|
}
|
|
223
293
|
const sub = await searchProjectCode.handler(
|
|
224
294
|
{ ...params, pattern: p, patterns: undefined },
|
|
225
|
-
ctx
|
|
295
|
+
ctx
|
|
226
296
|
);
|
|
227
297
|
const entry = { matches: sub.matches || [], total: sub.total || 0 };
|
|
228
298
|
state._searchCache.set(cacheKey, entry);
|
|
@@ -232,30 +302,46 @@ const searchProjectCode = {
|
|
|
232
302
|
batchResults,
|
|
233
303
|
patternsSearched: batchPatterns.length,
|
|
234
304
|
searchedFiles: (await _getProjectFiles(params, ctx)).files.length,
|
|
235
|
-
...(dedupCount > 0
|
|
305
|
+
...(dedupCount > 0
|
|
306
|
+
? {
|
|
307
|
+
_deduped: dedupCount,
|
|
308
|
+
hint: `${dedupCount} 个 pattern 命中缓存,请避免重复搜索相同关键词。`,
|
|
309
|
+
}
|
|
310
|
+
: {}),
|
|
236
311
|
};
|
|
237
312
|
}
|
|
238
313
|
|
|
239
314
|
// 兼容 AI 传 "query" / "search" / "keyword" 替代 "pattern"
|
|
240
|
-
const pattern =
|
|
241
|
-
|
|
242
|
-
const
|
|
315
|
+
const pattern =
|
|
316
|
+
params.pattern || params.query || params.search || params.keyword || params.search_query;
|
|
317
|
+
const { isRegex = false, contextLines = 3, maxResults = 5 } = params;
|
|
318
|
+
const _projectRoot = ctx.projectRoot || process.cwd();
|
|
243
319
|
|
|
244
320
|
if (!pattern || typeof pattern !== 'string') {
|
|
245
|
-
return {
|
|
321
|
+
return {
|
|
322
|
+
error: '参数错误: 请提供 pattern(搜索关键词或正则表达式)或 patterns 数组',
|
|
323
|
+
matches: [],
|
|
324
|
+
total: 0,
|
|
325
|
+
};
|
|
246
326
|
}
|
|
247
327
|
|
|
248
328
|
// ── 单 pattern 去重检查 ──
|
|
249
329
|
const cacheKey = `${pattern}|${params.isRegex || false}|${params.fileFilter || ''}`;
|
|
250
330
|
if (state._searchCache.has(cacheKey)) {
|
|
251
331
|
const cached = state._searchCache.get(cacheKey);
|
|
252
|
-
return {
|
|
332
|
+
return {
|
|
333
|
+
...cached,
|
|
334
|
+
_cached: true,
|
|
335
|
+
hint: `⚠ 已搜索过 "${pattern}",返回缓存结果。请搜索不同的关键词以获取新信息。`,
|
|
336
|
+
};
|
|
253
337
|
}
|
|
254
338
|
|
|
255
339
|
// 构建搜索正则
|
|
256
340
|
let searchRe;
|
|
257
341
|
try {
|
|
258
|
-
searchRe = isRegex
|
|
342
|
+
searchRe = isRegex
|
|
343
|
+
? new RegExp(pattern, 'gi')
|
|
344
|
+
: new RegExp(pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'gi');
|
|
259
345
|
} catch (err) {
|
|
260
346
|
return { error: `Invalid pattern: ${err.message}`, matches: [], total: 0 };
|
|
261
347
|
}
|
|
@@ -267,17 +353,23 @@ const searchProjectCode = {
|
|
|
267
353
|
let total = 0;
|
|
268
354
|
|
|
269
355
|
for (const f of files) {
|
|
270
|
-
if (!f.content)
|
|
356
|
+
if (!f.content) {
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
271
359
|
// 快速预过滤
|
|
272
360
|
searchRe.lastIndex = 0;
|
|
273
|
-
if (!searchRe.test(f.content))
|
|
361
|
+
if (!searchRe.test(f.content)) {
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
274
364
|
|
|
275
365
|
const lines = f.content.split('\n');
|
|
276
366
|
searchRe.lastIndex = 0;
|
|
277
367
|
|
|
278
368
|
for (let i = 0; i < lines.length; i++) {
|
|
279
369
|
searchRe.lastIndex = 0;
|
|
280
|
-
if (!searchRe.test(lines[i]))
|
|
370
|
+
if (!searchRe.test(lines[i])) {
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
281
373
|
total++;
|
|
282
374
|
|
|
283
375
|
if (matches.length < maxResults) {
|
|
@@ -307,14 +399,16 @@ const searchProjectCode = {
|
|
|
307
399
|
total,
|
|
308
400
|
searchedFiles: files.length,
|
|
309
401
|
skippedThirdParty,
|
|
310
|
-
...((
|
|
402
|
+
...(() => {
|
|
311
403
|
// P2.2: 搜索超限提示 — 引导使用 AST 工具
|
|
312
404
|
state._searchCallCount = (state._searchCallCount || 0) + 1;
|
|
313
405
|
if (state._searchCallCount > 12 && ctx.source === 'system') {
|
|
314
|
-
return {
|
|
406
|
+
return {
|
|
407
|
+
hint: `💡 你已搜索 ${state._searchCallCount} 次。考虑使用 get_class_info / get_class_hierarchy / get_project_overview 获取结构化信息,效率更高。`,
|
|
408
|
+
};
|
|
315
409
|
}
|
|
316
410
|
return {};
|
|
317
|
-
})()
|
|
411
|
+
})(),
|
|
318
412
|
};
|
|
319
413
|
|
|
320
414
|
// 缓存搜索结果
|
|
@@ -329,24 +423,34 @@ const searchProjectCode = {
|
|
|
329
423
|
// ────────────────────────────────────────────────────────────
|
|
330
424
|
const readProjectFile = {
|
|
331
425
|
name: 'read_project_file',
|
|
332
|
-
description:
|
|
426
|
+
description:
|
|
427
|
+
'读取项目中指定文件的内容(部分或全部)。' +
|
|
333
428
|
'通常在 search_project_code 找到匹配后使用,获取更完整的上下文。' +
|
|
334
429
|
'批量读取:传入 filePaths 数组可一次读取多个文件,减少工具调用次数。',
|
|
335
430
|
parameters: {
|
|
336
431
|
type: 'object',
|
|
337
432
|
properties: {
|
|
338
|
-
filePath:
|
|
339
|
-
filePaths:
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
433
|
+
filePath: { type: 'string', description: '相对于项目根目录的文件路径(单个文件时使用)' },
|
|
434
|
+
filePaths: {
|
|
435
|
+
type: 'array',
|
|
436
|
+
items: { type: 'string' },
|
|
437
|
+
description: '批量读取:多个文件路径数组。与 filePath 互斥,优先使用 filePaths。',
|
|
438
|
+
},
|
|
439
|
+
startLine: { type: 'number', description: '起始行号(1-based),默认 1' },
|
|
440
|
+
endLine: { type: 'number', description: '结束行号(1-based),默认文件末尾' },
|
|
441
|
+
maxLines: {
|
|
442
|
+
type: 'number',
|
|
443
|
+
description: '最大返回行数,默认 200(批量模式下每个文件最多 100 行)',
|
|
444
|
+
},
|
|
343
445
|
},
|
|
344
446
|
required: [],
|
|
345
447
|
},
|
|
346
448
|
handler: async (params, ctx) => {
|
|
347
449
|
// ── 去重缓存初始化 ──
|
|
348
450
|
const state = ctx._sharedState || ctx;
|
|
349
|
-
if (!state._readCache)
|
|
451
|
+
if (!state._readCache) {
|
|
452
|
+
state._readCache = new Map();
|
|
453
|
+
}
|
|
350
454
|
|
|
351
455
|
// ── 批量模式:filePaths 数组 ──
|
|
352
456
|
if (Array.isArray(params.filePaths) && params.filePaths.length > 0) {
|
|
@@ -361,23 +465,38 @@ const readProjectFile = {
|
|
|
361
465
|
continue;
|
|
362
466
|
}
|
|
363
467
|
const sub = await readProjectFile.handler(
|
|
364
|
-
{
|
|
365
|
-
|
|
468
|
+
{
|
|
469
|
+
...params,
|
|
470
|
+
filePath: fp,
|
|
471
|
+
filePaths: undefined,
|
|
472
|
+
maxLines: Math.min(params.maxLines || 100, 100),
|
|
473
|
+
},
|
|
474
|
+
ctx
|
|
366
475
|
);
|
|
367
|
-
const entry = sub.error
|
|
476
|
+
const entry = sub.error
|
|
477
|
+
? { error: sub.error }
|
|
478
|
+
: { content: sub.content, totalLines: sub.totalLines, language: sub.language };
|
|
368
479
|
state._readCache.set(cacheKey, entry);
|
|
369
480
|
batchResults[fp] = entry;
|
|
370
481
|
}
|
|
371
482
|
return {
|
|
372
483
|
batchResults,
|
|
373
484
|
filesRead: batchPaths.length,
|
|
374
|
-
...(dedupCount > 0
|
|
485
|
+
...(dedupCount > 0
|
|
486
|
+
? { _deduped: dedupCount, hint: `${dedupCount} 个文件命中缓存,请避免重复读取相同文件。` }
|
|
487
|
+
: {}),
|
|
375
488
|
};
|
|
376
489
|
}
|
|
377
490
|
|
|
378
491
|
// 兼容各种参数名变体 (ToolRegistry 层已做 snake→camel 归一化,
|
|
379
492
|
// 这里兜底处理漏网之鱼)
|
|
380
|
-
const filePath =
|
|
493
|
+
const filePath =
|
|
494
|
+
params.filePath ||
|
|
495
|
+
params.path ||
|
|
496
|
+
params.file_path ||
|
|
497
|
+
params.filepath ||
|
|
498
|
+
params.file ||
|
|
499
|
+
params.filename;
|
|
381
500
|
const { startLine = 1, maxLines = 200 } = params;
|
|
382
501
|
const projectRoot = ctx.projectRoot || process.cwd();
|
|
383
502
|
|
|
@@ -388,7 +507,11 @@ const readProjectFile = {
|
|
|
388
507
|
// ── 单文件去重检查 ──
|
|
389
508
|
const readCacheKey = `${filePath}|${startLine}|${params.endLine || ''}|${maxLines}`;
|
|
390
509
|
if (state._readCache.has(readCacheKey)) {
|
|
391
|
-
return {
|
|
510
|
+
return {
|
|
511
|
+
...state._readCache.get(readCacheKey),
|
|
512
|
+
_cached: true,
|
|
513
|
+
hint: `⚠ 已读取过该文件相同行范围,返回缓存结果。如需其他行范围请指定不同的 startLine/endLine。`,
|
|
514
|
+
};
|
|
392
515
|
}
|
|
393
516
|
|
|
394
517
|
// 安全检查: 禁止路径遍历
|
|
@@ -402,11 +525,14 @@ const readProjectFile = {
|
|
|
402
525
|
let content = null;
|
|
403
526
|
|
|
404
527
|
if (fileCache && Array.isArray(fileCache)) {
|
|
405
|
-
const cached = fileCache.find(
|
|
406
|
-
(f
|
|
407
|
-
|
|
528
|
+
const cached = fileCache.find(
|
|
529
|
+
(f) =>
|
|
530
|
+
(f.relativePath || f.path || '') === filePath ||
|
|
531
|
+
(f.relativePath || f.path || '') === normalized
|
|
408
532
|
);
|
|
409
|
-
if (cached)
|
|
533
|
+
if (cached) {
|
|
534
|
+
content = cached.content;
|
|
535
|
+
}
|
|
410
536
|
}
|
|
411
537
|
|
|
412
538
|
// 降级: 从磁盘读取
|
|
@@ -438,8 +564,7 @@ const readProjectFile = {
|
|
|
438
564
|
|
|
439
565
|
// 推断语言
|
|
440
566
|
const ext = path.extname(filePath).toLowerCase();
|
|
441
|
-
const
|
|
442
|
-
const language = langMap[ext] || 'unknown';
|
|
567
|
+
const language = LanguageService.langFromExt(ext);
|
|
443
568
|
|
|
444
569
|
const readResult = {
|
|
445
570
|
filePath,
|
|
@@ -462,14 +587,18 @@ const readProjectFile = {
|
|
|
462
587
|
// ────────────────────────────────────────────────────────────
|
|
463
588
|
const listProjectStructure = {
|
|
464
589
|
name: 'list_project_structure',
|
|
465
|
-
description:
|
|
590
|
+
description:
|
|
591
|
+
'列出项目目录结构和文件统计信息。不读取文件内容,只返回目录树和元数据。' +
|
|
466
592
|
'适用场景:了解项目整体布局、识别关键目录、规划探索路径。',
|
|
467
593
|
parameters: {
|
|
468
594
|
type: 'object',
|
|
469
595
|
properties: {
|
|
470
|
-
directory:
|
|
471
|
-
depth:
|
|
472
|
-
includeStats: {
|
|
596
|
+
directory: { type: 'string', description: '相对于项目根目录的子目录路径,默认根目录' },
|
|
597
|
+
depth: { type: 'number', description: '目录展开深度,默认 3' },
|
|
598
|
+
includeStats: {
|
|
599
|
+
type: 'boolean',
|
|
600
|
+
description: '是否包含文件统计(语言分布、行数),默认 true',
|
|
601
|
+
},
|
|
473
602
|
},
|
|
474
603
|
},
|
|
475
604
|
handler: async (params, ctx) => {
|
|
@@ -491,33 +620,36 @@ const listProjectStructure = {
|
|
|
491
620
|
const treeLines = [];
|
|
492
621
|
const stats = { totalFiles: 0, totalDirs: 0, byLanguage: {}, totalLines: 0 };
|
|
493
622
|
|
|
494
|
-
const LANG_MAP = {
|
|
495
|
-
'.m': 'Objective-C', '.mm': 'Objective-C++', '.h': 'Header',
|
|
496
|
-
'.swift': 'Swift', '.js': 'JavaScript', '.ts': 'TypeScript',
|
|
497
|
-
'.jsx': 'JSX', '.tsx': 'TSX', '.py': 'Python', '.java': 'Java',
|
|
498
|
-
'.kt': 'Kotlin', '.go': 'Go', '.rs': 'Rust', '.rb': 'Ruby',
|
|
499
|
-
'.c': 'C', '.cpp': 'C++',
|
|
500
|
-
};
|
|
501
|
-
|
|
502
623
|
const walk = (dir, relBase, currentDepth, prefix) => {
|
|
503
|
-
if (currentDepth > depth)
|
|
624
|
+
if (currentDepth > depth) {
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
504
627
|
let entries;
|
|
505
|
-
try {
|
|
506
|
-
|
|
628
|
+
try {
|
|
629
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
630
|
+
} catch {
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
507
633
|
|
|
508
634
|
// 排序: 目录在前,文件在后
|
|
509
635
|
entries.sort((a, b) => {
|
|
510
636
|
const aIsDir = a.isDirectory();
|
|
511
637
|
const bIsDir = b.isDirectory();
|
|
512
|
-
if (aIsDir !== bIsDir)
|
|
638
|
+
if (aIsDir !== bIsDir) {
|
|
639
|
+
return aIsDir ? -1 : 1;
|
|
640
|
+
}
|
|
513
641
|
return a.name.localeCompare(b.name);
|
|
514
642
|
});
|
|
515
643
|
|
|
516
644
|
// 过滤隐藏和三方
|
|
517
|
-
entries = entries.filter(e => {
|
|
518
|
-
if (e.name.startsWith('.'))
|
|
645
|
+
entries = entries.filter((e) => {
|
|
646
|
+
if (e.name.startsWith('.')) {
|
|
647
|
+
return false;
|
|
648
|
+
}
|
|
519
649
|
const rel = relBase ? `${relBase}/${e.name}` : e.name;
|
|
520
|
-
if (THIRD_PARTY_RE.test(rel
|
|
650
|
+
if (THIRD_PARTY_RE.test(`${rel}/`)) {
|
|
651
|
+
return false;
|
|
652
|
+
}
|
|
521
653
|
return true;
|
|
522
654
|
});
|
|
523
655
|
|
|
@@ -532,7 +664,11 @@ const listProjectStructure = {
|
|
|
532
664
|
if (entry.isDirectory()) {
|
|
533
665
|
// 计算子文件数
|
|
534
666
|
let childCount = 0;
|
|
535
|
-
try {
|
|
667
|
+
try {
|
|
668
|
+
childCount = fs.readdirSync(fullPath).length;
|
|
669
|
+
} catch {
|
|
670
|
+
/* skip */
|
|
671
|
+
}
|
|
536
672
|
treeLines.push(`${prefix}${connector}${entry.name}/ (${childCount})`);
|
|
537
673
|
stats.totalDirs++;
|
|
538
674
|
walk(fullPath, rel, currentDepth + 1, childPrefix);
|
|
@@ -549,10 +685,12 @@ const listProjectStructure = {
|
|
|
549
685
|
lineCount = content.split('\n').length;
|
|
550
686
|
stats.totalLines += lineCount;
|
|
551
687
|
}
|
|
552
|
-
} catch {
|
|
688
|
+
} catch {
|
|
689
|
+
/* skip */
|
|
690
|
+
}
|
|
553
691
|
}
|
|
554
|
-
const lang =
|
|
555
|
-
if (lang) {
|
|
692
|
+
const lang = LanguageService.displayNameFromExt(ext);
|
|
693
|
+
if (lang !== ext) {
|
|
556
694
|
stats.byLanguage[lang] = (stats.byLanguage[lang] || 0) + 1;
|
|
557
695
|
}
|
|
558
696
|
const sizeLabel = size > 1024 ? `${(size / 1024).toFixed(0)}KB` : `${size}B`;
|
|
@@ -580,31 +718,67 @@ const listProjectStructure = {
|
|
|
580
718
|
/** 语言相关的声明提取正则 */
|
|
581
719
|
const SUMMARY_EXTRACTORS = {
|
|
582
720
|
objectivec: {
|
|
583
|
-
imports:
|
|
584
|
-
declarations:
|
|
585
|
-
|
|
586
|
-
|
|
721
|
+
imports: /^\s*(#import\s+.+|#include\s+.+|@import\s+\w+;)/gm,
|
|
722
|
+
declarations:
|
|
723
|
+
/^\s*(@interface\s+\w+[\s:(].*|@protocol\s+\w+[\s<(].*|@implementation\s+\w+|typedef\s+(?:NS_ENUM|NS_OPTIONS)\s*\([^)]+\)\s*\{?)/gm,
|
|
724
|
+
methods: /^\s*[-+]\s*\([^)]+\)\s*[^;{]+/gm,
|
|
725
|
+
properties: /^\s*@property\s*\([^)]*\)\s*[^;]+;/gm,
|
|
587
726
|
},
|
|
588
727
|
swift: {
|
|
589
|
-
imports:
|
|
590
|
-
declarations:
|
|
591
|
-
|
|
592
|
-
|
|
728
|
+
imports: /^\s*import\s+\w+/gm,
|
|
729
|
+
declarations:
|
|
730
|
+
/^\s*(?:open|public|internal|fileprivate|private|final)?\s*(?:class|struct|enum|protocol|actor|extension)\s+\w+[^{]*/gm,
|
|
731
|
+
methods:
|
|
732
|
+
/^\s*(?:open|public|internal|fileprivate|private|override|static|class)?\s*func\s+\w+[^{]*/gm,
|
|
733
|
+
properties:
|
|
734
|
+
/^\s*(?:open|public|internal|fileprivate|private|static|class|lazy)?\s*(?:var|let)\s+\w+\s*:\s*[^={\n]+/gm,
|
|
593
735
|
},
|
|
594
736
|
javascript: {
|
|
595
|
-
imports:
|
|
737
|
+
imports: /^\s*(?:import\s+.+from\s+['"].+['"]|const\s+\{?\s*\w+.*\}?\s*=\s*require\s*\(.+\))/gm,
|
|
596
738
|
declarations: /^\s*(?:export\s+)?(?:default\s+)?(?:class|function|const|let|var)\s+\w+/gm,
|
|
597
|
-
methods:
|
|
739
|
+
methods: /^\s*(?:async\s+)?(?:static\s+)?(?:get\s+|set\s+)?(?:#?\w+)\s*\([^)]*\)\s*\{/gm,
|
|
598
740
|
},
|
|
599
741
|
typescript: {
|
|
600
|
-
imports:
|
|
601
|
-
declarations:
|
|
602
|
-
|
|
742
|
+
imports: /^\s*import\s+.+from\s+['"].+['"]/gm,
|
|
743
|
+
declarations:
|
|
744
|
+
/^\s*(?:export\s+)?(?:default\s+)?(?:class|interface|type|enum|function|const|let|var|abstract\s+class)\s+\w+/gm,
|
|
745
|
+
methods:
|
|
746
|
+
/^\s*(?:async\s+)?(?:static\s+)?(?:public|private|protected)?\s*(?:get\s+|set\s+)?(?:#?\w+)\s*\([^)]*\)\s*[:{]/gm,
|
|
603
747
|
},
|
|
604
748
|
python: {
|
|
605
|
-
imports:
|
|
749
|
+
imports: /^\s*(?:import\s+\w+|from\s+\w+\s+import\s+.+)/gm,
|
|
606
750
|
declarations: /^\s*class\s+\w+[^:]*:/gm,
|
|
607
|
-
methods:
|
|
751
|
+
methods: /^\s*(?:async\s+)?def\s+\w+\s*\([^)]*\)/gm,
|
|
752
|
+
},
|
|
753
|
+
go: {
|
|
754
|
+
imports: /^\s*(?:import\s+"[^"]+"|import\s+\w+\s+"[^"]+")/gm,
|
|
755
|
+
declarations:
|
|
756
|
+
/^\s*(?:type\s+\w+\s+(?:struct|interface|func)\b.*)/gm,
|
|
757
|
+
methods:
|
|
758
|
+
/^\s*func\s+(?:\(\s*\w+\s+\*?\w+\s*\)\s+)?\w+\s*\([^)]*\)[^{]*/gm,
|
|
759
|
+
},
|
|
760
|
+
java: {
|
|
761
|
+
imports: /^\s*import\s+(?:static\s+)?[\w.]+\*?;/gm,
|
|
762
|
+
declarations:
|
|
763
|
+
/^\s*(?:public|private|protected)?\s*(?:abstract|final|static)?\s*(?:class|interface|enum|record|@interface)\s+\w+/gm,
|
|
764
|
+
methods:
|
|
765
|
+
/^\s*(?:public|private|protected)?\s*(?:abstract|static|final|synchronized|default)?\s*(?:<[^>]+>\s+)?\w[\w<>\[\],\s]*\s+\w+\s*\([^)]*\)/gm,
|
|
766
|
+
},
|
|
767
|
+
kotlin: {
|
|
768
|
+
imports: /^\s*import\s+[\w.]+/gm,
|
|
769
|
+
declarations:
|
|
770
|
+
/^\s*(?:open|abstract|data|sealed|inner|value|inline)?\s*(?:class|interface|object|enum\s+class|fun\s+interface)\s+\w+/gm,
|
|
771
|
+
methods:
|
|
772
|
+
/^\s*(?:override\s+)?(?:suspend\s+)?(?:fun|val|var)\s+(?:<[^>]+>\s+)?\w+/gm,
|
|
773
|
+
},
|
|
774
|
+
dart: {
|
|
775
|
+
imports: /^\s*import\s+['"][^'"]+['"];?/gm,
|
|
776
|
+
declarations:
|
|
777
|
+
/^\s*(?:abstract\s+|sealed\s+)?(?:class|mixin|extension|enum|typedef)\s+\w+[^{]*/gm,
|
|
778
|
+
methods:
|
|
779
|
+
/^\s*(?:@override\s+)?(?:static\s+)?(?:Future|Stream|void|\w[\w<>?]*)?\s+\w+\s*\([^)]*\)/gm,
|
|
780
|
+
properties:
|
|
781
|
+
/^\s*(?:static\s+)?(?:final\s+|late\s+|const\s+)?(?:\w[\w<>?]*)\s+\w+\s*[;=]/gm,
|
|
608
782
|
},
|
|
609
783
|
};
|
|
610
784
|
// Alias variants
|
|
@@ -614,7 +788,8 @@ SUMMARY_EXTRACTORS.tsx = SUMMARY_EXTRACTORS.typescript;
|
|
|
614
788
|
|
|
615
789
|
const getFileSummary = {
|
|
616
790
|
name: 'get_file_summary',
|
|
617
|
-
description:
|
|
791
|
+
description:
|
|
792
|
+
'获取文件的结构摘要(导入、声明、方法签名),不包含实现代码。' +
|
|
618
793
|
'比 read_project_file 更轻量,适合快速了解文件角色和 API。',
|
|
619
794
|
parameters: {
|
|
620
795
|
type: 'object',
|
|
@@ -642,11 +817,14 @@ const getFileSummary = {
|
|
|
642
817
|
let content = null;
|
|
643
818
|
|
|
644
819
|
if (fileCache && Array.isArray(fileCache)) {
|
|
645
|
-
const cached = fileCache.find(
|
|
646
|
-
(f
|
|
647
|
-
|
|
820
|
+
const cached = fileCache.find(
|
|
821
|
+
(f) =>
|
|
822
|
+
(f.relativePath || f.path || '') === filePath ||
|
|
823
|
+
(f.relativePath || f.path || '') === normalized
|
|
648
824
|
);
|
|
649
|
-
if (cached)
|
|
825
|
+
if (cached) {
|
|
826
|
+
content = cached.content;
|
|
827
|
+
}
|
|
650
828
|
}
|
|
651
829
|
|
|
652
830
|
if (content === null) {
|
|
@@ -663,8 +841,7 @@ const getFileSummary = {
|
|
|
663
841
|
|
|
664
842
|
// 推断语言
|
|
665
843
|
const ext = path.extname(filePath).toLowerCase();
|
|
666
|
-
const
|
|
667
|
-
const language = langMap[ext] || 'unknown';
|
|
844
|
+
const language = LanguageService.langFromExt(ext);
|
|
668
845
|
const extractor = SUMMARY_EXTRACTORS[language];
|
|
669
846
|
|
|
670
847
|
const result = {
|
|
@@ -694,10 +871,18 @@ const getFileSummary = {
|
|
|
694
871
|
return matches;
|
|
695
872
|
};
|
|
696
873
|
|
|
697
|
-
if (extractor.imports)
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
if (extractor.
|
|
874
|
+
if (extractor.imports) {
|
|
875
|
+
result.imports = extract(extractor.imports);
|
|
876
|
+
}
|
|
877
|
+
if (extractor.declarations) {
|
|
878
|
+
result.declarations = extract(extractor.declarations);
|
|
879
|
+
}
|
|
880
|
+
if (extractor.methods) {
|
|
881
|
+
result.methods = extract(extractor.methods).slice(0, 50); // 限制数量
|
|
882
|
+
}
|
|
883
|
+
if (extractor.properties) {
|
|
884
|
+
result.properties = extract(extractor.properties).slice(0, 30);
|
|
885
|
+
}
|
|
701
886
|
|
|
702
887
|
return result;
|
|
703
888
|
},
|
|
@@ -708,14 +893,15 @@ const getFileSummary = {
|
|
|
708
893
|
// ────────────────────────────────────────────────────────────
|
|
709
894
|
const semanticSearchCode = {
|
|
710
895
|
name: 'semantic_search_code',
|
|
711
|
-
description:
|
|
896
|
+
description:
|
|
897
|
+
'在知识库中进行语义搜索。使用自然语言描述你要查找的代码模式或概念,' +
|
|
712
898
|
'返回语义最相关的知识条目。比关键词搜索更适合模糊/概念性查询。' +
|
|
713
899
|
'示例: "网络请求的错误处理策略"、"线程安全的单例实现"',
|
|
714
900
|
parameters: {
|
|
715
901
|
type: 'object',
|
|
716
902
|
properties: {
|
|
717
|
-
query:
|
|
718
|
-
topK:
|
|
903
|
+
query: { type: 'string', description: '自然语言搜索查询' },
|
|
904
|
+
topK: { type: 'number', description: '返回结果数量,默认 5' },
|
|
719
905
|
category: { type: 'string', description: '按分类过滤 (View/Service/Network/Model 等)' },
|
|
720
906
|
language: { type: 'string', description: '按语言过滤 (swift/objectivec 等)' },
|
|
721
907
|
},
|
|
@@ -734,18 +920,23 @@ const semanticSearchCode = {
|
|
|
734
920
|
let searchEngine = null;
|
|
735
921
|
try {
|
|
736
922
|
searchEngine = ctx.container?.get('searchEngine');
|
|
737
|
-
} catch {
|
|
923
|
+
} catch {
|
|
924
|
+
/* not available */
|
|
925
|
+
}
|
|
738
926
|
|
|
739
927
|
if (!searchEngine) {
|
|
740
928
|
// 尝试获取 VectorStore 直接搜索
|
|
741
929
|
let vectorStore = null;
|
|
742
930
|
try {
|
|
743
931
|
vectorStore = ctx.container?.get('vectorStore');
|
|
744
|
-
} catch {
|
|
932
|
+
} catch {
|
|
933
|
+
/* not available */
|
|
934
|
+
}
|
|
745
935
|
|
|
746
936
|
if (!vectorStore) {
|
|
747
937
|
return {
|
|
748
|
-
error:
|
|
938
|
+
error:
|
|
939
|
+
'语义搜索不可用: SearchEngine 和 VectorStore 均未初始化。可使用 search_project_code 进行关键词搜索替代。',
|
|
749
940
|
fallbackTool: 'search_project_code',
|
|
750
941
|
};
|
|
751
942
|
}
|
|
@@ -754,20 +945,26 @@ const semanticSearchCode = {
|
|
|
754
945
|
let aiProvider = null;
|
|
755
946
|
try {
|
|
756
947
|
aiProvider = ctx.container?.get('aiProvider');
|
|
757
|
-
} catch {
|
|
948
|
+
} catch {
|
|
949
|
+
/* not available */
|
|
950
|
+
}
|
|
758
951
|
|
|
759
952
|
if (!aiProvider || typeof aiProvider.generateEmbedding !== 'function') {
|
|
760
953
|
// 向量搜索需要 embedding,降级到关键词匹配
|
|
761
954
|
const filter = {};
|
|
762
|
-
if (category)
|
|
763
|
-
|
|
955
|
+
if (category) {
|
|
956
|
+
filter.category = category;
|
|
957
|
+
}
|
|
958
|
+
if (language) {
|
|
959
|
+
filter.language = language;
|
|
960
|
+
}
|
|
764
961
|
|
|
765
962
|
const results = await vectorStore.hybridSearch([], query, { topK, filter });
|
|
766
963
|
return {
|
|
767
964
|
mode: 'keyword-fallback',
|
|
768
965
|
query,
|
|
769
966
|
message: 'AI Provider 不支持 embedding,已降级到关键词匹配',
|
|
770
|
-
results: results.map(r => ({
|
|
967
|
+
results: results.map((r) => ({
|
|
771
968
|
id: r.item.id,
|
|
772
969
|
content: (r.item.content || '').slice(0, 500),
|
|
773
970
|
score: Math.round(r.score * 100) / 100,
|
|
@@ -780,14 +977,18 @@ const semanticSearchCode = {
|
|
|
780
977
|
try {
|
|
781
978
|
const embedding = await aiProvider.generateEmbedding(query);
|
|
782
979
|
const filter = {};
|
|
783
|
-
if (category)
|
|
784
|
-
|
|
980
|
+
if (category) {
|
|
981
|
+
filter.category = category;
|
|
982
|
+
}
|
|
983
|
+
if (language) {
|
|
984
|
+
filter.language = language;
|
|
985
|
+
}
|
|
785
986
|
|
|
786
987
|
const results = await vectorStore.hybridSearch(embedding, query, { topK, filter });
|
|
787
988
|
return {
|
|
788
989
|
mode: 'vector',
|
|
789
990
|
query,
|
|
790
|
-
results: results.map(r => ({
|
|
991
|
+
results: results.map((r) => ({
|
|
791
992
|
id: r.item.id,
|
|
792
993
|
content: (r.item.content || '').slice(0, 500),
|
|
793
994
|
score: Math.round(r.score * 100) / 100,
|
|
@@ -811,8 +1012,12 @@ const semanticSearchCode = {
|
|
|
811
1012
|
const actualMode = result?.mode || 'bm25';
|
|
812
1013
|
|
|
813
1014
|
// 应用过滤
|
|
814
|
-
if (category)
|
|
815
|
-
|
|
1015
|
+
if (category) {
|
|
1016
|
+
items = items.filter((i) => (i.category || '').toLowerCase() === category.toLowerCase());
|
|
1017
|
+
}
|
|
1018
|
+
if (language) {
|
|
1019
|
+
items = items.filter((i) => (i.language || '').toLowerCase() === language.toLowerCase());
|
|
1020
|
+
}
|
|
816
1021
|
items = items.slice(0, topK);
|
|
817
1022
|
|
|
818
1023
|
return {
|
|
@@ -820,7 +1025,7 @@ const semanticSearchCode = {
|
|
|
820
1025
|
query,
|
|
821
1026
|
degraded: actualMode !== 'semantic',
|
|
822
1027
|
totalResults: items.length,
|
|
823
|
-
results: items.map(item => ({
|
|
1028
|
+
results: items.map((item) => ({
|
|
824
1029
|
id: item.id,
|
|
825
1030
|
title: item.title || '',
|
|
826
1031
|
content: (item.content || item.description || '').slice(0, 500),
|
|
@@ -841,15 +1046,22 @@ const semanticSearchCode = {
|
|
|
841
1046
|
// ────────────────────────────────────────────────────────────
|
|
842
1047
|
const searchRecipes = {
|
|
843
1048
|
name: 'search_recipes',
|
|
844
|
-
description:
|
|
1049
|
+
description:
|
|
1050
|
+
'搜索知识库中的 Recipe(代码片段/最佳实践/架构模式)。支持关键词搜索和按分类/语言/类型筛选。',
|
|
845
1051
|
parameters: {
|
|
846
1052
|
type: 'object',
|
|
847
1053
|
properties: {
|
|
848
|
-
keyword:
|
|
849
|
-
category:
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
1054
|
+
keyword: { type: 'string', description: '搜索关键词' },
|
|
1055
|
+
category: {
|
|
1056
|
+
type: 'string',
|
|
1057
|
+
description: '分类过滤 (View/Service/Tool/Model/Network/Storage/UI/Utility)',
|
|
1058
|
+
},
|
|
1059
|
+
language: { type: 'string', description: '编程语言过滤 (swift/objectivec/typescript 等)' },
|
|
1060
|
+
knowledgeType: {
|
|
1061
|
+
type: 'string',
|
|
1062
|
+
description: '知识类型过滤 (code-standard/code-pattern/architecture/best-practice 等)',
|
|
1063
|
+
},
|
|
1064
|
+
limit: { type: 'number', description: '返回数量上限,默认 10' },
|
|
853
1065
|
},
|
|
854
1066
|
},
|
|
855
1067
|
handler: async (params, ctx) => {
|
|
@@ -861,9 +1073,15 @@ const searchRecipes = {
|
|
|
861
1073
|
}
|
|
862
1074
|
|
|
863
1075
|
const filters = { lifecycle: 'active' };
|
|
864
|
-
if (category)
|
|
865
|
-
|
|
866
|
-
|
|
1076
|
+
if (category) {
|
|
1077
|
+
filters.category = category;
|
|
1078
|
+
}
|
|
1079
|
+
if (language) {
|
|
1080
|
+
filters.language = language;
|
|
1081
|
+
}
|
|
1082
|
+
if (knowledgeType) {
|
|
1083
|
+
filters.knowledgeType = knowledgeType;
|
|
1084
|
+
}
|
|
867
1085
|
|
|
868
1086
|
return knowledgeService.list(filters, { page: 1, pageSize: limit });
|
|
869
1087
|
},
|
|
@@ -878,11 +1096,11 @@ const searchCandidates = {
|
|
|
878
1096
|
parameters: {
|
|
879
1097
|
type: 'object',
|
|
880
1098
|
properties: {
|
|
881
|
-
keyword:
|
|
882
|
-
status:
|
|
1099
|
+
keyword: { type: 'string', description: '搜索关键词' },
|
|
1100
|
+
status: { type: 'string', description: '状态过滤 (pending/approved/rejected/applied)' },
|
|
883
1101
|
language: { type: 'string', description: '编程语言过滤' },
|
|
884
1102
|
category: { type: 'string', description: '分类过滤' },
|
|
885
|
-
limit:
|
|
1103
|
+
limit: { type: 'number', description: '返回数量上限,默认 10' },
|
|
886
1104
|
},
|
|
887
1105
|
},
|
|
888
1106
|
handler: async (params, ctx) => {
|
|
@@ -895,9 +1113,15 @@ const searchCandidates = {
|
|
|
895
1113
|
|
|
896
1114
|
// V3: status 映射为 lifecycle
|
|
897
1115
|
const filters = {};
|
|
898
|
-
if (status)
|
|
899
|
-
|
|
900
|
-
|
|
1116
|
+
if (status) {
|
|
1117
|
+
filters.lifecycle = status;
|
|
1118
|
+
}
|
|
1119
|
+
if (language) {
|
|
1120
|
+
filters.language = language;
|
|
1121
|
+
}
|
|
1122
|
+
if (category) {
|
|
1123
|
+
filters.category = category;
|
|
1124
|
+
}
|
|
901
1125
|
|
|
902
1126
|
return knowledgeService.list(filters, { page: 1, pageSize: limit });
|
|
903
1127
|
},
|
|
@@ -932,7 +1156,8 @@ const getRecipeDetail = {
|
|
|
932
1156
|
// ────────────────────────────────────────────────────────────
|
|
933
1157
|
const getProjectStats = {
|
|
934
1158
|
name: 'get_project_stats',
|
|
935
|
-
description:
|
|
1159
|
+
description:
|
|
1160
|
+
'获取项目知识库的整体统计:Recipe 数量/分类分布、候选项数量/状态分布、知识图谱节点/边数。',
|
|
936
1161
|
parameters: { type: 'object', properties: {} },
|
|
937
1162
|
handler: async (_params, ctx) => {
|
|
938
1163
|
const knowledgeService = ctx.container.get('knowledgeService');
|
|
@@ -943,7 +1168,9 @@ const getProjectStats = {
|
|
|
943
1168
|
try {
|
|
944
1169
|
const kgService = ctx.container.get('knowledgeGraphService');
|
|
945
1170
|
graphStats = kgService.getStats();
|
|
946
|
-
} catch {
|
|
1171
|
+
} catch {
|
|
1172
|
+
/* KG not available */
|
|
1173
|
+
}
|
|
947
1174
|
|
|
948
1175
|
return {
|
|
949
1176
|
knowledge: stats,
|
|
@@ -962,7 +1189,7 @@ const searchKnowledge = {
|
|
|
962
1189
|
type: 'object',
|
|
963
1190
|
properties: {
|
|
964
1191
|
query: { type: 'string', description: '搜索查询' },
|
|
965
|
-
topK:
|
|
1192
|
+
topK: { type: 'number', description: '返回结果数,默认 5' },
|
|
966
1193
|
},
|
|
967
1194
|
required: ['query'],
|
|
968
1195
|
},
|
|
@@ -977,9 +1204,10 @@ const searchKnowledge = {
|
|
|
977
1204
|
const enriched = results.slice(0, topK).map((r, i) => ({
|
|
978
1205
|
...r,
|
|
979
1206
|
reasoning: {
|
|
980
|
-
whyRelevant:
|
|
981
|
-
|
|
982
|
-
|
|
1207
|
+
whyRelevant:
|
|
1208
|
+
r.score != null
|
|
1209
|
+
? `匹配分 ${(r.score * 100).toFixed(0)}%${r.matchType ? ` (${r.matchType})` : ''}`
|
|
1210
|
+
: '语义相关',
|
|
983
1211
|
rank: i + 1,
|
|
984
1212
|
},
|
|
985
1213
|
}));
|
|
@@ -993,7 +1221,9 @@ const searchKnowledge = {
|
|
|
993
1221
|
},
|
|
994
1222
|
};
|
|
995
1223
|
}
|
|
996
|
-
} catch {
|
|
1224
|
+
} catch {
|
|
1225
|
+
/* SearchEngine not available */
|
|
1226
|
+
}
|
|
997
1227
|
|
|
998
1228
|
// 降级: RetrievalFunnel + 全量候选
|
|
999
1229
|
try {
|
|
@@ -1003,7 +1233,7 @@ const searchKnowledge = {
|
|
|
1003
1233
|
const allRecipes = allResult?.items || [];
|
|
1004
1234
|
|
|
1005
1235
|
// 规范化为 funnel 输入格式
|
|
1006
|
-
const candidates = allRecipes.map(r => ({
|
|
1236
|
+
const candidates = allRecipes.map((r) => ({
|
|
1007
1237
|
id: r.id,
|
|
1008
1238
|
title: r.title,
|
|
1009
1239
|
content: r.content || r.code || '',
|
|
@@ -1017,9 +1247,19 @@ const searchKnowledge = {
|
|
|
1017
1247
|
const results = await funnel.execute(query, candidates, {});
|
|
1018
1248
|
return { source: 'retrievalFunnel', results: results.slice(0, topK) };
|
|
1019
1249
|
}
|
|
1020
|
-
} catch {
|
|
1250
|
+
} catch {
|
|
1251
|
+
/* RetrievalFunnel not available */
|
|
1252
|
+
}
|
|
1021
1253
|
|
|
1022
|
-
return {
|
|
1254
|
+
return {
|
|
1255
|
+
source: 'none',
|
|
1256
|
+
results: [],
|
|
1257
|
+
message: 'No search engine available',
|
|
1258
|
+
_meta: {
|
|
1259
|
+
confidence: 'none',
|
|
1260
|
+
hint: '搜索引擎不可用。请确认向量索引已构建(rebuild_index)。',
|
|
1261
|
+
},
|
|
1262
|
+
};
|
|
1023
1263
|
},
|
|
1024
1264
|
};
|
|
1025
1265
|
|
|
@@ -1033,7 +1273,11 @@ const getRelatedRecipes = {
|
|
|
1033
1273
|
type: 'object',
|
|
1034
1274
|
properties: {
|
|
1035
1275
|
recipeId: { type: 'string', description: 'Recipe ID' },
|
|
1036
|
-
relation: {
|
|
1276
|
+
relation: {
|
|
1277
|
+
type: 'string',
|
|
1278
|
+
description:
|
|
1279
|
+
'关系类型过滤 (requires/extends/enforces/depends_on/inherits/implements/calls/prerequisite),不传则返回全部关系',
|
|
1280
|
+
},
|
|
1037
1281
|
},
|
|
1038
1282
|
required: ['recipeId'],
|
|
1039
1283
|
},
|
|
@@ -1060,13 +1304,15 @@ const summarizeCode = {
|
|
|
1060
1304
|
parameters: {
|
|
1061
1305
|
type: 'object',
|
|
1062
1306
|
properties: {
|
|
1063
|
-
code:
|
|
1307
|
+
code: { type: 'string', description: '代码内容' },
|
|
1064
1308
|
language: { type: 'string', description: '编程语言' },
|
|
1065
1309
|
},
|
|
1066
1310
|
required: ['code'],
|
|
1067
1311
|
},
|
|
1068
1312
|
handler: async (params, ctx) => {
|
|
1069
|
-
if (!ctx.aiProvider)
|
|
1313
|
+
if (!ctx.aiProvider) {
|
|
1314
|
+
return { error: 'AI provider not available' };
|
|
1315
|
+
}
|
|
1070
1316
|
return ctx.aiProvider.summarize(params.code, params.language);
|
|
1071
1317
|
},
|
|
1072
1318
|
};
|
|
@@ -1076,17 +1322,27 @@ const summarizeCode = {
|
|
|
1076
1322
|
// ────────────────────────────────────────────────────────────
|
|
1077
1323
|
const extractRecipes = {
|
|
1078
1324
|
name: 'extract_recipes',
|
|
1079
|
-
description:
|
|
1325
|
+
description:
|
|
1326
|
+
'从源码文件中批量提取可复用的 Recipe 结构(代码标准、设计模式、最佳实践)。支持自动 provider fallback。',
|
|
1080
1327
|
parameters: {
|
|
1081
1328
|
type: 'object',
|
|
1082
1329
|
properties: {
|
|
1083
1330
|
targetName: { type: 'string', description: 'SPM Target / 模块名称' },
|
|
1084
|
-
files:
|
|
1331
|
+
files: {
|
|
1332
|
+
type: 'array',
|
|
1333
|
+
items: {
|
|
1334
|
+
type: 'object',
|
|
1335
|
+
properties: { name: { type: 'string' }, content: { type: 'string' } },
|
|
1336
|
+
},
|
|
1337
|
+
description: '文件数组 [{name, content}]',
|
|
1338
|
+
},
|
|
1085
1339
|
},
|
|
1086
1340
|
required: ['targetName', 'files'],
|
|
1087
1341
|
},
|
|
1088
1342
|
handler: async (params, ctx) => {
|
|
1089
|
-
if (!ctx.aiProvider)
|
|
1343
|
+
if (!ctx.aiProvider) {
|
|
1344
|
+
return { error: 'AI provider not available' };
|
|
1345
|
+
}
|
|
1090
1346
|
const { targetName, files, comprehensive } = params;
|
|
1091
1347
|
|
|
1092
1348
|
// 加载语言参考 Skill(如有),注入到 AI 提取 prompt
|
|
@@ -1097,20 +1353,22 @@ const extractRecipes = {
|
|
|
1097
1353
|
const primaryLang = langProfile?.primaryLanguage;
|
|
1098
1354
|
if (primaryLang) {
|
|
1099
1355
|
const skillCtx = loadBootstrapSkills(primaryLang);
|
|
1100
|
-
skillReference = skillCtx.languageSkill
|
|
1101
|
-
? skillCtx.languageSkill.substring(0, 2000)
|
|
1102
|
-
: null;
|
|
1356
|
+
skillReference = skillCtx.languageSkill ? skillCtx.languageSkill.substring(0, 2000) : null;
|
|
1103
1357
|
}
|
|
1104
|
-
} catch {
|
|
1358
|
+
} catch {
|
|
1359
|
+
/* Skills not available, proceed without */
|
|
1360
|
+
}
|
|
1105
1361
|
|
|
1106
1362
|
// AST 代码结构分析(如可用),注入到 AI 提取 prompt
|
|
1107
1363
|
let astContext = null;
|
|
1108
1364
|
try {
|
|
1109
|
-
const { analyzeProject, generateContextForAgent, isAvailable } = await import(
|
|
1365
|
+
const { analyzeProject, generateContextForAgent, isAvailable } = await import(
|
|
1366
|
+
'../../../core/AstAnalyzer.js'
|
|
1367
|
+
);
|
|
1110
1368
|
if (isAvailable()) {
|
|
1111
1369
|
const sourceFiles = files
|
|
1112
|
-
.filter(f => /\.(m|mm|h|swift|js|ts|jsx|tsx)$/.test(f.name || ''))
|
|
1113
|
-
.map(f => ({ path: f.name, source: f.content }));
|
|
1370
|
+
.filter((f) => /\.(m|mm|h|swift|js|ts|jsx|tsx)$/.test(f.name || ''))
|
|
1371
|
+
.map((f) => ({ path: f.name, source: f.content }));
|
|
1114
1372
|
if (sourceFiles.length > 0) {
|
|
1115
1373
|
const langProfile2 = ctx.aiProvider._detectLanguageProfile?.(files);
|
|
1116
1374
|
const lang = langProfile2?.primaryLanguage === 'swift' ? 'swift' : 'objc';
|
|
@@ -1118,12 +1376,20 @@ const extractRecipes = {
|
|
|
1118
1376
|
astContext = generateContextForAgent(summary);
|
|
1119
1377
|
}
|
|
1120
1378
|
}
|
|
1121
|
-
} catch {
|
|
1379
|
+
} catch {
|
|
1380
|
+
/* AST not available, proceed without */
|
|
1381
|
+
}
|
|
1122
1382
|
|
|
1123
1383
|
const extractOpts = {};
|
|
1124
|
-
if (skillReference)
|
|
1125
|
-
|
|
1126
|
-
|
|
1384
|
+
if (skillReference) {
|
|
1385
|
+
extractOpts.skillReference = skillReference;
|
|
1386
|
+
}
|
|
1387
|
+
if (astContext) {
|
|
1388
|
+
extractOpts.astContext = astContext;
|
|
1389
|
+
}
|
|
1390
|
+
if (comprehensive) {
|
|
1391
|
+
extractOpts.comprehensive = true;
|
|
1392
|
+
}
|
|
1127
1393
|
|
|
1128
1394
|
// 首选:使用当前 aiProvider
|
|
1129
1395
|
let recipes;
|
|
@@ -1145,23 +1411,41 @@ const extractRecipes = {
|
|
|
1145
1411
|
fallbackUsed = fbName;
|
|
1146
1412
|
recovered = true;
|
|
1147
1413
|
break;
|
|
1148
|
-
} catch {
|
|
1414
|
+
} catch {
|
|
1415
|
+
/* next fallback */
|
|
1416
|
+
}
|
|
1149
1417
|
}
|
|
1150
1418
|
}
|
|
1151
|
-
} catch {
|
|
1152
|
-
|
|
1419
|
+
} catch {
|
|
1420
|
+
/* AiFactory not available */
|
|
1421
|
+
}
|
|
1422
|
+
if (!recovered) {
|
|
1423
|
+
throw primaryErr;
|
|
1424
|
+
}
|
|
1153
1425
|
}
|
|
1154
1426
|
|
|
1155
|
-
if (!Array.isArray(recipes))
|
|
1427
|
+
if (!Array.isArray(recipes)) {
|
|
1428
|
+
recipes = [];
|
|
1429
|
+
}
|
|
1156
1430
|
if (recipes.length === 0) {
|
|
1157
|
-
ctx.logger?.warn?.(
|
|
1431
|
+
ctx.logger?.warn?.(
|
|
1432
|
+
`[extract_recipes] AI returned 0 recipes for ${targetName} (${files.length} files)`
|
|
1433
|
+
);
|
|
1158
1434
|
}
|
|
1159
1435
|
|
|
1160
1436
|
// ── V3 直透:AI 已输出完整 V3 结构,仅做来源标记 + 程序化评分/标签 ──
|
|
1161
1437
|
let qualityScorer = null;
|
|
1162
1438
|
let recipeExtractor = null;
|
|
1163
|
-
try {
|
|
1164
|
-
|
|
1439
|
+
try {
|
|
1440
|
+
qualityScorer = ctx.container?.get?.('qualityScorer');
|
|
1441
|
+
} catch {
|
|
1442
|
+
/* not available */
|
|
1443
|
+
}
|
|
1444
|
+
try {
|
|
1445
|
+
recipeExtractor = ctx.container?.get?.('recipeExtractor');
|
|
1446
|
+
} catch {
|
|
1447
|
+
/* not available */
|
|
1448
|
+
}
|
|
1165
1449
|
|
|
1166
1450
|
for (const recipe of recipes) {
|
|
1167
1451
|
// 来源 & 生命周期(非 AI 职责)
|
|
@@ -1173,15 +1457,23 @@ const extractRecipes = {
|
|
|
1173
1457
|
if (recipeExtractor && codeText) {
|
|
1174
1458
|
try {
|
|
1175
1459
|
const extracted = recipeExtractor.extractFromContent(
|
|
1176
|
-
codeText,
|
|
1460
|
+
codeText,
|
|
1461
|
+
`${recipe.title || 'unknown'}.${recipe.language || 'unknown'}`,
|
|
1462
|
+
''
|
|
1177
1463
|
);
|
|
1178
1464
|
if (extracted.semanticTags?.length > 0) {
|
|
1179
1465
|
recipe.tags = [...new Set([...(recipe.tags || []), ...extracted.semanticTags])];
|
|
1180
1466
|
}
|
|
1181
|
-
if (
|
|
1467
|
+
if (
|
|
1468
|
+
(!recipe.category || recipe.category === 'Utility') &&
|
|
1469
|
+
extracted.category &&
|
|
1470
|
+
extracted.category !== 'general'
|
|
1471
|
+
) {
|
|
1182
1472
|
recipe.category = extracted.category;
|
|
1183
1473
|
}
|
|
1184
|
-
} catch {
|
|
1474
|
+
} catch {
|
|
1475
|
+
/* best effort */
|
|
1476
|
+
}
|
|
1185
1477
|
}
|
|
1186
1478
|
|
|
1187
1479
|
// QualityScorer 评分 → quality 结构化
|
|
@@ -1195,12 +1487,16 @@ const extractRecipes = {
|
|
|
1195
1487
|
overall: scoreResult.score ?? 0,
|
|
1196
1488
|
grade: scoreResult.grade || '',
|
|
1197
1489
|
};
|
|
1198
|
-
} catch {
|
|
1490
|
+
} catch {
|
|
1491
|
+
/* best effort */
|
|
1492
|
+
}
|
|
1199
1493
|
}
|
|
1200
1494
|
}
|
|
1201
1495
|
|
|
1202
1496
|
const result = { targetName, extracted: recipes.length, recipes };
|
|
1203
|
-
if (fallbackUsed)
|
|
1497
|
+
if (fallbackUsed) {
|
|
1498
|
+
result.fallbackUsed = fallbackUsed;
|
|
1499
|
+
}
|
|
1204
1500
|
return result;
|
|
1205
1501
|
},
|
|
1206
1502
|
};
|
|
@@ -1210,16 +1506,23 @@ const extractRecipes = {
|
|
|
1210
1506
|
// ────────────────────────────────────────────────────────────
|
|
1211
1507
|
const enrichCandidate = {
|
|
1212
1508
|
name: 'enrich_candidate',
|
|
1213
|
-
description:
|
|
1509
|
+
description:
|
|
1510
|
+
'① 结构补齐 — 自动填充缺失的结构性语义字段(rationale/knowledgeType/complexity/scope/steps/constraints)。批量处理,只填空不覆盖。建议在 refine_bootstrap_candidates 之前执行。',
|
|
1214
1511
|
parameters: {
|
|
1215
1512
|
type: 'object',
|
|
1216
1513
|
properties: {
|
|
1217
|
-
candidateIds: {
|
|
1514
|
+
candidateIds: {
|
|
1515
|
+
type: 'array',
|
|
1516
|
+
items: { type: 'string' },
|
|
1517
|
+
description: '候选 ID 列表 (最多 20 个)',
|
|
1518
|
+
},
|
|
1218
1519
|
},
|
|
1219
1520
|
required: ['candidateIds'],
|
|
1220
1521
|
},
|
|
1221
1522
|
handler: async (params, ctx) => {
|
|
1222
|
-
if (!ctx.aiProvider)
|
|
1523
|
+
if (!ctx.aiProvider) {
|
|
1524
|
+
return { error: 'AI provider not available' };
|
|
1525
|
+
}
|
|
1223
1526
|
// V3: 使用 MCP handler enrichCandidates 的逻辑
|
|
1224
1527
|
const { enrichCandidates: enrichFn } = await import('../../external/mcp/handlers/candidate.js');
|
|
1225
1528
|
const result = await enrichFn(ctx, { candidateIds: params.candidateIds });
|
|
@@ -1232,17 +1535,27 @@ const enrichCandidate = {
|
|
|
1232
1535
|
// ────────────────────────────────────────────────────────────
|
|
1233
1536
|
const refineBootstrapCandidates = {
|
|
1234
1537
|
name: 'refine_bootstrap_candidates',
|
|
1235
|
-
description:
|
|
1538
|
+
description:
|
|
1539
|
+
'② 内容润色 — 逐条精炼 Bootstrap 候选的内容质量:改善 summary、补充架构 insight、推断 relations 关联、调整 confidence、丰富 tags。建议在 enrich_candidate 之后执行。',
|
|
1236
1540
|
parameters: {
|
|
1237
1541
|
type: 'object',
|
|
1238
1542
|
properties: {
|
|
1239
|
-
candidateIds: {
|
|
1240
|
-
|
|
1543
|
+
candidateIds: {
|
|
1544
|
+
type: 'array',
|
|
1545
|
+
items: { type: 'string' },
|
|
1546
|
+
description: '指定候选 ID 列表(可选,默认全部 bootstrap 候选)',
|
|
1547
|
+
},
|
|
1548
|
+
userPrompt: {
|
|
1549
|
+
type: 'string',
|
|
1550
|
+
description: '用户自定义润色提示词,指导 AI 润色方向(如“侧重描述线程安全注意事项”)',
|
|
1551
|
+
},
|
|
1241
1552
|
dryRun: { type: 'boolean', description: '仅预览 AI 润色结果,不写入数据库' },
|
|
1242
1553
|
},
|
|
1243
1554
|
},
|
|
1244
1555
|
handler: async (params, ctx) => {
|
|
1245
|
-
if (!ctx.aiProvider)
|
|
1556
|
+
if (!ctx.aiProvider) {
|
|
1557
|
+
return { error: 'AI provider not available' };
|
|
1558
|
+
}
|
|
1246
1559
|
// V3: 委托给 bootstrap handler 的 refine 逻辑
|
|
1247
1560
|
const { bootstrapRefine } = await import('../../external/mcp/handlers/bootstrap.js');
|
|
1248
1561
|
const result = await bootstrapRefine(ctx, {
|
|
@@ -1259,14 +1572,15 @@ const refineBootstrapCandidates = {
|
|
|
1259
1572
|
// ────────────────────────────────────────────────────────────
|
|
1260
1573
|
const checkDuplicate = {
|
|
1261
1574
|
name: 'check_duplicate',
|
|
1262
|
-
description:
|
|
1575
|
+
description:
|
|
1576
|
+
'候选查重 — 检测候选代码是否与已有 Recipe 重复(基于标题/摘要/代码的 Jaccard 相似度)。',
|
|
1263
1577
|
parameters: {
|
|
1264
1578
|
type: 'object',
|
|
1265
1579
|
properties: {
|
|
1266
|
-
candidate:
|
|
1580
|
+
candidate: { type: 'object', description: '候选对象 { title, summary, code, usageGuide }' },
|
|
1267
1581
|
candidateId: { type: 'string', description: '或提供候选 ID,从数据库读取' },
|
|
1268
1582
|
projectRoot: { type: 'string', description: '项目根目录(可选,默认当前项目)' },
|
|
1269
|
-
threshold:
|
|
1583
|
+
threshold: { type: 'number', description: '相似度阈值,默认 0.5' },
|
|
1270
1584
|
},
|
|
1271
1585
|
},
|
|
1272
1586
|
handler: async (params, ctx) => {
|
|
@@ -1288,10 +1602,14 @@ const checkDuplicate = {
|
|
|
1288
1602
|
usageGuide: '',
|
|
1289
1603
|
};
|
|
1290
1604
|
}
|
|
1291
|
-
} catch {
|
|
1605
|
+
} catch {
|
|
1606
|
+
/* ignore */
|
|
1607
|
+
}
|
|
1292
1608
|
}
|
|
1293
1609
|
|
|
1294
|
-
if (!cand)
|
|
1610
|
+
if (!cand) {
|
|
1611
|
+
return { similar: [], message: 'No candidate provided' };
|
|
1612
|
+
}
|
|
1295
1613
|
|
|
1296
1614
|
const similar = findSimilarRecipes(projectRoot, cand, {
|
|
1297
1615
|
threshold,
|
|
@@ -1300,14 +1618,16 @@ const checkDuplicate = {
|
|
|
1300
1618
|
|
|
1301
1619
|
return {
|
|
1302
1620
|
similar,
|
|
1303
|
-
hasDuplicate: similar.some(s => s.similarity >= 0.7),
|
|
1621
|
+
hasDuplicate: similar.some((s) => s.similarity >= 0.7),
|
|
1304
1622
|
highestSimilarity: similar.length > 0 ? similar[0].similarity : 0,
|
|
1305
1623
|
_meta: {
|
|
1306
|
-
confidence: similar.length === 0 ? 'none'
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1624
|
+
confidence: similar.length === 0 ? 'none' : similar[0].similarity >= 0.7 ? 'high' : 'low',
|
|
1625
|
+
hint:
|
|
1626
|
+
similar.length === 0
|
|
1627
|
+
? '未发现相似 Recipe,可放心提交。'
|
|
1628
|
+
: similar[0].similarity >= 0.7
|
|
1629
|
+
? '发现高度相似 Recipe,建议人工审核是否重复。'
|
|
1630
|
+
: '有低相似度匹配,大概率不是重复。',
|
|
1311
1631
|
},
|
|
1312
1632
|
};
|
|
1313
1633
|
},
|
|
@@ -1318,7 +1638,8 @@ const checkDuplicate = {
|
|
|
1318
1638
|
// ────────────────────────────────────────────────────────────
|
|
1319
1639
|
const discoverRelations = {
|
|
1320
1640
|
name: 'discover_relations',
|
|
1321
|
-
description:
|
|
1641
|
+
description:
|
|
1642
|
+
'AI 知识图谱关系发现 — 分析 Recipe 对之间的潜在关系(requires/extends/enforces/calls 等),并自动写入知识图谱。',
|
|
1322
1643
|
parameters: {
|
|
1323
1644
|
type: 'object',
|
|
1324
1645
|
properties: {
|
|
@@ -1327,30 +1648,55 @@ const discoverRelations = {
|
|
|
1327
1648
|
items: {
|
|
1328
1649
|
type: 'object',
|
|
1329
1650
|
properties: {
|
|
1330
|
-
a: {
|
|
1331
|
-
|
|
1651
|
+
a: {
|
|
1652
|
+
type: 'object',
|
|
1653
|
+
properties: {
|
|
1654
|
+
id: { type: 'string' },
|
|
1655
|
+
title: { type: 'string' },
|
|
1656
|
+
category: { type: 'string' },
|
|
1657
|
+
code: { type: 'string' },
|
|
1658
|
+
},
|
|
1659
|
+
},
|
|
1660
|
+
b: {
|
|
1661
|
+
type: 'object',
|
|
1662
|
+
properties: {
|
|
1663
|
+
id: { type: 'string' },
|
|
1664
|
+
title: { type: 'string' },
|
|
1665
|
+
category: { type: 'string' },
|
|
1666
|
+
code: { type: 'string' },
|
|
1667
|
+
},
|
|
1668
|
+
},
|
|
1332
1669
|
},
|
|
1333
1670
|
},
|
|
1334
|
-
description:
|
|
1671
|
+
description:
|
|
1672
|
+
'Recipe 对数组 [{ a: {id, title, category, code}, b: {id, title, category, code} }]',
|
|
1335
1673
|
},
|
|
1336
1674
|
dryRun: { type: 'boolean', description: '仅分析不写入,默认 false' },
|
|
1337
1675
|
},
|
|
1338
1676
|
required: ['recipePairs'],
|
|
1339
1677
|
},
|
|
1340
1678
|
handler: async (params, ctx) => {
|
|
1341
|
-
if (!ctx.aiProvider)
|
|
1679
|
+
if (!ctx.aiProvider) {
|
|
1680
|
+
return { error: 'AI provider not available' };
|
|
1681
|
+
}
|
|
1342
1682
|
|
|
1343
1683
|
const { recipePairs, dryRun = false } = params;
|
|
1344
|
-
if (!recipePairs || recipePairs.length === 0)
|
|
1684
|
+
if (!recipePairs || recipePairs.length === 0) {
|
|
1685
|
+
return { relations: [] };
|
|
1686
|
+
}
|
|
1345
1687
|
|
|
1346
1688
|
// 构建 LLM prompt
|
|
1347
|
-
const pairsText = recipePairs
|
|
1689
|
+
const pairsText = recipePairs
|
|
1690
|
+
.map(
|
|
1691
|
+
(p, i) => `
|
|
1348
1692
|
--- Pair #${i + 1} ---
|
|
1349
1693
|
Recipe A [${p.a.id}]: ${p.a.title} (${p.a.category}/${p.a.language || ''})
|
|
1350
1694
|
${p.a.code ? `Code: ${p.a.code.substring(0, 300)}` : ''}
|
|
1351
1695
|
|
|
1352
1696
|
Recipe B [${p.b.id}]: ${p.b.title} (${p.b.category}/${p.b.language || ''})
|
|
1353
|
-
${p.b.code ? `Code: ${p.b.code.substring(0, 300)}` : ''}`
|
|
1697
|
+
${p.b.code ? `Code: ${p.b.code.substring(0, 300)}` : ''}`
|
|
1698
|
+
)
|
|
1699
|
+
.join('\n');
|
|
1354
1700
|
|
|
1355
1701
|
const prompt = `# Role
|
|
1356
1702
|
You are a Software Architect analyzing relationships between code recipes (knowledge units).
|
|
@@ -1379,7 +1725,9 @@ Return ONLY a JSON array. No markdown, no extra text. Return [] if no relationsh
|
|
|
1379
1725
|
${pairsText}`;
|
|
1380
1726
|
|
|
1381
1727
|
const parsed = await ctx.aiProvider.chatWithStructuredOutput(prompt, {
|
|
1382
|
-
openChar: '[',
|
|
1728
|
+
openChar: '[',
|
|
1729
|
+
closeChar: ']',
|
|
1730
|
+
temperature: 0.2,
|
|
1383
1731
|
});
|
|
1384
1732
|
const relations = Array.isArray(parsed) ? parsed : [];
|
|
1385
1733
|
|
|
@@ -1389,21 +1737,22 @@ ${pairsText}`;
|
|
|
1389
1737
|
const kgService = ctx.container.get('knowledgeGraphService');
|
|
1390
1738
|
for (const rel of relations) {
|
|
1391
1739
|
if (rel.from_id && rel.to_id && rel.relation && rel.relation !== 'none') {
|
|
1392
|
-
kgService.addEdge(
|
|
1393
|
-
rel.
|
|
1394
|
-
rel.
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
);
|
|
1740
|
+
kgService.addEdge(rel.from_id, 'recipe', rel.to_id, 'recipe', rel.relation, {
|
|
1741
|
+
confidence: rel.confidence || 0.5,
|
|
1742
|
+
reason: rel.reason || '',
|
|
1743
|
+
source: 'ai-discovery',
|
|
1744
|
+
});
|
|
1398
1745
|
}
|
|
1399
1746
|
}
|
|
1400
|
-
} catch {
|
|
1747
|
+
} catch {
|
|
1748
|
+
/* KG not available */
|
|
1749
|
+
}
|
|
1401
1750
|
}
|
|
1402
1751
|
|
|
1403
1752
|
return {
|
|
1404
1753
|
analyzed: recipePairs.length,
|
|
1405
|
-
relations: relations.filter(r => r.relation !== 'none'),
|
|
1406
|
-
written: dryRun ? 0 : relations.filter(r => r.relation !== 'none').length,
|
|
1754
|
+
relations: relations.filter((r) => r.relation !== 'none'),
|
|
1755
|
+
written: dryRun ? 0 : relations.filter((r) => r.relation !== 'none').length,
|
|
1407
1756
|
};
|
|
1408
1757
|
},
|
|
1409
1758
|
};
|
|
@@ -1417,22 +1766,28 @@ const addGraphEdge = {
|
|
|
1417
1766
|
parameters: {
|
|
1418
1767
|
type: 'object',
|
|
1419
1768
|
properties: {
|
|
1420
|
-
fromId:
|
|
1769
|
+
fromId: { type: 'string', description: '源节点 ID' },
|
|
1421
1770
|
fromType: { type: 'string', description: '源节点类型 (recipe/candidate)' },
|
|
1422
|
-
toId:
|
|
1423
|
-
toType:
|
|
1424
|
-
relation: {
|
|
1425
|
-
|
|
1771
|
+
toId: { type: 'string', description: '目标节点 ID' },
|
|
1772
|
+
toType: { type: 'string', description: '目标节点类型 (recipe/candidate)' },
|
|
1773
|
+
relation: {
|
|
1774
|
+
type: 'string',
|
|
1775
|
+
description:
|
|
1776
|
+
'关系类型 (requires/extends/enforces/depends_on/inherits/implements/calls/prerequisite)',
|
|
1777
|
+
},
|
|
1778
|
+
weight: { type: 'number', description: '权重 0-1,默认 1.0' },
|
|
1426
1779
|
},
|
|
1427
1780
|
required: ['fromId', 'fromType', 'toId', 'toType', 'relation'],
|
|
1428
1781
|
},
|
|
1429
1782
|
handler: async (params, ctx) => {
|
|
1430
1783
|
const kgService = ctx.container.get('knowledgeGraphService');
|
|
1431
1784
|
return kgService.addEdge(
|
|
1432
|
-
params.fromId,
|
|
1433
|
-
params.
|
|
1785
|
+
params.fromId,
|
|
1786
|
+
params.fromType,
|
|
1787
|
+
params.toId,
|
|
1788
|
+
params.toType,
|
|
1434
1789
|
params.relation,
|
|
1435
|
-
{ weight: params.weight || 1.0, source: 'manual' }
|
|
1790
|
+
{ weight: params.weight || 1.0, source: 'manual' }
|
|
1436
1791
|
);
|
|
1437
1792
|
},
|
|
1438
1793
|
};
|
|
@@ -1464,16 +1819,21 @@ const listGuardRules = {
|
|
|
1464
1819
|
const guardService = ctx.container.get('guardService');
|
|
1465
1820
|
const dbRules = await guardService.listRules({}, { page: 1, pageSize: limit });
|
|
1466
1821
|
results.push(...(dbRules.data || dbRules.items || []));
|
|
1467
|
-
} catch {
|
|
1822
|
+
} catch {
|
|
1823
|
+
/* not available */
|
|
1824
|
+
}
|
|
1468
1825
|
|
|
1469
1826
|
// 内置规则
|
|
1470
1827
|
if (includeBuiltIn) {
|
|
1471
1828
|
try {
|
|
1472
1829
|
const guardCheckEngine = ctx.container.get('guardCheckEngine');
|
|
1473
|
-
const builtIn = guardCheckEngine
|
|
1474
|
-
.
|
|
1830
|
+
const builtIn = guardCheckEngine
|
|
1831
|
+
.getRules(language || null)
|
|
1832
|
+
.filter((r) => r.source === 'built-in');
|
|
1475
1833
|
results.push(...builtIn);
|
|
1476
|
-
} catch {
|
|
1834
|
+
} catch {
|
|
1835
|
+
/* not available */
|
|
1836
|
+
}
|
|
1477
1837
|
}
|
|
1478
1838
|
|
|
1479
1839
|
return { total: results.length, rules: results.slice(0, limit) };
|
|
@@ -1495,7 +1855,10 @@ const getRecommendations = {
|
|
|
1495
1855
|
handler: async (params, ctx) => {
|
|
1496
1856
|
const knowledgeService = ctx.container.get('knowledgeService');
|
|
1497
1857
|
// V3: 推荐 = 活跃条目按使用量排序
|
|
1498
|
-
return knowledgeService.list(
|
|
1858
|
+
return knowledgeService.list(
|
|
1859
|
+
{ lifecycle: 'active' },
|
|
1860
|
+
{ page: 1, pageSize: params.limit || 10 }
|
|
1861
|
+
);
|
|
1499
1862
|
},
|
|
1500
1863
|
};
|
|
1501
1864
|
|
|
@@ -1513,17 +1876,27 @@ const aiTranslate = {
|
|
|
1513
1876
|
},
|
|
1514
1877
|
},
|
|
1515
1878
|
handler: async (params, ctx) => {
|
|
1516
|
-
if (!ctx.aiProvider)
|
|
1879
|
+
if (!ctx.aiProvider) {
|
|
1880
|
+
return { error: 'AI provider not available' };
|
|
1881
|
+
}
|
|
1517
1882
|
const { summary, usageGuide } = params;
|
|
1518
|
-
if (!summary && !usageGuide)
|
|
1883
|
+
if (!summary && !usageGuide) {
|
|
1884
|
+
return { summaryEn: '', usageGuideEn: '' };
|
|
1885
|
+
}
|
|
1519
1886
|
|
|
1520
|
-
const systemPrompt =
|
|
1887
|
+
const systemPrompt =
|
|
1888
|
+
'You are a technical translator. Translate from Chinese to English. Keep technical terms unchanged. Return ONLY valid JSON: { "summaryEn": "...", "usageGuideEn": "..." }.';
|
|
1521
1889
|
const parts = [];
|
|
1522
|
-
if (summary)
|
|
1523
|
-
|
|
1890
|
+
if (summary) {
|
|
1891
|
+
parts.push(`summary: ${summary}`);
|
|
1892
|
+
}
|
|
1893
|
+
if (usageGuide) {
|
|
1894
|
+
parts.push(`usageGuide: ${usageGuide}`);
|
|
1895
|
+
}
|
|
1524
1896
|
|
|
1525
1897
|
const parsed = await ctx.aiProvider.chatWithStructuredOutput(parts.join('\n'), {
|
|
1526
|
-
systemPrompt,
|
|
1898
|
+
systemPrompt,
|
|
1899
|
+
temperature: 0.2,
|
|
1527
1900
|
});
|
|
1528
1901
|
return parsed || { summaryEn: summary || '', usageGuideEn: usageGuide || '' };
|
|
1529
1902
|
},
|
|
@@ -1538,9 +1911,9 @@ const guardCheckCode = {
|
|
|
1538
1911
|
parameters: {
|
|
1539
1912
|
type: 'object',
|
|
1540
1913
|
properties: {
|
|
1541
|
-
code:
|
|
1914
|
+
code: { type: 'string', description: '待检查的源代码' },
|
|
1542
1915
|
language: { type: 'string', description: '编程语言 (swift/objc/javascript 等)' },
|
|
1543
|
-
scope:
|
|
1916
|
+
scope: { type: 'string', description: '检查范围 (file/target/project),默认 file' },
|
|
1544
1917
|
},
|
|
1545
1918
|
required: ['code'],
|
|
1546
1919
|
},
|
|
@@ -1553,7 +1926,9 @@ const guardCheckCode = {
|
|
|
1553
1926
|
const violations = engine.checkCode(code, language || 'unknown', { scope });
|
|
1554
1927
|
// reasoning 已由 GuardCheckEngine.checkCode() 内置附加
|
|
1555
1928
|
return { violationCount: violations.length, violations };
|
|
1556
|
-
} catch {
|
|
1929
|
+
} catch {
|
|
1930
|
+
/* not available */
|
|
1931
|
+
}
|
|
1557
1932
|
|
|
1558
1933
|
// 降级到 GuardService.checkCode(仅 DB 规则)
|
|
1559
1934
|
try {
|
|
@@ -1575,7 +1950,7 @@ const queryViolations = {
|
|
|
1575
1950
|
parameters: {
|
|
1576
1951
|
type: 'object',
|
|
1577
1952
|
properties: {
|
|
1578
|
-
file:
|
|
1953
|
+
file: { type: 'string', description: '按文件路径过滤' },
|
|
1579
1954
|
limit: { type: 'number', description: '返回数量,默认 20' },
|
|
1580
1955
|
statsOnly: { type: 'boolean', description: '仅返回统计数据,默认 false' },
|
|
1581
1956
|
},
|
|
@@ -1605,15 +1980,20 @@ const generateGuardRule = {
|
|
|
1605
1980
|
parameters: {
|
|
1606
1981
|
type: 'object',
|
|
1607
1982
|
properties: {
|
|
1608
|
-
description: {
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1983
|
+
description: {
|
|
1984
|
+
type: 'string',
|
|
1985
|
+
description: '规则描述(例如 "禁止在主线程使用同步网络请求")',
|
|
1986
|
+
},
|
|
1987
|
+
language: { type: 'string', description: '目标语言 (swift/objc 等)' },
|
|
1988
|
+
severity: { type: 'string', description: '严重程度 (error/warning/info),默认 warning' },
|
|
1989
|
+
autoCreate: { type: 'boolean', description: '是否自动创建到数据库,默认 false' },
|
|
1612
1990
|
},
|
|
1613
1991
|
required: ['description'],
|
|
1614
1992
|
},
|
|
1615
1993
|
handler: async (params, ctx) => {
|
|
1616
|
-
if (!ctx.aiProvider)
|
|
1994
|
+
if (!ctx.aiProvider) {
|
|
1995
|
+
return { error: 'AI provider not available' };
|
|
1996
|
+
}
|
|
1617
1997
|
const { description, language = 'swift', severity = 'warning', autoCreate = false } = params;
|
|
1618
1998
|
|
|
1619
1999
|
const prompt = `Generate a Guard rule for this requirement:
|
|
@@ -1636,7 +2016,9 @@ Return ONLY valid JSON:
|
|
|
1636
2016
|
}`;
|
|
1637
2017
|
|
|
1638
2018
|
const rule = await ctx.aiProvider.chatWithStructuredOutput(prompt, { temperature: 0.2 });
|
|
1639
|
-
if (!rule)
|
|
2019
|
+
if (!rule) {
|
|
2020
|
+
return { error: 'Failed to parse AI response' };
|
|
2021
|
+
}
|
|
1640
2022
|
|
|
1641
2023
|
// 验证正则表达式
|
|
1642
2024
|
try {
|
|
@@ -1649,13 +2031,16 @@ Return ONLY valid JSON:
|
|
|
1649
2031
|
if (autoCreate && rule.name && rule.pattern) {
|
|
1650
2032
|
try {
|
|
1651
2033
|
const guardService = ctx.container.get('guardService');
|
|
1652
|
-
const created = await guardService.createRule(
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
2034
|
+
const created = await guardService.createRule(
|
|
2035
|
+
{
|
|
2036
|
+
name: rule.name,
|
|
2037
|
+
description: rule.description || description,
|
|
2038
|
+
pattern: rule.pattern,
|
|
2039
|
+
languages: rule.languages || [language],
|
|
2040
|
+
severity: rule.severity || severity,
|
|
2041
|
+
},
|
|
2042
|
+
{ userId: 'agent' }
|
|
2043
|
+
);
|
|
1659
2044
|
return { rule, created: true, recipeId: created.id };
|
|
1660
2045
|
} catch (err) {
|
|
1661
2046
|
return { rule, created: false, error: err.message };
|
|
@@ -1672,15 +2057,15 @@ Return ONLY valid JSON:
|
|
|
1672
2057
|
// ────────────────────────────────────────────────────────────
|
|
1673
2058
|
|
|
1674
2059
|
const DIMENSION_DISPLAY_GROUP = {
|
|
1675
|
-
|
|
1676
|
-
'code-pattern':
|
|
1677
|
-
'project-profile':
|
|
1678
|
-
'best-practice':
|
|
1679
|
-
'code-standard':
|
|
2060
|
+
architecture: 'architecture', // → 架构与设计
|
|
2061
|
+
'code-pattern': 'architecture', // → 架构与设计
|
|
2062
|
+
'project-profile': 'architecture', // → 架构与设计
|
|
2063
|
+
'best-practice': 'best-practice', // → 规范与实践
|
|
2064
|
+
'code-standard': 'best-practice', // → 规范与实践
|
|
1680
2065
|
'event-and-data-flow': 'event-and-data-flow', // → 事件与数据流
|
|
1681
|
-
'objc-deep-scan':
|
|
1682
|
-
'category-scan':
|
|
1683
|
-
'agent-guidelines':
|
|
2066
|
+
'objc-deep-scan': 'objc-deep-scan', // → 深度扫描
|
|
2067
|
+
'category-scan': 'objc-deep-scan', // → 深度扫描
|
|
2068
|
+
'agent-guidelines': 'agent-guidelines', // skill-only
|
|
1684
2069
|
};
|
|
1685
2070
|
|
|
1686
2071
|
// ────────────────────────────────────────────────────────────
|
|
@@ -1698,7 +2083,9 @@ const DIMENSION_DISPLAY_GROUP = {
|
|
|
1698
2083
|
function _checkDimensionType(dimensionMeta, params, logger) {
|
|
1699
2084
|
// 1. Skill-only 维度不允许提交 Candidate
|
|
1700
2085
|
if (dimensionMeta.outputType === 'skill') {
|
|
1701
|
-
logger?.info(
|
|
2086
|
+
logger?.info(
|
|
2087
|
+
`[submit_knowledge] ✗ rejected — dimension "${dimensionMeta.id}" is skill-only, cannot submit candidates`
|
|
2088
|
+
);
|
|
1702
2089
|
return {
|
|
1703
2090
|
status: 'rejected',
|
|
1704
2091
|
reason: `当前维度 "${dimensionMeta.id}" 的输出类型为 skill-only,不允许调用 submit_knowledge。请只在最终回复中提供 dimensionDigest JSON。`,
|
|
@@ -1710,7 +2097,9 @@ function _checkDimensionType(dimensionMeta, params, logger) {
|
|
|
1710
2097
|
if (allowed.length > 0 && params.knowledgeType) {
|
|
1711
2098
|
if (!allowed.includes(params.knowledgeType)) {
|
|
1712
2099
|
const corrected = allowed[0];
|
|
1713
|
-
logger?.warn(
|
|
2100
|
+
logger?.warn(
|
|
2101
|
+
`[submit_knowledge] knowledgeType "${params.knowledgeType}" → "${corrected}" (auto-corrected for dimension "${dimensionMeta.id}")`
|
|
2102
|
+
);
|
|
1714
2103
|
params.knowledgeType = corrected;
|
|
1715
2104
|
}
|
|
1716
2105
|
}
|
|
@@ -1727,31 +2116,55 @@ const submitCandidate = {
|
|
|
1727
2116
|
type: 'object',
|
|
1728
2117
|
properties: {
|
|
1729
2118
|
// ── 内容(V3 content 子对象) ──
|
|
1730
|
-
content:
|
|
2119
|
+
content: {
|
|
2120
|
+
type: 'object',
|
|
2121
|
+
description: '{ markdown: "项目特写 Markdown(≥200字)", pattern: "核心代码 3-8 行", rationale: "设计原理" }',
|
|
2122
|
+
},
|
|
1731
2123
|
|
|
1732
2124
|
// ── 基本信息 ──
|
|
1733
|
-
title:
|
|
1734
|
-
description:
|
|
1735
|
-
tags:
|
|
2125
|
+
title: { type: 'string', description: '候选标题(中文 ≤20 字)' },
|
|
2126
|
+
description: { type: 'string', description: '中文简述 ≤80 字,引用真实类名' },
|
|
2127
|
+
tags: { type: 'array', items: { type: 'string' }, description: '标签列表' },
|
|
1736
2128
|
|
|
1737
2129
|
// ── Cursor 交付(AI 必填)──
|
|
1738
|
-
trigger:
|
|
1739
|
-
kind:
|
|
1740
|
-
topicHint:
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
2130
|
+
trigger: { type: 'string', description: '@前缀 kebab-case 唯一标识符' },
|
|
2131
|
+
kind: { type: 'string', enum: ['rule', 'pattern', 'fact'], description: '知识类型' },
|
|
2132
|
+
topicHint: {
|
|
2133
|
+
type: 'string',
|
|
2134
|
+
enum: ['networking', 'ui', 'data', 'architecture', 'conventions'],
|
|
2135
|
+
description: '主题分类',
|
|
2136
|
+
},
|
|
2137
|
+
whenClause: { type: 'string', description: '触发场景英文' },
|
|
2138
|
+
doClause: { type: 'string', description: '正向指令英文祈使句 ≤60 tokens' },
|
|
2139
|
+
dontClause: { type: 'string', description: "反向约束英文(不以 Don't 开头)" },
|
|
1744
2140
|
|
|
1745
|
-
// ──
|
|
1746
|
-
reasoning:
|
|
2141
|
+
// ── 推理(必填) ──
|
|
2142
|
+
reasoning: {
|
|
2143
|
+
type: 'object',
|
|
2144
|
+
description: '{ whyStandard: string, sources: string[], confidence: number } — 全部必填',
|
|
2145
|
+
},
|
|
1747
2146
|
|
|
1748
2147
|
// ── V3 扩展字段 ──
|
|
1749
|
-
scope:
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
2148
|
+
scope: {
|
|
2149
|
+
type: 'string',
|
|
2150
|
+
enum: ['universal', 'project-specific', 'team-convention'],
|
|
2151
|
+
description: '适用范围',
|
|
2152
|
+
},
|
|
2153
|
+
complexity: {
|
|
2154
|
+
type: 'string',
|
|
2155
|
+
enum: ['basic', 'intermediate', 'advanced'],
|
|
2156
|
+
description: '复杂度',
|
|
2157
|
+
},
|
|
2158
|
+
headers: {
|
|
2159
|
+
type: 'array',
|
|
2160
|
+
items: { type: 'string' },
|
|
2161
|
+
description: '依赖的 import/require 行(无 import 时传 [])',
|
|
2162
|
+
},
|
|
2163
|
+
knowledgeType: { type: 'string', description: '知识维度:code-pattern / architecture / best-practice 等' },
|
|
2164
|
+
usageGuide: { type: 'string', description: '使用指南 Markdown(### 章节格式)' },
|
|
2165
|
+
sourceFile: { type: 'string', description: '来源文件相对路径' },
|
|
1753
2166
|
},
|
|
1754
|
-
required: ['content', 'title', 'trigger', 'kind', 'doClause'],
|
|
2167
|
+
required: ['content', 'title', 'trigger', 'kind', 'doClause', 'description', 'headers', 'reasoning'],
|
|
1755
2168
|
},
|
|
1756
2169
|
handler: async (params, ctx) => {
|
|
1757
2170
|
const knowledgeService = ctx.container.get('knowledgeService');
|
|
@@ -1760,21 +2173,26 @@ const submitCandidate = {
|
|
|
1760
2173
|
const dimMeta = ctx._dimensionMeta;
|
|
1761
2174
|
if (dimMeta && ctx.source === 'system') {
|
|
1762
2175
|
const rejected = _checkDimensionType(dimMeta, params, ctx.logger);
|
|
1763
|
-
if (rejected)
|
|
2176
|
+
if (rejected) {
|
|
2177
|
+
return rejected;
|
|
2178
|
+
}
|
|
1764
2179
|
|
|
1765
2180
|
// 自动注入维度标签
|
|
1766
|
-
if (!params.tags)
|
|
1767
|
-
|
|
1768
|
-
|
|
2181
|
+
if (!params.tags) {
|
|
2182
|
+
params.tags = [];
|
|
2183
|
+
}
|
|
2184
|
+
if (!params.tags.includes(dimMeta.id)) {
|
|
2185
|
+
params.tags.push(dimMeta.id);
|
|
2186
|
+
}
|
|
2187
|
+
if (!params.tags.includes('bootstrap')) {
|
|
2188
|
+
params.tags.push('bootstrap');
|
|
2189
|
+
}
|
|
1769
2190
|
|
|
1770
2191
|
// Bootstrap 模式: 将 category 覆盖为展示分组 ID
|
|
1771
2192
|
params._category = DIMENSION_DISPLAY_GROUP[dimMeta.id] || dimMeta.id;
|
|
1772
2193
|
|
|
1773
2194
|
// ── CandidateGuardrail 质量验证 ──
|
|
1774
|
-
const guardrail = new CandidateGuardrail(
|
|
1775
|
-
ctx._submittedTitles || new Set(),
|
|
1776
|
-
dimMeta,
|
|
1777
|
-
);
|
|
2195
|
+
const guardrail = new CandidateGuardrail(ctx._submittedTitles || new Set(), dimMeta, ctx._submittedPatterns || new Set());
|
|
1778
2196
|
const guardResult = guardrail.validate(params);
|
|
1779
2197
|
if (!guardResult.valid) {
|
|
1780
2198
|
ctx.logger?.info(`[submit_knowledge] ✗ guardrail rejected: ${guardResult.error}`);
|
|
@@ -1788,10 +2206,10 @@ const submitCandidate = {
|
|
|
1788
2206
|
|
|
1789
2207
|
// ── 系统自动设置 ──
|
|
1790
2208
|
const systemFields = {
|
|
1791
|
-
language:
|
|
1792
|
-
category:
|
|
2209
|
+
language: ctx._projectLanguage || '',
|
|
2210
|
+
category: dimMeta ? DIMENSION_DISPLAY_GROUP[dimMeta.id] || dimMeta.id : 'general',
|
|
1793
2211
|
knowledgeType: dimMeta?.allowedKnowledgeTypes?.[0] || 'code-pattern',
|
|
1794
|
-
source:
|
|
2212
|
+
source: ctx.source === 'system' ? 'bootstrap' : 'agent',
|
|
1795
2213
|
};
|
|
1796
2214
|
|
|
1797
2215
|
// ── 直传 → KnowledgeEntry ──
|
|
@@ -1801,39 +2219,42 @@ const submitCandidate = {
|
|
|
1801
2219
|
}
|
|
1802
2220
|
|
|
1803
2221
|
// V3 content 直透
|
|
1804
|
-
const contentObj =
|
|
1805
|
-
|
|
1806
|
-
|
|
2222
|
+
const contentObj =
|
|
2223
|
+
params.content && typeof params.content === 'object'
|
|
2224
|
+
? params.content
|
|
2225
|
+
: { markdown: '', pattern: '' };
|
|
1807
2226
|
|
|
1808
2227
|
const data = {
|
|
1809
2228
|
...systemFields,
|
|
1810
|
-
title:
|
|
2229
|
+
title: params.title || '',
|
|
1811
2230
|
description: params.description || '',
|
|
1812
|
-
tags:
|
|
1813
|
-
trigger:
|
|
1814
|
-
kind:
|
|
1815
|
-
topicHint:
|
|
1816
|
-
whenClause:
|
|
1817
|
-
doClause:
|
|
1818
|
-
dontClause:
|
|
1819
|
-
coreCode:
|
|
1820
|
-
content:
|
|
2231
|
+
tags: params.tags || [],
|
|
2232
|
+
trigger: params.trigger || '',
|
|
2233
|
+
kind: params.kind || 'pattern',
|
|
2234
|
+
topicHint: params.topicHint || '',
|
|
2235
|
+
whenClause: params.whenClause || '',
|
|
2236
|
+
doClause: params.doClause || '',
|
|
2237
|
+
dontClause: params.dontClause || '',
|
|
2238
|
+
coreCode: contentObj.pattern || '',
|
|
2239
|
+
content: contentObj,
|
|
1821
2240
|
reasoning,
|
|
1822
2241
|
// V3 扩展字段直透
|
|
1823
|
-
scope:
|
|
1824
|
-
complexity:
|
|
1825
|
-
headers:
|
|
2242
|
+
scope: params.scope || '',
|
|
2243
|
+
complexity: params.complexity || '',
|
|
2244
|
+
headers: params.headers || [],
|
|
1826
2245
|
// sourceFile: 优先取 params,Bootstrap 回退从 reasoning.sources 推断
|
|
1827
|
-
sourceFile:
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
2246
|
+
sourceFile:
|
|
2247
|
+
params.sourceFile ||
|
|
2248
|
+
(Array.isArray(reasoning.sources) &&
|
|
2249
|
+
reasoning.sources.length > 0 &&
|
|
2250
|
+
reasoning.sources[0] !== 'agent'
|
|
2251
|
+
? reasoning.sources[0]
|
|
2252
|
+
: ''),
|
|
1832
2253
|
// 7.3.9 agentNotes/aiInsight 注入
|
|
1833
|
-
agentNotes:
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
aiInsight:
|
|
2254
|
+
agentNotes: dimMeta
|
|
2255
|
+
? { dimensionId: dimMeta.id, outputType: dimMeta.outputType || 'candidate' }
|
|
2256
|
+
: null,
|
|
2257
|
+
aiInsight: reasoning.whyStandard || params.description || null,
|
|
1837
2258
|
};
|
|
1838
2259
|
|
|
1839
2260
|
if (dimMeta && ctx.source === 'system') {
|
|
@@ -1846,7 +2267,9 @@ const submitCandidate = {
|
|
|
1846
2267
|
// ── QualityScorer 自动评分 ──
|
|
1847
2268
|
try {
|
|
1848
2269
|
await knowledgeService.updateQuality(saved.id, { userId: 'agent' });
|
|
1849
|
-
} catch {
|
|
2270
|
+
} catch {
|
|
2271
|
+
/* best effort — 不阻塞创建流程 */
|
|
2272
|
+
}
|
|
1850
2273
|
|
|
1851
2274
|
return saved;
|
|
1852
2275
|
},
|
|
@@ -1857,15 +2280,24 @@ const submitCandidate = {
|
|
|
1857
2280
|
// ────────────────────────────────────────────────────────────
|
|
1858
2281
|
const saveDocument = {
|
|
1859
2282
|
name: 'save_document',
|
|
1860
|
-
description:
|
|
2283
|
+
description:
|
|
2284
|
+
'保存开发文档到知识库(架构设计、排查报告、决策记录、调研笔记等)。仅需 title + markdown,无需 Cursor Delivery 字段。文档自动发布,可通过 autosnippet_search 检索。',
|
|
1861
2285
|
parameters: {
|
|
1862
2286
|
type: 'object',
|
|
1863
2287
|
properties: {
|
|
1864
|
-
title:
|
|
1865
|
-
markdown:
|
|
2288
|
+
title: { type: 'string', description: '文档标题' },
|
|
2289
|
+
markdown: { type: 'string', description: '文档 Markdown 全文' },
|
|
1866
2290
|
description: { type: 'string', description: '一句话摘要(可选)' },
|
|
1867
|
-
tags:
|
|
1868
|
-
|
|
2291
|
+
tags: {
|
|
2292
|
+
type: 'array',
|
|
2293
|
+
items: { type: 'string' },
|
|
2294
|
+
description: '标签: adr, debug-report, design-doc, research, performance 等',
|
|
2295
|
+
},
|
|
2296
|
+
scope: {
|
|
2297
|
+
type: 'string',
|
|
2298
|
+
enum: ['universal', 'project-specific'],
|
|
2299
|
+
description: '适用范围(默认 project-specific)',
|
|
2300
|
+
},
|
|
1869
2301
|
},
|
|
1870
2302
|
required: ['title', 'markdown'],
|
|
1871
2303
|
},
|
|
@@ -1873,27 +2305,27 @@ const saveDocument = {
|
|
|
1873
2305
|
const knowledgeService = ctx.container.get('knowledgeService');
|
|
1874
2306
|
|
|
1875
2307
|
const data = {
|
|
1876
|
-
title:
|
|
1877
|
-
description:
|
|
2308
|
+
title: params.title.trim(),
|
|
2309
|
+
description: params.description || '',
|
|
1878
2310
|
knowledgeType: 'dev-document',
|
|
1879
|
-
kind:
|
|
1880
|
-
source:
|
|
1881
|
-
scope:
|
|
1882
|
-
tags:
|
|
2311
|
+
kind: 'fact',
|
|
2312
|
+
source: 'agent',
|
|
2313
|
+
scope: params.scope || 'project-specific',
|
|
2314
|
+
tags: params.tags || [],
|
|
1883
2315
|
content: {
|
|
1884
2316
|
markdown: params.markdown,
|
|
1885
|
-
pattern:
|
|
2317
|
+
pattern: '',
|
|
1886
2318
|
},
|
|
1887
|
-
trigger:
|
|
1888
|
-
doClause:
|
|
2319
|
+
trigger: '',
|
|
2320
|
+
doClause: '',
|
|
1889
2321
|
dontClause: '',
|
|
1890
2322
|
whenClause: '',
|
|
1891
|
-
topicHint:
|
|
1892
|
-
coreCode:
|
|
2323
|
+
topicHint: '',
|
|
2324
|
+
coreCode: '',
|
|
1893
2325
|
reasoning: {
|
|
1894
2326
|
whyStandard: 'Agent development document',
|
|
1895
|
-
sources:
|
|
1896
|
-
confidence:
|
|
2327
|
+
sources: ['agent'],
|
|
2328
|
+
confidence: 0.8,
|
|
1897
2329
|
},
|
|
1898
2330
|
};
|
|
1899
2331
|
|
|
@@ -1902,14 +2334,16 @@ const saveDocument = {
|
|
|
1902
2334
|
// 自动发布(文档不需要人工审核)
|
|
1903
2335
|
try {
|
|
1904
2336
|
await knowledgeService.publish(saved.id, { userId: 'agent' });
|
|
1905
|
-
} catch {
|
|
2337
|
+
} catch {
|
|
2338
|
+
/* best effort */
|
|
2339
|
+
}
|
|
1906
2340
|
|
|
1907
2341
|
return {
|
|
1908
|
-
id:
|
|
1909
|
-
title:
|
|
1910
|
-
lifecycle:
|
|
2342
|
+
id: saved.id,
|
|
2343
|
+
title: saved.title,
|
|
2344
|
+
lifecycle: 'active',
|
|
1911
2345
|
knowledgeType: 'dev-document',
|
|
1912
|
-
message:
|
|
2346
|
+
message: `文档「${saved.title}」已保存到知识库`,
|
|
1913
2347
|
};
|
|
1914
2348
|
},
|
|
1915
2349
|
};
|
|
@@ -1943,7 +2377,7 @@ const rejectCandidate = {
|
|
|
1943
2377
|
type: 'object',
|
|
1944
2378
|
properties: {
|
|
1945
2379
|
candidateId: { type: 'string', description: '候选 ID' },
|
|
1946
|
-
reason:
|
|
2380
|
+
reason: { type: 'string', description: '驳回理由' },
|
|
1947
2381
|
},
|
|
1948
2382
|
required: ['candidateId', 'reason'],
|
|
1949
2383
|
},
|
|
@@ -1982,7 +2416,7 @@ const deprecateRecipe = {
|
|
|
1982
2416
|
type: 'object',
|
|
1983
2417
|
properties: {
|
|
1984
2418
|
recipeId: { type: 'string', description: 'Recipe ID' },
|
|
1985
|
-
reason:
|
|
2419
|
+
reason: { type: 'string', description: '弃用原因' },
|
|
1986
2420
|
},
|
|
1987
2421
|
required: ['recipeId', 'reason'],
|
|
1988
2422
|
},
|
|
@@ -2002,7 +2436,7 @@ const updateRecipe = {
|
|
|
2002
2436
|
type: 'object',
|
|
2003
2437
|
properties: {
|
|
2004
2438
|
recipeId: { type: 'string', description: 'Recipe ID' },
|
|
2005
|
-
updates:
|
|
2439
|
+
updates: { type: 'object', description: '要更新的字段和值' },
|
|
2006
2440
|
},
|
|
2007
2441
|
required: ['recipeId', 'updates'],
|
|
2008
2442
|
},
|
|
@@ -2022,7 +2456,7 @@ const recordUsage = {
|
|
|
2022
2456
|
type: 'object',
|
|
2023
2457
|
properties: {
|
|
2024
2458
|
recipeId: { type: 'string', description: 'Recipe ID' },
|
|
2025
|
-
type:
|
|
2459
|
+
type: { type: 'string', description: 'adoption 或 application,默认 adoption' },
|
|
2026
2460
|
},
|
|
2027
2461
|
required: ['recipeId'],
|
|
2028
2462
|
},
|
|
@@ -2039,12 +2473,16 @@ const recordUsage = {
|
|
|
2039
2473
|
// ────────────────────────────────────────────────────────────
|
|
2040
2474
|
const qualityScore = {
|
|
2041
2475
|
name: 'quality_score',
|
|
2042
|
-
description:
|
|
2476
|
+
description:
|
|
2477
|
+
'Recipe 质量评分 — 5 维度综合评估(完整性/格式/代码质量/元数据/互动),返回分数和等级(A-F)。',
|
|
2043
2478
|
parameters: {
|
|
2044
2479
|
type: 'object',
|
|
2045
2480
|
properties: {
|
|
2046
2481
|
recipeId: { type: 'string', description: 'Recipe ID(从数据库读取后评分)' },
|
|
2047
|
-
recipe:
|
|
2482
|
+
recipe: {
|
|
2483
|
+
type: 'object',
|
|
2484
|
+
description: '或直接提供 Recipe 对象 { title, trigger, code, language, ... }',
|
|
2485
|
+
},
|
|
2048
2486
|
},
|
|
2049
2487
|
},
|
|
2050
2488
|
handler: async (params, ctx) => {
|
|
@@ -2060,7 +2498,9 @@ const qualityScore = {
|
|
|
2060
2498
|
return { error: `Knowledge entry '${params.recipeId}' not found` };
|
|
2061
2499
|
}
|
|
2062
2500
|
}
|
|
2063
|
-
if (!recipe)
|
|
2501
|
+
if (!recipe) {
|
|
2502
|
+
return { error: 'Provide recipeId or recipe object' };
|
|
2503
|
+
}
|
|
2064
2504
|
|
|
2065
2505
|
return qualityScorer.score(recipe);
|
|
2066
2506
|
},
|
|
@@ -2071,11 +2511,15 @@ const qualityScore = {
|
|
|
2071
2511
|
// ────────────────────────────────────────────────────────────
|
|
2072
2512
|
const validateCandidate = {
|
|
2073
2513
|
name: 'validate_candidate',
|
|
2074
|
-
description:
|
|
2514
|
+
description:
|
|
2515
|
+
'候选校验 — 检查候选是否满足提交要求(必填字段/格式/质量),返回 errors 和 warnings。',
|
|
2075
2516
|
parameters: {
|
|
2076
2517
|
type: 'object',
|
|
2077
2518
|
properties: {
|
|
2078
|
-
candidate: {
|
|
2519
|
+
candidate: {
|
|
2520
|
+
type: 'object',
|
|
2521
|
+
description: '候选对象 { title, trigger, category, language, code, reasoning, ... }',
|
|
2522
|
+
},
|
|
2079
2523
|
},
|
|
2080
2524
|
required: ['candidate'],
|
|
2081
2525
|
},
|
|
@@ -2095,7 +2539,7 @@ const getFeedbackStats = {
|
|
|
2095
2539
|
type: 'object',
|
|
2096
2540
|
properties: {
|
|
2097
2541
|
recipeId: { type: 'string', description: '查询指定 Recipe 的反馈(可选)' },
|
|
2098
|
-
topN:
|
|
2542
|
+
topN: { type: 'number', description: '热门 Recipe 数量,默认 10' },
|
|
2099
2543
|
},
|
|
2100
2544
|
},
|
|
2101
2545
|
handler: async (params, ctx) => {
|
|
@@ -2139,7 +2583,8 @@ const graphImpactAnalysis = {
|
|
|
2139
2583
|
// ────────────────────────────────────────────────────────────
|
|
2140
2584
|
const rebuildIndex = {
|
|
2141
2585
|
name: 'rebuild_index',
|
|
2142
|
-
description:
|
|
2586
|
+
description:
|
|
2587
|
+
'向量索引重建 — 重新扫描 Recipe 文件并更新向量索引(用于索引过期或新增大量 Recipe 后)。',
|
|
2143
2588
|
parameters: {
|
|
2144
2589
|
type: 'object',
|
|
2145
2590
|
properties: {
|
|
@@ -2162,17 +2607,24 @@ const queryAuditLog = {
|
|
|
2162
2607
|
parameters: {
|
|
2163
2608
|
type: 'object',
|
|
2164
2609
|
properties: {
|
|
2165
|
-
action: {
|
|
2166
|
-
|
|
2167
|
-
|
|
2610
|
+
action: {
|
|
2611
|
+
type: 'string',
|
|
2612
|
+
description: '按操作类型过滤 (create_candidate/approve_candidate/create_guard_rule 等)',
|
|
2613
|
+
},
|
|
2614
|
+
actor: { type: 'string', description: '按操作者过滤' },
|
|
2615
|
+
limit: { type: 'number', description: '返回数量,默认 20' },
|
|
2168
2616
|
},
|
|
2169
2617
|
},
|
|
2170
2618
|
handler: async (params, ctx) => {
|
|
2171
2619
|
const auditLogger = ctx.container.get('auditLogger');
|
|
2172
2620
|
const { action, actor, limit = 20 } = params;
|
|
2173
2621
|
|
|
2174
|
-
if (actor)
|
|
2175
|
-
|
|
2622
|
+
if (actor) {
|
|
2623
|
+
return auditLogger.getByActor(actor, limit);
|
|
2624
|
+
}
|
|
2625
|
+
if (action) {
|
|
2626
|
+
return auditLogger.getByAction(action, limit);
|
|
2627
|
+
}
|
|
2176
2628
|
return auditLogger.getStats();
|
|
2177
2629
|
},
|
|
2178
2630
|
};
|
|
@@ -2182,11 +2634,15 @@ const queryAuditLog = {
|
|
|
2182
2634
|
// ────────────────────────────────────────────────────────────
|
|
2183
2635
|
const loadSkill = {
|
|
2184
2636
|
name: 'load_skill',
|
|
2185
|
-
description:
|
|
2637
|
+
description:
|
|
2638
|
+
'加载指定的 Agent Skill 文档,获取领域操作指南和最佳实践参考。可用于冷启动指南 (autosnippet-coldstart)、语言参考 (autosnippet-reference-swift/objc/jsts/python/java/kotlin/go) 等。',
|
|
2186
2639
|
parameters: {
|
|
2187
2640
|
type: 'object',
|
|
2188
2641
|
properties: {
|
|
2189
|
-
skillName: {
|
|
2642
|
+
skillName: {
|
|
2643
|
+
type: 'string',
|
|
2644
|
+
description: 'Skill 目录名(如 autosnippet-coldstart, autosnippet-reference-swift, autosnippet-reference-go 等)',
|
|
2645
|
+
},
|
|
2190
2646
|
},
|
|
2191
2647
|
required: ['skillName'],
|
|
2192
2648
|
},
|
|
@@ -2201,8 +2657,16 @@ const loadSkill = {
|
|
|
2201
2657
|
return { skillName: params.skillName, source, content };
|
|
2202
2658
|
} catch {
|
|
2203
2659
|
const available = new Set();
|
|
2204
|
-
try {
|
|
2205
|
-
|
|
2660
|
+
try {
|
|
2661
|
+
fs.readdirSync(SKILLS_DIR, { withFileTypes: true })
|
|
2662
|
+
.filter((d) => d.isDirectory())
|
|
2663
|
+
.forEach((d) => available.add(d.name));
|
|
2664
|
+
} catch {}
|
|
2665
|
+
try {
|
|
2666
|
+
fs.readdirSync(PROJECT_SKILLS_DIR, { withFileTypes: true })
|
|
2667
|
+
.filter((d) => d.isDirectory())
|
|
2668
|
+
.forEach((d) => available.add(d.name));
|
|
2669
|
+
} catch {}
|
|
2206
2670
|
return { error: `Skill "${params.skillName}" not found`, availableSkills: [...available] };
|
|
2207
2671
|
}
|
|
2208
2672
|
},
|
|
@@ -2213,14 +2677,18 @@ const loadSkill = {
|
|
|
2213
2677
|
// ────────────────────────────────────────────────────────────
|
|
2214
2678
|
const createSkillTool = {
|
|
2215
2679
|
name: 'create_skill',
|
|
2216
|
-
description:
|
|
2680
|
+
description:
|
|
2681
|
+
'创建项目级 Skill 文档,写入 AutoSnippet/skills/<name>/SKILL.md。Skill 是 Agent 的领域知识增强文档。创建后自动更新编辑器索引。',
|
|
2217
2682
|
parameters: {
|
|
2218
2683
|
type: 'object',
|
|
2219
2684
|
properties: {
|
|
2220
|
-
name:
|
|
2685
|
+
name: {
|
|
2686
|
+
type: 'string',
|
|
2687
|
+
description: 'Skill 名称(kebab-case,如 my-auth-guide),3-64 字符',
|
|
2688
|
+
},
|
|
2221
2689
|
description: { type: 'string', description: 'Skill 一句话描述(写入 frontmatter)' },
|
|
2222
|
-
content:
|
|
2223
|
-
overwrite:
|
|
2690
|
+
content: { type: 'string', description: 'Skill 正文内容(Markdown 格式,不含 frontmatter)' },
|
|
2691
|
+
overwrite: { type: 'boolean', description: '如果同名 Skill 已存在,是否覆盖(默认 false)' },
|
|
2224
2692
|
},
|
|
2225
2693
|
required: ['name', 'description', 'content'],
|
|
2226
2694
|
},
|
|
@@ -2229,7 +2697,11 @@ const createSkillTool = {
|
|
|
2229
2697
|
// 根据 ChatAgent 的 source 推断 createdBy
|
|
2230
2698
|
const createdBy = ctx?.source === 'system' ? 'system-ai' : 'user-ai';
|
|
2231
2699
|
const raw = createSkill(null, { ...params, createdBy });
|
|
2232
|
-
try {
|
|
2700
|
+
try {
|
|
2701
|
+
return JSON.parse(raw);
|
|
2702
|
+
} catch {
|
|
2703
|
+
return { success: false, error: raw };
|
|
2704
|
+
}
|
|
2233
2705
|
},
|
|
2234
2706
|
};
|
|
2235
2707
|
|
|
@@ -2238,7 +2710,8 @@ const createSkillTool = {
|
|
|
2238
2710
|
// ────────────────────────────────────────────────────────────
|
|
2239
2711
|
const suggestSkills = {
|
|
2240
2712
|
name: 'suggest_skills',
|
|
2241
|
-
description:
|
|
2713
|
+
description:
|
|
2714
|
+
'基于项目使用模式分析,推荐创建 Skill。分析 Guard 违规频率、Memory 偏好积累、Recipe 分布缺口、候选积压率。返回推荐列表(含 name/description/rationale/priority),可据此直接调用 create_skill 创建。',
|
|
2242
2715
|
parameters: {
|
|
2243
2716
|
type: 'object',
|
|
2244
2717
|
properties: {},
|
|
@@ -2258,14 +2731,18 @@ const suggestSkills = {
|
|
|
2258
2731
|
// ────────────────────────────────────────────────────────────
|
|
2259
2732
|
const bootstrapKnowledgeTool = {
|
|
2260
2733
|
name: 'bootstrap_knowledge',
|
|
2261
|
-
description:
|
|
2734
|
+
description:
|
|
2735
|
+
'冷启动知识库初始化(纯启发式,不使用 AI): SPM Target 扫描 → 依赖图谱 → Guard 审计 → 9 维度 Candidate 自动创建。支持 Skill 增强维度定义。产出为初稿候选,后续由 DAG pipeline 自动编排 AI 增强(enrich → refine)。',
|
|
2262
2736
|
parameters: {
|
|
2263
2737
|
type: 'object',
|
|
2264
2738
|
properties: {
|
|
2265
2739
|
maxFiles: { type: 'number', description: '最大扫描文件数,默认 500' },
|
|
2266
2740
|
skipGuard: { type: 'boolean', description: '是否跳过 Guard 审计,默认 false' },
|
|
2267
2741
|
contentMaxLines: { type: 'number', description: '每文件读取最大行数,默认 120' },
|
|
2268
|
-
loadSkills: {
|
|
2742
|
+
loadSkills: {
|
|
2743
|
+
type: 'boolean',
|
|
2744
|
+
description: '是否加载 Skills 增强维度定义(推荐开启),默认 true',
|
|
2745
|
+
},
|
|
2269
2746
|
},
|
|
2270
2747
|
},
|
|
2271
2748
|
handler: async (params, ctx) => {
|
|
@@ -2278,7 +2755,7 @@ const bootstrapKnowledgeTool = {
|
|
|
2278
2755
|
skipGuard: params.skipGuard || false,
|
|
2279
2756
|
contentMaxLines: params.contentMaxLines || 120,
|
|
2280
2757
|
loadSkills: params.loadSkills ?? true,
|
|
2281
|
-
}
|
|
2758
|
+
}
|
|
2282
2759
|
);
|
|
2283
2760
|
// bootstrapKnowledge 返回 envelope JSON string,解析提取 data
|
|
2284
2761
|
const parsed = typeof result === 'string' ? JSON.parse(result) : result;
|
|
@@ -2295,11 +2772,12 @@ const bootstrapKnowledgeTool = {
|
|
|
2295
2772
|
// ────────────────────────────────────────────────────────────
|
|
2296
2773
|
const analyzeCode = {
|
|
2297
2774
|
name: 'analyze_code',
|
|
2298
|
-
description:
|
|
2775
|
+
description:
|
|
2776
|
+
'综合分析一段代码:Guard 规范检查 + 相关 Recipe 搜索。一次调用完成完整分析,减少多轮工具调用。',
|
|
2299
2777
|
parameters: {
|
|
2300
2778
|
type: 'object',
|
|
2301
2779
|
properties: {
|
|
2302
|
-
code:
|
|
2780
|
+
code: { type: 'string', description: '待分析的源码' },
|
|
2303
2781
|
language: { type: 'string', description: '编程语言 (swift/objc/javascript 等)' },
|
|
2304
2782
|
filePath: { type: 'string', description: '文件路径(可选,用于上下文)' },
|
|
2305
2783
|
},
|
|
@@ -2321,7 +2799,9 @@ const analyzeCode = {
|
|
|
2321
2799
|
const guardService = ctx.container.get('guardService');
|
|
2322
2800
|
const matches = await guardService.checkCode(code, { language });
|
|
2323
2801
|
return { violationCount: matches.length, violations: matches };
|
|
2324
|
-
} catch {
|
|
2802
|
+
} catch {
|
|
2803
|
+
return { violationCount: 0, violations: [] };
|
|
2804
|
+
}
|
|
2325
2805
|
}
|
|
2326
2806
|
})(),
|
|
2327
2807
|
(async () => {
|
|
@@ -2331,7 +2811,9 @@ const analyzeCode = {
|
|
|
2331
2811
|
const query = code.substring(0, 200).replace(/\n/g, ' ');
|
|
2332
2812
|
const rawResults = await searchEngine.search(query, { limit: 5 });
|
|
2333
2813
|
return { results: rawResults || [], total: rawResults?.length || 0 };
|
|
2334
|
-
} catch {
|
|
2814
|
+
} catch {
|
|
2815
|
+
return { results: [], total: 0 };
|
|
2816
|
+
}
|
|
2335
2817
|
})(),
|
|
2336
2818
|
]);
|
|
2337
2819
|
|
|
@@ -2356,7 +2838,8 @@ const analyzeCode = {
|
|
|
2356
2838
|
// ────────────────────────────────────────────────────────────
|
|
2357
2839
|
const knowledgeOverview = {
|
|
2358
2840
|
name: 'knowledge_overview',
|
|
2359
|
-
description:
|
|
2841
|
+
description:
|
|
2842
|
+
'一次性获取知识库全貌:各类型 Recipe 分布 + 候选状态 + 知识图谱概况 + 质量概览。比分别调用 get_project_stats + search_recipes 更高效。',
|
|
2360
2843
|
parameters: {
|
|
2361
2844
|
type: 'object',
|
|
2362
2845
|
properties: {
|
|
@@ -2374,14 +2857,20 @@ const knowledgeOverview = {
|
|
|
2374
2857
|
try {
|
|
2375
2858
|
const knowledgeService = ctx.container.get('knowledgeService');
|
|
2376
2859
|
return knowledgeService.getStats();
|
|
2377
|
-
} catch {
|
|
2860
|
+
} catch {
|
|
2861
|
+
return null;
|
|
2862
|
+
}
|
|
2378
2863
|
})(),
|
|
2379
2864
|
(async () => {
|
|
2380
|
-
if (!includeTopRecipes)
|
|
2865
|
+
if (!includeTopRecipes) {
|
|
2866
|
+
return null;
|
|
2867
|
+
}
|
|
2381
2868
|
try {
|
|
2382
2869
|
const feedbackCollector = ctx.container.get('feedbackCollector');
|
|
2383
2870
|
return feedbackCollector.getTopRecipes(limit);
|
|
2384
|
-
} catch {
|
|
2871
|
+
} catch {
|
|
2872
|
+
return null;
|
|
2873
|
+
}
|
|
2385
2874
|
})(),
|
|
2386
2875
|
]);
|
|
2387
2876
|
|
|
@@ -2393,9 +2882,13 @@ const knowledgeOverview = {
|
|
|
2393
2882
|
try {
|
|
2394
2883
|
const kgService = ctx.container.get('knowledgeGraphService');
|
|
2395
2884
|
result.knowledgeGraph = kgService.getStats();
|
|
2396
|
-
} catch {
|
|
2885
|
+
} catch {
|
|
2886
|
+
/* KG not available */
|
|
2887
|
+
}
|
|
2397
2888
|
|
|
2398
|
-
if (feedbackResult)
|
|
2889
|
+
if (feedbackResult) {
|
|
2890
|
+
result.topRecipes = feedbackResult;
|
|
2891
|
+
}
|
|
2399
2892
|
|
|
2400
2893
|
const recipeCount = result.recipes?.total || result.recipes?.count || 0;
|
|
2401
2894
|
result._meta = {
|
|
@@ -2412,22 +2905,29 @@ const knowledgeOverview = {
|
|
|
2412
2905
|
// ────────────────────────────────────────────────────────────
|
|
2413
2906
|
const submitWithCheck = {
|
|
2414
2907
|
name: 'submit_with_check',
|
|
2415
|
-
description:
|
|
2908
|
+
description:
|
|
2909
|
+
'安全提交候选:先执行查重检测,无重复则自动提交。一次调用完成 check_duplicate + submit_knowledge。',
|
|
2416
2910
|
parameters: {
|
|
2417
2911
|
type: 'object',
|
|
2418
2912
|
properties: {
|
|
2419
|
-
content:
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2913
|
+
content: {
|
|
2914
|
+
type: 'object',
|
|
2915
|
+
description: '{ markdown: "项目特写 Markdown", pattern: "核心代码 3-8 行,必须语法完整(括号配对、不能以 } 开头或 { 结尾)" }',
|
|
2916
|
+
},
|
|
2917
|
+
title: { type: 'string', description: '候选标题' },
|
|
2918
|
+
description: { type: 'string', description: '中文简述 ≤80 字' },
|
|
2919
|
+
trigger: { type: 'string', description: '@前缀 kebab-case 唯一标识符' },
|
|
2920
|
+
kind: { type: 'string', enum: ['rule', 'pattern', 'fact'] },
|
|
2921
|
+
topicHint: {
|
|
2922
|
+
type: 'string',
|
|
2923
|
+
enum: ['networking', 'ui', 'data', 'architecture', 'conventions'],
|
|
2924
|
+
},
|
|
2425
2925
|
whenClause: { type: 'string', description: '触发场景英文' },
|
|
2426
|
-
doClause:
|
|
2926
|
+
doClause: { type: 'string', description: '正向指令英文' },
|
|
2427
2927
|
dontClause: { type: 'string', description: '反向约束英文' },
|
|
2428
|
-
tags:
|
|
2429
|
-
reasoning:
|
|
2430
|
-
threshold:
|
|
2928
|
+
tags: { type: 'array', items: { type: 'string' } },
|
|
2929
|
+
reasoning: { type: 'object', description: '{ whyStandard, sources, confidence }' },
|
|
2930
|
+
threshold: { type: 'number', description: '相似度阈值,默认 0.7' },
|
|
2431
2931
|
},
|
|
2432
2932
|
required: ['content', 'title', 'trigger', 'kind', 'doClause'],
|
|
2433
2933
|
},
|
|
@@ -2438,21 +2938,34 @@ const submitWithCheck = {
|
|
|
2438
2938
|
const dimMeta = ctx._dimensionMeta;
|
|
2439
2939
|
if (dimMeta && ctx.source === 'system') {
|
|
2440
2940
|
const rejected = _checkDimensionType(dimMeta, params, ctx.logger);
|
|
2441
|
-
if (rejected)
|
|
2941
|
+
if (rejected) {
|
|
2942
|
+
return rejected;
|
|
2943
|
+
}
|
|
2442
2944
|
|
|
2443
|
-
if (!params.tags)
|
|
2444
|
-
|
|
2445
|
-
|
|
2945
|
+
if (!params.tags) {
|
|
2946
|
+
params.tags = [];
|
|
2947
|
+
}
|
|
2948
|
+
if (!params.tags.includes(dimMeta.id)) {
|
|
2949
|
+
params.tags.push(dimMeta.id);
|
|
2950
|
+
}
|
|
2951
|
+
if (!params.tags.includes('bootstrap')) {
|
|
2952
|
+
params.tags.push('bootstrap');
|
|
2953
|
+
}
|
|
2446
2954
|
}
|
|
2447
2955
|
|
|
2448
2956
|
// Step 1: 查重
|
|
2449
2957
|
const threshold = params.threshold || 0.7;
|
|
2450
|
-
const contentObj2 =
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2958
|
+
const contentObj2 =
|
|
2959
|
+
params.content && typeof params.content === 'object'
|
|
2960
|
+
? params.content
|
|
2961
|
+
: { markdown: '', pattern: '' };
|
|
2962
|
+
const cand = {
|
|
2963
|
+
title: params.title || '',
|
|
2964
|
+
summary: params.description || '',
|
|
2965
|
+
code: contentObj2.markdown || contentObj2.pattern || '',
|
|
2966
|
+
};
|
|
2454
2967
|
const similar = findSimilarRecipes(projectRoot, cand, { threshold: 0.5, topK: 5 });
|
|
2455
|
-
const hasDuplicate = similar.some(s => s.similarity >= threshold);
|
|
2968
|
+
const hasDuplicate = similar.some((s) => s.similarity >= threshold);
|
|
2456
2969
|
|
|
2457
2970
|
if (hasDuplicate) {
|
|
2458
2971
|
return {
|
|
@@ -2470,28 +2983,32 @@ const submitWithCheck = {
|
|
|
2470
2983
|
// Step 2: 提交 — 委托给 submit_knowledge handler
|
|
2471
2984
|
try {
|
|
2472
2985
|
const knowledgeService = ctx.container.get('knowledgeService');
|
|
2473
|
-
const reasoning = params.reasoning || {
|
|
2986
|
+
const reasoning = params.reasoning || {
|
|
2987
|
+
whyStandard: '',
|
|
2988
|
+
sources: ['agent'],
|
|
2989
|
+
confidence: 0.7,
|
|
2990
|
+
};
|
|
2474
2991
|
|
|
2475
2992
|
const systemFields = {
|
|
2476
|
-
language:
|
|
2477
|
-
category:
|
|
2993
|
+
language: ctx._projectLanguage || '',
|
|
2994
|
+
category: dimMeta ? DIMENSION_DISPLAY_GROUP[dimMeta.id] || dimMeta.id : 'general',
|
|
2478
2995
|
knowledgeType: dimMeta?.allowedKnowledgeTypes?.[0] || 'code-pattern',
|
|
2479
|
-
source:
|
|
2996
|
+
source: ctx.source === 'system' ? 'bootstrap' : 'agent',
|
|
2480
2997
|
};
|
|
2481
2998
|
|
|
2482
2999
|
const data = {
|
|
2483
3000
|
...systemFields,
|
|
2484
|
-
title:
|
|
3001
|
+
title: params.title || '',
|
|
2485
3002
|
description: params.description || '',
|
|
2486
|
-
tags:
|
|
2487
|
-
trigger:
|
|
2488
|
-
kind:
|
|
2489
|
-
topicHint:
|
|
2490
|
-
whenClause:
|
|
2491
|
-
doClause:
|
|
2492
|
-
dontClause:
|
|
2493
|
-
coreCode:
|
|
2494
|
-
content:
|
|
3003
|
+
tags: params.tags || [],
|
|
3004
|
+
trigger: params.trigger || '',
|
|
3005
|
+
kind: params.kind || 'pattern',
|
|
3006
|
+
topicHint: params.topicHint || '',
|
|
3007
|
+
whenClause: params.whenClause || '',
|
|
3008
|
+
doClause: params.doClause || '',
|
|
3009
|
+
dontClause: params.dontClause || '',
|
|
3010
|
+
coreCode: contentObj2.pattern || '',
|
|
3011
|
+
content: contentObj2,
|
|
2495
3012
|
reasoning,
|
|
2496
3013
|
};
|
|
2497
3014
|
|
|
@@ -2503,9 +3020,10 @@ const submitWithCheck = {
|
|
|
2503
3020
|
similar: similar.length > 0 ? similar : [],
|
|
2504
3021
|
_meta: {
|
|
2505
3022
|
confidence: 'high',
|
|
2506
|
-
hint:
|
|
2507
|
-
|
|
2508
|
-
|
|
3023
|
+
hint:
|
|
3024
|
+
similar.length > 0
|
|
3025
|
+
? `已提交,但有 ${similar.length} 个低相似度匹配。`
|
|
3026
|
+
: '已提交,无重复风险。',
|
|
2509
3027
|
},
|
|
2510
3028
|
};
|
|
2511
3029
|
} catch (err) {
|
|
@@ -2540,12 +3058,14 @@ const getToolDetails = {
|
|
|
2540
3058
|
},
|
|
2541
3059
|
handler: async ({ toolName }, context) => {
|
|
2542
3060
|
const registry = context.container?.get('toolRegistry');
|
|
2543
|
-
if (!registry)
|
|
3061
|
+
if (!registry) {
|
|
3062
|
+
return { error: 'ToolRegistry not available' };
|
|
3063
|
+
}
|
|
2544
3064
|
|
|
2545
3065
|
const schemas = registry.getToolSchemas();
|
|
2546
|
-
const found = schemas.find(t => t.name === toolName);
|
|
3066
|
+
const found = schemas.find((t) => t.name === toolName);
|
|
2547
3067
|
if (!found) {
|
|
2548
|
-
const allNames = schemas.map(t => t.name);
|
|
3068
|
+
const allNames = schemas.map((t) => t.name);
|
|
2549
3069
|
return {
|
|
2550
3070
|
error: `Tool "${toolName}" not found`,
|
|
2551
3071
|
availableTools: allNames,
|
|
@@ -2563,7 +3083,8 @@ const getToolDetails = {
|
|
|
2563
3083
|
// ─── 元工具: 任务规划 ───────────────────────────────────
|
|
2564
3084
|
const planTask = {
|
|
2565
3085
|
name: 'plan_task',
|
|
2566
|
-
description:
|
|
3086
|
+
description:
|
|
3087
|
+
'分析当前任务并制定结构化执行计划。在开始复杂任务前调用此工具可提高执行效率和决策质量。输出将记录到日志供审计,但不会改变实际执行流程。',
|
|
2567
3088
|
parameters: {
|
|
2568
3089
|
type: 'object',
|
|
2569
3090
|
properties: {
|
|
@@ -2611,7 +3132,8 @@ const planTask = {
|
|
|
2611
3132
|
// ─── 元工具: 自我质量审查 ───────────────────────────────
|
|
2612
3133
|
const reviewMyOutput = {
|
|
2613
3134
|
name: 'review_my_output',
|
|
2614
|
-
description:
|
|
3135
|
+
description:
|
|
3136
|
+
'回查本次会话中已提交的候选,检查质量红线是否满足。包括: 项目特写风格、description 泛化措辞、代码示例来源标注、Cursor 交付字段完整性等。返回通过/问题列表。建议在提交完所有候选后调用一次进行自检。',
|
|
2615
3137
|
parameters: {
|
|
2616
3138
|
type: 'object',
|
|
2617
3139
|
properties: {
|
|
@@ -2624,7 +3146,7 @@ const reviewMyOutput = {
|
|
|
2624
3146
|
},
|
|
2625
3147
|
handler: async (params, context) => {
|
|
2626
3148
|
const submitted = (context._sessionToolCalls || []).filter(
|
|
2627
|
-
tc => tc.tool === 'submit_knowledge' || tc.tool === 'submit_with_check'
|
|
3149
|
+
(tc) => tc.tool === 'submit_knowledge' || tc.tool === 'submit_with_check'
|
|
2628
3150
|
);
|
|
2629
3151
|
|
|
2630
3152
|
if (submitted.length === 0) {
|
|
@@ -2653,7 +3175,10 @@ const reviewMyOutput = {
|
|
|
2653
3175
|
candidateIssues.push('特写缺少代码示例,应包含基本用法代码');
|
|
2654
3176
|
}
|
|
2655
3177
|
// 去掉代码块后,剩余描述性文字应足够
|
|
2656
|
-
const proseLength = markdown
|
|
3178
|
+
const proseLength = markdown
|
|
3179
|
+
.replace(/```[\s\S]*?```/g, '')
|
|
3180
|
+
.replace(/[#>\-*`\n]/g, '')
|
|
3181
|
+
.trim().length;
|
|
2657
3182
|
if (proseLength < 50) {
|
|
2658
3183
|
candidateIssues.push('特写缺少项目特点描述,应融合基本用法和项目特点');
|
|
2659
3184
|
}
|
|
@@ -2665,7 +3190,9 @@ const reviewMyOutput = {
|
|
|
2665
3190
|
|
|
2666
3191
|
// 检查 4: description 过短
|
|
2667
3192
|
if (description.length < 15) {
|
|
2668
|
-
candidateIssues.push(
|
|
3193
|
+
candidateIssues.push(
|
|
3194
|
+
`description 过短 (${description.length} 字), 应≥15字并包含具体类名和数字`
|
|
3195
|
+
);
|
|
2669
3196
|
}
|
|
2670
3197
|
|
|
2671
3198
|
// 检查 5: content.markdown 过短(可能是空壳)
|
|
@@ -2680,14 +3207,24 @@ const reviewMyOutput = {
|
|
|
2680
3207
|
}
|
|
2681
3208
|
|
|
2682
3209
|
// 检查 7: Cursor 交付字段
|
|
2683
|
-
if (!p.trigger)
|
|
2684
|
-
|
|
2685
|
-
|
|
3210
|
+
if (!p.trigger) {
|
|
3211
|
+
candidateIssues.push('缺少 trigger 字段');
|
|
3212
|
+
}
|
|
3213
|
+
if (!p.doClause) {
|
|
3214
|
+
candidateIssues.push('缺少 doClause 字段');
|
|
3215
|
+
}
|
|
3216
|
+
if (!p.kind) {
|
|
3217
|
+
candidateIssues.push('缺少 kind 字段');
|
|
3218
|
+
}
|
|
2686
3219
|
|
|
2687
3220
|
if (candidateIssues.length > 0) {
|
|
2688
3221
|
issues.push({ title, issues: candidateIssues });
|
|
2689
3222
|
}
|
|
2690
|
-
checked.push({
|
|
3223
|
+
checked.push({
|
|
3224
|
+
title,
|
|
3225
|
+
passed: candidateIssues.length === 0,
|
|
3226
|
+
issueCount: candidateIssues.length,
|
|
3227
|
+
});
|
|
2691
3228
|
}
|
|
2692
3229
|
|
|
2693
3230
|
if (issues.length === 0) {
|
|
@@ -2699,7 +3236,7 @@ const reviewMyOutput = {
|
|
|
2699
3236
|
}
|
|
2700
3237
|
|
|
2701
3238
|
const issueLines = issues.flatMap(({ title, issues: iss }) =>
|
|
2702
|
-
iss.map(i => `• "${title}": ${i}`)
|
|
3239
|
+
iss.map((i) => `• "${title}": ${i}`)
|
|
2703
3240
|
);
|
|
2704
3241
|
|
|
2705
3242
|
return {
|
|
@@ -2735,7 +3272,8 @@ function _getProjectGraph(ctx) {
|
|
|
2735
3272
|
// ────────────────────────────────────────────────────────────
|
|
2736
3273
|
const getProjectOverview = {
|
|
2737
3274
|
name: 'get_project_overview',
|
|
2738
|
-
description:
|
|
3275
|
+
description:
|
|
3276
|
+
'获取项目的整体结构概览:文件统计、模块列表、入口点、类/协议/Category 数量。' +
|
|
2739
3277
|
'适用场景:了解项目规模和架构布局,规划探索路径。',
|
|
2740
3278
|
parameters: {
|
|
2741
3279
|
type: 'object',
|
|
@@ -2743,7 +3281,9 @@ const getProjectOverview = {
|
|
|
2743
3281
|
},
|
|
2744
3282
|
handler: async (_params, ctx) => {
|
|
2745
3283
|
const graph = _getProjectGraph(ctx);
|
|
2746
|
-
if (!graph)
|
|
3284
|
+
if (!graph) {
|
|
3285
|
+
return 'AST 分析不可用 — ProjectGraph 未构建。请检查 tree-sitter 是否已安装。';
|
|
3286
|
+
}
|
|
2747
3287
|
|
|
2748
3288
|
const o = graph.getOverview();
|
|
2749
3289
|
const lines = [
|
|
@@ -2772,7 +3312,8 @@ const getProjectOverview = {
|
|
|
2772
3312
|
// ────────────────────────────────────────────────────────────
|
|
2773
3313
|
const getClassHierarchy = {
|
|
2774
3314
|
name: 'get_class_hierarchy',
|
|
2775
|
-
description:
|
|
3315
|
+
description:
|
|
3316
|
+
'查看指定类的继承链(向上到根类)和直接子类列表。' +
|
|
2776
3317
|
'传入 className 查看指定类,不传则返回项目中所有根类及其子树。',
|
|
2777
3318
|
parameters: {
|
|
2778
3319
|
type: 'object',
|
|
@@ -2782,28 +3323,31 @@ const getClassHierarchy = {
|
|
|
2782
3323
|
},
|
|
2783
3324
|
handler: async (params, ctx) => {
|
|
2784
3325
|
const graph = _getProjectGraph(ctx);
|
|
2785
|
-
if (!graph)
|
|
3326
|
+
if (!graph) {
|
|
3327
|
+
return 'AST 分析不可用 — ProjectGraph 未构建。';
|
|
3328
|
+
}
|
|
2786
3329
|
|
|
2787
3330
|
const className = params.className || params.class_name;
|
|
2788
3331
|
if (className) {
|
|
2789
3332
|
const chain = graph.getInheritanceChain(className);
|
|
2790
3333
|
const subs = graph.getSubclasses(className);
|
|
2791
|
-
if (chain.length === 0)
|
|
3334
|
+
if (chain.length === 0) {
|
|
3335
|
+
return `未找到类 ${className}`;
|
|
3336
|
+
}
|
|
2792
3337
|
|
|
2793
|
-
const lines = [
|
|
2794
|
-
`🔗 ${className} 继承链:`,
|
|
2795
|
-
` ${chain.join(' → ')}`,
|
|
2796
|
-
];
|
|
3338
|
+
const lines = [`🔗 ${className} 继承链:`, ` ${chain.join(' → ')}`];
|
|
2797
3339
|
if (subs.length > 0) {
|
|
2798
3340
|
lines.push(``, `直接子类 (${subs.length}):`);
|
|
2799
|
-
for (const s of subs)
|
|
3341
|
+
for (const s of subs) {
|
|
3342
|
+
lines.push(` ├── ${s}`);
|
|
3343
|
+
}
|
|
2800
3344
|
}
|
|
2801
3345
|
return lines.join('\n');
|
|
2802
3346
|
}
|
|
2803
3347
|
|
|
2804
3348
|
// 全量: 找出所有根类 (没有父类或父类不在项目中的类)
|
|
2805
3349
|
const allClasses = graph.getAllClassNames();
|
|
2806
|
-
const roots = allClasses.filter(c => {
|
|
3350
|
+
const roots = allClasses.filter((c) => {
|
|
2807
3351
|
const chain = graph.getInheritanceChain(c);
|
|
2808
3352
|
return chain.length <= 1 || !allClasses.includes(chain[1]);
|
|
2809
3353
|
});
|
|
@@ -2815,9 +3359,13 @@ const getClassHierarchy = {
|
|
|
2815
3359
|
for (const d of descendants.slice(0, 5)) {
|
|
2816
3360
|
lines.push(` └── ${d}`);
|
|
2817
3361
|
}
|
|
2818
|
-
if (descendants.length > 5)
|
|
3362
|
+
if (descendants.length > 5) {
|
|
3363
|
+
lines.push(` ... 还有 ${descendants.length - 5} 个`);
|
|
3364
|
+
}
|
|
3365
|
+
}
|
|
3366
|
+
if (roots.length > 30) {
|
|
3367
|
+
lines.push(`... 还有 ${roots.length - 30} 棵树`);
|
|
2819
3368
|
}
|
|
2820
|
-
if (roots.length > 30) lines.push(`... 还有 ${roots.length - 30} 棵树`);
|
|
2821
3369
|
return lines.join('\n');
|
|
2822
3370
|
},
|
|
2823
3371
|
};
|
|
@@ -2837,11 +3385,15 @@ const getClassInfo = {
|
|
|
2837
3385
|
},
|
|
2838
3386
|
handler: async (params, ctx) => {
|
|
2839
3387
|
const graph = _getProjectGraph(ctx);
|
|
2840
|
-
if (!graph)
|
|
3388
|
+
if (!graph) {
|
|
3389
|
+
return 'AST 分析不可用 — ProjectGraph 未构建。';
|
|
3390
|
+
}
|
|
2841
3391
|
|
|
2842
3392
|
const className = params.className || params.class_name;
|
|
2843
3393
|
const info = graph.getClassInfo(className);
|
|
2844
|
-
if (!info)
|
|
3394
|
+
if (!info) {
|
|
3395
|
+
return `未找到类 "${className}"。可以使用 get_project_overview 查看项目中的所有类。`;
|
|
3396
|
+
}
|
|
2845
3397
|
|
|
2846
3398
|
const chain = graph.getInheritanceChain(className);
|
|
2847
3399
|
const cats = graph.getCategoryExtensions(className);
|
|
@@ -2867,8 +3419,8 @@ const getClassInfo = {
|
|
|
2867
3419
|
|
|
2868
3420
|
if (info.methods.length > 0) {
|
|
2869
3421
|
lines.push(``, `── 方法 (${info.methods.length}) ──`);
|
|
2870
|
-
const classMethods = info.methods.filter(m => m.isClassMethod);
|
|
2871
|
-
const instanceMethods = info.methods.filter(m => !m.isClassMethod);
|
|
3422
|
+
const classMethods = info.methods.filter((m) => m.isClassMethod);
|
|
3423
|
+
const instanceMethods = info.methods.filter((m) => !m.isClassMethod);
|
|
2872
3424
|
for (const m of classMethods) {
|
|
2873
3425
|
const cx = m.complexity > 3 ? ` [复杂度:${m.complexity}]` : '';
|
|
2874
3426
|
lines.push(` + ${m.selector} → ${m.returnType}${cx}`);
|
|
@@ -2882,14 +3434,16 @@ const getClassInfo = {
|
|
|
2882
3434
|
if (cats.length > 0) {
|
|
2883
3435
|
lines.push(``, `── Category 扩展 (${cats.length}) ──`);
|
|
2884
3436
|
for (const cat of cats) {
|
|
2885
|
-
const methodNames = cat.methods.map(m => m.selector).join(', ');
|
|
3437
|
+
const methodNames = cat.methods.map((m) => m.selector).join(', ');
|
|
2886
3438
|
lines.push(` ${info.name}(${cat.categoryName}) — ${cat.filePath} — [${methodNames}]`);
|
|
2887
3439
|
}
|
|
2888
3440
|
}
|
|
2889
3441
|
|
|
2890
3442
|
if (subs.length > 0) {
|
|
2891
3443
|
lines.push(``, `── 直接子类 (${subs.length}) ──`);
|
|
2892
|
-
for (const s of subs)
|
|
3444
|
+
for (const s of subs) {
|
|
3445
|
+
lines.push(` ${s}`);
|
|
3446
|
+
}
|
|
2893
3447
|
}
|
|
2894
3448
|
|
|
2895
3449
|
return lines.join('\n');
|
|
@@ -2911,16 +3465,17 @@ const getProtocolInfo = {
|
|
|
2911
3465
|
},
|
|
2912
3466
|
handler: async (params, ctx) => {
|
|
2913
3467
|
const graph = _getProjectGraph(ctx);
|
|
2914
|
-
if (!graph)
|
|
3468
|
+
if (!graph) {
|
|
3469
|
+
return 'AST 分析不可用 — ProjectGraph 未构建。';
|
|
3470
|
+
}
|
|
2915
3471
|
|
|
2916
3472
|
const protocolName = params.protocolName || params.protocol_name;
|
|
2917
3473
|
const info = graph.getProtocolInfo(protocolName);
|
|
2918
|
-
if (!info)
|
|
3474
|
+
if (!info) {
|
|
3475
|
+
return `未找到协议 "${protocolName}"。可以使用 get_project_overview 查看项目中的所有协议。`;
|
|
3476
|
+
}
|
|
2919
3477
|
|
|
2920
|
-
const lines = [
|
|
2921
|
-
`📋 @protocol ${info.name}`,
|
|
2922
|
-
`文件: ${info.filePath}:${info.line}`,
|
|
2923
|
-
];
|
|
3478
|
+
const lines = [`📋 @protocol ${info.name}`, `文件: ${info.filePath}:${info.line}`];
|
|
2924
3479
|
|
|
2925
3480
|
if (info.inherits.length > 0) {
|
|
2926
3481
|
lines.push(`继承: <${info.inherits.join(', ')}>`);
|
|
@@ -2942,7 +3497,9 @@ const getProtocolInfo = {
|
|
|
2942
3497
|
|
|
2943
3498
|
if (info.conformers.length > 0) {
|
|
2944
3499
|
lines.push(``, `── 遵循者 (${info.conformers.length}) ──`);
|
|
2945
|
-
for (const c of info.conformers)
|
|
3500
|
+
for (const c of info.conformers) {
|
|
3501
|
+
lines.push(` ${c}`);
|
|
3502
|
+
}
|
|
2946
3503
|
} else {
|
|
2947
3504
|
lines.push(``, `⚠️ 暂未发现遵循此协议的类`);
|
|
2948
3505
|
}
|
|
@@ -2960,14 +3517,16 @@ const getMethodOverrides = {
|
|
|
2960
3517
|
parameters: {
|
|
2961
3518
|
type: 'object',
|
|
2962
3519
|
properties: {
|
|
2963
|
-
className:
|
|
3520
|
+
className: { type: 'string', description: '定义该方法的基类名 (必填)' },
|
|
2964
3521
|
methodName: { type: 'string', description: '方法名或 selector (必填)' },
|
|
2965
3522
|
},
|
|
2966
3523
|
required: ['className', 'methodName'],
|
|
2967
3524
|
},
|
|
2968
3525
|
handler: async (params, ctx) => {
|
|
2969
3526
|
const graph = _getProjectGraph(ctx);
|
|
2970
|
-
if (!graph)
|
|
3527
|
+
if (!graph) {
|
|
3528
|
+
return 'AST 分析不可用 — ProjectGraph 未构建。';
|
|
3529
|
+
}
|
|
2971
3530
|
|
|
2972
3531
|
const className = params.className || params.class_name;
|
|
2973
3532
|
const methodName = params.methodName || params.method_name;
|
|
@@ -2977,9 +3536,7 @@ const getMethodOverrides = {
|
|
|
2977
3536
|
return `"${className}.${methodName}" 没有在任何子类中被覆写。`;
|
|
2978
3537
|
}
|
|
2979
3538
|
|
|
2980
|
-
const lines = [
|
|
2981
|
-
`🔀 ${className}.${methodName} 的覆写 (${overrides.length} 处):`,
|
|
2982
|
-
];
|
|
3539
|
+
const lines = [`🔀 ${className}.${methodName} 的覆写 (${overrides.length} 处):`];
|
|
2983
3540
|
for (const o of overrides) {
|
|
2984
3541
|
const cx = o.method.complexity > 3 ? ` [复杂度:${o.method.complexity}]` : '';
|
|
2985
3542
|
lines.push(` ${o.className} — ${o.filePath}:${o.method.line}${cx}`);
|
|
@@ -2993,21 +3550,29 @@ const getMethodOverrides = {
|
|
|
2993
3550
|
// ────────────────────────────────────────────────────────────
|
|
2994
3551
|
const getCategoryMap = {
|
|
2995
3552
|
name: 'get_category_map',
|
|
2996
|
-
description:
|
|
3553
|
+
description:
|
|
3554
|
+
'获取指定类或整个项目的 ObjC Category 扩展映射。Category 是 ObjC 的核心模式,了解它有助于发现功能划分。',
|
|
2997
3555
|
parameters: {
|
|
2998
3556
|
type: 'object',
|
|
2999
3557
|
properties: {
|
|
3000
|
-
className: {
|
|
3558
|
+
className: {
|
|
3559
|
+
type: 'string',
|
|
3560
|
+
description: '类名 — 可选, 不填则返回整个项目中有 Category 的类列表',
|
|
3561
|
+
},
|
|
3001
3562
|
},
|
|
3002
3563
|
},
|
|
3003
3564
|
handler: async (params, ctx) => {
|
|
3004
3565
|
const graph = _getProjectGraph(ctx);
|
|
3005
|
-
if (!graph)
|
|
3566
|
+
if (!graph) {
|
|
3567
|
+
return 'AST 分析不可用 — ProjectGraph 未构建。';
|
|
3568
|
+
}
|
|
3006
3569
|
|
|
3007
3570
|
const className = params.className || params.class_name;
|
|
3008
3571
|
if (className) {
|
|
3009
3572
|
const cats = graph.getCategoryExtensions(className);
|
|
3010
|
-
if (cats.length === 0)
|
|
3573
|
+
if (cats.length === 0) {
|
|
3574
|
+
return `"${className}" 没有 Category 扩展。`;
|
|
3575
|
+
}
|
|
3011
3576
|
|
|
3012
3577
|
const lines = [`📂 ${className} 的 Category 扩展 (${cats.length}):`];
|
|
3013
3578
|
for (const cat of cats) {
|
|
@@ -3025,18 +3590,22 @@ const getCategoryMap = {
|
|
|
3025
3590
|
// 全量概览
|
|
3026
3591
|
const allClasses = graph.getAllClassNames();
|
|
3027
3592
|
const withCats = allClasses
|
|
3028
|
-
.map(c => ({ name: c, cats: graph.getCategoryExtensions(c) }))
|
|
3029
|
-
.filter(x => x.cats.length > 0)
|
|
3593
|
+
.map((c) => ({ name: c, cats: graph.getCategoryExtensions(c) }))
|
|
3594
|
+
.filter((x) => x.cats.length > 0)
|
|
3030
3595
|
.sort((a, b) => b.cats.length - a.cats.length);
|
|
3031
3596
|
|
|
3032
|
-
if (withCats.length === 0)
|
|
3597
|
+
if (withCats.length === 0) {
|
|
3598
|
+
return '项目中没有发现 Category 扩展。';
|
|
3599
|
+
}
|
|
3033
3600
|
|
|
3034
3601
|
const lines = [`📂 项目 Category 概览 (${withCats.length} 个类有 Category):`];
|
|
3035
3602
|
for (const { name, cats } of withCats.slice(0, 30)) {
|
|
3036
|
-
const catNames = cats.map(c => c.categoryName).join(', ');
|
|
3603
|
+
const catNames = cats.map((c) => c.categoryName).join(', ');
|
|
3037
3604
|
lines.push(` ${name} — ${cats.length} 个: (${catNames})`);
|
|
3038
3605
|
}
|
|
3039
|
-
if (withCats.length > 30)
|
|
3606
|
+
if (withCats.length > 30) {
|
|
3607
|
+
lines.push(`... 还有 ${withCats.length - 30} 个类`);
|
|
3608
|
+
}
|
|
3040
3609
|
return lines.join('\n');
|
|
3041
3610
|
},
|
|
3042
3611
|
};
|
|
@@ -3047,7 +3616,8 @@ const getCategoryMap = {
|
|
|
3047
3616
|
|
|
3048
3617
|
const getPreviousAnalysis = {
|
|
3049
3618
|
name: 'get_previous_analysis',
|
|
3050
|
-
description:
|
|
3619
|
+
description:
|
|
3620
|
+
'获取前序维度的分析摘要。在 bootstrap 中,每个维度可能有前面维度的分析结果可用。' +
|
|
3051
3621
|
'调用此工具可以获取之前维度产出的候选标题、设计决策等上下文,避免重复分析。' +
|
|
3052
3622
|
'注意: 只有在你认为前序上下文对当前任务有帮助时才调用。',
|
|
3053
3623
|
parameters: {
|
|
@@ -3062,7 +3632,9 @@ const getPreviousAnalysis = {
|
|
|
3062
3632
|
}
|
|
3063
3633
|
|
|
3064
3634
|
const prev = meta.previousAnalysis;
|
|
3065
|
-
if (typeof prev === 'string')
|
|
3635
|
+
if (typeof prev === 'string') {
|
|
3636
|
+
return prev;
|
|
3637
|
+
}
|
|
3066
3638
|
|
|
3067
3639
|
// 格式化前序分析
|
|
3068
3640
|
const lines = ['📋 前序维度分析摘要:'];
|
|
@@ -3092,7 +3664,8 @@ const getPreviousAnalysis = {
|
|
|
3092
3664
|
// ────────────────────────────────────────────────────────────
|
|
3093
3665
|
const noteFinding = {
|
|
3094
3666
|
name: 'note_finding',
|
|
3095
|
-
description:
|
|
3667
|
+
description:
|
|
3668
|
+
'记录一个关键发现到工作记忆的 Scratchpad。在分析过程中发现重要模式、设计决策或事实时调用。' +
|
|
3096
3669
|
'这些发现会在上下文窗口压缩后依然保留,确保分析后期不会遗忘早期重要发现。' +
|
|
3097
3670
|
'建议在发现关键架构模式、核心类职责、重要设计约束时调用。',
|
|
3098
3671
|
parameters: {
|
|
@@ -3100,7 +3673,8 @@ const noteFinding = {
|
|
|
3100
3673
|
properties: {
|
|
3101
3674
|
finding: {
|
|
3102
3675
|
type: 'string',
|
|
3103
|
-
description:
|
|
3676
|
+
description:
|
|
3677
|
+
'关键发现描述 (≤150 字)。应是具体、可验证的陈述,例如 "BDNetworkManager 使用单例模式,所有请求通过其发起"',
|
|
3104
3678
|
},
|
|
3105
3679
|
evidence: {
|
|
3106
3680
|
type: 'string',
|
|
@@ -3135,7 +3709,8 @@ const noteFinding = {
|
|
|
3135
3709
|
// ────────────────────────────────────────────────────────────
|
|
3136
3710
|
const getPreviousEvidence = {
|
|
3137
3711
|
name: 'get_previous_evidence',
|
|
3138
|
-
description:
|
|
3712
|
+
description:
|
|
3713
|
+
'获取前序维度对特定文件/类/模式的分析证据。避免重复搜索和读取已经被其他维度分析过的内容。' +
|
|
3139
3714
|
'当你要搜索某个类名或文件时,先调用此工具看前序维度是否已有发现。',
|
|
3140
3715
|
parameters: {
|
|
3141
3716
|
type: 'object',
|
|
@@ -3157,10 +3732,7 @@ const getPreviousEvidence = {
|
|
|
3157
3732
|
return '没有前序维度的证据可用。';
|
|
3158
3733
|
}
|
|
3159
3734
|
|
|
3160
|
-
const results = episodicMemory.searchEvidence(
|
|
3161
|
-
params.query,
|
|
3162
|
-
params.dimId || undefined
|
|
3163
|
-
);
|
|
3735
|
+
const results = episodicMemory.searchEvidence(params.query, params.dimId || undefined);
|
|
3164
3736
|
|
|
3165
3737
|
if (results.length === 0) {
|
|
3166
3738
|
return `没有找到与 "${params.query}" 相关的前序证据。建议自行搜索。`;
|
|
@@ -3169,7 +3741,9 @@ const getPreviousEvidence = {
|
|
|
3169
3741
|
const lines = [`📋 前序维度证据 (匹配 "${params.query}", ${results.length} 条):`];
|
|
3170
3742
|
for (const r of results.slice(0, 8)) {
|
|
3171
3743
|
lines.push(` 📄 ${r.filePath}`);
|
|
3172
|
-
lines.push(
|
|
3744
|
+
lines.push(
|
|
3745
|
+
` [${r.evidence.dimId}] [${r.evidence.importance || 5}/10] ${r.evidence.finding}`
|
|
3746
|
+
);
|
|
3173
3747
|
}
|
|
3174
3748
|
if (results.length > 8) {
|
|
3175
3749
|
lines.push(` …还有 ${results.length - 8} 条证据`);
|
|
@@ -3183,15 +3757,25 @@ const getPreviousEvidence = {
|
|
|
3183
3757
|
// ────────────────────────────────────────────────────────────
|
|
3184
3758
|
const queryCodeGraph = {
|
|
3185
3759
|
name: 'query_code_graph',
|
|
3186
|
-
description:
|
|
3760
|
+
description:
|
|
3761
|
+
'查询代码实体图谱 (Code Entity Graph)。可查询类继承链、协议遵循者、实体搜索、影响分析等。' +
|
|
3187
3762
|
'图谱包含从 AST 提取的类、协议、Category、模块、设计模式及其关系。',
|
|
3188
3763
|
parameters: {
|
|
3189
3764
|
type: 'object',
|
|
3190
3765
|
properties: {
|
|
3191
3766
|
action: {
|
|
3192
3767
|
type: 'string',
|
|
3193
|
-
enum: [
|
|
3194
|
-
|
|
3768
|
+
enum: [
|
|
3769
|
+
'search',
|
|
3770
|
+
'inheritance_chain',
|
|
3771
|
+
'descendants',
|
|
3772
|
+
'conformances',
|
|
3773
|
+
'impact',
|
|
3774
|
+
'topology',
|
|
3775
|
+
'entity_edges',
|
|
3776
|
+
],
|
|
3777
|
+
description:
|
|
3778
|
+
'查询动作: search=搜索实体, inheritance_chain=继承链, descendants=子类/遵循者, conformances=协议遵循, impact=影响分析, topology=拓扑概览, entity_edges=实体的所有边',
|
|
3195
3779
|
},
|
|
3196
3780
|
entity_id: {
|
|
3197
3781
|
type: 'string',
|
|
@@ -3213,7 +3797,9 @@ const queryCodeGraph = {
|
|
|
3213
3797
|
try {
|
|
3214
3798
|
const { CodeEntityGraph } = await import('./../../service/knowledge/CodeEntityGraph.js');
|
|
3215
3799
|
const db = ctx?.container?.get('database');
|
|
3216
|
-
if (!db)
|
|
3800
|
+
if (!db) {
|
|
3801
|
+
return '代码实体图谱不可用: 数据库未初始化';
|
|
3802
|
+
}
|
|
3217
3803
|
|
|
3218
3804
|
const projectRoot = ctx?.projectRoot || process.env.ASD_PROJECT_DIR || '';
|
|
3219
3805
|
const ceg = new CodeEntityGraph(db, { projectRoot });
|
|
@@ -3225,24 +3811,32 @@ const queryCodeGraph = {
|
|
|
3225
3811
|
type: params.entity_type,
|
|
3226
3812
|
limit: 15,
|
|
3227
3813
|
});
|
|
3228
|
-
if (results.length === 0)
|
|
3814
|
+
if (results.length === 0) {
|
|
3815
|
+
return `未找到匹配 "${params.entity_id}" 的代码实体。`;
|
|
3816
|
+
}
|
|
3229
3817
|
const lines = [`🔍 代码实体搜索 "${params.entity_id}" (${results.length} 条):`];
|
|
3230
3818
|
for (const e of results) {
|
|
3231
|
-
lines.push(
|
|
3819
|
+
lines.push(
|
|
3820
|
+
` • ${e.entityType}: \`${e.name}\`${e.filePath ? ` (${e.filePath}:${e.line || '?'})` : ''}${e.superclass ? ` → ${e.superclass}` : ''}`
|
|
3821
|
+
);
|
|
3232
3822
|
}
|
|
3233
3823
|
return lines.join('\n');
|
|
3234
3824
|
}
|
|
3235
3825
|
|
|
3236
3826
|
case 'inheritance_chain': {
|
|
3237
3827
|
const chain = ceg.getInheritanceChain(params.entity_id, maxDepth);
|
|
3238
|
-
if (chain.length <= 1)
|
|
3828
|
+
if (chain.length <= 1) {
|
|
3829
|
+
return `\`${params.entity_id}\` 没有已知的继承关系。`;
|
|
3830
|
+
}
|
|
3239
3831
|
return `📐 继承链: \`${chain.join(' → ')}\``;
|
|
3240
3832
|
}
|
|
3241
3833
|
|
|
3242
3834
|
case 'descendants': {
|
|
3243
3835
|
const type = params.entity_type || 'class';
|
|
3244
3836
|
const desc = ceg.getDescendants(params.entity_id, type, maxDepth);
|
|
3245
|
-
if (desc.length === 0)
|
|
3837
|
+
if (desc.length === 0) {
|
|
3838
|
+
return `\`${params.entity_id}\` 没有已知的子类/遵循者。`;
|
|
3839
|
+
}
|
|
3246
3840
|
const lines = [`📊 ${params.entity_id} 的后代 (${desc.length}):`];
|
|
3247
3841
|
for (const d of desc.slice(0, 20)) {
|
|
3248
3842
|
lines.push(` ${' '.repeat(d.depth - 1)}└─ \`${d.id}\` (${d.type}, ${d.relation})`);
|
|
@@ -3252,14 +3846,18 @@ const queryCodeGraph = {
|
|
|
3252
3846
|
|
|
3253
3847
|
case 'conformances': {
|
|
3254
3848
|
const protos = ceg.getConformances(params.entity_id);
|
|
3255
|
-
if (protos.length === 0)
|
|
3256
|
-
|
|
3849
|
+
if (protos.length === 0) {
|
|
3850
|
+
return `\`${params.entity_id}\` 没有已知的协议遵循。`;
|
|
3851
|
+
}
|
|
3852
|
+
return `📋 \`${params.entity_id}\` 遵循: ${protos.map((p) => `\`${p}\``).join(', ')}`;
|
|
3257
3853
|
}
|
|
3258
3854
|
|
|
3259
3855
|
case 'impact': {
|
|
3260
3856
|
const type = params.entity_type || 'class';
|
|
3261
3857
|
const impact = ceg.getImpactRadius(params.entity_id, type, maxDepth);
|
|
3262
|
-
if (impact.length === 0)
|
|
3858
|
+
if (impact.length === 0) {
|
|
3859
|
+
return `修改 \`${params.entity_id}\` 没有检测到直接影响。`;
|
|
3860
|
+
}
|
|
3263
3861
|
const lines = [`⚡ 修改 \`${params.entity_id}\` 的影响范围 (${impact.length}):`];
|
|
3264
3862
|
for (const i of impact.slice(0, 20)) {
|
|
3265
3863
|
lines.push(` ${' '.repeat(i.depth - 1)}⬆ \`${i.id}\` (${i.type}, via ${i.relation})`);
|
|
@@ -3269,7 +3867,9 @@ const queryCodeGraph = {
|
|
|
3269
3867
|
|
|
3270
3868
|
case 'topology': {
|
|
3271
3869
|
const topo = ceg.getTopology();
|
|
3272
|
-
if (topo.totalEntities === 0)
|
|
3870
|
+
if (topo.totalEntities === 0) {
|
|
3871
|
+
return '代码实体图谱为空。需先执行 Bootstrap。';
|
|
3872
|
+
}
|
|
3273
3873
|
const lines = ['📈 代码实体图谱概览:'];
|
|
3274
3874
|
lines.push(' 实体:');
|
|
3275
3875
|
for (const [type, count] of Object.entries(topo.entities)) {
|
|
@@ -3289,7 +3889,9 @@ const queryCodeGraph = {
|
|
|
3289
3889
|
const type = params.entity_type || 'class';
|
|
3290
3890
|
const edges = ceg.getEntityEdges(params.entity_id, type);
|
|
3291
3891
|
const total = edges.outgoing.length + edges.incoming.length;
|
|
3292
|
-
if (total === 0)
|
|
3892
|
+
if (total === 0) {
|
|
3893
|
+
return `\`${params.entity_id}\` 没有已知的图谱边。`;
|
|
3894
|
+
}
|
|
3293
3895
|
const lines = [`🔗 \`${params.entity_id}\` 的关系 (${total} 条):`];
|
|
3294
3896
|
if (edges.outgoing.length > 0) {
|
|
3295
3897
|
lines.push(' 出边:');
|