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,855 @@
1
+ /**
2
+ * PDD Visual Manager - 数据对齐与合并器
3
+ *
4
+ * 负责协调状态存储(state)和文件系统扫描结果(scanner)之间的数据一致性:
5
+ * - 三路合并策略:state + scanner + 合并规则
6
+ * - 孤儿检测:发现无对应记录的文件或无对应文件的功能点
7
+ * - 冲突解决:自动处理 stage 和 quality 等字段的冲突
8
+ * - 新功能点发现:自动将扫描到的新功能点加入 state
9
+ *
10
+ * @module vm/reconciler
11
+ */
12
+
13
+ import { Feature, StageEnum, STAGE_VALUES, Priority } from './models.js';
14
+
15
+ // 可选的 chalk 彩色输出支持
16
+ let chalk;
17
+ try {
18
+ const chalkModule = await import('chalk');
19
+ chalk = chalkModule.default;
20
+ } catch {
21
+ chalk = {
22
+ cyan: (s) => s,
23
+ green: (s) => s,
24
+ yellow: (s) => s,
25
+ red: (s) => s,
26
+ blue: (s) => s,
27
+ gray: (s) => s,
28
+ bold: (s) => s,
29
+ magenta: (s) => s
30
+ };
31
+ }
32
+
33
+ /**
34
+ * 对齐结果类型
35
+ * @typedef {Object} ReconciliationResult
36
+ * @property {Feature[]} features - 合并后的功能点列表
37
+ * @property {OrphanFile[]} orphanFiles - 孤儿文件列表
38
+ * @property {Feature[]} newFeatures - 新发现的功能点
39
+ * @property {Conflict[]} conflicts - 冲突列表
40
+ * @property {string[]} warnings - 警告信息
41
+ * @property {Object} statistics - 统计信息
42
+ * @property {number} timestamp - 对齐时间戳
43
+ * @property {number} duration - 耗时(毫秒)
44
+ */
45
+
46
+ /**
47
+ * 孤儿文件信息
48
+ * @typedef {Object} OrphanFile
49
+ * @property {string} path - 文件路径
50
+ * @property {string} type - 文件类型 (spec/code/test/report)
51
+ * @property {string} reason - 被判定为孤儿的原因
52
+ * @property {string|null} suggestedFeatureId - 建议关联的功能点 ID
53
+ * @property {Date} lastModified - 最后修改时间
54
+ */
55
+
56
+ /**
57
+ * 冲突信息
58
+ * @typedef {Object} Conflict
59
+ * @property {string} featureId - 涉及的功能点 ID
60
+ * @property {string} field - 冲突字段名
61
+ * @property {*} stateValue - state 中的值
62
+ * @property {*} scannerValue - scanner 扫描到的值
63
+ * @property {string} resolvedValue - 解决后的值
64
+ * @property {string} resolutionStrategy - 解决策略
65
+ */
66
+
67
+ /**
68
+ * 数据对齐与合并器类
69
+ */
70
+ export class Reconciler {
71
+ /**
72
+ * 创建对齐器实例
73
+ * @param {import('./state-store.js').StateStore} stateStore - 状态存储器实例
74
+ * @param {import('./scanner.js').ProjectScanner} scanner - 项目扫描器实例
75
+ * @param {Object} [options={}] - 配置选项
76
+ * @param {boolean} [options.autoStageFromScanner=true] - 是否以 scanner 推断的 stage 为准
77
+ * @param {boolean} [options.autoCreateNewFeatures=true] - 是否自动创建新发现的功能点
78
+ * @param {boolean} [options.preserveStateQuality=true] - 是否保留 state 中的质量指标
79
+ * @param {number} [options.orphanThresholdDays=30] - 判定为孤儿的阈值天数
80
+ * @param {Function} [options.onConflict] - 冲突回调函数
81
+ */
82
+ constructor(stateStore, scanner, options = {}) {
83
+ if (!stateStore) {
84
+ throw new Error('Reconciler: stateStore 是必需的');
85
+ }
86
+ if (!scanner) {
87
+ throw new Error('Reconciler: scanner 是必需的');
88
+ }
89
+
90
+ /** @type {import('./state-store.js').StateStore} 状态存储器 */
91
+ this.stateStore = stateStore;
92
+
93
+ /** @type {import('./scanner.js').ProjectScanner} 项目扫描器 */
94
+ this.scanner = scanner;
95
+
96
+ /** @type {Object} 配置选项 */
97
+ this.options = {
98
+ autoStageFromScanner: true,
99
+ autoCreateNewFeatures: true,
100
+ preserveStateQuality: true,
101
+ orphanThresholdDays: 30,
102
+ ...options
103
+ };
104
+
105
+ /** @type {Array<Conflict>} 本次对齐发现的冲突 */
106
+ this._conflicts = [];
107
+
108
+ /** @type {Array<string>} 本次对齐产生的警告 */
109
+ this._warnings = [];
110
+
111
+ console.log(chalk.gray('[Reconciler] 初始化数据对齐器'));
112
+ }
113
+
114
+ /**
115
+ * 执行完整的数据对齐流程
116
+ *
117
+ * 三路合并策略:
118
+ * 1. state中的记录(命令Hook写入的权威数据)
119
+ * 2. scanner扫描结果(文件系统真实状态)
120
+ * 3. 合并规则:
121
+ * - state有+scanner有 → 取state为主,scanner补充缺失字段
122
+ * - state有+scanner无 → 标记为orphan(可能被删除),保留但warn
123
+ * - state无+scanner有 → 新发现的功能点,自动创建记录
124
+ * - 冲突解决:stage以scanner推断为准(文件不会撒谎),质量指标以state为准
125
+ *
126
+ * @returns {Promise<ReconciliationResult>} 对齐结果
127
+ */
128
+ async reconcile() {
129
+ const startTime = Date.now();
130
+
131
+ console.log(chalk.blue('\n[Reconciler] 开始数据对齐...\n'));
132
+
133
+ // 重置内部状态
134
+ this._conflicts = [];
135
+ this._warnings = [];
136
+
137
+ // 1. 加载当前状态
138
+ const state = await this.stateStore.loadState();
139
+ const stateFeatures = (state.project.features || []).map(f => Feature.fromJSON(f));
140
+
141
+ // 2. 执行全量扫描
142
+ const scanResult = await this.scanner.fullScan();
143
+
144
+ // 3. 构建扫描结果的索引
145
+ const scannedFeatureMap = this._buildScannedFeatureMap(scanResult);
146
+
147
+ // 4. 执行三路合并
148
+ const {
149
+ mergedFeatures,
150
+ orphanFiles,
151
+ newFeatures,
152
+ conflicts,
153
+ warnings
154
+ } = await this._performThreeWayMerge(stateFeatures, scannedFeatureMap, scanResult);
155
+
156
+ // 5. 统计信息
157
+ const statistics = this._calculateStatistics(
158
+ stateFeatures.length,
159
+ mergedFeatures.length,
160
+ newFeatures.length,
161
+ orphanFiles.length,
162
+ conflicts.length
163
+ );
164
+
165
+ const endTime = Date.now();
166
+ const duration = endTime - startTime;
167
+
168
+ const result = {
169
+ features: mergedFeatures,
170
+ orphanFiles,
171
+ newFeatures,
172
+ conflicts,
173
+ warnings,
174
+ statistics,
175
+ timestamp: endTime,
176
+ duration
177
+ };
178
+
179
+ // 输出对齐报告
180
+ this._printReconciliationReport(result);
181
+
182
+ return result;
183
+ }
184
+
185
+ /**
186
+ * 从扫描结果构建功能点映射
187
+ * 将各种制品文件关联到可能的功能点
188
+ *
189
+ * @param {Object} scanResult - 全量扫描结果
190
+ * @returns {Map<string, Object>} 功能点 ID -> 扫描数据 的映射
191
+ * @private
192
+ */
193
+ _buildScannedFeatureMap(scanResult) {
194
+ const featureMap = new Map();
195
+
196
+ // 从规格文件提取
197
+ for (const spec of scanResult.specs.files) {
198
+ const featureId = spec.parsed?.featureId;
199
+ if (featureId) {
200
+ if (!featureMap.has(featureId)) {
201
+ featureMap.set(featureId, {
202
+ id: featureId,
203
+ name: spec.parsed?.featureName || featureId,
204
+ fromSpec: spec,
205
+ fromCode: [],
206
+ fromReport: [],
207
+ inferredStage: null
208
+ });
209
+ }
210
+ featureMap.get(featureId).fromSpec = spec;
211
+ }
212
+
213
+ // 同时尝试从文件路径推断
214
+ const pathInferredId = this._extractFeatureIdFromPath(spec.path);
215
+ if (pathInferredId && !featureMap.has(pathInferredId)) {
216
+ featureMap.set(pathInferredId, {
217
+ id: pathInferredId,
218
+ name: spec.parsed?.featureName || pathInferredId,
219
+ fromSpec: spec,
220
+ fromCode: [],
221
+ fromReport: [],
222
+ inferredStage: null
223
+ });
224
+ }
225
+ }
226
+
227
+ // 从代码文件提取
228
+ for (const codeFile of scanResult.sourceCode.files) {
229
+ const codeFeatureId = codeFile.featureId;
230
+ if (codeFeatureId && featureMap.has(codeFeatureId)) {
231
+ featureMap.get(codeFeatureId).fromCode.push(codeFile);
232
+ } else if (codeFeatureId) {
233
+ featureMap.set(codeFeatureId, {
234
+ id: codeFeatureId,
235
+ name: codeFeatureId,
236
+ fromSpec: null,
237
+ fromCode: [codeFile],
238
+ fromReport: [],
239
+ inferredStage: StageEnum.IMPLEMENTING
240
+ });
241
+ }
242
+ }
243
+
244
+ // 从 PRD 提取功能点
245
+ for (const prdFeature of scanResult.prd.features) {
246
+ if (!featureMap.has(prdFeature.id)) {
247
+ featureMap.set(prdFeature.id, {
248
+ id: prdFeature.id,
249
+ name: prdFeature.name,
250
+ fromSpec: null,
251
+ fromCode: [],
252
+ fromReport: [],
253
+ inferredStage: StageEnum.PRD,
254
+ fromPRD: prdFeature
255
+ });
256
+ }
257
+ }
258
+
259
+ // 从报告提取关联的功能点
260
+ for (const report of scanResult.reports.reports) {
261
+ if (report.parsed && report.parsed.featureId) {
262
+ const reportFeatureId = report.parsed.featureId;
263
+ if (featureMap.has(reportFeatureId)) {
264
+ featureMap.get(reportFeatureId).fromReport.push(report);
265
+ }
266
+ }
267
+ }
268
+
269
+ return featureMap;
270
+ }
271
+
272
+ /**
273
+ * 从文件路径中提取功能点 ID
274
+ * @param {string} filePath - 文件路径
275
+ * @returns {string|null} 提取的 ID 或 null
276
+ * @private
277
+ */
278
+ _extractFeatureIdFromPath(filePath) {
279
+ // 匹配 feat-xxx 格式
280
+ const featMatch = filePath.match(/feat-[a-zA-Z0-9_-]+/i);
281
+ if (featMatch) return featMatch[0].toLowerCase();
282
+
283
+ // 匹配目录名作为潜在 ID
284
+ const parts = filePath.split(/[/\\]/);
285
+ if (parts[0] && /^[a-z][a-z0-9-]*$/.test(parts[0])) {
286
+ return parts[0];
287
+ }
288
+
289
+ return null;
290
+ }
291
+
292
+ /**
293
+ * 执行三路合并
294
+ *
295
+ * @param {Feature[]} stateFeatures - state 中的功能点
296
+ * @param {Map<string, Object>} scannedMap - 扫描到的功能点映射
297
+ * @param {Object} scanResult - 完整扫描结果
298
+ * @returns {Promise<Object>} 合并结果
299
+ * @private
300
+ */
301
+ async _performThreeWayMerge(stateFeatures, scannedMap, scanResult) {
302
+ const mergedFeatures = [];
303
+ const orphanFiles = [];
304
+ const newFeatures = [];
305
+ const conflicts = [...this._conflicts];
306
+ const warnings = [...this._warnings];
307
+
308
+ // 用于跟踪已处理的扫描项
309
+ const processedScanIds = new Set();
310
+
311
+ // === 第一轮:处理 state 中存在的功能点 ===
312
+ for (const stateFeat of stateFeatures) {
313
+ const scannedData = scannedMap.get(stateFeat.id);
314
+
315
+ if (scannedData) {
316
+ // 情况1:state有 + scanner有 -> 合并
317
+ processedScanIds.add(stateFeat.id);
318
+
319
+ const merged = await this._mergeFeatureWithScan(stateFeat, scannedData);
320
+ mergedFeatures.push(merged.merged);
321
+
322
+ if (merged.conflict) {
323
+ conflicts.push(merged.conflict);
324
+ }
325
+ if (merged.warning) {
326
+ warnings.push(merged.warning);
327
+ }
328
+ } else {
329
+ // 情况2:state有 + scanner无 -> 可能是孤儿
330
+ const orphanCheck = await this._checkForOrphan(stateFeat, scanResult);
331
+
332
+ if (orphanCheck.isOrphan) {
333
+ // 标记为孤儿,但保留在列表中
334
+ stateFeat.tags = [...(stateFeat.tags || []), '_orphan'];
335
+ mergedFeatures.push(stateFeat);
336
+
337
+ orphanFiles.push({
338
+ featureId: stateFeat.id,
339
+ reason: orphanCheck.reason,
340
+ lastSeen: stateFeat.updatedAt,
341
+ suggestion: orphanCheck.suggestion
342
+ });
343
+
344
+ warnings.push(`功能点 "${stateFeat.id}" 在文件系统中未找到对应制品 (${orphanCheck.reason})`);
345
+ } else {
346
+ // 可能是正常的(如刚规划的功能点),保留原样
347
+ mergedFeatures.push(stateFeat);
348
+ }
349
+ }
350
+ }
351
+
352
+ // === 第二轮:处理 scanner 有但 state 无的新功能点 ===
353
+ for (const [scanId, scanData] of scannedMap.entries()) {
354
+ if (!processedScanIds.has(scanId)) {
355
+ // 情况3:state无 + scanner有 -> 新发现
356
+
357
+ if (this.options.autoCreateNewFeatures) {
358
+ const newFeature = this._createFeatureFromScan(scanData);
359
+ newFeatures.push(newFeature);
360
+ mergedFeatures.push(newFeature);
361
+
362
+ // 自动添加到 state
363
+ try {
364
+ await this.stateStore.addFeature(newFeature);
365
+ console.log(chalk.green(`[Reconciler] 自动添加新功能点: ${newFeature.id}`));
366
+ } catch (addError) {
367
+ warnings.push(`无法自动保存新功能点 ${newFeature.id}: ${addError.message}`);
368
+ }
369
+ } else {
370
+ warnings.push(`发现未跟踪的功能点: ${scanId}`);
371
+ }
372
+ }
373
+ }
374
+
375
+ // === 第三轮:孤儿文件检测 ===
376
+ const additionalOrphans = await this._detectOrphanFiles(scanResult, mergedFeatures);
377
+ orphanFiles.push(...additionalOrphans);
378
+
379
+ return {
380
+ mergedFeatures,
381
+ orphanFiles,
382
+ newFeatures,
383
+ conflicts,
384
+ warnings
385
+ };
386
+ }
387
+
388
+ /**
389
+ * 合并单个功能点的 state 和 scanner 数据
390
+ *
391
+ * @param {Feature} stateFeature - state 中的功能点
392
+ * @param {Object} scannedData - 扫描数据
393
+ * @returns {Promise<{merged:Feature, conflict?:Conflict, warning?:string}>} 合并结果
394
+ * @private
395
+ */
396
+ async _mergeFeatureWithScan(stateFeature, scannedData) {
397
+ // 创建副本用于修改
398
+ const merged = Feature.fromJSON(stateFeature.toJSON());
399
+ let conflict = null;
400
+ let warning = null;
401
+
402
+ // 1. 阶段冲突解决:以 scanner 推断为准(文件不会撒谎)
403
+ if (this.options.autoStageFromScanner && scannedData.inferredStage) {
404
+ const scannerStage = scannedData.inferredStage;
405
+
406
+ if (merged.stage !== scannerStage) {
407
+ // 记录冲突
408
+ conflict = {
409
+ featureId: merged.id,
410
+ field: 'stage',
411
+ stateValue: merged.stage,
412
+ scannerValue: scannerStage,
413
+ resolvedValue: scannerStage,
414
+ resolutionStrategy: 'scanner_priority' // 以扫描结果为准
415
+ };
416
+
417
+ // 更新阶段
418
+ merged.stage = scannerStage;
419
+
420
+ // 添加时间线条目
421
+ merged.addTimelineEntry(scannerStage, `阶段由对齐器更新(基于文件系统扫描)`);
422
+ }
423
+ }
424
+
425
+ // 2. 名称补充:如果 state 中名称为空,使用扫描结果
426
+ if ((!merged.name || merged.name === merged.id) && scannedData.name && scannedData.name !== scannedData.id) {
427
+ merged.name = scannedData.name;
428
+ }
429
+
430
+ // 3. 描述补充
431
+ if (!merged.description && scannedData.fromSpec?.parsed?.description) {
432
+ merged.description = scannedData.fromSpec.parsed.description.substring(0, 2000);
433
+ }
434
+
435
+ // 4. 优先级补充
436
+ if (merged.priority === 'P2' && scannedData.fromSpec?.parsed?.priority) {
437
+ const scannedPriority = scannedData.fromSpec.parsed.priority;
438
+ if (Object.values(Priority).includes(scannedPriority)) {
439
+ merged.priority = scannedPriority;
440
+ }
441
+ }
442
+
443
+ // 5. 制品信息同步
444
+ if (scannedData.fromSpec) {
445
+ const existingSpecArtifact = merged.getArtifactByType('spec');
446
+ if (!existingSpecArtifact) {
447
+ merged.addArtifact({
448
+ type: 'spec',
449
+ path: scannedData.fromSpec.path,
450
+ size: scannedData.fromSpec.size,
451
+ lastModified: scannedData.fromSpec.lastModified.getTime(),
452
+ checksum: scannedData.fromSpec.checksum
453
+ });
454
+ }
455
+ }
456
+
457
+ // 同步代码制品
458
+ if (scannedData.fromCode && scannedData.fromCode.length > 0) {
459
+ const existingCodeArtifact = merged.getArtifactByType('code');
460
+ if (!existingCodeArtifact) {
461
+ // 使用第一个代码文件作为代表
462
+ const primaryCode = scannedData.fromCode[0];
463
+ merged.addArtifact({
464
+ type: 'code',
465
+ path: primaryCode.path,
466
+ size: primaryCode.size,
467
+ lastModified: primaryCode.lastModified.getTime()
468
+ });
469
+ }
470
+ }
471
+
472
+ // 同步报告制品
473
+ if (scannedData.fromReport && scannedData.fromReport.length > 0) {
474
+ const existingReportArtifact = merged.getArtifactByType('report');
475
+ if (!existingReportArtifact) {
476
+ const primaryReport = scannedData.fromReport[0];
477
+ merged.addArtifact({
478
+ type: 'report',
479
+ path: primaryReport.path,
480
+ size: primaryReport.size,
481
+ lastModified: primaryReport.lastModified.getTime()
482
+ });
483
+ }
484
+ }
485
+
486
+ // 6. 质量指标:保留 state 中的值(更准确)
487
+ if (this.options.preserveStateQuality && !merged.quality && scannedData.fromReport?.length > 0) {
488
+ // 尝试从报告中提取质量数据
489
+ for (const report of scannedData.fromReport) {
490
+ if (report.parsed?.score !== undefined || report.parsed?.qualityScore !== undefined) {
491
+ // 不在这里设置,保持 state 为准的原则
492
+ break;
493
+ }
494
+ }
495
+ }
496
+
497
+ // 7. 更新时间戳
498
+ merged.updatedAt = Date.now();
499
+
500
+ // 尝试保存合并后的功能点到 state
501
+ try {
502
+ await this.stateStore.updateFeature(merged.id, merged.toJSON());
503
+ } catch (saveError) {
504
+ warning = `无法保存合并后的功能点 ${merged.id}: ${saveError.message}`;
505
+ }
506
+
507
+ return { merged, conflict, warning };
508
+ }
509
+
510
+ /**
511
+ * 检查功能点是否为孤儿
512
+ *
513
+ * @param {Feature} feature - 要检查的功能点
514
+ * @param {Object} scanResult - 扫描结果
515
+ * @returns {Promise<{isOrphan:boolean, reason:string, suggestion:string}>} 检查结果
516
+ * @private
517
+ */
518
+ async _checkForOrphan(feature, scanResult) {
519
+ const now = Date.now();
520
+ const thresholdMs = this.options.orphanThresholdDays * 24 * 60 * 60 * 1000;
521
+ const timeSinceUpdate = now - (feature.updatedAt || feature.createdAt || now);
522
+
523
+ // 如果最近有更新,不太可能是孤儿
524
+ if (timeSinceUpdate < thresholdMs) {
525
+ return {
526
+ isOrphan: false,
527
+ reason: '',
528
+ suggestion: ''
529
+ };
530
+ }
531
+
532
+ // 根据阶段判断
533
+ switch (feature.stage) {
534
+ case StageEnum.PRD:
535
+ case StageEnum.EXTRACTED:
536
+ // 这些阶段没有制品是正常的
537
+ return { isOrphan: false, reason: '', suggestion: '' };
538
+
539
+ case StageEnum.SPEC:
540
+ // 应该有规格文件
541
+ if (scanResult.specs.totalCount > 0) {
542
+ const hasMatchingSpec = scanResult.specs.files.some(f =>
543
+ f.parsed?.featureId === feature.id ||
544
+ f.path.toLowerCase().includes(feature.id.toLowerCase())
545
+ );
546
+ if (!hasMatchingSpec) {
547
+ return {
548
+ isOrphan: true,
549
+ reason: `标记为 SPEC 阶段但未找到规格文件`,
550
+ suggestion: `检查 dev-specs/ 目录是否有 ${feature.id} 相关文件`
551
+ };
552
+ }
553
+ }
554
+ break;
555
+
556
+ case StageEnum.IMPLEMENTING:
557
+ case StageEnum.VERIFYING:
558
+ case StageEnum.DONE:
559
+ // 这些阶段应该有代码或报告
560
+ const hasCode = scanResult.sourceCode.fileCount > 0 &&
561
+ scanResult.sourceCode.files.some(f =>
562
+ f.featureId === feature.id ||
563
+ f.path.toLowerCase().includes(feature.id.toLowerCase())
564
+ );
565
+
566
+ const hasReport = scanResult.reports.reports.length > 0 &&
567
+ scanResult.reports.reports.some(r =>
568
+ r.path.toLowerCase().includes(feature.id.toLowerCase()) ||
569
+ (r.parsed && JSON.stringify(r.parsed).includes(feature.id))
570
+ );
571
+
572
+ if (!hasCode && !hasReport) {
573
+ return {
574
+ isOrphan: true,
575
+ reason: `标记为 ${feature.stage.toUpperCase()} 阶段但未找到对应的代码或报告`,
576
+ suggestion: `检查源码目录和 reports/ 目录`
577
+ };
578
+ }
579
+ break;
580
+ }
581
+
582
+ return { isOrphan: false, reason: '', suggestion: '' };
583
+ }
584
+
585
+ /**
586
+ * 从扫描数据创建新的功能点对象
587
+ *
588
+ * @param {Object} scanData - 扫描数据
589
+ * @returns {Feature} 新创建的功能点
590
+ * @private
591
+ */
592
+ _createFeatureFromScan(scanData) {
593
+ const now = Date.now();
594
+
595
+ // 推断阶段
596
+ let stage = scanData.inferredStage || StageEnum.PRD;
597
+
598
+ // 根据可用制品调整阶段
599
+ if (scanData.fromCode && scanData.fromCode.length > 0) {
600
+ stage = StageEnum.IMPLEMENTING;
601
+ }
602
+ if (scanData.fromReport && scanData.fromReport.length > 0) {
603
+ stage = StageEnum.VERIFYING;
604
+ }
605
+ if (scanData.fromSpec && !scanData.fromCode) {
606
+ stage = StageEnum.SPEC;
607
+ }
608
+
609
+ // 推断优先级
610
+ let priority = Priority.P2;
611
+ if (scanData.fromSpec?.parsed?.priority) {
612
+ const p = scanData.fromSpec.parsed.priority;
613
+ if (Object.values(Priority).includes(p)) {
614
+ priority = p;
615
+ }
616
+ }
617
+
618
+ // 创建功能点
619
+ const feature = new Feature({
620
+ id: scanData.id,
621
+ name: scanData.name || scanData.id,
622
+ description: scanData.fromSpec?.parsed?.description || '',
623
+ stage,
624
+ priority,
625
+ tags: ['_auto-discovered'],
626
+ createdAt: now,
627
+ updatedAt: now
628
+ });
629
+
630
+ // 添加初始时间线
631
+ feature.addTimelineEntry(stage, '由对齐器自动发现');
632
+
633
+ return feature;
634
+ }
635
+
636
+ /**
637
+ * 检测孤儿文件
638
+ * 发现没有对应功能点记录的制品文件
639
+ *
640
+ * @param {Object} scanResult - 扫描结果
641
+ * @param {Feature[]} knownFeatures - 已知功能点列表
642
+ * @returns {Promise<OrphanFile[]>} 孤儿文件列表
643
+ * @private
644
+ */
645
+ async _detectOrphanFiles(scanResult, knownFeatures) {
646
+ const orphans = [];
647
+ const knownIds = new Set(knownFeatures.map(f => f.id));
648
+
649
+ // 检查规格文件
650
+ for (const spec of scanResult.specs.files) {
651
+ const specId = spec.parsed?.featureId;
652
+ const pathId = this._extractFeatureIdFromPath(spec.path);
653
+
654
+ if (specId && !knownIds.has(specId)) {
655
+ orphans.push({
656
+ path: spec.path,
657
+ type: 'spec',
658
+ reason: '规格文件没有对应的功能点记录',
659
+ suggestedFeatureId: specId,
660
+ lastModified: spec.lastModified
661
+ });
662
+ } else if (pathId && !knownIds.has(pathId) && (!specId || specId === pathId)) {
663
+ orphans.push({
664
+ path: spec.path,
665
+ type: 'spec',
666
+ reason: '规格文件路径暗示了未知的功能点',
667
+ suggestedFeatureId: pathId,
668
+ lastModified: spec.lastModified
669
+ });
670
+ }
671
+ }
672
+
673
+ // 检查代码文件
674
+ for (const codeFile of scanResult.sourceCode.files) {
675
+ if (codeFile.featureId && !knownIds.has(codeFile.featureId)) {
676
+ orphans.push({
677
+ path: codeFile.path,
678
+ type: 'code',
679
+ reason: '代码文件引用了未知的功能点 ID',
680
+ suggestedFeatureId: codeFile.featureId,
681
+ lastModified: codeFile.lastModified
682
+ });
683
+ }
684
+ }
685
+
686
+ // 检查报告文件
687
+ for (const report of scanResult.reports.reports) {
688
+ if (report.parsed?.featureId && !knownIds.has(report.parsed.featureId)) {
689
+ orphans.push({
690
+ path: report.path,
691
+ type: 'report',
692
+ reason: '验证报告引用了未知的功能点',
693
+ suggestedFeatureId: report.parsed.featureId,
694
+ lastModified: report.lastModified
695
+ });
696
+ }
697
+ }
698
+
699
+ return orphans;
700
+ }
701
+
702
+ /**
703
+ * 计算统计信息
704
+ *
705
+ * @param {number} originalCount - 原始数量
706
+ * @param {number} mergedCount - 合并后数量
707
+ * @param {number} newCount - 新增数量
708
+ * @param {number} orphanCount - 孤儿数量
709
+ * @param {number} conflictCount - 冲突数量
710
+ * @returns {Object} 统计信息
711
+ * @private
712
+ */
713
+ _calculateStatistics(originalCount, mergedCount, newCount, orphanCount, conflictCount) {
714
+ return {
715
+ originalFeatureCount: originalCount,
716
+ finalFeatureCount: mergedCount,
717
+ newFeaturesDiscovered: newCount,
718
+ orphanFeaturesDetected: orphanCount,
719
+ conflictsResolved: conflictCount,
720
+ changeDelta: mergedCount - originalCount
721
+ };
722
+ }
723
+
724
+ /**
725
+ * 打印对齐报告
726
+ *
727
+ * @param {ReconciliationResult} result - 对齐结果
728
+ * @private
729
+ */
730
+ _printReconciliationReport(result) {
731
+ console.log(chalk.bold('\n' + '='.repeat(60)));
732
+ console.log(chalk.bold.cyan(' PDD Visual Manager - 数据对齐报告'));
733
+ console.log('='.repeat(60));
734
+
735
+ // 统计摘要
736
+ console.log(chalk.bold('\n统计摘要:'));
737
+ console.log(` 原始功能点数: ${result.statistics.originalFeatureCount}`);
738
+ console.log(` 最终功能点数: ${result.statistics.finalFeatureCount}`);
739
+ console.log(` 新发现功能点: ${chalk.green(String(result.statistics.newFeaturesDiscovered))}`);
740
+ console.log(` 孤儿功能点: ${chalk.yellow(String(result.statistics.orphanFeaturesDetected))}`);
741
+ console.log(` 解决的冲突: ${chalk.red(String(result.statistics.conflictsResolved))}`);
742
+
743
+ // 新功能点
744
+ if (result.newFeatures.length > 0) {
745
+ console.log(chalk.bold('\n新发现的功能点:'));
746
+ for (const feat of result.newFeatures) {
747
+ console.log(` ${chalk.green('+')} ${chalk.magenta(feat.id)} ${feat.name} [${chalk.cyan(feat.stage)}]`);
748
+ }
749
+ }
750
+
751
+ // 孤儿文件
752
+ if (result.orphanFiles.length > 0) {
753
+ console.log(chalk.bold('\n孤儿警告:'));
754
+ for (const orphan of result.orphanFiles) {
755
+ console.log(` ${chalk.yellow('!')} ${orphan.featureId || orphan.path}: ${orphan.reason}`);
756
+ if (orphan.suggestedFeatureId) {
757
+ console.log(` 建议: 关联到功能点 "${orphan.suggestedFeatureId}"`);
758
+ }
759
+ }
760
+ }
761
+
762
+ // 冲突详情
763
+ if (result.conflicts.length > 0) {
764
+ console.log(chalk.bold('\n解决的冲突:'));
765
+ for (const conflict of result.conflicts) {
766
+ console.log(` ${chalk.red('*')} [${conflict.featureId}] ${conflict.field}:`);
767
+ console.log(` State: ${conflict.stateValue}`);
768
+ console.log(` Scanner: ${conflict.scannerValue}`);
769
+ console.log(` -> 采用: ${chalk.green(String(conflict.resolvedValue))} (${conflict.resolutionStrategy})`);
770
+ }
771
+ }
772
+
773
+ // 警告
774
+ if (result.warnings.length > 0) {
775
+ console.log(chalk.bold('\n警告信息:'));
776
+ for (const warn of result.warnings.slice(0, 10)) {
777
+ console.log(` ${chalk.yellow('-')} ${warn}`);
778
+ }
779
+ if (result.warnings.length > 10) {
780
+ console.log(` ... 还有 ${result.warnings.length - 10} 条警告`);
781
+ }
782
+ }
783
+
784
+ console.log(chalk.bold('\n' + '='.repeat(60)));
785
+ console.log(`耗时: ${result.duration}ms | 时间: ${new Date(result.timestamp).toLocaleString()}`);
786
+ console.log('='.repeat(60) + '\n');
787
+ }
788
+
789
+ /**
790
+ * 仅执行冲突检测(不修改数据)
791
+ * 用于预览模式或 dry-run
792
+ *
793
+ * @returns {Promise<{conflicts:Conflict[], warnings:string[]}>} 检测结果
794
+ */
795
+ async detectConflictsOnly() {
796
+ console.log(chalk.blue('[Reconciler] 执行仅冲突检测模式...\n'));
797
+
798
+ const state = await this.stateStore.loadState();
799
+ const stateFeatures = (state.project.features || []).map(f => Feature.fromJSON(f));
800
+ const scanResult = await this.scanner.fullScan();
801
+ const scannedMap = this._buildScannedFeatureMap(scanResult);
802
+
803
+ const conflicts = [];
804
+ const warnings = [];
805
+
806
+ for (const stateFeat of stateFeatures) {
807
+ const scannedData = scannedMap.get(stateFeat.id);
808
+
809
+ if (scannedData) {
810
+ // 检查阶段差异
811
+ if (scannedData.inferredStage && stateFeat.stage !== scannedData.inferredStage) {
812
+ conflicts.push({
813
+ featureId: stateFeat.id,
814
+ field: 'stage',
815
+ stateValue: stateFeat.stage,
816
+ scannerValue: scannedData.inferredStage,
817
+ resolvedValue: null,
818
+ resolutionStrategy: 'pending'
819
+ });
820
+ }
821
+ }
822
+ }
823
+
824
+ // 检测潜在的孤儿
825
+ for (const stateFeat of stateFeatures) {
826
+ const orphanCheck = await this._checkForOrphan(stateFeat, scanResult);
827
+ if (orphanCheck.isOrphan) {
828
+ warnings.push(`[${stateFeat.id}] ${orphanCheck.reason}`);
829
+ }
830
+ }
831
+
832
+ return { conflicts, warnings };
833
+ }
834
+
835
+ /**
836
+ * 获取配置选项
837
+ * @returns {Object} 当前配置
838
+ */
839
+ getOptions() {
840
+ return { ...this.options };
841
+ }
842
+
843
+ /**
844
+ * 更新配置选项
845
+ * @param {Object} updates - 配置更新
846
+ */
847
+ setOptions(updates) {
848
+ Object.assign(this.options, updates);
849
+ }
850
+ }
851
+
852
+ /**
853
+ * 导出默认对象
854
+ */
855
+ export default Reconciler;