@hupan56/wlkj 2.3.3 → 2.3.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hupan56/wlkj",
3
- "version": "2.3.3",
3
+ "version": "2.3.5",
4
4
  "description": "AI Product R&D Workflow - PRD/Prototype/Search/Task/Report",
5
5
  "bin": {
6
6
  "wlkj": "bin/cli.js"
@@ -35,12 +35,23 @@ WAIT for the answer. Platform determines everything downstream:
35
35
 
36
36
  ## Mode Detection
37
37
 
38
- | Keyword in input | Mode | Sub-agent (.qoder/agents/) |
39
- |------------------|------|---------------------------|
40
- | (none, DEFAULT) | Reference | prd-reference.md |
41
- | "头脑风暴" / "brainstorm" | Brainstorm | prd-research.md (N parallel) |
42
- | "规划" / "plan" | Planning | prd-planning.md |
43
- | "快速" / "quick" | Quick | prd-quick.md |
38
+ | 用户怎么说 | 模式 | 做什么 | PRD? | 出原型? |
39
+ |-----------|------|--------|---------|---------|
40
+ | 直接说需求(默认) | **Reference** 参考模式 | 参考现有代码+历史PRD,出标准PRD+原型 | ✅ | ✅ |
41
+ | "探讨""聊聊""分析一下""我想做个XX不知道怎么做" | **Discuss** 探讨模式 | 纯讨论:理需求、拆问题、给方案建议,**不出PRD不出原型** | | ❌ |
42
+ | "头脑风暴""brainstorm" | **Brainstorm** 头脑风暴 | SCAMPER+多agent调研,产出创新方案→PRD+原型 | ✅ | ✅ |
43
+ | "规划""plan" | **Planning** 规划模式 | 现状分析+理想态+Gap+MVP路线图→PRD+原型 | ✅ | ✅ |
44
+ | "快速""quick""加个字段""小改动" | **Quick** 快速模式 | 快速定位+Mini-PRD+微型原型(只改diff) | ✅ | ✅ |
45
+
46
+ ### 什么时候用哪个模式?
47
+
48
+ - **不知道怎么做** → 探讨模式(先聊清楚再决定要不要出PRD)
49
+ - **照着已有的做类似的** → 参考模式(默认)
50
+ - **想创新/没思路** → 头脑风暴
51
+ - **大方向/季度规划** → 规划模式
52
+ - **小功能/Bug fix** → 快速模式
53
+
54
+ > 探讨模式结束后,用户可以说"按这个方向出PRD"→ 自动转参考模式生成。
44
55
 
45
56
  The agent files define the detailed workflow for each mode. When
46
57
  dispatching a sub-agent, ALWAYS include in the dispatch prompt:
@@ -65,6 +76,31 @@ Never reuse an existing number. push 时 team_sync 会校验 REQ-ID 唯一性。
65
76
 
66
77
  ---
67
78
 
79
+ ## Discuss Mode (探讨模式 — 纯讨论,不出 PRD/原型)
80
+
81
+ > 触发:用户说"探讨""聊聊""分析一下""我想做个XX但不知道怎么做"
82
+ > 或需求很模糊,需要先理清思路时。
83
+
84
+ ### 做什么
85
+ 1. **听需求**:让用户把想法说完,不要急着出方案
86
+ 2. **问关键问题**(一次问完,不要一条一条问):
87
+ - 要解决什么问题?(业务痛点)
88
+ - 谁用?(角色/场景)
89
+ - 现在怎么做的?(现状)
90
+ - 期望怎样?(目标)
91
+ 3. **给方向建议**:基于现有代码(跑 context_pack 了解现状)给出 2-3 个可行方向
92
+ 4. **不生成 PRD、不生成原型、不分配 REQ-ID**
93
+ 5. 讨论结束后问:"要按这个方向出 PRD 吗?" → 用户说是 → 转 Reference 模式
94
+
95
+ ### 不做什么
96
+ - ❌ 不写 PRD 文件
97
+ - ❌ 不画原型
98
+ - ❌ 不分配 REQ-ID
99
+ - ❌ 不跑 EVA 评估
100
+ - ❌ 不 push
101
+
102
+ ---
103
+
68
104
  ## Reference Mode (DEFAULT)
69
105
 
70
106
  ### Step 1: Get ALL Context in ONE Call (fast path)
@@ -38,10 +38,22 @@ trigger: "用户描述需求、要 PRD、要原型、要 mockup,或直接 /wl-
38
38
  - `.qoder/.current-task` — 当前活动任务
39
39
  - `data/index/.index-meta.json` — 知识图谱新鲜度(过期提示先 `/wl-init`,不阻塞)
40
40
 
41
- ## 完整流程
41
+ ## 五种模式(看用户怎么说自动选)
42
42
 
43
- `.qoder/commands/wl-prd.md` 执行(含 Reference/Brainstorm/Planning/Quick
44
- 四种模式)。核心步骤:
43
+ | 用户怎么说 | 模式 | 出PRD? | 出原型? |
44
+ |-----------|------|---------|---------|
45
+ | 直接说需求(默认) | Reference 参考 | ✅ | ✅ |
46
+ | "探讨""聊聊""分析一下""不知道怎么做" | **Discuss 探讨** | ❌ | ❌ |
47
+ | "头脑风暴""brainstorm" | Brainstorm 头脑风暴 | ✅ | ✅ |
48
+ | "规划""plan" | Planning 规划 | ✅ | ✅ |
49
+ | "快速""quick""加个字段" | Quick 快速 | ✅ | ✅ |
50
+
51
+ > **探讨模式**:纯讨论,理需求、拆问题、给方向建议。不出文件。
52
+ > 讨论完用户说"出PRD"→ 自动转参考模式。
53
+
54
+ ## 完整流程(Reference/Brainstorm/Planning/Quick 模式)
55
+
56
+ 按 `.qoder/commands/wl-prd.md` 执行。核心步骤:
45
57
 
46
58
  1. **一次取全上下文**(fast path):
47
59
  ```bash
package/templates/cli.js DELETED
@@ -1,198 +0,0 @@
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
- }
@@ -1,41 +0,0 @@
1
- #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
- """
4
- DEPRECATED: code_index.py used to keep its own (cruder) index builder
5
- whose schema conflicted with git_sync.py's - running it would overwrite
6
- the real indexes. It now delegates to the single implementation.
7
-
8
- Usage:
9
- python code_index.py scan # full index rebuild (= git_sync.py --index-only)
10
- python code_index.py search <kw> # use search_index.py instead
11
- """
12
-
13
- import os
14
- import sys
15
-
16
- # UTF-8 stdio (防御性: stdout 被捕获时不崩溃)
17
- try:
18
- sys.stdout.reconfigure(encoding='utf-8', errors='replace')
19
- except (AttributeError, TypeError, OSError, IOError):
20
- try:
21
- sys.stdout.reconfigure(encoding='utf-8')
22
- except Exception:
23
- pass
24
-
25
- sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
26
-
27
- if __name__ == '__main__':
28
- cmd = sys.argv[1] if len(sys.argv) > 1 else ''
29
- if cmd == 'scan':
30
- from git_sync import build_full_indexes, FAILURES
31
- build_full_indexes()
32
- sys.exit(1 if FAILURES else 0)
33
- elif cmd == 'search' and len(sys.argv) > 2:
34
- import subprocess
35
- script = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'search_index.py')
36
- sys.exit(subprocess.call([sys.executable, script] + sys.argv[2:]))
37
- else:
38
- print('Usage:')
39
- print(' python code_index.py scan # full index rebuild')
40
- print(' python code_index.py search <kw> # -> search_index.py')
41
- sys.exit(1)
@@ -1,5 +0,0 @@
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]))
@@ -1,29 +0,0 @@
1
- # team.py - Team member registration
2
- import os, json, sys
3
- NL = chr(10)
4
- BASE = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
5
-
6
- def add_member(name, role):
7
- members_dir = os.path.join(BASE, 'workspace', 'members')
8
- member_dir = os.path.join(members_dir, name)
9
- os.makedirs(os.path.join(member_dir, 'journal'), exist_ok=True)
10
- os.makedirs(os.path.join(member_dir, 'drafts'), exist_ok=True)
11
- os.makedirs(os.path.join(member_dir, 'inbox'), exist_ok=True)
12
- info = {'name': name, 'role': role}
13
- with open(os.path.join(member_dir, 'member.json'), 'w') as f:
14
- json.dump(info, f, indent=2)
15
- print('OK: member ' + name + ' (' + role + ') added')
16
-
17
- def list_members():
18
- md = os.path.join(BASE, 'workspace', 'members')
19
- if not os.path.isdir(md): print('No members'); return
20
- for d in sorted(os.listdir(md)):
21
- info_f = os.path.join(md, d, 'member.json')
22
- if os.path.isfile(info_f):
23
- info = json.load(open(info_f))
24
- print(d + ' [' + info.get('role', '?') + ']')
25
-
26
- if __name__ == '__main__':
27
- if len(sys.argv) < 2: list_members()
28
- elif sys.argv[1] == 'add' and len(sys.argv) >= 4: add_member(sys.argv[2], sys.argv[3])
29
- else: list_members()