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,402 @@
1
+ /**
2
+ * PDD Visual Manager - 事件总线 (VM-A022)
3
+ *
4
+ * 基于 Node.js EventEmitter 的事件发布/订阅系统,提供:
5
+ * - 类型安全的事件发射方法
6
+ * - SSE (Server-Sent Events) 订阅源支持
7
+ * - 事件历史记录与查询
8
+ * - 增量事件推送(基于 lastEventId)
9
+ *
10
+ * @module vm/event-bus
11
+ */
12
+
13
+ import { EventEmitter } from 'events';
14
+
15
+ /**
16
+ * VM 事件类型枚举
17
+ * 定义所有 PDD-VM 系统中的标准事件类型
18
+ *
19
+ * @readonly
20
+ * @enum {string}
21
+ */
22
+ export const VMEvents = Object.freeze({
23
+ /** 功能点阶段变更 */
24
+ FEATURE_STAGE_CHANGED: 'feature_stage_changed',
25
+ /** 质量指标更新 */
26
+ QUALITY_UPDATED: 'quality_updated',
27
+ /** Token 使用阈值告警 */
28
+ TOKEN_THRESHOLD: 'token_threshold',
29
+ ** 缓存命中统计 */
30
+ CACHE_HIT: 'cache_hit',
31
+ /** 迭代轮次完成 */
32
+ ITERATION_ROUND_COMPLETE: 'iteration_round_complete',
33
+ /** 系统级事件(启动、错误等) */
34
+ SYSTEM_EVENT: 'system_event',
35
+ /** 数据刷新完成 */
36
+ DATA_REFRESHED: 'data_refreshed'
37
+ });
38
+
39
+ /**
40
+ * VM 事件总线类
41
+ * 继承 Node.js EventEmitter,提供类型安全的事件接口和 SSE 支持
42
+ */
43
+ class VMEventBus extends EventEmitter {
44
+ /**
45
+ * 创建事件总线实例
46
+ */
47
+ constructor() {
48
+ super();
49
+
50
+ // 设置最大监听器数量(避免内存泄漏警告)
51
+ this.setMaxListeners(50);
52
+
53
+ /** @type {Array<Object>} 事件日志记录 */
54
+ this._eventLog = [];
55
+
56
+ /** @type {number} 事件 ID 自增计数器 */
57
+ this._eventId = 0;
58
+
59
+ /** @type {number} 最大日志容量 */
60
+ this._maxLogSize = 500;
61
+ }
62
+
63
+ // ==================== 类型安全的 emit 方法 ====================
64
+
65
+ /**
66
+ * 发射功能点阶段变更事件
67
+ * @param {string} featureId - 功能点 ID
68
+ * @param {string} oldStage - 旧阶段
69
+ * @param {string} newStage - 新阶段
70
+ * @param {import('./models.js').Feature} [feature] - 功能点对象(可选)
71
+ * @returns {boolean} 是否有监听器处理此事件
72
+ */
73
+ emitStageChange(featureId, oldStage, newStage, feature) {
74
+ const event = {
75
+ id: ++this._eventId,
76
+ type: VMEvents.FEATURE_STAGE_CHANGED,
77
+ data: {
78
+ featureId,
79
+ oldStage,
80
+ newStage,
81
+ feature: feature ? feature.toJSON() : null,
82
+ timestamp: Date.now()
83
+ }
84
+ };
85
+
86
+ this._log(event);
87
+ return this.emit(VMEvents.FEATURE_STAGE_CHANGED, event.data);
88
+ }
89
+
90
+ /**
91
+ * 发射质量指标更新事件
92
+ * @param {string} featureId - 功能点 ID
93
+ * @param {import('./models.js').QualityMetrics} quality - 新的质量指标
94
+ * @param {import('./models.js').QualityMetrics|null} [prevQuality] - 旧的质量指标
95
+ * @returns {boolean} 是否有监听器处理此事件
96
+ */
97
+ emitQualityUpdate(featureId, quality, prevQuality) {
98
+ const event = {
99
+ id: ++this._eventId,
100
+ type: VMEvents.QUALITY_UPDATED,
101
+ data: {
102
+ featureId,
103
+ quality: quality ? quality.toJSON() : null,
104
+ prevQuality: prevQuality ? prevQuality.toJSON() : null,
105
+ timestamp: Date.now()
106
+ }
107
+ };
108
+
109
+ this._log(event);
110
+ return this.emit(VMEvents.QUALITY_UPDATED, event.data);
111
+ }
112
+
113
+ /**
114
+ * 发射 Token 阈值告警事件
115
+ * @param {number} current - 当前使用量
116
+ * @param {number} total - 总配额
117
+ * @param {number} percent - 使用百分比
118
+ * @returns {boolean} 是否有监听器处理此事件
119
+ */
120
+ emitTokenThreshold(current, total, percent) {
121
+ const event = {
122
+ id: ++this._eventId,
123
+ type: VMEvents.TOKEN_THRESHOLD,
124
+ data: {
125
+ current,
126
+ total,
127
+ percent,
128
+ threshold: percent >= 90 ? 'critical' : percent >= 70 ? 'warning' : 'normal',
129
+ timestamp: Date.now()
130
+ }
131
+ };
132
+
133
+ this._log(event);
134
+ return this.emit(VMEvents.TOKEN_THRESHOLD, event.data);
135
+ }
136
+
137
+ /**
138
+ * 发射缓存命中统计事件
139
+ * @param {number} hitRate - 当前命中率
140
+ * @param {number} [prevHitRate] - 上次命中率(用于对比变化趋势)
141
+ * @returns {boolean} 是否有监听器处理此事件
142
+ */
143
+ emitCacheHit(hitRate, prevHitRate) {
144
+ const event = {
145
+ id: ++this._eventId,
146
+ type: VMEvents.CACHE_HIT,
147
+ data: {
148
+ hitRate,
149
+ prevHitRate: prevHitRate ?? null,
150
+ change: prevHitRate != null ? hitRate - prevHitRate : null,
151
+ timestamp: Date.now()
152
+ }
153
+ };
154
+
155
+ this._log(event);
156
+ return this.emit(VMEvents.CACHE_HIT, event.data);
157
+ }
158
+
159
+ /**
160
+ * 发射迭代轮次完成事件
161
+ * @param {string} featureId - 功能点 ID
162
+ * @param {number} round - 完成的轮次号
163
+ * @param {boolean} converged - 是否已收敛
164
+ * @returns {boolean} 是否有监听器处理此事件
165
+ */
166
+ emitIterationComplete(featureId, round, converged) {
167
+ const event = {
168
+ id: ++this._eventId,
169
+ type: VMEvents.ITERATION_ROUND_COMPLETE,
170
+ data: {
171
+ featureId,
172
+ round,
173
+ converged,
174
+ timestamp: Date.now()
175
+ }
176
+ };
177
+
178
+ this._log(event);
179
+ return this.emit(VMEvents.ITERATION_ROUND_COMPLETE, event.data);
180
+ }
181
+
182
+ /**
183
+ * 发射系统级事件
184
+ * @param {string} service - 服务名称(如 'api', 'scanner', 'reconciler')
185
+ * @param {string} status - 状态 ('online', 'offline', 'error', 'warning')
186
+ * @param {string} [detail=''] - 详细信息
187
+ * @returns {boolean} 是否有监听器处理此事件
188
+ */
189
+ emitSystemEvent(service, status, detail) {
190
+ const event = {
191
+ id: ++this._eventId,
192
+ type: VMEvents.SYSTEM_EVENT,
193
+ data: {
194
+ service,
195
+ status,
196
+ detail: detail || '',
197
+ timestamp: Date.now()
198
+ }
199
+ };
200
+
201
+ this._log(event);
202
+ return this.emit(VMEvents.SYSTEM_EVENT, event.data);
203
+ }
204
+
205
+ /**
206
+ * 发射数据刷新完成事件
207
+ * @param {import('./models.js').ProjectSummary} summary - 项目汇总数据
208
+ * @returns {boolean} 是否有监听器处理此事件
209
+ */
210
+ emitDataRefreshed(summary) {
211
+ const event = {
212
+ id: ++this._eventId,
213
+ type: VMEvents.DATA_REFRESHED,
214
+ data: {
215
+ summary: summary ? summary.toJSON() : null,
216
+ timestamp: Date.now()
217
+ }
218
+ };
219
+
220
+ this._log(event);
221
+ return this.emit(VMEvents.DATA_REFRESHED, event.data);
222
+ }
223
+
224
+ // ==================== SSE 订阅源方法 ====================
225
+
226
+ /**
227
+ * 获取 SSE 格式的增量事件
228
+ * 用于 Server-Sent Events 实时推送
229
+ *
230
+ * @param {number} [lastEventId=0] - 上次获取的事件 ID(用于增量获取)
231
+ * @returns {{id:number, type:string, data:Object, timestamp:number}|null}
232
+ * 下一个待推送的事件,如果没有新事件则返回 null
233
+ *
234
+ * @example
235
+ * // 在 HTTP SSE 端点中使用
236
+ * let lastId = req.headers['last-event-id'] || 0;
237
+ * while (clientConnected) {
238
+ * const event = eventBus.getSSEEvent(lastId);
239
+ * if (event) {
240
+ * res.write(`id: ${event.id}\n`);
241
+ * res.write(`event: ${event.type}\n`);
242
+ * res.write(`data: ${JSON.stringify(event.data)}\n\n`);
243
+ * lastId = event.id;
244
+ * }
245
+ * await sleep(1000);
246
+ * }
247
+ */
248
+ getSSEEvent(lastEventId) {
249
+ if (!lastEventId || lastEventId < 0) {
250
+ lastEventId = 0;
251
+ }
252
+
253
+ // 查找第一个 ID 大于 lastEventId 的事件
254
+ const event = this._eventLog.find(e => e.id > lastEventId);
255
+
256
+ if (!event) {
257
+ return null;
258
+ }
259
+
260
+ return {
261
+ id: event.id,
262
+ type: event.type,
263
+ data: event.data,
264
+ timestamp: event.data.timestamp || Date.now()
265
+ };
266
+ }
267
+
268
+ /**
269
+ * 获取事件历史记录
270
+ * 按时间倒序返回最近的事件
271
+ *
272
+ * @param {number} [limit=50] - 返回的最大数量
273
+ * @returns {Array<{id:number, type:string, data:Object, timestamp:number}>}
274
+ * 事件列表(从新到旧排序)
275
+ */
276
+ getEventHistory(limit) {
277
+ const maxLimit = limit || 50;
278
+
279
+ // 返回最近的 N 条记录(倒序)
280
+ return [...this._eventLog]
281
+ .sort((a, b) => b.id - a.id)
282
+ .slice(0, maxLimit)
283
+ .map(e => ({
284
+ id: e.id,
285
+ type: e.type,
286
+ data: e.data,
287
+ timestamp: e.data.timestamp || Date.now()
288
+ }));
289
+ }
290
+
291
+ /**
292
+ * 获取指定时间之后的所有事件
293
+ * 用于时间范围查询
294
+ *
295
+ * @param {number|Date} timestamp - 起始时间戳(毫秒)或 Date 对象
296
+ * @returns {Array<{id:number, type:string, data:Object, timestamp:number}>}
297
+ * 该时间之后的事件列表(按时间正序排列)
298
+ */
299
+ getEventSince(timestamp) {
300
+ const startTime = typeof timestamp === 'number'
301
+ ? timestamp
302
+ : timestamp instanceof Date
303
+ ? timestamp.getTime()
304
+ : (typeof timestamp === 'string' ? new Date(timestamp).getTime() : 0);
305
+
306
+ return this._eventLog
307
+ .filter(e => (e.data.timestamp || 0) >= startTime)
308
+ .sort((a, b) => a.id - b.id)
309
+ .map(e => ({
310
+ id: e.id,
311
+ type: e.type,
312
+ data: e.data,
313
+ timestamp: e.data.timestamp || Date.now()
314
+ }));
315
+ }
316
+
317
+ // ==================== 内部方法 ====================
318
+
319
+ /**
320
+ * 将事件记录到内部日志
321
+ * 自动管理日志大小,防止内存泄漏
322
+ *
323
+ * @param {Object} event - 事件对象
324
+ * @private
325
+ */
326
+ _log(event) {
327
+ // 添加到日志
328
+ this._eventLog.push(event);
329
+
330
+ // 如果超出最大容量,移除最旧的记录
331
+ if (this._eventLog.length > this._maxLogSize) {
332
+ const removeCount = this._eventLog.length - this._maxLogSize;
333
+ this._eventLog.splice(0, removeCount);
334
+ }
335
+ }
336
+
337
+ /**
338
+ * 清空事件日志
339
+ * 用于测试或重置场景
340
+ */
341
+ clearHistory() {
342
+ this._eventLog = [];
343
+ this._eventId = 0;
344
+ }
345
+
346
+ /**
347
+ * 获取当前事件总数
348
+ * @returns {number} 日志中的事件数量
349
+ */
350
+ getEventCount() {
351
+ return this._eventLog.length;
352
+ }
353
+
354
+ /**
355
+ * 获取最新事件 ID
356
+ * 用于 SSE 客户端跟踪位置
357
+ * @returns {number} 最新事件的 ID
358
+ */
359
+ getLatestEventId() {
360
+ return this._eventId;
361
+ }
362
+
363
+ /**
364
+ * 按类型过滤事件
365
+ * @param {string} eventType - 事件类型(VMEvents 枚举值)
366
+ * @param {number} [limit=20] - 最大返回数量
367
+ * @returns {Array<Object>} 匹配的事件列表
368
+ */
369
+ getEventsByType(eventType, limit) {
370
+ const maxLimit = limit || 20;
371
+
372
+ return this._eventLog
373
+ .filter(e => e.type === eventType)
374
+ .sort((a, b) => b.id - a.id)
375
+ .slice(0, maxLimit)
376
+ .map(e => ({
377
+ id: e.id,
378
+ type: e.type,
379
+ data: e.data,
380
+ timestamp: e.data.timestamp || Date.now()
381
+ }));
382
+ }
383
+ }
384
+
385
+ /**
386
+ * 创建事件总线实例的工厂函数
387
+ * 提供统一的创建入口,便于后续扩展(如添加中间件、拦截器等)
388
+ *
389
+ * @returns {VMEventBus} 新的事件总线实例
390
+ *
391
+ * @example
392
+ * import { createEventBus } from './event-bus.js';
393
+ * const bus = createEventBus();
394
+ * bus.on(bus.events.FEATURE_STAGE_CHANGED, (data) => {
395
+ * console.log(`阶段变更: ${data.featureId}: ${data.oldStage} -> ${data.newStage}`);
396
+ * });
397
+ */
398
+ export function createEventBus() {
399
+ return new VMEventBus();
400
+ }
401
+
402
+ export default VMEventBus;
@@ -0,0 +1,307 @@
1
+ /**
2
+ * @module lib/vm/hooks/extract-hook
3
+ * @description Extract 命令 Hook 实现
4
+ * 负责记录功能点提取命令的结果,包括:
5
+ * - after: 解析提取结果中的功能点列表
6
+ * 为每个新功能点创建 Feature 记录(stage=EXTRACTED)
7
+ * 批量添加到 state-store
8
+ *
9
+ * 注意:extract 命令通常在 before 时没有明确的 featureId,
10
+ * 因为它是一次性从 PRD 中批量提取多个功能点
11
+ */
12
+
13
+ import { PDDHook } from './hook-interface.js';
14
+ import { StageEnum } from '../models.js';
15
+ import { getStateStore } from '../state-store.js';
16
+
17
+ /**
18
+ * ExtractHook - 处理 `pdd extract` 命令的生命周期事件
19
+ *
20
+ * @class
21
+ * @extends PDDHook
22
+ *
23
+ * @example
24
+ * import { ExtractHook } from './lib/vm/hooks/extract-hook.js';
25
+ * const hook = new ExtractHook();
26
+ * hookManager.register(hook);
27
+ */
28
+ export class ExtractHook extends PDDHook {
29
+ /**
30
+ * 创建 ExtractHook 实例
31
+ */
32
+ constructor() {
33
+ super({ enabled: true });
34
+ /** @override */
35
+ this.name = 'extract';
36
+
37
+ /**
38
+ * StateStore 实例缓存
39
+ * @type {import('../state-store.js').StateStore|null}
40
+ * @private
41
+ */
42
+ this._store = null;
43
+ }
44
+
45
+ /**
46
+ * 获取或初始化 StateStore 实例
47
+ *
48
+ * @async
49
+ * @private
50
+ * @returns {Promise<import('../state-store.js').StateStore>}
51
+ */
52
+ async _getStore() {
53
+ if (!this._store) {
54
+ this._store = await getStateStore();
55
+ }
56
+ return this._store;
57
+ }
58
+
59
+ /**
60
+ * 命令执行前的钩子
61
+ * extract 命令在 before 阶段通常没有明确的 featureId,
62
+ * 因为它是从 PRD 批量提取功能点的过程。
63
+ *
64
+ * 这里可以记录提取开始的全局状态(可选)
65
+ *
66
+ * @async
67
+ * @override
68
+ * @param {import('./hook-interface.js').HookContext} ctx - 钩子上下文
69
+ * @returns {Promise<void>}
70
+ */
71
+ async before(ctx) {
72
+ if (!this._isEnabled()) return;
73
+
74
+ try {
75
+ // extract 的 before 主要用于日志记录和准备状态
76
+ this._log('before', ctx);
77
+ console.log(
78
+ `[ExtractHook] 开始功能点提取\n` +
79
+ ` PRD路径: ${ctx.options?.prd || '默认'}\n` +
80
+ ` 输出目录: ${ctx.options?.output || '默认'}`
81
+ );
82
+ } catch (error) {
83
+ console.error(`[ExtractHook] before 钩子执行失败: ${error.message}`);
84
+ }
85
+ }
86
+
87
+ /**
88
+ * 命令成功执行后的钩子
89
+ * 主要职责:
90
+ * 1. 从 ctx.result 中获取功能点列表
91
+ * 2. 为每个新功能点创建 Feature 记录:
92
+ * - stage = EXTRACTED
93
+ * - 设置初始 timeline
94
+ * - 提取元数据(优先级、复杂度等)
95
+ * 3. 批量 addFeature 到 state-store
96
+ * 4. 统计并输出摘要信息
97
+ *
98
+ * @async
99
+ * @override
100
+ * @param {import('./hook-interface.js').HookContext} ctx - 钩子上下文(包含 result)
101
+ * @returns {Promise<void>}
102
+ */
103
+ async after(ctx) {
104
+ if (!this._isEnabled()) return;
105
+
106
+ try {
107
+ const store = await this._getStore();
108
+ const result = ctx.result;
109
+
110
+ // 解析提取结果
111
+ const extractedFeatures = this._parseExtractResult(result);
112
+
113
+ if (!extractedFeatures || extractedFeatures.length === 0) {
114
+ console.log('[ExtractHook] 未提取到任何功能点');
115
+ this._log('after', ctx);
116
+ return;
117
+ }
118
+
119
+ console.log(
120
+ `[ExtractHook] 检测到 ${extractedFeatures.length} 个功能点,开始创建记录...`
121
+ );
122
+
123
+ // 为每个功能点创建 Feature 记录
124
+ const createdFeatures = [];
125
+ const skippedFeatures = [];
126
+
127
+ for (const featData of extractedFeatures) {
128
+ // 生成唯一 ID(如果未提供)
129
+ const featureId = featData.id || this._generateFeatureId(featData);
130
+
131
+ // 检查是否已存在
132
+ const existing = store.getFeature(featureId);
133
+
134
+ if (existing) {
135
+ skippedFeatures.push({ id: featureId, name: featData.name, reason: '已存在' });
136
+ continue;
137
+ }
138
+
139
+ // 创建新 Feature
140
+ const feature = await store.addFeature({
141
+ id: featureId,
142
+ name: featData.name || `未命名功能点-${featureId}`,
143
+ description: featData.description || '',
144
+ stage: StageEnum.EXTRACTED
145
+ });
146
+
147
+ // 补充额外元数据
148
+ await store.updateFeature(featureId, {
149
+ metadata: {
150
+ ...feature.metadata,
151
+ source: 'extract',
152
+ prdPath: ctx.options?.prd,
153
+ priority: featData.priority || undefined,
154
+ complexity: featData.complexity || undefined,
155
+ category: featData.category || undefined,
156
+ tags: featData.tags || [],
157
+ acceptanceCriteria: featData.acceptanceCriteria || [],
158
+ dependencies: featData.dependencies || []
159
+ },
160
+ createdAt: feature.createdAt,
161
+ updatedAt: new Date()
162
+ });
163
+
164
+ createdFeatures.push(feature);
165
+ }
166
+
167
+ this._log('after', ctx);
168
+
169
+ // 输出详细统计
170
+ console.log(
171
+ `\n[ExtractHook] 功能点提取完成:\n` +
172
+ ` ✓ 新建: ${createdFeatures.length} 个功能点\n` +
173
+ (skippedFeatures.length > 0 ?
174
+ ` ⊘ 跳过: ${skippedFeatures.length} 个(已存在)\n` : '') +
175
+ ` 总计: ${extractedFeatures.length} 个`
176
+ );
177
+
178
+ // 列出新建的功能点
179
+ if (createdFeatures.length > 0 && createdFeatures.length <= 10) {
180
+ console.log('\n 新建功能点列表:');
181
+ for (const f of createdFeatures) {
182
+ const meta = f.metadata || {};
183
+ console.log(
184
+ ` - [${f.id}] ${f.name}` +
185
+ (meta.priority ? ` (优先级: ${meta.priority})` : '')
186
+ );
187
+ }
188
+ } else if (createdFeatures.length > 10) {
189
+ console.log(
190
+ `\n ... 显示前10个,共 ${createdFeatures.length} 个`
191
+ );
192
+ for (const f of createdFeatures.slice(0, 10)) {
193
+ console.log(` - [${f.id}] ${f.name}`);
194
+ }
195
+ }
196
+
197
+ // 更新全局统计信息(可选)
198
+ const stats = store.getStats();
199
+ console.log(
200
+ `\n[ExtractHook] 当前项目总览:\n` +
201
+ ` 功能点总数: ${stats.totalFeatures}\n` +
202
+ ` 各阶段分布: ${JSON.stringify(stats.byStage, null, 2)}`
203
+ );
204
+ } catch (error) {
205
+ console.error(`[ExtractHook] after 钩子执行失败: ${error.message}`);
206
+ }
207
+ }
208
+
209
+ /**
210
+ * 命令执行出错时的钩子
211
+ *
212
+ * @async
213
+ * @override
214
+ * @param {import('./hook-interface.js').HookContext} ctx - 钩子上下文
215
+ * @returns {Promise<void>}
216
+ */
217
+ async error(ctx) {
218
+ if (!this._isEnabled()) return;
219
+
220
+ try {
221
+ this._log('error', ctx);
222
+ console.error(
223
+ `[ExtractHook] 功能点提取失败:\n` +
224
+ ` 错误: ${ctx.error?.message}\n` +
225
+ ` 类型: ${ctx.error?.name}`
226
+ );
227
+ } catch (error) {
228
+ console.error(`[ExtractHook] error 钩子执行失败: ${error.message}`);
229
+ }
230
+ }
231
+
232
+ /**
233
+ * 解析 extract 命令的结果对象
234
+ * 支持多种可能的返回格式
235
+ *
236
+ * @private
237
+ * @param {*} result - extract 函数的返回值
238
+ * @returns {Array<Object>} 功能点数据列表
239
+ */
240
+ _parseExtractResult(result) {
241
+ if (!result) {
242
+ return [];
243
+ }
244
+
245
+ // 格式1: { features: [...] }
246
+ if (result.features && Array.isArray(result.features)) {
247
+ return result.features;
248
+ }
249
+
250
+ // 格式2: { items: [...] }
251
+ if (result.items && Array.isArray(result.items)) {
252
+ return result.items;
253
+ }
254
+
255
+ // 格式3: 直接是数组
256
+ if (Array.isArray(result)) {
257
+ return result;
258
+ }
259
+
260
+ // 格式4: { data: { features: [...] } }
261
+ if (result.data?.features && Array.isArray(result.data.features)) {
262
+ return result.data.features;
263
+ }
264
+
265
+ // 格式5: { extracted: [...] }
266
+ if (result.extracted && Array.isArray(result.extracted)) {
267
+ return result.extracted;
268
+ }
269
+
270
+ // 尝试查找任何数组字段
271
+ for (const key of Object.keys(result)) {
272
+ if (Array.isArray(result[key]) && result[key].length > 0) {
273
+ // 简单验证是否像功能点数据
274
+ const firstItem = result[key][0];
275
+ if (
276
+ firstItem &&
277
+ (firstItem.name || firstItem.id || firstItem.title)
278
+ ) {
279
+ return result[key];
280
+ }
281
+ }
282
+ }
283
+
284
+ return [];
285
+ }
286
+
287
+ /**
288
+ * 根据功能点数据生成唯一 ID
289
+ *
290
+ * @private
291
+ * @param {Object} featData - 功能点数据
292
+ * @returns {string} 生成的 ID
293
+ */
294
+ _generateFeatureId(featData) {
295
+ // 使用名称的简短哈希 + 时间戳生成 ID
296
+ const name = featData.name || 'unknown';
297
+ const timestamp = Date.now().toString(36);
298
+ const shortHash = name
299
+ .slice(0, 8)
300
+ .toLowerCase()
301
+ .replace(/[^a-z0-9]/g, '');
302
+
303
+ return `feat-${shortHash}-${timestamp}`;
304
+ }
305
+ }
306
+
307
+ export default ExtractHook;