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,339 @@
1
+ /**
2
+ * PDD Visual Manager - Card 卡片组件 (VM-C014)
3
+ *
4
+ * 提供带边框的面板容器:
5
+ * - InfoCard: 键值对信息卡片
6
+ * - 双卡片并排布局
7
+ * - 可自定义边框风格和颜色
8
+ * - 支持标题、页脚
9
+ * - 响应式宽度适配
10
+ *
11
+ * 使用 Unicode 圆角/直角边框字符绘制。
12
+ */
13
+
14
+ import { ANSI, BORDER_CHARS } from '../renderer.js';
15
+
16
+ /**
17
+ * Card - 通用卡片组件
18
+ *
19
+ * 带边框的内容容器,可用于组织 UI 布局中的各个区块。
20
+ */
21
+ class Card {
22
+ /**
23
+ * 创建卡片实例
24
+ * @param {string} title - 卡片标题
25
+ * @param {string|string[]} content - 内容(字符串或行数组)
26
+ * @param {Object} options - 配置选项
27
+ */
28
+ constructor(title = '', content = '', options = {}) {
29
+ /** @type {string} 标题 */
30
+ this.title = title;
31
+
32
+ /** @type {string[]} 内容行数组 */
33
+ this.contentLines = typeof content === 'string'
34
+ ? content.split('\n')
35
+ : (Array.isArray(content) ? content : ['']);
36
+
37
+ /** @type {Object} 选项 */
38
+ this.options = {
39
+ width: 0, // 宽度 (0=自适应)
40
+ borderColor: null, // 边框颜色
41
+ titleColor: ANSI.BOLD + ANSI.CYAN, // 标题颜色
42
+ footer: '', // 页脚文字
43
+ borderStyle: 'rounded', // 边框风格 ('single'|'double'|'rounded')
44
+ padding: { x: 2, y: 1 }, // 内边距
45
+ ...options
46
+ };
47
+ }
48
+
49
+ /**
50
+ * 渲染卡片
51
+ * @returns {string} 格式化的卡片字符串
52
+ */
53
+ render() {
54
+ const opts = this.options;
55
+ const c = BORDER_CHARS[opts.borderStyle] || BORDER_CHARS.rounded;
56
+
57
+ // 确定实际宽度
58
+ const contentWidth = this._calcContentWidth();
59
+ const totalWidth = contentWidth + opts.padding.x * 2;
60
+ const innerWidth = totalWidth - 2; // 减去两边边框
61
+
62
+ const lines = [];
63
+
64
+ // 顶边框(可能包含标题)
65
+ lines.push(this._renderTopBorder(c, totalWidth));
66
+
67
+ // 上内边距
68
+ for (let i = 0; i < opts.padding.y; i++) {
69
+ lines.push(`${c.v}${' '.repeat(innerWidth)}${c.v}`);
70
+ }
71
+
72
+ // 内容行
73
+ for (const rawLine of this.contentLines) {
74
+ const padded = this._padLine(rawLine, innerWidth);
75
+ lines.push(`${c.v} ${padded} ${c.v}`);
76
+ }
77
+
78
+ // 下内边距
79
+ for (let i = 0; i < opts.padding.y; i++) {
80
+ lines.push(`${c.v}${' '.repeat(innerWidth)}${c.v}`);
81
+ }
82
+
83
+ // 页脚(如果存在)
84
+ if (opts.footer) {
85
+ const footerPadded = this._padLine(opts.footer, innerWidth);
86
+ lines.push(`${c.v} ${ANSI.DIM}${footerPadded}${ANSI.RESET} ${c.v}`);
87
+ }
88
+
89
+ // 底边框
90
+ lines.push(`${c.bl}${c.h.repeat(innerWidth)}${c.br}`);
91
+
92
+ return lines.join('\n');
93
+ }
94
+
95
+ /**
96
+ * 渲染顶边框(带标题)
97
+ * @param {Object} c - 边框字符
98
+ * @param {number} totalWidth - 总宽度
99
+ * @returns {string}
100
+ * @private
101
+ */
102
+ _renderTopBorder(c, totalWidth) {
103
+ const innerWidth = totalWidth - 2;
104
+
105
+ if (!this.title) {
106
+ return `${c.tl}${c.h.repeat(innerWidth)}${c.tr}`;
107
+ }
108
+
109
+ // 标题格式: "── title ──"
110
+ const titleStr = ` ${this.title} `;
111
+ const titleWidth = this._strWidth(titleStr);
112
+
113
+ if (titleWidth >= innerWidth) {
114
+ // 标题太长,截断
115
+ const truncated = this._truncate(this.title, innerWidth - 4);
116
+ return `${c.tl}─ ${truncated} ─${c.tr}`;
117
+ }
118
+
119
+ const before = Math.floor((innerWidth - titleWidth) / 2);
120
+ const after = innerWidth - titleWidth - before;
121
+
122
+ const titleColored = `${this.options.titleColor}${titleStr}${ANSI.RESET}`;
123
+ return `${c.tl}${c.h.repeat(before)}${titleColored}${c.h.repeat(after)}${c.tr}`;
124
+ }
125
+
126
+ /**
127
+ * 计算内容所需宽度
128
+ * @returns {number}
129
+ * @private
130
+ */
131
+ _calcContentWidth() {
132
+ if (this.options.width > 0) {
133
+ return this.options.width;
134
+ }
135
+
136
+ let maxW = 0;
137
+ for (const line of this.contentLines) {
138
+ const w = this._strWidth(line);
139
+ if (w > maxW) maxW = w;
140
+ }
141
+ if (this.title) {
142
+ const titleW = this._strWidth(this.title) + 2; // 加上两边空格
143
+ if (titleW > maxW) maxW = titleW;
144
+ }
145
+ if (this.options.footer) {
146
+ const footerW = this._strWidth(this.options.footer);
147
+ if (footerW > maxW) maxW = footerW;
148
+ }
149
+
150
+ return Math.max(maxW + 2, 10); // 最小宽度 10
151
+ }
152
+
153
+ /**
154
+ * 填充行到指定宽度
155
+ * @param {string} line - 原始行
156
+ * @param {number} width - 目标宽度
157
+ * @returns {string}
158
+ * @private
159
+ */
160
+ _padLine(line, width) {
161
+ const lineW = this._strWidth(line);
162
+ if (lineW >= width) {
163
+ return this._truncate(line, width);
164
+ }
165
+ return line + ' '.repeat(width - lineW);
166
+ }
167
+
168
+ /**
169
+ * 截断文本
170
+ * @param {string} text - 文本
171
+ * @param {number} maxLen - 最大宽度
172
+ * @returns {string}
173
+ * @private
174
+ */
175
+ _truncate(text, maxLen) {
176
+ if (this._strWidth(text) <= maxLen) return text;
177
+ let result = '';
178
+ let w = 0;
179
+ for (const ch of text) {
180
+ const cw = this._strWidth(ch);
181
+ if (w + cw > maxLen - 1) break;
182
+ result += ch;
183
+ w += cw;
184
+ }
185
+ return result + '…';
186
+ }
187
+
188
+ /**
189
+ * 计算字符串显示宽度
190
+ * @param {string} text
191
+ * @returns {number}
192
+ * @private
193
+ */
194
+ _strWidth(text) {
195
+ let w = 0;
196
+ for (const ch of String(text)) {
197
+ w += ch.charCodeAt(0) > 0xff ? 2 : 1;
198
+ }
199
+ return w;
200
+ }
201
+
202
+ // ============================================================
203
+ // 静态工厂方法
204
+ // ============================================================
205
+
206
+ /**
207
+ * 创建信息卡片(键值对列表)
208
+ * @param {string} title - 标题
209
+ * @param {Array<{label: string, value: string, color?: string}>} items - 键值对数组
210
+ * @param {Object} cardOpts - 卡片选项
211
+ * @returns {string} 渲染后的卡片
212
+ */
213
+ static info(title, items = [], cardOpts = {}) {
214
+ const lines = items.map(item => {
215
+ const valueColor = item.color || '';
216
+ const valueSuffix = item.value;
217
+ const formatted = valueColor
218
+ ? `${item.label}: ${valueColor}${valueSuffix}${ANSI.RESET}`
219
+ : `${item.label}: ${valueSuffix}`;
220
+ return formatted;
221
+ });
222
+
223
+ const card = new Card(title, lines, {
224
+ borderStyle: 'rounded',
225
+ ...cardOpts
226
+ });
227
+ return card.render();
228
+ }
229
+
230
+ /**
231
+ * 创建双卡片并排布局
232
+ * @param {Card} leftCard - 左侧卡片
233
+ * @param {Card} rightCard - 右侧卡片
234
+ * @param {number} spacing - 间距
235
+ * @returns {string} 并排的两个卡片
236
+ */
237
+ static sideBySide(leftCard, rightCard, spacing = 4) {
238
+ const leftLines = leftCard.render().split('\n');
239
+ const rightLines = rightCard.render().split('\n');
240
+
241
+ const maxRows = Math.max(leftLines.length, rightLines.length);
242
+ const spacer = ' '.repeat(spacing);
243
+ const result = [];
244
+
245
+ for (let i = 0; i < maxRows; i++) {
246
+ const left = i < leftLines.length ? leftLines[i] : ' '.repeat(leftLines[0]?.length || 0);
247
+ const right = i < rightLines.length ? rightLines[i] : ' '.repeat(rightLines[0]?.length || 0);
248
+ result.push(`${left}${spacer}${right}`);
249
+ }
250
+
251
+ return result.join('\n');
252
+ }
253
+
254
+ /**
255
+ * 创建三卡片横向布局
256
+ * @param {Card[]} cards - 卡片数组 (最多3个)
257
+ * @param {number} spacing - 间距
258
+ * @returns {string}
259
+ */
260
+ static triple(cards, spacing = 2) {
261
+ if (!cards || cards.length === 0) return '';
262
+
263
+ const renderedCards = cards.map(c => c.render().split('\n'));
264
+ const maxRows = Math.max(...renderedCards.map(lines => lines.length));
265
+ const result = [];
266
+
267
+ for (let row = 0; row < maxRows; row++) {
268
+ const parts = renderedCards.map((lines, idx) => {
269
+ const line = row < lines.length ? lines[row] : '';
270
+ // 补齐宽度
271
+ const refWidth = lines[0] ? lines[0].length : 0;
272
+ return line.padEnd(refWidth);
273
+ });
274
+ result.push(parts.join(' '.repeat(spacing)));
275
+ }
276
+
277
+ return result.join('\n');
278
+ }
279
+
280
+ /**
281
+ * 创建指标卡片(大数字+标签)
282
+ * @param {string} label - 标签
283
+ * @param {string|number} value - 值
284
+ * @param {string} unit - 单位
285
+ * @param {Object} opts - 选项
286
+ * @returns {string}
287
+ */
288
+ static metric(label, value, unit = '', opts = {}) {
289
+ const valueColor = opts.valueColor || ANSI.BOLD + ANSI.CYAN;
290
+ const valueStr = `${valueColor}${value}${ANSI.RESET}${unit ? ' ' + unit : ''}`;
291
+
292
+ const content = [
293
+ '',
294
+ ` ${valueStr}`,
295
+ ` ${ANSI.DIM}${label}${ANSI.RESET}`,
296
+ ''
297
+ ];
298
+
299
+ const card = new Card(opts.title || '', content, {
300
+ borderStyle: 'rounded',
301
+ titleColor: opts.titleColor || ANSI.DIM,
302
+ width: opts.width || 0,
303
+ ...opts
304
+ });
305
+ return card.render();
306
+ }
307
+
308
+ /**
309
+ * 创建警告/错误提示卡片
310
+ * @param {string} message - 消息内容
311
+ * @param {string} type - 类型 ('warn'|'error'|'info')
312
+ * @param {Object} opts - 选项
313
+ * @returns {string}
314
+ */
315
+ static alert(message, type = 'info', opts = {}) {
316
+ const typeConfig = {
317
+ warn: { icon: '⚠', color: ANSI.BRIGHT_YELLOW, title: '警告' },
318
+ error: { icon: '✖', color: ANSI.BRIGHT_RED, title: '错误' },
319
+ info: { icon: 'ℹ', color: ANSI.BLUE, title: '信息' }
320
+ };
321
+
322
+ const cfg = typeConfig[type] || typeConfig.info;
323
+ const content = [
324
+ '',
325
+ ` ${cfg.color}${cfg.icon} ${message}${ANSI.RESET}`,
326
+ ''
327
+ ];
328
+
329
+ const card = new Card(cfg.title, content, {
330
+ borderStyle: 'single',
331
+ titleColor: cfg.color,
332
+ borderColor: cfg.color,
333
+ ...opts
334
+ });
335
+ return card.render();
336
+ }
337
+ }
338
+
339
+ export default Card;
@@ -0,0 +1,368 @@
1
+ /**
2
+ * PDD Visual Manager - 进度条组件 (VM-C010)
3
+ *
4
+ * 提供多种风格的进度条渲染:
5
+ * - 水平进度条(默认)
6
+ * - 分段进度条
7
+ * - 环形进度条(ASCII艺术)
8
+ * - 带颜色渐变的智能进度条
9
+ * - 支持动画效果标记
10
+ *
11
+ * 纯 Unicode 字符绘制,零依赖。
12
+ */
13
+
14
+ import { ANSI } from '../renderer.js';
15
+
16
+ /**
17
+ * 进度条填充字符集
18
+ */
19
+ const BAR_CHARS = {
20
+ filled: '█',
21
+ empty: '░',
22
+ partial: ['▏', '▎', '▍', '▌', '▋', '▊', '▉', '█'],
23
+ thinFilled: '▓',
24
+ thinEmpty: '░'
25
+ };
26
+
27
+ /**
28
+ * 根据百分比获取颜色
29
+ * @param {number} pct - 百分比 (0-100)
30
+ * @returns {string} ANSI 颜色码
31
+ */
32
+ function getColorForPercent(pct) {
33
+ if (pct >= 90) return ANSI.BRIGHT_GREEN;
34
+ if (pct >= 70) return ANSI.GREEN;
35
+ if (pct >= 50) return ANSI.BRIGHT_YELLOW;
36
+ if (pct >= 30) return ANSI.YELLOW;
37
+ if (pct >= 15) return ANSI.BRIGHT_RED;
38
+ return ANSI.RED;
39
+ }
40
+
41
+ /**
42
+ * 获取等级颜色
43
+ * @param {string} grade - 等级 (S/A/B/C/D/F)
44
+ * @returns {string}
45
+ */
46
+ function getGradeColor(grade) {
47
+ const colors = {
48
+ 'S': ANSI.BRIGHT_GREEN,
49
+ 'A': ANSI.GREEN,
50
+ 'B': ANSI.BRIGHT_YELLOW,
51
+ 'C': ANSI.YELLOW,
52
+ 'D': ANSI.BRIGHT_RED,
53
+ 'F': ANSI.RED
54
+ };
55
+ return colors[grade] || ANSI.DIM;
56
+ }
57
+
58
+ /**
59
+ * ProgressBar - 进度条组件
60
+ *
61
+ * 支持多种显示风格和配置选项的通用进度条。
62
+ */
63
+ class ProgressBar {
64
+ /**
65
+ * 创建进度条实例
66
+ * @param {number} value - 当前值
67
+ * @param {number} max - 最大值
68
+ * @param {number} width - 进度条字符宽度
69
+ * @param {Object} options - 配置选项
70
+ */
71
+ constructor(value = 0, max = 100, width = 20, options = {}) {
72
+ /** @type {number} 当前值 */
73
+ this.value = value;
74
+
75
+ /** @type {number} 最大值 */
76
+ this.max = max;
77
+
78
+ /** @type {number} 显示宽度 */
79
+ this.width = Math.max(1, width);
80
+
81
+ /** @type {Object} 选项 */
82
+ this.options = {
83
+ style: 'horizontal', // horizontal | segmented | ring
84
+ filledChar: '█',
85
+ emptyChar: '░',
86
+ showLabel: true,
87
+ showPercent: true,
88
+ label: '',
89
+ color: null, // 自定义颜色,null 表示自动
90
+ animate: false,
91
+ ...options
92
+ };
93
+ }
94
+
95
+ /**
96
+ * 计算当前百分比
97
+ * @returns {number}
98
+ */
99
+ get percent() {
100
+ if (this.max <= 0) return 0;
101
+ return Math.min(100, Math.max(0, Math.round((this.value / this.max) * 100)));
102
+ }
103
+
104
+ /**
105
+ * 更新进度值
106
+ * @param {number} newValue - 新值
107
+ * @returns {ProgressBar} this(链式调用)
108
+ */
109
+ update(newValue) {
110
+ this.value = newValue;
111
+ return this;
112
+ }
113
+
114
+ /**
115
+ * 渲染水平进度条
116
+ * @returns {string}
117
+ */
118
+ render() {
119
+ switch (this.options.style) {
120
+ case 'segmented':
121
+ return this._renderSegmented();
122
+ case 'ring':
123
+ return this._renderRing();
124
+ case 'vertical':
125
+ return this._renderVertical();
126
+ default:
127
+ return this._renderHorizontal();
128
+ }
129
+ }
130
+
131
+ /**
132
+ * 渲染标准水平进度条
133
+ * @returns {string}
134
+ * @private
135
+ */
136
+ _renderHorizontal() {
137
+ const pct = this.percent;
138
+ const w = this.width;
139
+ const opts = this.options;
140
+
141
+ // 选择颜色
142
+ const color = opts.color || getColorForPercent(pct);
143
+
144
+ // 计算填充长度
145
+ const exactFilled = (pct / 100) * w;
146
+ const filled = Math.floor(exactFilled);
147
+ const partial = exactFilled - filled;
148
+
149
+ // 构建进度条
150
+ let bar = opts.filledChar.repeat(filled);
151
+
152
+ // 部分填充字符
153
+ if (filled < w && partial > 0) {
154
+ const partialIndex = Math.floor(partial * 8);
155
+ bar += BAR_CHARS.partial[Math.min(partialIndex, 7)];
156
+ } else if (filled < w) {
157
+ // 无部分填充,直接接空字符
158
+ }
159
+
160
+ // 填充剩余空位
161
+ const remaining = w - this._displayWidth(bar);
162
+ if (remaining > 0) {
163
+ bar += opts.emptyChar.repeat(remaining);
164
+ }
165
+
166
+ // 截断到目标宽度
167
+ bar = bar.substring(0, w);
168
+
169
+ // 应用颜色
170
+ const coloredBar = `${color}${bar}${ANSI.RESET}`;
171
+
172
+ // 组装最终输出
173
+ let result = '';
174
+ if (opts.label && opts.showLabel) {
175
+ result += `${opts.label} `;
176
+ }
177
+ result += coloredBar;
178
+
179
+ if (opts.showPercent !== false) {
180
+ result += ` ${pct}%`;
181
+ }
182
+
183
+ return result;
184
+ }
185
+
186
+ /**
187
+ * 渲染分段进度条
188
+ * 用于多段式进度显示,如 S/A/B/C/D/F 分布
189
+ * @param {Array<{value:number,label:string,color?:string}>} segments - 段定义
190
+ * @returns {string}
191
+ */
192
+ renderSegmented(segments) {
193
+ if (!segments || segments.length === 0) {
194
+ return this.render(); // 回退到默认渲染
195
+ }
196
+
197
+ const total = segments.reduce((sum, s) => sum + s.value, 0);
198
+ const w = this.width;
199
+
200
+ let result = '';
201
+ let remainingWidth = w;
202
+
203
+ for (let i = 0; i < segments.length; i++) {
204
+ const seg = segments[i];
205
+ const segPct = total > 0 ? (seg.value / total) : 0;
206
+ const segWidth = Math.max(1, Math.round(segPct * w));
207
+ const clampedWidth = Math.min(segWidth, remainingWidth);
208
+
209
+ if (clampedWidth <= 0) continue;
210
+
211
+ const color = seg.color || getColorForPercent(segPct * 100);
212
+ const char = this.options.filledChar.repeat(clampedWidth);
213
+ result += `${color}${char}${ANSI.RESET}`;
214
+
215
+ // 段标签(如果空间足够)
216
+ if (seg.label && clampedWidth >= 3) {
217
+ const label = seg.label.substring(0, clampedWidth);
218
+ result = result.substring(0, result.length - clampedWidth);
219
+ result += `${color}${this._centerText(label, clampedWidth)}${ANSI.RESET}`;
220
+ }
221
+
222
+ remainingWidth -= clampedWidth;
223
+ }
224
+
225
+ // 填充剩余空间
226
+ if (remainingWidth > 0) {
227
+ result += this.options.emptyChar.repeat(remainingWidth);
228
+ }
229
+
230
+ return result;
231
+ }
232
+
233
+ /**
234
+ * 内部分段渲染方法
235
+ * @returns {string}
236
+ * @private
237
+ */
238
+ _renderSegmented() {
239
+ // 默认使用百分比分段
240
+ const pct = this.percent;
241
+ const segments = [];
242
+
243
+ if (pct >= 80) segments.push({ value: pct - 80, color: ANSI.BRIGHT_GREEN });
244
+ if (pct >= 60) segments.push({ value: Math.min(pct, 80) - 60, color: ANSI.GREEN });
245
+ if (pct >= 40) segments.push({ value: Math.min(pct, 60) - 40, color: ANSI.YELLOW });
246
+ if (pct >= 20) segments.push({ value: Math.min(pct, 40) - 20, color: ANSI.BRIGHT_RED });
247
+ if (pct > 0) segments.push({ value: Math.min(pct, 20), color: ANSI.RED });
248
+
249
+ if (segments.length === 0) {
250
+ segments.push({ value: 100 - pct, color: ANSI.DIM });
251
+ }
252
+
253
+ return this.renderSegmented(segments);
254
+ }
255
+
256
+ /**
257
+ * 渲染环形/圆形进度指示器(ASCII 艺术)
258
+ * 使用 Unicode 字符模拟环形效果
259
+ * @returns {string}
260
+ * @private
261
+ */
262
+ _renderRing() {
263
+ const pct = this.percent;
264
+ const color = this.options.color || getColorForPercent(pct);
265
+
266
+ // 简化的环形表示:使用括号和填充
267
+ // 格式: [██████░░] 75%
268
+ const ringChars = '╭─╮││╰─╯';
269
+ const innerBar = this._renderHorizontal();
270
+
271
+ // 多行环形显示
272
+ const lines = [
273
+ ` ${ringChars[0]}${'─'.repeat(this.width + 2)}${ringChars[1]}`,
274
+ `${ringChars[3]} ${innerBar} ${ringChars[4]}`,
275
+ ` ${ringChars[5]}${'─'.repeat(this.width + 2)}${ringChars[6]}`
276
+ ];
277
+
278
+ return lines.join('\n');
279
+ }
280
+
281
+ /**
282
+ * 渲染垂直进度条
283
+ * @returns {string}
284
+ * @private
285
+ */
286
+ _renderVertical() {
287
+ const pct = this.percent;
288
+ const h = this.width; // 用 width 作为高度
289
+ const color = this.options.color || getColorForPercent(pct);
290
+ const filled = Math.round((pct / 100) * h);
291
+
292
+ let bar = '';
293
+ for (let i = 0; i < h; i++) {
294
+ if (i < h - filled) {
295
+ bar += `${this.options.emptyChar}\n`;
296
+ } else {
297
+ bar += `${color}${this.options.filledChar}${ANSI.RESET}\n`;
298
+ }
299
+ }
300
+
301
+ if (this.options.showPercent) {
302
+ bar += `${pct}%\n`;
303
+ }
304
+
305
+ return bar.trimEnd();
306
+ }
307
+
308
+ /**
309
+ * 创建等级进度条(用于质量评分)
310
+ * @param {string} grade - 等级 (S/A/B/C/D/F)
311
+ * @param {number} score - 分数
312
+ * @param {number} width - 宽度
313
+ * @returns {string}
314
+ */
315
+ static gradeBar(grade, score, width = 10) {
316
+ const color = getGradeColor(grade);
317
+ const pct = Math.min(100, Math.max(0, score));
318
+ const filled = Math.round((pct / 100) * width);
319
+
320
+ const bar = '█'.repeat(filled) + '░'.repeat(width - filled);
321
+ return `${color}${bar}${ANSI.RESET} ${grade}`;
322
+ }
323
+
324
+ /**
325
+ * 创建迷你进度条(紧凑型)
326
+ * @param {number} pct - 百分比
327
+ * @param {number} width - 宽度
328
+ * @returns {string}
329
+ */
330
+ static mini(pct, width = 8) {
331
+ const pb = new ProgressBar(pct, 100, width, {
332
+ showPercent: false,
333
+ showLabel: false
334
+ });
335
+ return pb.render();
336
+ }
337
+
338
+ /**
339
+ * 计算字符串显示宽度(简化版)
340
+ * @param {string} str
341
+ * @returns {number}
342
+ * @private
343
+ */
344
+ _displayWidth(str) {
345
+ let w = 0;
346
+ for (const ch of str) {
347
+ w += ch.charCodeAt(0) > 0xff ? 2 : 1;
348
+ }
349
+ return w;
350
+ }
351
+
352
+ /**
353
+ * 文本居中
354
+ * @param {string} text
355
+ * @param {number} width
356
+ * @returns {string}
357
+ * @private
358
+ */
359
+ _centerText(text, width) {
360
+ const len = text.length; // 简化:假设 ASCII
361
+ if (len >= width) return text.substring(0, width);
362
+ const pad = width - len;
363
+ return ' '.repeat(Math.floor(pad / 2)) + text + ' '.repeat(Math.ceil(pad / 2));
364
+ }
365
+ }
366
+
367
+ export default ProgressBar;
368
+ export { BAR_CHARS, getColorForPercent, getGradeColor };