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.
Files changed (115) hide show
  1. package/README.md +12 -12
  2. package/bin/cli.js +53 -40
  3. package/config/constitution.yaml +9 -2
  4. package/dashboard/dist/assets/{icons-CH-H9x0E.js → icons-D4IWpDIk.js} +105 -100
  5. package/dashboard/dist/assets/index-CWBNcF9z.css +1 -0
  6. package/dashboard/dist/assets/index-DHtzhbuG.js +120 -0
  7. package/dashboard/dist/index.html +3 -3
  8. package/lib/cli/AiScanService.js +35 -36
  9. package/lib/cli/KnowledgeSyncService.js +345 -0
  10. package/lib/cli/SetupService.js +8 -26
  11. package/lib/cli/UpgradeService.js +28 -0
  12. package/lib/core/gateway/GatewayActionRegistry.js +48 -58
  13. package/lib/domain/index.js +16 -11
  14. package/lib/domain/knowledge/KnowledgeEntry.js +289 -0
  15. package/lib/domain/knowledge/KnowledgeRepository.js +123 -0
  16. package/lib/domain/knowledge/Lifecycle.js +99 -0
  17. package/lib/domain/knowledge/index.js +27 -0
  18. package/lib/domain/knowledge/values/Constraints.js +128 -0
  19. package/lib/domain/knowledge/values/Content.js +69 -0
  20. package/lib/domain/knowledge/values/Quality.js +81 -0
  21. package/lib/domain/knowledge/values/Reasoning.js +70 -0
  22. package/lib/domain/knowledge/values/Relations.js +142 -0
  23. package/lib/domain/knowledge/values/Stats.js +72 -0
  24. package/lib/domain/knowledge/values/index.js +9 -0
  25. package/lib/external/ai/AiProvider.js +85 -11
  26. package/lib/external/mcp/McpServer.js +7 -5
  27. package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +18 -2
  28. package/lib/external/mcp/handlers/bootstrap.js +116 -11
  29. package/lib/external/mcp/handlers/browse.js +76 -73
  30. package/lib/external/mcp/handlers/candidate.js +26 -275
  31. package/lib/external/mcp/handlers/guard.js +2 -0
  32. package/lib/external/mcp/handlers/knowledge.js +267 -0
  33. package/lib/external/mcp/handlers/structure.js +25 -23
  34. package/lib/external/mcp/handlers/system.js +10 -12
  35. package/lib/external/mcp/tools.js +134 -140
  36. package/lib/http/HttpServer.js +14 -8
  37. package/lib/http/routes/ai.js +4 -3
  38. package/lib/http/routes/extract.js +48 -4
  39. package/lib/http/routes/knowledge.js +246 -0
  40. package/lib/http/routes/search.js +12 -17
  41. package/lib/infrastructure/database/migrations/016_unified_knowledge_entries.js +395 -0
  42. package/lib/infrastructure/database/migrations/017_camelcase_knowledge_entries.js +107 -0
  43. package/lib/infrastructure/external/XcodeAutomation.js +187 -103
  44. package/lib/injection/ServiceContainer.js +69 -60
  45. package/lib/repository/knowledge/KnowledgeRepository.impl.js +338 -0
  46. package/lib/service/automation/DirectiveDetector.js +2 -3
  47. package/lib/service/automation/FileWatcher.js +59 -28
  48. package/lib/service/automation/XcodeIntegration.js +931 -156
  49. package/lib/service/automation/handlers/AlinkHandler.js +5 -4
  50. package/lib/service/automation/handlers/CreateHandler.js +53 -19
  51. package/lib/service/automation/handlers/DraftHandler.js +1 -1
  52. package/lib/service/automation/handlers/GuardHandler.js +183 -20
  53. package/lib/service/automation/handlers/SearchHandler.js +25 -22
  54. package/lib/service/candidate/SimilarityService.js +2 -2
  55. package/lib/service/chat/AnalystAgent.js +9 -0
  56. package/lib/service/chat/CandidateGuardrail.js +22 -11
  57. package/lib/service/chat/ChatAgent.js +132 -54
  58. package/lib/service/chat/ContextWindow.js +5 -5
  59. package/lib/service/chat/HandoffProtocol.js +1 -0
  60. package/lib/service/chat/ProducerAgent.js +40 -13
  61. package/lib/service/chat/ReasoningLayer.js +854 -0
  62. package/lib/service/chat/ReasoningTrace.js +329 -0
  63. package/lib/service/chat/tools.js +308 -205
  64. package/lib/service/cursor/CursorDeliveryPipeline.js +279 -0
  65. package/lib/service/cursor/KnowledgeCompressor.js +87 -0
  66. package/lib/service/cursor/RulesGenerator.js +168 -0
  67. package/lib/service/cursor/SkillsSyncer.js +268 -0
  68. package/lib/service/cursor/TokenBudget.js +58 -0
  69. package/lib/service/cursor/TopicClassifier.js +141 -0
  70. package/lib/service/guard/GuardCheckEngine.js +99 -10
  71. package/lib/service/guard/GuardService.js +57 -46
  72. package/lib/service/knowledge/ConfidenceRouter.js +159 -0
  73. package/lib/service/knowledge/KnowledgeFileWriter.js +595 -0
  74. package/lib/service/knowledge/KnowledgeService.js +802 -0
  75. package/lib/service/recipe/RecipeParser.js +3 -12
  76. package/lib/service/search/SearchEngine.js +67 -22
  77. package/lib/service/skills/SignalCollector.js +14 -9
  78. package/lib/service/skills/SkillAdvisor.js +13 -11
  79. package/lib/service/snippet/SnippetFactory.js +5 -5
  80. package/lib/service/spm/SpmService.js +15 -48
  81. package/lib/shared/RecipeReadinessChecker.js +6 -11
  82. package/package.json +1 -1
  83. package/scripts/install-cursor-skill.js +0 -6
  84. package/scripts/migrate-md-to-knowledge.mjs +364 -0
  85. package/skills/autosnippet-analysis/SKILL.md +15 -7
  86. package/skills/autosnippet-candidates/SKILL.md +8 -8
  87. package/skills/autosnippet-coldstart/SKILL.md +8 -4
  88. package/skills/autosnippet-concepts/SKILL.md +7 -6
  89. package/skills/autosnippet-create/SKILL.md +13 -13
  90. package/skills/autosnippet-intent/SKILL.md +3 -2
  91. package/skills/autosnippet-lifecycle/SKILL.md +5 -5
  92. package/skills/autosnippet-recipes/SKILL.md +18 -6
  93. package/templates/constitution.yaml +1 -1
  94. package/templates/copilot-instructions.md +6 -6
  95. package/templates/recipes-setup/README.md +3 -3
  96. package/dashboard/dist/assets/index-CqJRvYRL.js +0 -197
  97. package/dashboard/dist/assets/index-DICm9PNa.css +0 -1
  98. package/lib/cli/CandidateSyncService.js +0 -261
  99. package/lib/cli/SyncService.js +0 -356
  100. package/lib/domain/candidate/Candidate.js +0 -196
  101. package/lib/domain/candidate/CandidateRepository.js +0 -107
  102. package/lib/domain/candidate/Reasoning.js +0 -52
  103. package/lib/domain/recipe/Recipe.js +0 -421
  104. package/lib/domain/recipe/RecipeRepository.js +0 -54
  105. package/lib/domain/types/CandidateStatus.js +0 -52
  106. package/lib/http/routes/candidates.js +0 -559
  107. package/lib/http/routes/recipes.js +0 -397
  108. package/lib/repository/candidate/CandidateRepository.impl.js +0 -230
  109. package/lib/repository/recipe/RecipeRepository.impl.js +0 -498
  110. package/lib/service/candidate/CandidateAggregator.js +0 -52
  111. package/lib/service/candidate/CandidateFileWriter.js +0 -383
  112. package/lib/service/candidate/CandidateService.js +0 -1001
  113. package/lib/service/recipe/RecipeFileWriter.js +0 -514
  114. package/lib/service/recipe/RecipeService.js +0 -786
  115. package/lib/service/recipe/RecipeStatsTracker.js +0 -148
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Lifecycle — 知识实体生命周期状态机(3 状态简化版)
3
+ *
4
+ * pending — 待审核(所有新条目初始状态)
5
+ * active — 已发布(可被搜索/Guard/Export 消费)
6
+ * deprecated — 已废弃
7
+ */
8
+
9
+ export const Lifecycle = {
10
+ /** 待审核 */
11
+ PENDING: 'pending',
12
+ /** 已发布(可被搜索/Guard/Export 消费) */
13
+ ACTIVE: 'active',
14
+ /** 已弃用 */
15
+ DEPRECATED: 'deprecated',
16
+ };
17
+
18
+ /** 候选阶段的所有状态 */
19
+ export const CANDIDATE_STATES = [
20
+ Lifecycle.PENDING,
21
+ ];
22
+
23
+ /** 合法状态转移表 */
24
+ const VALID_TRANSITIONS = {
25
+ [Lifecycle.PENDING]: [Lifecycle.ACTIVE, Lifecycle.DEPRECATED],
26
+ [Lifecycle.ACTIVE]: [Lifecycle.DEPRECATED],
27
+ [Lifecycle.DEPRECATED]: [Lifecycle.PENDING],
28
+ };
29
+
30
+ /**
31
+ * 规范化生命周期值
32
+ * @param {string} lifecycle
33
+ * @returns {string}
34
+ */
35
+ export function normalizeLifecycle(lifecycle) {
36
+ if (Object.values(Lifecycle).includes(lifecycle)) return lifecycle;
37
+ return Lifecycle.PENDING;
38
+ }
39
+
40
+ /**
41
+ * 检查状态转移是否合法
42
+ * @param {string} from
43
+ * @param {string} to
44
+ * @returns {boolean}
45
+ */
46
+ export function isValidTransition(from, to) {
47
+ const normalFrom = normalizeLifecycle(from);
48
+ const normalTo = normalizeLifecycle(to);
49
+ const allowed = VALID_TRANSITIONS[normalFrom];
50
+ return Array.isArray(allowed) && allowed.includes(normalTo);
51
+ }
52
+
53
+ /**
54
+ * 是否为合法的生命周期值
55
+ * @param {string} lifecycle
56
+ * @returns {boolean}
57
+ */
58
+ export function isValidLifecycle(lifecycle) {
59
+ return Object.values(Lifecycle).includes(lifecycle);
60
+ }
61
+
62
+ /**
63
+ * 是否处于候选阶段(待审核)
64
+ * @param {string} lifecycle
65
+ * @returns {boolean}
66
+ */
67
+ export function isCandidate(lifecycle) {
68
+ const normalized = normalizeLifecycle(lifecycle);
69
+ return normalized === Lifecycle.PENDING;
70
+ }
71
+
72
+ /* ── knowledgeType → kind 映射 ── */
73
+
74
+ const KIND_MAP = {
75
+ 'code-standard': 'rule',
76
+ 'code-style': 'rule',
77
+ 'best-practice': 'rule',
78
+ 'boundary-constraint': 'rule',
79
+ 'code-pattern': 'pattern',
80
+ 'architecture': 'pattern',
81
+ 'solution': 'pattern',
82
+ 'anti-pattern': 'pattern',
83
+ 'code-relation': 'fact',
84
+ 'inheritance': 'fact',
85
+ 'call-chain': 'fact',
86
+ 'data-flow': 'fact',
87
+ 'module-dependency': 'fact',
88
+ };
89
+
90
+ /**
91
+ * 从 knowledgeType 推导 kind
92
+ * @param {string} knowledgeType
93
+ * @returns {'rule'|'pattern'|'fact'}
94
+ */
95
+ export function inferKind(knowledgeType) {
96
+ return KIND_MAP[knowledgeType] || 'pattern';
97
+ }
98
+
99
+ export default Lifecycle;
@@ -0,0 +1,27 @@
1
+ /**
2
+ * KnowledgeEntry 领域层统一导出
3
+ */
4
+
5
+ // 实体
6
+ export { KnowledgeEntry } from './KnowledgeEntry.js';
7
+
8
+ // 生命周期
9
+ export {
10
+ Lifecycle,
11
+ isValidTransition,
12
+ isValidLifecycle,
13
+ isCandidate,
14
+ CANDIDATE_STATES,
15
+ inferKind,
16
+ } from './Lifecycle.js';
17
+
18
+ // 值对象
19
+ export { Content } from './values/Content.js';
20
+ export { Relations, RELATION_BUCKETS } from './values/Relations.js';
21
+ export { Constraints } from './values/Constraints.js';
22
+ export { Reasoning } from './values/Reasoning.js';
23
+ export { Quality } from './values/Quality.js';
24
+ export { Stats } from './values/Stats.js';
25
+
26
+ // Repository 接口
27
+ export { KnowledgeRepository } from './KnowledgeRepository.js';
@@ -0,0 +1,128 @@
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.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
+ if (typeof input === 'string') {
28
+ try { input = JSON.parse(input); } catch { return new Constraints(); }
29
+ }
30
+ return new Constraints(input);
31
+ }
32
+
33
+ /**
34
+ * 标准化 Guard 对象
35
+ * @param {Object} g
36
+ * @returns {Guard}
37
+ */
38
+ static _normalizeGuard(g) {
39
+ return {
40
+ id: g.id || null,
41
+ type: g.type || (g.ast_query ? 'ast' : 'regex'),
42
+ pattern: g.pattern || null,
43
+ ast_query: g.ast_query || null,
44
+ message: g.message || '',
45
+ severity: g.severity || 'warning',
46
+ fix_suggestion: g.fix_suggestion || null,
47
+ };
48
+ }
49
+
50
+ /**
51
+ * 获取 regex 类型的 Guard 规则
52
+ * @returns {Array<Guard>}
53
+ */
54
+ getRegexGuards() {
55
+ return this.guards.filter(g => g.type === 'regex' && g.pattern);
56
+ }
57
+
58
+ /**
59
+ * 获取 ast 类型的 Guard 规则
60
+ * @returns {Array<Guard>}
61
+ */
62
+ getAstGuards() {
63
+ return this.guards.filter(g => g.type === 'ast' && g.ast_query);
64
+ }
65
+
66
+ /**
67
+ * 添加 Guard 规则
68
+ * @param {Object} guard
69
+ * @returns {Constraints}
70
+ */
71
+ addGuard(guard) {
72
+ this.guards.push(Constraints._normalizeGuard(guard));
73
+ return this;
74
+ }
75
+
76
+ /**
77
+ * 是否有 Guard 规则
78
+ * @returns {boolean}
79
+ */
80
+ hasGuards() {
81
+ return this.guards.length > 0;
82
+ }
83
+
84
+ /**
85
+ * 是否为空
86
+ * @returns {boolean}
87
+ */
88
+ isEmpty() {
89
+ return this.guards.length === 0 &&
90
+ this.boundaries.length === 0 &&
91
+ this.preconditions.length === 0 &&
92
+ this.sideEffects.length === 0;
93
+ }
94
+
95
+ /**
96
+ * 转换为 wire format JSON
97
+ */
98
+ toJSON() {
99
+ return {
100
+ guards: this.guards,
101
+ boundaries: this.boundaries,
102
+ preconditions: this.preconditions,
103
+ sideEffects: this.sideEffects,
104
+ };
105
+ }
106
+
107
+ /**
108
+ * 从 wire format 创建
109
+ * @param {Object} data
110
+ * @returns {Constraints}
111
+ */
112
+ static fromJSON(data) {
113
+ return Constraints.from(data);
114
+ }
115
+ }
116
+
117
+ /**
118
+ * @typedef {Object} Guard
119
+ * @property {?string} id - Guard 唯一标识
120
+ * @property {'regex'|'ast'} type - 类型
121
+ * @property {?string} pattern - regex pattern (type=regex 时)
122
+ * @property {?Object} ast_query - AST 查询 (type=ast 时)
123
+ * @property {string} message - 错误/警告消息
124
+ * @property {'error'|'warning'|'info'} severity - 严重级别
125
+ * @property {?string} fix_suggestion - 关联修复 Recipe 的 trigger
126
+ */
127
+
128
+ export default Constraints;
@@ -0,0 +1,69 @@
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.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
+ if (typeof input === 'string') {
32
+ try { input = JSON.parse(input); } catch { return new Content(); }
33
+ }
34
+ return new Content(input);
35
+ }
36
+
37
+ /**
38
+ * 是否包含有效内容
39
+ * @returns {boolean}
40
+ */
41
+ hasContent() {
42
+ return !!(this.pattern || this.markdown || this.rationale || this.steps.length > 0);
43
+ }
44
+
45
+ /**
46
+ * 转换为 wire format JSON
47
+ */
48
+ toJSON() {
49
+ return {
50
+ pattern: this.pattern,
51
+ markdown: this.markdown,
52
+ rationale: this.rationale,
53
+ steps: this.steps,
54
+ codeChanges: this.codeChanges,
55
+ verification: this.verification,
56
+ };
57
+ }
58
+
59
+ /**
60
+ * 从 wire format 创建
61
+ * @param {Object} data
62
+ * @returns {Content}
63
+ */
64
+ static fromJSON(data) {
65
+ return new Content(data);
66
+ }
67
+ }
68
+
69
+ export default Content;
@@ -0,0 +1,81 @@
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
+ if (typeof input === 'string') {
28
+ try { input = JSON.parse(input); } catch { return new Quality(); }
29
+ }
30
+ return new Quality(input || {});
31
+ }
32
+
33
+ /**
34
+ * 从 3 维度计算综合分
35
+ * @returns {Quality}
36
+ */
37
+ recalculate() {
38
+ this.overall = Math.round(
39
+ ((this.completeness + this.adaptation + this.documentation) / 3) * 100
40
+ ) / 100;
41
+ this.grade = Quality.calcGrade(this.overall);
42
+ return this;
43
+ }
44
+
45
+ /**
46
+ * 根据分数计算等级
47
+ * @param {number} score 0-1
48
+ * @returns {string}
49
+ */
50
+ static calcGrade(score) {
51
+ if (score >= 0.9) return 'A';
52
+ if (score >= 0.75) return 'B';
53
+ if (score >= 0.6) return 'C';
54
+ if (score >= 0.4) return 'D';
55
+ return 'F';
56
+ }
57
+
58
+ /**
59
+ * 转换为 wire format JSON
60
+ */
61
+ toJSON() {
62
+ return {
63
+ completeness: this.completeness,
64
+ adaptation: this.adaptation,
65
+ documentation: this.documentation,
66
+ overall: this.overall,
67
+ grade: this.grade,
68
+ };
69
+ }
70
+
71
+ /**
72
+ * 从 wire format 创建
73
+ * @param {Object} data
74
+ * @returns {Quality}
75
+ */
76
+ static fromJSON(data) {
77
+ return Quality.from(data);
78
+ }
79
+ }
80
+
81
+ export default Quality;
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Reasoning — 推理值对象
3
+ */
4
+ export class Reasoning {
5
+ constructor(props = {}) {
6
+ /** @type {string} 为什么遵循标准 */
7
+ this.whyStandard = props.whyStandard ?? '';
8
+ /** @type {string[]} 来源列表 */
9
+ this.sources = props.sources || [];
10
+ /** @type {number} 置信度 0-1 */
11
+ this.confidence = props.confidence ?? 0.7;
12
+ /** @type {Object.<string, number>} 质量信号 */
13
+ this.qualitySignals = props.qualitySignals ?? {};
14
+ /** @type {string[]} 备选方案 */
15
+ this.alternatives = props.alternatives || [];
16
+ }
17
+
18
+ /**
19
+ * 从任意输入构造 Reasoning
20
+ * @param {Reasoning|Object|null} input
21
+ * @returns {Reasoning}
22
+ */
23
+ static from(input) {
24
+ if (input instanceof Reasoning) return input;
25
+ if (!input) return new Reasoning();
26
+ if (typeof input === 'string') {
27
+ try { input = JSON.parse(input); } catch { return new Reasoning(); }
28
+ }
29
+ return new Reasoning(input);
30
+ }
31
+
32
+ /**
33
+ * 验证推理信息的完整性
34
+ * @returns {boolean}
35
+ */
36
+ isValid() {
37
+ return !!(
38
+ this.whyStandard?.trim() &&
39
+ Array.isArray(this.sources) &&
40
+ this.sources.length > 0 &&
41
+ typeof this.confidence === 'number' &&
42
+ this.confidence >= 0 &&
43
+ this.confidence <= 1
44
+ );
45
+ }
46
+
47
+ /**
48
+ * 转换为 JSON
49
+ */
50
+ toJSON() {
51
+ return {
52
+ whyStandard: this.whyStandard,
53
+ sources: this.sources,
54
+ confidence: this.confidence,
55
+ qualitySignals: this.qualitySignals,
56
+ alternatives: this.alternatives,
57
+ };
58
+ }
59
+
60
+ /**
61
+ * 从 wire format 创建
62
+ * @param {Object} data
63
+ * @returns {Reasoning}
64
+ */
65
+ static fromJSON(data) {
66
+ return new Reasoning(data);
67
+ }
68
+ }
69
+
70
+ export default Reasoning;
@@ -0,0 +1,142 @@
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
+ export class Relations {
27
+ constructor(buckets = {}) {
28
+ /** @type {Object.<string, Array<{target:string, description:string}>>} */
29
+ this._b = {};
30
+ for (const k of RELATION_BUCKETS) {
31
+ const vals = buckets[k] || [];
32
+ this._b[k] = vals.map(r => ({
33
+ target: r.target || '',
34
+ description: r.description || '',
35
+ }));
36
+ }
37
+ }
38
+
39
+ /**
40
+ * 从任意输入构造 Relations
41
+ * @param {Relations|Array|Object|null} input
42
+ * @returns {Relations}
43
+ */
44
+ static from(input) {
45
+ if (input instanceof Relations) return input;
46
+ if (!input) return new Relations();
47
+ if (typeof input === 'string') {
48
+ try { input = JSON.parse(input); } catch { return new Relations(); }
49
+ }
50
+ if (Array.isArray(input)) {
51
+ // 扁平数组 → 自动分桶
52
+ const buckets = {};
53
+ for (const rel of input) {
54
+ const bucket = rel.type || 'related';
55
+ if (!buckets[bucket]) buckets[bucket] = [];
56
+ buckets[bucket].push({
57
+ target: rel.target || '',
58
+ description: rel.description || '',
59
+ });
60
+ }
61
+ return new Relations(buckets);
62
+ }
63
+ return new Relations(input);
64
+ }
65
+
66
+ /**
67
+ * 扁平视图(仅 Dashboard 渲染用)
68
+ * @returns {Array<{type:string, target:string, description:string}>}
69
+ */
70
+ toFlatArray() {
71
+ const result = [];
72
+ for (const [type, list] of Object.entries(this._b)) {
73
+ for (const r of list) {
74
+ result.push({ type, ...r });
75
+ }
76
+ }
77
+ return result;
78
+ }
79
+
80
+ /**
81
+ * 获取指定桶
82
+ * @param {string} type
83
+ * @returns {Array<{target:string, description:string}>}
84
+ */
85
+ getByType(type) {
86
+ return this._b[type] || [];
87
+ }
88
+
89
+ /**
90
+ * 是否为空
91
+ * @returns {boolean}
92
+ */
93
+ isEmpty() {
94
+ return Object.values(this._b).every(l => l.length === 0);
95
+ }
96
+
97
+ /**
98
+ * 添加关系
99
+ * @param {string} type 桶名
100
+ * @param {string} target 目标
101
+ * @param {string} description
102
+ * @returns {Relations}
103
+ */
104
+ add(type, target, description = '') {
105
+ if (!this._b[type]) this._b[type] = [];
106
+ if (!this._b[type].some(r => r.target === target)) {
107
+ this._b[type].push({ target, description });
108
+ }
109
+ return this;
110
+ }
111
+
112
+ /**
113
+ * 移除关系
114
+ * @param {string} type 桶名
115
+ * @param {string} target 目标
116
+ * @returns {Relations}
117
+ */
118
+ remove(type, target) {
119
+ if (this._b[type]) {
120
+ this._b[type] = this._b[type].filter(r => r.target !== target);
121
+ }
122
+ return this;
123
+ }
124
+
125
+ /**
126
+ * 转换为 wire format JSON (分桶)
127
+ */
128
+ toJSON() {
129
+ return { ...this._b };
130
+ }
131
+
132
+ /**
133
+ * 从 wire format 创建
134
+ * @param {Object|Array} data
135
+ * @returns {Relations}
136
+ */
137
+ static fromJSON(data) {
138
+ return Relations.from(data);
139
+ }
140
+ }
141
+
142
+ export default Relations;
@@ -0,0 +1,72 @@
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.guardHits ?? 0;
16
+ /** @type {number} 搜索命中次数 */
17
+ this.searchHits = 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
+ if (typeof input === 'string') {
30
+ try { input = JSON.parse(input); } catch { return new Stats(); }
31
+ }
32
+ return new Stats(input || {});
33
+ }
34
+
35
+ /**
36
+ * 增加计数
37
+ * @param {'views'|'adoptions'|'applications'|'guardHits'|'searchHits'} counter
38
+ * @param {number} delta
39
+ * @returns {Stats}
40
+ */
41
+ increment(counter, delta = 1) {
42
+ if (counter in this && typeof this[counter] === 'number') {
43
+ this[counter] += delta;
44
+ }
45
+ return this;
46
+ }
47
+
48
+ /**
49
+ * 转换为 JSON
50
+ */
51
+ toJSON() {
52
+ return {
53
+ views: this.views,
54
+ adoptions: this.adoptions,
55
+ applications: this.applications,
56
+ guardHits: this.guardHits,
57
+ searchHits: this.searchHits,
58
+ authority: this.authority,
59
+ };
60
+ }
61
+
62
+ /**
63
+ * 从 wire format 创建
64
+ * @param {Object} data
65
+ * @returns {Stats}
66
+ */
67
+ static fromJSON(data) {
68
+ return Stats.from(data);
69
+ }
70
+ }
71
+
72
+ 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';