@hupan56/wlkj 2.7.12 → 3.1.2

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 (253) hide show
  1. package/bin/cli.js +1435 -1107
  2. package/package.json +29 -29
  3. package/templates/qoder/agents/insight-planning.md +1 -1
  4. package/templates/qoder/agents/insight-research.md +28 -15
  5. package/templates/qoder/commands/optional/wl-insight.md +161 -161
  6. package/templates/qoder/commands/optional/wl-report.md +20 -39
  7. package/templates/qoder/commands/optional/wl-status.md +4 -4
  8. package/templates/qoder/commands/wl-code.md +2 -2
  9. package/templates/qoder/commands/wl-design.md +6 -6
  10. package/templates/qoder/commands/wl-init.md +3 -3
  11. package/templates/qoder/commands/wl-prd.md +26 -13
  12. package/templates/qoder/commands/wl-req.md +43 -0
  13. package/templates/qoder/commands/wl-search.md +78 -47
  14. package/templates/qoder/commands/wl-task.md +343 -40
  15. package/templates/qoder/commands/wl-test.md +43 -17
  16. package/templates/qoder/config.yaml +52 -14
  17. package/templates/qoder/hooks/post-tool-use.py +181 -0
  18. package/templates/qoder/hooks/session-start.py +94 -23
  19. package/templates/qoder/hooks/stop-eval.py +207 -0
  20. package/templates/qoder/hooks/user-prompt-submit.py +139 -0
  21. package/templates/qoder/rules/wl-pipeline.md +85 -49
  22. package/templates/qoder/scripts/README.md +102 -0
  23. package/templates/qoder/scripts/capability/__init__.py +26 -0
  24. package/templates/qoder/scripts/capability/__main__.py +72 -0
  25. package/templates/qoder/scripts/capability/adapters/__init__.py +29 -0
  26. package/templates/qoder/scripts/capability/adapters/cli.py +316 -0
  27. package/templates/qoder/scripts/capability/adapters/mcp.py +334 -0
  28. package/templates/qoder/scripts/capability/adapters/qw.py +271 -0
  29. package/templates/qoder/scripts/capability/caps/__init__.py +27 -0
  30. package/templates/qoder/scripts/capability/caps/context.py +36 -0
  31. package/templates/qoder/scripts/capability/caps/cron.py +50 -0
  32. package/templates/qoder/scripts/capability/caps/identity.py +43 -0
  33. package/templates/qoder/scripts/capability/caps/memory.py +71 -0
  34. package/templates/qoder/scripts/capability/caps/notify.py +41 -0
  35. package/templates/qoder/scripts/capability/caps/present.py +48 -0
  36. package/templates/qoder/scripts/capability/caps/repo.py +31 -0
  37. package/templates/qoder/scripts/capability/caps/sandbox.py +43 -0
  38. package/templates/qoder/scripts/capability/chain.py +92 -0
  39. package/templates/qoder/scripts/capability/memory_chain.py +41 -0
  40. package/templates/qoder/scripts/capability/registry.py +246 -0
  41. package/templates/qoder/scripts/capability/registry_mcp.py +250 -0
  42. package/templates/qoder/scripts/capability/smoke_test.py +211 -0
  43. package/templates/qoder/scripts/capability/smoke_test_report.json +94 -0
  44. package/templates/qoder/scripts/deployment/setup/__init__.py +11 -0
  45. package/templates/qoder/scripts/deployment/setup/carriers.py +669 -0
  46. package/templates/qoder/scripts/deployment/setup/carriers_verify.py +148 -0
  47. package/templates/qoder/scripts/{init_doctor.py → deployment/setup/init_doctor.py} +100 -39
  48. package/templates/qoder/scripts/{install_qoderwork.py → deployment/setup/install_qoderwork.py} +48 -30
  49. package/templates/qoder/scripts/{platform_doctor.py → deployment/setup/platform_doctor.py} +271 -259
  50. package/templates/qoder/scripts/{repo_root.py → deployment/setup/repo_root.py} +9 -2
  51. package/templates/qoder/scripts/{setup.py → deployment/setup/setup.py} +143 -14
  52. package/templates/qoder/scripts/{setup_lanhu.py → deployment/setup/setup_lanhu.py} +979 -963
  53. package/templates/qoder/scripts/domain/design/__init__.py +0 -0
  54. package/templates/qoder/scripts/{fill_prototype.py → domain/design/fill_prototype.py} +17 -5
  55. package/templates/qoder/scripts/{gen_design_doc.py → domain/design/gen_design_doc.py} +406 -394
  56. package/templates/qoder/scripts/domain/kg/__init__.py +11 -0
  57. package/templates/qoder/scripts/domain/kg/build/__init__.py +0 -0
  58. package/templates/qoder/scripts/domain/kg/build/build_entity_registry.py +201 -0
  59. package/templates/qoder/scripts/domain/kg/build/build_relations.py +132 -0
  60. package/templates/qoder/scripts/{build_style_index.py → domain/kg/build/build_style_index.py} +50 -15
  61. package/templates/qoder/scripts/domain/kg/build/build_workflows.py +149 -0
  62. package/templates/qoder/scripts/{kg_build.py → domain/kg/build/kg_build.py} +685 -612
  63. package/templates/qoder/scripts/{kg_build_db.py → domain/kg/build/kg_build_db.py} +338 -327
  64. package/templates/qoder/scripts/{kg_incremental.py → domain/kg/build/kg_incremental.py} +425 -393
  65. package/templates/qoder/scripts/{learn_aggregate.py → domain/kg/build/learn_aggregate.py} +212 -201
  66. package/templates/qoder/scripts/domain/kg/extract/__init__.py +0 -0
  67. package/templates/qoder/scripts/{common → domain/kg/extract}/extract.py +430 -419
  68. package/templates/qoder/scripts/domain/kg/extract/test_extract.py +115 -0
  69. package/templates/qoder/scripts/{common → domain/kg/extract}/ts_extract.py +614 -536
  70. package/templates/qoder/scripts/domain/kg/graph/__init__.py +0 -0
  71. package/templates/qoder/scripts/{kg_link_db.py → domain/kg/graph/kg_link_db.py} +235 -224
  72. package/templates/qoder/scripts/domain/kg/graph/kg_semantic.py +683 -0
  73. package/templates/qoder/scripts/{kg.py → domain/kg/kg.py} +871 -708
  74. package/templates/qoder/scripts/domain/kg/kg_capabilities.py +182 -0
  75. package/templates/qoder/scripts/domain/kg/search/__init__.py +0 -0
  76. package/templates/qoder/scripts/{context_pack.py → domain/kg/search/context_pack.py} +45 -17
  77. package/templates/qoder/scripts/{enrich_prompt.py → domain/kg/search/enrich_prompt.py} +238 -226
  78. package/templates/qoder/scripts/domain/kg/search/prefetch.py +384 -0
  79. package/templates/qoder/scripts/{common → domain/kg/search}/search_engine.py +207 -205
  80. package/templates/qoder/scripts/{search_index.py → domain/kg/search/search_index.py} +210 -88
  81. package/templates/qoder/scripts/domain/kg/server/__init__.py +0 -0
  82. package/templates/qoder/scripts/domain/kg/server/kgd.py +549 -0
  83. package/templates/qoder/scripts/domain/kg/server/perf_bench.py +197 -0
  84. package/templates/qoder/scripts/domain/kg/storage/__init__.py +0 -0
  85. package/templates/qoder/scripts/{kg_duckdb.py → domain/kg/storage/kg_duckdb.py} +70 -45
  86. package/templates/qoder/scripts/domain/learning/__init__.py +0 -0
  87. package/templates/qoder/scripts/{learn.py → domain/learning/learn.py} +157 -146
  88. package/templates/qoder/scripts/domain/report/__init__.py +11 -0
  89. package/templates/qoder/scripts/{add_session.py → domain/report/add_session.py} +256 -244
  90. package/templates/qoder/scripts/{export.py → domain/report/export.py} +72 -4
  91. package/templates/qoder/scripts/{report.py → domain/report/report.py} +292 -281
  92. package/templates/qoder/scripts/domain/report/report_snapshot.py +261 -0
  93. package/templates/qoder/scripts/domain/report/role.py +39 -0
  94. package/templates/qoder/scripts/{status.py → domain/report/status.py} +737 -628
  95. package/templates/qoder/scripts/domain/report/status_snapshot.py +191 -0
  96. package/templates/qoder/scripts/domain/requirement/__init__.py +0 -0
  97. package/templates/qoder/scripts/{archive_prd.py → domain/requirement/archive_prd.py} +389 -377
  98. package/templates/qoder/scripts/domain/requirement/req.py +228 -0
  99. package/templates/qoder/scripts/domain/task/__init__.py +11 -0
  100. package/templates/qoder/scripts/{git_sync.py → domain/task/git_sync.py} +90 -50
  101. package/templates/qoder/scripts/{syncgate.py → domain/task/syncgate.py} +18 -7
  102. package/templates/qoder/scripts/domain/task/task.py +229 -0
  103. package/templates/qoder/scripts/domain/task/task_lifecycle.py +606 -0
  104. package/templates/qoder/scripts/domain/task/task_query.py +162 -0
  105. package/templates/qoder/scripts/domain/task/task_relations.py +425 -0
  106. package/templates/qoder/scripts/{team_sync.py → domain/task/team_sync.py} +101 -23
  107. package/templates/qoder/scripts/domain/task/zentao_sync.py +370 -0
  108. package/templates/qoder/scripts/foundation/__init__.py +0 -0
  109. package/templates/qoder/scripts/foundation/bootstrap.py +145 -0
  110. package/templates/qoder/scripts/foundation/core/__init__.py +0 -0
  111. package/templates/qoder/scripts/foundation/core/cmd_registry.py +112 -0
  112. package/templates/qoder/scripts/foundation/core/config.py +187 -0
  113. package/templates/qoder/scripts/{common → foundation/core}/paths.py +706 -400
  114. package/templates/qoder/scripts/foundation/data/__init__.py +0 -0
  115. package/templates/qoder/scripts/foundation/data/contract.py +317 -0
  116. package/templates/qoder/scripts/{common → foundation/data}/events.py +2 -2
  117. package/templates/qoder/scripts/foundation/data/result.py +223 -0
  118. package/templates/qoder/scripts/{common → foundation/data}/task_utils.py +427 -392
  119. package/templates/qoder/scripts/foundation/identity/__init__.py +0 -0
  120. package/templates/qoder/scripts/foundation/identity/check_publish.py +103 -0
  121. package/templates/qoder/scripts/{common → foundation/identity}/developer.py +239 -238
  122. package/templates/qoder/scripts/foundation/identity/guard.py +159 -0
  123. package/templates/qoder/scripts/{common → foundation/identity}/identity.py +132 -9
  124. package/templates/qoder/scripts/foundation/identity/roles.py +60 -0
  125. package/templates/qoder/scripts/foundation/integrations/__init__.py +0 -0
  126. package/templates/qoder/scripts/{common → foundation/integrations}/active_task.py +30 -15
  127. package/templates/qoder/scripts/{common → foundation/integrations}/feishu.py +11 -10
  128. package/templates/qoder/scripts/{common → foundation/integrations}/terms.py +3 -3
  129. package/templates/qoder/scripts/foundation/integrations/zentao_client.py +220 -0
  130. package/templates/qoder/scripts/foundation/io/__init__.py +0 -0
  131. package/templates/qoder/scripts/{common → foundation/io}/filelock.py +1 -1
  132. package/templates/qoder/scripts/{common → foundation/io}/reqid.py +1 -1
  133. package/templates/qoder/scripts/foundation/protocol/__init__.py +0 -0
  134. package/templates/qoder/scripts/foundation/protocol/duckdb_conn.py +39 -0
  135. package/templates/qoder/scripts/foundation/protocol/mcp_base.py +268 -0
  136. package/templates/qoder/scripts/foundation/utils/__init__.py +0 -0
  137. package/templates/qoder/scripts/{common → foundation/utils}/eval_api.py +12 -1
  138. package/templates/qoder/scripts/{common → foundation/utils}/pip_install.py +23 -6
  139. package/templates/qoder/scripts/{common → foundation/utils}/platform_guard.py +1 -1
  140. package/templates/qoder/scripts/orchestration/__init__.py +0 -0
  141. package/templates/qoder/scripts/orchestration/wlkj.py +185 -0
  142. package/templates/qoder/scripts/protocol/__init__.py +0 -0
  143. package/templates/qoder/scripts/protocol/browser/README.md +207 -0
  144. package/templates/qoder/scripts/protocol/browser/SKILL.md +265 -0
  145. package/templates/qoder/scripts/protocol/browser/config.env +9 -0
  146. package/templates/qoder/scripts/protocol/browser/references/cdp-api.md +110 -0
  147. package/templates/qoder/scripts/protocol/browser/references/migration-2.5.3.md +72 -0
  148. package/templates/qoder/scripts/protocol/browser/references/site-patterns/.gitkeep +0 -0
  149. package/templates/qoder/scripts/protocol/browser/scripts/browser-discovery.mjs +138 -0
  150. package/templates/qoder/scripts/protocol/browser/scripts/cdp-proxy.mjs +672 -0
  151. package/templates/qoder/scripts/protocol/browser/scripts/check-deps.mjs +206 -0
  152. package/templates/qoder/scripts/protocol/browser/scripts/find-url.mjs +253 -0
  153. package/templates/qoder/scripts/protocol/browser/scripts/match-site.mjs +46 -0
  154. package/templates/qoder/scripts/protocol/browser/templates/config.env.template +9 -0
  155. package/templates/qoder/scripts/protocol/mcp/__init__.py +11 -0
  156. package/templates/qoder/scripts/protocol/mcp/kg_mcp_server.py +478 -0
  157. package/templates/qoder/scripts/{lanhu_stdio_wrapper.py → protocol/mcp/lanhu_stdio_wrapper.py} +131 -119
  158. package/templates/qoder/scripts/protocol/mcp/mcp_doctor.py +548 -0
  159. package/templates/qoder/scripts/protocol/mcp/mcp_launcher.py +600 -0
  160. package/templates/qoder/scripts/protocol/mcp/mysql_mcp_server.py +461 -0
  161. package/templates/qoder/scripts/protocol/mcp/zentao_mcp_server.py +1920 -0
  162. package/templates/qoder/scripts/protocol/transports/__init__.py +56 -0
  163. package/templates/qoder/scripts/protocol/transports/base.py +87 -0
  164. package/templates/qoder/scripts/protocol/transports/cli.py +132 -0
  165. package/templates/qoder/scripts/protocol/transports/http.py +141 -0
  166. package/templates/qoder/scripts/protocol/transports/stdio.py +293 -0
  167. package/templates/qoder/scripts/run_weekly_update.bat +27 -18
  168. package/templates/qoder/scripts/run_weekly_update.sh +22 -13
  169. package/templates/qoder/scripts/validation/__init__.py +0 -0
  170. package/templates/qoder/scripts/validation/eval/__init__.py +0 -0
  171. package/templates/qoder/scripts/validation/eval/perf-report-2026-06-24.md +139 -0
  172. package/templates/qoder/scripts/validation/eval/qwork_harness.py +617 -0
  173. package/templates/qoder/scripts/validation/eval/report-commands.md +220 -0
  174. package/templates/qoder/scripts/validation/eval/transcript_timing.py +386 -0
  175. package/templates/qoder/scripts/validation/metrics/__init__.py +0 -0
  176. package/templates/qoder/scripts/{eval_prd.py → validation/metrics/eval_prd.py} +84 -15
  177. package/templates/qoder/scripts/validation/metrics/usability_score.py +750 -0
  178. package/templates/qoder/scripts/validation/test/__init__.py +11 -0
  179. package/templates/qoder/scripts/validation/test/assertion_gen.py +551 -0
  180. package/templates/qoder/scripts/{autotest.py → validation/test/autotest.py} +1234 -1751
  181. package/templates/qoder/scripts/validation/test/autotest_auth.py +109 -0
  182. package/templates/qoder/scripts/{autotest_batch.py → validation/test/autotest_batch.py} +255 -224
  183. package/templates/qoder/scripts/validation/test/autotest_data.py +680 -0
  184. package/templates/qoder/scripts/validation/test/autotest_opencli.py +646 -0
  185. package/templates/qoder/scripts/{autotest_run.py → validation/test/autotest_run.py} +323 -297
  186. package/templates/qoder/scripts/validation/test/autotest_webaccess.py +571 -0
  187. package/templates/qoder/scripts/{benchmark.py → validation/test/benchmark.py} +17 -5
  188. package/templates/qoder/scripts/{kg_auto_login.py → validation/test/kg_auto_login.py} +208 -196
  189. package/templates/qoder/scripts/{kg_test_runner.py → validation/test/kg_test_runner.py} +13 -2
  190. package/templates/qoder/scripts/{page_probe.py → validation/test/page_probe.py} +470 -459
  191. package/templates/qoder/scripts/validation/test/smoke_all_commands.py +170 -0
  192. package/templates/qoder/settings.json +23 -2
  193. package/templates/qoder/skills/design-import/SKILL.md +235 -226
  194. package/templates/qoder/skills/design-review/SKILL.md +91 -82
  195. package/templates/qoder/skills/prd-generator/SKILL.md +38 -24
  196. package/templates/qoder/skills/prd-review/SKILL.md +8 -6
  197. package/templates/qoder/skills/prototype-generator/SKILL.md +296 -247
  198. package/templates/qoder/skills/spec-coder/SKILL.md +24 -15
  199. package/templates/qoder/skills/spec-generator/SKILL.md +24 -12
  200. package/templates/qoder/skills/test-generator/SKILL.md +9 -7
  201. package/templates/qoder/skills/wl-code/SKILL.md +25 -13
  202. package/templates/qoder/skills/wl-commit/SKILL.md +10 -9
  203. package/templates/qoder/skills/wl-design/SKILL.md +7 -5
  204. package/templates/qoder/skills/wl-init/SKILL.md +8 -8
  205. package/templates/qoder/skills/wl-insight/SKILL.md +25 -12
  206. package/templates/qoder/skills/wl-prd-full/SKILL.md +59 -8
  207. package/templates/qoder/skills/wl-prd-quick/SKILL.md +7 -7
  208. package/templates/qoder/skills/wl-prd-review/SKILL.md +18 -6
  209. package/templates/qoder/skills/wl-report/SKILL.md +27 -25
  210. package/templates/qoder/skills/wl-search/SKILL.md +151 -80
  211. package/templates/qoder/skills/wl-spec/SKILL.md +9 -7
  212. package/templates/qoder/skills/wl-status/SKILL.md +50 -18
  213. package/templates/qoder/skills/wl-task/SKILL.md +94 -12
  214. package/templates/qoder/skills/wl-test/SKILL.md +154 -45
  215. package/templates/qoder/templates/prd-full-template.md +7 -0
  216. package/templates/root/AGENTS.md +275 -237
  217. package/templates/root/requirements.txt +3 -0
  218. package/templates/root//344/275/277/347/224/250/350/257/264/346/230/216.md +3 -3
  219. package/templates/root//346/226/260/346/211/213/346/214/207/345/215/227.md +2 -2
  220. package/templates/qoder/hooks/inject-workflow-state.py +0 -169
  221. package/templates/qoder/scripts/__pycache__/check_mcp_launch.cpython-39.pyc +0 -0
  222. package/templates/qoder/scripts/__pycache__/install_qoderwork.cpython-39.pyc +0 -0
  223. package/templates/qoder/scripts/__pycache__/mcp_launcher.cpython-39.pyc +0 -0
  224. package/templates/qoder/scripts/__pycache__/platform_doctor.cpython-39.pyc +0 -0
  225. package/templates/qoder/scripts/check_carriers.py +0 -238
  226. package/templates/qoder/scripts/check_mcp.py +0 -298
  227. package/templates/qoder/scripts/check_mcp_launch.py +0 -183
  228. package/templates/qoder/scripts/check_qoderwork_consistency.py +0 -166
  229. package/templates/qoder/scripts/collect_prds.py +0 -31
  230. package/templates/qoder/scripts/common/mentions.py +0 -134
  231. package/templates/qoder/scripts/common/utf8.py +0 -38
  232. package/templates/qoder/scripts/extract_api_params.py +0 -246
  233. package/templates/qoder/scripts/extract_routes.py +0 -54
  234. package/templates/qoder/scripts/extract_routes_tree.py +0 -78
  235. package/templates/qoder/scripts/handoff.py +0 -22
  236. package/templates/qoder/scripts/init_developer.py +0 -76
  237. package/templates/qoder/scripts/kg_mcp_server.py +0 -801
  238. package/templates/qoder/scripts/kg_semantic.py +0 -150
  239. package/templates/qoder/scripts/mcp_launcher.py +0 -414
  240. package/templates/qoder/scripts/mysql_mcp_server.py +0 -396
  241. package/templates/qoder/scripts/parse_prds.py +0 -33
  242. package/templates/qoder/scripts/role.py +0 -51
  243. package/templates/qoder/scripts/sync_carriers.py +0 -259
  244. package/templates/qoder/scripts/task.py +0 -1261
  245. package/templates/qoder/scripts/workspace_init.py +0 -102
  246. package/templates/qoder/scripts/zentao_mcp_server.py +0 -424
  247. package/templates/qoder/skills/prompt-enrich/SKILL.md +0 -90
  248. /package/templates/qoder/scripts/{common → deployment}/__init__.py +0 -0
  249. /package/templates/qoder/{skills/prototype-generator/SKILL.md.zcode-79180-2af4721f-f9a6-412c-88db-c0af680d211b.tmp → scripts/domain/__init__.py} +0 -0
  250. /package/templates/qoder/scripts/{common → domain/kg/graph}/graph_traverse.py +0 -0
  251. /package/templates/qoder/scripts/{common → domain/kg/server}/repowiki.py +0 -0
  252. /package/templates/qoder/scripts/{common → foundation/io}/atomicio.py +0 -0
  253. /package/templates/qoder/scripts/{secure-ls.js → validation/test/secure-ls.js} +0 -0
@@ -1,245 +1,257 @@
1
- #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
- """
4
- QODER Pipeline - 会话日志记录
5
-
6
- 每次 AI 会话结束后, 记录一条日志到开发者的工作空间。
7
- 日志文件按行数自动轮转 (默认 2000 行)。
8
-
9
- Usage:
10
- python add_session.py --title "Title" --commit "hash" --summary "Summary"
11
- python add_session.py --title "Title" --branch "feat/my-branch"
12
-
13
- 日志文件: .qoder/workspace/<developer>/journal-N.md
14
- 索引文件: .qoder/workspace/<developer>/index.md
15
-
16
- 参考: Trellis 的 add_session.py 设计
17
- """
18
-
19
- from __future__ import annotations
20
-
21
- import argparse
22
- import re
23
- import subprocess
24
- import sys
25
- import os
26
- from datetime import datetime
27
- from pathlib import Path
28
-
29
- sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
30
-
31
- from common.paths import (
32
- FILE_JOURNAL_PREFIX,
33
- get_repo_root,
34
- get_developer,
35
- get_workspace_dir,
36
- get_active_journal_file,
37
- count_lines,
38
- )
39
- from common.developer import ensure_developer
40
-
41
-
42
- # =============================================================================
43
- # 默认配置
44
- # =============================================================================
45
-
46
- MAX_JOURNAL_LINES = 2000 # 每个日志文件最大行数
47
-
48
-
49
- # =============================================================================
50
- # 核心函数
51
- # =============================================================================
52
-
53
- def get_latest_journal_info(dev_dir: Path) -> tuple[Path | None, int, int]:
54
- """获取最新日志文件信息。
55
-
56
- Returns:
57
- (文件路径, 文件编号, 行数)
58
- """
59
- latest_file = None
60
- latest_num = 0
61
-
62
- for f in dev_dir.glob(f"{FILE_JOURNAL_PREFIX}*.md"):
63
- if not f.is_file():
64
- continue
65
- match = re.search(r"(\d+)$", f.stem)
66
- if match:
67
- num = int(match.group(1))
68
- if num > latest_num:
69
- latest_num = num
70
- latest_file = f
71
-
72
- if latest_file:
73
- lines = count_lines(latest_file)
74
- return latest_file, latest_num, lines
75
-
76
- return None, 0, 0
77
-
78
-
79
- def get_current_session(index_file: Path) -> int:
80
- """从 index.md 读取当前会话编号。"""
81
- if not index_file.is_file():
82
- return 0
83
-
84
- try:
85
- content = index_file.read_text(encoding="utf-8")
86
- for line in content.splitlines():
87
- if "Total Sessions" in line:
88
- match = re.search(r":\s*(\d+)", line)
89
- if match:
90
- return int(match.group(1))
91
- except (OSError, IOError):
92
- pass
93
- return 0
94
-
95
-
96
- def create_new_journal_file(
97
- dev_dir: Path, num: int, developer: str, max_lines: int = MAX_JOURNAL_LINES
98
- ) -> Path:
99
- """创建新的日志文件。"""
100
- prev_num = num - 1
101
- new_file = dev_dir / f"{FILE_JOURNAL_PREFIX}{num}.md"
102
- today = datetime.now().strftime("%Y-%m-%d")
103
-
104
- content = f"""# Journal - {developer} (Part {num})
105
-
106
- > Continuation from `{FILE_JOURNAL_PREFIX}{prev_num}.md` (archived at ~{max_lines} lines)
107
- > Started: {today}
108
-
109
- ---
110
-
111
- """
112
- new_file.write_text(content, encoding="utf-8")
113
- return new_file
114
-
115
-
116
- def update_index(
117
- index_file: Path,
118
- developer: str,
119
- session_num: int,
120
- journal_file: str,
121
- max_lines: int = MAX_JOURNAL_LINES,
122
- ) -> None:
123
- """更新工作空间索引文件 index.md。"""
124
- today = datetime.now().strftime("%Y-%m-%d %H:%M")
125
-
126
- # 生成日志表格
127
- dev_dir = index_file.parent
128
- table_rows = []
129
- for f in sorted(dev_dir.glob(f"{FILE_JOURNAL_PREFIX}*.md")):
130
- lines = count_lines(f)
131
- status = "Active" if f.name == journal_file else "Archived"
132
- table_rows.append(f"| `{f.name}` | ~{lines} | {status} |")
133
-
134
- journals_table = "\n".join(table_rows)
135
-
136
- index_content = f"""# Workspace Index - {developer}
137
-
138
- > Journal tracking for AI development sessions.
139
-
140
- ---
141
-
142
- ## Current Status
143
-
144
- <!-- @@@auto:current-status -->
145
- - **Active File**: `{journal_file}`
146
- - **Total Sessions**: {session_num}
147
- - **Last Active**: {today}
148
- <!-- @@@/auto:current-status -->
149
-
150
- ---
151
-
152
- ## Journals
153
-
154
- | File | Lines | Status |
155
- |------|-------|--------|
156
- <!-- @@@auto:journals-table -->
157
- {journals_table}
158
- <!-- @@@/auto:journals-table -->
159
-
160
- ---
161
-
162
- *Auto-managed by QODER Pipeline*
163
- """
164
- index_file.write_text(index_content, encoding="utf-8")
165
-
166
-
167
- # =============================================================================
168
- # Main
169
- # =============================================================================
170
-
171
- def main() -> int:
172
- """CLI 入口。"""
173
- parser = argparse.ArgumentParser(description="Record AI session to journal")
174
- parser.add_argument("--title", required=True, help="Session title")
175
- parser.add_argument("--summary", default="", help="Session summary")
176
- parser.add_argument("--commit", default="", help="Git commit hash")
177
- parser.add_argument("--branch", default="", help="Git branch (auto-detected if omitted)")
178
- args = parser.parse_args()
179
-
180
- repo_root = get_repo_root()
181
- developer = ensure_developer(repo_root)
182
-
183
- workspace = get_workspace_dir(repo_root)
184
- if not workspace:
185
- print("Error: No workspace directory", file=sys.stderr)
186
- return 1
187
-
188
- workspace.mkdir(parents=True, exist_ok=True)
189
-
190
- # 获取 Git 分支
191
- branch = args.branch
192
- if not branch:
193
- try:
194
- result = subprocess.run(
195
- ["git", "branch", "--show-current"],
196
- capture_output=True, text=True, cwd=str(repo_root)
197
- )
198
- branch = result.stdout.strip()
199
- except Exception:
200
- branch = "unknown"
201
-
202
- # 获取最新日志文件
203
- journal_file, journal_num, lines = get_latest_journal_info(workspace)
204
-
205
- # 如果没有日志文件或已满, 创建新的
206
- if not journal_file or lines >= MAX_JOURNAL_LINES:
207
- journal_num = journal_num + 1 if journal_file else 1
208
- journal_file = create_new_journal_file(workspace, journal_num, developer)
209
- lines = 0
210
-
211
- # 读取当前会话编号
212
- index_file = workspace / "index.md"
213
- session_num = get_current_session(index_file) + 1
214
-
215
- # 追加会话记录
216
- today = datetime.now().strftime("%Y-%m-%d %H:%M")
217
- commit_line = f"- **Commit**: `{args.commit}`\n" if args.commit else ""
218
- branch_line = f"- **Branch**: `{branch}`\n" if branch else ""
219
-
220
- entry = f"""### Session {session_num}: {args.title}
221
-
222
- - **Date**: {today}
223
- - **Developer**: {developer}
224
- {commit_line}{branch_line}
225
- **Summary**: {args.summary or 'No summary provided.'}
226
-
227
- ---
228
-
229
- """
230
- try:
231
- with open(journal_file, "a", encoding="utf-8") as f:
232
- f.write(entry)
233
- except (OSError, IOError) as e:
234
- print(f"Error: Failed to append to journal: {e}", file=sys.stderr)
235
- return 1
236
-
237
- # 更新索引
238
- update_index(index_file, developer, session_num, journal_file.name)
239
-
240
- print(f"Session {session_num} recorded in {journal_file.name}")
241
- return 0
242
-
243
-
244
- if __name__ == "__main__":
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ QODER Pipeline - 会话日志记录
5
+
6
+ 每次 AI 会话结束后, 记录一条日志到开发者的工作空间。
7
+ 日志文件按行数自动轮转 (默认 2000 行)。
8
+
9
+ Usage:
10
+ python add_session.py --title "Title" --commit "hash" --summary "Summary"
11
+ python add_session.py --title "Title" --branch "feat/my-branch"
12
+
13
+ 日志文件: .qoder/workspace/<developer>/journal-N.md
14
+ 索引文件: .qoder/workspace/<developer>/index.md
15
+
16
+ 参考: Trellis 的 add_session.py 设计
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ # v3.0 路径自举: 引导到 common/bootstrap, 统一 sys.path 逻辑
22
+ import os as _o, sys as _s
23
+ _f = _o.path.abspath(__file__)
24
+ for _ in range(10):
25
+ _f = _o.path.dirname(_f)
26
+ _cp = _o.path.join(_f, 'foundation')
27
+ if _o.path.isfile(_o.path.join(_cp, 'bootstrap.py')):
28
+ break
29
+ if _cp not in _s.path: _s.path.insert(0, _cp)
30
+ from bootstrap import setup; setup()
31
+ from foundation.core.paths import get_repo_root
32
+
33
+ import argparse
34
+ import re
35
+ import subprocess
36
+ import sys
37
+ import os
38
+ from datetime import datetime
39
+ from pathlib import Path
40
+
41
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
42
+
43
+ from foundation.core.paths import (
44
+ FILE_JOURNAL_PREFIX,
45
+ get_repo_root,
46
+ get_developer,
47
+ get_workspace_dir,
48
+ get_active_journal_file,
49
+ count_lines,
50
+ )
51
+ from foundation.identity.developer import ensure_developer
52
+
53
+
54
+ # =============================================================================
55
+ # 默认配置
56
+ # =============================================================================
57
+
58
+ MAX_JOURNAL_LINES = 2000 # 每个日志文件最大行数
59
+
60
+
61
+ # =============================================================================
62
+ # 核心函数
63
+ # =============================================================================
64
+
65
+ def get_latest_journal_info(dev_dir: Path) -> tuple[Path | None, int, int]:
66
+ """获取最新日志文件信息。
67
+
68
+ Returns:
69
+ (文件路径, 文件编号, 行数)
70
+ """
71
+ latest_file = None
72
+ latest_num = 0
73
+
74
+ for f in dev_dir.glob(f"{FILE_JOURNAL_PREFIX}*.md"):
75
+ if not f.is_file():
76
+ continue
77
+ match = re.search(r"(\d+)$", f.stem)
78
+ if match:
79
+ num = int(match.group(1))
80
+ if num > latest_num:
81
+ latest_num = num
82
+ latest_file = f
83
+
84
+ if latest_file:
85
+ lines = count_lines(latest_file)
86
+ return latest_file, latest_num, lines
87
+
88
+ return None, 0, 0
89
+
90
+
91
+ def get_current_session(index_file: Path) -> int:
92
+ """从 index.md 读取当前会话编号。"""
93
+ if not index_file.is_file():
94
+ return 0
95
+
96
+ try:
97
+ content = index_file.read_text(encoding="utf-8")
98
+ for line in content.splitlines():
99
+ if "Total Sessions" in line:
100
+ match = re.search(r":\s*(\d+)", line)
101
+ if match:
102
+ return int(match.group(1))
103
+ except (OSError, IOError):
104
+ pass
105
+ return 0
106
+
107
+
108
+ def create_new_journal_file(
109
+ dev_dir: Path, num: int, developer: str, max_lines: int = MAX_JOURNAL_LINES
110
+ ) -> Path:
111
+ """创建新的日志文件。"""
112
+ prev_num = num - 1
113
+ new_file = dev_dir / f"{FILE_JOURNAL_PREFIX}{num}.md"
114
+ today = datetime.now().strftime("%Y-%m-%d")
115
+
116
+ content = f"""# Journal - {developer} (Part {num})
117
+
118
+ > Continuation from `{FILE_JOURNAL_PREFIX}{prev_num}.md` (archived at ~{max_lines} lines)
119
+ > Started: {today}
120
+
121
+ ---
122
+
123
+ """
124
+ new_file.write_text(content, encoding="utf-8")
125
+ return new_file
126
+
127
+
128
+ def update_index(
129
+ index_file: Path,
130
+ developer: str,
131
+ session_num: int,
132
+ journal_file: str,
133
+ max_lines: int = MAX_JOURNAL_LINES,
134
+ ) -> None:
135
+ """更新工作空间索引文件 index.md。"""
136
+ today = datetime.now().strftime("%Y-%m-%d %H:%M")
137
+
138
+ # 生成日志表格
139
+ dev_dir = index_file.parent
140
+ table_rows = []
141
+ for f in sorted(dev_dir.glob(f"{FILE_JOURNAL_PREFIX}*.md")):
142
+ lines = count_lines(f)
143
+ status = "Active" if f.name == journal_file else "Archived"
144
+ table_rows.append(f"| `{f.name}` | ~{lines} | {status} |")
145
+
146
+ journals_table = "\n".join(table_rows)
147
+
148
+ index_content = f"""# Workspace Index - {developer}
149
+
150
+ > Journal tracking for AI development sessions.
151
+
152
+ ---
153
+
154
+ ## Current Status
155
+
156
+ <!-- @@@auto:current-status -->
157
+ - **Active File**: `{journal_file}`
158
+ - **Total Sessions**: {session_num}
159
+ - **Last Active**: {today}
160
+ <!-- @@@/auto:current-status -->
161
+
162
+ ---
163
+
164
+ ## Journals
165
+
166
+ | File | Lines | Status |
167
+ |------|-------|--------|
168
+ <!-- @@@auto:journals-table -->
169
+ {journals_table}
170
+ <!-- @@@/auto:journals-table -->
171
+
172
+ ---
173
+
174
+ *Auto-managed by QODER Pipeline*
175
+ """
176
+ index_file.write_text(index_content, encoding="utf-8")
177
+
178
+
179
+ # =============================================================================
180
+ # Main
181
+ # =============================================================================
182
+
183
+ def main() -> int:
184
+ """CLI 入口。"""
185
+ parser = argparse.ArgumentParser(description="Record AI session to journal")
186
+ parser.add_argument("--title", required=True, help="Session title")
187
+ parser.add_argument("--summary", default="", help="Session summary")
188
+ parser.add_argument("--commit", default="", help="Git commit hash")
189
+ parser.add_argument("--branch", default="", help="Git branch (auto-detected if omitted)")
190
+ args = parser.parse_args()
191
+
192
+ repo_root = get_repo_root()
193
+ developer = ensure_developer(repo_root)
194
+
195
+ workspace = get_workspace_dir(repo_root)
196
+ if not workspace:
197
+ print("Error: No workspace directory", file=sys.stderr)
198
+ return 1
199
+
200
+ workspace.mkdir(parents=True, exist_ok=True)
201
+
202
+ # 获取 Git 分支
203
+ branch = args.branch
204
+ if not branch:
205
+ try:
206
+ result = subprocess.run(
207
+ ["git", "branch", "--show-current"],
208
+ capture_output=True, text=True, cwd=str(repo_root)
209
+ )
210
+ branch = result.stdout.strip()
211
+ except Exception:
212
+ branch = "unknown"
213
+
214
+ # 获取最新日志文件
215
+ journal_file, journal_num, lines = get_latest_journal_info(workspace)
216
+
217
+ # 如果没有日志文件或已满, 创建新的
218
+ if not journal_file or lines >= MAX_JOURNAL_LINES:
219
+ journal_num = journal_num + 1 if journal_file else 1
220
+ journal_file = create_new_journal_file(workspace, journal_num, developer)
221
+ lines = 0
222
+
223
+ # 读取当前会话编号
224
+ index_file = workspace / "index.md"
225
+ session_num = get_current_session(index_file) + 1
226
+
227
+ # 追加会话记录
228
+ today = datetime.now().strftime("%Y-%m-%d %H:%M")
229
+ commit_line = f"- **Commit**: `{args.commit}`\n" if args.commit else ""
230
+ branch_line = f"- **Branch**: `{branch}`\n" if branch else ""
231
+
232
+ entry = f"""### Session {session_num}: {args.title}
233
+
234
+ - **Date**: {today}
235
+ - **Developer**: {developer}
236
+ {commit_line}{branch_line}
237
+ **Summary**: {args.summary or 'No summary provided.'}
238
+
239
+ ---
240
+
241
+ """
242
+ try:
243
+ with open(journal_file, "a", encoding="utf-8") as f:
244
+ f.write(entry)
245
+ except (OSError, IOError) as e:
246
+ print(f"Error: Failed to append to journal: {e}", file=sys.stderr)
247
+ return 1
248
+
249
+ # 更新索引
250
+ update_index(index_file, developer, session_num, journal_file.name)
251
+
252
+ print(f"Session {session_num} recorded in {journal_file.name}")
253
+ return 0
254
+
255
+
256
+ if __name__ == "__main__":
245
257
  sys.exit(main())
@@ -1,5 +1,16 @@
1
1
  #!/usr/bin/env python3
2
2
  # -*- coding: utf-8 -*-
3
+ # v3.0 路径自举: 引导到 common/bootstrap, 统一 sys.path 逻辑
4
+ import os as _o, sys as _s
5
+ _f = _o.path.abspath(__file__)
6
+ for _ in range(10):
7
+ _f = _o.path.dirname(_f)
8
+ _cp = _o.path.join(_f, 'foundation')
9
+ if _o.path.isfile(_o.path.join(_cp, 'bootstrap.py')):
10
+ break
11
+ if _cp not in _s.path: _s.path.insert(0, _cp)
12
+ from bootstrap import setup; setup()
13
+
3
14
  """
4
15
  export.py - 集成导出工具 (阶段 C)
5
16
 
@@ -33,9 +44,9 @@ except (AttributeError, TypeError, OSError):
33
44
 
34
45
  THIS_DIR = os.path.dirname(os.path.abspath(__file__))
35
46
  sys.path.insert(0, THIS_DIR)
36
- from common.paths import get_repo_root, get_developer, get_tasks_dir
37
- from common.task_utils import load_task_json
38
- from common.atomicio import safe_read_json
47
+ from foundation.core.paths import get_repo_root, get_developer, get_tasks_dir
48
+ from foundation.data.task_utils import load_task_json
49
+ from foundation.io.atomicio import safe_read_json
39
50
 
40
51
  BASE = get_repo_root()
41
52
  INDEX_DIR = BASE / 'data' / 'index'
@@ -407,7 +418,7 @@ def _build_callgraph():
407
418
 
408
419
  # 持久化到 data/index/code-callgraph.json (供后续查询复用)
409
420
  try:
410
- from common.atomicio import atomic_write_json
421
+ from foundation.io.atomicio import atomic_write_json
411
422
  atomic_write_json(str(INDEX_DIR / 'code-callgraph.json'), cg)
412
423
  except Exception as e:
413
424
  sys.stderr.write(f'[export] callgraph 持久化失败 (不影响导出): {e}\n')
@@ -470,6 +481,13 @@ def main():
470
481
  p_cg = sub.add_parser('callgraph', help='Java 调用图')
471
482
  p_cg.add_argument('--class', dest='class_name', help='查询指定类的依赖')
472
483
 
484
+ p_dx = sub.add_parser('docx', help='PRD导出Word(M5)')
485
+ p_dx.add_argument('prd', help='PRD md文件')
486
+ p_dx.add_argument('-o', '--output')
487
+ p_pf = sub.add_parser('pdf', help='PRD导出HTML(浏览器打印为PDF)')
488
+ p_pf.add_argument('prd', help='PRD md文件')
489
+ p_pf.add_argument('-o', '--output')
490
+
473
491
  args = parser.parse_args()
474
492
  if not args.format:
475
493
  parser.print_help()
@@ -482,6 +500,56 @@ def main():
482
500
  elif args.format == 'callgraph':
483
501
  return export_callgraph(class_name=args.class_name)
484
502
 
503
+ elif args.format == 'docx':
504
+ return export_docx(args.prd, args.output)
505
+ elif args.format == 'pdf':
506
+ return export_pdf(args.prd, args.output)
507
+
508
+
509
+ def export_docx(prd_path, output=None):
510
+ """PRD markdown -> Word docx (M5)。用 python-docx, 纯Python不依赖pandoc。"""
511
+ if not os.path.isfile(prd_path):
512
+ print('PRD 不存在: %s' % prd_path); return 1
513
+ try:
514
+ from docx import Document
515
+ except ImportError:
516
+ print('python-docx 未装。pip install python-docx'); return 1
517
+ doc = Document()
518
+ with open(prd_path, encoding='utf-8') as f:
519
+ for line in f:
520
+ ln = line.rstrip('\n'); s = ln.strip()
521
+ if s.startswith('# '): doc.add_heading(s[2:], level=1)
522
+ elif s.startswith('## '): doc.add_heading(s[3:], level=2)
523
+ elif s.startswith('### '): doc.add_heading(s[4:], level=3)
524
+ elif s.startswith('- ') or s.startswith('* '): doc.add_paragraph(s[2:], style='List Bullet')
525
+ elif s.startswith('<!--'): pass
526
+ elif s == '': pass
527
+ else: doc.add_paragraph(s)
528
+ if not output: output = os.path.splitext(prd_path)[0] + '.docx'
529
+ doc.save(output)
530
+ print('OK Word: %s' % output); return 0
531
+
532
+
533
+ def export_pdf(prd_path, output=None):
534
+ """PRD markdown -> HTML (浏览器Ctrl+P另存PDF) (M5)。避免wkhtmltopdf重依赖。"""
535
+ if not os.path.isfile(prd_path):
536
+ print('PRD 不存在: %s' % prd_path); return 1
537
+ try:
538
+ import markdown as md
539
+ except ImportError:
540
+ print('markdown 未装。pip install markdown'); return 1
541
+ with open(prd_path, encoding='utf-8') as f:
542
+ body = md.markdown(f.read(), extensions=['tables', 'fenced_code'])
543
+ html = ('<!DOCTYPE html><html lang="zh"><head><meta charset="UTF-8"><style>'
544
+ 'body{font-family:"Microsoft YaHei",sans-serif;max-width:800px;margin:40px auto;line-height:1.6}'
545
+ 'h1{border-bottom:2px solid #333}h2{border-bottom:1px solid #999;margin-top:24px}'
546
+ 'table{border-collapse:collapse;width:100%}th,td{border:1px solid #ccc;padding:6px}th{background:#f5f5f5}'
547
+ '@media print{body{margin:20px}}</style></head><body>' + body + '</body></html>')
548
+ if not output: output = os.path.splitext(prd_path)[0] + '.html'
549
+ with open(output, 'w', encoding='utf-8') as f:
550
+ f.write(html)
551
+ print('OK HTML: %s (浏览器打开 Ctrl+P 另存PDF)' % output); return 0
552
+
485
553
 
486
554
  if __name__ == '__main__':
487
555
  sys.exit(main())