@hupan56/wlkj 2.0.0 → 2.1.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
CHANGED
|
@@ -172,10 +172,12 @@ function doHelp() {
|
|
|
172
172
|
console.log("");
|
|
173
173
|
console.log("wlkj - AI 产品研发工作流 (v2.0)");
|
|
174
174
|
console.log("");
|
|
175
|
-
console.log("===
|
|
176
|
-
console.log(" npx wlkj
|
|
177
|
-
console.log("
|
|
178
|
-
console.log("
|
|
175
|
+
console.log("=== 环境安装 (什么都没装时先跑这个) ===");
|
|
176
|
+
console.log(" npx @hupan56/wlkj install-env 检测+自动装 Node/Python/git");
|
|
177
|
+
console.log(" npx @hupan56/wlkj install-env --check 只检测不装");
|
|
178
|
+
console.log("");
|
|
179
|
+
console.log("=== 一键安装 (装好环境后) ===");
|
|
180
|
+
console.log(" npx @hupan56/wlkj init [你的名字] 安装完整引擎 + 自动初始化");
|
|
179
181
|
console.log("");
|
|
180
182
|
console.log("=== 安装后怎么用 ===");
|
|
181
183
|
console.log(" 在 Qoder (IDE/Quest/QoderWork) 里:");
|
|
@@ -197,11 +199,93 @@ function doHelp() {
|
|
|
197
199
|
console.log("");
|
|
198
200
|
}
|
|
199
201
|
|
|
202
|
+
function doInstallEnv(checkOnly = false) {
|
|
203
|
+
const { execSync } = require("child_process");
|
|
204
|
+
console.log("\n=== 环境检测 ===\n");
|
|
205
|
+
|
|
206
|
+
// 检测函数
|
|
207
|
+
function check(cmd) {
|
|
208
|
+
try { return execSync(`${cmd} --version`, { encoding: "utf-8", timeout: 5000, stdio: "pipe" }).trim().split("\n")[0]; }
|
|
209
|
+
catch { return null; }
|
|
210
|
+
}
|
|
211
|
+
function has(cmd) {
|
|
212
|
+
try { execSync(`where ${cmd}`, { encoding: "utf-8", timeout: 3000, stdio: "pipe" }); return true; }
|
|
213
|
+
catch { try { execSync(`which ${cmd}`, { encoding: "utf-8", timeout: 3000, stdio: "pipe" }); return true; } catch { return false; } }
|
|
214
|
+
}
|
|
215
|
+
function install(name, wingetId, brewPkg) {
|
|
216
|
+
if (checkOnly) return false;
|
|
217
|
+
const isWin = process.platform === "win32";
|
|
218
|
+
const isMac = process.platform === "darwin";
|
|
219
|
+
try {
|
|
220
|
+
if (isWin && has("winget")) {
|
|
221
|
+
console.log(` 用 winget 装 ${name}...`);
|
|
222
|
+
execSync(`winget install --id ${wingetId} -e --source winget --accept-source-agreements --accept-package-agreements`,
|
|
223
|
+
{ stdio: "inherit", timeout: 300000 });
|
|
224
|
+
return true;
|
|
225
|
+
} else if (isMac && has("brew")) {
|
|
226
|
+
console.log(` 用 brew 装 ${name}...`);
|
|
227
|
+
execSync(`brew install ${brewPkg}`, { stdio: "inherit", timeout: 300000 });
|
|
228
|
+
return true;
|
|
229
|
+
} else if (!isWin && !isMac) {
|
|
230
|
+
// Linux: 尝试 apt/dnf/yum
|
|
231
|
+
for (const [mgr, pkg] of [["apt", brewPkg], ["dnf", brewPkg]]) {
|
|
232
|
+
if (has(mgr)) {
|
|
233
|
+
console.log(` 用 ${mgr} 装 ${name}...`);
|
|
234
|
+
execSync(`sudo ${mgr} install -y ${pkg}`, { stdio: "inherit", timeout: 300000 });
|
|
235
|
+
return true;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
} catch (e) { console.log(` 自动安装失败: ${(e.message || "").slice(0, 80)}`); }
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const nodeVer = check("node");
|
|
244
|
+
const pyVer = check("python") || check("python3");
|
|
245
|
+
const gitVer = check("git");
|
|
246
|
+
|
|
247
|
+
console.log(` Node.js: ${nodeVer ? "[OK] " + nodeVer : "[缺失] (npx 命令需要)"}`);
|
|
248
|
+
console.log(` Python: ${pyVer ? "[OK] " + pyVer : "[缺失] (引擎需要)"}`);
|
|
249
|
+
console.log(` git: ${gitVer ? "[OK] " + gitVer : "[缺失] (可选, 核心功能不依赖)"}`);
|
|
250
|
+
|
|
251
|
+
if (checkOnly) {
|
|
252
|
+
console.log("\n仅检测。去掉 --check 自动安装。");
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const need = [];
|
|
257
|
+
if (!nodeVer) {
|
|
258
|
+
console.log("");
|
|
259
|
+
if (nodeVer || install("Node.js", "OpenJS.NodeJS.LTS", "node")) { console.log(` [OK] Node.js 已装`); }
|
|
260
|
+
else { need.push("Node.js (手动: https://nodejs.org)"); }
|
|
261
|
+
}
|
|
262
|
+
if (!pyVer) {
|
|
263
|
+
console.log("");
|
|
264
|
+
if (install("Python", "Python.Python.3.12", "python@3.12")) { console.log(` [OK] Python 已装`); }
|
|
265
|
+
else { need.push("Python (手动: https://python.org, 勾 Add to PATH)"); }
|
|
266
|
+
}
|
|
267
|
+
if (!gitVer) {
|
|
268
|
+
console.log("");
|
|
269
|
+
if (install("git", "Git.Git", "git")) { console.log(` [OK] git 已装`); }
|
|
270
|
+
else { need.push("git (手动: https://git-scm.com, 可选)"); }
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
console.log("\n=== 结果 ===");
|
|
274
|
+
if (need.length === 0) {
|
|
275
|
+
console.log(" 环境就绪! 下一步: npx @hupan56/wlkj init");
|
|
276
|
+
console.log(" (新装的可能需要重新打开终端)");
|
|
277
|
+
} else {
|
|
278
|
+
console.log(" 以下需手动安装:");
|
|
279
|
+
need.forEach(n => console.log(" - " + n));
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
200
283
|
const [,, cmd, ...rest] = process.argv;
|
|
201
284
|
const mapped = rest.map(a => a === "-p" ? "--priority" : a);
|
|
202
285
|
|
|
203
286
|
switch (cmd) {
|
|
204
287
|
case "init": doInit(rest[0]); break;
|
|
288
|
+
case "install-env": doInstallEnv(rest[0] === "--check"); break;
|
|
205
289
|
case "task": process.stdout.write(py("task.py", mapped)); break;
|
|
206
290
|
case "status": doStatus(); break;
|
|
207
291
|
case "session": process.stdout.write(py("add_session.py", rest)); break;
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
install-env.py - 一键安装前置环境 (Node.js + Python + git)
|
|
5
|
+
|
|
6
|
+
在跑 npx @hupan56/wlkj init 之前, 如果什么都没装, 先跑这个。
|
|
7
|
+
它会检测并自动安装缺失的环境。
|
|
8
|
+
|
|
9
|
+
Usage:
|
|
10
|
+
python install-env.py # 检测 + 自动装缺失的
|
|
11
|
+
python install-env.py --check # 只检测, 不装
|
|
12
|
+
|
|
13
|
+
注意: 这个脚本本身需要 Python 才能跑。如果用户连 Python 都没有:
|
|
14
|
+
Windows: 去 python.org 下载安装 (勾 Add to PATH), 或用 winget:
|
|
15
|
+
winget install Python.Python.3.12
|
|
16
|
+
Mac: brew install python
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import argparse
|
|
20
|
+
import os
|
|
21
|
+
import platform
|
|
22
|
+
import subprocess
|
|
23
|
+
import sys
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
|
|
26
|
+
try:
|
|
27
|
+
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
|
|
28
|
+
sys.stderr.reconfigure(encoding='utf-8', errors='replace')
|
|
29
|
+
except (AttributeError, TypeError, OSError):
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
IS_WIN = sys.platform == 'win32'
|
|
33
|
+
IS_MAC = sys.platform == 'darwin'
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def run(cmd, **kw):
|
|
37
|
+
kw.setdefault('capture_output', True)
|
|
38
|
+
kw.setdefault('text', True)
|
|
39
|
+
kw.setdefault('encoding', 'utf-8')
|
|
40
|
+
kw.setdefault('errors', 'replace')
|
|
41
|
+
try:
|
|
42
|
+
return subprocess.run(cmd, **kw)
|
|
43
|
+
except FileNotFoundError:
|
|
44
|
+
import types
|
|
45
|
+
return types.SimpleNamespace(returncode=127, stdout='', stderr='not found')
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def check(cmd_name, version_flag='--version'):
|
|
49
|
+
"""检查某个命令是否存在, 返回 (是否可用, 版本字符串)。"""
|
|
50
|
+
r = run([cmd_name, version_flag])
|
|
51
|
+
if r.returncode == 0:
|
|
52
|
+
return True, r.stdout.strip().split('\n')[0]
|
|
53
|
+
return False, None
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def ask(prompt, default='y'):
|
|
57
|
+
if not sys.stdin.isatty():
|
|
58
|
+
return default.lower() in ('y', 'yes')
|
|
59
|
+
try:
|
|
60
|
+
ans = input(f'{prompt} [{default}] ').strip().lower() or default
|
|
61
|
+
return ans in ('y', 'yes')
|
|
62
|
+
except (EOFError, KeyboardInterrupt):
|
|
63
|
+
return default.lower() in ('y', 'yes')
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def has_winget():
|
|
67
|
+
return check('winget')[0]
|
|
68
|
+
|
|
69
|
+
def has_brew():
|
|
70
|
+
return check('brew')[0]
|
|
71
|
+
|
|
72
|
+
def has_choco():
|
|
73
|
+
return check('choco')[0]
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
# ============================================================
|
|
77
|
+
# 安装器
|
|
78
|
+
# ============================================================
|
|
79
|
+
|
|
80
|
+
def install_node():
|
|
81
|
+
"""安装 Node.js。"""
|
|
82
|
+
print('\n--- 安装 Node.js ---')
|
|
83
|
+
if IS_WIN:
|
|
84
|
+
# 优先 winget
|
|
85
|
+
if has_winget():
|
|
86
|
+
print(' 用 winget 安装 Node.js LTS...')
|
|
87
|
+
r = run(['winget', 'install', '--id', 'OpenJS.NodeJS.LTS', '-e',
|
|
88
|
+
'--source', 'winget', '--accept-source-agreements',
|
|
89
|
+
'--accept-package-agreements'])
|
|
90
|
+
if r.returncode == 0:
|
|
91
|
+
print(' [OK] Node.js 安装成功 (可能需要新终端生效)')
|
|
92
|
+
return True
|
|
93
|
+
print(' winget 失败: ' + (r.stderr or '').strip()[:100])
|
|
94
|
+
# 回退 choco
|
|
95
|
+
if has_choco():
|
|
96
|
+
print(' 用 choco 安装...')
|
|
97
|
+
r = run(['choco', 'install', 'nodejs-lts', '-y'])
|
|
98
|
+
if r.returncode == 0:
|
|
99
|
+
return True
|
|
100
|
+
print(' 自动安装失败。手动下载: https://nodejs.org (选 LTS)')
|
|
101
|
+
return False
|
|
102
|
+
elif IS_MAC:
|
|
103
|
+
if has_brew():
|
|
104
|
+
print(' 用 brew 安装 Node.js...')
|
|
105
|
+
r = run(['brew', 'install', 'node'])
|
|
106
|
+
return r.returncode == 0
|
|
107
|
+
print(' 请先装 Homebrew: https://brew.sh')
|
|
108
|
+
return False
|
|
109
|
+
else:
|
|
110
|
+
# Linux
|
|
111
|
+
for mgr, cmd in [('apt', ['sudo', 'apt-get', 'install', '-y', 'nodejs', 'npm']),
|
|
112
|
+
('dnf', ['sudo', 'dnf', 'install', '-y', 'nodejs', 'npm']),
|
|
113
|
+
('yum', ['sudo', 'yum', 'install', '-y', 'nodejs', 'npm'])]:
|
|
114
|
+
if check(mgr)[0]:
|
|
115
|
+
print(f' 用 {mgr} 安装...')
|
|
116
|
+
r = run(cmd)
|
|
117
|
+
return r.returncode == 0
|
|
118
|
+
print(' 请手动安装: https://nodejs.org')
|
|
119
|
+
return False
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def install_python():
|
|
123
|
+
"""安装 Python 3.9+。"""
|
|
124
|
+
print('\n--- 安装 Python ---')
|
|
125
|
+
if IS_WIN:
|
|
126
|
+
if has_winget():
|
|
127
|
+
print(' 用 winget 安装 Python 3.12...')
|
|
128
|
+
r = run(['winget', 'install', '--id', 'Python.Python.3.12', '-e',
|
|
129
|
+
'--source', 'winget', '--accept-source-agreements',
|
|
130
|
+
'--accept-package-agreements'])
|
|
131
|
+
if r.returncode == 0:
|
|
132
|
+
print(' [OK] Python 安装成功 (可能需要新终端生效)')
|
|
133
|
+
return True
|
|
134
|
+
print(' winget 失败: ' + (r.stderr or '').strip()[:100])
|
|
135
|
+
print(' 自动安装失败。手动下载: https://python.org (勾 Add to PATH)')
|
|
136
|
+
return False
|
|
137
|
+
elif IS_MAC:
|
|
138
|
+
if has_brew():
|
|
139
|
+
print(' 用 brew 安装 Python...')
|
|
140
|
+
r = run(['brew', 'install', 'python@3.12'])
|
|
141
|
+
return r.returncode == 0
|
|
142
|
+
print(' 请先装 Homebrew: https://brew.sh')
|
|
143
|
+
return False
|
|
144
|
+
else:
|
|
145
|
+
for mgr, cmd in [('apt', ['sudo', 'apt-get', 'install', '-y', 'python3', 'python3-pip']),
|
|
146
|
+
('dnf', ['sudo', 'dnf', 'install', '-y', 'python3']),
|
|
147
|
+
('yum', ['sudo', 'yum', 'install', '-y', 'python3'])]:
|
|
148
|
+
if check(mgr)[0]:
|
|
149
|
+
print(f' 用 {mgr} 安装...')
|
|
150
|
+
r = run(cmd)
|
|
151
|
+
return r.returncode == 0
|
|
152
|
+
print(' 请手动安装: https://python.org')
|
|
153
|
+
return False
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def install_git():
|
|
157
|
+
"""安装 git。"""
|
|
158
|
+
print('\n--- 安装 git ---')
|
|
159
|
+
if IS_WIN:
|
|
160
|
+
if has_winget():
|
|
161
|
+
print(' 用 winget 安装 Git...')
|
|
162
|
+
r = run(['winget', 'install', '--id', 'Git.Git', '-e',
|
|
163
|
+
'--source', 'winget', '--accept-source-agreements',
|
|
164
|
+
'--accept-package-agreements'])
|
|
165
|
+
if r.returncode == 0:
|
|
166
|
+
print(' [OK] git 安装成功 (可能需要新终端生效)')
|
|
167
|
+
return True
|
|
168
|
+
print(' 自动安装失败。手动下载: https://git-scm.com/download/win')
|
|
169
|
+
return False
|
|
170
|
+
elif IS_MAC:
|
|
171
|
+
# Mac 上 git 通常自带 (装了 Xcode Command Line Tools)
|
|
172
|
+
r = run(['xcode-select', '--install'])
|
|
173
|
+
print(' 通过 Xcode Command Line Tools 安装...')
|
|
174
|
+
return True
|
|
175
|
+
else:
|
|
176
|
+
for mgr, cmd in [('apt', ['sudo', 'apt-get', 'install', '-y', 'git']),
|
|
177
|
+
('dnf', ['sudo', 'dnf', 'install', '-y', 'git']),
|
|
178
|
+
('yum', ['sudo', 'yum', 'install', '-y', 'git'])]:
|
|
179
|
+
if check(mgr)[0]:
|
|
180
|
+
print(f' 用 {mgr} 安装...')
|
|
181
|
+
r = run(cmd)
|
|
182
|
+
return r.returncode == 0
|
|
183
|
+
print(' 请手动安装: https://git-scm.com')
|
|
184
|
+
return False
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
# ============================================================
|
|
188
|
+
# 主逻辑
|
|
189
|
+
# ============================================================
|
|
190
|
+
|
|
191
|
+
def main():
|
|
192
|
+
parser = argparse.ArgumentParser(
|
|
193
|
+
description='一键安装前置环境 (Node.js + Python + git)',
|
|
194
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
195
|
+
epilog='''安装完后跑:
|
|
196
|
+
npx @hupan56/wlkj init
|
|
197
|
+
|
|
198
|
+
如果连 Python 都没有 (无法跑本脚本):
|
|
199
|
+
Windows: winget install Python.Python.3.12
|
|
200
|
+
Mac: brew install python
|
|
201
|
+
''')
|
|
202
|
+
parser.add_argument('--check', action='store_true', help='只检测, 不安装')
|
|
203
|
+
args = parser.parse_args()
|
|
204
|
+
|
|
205
|
+
print('=' * 50)
|
|
206
|
+
print(' 环境检测' + (' (仅检测)' if args.check else ' + 自动安装'))
|
|
207
|
+
print('=' * 50)
|
|
208
|
+
|
|
209
|
+
# 检测当前状态
|
|
210
|
+
print('\n--- 当前状态 ---')
|
|
211
|
+
node_ok, node_ver = check('node')
|
|
212
|
+
py_ok, py_ver = check('python') or (sys.version_info >= (3, 9), f'Python {sys.version.split()[0]}')
|
|
213
|
+
git_ok, git_ver = check('git')
|
|
214
|
+
|
|
215
|
+
# Python 特殊: 本脚本在跑说明至少有 python, 但可能版本不够
|
|
216
|
+
if not py_ok:
|
|
217
|
+
py_ok = sys.version_info >= (3, 9)
|
|
218
|
+
py_ver = f'Python {sys.version_info[0]}.{sys.version_info[1]}'
|
|
219
|
+
|
|
220
|
+
print(f' Node.js: {"[OK] " + node_ver if node_ok else "[缺失]"}')
|
|
221
|
+
print(f' Python: {"[OK] " + py_ver if py_ok else "[缺失或版本低]"}')
|
|
222
|
+
print(f' git: {"[OK] " + git_ver if git_ok else "[缺失] (可选, 不装也能用核心功能)"}')
|
|
223
|
+
|
|
224
|
+
if args.check:
|
|
225
|
+
print('\n仅检测模式。缺少的环境手动装, 或去掉 --check 自动安装。')
|
|
226
|
+
return 0 if (node_ok and py_ok) else 1
|
|
227
|
+
|
|
228
|
+
# 按需安装
|
|
229
|
+
installed = []
|
|
230
|
+
if not node_ok:
|
|
231
|
+
if ask('\nNode.js 缺失 (npx 命令需要它), 自动安装?', 'y'):
|
|
232
|
+
if install_node():
|
|
233
|
+
installed.append('Node.js')
|
|
234
|
+
if not py_ok:
|
|
235
|
+
if ask('\nPython 版本过低或缺失, 自动安装?', 'y'):
|
|
236
|
+
if install_python():
|
|
237
|
+
installed.append('Python')
|
|
238
|
+
if not git_ok:
|
|
239
|
+
if ask('\ngit 缺失 (团队协作用, 核心功能不依赖), 自动安装?', 'y'):
|
|
240
|
+
if install_git():
|
|
241
|
+
installed.append('git')
|
|
242
|
+
|
|
243
|
+
# 重新检测
|
|
244
|
+
print('\n--- 安装后状态 ---')
|
|
245
|
+
node_ok2, _ = check('node')
|
|
246
|
+
# Python: 如果新装了, 当前进程还是旧的, 但 PATH 里应该有了
|
|
247
|
+
git_ok2, _ = check('git')
|
|
248
|
+
|
|
249
|
+
all_ok = node_ok or node_ok2
|
|
250
|
+
print(f' Node.js: {"[OK]" if all_ok else "[仍缺失, 可能需要新终端]"}')
|
|
251
|
+
print(f' Python: [OK] {py_ver}')
|
|
252
|
+
print(f' git: {"[OK]" if (git_ok or git_ok2) else "[缺失, 可选]"}')
|
|
253
|
+
|
|
254
|
+
if installed:
|
|
255
|
+
print(f'\n本次安装: {", ".join(installed)}')
|
|
256
|
+
print(' ⚠ 新装的环境可能需要**重新打开终端**才能生效')
|
|
257
|
+
|
|
258
|
+
if all_ok:
|
|
259
|
+
print('\n' + '=' * 50)
|
|
260
|
+
print(' 环境就绪! 下一步:')
|
|
261
|
+
print('=' * 50)
|
|
262
|
+
print('\n mkdir 我的项目 && cd 我的项目')
|
|
263
|
+
print(' npx @hupan56/wlkj init')
|
|
264
|
+
print('')
|
|
265
|
+
return 0
|
|
266
|
+
else:
|
|
267
|
+
print('\n Node.js 仍缺失, 请手动安装: https://nodejs.org')
|
|
268
|
+
return 1
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
if __name__ == '__main__':
|
|
272
|
+
sys.exit(main())
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hupan56/wlkj",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "AI
|
|
3
|
+
"version": "2.1.0",
|
|
4
|
+
"description": "AI Product R&D Workflow - PRD/Prototype/Search/Task/Report",
|
|
5
5
|
"bin": { "wlkj": "./bin/cli.js" },
|
|
6
6
|
"files": ["bin/", "templates/"],
|
|
7
7
|
"keywords": ["workflow", "ai", "prd", "pipeline", "qoder", "product", "team"],
|
|
8
8
|
"license": "MIT",
|
|
9
9
|
"publishConfig": { "access": "public" },
|
|
10
10
|
"engines": { "node": ">=16" }
|
|
11
|
-
}
|
|
11
|
+
}
|
|
12
|
+
|
|
@@ -21,6 +21,7 @@ Usage:
|
|
|
21
21
|
import os
|
|
22
22
|
import sys
|
|
23
23
|
import json
|
|
24
|
+
import time
|
|
24
25
|
|
|
25
26
|
# UTF-8 stdio (防御性: stdout 被捕获时不崩溃)
|
|
26
27
|
try:
|
|
@@ -71,6 +72,53 @@ def plat_filter(files, target):
|
|
|
71
72
|
return [f for f in files if target.lower() in f.lower()]
|
|
72
73
|
|
|
73
74
|
|
|
75
|
+
def _cache_key(query, platform, page_type):
|
|
76
|
+
"""生成缓存 key: query|platform|type|索引mtime 指纹。
|
|
77
|
+
|
|
78
|
+
索引文件没变就命中缓存, 变了自动失效。
|
|
79
|
+
"""
|
|
80
|
+
import hashlib
|
|
81
|
+
# 取 3 个主要索引的 mtime 作为指纹
|
|
82
|
+
mtime_str = ''
|
|
83
|
+
for idx_file in ['keyword-index.json', 'api-index.json', 'prd-index.json']:
|
|
84
|
+
p = os.path.join(INDEX_DIR, idx_file)
|
|
85
|
+
if os.path.isfile(p):
|
|
86
|
+
mtime_str += '%d_' % os.path.getmtime(p)
|
|
87
|
+
raw = '{}|{}|{}|{}'.format(query, platform or '', page_type or '', mtime_str)
|
|
88
|
+
return hashlib.md5(raw.encode('utf-8')).hexdigest()[:16]
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _try_cache(key):
|
|
92
|
+
"""读缓存命中则返回内容, 未命中返回 None。"""
|
|
93
|
+
cache_path = os.path.join(BASE, '.qoder', '.runtime', 'ctx-cache-%s.md' % key)
|
|
94
|
+
if not os.path.isfile(cache_path):
|
|
95
|
+
return None
|
|
96
|
+
try:
|
|
97
|
+
# 缓存 24 小时有效 (防 mtime 精度问题)
|
|
98
|
+
age = time.time() - os.path.getmtime(cache_path)
|
|
99
|
+
if age > 86400:
|
|
100
|
+
return None
|
|
101
|
+
with open(cache_path, encoding='utf-8') as f:
|
|
102
|
+
return f.read()
|
|
103
|
+
except OSError:
|
|
104
|
+
return None
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _save_cache(key, content):
|
|
108
|
+
"""写缓存 (best-effort, 失败不阻塞)。"""
|
|
109
|
+
try:
|
|
110
|
+
cache_dir = os.path.join(BASE, '.qoder', '.runtime')
|
|
111
|
+
os.makedirs(cache_dir, exist_ok=True)
|
|
112
|
+
cache_path = os.path.join(cache_dir, 'ctx-cache-%s.md' % key)
|
|
113
|
+
# 原子写
|
|
114
|
+
tmp = cache_path + '.tmp'
|
|
115
|
+
with open(tmp, 'w', encoding='utf-8') as f:
|
|
116
|
+
f.write(content)
|
|
117
|
+
os.replace(tmp, cache_path)
|
|
118
|
+
except OSError:
|
|
119
|
+
pass
|
|
120
|
+
|
|
121
|
+
|
|
74
122
|
def main():
|
|
75
123
|
args = sys.argv[1:]
|
|
76
124
|
platform = None
|
|
@@ -88,6 +136,30 @@ def main():
|
|
|
88
136
|
return 1
|
|
89
137
|
query = args[0]
|
|
90
138
|
|
|
139
|
+
# 结果缓存: 同 query+platform+索引未变 → 直接返回 (470ms → <10ms)
|
|
140
|
+
cache_key = _cache_key(query, platform, page_type)
|
|
141
|
+
cached = _try_cache(cache_key)
|
|
142
|
+
if cached is not None:
|
|
143
|
+
print(cached, end='')
|
|
144
|
+
return 0
|
|
145
|
+
|
|
146
|
+
# 缓存未命中, 正常计算, 末尾写入缓存
|
|
147
|
+
import io
|
|
148
|
+
old_stdout = sys.stdout
|
|
149
|
+
sys.stdout = buf = io.StringIO()
|
|
150
|
+
try:
|
|
151
|
+
_compute_and_print(query, platform, page_type)
|
|
152
|
+
finally:
|
|
153
|
+
sys.stdout = old_stdout
|
|
154
|
+
output = buf.getvalue()
|
|
155
|
+
print(output, end='')
|
|
156
|
+
_save_cache(cache_key, output)
|
|
157
|
+
return 0
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _compute_and_print(query, platform, page_type):
|
|
161
|
+
"""实际的 context_pack 计算 (原 main 逻辑)。"""
|
|
162
|
+
|
|
91
163
|
targets = []
|
|
92
164
|
if platform and platform.lower() not in ('both', '两端'):
|
|
93
165
|
targets = [PLATFORM_MAP.get(platform.lower(), platform)]
|
|
@@ -189,8 +261,6 @@ def main():
|
|
|
189
261
|
for w in wikis:
|
|
190
262
|
print('- ' + w)
|
|
191
263
|
|
|
192
|
-
return 0
|
|
193
|
-
|
|
194
264
|
|
|
195
265
|
if __name__ == '__main__':
|
|
196
266
|
sys.exit(main())
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
# 完整使用说明
|
|
2
|
+
|
|
3
|
+
> 从零开始,一步一步来。连 Python 都没有也能跟着做。
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 第一步:装前置环境(10 分钟)
|
|
8
|
+
|
|
9
|
+
### 1.1 装 Node.js(必须)
|
|
10
|
+
|
|
11
|
+
Node.js 用来跑 `npx` 安装命令。
|
|
12
|
+
|
|
13
|
+
**Windows:**
|
|
14
|
+
1. 打开 https://nodejs.org
|
|
15
|
+
2. 下载 **LTS 版**(左边那个绿色按钮)
|
|
16
|
+
3. 双击安装,一路点"下一步",全部默认
|
|
17
|
+
4. 装完按 `Win + R`,输 `cmd` 回车,在黑窗口里输:
|
|
18
|
+
```
|
|
19
|
+
node --version
|
|
20
|
+
```
|
|
21
|
+
看到 `v20.x.x` 之类的就成功了
|
|
22
|
+
|
|
23
|
+
**Mac:**
|
|
24
|
+
1. 打开终端(Launchpad → 其他 → 终端)
|
|
25
|
+
2. 输:`brew install node`(没装 brew 的先去 brew.sh 装)
|
|
26
|
+
3. 验证:`node --version`
|
|
27
|
+
|
|
28
|
+
### 1.2 装 Python(必须)
|
|
29
|
+
|
|
30
|
+
Python 用来跑工作流的脚本引擎。
|
|
31
|
+
|
|
32
|
+
**Windows:**
|
|
33
|
+
1. 打开 https://www.python.org/downloads/
|
|
34
|
+
2. 点黄色大按钮 "Download Python 3.x.x"
|
|
35
|
+
3. 双击安装包,**重要:勾选底部 "Add Python to PATH"**,再点 "Install Now"
|
|
36
|
+
4. 装完在 cmd 里输:
|
|
37
|
+
```
|
|
38
|
+
python --version
|
|
39
|
+
```
|
|
40
|
+
看到 `Python 3.9.x` 或更高就成功了
|
|
41
|
+
|
|
42
|
+
**Mac:**
|
|
43
|
+
1. 终端输:`brew install python`
|
|
44
|
+
2. 验证:`python3 --version`
|
|
45
|
+
|
|
46
|
+
### 1.3 装 git(推荐,不装也能用大部分功能)
|
|
47
|
+
|
|
48
|
+
git 用来团队协作和拉取源码。**没有 git 不影响写 PRD、建任务、出报告。**
|
|
49
|
+
|
|
50
|
+
**Windows:**
|
|
51
|
+
1. 打开 https://git-scm.com/download/win
|
|
52
|
+
2. 下载自动安装包,双击安装,全部默认
|
|
53
|
+
3. 验证:`git --version`
|
|
54
|
+
|
|
55
|
+
**Mac:**
|
|
56
|
+
- 打开终端,第一次输 `git` 时 Mac 会提示安装,点"安装"即可
|
|
57
|
+
- 或手动:`brew install git`
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## 第二步:创建项目并安装工作流(2 分钟)
|
|
62
|
+
|
|
63
|
+
### 2.1 建项目文件夹
|
|
64
|
+
|
|
65
|
+
**Windows:**
|
|
66
|
+
```
|
|
67
|
+
mkdir D:\我的项目
|
|
68
|
+
cd D:\我的项目
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**Mac:**
|
|
72
|
+
```
|
|
73
|
+
mkdir ~/我的项目
|
|
74
|
+
cd ~/我的项目
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### 2.2 一键安装
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
npx @hupan56/wlkj init
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
它会自动:
|
|
84
|
+
- 下载并安装完整工作流引擎
|
|
85
|
+
- 问你叫什么名字(或从 git 配置读)
|
|
86
|
+
- 注册你的身份
|
|
87
|
+
- 如果有 git,自动配置
|
|
88
|
+
- 如果配了源码地址,自动拉取源码并建索引
|
|
89
|
+
|
|
90
|
+
看到这个就成功了:
|
|
91
|
+
```
|
|
92
|
+
✓ 开发者: 你的名字
|
|
93
|
+
安装完成! 现在可以开始了
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
> 也可以指定名字:`npx @hupan56/wlkj init 小王`
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## 第三步:开始使用
|
|
101
|
+
|
|
102
|
+
### 方式 A:用 Qoder 客户端(推荐)
|
|
103
|
+
|
|
104
|
+
如果你装了 **Qoder IDE / Quest / QoderWork**:
|
|
105
|
+
|
|
106
|
+
1. 用 Qoder 打开你刚创建的项目文件夹
|
|
107
|
+
2. 在对话框里直接说中文:
|
|
108
|
+
- "写个保险异常筛选的需求"
|
|
109
|
+
- "查一下考勤代码在哪"
|
|
110
|
+
- "建个任务:登录优化"
|
|
111
|
+
3. 或输入 `/` 看所有命令(`/wl-prd` `/wl-search` 等)
|
|
112
|
+
|
|
113
|
+
**看不到 `/` 命令?**
|
|
114
|
+
- Quest 模式:新建一个对话
|
|
115
|
+
- QoderWork:跑 `python .qoder/scripts/install_qoderwork.py`,然后重启 QoderWork
|
|
116
|
+
|
|
117
|
+
### 方式 B:用命令行(无 Qoder 时)
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
python .qoder/scripts/search_index.py 保险
|
|
121
|
+
python .qoder/scripts/task.py create "登录优化"
|
|
122
|
+
python .qoder/scripts/report.py daily
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## 全部功能速查
|
|
128
|
+
|
|
129
|
+
### 写需求 + 原型
|
|
130
|
+
|
|
131
|
+
| 做什么 | 说什么 / 命令 |
|
|
132
|
+
|--------|-------------|
|
|
133
|
+
| 写完整需求 | "写个 XXX 的需求" 或 `/wl-prd` |
|
|
134
|
+
| 写小改动 | "快速写个 XXX" |
|
|
135
|
+
| 头脑风暴 | "脑暴一下 XXX" |
|
|
136
|
+
| 画原型 | 随需求自动生成 |
|
|
137
|
+
| 评审 PRD | "评审这个 PRD" |
|
|
138
|
+
|
|
139
|
+
### 查代码(不懂代码也能查)
|
|
140
|
+
|
|
141
|
+
| 做什么 | 说什么 / 命令 |
|
|
142
|
+
|--------|-------------|
|
|
143
|
+
| 找业务代码 | "保险的代码在哪" 或 `/wl-search 保险` |
|
|
144
|
+
| 找 API | `/wl-search --api 考勤` |
|
|
145
|
+
| 找字段用法 | `/wl-search --field nickName` |
|
|
146
|
+
| 找同类页面 | `/wl-search --style table` |
|
|
147
|
+
| 一键全上下文 | `python .qoder/scripts/context_pack.py 保险 --platform web` |
|
|
148
|
+
|
|
149
|
+
### 管任务
|
|
150
|
+
|
|
151
|
+
| 做什么 | 说什么 / 命令 |
|
|
152
|
+
|--------|-------------|
|
|
153
|
+
| 建任务 | "建个任务:登录优化" |
|
|
154
|
+
| 设截止日期 | `python .qoder/scripts/task.py set-due 任务名 2026-07-15` |
|
|
155
|
+
| 标记依赖 | `python .qoder/scripts/task.py block A B`(A 被 B 阻塞) |
|
|
156
|
+
| 看能开始的 | `python .qoder/scripts/task.py list --ready` |
|
|
157
|
+
| 看被卡住的 | `python .qoder/scripts/task.py list --blocked` |
|
|
158
|
+
| 看甘特图 | `python .qoder/scripts/task.py gantt` |
|
|
159
|
+
| 完成任务 | "这个任务做完了" |
|
|
160
|
+
|
|
161
|
+
### 看进度 + 出报告
|
|
162
|
+
|
|
163
|
+
| 做什么 | 说什么 / 命令 |
|
|
164
|
+
|--------|-------------|
|
|
165
|
+
| 看项目健康度 | "项目怎么样" 或 `python .qoder/scripts/status.py` |
|
|
166
|
+
| 出日报 | "今天干了啥" 或 `python .qoder/scripts/report.py daily` |
|
|
167
|
+
| 出周报 | `python .qoder/scripts/report.py weekly` |
|
|
168
|
+
|
|
169
|
+
### 导出
|
|
170
|
+
|
|
171
|
+
| 做什么 | 命令 |
|
|
172
|
+
|--------|------|
|
|
173
|
+
| 导 Jira CSV | `python .qoder/scripts/export.py jira` |
|
|
174
|
+
| 导 OpenAPI | `python .qoder/scripts/export.py openapi --project 你的项目` |
|
|
175
|
+
| 查调用图 | `python .qoder/scripts/export.py callgraph --class ClassName` |
|
|
176
|
+
|
|
177
|
+
### 团队协作
|
|
178
|
+
|
|
179
|
+
| 做什么 | 说什么 / 命令 |
|
|
180
|
+
|--------|-------------|
|
|
181
|
+
| 同步给团队 | "同步" 或 `python .qoder/scripts/team_sync.py push` |
|
|
182
|
+
| 拉取队友最新 | "拉取最新" 或 `python .qoder/scripts/team_sync.py pull` |
|
|
183
|
+
| 看同步状态 | `python .qoder/scripts/team_sync.py status` |
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## 团队配置(团队负责人做一次)
|
|
188
|
+
|
|
189
|
+
### 填源码仓库地址
|
|
190
|
+
|
|
191
|
+
打开 `.qoder/config.yaml`,找到这段,改成你们的 git 地址:
|
|
192
|
+
|
|
193
|
+
```yaml
|
|
194
|
+
git_sync:
|
|
195
|
+
projects:
|
|
196
|
+
你的项目名: # ← 改成你的项目名
|
|
197
|
+
url: http://你的git服务器/你的项目.git # ← 改成你的
|
|
198
|
+
branch: main # ← 改成你的主分支
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
> 不知道填什么?注释掉整段,手动把代码放到 `data/code/项目名/` 也行。
|
|
202
|
+
|
|
203
|
+
### 注册周五自动更新(全队只需一台机器)
|
|
204
|
+
|
|
205
|
+
```
|
|
206
|
+
python .qoder/scripts/setup_weekly_cron.bat
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### 配飞书通知(可选)
|
|
210
|
+
|
|
211
|
+
`.qoder/config.yaml` 里:
|
|
212
|
+
|
|
213
|
+
```yaml
|
|
214
|
+
feishu:
|
|
215
|
+
enabled: true
|
|
216
|
+
webhook_url: "https://open.feishu.cn/open-apis/bot/v2/hook/你的token"
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
## 常见问题
|
|
222
|
+
|
|
223
|
+
### 安装时
|
|
224
|
+
|
|
225
|
+
| 问题 | 解决 |
|
|
226
|
+
|------|------|
|
|
227
|
+
| `npx` 命令找不到 | 先装 Node.js(见第一步) |
|
|
228
|
+
| `python` 命令找不到 | 先装 Python(见第一步),**勾选 Add to PATH** |
|
|
229
|
+
| setup.py 报错 | 跑 `python .qoder/scripts/init_doctor.py --fix` 自动修复 |
|
|
230
|
+
|
|
231
|
+
### 使用时
|
|
232
|
+
|
|
233
|
+
| 问题 | 解决 |
|
|
234
|
+
|------|------|
|
|
235
|
+
| 搜索没结果 | 先跑 `python .qoder/scripts/setup.py` 建索引 |
|
|
236
|
+
| Qoder 里看不到 `/` 命令 | 新建对话 / 重启 QoderWork |
|
|
237
|
+
| 命令报错 | `python .qoder/scripts/init_doctor.py --fix` |
|
|
238
|
+
| 索引过期 | `python .qoder/scripts/git_sync.py --index-only` |
|
|
239
|
+
|
|
240
|
+
### 速查
|
|
241
|
+
|
|
242
|
+
```
|
|
243
|
+
安装 → npx @hupan56/wlkj init
|
|
244
|
+
环境修复 → python .qoder/scripts/init_doctor.py --fix
|
|
245
|
+
重新初始化 → python .qoder/scripts/setup.py
|
|
246
|
+
性能测试 → python .qoder/scripts/benchmark.py
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
## 环境要求总结
|
|
252
|
+
|
|
253
|
+
| 环境 | 必须? | 版本 |
|
|
254
|
+
|------|--------|------|
|
|
255
|
+
| **Node.js** | 必须(装工作流用) | 16+ |
|
|
256
|
+
| **Python** | 必须(跑引擎) | 3.9+ |
|
|
257
|
+
| **git** | 推荐(团队协作用,不装也能用核心功能) | 任意 |
|
|
258
|
+
| **Qoder 客户端** | 推荐(AI 对话驱动,不装也能用命令行) | IDE/Quest/QoderWork 任一 |
|
|
259
|
+
| **飞书** | 可选(通知推送) | 有群机器人即可 |
|