@michaelxwb/code-flow 0.8.1 → 1.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 (268) hide show
  1. package/README.md +73 -152
  2. package/bin/code-flow.js +12 -0
  3. package/docs/README.md +19 -0
  4. package/docs/USAGE.md +156 -1417
  5. package/docs/v1-design.md +1858 -0
  6. package/docs/v1-implementation.md +186 -0
  7. package/lib/adapters.js +126 -0
  8. package/lib/cli.js +258 -0
  9. package/lib/config.js +85 -0
  10. package/lib/doctor.js +283 -0
  11. package/lib/frontmatter.js +42 -0
  12. package/lib/fs.js +141 -0
  13. package/lib/glob.js +23 -0
  14. package/lib/hook.js +99 -0
  15. package/lib/init.js +132 -0
  16. package/lib/learn.js +170 -0
  17. package/lib/locks.js +52 -0
  18. package/lib/markdown.js +13 -0
  19. package/lib/process.js +116 -0
  20. package/lib/render.js +115 -0
  21. package/lib/repo.js +48 -0
  22. package/lib/runs.js +155 -0
  23. package/lib/session.js +40 -0
  24. package/lib/spec.js +80 -0
  25. package/lib/task.js +258 -0
  26. package/lib/taskdoc.js +308 -0
  27. package/lib/toml.js +140 -0
  28. package/lib/verify.js +168 -0
  29. package/lib/worktree.js +930 -0
  30. package/package.json +18 -21
  31. package/templates/adapters/opencode.mjs +65 -0
  32. package/templates/config.json +39 -0
  33. package/templates/instructions.md +13 -0
  34. package/templates/launcher/bridge.cjs +295 -0
  35. package/templates/launcher/hook.cmd +15 -0
  36. package/templates/launcher/hook.ps1 +10 -0
  37. package/templates/launcher/hook.sh +20 -0
  38. package/templates/skills/cf-check.md +19 -0
  39. package/templates/skills/cf-init.md +16 -0
  40. package/templates/skills/cf-spec.md +20 -0
  41. package/templates/skills/cf-task-align.md +19 -0
  42. package/templates/skills/cf-task-archive.md +20 -0
  43. package/templates/skills/cf-task-plan.md +31 -0
  44. package/templates/skills/cf-task-prd.md +14 -0
  45. package/templates/skills/cf-task-start.md +41 -0
  46. package/{src/core/code-flow/specs/shared/design → templates/workflow}/design-frontend.md +12 -12
  47. package/{src/core/code-flow/specs/shared/design → templates/workflow}/design-full.md +17 -19
  48. package/{src/core/code-flow/specs/shared/design → templates/workflow}/design-lite.md +12 -12
  49. package/{src/core/code-flow/specs/shared/prd-template.md → templates/workflow/prd.md} +1 -1
  50. package/templates/workflow/project-guidelines.md +48 -0
  51. package/templates/workflow/scenarios.md +67 -0
  52. package/templates/workflow/task.md +56 -0
  53. package/docs/acceptance-contract.md +0 -217
  54. package/docs/ci/acceptance-evidence.md +0 -77
  55. package/docs/migrations/runtime-0.7.0.md +0 -41
  56. package/docs/migrations/runtime-0.8.0.md +0 -154
  57. package/docs/migrations/runtime-0.9.0.md +0 -40
  58. package/docs/migrations/spec-workflow-0.6.0.md +0 -42
  59. package/docs/spec-injection-hardening.md +0 -54
  60. package/docs/spec-injection-performance.json +0 -73
  61. package/docs/spec-optimization.md +0 -12
  62. package/src/adapters/claude/CLAUDE.md +0 -45
  63. package/src/adapters/claude/commands/cf-init.md +0 -262
  64. package/src/adapters/claude/commands/cf-learn.md +0 -291
  65. package/src/adapters/claude/commands/cf-spec.md +0 -87
  66. package/src/adapters/claude/commands/cf-stats.md +0 -96
  67. package/src/adapters/claude/commands/cf-sync.md +0 -39
  68. package/src/adapters/claude/commands/cf-task/align.md +0 -331
  69. package/src/adapters/claude/commands/cf-task/archive.md +0 -155
  70. package/src/adapters/claude/commands/cf-task/block.md +0 -49
  71. package/src/adapters/claude/commands/cf-task/graph.md +0 -92
  72. package/src/adapters/claude/commands/cf-task/note.md +0 -248
  73. package/src/adapters/claude/commands/cf-task/plan.md +0 -477
  74. package/src/adapters/claude/commands/cf-task/prd.md +0 -173
  75. package/src/adapters/claude/commands/cf-task/start.md +0 -317
  76. package/src/adapters/claude/commands/cf-task/status.md +0 -71
  77. package/src/adapters/claude/commands/cf-task/verify-e2e.md +0 -148
  78. package/src/adapters/claude/commands/cf-validate.md +0 -100
  79. package/src/adapters/claude/settings.local.json +0 -50
  80. package/src/adapters/codex/AGENTS.md +0 -45
  81. package/src/adapters/codex/config.toml +0 -3
  82. package/src/adapters/codex/hooks.json +0 -54
  83. package/src/adapters/codex/skills/cf-init/SKILL.md +0 -273
  84. package/src/adapters/codex/skills/cf-learn/SKILL.md +0 -296
  85. package/src/adapters/codex/skills/cf-spec/SKILL.md +0 -88
  86. package/src/adapters/codex/skills/cf-stats/SKILL.md +0 -97
  87. package/src/adapters/codex/skills/cf-sync/SKILL.md +0 -45
  88. package/src/adapters/codex/skills/cf-task-align/SKILL.md +0 -332
  89. package/src/adapters/codex/skills/cf-task-archive/SKILL.md +0 -156
  90. package/src/adapters/codex/skills/cf-task-block/SKILL.md +0 -50
  91. package/src/adapters/codex/skills/cf-task-graph/SKILL.md +0 -93
  92. package/src/adapters/codex/skills/cf-task-note/SKILL.md +0 -245
  93. package/src/adapters/codex/skills/cf-task-plan/SKILL.md +0 -478
  94. package/src/adapters/codex/skills/cf-task-prd/SKILL.md +0 -174
  95. package/src/adapters/codex/skills/cf-task-start/SKILL.md +0 -318
  96. package/src/adapters/codex/skills/cf-task-status/SKILL.md +0 -72
  97. package/src/adapters/codex/skills/cf-task-verify-e2e/SKILL.md +0 -145
  98. package/src/adapters/codex/skills/cf-validate/SKILL.md +0 -102
  99. package/src/adapters/commands.json +0 -115
  100. package/src/adapters/costrict/CLAUDE.md +0 -45
  101. package/src/adapters/costrict/commands/cf-init.md +0 -262
  102. package/src/adapters/costrict/commands/cf-learn.md +0 -291
  103. package/src/adapters/costrict/commands/cf-spec.md +0 -87
  104. package/src/adapters/costrict/commands/cf-stats.md +0 -96
  105. package/src/adapters/costrict/commands/cf-sync.md +0 -39
  106. package/src/adapters/costrict/commands/cf-task/align.md +0 -331
  107. package/src/adapters/costrict/commands/cf-task/archive.md +0 -155
  108. package/src/adapters/costrict/commands/cf-task/block.md +0 -49
  109. package/src/adapters/costrict/commands/cf-task/graph.md +0 -92
  110. package/src/adapters/costrict/commands/cf-task/note.md +0 -248
  111. package/src/adapters/costrict/commands/cf-task/plan.md +0 -477
  112. package/src/adapters/costrict/commands/cf-task/prd.md +0 -173
  113. package/src/adapters/costrict/commands/cf-task/start.md +0 -317
  114. package/src/adapters/costrict/commands/cf-task/status.md +0 -71
  115. package/src/adapters/costrict/commands/cf-task/verify-e2e.md +0 -148
  116. package/src/adapters/costrict/commands/cf-validate.md +0 -100
  117. package/src/adapters/costrict/settings.local.json +0 -50
  118. package/src/adapters/install-plan.json +0 -100
  119. package/src/adapters/opencode/AGENTS.md +0 -45
  120. package/src/adapters/opencode/commands/cf-init.md +0 -281
  121. package/src/adapters/opencode/commands/cf-learn.md +0 -295
  122. package/src/adapters/opencode/commands/cf-spec.md +0 -91
  123. package/src/adapters/opencode/commands/cf-stats.md +0 -100
  124. package/src/adapters/opencode/commands/cf-sync.md +0 -44
  125. package/src/adapters/opencode/commands/cf-task/align.md +0 -331
  126. package/src/adapters/opencode/commands/cf-task/archive.md +0 -160
  127. package/src/adapters/opencode/commands/cf-task/block.md +0 -54
  128. package/src/adapters/opencode/commands/cf-task/graph.md +0 -97
  129. package/src/adapters/opencode/commands/cf-task/note.md +0 -253
  130. package/src/adapters/opencode/commands/cf-task/plan.md +0 -477
  131. package/src/adapters/opencode/commands/cf-task/prd.md +0 -178
  132. package/src/adapters/opencode/commands/cf-task/start.md +0 -317
  133. package/src/adapters/opencode/commands/cf-task/status.md +0 -76
  134. package/src/adapters/opencode/commands/cf-task/verify-e2e.md +0 -148
  135. package/src/adapters/opencode/commands/cf-validate.md +0 -105
  136. package/src/adapters/opencode/opencode.json +0 -3
  137. package/src/adapters/opencode/plugins/code-flow/index.js +0 -162
  138. package/src/adapters/opencode/plugins/code-flow/package.json +0 -6
  139. package/src/adapters/platform-bindings.json +0 -370
  140. package/src/cli.js +0 -1076
  141. package/src/core/code-flow/config.yml +0 -161
  142. package/src/core/code-flow/runtime-commands.json +0 -259
  143. package/src/core/code-flow/scripts/cf_acceptance_manifest.py +0 -10
  144. package/src/core/code-flow/scripts/cf_acceptance_runner.py +0 -10
  145. package/src/core/code-flow/scripts/cf_audit.py +0 -10
  146. package/src/core/code-flow/scripts/cf_codex_hook.py +0 -10
  147. package/src/core/code-flow/scripts/cf_design_lint.py +0 -10
  148. package/src/core/code-flow/scripts/cf_evidence_import.py +0 -10
  149. package/src/core/code-flow/scripts/cf_feedback.py +0 -10
  150. package/src/core/code-flow/scripts/cf_opencode_event.py +0 -10
  151. package/src/core/code-flow/scripts/cf_post_hook.py +0 -11
  152. package/src/core/code-flow/scripts/cf_pre_tool_hook.py +0 -11
  153. package/src/core/code-flow/scripts/cf_scenarios.py +0 -10
  154. package/src/core/code-flow/scripts/cf_spec_context.py +0 -10
  155. package/src/core/code-flow/scripts/cf_spec_gate.py +0 -10
  156. package/src/core/code-flow/scripts/cf_spec_session.py +0 -10
  157. package/src/core/code-flow/scripts/cf_stats.py +0 -10
  158. package/src/core/code-flow/scripts/cf_stop_hook.py +0 -11
  159. package/src/core/code-flow/scripts/cf_sync.py +0 -10
  160. package/src/core/code-flow/scripts/cf_task_index.py +0 -10
  161. package/src/core/code-flow/scripts/cf_task_parallel.py +0 -10
  162. package/src/core/code-flow/scripts/cf_task_workflow.py +0 -10
  163. package/src/core/code-flow/scripts/cf_user_prompt_hook.py +0 -11
  164. package/src/core/code-flow/scripts/cf_validation.py +0 -10
  165. package/src/core/code-flow/scripts/codeflow/__init__.py +0 -0
  166. package/src/core/code-flow/scripts/codeflow/acceptance/__init__.py +0 -0
  167. package/src/core/code-flow/scripts/codeflow/acceptance/cf_acceptance_evidence.py +0 -347
  168. package/src/core/code-flow/scripts/codeflow/acceptance/cf_acceptance_manifest_core.py +0 -660
  169. package/src/core/code-flow/scripts/codeflow/acceptance/cf_acceptance_plan.py +0 -202
  170. package/src/core/code-flow/scripts/codeflow/acceptance/cf_acceptance_registration.py +0 -60
  171. package/src/core/code-flow/scripts/codeflow/acceptance/cf_acceptance_risk.py +0 -73
  172. package/src/core/code-flow/scripts/codeflow/acceptance/cf_acceptance_schema.py +0 -282
  173. package/src/core/code-flow/scripts/codeflow/acceptance/cf_acceptance_selection.py +0 -266
  174. package/src/core/code-flow/scripts/codeflow/acceptance/cf_check_inputs.py +0 -172
  175. package/src/core/code-flow/scripts/codeflow/acceptance/cf_check_registration.py +0 -326
  176. package/src/core/code-flow/scripts/codeflow/acceptance/cf_checks.py +0 -333
  177. package/src/core/code-flow/scripts/codeflow/acceptance/cf_plan_quality.py +0 -197
  178. package/src/core/code-flow/scripts/codeflow/acceptance/cf_scenario_quality.py +0 -174
  179. package/src/core/code-flow/scripts/codeflow/acceptance/cf_scenario_source.py +0 -300
  180. package/src/core/code-flow/scripts/codeflow/acceptance/cf_traceability.py +0 -502
  181. package/src/core/code-flow/scripts/codeflow/cli/__init__.py +0 -0
  182. package/src/core/code-flow/scripts/codeflow/cli/cf_acceptance_manifest.py +0 -347
  183. package/src/core/code-flow/scripts/codeflow/cli/cf_acceptance_runner.py +0 -1490
  184. package/src/core/code-flow/scripts/codeflow/cli/cf_audit.py +0 -533
  185. package/src/core/code-flow/scripts/codeflow/cli/cf_design_lint.py +0 -172
  186. package/src/core/code-flow/scripts/codeflow/cli/cf_evidence_import.py +0 -607
  187. package/src/core/code-flow/scripts/codeflow/cli/cf_feedback.py +0 -53
  188. package/src/core/code-flow/scripts/codeflow/cli/cf_scenarios.py +0 -58
  189. package/src/core/code-flow/scripts/codeflow/cli/cf_spec_context.py +0 -1175
  190. package/src/core/code-flow/scripts/codeflow/cli/cf_spec_gate.py +0 -60
  191. package/src/core/code-flow/scripts/codeflow/cli/cf_spec_session.py +0 -46
  192. package/src/core/code-flow/scripts/codeflow/cli/cf_stats.py +0 -540
  193. package/src/core/code-flow/scripts/codeflow/cli/cf_sync.py +0 -144
  194. package/src/core/code-flow/scripts/codeflow/cli/cf_task_index.py +0 -177
  195. package/src/core/code-flow/scripts/codeflow/cli/cf_task_parallel.py +0 -1142
  196. package/src/core/code-flow/scripts/codeflow/cli/cf_task_workflow.py +0 -742
  197. package/src/core/code-flow/scripts/codeflow/cli/cf_validation.py +0 -31
  198. package/src/core/code-flow/scripts/codeflow/core/__init__.py +0 -0
  199. package/src/core/code-flow/scripts/codeflow/core/cf_context_cache.py +0 -67
  200. package/src/core/code-flow/scripts/codeflow/core/cf_core.py +0 -609
  201. package/src/core/code-flow/scripts/codeflow/core/cf_exec_base.py +0 -325
  202. package/src/core/code-flow/scripts/codeflow/core/cf_fs.py +0 -57
  203. package/src/core/code-flow/scripts/codeflow/core/cf_git.py +0 -92
  204. package/src/core/code-flow/scripts/codeflow/core/cf_log.py +0 -160
  205. package/src/core/code-flow/scripts/codeflow/core/cf_markdown.py +0 -77
  206. package/src/core/code-flow/scripts/codeflow/core/cf_native_paths.py +0 -26
  207. package/src/core/code-flow/scripts/codeflow/core/cf_patch.py +0 -33
  208. package/src/core/code-flow/scripts/codeflow/core/cf_runtime_install.py +0 -32
  209. package/src/core/code-flow/scripts/codeflow/core/cf_session_state.py +0 -37
  210. package/src/core/code-flow/scripts/codeflow/core/cf_stop_guard.py +0 -53
  211. package/src/core/code-flow/scripts/codeflow/core/cf_task_state.py +0 -9
  212. package/src/core/code-flow/scripts/codeflow/core/cf_validators.py +0 -408
  213. package/src/core/code-flow/scripts/codeflow/core/cf_worktree_env.py +0 -253
  214. package/src/core/code-flow/scripts/codeflow/evidence/__init__.py +0 -0
  215. package/src/core/code-flow/scripts/codeflow/evidence/cf_evidence_store.py +0 -527
  216. package/src/core/code-flow/scripts/codeflow/hooks/__init__.py +0 -0
  217. package/src/core/code-flow/scripts/codeflow/hooks/cf_codex_hook.py +0 -95
  218. package/src/core/code-flow/scripts/codeflow/hooks/cf_opencode_event.py +0 -98
  219. package/src/core/code-flow/scripts/codeflow/hooks/cf_post_hook.py +0 -30
  220. package/src/core/code-flow/scripts/codeflow/hooks/cf_pre_tool_hook.py +0 -41
  221. package/src/core/code-flow/scripts/codeflow/hooks/cf_stop_hook.py +0 -292
  222. package/src/core/code-flow/scripts/codeflow/hooks/cf_user_prompt_hook.py +0 -161
  223. package/src/core/code-flow/scripts/codeflow/spec/__init__.py +0 -0
  224. package/src/core/code-flow/scripts/codeflow/spec/cf_active_task.py +0 -412
  225. package/src/core/code-flow/scripts/codeflow/spec/cf_context_refresh.py +0 -301
  226. package/src/core/code-flow/scripts/codeflow/spec/cf_edit_service.py +0 -149
  227. package/src/core/code-flow/scripts/codeflow/spec/cf_injection.py +0 -111
  228. package/src/core/code-flow/scripts/codeflow/spec/cf_rule_readiness.py +0 -114
  229. package/src/core/code-flow/scripts/codeflow/spec/cf_scan.py +0 -367
  230. package/src/core/code-flow/scripts/codeflow/spec/cf_spec_budget.py +0 -55
  231. package/src/core/code-flow/scripts/codeflow/spec/cf_spec_context_model.py +0 -477
  232. package/src/core/code-flow/scripts/codeflow/spec/cf_spec_impact.py +0 -119
  233. package/src/core/code-flow/scripts/codeflow/spec/cf_spec_metadata.py +0 -387
  234. package/src/core/code-flow/scripts/codeflow/spec/cf_spec_migrate.py +0 -529
  235. package/src/core/code-flow/scripts/codeflow/spec/cf_spec_optimize.py +0 -193
  236. package/src/core/code-flow/scripts/codeflow/spec/cf_spec_optimize_apply.py +0 -187
  237. package/src/core/code-flow/scripts/codeflow/spec/cf_spec_resolver.py +0 -369
  238. package/src/core/code-flow/scripts/codeflow/spec/cf_spec_router.py +0 -304
  239. package/src/core/code-flow/scripts/codeflow/spec/cf_spec_verify.py +0 -687
  240. package/src/core/code-flow/scripts/codeflow/spec/cf_stage_gate.py +0 -330
  241. package/src/core/code-flow/scripts/codeflow/spec/cf_task_projection.py +0 -83
  242. package/src/core/code-flow/scripts/codeflow/spec/cf_workflow_service.py +0 -755
  243. package/src/core/code-flow/scripts/codeflow/spec/cf_workflow_transaction.py +0 -110
  244. package/src/core/code-flow/scripts/codeflow/task/__init__.py +0 -0
  245. package/src/core/code-flow/scripts/codeflow/task/cf_requirement_verification.py +0 -867
  246. package/src/core/code-flow/scripts/codeflow/task/cf_task_doc.py +0 -178
  247. package/src/core/code-flow/scripts/codeflow/task/cf_task_merge.py +0 -635
  248. package/src/core/code-flow/scripts/codeflow/task/cf_task_plan.py +0 -251
  249. package/src/core/code-flow/scripts/codeflow/task/cf_task_runtime.py +0 -570
  250. package/src/core/code-flow/scripts/codeflow/task/cf_validation_service.py +0 -73
  251. package/src/core/code-flow/scripts/runtime-resources.json +0 -29
  252. package/src/core/code-flow/specs/backend/_map.md +0 -56
  253. package/src/core/code-flow/specs/backend/code-quality-performance.md +0 -59
  254. package/src/core/code-flow/specs/backend/database.md +0 -51
  255. package/src/core/code-flow/specs/backend/directory-structure.md +0 -54
  256. package/src/core/code-flow/specs/backend/logging.md +0 -50
  257. package/src/core/code-flow/specs/backend/platform-rules.md +0 -55
  258. package/src/core/code-flow/specs/frontend/_map.md +0 -52
  259. package/src/core/code-flow/specs/frontend/component-specs.md +0 -68
  260. package/src/core/code-flow/specs/frontend/directory-structure.md +0 -53
  261. package/src/core/code-flow/specs/frontend/quality-standards.md +0 -58
  262. package/src/core/code-flow/specs/shared/_map.md +0 -57
  263. package/src/core/code-flow/specs/shared/scenarios-template.md +0 -55
  264. package/src/core/code-flow/tasks/.gitkeep +0 -0
  265. package/src/core/code-flow/validation.yml +0 -59
  266. package/src/migrate/runtime.js +0 -213
  267. package/src/migrate/spec-workflow.js +0 -253
  268. package/src/runtime.js +0 -219
@@ -1,251 +0,0 @@
1
- #!/usr/bin/env python3
2
- """能力级 plan 预算评估:解析 TASK 的 Estimate/Capability,输出合并门禁。
3
-
4
- 默认 `capability` 粒度要求每个任务 1-2 天、可独立演示;任务数超过 25 或
5
- 平均任务时长低于 2h 时返回 `requires_merge=true`,调用方必须先合并再进入
6
- start。`file` 粒度保留旧版 1-3 文件 / 15-60 分钟拆分方式,仅受任务数约束。
7
- """
8
-
9
- from __future__ import annotations
10
-
11
- import argparse
12
- import json
13
- import os
14
- import re
15
- import sys
16
- from dataclasses import dataclass
17
- from pathlib import Path
18
- from typing import IO, Optional, Sequence
19
-
20
- from codeflow.task.cf_task_doc import TaskDocumentError, parse_task_document
21
-
22
-
23
- HOURS_PER_DAY = 8.0
24
- DEFAULT_MAX_TASKS = 25
25
- DEFAULT_MIN_AVERAGE_HOURS = 2.0
26
- TASK_SIZES = ("capability", "file")
27
-
28
- _ESTIMATE_RE = re.compile(
29
- r"(?i)(\d+(?:\.\d+)?)\s*(?:[-–]\s*(\d+(?:\.\d+)?))?\s*"
30
- r"(days?|day|d|hrs?|hours?|hr|h|分钟|minutes?|mins?|m|小时|天)\b"
31
- )
32
-
33
-
34
- @dataclass(frozen=True)
35
- class PlanTask:
36
- task_id: str
37
- title: str
38
- status: str
39
- estimate_raw: str
40
- hours: Optional[float]
41
- capability: str
42
- scenario_count: int
43
-
44
-
45
- def parse_estimate_hours(text: str) -> Optional[float]:
46
- """把 `1.5d` / `4h` / `90m` / `2-3d` 转成小时;范围取上限,无法解析返回 None。"""
47
- match = _ESTIMATE_RE.search(text or "")
48
- if match is None:
49
- return None
50
- value = float(match.group(2) or match.group(1))
51
- unit = match.group(3).lower()
52
- if unit in ("d", "day", "days", "天"):
53
- return round(value * HOURS_PER_DAY, 2)
54
- if unit in ("h", "hr", "hrs", "hour", "hours", "小时"):
55
- return round(value, 2)
56
- return round(value / 60.0, 2)
57
-
58
-
59
- def _field(section: str, name: str) -> str:
60
- match = re.search(rf"(?m)^- \*\*{name}\*\*:[ \t]*(.*)$", section)
61
- return match.group(1).strip() if match else ""
62
-
63
-
64
- def _section_body(section: str, heading: str) -> str:
65
- match = re.search(rf"(?ms)^### {re.escape(heading)}\s*$\n(.*?)(?=^### |^## |\Z)", section)
66
- return match.group(1).strip() if match else ""
67
-
68
-
69
- def _capability(section: str) -> str:
70
- explicit = _field(section, "Capability") or _field(section, "Demo")
71
- if explicit:
72
- return explicit
73
- for line in _section_body(section, "Description").splitlines():
74
- stripped = line.strip().lstrip("-* ").strip()
75
- if stripped:
76
- return stripped
77
- return ""
78
-
79
-
80
- def _scenario_count(section: str) -> int:
81
- refs = _field(section, "Acceptance-Refs")
82
- ids = set(re.findall(r"\b(?:S|E|B|RULE)-\d+\b", refs))
83
- if ids:
84
- return len(ids)
85
- contract = _section_body(section, "Acceptance Contract")
86
- rows = re.findall(r"(?m)^\|[ \t]*((?:S|E|B|RULE)-\d+)[ \t]*\|", contract)
87
- return len(set(rows))
88
-
89
-
90
- def parse_plan_tasks(task_file: str) -> tuple[PlanTask, ...]:
91
- """解析任务文件中的 TASK 段;未知状态保留,缺少 Estimate 记为 None。"""
92
- try:
93
- text = Path(task_file).read_text(encoding="utf-8")
94
- except (OSError, UnicodeError) as exc:
95
- raise ValueError(f"task_read_error: {exc}") from exc
96
- try:
97
- document = parse_task_document(text, source=str(task_file))
98
- except TaskDocumentError as exc:
99
- raise ValueError(str(exc)) from exc
100
- tasks: list[PlanTask] = []
101
- for task in document.tasks:
102
- estimate_raw = task.fields.get("Estimate", "")
103
- tasks.append(
104
- PlanTask(
105
- task_id=task.id,
106
- title=task.title,
107
- status=task.status,
108
- estimate_raw=estimate_raw,
109
- hours=parse_estimate_hours(estimate_raw),
110
- capability=_capability(task.body),
111
- scenario_count=_scenario_count(task.body),
112
- )
113
- )
114
- return tuple(tasks)
115
-
116
-
117
- def _warning(code: str, message: str, tasks: Sequence[str] = ()) -> dict[str, object]:
118
- return {"code": code, "message": message, "tasks": list(tasks)}
119
-
120
-
121
- def _budget_warnings(
122
- tasks: Sequence[PlanTask],
123
- total_hours: float,
124
- average_hours: float,
125
- task_size: str,
126
- max_tasks: int,
127
- minimum_average: float,
128
- ) -> list[dict[str, object]]:
129
- warnings: list[dict[str, object]] = []
130
- missing_estimate = [task.task_id for task in tasks if task.hours is None]
131
- if missing_estimate:
132
- warnings.append(_warning(
133
- "missing_estimate",
134
- "以下 TASK 缺少可解析的 Estimate(支持 1.5d / 4h / 90m):"
135
- + ", ".join(missing_estimate)
136
- + ";无法评估计划预算,请补齐或合并。",
137
- missing_estimate,
138
- ))
139
- if len(tasks) > max_tasks:
140
- warnings.append(_warning(
141
- "task_count_exceeds_budget",
142
- f"任务数 {len(tasks)} 超过预算上限 {max_tasks};"
143
- "请按 1-2 天可演示能力合并后再拆解。",
144
- ))
145
- if total_hours > 0 and average_hours < minimum_average:
146
- warnings.append(_warning(
147
- "average_task_too_small",
148
- f"平均任务时长 {average_hours:.1f}h 低于 {minimum_average:.1f}h;"
149
- "任务粒度过碎,请合并为可独立演示的能力任务。",
150
- ))
151
- if task_size == "capability":
152
- missing_capability = [task.task_id for task in tasks if not task.capability]
153
- if missing_capability:
154
- warnings.append(_warning(
155
- "missing_capability",
156
- "以下 TASK 缺少 Capability/Demo(用户可演示能力):"
157
- + ", ".join(missing_capability)
158
- + ";请补充能力描述或合并。",
159
- missing_capability,
160
- ))
161
- return warnings
162
-
163
-
164
- def evaluate_plan(
165
- task_file: str,
166
- task_size: str = "capability",
167
- max_tasks: int = DEFAULT_MAX_TASKS,
168
- min_average_hours: Optional[float] = None,
169
- ) -> dict[str, object]:
170
- """评估任务文件的计划预算,返回可直接 JSON 序列化的结果。"""
171
- if task_size not in TASK_SIZES:
172
- raise ValueError(f"unknown_task_size: {task_size}")
173
- if max_tasks <= 0:
174
- raise ValueError("max_tasks must be positive")
175
- tasks = parse_plan_tasks(task_file)
176
- total_hours = round(sum(task.hours or 0.0 for task in tasks), 2)
177
- average_hours = round(total_hours / len(tasks), 2)
178
- minimum_average = 0.0 if task_size == "file" else (
179
- DEFAULT_MIN_AVERAGE_HOURS if min_average_hours is None else min_average_hours
180
- )
181
- if minimum_average < 0:
182
- raise ValueError("min_average_hours must be non-negative")
183
- warnings = _budget_warnings(
184
- tasks, total_hours, average_hours, task_size, max_tasks, minimum_average
185
- )
186
- requires_merge = bool(warnings)
187
- return {
188
- "ok": True,
189
- "task_file": str(task_file),
190
- "task_size": task_size,
191
- "decision": "merge_required" if requires_merge else "pass",
192
- "requires_merge": requires_merge,
193
- "task_count": len(tasks),
194
- "estimated_task_count": sum(1 for task in tasks if task.hours is not None),
195
- "total_hours": total_hours,
196
- "average_hours": average_hours,
197
- "max_tasks": max_tasks,
198
- "min_average_hours": minimum_average,
199
- "warnings": warnings,
200
- "tasks": [
201
- {
202
- "id": task.task_id,
203
- "title": task.title,
204
- "status": task.status,
205
- "estimate": task.estimate_raw,
206
- "hours": task.hours,
207
- "capability": task.capability,
208
- "scenario_count": task.scenario_count,
209
- }
210
- for task in tasks
211
- ],
212
- }
213
-
214
-
215
- def _write_text(stdout: IO[str], result: dict[str, object]) -> None:
216
- stdout.write(
217
- f"计划预算: {result['task_count']} 任务 / 总计 {result['total_hours']}h"
218
- f" / 平均 {result['average_hours']}h({result['task_size']})\n"
219
- )
220
- for warning in result["warnings"]:
221
- stdout.write(f" ! {warning['message']}\n")
222
- stdout.write(f"decision: {result['decision']}\n")
223
-
224
-
225
- def main(argv: Optional[Sequence[str]] = None, stdout: IO[str] = sys.stdout) -> int:
226
- parser = argparse.ArgumentParser(prog=os.environ.get("CF_RUNTIME_COMMAND", "cf_task_plan.py"))
227
- parser.add_argument("--task-file", required=True)
228
- parser.add_argument("--task-size", choices=TASK_SIZES, default="capability")
229
- parser.add_argument("--max-tasks", type=int, default=DEFAULT_MAX_TASKS)
230
- parser.add_argument("--min-average-hours", type=float, default=None)
231
- parser.add_argument("--json", action="store_true")
232
- args = parser.parse_args(argv)
233
- try:
234
- result = evaluate_plan(
235
- args.task_file,
236
- task_size=args.task_size,
237
- max_tasks=args.max_tasks,
238
- min_average_hours=args.min_average_hours,
239
- )
240
- except (OSError, ValueError) as exc:
241
- stdout.write(json.dumps({"ok": False, "error": str(exc)}, ensure_ascii=False))
242
- return 2
243
- if args.json:
244
- stdout.write(json.dumps(result, ensure_ascii=False))
245
- else:
246
- _write_text(stdout, result)
247
- return 0 if result["decision"] == "pass" else 1
248
-
249
-
250
- if __name__ == "__main__":
251
- raise SystemExit(main())