pdd-skills 3.0.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 (261) hide show
  1. package/README.md +1478 -0
  2. package/bin/pdd.js +354 -0
  3. package/config/bpmn-rules.yaml +166 -0
  4. package/config/checkstyle.xml +105 -0
  5. package/config/eslint.config.js +48 -0
  6. package/config/pmd.xml +91 -0
  7. package/config/prd-rules.yaml +113 -0
  8. package/config/ruff.toml +45 -0
  9. package/config/sqlfluff.cfg +82 -0
  10. package/hooks/hook-executor.js +332 -0
  11. package/index.js +43 -0
  12. package/lib/api-routes.js +750 -0
  13. package/lib/api-server.js +408 -0
  14. package/lib/cache/cache-config.js +209 -0
  15. package/lib/cache/system-cache.js +852 -0
  16. package/lib/config-manager.js +373 -0
  17. package/lib/generate.js +528 -0
  18. package/lib/grpc/grpc-routes.js +1134 -0
  19. package/lib/grpc/grpc-server.js +912 -0
  20. package/lib/grpc/proto-definitions.js +1033 -0
  21. package/lib/init.js +172 -0
  22. package/lib/iteration/auto-fixer.js +1025 -0
  23. package/lib/iteration/auto-reviewer.js +923 -0
  24. package/lib/iteration/controller.js +577 -0
  25. package/lib/list.js +130 -0
  26. package/lib/mcp-server.js +548 -0
  27. package/lib/openclaw/api-integration.js +535 -0
  28. package/lib/openclaw/cli-integration.js +567 -0
  29. package/lib/openclaw/data-sync.js +845 -0
  30. package/lib/openclaw/openclaw-adapter.js +783 -0
  31. package/lib/plugin/example-plugins/code-stats/index.js +332 -0
  32. package/lib/plugin/example-plugins/code-stats/plugin.json +1 -0
  33. package/lib/plugin/example-plugins/custom-linter/index.js +472 -0
  34. package/lib/plugin/example-plugins/custom-linter/plugin.json +1 -0
  35. package/lib/plugin/example-plugins/hello-world/index.js +86 -0
  36. package/lib/plugin/example-plugins/hello-world/plugin.json +1 -0
  37. package/lib/plugin/plugin-manager.js +655 -0
  38. package/lib/plugin/plugin-sdk.js +565 -0
  39. package/lib/plugin/sandbox.js +627 -0
  40. package/lib/quality/rules/maintainability.js +418 -0
  41. package/lib/quality/rules/performance.js +498 -0
  42. package/lib/quality/rules/readability.js +441 -0
  43. package/lib/quality/rules/robustness.js +504 -0
  44. package/lib/quality/rules/security.js +444 -0
  45. package/lib/quality/scorer.js +576 -0
  46. package/lib/report.js +669 -0
  47. package/lib/sdk-base.js +301 -0
  48. package/lib/sdk-js.js +446 -0
  49. package/lib/sdk-python/README.md +546 -0
  50. package/lib/sdk-python/examples/basic_usage.py +450 -0
  51. package/lib/sdk-python/pdd_sdk/__init__.py +180 -0
  52. package/lib/sdk-python/pdd_sdk/client.py +1170 -0
  53. package/lib/sdk-python/pdd_sdk/events.py +423 -0
  54. package/lib/sdk-python/pdd_sdk/exceptions.py +158 -0
  55. package/lib/sdk-python/pdd_sdk/models.py +518 -0
  56. package/lib/sdk-python/pdd_sdk/utils.py +759 -0
  57. package/lib/token/budget-alert.js +367 -0
  58. package/lib/token/budget-manager.js +485 -0
  59. package/lib/update.js +54 -0
  60. package/lib/utils/logger.js +88 -0
  61. package/lib/verify.js +741 -0
  62. package/lib/version.js +52 -0
  63. package/lib/vm/README.md +102 -0
  64. package/lib/vm/dashboard/api-routes.js +669 -0
  65. package/lib/vm/dashboard/server.js +391 -0
  66. package/lib/vm/dashboard/sse.js +358 -0
  67. package/lib/vm/dashboard/static/css/dashboard.css +1378 -0
  68. package/lib/vm/dashboard/static/index.html +118 -0
  69. package/lib/vm/dashboard/static/js/app.js +949 -0
  70. package/lib/vm/dashboard/static/js/charts.js +913 -0
  71. package/lib/vm/dashboard/static/js/kanban-view.js +1053 -0
  72. package/lib/vm/dashboard/static/js/pipeline-view.js +463 -0
  73. package/lib/vm/dashboard/static/js/quality-view.js +598 -0
  74. package/lib/vm/dashboard/static/js/system-view.js +1021 -0
  75. package/lib/vm/data-provider.js +1191 -0
  76. package/lib/vm/event-bus.js +402 -0
  77. package/lib/vm/hooks/extract-hook.js +307 -0
  78. package/lib/vm/hooks/generate-hook.js +374 -0
  79. package/lib/vm/hooks/hook-interface.js +458 -0
  80. package/lib/vm/hooks/report-hook.js +331 -0
  81. package/lib/vm/hooks/verify-hook.js +454 -0
  82. package/lib/vm/models.js +1003 -0
  83. package/lib/vm/reconciler.js +855 -0
  84. package/lib/vm/scanner.js +988 -0
  85. package/lib/vm/state-schema.js +955 -0
  86. package/lib/vm/state-store.js +733 -0
  87. package/lib/vm/tui/components/card.js +339 -0
  88. package/lib/vm/tui/components/progress-bar.js +368 -0
  89. package/lib/vm/tui/components/sparkline.js +327 -0
  90. package/lib/vm/tui/components/status-light.js +294 -0
  91. package/lib/vm/tui/components/table.js +370 -0
  92. package/lib/vm/tui/input.js +335 -0
  93. package/lib/vm/tui/renderer.js +548 -0
  94. package/lib/vm/tui/screens/kanban-screen.js +397 -0
  95. package/lib/vm/tui/screens/overview-screen.js +357 -0
  96. package/lib/vm/tui/screens/quality-screen.js +336 -0
  97. package/lib/vm/tui/screens/system-screen.js +379 -0
  98. package/lib/vm/tui/tui.js +805 -0
  99. package/package.json +1 -0
  100. package/scripts/cso-analyzer.js +198 -0
  101. package/scripts/eval-runner.js +359 -0
  102. package/scripts/i18n-checker.js +109 -0
  103. package/scripts/linter/activiti-linter.js +272 -0
  104. package/scripts/linter/prd-linter.js +162 -0
  105. package/scripts/linter/report-generator.js +207 -0
  106. package/scripts/linter/run-linters.js +285 -0
  107. package/scripts/linter/sql-linter.js +166 -0
  108. package/scripts/token-analyzer.js +162 -0
  109. package/scripts/vm-test.js +180 -0
  110. package/skills/core/official-doc-writer/LICENSE +21 -0
  111. package/skills/core/official-doc-writer/README.md +232 -0
  112. package/skills/core/official-doc-writer/SKILL.md +475 -0
  113. package/skills/core/official-doc-writer/_meta.json +1 -0
  114. package/skills/core/official-doc-writer/document_generator.py +580 -0
  115. package/skills/core/official-doc-writer/evals/default-evals.json +1 -0
  116. package/skills/core/official-doc-writer/examples.md +150 -0
  117. package/skills/core/official-doc-writer/fonts/FONTS_LIST.md +45 -0
  118. package/skills/core/official-doc-writer/fonts/README.md +141 -0
  119. package/skills/core/official-doc-writer/fonts/SIMFANG.TTF +0 -0
  120. package/skills/core/official-doc-writer/fonts/SIMHEI.TTF +0 -0
  121. package/skills/core/official-doc-writer/fonts/SIMKAI.TTF +0 -0
  122. package/skills/core/official-doc-writer/fonts/SIMSUN.TTC +0 -0
  123. package/skills/core/official-doc-writer/fonts//346/226/271/346/255/243/345/260/217/346/240/207/345/256/213GBK.TTF +0 -0
  124. package/skills/core/official-doc-writer/references/GBT_9704-2012_/345/205/232/346/224/277/346/234/272/345/205/263/345/205/254/346/226/207/346/240/274/345/274/217.md +422 -0
  125. package/skills/core/official-doc-writer/scripts/__pycache__/generate_official_doc.cpython-313.pyc +0 -0
  126. package/skills/core/official-doc-writer/scripts/dialog_manager.py +564 -0
  127. package/skills/core/official-doc-writer/scripts/generate_official_doc.py +252 -0
  128. package/skills/core/official-doc-writer/scripts/install_fonts.py +390 -0
  129. package/skills/core/official-doc-writer/scripts/smart_prompts.py +363 -0
  130. package/skills/core/pdd-ba/SKILL.md +305 -0
  131. package/skills/core/pdd-ba/_meta.json +1 -0
  132. package/skills/core/pdd-ba/evals/default-evals.json +1 -0
  133. package/skills/core/pdd-code-reviewer/SKILL.md +378 -0
  134. package/skills/core/pdd-code-reviewer/_meta.json +1 -0
  135. package/skills/core/pdd-code-reviewer/evals/default-evals.json +1 -0
  136. package/skills/core/pdd-doc-change/SKILL.md +350 -0
  137. package/skills/core/pdd-doc-change/_meta.json +1 -0
  138. package/skills/core/pdd-doc-change/evals/default-evals.json +1 -0
  139. package/skills/core/pdd-doc-gardener/SKILL.md +248 -0
  140. package/skills/core/pdd-doc-gardener/_meta.json +1 -0
  141. package/skills/core/pdd-doc-gardener/evals/default-evals.json +1 -0
  142. package/skills/core/pdd-entropy-reduction/SKILL.md +360 -0
  143. package/skills/core/pdd-entropy-reduction/_meta.json +1 -0
  144. package/skills/core/pdd-entropy-reduction/evals/default-evals.json +1 -0
  145. package/skills/core/pdd-entropy-reduction/references/entropy-report-template.md +287 -0
  146. package/skills/core/pdd-entropy-reduction/references/golden-principles.md +573 -0
  147. package/skills/core/pdd-entropy-reduction/scripts/entropy_scan.py +712 -0
  148. package/skills/core/pdd-extract-features/SKILL.md +320 -0
  149. package/skills/core/pdd-extract-features/_meta.json +1 -0
  150. package/skills/core/pdd-extract-features/evals/default-evals.json +1 -0
  151. package/skills/core/pdd-generate-spec/SKILL.md +418 -0
  152. package/skills/core/pdd-generate-spec/_meta.json +1 -0
  153. package/skills/core/pdd-generate-spec/evals/default-evals.json +1 -0
  154. package/skills/core/pdd-implement-feature/SKILL.md +332 -0
  155. package/skills/core/pdd-implement-feature/_meta.json +1 -0
  156. package/skills/core/pdd-implement-feature/evals/default-evals.json +1 -0
  157. package/skills/core/pdd-main/SKILL.md +540 -0
  158. package/skills/core/pdd-main/_meta.json +1 -0
  159. package/skills/core/pdd-main/evals/default-evals.json +1 -0
  160. package/skills/core/pdd-main/evals/evals.json +215 -0
  161. package/skills/core/pdd-verify-feature/SKILL.md +474 -0
  162. package/skills/core/pdd-verify-feature/_meta.json +1 -0
  163. package/skills/core/pdd-verify-feature/evals/default-evals.json +1 -0
  164. package/skills/core/pdd-vm/evals/default-evals.json +1 -0
  165. package/skills/core/traffic-accident-assessor/LICENSE +29 -0
  166. package/skills/core/traffic-accident-assessor/SKILL.md +439 -0
  167. package/skills/core/traffic-accident-assessor/evals/evals.json +1 -0
  168. package/skills/core/traffic-accident-assessor/references/accident-types.md +369 -0
  169. package/skills/core/traffic-accident-assessor/references/liability-rules.md +287 -0
  170. package/skills/core/traffic-accident-assessor/references/traffic-laws.md +226 -0
  171. package/skills/core/traffic-accident-assessor/references//351/253/230/345/260/224/345/244/253/350/257/264/346/230/216/344/271/246.pdf +32576 -106
  172. package/skills/core/traffic-accident-assessor/scripts/generate_official_statement.py +588 -0
  173. package/skills/core/traffic-accident-assessor/scripts/generate_report.py +495 -0
  174. package/skills/core/traffic-accident-assessor/scripts/generate_statement.py +528 -0
  175. package/skills/core/traffic-accident-assessor.zip +0 -0
  176. package/skills/entropy/expert-arch-enforcer/SKILL.md +292 -0
  177. package/skills/entropy/expert-arch-enforcer/_meta.json +1 -0
  178. package/skills/entropy/expert-arch-enforcer/evals/default-evals.json +1 -0
  179. package/skills/entropy/expert-auto-refactor/SKILL.md +327 -0
  180. package/skills/entropy/expert-auto-refactor/_meta.json +1 -0
  181. package/skills/entropy/expert-auto-refactor/evals/default-evals.json +1 -0
  182. package/skills/entropy/expert-code-quality/SKILL.md +468 -0
  183. package/skills/entropy/expert-code-quality/_meta.json +1 -0
  184. package/skills/entropy/expert-code-quality/evals/default-evals.json +1 -0
  185. package/skills/entropy/expert-code-quality/evals/evals.json +109 -0
  186. package/skills/entropy/expert-code-quality/references/code-smells.md +605 -0
  187. package/skills/entropy/expert-code-quality/references/design-patterns.md +1111 -0
  188. package/skills/entropy/expert-code-quality/references/refactoring-catalog.md +1281 -0
  189. package/skills/entropy/expert-code-quality/references/solid-principles.md +524 -0
  190. package/skills/entropy/expert-entropy-auditor/SKILL.md +276 -0
  191. package/skills/entropy/expert-entropy-auditor/_meta.json +1 -0
  192. package/skills/entropy/expert-entropy-auditor/evals/default-evals.json +1 -0
  193. package/skills/expert/expert-activiti/SKILL.md +497 -0
  194. package/skills/expert/expert-activiti/_meta.json +1 -0
  195. package/skills/expert/expert-mysql/SKILL.md +832 -0
  196. package/skills/expert/expert-mysql/_meta.json +1 -0
  197. package/skills/expert/expert-performance/SKILL.md +379 -0
  198. package/skills/expert/expert-performance/_meta.json +1 -0
  199. package/skills/expert/expert-performance/evals/default-evals.json +1 -0
  200. package/skills/expert/expert-ruoyi/SKILL.md +472 -0
  201. package/skills/expert/expert-ruoyi/_meta.json +1 -0
  202. package/skills/expert/expert-security/SKILL.md +1341 -0
  203. package/skills/expert/expert-security/_meta.json +1 -0
  204. package/skills/expert/expert-security/evals/default-evals.json +1 -0
  205. package/skills/expert/software-architect/SKILL.md +350 -0
  206. package/skills/expert/software-architect/_meta.json +1 -0
  207. package/skills/expert/software-engineer/SKILL.md +437 -0
  208. package/skills/expert/software-engineer/_meta.json +1 -0
  209. package/skills/expert/software-engineer/architecture.md +130 -0
  210. package/skills/expert/software-engineer/patterns.md +151 -0
  211. package/skills/expert/software-engineer/testing.md +135 -0
  212. package/skills/expert/system-architect/SKILL.md +628 -0
  213. package/skills/expert/system-architect/_meta.json +1 -0
  214. package/skills/expert/system-architect/assets/templates/ARCHITECTURE.md +25 -0
  215. package/skills/expert/system-architect/assets/templates/README.md +44 -0
  216. package/skills/expert/system-architect/references/js-ts-standards.md +18 -0
  217. package/skills/expert/system-architect/references/python-standards.md +19 -0
  218. package/skills/expert/system-architect/references/scaffolding.md +61 -0
  219. package/skills/expert/system-architect/references/security-checklist.md +21 -0
  220. package/skills/openspec/openspec-apply-change/SKILL.md +156 -0
  221. package/skills/openspec/openspec-apply-change/_meta.json +1 -0
  222. package/skills/openspec/openspec-archive-change/SKILL.md +114 -0
  223. package/skills/openspec/openspec-archive-change/_meta.json +1 -0
  224. package/skills/openspec/openspec-bulk-archive-change/SKILL.md +246 -0
  225. package/skills/openspec/openspec-bulk-archive-change/_meta.json +1 -0
  226. package/skills/openspec/openspec-continue-change/SKILL.md +118 -0
  227. package/skills/openspec/openspec-continue-change/_meta.json +1 -0
  228. package/skills/openspec/openspec-explore/SKILL.md +288 -0
  229. package/skills/openspec/openspec-explore/_meta.json +1 -0
  230. package/skills/openspec/openspec-ff-change/SKILL.md +101 -0
  231. package/skills/openspec/openspec-ff-change/_meta.json +1 -0
  232. package/skills/openspec/openspec-new-change/SKILL.md +74 -0
  233. package/skills/openspec/openspec-new-change/_meta.json +1 -0
  234. package/skills/openspec/openspec-onboard/SKILL.md +554 -0
  235. package/skills/openspec/openspec-onboard/_meta.json +1 -0
  236. package/skills/openspec/openspec-sync-specs/SKILL.md +138 -0
  237. package/skills/openspec/openspec-sync-specs/_meta.json +1 -0
  238. package/skills/openspec/openspec-verify-change/SKILL.md +168 -0
  239. package/skills/openspec/openspec-verify-change/_meta.json +1 -0
  240. package/skills/pr/pdd-multi-review/SKILL.md +534 -0
  241. package/skills/pr/pdd-multi-review/_meta.json +1 -0
  242. package/skills/pr/pdd-pr-batch/SKILL.md +303 -0
  243. package/skills/pr/pdd-pr-batch/_meta.json +1 -0
  244. package/skills/pr/pdd-pr-create/SKILL.md +344 -0
  245. package/skills/pr/pdd-pr-create/_meta.json +1 -0
  246. package/skills/pr/pdd-pr-merge/SKILL.md +286 -0
  247. package/skills/pr/pdd-pr-merge/_meta.json +1 -0
  248. package/skills/pr/pdd-pr-review/SKILL.md +217 -0
  249. package/skills/pr/pdd-pr-review/_meta.json +1 -0
  250. package/skills/pr/pdd-task-manager/SKILL.md +636 -0
  251. package/skills/pr/pdd-task-manager/_meta.json +1 -0
  252. package/skills/pr/pdd-template-engine/SKILL.md +306 -0
  253. package/skills/pr/pdd-template-engine/_meta.json +1 -0
  254. package/templates/behavior-shaping/iron-law-template.md +87 -0
  255. package/templates/behavior-shaping/rationalization-template.md +62 -0
  256. package/templates/behavior-shaping/red-flags-template.md +70 -0
  257. package/templates/bilingual-template.md +139 -0
  258. package/templates/config/default.yaml +47 -0
  259. package/templates/project/default/README.md +31 -0
  260. package/templates/project/frontend/README.md +46 -0
  261. package/templates/project/java/README.md +48 -0
@@ -0,0 +1,1191 @@
1
+ /**
2
+ * PDD Visual Manager - 数据提供者聚合层 (VM-A020 + VM-A021 + VM-A023)
3
+ *
4
+ * 作为 PDD-VM 系统的核心数据聚合器,统一管理:
5
+ * - 状态存储 (StateStore) - 项目状态文件读写
6
+ * - 项目扫描 (ProjectScanner) - 文件系统扫描
7
+ * - 数据对齐 (Reconciler) - state 与 scanner 的三路合并
8
+ * - 事件总线 (VMEventBus) - 事件发布与订阅
9
+ * - 引擎桥接 - 缓存、Token预算、质量评分、迭代控制等外部引擎
10
+ *
11
+ * 提供统一的查询 API:
12
+ * - 功能点 CRUD 和筛选排序
13
+ * - 质量矩阵分析
14
+ * - Token 使用统计
15
+ * - 迭代进度跟踪
16
+ * - 多格式导出(JSON/Markdown/CSV)
17
+ *
18
+ * @module vm/data-provider
19
+ */
20
+
21
+ import {
22
+ StageEnum,
23
+ STAGE_VALUES,
24
+ STAGE_ORDER,
25
+ Feature,
26
+ ProjectSummary,
27
+ QualityMetrics,
28
+ TokenUsage
29
+ } from './models.js';
30
+
31
+ import StateStore from './state-store.js';
32
+ import ProjectScanner from './scanner.js';
33
+ import Reconciler from './reconciler.js';
34
+ import { createEventBus, VMEvents } from './event-bus.js';
35
+
36
+ // 可选的 chalk 彩色输出支持
37
+ let chalk;
38
+ try {
39
+ const chalkModule = await import('chalk');
40
+ chalk = chalkModule.default;
41
+ } catch {
42
+ chalk = {
43
+ cyan: (s) => s,
44
+ green: (s) => s,
45
+ yellow: (s) => s,
46
+ red: (s) => s,
47
+ blue: (s) => s,
48
+ gray: (s) => s,
49
+ bold: (s) => s,
50
+ magenta: (s) => s
51
+ };
52
+ }
53
+
54
+ /**
55
+ * PDD 数据提供者类
56
+ * 系统的核心数据聚合层,协调所有子模块的工作
57
+ */
58
+ class PDDDataProvider {
59
+ /**
60
+ * 创建数据提供者实例
61
+ *
62
+ * @param {string} projectRoot - 项目根目录路径
63
+ * @param {Object} [options={}] - 配置选项
64
+ * @param {boolean} [options.autoRefresh=false] - 是否自动定期刷新
65
+ * @param {number} [options.refreshInterval=60000] - 自动刷新间隔(毫秒)
66
+ * @param {boolean} [options.enableEngineBridges=true] - 是否启用引擎桥接
67
+ */
68
+ constructor(projectRoot, options = {}) {
69
+ if (!projectRoot || typeof projectRoot !== 'string') {
70
+ throw new Error('PDDDataProvider: projectRoot 必须是非空字符串');
71
+ }
72
+
73
+ /** @type {string} 项目根目录绝对路径 */
74
+ this.projectRoot = projectRoot;
75
+
76
+ /** @type {Object} 配置选项 */
77
+ this.options = {
78
+ autoRefresh: false,
79
+ refreshInterval: 60000,
80
+ enableEngineBridges: true,
81
+ ...options
82
+ };
83
+
84
+ /** @type {StateStore|null} 状态存储器实例 */
85
+ this.stateStore = null;
86
+
87
+ /** @type {ProjectScanner|null} 项目扫描器实例 */
88
+ this.scanner = null;
89
+
90
+ /** @type {Reconciler|null} 数据对齐器实例 */
91
+ this.reconciler = null;
92
+
93
+ /** @type {VMEventBus} 事件总线实例 */
94
+ this.eventBus = createEventBus();
95
+
96
+ /**
97
+ * 引擎桥接对象
98
+ * 用于连接外部引擎(缓存、Token预算、质量评分、迭代控制)
99
+ * @type {Object<string, Object|null>}
100
+ */
101
+ this.engineBridges = {
102
+ cache: null, // SystemCache 实例
103
+ budget: null, // BudgetManager 实例
104
+ scorer: null, // Scorer 实例
105
+ iteration: null // IterationController 实例
106
+ };
107
+
108
+ /** @type {boolean} 是否已完成初始化 */
109
+ this.initialized = false;
110
+
111
+ /** @type {Date|null} 最后刷新时间 */
112
+ this.lastRefreshTime = null;
113
+
114
+ /**
115
+ * 功能点内存缓存
116
+ * @type {Map<string, Feature>} ID -> Feature 对象的映射
117
+ * @private
118
+ */
119
+ this._features = new Map();
120
+
121
+ /** @type {ProjectSummary|null} 项目汇总信息 */
122
+ this._summary = null;
123
+
124
+ /** @type {number|null} 自动刷新定时器 ID */
125
+ this._refreshTimer = null;
126
+
127
+ console.log(chalk.gray(`[DataProvider] 初始化数据提供者: ${projectRoot}`));
128
+ }
129
+
130
+ // ==================== 初始化方法 ====================
131
+
132
+ /**
133
+ * 初始化数据提供者
134
+ * 按顺序执行:创建子模块 -> 加载引擎 -> 扫描项目 -> 对齐数据 -> 构建缓存
135
+ *
136
+ * @returns {Promise<void>}
137
+ * @throws {Error} 当初始化步骤失败时抛出异常
138
+ *
139
+ * @example
140
+ * const provider = new PDDDataProvider('./my-project');
141
+ * await provider.init();
142
+ * console.log(provider.getSummary());
143
+ */
144
+ async init() {
145
+ if (this.initialized) {
146
+ console.log(chalk.yellow('[DataProvider] 已经初始化过,跳过重复初始化'));
147
+ return;
148
+ }
149
+
150
+ const startTime = Date.now();
151
+
152
+ console.log(chalk.blue('\n[DataProvider] 开始初始化...\n'));
153
+
154
+ try {
155
+ // 1. 创建状态存储器
156
+ console.log(chalk.gray('[DataProvider] 步骤1/8: 创建状态存储器...'));
157
+ this.stateStore = new StateStore(this.projectRoot, this.options.stateStoreOptions || {});
158
+
159
+ // 2. 创建项目扫描器
160
+ console.log(chalk.gray('[DataProvider] 步骤2/8: 创建项目扫描器...'));
161
+ this.scanner = new ProjectScanner(this.projectRoot, this.options.scannerConfig || {});
162
+
163
+ // 3. 创建数据对齐器
164
+ console.log(chalk.gray('[DataProvider] 步骤3/8: 创建数据对齐器...'));
165
+ this.reconciler = new Reconciler(this.stateStore, this.scanner, this.options.reconcilerOptions || {});
166
+
167
+ // 4. 尝试加载引擎桥接
168
+ if (this.options.enableEngineBridges !== false) {
169
+ console.log(chalk.gray('[DataProvider] 步骤4/8: 加载引擎桥接...'));
170
+ await this._bridgeEngineCaches();
171
+ }
172
+
173
+ // 5. 执行全量扫描
174
+ console.log(chalk.gray('[DataProvider] 步骤5/8: 执行全量项目扫描...'));
175
+ const scanResult = await this.scanner.fullScan();
176
+
177
+ // 6. 执行数据对齐
178
+ console.log(chalk.gray('[DataProvider] 步骤6/8: 执行数据对齐...'));
179
+ const reconcileResult = await this.reconciler.reconcile();
180
+
181
+ // 7. 加载状态并构建功能点映射
182
+ console.log(chalk.gray('[DataProvider] 步骤7/8: 加载状态到内存...'));
183
+ const state = await this.stateStore.loadState();
184
+
185
+ // 将 features 存入 _features Map
186
+ this._features.clear();
187
+ const stateFeatures = state.project.features || [];
188
+ for (const featData of stateFeatures) {
189
+ try {
190
+ const feature = Feature.fromJSON(featData);
191
+ this._features.set(feature.id, feature);
192
+ } catch (error) {
193
+ console.warn(chalk.yellow(`[DataProvider] 解析功能点失败: ${featData.id} - ${error.message}`));
194
+ }
195
+ }
196
+
197
+ // 8. 构建项目摘要
198
+ console.log(chalk.gray('[DataProvider] 步骤8/8: 构建项目摘要...'));
199
+ this._summary = this._buildSummary();
200
+
201
+ // 标记初始化完成
202
+ this.initialized = true;
203
+ this.lastRefreshTime = new Date();
204
+
205
+ const duration = Date.now() - startTime;
206
+
207
+ // 发射系统事件
208
+ this.eventBus.emitSystemEvent('dataProvider', 'online', `初始化完成,耗时 ${duration}ms`);
209
+ this.eventBus.emitDataRefreshed(this._summary);
210
+
211
+ console.log(chalk.green(`\n[DataProvider] 初始化完成!`));
212
+ console.log(chalk.gray(` - 功能点数量: ${this._features.size}`));
213
+ console.log(chalk.gray(` - 扫描耗时: ${scanResult.duration}ms`));
214
+ console.log(chalk.gray(` - 对齐耗时: ${reconcileResult.duration}ms`));
215
+ console.log(chalk.gray(` - 总耗时: ${duration}ms\n`));
216
+
217
+ // 启动自动刷新(如果配置了)
218
+ if (this.options.autoRefresh) {
219
+ this._startAutoRefresh();
220
+ }
221
+
222
+ } catch (error) {
223
+ console.error(chalk.red(`[DataProvider] 初始化失败: ${error.message}`));
224
+ this.eventBus.emitSystemEvent('dataProvider', 'error', error.message);
225
+ throw error;
226
+ }
227
+ }
228
+
229
+ /**
230
+ * 刷新数据
231
+ * 重新执行完整的初始化流程,并检测变更
232
+ *
233
+ * @returns {Promise<{added:number, removed:number, changed:number}>}
234
+ * 变更统计信息
235
+ */
236
+ async refresh() {
237
+ if (!this.initialized) {
238
+ await this.init();
239
+ return { added: 0, removed: 0, changed: 0 };
240
+ }
241
+
242
+ console.log(chalk.blue('\n[DataProvider] 开始数据刷新...\n'));
243
+
244
+ // 保存旧的功能点快照用于变更检测
245
+ const oldFeatures = new Map(this._features);
246
+
247
+ // 重新初始化
248
+ await this.init();
249
+
250
+ // 检测变更
251
+ const changes = this._detectChanges(oldFeatures, this._features);
252
+
253
+ console.log(chalk.green(
254
+ `[DataProvider] 刷新完成: +${changes.added} /-${changes.removed} ~${changes.changed}\n`
255
+ ));
256
+
257
+ return changes;
258
+ }
259
+
260
+ /**
261
+ * 启动自动刷新定时器
262
+ * @private
263
+ */
264
+ _startAutoRefresh() {
265
+ if (this._refreshTimer) {
266
+ clearInterval(this._refreshTimer);
267
+ }
268
+
269
+ this._refreshTimer = setInterval(async () => {
270
+ try {
271
+ console.log(chalk.gray('[DataProvider] 自动刷新触发...'));
272
+ await this.refresh();
273
+ } catch (error) {
274
+ console.error(chalk.red(`[DataProvider] 自动刷新失败: ${error.message}`));
275
+ }
276
+ }, this.options.refreshInterval);
277
+
278
+ console.log(chalk.gray(
279
+ `[DataProvider] 已启动自动刷新,间隔: ${this.options.refreshInterval / 1000}s`
280
+ ));
281
+ }
282
+
283
+ /**
284
+ * 停止自动刷新定时器
285
+ */
286
+ stopAutoRefresh() {
287
+ if (this._refreshTimer) {
288
+ clearInterval(this._refreshTimer);
289
+ this._refreshTimer = null;
290
+ console.log(chalk.gray('[DataProvider] 已停止自动刷新'));
291
+ }
292
+ }
293
+
294
+ // ==================== 查询 API ====================
295
+
296
+ /**
297
+ * 获取所有功能点
298
+ * @returns {Feature[]} 功能点数组
299
+ */
300
+ getFeatures() {
301
+ this._checkInitialized();
302
+ return Array.from(this._features.values());
303
+ }
304
+
305
+ /**
306
+ * 根据 ID 获取功能点
307
+ * @param {string} id - 功能点 ID
308
+ * @returns {Feature|null} 功能点对象或 null
309
+ */
310
+ getFeatureById(id) {
311
+ this._checkInitialized();
312
+ return this._features.get(id) || null;
313
+ }
314
+
315
+ /**
316
+ * 根据阶段获取功能点列表
317
+ * @param {string} stage - 阶段值 (StageEnum)
318
+ * @returns {Feature[]} 符合条件的功能点数组
319
+ */
320
+ getFeaturesByStage(stage) {
321
+ this._checkInitialized();
322
+ return this.getFeatures().filter(f => f.stage === stage);
323
+ }
324
+
325
+ /**
326
+ * 获取项目汇总信息
327
+ * @returns {ProjectSummary|null} 项目汇总对象
328
+ */
329
+ getSummary() {
330
+ this._checkInitialized();
331
+ return this._summary;
332
+ }
333
+
334
+ /**
335
+ * 搜索功能点(模糊匹配)
336
+ * 在名称、描述、标签中进行模糊搜索
337
+ *
338
+ * @param {string} query - 搜索关键词
339
+ * @returns {Feature[]} 匹配的功能点数组
340
+ *
341
+ * @example
342
+ * provider.search('auth') // 搜索名称/描述/标签中包含 "auth" 的功能点
343
+ */
344
+ search(query) {
345
+ this._checkInitialized();
346
+
347
+ if (!query || typeof query !== 'string') {
348
+ return [];
349
+ }
350
+
351
+ const q = query.toLowerCase().trim();
352
+
353
+ return this.getFeatures().filter(f =>
354
+ f.name.toLowerCase().includes(q) ||
355
+ (f.description || '').toLowerCase().includes(q) ||
356
+ (f.tags || []).some(t => t.toLowerCase().includes(q)) ||
357
+ f.id.toLowerCase().includes(q)
358
+ );
359
+ }
360
+
361
+ /**
362
+ * 高级筛选功能点
363
+ * 支持按阶段、质量分数、问题等条件组合筛选
364
+ *
365
+ * @param {Object} criteria - 筛选条件
366
+ * @param {string} [criteria.stage] - 阶段过滤
367
+ * @param {number} [criteria.minScore] - 最小质量分数
368
+ * @param {number} [criteria.maxScore] - 最大质量分数
369
+ * @param {boolean} [criteria.hasIssues] - 是否有问题(true=仅返回有问题的,false=仅返回无问题的)
370
+ * @param {string} [criteria.priority] - 优先级过滤 (P0/P1/P2/P3)
371
+ * @param {string} [criteria.tag] - 标签过滤
372
+ * @returns {Feature[]} 筛选后的功能点数组
373
+ *
374
+ * @example
375
+ * // 获取实现阶段且质量分 >= 70 且有问题的功能点
376
+ * provider.filter({ stage: 'implementing', minScore: 70, hasIssues: true })
377
+ */
378
+ filter(criteria = {}) {
379
+ this._checkInitialized();
380
+
381
+ let results = this.getFeatures();
382
+
383
+ // 阶段过滤
384
+ if (criteria.stage && STAGE_VALUES.includes(criteria.stage)) {
385
+ results = results.filter(f => f.stage === criteria.stage);
386
+ }
387
+
388
+ // 最小质量分数
389
+ if (criteria.minScore !== undefined && criteria.minScore != null) {
390
+ results = results.filter(f => (f.quality?.score ?? 0) >= criteria.minScore);
391
+ }
392
+
393
+ // 最大质量分数
394
+ if (criteria.maxScore !== undefined && criteria.maxScore != null) {
395
+ results = results.filter(f => (f.quality?.score ?? 0) <= criteria.maxScore);
396
+ }
397
+
398
+ // 问题过滤
399
+ if (criteria.hasIssues === true) {
400
+ results = results.filter(f => f.quality?.issues?.length > 0);
401
+ } else if (criteria.hasIssues === false) {
402
+ results = results.filter(f => !f.quality?.issues || f.quality.issues.length === 0);
403
+ }
404
+
405
+ // 优先级过滤
406
+ if (criteria.priority) {
407
+ results = results.filter(f => f.priority === criteria.priority);
408
+ }
409
+
410
+ // 标签过滤
411
+ if (criteria.tag) {
412
+ results = results.filter(f =>
413
+ f.tags && Array.isArray(f.tags) && f.tags.includes(criteria.tag)
414
+ );
415
+ }
416
+
417
+ return results;
418
+ }
419
+
420
+ /**
421
+ * 对功能点列表进行排序
422
+ *
423
+ * @param {Feature[]} features - 要排序的功能点数组(可选,默认使用全部)
424
+ * @param {string} [by='name'] - 排序字段 ('stage'|'score'|'name'|'tokens'|'date')
425
+ * @param {string} [order='asc'] - 排序方向 ('asc'|'desc')
426
+ * @returns {Feature[]} 排序后的新数组(不修改原数组)
427
+ *
428
+ * @example
429
+ * provider.sort(provider.getFeaturesByStage('implementing'), 'score', 'desc')
430
+ */
431
+ sort(features, by = 'name', order = 'asc') {
432
+ const targetArray = features || this.getFeatures();
433
+ const sorted = [...targetArray];
434
+
435
+ // 排序比较器映射
436
+ const comparators = {
437
+ stage: (a, b) => (STAGE_ORDER[a.stage] ?? 99) - (STAGE_ORDER[b.stage] ?? 99),
438
+ score: (a, b) => (a.quality?.score ?? 0) - (b.quality?.score ?? 0),
439
+ name: (a, b) => a.name.localeCompare(b.name),
440
+ tokens: (a, b) => (a.tokens?.used ?? 0) - (b.tokens?.used ?? 0),
441
+ date: (a, b) => new Date(a.updatedAt) - new Date(b.updatedAt),
442
+ priority: (a, b) => {
443
+ const orderMap = { P0: 0, P1: 1, P2: 2, P3: 3 };
444
+ return (orderMap[a.priority] ?? 99) - (orderMap[b.priority] ?? 99);
445
+ },
446
+ iterations: (a, b) => (a.iterations?.length ?? 0) - (b.iterations?.length ?? 0)
447
+ };
448
+
449
+ const comparatorFn = comparators[by] || comparators.name;
450
+
451
+ sorted.sort(order === 'desc' ? (a, b) => comparatorFn(b, a) : comparatorFn);
452
+
453
+ return sorted;
454
+ }
455
+
456
+ // ==================== 质量分析 API ====================
457
+
458
+ /**
459
+ * 获取质量矩阵
460
+ * 统计所有功能点的质量指标分布情况
461
+ *
462
+ * @returns {{
463
+ * avgScore: number,
464
+ * avgCoverage: number,
465
+ * avgPassRate: number,
466
+ * gradeDistribution: Object<string, number>,
467
+ * topIssues: Array<{type:string, count:number, features:string[]}>
468
+ * }} 质量矩阵数据
469
+ */
470
+ getQualityMatrix() {
471
+ this._checkInitialized();
472
+
473
+ const features = this.getFeatures();
474
+ const withQuality = features.filter(f => f.quality);
475
+
476
+ // 等级分布统计
477
+ const gradeDist = { S: 0, A: 0, B: 0, C: 0, D: 0, F: 0 };
478
+
479
+ let totalScore = 0;
480
+ let totalCoverage = 0;
481
+ let totalPassRate = 0;
482
+
483
+ // 问题类型聚合
484
+ const issueMap = new Map(); // type -> { count, features[] }
485
+
486
+ withQuality.forEach(f => {
487
+ // 统计等级分布
488
+ const g = f.quality.grade || 'F';
489
+ gradeDist[g] = (gradeDist[g] || 0) + 1;
490
+
491
+ // 累加各项指标
492
+ totalScore += f.quality.score || 0;
493
+ totalCoverage += f.quality.coverage || 0;
494
+ totalPassRate += f.quality.passRate || 0;
495
+
496
+ // 聚合问题类型
497
+ (f.quality.issues || []).forEach(issue => {
498
+ const key = issue.type || issue.name || 'unknown';
499
+
500
+ if (!issueMap.has(key)) {
501
+ issueMap.set(key, { count: 0, features: [] });
502
+ }
503
+
504
+ const entry = issueMap.get(key);
505
+ entry.count++;
506
+
507
+ // 记录关联的功能点名称(去重)
508
+ if (!entry.features.includes(f.name)) {
509
+ entry.features.push(f.name);
510
+ }
511
+ });
512
+ });
513
+
514
+ const n = withQuality.length || 1;
515
+
516
+ // 取前10个最常见的问题
517
+ const topIssues = Array.from(issueMap.entries())
518
+ .sort((a, b) => b[1].count - a[1].count)
519
+ .slice(0, 10)
520
+ .map(([type, data]) => ({
521
+ type,
522
+ count: data.count,
523
+ features: data.features
524
+ }));
525
+
526
+ return {
527
+ avgScore: Math.round(totalScore / n * 10) / 10,
528
+ avgCoverage: Math.round(totalCoverage / n * 10) / 10,
529
+ avgPassRate: Math.round(totalPassRate / n * 10) / 10,
530
+ gradeDistribution: gradeDist,
531
+ topIssues,
532
+ evaluatedCount: withQuality.length,
533
+ totalCount: features.length
534
+ };
535
+ }
536
+
537
+ // ==================== Token 统计 API ====================
538
+
539
+ /**
540
+ * 获取 Token 使用统计
541
+ * 汇总所有功能点的 Token 消耗情况
542
+ *
543
+ * @returns {{
544
+ * total: number,
545
+ * used: number,
546
+ * remaining: number,
547
+ * percent: number,
548
+ * byStage: Object<string, number>,
549
+ * trend: Array<Object>
550
+ * }} Token 统计信息
551
+ */
552
+ getTokenStats() {
553
+ this._checkInitialized();
554
+
555
+ const features = this.getFeatures();
556
+
557
+ let totalUsed = 0;
558
+ let totalBudget = 0;
559
+
560
+ // 按阶段统计
561
+ const byStage = {};
562
+ STAGE_VALUES.forEach(s => { byStage[s] = 0; });
563
+
564
+ // 历史趋势数据
565
+ const history = [];
566
+
567
+ features.forEach(f => {
568
+ if (f.tokens) {
569
+ totalUsed += f.tokens.used || 0;
570
+ totalBudget += f.tokens.total || 0;
571
+
572
+ // 按阶段累加
573
+ if (f.tokens.byStage) {
574
+ Object.entries(f.tokens.byStage).forEach(([stage, amount]) => {
575
+ byStage[stage] = (byStage[stage] || 0) + amount;
576
+ });
577
+ }
578
+
579
+ // 收集历史记录
580
+ if (f.tokens.history && Array.isArray(f.tokens.history)) {
581
+ history.push(...f.tokens.history);
582
+ }
583
+ }
584
+ });
585
+
586
+ // 排序历史记录(取最近20条)
587
+ const trend = history
588
+ .sort((a, b) => (a.timestamp || 0) - (b.timestamp || 0))
589
+ .slice(-20);
590
+
591
+ const percent = totalBudget > 0 ? Math.round(totalUsed / totalBudget * 100) : 0;
592
+
593
+ // 检查是否需要发射阈值告警
594
+ if (percent >= 90 || percent >= 70) {
595
+ this.eventBus.emitTokenThreshold(totalUsed, totalBudget, percent);
596
+ }
597
+
598
+ return {
599
+ total: totalBudget,
600
+ used: totalUsed,
601
+ remaining: Math.max(0, totalBudget - totalUsed),
602
+ percent,
603
+ byStage,
604
+ trend
605
+ };
606
+ }
607
+
608
+ // ==================== 迭代跟踪 API ====================
609
+
610
+ /**
611
+ * 获取迭代列表
612
+ * 返回所有有迭代记录的功能点的迭代概览
613
+ *
614
+ * @returns {Array<{
615
+ * featureId: string,
616
+ * featureName: string,
617
+ * rounds: number,
618
+ * converged: boolean,
619
+ * lastScore: number
620
+ * }>} 迭代概览列表
621
+ */
622
+ getIterationList() {
623
+ this._checkInitialized();
624
+
625
+ return this.getFeatures()
626
+ .filter(f => f.iterations && f.iterations.length > 0)
627
+ .map(f => {
628
+ const sortedIters = [...f.iterations].sort((a, b) => b.round - a.round);
629
+ const lastRound = sortedIters[0];
630
+
631
+ return {
632
+ featureId: f.id,
633
+ featureName: f.name,
634
+ rounds: f.iterations.length,
635
+ converged: f.isConverged ? f.isConverged() : f.iterations.length >= 3,
636
+ lastScore: lastRound ? lastRound.score : 0
637
+ };
638
+ });
639
+ }
640
+
641
+ /**
642
+ * 获取指定功能点的迭代历史
643
+ * @param {string} featureId - 功能点 ID
644
+ * @returns {import('./models.js').IterationRound[]} 迭代轮次数组
645
+ */
646
+ getFeatureIterations(featureId) {
647
+ this._checkInitialized();
648
+
649
+ const f = this.getFeatureById(featureId);
650
+ if (!f) {
651
+ return [];
652
+ }
653
+
654
+ return (f.iterations || []).sort((a, b) => a.round - b.round);
655
+ }
656
+
657
+ // ==================== 引擎桥接 API (VM-A021) ====================
658
+
659
+ /**
660
+ * 获取缓存统计信息
661
+ * 从 SystemCache 引擎获取缓存命中率等统计数据
662
+ *
663
+ * @returns {Object|null} 缓存统计信息,如果缓存引擎未加载则返回 null
664
+ */
665
+ getCacheStats() {
666
+ if (!this.engineBridges.cache) {
667
+ return null;
668
+ }
669
+
670
+ try {
671
+ return this.engineBridges.cache.getStats();
672
+ } catch (error) {
673
+ console.warn(chalk.yellow(`[DataProvider] 获取缓存统计失败: ${error.message}`));
674
+ return null;
675
+ }
676
+ }
677
+
678
+ /**
679
+ * 获取系统健康状态
680
+ * 检查各子系统(API、MCP、gRPC 等)的运行状态
681
+ *
682
+ * @returns {{
683
+ * api: { status: string, latency: number },
684
+ * mcp: { status: string, latency: number },
685
+ * grpc: { status: string, latency: number },
686
+ * openclaw: { status: string, latency: number },
687
+ * plugins: Array<Object>,
688
+ * uptime: number,
689
+ * memory: NodeJS.MemoryUsage
690
+ * }} 系统健康状态
691
+ */
692
+ getSystemHealth() {
693
+ // 尝试从各引擎获取真实状态
694
+ const health = {
695
+ api: { status: 'unknown', latency: 0 },
696
+ mcp: { status: 'unknown', latency: 0 },
697
+ grpc: { status: 'unknown', latency: 0 },
698
+ openclaw: { status: 'unknown', latency: 0 },
699
+ plugins: [],
700
+ uptime: process.uptime(),
701
+ memory: process.memoryUsage()
702
+ };
703
+
704
+ // 如果有缓存引擎,尝试获取其健康状态
705
+ if (this.engineBridges.cache && typeof this.engineBridges.cache.healthCheck === 'function') {
706
+ try {
707
+ const cacheHealth = this.engineBridges.cache.healthCheck();
708
+ if (cacheHealth) {
709
+ health.plugins.push({
710
+ name: 'cache',
711
+ status: cacheHealth.status || 'ok',
712
+ details: cacheHealth
713
+ });
714
+ }
715
+ } catch (e) {
716
+ // 忽略错误
717
+ }
718
+ }
719
+
720
+ // 如果有预算引擎,尝试获取其状态
721
+ if (this.engineBridges.budget && typeof this.engineBridges.budget.getStatus === 'function') {
722
+ try {
723
+ const budgetStatus = this.engineBridges.budget.getStatus();
724
+ health.plugins.push({
725
+ name: 'budget',
726
+ status: 'ok',
727
+ details: budgetStatus
728
+ });
729
+ } catch (e) {
730
+ // 忽略错误
731
+ }
732
+ }
733
+
734
+ return health;
735
+ }
736
+
737
+ // ==================== 导出 API (VM-A023) ====================
738
+
739
+ /**
740
+ * 导出为 JSON 格式
741
+ *
742
+ * @param {Object} [options={}] - 导出选项
743
+ * @param {string} [options.mode='full'] - 导出模式 ('full'|'summary')
744
+ * @param {string[]} [options.fields] - 仅导出指定字段(mode='full' 时有效)
745
+ * @returns {string} JSON 字符串
746
+ *
747
+ * @example
748
+ * // 导出完整数据
749
+ * provider.exportJSON()
750
+ *
751
+ * // 仅导出摘要
752
+ * provider.exportJSON({ mode: 'summary' })
753
+ *
754
+ * // 只导出特定字段
755
+ * provider.exportJSON({ fields: ['id', 'name', 'stage', 'quality'] })
756
+ */
757
+ exportJSON(options = {}) {
758
+ this._checkInitialized();
759
+
760
+ const mode = options.mode || 'full';
761
+
762
+ if (mode === 'summary') {
763
+ return JSON.stringify({
764
+ summary: this._summary ? this._summary.toJSON() : null,
765
+ exportedAt: new Date().toISOString(),
766
+ version: '1.0.0'
767
+ }, null, 2);
768
+ }
769
+
770
+ // 完整模式
771
+ let features;
772
+
773
+ if (options.fields && Array.isArray(options.fields) && options.fields.length > 0) {
774
+ // 仅导出指定字段
775
+ features = this.getFeatures().map(f => {
776
+ const obj = {};
777
+ options.fields.forEach(key => {
778
+ if (f[key] != null) {
779
+ obj[key] = typeof f[key]?.toJSON === 'function' ? f[key].toJSON() : f[key];
780
+ }
781
+ });
782
+ return obj;
783
+ });
784
+ } else {
785
+ // 导出完整数据
786
+ features = this.getFeatures().map(f => f.toJSON());
787
+ }
788
+
789
+ return JSON.stringify({
790
+ summary: this._summary ? this._summary.toJSON() : null,
791
+ features,
792
+ exportedAt: new Date().toISOString(),
793
+ version: '1.0.0',
794
+ totalFeatures: this._features.size
795
+ }, null, 2);
796
+ }
797
+
798
+ /**
799
+ * 导出为 Markdown 格式
800
+ * 生成美观的项目状态报告
801
+ *
802
+ * @param {Object} [options={}] - 导出选项
803
+ * @param {boolean} [options.includeDetails=true] - 是否包含详细信息
804
+ * @param {boolean} [options.includeQualityMatrix=false] - 是否包含质量矩阵
805
+ * @returns {string} Markdown 格式的报告文本
806
+ */
807
+ exportMarkdown(options = {}) {
808
+ this._checkInitialized();
809
+
810
+ const includeDetails = options.includeDetails !== false;
811
+ const includeQualityMatrix = options.includeQualityMatrix || false;
812
+
813
+ // 按阶段排序的功能点
814
+ const features = this.sort(this.getFeatures(), 'stage');
815
+ const summary = this._summary || {};
816
+
817
+ let md = '# PDD 项目状态报告\n\n';
818
+
819
+ // 报告头信息
820
+ md += `> **导出时间**: ${new Date().toLocaleString('zh-CN')}\n`;
821
+ md += `> **项目路径**: \`${this.projectRoot}\`\n\n`;
822
+
823
+ // ===== 项目概览 =====
824
+ md += '## 项目概览\n\n';
825
+ md += '| 指标 | 值 |\n|------|-----|\n';
826
+ md += `| 总功能点 | **${summary.totalFeatures || 0}** |\n`;
827
+ md += `| 整体进度 | ${this._generateProgressBar(summary.overallProgress || 0)} ${summary.overallProgress || 0}% |\n`;
828
+ md += `| 平均质量分 | ${summary.avgQualityScore || '-'} |\n`;
829
+ md += `| Token消耗 | ${(summary.totalTokens || 0).toLocaleString()} |\n`;
830
+ md += `| 平均迭代 | ${summary.avgIterations || 0} 轮 |\n`;
831
+
832
+ if (this.lastRefreshTime) {
833
+ md += `| 最后刷新 | ${this.lastRefreshTime.toLocaleString('zh-CN')} |\n`;
834
+ }
835
+
836
+ md += '\n';
837
+
838
+ // ===== 阶段分布 =====
839
+ if (summary.stageDistribution) {
840
+ md += '## 阶段分布\n\n';
841
+ md += '| 阶段 | 数量 | 占比 |\n|------|------|------|\n';
842
+
843
+ const total = summary.totalFeatures || 1;
844
+ for (const [stage, count] of Object.entries(summary.stageDistribution)) {
845
+ const pct = total > 0 ? Math.round(count / total * 100) : 0;
846
+ const bar = '█'.repeat(Math.ceil(pct / 5)) || '-';
847
+ md += `| ${stage} | ${count} | ${pct}% ${bar} |\n`;
848
+ }
849
+
850
+ md += '\n';
851
+ }
852
+
853
+ // ===== 功能点列表 =====
854
+ md += '## 功能点列表\n\n';
855
+
856
+ if (includeDetails) {
857
+ md += '| ID | 名称 | 阶段 | 优先级 | 进度 | 评分 | 等级 | Token | 迭代 |\n';
858
+ md += '|----|------|------|--------|------|------|------|-------|------|\n';
859
+
860
+ features.forEach(f => {
861
+ const progress = f.progress ? f.progress() : 0;
862
+ md += `| ${f.id} | ${f.name} | ${f.stage} | ${f.priority} | ${progress}% | `;
863
+ md += `${f.quality?.score || '-'} | ${f.quality?.grade || '-'} | `;
864
+ md += `${f.tokens?.used || 0} | ${f.iterations?.length || 0} |\n`;
865
+ });
866
+ } else {
867
+ md += '| ID | 名称 | 阶段 | 评分 | Token |\n|----|------|------|------|-------|\n';
868
+
869
+ features.forEach(f => {
870
+ md += `| ${f.id} | ${f.name} | ${f.stage} | ${f.quality?.score||'-'} | ${f.tokens?.used||0} |\n`;
871
+ });
872
+ }
873
+
874
+ md += '\n';
875
+
876
+ // ===== 质量矩阵(可选)=====
877
+ if (includeQualityMatrix) {
878
+ const qualityMatrix = this.getQualityMatrix();
879
+
880
+ md += '## 质量分析\n\n';
881
+ md += `- **平均评分**: ${qualityMatrix.avgScore}\n`;
882
+ md += `- **平均覆盖率**: ${qualityMatrix.avgCoverage}%\n`;
883
+ md += `- **平均通过率**: ${qualityMatrix.avgPassRate}%\n`;
884
+ md += `- **已评估**: ${qualityMatrix.evaluatedCount}/${qualityMatrix.totalCount}\n\n`;
885
+
886
+ if (qualityMatrix.topIssues.length > 0) {
887
+ md += '### Top 问题\n\n';
888
+ md += '| 类型 | 出现次数 | 关联功能点 |\n|------|----------|------------|\n';
889
+
890
+ qualityMatrix.topIssues.slice(0, 5).forEach(issue => {
891
+ md += `| ${issue.type} | ${issue.count} | ${issue.features.slice(0, 3).join(', ')}${issue.features.length > 3 ? '...' : ''} |\n`;
892
+ });
893
+
894
+ md += '\n';
895
+ }
896
+ }
897
+
898
+ // ===== Token 统计 =====
899
+ const tokenStats = this.getTokenStats();
900
+ md += '## Token 使用统计\n\n';
901
+ md += `- **总配额**: ${tokenStats.total.toLocaleString()}\n`;
902
+ md += `- **已使用**: ${tokenStats.used.toLocaleString()} (${tokenStats.percent}%)\n`;
903
+ md += `- **剩余**: ${tokenStats.remaining.toLocaleString()}\n\n';
904
+
905
+ return md;
906
+ }
907
+
908
+ /**
909
+ * 导出为 CSV 格式
910
+ * 适合在 Excel 或其他表格工具中使用
911
+ *
912
+ * @param {Object} [options={}] - 导出选项
913
+ * @returns {string} CSV 格式的文本
914
+ */
915
+ exportCSV(options = {}) {
916
+ this._checkInitialized();
917
+
918
+ // 按阶段排序
919
+ const features = this.sort(this.getFeatures(), 'stage');
920
+
921
+ // CSV 头部
922
+ let csv = 'ID,Name,Stage,Priority,Score,Grade,Coverage,PassRate,TokenUsed,TokenTotal,Iterations,Tags,CreatedAt,UpdatedAt\n';
923
+
924
+ // 数据行
925
+ features.forEach(f => {
926
+ // 转义字段中的特殊字符
927
+ const escapeField = (value) => {
928
+ if (value == null || value === '') return '';
929
+ const str = String(value);
930
+ // 如果包含逗号、引号或换行,需要用引号包裹并转义内部引号
931
+ if (str.includes(',') || str.includes('"') || str.includes('\n')) {
932
+ return `"${str.replace(/"/g, '""')}"`;
933
+ }
934
+ return str;
935
+ };
936
+
937
+ csv += [
938
+ escapeField(f.id),
939
+ escapeField(f.name),
940
+ escapeField(f.stage),
941
+ escapeField(f.priority),
942
+ escapeField(f.quality?.score),
943
+ escapeField(f.quality?.grade),
944
+ escapeField(f.quality?.coverage),
945
+ escapeField(f.quality?.passRate),
946
+ escapeField(f.tokens?.used || 0),
947
+ escapeField(f.tokens?.total || 0),
948
+ escapeField(f.iterations?.length || 0),
949
+ escapeField((f.tags || []).join(';')),
950
+ escapeField(f.createdAt ? new Date(f.createdAt).toISOString() : ''),
951
+ escapeField(f.updatedAt ? new Date(f.updatedAt).toISOString() : '')
952
+ ].join(',') + '\n';
953
+ });
954
+
955
+ return csv;
956
+ }
957
+
958
+ // ==================== 内部方法 ====================
959
+
960
+ /**
961
+ * 检查是否已初始化
962
+ * @private
963
+ * @throws {Error} 未初始化时抛出异常
964
+ */
965
+ _checkInitialized() {
966
+ if (!this.initialized) {
967
+ throw new Error('PDDDataProvider: 尚未初始化,请先调用 init() 方法');
968
+ }
969
+ }
970
+
971
+ /**
972
+ * 构建项目摘要
973
+ * 从当前功能点集合计算汇总统计信息
974
+ *
975
+ * @returns {ProjectSummary} 项目摘要对象
976
+ * @private
977
+ */
978
+ _buildSummary() {
979
+ const features = this.getFeatures();
980
+ const total = features.length;
981
+
982
+ // 阶段分布统计
983
+ const stageDist = {};
984
+ STAGE_VALUES.forEach(s => { stageDist[s] = 0; });
985
+
986
+ let totalProgress = 0;
987
+ let totalScore = 0;
988
+ let totalTokens = 0;
989
+ let totalIters = 0;
990
+ let qualityCount = 0;
991
+
992
+ features.forEach(f => {
993
+ // 阶段计数
994
+ stageDist[f.stage] = (stageDist[f.stage] || 0) + 1;
995
+
996
+ // 进度累加
997
+ totalProgress += f.progress ? f.progress() : (STAGE_ORDER[f.stage] || 0) / 5 * 100;
998
+
999
+ // 质量分数累加
1000
+ if (f.quality) {
1001
+ totalScore += f.quality.score || 0;
1002
+ qualityCount++;
1003
+ }
1004
+
1005
+ // Token 累加
1006
+ totalTokens += f.tokens?.used || 0;
1007
+
1008
+ // 迭代轮次累加
1009
+ totalIters += f.iterations?.length || 0;
1010
+ });
1011
+
1012
+ // 计算平均值
1013
+ const n = total || 1;
1014
+
1015
+ return new ProjectSummary({
1016
+ name: this.projectRoot.split(/[/\\]/).pop() || 'pdd-project',
1017
+ version: '1.0.0',
1018
+ totalFeatures: total,
1019
+ stageDistribution: stageDist,
1020
+ overallProgress: total > 0 ? Math.round(totalProgress / total) : 0,
1021
+ avgQualityScore: qualityCount > 0 ? Math.round(totalScore / qualityCount * 10) / 10 : 0,
1022
+ totalTokens,
1023
+ avgIterations: total > 0 ? Math.round(totalIters / total * 10) / 10 : 0,
1024
+ lastUpdated: Date.now()
1025
+ });
1026
+ }
1027
+
1028
+ /**
1029
+ * 桥接外部引擎
1030
+ * 尝试加载可选的外部引擎模块(缓存、预算、评分、迭代控制)
1031
+ * 每个引擎都是可选的,加载失败不会影响主流程
1032
+ *
1033
+ * @returns {Promise<void>}
1034
+ * @private
1035
+ */
1036
+ async _bridgeEngineCaches() {
1037
+ // 引擎模块路径定义(相对于 lib/vm/data-provider.js 的位置)
1038
+ const modulePaths = {
1039
+ cache: '../../cache/system-cache.js',
1040
+ budget: '../../token/budget-manager.js',
1041
+ scorer: '../../quality/scorer.js',
1042
+ iteration: '../../iteration/controller.js'
1043
+ };
1044
+
1045
+ // 尝试加载每个引擎
1046
+ for (const [key, modulePath] of Object.entries(modulePaths)) {
1047
+ try {
1048
+ console.log(chalk.gray(`[DataProvider] 尝试加载引擎: ${key} (${modulePath})...`));
1049
+
1050
+ // 动态导入模块
1051
+ const module = await import(modulePath);
1052
+
1053
+ // 获取默认导出或命名导出
1054
+ const EngineClass = module.default || module;
1055
+
1056
+ // 创建实例(如果导出的是类)
1057
+ if (typeof EngineClass === 'function') {
1058
+ this.engineBridges[key] = new EngineClass(this.projectRoot);
1059
+ } else if (typeof EngineClass === 'object' && EngineClass !== null) {
1060
+ // 如果是对象(可能是单例),直接使用
1061
+ this.engineBridges[key] = EngineClass;
1062
+ }
1063
+
1064
+ console.log(chalk.green(`[DataProvider] ✓ 引擎加载成功: ${key}`));
1065
+
1066
+ } catch (error) {
1067
+ // 加载失败是正常的(引擎可能不存在),设为 null 并继续
1068
+ this.engineBridges[key] = null;
1069
+ console.warn(chalk.yellow(
1070
+ `[DataProvider] ⚠ 引擎未加载: ${key} - ${error.message.substring(0, 100)}`
1071
+ ));
1072
+ }
1073
+ }
1074
+ }
1075
+
1076
+ /**
1077
+ * 检测新旧功能点之间的变更
1078
+ * 通过对比新旧 Map,识别新增、删除、修改的功能点
1079
+ *
1080
+ * @param {Map<string, Feature>} oldMap - 旧的功能点映射
1081
+ * @param {Map<string, Feature>} newMap - 新的功能点映射
1082
+ * @returns {{ added:number, removed:number, changed:number }}
1083
+ * 变更统计
1084
+ * @private
1085
+ */
1086
+ _detectChanges(oldMap, newMap) {
1087
+ let added = 0;
1088
+ let removed = 0;
1089
+ let changed = 0;
1090
+
1091
+ const oldIds = new Set(oldMap.keys());
1092
+ const newIds = new Set(newMap.keys());
1093
+
1094
+ // 检测新增的功能点
1095
+ for (const id of newIds) {
1096
+ if (!oldIds.has(id)) {
1097
+ added++;
1098
+ const newFeat = newMap.get(id);
1099
+ this.eventBus.emitSystemEvent('feature', 'added', `新功能点: ${newFeat.name}`);
1100
+ }
1101
+ }
1102
+
1103
+ // 检测删除的功能点
1104
+ for (const id of oldIds) {
1105
+ if (!newIds.has(id)) {
1106
+ removed++;
1107
+ const oldFeat = oldMap.get(id);
1108
+ this.eventBus.emitSystemEvent('feature', 'removed', `删除功能点: ${oldFeat.name}`);
1109
+ }
1110
+ }
1111
+
1112
+ // 检测修改的功能点
1113
+ for (const id of newIds) {
1114
+ if (oldIds.has(id)) {
1115
+ const oldFeat = oldMap.get(id);
1116
+ const newFeat = newMap.get(id);
1117
+
1118
+ // 检查阶段变更
1119
+ if (oldFeat.stage !== newFeat.stage) {
1120
+ changed++;
1121
+ this.eventBus.emitStageChange(newFeat.id, oldFeat.stage, newFeat.stage, newFeat);
1122
+ }
1123
+
1124
+ // 检查质量变更
1125
+ const oldScore = oldFeat.quality?.score ?? null;
1126
+ const newScore = newFeat.quality?.score ?? null;
1127
+
1128
+ if (oldScore !== newScore && newFeat.quality) {
1129
+ this.eventBus.emitQualityUpdate(newFeat.id, newFeat.quality, oldFeat.quality || null);
1130
+ }
1131
+
1132
+ // 如果不是阶段变更但其他字段变了,也算作 changed
1133
+ if (oldFeat.stage === newFeat.stage && oldScore === newScore) {
1134
+ // 检查其他关键字段的变更
1135
+ const oldJson = JSON.stringify(oldFeat.toJSON());
1136
+ const newJson = JSON.stringify(newFeat.toJSON());
1137
+
1138
+ if (oldJson !== newJson) {
1139
+ changed++;
1140
+ }
1141
+ }
1142
+ }
1143
+ }
1144
+
1145
+ return { added, removed, changed };
1146
+ }
1147
+
1148
+ /**
1149
+ * 生成进度条字符串
1150
+ * @param {number} percent - 百分比 (0-100)
1151
+ * @returns {string} 进度条可视化字符串
1152
+ * @private
1153
+ */
1154
+ _generateProgressBar(percent) {
1155
+ const p = Math.max(0, Math.min(100, percent));
1156
+ const filled = Math.floor(p / 5); // 每5%一个字符
1157
+ const empty = 20 - filled;
1158
+
1159
+ return '[' + '█'.repeat(filled) + '░'.repeat(empty) + ']';
1160
+ }
1161
+
1162
+ // ==================== 清理和销毁 ====================
1163
+
1164
+ /**
1165
+ * 销毁数据提供者
1166
+ * 释放资源,停止定时器
1167
+ */
1168
+ async destroy() {
1169
+ // 停止自动刷新
1170
+ this.stopAutoRefresh();
1171
+
1172
+ // 清空内存缓存
1173
+ this._features.clear();
1174
+ this._summary = null;
1175
+
1176
+ // 清空事件日志
1177
+ this.eventBus.clearHistory();
1178
+
1179
+ // 移除所有事件监听器
1180
+ this.eventBus.removeAllListeners();
1181
+
1182
+ // 重置状态
1183
+ this.initialized = false;
1184
+ this.lastRefreshTime = null;
1185
+
1186
+ console.log(chalk.gray('[DataProvider] 已销毁'));
1187
+ }
1188
+ }
1189
+
1190
+ export { PDDDataProvider };
1191
+ export default PDDDataProvider;