@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.
Files changed (152) hide show
  1. package/bin/cli.js +344 -78
  2. package/package.json +29 -29
  3. package/templates/.qoder/.runtime/ctx-cache-5660152f1d6dd819.md +23 -0
  4. package/templates/.qoder/.runtime/ctx-cache-afdce0dac06b25b0.md +23 -0
  5. package/templates/.qoder/.runtime/search-cache-eae7644e7b122f35.txt +1 -0
  6. package/templates/.qoder/learning/eval-history.jsonl +28 -0
  7. package/templates/data/index/wiki-index.json +8 -0
  8. package/templates/qoder/agents/insight-planning.md +1 -1
  9. package/templates/qoder/agents/insight-research.md +28 -15
  10. package/templates/qoder/commands/optional/wl-insight.md +159 -161
  11. package/templates/qoder/commands/optional/wl-report.md +4 -4
  12. package/templates/qoder/commands/optional/wl-status.md +2 -2
  13. package/templates/qoder/commands/wl-code.md +2 -2
  14. package/templates/qoder/commands/wl-design.md +2 -2
  15. package/templates/qoder/commands/wl-init.md +3 -3
  16. package/templates/qoder/commands/wl-prd.md +2 -2
  17. package/templates/qoder/commands/wl-req.md +43 -0
  18. package/templates/qoder/commands/wl-search.md +8 -8
  19. package/templates/qoder/commands/wl-task.md +17 -17
  20. package/templates/qoder/commands/wl-test.md +41 -15
  21. package/templates/qoder/config.yaml +17 -1
  22. package/templates/qoder/hooks/session-start.py +12 -22
  23. package/templates/qoder/nul +4 -0
  24. package/templates/qoder/rules/wl-pipeline.md +22 -48
  25. package/templates/qoder/scripts/README.md +139 -0
  26. package/templates/qoder/scripts/common/autotest_auth.py +109 -0
  27. package/templates/qoder/scripts/common/bootstrap.py +145 -0
  28. package/templates/qoder/scripts/common/check_publish.py +98 -0
  29. package/templates/qoder/scripts/common/cmd_registry.py +112 -0
  30. package/templates/qoder/scripts/common/config.py +187 -0
  31. package/templates/qoder/scripts/common/contract.py +317 -0
  32. package/templates/qoder/scripts/common/developer.py +2 -1
  33. package/templates/qoder/scripts/common/feishu.py +10 -9
  34. package/templates/qoder/scripts/common/guard.py +159 -0
  35. package/templates/qoder/scripts/common/identity.py +121 -2
  36. package/templates/qoder/scripts/common/kg_capabilities.py +182 -0
  37. package/templates/qoder/scripts/common/mcp_base.py +268 -0
  38. package/templates/qoder/scripts/common/paths.py +187 -1
  39. package/templates/qoder/scripts/common/result.py +223 -0
  40. package/templates/qoder/scripts/common/roles.py +60 -0
  41. package/templates/qoder/scripts/common/task_utils.py +21 -9
  42. package/templates/qoder/scripts/common/test_extract.py +115 -0
  43. package/templates/qoder/scripts/kg/__init__.py +11 -0
  44. package/templates/qoder/scripts/kg/build_entity_registry.py +196 -0
  45. package/templates/qoder/scripts/kg/build_relations.py +127 -0
  46. package/templates/qoder/scripts/{build_style_index.py → kg/build_style_index.py} +39 -10
  47. package/templates/qoder/scripts/kg/build_workflows.py +144 -0
  48. package/templates/qoder/scripts/{context_pack.py → kg/context_pack.py} +18 -11
  49. package/templates/qoder/scripts/{enrich_prompt.py → kg/enrich_prompt.py} +232 -226
  50. package/templates/qoder/scripts/{extract_api_params.py → kg/extract.py} +398 -246
  51. package/templates/qoder/scripts/{kg.py → kg/kg.py} +638 -708
  52. package/templates/qoder/scripts/{kg_build.py → kg/kg_build.py} +618 -612
  53. package/templates/qoder/scripts/{kg_build_db.py → kg/kg_build_db.py} +333 -327
  54. package/templates/qoder/scripts/{kg_duckdb.py → kg/kg_duckdb.py} +38 -37
  55. package/templates/qoder/scripts/{kg_incremental.py → kg/kg_incremental.py} +420 -393
  56. package/templates/qoder/scripts/{kg_link_db.py → kg/kg_link_db.py} +230 -224
  57. package/templates/qoder/scripts/{kg_semantic.py → kg/kg_semantic.py} +156 -150
  58. package/templates/qoder/scripts/kg/prefetch.py +359 -0
  59. package/templates/qoder/scripts/{search_index.py → kg/search_index.py} +70 -14
  60. package/templates/qoder/scripts/mcp/__init__.py +11 -0
  61. package/templates/qoder/scripts/{kg_mcp_server.py → mcp/kg_mcp_server.py} +77 -272
  62. package/templates/qoder/scripts/{lanhu_stdio_wrapper.py → mcp/lanhu_stdio_wrapper.py} +125 -119
  63. package/templates/qoder/scripts/{check_mcp.py → mcp/mcp_doctor.py} +515 -298
  64. package/templates/qoder/scripts/{mcp_launcher.py → mcp/mcp_launcher.py} +442 -414
  65. package/templates/qoder/scripts/{mysql_mcp_server.py → mcp/mysql_mcp_server.py} +347 -396
  66. package/templates/qoder/scripts/{zentao_mcp_server.py → mcp/zentao_mcp_server.py} +384 -424
  67. package/templates/qoder/scripts/report/__init__.py +11 -0
  68. package/templates/qoder/scripts/{add_session.py → report/add_session.py} +250 -244
  69. package/templates/qoder/scripts/{archive_prd.py → report/archive_prd.py} +383 -377
  70. package/templates/qoder/scripts/{eval_prd.py → report/eval_prd.py} +73 -11
  71. package/templates/qoder/scripts/{export.py → report/export.py} +63 -0
  72. package/templates/qoder/scripts/{fill_prototype.py → report/fill_prototype.py} +6 -0
  73. package/templates/qoder/scripts/{gen_design_doc.py → report/gen_design_doc.py} +400 -394
  74. package/templates/qoder/scripts/{learn.py → report/learn.py} +152 -146
  75. package/templates/qoder/scripts/{learn_aggregate.py → report/learn_aggregate.py} +207 -201
  76. package/templates/qoder/scripts/{report.py → report/report.py} +287 -281
  77. package/templates/qoder/scripts/report/req.py +222 -0
  78. package/templates/qoder/scripts/report/role.py +33 -0
  79. package/templates/qoder/scripts/{status.py → report/status.py} +634 -628
  80. package/templates/qoder/scripts/setup/__init__.py +11 -0
  81. package/templates/qoder/scripts/setup/carriers.py +662 -0
  82. package/templates/qoder/scripts/{init_doctor.py → setup/init_doctor.py} +63 -26
  83. package/templates/qoder/scripts/{install_qoderwork.py → setup/install_qoderwork.py} +36 -26
  84. package/templates/qoder/scripts/{platform_doctor.py → setup/platform_doctor.py} +265 -259
  85. package/templates/qoder/scripts/{repo_root.py → setup/repo_root.py} +112 -106
  86. package/templates/qoder/scripts/{setup.py → setup/setup.py} +113 -4
  87. package/templates/qoder/scripts/{setup_lanhu.py → setup/setup_lanhu.py} +973 -963
  88. package/templates/qoder/scripts/task/__init__.py +11 -0
  89. package/templates/qoder/scripts/{git_sync.py → task/git_sync.py} +52 -31
  90. package/templates/qoder/scripts/{syncgate.py → task/syncgate.py} +6 -0
  91. package/templates/qoder/scripts/task/task.py +221 -0
  92. package/templates/qoder/scripts/task/task_lifecycle.py +596 -0
  93. package/templates/qoder/scripts/task/task_query.py +161 -0
  94. package/templates/qoder/scripts/task/task_relations.py +424 -0
  95. package/templates/qoder/scripts/{team_sync.py → task/team_sync.py} +93 -20
  96. package/templates/qoder/scripts/test/__init__.py +11 -0
  97. package/templates/qoder/scripts/{autotest.py → test/autotest.py} +1174 -1751
  98. package/templates/qoder/scripts/{autotest_batch.py → test/autotest_batch.py} +242 -224
  99. package/templates/qoder/scripts/test/autotest_data.py +675 -0
  100. package/templates/qoder/scripts/{autotest_run.py → test/autotest_run.py} +309 -297
  101. package/templates/qoder/scripts/{benchmark.py → test/benchmark.py} +6 -0
  102. package/templates/qoder/scripts/{kg_auto_login.py → test/kg_auto_login.py} +202 -196
  103. package/templates/qoder/scripts/{kg_test_runner.py → test/kg_test_runner.py} +7 -1
  104. package/templates/qoder/scripts/{page_probe.py → test/page_probe.py} +465 -459
  105. package/templates/qoder/scripts/wlkj.py +116 -0
  106. package/templates/qoder/settings.json +1 -10
  107. package/templates/qoder/skills/design-import/SKILL.md +226 -226
  108. package/templates/qoder/skills/design-review/SKILL.md +82 -82
  109. package/templates/qoder/skills/prd-generator/SKILL.md +26 -16
  110. package/templates/qoder/skills/prd-review/SKILL.md +5 -5
  111. package/templates/qoder/skills/prototype-generator/SKILL.md +256 -256
  112. package/templates/qoder/skills/spec-coder/SKILL.md +4 -4
  113. package/templates/qoder/skills/spec-generator/SKILL.md +4 -4
  114. package/templates/qoder/skills/test-generator/SKILL.md +5 -5
  115. package/templates/qoder/skills/wl-code/SKILL.md +4 -4
  116. package/templates/qoder/skills/wl-commit/SKILL.md +4 -4
  117. package/templates/qoder/skills/wl-design/SKILL.md +3 -3
  118. package/templates/qoder/skills/wl-init/SKILL.md +8 -8
  119. package/templates/qoder/skills/wl-insight/SKILL.md +5 -5
  120. package/templates/qoder/skills/wl-prd-full/SKILL.md +6 -6
  121. package/templates/qoder/skills/wl-prd-quick/SKILL.md +6 -6
  122. package/templates/qoder/skills/wl-prd-review/SKILL.md +4 -4
  123. package/templates/qoder/skills/wl-report/SKILL.md +7 -7
  124. package/templates/qoder/skills/wl-search/SKILL.md +13 -13
  125. package/templates/qoder/skills/wl-spec/SKILL.md +5 -5
  126. package/templates/qoder/skills/wl-status/SKILL.md +5 -5
  127. package/templates/qoder/skills/wl-task/SKILL.md +6 -6
  128. package/templates/qoder/skills/wl-test/SKILL.md +102 -39
  129. package/templates/root/AGENTS.md +39 -40
  130. package/templates/qoder/hooks/inject-workflow-state.py +0 -169
  131. package/templates/qoder/scripts/__pycache__/check_mcp_launch.cpython-39.pyc +0 -0
  132. package/templates/qoder/scripts/__pycache__/install_qoderwork.cpython-39.pyc +0 -0
  133. package/templates/qoder/scripts/__pycache__/mcp_launcher.cpython-39.pyc +0 -0
  134. package/templates/qoder/scripts/__pycache__/platform_doctor.cpython-39.pyc +0 -0
  135. package/templates/qoder/scripts/check_carriers.py +0 -238
  136. package/templates/qoder/scripts/check_mcp_launch.py +0 -183
  137. package/templates/qoder/scripts/check_qoderwork_consistency.py +0 -166
  138. package/templates/qoder/scripts/collect_prds.py +0 -31
  139. package/templates/qoder/scripts/common/mentions.py +0 -134
  140. package/templates/qoder/scripts/common/utf8.py +0 -38
  141. package/templates/qoder/scripts/extract_routes.py +0 -54
  142. package/templates/qoder/scripts/extract_routes_tree.py +0 -78
  143. package/templates/qoder/scripts/handoff.py +0 -22
  144. package/templates/qoder/scripts/init_developer.py +0 -76
  145. package/templates/qoder/scripts/parse_prds.py +0 -33
  146. package/templates/qoder/scripts/role.py +0 -51
  147. package/templates/qoder/scripts/sync_carriers.py +0 -259
  148. package/templates/qoder/scripts/task.py +0 -1261
  149. package/templates/qoder/scripts/workspace_init.py +0 -102
  150. package/templates/qoder/skills/prompt-enrich/SKILL.md +0 -90
  151. package/templates/qoder/skills/prototype-generator/SKILL.md.zcode-79180-2af4721f-f9a6-412c-88db-c0af680d211b.tmp +0 -0
  152. /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 yaml
71
- with open(CONFIG_PATH, 'r', encoding='utf-8') as f:
72
- return yaml.safe_load(f) or {}
73
- except:
74
- return {}
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 = subprocess.run(
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
- subprocess.run(['git', 'fetch', 'origin'],
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 = subprocess.run(
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 = subprocess.run(
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 = subprocess.run(
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())