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
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
* │ 17. generate_guard_rule AI 生成 Guard 规则 │
|
|
34
34
|
* └─────────────────────────────────────────────────────┘
|
|
35
35
|
* ┌─── 生命周期操作类 (7) ─────────────────────────────┐
|
|
36
|
-
* │ 18.
|
|
36
|
+
* │ 18. submit_knowledge 提交候选 │
|
|
37
37
|
* │ 19. approve_candidate 批准候选 │
|
|
38
38
|
* │ 20. reject_candidate 驳回候选 │
|
|
39
39
|
* │ 21. publish_recipe 发布 Recipe │
|
|
@@ -853,19 +853,19 @@ const searchRecipes = {
|
|
|
853
853
|
},
|
|
854
854
|
},
|
|
855
855
|
handler: async (params, ctx) => {
|
|
856
|
-
const
|
|
856
|
+
const knowledgeService = ctx.container.get('knowledgeService');
|
|
857
857
|
const { keyword, category, language, knowledgeType, limit = 10 } = params;
|
|
858
858
|
|
|
859
859
|
if (keyword) {
|
|
860
|
-
return
|
|
860
|
+
return knowledgeService.search(keyword, { page: 1, pageSize: limit });
|
|
861
861
|
}
|
|
862
862
|
|
|
863
|
-
const filters = {};
|
|
863
|
+
const filters = { lifecycle: 'active' };
|
|
864
864
|
if (category) filters.category = category;
|
|
865
865
|
if (language) filters.language = language;
|
|
866
866
|
if (knowledgeType) filters.knowledgeType = knowledgeType;
|
|
867
867
|
|
|
868
|
-
return
|
|
868
|
+
return knowledgeService.list(filters, { page: 1, pageSize: limit });
|
|
869
869
|
},
|
|
870
870
|
};
|
|
871
871
|
|
|
@@ -886,19 +886,20 @@ const searchCandidates = {
|
|
|
886
886
|
},
|
|
887
887
|
},
|
|
888
888
|
handler: async (params, ctx) => {
|
|
889
|
-
const
|
|
889
|
+
const knowledgeService = ctx.container.get('knowledgeService');
|
|
890
890
|
const { keyword, status, language, category, limit = 10 } = params;
|
|
891
891
|
|
|
892
892
|
if (keyword) {
|
|
893
|
-
return
|
|
893
|
+
return knowledgeService.search(keyword, { page: 1, pageSize: limit });
|
|
894
894
|
}
|
|
895
895
|
|
|
896
|
+
// V3: status 映射为 lifecycle
|
|
896
897
|
const filters = {};
|
|
897
|
-
if (status) filters.
|
|
898
|
+
if (status) filters.lifecycle = status;
|
|
898
899
|
if (language) filters.language = language;
|
|
899
900
|
if (category) filters.category = category;
|
|
900
901
|
|
|
901
|
-
return
|
|
902
|
+
return knowledgeService.list(filters, { page: 1, pageSize: limit });
|
|
902
903
|
},
|
|
903
904
|
};
|
|
904
905
|
|
|
@@ -916,10 +917,13 @@ const getRecipeDetail = {
|
|
|
916
917
|
required: ['recipeId'],
|
|
917
918
|
},
|
|
918
919
|
handler: async (params, ctx) => {
|
|
919
|
-
const
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
920
|
+
const knowledgeService = ctx.container.get('knowledgeService');
|
|
921
|
+
try {
|
|
922
|
+
const entry = await knowledgeService.get(params.recipeId);
|
|
923
|
+
return typeof entry.toJSON === 'function' ? entry.toJSON() : entry;
|
|
924
|
+
} catch {
|
|
925
|
+
return { error: `Knowledge entry '${params.recipeId}' not found` };
|
|
926
|
+
}
|
|
923
927
|
},
|
|
924
928
|
};
|
|
925
929
|
|
|
@@ -931,13 +935,8 @@ const getProjectStats = {
|
|
|
931
935
|
description: '获取项目知识库的整体统计:Recipe 数量/分类分布、候选项数量/状态分布、知识图谱节点/边数。',
|
|
932
936
|
parameters: { type: 'object', properties: {} },
|
|
933
937
|
handler: async (_params, ctx) => {
|
|
934
|
-
const
|
|
935
|
-
const
|
|
936
|
-
|
|
937
|
-
const [recipeStats, candidateStats] = await Promise.all([
|
|
938
|
-
recipeService.getRecipeStats(),
|
|
939
|
-
candidateService.getCandidateStats(),
|
|
940
|
-
]);
|
|
938
|
+
const knowledgeService = ctx.container.get('knowledgeService');
|
|
939
|
+
const stats = await knowledgeService.getStats();
|
|
941
940
|
|
|
942
941
|
// 尝试获取知识图谱统计
|
|
943
942
|
let graphStats = null;
|
|
@@ -947,8 +946,7 @@ const getProjectStats = {
|
|
|
947
946
|
} catch { /* KG not available */ }
|
|
948
947
|
|
|
949
948
|
return {
|
|
950
|
-
|
|
951
|
-
candidates: candidateStats,
|
|
949
|
+
knowledge: stats,
|
|
952
950
|
knowledgeGraph: graphStats,
|
|
953
951
|
};
|
|
954
952
|
},
|
|
@@ -1000,15 +998,16 @@ const searchKnowledge = {
|
|
|
1000
998
|
// 降级: RetrievalFunnel + 全量候选
|
|
1001
999
|
try {
|
|
1002
1000
|
const funnel = ctx.container.get('retrievalFunnel');
|
|
1003
|
-
const
|
|
1004
|
-
const
|
|
1001
|
+
const knowledgeRepo = ctx.container.get('knowledgeRepository');
|
|
1002
|
+
const allResult = await knowledgeRepo.findWithPagination({}, { page: 1, pageSize: 500 });
|
|
1003
|
+
const allRecipes = allResult?.items || [];
|
|
1005
1004
|
|
|
1006
1005
|
// 规范化为 funnel 输入格式
|
|
1007
1006
|
const candidates = allRecipes.map(r => ({
|
|
1008
1007
|
id: r.id,
|
|
1009
1008
|
title: r.title,
|
|
1010
1009
|
content: r.content || r.code || '',
|
|
1011
|
-
description: r.description ||
|
|
1010
|
+
description: r.description || '',
|
|
1012
1011
|
language: r.language,
|
|
1013
1012
|
category: r.category,
|
|
1014
1013
|
trigger: r.trigger || '',
|
|
@@ -1088,7 +1087,7 @@ const extractRecipes = {
|
|
|
1088
1087
|
},
|
|
1089
1088
|
handler: async (params, ctx) => {
|
|
1090
1089
|
if (!ctx.aiProvider) return { error: 'AI provider not available' };
|
|
1091
|
-
const { targetName, files } = params;
|
|
1090
|
+
const { targetName, files, comprehensive } = params;
|
|
1092
1091
|
|
|
1093
1092
|
// 加载语言参考 Skill(如有),注入到 AI 提取 prompt
|
|
1094
1093
|
let skillReference = null;
|
|
@@ -1124,17 +1123,16 @@ const extractRecipes = {
|
|
|
1124
1123
|
const extractOpts = {};
|
|
1125
1124
|
if (skillReference) extractOpts.skillReference = skillReference;
|
|
1126
1125
|
if (astContext) extractOpts.astContext = astContext;
|
|
1126
|
+
if (comprehensive) extractOpts.comprehensive = true;
|
|
1127
1127
|
|
|
1128
1128
|
// 首选:使用当前 aiProvider
|
|
1129
|
+
let recipes;
|
|
1130
|
+
let fallbackUsed;
|
|
1129
1131
|
try {
|
|
1130
|
-
|
|
1131
|
-
const count = Array.isArray(recipes) ? recipes.length : 0;
|
|
1132
|
-
if (count === 0) {
|
|
1133
|
-
ctx.logger?.warn?.(`[extract_recipes] AI returned 0 recipes for ${targetName} (${files.length} files)`);
|
|
1134
|
-
}
|
|
1135
|
-
return { targetName, extracted: count, recipes: Array.isArray(recipes) ? recipes : [] };
|
|
1132
|
+
recipes = await ctx.aiProvider.extractRecipes(targetName, files, extractOpts);
|
|
1136
1133
|
} catch (primaryErr) {
|
|
1137
1134
|
// 尝试 fallback(如果 AiFactory 可用)
|
|
1135
|
+
let recovered = false;
|
|
1138
1136
|
try {
|
|
1139
1137
|
const aiFactory = ctx.container?.singletons?._aiFactory;
|
|
1140
1138
|
if (aiFactory?.isGeoOrProviderError?.(primaryErr)) {
|
|
@@ -1143,14 +1141,67 @@ const extractRecipes = {
|
|
|
1143
1141
|
for (const fbName of fallbacks) {
|
|
1144
1142
|
try {
|
|
1145
1143
|
const fbProvider = aiFactory.createProvider({ provider: fbName });
|
|
1146
|
-
|
|
1147
|
-
|
|
1144
|
+
recipes = await fbProvider.extractRecipes(targetName, files, extractOpts);
|
|
1145
|
+
fallbackUsed = fbName;
|
|
1146
|
+
recovered = true;
|
|
1147
|
+
break;
|
|
1148
1148
|
} catch { /* next fallback */ }
|
|
1149
1149
|
}
|
|
1150
1150
|
}
|
|
1151
|
-
} catch { /* AiFactory not available
|
|
1152
|
-
throw primaryErr;
|
|
1151
|
+
} catch { /* AiFactory not available */ }
|
|
1152
|
+
if (!recovered) throw primaryErr;
|
|
1153
1153
|
}
|
|
1154
|
+
|
|
1155
|
+
if (!Array.isArray(recipes)) recipes = [];
|
|
1156
|
+
if (recipes.length === 0) {
|
|
1157
|
+
ctx.logger?.warn?.(`[extract_recipes] AI returned 0 recipes for ${targetName} (${files.length} files)`);
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
// ── V3 直透:AI 已输出完整 V3 结构,仅做来源标记 + 程序化评分/标签 ──
|
|
1161
|
+
let qualityScorer = null;
|
|
1162
|
+
let recipeExtractor = null;
|
|
1163
|
+
try { qualityScorer = ctx.container?.get?.('qualityScorer'); } catch { /* not available */ }
|
|
1164
|
+
try { recipeExtractor = ctx.container?.get?.('recipeExtractor'); } catch { /* not available */ }
|
|
1165
|
+
|
|
1166
|
+
for (const recipe of recipes) {
|
|
1167
|
+
// 来源 & 生命周期(非 AI 职责)
|
|
1168
|
+
recipe.source = recipe.source || 'ai-scan';
|
|
1169
|
+
recipe.lifecycle = recipe.lifecycle || 'pending';
|
|
1170
|
+
|
|
1171
|
+
// RecipeExtractor 语义标签增强(程序化补充,不替代 AI tags)
|
|
1172
|
+
const codeText = recipe.content?.pattern || '';
|
|
1173
|
+
if (recipeExtractor && codeText) {
|
|
1174
|
+
try {
|
|
1175
|
+
const extracted = recipeExtractor.extractFromContent(
|
|
1176
|
+
codeText, `${recipe.title || 'unknown'}.${recipe.language || 'swift'}`, ''
|
|
1177
|
+
);
|
|
1178
|
+
if (extracted.semanticTags?.length > 0) {
|
|
1179
|
+
recipe.tags = [...new Set([...(recipe.tags || []), ...extracted.semanticTags])];
|
|
1180
|
+
}
|
|
1181
|
+
if ((!recipe.category || recipe.category === 'Utility') && extracted.category && extracted.category !== 'general') {
|
|
1182
|
+
recipe.category = extracted.category;
|
|
1183
|
+
}
|
|
1184
|
+
} catch { /* best effort */ }
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
// QualityScorer 评分 → quality 结构化
|
|
1188
|
+
if (qualityScorer) {
|
|
1189
|
+
try {
|
|
1190
|
+
const scoreResult = qualityScorer.score(recipe);
|
|
1191
|
+
recipe.quality = {
|
|
1192
|
+
completeness: 0,
|
|
1193
|
+
adaptation: 0,
|
|
1194
|
+
documentation: 0,
|
|
1195
|
+
overall: scoreResult.score ?? 0,
|
|
1196
|
+
grade: scoreResult.grade || '',
|
|
1197
|
+
};
|
|
1198
|
+
} catch { /* best effort */ }
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
const result = { targetName, extracted: recipes.length, recipes };
|
|
1203
|
+
if (fallbackUsed) result.fallbackUsed = fallbackUsed;
|
|
1204
|
+
return result;
|
|
1154
1205
|
},
|
|
1155
1206
|
};
|
|
1156
1207
|
|
|
@@ -1169,12 +1220,10 @@ const enrichCandidate = {
|
|
|
1169
1220
|
},
|
|
1170
1221
|
handler: async (params, ctx) => {
|
|
1171
1222
|
if (!ctx.aiProvider) return { error: 'AI provider not available' };
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
{ userId: 'agent' },
|
|
1177
|
-
);
|
|
1223
|
+
// V3: 使用 MCP handler enrichCandidates 的逻辑
|
|
1224
|
+
const { enrichCandidates: enrichFn } = await import('../../external/mcp/handlers/candidate.js');
|
|
1225
|
+
const result = await enrichFn(ctx, { candidateIds: params.candidateIds });
|
|
1226
|
+
return result?.data || result;
|
|
1178
1227
|
},
|
|
1179
1228
|
};
|
|
1180
1229
|
|
|
@@ -1194,20 +1243,14 @@ const refineBootstrapCandidates = {
|
|
|
1194
1243
|
},
|
|
1195
1244
|
handler: async (params, ctx) => {
|
|
1196
1245
|
if (!ctx.aiProvider) return { error: 'AI provider not available' };
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
return candidateService.refineBootstrapCandidates(
|
|
1207
|
-
ctx.aiProvider,
|
|
1208
|
-
{ candidateIds: params.candidateIds, userPrompt: params.userPrompt, dryRun: params.dryRun, onProgress },
|
|
1209
|
-
{ userId: 'agent' },
|
|
1210
|
-
);
|
|
1246
|
+
// V3: 委托给 bootstrap handler 的 refine 逻辑
|
|
1247
|
+
const { bootstrapRefine } = await import('../../external/mcp/handlers/bootstrap.js');
|
|
1248
|
+
const result = await bootstrapRefine(ctx, {
|
|
1249
|
+
candidateIds: params.candidateIds,
|
|
1250
|
+
userPrompt: params.userPrompt,
|
|
1251
|
+
dryRun: params.dryRun,
|
|
1252
|
+
});
|
|
1253
|
+
return result?.data || result;
|
|
1211
1254
|
},
|
|
1212
1255
|
};
|
|
1213
1256
|
|
|
@@ -1231,18 +1274,18 @@ const checkDuplicate = {
|
|
|
1231
1274
|
const projectRoot = params.projectRoot || ctx.projectRoot;
|
|
1232
1275
|
const threshold = params.threshold ?? 0.5;
|
|
1233
1276
|
|
|
1234
|
-
// 如果提供 candidateId
|
|
1277
|
+
// 如果提供 candidateId,从数据库读取条目信息
|
|
1235
1278
|
if (!cand && params.candidateId) {
|
|
1236
1279
|
try {
|
|
1237
|
-
const
|
|
1238
|
-
const found = await
|
|
1280
|
+
const knowledgeService = ctx.container.get('knowledgeService');
|
|
1281
|
+
const found = await knowledgeService.get(params.candidateId);
|
|
1239
1282
|
if (found) {
|
|
1240
|
-
const
|
|
1283
|
+
const json = typeof found.toJSON === 'function' ? found.toJSON() : found;
|
|
1241
1284
|
cand = {
|
|
1242
|
-
title:
|
|
1243
|
-
summary:
|
|
1244
|
-
code:
|
|
1245
|
-
usageGuide:
|
|
1285
|
+
title: json.title || '',
|
|
1286
|
+
summary: json.description || '',
|
|
1287
|
+
code: json.content?.pattern || '',
|
|
1288
|
+
usageGuide: '',
|
|
1246
1289
|
};
|
|
1247
1290
|
}
|
|
1248
1291
|
} catch { /* ignore */ }
|
|
@@ -1442,8 +1485,9 @@ const getRecommendations = {
|
|
|
1442
1485
|
},
|
|
1443
1486
|
},
|
|
1444
1487
|
handler: async (params, ctx) => {
|
|
1445
|
-
const
|
|
1446
|
-
|
|
1488
|
+
const knowledgeService = ctx.container.get('knowledgeService');
|
|
1489
|
+
// V3: 推荐 = 活跃条目按使用量排序
|
|
1490
|
+
return knowledgeService.list({ lifecycle: 'active' }, { page: 1, pageSize: params.limit || 10 });
|
|
1447
1491
|
},
|
|
1448
1492
|
};
|
|
1449
1493
|
|
|
@@ -1463,16 +1507,16 @@ const aiTranslate = {
|
|
|
1463
1507
|
handler: async (params, ctx) => {
|
|
1464
1508
|
if (!ctx.aiProvider) return { error: 'AI provider not available' };
|
|
1465
1509
|
const { summary, usageGuide } = params;
|
|
1466
|
-
if (!summary && !usageGuide) return {
|
|
1510
|
+
if (!summary && !usageGuide) return { summaryEn: '', usageGuideEn: '' };
|
|
1467
1511
|
|
|
1468
|
-
const systemPrompt = 'You are a technical translator. Translate from Chinese to English. Keep technical terms unchanged. Return ONLY valid JSON: { "
|
|
1512
|
+
const systemPrompt = 'You are a technical translator. Translate from Chinese to English. Keep technical terms unchanged. Return ONLY valid JSON: { "summaryEn": "...", "usageGuideEn": "..." }.';
|
|
1469
1513
|
const parts = [];
|
|
1470
1514
|
if (summary) parts.push(`summary: ${summary}`);
|
|
1471
1515
|
if (usageGuide) parts.push(`usageGuide: ${usageGuide}`);
|
|
1472
1516
|
|
|
1473
1517
|
const raw = await ctx.aiProvider.chat(parts.join('\n'), { systemPrompt, temperature: 0.2 });
|
|
1474
1518
|
const parsed = ctx.aiProvider.extractJSON(raw, '{', '}');
|
|
1475
|
-
return parsed || {
|
|
1519
|
+
return parsed || { summaryEn: summary || '', usageGuideEn: usageGuide || '' };
|
|
1476
1520
|
},
|
|
1477
1521
|
};
|
|
1478
1522
|
|
|
@@ -1632,24 +1676,24 @@ const DIMENSION_DISPLAY_GROUP = {
|
|
|
1632
1676
|
};
|
|
1633
1677
|
|
|
1634
1678
|
// ────────────────────────────────────────────────────────────
|
|
1635
|
-
// Bootstrap 维度类型校验 —
|
|
1679
|
+
// Bootstrap 维度类型校验 — submit_knowledge / submit_with_check 共用
|
|
1636
1680
|
// 基于 dimensionMeta 类型标注系统,而非关键词模糊匹配
|
|
1637
1681
|
// ────────────────────────────────────────────────────────────
|
|
1638
1682
|
|
|
1639
1683
|
/**
|
|
1640
1684
|
* 基于维度元数据 (dimensionMeta) 检查提交是否合法
|
|
1641
1685
|
* @param {{ id: string, outputType: 'candidate'|'skill'|'dual', allowedKnowledgeTypes: string[] }} dimensionMeta
|
|
1642
|
-
* @param {object} params -
|
|
1686
|
+
* @param {object} params - submit_knowledge 的参数
|
|
1643
1687
|
* @param {object} [logger]
|
|
1644
1688
|
* @returns {{ status: string, reason: string } | null} 不合法返回 rejected,合法返回 null
|
|
1645
1689
|
*/
|
|
1646
1690
|
function _checkDimensionType(dimensionMeta, params, logger) {
|
|
1647
1691
|
// 1. Skill-only 维度不允许提交 Candidate
|
|
1648
1692
|
if (dimensionMeta.outputType === 'skill') {
|
|
1649
|
-
logger?.info(`[
|
|
1693
|
+
logger?.info(`[submit_knowledge] ✗ rejected — dimension "${dimensionMeta.id}" is skill-only, cannot submit candidates`);
|
|
1650
1694
|
return {
|
|
1651
1695
|
status: 'rejected',
|
|
1652
|
-
reason: `当前维度 "${dimensionMeta.id}" 的输出类型为 skill-only,不允许调用
|
|
1696
|
+
reason: `当前维度 "${dimensionMeta.id}" 的输出类型为 skill-only,不允许调用 submit_knowledge。请只在最终回复中提供 dimensionDigest JSON。`,
|
|
1653
1697
|
};
|
|
1654
1698
|
}
|
|
1655
1699
|
|
|
@@ -1658,7 +1702,7 @@ function _checkDimensionType(dimensionMeta, params, logger) {
|
|
|
1658
1702
|
if (allowed.length > 0 && params.knowledgeType) {
|
|
1659
1703
|
if (!allowed.includes(params.knowledgeType)) {
|
|
1660
1704
|
const corrected = allowed[0];
|
|
1661
|
-
logger?.warn(`[
|
|
1705
|
+
logger?.warn(`[submit_knowledge] knowledgeType "${params.knowledgeType}" → "${corrected}" (auto-corrected for dimension "${dimensionMeta.id}")`);
|
|
1662
1706
|
params.knowledgeType = corrected;
|
|
1663
1707
|
}
|
|
1664
1708
|
}
|
|
@@ -1666,93 +1710,137 @@ function _checkDimensionType(dimensionMeta, params, logger) {
|
|
|
1666
1710
|
return null;
|
|
1667
1711
|
}
|
|
1668
1712
|
|
|
1669
|
-
// 16.
|
|
1713
|
+
// 16. submit_knowledge
|
|
1670
1714
|
// ────────────────────────────────────────────────────────────
|
|
1671
1715
|
const submitCandidate = {
|
|
1672
|
-
name: '
|
|
1716
|
+
name: 'submit_knowledge',
|
|
1673
1717
|
description: '提交新的代码候选项到知识库审核队列。',
|
|
1674
1718
|
parameters: {
|
|
1675
1719
|
type: 'object',
|
|
1676
1720
|
properties: {
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1721
|
+
// ── 内容(V3 content 子对象) ──
|
|
1722
|
+
content: { type: 'object', description: '{ markdown: "项目特写 Markdown", pattern: "核心代码 3-8 行" }' },
|
|
1723
|
+
|
|
1724
|
+
// ── 基本信息 ──
|
|
1725
|
+
title: { type: 'string', description: '候选标题' },
|
|
1726
|
+
description: { type: 'string', description: '中文简述 ≤80 字,引用真实类名' },
|
|
1727
|
+
tags: { type: 'array', items: { type: 'string' }, description: '标签列表' },
|
|
1728
|
+
|
|
1729
|
+
// ── Cursor 交付(AI 必填)──
|
|
1730
|
+
trigger: { type: 'string', description: '@前缀 kebab-case 唯一标识符' },
|
|
1731
|
+
kind: { type: 'string', enum: ['rule', 'pattern', 'fact'], description: '知识类型' },
|
|
1732
|
+
topicHint: { type: 'string', enum: ['networking', 'ui', 'data', 'architecture', 'conventions'], description: '主题分类' },
|
|
1733
|
+
whenClause: { type: 'string', description: '触发场景英文' },
|
|
1734
|
+
doClause: { type: 'string', description: '正向指令英文祈使句 ≤60 tokens' },
|
|
1735
|
+
dontClause: { type: 'string', description: '反向约束英文(不以 Don\'t 开头)' },
|
|
1736
|
+
|
|
1737
|
+
// ── 推理 ──
|
|
1738
|
+
reasoning: { type: 'object', description: '{ whyStandard: string, sources: string[], confidence: number }' },
|
|
1739
|
+
|
|
1740
|
+
// ── V3 扩展字段 ──
|
|
1741
|
+
scope: { type: 'string', enum: ['universal', 'project-specific', 'team-convention'], description: '适用范围' },
|
|
1742
|
+
complexity: { type: 'string', enum: ['basic', 'intermediate', 'advanced'], description: '复杂度' },
|
|
1743
|
+
headers: { type: 'array', items: { type: 'string' }, description: '依赖的 import/require 行' },
|
|
1744
|
+
sourceFile: { type: 'string', description: '来源文件相对路径' },
|
|
1687
1745
|
},
|
|
1688
|
-
required: ['
|
|
1746
|
+
required: ['content', 'title', 'trigger', 'kind', 'doClause'],
|
|
1689
1747
|
},
|
|
1690
1748
|
handler: async (params, ctx) => {
|
|
1691
|
-
const
|
|
1749
|
+
const knowledgeService = ctx.container.get('knowledgeService');
|
|
1692
1750
|
|
|
1693
|
-
// ── Bootstrap 维度类型校验
|
|
1751
|
+
// ── Bootstrap 维度类型校验 ──
|
|
1694
1752
|
const dimMeta = ctx._dimensionMeta;
|
|
1695
1753
|
if (dimMeta && ctx.source === 'system') {
|
|
1696
1754
|
const rejected = _checkDimensionType(dimMeta, params, ctx.logger);
|
|
1697
1755
|
if (rejected) return rejected;
|
|
1698
1756
|
|
|
1699
|
-
//
|
|
1757
|
+
// 自动注入维度标签
|
|
1700
1758
|
if (!params.tags) params.tags = [];
|
|
1701
1759
|
if (!params.tags.includes(dimMeta.id)) params.tags.push(dimMeta.id);
|
|
1702
1760
|
if (!params.tags.includes('bootstrap')) params.tags.push('bootstrap');
|
|
1703
1761
|
|
|
1704
|
-
//
|
|
1705
|
-
|
|
1706
|
-
params.knowledgeType = dimMeta.allowedKnowledgeTypes[0];
|
|
1707
|
-
}
|
|
1708
|
-
|
|
1709
|
-
// Bootstrap 模式: 将 category 覆盖为展示分组 ID,确保前端按合并后的分组展示
|
|
1710
|
-
// AI 可能填 "UI"/"Core" 等功能分类,但前端通过 BOOTSTRAP_DIM_LABELS 按展示分组分组
|
|
1711
|
-
params.category = DIMENSION_DISPLAY_GROUP[dimMeta.id] || dimMeta.id;
|
|
1762
|
+
// Bootstrap 模式: 将 category 覆盖为展示分组 ID
|
|
1763
|
+
params._category = DIMENSION_DISPLAY_GROUP[dimMeta.id] || dimMeta.id;
|
|
1712
1764
|
|
|
1713
|
-
// ── CandidateGuardrail 质量验证
|
|
1765
|
+
// ── CandidateGuardrail 质量验证 ──
|
|
1714
1766
|
const guardrail = new CandidateGuardrail(
|
|
1715
1767
|
ctx._submittedTitles || new Set(),
|
|
1716
1768
|
dimMeta,
|
|
1717
1769
|
);
|
|
1718
1770
|
const guardResult = guardrail.validate(params);
|
|
1719
1771
|
if (!guardResult.valid) {
|
|
1720
|
-
ctx.logger?.info(`[
|
|
1772
|
+
ctx.logger?.info(`[submit_knowledge] ✗ guardrail rejected: ${guardResult.error}`);
|
|
1721
1773
|
return {
|
|
1722
1774
|
status: 'rejected',
|
|
1723
1775
|
error: guardResult.error,
|
|
1724
|
-
hint: '
|
|
1776
|
+
hint: '请根据错误信息调整内容后重新提交。',
|
|
1725
1777
|
};
|
|
1726
1778
|
}
|
|
1727
1779
|
}
|
|
1728
1780
|
|
|
1729
|
-
//
|
|
1730
|
-
|
|
1731
|
-
|
|
1781
|
+
// ── 系统自动设置 ──
|
|
1782
|
+
const systemFields = {
|
|
1783
|
+
language: ctx._projectLanguage || '',
|
|
1784
|
+
category: dimMeta ? (DIMENSION_DISPLAY_GROUP[dimMeta.id] || dimMeta.id) : 'general',
|
|
1785
|
+
knowledgeType: dimMeta?.allowedKnowledgeTypes?.[0] || 'code-pattern',
|
|
1786
|
+
source: ctx.source === 'system' ? 'bootstrap' : 'agent',
|
|
1787
|
+
};
|
|
1732
1788
|
|
|
1733
|
-
//
|
|
1734
|
-
const
|
|
1735
|
-
if (Array.isArray(
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1789
|
+
// ── 直传 → KnowledgeEntry ──
|
|
1790
|
+
const reasoning = params.reasoning || { whyStandard: '', sources: ['agent'], confidence: 0.7 };
|
|
1791
|
+
if (Array.isArray(reasoning.sources) && reasoning.sources.length === 0) {
|
|
1792
|
+
reasoning.sources = ['agent'];
|
|
1793
|
+
}
|
|
1794
|
+
|
|
1795
|
+
// V3 content 直透
|
|
1796
|
+
const contentObj = params.content && typeof params.content === 'object'
|
|
1797
|
+
? params.content
|
|
1798
|
+
: { markdown: '', pattern: '' };
|
|
1799
|
+
|
|
1800
|
+
const data = {
|
|
1801
|
+
...systemFields,
|
|
1802
|
+
title: params.title || '',
|
|
1803
|
+
description: params.description || '',
|
|
1804
|
+
tags: params.tags || [],
|
|
1805
|
+
trigger: params.trigger || '',
|
|
1806
|
+
kind: params.kind || 'pattern',
|
|
1807
|
+
topicHint: params.topicHint || '',
|
|
1808
|
+
whenClause: params.whenClause || '',
|
|
1809
|
+
doClause: params.doClause || '',
|
|
1810
|
+
dontClause: params.dontClause || '',
|
|
1811
|
+
coreCode: contentObj.pattern || '',
|
|
1812
|
+
content: contentObj,
|
|
1813
|
+
reasoning,
|
|
1814
|
+
// V3 扩展字段直透
|
|
1815
|
+
scope: params.scope || '',
|
|
1816
|
+
complexity: params.complexity || '',
|
|
1817
|
+
headers: params.headers || [],
|
|
1818
|
+
// sourceFile: 优先取 params,Bootstrap 回退从 reasoning.sources 推断
|
|
1819
|
+
sourceFile: params.sourceFile
|
|
1820
|
+
|| (Array.isArray(reasoning.sources) && reasoning.sources.length > 0
|
|
1821
|
+
&& reasoning.sources[0] !== 'agent'
|
|
1822
|
+
? reasoning.sources[0]
|
|
1823
|
+
: ''),
|
|
1824
|
+
// 7.3.9 agentNotes/aiInsight 注入
|
|
1825
|
+
agentNotes: dimMeta
|
|
1826
|
+
? { dimensionId: dimMeta.id, outputType: dimMeta.outputType || 'candidate' }
|
|
1827
|
+
: null,
|
|
1828
|
+
aiInsight: reasoning.whyStandard || params.description || null,
|
|
1829
|
+
};
|
|
1830
|
+
|
|
1831
|
+
if (dimMeta && ctx.source === 'system') {
|
|
1832
|
+
const displayGroup = DIMENSION_DISPLAY_GROUP[dimMeta.id] || dimMeta.id;
|
|
1833
|
+
data.tags = [...new Set([...(data.tags || []), displayGroup])];
|
|
1742
1834
|
}
|
|
1743
1835
|
|
|
1744
|
-
const
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
// Bootstrap 模式额外注入 targetName — 前端优先按 meta.targetName 分组
|
|
1753
|
-
const displayGroup = dimMeta ? (DIMENSION_DISPLAY_GROUP[dimMeta.id] || dimMeta.id) : null;
|
|
1754
|
-
const extraMeta = (dimMeta && ctx.source === 'system') ? { targetName: displayGroup } : {};
|
|
1755
|
-
return candidateService.createFromToolParams(item, source || 'agent', extraMeta, { userId: 'agent' });
|
|
1836
|
+
const saved = await knowledgeService.create(data, { userId: 'agent' });
|
|
1837
|
+
|
|
1838
|
+
// ── QualityScorer 自动评分 ──
|
|
1839
|
+
try {
|
|
1840
|
+
await knowledgeService.updateQuality(saved.id, { userId: 'agent' });
|
|
1841
|
+
} catch { /* best effort — 不阻塞创建流程 */ }
|
|
1842
|
+
|
|
1843
|
+
return saved;
|
|
1756
1844
|
},
|
|
1757
1845
|
};
|
|
1758
1846
|
|
|
@@ -1770,8 +1858,8 @@ const approveCandidate = {
|
|
|
1770
1858
|
required: ['candidateId'],
|
|
1771
1859
|
},
|
|
1772
1860
|
handler: async (params, ctx) => {
|
|
1773
|
-
const
|
|
1774
|
-
return
|
|
1861
|
+
const knowledgeService = ctx.container.get('knowledgeService');
|
|
1862
|
+
return knowledgeService.approve(params.candidateId, { userId: 'agent' });
|
|
1775
1863
|
},
|
|
1776
1864
|
};
|
|
1777
1865
|
|
|
@@ -1790,8 +1878,8 @@ const rejectCandidate = {
|
|
|
1790
1878
|
required: ['candidateId', 'reason'],
|
|
1791
1879
|
},
|
|
1792
1880
|
handler: async (params, ctx) => {
|
|
1793
|
-
const
|
|
1794
|
-
return
|
|
1881
|
+
const knowledgeService = ctx.container.get('knowledgeService');
|
|
1882
|
+
return knowledgeService.reject(params.candidateId, params.reason, { userId: 'agent' });
|
|
1795
1883
|
},
|
|
1796
1884
|
};
|
|
1797
1885
|
|
|
@@ -1809,8 +1897,8 @@ const publishRecipe = {
|
|
|
1809
1897
|
required: ['recipeId'],
|
|
1810
1898
|
},
|
|
1811
1899
|
handler: async (params, ctx) => {
|
|
1812
|
-
const
|
|
1813
|
-
return
|
|
1900
|
+
const knowledgeService = ctx.container.get('knowledgeService');
|
|
1901
|
+
return knowledgeService.publish(params.recipeId, { userId: 'agent' });
|
|
1814
1902
|
},
|
|
1815
1903
|
};
|
|
1816
1904
|
|
|
@@ -1829,8 +1917,8 @@ const deprecateRecipe = {
|
|
|
1829
1917
|
required: ['recipeId', 'reason'],
|
|
1830
1918
|
},
|
|
1831
1919
|
handler: async (params, ctx) => {
|
|
1832
|
-
const
|
|
1833
|
-
return
|
|
1920
|
+
const knowledgeService = ctx.container.get('knowledgeService');
|
|
1921
|
+
return knowledgeService.deprecate(params.recipeId, params.reason, { userId: 'agent' });
|
|
1834
1922
|
},
|
|
1835
1923
|
};
|
|
1836
1924
|
|
|
@@ -1849,8 +1937,8 @@ const updateRecipe = {
|
|
|
1849
1937
|
required: ['recipeId', 'updates'],
|
|
1850
1938
|
},
|
|
1851
1939
|
handler: async (params, ctx) => {
|
|
1852
|
-
const
|
|
1853
|
-
return
|
|
1940
|
+
const knowledgeService = ctx.container.get('knowledgeService');
|
|
1941
|
+
return knowledgeService.update(params.recipeId, params.updates, { userId: 'agent' });
|
|
1854
1942
|
},
|
|
1855
1943
|
};
|
|
1856
1944
|
|
|
@@ -1869,9 +1957,9 @@ const recordUsage = {
|
|
|
1869
1957
|
required: ['recipeId'],
|
|
1870
1958
|
},
|
|
1871
1959
|
handler: async (params, ctx) => {
|
|
1872
|
-
const
|
|
1960
|
+
const knowledgeService = ctx.container.get('knowledgeService');
|
|
1873
1961
|
const type = params.type || 'adoption';
|
|
1874
|
-
await
|
|
1962
|
+
await knowledgeService.incrementUsage(params.recipeId, type);
|
|
1875
1963
|
return { success: true, recipeId: params.recipeId, type };
|
|
1876
1964
|
},
|
|
1877
1965
|
};
|
|
@@ -1894,9 +1982,13 @@ const qualityScore = {
|
|
|
1894
1982
|
let recipe = params.recipe;
|
|
1895
1983
|
|
|
1896
1984
|
if (!recipe && params.recipeId) {
|
|
1897
|
-
const
|
|
1898
|
-
|
|
1899
|
-
|
|
1985
|
+
const knowledgeService = ctx.container.get('knowledgeService');
|
|
1986
|
+
try {
|
|
1987
|
+
const entry = await knowledgeService.get(params.recipeId);
|
|
1988
|
+
recipe = typeof entry.toJSON === 'function' ? entry.toJSON() : entry;
|
|
1989
|
+
} catch {
|
|
1990
|
+
return { error: `Knowledge entry '${params.recipeId}' not found` };
|
|
1991
|
+
}
|
|
1900
1992
|
}
|
|
1901
1993
|
if (!recipe) return { error: 'Provide recipeId or recipe object' };
|
|
1902
1994
|
|
|
@@ -2210,13 +2302,8 @@ const knowledgeOverview = {
|
|
|
2210
2302
|
const [statsResult, feedbackResult] = await Promise.all([
|
|
2211
2303
|
(async () => {
|
|
2212
2304
|
try {
|
|
2213
|
-
const
|
|
2214
|
-
|
|
2215
|
-
const [rs, cs] = await Promise.all([
|
|
2216
|
-
recipeService.getRecipeStats(),
|
|
2217
|
-
candidateService.getCandidateStats(),
|
|
2218
|
-
]);
|
|
2219
|
-
return { recipes: rs, candidates: cs };
|
|
2305
|
+
const knowledgeService = ctx.container.get('knowledgeService');
|
|
2306
|
+
return knowledgeService.getStats();
|
|
2220
2307
|
} catch { return null; }
|
|
2221
2308
|
})(),
|
|
2222
2309
|
(async () => {
|
|
@@ -2229,8 +2316,7 @@ const knowledgeOverview = {
|
|
|
2229
2316
|
]);
|
|
2230
2317
|
|
|
2231
2318
|
if (statsResult) {
|
|
2232
|
-
result.
|
|
2233
|
-
result.candidates = statsResult.candidates;
|
|
2319
|
+
result.knowledge = statsResult;
|
|
2234
2320
|
}
|
|
2235
2321
|
|
|
2236
2322
|
// 知识图谱统计
|
|
@@ -2256,45 +2342,45 @@ const knowledgeOverview = {
|
|
|
2256
2342
|
// ────────────────────────────────────────────────────────────
|
|
2257
2343
|
const submitWithCheck = {
|
|
2258
2344
|
name: 'submit_with_check',
|
|
2259
|
-
description: '
|
|
2345
|
+
description: '安全提交候选:先执行查重检测,无重复则自动提交。一次调用完成 check_duplicate + submit_knowledge。',
|
|
2260
2346
|
parameters: {
|
|
2261
2347
|
type: 'object',
|
|
2262
2348
|
properties: {
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2349
|
+
content: { type: 'object', description: '{ markdown: "项目特写 Markdown", pattern: "核心代码 3-8 行" }' },
|
|
2350
|
+
title: { type: 'string', description: '候选标题' },
|
|
2351
|
+
description:{ type: 'string', description: '中文简述 ≤80 字' },
|
|
2352
|
+
trigger: { type: 'string', description: '@前缀 kebab-case 唯一标识符' },
|
|
2353
|
+
kind: { type: 'string', enum: ['rule', 'pattern', 'fact'] },
|
|
2354
|
+
topicHint: { type: 'string', enum: ['networking', 'ui', 'data', 'architecture', 'conventions'] },
|
|
2355
|
+
whenClause: { type: 'string', description: '触发场景英文' },
|
|
2356
|
+
doClause: { type: 'string', description: '正向指令英文' },
|
|
2357
|
+
dontClause: { type: 'string', description: '反向约束英文' },
|
|
2358
|
+
tags: { type: 'array', items: { type: 'string' } },
|
|
2359
|
+
reasoning: { type: 'object', description: '{ whyStandard, sources, confidence }' },
|
|
2360
|
+
threshold: { type: 'number', description: '相似度阈值,默认 0.7' },
|
|
2269
2361
|
},
|
|
2270
|
-
required: ['
|
|
2362
|
+
required: ['content', 'title', 'trigger', 'kind', 'doClause'],
|
|
2271
2363
|
},
|
|
2272
2364
|
handler: async (params, ctx) => {
|
|
2273
|
-
const { code, language, category, title, summary, threshold = 0.7 } = params;
|
|
2274
2365
|
const projectRoot = ctx.projectRoot;
|
|
2275
2366
|
|
|
2276
|
-
// ── Bootstrap 维度类型校验
|
|
2367
|
+
// ── Bootstrap 维度类型校验 ──
|
|
2277
2368
|
const dimMeta = ctx._dimensionMeta;
|
|
2278
2369
|
if (dimMeta && ctx.source === 'system') {
|
|
2279
2370
|
const rejected = _checkDimensionType(dimMeta, params, ctx.logger);
|
|
2280
2371
|
if (rejected) return rejected;
|
|
2281
2372
|
|
|
2282
|
-
// 自动注入维度标签
|
|
2283
2373
|
if (!params.tags) params.tags = [];
|
|
2284
2374
|
if (!params.tags.includes(dimMeta.id)) params.tags.push(dimMeta.id);
|
|
2285
2375
|
if (!params.tags.includes('bootstrap')) params.tags.push('bootstrap');
|
|
2286
|
-
|
|
2287
|
-
// 自动补充 knowledgeType(AI 未填时用维度默认值)
|
|
2288
|
-
if (!params.knowledgeType && dimMeta.allowedKnowledgeTypes?.length > 0) {
|
|
2289
|
-
params.knowledgeType = dimMeta.allowedKnowledgeTypes[0];
|
|
2290
|
-
}
|
|
2291
|
-
|
|
2292
|
-
// 覆盖 category 为展示分组
|
|
2293
|
-
params.category = DIMENSION_DISPLAY_GROUP[dimMeta.id] || dimMeta.id;
|
|
2294
2376
|
}
|
|
2295
2377
|
|
|
2296
2378
|
// Step 1: 查重
|
|
2297
|
-
const
|
|
2379
|
+
const threshold = params.threshold || 0.7;
|
|
2380
|
+
const contentObj2 = params.content && typeof params.content === 'object'
|
|
2381
|
+
? params.content
|
|
2382
|
+
: { markdown: '', pattern: '' };
|
|
2383
|
+
const cand = { title: params.title || '', summary: params.description || '', code: contentObj2.markdown || contentObj2.pattern || '' };
|
|
2298
2384
|
const similar = findSimilarRecipes(projectRoot, cand, { threshold: 0.5, topK: 5 });
|
|
2299
2385
|
const hasDuplicate = similar.some(s => s.similarity >= threshold);
|
|
2300
2386
|
|
|
@@ -2306,38 +2392,49 @@ const submitWithCheck = {
|
|
|
2306
2392
|
highestSimilarity: similar[0]?.similarity || 0,
|
|
2307
2393
|
_meta: {
|
|
2308
2394
|
confidence: 'high',
|
|
2309
|
-
hint: `发现高度相似 Recipe(相似度 ${(similar[0]?.similarity * 100).toFixed(0)}
|
|
2395
|
+
hint: `发现高度相似 Recipe(相似度 ${(similar[0]?.similarity * 100).toFixed(0)}%),已阻止提交。`,
|
|
2310
2396
|
},
|
|
2311
2397
|
};
|
|
2312
2398
|
}
|
|
2313
2399
|
|
|
2314
|
-
// Step 2: 提交
|
|
2400
|
+
// Step 2: 提交 — 委托给 submit_knowledge handler
|
|
2315
2401
|
try {
|
|
2316
|
-
const
|
|
2317
|
-
const
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
language,
|
|
2321
|
-
category,
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2402
|
+
const knowledgeService = ctx.container.get('knowledgeService');
|
|
2403
|
+
const reasoning = params.reasoning || { whyStandard: '', sources: ['agent'], confidence: 0.7 };
|
|
2404
|
+
|
|
2405
|
+
const systemFields = {
|
|
2406
|
+
language: ctx._projectLanguage || '',
|
|
2407
|
+
category: dimMeta ? (DIMENSION_DISPLAY_GROUP[dimMeta.id] || dimMeta.id) : 'general',
|
|
2408
|
+
knowledgeType: dimMeta?.allowedKnowledgeTypes?.[0] || 'code-pattern',
|
|
2409
|
+
source: ctx.source === 'system' ? 'bootstrap' : 'agent',
|
|
2410
|
+
};
|
|
2411
|
+
|
|
2412
|
+
const data = {
|
|
2413
|
+
...systemFields,
|
|
2414
|
+
title: params.title || '',
|
|
2415
|
+
description: params.description || '',
|
|
2416
|
+
tags: params.tags || [],
|
|
2417
|
+
trigger: params.trigger || '',
|
|
2418
|
+
kind: params.kind || 'pattern',
|
|
2419
|
+
topicHint: params.topicHint || '',
|
|
2420
|
+
whenClause: params.whenClause || '',
|
|
2421
|
+
doClause: params.doClause || '',
|
|
2422
|
+
dontClause: params.dontClause || '',
|
|
2423
|
+
coreCode: contentObj2.pattern || '',
|
|
2424
|
+
content: contentObj2,
|
|
2425
|
+
reasoning,
|
|
2327
2426
|
};
|
|
2328
|
-
|
|
2329
|
-
const
|
|
2330
|
-
const swcExtraMeta = (dimMeta && ctx.source === 'system') ? { targetName: swcDisplayGroup } : {};
|
|
2331
|
-
const created = await candidateService.createFromToolParams(item, paramSource || 'agent', swcExtraMeta, { userId: 'agent' });
|
|
2427
|
+
|
|
2428
|
+
const created = await knowledgeService.create(data, { userId: 'agent' });
|
|
2332
2429
|
|
|
2333
2430
|
return {
|
|
2334
2431
|
submitted: true,
|
|
2335
|
-
|
|
2432
|
+
entry: typeof created.toJSON === 'function' ? created.toJSON() : created,
|
|
2336
2433
|
similar: similar.length > 0 ? similar : [],
|
|
2337
2434
|
_meta: {
|
|
2338
2435
|
confidence: 'high',
|
|
2339
2436
|
hint: similar.length > 0
|
|
2340
|
-
? `已提交,但有 ${similar.length}
|
|
2437
|
+
? `已提交,但有 ${similar.length} 个低相似度匹配。`
|
|
2341
2438
|
: '已提交,无重复风险。',
|
|
2342
2439
|
},
|
|
2343
2440
|
};
|
|
@@ -2444,7 +2541,7 @@ const planTask = {
|
|
|
2444
2541
|
// ─── 元工具: 自我质量审查 ───────────────────────────────
|
|
2445
2542
|
const reviewMyOutput = {
|
|
2446
2543
|
name: 'review_my_output',
|
|
2447
|
-
description: '回查本次会话中已提交的候选,检查质量红线是否满足。包括: 项目特写风格、
|
|
2544
|
+
description: '回查本次会话中已提交的候选,检查质量红线是否满足。包括: 项目特写风格、description 泛化措辞、代码示例来源标注、Cursor 交付字段完整性等。返回通过/问题列表。建议在提交完所有候选后调用一次进行自检。',
|
|
2448
2545
|
parameters: {
|
|
2449
2546
|
type: 'object',
|
|
2450
2547
|
properties: {
|
|
@@ -2457,7 +2554,7 @@ const reviewMyOutput = {
|
|
|
2457
2554
|
},
|
|
2458
2555
|
handler: async (params, context) => {
|
|
2459
2556
|
const submitted = (context._sessionToolCalls || []).filter(
|
|
2460
|
-
tc => tc.tool === '
|
|
2557
|
+
tc => tc.tool === 'submit_knowledge' || tc.tool === 'submit_with_check',
|
|
2461
2558
|
);
|
|
2462
2559
|
|
|
2463
2560
|
if (submitted.length === 0) {
|
|
@@ -2469,48 +2566,54 @@ const reviewMyOutput = {
|
|
|
2469
2566
|
|
|
2470
2567
|
for (const tc of submitted) {
|
|
2471
2568
|
const p = tc.params || {};
|
|
2472
|
-
const
|
|
2569
|
+
const contentObj3 = p.content && typeof p.content === 'object' ? p.content : {};
|
|
2570
|
+
const markdown = contentObj3.markdown || '';
|
|
2473
2571
|
const title = p.title || '';
|
|
2474
|
-
const
|
|
2572
|
+
const description = p.description || '';
|
|
2475
2573
|
const candidateIssues = [];
|
|
2476
2574
|
|
|
2477
2575
|
// 检查 1: 项目特写后缀
|
|
2478
|
-
if (!title.includes('— 项目特写') && !
|
|
2576
|
+
if (!title.includes('— 项目特写') && !markdown.includes('— 项目特写')) {
|
|
2479
2577
|
candidateIssues.push('缺少 "— 项目特写" 后缀');
|
|
2480
2578
|
}
|
|
2481
2579
|
|
|
2482
2580
|
// 检查 2: 项目特写融合叙事质量 — 必须同时包含代码和描述性文字
|
|
2483
|
-
const hasCodeBlock = /```[\s\S]*?```/.test(
|
|
2581
|
+
const hasCodeBlock = /```[\s\S]*?```/.test(markdown);
|
|
2484
2582
|
if (!hasCodeBlock) {
|
|
2485
2583
|
candidateIssues.push('特写缺少代码示例,应包含基本用法代码');
|
|
2486
2584
|
}
|
|
2487
2585
|
// 去掉代码块后,剩余描述性文字应足够
|
|
2488
|
-
const proseLength =
|
|
2586
|
+
const proseLength = markdown.replace(/```[\s\S]*?```/g, '').replace(/[#>\-*`\n]/g, '').trim().length;
|
|
2489
2587
|
if (proseLength < 50) {
|
|
2490
2588
|
candidateIssues.push('特写缺少项目特点描述,应融合基本用法和项目特点');
|
|
2491
2589
|
}
|
|
2492
2590
|
|
|
2493
|
-
// 检查 3:
|
|
2494
|
-
if (/本模块|该文件|这个类|该项目/.test(
|
|
2495
|
-
candidateIssues.push('
|
|
2591
|
+
// 检查 3: description 泛化措辞
|
|
2592
|
+
if (/本模块|该文件|这个类|该项目/.test(description)) {
|
|
2593
|
+
candidateIssues.push('description 使用了泛化措辞,应引用具体类名和数字');
|
|
2496
2594
|
}
|
|
2497
2595
|
|
|
2498
|
-
// 检查 4:
|
|
2499
|
-
if (
|
|
2500
|
-
candidateIssues.push(`
|
|
2596
|
+
// 检查 4: description 过短
|
|
2597
|
+
if (description.length < 15) {
|
|
2598
|
+
candidateIssues.push(`description 过短 (${description.length} 字), 应≥15字并包含具体类名和数字`);
|
|
2501
2599
|
}
|
|
2502
2600
|
|
|
2503
|
-
// 检查 5:
|
|
2504
|
-
if (
|
|
2505
|
-
candidateIssues.push(`
|
|
2601
|
+
// 检查 5: content.markdown 过短(可能是空壳)
|
|
2602
|
+
if (markdown.length < 200) {
|
|
2603
|
+
candidateIssues.push(`content.markdown 文档过短 (${markdown.length} 字), 可能缺少实质内容`);
|
|
2506
2604
|
}
|
|
2507
2605
|
|
|
2508
2606
|
// 检查 6: 代码示例来源
|
|
2509
|
-
const hasSourceAnnotation = /\([^)]*\.\w+[^)]*:\d+\)|\([^)]*\.\w+[^)]*\)/.test(
|
|
2607
|
+
const hasSourceAnnotation = /\([^)]*\.\w+[^)]*:\d+\)|\([^)]*\.\w+[^)]*\)/.test(markdown);
|
|
2510
2608
|
if (hasCodeBlock && !hasSourceAnnotation) {
|
|
2511
2609
|
candidateIssues.push('代码示例可能缺少来源文件标注 (建议标注 "来源: FileName.m:行号")');
|
|
2512
2610
|
}
|
|
2513
2611
|
|
|
2612
|
+
// 检查 7: Cursor 交付字段
|
|
2613
|
+
if (!p.trigger) candidateIssues.push('缺少 trigger 字段');
|
|
2614
|
+
if (!p.doClause) candidateIssues.push('缺少 doClause 字段');
|
|
2615
|
+
if (!p.kind) candidateIssues.push('缺少 kind 字段');
|
|
2616
|
+
|
|
2514
2617
|
if (candidateIssues.length > 0) {
|
|
2515
2618
|
issues.push({ title, issues: candidateIssues });
|
|
2516
2619
|
}
|