@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,339 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ install_qoderwork.py - 把 .qoder/skills/ 安装到 QoderWork 桌面端可识别的位置
4
+
5
+ QoderWork 从 %USERPROFILE%\\.qoderworkcn\\skills\\ 加载技能(每个子目录一个 skill)。
6
+ 本项目源在 <repo>/.qoder/skills/,QoderWork 不会读项目目录,必须软链/拷过去。
7
+
8
+ 为什么用 junction(mklink /J)而不是 symlink(mklink /D):
9
+ - junction 不需要管理员权限,普通用户可建
10
+ - 本地评估默认启用,跨进程透明
11
+ - symlink 在未开开发者模式的 Windows 上需要 SeCreateSymbolicLink 特权
12
+
13
+ 用法:
14
+ python .qoder/scripts/install_qoderwork.py # 安装/同步(幂等)
15
+ python .qoder/scripts/install_qoderwork.py --uninstall # 删除所有 junction(不删源)
16
+ python .qoder/scripts/install_qoderwork.py --check # 仅检查,不改动
17
+ python .qoder/scripts/install_qoderwork.py --copy # 改用拷贝(非 Windows 或不想软链)
18
+
19
+ 幂等:已存在的 junction 会跳过;已存在的非 junction 目录会报错让人工处理。
20
+ """
21
+
22
+ import argparse
23
+ import os
24
+ import shutil
25
+ import subprocess
26
+ import sys
27
+ from pathlib import Path
28
+
29
+ # 确保 UTF-8 输出(Windows 控制台默认 GBK)
30
+ if sys.platform == "win32":
31
+ try:
32
+ sys.stdout.reconfigure(encoding="utf-8")
33
+ sys.stderr.reconfigure(encoding="utf-8")
34
+ except (AttributeError, IOError):
35
+ pass
36
+
37
+ # 路径自检:项目根 = 本文件上三级(scripts/ -> .qoder/ -> repo)
38
+ SCRIPT_DIR = Path(__file__).resolve().parent
39
+ PROJECT_ROOT = SCRIPT_DIR.parent.parent
40
+ SOURCE_SKILLS_DIR = PROJECT_ROOT / ".qoder" / "skills"
41
+ SOURCE_COMMANDS_DIR = PROJECT_ROOT / ".qoder" / "commands"
42
+
43
+ # QoderWork 目标目录 (官方: ~/.qoderwork/skills/ 和 ~/.qoderwork/commands/)
44
+ # 注意: 之前误用了 ~/.qoderworkcn/ (国内版旧路径), 官方文档明确是 ~/.qoderwork/
45
+ # https://docs.qoder.com/zh/qoderwork/skills
46
+ if sys.platform == "win32":
47
+ _HOME = Path(os.environ.get("USERPROFILE", str(Path.home())))
48
+ else:
49
+ _HOME = Path.home()
50
+ QODERWORK_SKILLS_DIR = _HOME / ".qoderwork" / "skills"
51
+ QODERWORK_COMMANDS_DIR = _HOME / ".qoderwork" / "commands"
52
+
53
+ # 旧的错误路径 (迁移清理用)
54
+ LEGACY_WRONG_DIR = _HOME / ".qoderworkcn" / "skills"
55
+
56
+
57
+ def is_junction(path: Path) -> bool:
58
+ """判断目录是否是 junction/symlink(reparse point)。"""
59
+ if not path.exists():
60
+ return False
61
+ # Windows: 用 dir 命令检测 <JUNCTION> 标记最可靠(无需 ctypes)
62
+ if sys.platform == "win32":
63
+ try:
64
+ # os.lstat: reparse point 的 FILE_ATTRIBUTE_REPARSE_POINT (0x400) 会体现在 st_file_attributes
65
+ import stat as _stat
66
+ st = path.lstat()
67
+ # FILE_ATTRIBUTE_REPARSE_POINT = 0x400
68
+ return bool(getattr(st, "st_file_attributes", 0) & 0x400)
69
+ except (OSError, AttributeError):
70
+ return False
71
+ else:
72
+ return path.is_symlink()
73
+
74
+
75
+ def find_source_skills() -> list:
76
+ """扫描 .qoder/skills/*/SKILL.md,返回 [(name, src_dir), ...]。"""
77
+ if not SOURCE_SKILLS_DIR.is_dir():
78
+ return []
79
+ result = []
80
+ for child in sorted(SOURCE_SKILLS_DIR.iterdir()):
81
+ if not child.is_dir():
82
+ continue
83
+ if (child / "SKILL.md").is_file():
84
+ result.append((child.name, child))
85
+ return result
86
+
87
+
88
+ def create_junction(link: Path, target: Path) -> bool:
89
+ """用 mklink /J 创建 junction。返回是否成功。"""
90
+ # 用 errors="replace" 防止 Windows GBK 输出(如"为...创建的联接")触发 UnicodeDecodeError
91
+ cmd = ["cmd", "/c", "mklink", "/J", str(link), str(target)]
92
+ try:
93
+ r = subprocess.run(
94
+ cmd,
95
+ stdout=subprocess.PIPE,
96
+ stderr=subprocess.PIPE,
97
+ # 不用 text=True,自己 decode 防编码异常
98
+ )
99
+ return r.returncode == 0
100
+ except (OSError, subprocess.SubprocessError):
101
+ return False
102
+
103
+
104
+ def install_one(name: str, src: Path, mode: str, dry: bool = False) -> str:
105
+ """
106
+ 安装一个 skill 到 QoderWork 目录。
107
+ 返回状态字符串:'created' / 'ok-existing' / 'copied' / 'error: ...' / 'skip: ...'
108
+ """
109
+ link = QODERWORK_SKILLS_DIR / name
110
+ if link.exists() or link.is_symlink():
111
+ if is_junction(link):
112
+ # 悬空 junction 检测: junction 存在但目标已移走/删除
113
+ # (常见于团队重命名 skill 后, 旧 junction 变成死链)
114
+ # link.exists() 对悬空 junction 返回 False, 但 link.is_symlink() 可能 True
115
+ # 用 os.path.exists 解析目标判断
116
+ try:
117
+ target_alive = os.path.exists(str(link))
118
+ except OSError:
119
+ target_alive = False
120
+ if target_alive:
121
+ return "ok-existing"
122
+ else:
123
+ # 悬空 junction, 删除后重建
124
+ if dry:
125
+ return "would-recreate (dangling)"
126
+ try:
127
+ if sys.platform == "win32":
128
+ subprocess.run(["cmd", "/c", "rmdir", str(link)],
129
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True)
130
+ else:
131
+ os.unlink(str(link))
132
+ except (OSError, subprocess.SubprocessError) as e:
133
+ return "error: 悬空 junction 无法删除 (手动 rmdir {}): {}".format(link, e)
134
+ # 落到下面 recreate 逻辑
135
+ else:
136
+ # 非 junction 的真实目录/文件 -> 不覆盖
137
+ return f"skip: {name} 已存在且不是软链(避免覆盖,请人工确认 {link})"
138
+
139
+ if dry:
140
+ return "would-create"
141
+
142
+ QODERWORK_SKILLS_DIR.mkdir(parents=True, exist_ok=True)
143
+
144
+ if mode == "copy":
145
+ try:
146
+ shutil.copytree(src, link)
147
+ return "copied"
148
+ except (OSError, shutil.Error) as e:
149
+ return f"error: copy 失败 {e}"
150
+ else:
151
+ # junction 模式(默认)
152
+ if sys.platform != "win32":
153
+ # 非 Windows 退化为 symlink,失败再退化为 copy
154
+ try:
155
+ os.symlink(src, link, target_is_directory=True)
156
+ return "created(symlink)"
157
+ except (OSError, NotImplementedError):
158
+ try:
159
+ shutil.copytree(src, link)
160
+ return "copied(fallback)"
161
+ except (OSError, shutil.Error) as e:
162
+ return f"error: {e}"
163
+ if create_junction(link, src):
164
+ return "created"
165
+ return "error: mklink /J 失败(可能需要检查路径或权限)"
166
+
167
+
168
+ def uninstall_one(name: str) -> str:
169
+ """删除一个 junction(只删 link,不删源)。"""
170
+ link = QODERWORK_SKILLS_DIR / name
171
+ if not link.exists() and not link.is_symlink():
172
+ return "absent"
173
+ if not is_junction(link):
174
+ return f"skip: {name} 不是软链(不动真实目录,请人工删除 {link})"
175
+ try:
176
+ # 删 junction 用 rmdir(不递归到源)
177
+ if sys.platform == "win32":
178
+ subprocess.run(["cmd", "/c", "rmdir", str(link)],
179
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True)
180
+ else:
181
+ os.unlink(str(link))
182
+ return "removed"
183
+ except (OSError, subprocess.SubprocessError) as e:
184
+ return f"error: {e}"
185
+
186
+
187
+ def main():
188
+ parser = argparse.ArgumentParser(
189
+ description="把 .qoder/skills/ 安装到 QoderWork 桌面端目录"
190
+ )
191
+ parser.add_argument("--uninstall", action="store_true",
192
+ help="删除所有 junction(不删源)")
193
+ parser.add_argument("--check", action="store_true",
194
+ help="仅检查状态,不改动")
195
+ parser.add_argument("--copy", action="store_true",
196
+ help="用拷贝代替 junction(非 Windows 或不想软链时用)")
197
+ args = parser.parse_args()
198
+
199
+ print("=" * 56)
200
+ print("QoderWork 技能安装器")
201
+ print(f" 源: {SOURCE_SKILLS_DIR}")
202
+ print(f" 目标: {QODERWORK_SKILLS_DIR}")
203
+ print("=" * 56)
204
+
205
+ # 迁移清理: 旧版本错装到了 ~/.qoderworkcn/, 清理掉避免混淆
206
+ if not args.check and LEGACY_WRONG_DIR.exists():
207
+ legacy_junctions = []
208
+ try:
209
+ for child in LEGACY_WRONG_DIR.iterdir():
210
+ if is_junction(child):
211
+ legacy_junctions.append(child.name)
212
+ except OSError:
213
+ pass
214
+ if legacy_junctions:
215
+ print(f"\n[迁移] 发现旧版本错装在 {LEGACY_WRONG_DIR} ({len(legacy_junctions)} 个)")
216
+ print(f" QoderWork 实际读的是 ~/.qoderwork/, 正在清理旧 junction...")
217
+ for name in legacy_junctions:
218
+ try:
219
+ subprocess.run(["cmd", "/c", "rmdir", str(LEGACY_WRONG_DIR / name)],
220
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True)
221
+ except (OSError, subprocess.SubprocessError):
222
+ pass
223
+ print(f" 已清理 {len(legacy_junctions)} 个旧 junction")
224
+ # 若旧目录空了, 删掉
225
+ try:
226
+ remaining = list(LEGACY_WRONG_DIR.iterdir())
227
+ if not remaining and LEGACY_WRONG_DIR.parent.exists():
228
+ LEGACY_WRONG_DIR.rmdir()
229
+ print(f" 已删除空目录 {LEGACY_WRONG_DIR.parent}")
230
+ except OSError:
231
+ pass
232
+
233
+ sources = find_source_skills()
234
+ if not sources:
235
+ print(f"[ERR] 源目录无 skill:{SOURCE_SKILLS_DIR}")
236
+ print(" 检查 .qoder/skills/*/SKILL.md 是否存在")
237
+ return 1
238
+
239
+ mode = "copy" if args.copy else "junction"
240
+ action = "check" if args.check else ("uninstall" if args.uninstall else "install")
241
+ print(f"\n模式: {action} / link={mode},发现 {len(sources)} 个源 skill\n")
242
+
243
+ results = {}
244
+ for name, src in sources:
245
+ if action == "uninstall":
246
+ results[name] = uninstall_one(name)
247
+ elif action == "check":
248
+ link = QODERWORK_SKILLS_DIR / name
249
+ if link.exists() or link.is_symlink():
250
+ results[name] = "ok(junction)" if is_junction(link) else "WARN(非软链)"
251
+ else:
252
+ results[name] = "missing"
253
+ else: # install
254
+ results[name] = install_one(name, src, mode, dry=False)
255
+
256
+ # 打印结果
257
+ print("-" * 56)
258
+ counts = {"ok": 0, "create": 0, "copy": 0, "warn": 0, "err": 0, "missing": 0}
259
+ for name, status in sorted(results.items()):
260
+ tag = ""
261
+ if status.startswith("ok") or status == "copied" or status.startswith("created"):
262
+ if status.startswith("created") or status == "copied":
263
+ counts["create"] += 1
264
+ tag = "[NEW]"
265
+ else:
266
+ counts["ok"] += 1
267
+ tag = "[OK]"
268
+ elif status.startswith("skip"):
269
+ counts["warn"] += 1
270
+ tag = "[WARN]"
271
+ elif status.startswith("error"):
272
+ counts["err"] += 1
273
+ tag = "[ERR]"
274
+ elif status == "missing":
275
+ counts["missing"] += 1
276
+ tag = "[MISS]"
277
+ elif status == "removed":
278
+ counts["ok"] += 1
279
+ tag = "[DEL]"
280
+ elif status.startswith("WARN"):
281
+ counts["warn"] += 1
282
+ tag = "[WARN]"
283
+ else:
284
+ counts["ok"] += 1
285
+ tag = "[OK]"
286
+ print(f" {tag:6} {name:22} {status}")
287
+
288
+ print("-" * 56)
289
+ print(f"\n小结: 新建 {counts['create']} / 已存在 {counts['ok']} / "
290
+ f"缺失 {counts['missing']} / 警告 {counts['warn']} / 错误 {counts['err']}")
291
+
292
+ # 同时安装 commands (让 QoderWork 用户级也能看到 /wl-* 命令)
293
+ if action in ("install", "check") and SOURCE_COMMANDS_DIR.is_dir():
294
+ print("\n--- Commands (/wl-* 斜杠命令) ---")
295
+ cmd_count = {"ok": 0, "new": 0}
296
+ for cmd_file in sorted(SOURCE_COMMANDS_DIR.glob("wl-*.md")):
297
+ name = cmd_file.name # e.g. wl-prd.md
298
+ target = QODERWORK_COMMANDS_DIR / name
299
+ if action == "check":
300
+ if target.exists():
301
+ cmd_count["ok"] += 1
302
+ else:
303
+ print(f" [MISS] {name}")
304
+ else: # install
305
+ if target.exists():
306
+ cmd_count["ok"] += 1
307
+ else:
308
+ try:
309
+ QODERWORK_COMMANDS_DIR.mkdir(parents=True, exist_ok=True)
310
+ # commands 是单文件, 用拷贝 (不像 skills 是目录用 junction)
311
+ import shutil
312
+ shutil.copy2(str(cmd_file), str(target))
313
+ cmd_count["new"] += 1
314
+ except OSError as e:
315
+ print(f" [ERR] {name}: {e}")
316
+ print(f" commands: {cmd_count['new']} 新建 / {cmd_count['ok']} 已存在")
317
+
318
+ if action == "install" and counts["err"] == 0:
319
+ print("\n✓ 安装完成。重启 QoderWork(或新建对话)后技能生效。")
320
+ print(" 在 QoderWork 里直接用自然语言即可,例如:")
321
+ print(' "写个保险异常筛选的 PRD" -> prd-generator + prototype-generator')
322
+ print(' "查一下考勤代码在哪" -> wl-search')
323
+ elif action == "install" and counts["err"] > 0:
324
+ print(f"\n⚠ 有 {counts['err']} 个安装失败,见上方 [ERR] 行。")
325
+ print(" 常见原因: 目标路径被占用为非软链。手动删除该目录后重跑本脚本。")
326
+ return 1
327
+ elif action == "uninstall":
328
+ print("\n✓ 卸载完成(源文件未动)。")
329
+ elif action == "check":
330
+ if counts["missing"] > 0 or counts["warn"] > 0:
331
+ print("\n⚠ 检测到缺失/异常,建议跑: python .qoder/scripts/install_qoderwork.py")
332
+ return 1
333
+ else:
334
+ print("\n✓ 全部 skill 已正确安装到 QoderWork 目录。")
335
+ return 0
336
+
337
+
338
+ if __name__ == "__main__":
339
+ sys.exit(main())
@@ -0,0 +1,67 @@
1
+ # learn.py - Learning engine for QODER evolution
2
+ import os, json
3
+ from datetime import datetime
4
+
5
+ BASE = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
6
+ LEARNING_DIR = os.path.join(BASE, '.qoder', 'learning')
7
+ FEEDBACK_FILE = os.path.join(LEARNING_DIR, 'feedback.jsonl')
8
+ PREFS_FILE = os.path.join(LEARNING_DIR, 'preferences.yaml')
9
+
10
+ def record_feedback(event, data):
11
+ entry = {
12
+ 'ts': datetime.now().isoformat(),
13
+ 'event': event,
14
+ 'data': data
15
+ }
16
+ with open(FEEDBACK_FILE, 'a', encoding='utf-8') as f:
17
+ f.write(json.dumps(entry, ensure_ascii=False) + chr(10))
18
+ _update_stats(event)
19
+ print('Recorded: ' + event)
20
+
21
+ def _update_stats(event):
22
+ import yaml
23
+ prefs = _load_prefs()
24
+ stats = {
25
+ 'prd_accepted': 'prds_generated',
26
+ 'prd_rejected': 'rejections',
27
+ 'review_done': 'reviews_done',
28
+ 'task_completed': 'tasks_completed',
29
+ 'handoff_done': 'handoffs_completed',
30
+ }
31
+ key = stats.get(event)
32
+ if key:
33
+ prefs[key] = prefs.get(key, 0) + 1
34
+ _save_prefs(prefs)
35
+
36
+ def _load_prefs():
37
+ import yaml
38
+ if not os.path.isfile(PREFS_FILE): return {}
39
+ try: return yaml.safe_load(open(PREFS_FILE, encoding='utf-8')) or {}
40
+ except: return {}
41
+
42
+ def _save_prefs(prefs):
43
+ import yaml
44
+ with open(PREFS_FILE, 'w', encoding='utf-8') as f:
45
+ yaml.dump(prefs, f, allow_unicode=True, default_flow_style=False)
46
+
47
+ def get_prefs():
48
+ return _load_prefs()
49
+
50
+ def should_evolve():
51
+ prefs = _load_prefs()
52
+ tc = prefs.get('tasks_completed', 0)
53
+ return tc > 0 and tc % 10 == 0
54
+
55
+ if __name__ == '__main__':
56
+ import sys
57
+ if len(sys.argv) < 2:
58
+ print('Usage: learn.py record <event> [json] | learn.py status')
59
+ elif sys.argv[1] == 'status':
60
+ prefs = get_prefs()
61
+ print('Learning Stats:')
62
+ for k, v in prefs.items():
63
+ if isinstance(v, int): print(' ' + k + ': ' + str(v))
64
+ print(' Evolve ready: ' + str(should_evolve()))
65
+ elif sys.argv[1] == 'record' and len(sys.argv) >= 3:
66
+ data = json.loads(sys.argv[3]) if len(sys.argv) > 3 else {}
67
+ record_feedback(sys.argv[2], data)
@@ -0,0 +1,5 @@
1
+ # notify.py - Simple console notification
2
+ class Notifier:
3
+ def notify(self, event, title, content):
4
+ sep = '=' * 50
5
+ print(chr(10).join(['', sep, 'NOTIFY: ' + title, sep, content, sep]))
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Parse PRD files and build PRD index (data/index/prd-index.json).
5
+
6
+ THIN WRAPPER: the actual parsing logic lives in git_sync.py (single
7
+ source of truth - previously this file kept its own drifted copy of
8
+ the parser and term maps). Kept for backwards compatibility.
9
+
10
+ Usage:
11
+ python parse_prds.py # rebuild prd-index.json + prd-code-map.json
12
+ """
13
+
14
+ import os
15
+ import sys
16
+
17
+ # UTF-8 stdio (防御性: stdout 被捕获时不崩溃)
18
+ try:
19
+ sys.stdout.reconfigure(encoding='utf-8', errors='replace')
20
+ except (AttributeError, TypeError, OSError, IOError):
21
+ try:
22
+ sys.stdout.reconfigure(encoding='utf-8')
23
+ except Exception:
24
+ pass
25
+
26
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
27
+
28
+ from git_sync import build_prd_index, build_prd_code_mapping, FAILURES
29
+
30
+ if __name__ == '__main__':
31
+ build_prd_index()
32
+ build_prd_code_mapping()
33
+ sys.exit(1 if FAILURES else 0)