@hupan56/wlkj 2.2.4 → 2.2.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/bin/cli.js +532 -532
- package/package.json +28 -28
- package/templates/qoder/hooks/inject-workflow-state.py +117 -117
- package/templates/qoder/hooks/session-start.py +204 -204
- package/templates/qoder/scripts/common/developer.py +231 -161
- package/templates/qoder/scripts/common/paths.py +310 -310
- package/templates/qoder/scripts/common/task_utils.py +392 -392
- package/templates/qoder/scripts/init_developer.py +75 -75
- package/templates/qoder/scripts/install_qoderwork.py +367 -367
- package/templates/qoder/scripts/role.py +39 -39
- package/templates/qoder/scripts/syncgate.py +333 -333
- package/templates/qoder/scripts/team_sync.py +439 -439
- package/templates/qoder/skills/design-review/SKILL.md +25 -25
- package/templates/qoder/skills/prd-generator/SKILL.md +180 -180
- package/templates/qoder/skills/prd-review/SKILL.md +36 -36
- package/templates/qoder/skills/prototype-generator/SKILL.md +141 -141
- package/templates/qoder/skills/spec-coder/SKILL.md +68 -68
- package/templates/qoder/skills/spec-generator/SKILL.md +66 -66
- package/templates/qoder/skills/test-generator/SKILL.md +71 -71
- package/templates/root/AGENTS.md +182 -182
package/package.json
CHANGED
|
@@ -1,28 +1,28 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@hupan56/wlkj",
|
|
3
|
-
"version": "2.2.
|
|
4
|
-
"description": "AI Product R&D Workflow - PRD/Prototype/Search/Task/Report",
|
|
5
|
-
"bin": {
|
|
6
|
-
"wlkj": "bin/cli.js"
|
|
7
|
-
},
|
|
8
|
-
"files": [
|
|
9
|
-
"bin/",
|
|
10
|
-
"templates/"
|
|
11
|
-
],
|
|
12
|
-
"keywords": [
|
|
13
|
-
"workflow",
|
|
14
|
-
"ai",
|
|
15
|
-
"prd",
|
|
16
|
-
"pipeline",
|
|
17
|
-
"qoder",
|
|
18
|
-
"product",
|
|
19
|
-
"team"
|
|
20
|
-
],
|
|
21
|
-
"license": "MIT",
|
|
22
|
-
"publishConfig": {
|
|
23
|
-
"access": "public"
|
|
24
|
-
},
|
|
25
|
-
"engines": {
|
|
26
|
-
"node": ">=16"
|
|
27
|
-
}
|
|
28
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@hupan56/wlkj",
|
|
3
|
+
"version": "2.2.5",
|
|
4
|
+
"description": "AI Product R&D Workflow - PRD/Prototype/Search/Task/Report",
|
|
5
|
+
"bin": {
|
|
6
|
+
"wlkj": "bin/cli.js"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"bin/",
|
|
10
|
+
"templates/"
|
|
11
|
+
],
|
|
12
|
+
"keywords": [
|
|
13
|
+
"workflow",
|
|
14
|
+
"ai",
|
|
15
|
+
"prd",
|
|
16
|
+
"pipeline",
|
|
17
|
+
"qoder",
|
|
18
|
+
"product",
|
|
19
|
+
"team"
|
|
20
|
+
],
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"publishConfig": {
|
|
23
|
+
"access": "public"
|
|
24
|
+
},
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=16"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -1,117 +1,117 @@
|
|
|
1
|
-
# inject-workflow-state.py - Inject workflow state on every message
|
|
2
|
-
import os, json, sys
|
|
3
|
-
|
|
4
|
-
if sys.platform == 'win32':
|
|
5
|
-
try:
|
|
6
|
-
sys.stdout.reconfigure(encoding='utf-8')
|
|
7
|
-
except Exception:
|
|
8
|
-
pass
|
|
9
|
-
|
|
10
|
-
NL = chr(10)
|
|
11
|
-
BASE = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
12
|
-
|
|
13
|
-
# mtime 缓存 (审计 H5: 每条用户消息都跑这个 hook, 缓存避免重复读盘)
|
|
14
|
-
# 格式: {filepath: (mtime, parsed_value)}
|
|
15
|
-
_CACHE = {}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def _read_developer():
|
|
19
|
-
"""读当前开发者名 (容错: UTF-8/GBK)."""
|
|
20
|
-
df = os.path.join(BASE, '.qoder', '.developer')
|
|
21
|
-
if not os.path.isfile(df):
|
|
22
|
-
return None
|
|
23
|
-
for enc in ('utf-8', 'gbk', 'utf-8-sig'):
|
|
24
|
-
try:
|
|
25
|
-
with open(df, encoding=enc) as f:
|
|
26
|
-
for line in f:
|
|
27
|
-
line = line.strip()
|
|
28
|
-
if line.startswith('name='):
|
|
29
|
-
return line.split('=', 1)[1].strip()
|
|
30
|
-
if line.startswith('name:') or ':' in line:
|
|
31
|
-
# 容错 name: x 格式
|
|
32
|
-
for sep in ('=', ':'):
|
|
33
|
-
if sep in line:
|
|
34
|
-
k, v = line.split(sep, 1)
|
|
35
|
-
if k.strip() == 'name':
|
|
36
|
-
return v.strip()
|
|
37
|
-
except (OSError, UnicodeDecodeError):
|
|
38
|
-
continue
|
|
39
|
-
return None
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
def _cached_read(path, parser):
|
|
43
|
-
"""带 mtime 缓存的文件读。parser: callable(content_str) -> value."""
|
|
44
|
-
try:
|
|
45
|
-
mtime = os.path.getmtime(path)
|
|
46
|
-
except OSError:
|
|
47
|
-
return None
|
|
48
|
-
cached = _CACHE.get(path)
|
|
49
|
-
if cached and cached[0] == mtime:
|
|
50
|
-
return cached[1]
|
|
51
|
-
# 重读
|
|
52
|
-
for enc in ('utf-8', 'gbk', 'utf-8-sig'):
|
|
53
|
-
try:
|
|
54
|
-
with open(path, encoding=enc) as f:
|
|
55
|
-
content = f.read()
|
|
56
|
-
value = parser(content)
|
|
57
|
-
_CACHE[path] = (mtime, value)
|
|
58
|
-
return value
|
|
59
|
-
except (OSError, UnicodeDecodeError):
|
|
60
|
-
continue
|
|
61
|
-
return None
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
def get_status():
|
|
65
|
-
# 优先读按开发者隔离的 current-task 文件
|
|
66
|
-
dev = _read_developer()
|
|
67
|
-
ct_candidates = []
|
|
68
|
-
if dev:
|
|
69
|
-
safe = ''.join(c for c in dev if c.isalnum() or c in '_-') or 'anon'
|
|
70
|
-
ct_candidates.append(os.path.join(BASE, '.qoder', '.runtime', 'current-task.' + safe))
|
|
71
|
-
ct_candidates.append(os.path.join(BASE, '.qoder', '.current-task')) # 旧格式回退
|
|
72
|
-
|
|
73
|
-
rel = None
|
|
74
|
-
for ct in ct_candidates:
|
|
75
|
-
rel = _cached_read(ct, lambda c: c.strip() or None)
|
|
76
|
-
if rel:
|
|
77
|
-
break
|
|
78
|
-
if not rel:
|
|
79
|
-
return None, None
|
|
80
|
-
|
|
81
|
-
tj = os.path.join(BASE, rel, 'task.json')
|
|
82
|
-
if os.path.isfile(tj):
|
|
83
|
-
d = _cached_read(tj, lambda c: json.loads(c) if c.strip() else None)
|
|
84
|
-
if d:
|
|
85
|
-
return rel, d.get('status', 'unknown')
|
|
86
|
-
return rel, None
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
# Statuses written by task.py: planning -> in_progress -> completed
|
|
90
|
-
GUIDE = {
|
|
91
|
-
None: 'No active task. Use /wl-prd to start, or /wl-task create.',
|
|
92
|
-
'planning': 'Task in planning. Use /wl-prd to write the PRD, then /wl-task start.',
|
|
93
|
-
'in_progress': 'Dev in progress. /wl-spec to generate spec, /wl-code to implement.',
|
|
94
|
-
'completed': 'Task completed. /wl-task archive to archive, or start the next one.',
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
def main():
|
|
99
|
-
tp, st = get_status()
|
|
100
|
-
g = GUIDE.get(st, 'Status "{}" - check task.json, expected planning/in_progress/completed.'.format(st))
|
|
101
|
-
parts = []
|
|
102
|
-
parts.append('<qoder-workflow>')
|
|
103
|
-
if tp:
|
|
104
|
-
parts.append('Task: ' + tp)
|
|
105
|
-
parts.append('Status: ' + str(st))
|
|
106
|
-
parts.append('Do: ' + g)
|
|
107
|
-
parts.append('</qoder-workflow>')
|
|
108
|
-
print(NL.join(parts))
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
if __name__ == '__main__':
|
|
112
|
-
try:
|
|
113
|
-
main()
|
|
114
|
-
except Exception as e:
|
|
115
|
-
print('<qoder-workflow>')
|
|
116
|
-
print('hook error: ' + str(e)[:200])
|
|
117
|
-
print('</qoder-workflow>')
|
|
1
|
+
# inject-workflow-state.py - Inject workflow state on every message
|
|
2
|
+
import os, json, sys
|
|
3
|
+
|
|
4
|
+
if sys.platform == 'win32':
|
|
5
|
+
try:
|
|
6
|
+
sys.stdout.reconfigure(encoding='utf-8')
|
|
7
|
+
except Exception:
|
|
8
|
+
pass
|
|
9
|
+
|
|
10
|
+
NL = chr(10)
|
|
11
|
+
BASE = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
12
|
+
|
|
13
|
+
# mtime 缓存 (审计 H5: 每条用户消息都跑这个 hook, 缓存避免重复读盘)
|
|
14
|
+
# 格式: {filepath: (mtime, parsed_value)}
|
|
15
|
+
_CACHE = {}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _read_developer():
|
|
19
|
+
"""读当前开发者名 (容错: UTF-8/GBK)."""
|
|
20
|
+
df = os.path.join(BASE, '.qoder', '.developer')
|
|
21
|
+
if not os.path.isfile(df):
|
|
22
|
+
return None
|
|
23
|
+
for enc in ('utf-8', 'gbk', 'utf-8-sig'):
|
|
24
|
+
try:
|
|
25
|
+
with open(df, encoding=enc) as f:
|
|
26
|
+
for line in f:
|
|
27
|
+
line = line.strip()
|
|
28
|
+
if line.startswith('name='):
|
|
29
|
+
return line.split('=', 1)[1].strip()
|
|
30
|
+
if line.startswith('name:') or ':' in line:
|
|
31
|
+
# 容错 name: x 格式
|
|
32
|
+
for sep in ('=', ':'):
|
|
33
|
+
if sep in line:
|
|
34
|
+
k, v = line.split(sep, 1)
|
|
35
|
+
if k.strip() == 'name':
|
|
36
|
+
return v.strip()
|
|
37
|
+
except (OSError, UnicodeDecodeError):
|
|
38
|
+
continue
|
|
39
|
+
return None
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _cached_read(path, parser):
|
|
43
|
+
"""带 mtime 缓存的文件读。parser: callable(content_str) -> value."""
|
|
44
|
+
try:
|
|
45
|
+
mtime = os.path.getmtime(path)
|
|
46
|
+
except OSError:
|
|
47
|
+
return None
|
|
48
|
+
cached = _CACHE.get(path)
|
|
49
|
+
if cached and cached[0] == mtime:
|
|
50
|
+
return cached[1]
|
|
51
|
+
# 重读
|
|
52
|
+
for enc in ('utf-8', 'gbk', 'utf-8-sig'):
|
|
53
|
+
try:
|
|
54
|
+
with open(path, encoding=enc) as f:
|
|
55
|
+
content = f.read()
|
|
56
|
+
value = parser(content)
|
|
57
|
+
_CACHE[path] = (mtime, value)
|
|
58
|
+
return value
|
|
59
|
+
except (OSError, UnicodeDecodeError):
|
|
60
|
+
continue
|
|
61
|
+
return None
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def get_status():
|
|
65
|
+
# 优先读按开发者隔离的 current-task 文件
|
|
66
|
+
dev = _read_developer()
|
|
67
|
+
ct_candidates = []
|
|
68
|
+
if dev:
|
|
69
|
+
safe = ''.join(c for c in dev if c.isalnum() or c in '_-') or 'anon'
|
|
70
|
+
ct_candidates.append(os.path.join(BASE, '.qoder', '.runtime', 'current-task.' + safe))
|
|
71
|
+
ct_candidates.append(os.path.join(BASE, '.qoder', '.current-task')) # 旧格式回退
|
|
72
|
+
|
|
73
|
+
rel = None
|
|
74
|
+
for ct in ct_candidates:
|
|
75
|
+
rel = _cached_read(ct, lambda c: c.strip() or None)
|
|
76
|
+
if rel:
|
|
77
|
+
break
|
|
78
|
+
if not rel:
|
|
79
|
+
return None, None
|
|
80
|
+
|
|
81
|
+
tj = os.path.join(BASE, rel, 'task.json')
|
|
82
|
+
if os.path.isfile(tj):
|
|
83
|
+
d = _cached_read(tj, lambda c: json.loads(c) if c.strip() else None)
|
|
84
|
+
if d:
|
|
85
|
+
return rel, d.get('status', 'unknown')
|
|
86
|
+
return rel, None
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
# Statuses written by task.py: planning -> in_progress -> completed
|
|
90
|
+
GUIDE = {
|
|
91
|
+
None: 'No active task. Use /wl-prd to start, or /wl-task create.',
|
|
92
|
+
'planning': 'Task in planning. Use /wl-prd to write the PRD, then /wl-task start.',
|
|
93
|
+
'in_progress': 'Dev in progress. /wl-spec to generate spec, /wl-code to implement.',
|
|
94
|
+
'completed': 'Task completed. /wl-task archive to archive, or start the next one.',
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def main():
|
|
99
|
+
tp, st = get_status()
|
|
100
|
+
g = GUIDE.get(st, 'Status "{}" - check task.json, expected planning/in_progress/completed.'.format(st))
|
|
101
|
+
parts = []
|
|
102
|
+
parts.append('<qoder-workflow>')
|
|
103
|
+
if tp:
|
|
104
|
+
parts.append('Task: ' + tp)
|
|
105
|
+
parts.append('Status: ' + str(st))
|
|
106
|
+
parts.append('Do: ' + g)
|
|
107
|
+
parts.append('</qoder-workflow>')
|
|
108
|
+
print(NL.join(parts))
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
if __name__ == '__main__':
|
|
112
|
+
try:
|
|
113
|
+
main()
|
|
114
|
+
except Exception as e:
|
|
115
|
+
print('<qoder-workflow>')
|
|
116
|
+
print('hook error: ' + str(e)[:200])
|
|
117
|
+
print('</qoder-workflow>')
|