@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
package/bin/cli.js
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// wlkj - workflow toolkit
|
|
3
|
+
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const fs = require("fs");
|
|
6
|
+
const { execSync } = require("child_process");
|
|
7
|
+
|
|
8
|
+
const T = path.join(__dirname, "..", "templates");
|
|
9
|
+
|
|
10
|
+
function py(script, args = []) {
|
|
11
|
+
const p = path.join(process.cwd(), ".qoder", "scripts", script);
|
|
12
|
+
if (!fs.existsSync(p)) { console.log("请先运行: npx wlkj init"); process.exit(1); }
|
|
13
|
+
try {
|
|
14
|
+
return execSync(`python "${p}" ${args.map(a => `"${a}"`).join(" ")}`, { cwd: process.cwd(), encoding: "utf-8", timeout: 15000 });
|
|
15
|
+
} catch (e) { return (e.stdout || e.message); }
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function cp(src, dest) {
|
|
19
|
+
const s = path.join(T, src), d = path.join(process.cwd(), dest);
|
|
20
|
+
if (!fs.existsSync(s)) return;
|
|
21
|
+
fs.mkdirSync(path.dirname(d), { recursive: true });
|
|
22
|
+
if (!fs.existsSync(d)) { fs.copyFileSync(s, d); return true; }
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function gitOp(op) {
|
|
27
|
+
const cwd = process.cwd();
|
|
28
|
+
const cmds = {
|
|
29
|
+
"提交": 'git add . && git commit -m "update [ai-assisted]" && git push',
|
|
30
|
+
"推送": "git push",
|
|
31
|
+
"拉取最新": "git pull",
|
|
32
|
+
"同步": "git pull",
|
|
33
|
+
"查看状态": "git status",
|
|
34
|
+
"提交PRD": 'git add workspace/specs/prd/ && git commit -m "docs(ai): PRD update [ai-generated]" && git push',
|
|
35
|
+
"提交Spec": 'git add workspace/specs/ && git commit -m "docs(ai): Spec update [ai-generated]" && git push',
|
|
36
|
+
"提交任务": 'git add workspace/tasks/ && git commit -m "chore: task update [ai-assisted]" && git push',
|
|
37
|
+
};
|
|
38
|
+
const cmd = cmds[op];
|
|
39
|
+
if (!cmd) { console.log("可用: 提交 推送 拉取最新 同步 查看状态 提交PRD 提交Spec 提交任务"); return; }
|
|
40
|
+
try { console.log(execSync(cmd, { cwd, encoding: "utf-8", timeout: 30000, stdio: "pipe" }) || `${op} done`); }
|
|
41
|
+
catch (e) { console.log(e.stdout || e.message); }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function doInit(name) {
|
|
45
|
+
const cwd = process.cwd();
|
|
46
|
+
const hasExisting = fs.existsSync(path.join(cwd, ".qoder", "scripts", "setup.py"));
|
|
47
|
+
console.log(`\nwlkj init -> ${cwd}${hasExisting ? " (已存在, 增量更新)" : ""}\n`);
|
|
48
|
+
|
|
49
|
+
// === 1. 拷贝完整引擎 (镜像 .qoder/ 结构) ===
|
|
50
|
+
// 源: templates/qoder/* 目标: .qoder/*
|
|
51
|
+
const qoderSrc = path.join(T, "qoder");
|
|
52
|
+
let copied = 0;
|
|
53
|
+
if (fs.existsSync(qoderSrc)) {
|
|
54
|
+
copied = copyDirRecursive(qoderSrc, path.join(cwd, ".qoder"));
|
|
55
|
+
}
|
|
56
|
+
console.log(` 引擎: ${copied} 个文件 (${hasExisting ? "更新" : "新建"})`);
|
|
57
|
+
|
|
58
|
+
// === 2. 根文件 (AGENTS.md, 新手指南.md) ===
|
|
59
|
+
["root/AGENTS.md", "root/新手指南.md"].forEach(f => {
|
|
60
|
+
const src = path.join(T, f);
|
|
61
|
+
if (fs.existsSync(src)) {
|
|
62
|
+
const dstName = path.basename(f);
|
|
63
|
+
const dst = path.join(cwd, dstName);
|
|
64
|
+
if (!fs.existsSync(dst)) {
|
|
65
|
+
fs.copyFileSync(src, dst);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// === 3. workspace 目录结构 ===
|
|
71
|
+
["workspace/specs/prd", "workspace/tasks", "workspace/constitution",
|
|
72
|
+
"workspace/members", "data/docs/prd", "data/index", "data/code"].forEach(d => {
|
|
73
|
+
fs.mkdirSync(path.join(cwd, d), { recursive: true });
|
|
74
|
+
});
|
|
75
|
+
console.log(` workspace 目录就绪`);
|
|
76
|
+
|
|
77
|
+
// === 4. git init (若没有) ===
|
|
78
|
+
if (!fs.existsSync(path.join(cwd, ".git"))) {
|
|
79
|
+
try { execSync("git init", { cwd, stdio: "pipe" }); console.log(` git init done`); }
|
|
80
|
+
catch (_) { /* git 可能没装, 不阻塞 */ }
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// === 5. .gitignore (若没有) ===
|
|
84
|
+
const gitignorePath = path.join(cwd, ".gitignore");
|
|
85
|
+
if (!fs.existsSync(gitignorePath)) {
|
|
86
|
+
const baseGitignore = [
|
|
87
|
+
"# Source code repos (cloned by git_sync.py)", "data/code/",
|
|
88
|
+
"", "# AI pipeline runtime", ".qoder/.developer", ".qoder/.current-task",
|
|
89
|
+
".qoder/.runtime/", "", "# Personal learning", ".qoder/learning/feedback.jsonl",
|
|
90
|
+
"", "# Machine-local", "data/index/.last-sync", "data/index/.prd-collected.json",
|
|
91
|
+
"data/index/.index-meta.json", "data/index/.sync-lock", "data/index/.file-keys.json",
|
|
92
|
+
"data/index/.inverted-cache.json", "data/index/*.corrupt",
|
|
93
|
+
"", "# Identity (secret)", "workspace/members/*/.signing_key", "",
|
|
94
|
+
"# Python", "__pycache__/", "*.pyc", "*.lock", "",
|
|
95
|
+
].join("\n");
|
|
96
|
+
fs.writeFileSync(gitignorePath, baseGitignore, "utf-8");
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// === 6. 自动跑 setup.py (名字探测/git/索引/cron/QoderWork) ===
|
|
100
|
+
console.log(`\n--- 自动初始化 ---`);
|
|
101
|
+
const setupPath = path.join(cwd, ".qoder", "scripts", "setup.py");
|
|
102
|
+
if (fs.existsSync(setupPath)) {
|
|
103
|
+
const setupArgs = [name, "pm", "--skip-cron", "--skip-qoderwork"].filter(Boolean);
|
|
104
|
+
try {
|
|
105
|
+
const cmd = `python "${setupPath}" ${setupArgs.map(a => `"${a}"`).join(" ")}`;
|
|
106
|
+
console.log(` 运行: setup.py ${setupArgs.join(" ")}`);
|
|
107
|
+
execSync(cmd, { cwd, stdio: "inherit", timeout: 300000 });
|
|
108
|
+
} catch (e) {
|
|
109
|
+
console.log(` setup 部分失败 (不阻塞): ${(e.message || "").slice(0, 100)}`);
|
|
110
|
+
console.log(` 可手动重跑: python .qoder/scripts/setup.py`);
|
|
111
|
+
}
|
|
112
|
+
} else {
|
|
113
|
+
console.log(` setup.py 未找到, 跳过自动初始化`);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
console.log(`\n${"=".repeat(50)}`);
|
|
117
|
+
console.log(` 安装完成!`);
|
|
118
|
+
console.log(`${"=".repeat(50)}`);
|
|
119
|
+
console.log(`\n 现在可以开始了:`);
|
|
120
|
+
console.log(` 在 Qoder 里说 "写个 XX 的需求"`);
|
|
121
|
+
console.log(` 或输 / 看命令列表 (/wl-prd /wl-search /wl-task)`);
|
|
122
|
+
console.log(`\n 看不到命令? 新建对话 / 重启 QoderWork`);
|
|
123
|
+
console.log(` 环境问题? python .qoder/scripts/init_doctor.py --fix\n`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// 递归拷贝目录, 返回文件数
|
|
127
|
+
function copyDirRecursive(src, dst) {
|
|
128
|
+
let count = 0;
|
|
129
|
+
if (!fs.existsSync(src)) return 0;
|
|
130
|
+
fs.mkdirSync(dst, { recursive: true });
|
|
131
|
+
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
132
|
+
if (entry.name === "__pycache__" || entry.name.endsWith(".pyc")) continue;
|
|
133
|
+
const s = path.join(src, entry.name);
|
|
134
|
+
const d = path.join(dst, entry.name);
|
|
135
|
+
if (entry.isDirectory()) {
|
|
136
|
+
count += copyDirRecursive(s, d);
|
|
137
|
+
} else {
|
|
138
|
+
// 不覆盖已存在的 (增量更新, 保留用户改动)
|
|
139
|
+
// 但 .py 和 .md / config.yaml / rules 始终更新 (引擎文件)
|
|
140
|
+
const isEngine = entry.name.endsWith(".py") || entry.name.endsWith(".md") ||
|
|
141
|
+
entry.name.endsWith(".yaml") || entry.name.endsWith(".yml") ||
|
|
142
|
+
entry.name.endsWith(".toml") || entry.name.endsWith(".json") ||
|
|
143
|
+
entry.name.endsWith(".html") || entry.name.endsWith(".txt");
|
|
144
|
+
if (isEngine || !fs.existsSync(d)) {
|
|
145
|
+
fs.copyFileSync(s, d);
|
|
146
|
+
}
|
|
147
|
+
count++;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return count;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function doStatus() {
|
|
154
|
+
console.log("");
|
|
155
|
+
const dev = path.join(process.cwd(), ".qoder", ".developer");
|
|
156
|
+
if (fs.existsSync(dev)) {
|
|
157
|
+
const line = fs.readFileSync(dev, "utf-8").split("\n")[0];
|
|
158
|
+
console.log("dev: " + line.split("=")[1]?.trim());
|
|
159
|
+
} else { console.log("dev: - (run: npx wlkj init <name>)"); }
|
|
160
|
+
|
|
161
|
+
process.stdout.write(py("task.py", ["current", "--source"]));
|
|
162
|
+
|
|
163
|
+
const wsSpecs = path.join(process.cwd(), "workspace", "specs", "prd");
|
|
164
|
+
if (fs.existsSync(wsSpecs)) {
|
|
165
|
+
const n = fs.readdirSync(wsSpecs).filter(f => f.endsWith(".md")).length;
|
|
166
|
+
console.log(`PRDs: ${n}`);
|
|
167
|
+
}
|
|
168
|
+
console.log("");
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function doHelp() {
|
|
172
|
+
console.log("");
|
|
173
|
+
console.log("wlkj - AI 产品研发工作流 (v2.0)");
|
|
174
|
+
console.log("");
|
|
175
|
+
console.log("=== 一键安装 (新项目第一步) ===");
|
|
176
|
+
console.log(" npx wlkj init [你的名字] 安装完整引擎 + 自动初始化");
|
|
177
|
+
console.log(" 例: npx wlkj init 小王");
|
|
178
|
+
console.log(" 例: npx wlkj init (从 git 探测名字)");
|
|
179
|
+
console.log("");
|
|
180
|
+
console.log("=== 安装后怎么用 ===");
|
|
181
|
+
console.log(" 在 Qoder (IDE/Quest/QoderWork) 里:");
|
|
182
|
+
console.log(" 说中文: '写个 XX 的需求' '查一下 XX 代码' '建个任务'");
|
|
183
|
+
console.log(" 或斜杠: /wl-prd /wl-search /wl-task /wl-status /wl-report");
|
|
184
|
+
console.log("");
|
|
185
|
+
console.log("=== 状态 ===");
|
|
186
|
+
console.log(" npx wlkj status 查看当前状态");
|
|
187
|
+
console.log("");
|
|
188
|
+
console.log("=== 环境修复 ===");
|
|
189
|
+
console.log(" python .qoder/scripts/init_doctor.py --fix 自动修复环境");
|
|
190
|
+
console.log(" python .qoder/scripts/setup.py 重新初始化");
|
|
191
|
+
console.log("");
|
|
192
|
+
console.log("=== Git (中文命令) ===");
|
|
193
|
+
console.log(" npx wlkj 提交PRD 提交 PRD");
|
|
194
|
+
console.log(" npx wlkj 提交任务 提交任务");
|
|
195
|
+
console.log(" npx wlkj 提交 全部提交并推送");
|
|
196
|
+
console.log(" npx wlkj 拉取最新 / 同步 git pull");
|
|
197
|
+
console.log("");
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const [,, cmd, ...rest] = process.argv;
|
|
201
|
+
const mapped = rest.map(a => a === "-p" ? "--priority" : a);
|
|
202
|
+
|
|
203
|
+
switch (cmd) {
|
|
204
|
+
case "init": doInit(rest[0]); break;
|
|
205
|
+
case "task": process.stdout.write(py("task.py", mapped)); break;
|
|
206
|
+
case "status": doStatus(); break;
|
|
207
|
+
case "session": process.stdout.write(py("add_session.py", rest)); break;
|
|
208
|
+
case "help": case "--help": case "-h": doHelp(); break;
|
|
209
|
+
case "提交": case "推送": case "拉取最新": case "同步":
|
|
210
|
+
case "查看状态": case "提交PRD": case "提交Spec": case "提交任务":
|
|
211
|
+
gitOp(cmd); break;
|
|
212
|
+
default: doHelp();
|
|
213
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hupan56/wlkj",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "AI 产品研发工作流 - PRD/原型/搜索/任务/报告 一键就绪",
|
|
5
|
+
"bin": { "wlkj": "./bin/cli.js" },
|
|
6
|
+
"files": ["bin/", "templates/"],
|
|
7
|
+
"keywords": ["workflow", "ai", "prd", "pipeline", "qoder", "product", "team"],
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"publishConfig": { "access": "public" },
|
|
10
|
+
"engines": { "node": ">=16" }
|
|
11
|
+
}
|
package/templates/cli.js
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// wlkj - workflow toolkit
|
|
3
|
+
// 参考 Trellis 架构:用户看到的极简,背后 scripts/hooks 精确控制
|
|
4
|
+
|
|
5
|
+
const path = require("path");
|
|
6
|
+
const fs = require("fs");
|
|
7
|
+
const { execSync } = require("child_process");
|
|
8
|
+
|
|
9
|
+
const T = path.join(__dirname, "..", "templates");
|
|
10
|
+
|
|
11
|
+
// --- helpers ---
|
|
12
|
+
function py(script, args = []) {
|
|
13
|
+
const p = path.join(process.cwd(), ".qoder", "scripts", script);
|
|
14
|
+
if (!fs.existsSync(p)) {
|
|
15
|
+
console.log("请先运行: npx wlkj init");
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
return execSync(
|
|
20
|
+
`python "${p}" ${args.map(a => `"${a}"`).join(" ")}`,
|
|
21
|
+
{ cwd: process.cwd(), encoding: "utf-8", timeout: 15000 }
|
|
22
|
+
);
|
|
23
|
+
} catch (e) {
|
|
24
|
+
return (e.stdout || e.message);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function cp(src, dest) {
|
|
29
|
+
const s = path.join(T, src);
|
|
30
|
+
const d = path.join(process.cwd(), dest);
|
|
31
|
+
if (!fs.existsSync(s)) return;
|
|
32
|
+
fs.mkdirSync(path.dirname(d), { recursive: true });
|
|
33
|
+
if (!fs.existsSync(d)) { fs.copyFileSync(s, d); return true; }
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function cpDir(src, dest) {
|
|
38
|
+
const s = path.join(T, src);
|
|
39
|
+
const d = path.join(process.cwd(), dest);
|
|
40
|
+
if (!fs.existsSync(s)) return;
|
|
41
|
+
fs.mkdirSync(d, { recursive: true });
|
|
42
|
+
const items = fs.readdirSync(s);
|
|
43
|
+
items.forEach(item => {
|
|
44
|
+
const srcPath = path.join(s, item);
|
|
45
|
+
const destPath = path.join(d, item);
|
|
46
|
+
if (fs.statSync(srcPath).isDirectory()) {
|
|
47
|
+
cpDir(path.join(src, item), path.join(dest, item));
|
|
48
|
+
} else if (!fs.existsSync(destPath)) {
|
|
49
|
+
fs.copyFileSync(srcPath, destPath);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// --- init ---
|
|
55
|
+
function doInit(name) {
|
|
56
|
+
const cwd = process.cwd();
|
|
57
|
+
console.log(``);
|
|
58
|
+
console.log(`wlkj init -> ${cwd}`);
|
|
59
|
+
|
|
60
|
+
// 目录结构
|
|
61
|
+
const dirs = [
|
|
62
|
+
".qoder/rules", ".qoder/context", ".qoder/scripts/common",
|
|
63
|
+
".qoder/scripts/hooks", ".qoder/skills/prd-generator",
|
|
64
|
+
".qoder/skills/spec-generator", ".qoder/skills/spec-coder",
|
|
65
|
+
".qoder/skills/test-generator", ".qoder/agents", ".qoder/hooks",
|
|
66
|
+
".qoder/tasks", ".qoder/archive", ".qoder/workspace",
|
|
67
|
+
".qoder/.runtime/sessions",
|
|
68
|
+
".codex/hooks", ".codex/agents",
|
|
69
|
+
"docs/ai/prd", "docs/ai/specs", "src", "tests"
|
|
70
|
+
];
|
|
71
|
+
dirs.forEach(d => fs.mkdirSync(path.join(cwd, d), { recursive: true }));
|
|
72
|
+
|
|
73
|
+
// 模板文件(flat list,一目了然)
|
|
74
|
+
let copied = 0;
|
|
75
|
+
const files = [
|
|
76
|
+
// .qoder 核心
|
|
77
|
+
["qoder/config.yaml", ".qoder/config.yaml"],
|
|
78
|
+
["qoder/workflow.md", ".qoder/workflow.md"],
|
|
79
|
+
// rules
|
|
80
|
+
["rules/code-style.md", ".qoder/rules/code-style.md"],
|
|
81
|
+
["rules/prd-template.md", ".qoder/rules/prd-template.md"],
|
|
82
|
+
["rules/spec-template.md", ".qoder/rules/spec-template.md"],
|
|
83
|
+
["rules/testing.md", ".qoder/rules/testing.md"],
|
|
84
|
+
// context
|
|
85
|
+
["context/architecture.md", ".qoder/context/architecture.md"],
|
|
86
|
+
["context/data-dictionary.md",".qoder/context/data-dictionary.md"],
|
|
87
|
+
// skills
|
|
88
|
+
["skills/prd-generator/SKILL.md", ".qoder/skills/prd-generator/SKILL.md"],
|
|
89
|
+
["skills/spec-generator/SKILL.md", ".qoder/skills/spec-generator/SKILL.md"],
|
|
90
|
+
["skills/spec-coder/SKILL.md", ".qoder/skills/spec-coder/SKILL.md"],
|
|
91
|
+
["skills/test-generator/SKILL.md", ".qoder/skills/test-generator/SKILL.md"],
|
|
92
|
+
// agents
|
|
93
|
+
["agents/qoder-spec-gen.toml", ".qoder/agents/qoder-spec-gen.toml"],
|
|
94
|
+
["agents/qoder-coder.toml", ".qoder/agents/qoder-coder.toml"],
|
|
95
|
+
["agents/qoder-test-gen.toml", ".qoder/agents/qoder-test-gen.toml"],
|
|
96
|
+
// hooks
|
|
97
|
+
["hooks/post-prd-push.py", ".qoder/hooks/post-prd-push.py"],
|
|
98
|
+
// scripts
|
|
99
|
+
["scripts/init_developer.py", ".qoder/scripts/init_developer.py"],
|
|
100
|
+
["scripts/task.py", ".qoder/scripts/task.py"],
|
|
101
|
+
["scripts/add_session.py", ".qoder/scripts/add_session.py"],
|
|
102
|
+
["scripts/common/__init__.py", ".qoder/scripts/common/__init__.py"],
|
|
103
|
+
["scripts/common/paths.py", ".qoder/scripts/common/paths.py"],
|
|
104
|
+
["scripts/common/developer.py", ".qoder/scripts/common/developer.py"],
|
|
105
|
+
["scripts/common/active_task.py",".qoder/scripts/common/active_task.py"],
|
|
106
|
+
["scripts/common/task_utils.py", ".qoder/scripts/common/task_utils.py"],
|
|
107
|
+
// codex hooks
|
|
108
|
+
["codex/config.toml", ".codex/config.toml"],
|
|
109
|
+
["codex/hooks.json", ".codex/hooks.json"],
|
|
110
|
+
["codex/hooks/inject-pipeline-state.py", ".codex/hooks/inject-pipeline-state.py"],
|
|
111
|
+
["codex/agents/qoder-spec-gen.toml", ".codex/agents/qoder-spec-gen.toml"],
|
|
112
|
+
["codex/agents/qoder-coder.toml", ".codex/agents/qoder-coder.toml"],
|
|
113
|
+
["codex/agents/qoder-test-gen.toml", ".codex/agents/qoder-test-gen.toml"],
|
|
114
|
+
// root
|
|
115
|
+
["root/AGENTS.md", "AGENTS.md"],
|
|
116
|
+
];
|
|
117
|
+
|
|
118
|
+
files.forEach(([s, d]) => { if (cp(s, d)) copied++; });
|
|
119
|
+
console.log(` copied ${copied} files`);
|
|
120
|
+
|
|
121
|
+
// git init
|
|
122
|
+
if (!fs.existsSync(path.join(cwd, ".git"))) {
|
|
123
|
+
try {
|
|
124
|
+
execSync("git init", { cwd, stdio: "pipe" });
|
|
125
|
+
console.log(" git init done");
|
|
126
|
+
} catch (_) {}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// developer
|
|
130
|
+
if (name) {
|
|
131
|
+
console.log("");
|
|
132
|
+
process.stdout.write(py("init_developer.py", [name]));
|
|
133
|
+
} else {
|
|
134
|
+
console.log("");
|
|
135
|
+
console.log(" next: npx wlkj init <your-name>");
|
|
136
|
+
}
|
|
137
|
+
console.log("");
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// --- status ---
|
|
141
|
+
function doStatus() {
|
|
142
|
+
console.log("");
|
|
143
|
+
// developer
|
|
144
|
+
const dev = path.join(process.cwd(), ".qoder", ".developer");
|
|
145
|
+
if (fs.existsSync(dev)) {
|
|
146
|
+
const line = fs.readFileSync(dev, "utf-8").split("\n")[0];
|
|
147
|
+
console.log("dev: " + line.split("=")[1]?.trim());
|
|
148
|
+
} else {
|
|
149
|
+
console.log("dev: -");
|
|
150
|
+
}
|
|
151
|
+
// task
|
|
152
|
+
process.stdout.write(py("task.py", ["current", "--source"]));
|
|
153
|
+
// docs
|
|
154
|
+
const count = (d) => {
|
|
155
|
+
const p = path.join(process.cwd(), d);
|
|
156
|
+
return fs.existsSync(p) ? fs.readdirSync(p).filter(f => f.endsWith(".md")).length : 0;
|
|
157
|
+
};
|
|
158
|
+
console.log(`prd: ${count("docs/ai/prd")} spec: ${count("docs/ai/specs")}`);
|
|
159
|
+
console.log("");
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// --- main ---
|
|
163
|
+
const [,, cmd, ...rest] = process.argv;
|
|
164
|
+
|
|
165
|
+
switch (cmd) {
|
|
166
|
+
case "init":
|
|
167
|
+
doInit(rest[0]);
|
|
168
|
+
break;
|
|
169
|
+
case "task":
|
|
170
|
+
// 映射 -p 到 --priority(CLI 便捷)
|
|
171
|
+
const mapped = rest.map(a => a === "-p" ? "--priority" : a);
|
|
172
|
+
process.stdout.write(py("task.py", mapped));
|
|
173
|
+
break;
|
|
174
|
+
case "status":
|
|
175
|
+
doStatus();
|
|
176
|
+
break;
|
|
177
|
+
case "session":
|
|
178
|
+
process.stdout.write(py("add_session.py", rest));
|
|
179
|
+
break;
|
|
180
|
+
default:
|
|
181
|
+
console.log("");
|
|
182
|
+
console.log("wlkj - workflow toolkit");
|
|
183
|
+
console.log("");
|
|
184
|
+
console.log(" npx wlkj init [name] init workflow + developer");
|
|
185
|
+
console.log(" npx wlkj status show status");
|
|
186
|
+
console.log(" npx wlkj task <cmd> task management");
|
|
187
|
+
console.log(" npx wlkj session <args> record session log");
|
|
188
|
+
console.log("");
|
|
189
|
+
console.log("task commands:");
|
|
190
|
+
console.log(' create "Title" [-p P0|P1|P2|P3] [--assignee who]');
|
|
191
|
+
console.log(" list [--mine] [--status planning|in_progress]");
|
|
192
|
+
console.log(" start <name>");
|
|
193
|
+
console.log(" finish");
|
|
194
|
+
console.log(" current");
|
|
195
|
+
console.log(" archive <name>");
|
|
196
|
+
console.log(" add-subtask <parent> <child>");
|
|
197
|
+
console.log("");
|
|
198
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: wl-code
|
|
3
|
+
description: "Implement code following a Spec strictly. Needs confirmation."
|
|
4
|
+
argument-hint: "[REQ-ID or spec-filename]"
|
|
5
|
+
auto-approve: false
|
|
6
|
+
allowed-tools: [Read, Glob, Grep, Bash, Write, Edit]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# /wl-code - Implement Code
|
|
10
|
+
|
|
11
|
+
User input: $ARGUMENTS
|
|
12
|
+
|
|
13
|
+
## Step 1: Load Spec
|
|
14
|
+
|
|
15
|
+
If REQ-ID provided, search workspace/specs/ for matching spec.
|
|
16
|
+
If no argument, check .qoder/.current-task for active task.
|
|
17
|
+
Read the spec file to understand requirements.
|
|
18
|
+
|
|
19
|
+
## Step 2: Search Related Code
|
|
20
|
+
|
|
21
|
+
Run: python .qoder/scripts/search_index.py <keyword>
|
|
22
|
+
Find existing code that this implementation relates to.
|
|
23
|
+
Read relevant files for context.
|
|
24
|
+
|
|
25
|
+
## Step 3: Implement
|
|
26
|
+
|
|
27
|
+
Follow the spec strictly:
|
|
28
|
+
1. Read spec-coder skill for coding workflow
|
|
29
|
+
2. Implement each requirement from spec
|
|
30
|
+
3. Follow team conventions from data/code/ patterns
|
|
31
|
+
4. Write clean, commented code
|
|
32
|
+
|
|
33
|
+
## Step 4: Self-Check
|
|
34
|
+
|
|
35
|
+
After implementation:
|
|
36
|
+
- [ ] All spec requirements covered
|
|
37
|
+
- [ ] No TODO/FIXME left
|
|
38
|
+
- [ ] Code follows project conventions
|
|
39
|
+
- [ ] Error handling in place
|
|
40
|
+
|
|
41
|
+
## Step 5: Report
|
|
42
|
+
|
|
43
|
+
Tell user what was implemented and suggest /wl-test next.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: wl-commit
|
|
3
|
+
description: "Git commit, push, and sync. Includes pre-commit quality check."
|
|
4
|
+
argument-hint: "[commit message]"
|
|
5
|
+
auto-approve: false
|
|
6
|
+
allowed-tools: [Read, Glob, Grep, Bash, Write, Edit]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# /wl-commit - Git Commit + Push + Sync
|
|
10
|
+
|
|
11
|
+
User input: $ARGUMENTS (commit message)
|
|
12
|
+
|
|
13
|
+
## Flow
|
|
14
|
+
1. Check git status for uncommitted changes
|
|
15
|
+
2. Auto-stage changed files
|
|
16
|
+
3. Generate commit message if not provided:
|
|
17
|
+
- Format: [ai-generated] {type}: {description}
|
|
18
|
+
- Types: feat/fix/docs/refactor/test
|
|
19
|
+
4. Ask user to confirm commit message
|
|
20
|
+
5. Commit with message
|
|
21
|
+
6. Pull latest from remote (sync)
|
|
22
|
+
7. Push to remote
|
|
23
|
+
8. Record to learning system
|
|
24
|
+
|
|
25
|
+
## Pre-commit Quality Gate
|
|
26
|
+
Before committing, check:
|
|
27
|
+
- [ ] All tests pass (if /wl-test was run)
|
|
28
|
+
- [ ] No TODO/FIXME left in code
|
|
29
|
+
- [ ] Commit tagged [ai-generated] or [ai-assisted]
|
|
30
|
+
- [ ] Amount fields use BigDecimal (if applicable)
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: wl-init
|
|
3
|
+
description: "Initialize or switch developer + full environment health check. Idempotent."
|
|
4
|
+
argument-hint: "[name] [role]"
|
|
5
|
+
auto-approve: true
|
|
6
|
+
allowed-tools: [Read, Glob, Grep, Bash, Write, Edit]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# /wl-init - One-stop Init (Developer + Environment Doctor)
|
|
10
|
+
|
|
11
|
+
Usage:
|
|
12
|
+
/wl-init - 体检环境 + 显示状态
|
|
13
|
+
/wl-init 小王 - 切换/注册开发者 + 体检 + 自动修复
|
|
14
|
+
/wl-init 小王 pm - 注册新开发者(带角色) + 体检 + 自动修复
|
|
15
|
+
|
|
16
|
+
## Engine: init_doctor.py (幂等, 增量, 永不重头再来)
|
|
17
|
+
|
|
18
|
+
### Case 1: /wl-init (no arguments)
|
|
19
|
+
```bash
|
|
20
|
+
python .qoder/scripts/init_doctor.py
|
|
21
|
+
```
|
|
22
|
+
Report the doctor's findings to the user in friendly language.
|
|
23
|
+
If it found fixable issues, ask: "发现 N 个问题,要我自动修复吗?"
|
|
24
|
+
then run with --fix.
|
|
25
|
+
|
|
26
|
+
### Case 2/3: /wl-init 小王 [pm]
|
|
27
|
+
```bash
|
|
28
|
+
python .qoder/scripts/init_doctor.py --fix 小王 [pm]
|
|
29
|
+
```
|
|
30
|
+
This single command does ALL of:
|
|
31
|
+
1. 注册/切换开发者 (.qoder/.developer, key=value 格式) + 个人空间目录
|
|
32
|
+
2. 拉取团队最新 (team_sync pull - 拿到别人的 PRD 和最新知识图谱)
|
|
33
|
+
3. 克隆缺失的源码仓库 (按 config.yaml git_sync.projects)
|
|
34
|
+
4. 知识图谱: 7天内新鲜->跳过 / 过期->增量同步 / 缺失->全量构建(仅首次)
|
|
35
|
+
5. 校验风格约束文件 + PRD 模板
|
|
36
|
+
6. 检查周五自动构建状态, 给出本系统的设置命令
|
|
37
|
+
|
|
38
|
+
After doctor finishes:
|
|
39
|
+
- Create member.json with role if not exists (workspace/members/{name}/member.json)
|
|
40
|
+
- Run team.py add if it's a new member
|
|
41
|
+
|
|
42
|
+
## Incremental Guarantee (为什么不会重头再来)
|
|
43
|
+
|
|
44
|
+
| 状态 | 行为 |
|
|
45
|
+
|------|------|
|
|
46
|
+
| 本周已 init 过 / 周五任务跑过 | 索引新鲜(≤7天) -> 全部跳过, 秒级完成 |
|
|
47
|
+
| 周五任务没跑/失效 | 索引过期 -> git diff 增量更新, 只处理变更文件 |
|
|
48
|
+
| 全新机器 | 克隆仓库 + 全量构建一次, 之后永远增量 |
|
|
49
|
+
| 团队其他人已更新图谱 | team_sync pull 直接拿到, 本机不用构建 |
|
|
50
|
+
|
|
51
|
+
## .developer File Format (MANDATORY)
|
|
52
|
+
|
|
53
|
+
ALWAYS write `.qoder/.developer` in key=value format:
|
|
54
|
+
```
|
|
55
|
+
name=小王
|
|
56
|
+
role=pm
|
|
57
|
+
initialized_at=2026-06-12T10:00:00
|
|
58
|
+
```
|
|
59
|
+
Do NOT use `name: value` style. (Parsers accept both for legacy files,
|
|
60
|
+
but new writes must use `=`.)
|
|
61
|
+
|
|
62
|
+
## Git Author Check
|
|
63
|
+
|
|
64
|
+
The doctor reports if `git config user.name` differs from the developer
|
|
65
|
+
name (it breaks /wl-report's commit statistics). If the user agrees,
|
|
66
|
+
run: `git config user.name {name}`. Do not change it without confirmation.
|
|
67
|
+
|
|
68
|
+
## Status Display
|
|
69
|
+
After any operation, show:
|
|
70
|
+
```
|
|
71
|
+
Developer: 小王 (pm)
|
|
72
|
+
Team: 3 members (1 PM, 2 Dev)
|
|
73
|
+
Active tasks: 2 (1 in progress, 1 draft)
|
|
74
|
+
知识图谱: 2 天前更新 (新鲜)
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Safety Rules
|
|
78
|
+
- NEVER delete workspace/ content
|
|
79
|
+
- NEVER overwrite member.json or task.json
|
|
80
|
+
- Full index build ONLY when index is completely missing (doctor handles this)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: wl-insight
|
|
3
|
+
description: "Data insight: user feedback analysis + product metrics review in one command"
|
|
4
|
+
argument-hint: "[type: feedback/metrics/all] [data or description]"
|
|
5
|
+
auto-approve: true
|
|
6
|
+
allowed-tools: [Read, Glob, Grep, Bash, Write, Edit]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# /wl-insight - Data Insight (Feedback + Metrics)
|
|
10
|
+
|
|
11
|
+
User input: $ARGUMENTS
|
|
12
|
+
|
|
13
|
+
## Type Detection
|
|
14
|
+
- "feedback" or paste feedback text -> Feedback analysis
|
|
15
|
+
- "metrics" or paste data table -> Metrics review
|
|
16
|
+
- "all" or no keyword -> Both (if data available)
|
|
17
|
+
- Default: ask user what they want to analyze
|
|
18
|
+
|
|
19
|
+
## Feedback Analysis
|
|
20
|
+
1. Parse feedback data (text/CSV/paste)
|
|
21
|
+
2. Classify: feature-request / bug / UX / performance / praise / question
|
|
22
|
+
3. Sentiment: positive / neutral / negative
|
|
23
|
+
4. Extract pain points (frequency x severity)
|
|
24
|
+
5. Output:
|
|
25
|
+
- Category breakdown
|
|
26
|
+
- Top 10 pain points with representative quotes
|
|
27
|
+
- Prioritized improvement recommendations
|
|
28
|
+
6. Save to: workspace/members/{dev}/drafts/wl-insight-feedback-{date}.md
|
|
29
|
+
|
|
30
|
+
## Metrics Review
|
|
31
|
+
1. Parse metrics data
|
|
32
|
+
2. North Star decomposition (North Star -> L1 -> L2)
|
|
33
|
+
3. Growth: DAU/WAU/MAU + user segments
|
|
34
|
+
4. Retention: D1/D7/D30 vs industry benchmarks
|
|
35
|
+
5. Conversion funnel: bottleneck identification
|
|
36
|
+
6. A/B experiment interpretation (if data provided)
|
|
37
|
+
7. OKR alignment check
|
|
38
|
+
8. Anomaly attribution (if metric changed)
|
|
39
|
+
9. Output:
|
|
40
|
+
- Health overview table
|
|
41
|
+
- Key insights (data-backed)
|
|
42
|
+
- Action recommendations
|
|
43
|
+
10. Save to: workspace/members/{dev}/drafts/wl-insight-metrics-{date}.md
|
|
44
|
+
|
|
45
|
+
## Combined (all)
|
|
46
|
+
Run both analyses. Cross-reference:
|
|
47
|
+
- Metrics anomaly + user feedback = root cause
|
|
48
|
+
- Feedback trends + metric changes = validation
|
|
49
|
+
|
|
50
|
+
## Output
|
|
51
|
+
Always end with: "Use /wl-prd to convert insights into action"
|