@hupan56/wlkj 2.7.12 → 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 (152) hide show
  1. package/bin/cli.js +344 -78
  2. package/package.json +29 -29
  3. package/templates/.qoder/.runtime/ctx-cache-5660152f1d6dd819.md +23 -0
  4. package/templates/.qoder/.runtime/ctx-cache-afdce0dac06b25b0.md +23 -0
  5. package/templates/.qoder/.runtime/search-cache-eae7644e7b122f35.txt +1 -0
  6. package/templates/.qoder/learning/eval-history.jsonl +28 -0
  7. package/templates/data/index/wiki-index.json +8 -0
  8. package/templates/qoder/agents/insight-planning.md +1 -1
  9. package/templates/qoder/agents/insight-research.md +28 -15
  10. package/templates/qoder/commands/optional/wl-insight.md +159 -161
  11. package/templates/qoder/commands/optional/wl-report.md +4 -4
  12. package/templates/qoder/commands/optional/wl-status.md +2 -2
  13. package/templates/qoder/commands/wl-code.md +2 -2
  14. package/templates/qoder/commands/wl-design.md +2 -2
  15. package/templates/qoder/commands/wl-init.md +3 -3
  16. package/templates/qoder/commands/wl-prd.md +2 -2
  17. package/templates/qoder/commands/wl-req.md +43 -0
  18. package/templates/qoder/commands/wl-search.md +8 -8
  19. package/templates/qoder/commands/wl-task.md +17 -17
  20. package/templates/qoder/commands/wl-test.md +41 -15
  21. package/templates/qoder/config.yaml +17 -1
  22. package/templates/qoder/hooks/session-start.py +12 -22
  23. package/templates/qoder/nul +4 -0
  24. package/templates/qoder/rules/wl-pipeline.md +22 -48
  25. package/templates/qoder/scripts/README.md +139 -0
  26. package/templates/qoder/scripts/common/autotest_auth.py +109 -0
  27. package/templates/qoder/scripts/common/bootstrap.py +145 -0
  28. package/templates/qoder/scripts/common/check_publish.py +98 -0
  29. package/templates/qoder/scripts/common/cmd_registry.py +112 -0
  30. package/templates/qoder/scripts/common/config.py +187 -0
  31. package/templates/qoder/scripts/common/contract.py +317 -0
  32. package/templates/qoder/scripts/common/developer.py +2 -1
  33. package/templates/qoder/scripts/common/feishu.py +10 -9
  34. package/templates/qoder/scripts/common/guard.py +159 -0
  35. package/templates/qoder/scripts/common/identity.py +121 -2
  36. package/templates/qoder/scripts/common/kg_capabilities.py +182 -0
  37. package/templates/qoder/scripts/common/mcp_base.py +268 -0
  38. package/templates/qoder/scripts/common/paths.py +187 -1
  39. package/templates/qoder/scripts/common/result.py +223 -0
  40. package/templates/qoder/scripts/common/roles.py +60 -0
  41. package/templates/qoder/scripts/common/task_utils.py +21 -9
  42. package/templates/qoder/scripts/common/test_extract.py +115 -0
  43. package/templates/qoder/scripts/kg/__init__.py +11 -0
  44. package/templates/qoder/scripts/kg/build_entity_registry.py +196 -0
  45. package/templates/qoder/scripts/kg/build_relations.py +127 -0
  46. package/templates/qoder/scripts/{build_style_index.py → kg/build_style_index.py} +39 -10
  47. package/templates/qoder/scripts/kg/build_workflows.py +144 -0
  48. package/templates/qoder/scripts/{context_pack.py → kg/context_pack.py} +18 -11
  49. package/templates/qoder/scripts/{enrich_prompt.py → kg/enrich_prompt.py} +232 -226
  50. package/templates/qoder/scripts/{extract_api_params.py → kg/extract.py} +398 -246
  51. package/templates/qoder/scripts/{kg.py → kg/kg.py} +638 -708
  52. package/templates/qoder/scripts/{kg_build.py → kg/kg_build.py} +618 -612
  53. package/templates/qoder/scripts/{kg_build_db.py → kg/kg_build_db.py} +333 -327
  54. package/templates/qoder/scripts/{kg_duckdb.py → kg/kg_duckdb.py} +38 -37
  55. package/templates/qoder/scripts/{kg_incremental.py → kg/kg_incremental.py} +420 -393
  56. package/templates/qoder/scripts/{kg_link_db.py → kg/kg_link_db.py} +230 -224
  57. package/templates/qoder/scripts/{kg_semantic.py → kg/kg_semantic.py} +156 -150
  58. package/templates/qoder/scripts/kg/prefetch.py +359 -0
  59. package/templates/qoder/scripts/{search_index.py → kg/search_index.py} +70 -14
  60. package/templates/qoder/scripts/mcp/__init__.py +11 -0
  61. package/templates/qoder/scripts/{kg_mcp_server.py → mcp/kg_mcp_server.py} +77 -272
  62. package/templates/qoder/scripts/{lanhu_stdio_wrapper.py → mcp/lanhu_stdio_wrapper.py} +125 -119
  63. package/templates/qoder/scripts/{check_mcp.py → mcp/mcp_doctor.py} +515 -298
  64. package/templates/qoder/scripts/{mcp_launcher.py → mcp/mcp_launcher.py} +442 -414
  65. package/templates/qoder/scripts/{mysql_mcp_server.py → mcp/mysql_mcp_server.py} +347 -396
  66. package/templates/qoder/scripts/{zentao_mcp_server.py → mcp/zentao_mcp_server.py} +384 -424
  67. package/templates/qoder/scripts/report/__init__.py +11 -0
  68. package/templates/qoder/scripts/{add_session.py → report/add_session.py} +250 -244
  69. package/templates/qoder/scripts/{archive_prd.py → report/archive_prd.py} +383 -377
  70. package/templates/qoder/scripts/{eval_prd.py → report/eval_prd.py} +73 -11
  71. package/templates/qoder/scripts/{export.py → report/export.py} +63 -0
  72. package/templates/qoder/scripts/{fill_prototype.py → report/fill_prototype.py} +6 -0
  73. package/templates/qoder/scripts/{gen_design_doc.py → report/gen_design_doc.py} +400 -394
  74. package/templates/qoder/scripts/{learn.py → report/learn.py} +152 -146
  75. package/templates/qoder/scripts/{learn_aggregate.py → report/learn_aggregate.py} +207 -201
  76. package/templates/qoder/scripts/{report.py → report/report.py} +287 -281
  77. package/templates/qoder/scripts/report/req.py +222 -0
  78. package/templates/qoder/scripts/report/role.py +33 -0
  79. package/templates/qoder/scripts/{status.py → report/status.py} +634 -628
  80. package/templates/qoder/scripts/setup/__init__.py +11 -0
  81. package/templates/qoder/scripts/setup/carriers.py +662 -0
  82. package/templates/qoder/scripts/{init_doctor.py → setup/init_doctor.py} +63 -26
  83. package/templates/qoder/scripts/{install_qoderwork.py → setup/install_qoderwork.py} +36 -26
  84. package/templates/qoder/scripts/{platform_doctor.py → setup/platform_doctor.py} +265 -259
  85. package/templates/qoder/scripts/{repo_root.py → setup/repo_root.py} +112 -106
  86. package/templates/qoder/scripts/{setup.py → setup/setup.py} +113 -4
  87. package/templates/qoder/scripts/{setup_lanhu.py → setup/setup_lanhu.py} +973 -963
  88. package/templates/qoder/scripts/task/__init__.py +11 -0
  89. package/templates/qoder/scripts/{git_sync.py → task/git_sync.py} +52 -31
  90. package/templates/qoder/scripts/{syncgate.py → task/syncgate.py} +6 -0
  91. package/templates/qoder/scripts/task/task.py +221 -0
  92. package/templates/qoder/scripts/task/task_lifecycle.py +596 -0
  93. package/templates/qoder/scripts/task/task_query.py +161 -0
  94. package/templates/qoder/scripts/task/task_relations.py +424 -0
  95. package/templates/qoder/scripts/{team_sync.py → task/team_sync.py} +93 -20
  96. package/templates/qoder/scripts/test/__init__.py +11 -0
  97. package/templates/qoder/scripts/{autotest.py → test/autotest.py} +1174 -1751
  98. package/templates/qoder/scripts/{autotest_batch.py → test/autotest_batch.py} +242 -224
  99. package/templates/qoder/scripts/test/autotest_data.py +675 -0
  100. package/templates/qoder/scripts/{autotest_run.py → test/autotest_run.py} +309 -297
  101. package/templates/qoder/scripts/{benchmark.py → test/benchmark.py} +6 -0
  102. package/templates/qoder/scripts/{kg_auto_login.py → test/kg_auto_login.py} +202 -196
  103. package/templates/qoder/scripts/{kg_test_runner.py → test/kg_test_runner.py} +7 -1
  104. package/templates/qoder/scripts/{page_probe.py → test/page_probe.py} +465 -459
  105. package/templates/qoder/scripts/wlkj.py +116 -0
  106. package/templates/qoder/settings.json +1 -10
  107. package/templates/qoder/skills/design-import/SKILL.md +226 -226
  108. package/templates/qoder/skills/design-review/SKILL.md +82 -82
  109. package/templates/qoder/skills/prd-generator/SKILL.md +26 -16
  110. package/templates/qoder/skills/prd-review/SKILL.md +5 -5
  111. package/templates/qoder/skills/prototype-generator/SKILL.md +256 -256
  112. package/templates/qoder/skills/spec-coder/SKILL.md +4 -4
  113. package/templates/qoder/skills/spec-generator/SKILL.md +4 -4
  114. package/templates/qoder/skills/test-generator/SKILL.md +5 -5
  115. package/templates/qoder/skills/wl-code/SKILL.md +4 -4
  116. package/templates/qoder/skills/wl-commit/SKILL.md +4 -4
  117. package/templates/qoder/skills/wl-design/SKILL.md +3 -3
  118. package/templates/qoder/skills/wl-init/SKILL.md +8 -8
  119. package/templates/qoder/skills/wl-insight/SKILL.md +5 -5
  120. package/templates/qoder/skills/wl-prd-full/SKILL.md +6 -6
  121. package/templates/qoder/skills/wl-prd-quick/SKILL.md +6 -6
  122. package/templates/qoder/skills/wl-prd-review/SKILL.md +4 -4
  123. package/templates/qoder/skills/wl-report/SKILL.md +7 -7
  124. package/templates/qoder/skills/wl-search/SKILL.md +13 -13
  125. package/templates/qoder/skills/wl-spec/SKILL.md +5 -5
  126. package/templates/qoder/skills/wl-status/SKILL.md +5 -5
  127. package/templates/qoder/skills/wl-task/SKILL.md +6 -6
  128. package/templates/qoder/skills/wl-test/SKILL.md +102 -39
  129. package/templates/root/AGENTS.md +39 -40
  130. package/templates/qoder/hooks/inject-workflow-state.py +0 -169
  131. package/templates/qoder/scripts/__pycache__/check_mcp_launch.cpython-39.pyc +0 -0
  132. package/templates/qoder/scripts/__pycache__/install_qoderwork.cpython-39.pyc +0 -0
  133. package/templates/qoder/scripts/__pycache__/mcp_launcher.cpython-39.pyc +0 -0
  134. package/templates/qoder/scripts/__pycache__/platform_doctor.cpython-39.pyc +0 -0
  135. package/templates/qoder/scripts/check_carriers.py +0 -238
  136. package/templates/qoder/scripts/check_mcp_launch.py +0 -183
  137. package/templates/qoder/scripts/check_qoderwork_consistency.py +0 -166
  138. package/templates/qoder/scripts/collect_prds.py +0 -31
  139. package/templates/qoder/scripts/common/mentions.py +0 -134
  140. package/templates/qoder/scripts/common/utf8.py +0 -38
  141. package/templates/qoder/scripts/extract_routes.py +0 -54
  142. package/templates/qoder/scripts/extract_routes_tree.py +0 -78
  143. package/templates/qoder/scripts/handoff.py +0 -22
  144. package/templates/qoder/scripts/init_developer.py +0 -76
  145. package/templates/qoder/scripts/parse_prds.py +0 -33
  146. package/templates/qoder/scripts/role.py +0 -51
  147. package/templates/qoder/scripts/sync_carriers.py +0 -259
  148. package/templates/qoder/scripts/task.py +0 -1261
  149. package/templates/qoder/scripts/workspace_init.py +0 -102
  150. package/templates/qoder/skills/prompt-enrich/SKILL.md +0 -90
  151. package/templates/qoder/skills/prototype-generator/SKILL.md.zcode-79180-2af4721f-f9a6-412c-88db-c0af680d211b.tmp +0 -0
  152. /package/templates/qoder/scripts/{secure-ls.js → test/secure-ls.js} +0 -0
@@ -0,0 +1,11 @@
1
+ # 子包初始化: 注入 scripts/根 + 所有子包到 sys.path
2
+ # 让跨包 import (旧式不带前缀) 仍可用, 兼容现有代码
3
+ import os as _os, sys as _sys
4
+ _root = _os.path.dirname(_os.path.dirname(_os.path.abspath(__file__)))
5
+ if _root not in _sys.path:
6
+ _sys.path.insert(0, _root)
7
+ # 注入所有子包目录 (兼容旧式 import xxx)
8
+ for _sub in ('kg', 'build', 'test', 'setup', 'io'):
9
+ _subdir = _os.path.join(_root, _sub)
10
+ if _os.path.isdir(_subdir) and _subdir not in _sys.path:
11
+ _sys.path.insert(0, _subdir)
@@ -1,245 +1,251 @@
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
+ _cp = _o.path.join(_o.path.dirname(_o.path.dirname(_o.path.abspath(__file__))), 'common')
24
+ if _cp not in _s.path: _s.path.insert(0, _cp)
25
+ from bootstrap import setup; setup()
26
+
27
+ import argparse
28
+ import re
29
+ import subprocess
30
+ import sys
31
+ import os
32
+ from datetime import datetime
33
+ from pathlib import Path
34
+
35
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
36
+
37
+ from common.paths import (
38
+ FILE_JOURNAL_PREFIX,
39
+ get_repo_root,
40
+ get_developer,
41
+ get_workspace_dir,
42
+ get_active_journal_file,
43
+ count_lines,
44
+ )
45
+ from common.developer import ensure_developer
46
+
47
+
48
+ # =============================================================================
49
+ # 默认配置
50
+ # =============================================================================
51
+
52
+ MAX_JOURNAL_LINES = 2000 # 每个日志文件最大行数
53
+
54
+
55
+ # =============================================================================
56
+ # 核心函数
57
+ # =============================================================================
58
+
59
+ def get_latest_journal_info(dev_dir: Path) -> tuple[Path | None, int, int]:
60
+ """获取最新日志文件信息。
61
+
62
+ Returns:
63
+ (文件路径, 文件编号, 行数)
64
+ """
65
+ latest_file = None
66
+ latest_num = 0
67
+
68
+ for f in dev_dir.glob(f"{FILE_JOURNAL_PREFIX}*.md"):
69
+ if not f.is_file():
70
+ continue
71
+ match = re.search(r"(\d+)$", f.stem)
72
+ if match:
73
+ num = int(match.group(1))
74
+ if num > latest_num:
75
+ latest_num = num
76
+ latest_file = f
77
+
78
+ if latest_file:
79
+ lines = count_lines(latest_file)
80
+ return latest_file, latest_num, lines
81
+
82
+ return None, 0, 0
83
+
84
+
85
+ def get_current_session(index_file: Path) -> int:
86
+ """从 index.md 读取当前会话编号。"""
87
+ if not index_file.is_file():
88
+ return 0
89
+
90
+ try:
91
+ content = index_file.read_text(encoding="utf-8")
92
+ for line in content.splitlines():
93
+ if "Total Sessions" in line:
94
+ match = re.search(r":\s*(\d+)", line)
95
+ if match:
96
+ return int(match.group(1))
97
+ except (OSError, IOError):
98
+ pass
99
+ return 0
100
+
101
+
102
+ def create_new_journal_file(
103
+ dev_dir: Path, num: int, developer: str, max_lines: int = MAX_JOURNAL_LINES
104
+ ) -> Path:
105
+ """创建新的日志文件。"""
106
+ prev_num = num - 1
107
+ new_file = dev_dir / f"{FILE_JOURNAL_PREFIX}{num}.md"
108
+ today = datetime.now().strftime("%Y-%m-%d")
109
+
110
+ content = f"""# Journal - {developer} (Part {num})
111
+
112
+ > Continuation from `{FILE_JOURNAL_PREFIX}{prev_num}.md` (archived at ~{max_lines} lines)
113
+ > Started: {today}
114
+
115
+ ---
116
+
117
+ """
118
+ new_file.write_text(content, encoding="utf-8")
119
+ return new_file
120
+
121
+
122
+ def update_index(
123
+ index_file: Path,
124
+ developer: str,
125
+ session_num: int,
126
+ journal_file: str,
127
+ max_lines: int = MAX_JOURNAL_LINES,
128
+ ) -> None:
129
+ """更新工作空间索引文件 index.md"""
130
+ today = datetime.now().strftime("%Y-%m-%d %H:%M")
131
+
132
+ # 生成日志表格
133
+ dev_dir = index_file.parent
134
+ table_rows = []
135
+ for f in sorted(dev_dir.glob(f"{FILE_JOURNAL_PREFIX}*.md")):
136
+ lines = count_lines(f)
137
+ status = "Active" if f.name == journal_file else "Archived"
138
+ table_rows.append(f"| `{f.name}` | ~{lines} | {status} |")
139
+
140
+ journals_table = "\n".join(table_rows)
141
+
142
+ index_content = f"""# Workspace Index - {developer}
143
+
144
+ > Journal tracking for AI development sessions.
145
+
146
+ ---
147
+
148
+ ## Current Status
149
+
150
+ <!-- @@@auto:current-status -->
151
+ - **Active File**: `{journal_file}`
152
+ - **Total Sessions**: {session_num}
153
+ - **Last Active**: {today}
154
+ <!-- @@@/auto:current-status -->
155
+
156
+ ---
157
+
158
+ ## Journals
159
+
160
+ | File | Lines | Status |
161
+ |------|-------|--------|
162
+ <!-- @@@auto:journals-table -->
163
+ {journals_table}
164
+ <!-- @@@/auto:journals-table -->
165
+
166
+ ---
167
+
168
+ *Auto-managed by QODER Pipeline*
169
+ """
170
+ index_file.write_text(index_content, encoding="utf-8")
171
+
172
+
173
+ # =============================================================================
174
+ # Main
175
+ # =============================================================================
176
+
177
+ def main() -> int:
178
+ """CLI 入口。"""
179
+ parser = argparse.ArgumentParser(description="Record AI session to journal")
180
+ parser.add_argument("--title", required=True, help="Session title")
181
+ parser.add_argument("--summary", default="", help="Session summary")
182
+ parser.add_argument("--commit", default="", help="Git commit hash")
183
+ parser.add_argument("--branch", default="", help="Git branch (auto-detected if omitted)")
184
+ args = parser.parse_args()
185
+
186
+ repo_root = get_repo_root()
187
+ developer = ensure_developer(repo_root)
188
+
189
+ workspace = get_workspace_dir(repo_root)
190
+ if not workspace:
191
+ print("Error: No workspace directory", file=sys.stderr)
192
+ return 1
193
+
194
+ workspace.mkdir(parents=True, exist_ok=True)
195
+
196
+ # 获取 Git 分支
197
+ branch = args.branch
198
+ if not branch:
199
+ try:
200
+ result = subprocess.run(
201
+ ["git", "branch", "--show-current"],
202
+ capture_output=True, text=True, cwd=str(repo_root)
203
+ )
204
+ branch = result.stdout.strip()
205
+ except Exception:
206
+ branch = "unknown"
207
+
208
+ # 获取最新日志文件
209
+ journal_file, journal_num, lines = get_latest_journal_info(workspace)
210
+
211
+ # 如果没有日志文件或已满, 创建新的
212
+ if not journal_file or lines >= MAX_JOURNAL_LINES:
213
+ journal_num = journal_num + 1 if journal_file else 1
214
+ journal_file = create_new_journal_file(workspace, journal_num, developer)
215
+ lines = 0
216
+
217
+ # 读取当前会话编号
218
+ index_file = workspace / "index.md"
219
+ session_num = get_current_session(index_file) + 1
220
+
221
+ # 追加会话记录
222
+ today = datetime.now().strftime("%Y-%m-%d %H:%M")
223
+ commit_line = f"- **Commit**: `{args.commit}`\n" if args.commit else ""
224
+ branch_line = f"- **Branch**: `{branch}`\n" if branch else ""
225
+
226
+ entry = f"""### Session {session_num}: {args.title}
227
+
228
+ - **Date**: {today}
229
+ - **Developer**: {developer}
230
+ {commit_line}{branch_line}
231
+ **Summary**: {args.summary or 'No summary provided.'}
232
+
233
+ ---
234
+
235
+ """
236
+ try:
237
+ with open(journal_file, "a", encoding="utf-8") as f:
238
+ f.write(entry)
239
+ except (OSError, IOError) as e:
240
+ print(f"Error: Failed to append to journal: {e}", file=sys.stderr)
241
+ return 1
242
+
243
+ # 更新索引
244
+ update_index(index_file, developer, session_num, journal_file.name)
245
+
246
+ print(f"Session {session_num} recorded in {journal_file.name}")
247
+ return 0
248
+
249
+
250
+ if __name__ == "__main__":
245
251
  sys.exit(main())