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,1170 @@
1
+ """
2
+ PDD Python SDK 核心客户端
3
+
4
+ 提供与 PDD 服务端交互的所有 API,包括规格生成、代码生成、
5
+ 功能验证、代码审查等功能。基于 Python 标准库实现异步 HTTP 通信。
6
+ """
7
+
8
+ import asyncio
9
+ import json
10
+ import time
11
+ from typing import Any, Callable, Dict, List, Optional, Union
12
+ from urllib.error import HTTPError, URLError
13
+ from urllib.parse import urlencode
14
+ from urllib.request import Request, urlopen
15
+
16
+ # 导入内部模块
17
+ from .exceptions import (
18
+ PDDError,
19
+ ConnectionError as PDDConnectionError,
20
+ AuthError,
21
+ ValidationError,
22
+ ServerError,
23
+ TimeoutError as PDDTimeoutError,
24
+ RateLimitError,
25
+ )
26
+ from .models import (
27
+ SpecResult,
28
+ CodeResult,
29
+ VerifyResult,
30
+ ReviewResult,
31
+ SkillInfo,
32
+ Session,
33
+ StatusResult,
34
+ ServerInfo,
35
+ BatchResult,
36
+ FeatureInfo,
37
+ GeneratedFile,
38
+ VerifyIssue,
39
+ VerifyCriterion,
40
+ ReviewFinding,
41
+ Severity,
42
+ TaskStatus,
43
+ EventData,
44
+ )
45
+ from .events import EventEmitter, Events, EventHandler
46
+ from .utils import (
47
+ get_logger,
48
+ format_duration,
49
+ validate_endpoint,
50
+ validate_api_key,
51
+ retry,
52
+ )
53
+
54
+ # 模块级日志器
55
+ logger = get_logger("pdd_sdk.client")
56
+
57
+
58
+ class PDDClient:
59
+ """
60
+ PDD SDK 核心客户端类
61
+
62
+ 提供完整的 PDD API 交互能力,包括:
63
+ - 规格生成(从 PRD 提取功能点)
64
+ - 代码生成(根据规格生成实现代码)
65
+ - 功能验证(验证代码是否符合验收标准)
66
+ - 代码审查(静态代码分析)
67
+ - 批量操作(批量生成/验证)
68
+ - 会话管理(开发会话生命周期)
69
+ - 事件系统(请求生命周期监听)
70
+
71
+ 所有核心 API 方法都是异步的(async),使用 asyncio 和标准库实现。
72
+
73
+ Attributes:
74
+ endpoint: API 服务端地址
75
+ api_key: 认证密钥
76
+ timeout: 请求超时时间(秒)
77
+ debug: 调试模式开关
78
+ max_retries: 最大重试次数
79
+ retry_delay: 重试初始延迟(秒)
80
+ enable_cache: 是否启用内存缓存
81
+ cache_ttl: 缓存有效期(秒)
82
+
83
+ Example:
84
+ >>> import asyncio
85
+ >>> from pdd_sdk import PDDClient
86
+ >>>
87
+ >>> async def main():
88
+ ... client = PDDClient(
89
+ ... endpoint="http://localhost:3000",
90
+ ... api_key="your-api-key",
91
+ ... debug=True
92
+ ... )
93
+ ...
94
+ ... # 监听事件
95
+ ... client.on("request:end", lambda e: print(f"完成: {e['path']}"))
96
+ ...
97
+ ... # 生成规格
98
+ ... result = await client.generate_spec(prd_path="./docs/prd.prdx")
99
+ ... print(f"提取了 {result.feature_count} 个功能点")
100
+ >>>
101
+ >>> asyncio.run(main())
102
+ """
103
+
104
+ # 默认配置常量
105
+ DEFAULT_ENDPOINT = "http://localhost:3000"
106
+ """默认 API 端点"""
107
+ DEFAULT_TIMEOUT = 30
108
+ """默认超时时间(秒)"""
109
+ DEFAULT_MAX_RETRIES = 3
110
+ """默认最大重试次数"""
111
+ DEFAULT_RETRY_DELAY = 1.0
112
+ """默认重试延迟(秒)"""
113
+ DEFAULT_CACHE_TTL = 300
114
+ """默认缓存有效期(秒)"""
115
+
116
+ def __init__(
117
+ self,
118
+ endpoint: str = DEFAULT_ENDPOINT,
119
+ api_key: str = "",
120
+ timeout: int = DEFAULT_TIMEOUT,
121
+ debug: bool = False,
122
+ max_retries: int = DEFAULT_MAX_RETRIES,
123
+ retry_delay: float = DEFAULT_RETRY_DELAY,
124
+ enable_cache: bool = True,
125
+ cache_ttl: int = DEFAULT_CACHE_TTL,
126
+ ):
127
+ """
128
+ 初始化 PDD 客户端
129
+
130
+ Args:
131
+ endpoint: PDD 服务端 URL 地址(如 http://localhost:3000)
132
+ api_key: API 认证密钥
133
+ timeout: HTTP 请求超时时间(秒),默认 30 秒
134
+ debug: 是否启用调试模式(输出详细日志)
135
+ max_retries: 失败时的最大自动重试次数
136
+ retry_delay: 重试之间的初始延迟时间(秒)
137
+ enable_cache: 是否启用响应缓存
138
+ cache_ttl: 缓存有效期(秒)
139
+
140
+ Raises:
141
+ ValueError: 如果 endpoint 或 api_key 格式无效
142
+ """
143
+ # 验证并存储配置
144
+ self.endpoint = validate_endpoint(endpoint)
145
+ self.api_key = validate_api_key(api_key) if api_key else ""
146
+ self.timeout = timeout
147
+ self.debug = debug
148
+ self.max_retries = max_retries
149
+ self.retry_delay = retry_delay
150
+ self.enable_cache = enable_cache
151
+ self.cache_ttl = cache_ttl
152
+
153
+ # 初始化日志级别
154
+ if debug:
155
+ self._logger = get_logger("pdd_sdk.client", level=10) # DEBUG
156
+ else:
157
+ self._logger = logger
158
+
159
+ # 初始化事件发射器
160
+ self._events = EventEmitter()
161
+
162
+ # 内部状态
163
+ self._session_id: Optional[str] = None
164
+ self._request_count = 0
165
+ self._last_request_time: Optional[float] = None
166
+
167
+ self._logger.info(
168
+ f"PDD 客户端初始化完成 | 端点: {self.endpoint} | "
169
+ f"超时: {timeout}s | 重试: {max_retries}次 | 缓存: {'开启' if enable_cache else '关闭'}"
170
+ )
171
+
172
+ # ==================== 事件系统代理方法 ====================
173
+
174
+ def on(self, event: str, callback: EventHandler) -> "PDDClient":
175
+ """
176
+ 注册事件监听器
177
+
178
+ Args:
179
+ event: 事件名称(使用 Events 常量或自定义字符串)
180
+ callback: 回调函数
181
+
182
+ Returns:
183
+ 自身,支持链式调用
184
+
185
+ Example:
186
+ >>> client.on(Events.REQUEST_END, lambda e: print(e))
187
+ """
188
+ self._events.on(event, callback)
189
+ return self
190
+
191
+ def off(self, event: str, callback: Optional[EventHandler] = None) -> "PDDClient":
192
+ """
193
+ 移除事件监听器
194
+
195
+ Args:
196
+ event: 事件名称
197
+ callback: 要移除的回调,None 表示移除所有
198
+
199
+ Returns:
200
+ 自身,支持链式调用
201
+ """
202
+ self._events.off(event, callback)
203
+ return self
204
+
205
+ async def emit(self, event: str, **data: Any) -> None:
206
+ """
207
+ 异步触发事件
208
+
209
+ Args:
210
+ event: 事件名称
211
+ **data: 事件负载数据
212
+ """
213
+ await self._events.async_emit(event, **data)
214
+
215
+ @property
216
+ def events(self) -> EventEmitter:
217
+ """获取事件发射器实例"""
218
+ return self._events
219
+
220
+ # ==================== 核心 API 方法 ====================
221
+
222
+ async def generate_spec(
223
+ self,
224
+ prd_path: str,
225
+ template: Optional[str] = None,
226
+ output_dir: Optional[str] = None,
227
+ dry_run: bool = False,
228
+ ) -> SpecResult:
229
+ """
230
+ 从 PRD 文档生成开发规格
231
+
232
+ 解析 PRD 文档,提取功能点矩阵,生成结构化的开发规格文档。
233
+
234
+ Args:
235
+ prd_path: PRD 文档路径(支持 .prdx, .md, .docx 等格式)
236
+ template: 可选的规格模板名称
237
+ output_dir: 输出目录(可选)
238
+ dry_run: 是否仅预览不实际生成文件
239
+
240
+ Returns:
241
+ SpecResult 包含生成的规格信息、功能点列表等
242
+
243
+ Raises:
244
+ ValidationError: 如果 PRD 路径无效或格式不支持
245
+ PDDConnectionError: 如果无法连接到服务端
246
+ AuthError: 如果认证失败
247
+ ServerError: 如果服务端处理出错
248
+
249
+ Example:
250
+ >>> result = await client.generate_spec(
251
+ ... prd_path="./requirements/user-system.prdx",
252
+ ... template="standard",
253
+ ... output_dir="./specs"
254
+ ... )
255
+ >>> print(f"成功提取 {result.feature_count} 个功能点")
256
+ """
257
+ start_time = time.time()
258
+ method = "POST"
259
+ path = "/api/v1/specs/generate"
260
+
261
+ payload = {
262
+ "prd_path": prd_path,
263
+ "dry_run": dry_run,
264
+ }
265
+ if template:
266
+ payload["template"] = template
267
+ if output_dir:
268
+ payload["output_dir"] = output_dir
269
+
270
+ try:
271
+ await self._emit_event(Events.REQUEST_START, {
272
+ "method": method, "path": path, "payload": payload
273
+ })
274
+
275
+ response = await self._request(method, path, payload)
276
+ result = self._parse_spec_result(response)
277
+
278
+ duration_ms = int((time.time() - start_time) * 1000)
279
+ result.duration_ms = duration_ms
280
+
281
+ await self._emit_event(Events.REQUEST_END, {
282
+ "method": method,
283
+ "path": path,
284
+ "duration_ms": duration_ms,
285
+ "success": result.success,
286
+ })
287
+
288
+ self._logger.info(
289
+ f"规格生成完成 | 功能点数: {result.feature_count} | "
290
+ f"耗时: {format_duration(duration_ms)}"
291
+ )
292
+
293
+ return result
294
+
295
+ except Exception as e:
296
+ duration_ms = int((time.time() - start_time) * 1000)
297
+ await self._emit_event(Events.REQUEST_ERROR, {
298
+ "method": method, "path": path, "error": str(e),
299
+ "duration_ms": duration_ms
300
+ })
301
+ raise
302
+
303
+ async def generate_code(
304
+ self,
305
+ spec_path: str,
306
+ feature_id: Optional[str] = None,
307
+ output_dir: Optional[str] = None,
308
+ dry_run: bool = False,
309
+ ) -> CodeResult:
310
+ """
311
+ 根据开发规格生成代码
312
+
313
+ 将规格中的功能点转化为可执行的代码实现。
314
+
315
+ Args:
316
+ spec_path: 开发规格文件路径
317
+ feature_id: 指定要生成的功能点 ID(None 则生成全部)
318
+ output_dir: 代码输出目录
319
+ dry_run: 是否仅预览
320
+
321
+ Returns:
322
+ CodeResult 包含生成的文件列表、代码行数等信息
323
+
324
+ Raises:
325
+ ValidationError: 如果规格路径无效
326
+ PDDConnectionError: 连接失败
327
+ ServerError: 服务端错误
328
+
329
+ Example:
330
+ >>> result = await client.generate_code(
331
+ ... spec_path="./specs/user-system.spec.json",
332
+ ... feature_id="F001",
333
+ ... output_dir="./src"
334
+ ... )
335
+ >>> print(f"生成了 {result.file_count} 个文件")
336
+ """
337
+ start_time = time.time()
338
+ method = "POST"
339
+ path = "/api/v1/code/generate"
340
+
341
+ payload = {
342
+ "spec_path": spec_path,
343
+ "dry_run": dry_run,
344
+ }
345
+ if feature_id:
346
+ payload["feature_id"] = feature_id
347
+ if output_dir:
348
+ payload["output_dir"] = output_dir
349
+
350
+ try:
351
+ await self._emit_event(Events.REQUEST_START, {
352
+ "method": method, "path": path, "payload": payload
353
+ })
354
+
355
+ response = await self._request(method, path, payload)
356
+ result = self._parse_code_result(response)
357
+
358
+ duration_ms = int((time.time() - start_time) * 1000)
359
+ result.duration_ms = duration_ms
360
+
361
+ await self._emit_event(Events.REQUEST_END, {
362
+ "method": method,
363
+ "path": path,
364
+ "duration_ms": duration_ms,
365
+ "success": result.success,
366
+ })
367
+
368
+ self._logger.info(
369
+ f"代码生成完成 | 功能点: {result.feature_id} | "
370
+ f"文件数: {result.file_count} | 代码行数: {result.lines_of_code}"
371
+ )
372
+
373
+ return result
374
+
375
+ except Exception as e:
376
+ duration_ms = int((time.time() - start_time) * 1000)
377
+ await self._emit_event(Events.REQUEST_ERROR, {
378
+ "method": method, "path": path, "error": str(e),
379
+ "duration_ms": duration_ms
380
+ })
381
+ raise
382
+
383
+ async def verify_feature(
384
+ self,
385
+ spec_path: str,
386
+ source_dir: str = "./src",
387
+ format: str = "json",
388
+ ) -> VerifyResult:
389
+ """
390
+ 验证功能实现是否符合开发规格
391
+
392
+ 对比源代码和规格定义的验收标准,检查覆盖率。
393
+
394
+ Args:
395
+ spec_path: 开发规格文件路径
396
+ source_dir: 源代码目录
397
+ format: 输出格式 (json/table/markdown)
398
+
399
+ Returns:
400
+ VerifyResult 包含覆盖率、通过/未通过的标准列表等
401
+
402
+ Raises:
403
+ ValidationError: 参数验证失败
404
+ PDDConnectionError: 连接失败
405
+
406
+ Example:
407
+ >>> result = await client.verify_feature(
408
+ ... spec_path="./specs/user-system.spec.json",
409
+ ... source_dir="./src"
410
+ ... )
411
+ >>> print(f"覆盖率: {result.coverage_percent}%")
412
+ """
413
+ start_time = time.time()
414
+ method = "POST"
415
+ path = "/api/v1/verify"
416
+
417
+ payload = {
418
+ "spec_path": spec_path,
419
+ "source_dir": source_dir,
420
+ "format": format,
421
+ }
422
+
423
+ try:
424
+ await self._emit_event(Events.REQUEST_START, {
425
+ "method": method, "path": path, "payload": payload
426
+ })
427
+
428
+ response = await self._request(method, path, payload)
429
+ result = self._parse_verify_result(response)
430
+
431
+ duration_ms = int((time.time() - start_time) * 1000)
432
+ result.duration_ms = duration_ms
433
+
434
+ await self._emit_event(Events.REQUEST_END, {
435
+ "method": method,
436
+ "path": path,
437
+ "duration_ms": duration_ms,
438
+ "success": result.success,
439
+ })
440
+
441
+ self._logger.info(
442
+ f"功能验证完成 | 覆盖率: {result.coverage_percent}% | "
443
+ f"通过: {len(result.criteria_passed)} | 未通过: {len(result.criteria_failed)}"
444
+ )
445
+
446
+ return result
447
+
448
+ except Exception as e:
449
+ duration_ms = int((time.time() - start_time) * 1000)
450
+ await self._emit_event(Events.REQUEST_ERROR, {
451
+ "method": method, "path": path, "error": str(e),
452
+ "duration_ms": duration_ms
453
+ })
454
+ raise
455
+
456
+ async def code_review(
457
+ self,
458
+ source_dir: str = "./src",
459
+ rules: Optional[List[str]] = None,
460
+ format: str = "table",
461
+ ) -> ReviewResult:
462
+ """
463
+ 执行代码审查
464
+
465
+ 对源代码进行静态分析和质量评估。
466
+
467
+ Args:
468
+ source_dir: 待审查的源代码目录
469
+ rules: 指定应用的规则集(None 使用默认规则)
470
+ format: 输出格式 (json/table/markdown)
471
+
472
+ Returns:
473
+ ReviewResult 包含评分、等级、问题列表等
474
+
475
+ Raises:
476
+ ValidationError: 参数验证失败
477
+ PDDConnectionError: 连接失败
478
+
479
+ Example:
480
+ >>> result = await client.code_review(source_dir="./src")
481
+ >>> print(f"评分: {result.score}/100 | 等级: {result.grade}")
482
+ """
483
+ start_time = time.time()
484
+ method = "POST"
485
+ path = "/api/v1/review"
486
+
487
+ payload = {"source_dir": source_dir, "format": format}
488
+ if rules:
489
+ payload["rules"] = rules
490
+
491
+ try:
492
+ await self._emit_event(Events.REQUEST_START, {
493
+ "method": method, "path": path, "payload": payload
494
+ })
495
+
496
+ response = await self._request(method, path, payload)
497
+ result = self._parse_review_result(response)
498
+
499
+ duration_ms = int((time.time() - start_time) * 1000)
500
+ result.duration_ms = duration_ms
501
+
502
+ await self._emit_event(Events.REQUEST_END, {
503
+ "method": method,
504
+ "path": path,
505
+ "duration_ms": duration_ms,
506
+ "success": result.success,
507
+ })
508
+
509
+ self._logger.info(
510
+ f"代码审查完成 | 评分: {result.score}/100 | "
511
+ f"等级: {result.grade} | 问题数: {result.finding_count}"
512
+ )
513
+
514
+ return result
515
+
516
+ except Exception as e:
517
+ duration_ms = int((time.time() - start_time) * 1000)
518
+ await self._emit_event(Events.REQUEST_ERROR, {
519
+ "method": method, "path": path, "error": str(e),
520
+ "duration_ms": duration_ms
521
+ })
522
+ raise
523
+
524
+ async def list_skills(
525
+ self,
526
+ category: Optional[str] = None,
527
+ language: Optional[str] = None,
528
+ ) -> List[SkillInfo]:
529
+ """
530
+ 获取可用技能列表
531
+
532
+ 查询当前 PDD 实例中注册的所有技能信息。
533
+
534
+ Args:
535
+ category: 技能分类过滤(可选)
536
+ language: 编程语言过滤(可选)
537
+
538
+ Returns:
539
+ 技能信息列表
540
+
541
+ Example:
542
+ >>> skills = await client.list_skills(category="analysis")
543
+ >>> for skill in skills:
544
+ ... print(f"{skill.name}: {skill.description}")
545
+ """
546
+ params: Dict[str, str] = {}
547
+ if category:
548
+ params["category"] = category
549
+ if language:
550
+ params["language"] = language
551
+
552
+ query_string = urlencode(params) if params else ""
553
+ path = f"/api/v1/skills{('?'+query_string) if query_string else ''}"
554
+
555
+ response = await self._request("GET", path)
556
+ return self._parse_skills_list(response)
557
+
558
+ async def get_status(self) -> StatusResult:
559
+ """
560
+ 获取服务状态
561
+
562
+ 检查 PDD 服务端的健康状态和运行信息。
563
+
564
+ Returns:
565
+ StatusResult 包含健康状态、版本信息等
566
+
567
+ Example:
568
+ >>> status = await client.get_status()
569
+ >>> print(f"服务{'正常' if status.healthy else '异常'}")
570
+ """
571
+ start_time = time.time()
572
+ path = "/api/v1/status"
573
+
574
+ try:
575
+ response = await self._request("GET", path)
576
+ server_info = self._parse_server_info(response.get("server", {}))
577
+
578
+ duration_ms = int((time.time() - start_time) * 1000)
579
+
580
+ return StatusResult(
581
+ healthy=response.get("healthy", False),
582
+ server_info=server_info,
583
+ response_time_ms=duration_ms,
584
+ )
585
+
586
+ except Exception:
587
+ return StatusResult(
588
+ healthy=False,
589
+ response_time_ms=int((time.time() - start_time) * 1000),
590
+ )
591
+
592
+ # ==================== 批量操作 ====================
593
+
594
+ async def batch_generate_specs(
595
+ self, specs_list: List[Dict[str, Any]]
596
+ ) -> List[SpecResult]:
597
+ """
598
+ 批量生成多个规格
599
+
600
+ 并发执行多个规格生成任务。
601
+
602
+ Args:
603
+ specs_list: 规格参数列表,每个元素包含 generate_spec 所需参数
604
+
605
+ Returns:
606
+ 规格结果列表,顺序与输入一致
607
+
608
+ Example:
609
+ >>> specs = [
610
+ ... {"prd_path": "./prd1.prdx"},
611
+ ... {"prd_path": "./prd2.prdx"},
612
+ ... ]
613
+ >>> results = await client.batch_generate_specs(specs)
614
+ """
615
+ total_start = time.time()
616
+ await self._emit_event(Events.BATCH_START, {
617
+ "total": len(specs_list), "type": "generate_spec"
618
+ })
619
+
620
+ tasks = [self.generate_spec(**params) for params in specs_list]
621
+ results = await asyncio.gather(*tasks, return_exceptions=True)
622
+
623
+ processed_results = []
624
+ for i, result in enumerate(results):
625
+ if isinstance(result, Exception):
626
+ processed_results.append(SpecResult(
627
+ success=False,
628
+ spec_id="",
629
+ spec_path="",
630
+ errors=[str(result)]
631
+ ))
632
+ else:
633
+ processed_results.append(result)
634
+
635
+ await self._emit_event(Events.BATCH_PROGRESS, {
636
+ "current": i + 1,
637
+ "total": len(specs_list),
638
+ })
639
+
640
+ total_duration = int((time.time() - total_start) * 1000)
641
+ await self._emit_event(Events.BATCH_COMPLETE, {
642
+ "total": len(specs_list),
643
+ "duration_ms": total_duration,
644
+ })
645
+
646
+ return processed_results
647
+
648
+ async def batch_verify(self, features_list: List[Dict[str, Any]]) -> List[VerifyResult]:
649
+ """
650
+ 批量验证多个功能
651
+
652
+ 并发执行多个功能验证任务。
653
+
654
+ Args:
655
+ features_list: 验证参数列表
656
+
657
+ Returns:
658
+ 验证结果列表
659
+ """
660
+ tasks = [
661
+ self.verify_feature(**params) for params in features_list
662
+ ]
663
+ results = await asyncio.gather(*tasks, return_exceptions=True)
664
+
665
+ processed_results = []
666
+ for result in results:
667
+ if isinstance(result, Exception):
668
+ processed_results.append(VerifyResult(
669
+ success=False,
670
+ errors=[str(result)]
671
+ ))
672
+ else:
673
+ processed_results.append(result)
674
+
675
+ return processed_results
676
+
677
+ # ==================== 会话管理 ====================
678
+
679
+ async def create_session(self, name: Optional[str] = None) -> Session:
680
+ """
681
+ 创建新的开发会话
682
+
683
+ 会话用于组织相关的规格和代码生成任务。
684
+
685
+ Args:
686
+ name: 会话名称(可选,自动生成唯一名称)
687
+
688
+ Returns:
689
+ 新创建的会话对象
690
+
691
+ Example:
692
+ >>> session = await client.create_session(name="用户模块开发")
693
+ >>> print(f"会话ID: {session.session_id}")
694
+ """
695
+ payload: Dict[str, Any] = {}
696
+ if name:
697
+ payload["name"] = name
698
+
699
+ response = await self._request("POST", "/api/v1/sessions", payload)
700
+ session_data = response.get("session", {})
701
+ session = self._parse_session(session_data)
702
+
703
+ await self._emit_event(Events.SESSION_CREATED, {
704
+ "session_id": session.session_id,
705
+ "name": session.name,
706
+ })
707
+
708
+ self._session_id = session.session_id
709
+ return session
710
+
711
+ async def get_session(self, session_id: str) -> Session:
712
+ """
713
+ 获取会话详情
714
+
715
+ Args:
716
+ session_id: 会话 ID
717
+
718
+ Returns:
719
+ 会话对象
720
+
721
+ Raises:
722
+ PDDError: 如果会话不存在
723
+ """
724
+ response = await self._request("GET", f"/api/v1/sessions/{session_id}")
725
+ return self._parse_session(response.get("session", {}))
726
+
727
+ async def list_sessions(self) -> List[Session]:
728
+ """
729
+ 列出所有会话
730
+
731
+ Returns:
732
+ 会话列表
733
+ """
734
+ response = await self._request("GET", "/api/v1/sessions")
735
+ sessions_data = response.get("sessions", [])
736
+ return [self._parse_session(s) for s in sessions_data]
737
+
738
+ # ==================== 工具方法 ====================
739
+
740
+ def health_check(self) -> bool:
741
+ """
742
+ 同步健康检查
743
+
744
+ 快速检测服务端是否可达。此方法是同步的,适合在非异步上下文中使用。
745
+
746
+ Returns:
747
+ True 如果服务端健康,False 否则
748
+
749
+ Example:
750
+ >>> if client.health_check():
751
+ ... print("服务可用")
752
+ """
753
+ try:
754
+ url = f"{self.endpoint}/health"
755
+ req = Request(url, method="GET")
756
+
757
+ with urlopen(req, timeout=min(self.timeout, 5)) as resp:
758
+ data = json.loads(resp.read().decode())
759
+ return data.get("status") == "ok"
760
+ except Exception as e:
761
+ self._logger.debug(f"健康检查失败: {e}")
762
+ return False
763
+
764
+ async def async_health_check(self) -> bool:
765
+ """
766
+ 异步健康检查
767
+
768
+ Returns:
769
+ True 如果服务端健康
770
+ """
771
+ loop = asyncio.get_event_loop()
772
+ return await loop.run_in_executor(None, self.health_check)
773
+
774
+ def get_server_info(self) -> Optional[ServerInfo]:
775
+ """
776
+ 同步获取服务器信息
777
+
778
+ Returns:
779
+ ServerInfo 或 None(如果无法连接)
780
+ """
781
+ try:
782
+ url = f"{self.endpoint}/api/v1/status"
783
+ req = Request(url, method="GET")
784
+ self._add_headers(req)
785
+
786
+ with urlopen(req, timeout=self.timeout) as resp:
787
+ data = json.loads(resp.read().decode())
788
+ return self._parse_server_info(data.get("server", {}))
789
+ except Exception as e:
790
+ self._logger.debug(f"获取服务器信息失败: {e}")
791
+ return None
792
+
793
+ @property
794
+ def request_stats(self) -> Dict[str, Any]:
795
+ """
796
+ 获取请求统计信息
797
+
798
+ Returns:
799
+ 包含请求数、最后请求时间等的字典
800
+ """
801
+ return {
802
+ "total_requests": self._request_count,
803
+ "last_request_time": self._last_request_time,
804
+ "endpoint": self.endpoint,
805
+ "has_active_session": self._session_id is not None,
806
+ }
807
+
808
+ async def close(self) -> None:
809
+ """
810
+ 关闭客户端,释放资源
811
+
812
+ 清理缓存、移除所有事件监听器。
813
+ 通常在程序退出前调用。
814
+ """
815
+ self._events.remove_all_listeners()
816
+ self._logger.info("客户端已关闭")
817
+
818
+ # ==================== 内部方法 ====================
819
+
820
+ async def _request(
821
+ self,
822
+ method: str,
823
+ path: str,
824
+ data: Optional[Dict[str, Any]] = None,
825
+ ) -> Dict[str, Any]:
826
+ """
827
+ 发送 HTTP 请求(内部方法)
828
+
829
+ 使用线程池执行同步 HTTP 请求,模拟异步行为。
830
+
831
+ Args:
832
+ method: HTTP 方法 (GET/POST/PUT/DELETE)
833
+ path: API 路径
834
+ data: 请求数据(仅 POST/PUT 时使用)
835
+
836
+ Returns:
837
+ 解析后的 JSON 响应数据
838
+
839
+ Raises:
840
+ PDDConnectionError: 连接失败
841
+ AuthError: 认证失败 (401)
842
+ ValidationError: 参数错误 (400)
843
+ ServerError: 服务端错误 (5xx)
844
+ PDDTimeoutError: 超时
845
+ RateLimitError: 限流 (429)
846
+ """
847
+ self._request_count += 1
848
+ self._last_request_time = time.time()
849
+
850
+ # 在线程池中执行同步 HTTP 请求
851
+ loop = asyncio.get_event_loop()
852
+ response = await loop.run_in_executor(
853
+ None,
854
+ lambda: self._sync_request(method, path, data)
855
+ )
856
+
857
+ return response
858
+
859
+ def _sync_request(
860
+ self,
861
+ method: str,
862
+ path: str,
863
+ data: Optional[Dict[str, Any]] = None,
864
+ ) -> Dict[str, Any]:
865
+ """
866
+ 同步 HTTP 请求实现(内部方法)
867
+
868
+ 使用标准库 urllib 实现 HTTP 通信。
869
+
870
+ Args:
871
+ method: HTTP 方法
872
+ path: API 路径
873
+ data: 请求数据
874
+
875
+ Returns:
876
+ JSON 响应数据
877
+ """
878
+ url = f"{self.endpoint}{path}"
879
+ body = None
880
+
881
+ if data and method in ("POST", "PUT"):
882
+ body = json.dumps(data).encode("utf-8")
883
+
884
+ req = Request(url, data=body, method=method)
885
+ self._add_headers(req)
886
+
887
+ try:
888
+ self._logger.debug(f"发送请求: {method} {url}")
889
+
890
+ with urlopen(req, timeout=self.timeout) as resp:
891
+ response_data = resp.read()
892
+ status_code = resp.status if hasattr(resp, 'status') else 200
893
+
894
+ self._logger.debug(
895
+ f"收到响应: {status_code} | 大小: {len(response_data)} bytes"
896
+ )
897
+
898
+ result = json.loads(response_data.decode())
899
+
900
+ # 检查业务层错误
901
+ if not result.get("success", True):
902
+ error_msg = result.get("message", "未知错误")
903
+ error_code = result.get("code")
904
+ raise self._map_error(error_msg, error_code, status_code)
905
+
906
+ return result
907
+
908
+ except HTTPError as e:
909
+ error_body = {}
910
+ try:
911
+ error_body = json.loads(e.read().decode())
912
+ except Exception:
913
+ pass
914
+
915
+ status_code = e.code
916
+ raise self._map_error(
917
+ error_body.get("message", str(e)),
918
+ error_body.get("code"),
919
+ status_code
920
+ )
921
+
922
+ except URLError as e:
923
+ raise PDDConnectionError(
924
+ message=f"无法连接到服务端: {e.reason}",
925
+ details={"endpoint": self.endpoint, "reason": str(e.reason)}
926
+ )
927
+
928
+ except TimeoutError as e:
929
+ raise PDDTimeoutError(
930
+ message=f"请求超时 ({self.timeout}s)",
931
+ details={"timeout": self.timeout}
932
+ )
933
+
934
+ def _add_headers(self, req: Request) -> None:
935
+ """
936
+ 添加 HTTP 请求头
937
+
938
+ Args:
939
+ req: urllib Request 对象
940
+ """
941
+ req.add_header("Content-Type", "application/json; charset=utf-8")
942
+ req.add_header("Accept", "application/json")
943
+ req.add_header("User-Agent", "PDD-Python-SDK/1.0.0")
944
+
945
+ if self.api_key:
946
+ req.add_header("Authorization", f"Bearer {self.api_key}")
947
+
948
+ if self._session_id:
949
+ req.add_header("X-Session-ID", self._session_id)
950
+
951
+ def _map_error(
952
+ self,
953
+ message: str,
954
+ code: Optional[str],
955
+ status_code: int,
956
+ ) -> PDDError:
957
+ """
958
+ 将 HTTP 错误映射为 SDK 异常
959
+
960
+ Args:
961
+ message: 错误消息
962
+ code: 错误代码
963
+ status_code: HTTP 状态码
964
+
965
+ Returns:
966
+ 对应类型的 PDDError 子类
967
+ """
968
+ if status_code == 401 or code == "AUTH_ERROR":
969
+ return AuthError(message=message, details={"status_code": status_code})
970
+ elif status_code == 400 or code == "VALIDATION_ERROR":
971
+ return ValidationError(message=message, details={"status_code": status_code})
972
+ elif status_code == 429 or code == "RATE_LIMIT_ERROR":
973
+ return RateLimitError(message=message, details={"status_code": status_code})
974
+ elif status_code >= 500:
975
+ return ServerError(message=message, details={"status_code": status_code})
976
+ else:
977
+ return PDDError(message=message, code=code, details={"status_code": status_code})
978
+
979
+ async def _emit_event(self, event_name: str, data: Dict[str, Any]) -> None:
980
+ """内部方法:触发事件"""
981
+ try:
982
+ await self._events.async_emit(event_name, **data)
983
+ except Exception as e:
984
+ self._logger.debug(f"事件触发失败 ({event_name}): {e}")
985
+
986
+ # ==================== 响应解析方法 ====================
987
+
988
+ @staticmethod
989
+ def _parse_spec_result(data: Dict[str, Any]) -> SpecResult:
990
+ """解析规格生成响应"""
991
+ raw_features = data.get("features", [])
992
+ features = []
993
+ for f in raw_features:
994
+ if isinstance(f, dict):
995
+ features.append({
996
+ "id": f.get("id", ""),
997
+ "name": f.get("name", ""),
998
+ "description": f.get("description", ""),
999
+ })
1000
+ elif isinstance(f, str):
1001
+ features.append({"id": f, "name": f})
1002
+
1003
+ return SpecResult(
1004
+ success=data.get("success", False),
1005
+ spec_id=data.get("spec_id", ""),
1006
+ spec_path=data.get("spec_path", ""),
1007
+ features=features,
1008
+ warnings=data.get("warnings", []),
1009
+ errors=data.get("errors", []),
1010
+ metadata=data.get("metadata", {}),
1011
+ )
1012
+
1013
+ @staticmethod
1014
+ def _parse_code_result(data: Dict[str, Any]) -> CodeResult:
1015
+ """解析代码生成响应"""
1016
+ raw_files = data.get("files_generated", [])
1017
+ files = []
1018
+ for f in raw_files:
1019
+ if isinstance(f, dict):
1020
+ files.append(GeneratedFile(
1021
+ path=f.get("path", ""),
1022
+ absolute_path=f.get("absolute_path", ""),
1023
+ lines_of_code=f.get("lines_of_code", 0),
1024
+ language=f.get("language", ""),
1025
+ size_bytes=f.get("size_bytes", 0),
1026
+ ))
1027
+ elif isinstance(f, str):
1028
+ files.append(GeneratedFile(path=f))
1029
+
1030
+ return CodeResult(
1031
+ success=data.get("success", False),
1032
+ feature_id=data.get("feature_id", ""),
1033
+ files_generated=files,
1034
+ lines_of_code=data.get("lines_of_code", 0),
1035
+ warnings=data.get("warnings", []),
1036
+ errors=data.get("errors", []),
1037
+ )
1038
+
1039
+ @staticmethod
1040
+ def _parse_verify_result(data: Dict[str, Any]) -> VerifyResult:
1041
+ """解析验证响应"""
1042
+ raw_issues = data.get("issues", [])
1043
+ issues = []
1044
+ for issue in raw_issues:
1045
+ severity_val = issue.get("severity", "error")
1046
+ severity = next(
1047
+ (s for s in Severity if s.value == severity_val), Severity.ERROR
1048
+ )
1049
+ issues.append(VerifyIssue(
1050
+ file_path=issue.get("file_path", ""),
1051
+ message=issue.get("message", ""),
1052
+ severity=severity,
1053
+ line_number=issue.get("line_number"),
1054
+ suggestion=issue.get("suggestion", ""),
1055
+ ))
1056
+
1057
+ return VerifyResult(
1058
+ success=data.get("success", False),
1059
+ coverage_percent=float(data.get("coverage_percent", 0)),
1060
+ criteria_passed=data.get("criteria_passed", []),
1061
+ criteria_failed=data.get("criteria_failed", []),
1062
+ issues=issues,
1063
+ summary=data.get("summary", ""),
1064
+ )
1065
+
1066
+ @staticmethod
1067
+ def _parse_review_result(data: Dict[str, Any]) -> ReviewResult:
1068
+ """解析代码审查响应"""
1069
+ raw_findings = data.get("findings", [])
1070
+ findings = []
1071
+ for f in raw_findings:
1072
+ severity_val = f.get("severity", "error")
1073
+ severity = next(
1074
+ (s for s in Severity if s.value == severity_val), Severity.ERROR
1075
+ )
1076
+ findings.append(ReviewFinding(
1077
+ rule_id=f.get("rule_id", ""),
1078
+ category=f.get("category", "general"),
1079
+ severity=severity,
1080
+ message=f.get("message", ""),
1081
+ file_path=f.get("file_path", ""),
1082
+ line_number=f.get("line_number"),
1083
+ suggestion=f.get("suggestion", ""),
1084
+ ))
1085
+
1086
+ return ReviewResult(
1087
+ success=data.get("success", False),
1088
+ score=float(data.get("score", 0)),
1089
+ grade=data.get("grade", ""),
1090
+ findings=findings,
1091
+ metrics=data.get("metrics", {}),
1092
+ summary=data.get("summary", ""),
1093
+ )
1094
+
1095
+ @staticmethod
1096
+ def _parse_skills_list(data: Union[List, Dict]) -> List[SkillInfo]:
1097
+ """解析技能列表响应"""
1098
+ items = data if isinstance(data, list) else data.get("skills", [])
1099
+ skills = []
1100
+ for item in items:
1101
+ skills.append(SkillInfo(
1102
+ name=item.get("name", ""),
1103
+ version=item.get("version", "1.0.0"),
1104
+ description=item.get("description", ""),
1105
+ category=item.get("category", "general"),
1106
+ triggers=item.get("triggers", []),
1107
+ has_evals=item.get("has_evals", False),
1108
+ author=item.get("author"),
1109
+ tags=item.get("tags", []),
1110
+ ))
1111
+ return skills
1112
+
1113
+ @staticmethod
1114
+ def _parse_server_info(data: Dict[str, Any]) -> ServerInfo:
1115
+ """解析服务器信息"""
1116
+ return ServerInfo(
1117
+ version=data.get("version", "unknown"),
1118
+ uptime=float(data.get("uptime", 0)),
1119
+ supported_apis=data.get("supported_apis", []),
1120
+ available_skills=int(data.get("available_skills", 0)),
1121
+ system_info=data.get("system_info", {}),
1122
+ )
1123
+
1124
+ @staticmethod
1125
+ def _parse_session(data: Dict[str, Any]) -> Session:
1126
+ """解析会话数据"""
1127
+ from datetime import datetime
1128
+
1129
+ created_at_str = data.get("created_at")
1130
+ updated_at_str = data.get("updated_at")
1131
+
1132
+ try:
1133
+ created_at = datetime.fromisoformat(created_at_str) if created_at_str else datetime.now()
1134
+ except (ValueError, TypeError):
1135
+ created_at = datetime.now()
1136
+
1137
+ try:
1138
+ updated_at = datetime.fromisoformat(updated_at_str) if updated_at_str else datetime.now()
1139
+ except (ValueError, TypeError):
1140
+ updated_at = datetime.now()
1141
+
1142
+ status_val = data.get("status", "pending")
1143
+ status = next(
1144
+ (s for s in TaskStatus if s.value == status_val), TaskStatus.PENDING
1145
+ )
1146
+
1147
+ return Session(
1148
+ session_id=data.get("session_id", ""),
1149
+ name=data.get("name", ""),
1150
+ created_at=created_at,
1151
+ updated_at=updated_at,
1152
+ status=status,
1153
+ specs=data.get("specs", []),
1154
+ metadata=data.get("metadata", {}),
1155
+ )
1156
+
1157
+ def __repr__(self) -> str:
1158
+ """返回对象的字符串表示"""
1159
+ return (
1160
+ f"PDDClient(endpoint='{self.endpoint}', "
1161
+ f"debug={self.debug}, retries={self.max_retries})"
1162
+ )
1163
+
1164
+ async def __aenter__(self) -> "PDDClient":
1165
+ """异步上下文管理器入口"""
1166
+ return self
1167
+
1168
+ async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
1169
+ """异步上下文管理器出口"""
1170
+ await self.close()