autosnippet 2.9.0 → 2.10.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 +4 -4
- package/bin/cli.js +5 -33
- package/config/constitution.yaml +9 -2
- package/dashboard/dist/assets/{icons-CH-H9x0E.js → icons-BkT3XrKf.js} +105 -100
- package/dashboard/dist/assets/index-BsB7DzW4.css +1 -0
- package/dashboard/dist/assets/index-DdmQMrJJ.js +155 -0
- package/dashboard/dist/index.html +3 -3
- package/lib/cli/AiScanService.js +13 -11
- package/lib/cli/KnowledgeSyncService.js +343 -0
- package/lib/cli/SetupService.js +8 -26
- package/lib/core/gateway/GatewayActionRegistry.js +48 -58
- package/lib/domain/index.js +16 -11
- package/lib/domain/knowledge/KnowledgeEntry.js +351 -0
- package/lib/domain/knowledge/KnowledgeRepository.js +123 -0
- package/lib/domain/knowledge/Lifecycle.js +109 -0
- package/lib/domain/knowledge/index.js +27 -0
- package/lib/domain/knowledge/values/Constraints.js +125 -0
- package/lib/domain/knowledge/values/Content.js +86 -0
- package/lib/domain/knowledge/values/Quality.js +93 -0
- package/lib/domain/knowledge/values/Reasoning.js +69 -0
- package/lib/domain/knowledge/values/Relations.js +168 -0
- package/lib/domain/knowledge/values/Stats.js +87 -0
- package/lib/domain/knowledge/values/index.js +9 -0
- package/lib/external/ai/AiProvider.js +48 -0
- package/lib/external/mcp/McpServer.js +7 -5
- package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +2 -2
- package/lib/external/mcp/handlers/bootstrap.js +116 -11
- package/lib/external/mcp/handlers/browse.js +77 -73
- package/lib/external/mcp/handlers/candidate.js +29 -276
- package/lib/external/mcp/handlers/guard.js +2 -0
- package/lib/external/mcp/handlers/knowledge.js +205 -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 +125 -138
- package/lib/http/HttpServer.js +4 -8
- 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/external/XcodeAutomation.js +187 -103
- package/lib/injection/ServiceContainer.js +49 -60
- package/lib/repository/knowledge/KnowledgeRepository.impl.js +373 -0
- package/lib/service/automation/DirectiveDetector.js +2 -3
- package/lib/service/automation/FileWatcher.js +67 -28
- package/lib/service/automation/XcodeIntegration.js +931 -156
- package/lib/service/automation/handlers/AlinkHandler.js +6 -4
- package/lib/service/automation/handlers/CreateHandler.js +53 -18
- package/lib/service/automation/handlers/GuardHandler.js +183 -20
- package/lib/service/automation/handlers/SearchHandler.js +35 -17
- package/lib/service/chat/CandidateGuardrail.js +1 -1
- package/lib/service/chat/ChatAgent.js +46 -45
- package/lib/service/chat/ContextWindow.js +5 -5
- package/lib/service/chat/ProducerAgent.js +7 -7
- package/lib/service/chat/tools.js +130 -123
- package/lib/service/guard/GuardCheckEngine.js +114 -10
- package/lib/service/guard/GuardService.js +59 -48
- package/lib/service/knowledge/ConfidenceRouter.js +159 -0
- package/lib/service/knowledge/KnowledgeFileWriter.js +602 -0
- package/lib/service/knowledge/KnowledgeService.js +725 -0
- package/lib/service/search/SearchEngine.js +92 -19
- package/lib/service/skills/SignalCollector.js +12 -7
- package/lib/service/skills/SkillAdvisor.js +13 -11
- package/lib/service/snippet/SnippetFactory.js +5 -5
- 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 +6 -6
- package/skills/autosnippet-coldstart/SKILL.md +7 -3
- 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 +16 -4
- 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
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Constraints — 约束值对象
|
|
3
|
+
*
|
|
4
|
+
* 包含 Guard 规则 (regex + ast)、边界约束、前置条件、副作用。
|
|
5
|
+
* Guard 规则预留 AST 类型,为语义规则做前瞻设计。
|
|
6
|
+
*/
|
|
7
|
+
export class Constraints {
|
|
8
|
+
constructor(props = {}) {
|
|
9
|
+
/** @type {Array<Guard>} Guard 规则列表 */
|
|
10
|
+
this.guards = (props.guards || []).map(Constraints._normalizeGuard);
|
|
11
|
+
/** @type {string[]} 边界约束 */
|
|
12
|
+
this.boundaries = props.boundaries || [];
|
|
13
|
+
/** @type {string[]} 前置条件 */
|
|
14
|
+
this.preconditions = props.preconditions || [];
|
|
15
|
+
/** @type {string[]} 副作用 */
|
|
16
|
+
this.sideEffects = props.side_effects ?? props.sideEffects ?? [];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 从任意输入构造 Constraints
|
|
21
|
+
* @param {Constraints|Object|null} input
|
|
22
|
+
* @returns {Constraints}
|
|
23
|
+
*/
|
|
24
|
+
static from(input) {
|
|
25
|
+
if (input instanceof Constraints) return input;
|
|
26
|
+
if (!input) return new Constraints();
|
|
27
|
+
return new Constraints(input);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* 标准化 Guard 对象
|
|
32
|
+
* @param {Object} g
|
|
33
|
+
* @returns {Guard}
|
|
34
|
+
*/
|
|
35
|
+
static _normalizeGuard(g) {
|
|
36
|
+
return {
|
|
37
|
+
id: g.id || null,
|
|
38
|
+
type: g.type || (g.ast_query ? 'ast' : 'regex'),
|
|
39
|
+
pattern: g.pattern || null,
|
|
40
|
+
ast_query: g.ast_query || null,
|
|
41
|
+
message: g.message || '',
|
|
42
|
+
severity: g.severity || 'warning',
|
|
43
|
+
fix_suggestion: g.fix_suggestion || null,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 获取 regex 类型的 Guard 规则
|
|
49
|
+
* @returns {Array<Guard>}
|
|
50
|
+
*/
|
|
51
|
+
getRegexGuards() {
|
|
52
|
+
return this.guards.filter(g => g.type === 'regex' && g.pattern);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* 获取 ast 类型的 Guard 规则
|
|
57
|
+
* @returns {Array<Guard>}
|
|
58
|
+
*/
|
|
59
|
+
getAstGuards() {
|
|
60
|
+
return this.guards.filter(g => g.type === 'ast' && g.ast_query);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* 添加 Guard 规则
|
|
65
|
+
* @param {Object} guard
|
|
66
|
+
* @returns {Constraints}
|
|
67
|
+
*/
|
|
68
|
+
addGuard(guard) {
|
|
69
|
+
this.guards.push(Constraints._normalizeGuard(guard));
|
|
70
|
+
return this;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* 是否有 Guard 规则
|
|
75
|
+
* @returns {boolean}
|
|
76
|
+
*/
|
|
77
|
+
hasGuards() {
|
|
78
|
+
return this.guards.length > 0;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* 是否为空
|
|
83
|
+
* @returns {boolean}
|
|
84
|
+
*/
|
|
85
|
+
isEmpty() {
|
|
86
|
+
return this.guards.length === 0 &&
|
|
87
|
+
this.boundaries.length === 0 &&
|
|
88
|
+
this.preconditions.length === 0 &&
|
|
89
|
+
this.sideEffects.length === 0;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* 转换为 wire format JSON
|
|
94
|
+
*/
|
|
95
|
+
toJSON() {
|
|
96
|
+
return {
|
|
97
|
+
guards: this.guards,
|
|
98
|
+
boundaries: this.boundaries,
|
|
99
|
+
preconditions: this.preconditions,
|
|
100
|
+
side_effects: this.sideEffects,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* 从 wire format 创建
|
|
106
|
+
* @param {Object} data
|
|
107
|
+
* @returns {Constraints}
|
|
108
|
+
*/
|
|
109
|
+
static fromJSON(data) {
|
|
110
|
+
return Constraints.from(data);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* @typedef {Object} Guard
|
|
116
|
+
* @property {?string} id - Guard 唯一标识
|
|
117
|
+
* @property {'regex'|'ast'} type - 类型
|
|
118
|
+
* @property {?string} pattern - regex pattern (type=regex 时)
|
|
119
|
+
* @property {?Object} ast_query - AST 查询 (type=ast 时)
|
|
120
|
+
* @property {string} message - 错误/警告消息
|
|
121
|
+
* @property {'error'|'warning'|'info'} severity - 严重级别
|
|
122
|
+
* @property {?string} fix_suggestion - 关联修复 Recipe 的 trigger
|
|
123
|
+
*/
|
|
124
|
+
|
|
125
|
+
export default Constraints;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content — 内容值对象
|
|
3
|
+
*
|
|
4
|
+
* 统一承载代码片段 (pattern) 或 Markdown 全文 (markdown),
|
|
5
|
+
* 以及设计原理、实施步骤、代码变更、验证方式。
|
|
6
|
+
*/
|
|
7
|
+
export class Content {
|
|
8
|
+
constructor(props = {}) {
|
|
9
|
+
/** @type {string} 代码片段 */
|
|
10
|
+
this.pattern = props.pattern ?? '';
|
|
11
|
+
/** @type {string} Markdown 全文(与 pattern 二选一) */
|
|
12
|
+
this.markdown = props.markdown ?? '';
|
|
13
|
+
/** @type {string} 设计原理 */
|
|
14
|
+
this.rationale = props.rationale ?? '';
|
|
15
|
+
/** @type {Array<{title?:string, description?:string, code?:string}>} 实施步骤 */
|
|
16
|
+
this.steps = props.steps ?? [];
|
|
17
|
+
/** @type {Array<{file:string, before:string, after:string, explanation:string}>} 代码变更 */
|
|
18
|
+
this.codeChanges = props.code_changes ?? props.codeChanges ?? [];
|
|
19
|
+
/** @type {?{method?:string, expected_result?:string, test_code?:string}} 验证方式 */
|
|
20
|
+
this.verification = props.verification ?? null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 从任意输入构造 Content
|
|
25
|
+
* @param {Content|Object|null} input
|
|
26
|
+
* @returns {Content}
|
|
27
|
+
*/
|
|
28
|
+
static from(input) {
|
|
29
|
+
if (input instanceof Content) return input;
|
|
30
|
+
if (!input) return new Content();
|
|
31
|
+
return new Content(input);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 从旧 Candidate 的 code + metadata 构建
|
|
36
|
+
* @param {string} code
|
|
37
|
+
* @param {Object} meta
|
|
38
|
+
* @returns {Content}
|
|
39
|
+
*/
|
|
40
|
+
static fromLegacyCandidate(code, meta = {}) {
|
|
41
|
+
const isMarkdown = code && (
|
|
42
|
+
code.includes('— 项目特写') || /^#{1,3}\s/.test(code.trimStart())
|
|
43
|
+
);
|
|
44
|
+
return new Content({
|
|
45
|
+
pattern: isMarkdown ? '' : (code || ''),
|
|
46
|
+
markdown: isMarkdown ? code : '',
|
|
47
|
+
rationale: meta.rationale || '',
|
|
48
|
+
steps: meta.steps || [],
|
|
49
|
+
code_changes: meta.codeChanges || meta.code_changes || [],
|
|
50
|
+
verification: meta.verification || null,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 是否包含有效内容
|
|
56
|
+
* @returns {boolean}
|
|
57
|
+
*/
|
|
58
|
+
hasContent() {
|
|
59
|
+
return !!(this.pattern || this.markdown || this.rationale || this.steps.length > 0);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 转换为 wire format JSON
|
|
64
|
+
*/
|
|
65
|
+
toJSON() {
|
|
66
|
+
return {
|
|
67
|
+
pattern: this.pattern,
|
|
68
|
+
markdown: this.markdown,
|
|
69
|
+
rationale: this.rationale,
|
|
70
|
+
steps: this.steps,
|
|
71
|
+
code_changes: this.codeChanges,
|
|
72
|
+
verification: this.verification,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* 从 wire format 创建
|
|
78
|
+
* @param {Object} data
|
|
79
|
+
* @returns {Content}
|
|
80
|
+
*/
|
|
81
|
+
static fromJSON(data) {
|
|
82
|
+
return new Content(data);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export default Content;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quality — 质量值对象
|
|
3
|
+
*
|
|
4
|
+
* 4 维度评分 + 综合分 + 等级。
|
|
5
|
+
*/
|
|
6
|
+
export class Quality {
|
|
7
|
+
constructor(props = {}) {
|
|
8
|
+
/** @type {number} 内容完整度 (0-1) */
|
|
9
|
+
this.completeness = props.completeness ?? 0;
|
|
10
|
+
/** @type {number} 项目适配度 (0-1) */
|
|
11
|
+
this.adaptation = props.adaptation ?? 0;
|
|
12
|
+
/** @type {number} 文档清晰度 (0-1) */
|
|
13
|
+
this.documentation = props.documentation ?? 0;
|
|
14
|
+
/** @type {number} 综合分 (0-1) */
|
|
15
|
+
this.overall = props.overall ?? 0;
|
|
16
|
+
/** @type {string} 等级 A-F */
|
|
17
|
+
this.grade = props.grade || Quality.calcGrade(this.overall);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 从任意输入构造 Quality
|
|
22
|
+
* @param {Quality|Object|null} input
|
|
23
|
+
* @returns {Quality}
|
|
24
|
+
*/
|
|
25
|
+
static from(input) {
|
|
26
|
+
if (input instanceof Quality) return input;
|
|
27
|
+
return new Quality(input || {});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* 从旧 Recipe quality 字段映射
|
|
32
|
+
* @param {Object} old { codeCompleteness, projectAdaptation, documentationClarity, overall }
|
|
33
|
+
* @returns {Quality}
|
|
34
|
+
*/
|
|
35
|
+
static fromLegacyRecipe(old) {
|
|
36
|
+
if (!old) return new Quality();
|
|
37
|
+
return new Quality({
|
|
38
|
+
completeness: old.codeCompleteness ?? old.completeness ?? 0,
|
|
39
|
+
adaptation: old.projectAdaptation ?? old.adaptation ?? 0,
|
|
40
|
+
documentation: old.documentationClarity ?? old.documentation ?? 0,
|
|
41
|
+
overall: old.overall ?? 0,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* 从 3 维度计算综合分
|
|
47
|
+
* @returns {Quality}
|
|
48
|
+
*/
|
|
49
|
+
recalculate() {
|
|
50
|
+
this.overall = Math.round(
|
|
51
|
+
((this.completeness + this.adaptation + this.documentation) / 3) * 100
|
|
52
|
+
) / 100;
|
|
53
|
+
this.grade = Quality.calcGrade(this.overall);
|
|
54
|
+
return this;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* 根据分数计算等级
|
|
59
|
+
* @param {number} score 0-1
|
|
60
|
+
* @returns {string}
|
|
61
|
+
*/
|
|
62
|
+
static calcGrade(score) {
|
|
63
|
+
if (score >= 0.9) return 'A';
|
|
64
|
+
if (score >= 0.75) return 'B';
|
|
65
|
+
if (score >= 0.6) return 'C';
|
|
66
|
+
if (score >= 0.4) return 'D';
|
|
67
|
+
return 'F';
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* 转换为 wire format JSON
|
|
72
|
+
*/
|
|
73
|
+
toJSON() {
|
|
74
|
+
return {
|
|
75
|
+
completeness: this.completeness,
|
|
76
|
+
adaptation: this.adaptation,
|
|
77
|
+
documentation: this.documentation,
|
|
78
|
+
overall: this.overall,
|
|
79
|
+
grade: this.grade,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* 从 wire format 创建
|
|
85
|
+
* @param {Object} data
|
|
86
|
+
* @returns {Quality}
|
|
87
|
+
*/
|
|
88
|
+
static fromJSON(data) {
|
|
89
|
+
return Quality.from(data);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export default Quality;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reasoning — 推理值对象(V3 统一版)
|
|
3
|
+
*
|
|
4
|
+
* 与旧 Candidate.Reasoning 逻辑一致,但 wire format 统一为 snake_case。
|
|
5
|
+
*/
|
|
6
|
+
export class Reasoning {
|
|
7
|
+
constructor(props = {}) {
|
|
8
|
+
/** @type {string} 为什么遵循标准 */
|
|
9
|
+
this.whyStandard = props.why_standard ?? props.whyStandard ?? '';
|
|
10
|
+
/** @type {string[]} 来源列表 */
|
|
11
|
+
this.sources = props.sources || [];
|
|
12
|
+
/** @type {number} 置信度 0-1 */
|
|
13
|
+
this.confidence = props.confidence ?? 0.7;
|
|
14
|
+
/** @type {Object.<string, number>} 质量信号 */
|
|
15
|
+
this.qualitySignals = props.quality_signals ?? props.qualitySignals ?? {};
|
|
16
|
+
/** @type {string[]} 备选方案 */
|
|
17
|
+
this.alternatives = props.alternatives || [];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 从任意输入构造 Reasoning
|
|
22
|
+
* @param {Reasoning|Object|null} input
|
|
23
|
+
* @returns {Reasoning}
|
|
24
|
+
*/
|
|
25
|
+
static from(input) {
|
|
26
|
+
if (input instanceof Reasoning) return input;
|
|
27
|
+
if (!input) return new Reasoning();
|
|
28
|
+
return new Reasoning(input);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* 验证推理信息的完整性
|
|
33
|
+
* @returns {boolean}
|
|
34
|
+
*/
|
|
35
|
+
isValid() {
|
|
36
|
+
return !!(
|
|
37
|
+
this.whyStandard?.trim() &&
|
|
38
|
+
Array.isArray(this.sources) &&
|
|
39
|
+
this.sources.length > 0 &&
|
|
40
|
+
typeof this.confidence === 'number' &&
|
|
41
|
+
this.confidence >= 0 &&
|
|
42
|
+
this.confidence <= 1
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 转换为 wire format JSON (snake_case)
|
|
48
|
+
*/
|
|
49
|
+
toJSON() {
|
|
50
|
+
return {
|
|
51
|
+
why_standard: this.whyStandard,
|
|
52
|
+
sources: this.sources,
|
|
53
|
+
confidence: this.confidence,
|
|
54
|
+
quality_signals: this.qualitySignals,
|
|
55
|
+
alternatives: this.alternatives,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* 从 wire format 创建
|
|
61
|
+
* @param {Object} data
|
|
62
|
+
* @returns {Reasoning}
|
|
63
|
+
*/
|
|
64
|
+
static fromJSON(data) {
|
|
65
|
+
return new Reasoning(data);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export default Reasoning;
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Relations — 关系图值对象
|
|
3
|
+
*
|
|
4
|
+
* 统一为分桶结构(非扁平数组)。
|
|
5
|
+
* 每个桶存储 [{ target, description }] 格式的关系列表。
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** 所有合法的关系桶名 (snake_case) */
|
|
9
|
+
export const RELATION_BUCKETS = [
|
|
10
|
+
'inherits', // 继承
|
|
11
|
+
'implements', // 实现接口/协议
|
|
12
|
+
'calls', // 调用
|
|
13
|
+
'depends_on', // 依赖
|
|
14
|
+
'data_flow', // 数据流向
|
|
15
|
+
'conflicts', // 冲突
|
|
16
|
+
'extends', // 扩展
|
|
17
|
+
'related', // 弱关联
|
|
18
|
+
'alternative', // 替代方案
|
|
19
|
+
'prerequisite', // 前置条件
|
|
20
|
+
'deprecated_by', // 被取代
|
|
21
|
+
'solves', // 解决问题
|
|
22
|
+
'enforces', // 强制约束
|
|
23
|
+
'references', // 引用
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
/** camelCase → snake_case 桶名映射(兼容旧数据) */
|
|
27
|
+
const LEGACY_BUCKET_MAP = {
|
|
28
|
+
dependsOn: 'depends_on',
|
|
29
|
+
dataFlow: 'data_flow',
|
|
30
|
+
deprecatedBy: 'deprecated_by',
|
|
31
|
+
dataFlowTo: 'data_flow',
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export class Relations {
|
|
35
|
+
constructor(buckets = {}) {
|
|
36
|
+
/** @type {Object.<string, Array<{target:string, description:string}>>} */
|
|
37
|
+
this._b = {};
|
|
38
|
+
for (const k of RELATION_BUCKETS) {
|
|
39
|
+
// 同时尝试 snake_case 和 camelCase 的 key
|
|
40
|
+
const vals = buckets[k] || [];
|
|
41
|
+
this._b[k] = vals.map(r => ({
|
|
42
|
+
target: r.target || '',
|
|
43
|
+
description: r.description || '',
|
|
44
|
+
}));
|
|
45
|
+
}
|
|
46
|
+
// 处理旧 camelCase key
|
|
47
|
+
for (const [legacy, canonical] of Object.entries(LEGACY_BUCKET_MAP)) {
|
|
48
|
+
if (buckets[legacy]?.length > 0) {
|
|
49
|
+
for (const r of buckets[legacy]) {
|
|
50
|
+
if (!this._b[canonical].some(x => x.target === r.target)) {
|
|
51
|
+
this._b[canonical].push({
|
|
52
|
+
target: r.target || '',
|
|
53
|
+
description: r.description || '',
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* 从任意输入构造 Relations
|
|
63
|
+
* @param {Relations|Array|Object|null} input
|
|
64
|
+
* @returns {Relations}
|
|
65
|
+
*/
|
|
66
|
+
static from(input) {
|
|
67
|
+
if (input instanceof Relations) return input;
|
|
68
|
+
if (!input) return new Relations();
|
|
69
|
+
// 扁平数组 → 自动分桶(兼容旧 Candidate relations)
|
|
70
|
+
if (Array.isArray(input)) return Relations.fromFlat(input);
|
|
71
|
+
return new Relations(input);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* 从扁平数组 [{type, target, description}] 构建分桶
|
|
76
|
+
* @param {Array<{type:string, target:string, description?:string}>} arr
|
|
77
|
+
* @returns {Relations}
|
|
78
|
+
*/
|
|
79
|
+
static fromFlat(arr) {
|
|
80
|
+
const buckets = {};
|
|
81
|
+
for (const rel of arr) {
|
|
82
|
+
const bucket = LEGACY_BUCKET_MAP[rel.type] || rel.type || 'related';
|
|
83
|
+
if (!buckets[bucket]) buckets[bucket] = [];
|
|
84
|
+
buckets[bucket].push({
|
|
85
|
+
target: rel.target || '',
|
|
86
|
+
description: rel.description || '',
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
return new Relations(buckets);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* 扁平视图(仅 Dashboard 渲染用)
|
|
94
|
+
* @returns {Array<{type:string, target:string, description:string}>}
|
|
95
|
+
*/
|
|
96
|
+
toFlatArray() {
|
|
97
|
+
const result = [];
|
|
98
|
+
for (const [type, list] of Object.entries(this._b)) {
|
|
99
|
+
for (const r of list) {
|
|
100
|
+
result.push({ type, ...r });
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return result;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* 获取指定桶
|
|
108
|
+
* @param {string} type
|
|
109
|
+
* @returns {Array<{target:string, description:string}>}
|
|
110
|
+
*/
|
|
111
|
+
getByType(type) {
|
|
112
|
+
return this._b[type] || [];
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* 是否为空
|
|
117
|
+
* @returns {boolean}
|
|
118
|
+
*/
|
|
119
|
+
isEmpty() {
|
|
120
|
+
return Object.values(this._b).every(l => l.length === 0);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* 添加关系
|
|
125
|
+
* @param {string} type 桶名
|
|
126
|
+
* @param {string} target 目标
|
|
127
|
+
* @param {string} description
|
|
128
|
+
* @returns {Relations}
|
|
129
|
+
*/
|
|
130
|
+
add(type, target, description = '') {
|
|
131
|
+
if (!this._b[type]) this._b[type] = [];
|
|
132
|
+
if (!this._b[type].some(r => r.target === target)) {
|
|
133
|
+
this._b[type].push({ target, description });
|
|
134
|
+
}
|
|
135
|
+
return this;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* 移除关系
|
|
140
|
+
* @param {string} type 桶名
|
|
141
|
+
* @param {string} target 目标
|
|
142
|
+
* @returns {Relations}
|
|
143
|
+
*/
|
|
144
|
+
remove(type, target) {
|
|
145
|
+
if (this._b[type]) {
|
|
146
|
+
this._b[type] = this._b[type].filter(r => r.target !== target);
|
|
147
|
+
}
|
|
148
|
+
return this;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* 转换为 wire format JSON (分桶)
|
|
153
|
+
*/
|
|
154
|
+
toJSON() {
|
|
155
|
+
return { ...this._b };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* 从 wire format 创建
|
|
160
|
+
* @param {Object|Array} data
|
|
161
|
+
* @returns {Relations}
|
|
162
|
+
*/
|
|
163
|
+
static fromJSON(data) {
|
|
164
|
+
return Relations.from(data);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export default Relations;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stats — 统计值对象
|
|
3
|
+
*
|
|
4
|
+
* 记录知识条目的使用统计:浏览、采用、应用、Guard 命中、搜索命中、权威分。
|
|
5
|
+
*/
|
|
6
|
+
export class Stats {
|
|
7
|
+
constructor(props = {}) {
|
|
8
|
+
/** @type {number} 浏览次数 */
|
|
9
|
+
this.views = props.views ?? 0;
|
|
10
|
+
/** @type {number} 采用次数 */
|
|
11
|
+
this.adoptions = props.adoptions ?? 0;
|
|
12
|
+
/** @type {number} 应用次数 */
|
|
13
|
+
this.applications = props.applications ?? 0;
|
|
14
|
+
/** @type {number} Guard 命中次数 */
|
|
15
|
+
this.guardHits = props.guard_hits ?? props.guardHits ?? 0;
|
|
16
|
+
/** @type {number} 搜索命中次数 */
|
|
17
|
+
this.searchHits = props.search_hits ?? props.searchHits ?? 0;
|
|
18
|
+
/** @type {number} 权威分 0-5 */
|
|
19
|
+
this.authority = props.authority ?? 0;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 从任意输入构造 Stats
|
|
24
|
+
* @param {Stats|Object|null} input
|
|
25
|
+
* @returns {Stats}
|
|
26
|
+
*/
|
|
27
|
+
static from(input) {
|
|
28
|
+
if (input instanceof Stats) return input;
|
|
29
|
+
return new Stats(input || {});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* 从旧 Recipe statistics 字段映射
|
|
34
|
+
* @param {Object} old
|
|
35
|
+
* @param {number} qualityOverall 用于初始化 authority
|
|
36
|
+
* @returns {Stats}
|
|
37
|
+
*/
|
|
38
|
+
static fromLegacyRecipe(old, qualityOverall = 0) {
|
|
39
|
+
if (!old) return new Stats();
|
|
40
|
+
return new Stats({
|
|
41
|
+
views: old.viewCount ?? old.views ?? 0,
|
|
42
|
+
adoptions: old.adoptionCount ?? old.adoptions ?? 0,
|
|
43
|
+
applications: old.applicationCount ?? old.applications ?? 0,
|
|
44
|
+
guard_hits: old.guardHitCount ?? old.guardHits ?? 0,
|
|
45
|
+
search_hits: old.searchHits ?? 0,
|
|
46
|
+
authority: qualityOverall * 5,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 增加计数
|
|
52
|
+
* @param {'views'|'adoptions'|'applications'|'guardHits'|'searchHits'} counter
|
|
53
|
+
* @param {number} delta
|
|
54
|
+
* @returns {Stats}
|
|
55
|
+
*/
|
|
56
|
+
increment(counter, delta = 1) {
|
|
57
|
+
if (counter in this && typeof this[counter] === 'number') {
|
|
58
|
+
this[counter] += delta;
|
|
59
|
+
}
|
|
60
|
+
return this;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* 转换为 wire format JSON (snake_case)
|
|
65
|
+
*/
|
|
66
|
+
toJSON() {
|
|
67
|
+
return {
|
|
68
|
+
views: this.views,
|
|
69
|
+
adoptions: this.adoptions,
|
|
70
|
+
applications: this.applications,
|
|
71
|
+
guard_hits: this.guardHits,
|
|
72
|
+
search_hits: this.searchHits,
|
|
73
|
+
authority: this.authority,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* 从 wire format 创建
|
|
79
|
+
* @param {Object} data
|
|
80
|
+
* @returns {Stats}
|
|
81
|
+
*/
|
|
82
|
+
static fromJSON(data) {
|
|
83
|
+
return Stats.from(data);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export default Stats;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 值对象统一导出
|
|
3
|
+
*/
|
|
4
|
+
export { Content } from './Content.js';
|
|
5
|
+
export { Relations, RELATION_BUCKETS } from './Relations.js';
|
|
6
|
+
export { Constraints } from './Constraints.js';
|
|
7
|
+
export { Reasoning } from './Reasoning.js';
|
|
8
|
+
export { Quality } from './Quality.js';
|
|
9
|
+
export { Stats } from './Stats.js';
|
|
@@ -174,6 +174,54 @@ 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
|
+
- Preserve the file's actual code. Use \`<#placeholder#>\` ONLY for literal strings/values a developer would customize
|
|
199
|
+
- Every recipe must be traceable to real code in the file. Do NOT invent code
|
|
200
|
+
- Include relevant \`headers\` (import/require lines) that the code depends on
|
|
201
|
+
- You **MUST** extract at least ONE recipe — every source file has something worth capturing
|
|
202
|
+
|
|
203
|
+
${langProfile.extractionExamples}
|
|
204
|
+
|
|
205
|
+
# Output (JSON Array)
|
|
206
|
+
Each item:
|
|
207
|
+
- title (string): Descriptive English name
|
|
208
|
+
- summary_cn (string): Chinese description (2-3 sentences explaining what this code does)
|
|
209
|
+
- summary_en (string): English description (2-3 sentences)
|
|
210
|
+
- trigger (string): @shortcut
|
|
211
|
+
- category: ${langProfile.categories}
|
|
212
|
+
- language: "${langProfile.primaryLanguage}"
|
|
213
|
+
- code (string): Complete function/method/class from the file
|
|
214
|
+
- headers (string[]): Required import/require lines
|
|
215
|
+
- tags (string[]): Search keywords
|
|
216
|
+
- usageGuide_cn (string): "何时使用" + "关键要点" (2-3 lines)
|
|
217
|
+
- usageGuide_en (string): "When to use" + "Key points" (2-3 lines)
|
|
218
|
+
|
|
219
|
+
Return ONLY a JSON array. Do NOT return an empty array.
|
|
220
|
+
|
|
221
|
+
Files Content:
|
|
222
|
+
${files}`;
|
|
223
|
+
}
|
|
224
|
+
|
|
177
225
|
return `# Role
|
|
178
226
|
You are a ${langProfile.role} extracting production-quality reusable code patterns.
|
|
179
227
|
|