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,733 @@
1
+ /**
2
+ * PDD Visual Manager - 状态存储器
3
+ *
4
+ * 负责项目状态文件 (project-state.json) 的读写操作,提供:
5
+ * - 原子写入(write tmp + rename)
6
+ * - 自动备份(.bak 文件)
7
+ * - 功能点 CRUD 操作
8
+ * - 状态查询与汇总
9
+ *
10
+ * @module vm/state-store
11
+ */
12
+
13
+ import fs from 'fs';
14
+ import path from 'path';
15
+ import { fileURLToPath } from 'url';
16
+
17
+ import { Feature, ProjectSummary } from './models.js';
18
+ import { validate, migrate, createEmptyState, SCHEMA_VERSION } from './state-schema.js';
19
+
20
+ const __filename = fileURLToPath(import.meta.url);
21
+ const __dirname = path.dirname(__filename);
22
+
23
+ // 可选的 chalk 彩色输出支持
24
+ let chalk;
25
+ try {
26
+ const chalkModule = await import('chalk');
27
+ chalk = chalkModule.default;
28
+ } catch {
29
+ chalk = {
30
+ cyan: (s) => s,
31
+ green: (s) => s,
32
+ yellow: (s) => s,
33
+ red: (s) => s,
34
+ blue: (s) => s,
35
+ gray: (s) => s,
36
+ bold: (s) => s
37
+ };
38
+ }
39
+
40
+ /**
41
+ * 状态文件名常量
42
+ * @type {string}
43
+ */
44
+ export const STATE_FILENAME = 'project-state.json';
45
+
46
+ /**
47
+ * 备份文件扩展名
48
+ * @type {string}
49
+ */
50
+ export const BACKUP_EXT = '.bak';
51
+
52
+ /**
53
+ * 最大备份数量
54
+ * @type {number}
55
+ */
56
+ export const MAX_BACKUPS = 5;
57
+
58
+ /**
59
+ * 状态存储器类
60
+ * 管理 project-state.json 文件的读写和功能点数据操作
61
+ */
62
+ export class StateStore {
63
+ /**
64
+ * 创建状态存储器实例
65
+ * @param {string} projectRoot - 项目根目录路径
66
+ * @param {Object} [options={}] - 配置选项
67
+ * @param {boolean} [options.autoBackup=true] - 是否自动备份
68
+ * @param {number} [options.maxBackups=MAX_BACKUPS] - 最大备份数量
69
+ * @param {boolean} [options.validateOnLoad=true] - 加载时是否验证
70
+ * @param {boolean} [options.validateOnSave=true] - 保存时是否验证
71
+ * @param {string} [options.stateDir='.pdd-vm'] - 状态文件所在目录(相对于 projectRoot)
72
+ */
73
+ constructor(projectRoot, options = {}) {
74
+ if (!projectRoot || typeof projectRoot !== 'string') {
75
+ throw new Error('StateStore: projectRoot 必须是非空字符串');
76
+ }
77
+
78
+ /** @type {string} 项目根目录绝对路径 */
79
+ this.projectRoot = path.resolve(projectRoot);
80
+
81
+ /** @type {string} 状态目录名称 */
82
+ this.stateDirName = options.stateDir || '.pdd-vm';
83
+
84
+ /** @type {string} 状态目录完整路径 */
85
+ this.stateDir = path.join(this.projectRoot, this.stateDirName);
86
+
87
+ /** @type {string} 状态文件完整路径 */
88
+ this.statePath = path.join(this.stateDir, STATE_FILENAME);
89
+
90
+ /** @type {string} 备份目录路径 */
91
+ this.backupDir = path.join(this.stateDir, 'backups');
92
+
93
+ /** @type {boolean} 是否自动备份 */
94
+ this.autoBackup = options.autoBackup !== false;
95
+
96
+ /** @type {number} 最大备份数量 */
97
+ this.maxBackups = options.maxBackups || MAX_BACKUPS;
98
+
99
+ /** @type {boolean} 加载时是否验证 */
100
+ this.validateOnLoad = options.validateOnLoad !== false;
101
+
102
+ /** @type {boolean} 保存时是否验证 */
103
+ this.validateOnSave = options.validateOnSave !== false;
104
+
105
+ /** @type {Object|null} 内存中的状态缓存 */
106
+ this._cache = null;
107
+
108
+ /** @type {boolean} 缓存是否脏 */
109
+ this._dirty = false;
110
+ }
111
+
112
+ /**
113
+ * 确保状态目录存在
114
+ * 如果不存在则创建
115
+ * @returns {Promise<void>}
116
+ * @private
117
+ */
118
+ async _ensureDir() {
119
+ // 创建主状态目录
120
+ if (!fs.existsSync(this.stateDir)) {
121
+ await fs.promises.mkdir(this.stateDir, { recursive: true });
122
+ console.log(chalk.gray(`[StateStore] 创建状态目录: ${this.stateDir}`));
123
+ }
124
+
125
+ // 创建备份目录
126
+ if (!fs.existsSync(this.backupDir)) {
127
+ await fs.promises.mkdir(this.backupDir, { recursive: true });
128
+ }
129
+ }
130
+
131
+ /**
132
+ * 创建当前状态的备份
133
+ * @returns {Promise<string|null>} 备份文件路径,失败返回 null
134
+ * @private
135
+ */
136
+ async _createBackup() {
137
+ try {
138
+ if (!fs.existsSync(this.statePath)) {
139
+ return null; // 没有文件可备份
140
+ }
141
+
142
+ await this._ensureDir();
143
+
144
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
145
+ const backupFileName = `${STATE_FILENAME}.${timestamp}${BACKUP_EXT}`;
146
+ const backupPath = path.join(this.backupDir, backupFileName);
147
+
148
+ // 复制文件到备份位置
149
+ await fs.promises.copyFile(this.statePath, backupPath);
150
+
151
+ // 清理旧备份
152
+ await this._cleanupOldBackups();
153
+
154
+ console.log(chalk.gray(`[StateStore] 已创建备份: ${backupFileName}`));
155
+ return backupPath;
156
+ } catch (error) {
157
+ console.error(chalk.red(`[StateStore] 备份失败: ${error.message}`));
158
+ return null;
159
+ }
160
+ }
161
+
162
+ /**
163
+ * 清理超出数量限制的旧备份
164
+ * @returns {Promise<void>}
165
+ * @private
166
+ */
167
+ async _cleanupOldBackups() {
168
+ try {
169
+ const files = await fs.promises.readdir(this.backupDir);
170
+ const backups = files
171
+ .filter(f => f.endsWith(BACKUP_EXT))
172
+ .map(f => ({
173
+ name: f,
174
+ path: path.join(this.backupDir, f),
175
+ mtime: fs.statSync(path.join(this.backupDir, f)).mtimeMs
176
+ }))
177
+ .sort((a, b) => b.mtime - a.mtime); // 按修改时间降序
178
+
179
+ // 删除超出的旧备份
180
+ if (backups.length > this.maxBackups) {
181
+ for (let i = this.maxBackups; i < backups.length; i++) {
182
+ await fs.promises.unlink(backups[i].path).catch(() => {});
183
+ console.log(chalk.gray(`[StateStore] 删除旧备份: ${backups[i].name}`));
184
+ }
185
+ }
186
+ } catch (error) {
187
+ console.warn(chalk.yellow(`[StateStore] 清理备份失败: ${error.message}`));
188
+ }
189
+ }
190
+
191
+ /**
192
+ * 原子写入文件
193
+ * 先写入临时文件,然后重命名到目标位置
194
+ * 这确保了在写入过程中不会产生损坏的文件
195
+ *
196
+ * @param {string} filePath - 目标文件路径
197
+ * @param {string|Buffer} content - 要写入的内容
198
+ * @returns {Promise<void>}
199
+ * @private
200
+ */
201
+ async _atomicWrite(filePath, content) {
202
+ const dir = path.dirname(filePath);
203
+ await this._ensureDir();
204
+
205
+ // 生成临时文件路径
206
+ const tmpPath = `${filePath}.tmp.${Date.now()}`;
207
+
208
+ try {
209
+ // 写入临时文件
210
+ await fs.promises.writeFile(tmpPath, content, 'utf-8');
211
+
212
+ // 同步确保数据落盘
213
+ await fs.promises.fsync(await fs.promises.open(tmpPath, 'r')).then(fd => fd.close()).catch(() => {});
214
+
215
+ // 原子重命名(在大多数文件系统上是原子操作)
216
+ await fs.promises.rename(tmpPath, filePath);
217
+ } catch (error) {
218
+ // 清理临时文件
219
+ try {
220
+ if (fs.existsSync(tmpPath)) {
221
+ await fs.promises.unlink(tmpPath);
222
+ }
223
+ } catch (cleanupError) {
224
+ // 忽略清理错误
225
+ }
226
+ throw error;
227
+ }
228
+ }
229
+
230
+ /**
231
+ * 加载项目状态
232
+ * 从磁盘读取并解析 project-state.json
233
+ *
234
+ * @returns {Promise<Object>} 项目状态对象,包含 features 数组
235
+ * @throws {Error} 当文件不存在或格式无效时抛出异常
236
+ *
237
+ * @example
238
+ * const store = new StateStore('./my-project');
239
+ * const state = await store.loadState();
240
+ * console.log(state.project.features);
241
+ */
242
+ async loadState() {
243
+ await this._ensureDir();
244
+
245
+ // 检查文件是否存在
246
+ if (!fs.existsSync(this.statePath)) {
247
+ console.log(chalk.yellow(`[StateStore] 状态文件不存在: ${this.statePath}`));
248
+ return createEmptyState(path.basename(this.projectRoot));
249
+ }
250
+
251
+ try {
252
+ // 读取文件内容
253
+ const rawContent = await fs.promises.readFile(this.statePath, 'utf-8');
254
+
255
+ // 解析 JSON
256
+ let stateData = JSON.parse(rawContent);
257
+
258
+ // 版本迁移检查
259
+ if (stateData.version && stateData.version !== SCHEMA_VERSION) {
260
+ console.log(chalk.yellow(`[StateStore] 检测到版本差异: ${stateData.version} -> ${SCHEMA_VERSION}`));
261
+
262
+ if (compareVersions(stateData.version, SCHEMA_VERSION) < 0) {
263
+ // 需要升级
264
+ try {
265
+ stateData = migrate(stateData, stateData.version, SCHEMA_VERSION);
266
+ console.log(chalk.green('[StateStore] 版本迁移完成'));
267
+ // 迁移后自动保存
268
+ await this._atomicWrite(this.statePath, JSON.stringify(stateData, null, 2));
269
+ } catch (migrateError) {
270
+ console.warn(chalk.yellow(`[StateStore] 自动迁移失败: ${migrateError.message}`));
271
+ }
272
+ }
273
+ }
274
+
275
+ // 验证数据
276
+ if (this.validateOnLoad) {
277
+ const validation = validate(stateData);
278
+ if (!validation.valid) {
279
+ console.warn(chalk.yellow(`[StateStore] 数据验证警告 (${validation.errors.length} 个问题):`));
280
+ validation.errors.slice(0, 3).forEach(err => {
281
+ console.warn(chalk.yellow(` - ${err.path}: ${err.message}`));
282
+ });
283
+ }
284
+ }
285
+
286
+ // 更新缓存
287
+ this._cache = stateData;
288
+ this._dirty = false;
289
+
290
+ return stateData;
291
+ } catch (error) {
292
+ if (error instanceof SyntaxError) {
293
+ throw new Error(`[StateStore] JSON 解析失败: ${error.message}`);
294
+ }
295
+ throw error;
296
+ }
297
+ }
298
+
299
+ /**
300
+ * 保存项目状态
301
+ * 使用原子写入方式保存状态到磁盘
302
+ *
303
+ * @param {Object} state - 项目状态对象
304
+ * @returns {Promise<void>}
305
+ * @throws {Error} 当验证失败或写入失败时抛出异常
306
+ *
307
+ * @example
308
+ * await store.saveState({
309
+ * version: '1.0.0',
310
+ * project: { name: 'MyProject', features: [...] },
311
+ * metadata: { ... }
312
+ * });
313
+ */
314
+ async saveState(state) {
315
+ if (!state || typeof state !== 'object') {
316
+ throw new Error('saveState: state 必须是有效的对象');
317
+ }
318
+
319
+ // 保存前验证
320
+ if (this.validateOnSave) {
321
+ const validation = validate(state);
322
+ if (!validation.valid) {
323
+ const errorMessages = validation.errors.map(e => `${e.path}: ${e.message}`).join('; ');
324
+ throw new Error(`saveState: 数据验证失败 - ${errorMessages}`);
325
+ }
326
+ }
327
+
328
+ // 更新元数据时间戳
329
+ if (state.metadata) {
330
+ state.metadata.lastUpdated = Date.now();
331
+ }
332
+
333
+ // 序列化 JSON(美化输出)
334
+ const jsonContent = JSON.stringify(state, null, 2);
335
+
336
+ // 创建备份
337
+ if (this.autoBackup && fs.existsSync(this.statePath)) {
338
+ await this._createBackup();
339
+ }
340
+
341
+ // 原子写入
342
+ await this._atomicWrite(this.statePath, jsonContent);
343
+
344
+ // 更新缓存
345
+ this._cache = state;
346
+ this._dirty = false;
347
+
348
+ console.log(chalk.green(`[StateStore] 状态已保存: ${this.statePath}`));
349
+ }
350
+
351
+ /**
352
+ * 获取状态文件路径
353
+ * @returns {string} 状态文件的绝对路径
354
+ */
355
+ getStatePath() {
356
+ return this.statePath;
357
+ }
358
+
359
+ /**
360
+ * 获取项目汇总信息
361
+ * @returns {Promise<ProjectSummary>} 项目汇总对象
362
+ */
363
+ async getProject() {
364
+ const state = await this.loadState();
365
+ const features = (state.project.features || []).map(
366
+ f => Feature.fromJSON(f)
367
+ );
368
+ return ProjectSummary.fromFeatures(features, state.project.name, state.project.version);
369
+ }
370
+
371
+ /**
372
+ * 根据 ID 获取功能点
373
+ * @param {string} id - 功能点 ID
374
+ * @returns {Promise<Feature|null>} Feature 对象或 null
375
+ */
376
+ async getFeatureById(id) {
377
+ const state = await this.loadState();
378
+ const featureData = (state.project.features || []).find(f => f.id === id);
379
+
380
+ if (!featureData) {
381
+ return null;
382
+ }
383
+
384
+ return Feature.fromJSON(featureData);
385
+ }
386
+
387
+ /**
388
+ * 根据阶段获取功能点列表
389
+ * @param {string} stage - 开发阶段
390
+ * @returns {Promise<Feature[]>} 符合条件的 Feature 数组
391
+ */
392
+ async getFeaturesByStage(stage) {
393
+ const state = await this.loadState();
394
+ const features = (state.project.features || [])
395
+ .filter(f => f.stage === stage)
396
+ .map(f => Feature.fromJSON(f));
397
+ return features;
398
+ }
399
+
400
+ /**
401
+ * 更新指定功能点
402
+ * 使用合并策略更新,只修改传入的字段
403
+ *
404
+ * @param {string} id - 功能点 ID
405
+ * @param {Object} updates - 要更新的字段
406
+ * @returns {Promise<Feature>} 更新后的 Feature 对象
407
+ * @throws {Error} 当功能点不存在时抛出异常
408
+ *
409
+ * @example
410
+ * const updated = await store.updateFeature('feat-001', {
411
+ * stage: 'implementing',
412
+ * priority: 'P1'
413
+ * });
414
+ */
415
+ async updateFeature(id, updates) {
416
+ const state = await this.loadState();
417
+ const featureIndex = (state.project.features || []).findIndex(f => f.id === id);
418
+
419
+ if (featureIndex === -1) {
420
+ throw new Error(`updateFeature: 找不到 ID 为 "${id}" 的功能点`);
421
+ }
422
+
423
+ // 合并更新
424
+ const existing = state.project.features[featureIndex];
425
+ const merged = {
426
+ ...existing,
427
+ ...updates,
428
+ updatedAt: Date.now()
429
+ };
430
+
431
+ // 特殊处理嵌套对象的深度合并
432
+ if (updates.tokens && typeof updates.tokens === 'object') {
433
+ merged.tokens = { ...existing.tokens, ...updates.tokens };
434
+ }
435
+ if (updates.quality && typeof updates.quality === 'object') {
436
+ merged.quality = { ...(existing.quality || {}), ...updates.quality };
437
+ }
438
+ if (Array.isArray(updates.tags)) {
439
+ merged.tags = updates.tags;
440
+ }
441
+ if (Array.isArray(updates.artifacts)) {
442
+ merged.artifacts = updates.artifacts;
443
+ }
444
+
445
+ state.project.features[featureIndex] = merged;
446
+
447
+ // 保存状态
448
+ await this.saveState(state);
449
+
450
+ return Feature.fromJSON(merged);
451
+ }
452
+
453
+ /**
454
+ * 批量更新多个功能点
455
+ * @param {Array<{id:string, updates:Object}>} updateList - 更新列表
456
+ * @returns {Promise<Feature[]>} 更新后的 Feature 数组
457
+ */
458
+ async batchUpdate(updateList) {
459
+ if (!Array.isArray(updateList) || updateList.length === 0) {
460
+ return [];
461
+ }
462
+
463
+ const state = await this.loadState();
464
+ const results = [];
465
+
466
+ for (const { id, updates } of updateList) {
467
+ const index = (state.project.features || []).findIndex(f => f.id === id);
468
+
469
+ if (index !== -1) {
470
+ const existing = state.project.features[index];
471
+ const merged = {
472
+ ...existing,
473
+ ...updates,
474
+ updatedAt: Date.now()
475
+ };
476
+
477
+ // 深度合并特殊字段
478
+ if (updates.tokens && typeof updates.tokens === 'object') {
479
+ merged.tokens = { ...existing.tokens, ...updates.tokens };
480
+ }
481
+ if (updates.quality && typeof updates.quality === 'object') {
482
+ merged.quality = { ...(existing.quality || {}), ...updates.quality };
483
+ }
484
+
485
+ state.project.features[index] = merged;
486
+ results.push(Feature.fromJSON(merged));
487
+ } else {
488
+ console.warn(chalk.yellow(`[batchUpdate] 找不到功能点: ${id}`));
489
+ }
490
+ }
491
+
492
+ // 统一保存一次
493
+ await this.saveState(state);
494
+
495
+ return results;
496
+ }
497
+
498
+ /**
499
+ * 添加新功能点
500
+ * @param {Object|Feature} featureData - 功能点数据或 Feature 实例
501
+ * @returns {Promise<Feature>} 新创建的 Feature 对象
502
+ * @throws {Error} 当 ID 已存在时抛出异常
503
+ */
504
+ async addFeature(featureData) {
505
+ const state = await this.loadState();
506
+
507
+ // 确保 features 数组存在
508
+ if (!state.project.features) {
509
+ state.project.features = [];
510
+ }
511
+
512
+ // 处理输入:如果是 Feature 实例则序列化
513
+ const data = featureData instanceof Feature ? featureData.toJSON() : featureData;
514
+
515
+ // 检查 ID 是否重复
516
+ const exists = state.project.features.some(f => f.id === data.id);
517
+ if (exists) {
518
+ throw new Error(`addFeature: 功能点 ID "${data.id}" 已存在`);
519
+ }
520
+
521
+ // 设置默认值
522
+ const now = Date.now();
523
+ const newFeature = {
524
+ id: data.id,
525
+ name: data.name,
526
+ description: data.description || '',
527
+ stage: data.stage || 'prd',
528
+ priority: data.priority || 'P2',
529
+ timeline: data.timeline || [],
530
+ artifacts: data.artifacts || [],
531
+ quality: data.quality || null,
532
+ tokens: data.tokens || { total: 0, used: 0, remaining: 0, byStage: {}, history: [] },
533
+ iterations: data.iterations || [],
534
+ tags: data.tags || [],
535
+ createdAt: data.createdAt || now,
536
+ updatedAt: data.updatedAt || now
537
+ };
538
+
539
+ state.project.features.push(newFeature);
540
+
541
+ // 保存
542
+ await this.saveState(state);
543
+
544
+ console.log(chalk.green(`[StateStore] 已添加功能点: ${data.id} - ${data.name}`));
545
+
546
+ return Feature.fromJSON(newFeature);
547
+ }
548
+
549
+ /**
550
+ * 删除指定功能点
551
+ * @param {string} id - 功能点 ID
552
+ * @returns {Promise<boolean>} 是否成功删除
553
+ */
554
+ async removeFeature(id) {
555
+ const state = await this.loadState();
556
+ const initialLength = (state.project.features || []).length;
557
+
558
+ // 过滤掉要删除的功能点
559
+ state.project.features = (state.project.features || []).filter(f => f.id !== id);
560
+
561
+ const removed = state.project.features.length < initialLength;
562
+
563
+ if (removed) {
564
+ await this.saveState(state);
565
+ console.log(chalk.green(`[StateStore] 已删除功能点: ${id}`));
566
+ } else {
567
+ console.warn(chalk.yellow(`[StateStore] 未找到要删除的功能点: ${id}`));
568
+ }
569
+
570
+ return removed;
571
+ }
572
+
573
+ /**
574
+ * 获取所有功能点
575
+ * @returns {Promise<Feature[]>} 所有功能点数组
576
+ */
577
+ async getAllFeatures() {
578
+ const state = await this.loadState();
579
+ return (state.project.features || []).map(f => Feature.fromJSON(f));
580
+ }
581
+
582
+ /**
583
+ * 根据标签搜索功能点
584
+ * @param {string} tag - 标签名
585
+ * @returns {Promise<Feature[]>} 匹配的功能点数组
586
+ */
587
+ async getFeaturesByTag(tag) {
588
+ const state = await this.loadState();
589
+ return (state.project.features || [])
590
+ .filter(f => Array.isArray(f.tags) && f.tags.includes(tag))
591
+ .map(f => Feature.fromJSON(f));
592
+ }
593
+
594
+ /**
595
+ * 根据优先级获取功能点
596
+ * @param {string} priority - 优先级 (P0/P1/P2/P3)
597
+ * @returns {Promise<Feature[]>} 匹配的功能点数组
598
+ */
599
+ async getFeaturesByPriority(priority) {
600
+ const state = await this.loadState();
601
+ return (state.project.features || [])
602
+ .filter(f => f.priority === priority)
603
+ .map(f => Feature.fromJSON(f));
604
+ }
605
+
606
+ /**
607
+ * 获取统计摘要(不加载完整状态)
608
+ * 快速获取基本信息用于显示
609
+ * @returns {Promise<{totalFeatures:number, lastUpdated:number|null}>} 统计信息
610
+ */
611
+ async getQuickStats() {
612
+ try {
613
+ if (!fs.existsSync(this.statePath)) {
614
+ return { totalFeatures: 0, lastUpdated: null };
615
+ }
616
+
617
+ const content = await fs.promises.readFile(this.statePath, 'utf-8');
618
+ const state = JSON.parse(content);
619
+
620
+ return {
621
+ totalFeatures: (state.project?.features || []).length,
622
+ lastUpdated: state.metadata?.lastUpdated || null
623
+ };
624
+ } catch (error) {
625
+ return { totalFeatures: 0, lastUpdated: null };
626
+ }
627
+ }
628
+
629
+ /**
630
+ * 列出可用的备份文件
631
+ * @returns {Promise<Array<{name:string,path:string,size:number,time:Date}>>} 备份文件列表
632
+ */
633
+ async listBackups() {
634
+ try {
635
+ if (!fs.existsSync(this.backupDir)) {
636
+ return [];
637
+ }
638
+
639
+ const files = await fs.promises.readdir(this.backupDir);
640
+ const backups = files
641
+ .filter(f => f.endsWith(BACKUP_EXT))
642
+ .map(f => {
643
+ const filePath = path.join(this.backupDir, f);
644
+ const stat = fs.statSync(filePath);
645
+ return {
646
+ name: f,
647
+ path: filePath,
648
+ size: stat.size,
649
+ time: stat.mtime
650
+ };
651
+ })
652
+ .sort((a, b) => b.time - a.time);
653
+
654
+ return backups;
655
+ } catch (error) {
656
+ console.warn(chalk.yellow(`[listBackups] 读取备份目录失败: ${error.message}`));
657
+ return [];
658
+ }
659
+ }
660
+
661
+ /**
662
+ * 从备份恢复状态
663
+ * @param {string} backupName - 备份文件名
664
+ * @returns {Promise<boolean>} 是否恢复成功
665
+ */
666
+ async restoreFromBackup(backupName) {
667
+ const backupPath = path.join(this.backupDir, backupName);
668
+
669
+ if (!fs.existsSync(backupPath)) {
670
+ throw new Error(`restoreFromBackup: 备份文件不存在: ${backupName}`);
671
+ }
672
+
673
+ // 先备份当前状态
674
+ if (fs.existsSync(this.statePath)) {
675
+ await this._createBackup();
676
+ }
677
+
678
+ // 从备份恢复
679
+ await fs.promises.copyFile(backupPath, this.statePath);
680
+
681
+ // 清除缓存
682
+ this._cache = null;
683
+
684
+ console.log(chalk.green(`[StateStore] 已从备份恢复: ${backupName}`));
685
+
686
+ return true;
687
+ }
688
+
689
+ /**
690
+ * 重置状态(清空所有功能点)
691
+ * @param {string} [projectName] - 项目名称(可选)
692
+ * @returns {Promise<void>}
693
+ */
694
+ async resetState(projectName) {
695
+ const name = projectName || path.basename(this.projectRoot);
696
+ const emptyState = createEmptyState(name);
697
+
698
+ // 先备份当前状态
699
+ if (fs.existsSync(this.statePath)) {
700
+ await this._createBackup();
701
+ }
702
+
703
+ await this.saveState(emptyState);
704
+
705
+ console.log(chalk.yellow('[StateStore] 状态已重置'));
706
+ }
707
+ }
708
+
709
+ /**
710
+ * 版本比较函数(内部使用)
711
+ * @param {string} v1 - 版本1
712
+ * @param {string} v2 - 版本2
713
+ * @returns {number} 比较结果
714
+ */
715
+ function compareVersions(v1, v2) {
716
+ const parts1 = v1.split('.').map(Number);
717
+ const parts2 = v2.split('.').map(Number);
718
+
719
+ for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
720
+ const p1 = parts1[i] || 0;
721
+ const p2 = parts2[i] || 0;
722
+
723
+ if (p1 < p2) return -1;
724
+ if (p1 > p2) return 1;
725
+ }
726
+
727
+ return 0;
728
+ }
729
+
730
+ /**
731
+ * 导出默认对象
732
+ */
733
+ export default StateStore;