@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.
- package/bin/cli.js +213 -0
- package/package.json +11 -0
- package/templates/cli.js +198 -0
- package/templates/qoder/commands/wl-code.md +43 -0
- package/templates/qoder/commands/wl-commit.md +30 -0
- package/templates/qoder/commands/wl-init.md +80 -0
- package/templates/qoder/commands/wl-insight.md +51 -0
- package/templates/qoder/commands/wl-prd.md +199 -0
- package/templates/qoder/commands/wl-report.md +166 -0
- package/templates/qoder/commands/wl-search.md +52 -0
- package/templates/qoder/commands/wl-spec.md +18 -0
- package/templates/qoder/commands/wl-status.md +51 -0
- package/templates/qoder/commands/wl-task.md +71 -0
- package/templates/qoder/commands/wl-test.md +42 -0
- package/templates/qoder/config.toml +5 -0
- package/templates/qoder/config.yaml +141 -0
- package/templates/qoder/hooks/inject-workflow-state.py +117 -0
- package/templates/qoder/hooks/session-start.py +204 -0
- package/templates/qoder/rules/wl-pipeline.md +105 -0
- package/templates/qoder/scripts/add_session.py +245 -0
- package/templates/qoder/scripts/benchmark.py +209 -0
- package/templates/qoder/scripts/build_style_index.py +268 -0
- package/templates/qoder/scripts/code_index.py +41 -0
- package/templates/qoder/scripts/collect_prds.py +31 -0
- package/templates/qoder/scripts/common/__init__.py +0 -0
- package/templates/qoder/scripts/common/active_task.py +230 -0
- package/templates/qoder/scripts/common/atomicio.py +172 -0
- package/templates/qoder/scripts/common/developer.py +161 -0
- package/templates/qoder/scripts/common/eval_api.py +144 -0
- package/templates/qoder/scripts/common/feishu.py +278 -0
- package/templates/qoder/scripts/common/filelock.py +211 -0
- package/templates/qoder/scripts/common/identity.py +285 -0
- package/templates/qoder/scripts/common/mentions.py +134 -0
- package/templates/qoder/scripts/common/paths.py +311 -0
- package/templates/qoder/scripts/common/reqid.py +218 -0
- package/templates/qoder/scripts/common/search_engine.py +205 -0
- package/templates/qoder/scripts/common/task_utils.py +342 -0
- package/templates/qoder/scripts/common/terms.py +234 -0
- package/templates/qoder/scripts/common/utf8.py +38 -0
- package/templates/qoder/scripts/context_pack.py +196 -0
- package/templates/qoder/scripts/eval_prd.py +225 -0
- package/templates/qoder/scripts/export.py +487 -0
- package/templates/qoder/scripts/git_sync.py +1087 -0
- package/templates/qoder/scripts/handoff.py +22 -0
- package/templates/qoder/scripts/init_developer.py +76 -0
- package/templates/qoder/scripts/init_doctor.py +527 -0
- package/templates/qoder/scripts/install_qoderwork.py +339 -0
- package/templates/qoder/scripts/learn.py +67 -0
- package/templates/qoder/scripts/notify.py +5 -0
- package/templates/qoder/scripts/parse_prds.py +33 -0
- package/templates/qoder/scripts/report.py +281 -0
- package/templates/qoder/scripts/role.py +39 -0
- package/templates/qoder/scripts/run_weekly_update.bat +17 -0
- package/templates/qoder/scripts/run_weekly_update.sh +20 -0
- package/templates/qoder/scripts/search_index.py +352 -0
- package/templates/qoder/scripts/setup.py +453 -0
- package/templates/qoder/scripts/setup_weekly_cron.bat +22 -0
- package/templates/qoder/scripts/setup_weekly_cron.sh +19 -0
- package/templates/qoder/scripts/status.py +389 -0
- package/templates/qoder/scripts/syncgate.py +330 -0
- package/templates/qoder/scripts/task.py +954 -0
- package/templates/qoder/scripts/team.py +29 -0
- package/templates/qoder/scripts/team_sync.py +419 -0
- package/templates/qoder/scripts/workspace_init.py +102 -0
- package/templates/qoder/settings.json +53 -0
- package/templates/qoder/skills/design-review/SKILL.md +25 -0
- package/templates/qoder/skills/prd-generator/SKILL.md +180 -0
- package/templates/qoder/skills/prd-review/SKILL.md +36 -0
- package/templates/qoder/skills/prototype-generator/SKILL.md +141 -0
- package/templates/qoder/skills/spec-coder/SKILL.md +69 -0
- package/templates/qoder/skills/spec-generator/SKILL.md +67 -0
- package/templates/qoder/skills/test-generator/SKILL.md +72 -0
- package/templates/qoder/skills/wl-commit/SKILL.md +76 -0
- package/templates/qoder/skills/wl-init/SKILL.md +67 -0
- package/templates/qoder/skills/wl-insight/SKILL.md +81 -0
- package/templates/qoder/skills/wl-report/SKILL.md +87 -0
- package/templates/qoder/skills/wl-search/SKILL.md +75 -0
- package/templates/qoder/skills/wl-status/SKILL.md +61 -0
- package/templates/qoder/skills/wl-task/SKILL.md +58 -0
- package/templates/qoder/templates/prd-full-template.md +103 -0
- package/templates/qoder/templates/prd-quick-template.md +69 -0
- package/templates/qoder/templates/prototype-app.html +344 -0
- package/templates/qoder/templates/prototype-web.html +310 -0
- package/templates/root/AGENTS.md +182 -0
- package/templates/root/README-pipeline.md +56 -0
- package/templates/root/ROLES.md +85 -0
- package/templates/root//346/226/260/346/211/213/346/214/207/345/215/227.md +186 -0
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
setup.py - 一键就绪 (小而美)
|
|
5
|
+
|
|
6
|
+
跑这一条命令, 把整套工作流配好可用:
|
|
7
|
+
python .qoder/scripts/setup.py
|
|
8
|
+
python .qoder/scripts/setup.py 小王 # 指定名字
|
|
9
|
+
python .qoder/scripts/setup.py 小王 dev # 指定名字+角色
|
|
10
|
+
python .qoder/scripts/setup.py --skip-cron # 跳过 cron 询问
|
|
11
|
+
|
|
12
|
+
自动完成:
|
|
13
|
+
1. 环境自检 (Python / git)
|
|
14
|
+
2. 身份探测 (从 git config user.name, 或交互输入)
|
|
15
|
+
3. 调 init_doctor --fix (写 .developer / git 对齐 / 拉源码 / 建索引 / QoderWork 检查)
|
|
16
|
+
4. config.yaml 引导 (源码仓库未配时提示)
|
|
17
|
+
5. cron 一键注册 (询问式 opt-in)
|
|
18
|
+
6. QoderWork 技能安装 (检测到桌面端时询问)
|
|
19
|
+
7. 打印就绪报告
|
|
20
|
+
|
|
21
|
+
幂等: 可重复跑, 不会重复安装。
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
import argparse
|
|
25
|
+
import os
|
|
26
|
+
import subprocess
|
|
27
|
+
import sys
|
|
28
|
+
from pathlib import Path
|
|
29
|
+
|
|
30
|
+
try:
|
|
31
|
+
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
|
|
32
|
+
sys.stderr.reconfigure(encoding='utf-8', errors='replace')
|
|
33
|
+
except (AttributeError, TypeError, OSError):
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
THIS_DIR = Path(__file__).resolve().parent
|
|
37
|
+
BASE = THIS_DIR.parent.parent # repo root
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# ============================================================
|
|
41
|
+
# 工具函数
|
|
42
|
+
# ============================================================
|
|
43
|
+
|
|
44
|
+
def ok(msg):
|
|
45
|
+
print(f' [\u2713] {msg}') # ✓
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def warn(msg):
|
|
49
|
+
print(f' [!] {msg}')
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def fail(msg):
|
|
53
|
+
print(f' [\u2717] {msg}', file=sys.stderr)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def ask(prompt, default='y'):
|
|
57
|
+
"""交互式 yes/no, 默认 default。非交互环境 (无 stdin) 返回 default。"""
|
|
58
|
+
if not sys.stdin.isatty():
|
|
59
|
+
return default.lower() in ('y', 'yes')
|
|
60
|
+
try:
|
|
61
|
+
ans = input(f'{prompt} [{default}] ').strip().lower() or default
|
|
62
|
+
return ans in ('y', 'yes')
|
|
63
|
+
except (EOFError, KeyboardInterrupt):
|
|
64
|
+
return default.lower() in ('y', 'yes')
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def run(cmd, **kwargs):
|
|
68
|
+
"""跑子进程, 返回 CompletedProcess。encoding 容错。"""
|
|
69
|
+
kwargs.setdefault('capture_output', True)
|
|
70
|
+
kwargs.setdefault('text', True)
|
|
71
|
+
kwargs.setdefault('encoding', 'utf-8')
|
|
72
|
+
kwargs.setdefault('errors', 'replace')
|
|
73
|
+
return subprocess.run(cmd, **kwargs)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
# ============================================================
|
|
77
|
+
# Step 0: 环境自检
|
|
78
|
+
# ============================================================
|
|
79
|
+
|
|
80
|
+
def check_env():
|
|
81
|
+
print('\n--- 环境自检 ---')
|
|
82
|
+
# Python 版本
|
|
83
|
+
if sys.version_info < (3, 9):
|
|
84
|
+
fail(f'Python {sys.version.split()[0]} 版本过低, 需要 3.9+')
|
|
85
|
+
return False
|
|
86
|
+
ok(f'Python {sys.version.split()[0]}')
|
|
87
|
+
# git (可选: 无 git 时尝试自动装, 装不上就降级)
|
|
88
|
+
r = None
|
|
89
|
+
try:
|
|
90
|
+
r = run(['git', '--version'])
|
|
91
|
+
except FileNotFoundError:
|
|
92
|
+
pass
|
|
93
|
+
if not r or r.returncode != 0:
|
|
94
|
+
# 尝试自动安装 git
|
|
95
|
+
if try_install_git():
|
|
96
|
+
ok('git 已自动安装')
|
|
97
|
+
else:
|
|
98
|
+
warn('git 未安装 — 团队同步/源码克隆将禁用, 本地 PRD/搜索/任务/报告仍可用')
|
|
99
|
+
print(' (想启用团队协作: 安装 git 后重跑 setup.py)')
|
|
100
|
+
return True # 不阻塞, 继续
|
|
101
|
+
ok('git 可用')
|
|
102
|
+
return True
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def try_install_git():
|
|
106
|
+
"""尝试自动安装 git (Windows: winget; macOS: brew; Linux: apt)。
|
|
107
|
+
成功返回 True, 失败/无法自动装返回 False。"""
|
|
108
|
+
print('\n 检测到 git 未安装, 尝试自动安装...')
|
|
109
|
+
if not sys.stdin.isatty():
|
|
110
|
+
# 非交互环境, 不自动装 (避免意外操作)
|
|
111
|
+
warn('非交互环境, 跳过自动安装')
|
|
112
|
+
return False
|
|
113
|
+
|
|
114
|
+
platform = sys.platform
|
|
115
|
+
if platform == 'win32':
|
|
116
|
+
# Windows: 优先 winget, 回退 choco
|
|
117
|
+
if ask('用 winget 安装 Git for Windows? (需要网络, 可能要管理员权限)', 'y'):
|
|
118
|
+
print(' 运行: winget install --id Git.Git -e --source winget')
|
|
119
|
+
r = run(['winget', 'install', '--id', 'Git.Git', '-e', '--source', 'winget',
|
|
120
|
+
'--accept-source-agreements', '--accept-package-agreements'])
|
|
121
|
+
if r.returncode == 0:
|
|
122
|
+
# 刷新 PATH (winget 装的 git 需要新终端才生效, 这里尝试常见路径)
|
|
123
|
+
_refresh_git_path()
|
|
124
|
+
return _git_now_available()
|
|
125
|
+
else:
|
|
126
|
+
warn('winget 安装失败: ' + (r.stderr or '').strip()[:150])
|
|
127
|
+
print(' 手动下载: https://git-scm.com/download/win')
|
|
128
|
+
return False
|
|
129
|
+
elif platform == 'darwin':
|
|
130
|
+
# macOS: brew
|
|
131
|
+
if ask('用 Homebrew 安装 git? (brew install git)', 'y'):
|
|
132
|
+
r = run(['brew', 'install', 'git'])
|
|
133
|
+
if r.returncode == 0:
|
|
134
|
+
return _git_now_available()
|
|
135
|
+
else:
|
|
136
|
+
warn('brew 安装失败: ' + (r.stderr or '').strip()[:150])
|
|
137
|
+
print(' 或手动: https://git-scm.com/download/mac')
|
|
138
|
+
return False
|
|
139
|
+
else:
|
|
140
|
+
# Linux: apt (Debian/Ubuntu) / yum (CentOS) / dnf
|
|
141
|
+
for mgr, cmd in [('apt', ['sudo', 'apt-get', 'install', '-y', 'git']),
|
|
142
|
+
('dnf', ['sudo', 'dnf', 'install', '-y', 'git']),
|
|
143
|
+
('yum', ['sudo', 'yum', 'install', '-y', 'git'])]:
|
|
144
|
+
# 检测包管理器是否存在
|
|
145
|
+
check = run(['which', mgr])
|
|
146
|
+
if check.returncode == 0:
|
|
147
|
+
if ask(f'用 {mgr} 安装 git?', 'y'):
|
|
148
|
+
r = run(cmd)
|
|
149
|
+
if r.returncode == 0:
|
|
150
|
+
return _git_now_available()
|
|
151
|
+
else:
|
|
152
|
+
warn(f'{mgr} 安装失败: ' + (r.stderr or '').strip()[:150])
|
|
153
|
+
return False
|
|
154
|
+
warn('未检测到 apt/dnf/yum, 请手动安装 git')
|
|
155
|
+
return False
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _refresh_git_path():
|
|
159
|
+
"""winget 装完 git 后, 尝试把常见路径加到 PATH (当前进程)。"""
|
|
160
|
+
candidates = [
|
|
161
|
+
r'C:\Program Files\Git\cmd',
|
|
162
|
+
r'C:\Program Files\Git\bin',
|
|
163
|
+
r'C:\Program Files (x86)\Git\cmd',
|
|
164
|
+
]
|
|
165
|
+
for c in candidates:
|
|
166
|
+
if os.path.isdir(c):
|
|
167
|
+
os.environ['PATH'] = c + os.pathsep + os.environ.get('PATH', '')
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def _git_now_available():
|
|
171
|
+
"""检查 git 现在是否可用 (装完后立即验证)。"""
|
|
172
|
+
try:
|
|
173
|
+
r = run(['git', '--version'])
|
|
174
|
+
return r.returncode == 0
|
|
175
|
+
except FileNotFoundError:
|
|
176
|
+
return False
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
# ============================================================
|
|
180
|
+
# Step 1: 身份探测
|
|
181
|
+
# ============================================================
|
|
182
|
+
|
|
183
|
+
def detect_name(explicit=None):
|
|
184
|
+
"""身份探测: 显式参数 > git config user.name > 交互输入。"""
|
|
185
|
+
print('\n--- 身份探测 ---')
|
|
186
|
+
if explicit:
|
|
187
|
+
ok(f'使用指定名字: {explicit}')
|
|
188
|
+
return explicit
|
|
189
|
+
# 从 git config 探测 (git 可能未安装)
|
|
190
|
+
git_name = ''
|
|
191
|
+
try:
|
|
192
|
+
r = run(['git', 'config', 'user.name'])
|
|
193
|
+
if r.returncode == 0:
|
|
194
|
+
git_name = r.stdout.strip()
|
|
195
|
+
except FileNotFoundError:
|
|
196
|
+
pass # git 未装, 跳过
|
|
197
|
+
if git_name:
|
|
198
|
+
if ask(f'检测到 git user.name = "{git_name}", 用这个名字?', 'y'):
|
|
199
|
+
ok(f'使用 git 名字: {git_name}')
|
|
200
|
+
return git_name
|
|
201
|
+
# 交互输入
|
|
202
|
+
if not sys.stdin.isatty():
|
|
203
|
+
# 非交互环境 (如 AI 调用), 默认用 git 名字或 'me'
|
|
204
|
+
name = git_name or 'me'
|
|
205
|
+
warn(f'非交互环境, 默认用: {name}')
|
|
206
|
+
return name
|
|
207
|
+
while True:
|
|
208
|
+
try:
|
|
209
|
+
name = input('你叫什么名字? ').strip()
|
|
210
|
+
if name and len(name) <= 32:
|
|
211
|
+
ok(f'名字: {name}')
|
|
212
|
+
return name
|
|
213
|
+
print(' 名字需 1-32 字符, 重试。')
|
|
214
|
+
except (EOFError, KeyboardInterrupt):
|
|
215
|
+
warn('未输入, 用 git 名字或 "me"')
|
|
216
|
+
return git_name or 'me'
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
# ============================================================
|
|
220
|
+
# Step 2: 调 init_doctor --fix (主体)
|
|
221
|
+
# ============================================================
|
|
222
|
+
|
|
223
|
+
def run_doctor(name, role):
|
|
224
|
+
print('\n--- 初始化 (init_doctor) ---')
|
|
225
|
+
cmd = [sys.executable, str(THIS_DIR / 'init_doctor.py'), '--fix', name, role]
|
|
226
|
+
r = subprocess.run(cmd, cwd=str(BASE))
|
|
227
|
+
# init_doctor 返回 0=健康, 1=有未解决问题
|
|
228
|
+
if r.returncode == 0:
|
|
229
|
+
ok('init_doctor 完成, 环境健康')
|
|
230
|
+
return True
|
|
231
|
+
elif r.returncode == 1:
|
|
232
|
+
warn('init_doctor 完成, 但有未解决问题 (见上方 [WARN])')
|
|
233
|
+
return True # 不阻塞, 用户可继续
|
|
234
|
+
else:
|
|
235
|
+
fail(f'init_doctor 失败 (exit {r.returncode})')
|
|
236
|
+
return False
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
# ============================================================
|
|
240
|
+
# Step 3: config.yaml 引导
|
|
241
|
+
# ============================================================
|
|
242
|
+
|
|
243
|
+
def guide_config():
|
|
244
|
+
"""检查 config.yaml 的 git_sync.projects 是否有效配置。"""
|
|
245
|
+
print('\n--- 源码仓库配置 ---')
|
|
246
|
+
cfg = BASE / '.qoder' / 'config.yaml'
|
|
247
|
+
if not cfg.is_file():
|
|
248
|
+
warn('config.yaml 不存在 (init_doctor 会创建默认)')
|
|
249
|
+
return
|
|
250
|
+
|
|
251
|
+
try:
|
|
252
|
+
content = cfg.read_text(encoding='utf-8')
|
|
253
|
+
except Exception:
|
|
254
|
+
return
|
|
255
|
+
|
|
256
|
+
# 检测 git_sync.projects 是否有真实 URL (非占位/注释)
|
|
257
|
+
has_real_url = False
|
|
258
|
+
in_projects = False
|
|
259
|
+
for line in content.splitlines():
|
|
260
|
+
s = line.strip()
|
|
261
|
+
if s.startswith('projects:'):
|
|
262
|
+
in_projects = True
|
|
263
|
+
continue
|
|
264
|
+
if in_projects:
|
|
265
|
+
if s and not s.startswith('#') and not line[0].isspace():
|
|
266
|
+
in_projects = False # 离开 projects 段
|
|
267
|
+
continue
|
|
268
|
+
if 'url:' in s and 'your-' not in s and 'example.com' not in s and '占位' not in s and not s.startswith('#'):
|
|
269
|
+
has_real_url = True
|
|
270
|
+
break
|
|
271
|
+
|
|
272
|
+
if has_real_url:
|
|
273
|
+
ok('源码仓库已配置')
|
|
274
|
+
else:
|
|
275
|
+
warn('config.yaml 的 git_sync.projects 未填真实 URL')
|
|
276
|
+
print(' 打开 .qoder/config.yaml, 找到 git_sync.projects 段,')
|
|
277
|
+
print(' 把 url 改成你团队的 git 地址。或手动把代码放到 data/code/。')
|
|
278
|
+
print(' 填完后重跑 setup.py 即可自动克隆+建索引。')
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
# ============================================================
|
|
282
|
+
# Step 4: cron 一键注册
|
|
283
|
+
# ============================================================
|
|
284
|
+
|
|
285
|
+
def offer_cron(skip=False):
|
|
286
|
+
if skip:
|
|
287
|
+
return
|
|
288
|
+
print('\n--- 周五自动更新 ---')
|
|
289
|
+
# 检测是否已注册
|
|
290
|
+
if sys.platform == 'win32':
|
|
291
|
+
r = run(['schtasks', '/query', '/tn', 'QODER-WeeklyUpdate'])
|
|
292
|
+
registered = r.returncode == 0
|
|
293
|
+
else:
|
|
294
|
+
r = run(['crontab', '-l'])
|
|
295
|
+
registered = 'QODER-WeeklyUpdate' in (r.stdout or '') or 'run_weekly_update' in (r.stdout or '')
|
|
296
|
+
|
|
297
|
+
if registered:
|
|
298
|
+
ok('周五 cron 已注册')
|
|
299
|
+
return
|
|
300
|
+
|
|
301
|
+
if ask('注册周五 18:00 自动更新知识图谱? (团队只需一台机器注册)', 'y'):
|
|
302
|
+
if sys.platform == 'win32':
|
|
303
|
+
r = run(['cmd', '/c', str(THIS_DIR / 'setup_weekly_cron.bat')], cwd=str(BASE))
|
|
304
|
+
else:
|
|
305
|
+
r = run(['bash', str(THIS_DIR / 'setup_weekly_cron.sh')], cwd=str(BASE))
|
|
306
|
+
if r.returncode == 0:
|
|
307
|
+
ok('cron 已注册 (周五 18:00)')
|
|
308
|
+
else:
|
|
309
|
+
warn(f'cron 注册失败: {(r.stderr or "").strip()[:100]}')
|
|
310
|
+
else:
|
|
311
|
+
print(' 跳过。以后可手动跑: python .qoder/scripts/setup_weekly_cron.bat')
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
# ============================================================
|
|
315
|
+
# Step 5: QoderWork 技能
|
|
316
|
+
# ============================================================
|
|
317
|
+
|
|
318
|
+
def offer_qoderwork(skip=False):
|
|
319
|
+
if skip:
|
|
320
|
+
return
|
|
321
|
+
print('\n--- QoderWork 桌面端 ---')
|
|
322
|
+
home = Path(os.environ.get('USERPROFILE') or os.environ.get('HOME') or '~').expanduser()
|
|
323
|
+
qw_dir = home / '.qoderworkcn'
|
|
324
|
+
if not qw_dir.exists():
|
|
325
|
+
print(' 未检测到 QoderWork 桌面端, 跳过。')
|
|
326
|
+
return
|
|
327
|
+
|
|
328
|
+
# 检查是否已装
|
|
329
|
+
skills_dir = qw_dir / 'skills'
|
|
330
|
+
installed = skills_dir.is_dir() and any(skills_dir.iterdir()) if skills_dir.exists() else False
|
|
331
|
+
if installed:
|
|
332
|
+
ok('QoderWork 技能已安装')
|
|
333
|
+
return
|
|
334
|
+
|
|
335
|
+
if ask('检测到 QoderWork, 安装技能 (软链到 ~/.qoderworkcn/skills/)?', 'y'):
|
|
336
|
+
r = subprocess.run([sys.executable, str(THIS_DIR / 'install_qoderwork.py')], cwd=str(BASE))
|
|
337
|
+
if r.returncode == 0:
|
|
338
|
+
ok('QoderWork 技能已安装')
|
|
339
|
+
else:
|
|
340
|
+
warn('安装失败, 见上方输出')
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
# ============================================================
|
|
344
|
+
# Step 6: 就绪报告
|
|
345
|
+
# ============================================================
|
|
346
|
+
|
|
347
|
+
def print_ready(name, role):
|
|
348
|
+
print('\n' + '=' * 56)
|
|
349
|
+
print(' 一键就绪完成')
|
|
350
|
+
print('=' * 56)
|
|
351
|
+
|
|
352
|
+
# 读关键状态
|
|
353
|
+
dev_file = BASE / '.qoder' / '.developer'
|
|
354
|
+
if dev_file.is_file():
|
|
355
|
+
ok(f'开发者: {name} ({role})')
|
|
356
|
+
|
|
357
|
+
# git config (git 可能未装)
|
|
358
|
+
try:
|
|
359
|
+
r = run(['git', 'config', 'user.name'], cwd=str(BASE))
|
|
360
|
+
if r.returncode == 0 and r.stdout.strip():
|
|
361
|
+
ok(f'Git: user.name={r.stdout.strip()}')
|
|
362
|
+
except FileNotFoundError:
|
|
363
|
+
pass # 无 git, 跳过此项
|
|
364
|
+
|
|
365
|
+
# 源码仓库
|
|
366
|
+
code_dir = BASE / 'data' / 'code'
|
|
367
|
+
if code_dir.is_dir():
|
|
368
|
+
projects = [d.name for d in code_dir.iterdir() if d.is_dir() and not d.name.startswith('.')]
|
|
369
|
+
if projects:
|
|
370
|
+
ok(f'源码仓库: {len(projects)} 个项目 ({", ".join(projects[:3])})')
|
|
371
|
+
|
|
372
|
+
# 索引
|
|
373
|
+
ki = BASE / 'data' / 'index' / 'keyword-index.json'
|
|
374
|
+
if ki.is_file():
|
|
375
|
+
try:
|
|
376
|
+
import json
|
|
377
|
+
data = json.loads(ki.read_text(encoding='utf-8'))
|
|
378
|
+
ok(f'知识图谱: {len(data)} keywords')
|
|
379
|
+
except Exception:
|
|
380
|
+
pass
|
|
381
|
+
|
|
382
|
+
# cron
|
|
383
|
+
if sys.platform == 'win32':
|
|
384
|
+
r = run(['schtasks', '/query', '/tn', 'QODER-WeeklyUpdate'])
|
|
385
|
+
if r.returncode == 0:
|
|
386
|
+
ok('周五 cron: 已注册')
|
|
387
|
+
|
|
388
|
+
print()
|
|
389
|
+
print(' 现在可以开始了:')
|
|
390
|
+
print(' /wl-prd 写需求 /wl-search 查代码 /wl-task 建任务')
|
|
391
|
+
print(' /wl-status 看进度 /wl-report 出日报 /wl-commit 提交')
|
|
392
|
+
print()
|
|
393
|
+
print(' 或直接用自然语言:')
|
|
394
|
+
print(' "写个保险异常筛选的 PRD"')
|
|
395
|
+
print(' "查一下考勤代码在哪"')
|
|
396
|
+
print('=' * 56)
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
# ============================================================
|
|
400
|
+
# Main
|
|
401
|
+
# ============================================================
|
|
402
|
+
|
|
403
|
+
def main():
|
|
404
|
+
parser = argparse.ArgumentParser(
|
|
405
|
+
description='一键就绪: 配置开发者/git/源码/索引/cron/QoderWork',
|
|
406
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
407
|
+
epilog='''示例:
|
|
408
|
+
python .qoder/scripts/setup.py # 自动探测名字
|
|
409
|
+
python .qoder/scripts/setup.py 小王 # 指定名字
|
|
410
|
+
python .qoder/scripts/setup.py 小王 dev # 名字+角色
|
|
411
|
+
python .qoder/scripts/setup.py --skip-cron # 跳过 cron 询问
|
|
412
|
+
''')
|
|
413
|
+
parser.add_argument('name', nargs='?', default=None, help='开发者名字 (不传则从 git 探测)')
|
|
414
|
+
parser.add_argument('role', nargs='?', default='pm', help='角色 (pm/dev/design/admin)')
|
|
415
|
+
parser.add_argument('--skip-cron', action='store_true', help='跳过 cron 注册')
|
|
416
|
+
parser.add_argument('--skip-qoderwork', action='store_true', help='跳过 QoderWork 安装')
|
|
417
|
+
args = parser.parse_args()
|
|
418
|
+
|
|
419
|
+
print('=' * 56)
|
|
420
|
+
print(' QODER 一键就绪')
|
|
421
|
+
print('=' * 56)
|
|
422
|
+
|
|
423
|
+
# Step 0
|
|
424
|
+
if not check_env():
|
|
425
|
+
return 1
|
|
426
|
+
|
|
427
|
+
# Step 1
|
|
428
|
+
name = detect_name(args.name)
|
|
429
|
+
if not name or not name.strip():
|
|
430
|
+
fail('未能确定开发者名字')
|
|
431
|
+
return 1
|
|
432
|
+
|
|
433
|
+
# Step 2
|
|
434
|
+
if not run_doctor(name, args.role):
|
|
435
|
+
warn('init_doctor 未完全成功, 继续 setup 后续步骤...')
|
|
436
|
+
|
|
437
|
+
# Step 3
|
|
438
|
+
guide_config()
|
|
439
|
+
|
|
440
|
+
# Step 4
|
|
441
|
+
offer_cron(skip=args.skip_cron)
|
|
442
|
+
|
|
443
|
+
# Step 5
|
|
444
|
+
offer_qoderwork(skip=args.skip_qoderwork)
|
|
445
|
+
|
|
446
|
+
# Step 6
|
|
447
|
+
print_ready(name, args.role)
|
|
448
|
+
|
|
449
|
+
return 0
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
if __name__ == '__main__':
|
|
453
|
+
sys.exit(main())
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
@echo off
|
|
2
|
+
REM Create Windows Task Scheduler for QODER weekly update
|
|
3
|
+
REM Runs every Friday at 18:00 (configurable via argument, e.g. setup_weekly_cron.bat 09:00)
|
|
4
|
+
REM Recommended: one machine per team runs this; others get updates via team_sync pull.
|
|
5
|
+
|
|
6
|
+
set ST=%1
|
|
7
|
+
if "%ST%"=="" set ST=18:00
|
|
8
|
+
|
|
9
|
+
REM Create log dir if missing
|
|
10
|
+
if not exist "%~dp0..\..\..\.qoder\logs" mkdir "%~dp0..\..\..\.qoder\logs"
|
|
11
|
+
|
|
12
|
+
schtasks /create /tn "QODER-WeeklyUpdate" /tr "%~dp0run_weekly_update.bat" /sc weekly /d FRI /st %ST% /f
|
|
13
|
+
|
|
14
|
+
if errorlevel 1 (
|
|
15
|
+
echo [FAIL] Task creation failed. Run as administrator if needed.
|
|
16
|
+
exit /b 1
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
echo [OK] Task "QODER-WeeklyUpdate" created (Friday %ST%).
|
|
20
|
+
echo Verify: schtasks /query /tn "QODER-WeeklyUpdate"
|
|
21
|
+
echo Delete: schtasks /delete /tn "QODER-WeeklyUpdate" /f
|
|
22
|
+
echo Log: .qoder\logs\weekly-update.log
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Set up the Friday 18:00 weekly update on macOS / Linux (cron)
|
|
3
|
+
# Usage: bash .qoder/scripts/setup_weekly_cron.sh
|
|
4
|
+
set -eu
|
|
5
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
6
|
+
RUNNER="$SCRIPT_DIR/run_weekly_update.sh"
|
|
7
|
+
chmod +x "$RUNNER"
|
|
8
|
+
|
|
9
|
+
CRON_LINE="0 18 * * 5 /bin/bash \"$RUNNER\""
|
|
10
|
+
|
|
11
|
+
# Install idempotently: remove old entry for this runner, then add
|
|
12
|
+
( crontab -l 2>/dev/null | grep -vF "$RUNNER" ; echo "$CRON_LINE" ) | crontab -
|
|
13
|
+
|
|
14
|
+
echo "Cron installed: $CRON_LINE"
|
|
15
|
+
echo "Verify: crontab -l"
|
|
16
|
+
echo "Remove: crontab -l | grep -vF \"$RUNNER\" | crontab -"
|
|
17
|
+
echo ""
|
|
18
|
+
echo "Note (macOS): if cron lacks disk access, grant 'Full Disk Access' to"
|
|
19
|
+
echo "/usr/sbin/cron in System Settings, or use launchd instead."
|