@hupan56/wlkj 2.7.12 → 3.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 +344 -78
- package/package.json +29 -29
- package/templates/.qoder/.runtime/ctx-cache-5660152f1d6dd819.md +23 -0
- package/templates/.qoder/.runtime/ctx-cache-afdce0dac06b25b0.md +23 -0
- package/templates/.qoder/.runtime/search-cache-eae7644e7b122f35.txt +1 -0
- package/templates/.qoder/learning/eval-history.jsonl +28 -0
- package/templates/data/index/wiki-index.json +8 -0
- package/templates/qoder/agents/insight-planning.md +1 -1
- package/templates/qoder/agents/insight-research.md +28 -15
- package/templates/qoder/commands/optional/wl-insight.md +159 -161
- package/templates/qoder/commands/optional/wl-report.md +4 -4
- package/templates/qoder/commands/optional/wl-status.md +2 -2
- package/templates/qoder/commands/wl-code.md +2 -2
- package/templates/qoder/commands/wl-design.md +2 -2
- package/templates/qoder/commands/wl-init.md +3 -3
- package/templates/qoder/commands/wl-prd.md +2 -2
- package/templates/qoder/commands/wl-req.md +43 -0
- package/templates/qoder/commands/wl-search.md +8 -8
- package/templates/qoder/commands/wl-task.md +17 -17
- package/templates/qoder/commands/wl-test.md +41 -15
- package/templates/qoder/config.yaml +17 -1
- package/templates/qoder/hooks/session-start.py +12 -22
- package/templates/qoder/nul +4 -0
- package/templates/qoder/rules/wl-pipeline.md +22 -48
- package/templates/qoder/scripts/README.md +139 -0
- package/templates/qoder/scripts/common/autotest_auth.py +109 -0
- package/templates/qoder/scripts/common/bootstrap.py +145 -0
- package/templates/qoder/scripts/common/check_publish.py +98 -0
- package/templates/qoder/scripts/common/cmd_registry.py +112 -0
- package/templates/qoder/scripts/common/config.py +187 -0
- package/templates/qoder/scripts/common/contract.py +317 -0
- package/templates/qoder/scripts/common/developer.py +2 -1
- package/templates/qoder/scripts/common/feishu.py +10 -9
- package/templates/qoder/scripts/common/guard.py +159 -0
- package/templates/qoder/scripts/common/identity.py +121 -2
- package/templates/qoder/scripts/common/kg_capabilities.py +182 -0
- package/templates/qoder/scripts/common/mcp_base.py +268 -0
- package/templates/qoder/scripts/common/paths.py +187 -1
- package/templates/qoder/scripts/common/result.py +223 -0
- package/templates/qoder/scripts/common/roles.py +60 -0
- package/templates/qoder/scripts/common/task_utils.py +21 -9
- package/templates/qoder/scripts/common/test_extract.py +115 -0
- package/templates/qoder/scripts/kg/__init__.py +11 -0
- package/templates/qoder/scripts/kg/build_entity_registry.py +196 -0
- package/templates/qoder/scripts/kg/build_relations.py +127 -0
- package/templates/qoder/scripts/{build_style_index.py → kg/build_style_index.py} +39 -10
- package/templates/qoder/scripts/kg/build_workflows.py +144 -0
- package/templates/qoder/scripts/{context_pack.py → kg/context_pack.py} +18 -11
- package/templates/qoder/scripts/{enrich_prompt.py → kg/enrich_prompt.py} +232 -226
- package/templates/qoder/scripts/{extract_api_params.py → kg/extract.py} +398 -246
- package/templates/qoder/scripts/{kg.py → kg/kg.py} +638 -708
- package/templates/qoder/scripts/{kg_build.py → kg/kg_build.py} +618 -612
- package/templates/qoder/scripts/{kg_build_db.py → kg/kg_build_db.py} +333 -327
- package/templates/qoder/scripts/{kg_duckdb.py → kg/kg_duckdb.py} +38 -37
- package/templates/qoder/scripts/{kg_incremental.py → kg/kg_incremental.py} +420 -393
- package/templates/qoder/scripts/{kg_link_db.py → kg/kg_link_db.py} +230 -224
- package/templates/qoder/scripts/{kg_semantic.py → kg/kg_semantic.py} +156 -150
- package/templates/qoder/scripts/kg/prefetch.py +359 -0
- package/templates/qoder/scripts/{search_index.py → kg/search_index.py} +70 -14
- package/templates/qoder/scripts/mcp/__init__.py +11 -0
- package/templates/qoder/scripts/{kg_mcp_server.py → mcp/kg_mcp_server.py} +77 -272
- package/templates/qoder/scripts/{lanhu_stdio_wrapper.py → mcp/lanhu_stdio_wrapper.py} +125 -119
- package/templates/qoder/scripts/{check_mcp.py → mcp/mcp_doctor.py} +515 -298
- package/templates/qoder/scripts/{mcp_launcher.py → mcp/mcp_launcher.py} +442 -414
- package/templates/qoder/scripts/{mysql_mcp_server.py → mcp/mysql_mcp_server.py} +347 -396
- package/templates/qoder/scripts/{zentao_mcp_server.py → mcp/zentao_mcp_server.py} +384 -424
- package/templates/qoder/scripts/report/__init__.py +11 -0
- package/templates/qoder/scripts/{add_session.py → report/add_session.py} +250 -244
- package/templates/qoder/scripts/{archive_prd.py → report/archive_prd.py} +383 -377
- package/templates/qoder/scripts/{eval_prd.py → report/eval_prd.py} +73 -11
- package/templates/qoder/scripts/{export.py → report/export.py} +63 -0
- package/templates/qoder/scripts/{fill_prototype.py → report/fill_prototype.py} +6 -0
- package/templates/qoder/scripts/{gen_design_doc.py → report/gen_design_doc.py} +400 -394
- package/templates/qoder/scripts/{learn.py → report/learn.py} +152 -146
- package/templates/qoder/scripts/{learn_aggregate.py → report/learn_aggregate.py} +207 -201
- package/templates/qoder/scripts/{report.py → report/report.py} +287 -281
- package/templates/qoder/scripts/report/req.py +222 -0
- package/templates/qoder/scripts/report/role.py +33 -0
- package/templates/qoder/scripts/{status.py → report/status.py} +634 -628
- package/templates/qoder/scripts/setup/__init__.py +11 -0
- package/templates/qoder/scripts/setup/carriers.py +662 -0
- package/templates/qoder/scripts/{init_doctor.py → setup/init_doctor.py} +63 -26
- package/templates/qoder/scripts/{install_qoderwork.py → setup/install_qoderwork.py} +36 -26
- package/templates/qoder/scripts/{platform_doctor.py → setup/platform_doctor.py} +265 -259
- package/templates/qoder/scripts/{repo_root.py → setup/repo_root.py} +112 -106
- package/templates/qoder/scripts/{setup.py → setup/setup.py} +113 -4
- package/templates/qoder/scripts/{setup_lanhu.py → setup/setup_lanhu.py} +973 -963
- package/templates/qoder/scripts/task/__init__.py +11 -0
- package/templates/qoder/scripts/{git_sync.py → task/git_sync.py} +52 -31
- package/templates/qoder/scripts/{syncgate.py → task/syncgate.py} +6 -0
- package/templates/qoder/scripts/task/task.py +221 -0
- package/templates/qoder/scripts/task/task_lifecycle.py +596 -0
- package/templates/qoder/scripts/task/task_query.py +161 -0
- package/templates/qoder/scripts/task/task_relations.py +424 -0
- package/templates/qoder/scripts/{team_sync.py → task/team_sync.py} +93 -20
- package/templates/qoder/scripts/test/__init__.py +11 -0
- package/templates/qoder/scripts/{autotest.py → test/autotest.py} +1174 -1751
- package/templates/qoder/scripts/{autotest_batch.py → test/autotest_batch.py} +242 -224
- package/templates/qoder/scripts/test/autotest_data.py +675 -0
- package/templates/qoder/scripts/{autotest_run.py → test/autotest_run.py} +309 -297
- package/templates/qoder/scripts/{benchmark.py → test/benchmark.py} +6 -0
- package/templates/qoder/scripts/{kg_auto_login.py → test/kg_auto_login.py} +202 -196
- package/templates/qoder/scripts/{kg_test_runner.py → test/kg_test_runner.py} +7 -1
- package/templates/qoder/scripts/{page_probe.py → test/page_probe.py} +465 -459
- package/templates/qoder/scripts/wlkj.py +116 -0
- package/templates/qoder/settings.json +1 -10
- package/templates/qoder/skills/design-import/SKILL.md +226 -226
- package/templates/qoder/skills/design-review/SKILL.md +82 -82
- package/templates/qoder/skills/prd-generator/SKILL.md +26 -16
- package/templates/qoder/skills/prd-review/SKILL.md +5 -5
- package/templates/qoder/skills/prototype-generator/SKILL.md +256 -256
- package/templates/qoder/skills/spec-coder/SKILL.md +4 -4
- package/templates/qoder/skills/spec-generator/SKILL.md +4 -4
- package/templates/qoder/skills/test-generator/SKILL.md +5 -5
- package/templates/qoder/skills/wl-code/SKILL.md +4 -4
- package/templates/qoder/skills/wl-commit/SKILL.md +4 -4
- package/templates/qoder/skills/wl-design/SKILL.md +3 -3
- package/templates/qoder/skills/wl-init/SKILL.md +8 -8
- package/templates/qoder/skills/wl-insight/SKILL.md +5 -5
- package/templates/qoder/skills/wl-prd-full/SKILL.md +6 -6
- package/templates/qoder/skills/wl-prd-quick/SKILL.md +6 -6
- package/templates/qoder/skills/wl-prd-review/SKILL.md +4 -4
- package/templates/qoder/skills/wl-report/SKILL.md +7 -7
- package/templates/qoder/skills/wl-search/SKILL.md +13 -13
- package/templates/qoder/skills/wl-spec/SKILL.md +5 -5
- package/templates/qoder/skills/wl-status/SKILL.md +5 -5
- package/templates/qoder/skills/wl-task/SKILL.md +6 -6
- package/templates/qoder/skills/wl-test/SKILL.md +102 -39
- package/templates/root/AGENTS.md +39 -40
- package/templates/qoder/hooks/inject-workflow-state.py +0 -169
- package/templates/qoder/scripts/__pycache__/check_mcp_launch.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/__pycache__/install_qoderwork.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/__pycache__/mcp_launcher.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/__pycache__/platform_doctor.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/check_carriers.py +0 -238
- package/templates/qoder/scripts/check_mcp_launch.py +0 -183
- package/templates/qoder/scripts/check_qoderwork_consistency.py +0 -166
- package/templates/qoder/scripts/collect_prds.py +0 -31
- package/templates/qoder/scripts/common/mentions.py +0 -134
- package/templates/qoder/scripts/common/utf8.py +0 -38
- package/templates/qoder/scripts/extract_routes.py +0 -54
- package/templates/qoder/scripts/extract_routes_tree.py +0 -78
- package/templates/qoder/scripts/handoff.py +0 -22
- package/templates/qoder/scripts/init_developer.py +0 -76
- package/templates/qoder/scripts/parse_prds.py +0 -33
- package/templates/qoder/scripts/role.py +0 -51
- package/templates/qoder/scripts/sync_carriers.py +0 -259
- package/templates/qoder/scripts/task.py +0 -1261
- package/templates/qoder/scripts/workspace_init.py +0 -102
- package/templates/qoder/skills/prompt-enrich/SKILL.md +0 -90
- package/templates/qoder/skills/prototype-generator/SKILL.md.zcode-79180-2af4721f-f9a6-412c-88db-c0af680d211b.tmp +0 -0
- /package/templates/qoder/scripts/{secure-ls.js → test/secure-ls.js} +0 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# 子包初始化: 注入 scripts/根 + 所有子包到 sys.path
|
|
2
|
+
# 让跨包 import (旧式不带前缀) 仍可用, 兼容现有代码
|
|
3
|
+
import os as _os, sys as _sys
|
|
4
|
+
_root = _os.path.dirname(_os.path.dirname(_os.path.abspath(__file__)))
|
|
5
|
+
if _root not in _sys.path:
|
|
6
|
+
_sys.path.insert(0, _root)
|
|
7
|
+
# 注入所有子包目录 (兼容旧式 import xxx)
|
|
8
|
+
for _sub in ('kg', 'build', 'test', 'setup', 'io'):
|
|
9
|
+
_subdir = _os.path.join(_root, _sub)
|
|
10
|
+
if _os.path.isdir(_subdir) and _subdir not in _sys.path:
|
|
11
|
+
_sys.path.insert(0, _subdir)
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
# -*- coding: utf-8 -*-
|
|
3
|
+
# v3.0 路径自举: 引导到 common/bootstrap, 统一 sys.path 逻辑
|
|
4
|
+
import os as _o, sys as _s
|
|
5
|
+
_cp = _o.path.join(_o.path.dirname(_o.path.dirname(_o.path.abspath(__file__))), 'common')
|
|
6
|
+
if _cp not in _s.path: _s.path.insert(0, _cp)
|
|
7
|
+
from bootstrap import setup; setup()
|
|
8
|
+
|
|
3
9
|
"""
|
|
4
10
|
Weekly Knowledge Graph Update
|
|
5
11
|
|
|
@@ -50,7 +56,7 @@ def load_json(path, default=None):
|
|
|
50
56
|
try:
|
|
51
57
|
with open(path, 'r', encoding='utf-8') as f:
|
|
52
58
|
return json.load(f)
|
|
53
|
-
except:
|
|
59
|
+
except Exception:
|
|
54
60
|
pass
|
|
55
61
|
return default or {}
|
|
56
62
|
|
|
@@ -63,15 +69,43 @@ def save_json(path, data):
|
|
|
63
69
|
|
|
64
70
|
|
|
65
71
|
def load_config():
|
|
66
|
-
"""Load config from config.yaml"""
|
|
67
|
-
if not os.path.exists(CONFIG_PATH):
|
|
68
|
-
return {}
|
|
72
|
+
"""Load config from config.yaml (v3.0: 转发到 Config 单例, 带缓存)。"""
|
|
69
73
|
try:
|
|
70
|
-
import
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
74
|
+
from common.config import Config
|
|
75
|
+
return Config.load().raw
|
|
76
|
+
except Exception:
|
|
77
|
+
# 回退: 直接读 (sys.path 未就绪时)
|
|
78
|
+
if not os.path.exists(CONFIG_PATH):
|
|
79
|
+
return {}
|
|
80
|
+
try:
|
|
81
|
+
import yaml
|
|
82
|
+
with open(CONFIG_PATH, 'r', encoding='utf-8') as f:
|
|
83
|
+
return yaml.safe_load(f) or {}
|
|
84
|
+
except Exception:
|
|
85
|
+
return {}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _git(*args, cwd=None, timeout=None):
|
|
89
|
+
"""git 命令包装(复刻 team_sync.git 风格)。
|
|
90
|
+
|
|
91
|
+
网络/远程操作 (fetch/pull/push/clone) 必须传 timeout —— 50 人团队周五 cron
|
|
92
|
+
跑时, 远程要认证但凭证没缓存 / 弱网 / 仓库不存在, 会让裸 subprocess.run 永久
|
|
93
|
+
阻塞, 整个周五同步挂死。本地操作 (rev-parse/diff/log) 可不传。
|
|
94
|
+
|
|
95
|
+
timeout 到期返回 rc=124 (与 timeout 命令一致), 不让 cron 无限阻塞。
|
|
96
|
+
git 未安装返回 rc=127, 不崩溃。
|
|
97
|
+
"""
|
|
98
|
+
try:
|
|
99
|
+
kw = dict(cwd=cwd, capture_output=True, text=True, encoding='utf-8', errors='replace')
|
|
100
|
+
if timeout:
|
|
101
|
+
kw['timeout'] = timeout
|
|
102
|
+
return subprocess.run(['git'] + list(args), **kw)
|
|
103
|
+
except FileNotFoundError:
|
|
104
|
+
import types
|
|
105
|
+
return types.SimpleNamespace(returncode=127, stdout='', stderr='git not installed')
|
|
106
|
+
except subprocess.TimeoutExpired:
|
|
107
|
+
import types
|
|
108
|
+
return types.SimpleNamespace(returncode=124, stdout='', stderr='git {} timed out after {}s'.format(' '.join(args[:3]), timeout))
|
|
75
109
|
|
|
76
110
|
|
|
77
111
|
# ============================================================
|
|
@@ -86,32 +120,22 @@ def git_pull_project(project_name):
|
|
|
86
120
|
return []
|
|
87
121
|
|
|
88
122
|
# Get current commit before pull
|
|
89
|
-
result =
|
|
90
|
-
['git', 'rev-parse', 'HEAD'],
|
|
91
|
-
cwd=project_dir, capture_output=True, text=True, encoding='utf-8'
|
|
92
|
-
)
|
|
123
|
+
result = _git('rev-parse', 'HEAD', cwd=project_dir)
|
|
93
124
|
old_commit = result.stdout.strip()[:8] if result.returncode == 0 else ''
|
|
94
125
|
|
|
95
|
-
# Fetch + pull
|
|
126
|
+
# Fetch + pull (网络操作必须带 timeout, 防弱网/认证弹窗挂死周五 cron)
|
|
96
127
|
print(f' {project_name}: Fetching...')
|
|
97
|
-
|
|
98
|
-
cwd=project_dir, capture_output=True)
|
|
128
|
+
_git('fetch', 'origin', cwd=project_dir, timeout=120)
|
|
99
129
|
|
|
100
130
|
print(f' {project_name}: Pulling...')
|
|
101
|
-
result =
|
|
102
|
-
['git', 'pull', 'origin'],
|
|
103
|
-
cwd=project_dir, capture_output=True, text=True, encoding='utf-8'
|
|
104
|
-
)
|
|
131
|
+
result = _git('pull', 'origin', cwd=project_dir, timeout=180)
|
|
105
132
|
|
|
106
133
|
if result.returncode != 0:
|
|
107
|
-
print(f' {project_name}: Pull failed - {result.stderr[:100]}')
|
|
134
|
+
print(f' {project_name}: Pull failed - {(result.stderr or "")[:100]}')
|
|
108
135
|
return []
|
|
109
136
|
|
|
110
137
|
# Get new commit
|
|
111
|
-
result =
|
|
112
|
-
['git', 'rev-parse', 'HEAD'],
|
|
113
|
-
cwd=project_dir, capture_output=True, text=True, encoding='utf-8'
|
|
114
|
-
)
|
|
138
|
+
result = _git('rev-parse', 'HEAD', cwd=project_dir)
|
|
115
139
|
new_commit = result.stdout.strip()[:8] if result.returncode == 0 else ''
|
|
116
140
|
|
|
117
141
|
if old_commit == new_commit:
|
|
@@ -119,10 +143,7 @@ def git_pull_project(project_name):
|
|
|
119
143
|
return []
|
|
120
144
|
|
|
121
145
|
# Get changed files
|
|
122
|
-
result =
|
|
123
|
-
['git', 'diff', '--name-only', old_commit, new_commit],
|
|
124
|
-
cwd=project_dir, capture_output=True, text=True, encoding='utf-8'
|
|
125
|
-
)
|
|
146
|
+
result = _git('diff', '--name-only', old_commit, new_commit, cwd=project_dir)
|
|
126
147
|
changed = [f.strip() for f in result.stdout.strip().split('\n') if f.strip()]
|
|
127
148
|
print(f' {project_name}: {old_commit} -> {new_commit}, {len(changed)} files changed')
|
|
128
149
|
return changed
|
|
@@ -383,7 +404,7 @@ def parse_java_file(filepath):
|
|
|
383
404
|
'keywords': re.findall('[A-Z]?[a-z]+', class_name or '') if class_name else []
|
|
384
405
|
}
|
|
385
406
|
entities.append(entity)
|
|
386
|
-
except:
|
|
407
|
+
except Exception:
|
|
387
408
|
pass
|
|
388
409
|
return entities
|
|
389
410
|
|
|
@@ -411,7 +432,7 @@ def parse_frontend_file(filepath):
|
|
|
411
432
|
'type': 'vue' if filepath.endswith('.vue') else 'js/ts'
|
|
412
433
|
}
|
|
413
434
|
entities.append(entity)
|
|
414
|
-
except:
|
|
435
|
+
except Exception:
|
|
415
436
|
pass
|
|
416
437
|
return entities
|
|
417
438
|
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
|
+
# v3.0 路径自举: 引导到 common/bootstrap, 统一 sys.path 逻辑
|
|
3
|
+
import os as _o, sys as _s
|
|
4
|
+
_cp = _o.path.join(_o.path.dirname(_o.path.dirname(_o.path.abspath(__file__))), 'common')
|
|
5
|
+
if _cp not in _s.path: _s.path.insert(0, _cp)
|
|
6
|
+
from bootstrap import setup; setup()
|
|
7
|
+
|
|
2
8
|
"""
|
|
3
9
|
syncgate.py - team_sync 推送前的零信任门禁
|
|
4
10
|
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
QODER Pipeline - 任务生命周期管理
|
|
5
|
+
|
|
6
|
+
完整的任务 CRUD 和生命周期管理:
|
|
7
|
+
- create: 创建任务 (含 task.json, prd.md 模板, implement.jsonl, check.jsonl)
|
|
8
|
+
- start: 开始任务 (设置活跃任务, 状态 planning -> in_progress)
|
|
9
|
+
- current: 查看当前活跃任务
|
|
10
|
+
- finish: 完成任务 (清除活跃任务)
|
|
11
|
+
- archive: 归档任务 (移动到 archive/ 目录)
|
|
12
|
+
- list: 列出活跃/归档任务
|
|
13
|
+
- show: 查看任务详情
|
|
14
|
+
- reassign: 改派任务负责人 (多角色协作, PM 建任务后转给开发)
|
|
15
|
+
- add-subtask / remove-subtask: 父子任务关联
|
|
16
|
+
- set-due / block / unblock / gantt: 排期与依赖
|
|
17
|
+
|
|
18
|
+
参考: Trellis 的 task.py 设计
|
|
19
|
+
|
|
20
|
+
Usage:
|
|
21
|
+
python task.py create "<title>" [--slug <name>] [--assignee <dev>] [--priority P0|P1|P2|P3]
|
|
22
|
+
python task.py start <name>
|
|
23
|
+
python task.py current [--source]
|
|
24
|
+
python task.py finish
|
|
25
|
+
python task.py archive <name>
|
|
26
|
+
python task.py reassign <name> <new_assignee>
|
|
27
|
+
python task.py list [--mine] [--status <s>]
|
|
28
|
+
python task.py add-subtask <parent> <child>
|
|
29
|
+
python task.py remove-subtask <parent> <child>
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
from __future__ import annotations
|
|
33
|
+
|
|
34
|
+
# v3.0 路径自举: 引导到 common/bootstrap, 统一 sys.path 逻辑
|
|
35
|
+
import os as _o, sys as _s
|
|
36
|
+
_cp = _o.path.join(_o.path.dirname(_o.path.dirname(_o.path.abspath(__file__))), 'common')
|
|
37
|
+
if _cp not in _s.path: _s.path.insert(0, _cp)
|
|
38
|
+
from bootstrap import setup; setup()
|
|
39
|
+
|
|
40
|
+
import argparse
|
|
41
|
+
import json
|
|
42
|
+
import os
|
|
43
|
+
import shutil
|
|
44
|
+
import sys
|
|
45
|
+
from datetime import datetime
|
|
46
|
+
from pathlib import Path
|
|
47
|
+
|
|
48
|
+
# 将 scripts 目录加入路径
|
|
49
|
+
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
50
|
+
|
|
51
|
+
from common.paths import (
|
|
52
|
+
DIR_TASKS,
|
|
53
|
+
DIR_ARCHIVE,
|
|
54
|
+
FILE_TASK_JSON,
|
|
55
|
+
get_repo_root,
|
|
56
|
+
get_developer,
|
|
57
|
+
get_tasks_dir,
|
|
58
|
+
)
|
|
59
|
+
from common.active_task import (
|
|
60
|
+
clear_active_task,
|
|
61
|
+
resolve_active_task,
|
|
62
|
+
resolve_context_key,
|
|
63
|
+
set_active_task,
|
|
64
|
+
)
|
|
65
|
+
from common.task_utils import (
|
|
66
|
+
resolve_task_dir,
|
|
67
|
+
run_task_hooks,
|
|
68
|
+
load_task_json,
|
|
69
|
+
write_task_json,
|
|
70
|
+
modify_task_json,
|
|
71
|
+
assert_can_modify_task,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
# =============================================================================
|
|
76
|
+
# 命令: create
|
|
77
|
+
# =============================================================================
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
# v3.0 f6: cmd_* 拆到三个模块
|
|
81
|
+
from task_lifecycle import cmd_create, cmd_start, cmd_current, cmd_finish, cmd_archive
|
|
82
|
+
from task_query import cmd_list, cmd_show
|
|
83
|
+
from task_relations import cmd_add_subtask, cmd_remove_subtask, cmd_set_due, cmd_reassign, cmd_block, cmd_unblock, cmd_gantt
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def main() -> int:
|
|
87
|
+
"""CLI 入口。v3.0: 用 cmd_registry 声明式注册 (消除 ~81 行 argparse 样板)。"""
|
|
88
|
+
try:
|
|
89
|
+
from common.cmd_registry import run_command
|
|
90
|
+
except ImportError:
|
|
91
|
+
# 回退: 内联 parser (common 不可用时)
|
|
92
|
+
return _main_legacy()
|
|
93
|
+
|
|
94
|
+
COMMANDS = [
|
|
95
|
+
('create', 'Create a new task', [
|
|
96
|
+
('title', {'help': 'Task title'}),
|
|
97
|
+
('--slug', {'help': 'URL-safe slug (auto-generated if omitted)'}),
|
|
98
|
+
('--assignee', {'help': 'Assignee (defaults to current developer)'}),
|
|
99
|
+
('--priority', {'choices': ['P0', 'P1', 'P2', 'P3'], 'default': 'P2'}),
|
|
100
|
+
], cmd_create),
|
|
101
|
+
('start', 'Start a task', [
|
|
102
|
+
('dir', {'help': 'Task directory or name'}),
|
|
103
|
+
], cmd_start),
|
|
104
|
+
('current', 'Show current task', [
|
|
105
|
+
('--source', {'action': 'store_true', 'help': 'Show source info'}),
|
|
106
|
+
], cmd_current),
|
|
107
|
+
('finish', 'Finish current task', [], cmd_finish),
|
|
108
|
+
('archive', 'Archive a task', [
|
|
109
|
+
('dir', {'help': 'Task directory or name'}),
|
|
110
|
+
], cmd_archive),
|
|
111
|
+
('list', 'List tasks', [
|
|
112
|
+
('--mine', {'action': 'store_true', 'help': 'Only my tasks (assignee or creator)'}),
|
|
113
|
+
('--status', {'help': 'Filter by status'}),
|
|
114
|
+
('--ready', {'action': 'store_true', 'help': '只显示可立即开始的 (无未完成依赖)'}),
|
|
115
|
+
('--blocked', {'action': 'store_true', 'help': '只显示被阻塞的任务'}),
|
|
116
|
+
], cmd_list),
|
|
117
|
+
('show', 'Show task details', [
|
|
118
|
+
('dir', {'help': 'Task directory or name'}),
|
|
119
|
+
], cmd_show),
|
|
120
|
+
('add-subtask', 'Add subtask', [
|
|
121
|
+
('parent', {'help': 'Parent task'}),
|
|
122
|
+
('child', {'help': 'Child task'}),
|
|
123
|
+
], cmd_add_subtask),
|
|
124
|
+
('remove-subtask', 'Remove subtask', [
|
|
125
|
+
('parent', {'help': 'Parent task'}),
|
|
126
|
+
('child', {'help': 'Child task'}),
|
|
127
|
+
], cmd_remove_subtask),
|
|
128
|
+
('set-due', 'Set due date (YYYY-MM-DD)', [
|
|
129
|
+
('task', {'help': 'Task name'}),
|
|
130
|
+
('due_date', {'help': 'Due date YYYY-MM-DD'}),
|
|
131
|
+
], cmd_set_due),
|
|
132
|
+
('reassign', 'Reassign task to another developer', [
|
|
133
|
+
('task', {'help': 'Task name'}),
|
|
134
|
+
('new_assignee', {'help': 'New assignee (developer name)'}),
|
|
135
|
+
], cmd_reassign),
|
|
136
|
+
('block', 'Mark task blocked by another', [
|
|
137
|
+
('task', {'help': 'Task that is blocked'}),
|
|
138
|
+
('blocker', {'help': 'Task that blocks it (must finish first)'}),
|
|
139
|
+
], cmd_block),
|
|
140
|
+
('unblock', 'Remove block relationship', [
|
|
141
|
+
('task', {'help': 'Task'}),
|
|
142
|
+
('blocker', {'help': 'Blocker task'}),
|
|
143
|
+
], cmd_unblock),
|
|
144
|
+
('gantt', 'Render markdown Gantt chart', [
|
|
145
|
+
('--weeks', {'type': int, 'default': 4, 'help': 'Number of weeks to show'}),
|
|
146
|
+
], cmd_gantt),
|
|
147
|
+
]
|
|
148
|
+
return run_command(COMMANDS, prog='task', description='QODER Task Management')
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _main_legacy() -> int:
|
|
152
|
+
"""回退: common.cmd_registry 不可用时的内联 argparse (与旧版行为一致)。"""
|
|
153
|
+
parser = argparse.ArgumentParser(description="QODER Task Management")
|
|
154
|
+
subparsers = parser.add_subparsers(dest="command", help="Available commands")
|
|
155
|
+
|
|
156
|
+
p_create = subparsers.add_parser("create", help="Create a new task")
|
|
157
|
+
p_create.add_argument("title", help="Task title")
|
|
158
|
+
p_create.add_argument("--slug", help="URL-safe slug (auto-generated if omitted)")
|
|
159
|
+
p_create.add_argument("--assignee", help="Assignee (defaults to current developer)")
|
|
160
|
+
p_create.add_argument("--priority", choices=["P0", "P1", "P2", "P3"], default="P2")
|
|
161
|
+
p_create.set_defaults(func=cmd_create)
|
|
162
|
+
|
|
163
|
+
p_start = subparsers.add_parser("start", help="Start a task")
|
|
164
|
+
p_start.add_argument("dir", help="Task directory or name")
|
|
165
|
+
p_start.set_defaults(func=cmd_start)
|
|
166
|
+
|
|
167
|
+
p_current = subparsers.add_parser("current", help="Show current task")
|
|
168
|
+
p_current.add_argument("--source", action="store_true", help="Show source info")
|
|
169
|
+
p_current.set_defaults(func=cmd_current)
|
|
170
|
+
|
|
171
|
+
p_finish = subparsers.add_parser("finish", help="Finish current task")
|
|
172
|
+
p_finish.set_defaults(func=cmd_finish)
|
|
173
|
+
|
|
174
|
+
p_archive = subparsers.add_parser("archive", help="Archive a task")
|
|
175
|
+
p_archive.add_argument("dir", help="Task directory or name")
|
|
176
|
+
p_archive.set_defaults(func=cmd_archive)
|
|
177
|
+
|
|
178
|
+
p_list = subparsers.add_parser("list", help="List tasks")
|
|
179
|
+
p_list.add_argument("--mine", action="store_true", help="Only my tasks")
|
|
180
|
+
p_list.add_argument("--status", help="Filter by status")
|
|
181
|
+
p_list.add_argument("--ready", action="store_true", help="可立即开始")
|
|
182
|
+
p_list.add_argument("--blocked", action="store_true", help="被阻塞")
|
|
183
|
+
p_list.set_defaults(func=cmd_list)
|
|
184
|
+
|
|
185
|
+
p_show = subparsers.add_parser("show", help="Show task details")
|
|
186
|
+
p_show.add_argument("dir", help="Task directory or name")
|
|
187
|
+
p_show.set_defaults(func=cmd_show)
|
|
188
|
+
|
|
189
|
+
for cmd, fn in [
|
|
190
|
+
("add-subtask", cmd_add_subtask), ("remove-subtask", cmd_remove_subtask),
|
|
191
|
+
]:
|
|
192
|
+
p = subparsers.add_parser(cmd)
|
|
193
|
+
p.add_argument("parent"); p.add_argument("child")
|
|
194
|
+
p.set_defaults(func=fn)
|
|
195
|
+
|
|
196
|
+
p_due = subparsers.add_parser("set-due")
|
|
197
|
+
p_due.add_argument("task"); p_due.add_argument("due_date")
|
|
198
|
+
p_due.set_defaults(func=cmd_set_due)
|
|
199
|
+
|
|
200
|
+
p_reassign = subparsers.add_parser("reassign")
|
|
201
|
+
p_reassign.add_argument("task"); p_reassign.add_argument("new_assignee")
|
|
202
|
+
p_reassign.set_defaults(func=cmd_reassign)
|
|
203
|
+
|
|
204
|
+
for cmd, fn in [("block", cmd_block), ("unblock", cmd_unblock)]:
|
|
205
|
+
p = subparsers.add_parser(cmd)
|
|
206
|
+
p.add_argument("task"); p.add_argument("blocker")
|
|
207
|
+
p.set_defaults(func=fn)
|
|
208
|
+
|
|
209
|
+
p_gantt = subparsers.add_parser("gantt")
|
|
210
|
+
p_gantt.add_argument("--weeks", type=int, default=4)
|
|
211
|
+
p_gantt.set_defaults(func=cmd_gantt)
|
|
212
|
+
|
|
213
|
+
args = parser.parse_args()
|
|
214
|
+
if not hasattr(args, "func"):
|
|
215
|
+
parser.print_help()
|
|
216
|
+
return 1
|
|
217
|
+
return args.func(args)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
if __name__ == "__main__":
|
|
221
|
+
sys.exit(main())
|