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,370 @@
1
+ /**
2
+ * PDD Visual Manager - 表格组件 (VM-C011)
3
+ *
4
+ * 提供灵活的终端表格渲染能力:
5
+ * - 自动列宽计算
6
+ * - 多种对齐方式
7
+ * - 行高亮选择
8
+ * - 固定表头/表尾
9
+ * - 文本自动换行
10
+ * - 多种边框风格
11
+ *
12
+ * 纯 Unicode 绘制,零依赖。
13
+ */
14
+
15
+ import { ANSI, BORDER_CHARS } from '../renderer.js';
16
+
17
+ /** 默认对齐方式映射 */
18
+ const DEFAULT_ALIGN = {
19
+ number: 'right',
20
+ default: 'left'
21
+ };
22
+
23
+ /**
24
+ * Table - 终端表格组件
25
+ *
26
+ * 功能丰富的表格渲染器,适用于数据展示场景。
27
+ */
28
+ class Table {
29
+ /**
30
+ * 创建表格实例
31
+ * @param {string[]} headers - 表头数组
32
+ * @param {Array<Array<string|number>>} rows - 数据行二维数组
33
+ * @param {Object} options - 配置选项
34
+ */
35
+ constructor(headers = [], rows = [], options = {}) {
36
+ /** @type {string[]} 表头 */
37
+ this.headers = headers.map(h => String(h || ''));
38
+
39
+ /** @type {Array<Array<string>>} 数据行(已转为字符串) */
40
+ this.rows = (rows || []).map(row =>
41
+ (row || []).map(cell => String(cell ?? ''))
42
+ );
43
+
44
+ /** @type {Object} 配置选项 */
45
+ this.options = {
46
+ widths: null, // 指定列宽数组
47
+ aligns: [], // 对齐方式数组 ('left'|'right'|'center')
48
+ highlightRow: -1, // 高亮行索引 (-1=无)
49
+ fixedHeader: true, // 固定表头
50
+ fixedFooter: false, // 固定表尾
51
+ maxWidth: 0, // 最大表格宽度 (0=不限制)
52
+ wrapText: false, // 是否换行文本
53
+ borderStyle: 'single', // 边框风格 ('single'|'double'|'rounded')
54
+ title: '', // 表格标题
55
+ footer: null, // 表尾数据行
56
+ showRowNumbers: false, // 显示行号
57
+ compact: false, // 紧凑模式(减少内边距)
58
+ ...options
59
+ };
60
+
61
+ // 计算或使用指定的列宽
62
+ this._calculateColumnWidths();
63
+ }
64
+
65
+ /**
66
+ * 计算各列最佳宽度
67
+ * @private
68
+ */
69
+ _calculateColumnWidths() {
70
+ if (this.options.widths && Array.isArray(this.options.widths)) {
71
+ this.columnWidths = [...this.options.widths];
72
+ return;
73
+ }
74
+
75
+ // 初始化为表头宽度
76
+ this.columnWidths = this.headers.map(h => this._strWidth(h));
77
+
78
+ // 考虑每行的内容
79
+ for (const row of this.rows) {
80
+ for (let i = 0; i < row.length; i++) {
81
+ const cellWidth = this._strWidth(row[i]);
82
+ if (i < this.columnWidths.length) {
83
+ if (cellWidth > this.columnWidths[i]) {
84
+ this.columnWidths[i] = cellWidth;
85
+ }
86
+ } else {
87
+ this.columnWidths.push(cellWidth);
88
+ }
89
+ }
90
+ }
91
+
92
+ // 最小宽度 3
93
+ this.columnWidths = this.columnWidths.map(w => Math.max(w, 3));
94
+ }
95
+
96
+ /**
97
+ * 设置高亮行
98
+ * @param {number} index - 行索引 (-1 取消高亮)
99
+ */
100
+ setHighlight(index) {
101
+ this.options.highlightRow = index;
102
+ }
103
+
104
+ /**
105
+ * 渲染完整表格
106
+ * @returns {string} 格式化的表格字符串
107
+ */
108
+ render() {
109
+ const opts = this.options;
110
+ const c = BORDER_CHARS[opts.borderStyle] || BORDER_CHARS.single;
111
+ const widths = this.columnWidths;
112
+ const aligns = opts.aligns;
113
+
114
+ const lines = [];
115
+
116
+ // 标题
117
+ if (opts.title) {
118
+ lines.push(this._renderTitle(opts.title, widths, c));
119
+ }
120
+
121
+ // 顶边框
122
+ lines.push(this._makeBorder(c.tl, c.bt, c.tr, widths));
123
+
124
+ // 表头
125
+ lines.push(this._renderHeader(c, widths, aligns));
126
+
127
+ // 表头下分隔线
128
+ lines.push(this._makeBorder(c.lt, c.cross, c.rt, widths));
129
+
130
+ // 数据行
131
+ for (let r = 0; r < this.rows.length; r++) {
132
+ const isHighlighted = r === opts.highlightRow;
133
+ lines.push(this._renderDataRow(r, c, widths, aligns, isHighlighted));
134
+ }
135
+
136
+ // 表尾(如果有)
137
+ if (opts.footer) {
138
+ lines.push(this._makeBorder(c.lt, c.cross, c.rt, widths));
139
+ const footerRow = opts.footer.map(f => String(f ?? ''));
140
+ lines.push(this._renderCells(footerRow, c, widths, aligns, false));
141
+ }
142
+
143
+ // 底边框
144
+ lines.push(this._makeBorder(c.bl, c.bb, c.br, widths));
145
+
146
+ return lines.join('\n');
147
+ }
148
+
149
+ /**
150
+ * 渲染紧凑版表格(无左右边框线)
151
+ * @returns {string}
152
+ */
153
+ renderCompact() {
154
+ const widths = this.columnWidths;
155
+ const aligns = this.options.aligns;
156
+ const lines = [];
157
+
158
+ // 表头
159
+ const headerCells = this.headers.map((h, i) =>
160
+ this._pad(String(h), widths[i], aligns[i] || 'left')
161
+ );
162
+ lines.push(headerCells.join(' '));
163
+
164
+ // 分隔线
165
+ lines.push(widths.map(w => '─'.repeat(w)).join(' '));
166
+
167
+ // 数据行
168
+ for (let r = 0; r < this.rows.length; r++) {
169
+ const cells = this.rows[r].map((cell, i) =>
170
+ this._pad(String(cell), widths[i], aligns[i] || 'left')
171
+ );
172
+ lines.push(cells.join(' '));
173
+ }
174
+
175
+ return lines.join('\n');
176
+ }
177
+
178
+ /**
179
+ * 渲染标题行
180
+ * @param {string} title - 标题文本
181
+ * @param {number[]} widths - 列宽数组
182
+ * @param {Object} c - 边框字符
183
+ * @returns {string}
184
+ * @private
185
+ */
186
+ _renderTitle(title, widths, c) {
187
+ const totalWidth = widths.reduce((sum, w) => sum + w + 1, 0) + 1;
188
+ const titleStr = ` ${title} `;
189
+ const padding = Math.max(0, totalWidth - this._strWidth(titleStr) - 2);
190
+ const leftPad = Math.floor(padding / 2);
191
+ const rightPad = padding - leftPad;
192
+
193
+ return `${c.tl}─${' '.repeat(leftPad)}${titleStr}${' '.repeat(rightPad)}─${c.tr}`;
194
+ }
195
+
196
+ /**
197
+ * 渲染表头行
198
+ * @param {Object} c - 边框字符
199
+ * @param {number[]} widths - 列宽
200
+ * @param {string[]} aligns - 对齐方式
201
+ * @returns {string}
202
+ * @private
203
+ */
204
+ _renderHeader(c, widths, aligns) {
205
+ const cells = this.headers.map((h, i) => {
206
+ const padded = this._pad(String(h), widths[i], aligns[i] || 'center');
207
+ return ANSI.BOLD + padded + ANSI.RESET;
208
+ });
209
+
210
+ return this._joinRow(cells, c.v);
211
+ }
212
+
213
+ /**
214
+ * 渲染数据行
215
+ * @param {number} rowIndex - 行索引
216
+ * @param {Object} c - 边框字符
217
+ * @param {number[]} widths - 列宽
218
+ * @param {string[]} aligns - 对齐方式
219
+ * @param {boolean} highlighted - 是否高亮
220
+ * @returns {string}
221
+ * @private
222
+ */
223
+ _renderDataRow(rowIndex, c, widths, aligns, highlighted) {
224
+ const row = this.rows[rowIndex] || [];
225
+ const line = this._renderCells(row, c, widths, aligns, highlighted);
226
+
227
+ // 可选的行号前缀
228
+ if (this.options.showRowNumbers) {
229
+ const numStr = String(rowIndex + 1).padStart(3);
230
+ const prefix = highlighted
231
+ ? `${ANSI.REVERSE} ${numStr} ${ANSI.RESET}`
232
+ : ` ${ANSI.DIM}${numStr}${ANSI.RESET} `;
233
+ return prefix + line;
234
+ }
235
+
236
+ return line;
237
+ }
238
+
239
+ /**
240
+ * 渲染单元格
241
+ * @param {string[]} cells - 单元格数据
242
+ * @param {Object} c - 边框字符
243
+ * @param {number[]} widths - 列宽
244
+ * @param {string[]} aligns - 对齐方式
245
+ * @param {boolean} highlighted - 是否高亮
246
+ * @returns {string}
247
+ * @private
248
+ */
249
+ _renderCells(cells, c, widths, aligns, highlighted) {
250
+ const padded = cells.map((cell, i) => {
251
+ const w = i < widths.length ? widths[i] : 10;
252
+ const align = (aligns && aligns[i]) || 'left';
253
+ return this._pad(String(cell), w, align);
254
+ });
255
+
256
+ const rowStr = this._joinRow(padded, c.v);
257
+ return highlighted ? ANSI.REVERSE + rowStr + ANSI.RESET : rowStr;
258
+ }
259
+
260
+ /**
261
+ * 生成边框线
262
+ * @param {string} left - 左角字符
263
+ * @param {string} mid - 中间分隔字符
264
+ * @param {string} right - 右角字符
265
+ * @param {number[]} widths - 列宽数组
266
+ * @returns {string}
267
+ * @private
268
+ */
269
+ _makeBorder(left, mid, right, widths) {
270
+ return left + widths.map(w => (mid === '─' || mid === '═' ? mid : '─').repeat(w)).join(mid) + right;
271
+ }
272
+
273
+ /**
274
+ * 连接一行单元格
275
+ * @param {string[]} cells - 已填充的单元格
276
+ * @param {string} sep - 分隔符
277
+ * @returns {string}
278
+ * @private
279
+ */
280
+ _joinRow(cells, sep) {
281
+ return sep + ' ' + cells.join(' ' + sep + ' ') + ' ' + sep;
282
+ }
283
+
284
+ /**
285
+ * 文本填充到指定宽度
286
+ * @param {string} text - 文本
287
+ * @param {number} width - 目标宽度
288
+ * @param {string} align - 对齐方式
289
+ * @returns {string}
290
+ * @private
291
+ */
292
+ _pad(text, width, align = 'left') {
293
+ const displayLen = this._strWidth(text);
294
+ if (displayLen >= width) {
295
+ return this._truncate(text, width);
296
+ }
297
+ const pad = width - displayLen;
298
+ switch (align) {
299
+ case 'right':
300
+ return ' '.repeat(pad) + text;
301
+ case 'center':
302
+ return ' '.repeat(Math.floor(pad / 2)) + text + ' '.repeat(Math.ceil(pad / 2));
303
+ default:
304
+ return text + ' '.repeat(pad);
305
+ }
306
+ }
307
+
308
+ /**
309
+ * 截断文本
310
+ * @param {string} text - 文本
311
+ * @param {number} maxLen - 最大宽度
312
+ * @returns {string}
313
+ * @private
314
+ */
315
+ _truncate(text, maxLen) {
316
+ if (this._strWidth(text) <= maxLen) return text;
317
+ let result = '';
318
+ let currentW = 0;
319
+ for (const ch of text) {
320
+ const cw = this._strWidth(ch);
321
+ if (currentW + cw > maxLen - 1) break;
322
+ result += ch;
323
+ currentW += cw;
324
+ }
325
+ return result + '…';
326
+ }
327
+
328
+ /**
329
+ * 计算字符串显示宽度
330
+ * @param {string} text
331
+ * @returns {number}
332
+ * @private
333
+ */
334
+ _strWidth(text) {
335
+ let w = 0;
336
+ for (const ch of String(text)) {
337
+ w += ch.charCodeAt(0) > 0xff ? 2 : 1;
338
+ }
339
+ return w;
340
+ }
341
+
342
+ /**
343
+ * 快速创建简单表格的静态工厂方法
344
+ * @param {string[]} headers - 表头
345
+ * @param {Array<Array>} rows - 数据行
346
+ * @param {Object} options - 选项
347
+ * @returns {string} 渲染结果
348
+ */
349
+ static quick(headers, rows, options = {}) {
350
+ const table = new Table(headers, rows, options);
351
+ return table.render();
352
+ }
353
+
354
+ /**
355
+ * 创建键值对信息表(两列布局)
356
+ * @param {Array<[string, string]>} data - 键值对数组
357
+ * @param {Object} options - 选项
358
+ * @returns {string}
359
+ */
360
+ static keyValue(data, options = {}) {
361
+ const headers = options.headers || ['属性', '值'];
362
+ const rows = data.map(([label, value]) => [label, String(value ?? '')]);
363
+ return new Table(headers, rows, {
364
+ ...options,
365
+ aligns: ['left', 'left']
366
+ }).render();
367
+ }
368
+ }
369
+
370
+ export default Table;
@@ -0,0 +1,335 @@
1
+ /**
2
+ * PDD Visual Manager - 键盘输入处理器 (VM-C003)
3
+ *
4
+ * 处理终端原始键盘输入,支持:
5
+ * - Raw mode 模式捕获所有按键
6
+ * - 特殊键解析(方向键、Ctrl组合键、功能键等)
7
+ * - 终端 resize 事件监听
8
+ * - 可插拔的按键回调机制
9
+ *
10
+ * 纯 Node.js 内置模块实现,零外部依赖。
11
+ */
12
+
13
+ import readline from 'readline';
14
+
15
+ /**
16
+ * 特殊按键映射表
17
+ * 将原始字节序列映射为标准按键名称
18
+ */
19
+ const KEY_MAP = {
20
+ // 控制键
21
+ '\x03': 'ctrl_c', // Ctrl+C
22
+ '\x04': 'ctrl_d', // Ctrl+D
23
+ '\x08': 'ctrl_h', // Ctrl+H (Backspace)
24
+ '\x09': 'tab', // Tab
25
+ '\x0a': 'enter', // Enter (LF)
26
+ '\x0d': 'return', // Return (CR)
27
+ '\x7f': 'backspace', // Backspace
28
+ '\x1b': 'escape', // Esc (单个)
29
+
30
+ // ESC 序列 - 光标键
31
+ '\x1b[A': 'up',
32
+ '\x1b[B': 'down',
33
+ '\x1b[C': 'right',
34
+ '\x1b[D': 'left',
35
+
36
+ // ESC 序列 - 功能/编辑键
37
+ '\x1b[F': 'end',
38
+ '\x1b[H': 'home',
39
+ '\x1b[2~': 'insert',
40
+ '\x1b[3~': 'delete',
41
+ '\x1b[5~': 'pageup',
42
+ '\x1b[6~': 'pagedown',
43
+
44
+ // Shift + 方向键
45
+ '\x1b[a': 'shift_up',
46
+ '\x1b[b': 'shift_down',
47
+ '\x1b[c': 'shift_right',
48
+ '\x1b[d': 'shift_left',
49
+
50
+ // Ctrl + 方向键
51
+ '\x1b[1;5A': 'ctrl_up',
52
+ '\x1b[1;5B': 'ctrl_down',
53
+ '\x1b[1;5C': 'ctrl_right',
54
+ '\x1b[1;5D': 'ctrl_left',
55
+
56
+ // Shift+Tab
57
+ '\x1b[Z': 'shift_tab',
58
+
59
+ // F1-F12
60
+ '\x1bOP': 'f1',
61
+ '\x1bOQ': 'f2',
62
+ '\x1bOR': 'f3',
63
+ '\x1bOS': 'f4',
64
+ '\x1b[15~': 'f5',
65
+ '\x1b[17~': 'f6',
66
+ '\x1b[18~': 'f7',
67
+ '\x1b[19~': 'f8',
68
+ '\x1b[20~': 'f9',
69
+ '\x1b[21~': 'f10',
70
+ '\x1b[23~': 'f11',
71
+ '\x1b[24~': 'f12'
72
+ };
73
+
74
+ /**
75
+ * InputHandler - 键盘输入处理器
76
+ *
77
+ * 管理 raw mode 终端输入,将原始字节流转换为结构化按键事件。
78
+ *
79
+ * 使用方式:
80
+ * ```js
81
+ * const handler = new InputHandler((key) => {
82
+ * console.log('Key pressed:', key.name);
83
+ * });
84
+ * handler.setup();
85
+ * // ... 使用后 ...
86
+ * handler.teardown();
87
+ * ```
88
+ */
89
+ class InputHandler {
90
+ /**
91
+ * 创建输入处理器
92
+ * @param {Function} onKeyPress - 按键回调函数,接收 { name, char, raw } 对象
93
+ */
94
+ constructor(onKeyPress = null) {
95
+ /** @type {Function|null} 按键回调 */
96
+ this.onKeyPress = onKeyPress;
97
+
98
+ /** @type {readline.Interface|null} readline 接口 */
99
+ this.rl = null;
100
+
101
+ /** @type {string} 输入缓冲区(用于多字节序列) */
102
+ this.buffer = '';
103
+
104
+ /** @type {boolean} 是否已激活 */
105
+ this.active = false;
106
+
107
+ /** @type {boolean} 是否已设置 raw mode */
108
+ this.rawModeSet = false;
109
+ }
110
+
111
+ /**
112
+ * 初始化输入处理器
113
+ * 设置 raw mode 和事件监听
114
+ */
115
+ setup() {
116
+ if (this.active) return;
117
+
118
+ // 创建 readline 接口(保持 stdin 引用活跃)
119
+ this.rl = readline.createInterface({
120
+ input: process.stdin,
121
+ output: process.stdout,
122
+ terminal: false,
123
+ prompt: ''
124
+ });
125
+
126
+ // 启用 raw mode 以捕获所有按键
127
+ try {
128
+ process.stdin.setRawMode(true);
129
+ this.rawModeSet = true;
130
+ } catch (e) {
131
+ console.error('[TUI] Warning: Cannot set raw mode:', e.message);
132
+ }
133
+
134
+ // 确保 stdin 处于 flowing mode
135
+ process.stdin.resume();
136
+ process.stdin.setEncoding('utf8');
137
+
138
+ // 监听数据输入
139
+ process.stdin.on('data', (chunk) => {
140
+ this._handleChunk(chunk);
141
+ });
142
+
143
+ // 监听终端尺寸变化
144
+ process.stdout.on('resize', () => {
145
+ if (this.onKeyPress) {
146
+ this.onKeyPress({
147
+ name: 'resize',
148
+ char: '',
149
+ raw: 'resize',
150
+ columns: process.stdout.columns,
151
+ rows: process.stdout.rows
152
+ });
153
+ }
154
+ });
155
+
156
+ this.active = true;
157
+ }
158
+
159
+ /**
160
+ * 处理输入数据块
161
+ * @param {Buffer|string} chunk - 输入数据
162
+ * @private
163
+ */
164
+ _handleChunk(chunk) {
165
+ const data = typeof chunk === 'string' ? chunk : chunk.toString('utf8');
166
+ this.buffer += data;
167
+
168
+ // 尝试从缓冲区提取完整的按键
169
+ while (this.buffer.length > 0) {
170
+ const key = this._parseNextKey();
171
+ if (!key) break; // 需要更多数据
172
+
173
+ if (this.onKeyPress) {
174
+ try {
175
+ this.onKeyPress(key);
176
+ } catch (e) {
177
+ console.error('[TUI] Key handler error:', e.message);
178
+ }
179
+ }
180
+ }
181
+ }
182
+
183
+ /**
184
+ * 从缓冲区解析下一个完整按键
185
+ * @returns {Object|null} 按键对象或 null(需要更多数据)
186
+ * @private
187
+ */
188
+ _parseNextKey() {
189
+ const buf = this.buffer;
190
+
191
+ // 直接查找已知按键序列(优先匹配长序列)
192
+ for (const len = Math.min(buf.length, 10); len > 0; len--) {
193
+ const seq = buf.substring(0, len);
194
+ if (KEY_MAP.hasOwnProperty(seq)) {
195
+ this.buffer = buf.substring(len);
196
+ return {
197
+ name: KEY_MAP[seq],
198
+ char: seq,
199
+ raw: seq
200
+ };
201
+ }
202
+ }
203
+
204
+ // 单个可打印字符
205
+ if (buf.length >= 1) {
206
+ const first = buf.charCodeAt(0);
207
+
208
+ // Esc 键特殊处理:如果是单独的 \x1b 且后面没有跟其他字符
209
+ // 这里我们采用简单策略:如果第一个是 \x1b 且不在已知序列中,
210
+ // 先等待一下看是否有后续字符。但为了简化,直接返回 escape
211
+ if (first === 0x1b) {
212
+ // 如果是单独的 esc 或未知转义序列
213
+ if (buf.length === 1 || buf[1] !== '[' && buf[1] !== 'O') {
214
+ this.buffer = buf.substring(1);
215
+ return { name: 'escape', char: '\x1b', raw: '\x1b' };
216
+ }
217
+ // CSI 序列,继续收集
218
+ if (buf[1] === '[' || buf[1] === 'O') {
219
+ // 等待完整序列 - 简单处理:最多等待 6 个字符
220
+ if (buf.length < 3) return null; // 需要更多数据
221
+
222
+ // 尝试匹配已知的 CSI 序列
223
+ const csiEnd = this._findCSIEnd(buf);
224
+ if (csiEnd > 0) {
225
+ const seq = buf.substring(0, csiEnd);
226
+ this.buffer = buf.substring(csiEnd);
227
+ const knownName = KEY_MAP[seq];
228
+ return {
229
+ name: knownName || 'unknown_csi',
230
+ char: seq,
231
+ raw: seq
232
+ };
233
+ }
234
+ // 如果还没结束且缓冲区不够长,等待更多数据
235
+ if (buf.length < 7) return null;
236
+ // 超过合理长度,当作未知序列消费掉
237
+ this.buffer = buf.substring(1);
238
+ return { name: 'unknown_sequence', char: buf[0], raw: buf[0] };
239
+ }
240
+ }
241
+
242
+ // 可打印 ASCII 字符 (32-126)
243
+ if (first >= 32 && first <= 126) {
244
+ this.buffer = buf.substring(1);
245
+ const char = buf[0];
246
+ return {
247
+ name: char,
248
+ char: char,
249
+ raw: char
250
+ };
251
+ }
252
+
253
+ // 其他控制字符,跳过
254
+ this.buffer = buf.substring(1);
255
+ return {
256
+ name: 'control',
257
+ char: String.fromCharCode(first),
258
+ raw: buf[0]
259
+ };
260
+ }
261
+
262
+ return null;
263
+ }
264
+
265
+ /**
266
+ * 查找 CSI 序列的结束位置
267
+ * @param {string} buf - 缓冲区
268
+ * @returns {number} CSI 序列结束位置(不包含),0 表示未找到
269
+ * @private
270
+ */
271
+ _findCSIEnd(buf) {
272
+ // CSI 序列格式: ESC [ 参数(可选) 中间符(可选) 最终字节
273
+ // 最终字节通常是 0x40-0x7E 范围内的可打印字符
274
+ let i = 2; // 跳过 ESC [
275
+ while (i < buf.length) {
276
+ const code = buf.charCodeAt(i);
277
+ // 参数: 0x30-0x3F (0-9:;<=>?)
278
+ // 中间符: 0x20-0x2F (空格!"#$%&'()*+,-./)
279
+ // 最终字节: 0x40-0x7E (@A-Z[\]^_`a-z{|}~)
280
+ if (code >= 0x40 && code <= 0x7e) {
281
+ return i + 1;
282
+ }
283
+ i++;
284
+ }
285
+ return 0;
286
+ }
287
+
288
+ /**
289
+ * 清理资源,恢复终端状态
290
+ */
291
+ teardown() {
292
+ if (!this.active) return;
293
+
294
+ // 恢复正常模式
295
+ if (this.rawModeSet) {
296
+ try {
297
+ process.stdin.setRawMode(false);
298
+ } catch (e) {
299
+ // 忽略错误
300
+ }
301
+ this.rawModeSet = false;
302
+ }
303
+
304
+ // 暂停 stdin
305
+ try {
306
+ process.stdin.pause();
307
+ } catch (e) {
308
+ // 忽略
309
+ }
310
+
311
+ // 关闭 readline
312
+ if (this.rl) {
313
+ try {
314
+ this.rl.close();
315
+ } catch (e) {
316
+ // 忽略
317
+ }
318
+ this.rl = null;
319
+ }
320
+
321
+ this.active = false;
322
+ this.buffer = '';
323
+ }
324
+
325
+ /**
326
+ * 检查是否处于活跃状态
327
+ * @returns {boolean}
328
+ */
329
+ get isActive() {
330
+ return this.active;
331
+ }
332
+ }
333
+
334
+ export default InputHandler;
335
+ export { KEY_MAP };