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,1053 @@
1
+ /**
2
+ * KanbanView - 看板与详情视图 (Panel 2)
3
+ * 提供6列看板展示、筛选搜索、功能点详情弹窗
4
+ *
5
+ * @module KanbanView
6
+ * @version 1.0.0
7
+ * @requires App, PipelineView
8
+ */
9
+
10
+ const KanbanView = {
11
+ name: 'kanban',
12
+
13
+ // 当前查看的feature索引
14
+ currentIndex: 0,
15
+
16
+ // 筛选后的功能点列表
17
+ filteredFeatures: [],
18
+
19
+ // 当前筛选条件
20
+ filters: {
21
+ stage: '',
22
+ search: '',
23
+ sortBy: 'name',
24
+ viewMode: 'card' // card | list | table
25
+ },
26
+
27
+ // 阶段配置(与PipelineView保持一致)
28
+ stages: [
29
+ { id: 'prd', name: 'PRD', color: '#9b59b6', icon: '📋' },
30
+ { id: 'extracted', name: 'EXTRACTED', color: '#3498db', icon: '🔍' },
31
+ { id: 'spec', name: 'SPEC', color: '#2ecc71', icon: '📝' },
32
+ { id: 'implementing', name: 'IMPLEMENTING', color: '#f39c12', icon: '⚙️' },
33
+ { id: 'verifying', name: 'VERIFYING', color: '#e74c3c', icon: '✅' },
34
+ { id: 'done', name: 'DONE', color: '#1abc9c', icon: '🎉' }
35
+ ],
36
+
37
+ /**
38
+ * 初始化视图
39
+ */
40
+ init() {
41
+ App.registerView(this.name, this);
42
+ this.bindFilterEvents();
43
+ console.log('[KanbanView] Initialized');
44
+ },
45
+
46
+ /**
47
+ * 视图显示时调用
48
+ */
49
+ onShow() {
50
+ this.render(App.state.features || [], App.state.summary || {});
51
+ },
52
+
53
+ /**
54
+ * SSE事件处理
55
+ * @param {string} type - 事件类型
56
+ * @param {*} data - 事件数据
57
+ */
58
+ onEvent(type, data) {
59
+ if (type === 'stage_change' || type === 'data_refreshed') {
60
+ this.render(App.state.features || [], App.state.summary || {});
61
+ // 如果当前有打开的详情弹窗,更新内容
62
+ if (this.currentFeatureId) {
63
+ this.showFeatureDetail(this.currentFeatureId);
64
+ }
65
+ }
66
+ },
67
+
68
+ /**
69
+ * 绑定筛选栏事件
70
+ */
71
+ bindFilterEvents() {
72
+ // 使用事件委托,在render后绑定
73
+ document.addEventListener('click', (e) => {
74
+ // 阶段筛选按钮
75
+ if (e.target.matches('.filter-stage-btn')) {
76
+ const stage = e.target.dataset.stage;
77
+ this.setFilter('stage', stage === this.filters.stage ? '' : stage);
78
+ }
79
+
80
+ // 视图模式切换
81
+ if (e.target.matches('.view-mode-btn')) {
82
+ this.setFilter('viewMode', e.target.dataset.mode);
83
+ }
84
+
85
+ // 排序选择
86
+ if (e.target.matches('#sort-select')) {
87
+ return; // change事件处理
88
+ }
89
+
90
+ // 搜索框(实时搜索)
91
+ if (e.target.matches('#search-input')) {
92
+ return; // input事件处理
93
+ }
94
+
95
+ // 清除筛选
96
+ if (e.target.matches('.clear-filters-btn')) {
97
+ this.clearFilters();
98
+ }
99
+ });
100
+
101
+ // 搜索输入事件
102
+ document.addEventListener('input', (e) => {
103
+ if (e.target.matches('#search-input')) {
104
+ this.setFilter('search', e.target.value);
105
+ }
106
+ });
107
+
108
+ // 排序改变事件
109
+ document.addEventListener('change', (e) => {
110
+ if (e.target.matches('#sort-select')) {
111
+ this.setFilter('sortBy', e.target.value);
112
+ }
113
+ });
114
+
115
+ // 键盘导航(ESC关闭Modal,左右切换Feature)
116
+ document.addEventListener('keydown', (e) => {
117
+ if (!this.currentFeatureId) return;
118
+
119
+ switch(e.key) {
120
+ case 'Escape':
121
+ App.closeModal();
122
+ this.currentFeatureId = null;
123
+ break;
124
+ case 'ArrowLeft':
125
+ e.preventDefault();
126
+ this.navigateFeature(-1);
127
+ break;
128
+ case 'ArrowRight':
129
+ e.preventDefault();
130
+ this.navigateFeature(1);
131
+ break;
132
+ }
133
+ });
134
+ },
135
+
136
+ /**
137
+ * 设置筛选条件并重新渲染
138
+ * @param {string} key - 筛选键
139
+ * @param {*} value - 筛选值
140
+ */
141
+ setFilter(key, value) {
142
+ this.filters[key] = value;
143
+ this.render(App.state.features || [], App.state.summary || {});
144
+ },
145
+
146
+ /**
147
+ * 清除所有筛选
148
+ */
149
+ clearFilters() {
150
+ this.filters = {
151
+ stage: '',
152
+ search: '',
153
+ sortBy: 'name',
154
+ viewMode: 'card'
155
+ };
156
+ this.render(App.state.features || [], App.state.summary || {});
157
+ },
158
+
159
+ /**
160
+ * 主渲染方法
161
+ * @param {Array} features - 功能点列表
162
+ * @param {Object} summary - 项目汇总
163
+ */
164
+ render(features, summary) {
165
+ const container = document.getElementById('view-kanban');
166
+ if (!container) return;
167
+
168
+ // 应用筛选
169
+ this.filteredFeatures = this._applyFilters(features);
170
+
171
+ container.innerHTML = `
172
+ <div class="kanban-view">
173
+ ${this._renderFilterBar()}
174
+ ${this._renderKanbanBoard()}
175
+ </div>
176
+ `;
177
+
178
+ // 绑定卡片点击事件
179
+ this._bindCardEvents();
180
+ },
181
+
182
+ /**
183
+ * 应用筛选条件
184
+ * @param {Array} features - 原始功能点列表
185
+ * @returns {Array} 筛选后的列表
186
+ */
187
+ _applyFilters(features) {
188
+ let result = [...(features || [])];
189
+
190
+ // 阶段筛选
191
+ if (this.filters.stage) {
192
+ result = result.filter(f => f.stage === this.filters.stage);
193
+ }
194
+
195
+ // 搜索筛选
196
+ if (this.filters.search) {
197
+ const searchTerm = this.filters.search.toLowerCase();
198
+ result = result.filter(f =>
199
+ f.name.toLowerCase().includes(searchTerm) ||
200
+ (f.description && f.description.toLowerCase().includes(searchTerm))
201
+ );
202
+ }
203
+
204
+ // 排序
205
+ result.sort((a, b) => {
206
+ switch (this.filters.sortBy) {
207
+ case 'name':
208
+ return a.name.localeCompare(b.name);
209
+ case 'score':
210
+ return (b.quality?.score || 0) - (a.quality?.score || 0);
211
+ case 'tokens':
212
+ return (b.tokens?.total || 0) - (a.tokens?.total || 0);
213
+ case 'date':
214
+ return new Date(b.updatedAt || b.createdAt) - new Date(a.updatedAt || a.createdAt);
215
+ default:
216
+ return 0;
217
+ }
218
+ });
219
+
220
+ return result;
221
+ },
222
+
223
+ /**
224
+ * 渲染筛选栏
225
+ * @returns {string} HTML
226
+ */
227
+ _renderFilterBar() {
228
+ return `
229
+ <div class="filter-bar">
230
+ <div class="filter-group filter-stages">
231
+ <span class="filter-label">阶段:</span>
232
+ <button class="filter-stage-btn ${!this.filters.stage ? 'active' : ''}" data-stage="">
233
+ 全部 (${App.state.features?.length || 0})
234
+ </button>
235
+ ${this.stages.map(stage => {
236
+ const count = (App.state.features || []).filter(f => f.stage === stage.id).length;
237
+ return `
238
+ <button class="filter-stage-btn ${this.filters.stage === stage.id ? 'active' : ''}"
239
+ data-stage="${stage.id}"
240
+ style="--stage-color: ${stage.color}">
241
+ ${stage.icon} ${stage.name} (${count})
242
+ </button>
243
+ `;
244
+ }).join('')}
245
+ </div>
246
+
247
+ <div class="filter-group filter-search">
248
+ <input type="text" id="search-input"
249
+ placeholder="搜索功能点..."
250
+ value="${this.filters.search}"
251
+ class="search-input" />
252
+ </div>
253
+
254
+ <div class="filter-group filter-sort">
255
+ <select id="sort-select" class="sort-select">
256
+ <option value="name" ${this.filters.sortBy === 'name' ? 'selected' : ''}>按名称</option>
257
+ <option value="score" ${this.filters.sortBy === 'score' ? 'selected' : ''}>按评分</option>
258
+ <option value="tokens" ${this.filters.sortBy === 'tokens' ? 'selected' : ''}>按Token</option>
259
+ <option value="date" ${this.filters.sortBy === 'date' ? 'selected' : ''}>按日期</option>
260
+ </select>
261
+ </div>
262
+
263
+ <div class="filter-group view-modes">
264
+ <button class="view-mode-btn ${this.filters.viewMode === 'card' ? 'active' : ''}" data-mode="card" title="卡片视图">
265
+ 🃏
266
+ </button>
267
+ <button class="view-mode-btn ${this.filters.viewMode === 'list' ? 'active' : ''}" data-mode="list" title="列表视图">
268
+ 📋
269
+ </button>
270
+ <button class="view-mode-btn ${this.filters.viewMode === 'table' ? 'active' : ''}" data-mode="table" title="表格视图">
271
+ 📊
272
+ </button>
273
+ </div>
274
+
275
+ ${(this.filters.stage || this.filters.search) ? `
276
+ <button class="btn btn-sm btn-outline clear-filters-btn">清除筛选</button>
277
+ ` : ''}
278
+ </div>
279
+ `;
280
+ },
281
+
282
+ /**
283
+ * 渲染看板面板
284
+ * @returns {string} HTML
285
+ */
286
+ _renderKanbanBoard() {
287
+ if (this.filteredFeatures.length === 0) {
288
+ return `
289
+ <div class="card empty-state">
290
+ <div class="empty-icon">🔍</div>
291
+ <h3>没有找到匹配的功能点</h3>
292
+ <p>尝试调整筛选条件或清除筛选</p>
293
+ <button class="btn btn-primary" onclick="KanbanView.clearFilters()">清除所有筛选</button>
294
+ </div>
295
+ `;
296
+ }
297
+
298
+ if (this.filters.viewMode === 'table') {
299
+ return this._renderTableView();
300
+ }
301
+
302
+ if (this.filters.viewMode === 'list') {
303
+ return this._renderListView();
304
+ }
305
+
306
+ // 默认卡片视图(看板)
307
+ return `
308
+ <div class="kanban-board">
309
+ ${this.stages.map(stage => {
310
+ const stageFeatures = this.filteredFeatures.filter(f => f.stage === stage.id);
311
+ return `
312
+ <div class="kanban-column" data-stage="${stage.id}">
313
+ <div class="column-header" style="background-color: ${stage.color}; border-color: ${stage.color}">
314
+ <span class="column-name">${stage.icon} ${stage.name}</span>
315
+ <span class="column-count">${stageFeatures.length}</span>
316
+ </div>
317
+ <div class="column-cards">
318
+ ${stageFeatures.map(feature => this._renderCard(feature)).join('')}
319
+ ${stageFeatures.length === 0 ? '<div class="empty-column">暂无功能点</div>' : ''}
320
+ </div>
321
+ </div>
322
+ `;
323
+ }).join('')}
324
+ </div>
325
+ `;
326
+ },
327
+
328
+ /**
329
+ * 渲染单个看板卡片
330
+ * @param {Object} feature - 功能点数据
331
+ * @returns {string} HTML
332
+ */
333
+ _renderCard(feature) {
334
+ const score = feature.quality?.score || 0;
335
+ const grade = Charts.getGrade(score);
336
+ const tokens = feature.tokens?.total || 0;
337
+
338
+ return `
339
+ <div class="kanban-card" data-id="${feature.id}" title="点击查看详情">
340
+ <div class="card-title-row">
341
+ <h4 class="card-name" title="${feature.name}">${this._truncate(feature.name, 25)}</h4>
342
+ </div>
343
+ <div class="card-meta">
344
+ <span class="card-grade badge badge-${grade.toLowerCase()}">${grade}</span>
345
+ <span class="card-tokens">🪙 ${this._formatNumber(tokens)}</span>
346
+ </div>
347
+ <div class="card-progress-hint">
348
+ ${this._getProgressHint(feature.stage)}
349
+ </div>
350
+ </div>
351
+ `;
352
+ },
353
+
354
+ /**
355
+ * 获取进度提示文字
356
+ * @param {string} stage - 当前阶段
357
+ * @returns {string}
358
+ */
359
+ _getProgressHint(stage) {
360
+ const hints = {
361
+ prd: '需求分析中...',
362
+ extracted: '已提取,待生成规格',
363
+ spec: '规格生成完成,待实现',
364
+ implementing: '开发实现中...',
365
+ verifying: '质量验证中...',
366
+ done: '✨ 已完成'
367
+ };
368
+ return hints[stage] || '进行中';
369
+ },
370
+
371
+ /**
372
+ * 渲染列表视图
373
+ * @returns {string} HTML
374
+ */
375
+ _renderListView() {
376
+ return `
377
+ <div class="list-view">
378
+ ${this.filteredFeatures.map(feature => {
379
+ const stageConfig = this.stages.find(s => s.id === feature.stage) || this.stages[0];
380
+ const score = feature.quality?.score || 0;
381
+ const grade = Charts.getGrade(score);
382
+
383
+ return `
384
+ <div class="list-item" data-id="${feature.id}">
385
+ <div class="list-item-main" onclick="KanbanView.showFeatureDetail('${feature.id}')">
386
+ <span class="item-stage-badge" style="background-color: ${stageConfig.color}20; color: ${stageConfig.color}">
387
+ ${stageConfig.icon}
388
+ </span>
389
+ <div class="item-info">
390
+ <h4 class="item-name">${feature.name}</h4>
391
+ <p class="item-desc">${feature.description || '暂无描述'}</p>
392
+ </div>
393
+ <div class="item-stats">
394
+ <span class="item-score" style="color: ${Charts.getColor(score)}">${score.toFixed(1)} (${grade})</span>
395
+ <span class="item-tokens">🪙 ${this._formatNumber(feature.tokens?.total || 0)}</span>
396
+ </div>
397
+ </div>
398
+ </div>
399
+ `;
400
+ }).join('')}
401
+ </div>
402
+ `;
403
+ },
404
+
405
+ /**
406
+ * 渲染表格视图
407
+ * @returns {string} HTML
408
+ */
409
+ _renderTableView() {
410
+ return `
411
+ <div class="table-view card">
412
+ <table class="data-table kanban-table">
413
+ <thead>
414
+ <tr>
415
+ <th>名称</th>
416
+ <th>阶段</th>
417
+ <th>评分</th>
418
+ <th>Token</th>
419
+ <th>更新时间</th>
420
+ <th>操作</th>
421
+ </tr>
422
+ </thead>
423
+ <tbody>
424
+ ${this.filteredFeatures.map(feature => {
425
+ const stageConfig = this.stages.find(s => s.id === feature.stage) || this.stages[0];
426
+ const score = feature.quality?.score || 0;
427
+ const grade = Charts.getGrade(score);
428
+
429
+ return `
430
+ <tr onclick="KanbanView.showFeatureDetail('${feature.id}')" style="cursor:pointer;">
431
+ <td><strong>${this._truncate(feature.name, 30)}</strong></td>
432
+ <td>
433
+ <span class="badge" style="background-color: ${stageConfig.color}20; color: ${stageConfig.color}">
434
+ ${stageConfig.icon} ${stageConfig.name}
435
+ </span>
436
+ </td>
437
+ <td><span style="color: ${Charts.getColor(score)}; font-weight: bold;">${score.toFixed(1)} (${grade})</span></td>
438
+ <td>${this._formatNumber(feature.tokens?.total || 0)}</td>
439
+ <td>${this._formatDate(feature.updatedAt || feature.createdAt)}</td>
440
+ <td>
441
+ <button class="btn btn-sm btn-outline" onclick="event.stopPropagation(); KanbanView.showFeatureDetail('${feature.id}')">
442
+ 详情
443
+ </button>
444
+ </td>
445
+ </tr>
446
+ `;
447
+ }).join('')}
448
+ </tbody>
449
+ </table>
450
+ </div>
451
+ `;
452
+ },
453
+
454
+ /**
455
+ * 绑定卡片点击事件
456
+ */
457
+ _bindCardEvents() {
458
+ document.querySelectorAll('.kanban-card[data-id], .list-item[data-id]').forEach(el => {
459
+ el.addEventListener('click', () => {
460
+ this.showFeatureDetail(el.dataset.id);
461
+ });
462
+ });
463
+ },
464
+
465
+ /**
466
+ * 显示功能点详情
467
+ * @param {string} featureId - 功能点ID
468
+ */
469
+ async showFeatureDetail(featureId) {
470
+ this.currentFeatureId = featureId;
471
+
472
+ try {
473
+ // 先从本地状态查找
474
+ let feature = (App.state.features || []).find(f => f.id === featureId);
475
+
476
+ // 如果本地没有,从API获取
477
+ if (!feature) {
478
+ const response = await fetch(`/api/feature/${featureId}`);
479
+ if (!response.ok) throw new Error('获取功能点详情失败');
480
+ const data = await response.json();
481
+ feature = data.feature;
482
+ }
483
+
484
+ if (!feature) throw new Error('功能点不存在');
485
+
486
+ // 更新当前索引
487
+ this.currentIndex = this.filteredFeatures.findIndex(f => f.id === featureId);
488
+
489
+ // 渲染详情Modal
490
+ const html = this._renderDetailView(feature);
491
+ App.showModal(html);
492
+
493
+ // 绑定导航按钮事件
494
+ setTimeout(() => this._bindNavigationEvents(), 100);
495
+
496
+ } catch (error) {
497
+ console.error('[KanbanView] Error loading feature detail:', error);
498
+ App.showModal(`
499
+ <div class="error-state">
500
+ <div class="error-icon">❌</div>
501
+ <h3>加载失败</h3>
502
+ <p>${error.message}</p>
503
+ <button class="btn btn-primary" onclick="App.closeModal()">关闭</button>
504
+ </div>
505
+ `);
506
+ }
507
+ },
508
+
509
+ /**
510
+ * 导航到相邻功能点
511
+ * @param {number} direction - 方向 (-1=上一个, 1=下一个)
512
+ */
513
+ navigateFeature(direction) {
514
+ if (this.filteredFeatures.length === 0) return;
515
+
516
+ let newIndex = this.currentIndex + direction;
517
+
518
+ // 循环导航
519
+ if (newIndex < 0) newIndex = this.filteredFeatures.length - 1;
520
+ if (newIndex >= this.filteredFeatures.length) newIndex = 0;
521
+
522
+ const nextFeature = this.filteredFeatures[newIndex];
523
+ if (nextFeature) {
524
+ this.showFeatureDetail(nextFeature.id);
525
+ }
526
+ },
527
+
528
+ /**
529
+ * 渲染详情视图完整HTML
530
+ * @param {Object} feature - 功能点数据
531
+ * @returns {string} HTML
532
+ */
533
+ _renderDetailView(feature) {
534
+ const stageConfig = this.stages.find(s => s.id === feature.stage) || this.stages[0];
535
+ const score = feature.quality?.score || 0;
536
+ const grade = Charts.getGrade(score);
537
+
538
+ return `
539
+ <div class="feature-detail-modal">
540
+ <!-- 导航栏 -->
541
+ <div class="detail-nav">
542
+ <button class="nav-btn nav-prev" onclick="KanbanView.navigateFeature(-1)" title="上一个 (←)">
543
+
544
+ </button>
545
+ <div class="nav-info">
546
+ <span class="nav-counter">${this.currentIndex + 1} / ${this.filteredFeatures.length}</span>
547
+ <span class="nav-feature-name">${feature.name}</span>
548
+ </div>
549
+ <button class="nav-btn nav-next" onclick="KanbanView.navigateFeature(1)" title="下一个 (→)">
550
+
551
+ </button>
552
+ <button class="nav-btn nav-close" onclick="App.closeModal(); KanbanView.currentFeatureId = null;" title="关闭 (ESC)">
553
+
554
+ </button>
555
+ </div>
556
+
557
+ <!-- 内容区域 -->
558
+ <div class="detail-content">
559
+ <!-- Tab导航 -->
560
+ <div class="detail-tabs">
561
+ <button class="detail-tab active" data-tab="overview">概览</button>
562
+ <button class="detail-tab" data-tab="timeline">时间线</button>
563
+ <button class="detail-tab" data-tab="spec">规格信息</button>
564
+ <button class="detail-tab" data-tab="code">代码统计</button>
565
+ <button class="detail-tab" data-tab="quality">验证结果</button>
566
+ <button class="detail-tab" data-tab="issues">问题列表</button>
567
+ <button class="detail-tab" data-tab="tokens">Token消耗</button>
568
+ <button class="detail-tab" data-tab="iterations">迭代历史</button>
569
+ </div>
570
+
571
+ <!-- Tab内容 -->
572
+ <div class="detail-panels">
573
+ ${this._renderOverviewPanel(feature, stageConfig, grade)}
574
+ ${this._renderTimelinePanel(feature)}
575
+ ${this._renderSpecPanel(feature)}
576
+ ${this._renderCodePanel(feature)}
577
+ ${this._renderQualityPanel(feature)}
578
+ ${this._renderIssuesPanel(feature)}
579
+ ${this._renderTokenPanel(feature)}
580
+ ${this._renderIterationPanel(feature)}
581
+ </div>
582
+ </div>
583
+ </div>
584
+ `;
585
+ },
586
+
587
+ /**
588
+ * 渲染概览面板
589
+ */
590
+ _renderOverviewPanel(feature, stageConfig, grade) {
591
+ return `
592
+ <div class="detail-panel active" data-panel="overview">
593
+ <div class="panel-grid">
594
+ <div class="info-section">
595
+ <h4>基本信息</h4>
596
+ <dl class="info-list">
597
+ <dt>ID</dt><dd>${feature.id}</dd>
598
+ <dt>名称</dt><dd><strong>${feature.name}</strong></dd>
599
+ <dt>当前阶段</dt>
600
+ <dd><span class="badge" style="background-color: ${stageConfig.color}20; color: ${stageConfig.color}">${stageConfig.icon} ${stageConfig.name}</span></dd>
601
+ <dt>优先级</dt><dd>${feature.priority || '-'}</dd>
602
+ <dt>评分/等级</dt>
603
+ <dd><span style="color: ${Charts.getColor(feature.quality?.score || 0)}; font-weight: bold;">${(feature.quality?.score || 0).toFixed(1)} (${grade})</span></dd>
604
+ </dl>
605
+ </div>
606
+
607
+ <div class="info-section">
608
+ <h4>描述</h4>
609
+ <p class="description-text">${feature.description || '暂无描述'}</p>
610
+
611
+ <h4 style="margin-top: 16px;">标签</h4>
612
+ <div class="tags-container">
613
+ ${(feature.tags || []).map(tag => `<span class="tag">${tag}</span>`).join('') || '<span class="text-muted">无标签</span>'}
614
+ </div>
615
+ </div>
616
+
617
+ <div class="info-section">
618
+ <h4>时间信息</h4>
619
+ <dl class="info-list">
620
+ <dt>创建时间</dt><dd>${this._formatDateTime(feature.createdAt)}</dd>
621
+ <dt>更新时间</dt><dd>${this._formatDateTime(feature.updatedAt)}</dd>
622
+ </dl>
623
+ </div>
624
+ </div>
625
+ </div>
626
+ `;
627
+ },
628
+
629
+ /**
630
+ * 渲染时间线面板
631
+ */
632
+ _renderTimelinePanel(feature) {
633
+ const timeline = feature.timeline || [];
634
+
635
+ if (timeline.length === 0) {
636
+ return `
637
+ <div class="detail-panel" data-panel="timeline">
638
+ <div class="empty-panel">暂无时间线记录</div>
639
+ </div>
640
+ `;
641
+ }
642
+
643
+ return `
644
+ <div class="detail-panel" data-panel="timeline">
645
+ <div class="vertical-timeline">
646
+ ${timeline.map((entry, index) => {
647
+ const fromStage = this.stages.find(s => s.id === entry.fromStage);
648
+ const toStage = this.stages.find(s => s.id === entry.toStage);
649
+ const isLatest = index === 0;
650
+
651
+ return `
652
+ <div class="vt-item ${isLatest ? 'latest' : ''}">
653
+ <div class="vt-dot" style="background-color: ${toStage?.color || '#95a5a6'}"></div>
654
+ <div class="vt-content">
655
+ <div class="vt-header">
656
+ <strong>${entry.type || '阶段变更'}</strong>
657
+ <span class="vt-time">${this._formatDateTime(entry.timestamp || entry.time)}</span>
658
+ </div>
659
+ <div class="vt-body">
660
+ ${fromStage ? `<span class="badge" style="background-color: ${fromStage.color}20; color: ${fromStage.color}">${fromStage.name}</span>` : ''}
661
+
662
+ ${toStage ? `<span class="badge" style="background-color: ${toStage.color}20; color: ${toStage.color}">${toStage.name}</span>` : ''}
663
+ ${entry.description ? `<p class="mt-1 text-muted">${entry.description}</p>` : ''}
664
+ </div>
665
+ </div>
666
+ </div>
667
+ `;
668
+ }).join('')}
669
+ </div>
670
+ </div>
671
+ `;
672
+ },
673
+
674
+ /**
675
+ * 渲染规格信息面板
676
+ */
677
+ _renderSpecPanel(feature) {
678
+ const spec = feature.artifacts?.spec;
679
+
680
+ if (!spec) {
681
+ return `
682
+ <div class="detail-panel" data-panel="spec">
683
+ <div class="empty-panel">暂无规格信息</div>
684
+ </div>
685
+ `;
686
+ }
687
+
688
+ return `
689
+ <div class="detail-panel" data-panel="spec">
690
+ <dl class="info-list">
691
+ <dt>规格文件</dt><dd>${spec.fileName || spec.path || '-'}</dd>
692
+ <dt>模板类型</dt><dd>${spec.templateType || '-'}</dd>
693
+ <dt>生成时间</dt><dd>${this._formatDateTime(spec.generatedAt)}</dd>
694
+ <dt>状态</dt><dd><span class="badge badge-${spec.status || 'unknown'}">${spec.status || '未知'}</span></dd>
695
+ </dl>
696
+
697
+ ${spec.content ? `
698
+ <div class="code-preview">
699
+ <pre><code>${this._escapeHtml(spec.content.substring(0, 2000))}${spec.content.length > 2000 ? '\n...(截断)' : ''}</code></pre>
700
+ </div>
701
+ ` : ''}
702
+ </div>
703
+ `;
704
+ },
705
+
706
+ /**
707
+ * 渲染代码统计面板
708
+ */
709
+ _renderCodePanel(feature) {
710
+ const code = feature.artifacts?.code;
711
+
712
+ if (!code) {
713
+ return `
714
+ <div class="detail-panel" data-panel="code">
715
+ <div class="empty-panel">暂无代码统计</div>
716
+ </div>
717
+ `;
718
+ }
719
+
720
+ return `
721
+ <div class="detail-panel" data-panel="code">
722
+ <div class="stats-grid">
723
+ <div class="stat-box">
724
+ <div class="stat-value">${code.fileCount || 0}</div>
725
+ <div class="stat-label">文件数</div>
726
+ </div>
727
+ <div class="stat-box">
728
+ <div class="stat-value">${this._formatNumber(code.loc || 0)}</div>
729
+ <div class="stat-label">总行数 (LOC)</div>
730
+ </div>
731
+ <div class="stat-box">
732
+ <div class="stat-value">${code.languageCount || Object.keys(code.languages || {}).length}</div>
733
+ <div class="stat-label">语言数</div>
734
+ </div>
735
+ </div>
736
+
737
+ ${code.languages && Object.keys(code.languages).length > 0 ? `
738
+ <h5 class="mt-3">语言分布</h5>
739
+ <div class="language-bars">
740
+ ${Object.entries(code.languages).map(([lang, stats]) => `
741
+ <div class="lang-item">
742
+ <span class="lang-name">${lang}</span>
743
+ <div class="lang-bar">
744
+ <div class="lang-bar-fill" style="width: ${(stats.loc / code.loc * 100)}%; background-color: ${this._getLanguageColor(lang)}"></div>
745
+ </div>
746
+ <span class="lang-stats">${stats.files} 文件 / ${this._formatNumber(stats.loc)} 行</span>
747
+ </div>
748
+ `).join('')}
749
+ </div>
750
+ ` : ''}
751
+ </div>
752
+ `;
753
+ },
754
+
755
+ /**
756
+ * 渲染验证结果面板
757
+ */
758
+ _renderQualityPanel(feature) {
759
+ const quality = feature.quality;
760
+
761
+ if (!quality) {
762
+ return `
763
+ <div class="detail-panel" data-panel="quality">
764
+ <div class="empty-panel">暂无验证结果</div>
765
+ </div>
766
+ `;
767
+ }
768
+
769
+ const score = quality.score || 0;
770
+ const grade = Charts.getGrade(score);
771
+
772
+ return `
773
+ <div class="detail-panel" data-panel="quality">
774
+ <div class="quality-summary">
775
+ <div class="quality-score-display">
776
+ <div class="big-score" style="color: ${Charts.getColor(score)}">${score.toFixed(1)}</div>
777
+ <div class="grade-label badge badge-${grade.toLowerCase()}" style="font-size: 16px;">${grade}</div>
778
+ </div>
779
+ <div class="quality-metrics">
780
+ <div class="metric-item">
781
+ <span class="metric-label">通过率</span>
782
+ <span class="metric-value">${(quality.passRate || 0).toFixed(1)}%</span>
783
+ </div>
784
+ <div class="metric-item">
785
+ <span class="metric-label">问题数</span>
786
+ <span class="metric-value">${(quality.issues || []).length}</span>
787
+ </div>
788
+ <div class="metric-item">
789
+ <span class="metric-label">测试覆盖</span>
790
+ <span class="metric-value">${(quality.coverage || 0).toFixed(1)}%</span>
791
+ </div>
792
+ </div>
793
+ </div>
794
+
795
+ ${quality.dimensions && Object.keys(quality.dimensions).length > 0 ? `
796
+ <h5 class="mt-3">维度得分</h5>
797
+ <div class="dimension-scores">
798
+ ${Object.entries(quality.dimensions).map(([dim, val]) => `
799
+ <div class="dim-item">
800
+ <span class="dim-name">${dim}</span>
801
+ <div class="dim-bar">
802
+ <div class="dim-bar-fill" style="width: ${val}%; background-color: ${Charts.getColor(val)}"></div>
803
+ </div>
804
+ <span class="dim-value">${val}</span>
805
+ </div>
806
+ `).join('')}
807
+ </div>
808
+ ` : ''}
809
+ </div>
810
+ `;
811
+ },
812
+
813
+ /**
814
+ * 渲染问题列表面板
815
+ */
816
+ _renderIssuesPanel(feature) {
817
+ const issues = feature.quality?.issues || [];
818
+
819
+ if (issues.length === 0) {
820
+ return `
821
+ <div class="detail-panel" data-panel="issues">
822
+ <div class="empty-panel success">✅ 未发现问题</div>
823
+ </div>
824
+ `;
825
+ }
826
+
827
+ return `
828
+ <div class="detail-panel" data-panel="issues">
829
+ <table class="data-table issues-table">
830
+ <thead>
831
+ <tr>
832
+ <th>类型</th>
833
+ <th>描述</th>
834
+ <th>严重程度</th>
835
+ <th>状态</th>
836
+ </tr>
837
+ </thead>
838
+ <tbody>
839
+ ${issues.map(issue => `
840
+ <tr>
841
+ <td><span class="badge badge-outline">${issue.type || issue.category || '-'}</span></td>
842
+ <td>${issue.description || issue.message || '-'}</td>
843
+ <td>
844
+ <span class="severity-${issue.severity || 'low'}">${this._capitalize(issue.severity || 'low')}</span>
845
+ </td>
846
+ <td><span class="status-dot status-${issue.status || 'open'}"></span> ${issue.status || 'open'}</td>
847
+ </tr>
848
+ `).join('')}
849
+ </tbody>
850
+ </table>
851
+ </div>
852
+ `;
853
+ },
854
+
855
+ /**
856
+ * 渲染Token消耗面板
857
+ */
858
+ _renderTokenPanel(feature) {
859
+ const tokens = feature.tokens;
860
+
861
+ if (!tokens) {
862
+ return `
863
+ <div class="detail-panel" data-panel="tokens">
864
+ <div class="empty-panel">暂无Token数据</div>
865
+ </div>
866
+ `;
867
+ }
868
+
869
+ const total = tokens.total || 0;
870
+ const stages = tokens.byStage || {};
871
+
872
+ return `
873
+ <div class="detail-panel" data-panel="tokens">
874
+ <div class="token-summary">
875
+ <div class="token-total">
876
+ <span class="token-big-number">${this._formatNumber(total)}</span>
877
+ <span class="token-unit">Tokens</span>
878
+ </div>
879
+ </div>
880
+
881
+ ${Object.keys(stages).length > 0 ? `
882
+ <h5 class="mt-3">各阶段消耗</h5>
883
+ <div class="token-breakdown">
884
+ ${Object.entries(stages).map(([stage, amount]) => {
885
+ const stageConfig = this.stages.find(s => s.id === stage);
886
+ const percent = total > 0 ? ((amount / total) * 100).toFixed(1) : 0;
887
+
888
+ return `
889
+ <div class="token-stage-item">
890
+ <span class="ts-name" style="color: ${stageConfig?.color || '#95a5a6'}">
891
+ ${stageConfig?.icon || ''} ${stage.toUpperCase()}
892
+ </span>
893
+ <div class="ts-bar">
894
+ <div class="ts-bar-fill" style="width: ${percent}%; background-color: ${stageConfig?.color || '#95a5a6'}"></div>
895
+ </div>
896
+ <span class="ts-value">${this._formatNumber(amount)} (${percent}%)</span>
897
+ </div>
898
+ `;
899
+ }).join('')}
900
+ </div>
901
+ ` : ''}
902
+ </div>
903
+ `;
904
+ },
905
+
906
+ /**
907
+ * 渲染迭代历史面板
908
+ */
909
+ _renderIterationPanel(feature) {
910
+ const iterations = feature.iterations || [];
911
+
912
+ if (iterations.length === 0) {
913
+ return `
914
+ <div class="detail-panel" data-panel="iterations">
915
+ <div class="empty-panel">暂无迭代记录</div>
916
+ </div>
917
+ `;
918
+ }
919
+
920
+ return `
921
+ <div class="detail-panel" data-panel="iterations">
922
+ <table class="data-table iteration-table">
923
+ <thead>
924
+ <tr>
925
+ <th>轮次</th>
926
+ <th>评分</th>
927
+ <th>修复问题</th>
928
+ <th>Token消耗</th>
929
+ <th>时间</th>
930
+ </tr>
931
+ </thead>
932
+ <tbody>
933
+ ${iterations.map((iter, index) => `
934
+ <tr class="${index === iterations.length - 1 ? 'latest-iteration' : ''}">
935
+ <td><strong>R${index + 1}</strong></td>
936
+ <td>
937
+ <span style="color: ${Charts.getColor(iter.score || 0)}; font-weight: bold;">
938
+ ${(iter.score || 0).toFixed(1)}
939
+ </span>
940
+ <span class="badge badge-${Charts.getGrade(iter.score || 0).toLowerCase()} ml-1">
941
+ ${Charts.getGrade(iter.score || 0)}
942
+ </span>
943
+ </td>
944
+ <td>${iter.fixedIssues || iter.issuesFixed || 0}</td>
945
+ <td>${this._formatNumber(iter.tokens || 0)}</td>
946
+ <td>${this._formatDateTime(iter.timestamp || iter.time)}</td>
947
+ </tr>
948
+ `).join('')}
949
+ </tbody>
950
+ </table>
951
+ </div>
952
+ `;
953
+ },
954
+
955
+ /**
956
+ * 绑定导航和Tab事件
957
+ */
958
+ _bindNavigationEvents() {
959
+ // Tab切换
960
+ document.querySelectorAll('.detail-tab').forEach(tab => {
961
+ tab.addEventListener('click', () => {
962
+ // 移除所有active
963
+ document.querySelectorAll('.detail-tab').forEach(t => t.classList.remove('active'));
964
+ document.querySelectorAll('.detail-panel').forEach(p => p.classList.remove('active'));
965
+
966
+ // 添加active
967
+ tab.classList.add('active');
968
+ const panelName = tab.dataset.tab;
969
+ const panel = document.querySelector(`.detail-panel[data-panel="${panelName}"]`);
970
+ if (panel) panel.classList.add('active');
971
+ });
972
+ });
973
+ },
974
+
975
+ // ==================== 工具方法 ====================
976
+
977
+ /**
978
+ * 格式化数字
979
+ */
980
+ _formatNumber(num) {
981
+ if (!num) return '0';
982
+ return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
983
+ },
984
+
985
+ /**
986
+ * 截断文本
987
+ */
988
+ _truncate(text, maxLen = 50) {
989
+ if (!text) return '';
990
+ return text.length > maxLen ? text.substring(0, maxLen) + '...' : text;
991
+ },
992
+
993
+ /**
994
+ * 格式化日期
995
+ */
996
+ _formatDate(dateStr) {
997
+ if (!dateStr) return '-';
998
+ try {
999
+ return new Date(dateStr).toLocaleDateString('zh-CN');
1000
+ } catch (e) {
1001
+ return dateStr;
1002
+ }
1003
+ },
1004
+
1005
+ /**
1006
+ * 格式化日期时间
1007
+ */
1008
+ _formatDateTime(dateStr) {
1009
+ if (!dateStr) return '-';
1010
+ try {
1011
+ return new Date(dateStr).toLocaleString('zh-CN');
1012
+ } catch (e) {
1013
+ return dateStr;
1014
+ }
1015
+ },
1016
+
1017
+ /**
1018
+ * 首字母大写
1019
+ */
1020
+ _capitalize(str) {
1021
+ if (!str) return '';
1022
+ return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
1023
+ },
1024
+
1025
+ /**
1026
+ * HTML转义
1027
+ */
1028
+ _escapeHtml(text) {
1029
+ if (!text) return '';
1030
+ const div = document.createElement('div');
1031
+ div.textContent = text;
1032
+ return div.innerHTML;
1033
+ },
1034
+
1035
+ /**
1036
+ * 获取语言颜色
1037
+ */
1038
+ _getLanguageColor(lang) {
1039
+ const colors = {
1040
+ javascript: '#f7df1e', typescript: '#3178c6', python: '#3776ab',
1041
+ java: '#007396', go: '#00add8', rust: '#dea584',
1042
+ cpp: '#00599c', c: '#a8b9cc', ruby: '#cc342d',
1043
+ php: '#777bb4', swift: '#f05138', kotlin: '#7f52ff'
1044
+ };
1045
+ return colors[lang.toLowerCase()] || '#95a5a6';
1046
+ }
1047
+ };
1048
+
1049
+ // 初始化
1050
+ KanbanView.init();
1051
+
1052
+ // 导出全局使用
1053
+ window.KanbanView = KanbanView;