autosnippet 2.9.0 → 2.11.0
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 +12 -12
- package/bin/cli.js +53 -40
- package/config/constitution.yaml +9 -2
- package/dashboard/dist/assets/{icons-CH-H9x0E.js → icons-D4IWpDIk.js} +105 -100
- package/dashboard/dist/assets/index-CWBNcF9z.css +1 -0
- package/dashboard/dist/assets/index-DHtzhbuG.js +120 -0
- package/dashboard/dist/index.html +3 -3
- package/lib/cli/AiScanService.js +35 -36
- package/lib/cli/KnowledgeSyncService.js +345 -0
- package/lib/cli/SetupService.js +8 -26
- package/lib/cli/UpgradeService.js +28 -0
- package/lib/core/gateway/GatewayActionRegistry.js +48 -58
- package/lib/domain/index.js +16 -11
- package/lib/domain/knowledge/KnowledgeEntry.js +289 -0
- package/lib/domain/knowledge/KnowledgeRepository.js +123 -0
- package/lib/domain/knowledge/Lifecycle.js +99 -0
- package/lib/domain/knowledge/index.js +27 -0
- package/lib/domain/knowledge/values/Constraints.js +128 -0
- package/lib/domain/knowledge/values/Content.js +69 -0
- package/lib/domain/knowledge/values/Quality.js +81 -0
- package/lib/domain/knowledge/values/Reasoning.js +70 -0
- package/lib/domain/knowledge/values/Relations.js +142 -0
- package/lib/domain/knowledge/values/Stats.js +72 -0
- package/lib/domain/knowledge/values/index.js +9 -0
- package/lib/external/ai/AiProvider.js +85 -11
- package/lib/external/mcp/McpServer.js +7 -5
- package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +18 -2
- package/lib/external/mcp/handlers/bootstrap.js +116 -11
- package/lib/external/mcp/handlers/browse.js +76 -73
- package/lib/external/mcp/handlers/candidate.js +26 -275
- package/lib/external/mcp/handlers/guard.js +2 -0
- package/lib/external/mcp/handlers/knowledge.js +267 -0
- package/lib/external/mcp/handlers/structure.js +25 -23
- package/lib/external/mcp/handlers/system.js +10 -12
- package/lib/external/mcp/tools.js +134 -140
- package/lib/http/HttpServer.js +14 -8
- package/lib/http/routes/ai.js +4 -3
- package/lib/http/routes/extract.js +48 -4
- package/lib/http/routes/knowledge.js +246 -0
- package/lib/http/routes/search.js +12 -17
- package/lib/infrastructure/database/migrations/016_unified_knowledge_entries.js +395 -0
- package/lib/infrastructure/database/migrations/017_camelcase_knowledge_entries.js +107 -0
- package/lib/infrastructure/external/XcodeAutomation.js +187 -103
- package/lib/injection/ServiceContainer.js +69 -60
- package/lib/repository/knowledge/KnowledgeRepository.impl.js +338 -0
- package/lib/service/automation/DirectiveDetector.js +2 -3
- package/lib/service/automation/FileWatcher.js +59 -28
- package/lib/service/automation/XcodeIntegration.js +931 -156
- package/lib/service/automation/handlers/AlinkHandler.js +5 -4
- package/lib/service/automation/handlers/CreateHandler.js +53 -19
- package/lib/service/automation/handlers/DraftHandler.js +1 -1
- package/lib/service/automation/handlers/GuardHandler.js +183 -20
- package/lib/service/automation/handlers/SearchHandler.js +25 -22
- package/lib/service/candidate/SimilarityService.js +2 -2
- package/lib/service/chat/AnalystAgent.js +9 -0
- package/lib/service/chat/CandidateGuardrail.js +22 -11
- package/lib/service/chat/ChatAgent.js +132 -54
- package/lib/service/chat/ContextWindow.js +5 -5
- package/lib/service/chat/HandoffProtocol.js +1 -0
- package/lib/service/chat/ProducerAgent.js +40 -13
- package/lib/service/chat/ReasoningLayer.js +854 -0
- package/lib/service/chat/ReasoningTrace.js +329 -0
- package/lib/service/chat/tools.js +308 -205
- package/lib/service/cursor/CursorDeliveryPipeline.js +279 -0
- package/lib/service/cursor/KnowledgeCompressor.js +87 -0
- package/lib/service/cursor/RulesGenerator.js +168 -0
- package/lib/service/cursor/SkillsSyncer.js +268 -0
- package/lib/service/cursor/TokenBudget.js +58 -0
- package/lib/service/cursor/TopicClassifier.js +141 -0
- package/lib/service/guard/GuardCheckEngine.js +99 -10
- package/lib/service/guard/GuardService.js +57 -46
- package/lib/service/knowledge/ConfidenceRouter.js +159 -0
- package/lib/service/knowledge/KnowledgeFileWriter.js +595 -0
- package/lib/service/knowledge/KnowledgeService.js +802 -0
- package/lib/service/recipe/RecipeParser.js +3 -12
- package/lib/service/search/SearchEngine.js +67 -22
- package/lib/service/skills/SignalCollector.js +14 -9
- package/lib/service/skills/SkillAdvisor.js +13 -11
- package/lib/service/snippet/SnippetFactory.js +5 -5
- package/lib/service/spm/SpmService.js +15 -48
- package/lib/shared/RecipeReadinessChecker.js +6 -11
- package/package.json +1 -1
- package/scripts/install-cursor-skill.js +0 -6
- package/scripts/migrate-md-to-knowledge.mjs +364 -0
- package/skills/autosnippet-analysis/SKILL.md +15 -7
- package/skills/autosnippet-candidates/SKILL.md +8 -8
- package/skills/autosnippet-coldstart/SKILL.md +8 -4
- package/skills/autosnippet-concepts/SKILL.md +7 -6
- package/skills/autosnippet-create/SKILL.md +13 -13
- package/skills/autosnippet-intent/SKILL.md +3 -2
- package/skills/autosnippet-lifecycle/SKILL.md +5 -5
- package/skills/autosnippet-recipes/SKILL.md +18 -6
- package/templates/constitution.yaml +1 -1
- package/templates/copilot-instructions.md +6 -6
- package/templates/recipes-setup/README.md +3 -3
- package/dashboard/dist/assets/index-CqJRvYRL.js +0 -197
- package/dashboard/dist/assets/index-DICm9PNa.css +0 -1
- package/lib/cli/CandidateSyncService.js +0 -261
- package/lib/cli/SyncService.js +0 -356
- package/lib/domain/candidate/Candidate.js +0 -196
- package/lib/domain/candidate/CandidateRepository.js +0 -107
- package/lib/domain/candidate/Reasoning.js +0 -52
- package/lib/domain/recipe/Recipe.js +0 -421
- package/lib/domain/recipe/RecipeRepository.js +0 -54
- package/lib/domain/types/CandidateStatus.js +0 -52
- package/lib/http/routes/candidates.js +0 -559
- package/lib/http/routes/recipes.js +0 -397
- package/lib/repository/candidate/CandidateRepository.impl.js +0 -230
- package/lib/repository/recipe/RecipeRepository.impl.js +0 -498
- package/lib/service/candidate/CandidateAggregator.js +0 -52
- package/lib/service/candidate/CandidateFileWriter.js +0 -383
- package/lib/service/candidate/CandidateService.js +0 -1001
- package/lib/service/recipe/RecipeFileWriter.js +0 -514
- package/lib/service/recipe/RecipeService.js +0 -786
- package/lib/service/recipe/RecipeStatsTracker.js +0 -148
|
@@ -174,6 +174,67 @@ export class AiProvider {
|
|
|
174
174
|
? `\n# Code Structure Analysis (AST)\nThe following is a Tree-sitter AST analysis of the project. Use this structural context to better understand class hierarchies, design patterns, and code quality when extracting recipes:\n\n${options.astContext.substring(0, 3000)}\n`
|
|
175
175
|
: '';
|
|
176
176
|
|
|
177
|
+
// comprehensive 模式:全量分析整个文件,不跳过任何有意义的方法
|
|
178
|
+
if (options.comprehensive) {
|
|
179
|
+
return `# Role
|
|
180
|
+
You are a ${langProfile.role} performing a **comprehensive full-file analysis**.
|
|
181
|
+
|
|
182
|
+
# Goal
|
|
183
|
+
Thoroughly analyze ALL code in "${targetName}" and create Recipe entries for **every significant method, function, or code block**.
|
|
184
|
+
This is a full-file analysis — do NOT skip methods just because they seem "simple" or "standard".
|
|
185
|
+
${skillSection}${astSection}
|
|
186
|
+
|
|
187
|
+
# What to extract
|
|
188
|
+
- **Every** complete method/function with 5+ lines of implementation
|
|
189
|
+
- Initialization and configuration methods (init, viewDidLoad, setup, configure)
|
|
190
|
+
- Event handlers and action methods
|
|
191
|
+
- Data processing and business logic
|
|
192
|
+
- Protocol/delegate implementations
|
|
193
|
+
- ANY code block that a developer might reference, learn from, or reuse
|
|
194
|
+
|
|
195
|
+
# Extraction Rules
|
|
196
|
+
- Extract **BROADLY** — include all meaningful code units, not just "clever" or "novel" patterns
|
|
197
|
+
- Each recipe must be a **complete, standalone** code unit with full signature and body
|
|
198
|
+
- Put the complete code in \`content.pattern\`, and a meaningful project writeup in \`content.markdown\`
|
|
199
|
+
- Preserve the file's actual code. Use \`<#placeholder#>\` ONLY for literal strings/values a developer would customize
|
|
200
|
+
- Every recipe must be traceable to real code in the file. Do NOT invent code
|
|
201
|
+
- Include relevant \`headers\` (import/require lines) that the code depends on
|
|
202
|
+
- You **MUST** extract at least ONE recipe — every source file has something worth capturing
|
|
203
|
+
- For each recipe, provide a concise \`doClause\` (imperative sentence) and a \`topicHint\` group label
|
|
204
|
+
|
|
205
|
+
${langProfile.extractionExamples}
|
|
206
|
+
|
|
207
|
+
# Output (JSON Array)
|
|
208
|
+
Each item MUST use the following V3 KnowledgeEntry structure:
|
|
209
|
+
- title (string): Descriptive English name
|
|
210
|
+
- description (string): 2-3 sentences explaining what this code does, written as a project-specific guide
|
|
211
|
+
- trigger (string): @shortcut (kebab-case, e.g. "@url-parser")
|
|
212
|
+
- kind (string): "rule" | "pattern" | "fact" — rule = always-do/never-do, pattern = reusable recipe with code, fact = reference info
|
|
213
|
+
- knowledgeType (string): "code-pattern" | "architecture" | "api-usage" | "naming-convention" | "error-handling" | "performance" | "best-practice"
|
|
214
|
+
- complexity (string): "basic" | "intermediate" | "advanced"
|
|
215
|
+
- scope (string): "universal" | "project-specific" | "team-convention"
|
|
216
|
+
- category: ${langProfile.categories}
|
|
217
|
+
- language: "${langProfile.primaryLanguage}"
|
|
218
|
+
- content (object): { "pattern": "<complete function/method/class from the file>", "markdown": "<project-specific writeup: what this pattern does, when to use it, key design decisions>", "rationale": "<why this pattern is designed this way — design trade-offs, alternatives considered>" }
|
|
219
|
+
- reasoning (object): { "whyStandard": "<why this is a standard/best-practice worth following>", "sources": ["<source file names>"], "confidence": <0.0-1.0> }
|
|
220
|
+
- headers (string[]): Required import/require lines
|
|
221
|
+
- tags (string[]): Search keywords
|
|
222
|
+
- doClause (string): One-sentence imperative: what to do (e.g. "Use dependency injection via constructor")
|
|
223
|
+
- dontClause (string): What NOT to do (e.g. "Don't instantiate services with new directly")
|
|
224
|
+
- whenClause (string): When this pattern applies (e.g. "When creating a new ViewController subclass")
|
|
225
|
+
- topicHint (string): Group label for related patterns (e.g. "Networking", "UI-Layout", "Error-Handling")
|
|
226
|
+
- coreCode (string): Minimal 3-10 line code skeleton that captures the essence
|
|
227
|
+
- constraints (object, optional): { "preconditions": ["<conditions that must be true before using this pattern>"], "sideEffects": ["<observable side effects of using this code>"], "boundaries": ["<usage limitations or scope restrictions>"] }
|
|
228
|
+
- aiInsight (string, optional): One-sentence concise insight — the single most important takeaway about this code pattern
|
|
229
|
+
|
|
230
|
+
IMPORTANT: content.pattern must contain the COMPLETE source code. content.markdown must be a meaningful project-specific writeup, NOT just a copy of description. content.rationale must explain WHY this pattern is designed this way. reasoning.whyStandard must explain WHY this pattern matters.
|
|
231
|
+
|
|
232
|
+
Return ONLY a JSON array. Do NOT return an empty array.
|
|
233
|
+
|
|
234
|
+
Files Content:
|
|
235
|
+
${files}`;
|
|
236
|
+
}
|
|
237
|
+
|
|
177
238
|
return `# Role
|
|
178
239
|
You are a ${langProfile.role} extracting production-quality reusable code patterns.
|
|
179
240
|
|
|
@@ -197,24 +258,37 @@ For each function/method/class in the file, ask: "Would a developer benefit from
|
|
|
197
258
|
${langProfile.extractionExamples}
|
|
198
259
|
|
|
199
260
|
# Rules
|
|
200
|
-
1.
|
|
201
|
-
2.
|
|
202
|
-
3.
|
|
203
|
-
4.
|
|
261
|
+
1. \`content.pattern\` must contain a **complete function/method or logical unit** — include the signature and full body
|
|
262
|
+
2. \`content.markdown\` must be a meaningful project-specific writeup explaining what this code does and when to use it
|
|
263
|
+
3. Preserve the file's actual code. Use \`<#placeholder#>\` ONLY for literal strings/values a developer would customize
|
|
264
|
+
4. Every recipe must be traceable to real code in the file. Do NOT invent code
|
|
265
|
+
5. Include relevant \`headers\` (import/require lines) that the code depends on
|
|
266
|
+
6. Every recipe must have a concise \`doClause\` and a \`topicHint\` group label
|
|
204
267
|
|
|
205
268
|
# Output (JSON Array)
|
|
206
|
-
Each item:
|
|
269
|
+
Each item MUST use the following V3 KnowledgeEntry structure:
|
|
207
270
|
- title (string): Descriptive English name
|
|
208
|
-
-
|
|
209
|
-
-
|
|
210
|
-
-
|
|
271
|
+
- description (string): 2-3 sentences explaining what this code does, written as a project-specific guide
|
|
272
|
+
- trigger (string): @shortcut (kebab-case, e.g. "@url-parser")
|
|
273
|
+
- kind (string): "rule" | "pattern" | "fact" — rule = always-do/never-do, pattern = reusable recipe with code, fact = reference info
|
|
274
|
+
- knowledgeType (string): "code-pattern" | "architecture" | "api-usage" | "naming-convention" | "error-handling" | "performance" | "best-practice"
|
|
275
|
+
- complexity (string): "basic" | "intermediate" | "advanced"
|
|
276
|
+
- scope (string): "universal" | "project-specific" | "team-convention"
|
|
211
277
|
- category: ${langProfile.categories}
|
|
212
278
|
- language: "${langProfile.primaryLanguage}"
|
|
213
|
-
-
|
|
279
|
+
- content (object): { "pattern": "<complete function/method/class from the file>", "markdown": "<project-specific writeup: what this pattern does, when to use it, key design decisions>", "rationale": "<why this pattern is designed this way — design trade-offs, alternatives considered>" }
|
|
280
|
+
- reasoning (object): { "whyStandard": "<why this is a standard/best-practice worth following>", "sources": ["<source file names>"], "confidence": <0.0-1.0> }
|
|
214
281
|
- headers (string[]): Required import/require lines
|
|
215
282
|
- tags (string[]): Search keywords
|
|
216
|
-
-
|
|
217
|
-
-
|
|
283
|
+
- doClause (string): One-sentence imperative: what to do (e.g. "Use dependency injection via constructor")
|
|
284
|
+
- dontClause (string): What NOT to do (e.g. "Don't instantiate services with new directly")
|
|
285
|
+
- whenClause (string): When this pattern applies (e.g. "When creating a new ViewController subclass")
|
|
286
|
+
- topicHint (string): Group label (e.g. "Networking", "UI-Layout")
|
|
287
|
+
- coreCode (string): Minimal 3-10 line code skeleton that captures the essence
|
|
288
|
+
- constraints (object, optional): { "preconditions": ["<conditions that must be true before using this pattern>"], "sideEffects": ["<observable side effects of using this code>"], "boundaries": ["<usage limitations or scope restrictions>"] }
|
|
289
|
+
- aiInsight (string, optional): One-sentence concise insight — the single most important takeaway about this code pattern
|
|
290
|
+
|
|
291
|
+
IMPORTANT: content.pattern must contain the COMPLETE source code. content.markdown must be a meaningful project-specific writeup, NOT just a copy of description. content.rationale must explain WHY this pattern is designed this way. reasoning.whyStandard must explain WHY this pattern matters.
|
|
218
292
|
|
|
219
293
|
Return ONLY a JSON array. If no meaningful patterns found, return [].
|
|
220
294
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Model Context Protocol (stdio transport)
|
|
5
5
|
* 提供给 IDE AI Agent (Cursor/VSCode Copilot) 的工具集
|
|
6
|
-
*
|
|
6
|
+
* 38 工具,全部基于 V2 服务层,不依赖 V1
|
|
7
7
|
* Gateway 权限 gating: 写操作经过 Gateway 权限/宪法/审计检查
|
|
8
8
|
*
|
|
9
9
|
* 本文件仅包含服务编排层(初始化、路由、Gateway gating、生命周期)。
|
|
@@ -31,6 +31,7 @@ import * as candidateHandlers from './handlers/candidate.js';
|
|
|
31
31
|
import * as guardHandlers from './handlers/guard.js';
|
|
32
32
|
import * as bootstrapHandlers from './handlers/bootstrap.js';
|
|
33
33
|
import * as skillHandlers from './handlers/skill.js';
|
|
34
|
+
import * as knowledgeHandlers from './handlers/knowledge.js';
|
|
34
35
|
|
|
35
36
|
// ─── McpServer 类 ─────────────────────────────────────────────
|
|
36
37
|
|
|
@@ -140,12 +141,9 @@ export class McpServer {
|
|
|
140
141
|
case 'autosnippet_graph_impact': return structureHandlers.graphImpact(ctx, args);
|
|
141
142
|
case 'autosnippet_graph_path': return structureHandlers.graphPath(ctx, args);
|
|
142
143
|
case 'autosnippet_graph_stats': return structureHandlers.graphStats(ctx);
|
|
143
|
-
// 候选校验 &
|
|
144
|
+
// 候选校验 & AI 补全(提交已移至 V3 knowledge handlers)
|
|
144
145
|
case 'autosnippet_validate_candidate': return candidateHandlers.validateCandidate(ctx, args);
|
|
145
146
|
case 'autosnippet_check_duplicate': return candidateHandlers.checkDuplicate(ctx, args);
|
|
146
|
-
case 'autosnippet_submit_candidate': return candidateHandlers.submitSingle(ctx, args);
|
|
147
|
-
case 'autosnippet_submit_candidates': return candidateHandlers.submitBatch(ctx, args);
|
|
148
|
-
case 'autosnippet_submit_draft_recipes': return candidateHandlers.submitDrafts(ctx, args);
|
|
149
147
|
case 'autosnippet_enrich_candidates': return candidateHandlers.enrichCandidates(ctx, args);
|
|
150
148
|
// Guard & 扫描
|
|
151
149
|
case 'autosnippet_guard_check': return guardHandlers.guardCheck(ctx, args);
|
|
@@ -161,6 +159,10 @@ export class McpServer {
|
|
|
161
159
|
case 'autosnippet_delete_skill': return skillHandlers.deleteSkill(ctx, args);
|
|
162
160
|
case 'autosnippet_update_skill': return skillHandlers.updateSkill(ctx, args);
|
|
163
161
|
case 'autosnippet_suggest_skills': return skillHandlers.suggestSkills(ctx);
|
|
162
|
+
// V3 知识条目
|
|
163
|
+
case 'autosnippet_submit_knowledge': return knowledgeHandlers.submitKnowledge(ctx, args);
|
|
164
|
+
case 'autosnippet_submit_knowledge_batch': return knowledgeHandlers.submitKnowledgeBatch(ctx, args);
|
|
165
|
+
case 'autosnippet_knowledge_lifecycle': return knowledgeHandlers.knowledgeLifecycle(ctx, args);
|
|
164
166
|
default: throw new Error(`Unknown tool: ${name}`);
|
|
165
167
|
}
|
|
166
168
|
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*
|
|
6
6
|
* 1. Analyst Agent 自由探索代码 (AST 工具 + 文件搜索)
|
|
7
7
|
* 2. HandoffProtocol 质量门控
|
|
8
|
-
* 3. Producer Agent 格式化输出 (
|
|
8
|
+
* 3. Producer Agent 格式化输出 (submit_knowledge)
|
|
9
9
|
* 4. TierScheduler 分层并行执行
|
|
10
10
|
*
|
|
11
11
|
* @module pipeline/orchestrator
|
|
@@ -412,7 +412,7 @@ export async function fillDimensionsV3(fillContext) {
|
|
|
412
412
|
// 记录到 DimensionContext
|
|
413
413
|
for (const tc of (producerResult.toolCalls || [])) {
|
|
414
414
|
const tool = tc.tool || tc.name;
|
|
415
|
-
if (tool === '
|
|
415
|
+
if (tool === 'submit_knowledge' || tool === 'submit_with_check') {
|
|
416
416
|
dimContext.addSubmittedCandidate(dimId, {
|
|
417
417
|
title: tc.params?.title || '',
|
|
418
418
|
subTopic: tc.params?.category || '',
|
|
@@ -652,6 +652,22 @@ export async function fillDimensionsV3(fillContext) {
|
|
|
652
652
|
// 释放文件缓存
|
|
653
653
|
allFiles = null;
|
|
654
654
|
chatAgent.setFileCache(null);
|
|
655
|
+
|
|
656
|
+
// ── Cursor Delivery: 生成 4 通道交付物料 ──
|
|
657
|
+
try {
|
|
658
|
+
const { getServiceContainer } = await import('../../../../../injection/ServiceContainer.js');
|
|
659
|
+
const container = getServiceContainer();
|
|
660
|
+
if (container.services.cursorDeliveryPipeline) {
|
|
661
|
+
const pipeline = container.get('cursorDeliveryPipeline');
|
|
662
|
+
const deliveryResult = await pipeline.deliver();
|
|
663
|
+
logger.info(`[Bootstrap-v3] 🚀 Cursor Delivery complete — ` +
|
|
664
|
+
`A: ${deliveryResult.channelA.rulesCount} rules, ` +
|
|
665
|
+
`B: ${deliveryResult.channelB.topicCount} topics, ` +
|
|
666
|
+
`C: ${deliveryResult.channelC.synced} skills`);
|
|
667
|
+
}
|
|
668
|
+
} catch (deliveryErr) {
|
|
669
|
+
logger.warn(`[Bootstrap-v3] Cursor Delivery failed (non-blocking): ${deliveryErr.message}`);
|
|
670
|
+
}
|
|
655
671
|
}
|
|
656
672
|
|
|
657
673
|
export { clearCheckpoints };
|
|
@@ -392,7 +392,7 @@ export async function bootstrapKnowledge(ctx, args) {
|
|
|
392
392
|
skillWorthyDimensions: dimensions.filter(d => d.skillWorthy).map(d => d.id),
|
|
393
393
|
candidateOnlyDimensions: dimensions.filter(d => !d.skillWorthy).map(d => d.id),
|
|
394
394
|
candidateRequiredFields: ['title', 'code', 'language', 'category', 'knowledgeType', 'reasoning'],
|
|
395
|
-
submissionTool: '
|
|
395
|
+
submissionTool: 'autosnippet_submit_knowledge_batch',
|
|
396
396
|
expectedOutput: '候选知识(微观代码维度:code-pattern/best-practice/event-and-data-flow + 深度扫描:objc-deep-scan/category-scan)+ 6 个 Project Skills(宏观叙事维度:code-standard/architecture/project-profile/agent-guidelines + 深度扫描:objc-deep-scan/category-scan)',
|
|
397
397
|
},
|
|
398
398
|
|
|
@@ -423,7 +423,7 @@ export async function bootstrapKnowledge(ctx, args) {
|
|
|
423
423
|
'== 完成后可执行的后续操作 ==',
|
|
424
424
|
'1. 调用 autosnippet_enrich_candidates(candidateIds) 补全候选缺失字段',
|
|
425
425
|
'2. 调用 autosnippet_bootstrap_refine() 对候选进行 AI 精炼',
|
|
426
|
-
'3. 使用
|
|
426
|
+
'3. 使用 autosnippet_submit_knowledge_batch 手动提交更多知识条目',
|
|
427
427
|
'4. 使用 autosnippet_load_skill 加载自动生成的 Project Skills',
|
|
428
428
|
'',
|
|
429
429
|
'== 宏观维度 → Project Skills ==',
|
|
@@ -520,7 +520,7 @@ export async function bootstrapKnowledge(ctx, args) {
|
|
|
520
520
|
/**
|
|
521
521
|
* bootstrapRefine — Phase 6 AI 润色
|
|
522
522
|
*
|
|
523
|
-
* 对 Bootstrap Phase 5
|
|
523
|
+
* 对 Bootstrap Phase 5 产出的知识条目进行 AI 二次精炼:
|
|
524
524
|
* - 改善模板化描述 → 更自然精准
|
|
525
525
|
* - 补充高阶架构洞察
|
|
526
526
|
* - 推断并填充 relations 关联
|
|
@@ -531,7 +531,7 @@ export async function bootstrapKnowledge(ctx, args) {
|
|
|
531
531
|
*/
|
|
532
532
|
export async function bootstrapRefine(ctx, args) {
|
|
533
533
|
const t0 = Date.now();
|
|
534
|
-
const
|
|
534
|
+
const knowledgeService = ctx.container.get('knowledgeService');
|
|
535
535
|
const aiProvider = ctx.container.get('aiProvider');
|
|
536
536
|
|
|
537
537
|
if (!aiProvider) {
|
|
@@ -545,17 +545,122 @@ export async function bootstrapRefine(ctx, args) {
|
|
|
545
545
|
onProgress = (eventName, data) => taskManager.emitProgress(eventName, data);
|
|
546
546
|
} catch { /* optional */ }
|
|
547
547
|
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
548
|
+
// 1. 收集待润色条目
|
|
549
|
+
let entries;
|
|
550
|
+
if (args.candidateIds?.length) {
|
|
551
|
+
entries = [];
|
|
552
|
+
for (const id of args.candidateIds) {
|
|
553
|
+
const e = await knowledgeService.get(id);
|
|
554
|
+
if (e) entries.push(typeof e.toJSON === 'function' ? e.toJSON() : e);
|
|
555
|
+
}
|
|
556
|
+
} else {
|
|
557
|
+
const result = await knowledgeService.list({ lifecycle: 'pending', source: 'bootstrap' }, { page: 1, pageSize: 200 });
|
|
558
|
+
entries = (result.items || []).map(e => typeof e.toJSON === 'function' ? e.toJSON() : e);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
if (entries.length === 0) {
|
|
562
|
+
return envelope({ success: true, data: { refined: 0, total: 0, errors: [], results: [] }, meta: { tool: 'autosnippet_bootstrap_refine', responseTimeMs: Date.now() - t0 } });
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
onProgress?.('refine:started', { total: entries.length, candidateIds: entries.map(e => e.id) });
|
|
566
|
+
|
|
567
|
+
// 2. 逐条 AI 润色
|
|
568
|
+
const allTitles = entries.map(e => e.title).filter(Boolean);
|
|
569
|
+
const results = [];
|
|
570
|
+
const errors = [];
|
|
571
|
+
let refined = 0;
|
|
572
|
+
let processed = 0;
|
|
573
|
+
|
|
574
|
+
for (const entry of entries) {
|
|
575
|
+
processed++;
|
|
576
|
+
onProgress?.('refine:item-started', { candidateId: entry.id, title: entry.title, current: processed, total: entries.length, progress: Math.round(((processed - 1) / entries.length) * 100) });
|
|
577
|
+
|
|
578
|
+
try {
|
|
579
|
+
const prompt = `你是一位高级代码知识管理专家。请改进以下知识条目:
|
|
580
|
+
|
|
581
|
+
标题: ${entry.title}
|
|
582
|
+
类型: ${entry.knowledgeType}
|
|
583
|
+
语言: ${entry.language}
|
|
584
|
+
描述: ${entry.description || ''}
|
|
585
|
+
代码:
|
|
586
|
+
\`\`\`
|
|
587
|
+
${(entry.content?.pattern || '').substring(0, 2000)}
|
|
588
|
+
\`\`\`
|
|
589
|
+
|
|
590
|
+
同批次知识: ${allTitles.slice(0, 20).join(', ')}
|
|
591
|
+
${args.userPrompt ? `用户补充: ${args.userPrompt}` : ''}
|
|
592
|
+
|
|
593
|
+
请返回 JSON:
|
|
594
|
+
{
|
|
595
|
+
"summary": "改进后的中文摘要",
|
|
596
|
+
"tags": ["建议标签"],
|
|
597
|
+
"relations": [{"target":"相关条目标题","relation":"depends_on|extends|related_to"}],
|
|
598
|
+
"confidence": 0.0-1.0,
|
|
599
|
+
"insight": "架构洞察(一句话)"
|
|
600
|
+
}`;
|
|
601
|
+
|
|
602
|
+
const response = await aiProvider.chat(prompt, { temperature: 0.3 });
|
|
603
|
+
const parsed = aiProvider.extractJSON(response, '{', '}');
|
|
604
|
+
|
|
605
|
+
if (!parsed) {
|
|
606
|
+
errors.push({ id: entry.id, title: entry.title, error: 'AI returned no valid JSON' });
|
|
607
|
+
onProgress?.('refine:item-failed', { candidateId: entry.id, title: entry.title, error: 'No valid JSON', current: processed, total: entries.length, progress: Math.round((processed / entries.length) * 100) });
|
|
608
|
+
continue;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
if (args.dryRun) {
|
|
612
|
+
results.push({ id: entry.id, title: entry.title, preview: parsed });
|
|
613
|
+
onProgress?.('refine:item-completed', { candidateId: entry.id, title: entry.title, refined: false, current: processed, total: entries.length, progress: Math.round((processed / entries.length) * 100) });
|
|
614
|
+
continue;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// 构建更新数据
|
|
618
|
+
const updateData = {};
|
|
619
|
+
let changed = false;
|
|
620
|
+
|
|
621
|
+
if (parsed.summary && parsed.summary !== entry.description) {
|
|
622
|
+
updateData.description = parsed.summary;
|
|
623
|
+
changed = true;
|
|
624
|
+
}
|
|
625
|
+
if (parsed.tags && Array.isArray(parsed.tags)) {
|
|
626
|
+
const merged = [...new Set([...(entry.tags || []), ...parsed.tags])];
|
|
627
|
+
if (merged.length > (entry.tags || []).length) {
|
|
628
|
+
updateData.tags = merged;
|
|
629
|
+
changed = true;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
if (typeof parsed.confidence === 'number' && parsed.confidence !== 0.6) {
|
|
633
|
+
updateData.reasoning = { ...(entry.reasoning || {}), confidence: parsed.confidence };
|
|
634
|
+
changed = true;
|
|
635
|
+
}
|
|
636
|
+
if (parsed.insight) {
|
|
637
|
+
updateData.description = `${entry.description || ''}\n\n**AI Insight**: ${parsed.insight}`.trim();
|
|
638
|
+
changed = true;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
if (changed) {
|
|
642
|
+
await knowledgeService.update(entry.id, updateData);
|
|
643
|
+
refined++;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
results.push({ id: entry.id, title: entry.title, refined: changed, fields: Object.keys(parsed) });
|
|
647
|
+
onProgress?.('refine:item-completed', { candidateId: entry.id, title: entry.title, refined: changed, current: processed, total: entries.length, progress: Math.round((processed / entries.length) * 100), refinedSoFar: refined });
|
|
648
|
+
} catch (err) {
|
|
649
|
+
errors.push({ id: entry.id, title: entry.title, error: err.message });
|
|
650
|
+
onProgress?.('refine:item-failed', { candidateId: entry.id, title: entry.title, error: err.message, current: processed, total: entries.length, progress: Math.round((processed / entries.length) * 100) });
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
onProgress?.('refine:completed', { total: entries.length, refined, failed: errors.length });
|
|
553
655
|
|
|
554
656
|
return envelope({
|
|
555
657
|
success: true,
|
|
556
658
|
data: {
|
|
557
|
-
|
|
558
|
-
|
|
659
|
+
refined,
|
|
660
|
+
total: entries.length,
|
|
661
|
+
errors,
|
|
662
|
+
results,
|
|
663
|
+
message: `Phase 6 AI 润色完成: ${refined}/${entries.length} 条知识条目已更新${args.dryRun ? '(预览模式)' : ''}`,
|
|
559
664
|
},
|
|
560
665
|
meta: { tool: 'autosnippet_bootstrap_refine', responseTimeMs: Date.now() - t0 },
|
|
561
666
|
});
|
|
@@ -1,65 +1,69 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* MCP Handlers — 知识浏览类
|
|
3
|
-
* listByKind, listRecipes, getRecipe, recipeInsights,
|
|
2
|
+
* MCP Handlers — 知识浏览类 (V3: 使用 knowledgeService)
|
|
3
|
+
* listByKind, listRecipes, getRecipe, recipeInsights, confirmUsage
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { envelope } from '../envelope.js';
|
|
7
7
|
|
|
8
|
+
/** 将 KnowledgeEntry 的 V3 字段投影为列表摘要 */
|
|
9
|
+
function _projectItem(r) {
|
|
10
|
+
const json = typeof r.toJSON === 'function' ? r.toJSON() : r;
|
|
11
|
+
return {
|
|
12
|
+
id: json.id, title: json.title, description: json.description,
|
|
13
|
+
trigger: json.trigger || '', lifecycle: json.lifecycle, kind: json.kind,
|
|
14
|
+
language: json.language, category: json.category,
|
|
15
|
+
knowledgeType: json.knowledgeType, complexity: json.complexity,
|
|
16
|
+
scope: json.scope, tags: json.tags || [],
|
|
17
|
+
quality: json.quality || null, stats: json.stats || null,
|
|
18
|
+
status: json.lifecycle,
|
|
19
|
+
statistics: json.stats,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
8
23
|
export async function listByKind(ctx, kind, args) {
|
|
9
|
-
const
|
|
24
|
+
const ks = ctx.container.get('knowledgeService');
|
|
10
25
|
const filters = { kind };
|
|
11
|
-
if (args.
|
|
12
|
-
if (args.
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
const items = (result?.data || result?.items || []).map(r => ({
|
|
16
|
-
id: r.id, title: r.title || r.name, description: r.description,
|
|
17
|
-
trigger: r.trigger || '', status: r.status, language: r.language, category: r.category,
|
|
18
|
-
knowledgeType: r.knowledgeType, kind: r.kind,
|
|
19
|
-
complexity: r.complexity, scope: r.scope, tags: r.tags || [],
|
|
20
|
-
quality: r.quality || null, statistics: r.statistics || null,
|
|
21
|
-
}));
|
|
26
|
+
if (args.language) filters.language = args.language;
|
|
27
|
+
if (args.category) filters.category = args.category;
|
|
28
|
+
const result = await ks.list(filters, { page: 1, pageSize: args.limit || 20 });
|
|
29
|
+
const items = (result?.data || []).map(_projectItem);
|
|
22
30
|
return envelope({ success: true, data: { kind, count: items.length, total: result?.pagination?.total || items.length, items }, meta: { tool: `autosnippet_list_${kind}s` } });
|
|
23
31
|
}
|
|
24
32
|
|
|
25
33
|
export async function listRecipes(ctx, args) {
|
|
26
|
-
const
|
|
34
|
+
const ks = ctx.container.get('knowledgeService');
|
|
27
35
|
const filters = {};
|
|
28
|
-
if (args.kind)
|
|
29
|
-
if (args.language)
|
|
30
|
-
if (args.category)
|
|
31
|
-
if (args.knowledgeType)
|
|
32
|
-
if (args.
|
|
33
|
-
if (args.
|
|
34
|
-
const result = await
|
|
35
|
-
const items = (result?.data ||
|
|
36
|
-
id: r.id, title: r.title, description: r.description,
|
|
37
|
-
trigger: r.trigger || '', status: r.status, language: r.language, category: r.category,
|
|
38
|
-
kind: r.kind, knowledgeType: r.knowledgeType, complexity: r.complexity,
|
|
39
|
-
scope: r.scope, tags: r.tags,
|
|
40
|
-
quality: r.quality, statistics: r.statistics,
|
|
41
|
-
}));
|
|
36
|
+
if (args.kind) filters.kind = args.kind;
|
|
37
|
+
if (args.language) filters.language = args.language;
|
|
38
|
+
if (args.category) filters.category = args.category;
|
|
39
|
+
if (args.knowledgeType) filters.knowledgeType = args.knowledgeType;
|
|
40
|
+
if (args.complexity) filters.complexity = args.complexity;
|
|
41
|
+
if (args.status) filters.lifecycle = args.status;
|
|
42
|
+
const result = await ks.list(filters, { page: 1, pageSize: args.limit || 20 });
|
|
43
|
+
const items = (result?.data || []).map(_projectItem);
|
|
42
44
|
return envelope({ success: true, data: { count: items.length, total: result?.pagination?.total || items.length, items }, meta: { tool: 'autosnippet_list_recipes' } });
|
|
43
45
|
}
|
|
44
46
|
|
|
45
47
|
export async function getRecipe(ctx, args) {
|
|
46
48
|
if (!args.id) throw new Error('id is required');
|
|
47
|
-
const
|
|
48
|
-
const
|
|
49
|
-
if (!
|
|
50
|
-
|
|
49
|
+
const ks = ctx.container.get('knowledgeService');
|
|
50
|
+
const entry = await ks.get(args.id);
|
|
51
|
+
if (!entry) throw new Error(`Knowledge entry not found: ${args.id}`);
|
|
52
|
+
const json = typeof entry.toJSON === 'function' ? entry.toJSON() : entry;
|
|
53
|
+
return envelope({ success: true, data: json, meta: { tool: 'autosnippet_get_recipe' } });
|
|
51
54
|
}
|
|
52
55
|
|
|
53
56
|
export async function recipeInsights(ctx, args) {
|
|
54
57
|
if (!args.id) throw new Error('id is required');
|
|
55
|
-
const
|
|
56
|
-
const
|
|
57
|
-
if (!
|
|
58
|
+
const ks = ctx.container.get('knowledgeService');
|
|
59
|
+
const entry = await ks.get(args.id);
|
|
60
|
+
if (!entry) throw new Error(`Knowledge entry not found: ${args.id}`);
|
|
61
|
+
const json = typeof entry.toJSON === 'function' ? entry.toJSON() : entry;
|
|
58
62
|
|
|
59
63
|
// 聚合关系摘要
|
|
60
64
|
const relationsSummary = {};
|
|
61
|
-
if (
|
|
62
|
-
for (const [type, targets] of Object.entries(
|
|
65
|
+
if (json.relations) {
|
|
66
|
+
for (const [type, targets] of Object.entries(json.relations)) {
|
|
63
67
|
if (Array.isArray(targets) && targets.length > 0) {
|
|
64
68
|
relationsSummary[type] = targets.length;
|
|
65
69
|
}
|
|
@@ -68,8 +72,8 @@ export async function recipeInsights(ctx, args) {
|
|
|
68
72
|
|
|
69
73
|
// 约束条件概览
|
|
70
74
|
const constraintsSummary = {};
|
|
71
|
-
if (
|
|
72
|
-
for (const [type, items] of Object.entries(
|
|
75
|
+
if (json.constraints) {
|
|
76
|
+
for (const [type, items] of Object.entries(json.constraints)) {
|
|
73
77
|
if (Array.isArray(items) && items.length > 0) {
|
|
74
78
|
constraintsSummary[type] = items;
|
|
75
79
|
}
|
|
@@ -77,43 +81,42 @@ export async function recipeInsights(ctx, args) {
|
|
|
77
81
|
}
|
|
78
82
|
|
|
79
83
|
const insights = {
|
|
80
|
-
id:
|
|
81
|
-
title:
|
|
82
|
-
trigger:
|
|
83
|
-
kind:
|
|
84
|
-
|
|
85
|
-
language:
|
|
86
|
-
category:
|
|
87
|
-
knowledgeType:
|
|
84
|
+
id: json.id,
|
|
85
|
+
title: json.title,
|
|
86
|
+
trigger: json.trigger || '',
|
|
87
|
+
kind: json.kind,
|
|
88
|
+
lifecycle: json.lifecycle,
|
|
89
|
+
language: json.language,
|
|
90
|
+
category: json.category,
|
|
91
|
+
knowledgeType: json.knowledgeType,
|
|
88
92
|
quality: {
|
|
89
|
-
overall:
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
+
overall: json.quality?.overall ?? null,
|
|
94
|
+
completeness: json.quality?.completeness ?? null,
|
|
95
|
+
adaptation: json.quality?.adaptation ?? null,
|
|
96
|
+
documentation: json.quality?.documentation ?? null,
|
|
93
97
|
},
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
feedbackScore: recipe.statistics?.feedbackScore ?? 0,
|
|
98
|
+
stats: {
|
|
99
|
+
adoptions: json.stats?.adoptions ?? 0,
|
|
100
|
+
applications: json.stats?.applications ?? 0,
|
|
101
|
+
guardHits: json.stats?.guardHits ?? 0,
|
|
102
|
+
views: json.stats?.views ?? 0,
|
|
103
|
+
searchHits: json.stats?.searchHits ?? 0,
|
|
101
104
|
},
|
|
102
105
|
content: {
|
|
103
|
-
hasPattern: !!
|
|
104
|
-
hasRationale: !!
|
|
105
|
-
hasMarkdown: !!
|
|
106
|
-
stepsCount:
|
|
107
|
-
codeChangesCount:
|
|
106
|
+
hasPattern: !!json.content?.pattern,
|
|
107
|
+
hasRationale: !!json.content?.rationale,
|
|
108
|
+
hasMarkdown: !!json.content?.markdown,
|
|
109
|
+
stepsCount: json.content?.steps?.length ?? 0,
|
|
110
|
+
codeChangesCount: json.content?.codeChanges?.length ?? 0,
|
|
108
111
|
},
|
|
109
112
|
relations: relationsSummary,
|
|
110
113
|
constraints: constraintsSummary,
|
|
111
|
-
tags:
|
|
112
|
-
complexity:
|
|
113
|
-
scope:
|
|
114
|
-
createdBy:
|
|
115
|
-
createdAt:
|
|
116
|
-
updatedAt:
|
|
114
|
+
tags: json.tags || [],
|
|
115
|
+
complexity: json.complexity,
|
|
116
|
+
scope: json.scope,
|
|
117
|
+
createdBy: json.createdBy,
|
|
118
|
+
createdAt: json.createdAt,
|
|
119
|
+
updatedAt: json.updatedAt,
|
|
117
120
|
};
|
|
118
121
|
|
|
119
122
|
return envelope({ success: true, data: insights, meta: { tool: 'autosnippet_recipe_insights' } });
|
|
@@ -121,11 +124,11 @@ export async function recipeInsights(ctx, args) {
|
|
|
121
124
|
|
|
122
125
|
export async function confirmUsage(ctx, args) {
|
|
123
126
|
if (!args.recipeId) throw new Error('recipeId is required');
|
|
124
|
-
const
|
|
127
|
+
const ks = ctx.container.get('knowledgeService');
|
|
125
128
|
const usageType = args.usageType || 'adoption';
|
|
126
129
|
const feedback = args.feedback || null;
|
|
127
130
|
|
|
128
|
-
await
|
|
131
|
+
await ks.incrementUsage(args.recipeId, usageType, {
|
|
129
132
|
feedback,
|
|
130
133
|
actor: 'mcp_user',
|
|
131
134
|
});
|
|
@@ -146,7 +149,7 @@ export async function confirmUsage(ctx, args) {
|
|
|
146
149
|
return envelope({
|
|
147
150
|
success: true,
|
|
148
151
|
data: { recipeId: args.recipeId, usageType, feedback },
|
|
149
|
-
message:
|
|
152
|
+
message: `已记录使用 ${args.recipeId} 的${usageType === 'adoption' ? '采纳' : '应用'}`,
|
|
150
153
|
meta: { tool: 'autosnippet_confirm_usage' },
|
|
151
154
|
});
|
|
152
155
|
}
|