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,272 @@
1
+ import chalk from 'chalk';
2
+ import fs from 'fs-extra';
3
+ import path from 'path';
4
+ import xml2js from 'xml2js';
5
+ import yaml from 'yaml';
6
+ import { fileURLToPath } from 'url';
7
+
8
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
9
+ const RULES_PATH = path.join(__dirname, '..', '..', 'config', 'bpmn-rules.yaml');
10
+
11
+ export async function runActivitiChecks(projectRoot) {
12
+ const start = Date.now();
13
+
14
+ const rulesContent = fs.existsSync(RULES_PATH)
15
+ ? fs.readFileSync(RULES_PATH, 'utf-8')
16
+ : '';
17
+ const rules = rulesContent ? parseYAML(rulesContent) : getDefaultRules();
18
+
19
+ const bpmnFiles = findBPMNFiles(projectRoot);
20
+
21
+ if (bpmnFiles.length === 0) {
22
+ return {
23
+ runner: 'activiti-linter',
24
+ success: true,
25
+ issueCount: 0,
26
+ errorCount: 0,
27
+ warningCount: 0,
28
+ issues: [],
29
+ message: '未找到BPMN文件',
30
+ duration: Date.now() - start
31
+ };
32
+ }
33
+
34
+ const allIssues = [];
35
+
36
+ for (const filePath of bpmnFiles) {
37
+ const fileIssues = await checkBPMNFile(filePath, rules);
38
+ allIssues.push(...fileIssues);
39
+ }
40
+
41
+ return {
42
+ runner: 'activiti-linter',
43
+ success: allIssues.filter(i => i.severity === 'error').length === 0,
44
+ issueCount: allIssues.length,
45
+ errorCount: allIssues.filter(i => i.severity === 'error').length,
46
+ warningCount: allIssues.filter(i => i.severity === 'warn').length,
47
+ issues: allIssues.slice(0, 100),
48
+ filesChecked: bpmnFiles.length,
49
+ duration: Date.now() - start
50
+ };
51
+ }
52
+
53
+ function findBPMNFiles(root) {
54
+ const patterns = ['**/*.bpmn', '**/*.bpmn20.xml', '**/*-process.xml', '**/processes/**/*.xml'];
55
+ const found = new Set();
56
+
57
+ for (const pattern of patterns) {
58
+ try {
59
+ const files = fs.globSync(pattern, {
60
+ cwd: root,
61
+ ignore: ['**/node_modules/**', '**/.git/**', '**/.pdd/**', '**/target/**']
62
+ });
63
+ for (const f of files) found.add(path.join(root, f));
64
+ } catch {}
65
+ }
66
+
67
+ return Array.from(found).filter(f => {
68
+ try { const c = fs.readFileSync(f, 'utf-8'); return c.includes('bpmn') || c.includes('definitions'); }
69
+ catch { return false; }
70
+ });
71
+ }
72
+
73
+ async function checkBPMNFile(filePath, rules) {
74
+ const issues = [];
75
+ let xmlObj = null;
76
+
77
+ try {
78
+ const content = fs.readFileSync(filePath, 'utf-8');
79
+ const parser = new xml2js.Parser({ explicitArray: true });
80
+ xmlObj = await parser.parseStringPromise(content);
81
+ } catch (e) {
82
+ issues.push({
83
+ ruleId: 'bpmn-parse-error',
84
+ severity: 'error',
85
+ message: `XML解析失败: ${e.message}`,
86
+ file: filePath,
87
+ line: 0,
88
+ category: 'structure'
89
+ });
90
+ return issues;
91
+ }
92
+
93
+ const definitions = xmlObj.definitions || xmlObj['bpmn:definitions'] || {};
94
+ const processes = definitions.process || definitions['bpmn:process'] || [];
95
+ const processList = Array.isArray(processes) ? processes : [processes];
96
+
97
+ for (const category of Object.keys(rules.rules || {})) {
98
+ const catRules = rules.rules[category];
99
+ if (!catRules.enabled) continue;
100
+
101
+ for (const processEl of processList) {
102
+ if (!processEl || typeof processEl !== 'object') continue;
103
+
104
+ for (const rule of (catRules.checks || [])) {
105
+ const result = applyBPMNRule(rule, processEl, filePath, xmlObj);
106
+ if (!result.passed) {
107
+ issues.push({
108
+ ruleId: rule.id,
109
+ ruleName: rule.name,
110
+ severity: rule.severity || 'warn',
111
+ message: rule.message,
112
+ file: filePath,
113
+ line: 0,
114
+ category
115
+ });
116
+ }
117
+ }
118
+ }
119
+ }
120
+
121
+ const securityIssues = runSecurityChecks(filePath, fs.readFileSync(filePath, 'utf-8'));
122
+ issues.push(...securityIssues);
123
+
124
+ return issues;
125
+ }
126
+
127
+ function applyBPMNRule(rule, processEl, filePath, xmlObj) {
128
+ switch (rule.id) {
129
+ case 'bpmn-has-start-event':
130
+ const starts = findElements(processEl, 'startEvent');
131
+ return { passed: starts.length > 0 };
132
+
133
+ case 'bpmn-has-end-event':
134
+ const ends = findElements(processEl, 'endEvent');
135
+ return { passed: ends.length > 0 };
136
+
137
+ case 'bpmn-single-start-event':
138
+ const allStarts = findElements(processEl, 'startEvent');
139
+ return { passed: allStarts.length <= (rule.max_count || 1) };
140
+
141
+ case 'bpmn-process-id-required':
142
+ const procId = getAttr(processEl, 'id');
143
+ if (!procId) return { passed: false };
144
+ if (rule.pattern) return { passed: new RegExp(rule.pattern).test(procId) };
145
+ return { passed: true };
146
+
147
+ case 'bpmn-process-name-required':
148
+ const procName = getAttr(processEl, 'name');
149
+ return { passed: !!procName };
150
+
151
+ case 'bpmn-reasonable-size': {
152
+ const nodeCount = countAllNodes(processEl);
153
+ return { passed: nodeCount <= (rule.max_nodes || 50) };
154
+ }
155
+
156
+ case 'bpmn-user-task-has-assignee': {
157
+ const userTasks = findElements(processEl, 'userTask');
158
+ for (const t of userTasks) {
159
+ const assignee = getAttr(t, 'assignee');
160
+ const candidates = getAttr(t, 'candidateUsers') || getAttr(t, 'candidateGroups');
161
+ if (!assignee && !candidates) return { passed: false };
162
+ }
163
+ return { passed: true };
164
+ }
165
+
166
+ case 'bpmn-service-task-has-class': {
167
+ const serviceTasks = findElements(processEl, 'serviceTask');
168
+ for (const t of serviceTasks) {
169
+ const cls = getAttr(t, 'class');
170
+ const expr = getAttr(t, 'expression');
171
+ const delegate = getAttr(t, 'delegateExpression');
172
+ if (!cls && !expr && !delegate) return { passed: false };
173
+ }
174
+ return { passed: true };
175
+ }
176
+
177
+ default:
178
+ return { passed: true };
179
+ }
180
+ }
181
+
182
+ function findElements(el, tagName) {
183
+ const results = [];
184
+ const variants = [tagName, `bpmn:${tagName}`];
185
+
186
+ for (const variant of variants) {
187
+ if (el[variant]) {
188
+ results.push(...(Array.isArray(el[variant]) ? el[variant] : [el[variant]]));
189
+ }
190
+ }
191
+
192
+ return results.filter(e => e && typeof e === 'object');
193
+ }
194
+
195
+ function getAttr(el, attrName) {
196
+ if (!el || typeof el !== 'object') return null;
197
+
198
+ if (el.$ && el.$[attrName] !== undefined) return el.$[attrName];
199
+ if (el[attrName] !== undefined) return el[attrName];
200
+
201
+ return null;
202
+ }
203
+
204
+ function countAllNodes(processEl) {
205
+ const nodeTypes = [
206
+ 'startEvent', 'endEvent', 'userTask', 'serviceTask', 'scriptTask',
207
+ 'manualTask', 'businessRuleTask', 'sendTask', 'receiveTask',
208
+ 'exclusiveGateway', 'parallelGateway', 'inclusiveGateway',
209
+ 'eventBasedGateway', 'subProcess', 'callActivity'
210
+ ];
211
+
212
+ let count = 0;
213
+ for (const type of nodeTypes) {
214
+ count += findElements(processEl, type).length;
215
+ }
216
+ return count;
217
+ }
218
+
219
+ function runSecurityChecks(filePath, content) {
220
+ const issues = [];
221
+ const lines = content.split('\n');
222
+
223
+ const securityPatterns = [
224
+ { pattern: /password\s*[:=]\s*["'][^"']+["']/i, severity: 'error', ruleId: 'bpmn-no-hardcoded-passwords', message: '检测到可能的硬编码密码' },
225
+ { pattern: /\$\{.*(SELECT|INSERT|UPDATE|DELETE|DROP)/i, severity: 'error', ruleId: 'bpmn-no-sql-injection-risk', message: '检测到潜在SQL注入风险' },
226
+ { pattern: /<script[^>]*>[^<]*(?:eval|exec|Runtime)/i, severity: 'warn', ruleId: 'bpmn-dangerous-script', message: '检测到危险脚本调用' },
227
+ { pattern: /admin|root|sa\b/i, severity: 'info', ruleId: 'bpmn-default-account', message: '使用了默认管理员账户名' }
228
+ ];
229
+
230
+ for (let idx = 0; idx < lines.length; idx++) {
231
+ for (const check of securityPatterns) {
232
+ if (check.pattern.test(lines[idx])) {
233
+ issues.push({
234
+ ruleId: check.ruleId,
235
+ severity: check.severity,
236
+ message: check.message,
237
+ file: filePath,
238
+ line: idx + 1,
239
+ category: 'security'
240
+ });
241
+ }
242
+ }
243
+ }
244
+
245
+ return issues;
246
+ }
247
+
248
+ function parseYAML(content) {
249
+ try {
250
+ return yaml.parse(content);
251
+ } catch {
252
+ return getDefaultRules();
253
+ }
254
+ }
255
+
256
+ function getDefaultRules() {
257
+ return {
258
+ rules: {
259
+ structure: { enabled: true, checks: [
260
+ { id: 'bpmn-has-start-event', name: '流程必须有开始事件', severity: 'error', message: 'BPMN流程缺少开始事件' },
261
+ { id: 'bpmn-has-end-event', name: '流程必须有结束事件', severity: 'error', message: 'BPMN流程缺少结束事件' },
262
+ { id: 'bpmn-user-task-has-assignee', name: '用户任务需指定处理人', severity: 'error', message: '用户任务必须指定处理人' },
263
+ { id: 'bpmn-service-task-has-class', name: '服务任务需指定实现类', severity: 'error', message: '服务任务必须指定实现类' }
264
+ ]},
265
+ security: { enabled: true, checks: [] },
266
+ naming: { enabled: true, checks: [] },
267
+ best_practices: { enabled: true, checks: [] },
268
+ documentation: { enabled: true, checks: [] },
269
+ connectivity: { enabled: true, checks: [] }
270
+ }
271
+ };
272
+ }
@@ -0,0 +1,162 @@
1
+ import chalk from 'chalk';
2
+ import fs from 'fs-extra';
3
+ import path from 'path';
4
+ import yaml from 'yaml';
5
+ import { fileURLToPath } from 'url';
6
+
7
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
8
+ const RULES_PATH = path.join(__dirname, '..', '..', 'config', 'prd-rules.yaml');
9
+
10
+ export async function runPRDChecks(projectRoot) {
11
+ const start = Date.now();
12
+
13
+ const rulesContent = fs.existsSync(RULES_PATH)
14
+ ? fs.readFileSync(RULES_PATH, 'utf-8')
15
+ : '';
16
+ const rules = rulesContent ? yaml.parse(rulesContent) : getDefaultRules();
17
+
18
+ const prdFiles = findPRDFiles(projectRoot);
19
+
20
+ if (prdFiles.length === 0) {
21
+ return {
22
+ runner: 'prd-linter',
23
+ success: true,
24
+ issueCount: 0,
25
+ errorCount: 0,
26
+ warningCount: 0,
27
+ issues: [],
28
+ message: '未找到PRD文件',
29
+ duration: Date.now() - start
30
+ };
31
+ }
32
+
33
+ const allIssues = [];
34
+
35
+ for (const filePath of prdFiles) {
36
+ const content = fs.readFileSync(filePath, 'utf-8');
37
+ const fileIssues = checkFile(filePath, content, rules);
38
+ allIssues.push(...fileIssues);
39
+ }
40
+
41
+ return {
42
+ runner: 'prd-linter',
43
+ success: allIssues.filter(i => i.severity === 'error').length === 0,
44
+ issueCount: allIssues.length,
45
+ errorCount: allIssues.filter(i => i.severity === 'error').length,
46
+ warningCount: allIssues.filter(i => i.severity === 'warn').length,
47
+ issues: allIssues.slice(0, 100),
48
+ filesChecked: prdFiles.length,
49
+ duration: Date.now() - start
50
+ };
51
+ }
52
+
53
+ function findPRDFiles(root) {
54
+ const patterns = [
55
+ '**/*.md',
56
+ '**/PRD*.md',
57
+ '**/prd*.md',
58
+ '**/requirements*.md',
59
+ '**/spec*.md'
60
+ ];
61
+
62
+ const found = new Set();
63
+
64
+ for (const pattern of patterns) {
65
+ try {
66
+ const files = fs.globSync(pattern, { cwd: root, ignore: ['**/node_modules/**', '**/.git/**', '**/.pdd/**'] });
67
+ for (const f of files) found.add(path.join(root, f));
68
+ } catch {}
69
+ }
70
+
71
+ return Array.from(found).filter(f => isLikelyPRD(f));
72
+ }
73
+
74
+ function isLikelyPRD(filePath) {
75
+ const name = path.basename(filePath).toLowerCase();
76
+ const keywords = ['prd', 'requirement', 'spec', 'feature', '需求', '规格', '功能'];
77
+ return keywords.some(k => name.includes(k));
78
+ }
79
+
80
+ function checkFile(filePath, content, rules) {
81
+ const issues = [];
82
+ const lines = content.split('\n');
83
+
84
+ for (const [category, categoryRules] of Object.entries(rules.rules || {})) {
85
+ if (!categoryRules.enabled) continue;
86
+
87
+ for (const rule of (categoryRules.checks || [])) {
88
+ const match = applyRule(rule, content, lines);
89
+ if (!match.passed) {
90
+ issues.push({
91
+ ruleId: rule.id,
92
+ ruleName: rule.name,
93
+ severity: rule.severity || 'info',
94
+ message: rule.message,
95
+ file: filePath,
96
+ line: match.line || 0,
97
+ category
98
+ });
99
+ }
100
+ }
101
+ }
102
+
103
+ return issues;
104
+ }
105
+
106
+ function applyRule(rule, content, lines) {
107
+ if (rule.pattern) {
108
+ try {
109
+ const regex = new RegExp(rule.pattern, 'i');
110
+ const match = content.match(regex);
111
+
112
+ if (match) {
113
+ const lineNum = content.substring(0, match.index).split('\n').length;
114
+
115
+ if (rule.exclude_patterns) {
116
+ for (const excl of rule.exclude_patterns) {
117
+ if (new RegExp(excl, 'i').test(match[0])) {
118
+ return { passed: true };
119
+ }
120
+ }
121
+ }
122
+
123
+ return { passed: true, line: lineNum };
124
+ }
125
+
126
+ return { passed: false, line: 0 };
127
+ } catch {
128
+ return { passed: true };
129
+ }
130
+ }
131
+
132
+ if (rule.max_depth) {
133
+ let maxDepth = 0;
134
+ for (const line of lines) {
135
+ const headerMatch = line.match(/^(#{1,6})\s/);
136
+ if (headerMatch) {
137
+ maxDepth = Math.max(maxDepth, headerMatch[1].length);
138
+ }
139
+ }
140
+ return { passed: maxDepth <= rule.max_depth };
141
+ }
142
+
143
+ return { passed: true };
144
+ }
145
+
146
+ function getDefaultRules() {
147
+ return {
148
+ rules: {
149
+ structure: {
150
+ enabled: true,
151
+ checks: [
152
+ { id: 'prd-has-title', name: 'PRD必须有标题', pattern: '^#+\\s+.+', severity: 'error', message: 'PRD文档缺少标题' },
153
+ { id: 'prd-has-version', name: 'PRD必须包含版本信息', pattern: '(?i)(version|版本|v\\d+\\.\\d+)', severity: 'error', message: 'PRD文档缺少版本号' },
154
+ { id: 'prd-has-goals', name: 'PRD必须包含目标', pattern: '(?i)(目标|goal)', severity: 'error', message: 'PRD文档缺少目标定义' }
155
+ ]
156
+ },
157
+ content: { enabled: true, checks: [] },
158
+ format: { enabled: true, checks: [] },
159
+ consistency: { enabled: true, checks: [] }
160
+ }
161
+ };
162
+ }
@@ -0,0 +1,207 @@
1
+ import chalk from 'chalk';
2
+ import fs from 'fs-extra';
3
+ import path from 'path';
4
+
5
+ export class ReportGenerator {
6
+ constructor(projectRoot) {
7
+ this.projectRoot = projectRoot;
8
+ this.reportDir = path.join(projectRoot, '.pdd', 'cache', 'reports');
9
+ }
10
+
11
+ async generate(linterResults = [], options = {}) {
12
+ await fs.ensureDir(this.reportDir);
13
+
14
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
15
+ const format = options.format || 'markdown';
16
+
17
+ switch (format) {
18
+ case 'json':
19
+ return await this.generateJSONReport(linterResults, timestamp);
20
+ case 'html':
21
+ return await this.generateHTMLReport(linterResults, timestamp);
22
+ case 'markdown':
23
+ default:
24
+ return await this.generateMarkdownReport(linterResults, timestamp);
25
+ }
26
+ }
27
+
28
+ async generateMarkdownReport(results, timestamp) {
29
+ const summary = this.buildSummary(results);
30
+ const lines = [
31
+ '# PDD Linter 检查报告',
32
+ '',
33
+ `> 生成时间: ${new Date().toLocaleString('zh-CN')}`,
34
+ `> 项目: ${path.basename(this.projectRoot)}`,
35
+ '',
36
+ '## 执行摘要',
37
+ '',
38
+ `| 指标 | 数值 |`,
39
+ `|------|------|`,
40
+ `| 总检查项 | ${summary.totalRunners} |`,
41
+ `| 通过 | ${chalk.green(summary.passed.toString())} |`,
42
+ `| 失败 | ${chalk.red(summary.failed.toString())} |`,
43
+ `| 总问题数 | ${summary.totalIssues} |`,
44
+ `| 错误 | ${chalk.red(summary.totalErrors.toString())} |`,
45
+ `| 警告 | ${chalk.yellow(summary.totalWarnings.toString())} |`,
46
+ `| 总耗时 | ${summary.totalDuration}ms |`,
47
+ ''
48
+ ];
49
+
50
+ if (summary.failed > 0) {
51
+ lines.push('> ⚠️ **存在未通过检查项,请查看详情**', '');
52
+ } else {
53
+ lines.push('> ✅ **所有检查通过**', '');
54
+ }
55
+
56
+ lines.push('## 详细结果', '');
57
+
58
+ for (const result of results) {
59
+ lines.push(`### ${result.runner}`, '');
60
+ lines.push(`- 状态: ${result.success ? '✅ 通过' : '❌ 失败'}`);
61
+ lines.push(`- 耗时: ${result.duration || 0}ms`);
62
+
63
+ if (result.issueCount !== undefined) {
64
+ lines.push(`- 问题数: ${result.issueCount} (${result.errorCount || 0} 错误, ${result.warningCount || 0} 警告)`);
65
+ }
66
+
67
+ if (result.filesChecked) {
68
+ lines.push(`- 检查文件数: ${result.filesChecked}`);
69
+ }
70
+
71
+ if (result.message && result.issueCount === 0) {
72
+ lines.push(`- 备注: ${result.message}`);
73
+ }
74
+
75
+ if (result.issues && result.issues.length > 0) {
76
+ lines.push('', '**问题列表:**', '');
77
+ lines.push('| 严重度 | 规则 | 描述 | 文件 | 行号 |');
78
+ lines.push('|--------|------|------|------|------|');
79
+
80
+ for (const issue of result.issues.slice(0, 50)) {
81
+ const sevIcon = issue.severity === 'error' ? '🔴' : issue.severity === 'warn' ? '🟡' : '🔵';
82
+ const file = issue.file ? path.basename(issue.file) : '-';
83
+ lines.push(`${sevIcon} | ${issue.ruleId || '-'} | ${issue.message || '-'} | ${file} | ${issue.line || '-'}`);
84
+ }
85
+ }
86
+
87
+ lines.push('');
88
+ }
89
+
90
+ lines.push('---', '', `_由 PDD-Skills v1.0 自动生成_`);
91
+
92
+ const reportPath = path.join(this.reportDir, `linter-${timestamp}.md`);
93
+ await fs.writeFile(reportPath, lines.join('\n'), 'utf-8');
94
+ console.log(chalk.gray(` 📄 Markdown报告: ${reportPath}`));
95
+
96
+ return { path: reportPath, format: 'markdown', summary };
97
+ }
98
+
99
+ async generateJSONReport(results, timestamp) {
100
+ const summary = this.buildSummary(results);
101
+ const report = {
102
+ generated_at: new Date().toISOString(),
103
+ project: path.basename(this.projectRoot),
104
+ version: '1.0.0',
105
+ summary,
106
+ results
107
+ };
108
+
109
+ const reportPath = path.join(this.reportDir, `linter-${timestamp}.json`);
110
+ await fs.writeFile(reportPath, JSON.stringify(report, null, 2), 'utf-8');
111
+ console.log(chalk.gray(` 📄 JSON报告: ${reportPath}`));
112
+
113
+ return { path: reportPath, format: 'json', summary };
114
+ }
115
+
116
+ async generateHTMLReport(results, timestamp) {
117
+ const summary = this.buildSummary(results);
118
+ const issuesHtml = results.flatMap(r => (r.issues || []).map(i => ({
119
+ ...i,
120
+ runner: r.runner
121
+ })));
122
+
123
+ const html = `<!DOCTYPE html>
124
+ <html lang="zh-CN">
125
+ <head>
126
+ <meta charset="UTF-8">
127
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
128
+ <title>PDD Linter 报告 - ${path.basename(this.projectRoot)}</title>
129
+ <style>
130
+ *{margin:0;padding:0;box-sizing:border-box}
131
+ body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;background:#f5f5f5;color:#333;padding:20px}
132
+ .header{background:#fff;border-radius:8px;padding:24px;margin-bottom:20px;box-shadow:0 1px 3px rgba(0,0,0,.1)}
133
+ .header h1{font-size:24px;color:#1a73e8;margin-bottom:8px}
134
+ .meta{color:#666;font-size:14px}
135
+ .summary{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:16px;margin-bottom:20px}
136
+ .card{background:#fff;border-radius:8px;padding:20px;text-align:center;box-shadow:0 1px 3px rgba(0,0,0,.1)}
137
+ .card .value{font-size:32px;font-weight:bold}
138
+ .card .label{color:#666;font-size:14px;margin-top:4px}
139
+ .pass .value{color:#34a853}.fail .value{color:#ea4335}.warn .value{#f9ab00}
140
+ .section{background:#fff;border-radius:8px;padding:24px;margin-bottom:20px;box-shadow:0 1px 3px rgba(0,0,0,.1)}
141
+ .section h2{font-size:18px;margin-bottom:16px;color:#333;border-bottom:1px solid #eee;padding-bottom:8px}
142
+ table{width:100%;border-collapse:collapse}
143
+ th{text-align:left;background:#f8f9fa;padding:10px;font-weight:600;font-size:13px;color:#555}
144
+ td{padding:10px;border-bottom:1px solid #eee;font-size:13px}
145
+ tr:hover{background:#f8f9fa}
146
+ .severity-error{color:#ea4335;font-weight:600}.severity-warn{color:#f9ab00}.severity-info{color:#4285f4}
147
+ </style>
148
+ </head>
149
+ <body>
150
+ <div class="header">
151
+ <h1>🔍 PDD Linter 检查报告</h1>
152
+ <div class="meta">项目: ${path.basename(this.projectRoot)} | 生成时间: ${new Date().toLocaleString('zh-CN')}</div>
153
+ </div>
154
+ <div class="summary">
155
+ <div class="card ${summary.failed === 0 ? 'pass' : 'fail'}"><div class="value">${summary.totalRunners}</div><div class="label">总检查项</div></div>
156
+ <div class="card pass"><div class="value">${summary.passed}</div><div class="label">通过</div></div>
157
+ <div class="card fail"><div class="value">${summary.failed}</div><div class="label">失败</div></div>
158
+ <div class="card warn"><div class="value">${summary.totalIssues}</div><div class="label">总问题</div></div>
159
+ <div class="card"><div class="value">${summary.totalDuration}ms</div><div class="label">耗时</div></div>
160
+ </div>
161
+ ${results.map(r => `
162
+ <div class="section">
163
+ <h2>${r.success ? '✅' : '❌'} ${r.runner}</h2>
164
+ <table>
165
+ <tr><th>指标</th><th>数值</th></tr>
166
+ <tr><td>状态</td><td>${r.success ? '通过' : '失败'}</td></tr>
167
+ <tr><td>耗时</td><td>${r.duration || 0}ms</td></tr>
168
+ <tr><td>错误</td><td>${r.errorCount || 0}</td></tr>
169
+ <tr><td>警告</td><td>${r.warningCount || 0}</td></tr>
170
+ ${r.filesChecked ? `<tr><td>文件数</td><td>${r.filesChecked}</td></tr>` : ''}
171
+ </table>
172
+ ${(r.issues || []).length > 0 ? `
173
+ <table style="margin-top:12px">
174
+ <tr><th>严重度</th><th>规则</th><th>描述</th><th>文件</th><th>行号</th></tr>
175
+ ${r.issues.slice(0, 30).map(i => `
176
+ <tr><td class="severity-${i.severity}">${i.severity.toUpperCase()}</td><td>${i.ruleId || '-'}</td><td>${(i.message || '').replace(/</g,'&lt;')}</td><td>${i.file ? path.basename(i.file) : '-'}</td><td>${i.line || '-'}</td></tr>`).join('')}
177
+ </table>` : ''}
178
+ </div>`).join('')}
179
+ </body>
180
+ </html>`;
181
+
182
+ const reportPath = path.join(this.reportDir, `linter-${timestamp}.html`);
183
+ await fs.writeFile(reportPath, html, 'utf-8');
184
+ console.log(chalk.gray(` 📄 HTML报告: ${reportPath}`));
185
+
186
+ return { path: reportPath, format: 'html', summary };
187
+ }
188
+
189
+ buildSummary(results) {
190
+ let totalRunners = results.length;
191
+ let passed = results.filter(r => r.success).length;
192
+ let failed = totalRunners - passed;
193
+ let totalIssues = 0;
194
+ let totalErrors = 0;
195
+ let totalWarnings = 0;
196
+ let totalDuration = 0;
197
+
198
+ for (const r of results) {
199
+ totalIssues += r.issueCount || 0;
200
+ totalErrors += r.errorCount || 0;
201
+ totalWarnings += r.warningCount || 0;
202
+ totalDuration += r.duration || 0;
203
+ }
204
+
205
+ return { totalRunners, passed, failed, totalIssues, totalErrors, totalWarnings, totalDuration };
206
+ }
207
+ }