@hupan56/wlkj 2.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 (87) hide show
  1. package/bin/cli.js +213 -0
  2. package/package.json +11 -0
  3. package/templates/cli.js +198 -0
  4. package/templates/qoder/commands/wl-code.md +43 -0
  5. package/templates/qoder/commands/wl-commit.md +30 -0
  6. package/templates/qoder/commands/wl-init.md +80 -0
  7. package/templates/qoder/commands/wl-insight.md +51 -0
  8. package/templates/qoder/commands/wl-prd.md +199 -0
  9. package/templates/qoder/commands/wl-report.md +166 -0
  10. package/templates/qoder/commands/wl-search.md +52 -0
  11. package/templates/qoder/commands/wl-spec.md +18 -0
  12. package/templates/qoder/commands/wl-status.md +51 -0
  13. package/templates/qoder/commands/wl-task.md +71 -0
  14. package/templates/qoder/commands/wl-test.md +42 -0
  15. package/templates/qoder/config.toml +5 -0
  16. package/templates/qoder/config.yaml +141 -0
  17. package/templates/qoder/hooks/inject-workflow-state.py +117 -0
  18. package/templates/qoder/hooks/session-start.py +204 -0
  19. package/templates/qoder/rules/wl-pipeline.md +105 -0
  20. package/templates/qoder/scripts/add_session.py +245 -0
  21. package/templates/qoder/scripts/benchmark.py +209 -0
  22. package/templates/qoder/scripts/build_style_index.py +268 -0
  23. package/templates/qoder/scripts/code_index.py +41 -0
  24. package/templates/qoder/scripts/collect_prds.py +31 -0
  25. package/templates/qoder/scripts/common/__init__.py +0 -0
  26. package/templates/qoder/scripts/common/active_task.py +230 -0
  27. package/templates/qoder/scripts/common/atomicio.py +172 -0
  28. package/templates/qoder/scripts/common/developer.py +161 -0
  29. package/templates/qoder/scripts/common/eval_api.py +144 -0
  30. package/templates/qoder/scripts/common/feishu.py +278 -0
  31. package/templates/qoder/scripts/common/filelock.py +211 -0
  32. package/templates/qoder/scripts/common/identity.py +285 -0
  33. package/templates/qoder/scripts/common/mentions.py +134 -0
  34. package/templates/qoder/scripts/common/paths.py +311 -0
  35. package/templates/qoder/scripts/common/reqid.py +218 -0
  36. package/templates/qoder/scripts/common/search_engine.py +205 -0
  37. package/templates/qoder/scripts/common/task_utils.py +342 -0
  38. package/templates/qoder/scripts/common/terms.py +234 -0
  39. package/templates/qoder/scripts/common/utf8.py +38 -0
  40. package/templates/qoder/scripts/context_pack.py +196 -0
  41. package/templates/qoder/scripts/eval_prd.py +225 -0
  42. package/templates/qoder/scripts/export.py +487 -0
  43. package/templates/qoder/scripts/git_sync.py +1087 -0
  44. package/templates/qoder/scripts/handoff.py +22 -0
  45. package/templates/qoder/scripts/init_developer.py +76 -0
  46. package/templates/qoder/scripts/init_doctor.py +527 -0
  47. package/templates/qoder/scripts/install_qoderwork.py +339 -0
  48. package/templates/qoder/scripts/learn.py +67 -0
  49. package/templates/qoder/scripts/notify.py +5 -0
  50. package/templates/qoder/scripts/parse_prds.py +33 -0
  51. package/templates/qoder/scripts/report.py +281 -0
  52. package/templates/qoder/scripts/role.py +39 -0
  53. package/templates/qoder/scripts/run_weekly_update.bat +17 -0
  54. package/templates/qoder/scripts/run_weekly_update.sh +20 -0
  55. package/templates/qoder/scripts/search_index.py +352 -0
  56. package/templates/qoder/scripts/setup.py +453 -0
  57. package/templates/qoder/scripts/setup_weekly_cron.bat +22 -0
  58. package/templates/qoder/scripts/setup_weekly_cron.sh +19 -0
  59. package/templates/qoder/scripts/status.py +389 -0
  60. package/templates/qoder/scripts/syncgate.py +330 -0
  61. package/templates/qoder/scripts/task.py +954 -0
  62. package/templates/qoder/scripts/team.py +29 -0
  63. package/templates/qoder/scripts/team_sync.py +419 -0
  64. package/templates/qoder/scripts/workspace_init.py +102 -0
  65. package/templates/qoder/settings.json +53 -0
  66. package/templates/qoder/skills/design-review/SKILL.md +25 -0
  67. package/templates/qoder/skills/prd-generator/SKILL.md +180 -0
  68. package/templates/qoder/skills/prd-review/SKILL.md +36 -0
  69. package/templates/qoder/skills/prototype-generator/SKILL.md +141 -0
  70. package/templates/qoder/skills/spec-coder/SKILL.md +69 -0
  71. package/templates/qoder/skills/spec-generator/SKILL.md +67 -0
  72. package/templates/qoder/skills/test-generator/SKILL.md +72 -0
  73. package/templates/qoder/skills/wl-commit/SKILL.md +76 -0
  74. package/templates/qoder/skills/wl-init/SKILL.md +67 -0
  75. package/templates/qoder/skills/wl-insight/SKILL.md +81 -0
  76. package/templates/qoder/skills/wl-report/SKILL.md +87 -0
  77. package/templates/qoder/skills/wl-search/SKILL.md +75 -0
  78. package/templates/qoder/skills/wl-status/SKILL.md +61 -0
  79. package/templates/qoder/skills/wl-task/SKILL.md +58 -0
  80. package/templates/qoder/templates/prd-full-template.md +103 -0
  81. package/templates/qoder/templates/prd-quick-template.md +69 -0
  82. package/templates/qoder/templates/prototype-app.html +344 -0
  83. package/templates/qoder/templates/prototype-web.html +310 -0
  84. package/templates/root/AGENTS.md +182 -0
  85. package/templates/root/README-pipeline.md +56 -0
  86. package/templates/root/ROLES.md +85 -0
  87. package/templates/root//346/226/260/346/211/213/346/214/207/345/215/227.md +186 -0
@@ -0,0 +1,245 @@
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__":
245
+ sys.exit(main())
@@ -0,0 +1,209 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ benchmark.py - QODER 流水线性能基准 (性能优化 A7)
5
+
6
+ 测量关键操作的延迟和内存, 供优化前后对比。
7
+
8
+ Usage:
9
+ python benchmark.py # 跑全部基准
10
+ python benchmark.py --search # 只测搜索
11
+ python benchmark.py --build # 只测构建 (耗时)
12
+ python benchmark.py --json # 输出 JSON (供对比)
13
+
14
+ 指标:
15
+ - search_cold: 冷启动搜索 (Python 启动 + JSON 加载 + 扫描)
16
+ - search_warm: 热搜索 (倒排索引缓存命中)
17
+ - index_load: keyword-index.json 加载时间
18
+ - inverted_build: 倒排索引构建时间
19
+ - inverted_cache_load: 缓存加载时间
20
+ - cnmap_lookup: CN_MAP 查询 (含 auto-expand)
21
+ """
22
+
23
+ import argparse
24
+ import json
25
+ import os
26
+ import sys
27
+ import time
28
+ from datetime import datetime
29
+
30
+ try:
31
+ sys.stdout.reconfigure(encoding='utf-8', errors='replace')
32
+ except (AttributeError, TypeError, OSError):
33
+ pass
34
+
35
+ THIS_DIR = os.path.dirname(os.path.abspath(__file__)) # .qoder/scripts
36
+ sys.path.insert(0, THIS_DIR)
37
+ # BASE = repo root = scripts 的上两级 (.qoder/scripts -> .qoder -> repo)
38
+ BASE = os.path.dirname(os.path.dirname(THIS_DIR))
39
+
40
+ INDEX_DIR = os.path.join(BASE, 'data', 'index')
41
+
42
+
43
+ def measure(fn, repeat=1):
44
+ """测函数执行时间, 返回 (avg_ms, min_ms, max_ms)。"""
45
+ times = []
46
+ for _ in range(repeat):
47
+ t0 = time.perf_counter()
48
+ fn()
49
+ times.append((time.perf_counter() - t0) * 1000)
50
+ return sum(times) / len(times), min(times), max(times)
51
+
52
+
53
+ def bench_search():
54
+ """搜索基准: 冷启动 + 热搜索对比。"""
55
+ results = {}
56
+
57
+ # 1. JSON 加载 (冷)
58
+ ki_path = os.path.join(INDEX_DIR, 'keyword-index.json')
59
+ def load_ki():
60
+ with open(ki_path, encoding='utf-8') as f:
61
+ return json.load(f)
62
+ results['index_load_ms'] = measure(load_ki)[0]
63
+ ki = load_ki()
64
+ results['keyword_count'] = len(ki)
65
+
66
+ # 2. 全扫描 (旧逻辑)
67
+ from search_index import _match_keyword
68
+ queries = ['insurance', 'attendance', 'salary', 'vehicle', 'user']
69
+ def full_scan():
70
+ for q in queries:
71
+ for kw in ki:
72
+ if _match_keyword(q, kw):
73
+ pass
74
+ results['full_scan_5q_ms'] = measure(full_scan, repeat=5)[0] / 5
75
+
76
+ # 3. 倒排索引构建 (首次)
77
+ from common.search_engine import get_inverted_index, search_keywords_fast, build_inverted_index
78
+ # 清缓存测构建
79
+ cache_path = os.path.join(INDEX_DIR, '.inverted-cache.json')
80
+ if os.path.exists(cache_path):
81
+ os.remove(cache_path)
82
+ results['inverted_build_ms'] = measure(lambda: get_inverted_index(BASE))[0]
83
+
84
+ # 4. 缓存加载 (热)
85
+ results['inverted_cache_load_ms'] = measure(lambda: get_inverted_index(BASE), repeat=5)[0]
86
+
87
+ # 5. 倒排搜索 (热)
88
+ inverted = get_inverted_index(BASE)
89
+ def inverted_search():
90
+ for q in queries:
91
+ search_keywords_fast([q], inverted, ki)
92
+ results['inverted_scan_5q_ms'] = measure(inverted_search, repeat=5)[0] / 5
93
+
94
+ # 6. 提速比
95
+ if results['inverted_scan_5q_ms'] > 0:
96
+ results['speedup'] = round(results['full_scan_5q_ms'] / results['inverted_scan_5q_ms'], 1)
97
+ else:
98
+ results['speedup'] = float('inf')
99
+
100
+ return results
101
+
102
+
103
+ def bench_cnmap():
104
+ """CN_MAP 查询基准。"""
105
+ from common.terms import CN_MAP, BUSINESS_PATH_MAP, get_cn_map_with_auto
106
+ results = {}
107
+ results['cn_map_static_count'] = len(CN_MAP)
108
+ results['business_path_map_count'] = len(BUSINESS_PATH_MAP)
109
+
110
+ # auto-expand (含 PRD 扫描)
111
+ def auto_expand():
112
+ return get_cn_map_with_auto()
113
+ results['auto_expand_ms'] = measure(auto_expand)[0]
114
+ results['cn_map_total_count'] = len(get_cn_map_with_auto())
115
+
116
+ # 查询扩展
117
+ test_terms = ['考勤', '保险', '库存', '采购', '发票']
118
+ def expand_query():
119
+ out = []
120
+ for t in test_terms:
121
+ if t in CN_MAP:
122
+ out.extend(CN_MAP[t].split())
123
+ return out
124
+ results['query_expand_5q_ms'] = measure(expand_query, repeat=10)[0] / 10
125
+
126
+ return results
127
+
128
+
129
+ def bench_index_sizes():
130
+ """索引文件大小。"""
131
+ results = {}
132
+ for name in ['keyword-index.json', 'api-index.json', 'style-index.json',
133
+ 'prd-index.json', 'module-map.json', 'prd-code-map.json']:
134
+ p = os.path.join(INDEX_DIR, name)
135
+ if os.path.isfile(p):
136
+ results[name] = os.path.getsize(p)
137
+ # 缓存文件
138
+ for name in ['.inverted-cache.json', '.file-keys.json']:
139
+ p = os.path.join(INDEX_DIR, name)
140
+ if os.path.isfile(p):
141
+ results[name] = os.path.getsize(p)
142
+ return results
143
+
144
+
145
+ def main():
146
+ parser = argparse.ArgumentParser(description='QODER 性能基准')
147
+ parser.add_argument('--search', action='store_true', help='只测搜索')
148
+ parser.add_argument('--build', action='store_true', help='测构建 (耗时)')
149
+ parser.add_argument('--json', action='store_true', help='JSON 输出')
150
+ args = parser.parse_args()
151
+
152
+ report = {
153
+ 'timestamp': datetime.now().isoformat(timespec='seconds'),
154
+ 'python': sys.version.split()[0],
155
+ 'platform': sys.platform,
156
+ 'base': os.path.basename(BASE),
157
+ }
158
+
159
+ if not args.build:
160
+ print('=' * 50)
161
+ print('QODER 性能基准')
162
+ print('=' * 50)
163
+
164
+ if not args.build or args.search:
165
+ if not args.json:
166
+ print('\n--- 搜索性能 ---')
167
+ sr = bench_search()
168
+ report['search'] = sr
169
+ if not args.json:
170
+ print(' keyword-index 加载: %.0f ms (%d keys)' % (sr['index_load_ms'], sr['keyword_count']))
171
+ print(' 全扫描 (5 query): %.2f ms' % sr['full_scan_5q_ms'])
172
+ print(' 倒排索引构建: %.0f ms (首次)' % sr['inverted_build_ms'])
173
+ print(' 缓存加载: %.2f ms (热)' % sr['inverted_cache_load_ms'])
174
+ print(' 倒排搜索 (5 query): %.2f ms' % sr['inverted_scan_5q_ms'])
175
+ print(' 提速: %.1fx' % sr['speedup'])
176
+
177
+ if not args.json:
178
+ print('\n--- CN_MAP ---')
179
+ cr = bench_cnmap()
180
+ report['cnmap'] = cr
181
+ if not args.json:
182
+ print(' 静态词: %d, 路径映射: %d' % (cr['cn_map_static_count'], cr['business_path_map_count']))
183
+ print(' auto-expand: %.0f ms, 总计 %d 词' % (cr['auto_expand_ms'], cr['cn_map_total_count']))
184
+
185
+ if not args.build:
186
+ if not args.json:
187
+ print('\n--- 索引大小 ---')
188
+ sz = bench_index_sizes()
189
+ report['index_sizes'] = sz
190
+ if not args.json:
191
+ for name, size in sorted(sz.items(), key=lambda x: -x[1]):
192
+ print(' %-30s %s' % (name, format_size(size)))
193
+
194
+ if args.json:
195
+ print(json.dumps(report, indent=2, ensure_ascii=False))
196
+ else:
197
+ print('\n' + '=' * 50)
198
+
199
+
200
+ def format_size(n):
201
+ for unit in ['B', 'KB', 'MB']:
202
+ if n < 1024:
203
+ return '%.1f %s' % (n, unit)
204
+ n /= 1024
205
+ return '%.1f GB' % n
206
+
207
+
208
+ if __name__ == '__main__':
209
+ main()