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.
Files changed (97) hide show
  1. package/README.md +4 -4
  2. package/bin/cli.js +5 -33
  3. package/config/constitution.yaml +9 -2
  4. package/dashboard/dist/assets/{icons-CH-H9x0E.js → icons-BkT3XrKf.js} +105 -100
  5. package/dashboard/dist/assets/index-BsB7DzW4.css +1 -0
  6. package/dashboard/dist/assets/index-DdmQMrJJ.js +155 -0
  7. package/dashboard/dist/index.html +3 -3
  8. package/lib/cli/AiScanService.js +13 -11
  9. package/lib/cli/KnowledgeSyncService.js +343 -0
  10. package/lib/cli/SetupService.js +8 -26
  11. package/lib/core/gateway/GatewayActionRegistry.js +48 -58
  12. package/lib/domain/index.js +16 -11
  13. package/lib/domain/knowledge/KnowledgeEntry.js +351 -0
  14. package/lib/domain/knowledge/KnowledgeRepository.js +123 -0
  15. package/lib/domain/knowledge/Lifecycle.js +109 -0
  16. package/lib/domain/knowledge/index.js +27 -0
  17. package/lib/domain/knowledge/values/Constraints.js +125 -0
  18. package/lib/domain/knowledge/values/Content.js +86 -0
  19. package/lib/domain/knowledge/values/Quality.js +93 -0
  20. package/lib/domain/knowledge/values/Reasoning.js +69 -0
  21. package/lib/domain/knowledge/values/Relations.js +168 -0
  22. package/lib/domain/knowledge/values/Stats.js +87 -0
  23. package/lib/domain/knowledge/values/index.js +9 -0
  24. package/lib/external/ai/AiProvider.js +48 -0
  25. package/lib/external/mcp/McpServer.js +7 -5
  26. package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +2 -2
  27. package/lib/external/mcp/handlers/bootstrap.js +116 -11
  28. package/lib/external/mcp/handlers/browse.js +77 -73
  29. package/lib/external/mcp/handlers/candidate.js +29 -276
  30. package/lib/external/mcp/handlers/guard.js +2 -0
  31. package/lib/external/mcp/handlers/knowledge.js +205 -0
  32. package/lib/external/mcp/handlers/structure.js +25 -23
  33. package/lib/external/mcp/handlers/system.js +10 -12
  34. package/lib/external/mcp/tools.js +125 -138
  35. package/lib/http/HttpServer.js +4 -8
  36. package/lib/http/routes/extract.js +48 -4
  37. package/lib/http/routes/knowledge.js +246 -0
  38. package/lib/http/routes/search.js +12 -17
  39. package/lib/infrastructure/database/migrations/016_unified_knowledge_entries.js +395 -0
  40. package/lib/infrastructure/external/XcodeAutomation.js +187 -103
  41. package/lib/injection/ServiceContainer.js +49 -60
  42. package/lib/repository/knowledge/KnowledgeRepository.impl.js +373 -0
  43. package/lib/service/automation/DirectiveDetector.js +2 -3
  44. package/lib/service/automation/FileWatcher.js +67 -28
  45. package/lib/service/automation/XcodeIntegration.js +931 -156
  46. package/lib/service/automation/handlers/AlinkHandler.js +6 -4
  47. package/lib/service/automation/handlers/CreateHandler.js +53 -18
  48. package/lib/service/automation/handlers/GuardHandler.js +183 -20
  49. package/lib/service/automation/handlers/SearchHandler.js +35 -17
  50. package/lib/service/chat/CandidateGuardrail.js +1 -1
  51. package/lib/service/chat/ChatAgent.js +46 -45
  52. package/lib/service/chat/ContextWindow.js +5 -5
  53. package/lib/service/chat/ProducerAgent.js +7 -7
  54. package/lib/service/chat/tools.js +130 -123
  55. package/lib/service/guard/GuardCheckEngine.js +114 -10
  56. package/lib/service/guard/GuardService.js +59 -48
  57. package/lib/service/knowledge/ConfidenceRouter.js +159 -0
  58. package/lib/service/knowledge/KnowledgeFileWriter.js +602 -0
  59. package/lib/service/knowledge/KnowledgeService.js +725 -0
  60. package/lib/service/search/SearchEngine.js +92 -19
  61. package/lib/service/skills/SignalCollector.js +12 -7
  62. package/lib/service/skills/SkillAdvisor.js +13 -11
  63. package/lib/service/snippet/SnippetFactory.js +5 -5
  64. package/package.json +1 -1
  65. package/scripts/install-cursor-skill.js +0 -6
  66. package/scripts/migrate-md-to-knowledge.mjs +364 -0
  67. package/skills/autosnippet-analysis/SKILL.md +15 -7
  68. package/skills/autosnippet-candidates/SKILL.md +6 -6
  69. package/skills/autosnippet-coldstart/SKILL.md +7 -3
  70. package/skills/autosnippet-concepts/SKILL.md +7 -6
  71. package/skills/autosnippet-create/SKILL.md +13 -13
  72. package/skills/autosnippet-intent/SKILL.md +3 -2
  73. package/skills/autosnippet-lifecycle/SKILL.md +5 -5
  74. package/skills/autosnippet-recipes/SKILL.md +16 -4
  75. package/templates/constitution.yaml +1 -1
  76. package/templates/copilot-instructions.md +6 -6
  77. package/templates/recipes-setup/README.md +3 -3
  78. package/dashboard/dist/assets/index-CqJRvYRL.js +0 -197
  79. package/dashboard/dist/assets/index-DICm9PNa.css +0 -1
  80. package/lib/cli/CandidateSyncService.js +0 -261
  81. package/lib/cli/SyncService.js +0 -356
  82. package/lib/domain/candidate/Candidate.js +0 -196
  83. package/lib/domain/candidate/CandidateRepository.js +0 -107
  84. package/lib/domain/candidate/Reasoning.js +0 -52
  85. package/lib/domain/recipe/Recipe.js +0 -421
  86. package/lib/domain/recipe/RecipeRepository.js +0 -54
  87. package/lib/domain/types/CandidateStatus.js +0 -52
  88. package/lib/http/routes/candidates.js +0 -559
  89. package/lib/http/routes/recipes.js +0 -397
  90. package/lib/repository/candidate/CandidateRepository.impl.js +0 -230
  91. package/lib/repository/recipe/RecipeRepository.impl.js +0 -498
  92. package/lib/service/candidate/CandidateAggregator.js +0 -52
  93. package/lib/service/candidate/CandidateFileWriter.js +0 -383
  94. package/lib/service/candidate/CandidateService.js +0 -1001
  95. package/lib/service/recipe/RecipeFileWriter.js +0 -514
  96. package/lib/service/recipe/RecipeService.js +0 -786
  97. 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