@michaelxwb/code-flow 0.6.5 → 0.8.1

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 (207) hide show
  1. package/README.md +47 -3
  2. package/docs/USAGE.md +1529 -0
  3. package/docs/acceptance-contract.md +217 -0
  4. package/docs/ci/acceptance-evidence.md +77 -0
  5. package/docs/migrations/runtime-0.7.0.md +41 -0
  6. package/docs/migrations/runtime-0.8.0.md +154 -0
  7. package/docs/migrations/runtime-0.9.0.md +40 -0
  8. package/docs/spec-injection-hardening.md +54 -0
  9. package/docs/spec-injection-performance.json +73 -0
  10. package/docs/spec-optimization.md +12 -0
  11. package/package.json +18 -3
  12. package/src/adapters/claude/CLAUDE.md +1 -1
  13. package/src/adapters/claude/commands/cf-init.md +1 -3
  14. package/src/adapters/claude/commands/cf-spec.md +23 -3
  15. package/src/adapters/claude/commands/cf-stats.md +11 -1
  16. package/src/adapters/claude/commands/cf-sync.md +20 -6
  17. package/src/adapters/claude/commands/cf-task/align.md +25 -3
  18. package/src/adapters/claude/commands/cf-task/archive.md +36 -42
  19. package/src/adapters/claude/commands/cf-task/block.md +15 -2
  20. package/src/adapters/claude/commands/cf-task/graph.md +13 -1
  21. package/src/adapters/claude/commands/cf-task/note.md +13 -1
  22. package/src/adapters/claude/commands/cf-task/plan.md +137 -23
  23. package/src/adapters/claude/commands/cf-task/prd.md +13 -2
  24. package/src/adapters/claude/commands/cf-task/start.md +52 -39
  25. package/src/adapters/claude/commands/cf-task/status.md +12 -0
  26. package/src/adapters/claude/commands/cf-task/verify-e2e.md +68 -35
  27. package/src/adapters/claude/commands/cf-validate.md +30 -5
  28. package/src/adapters/codex/AGENTS.md +1 -1
  29. package/src/adapters/codex/hooks.json +14 -10
  30. package/src/adapters/codex/skills/cf-init/SKILL.md +4 -4
  31. package/src/adapters/codex/skills/cf-spec/SKILL.md +34 -6
  32. package/src/adapters/codex/skills/cf-stats/SKILL.md +11 -1
  33. package/src/adapters/codex/skills/cf-sync/SKILL.md +21 -6
  34. package/src/adapters/codex/skills/cf-task-align/SKILL.md +30 -8
  35. package/src/adapters/codex/skills/cf-task-archive/SKILL.md +36 -42
  36. package/src/adapters/codex/skills/cf-task-block/SKILL.md +15 -2
  37. package/src/adapters/codex/skills/cf-task-graph/SKILL.md +13 -1
  38. package/src/adapters/codex/skills/cf-task-note/SKILL.md +13 -1
  39. package/src/adapters/codex/skills/cf-task-plan/SKILL.md +175 -21
  40. package/src/adapters/codex/skills/cf-task-prd/SKILL.md +13 -2
  41. package/src/adapters/codex/skills/cf-task-start/SKILL.md +52 -39
  42. package/src/adapters/codex/skills/cf-task-status/SKILL.md +12 -0
  43. package/src/adapters/codex/skills/cf-task-verify-e2e/SKILL.md +69 -35
  44. package/src/adapters/codex/skills/cf-validate/SKILL.md +31 -5
  45. package/src/adapters/commands.json +115 -0
  46. package/src/adapters/costrict/CLAUDE.md +1 -1
  47. package/src/adapters/costrict/commands/cf-init.md +1 -3
  48. package/src/adapters/costrict/commands/cf-spec.md +23 -3
  49. package/src/adapters/costrict/commands/cf-stats.md +11 -1
  50. package/src/adapters/costrict/commands/cf-sync.md +20 -6
  51. package/src/adapters/costrict/commands/cf-task/align.md +25 -3
  52. package/src/adapters/costrict/commands/cf-task/archive.md +36 -42
  53. package/src/adapters/costrict/commands/cf-task/block.md +15 -2
  54. package/src/adapters/costrict/commands/cf-task/graph.md +13 -1
  55. package/src/adapters/costrict/commands/cf-task/note.md +13 -1
  56. package/src/adapters/costrict/commands/cf-task/plan.md +137 -23
  57. package/src/adapters/costrict/commands/cf-task/prd.md +13 -2
  58. package/src/adapters/costrict/commands/cf-task/start.md +52 -39
  59. package/src/adapters/costrict/commands/cf-task/status.md +12 -0
  60. package/src/adapters/costrict/commands/cf-task/verify-e2e.md +68 -35
  61. package/src/adapters/costrict/commands/cf-validate.md +30 -5
  62. package/src/adapters/install-plan.json +100 -0
  63. package/src/adapters/opencode/AGENTS.md +1 -1
  64. package/src/adapters/opencode/commands/cf-init.md +7 -7
  65. package/src/adapters/opencode/commands/cf-learn.md +6 -6
  66. package/src/adapters/opencode/commands/cf-spec.md +34 -2
  67. package/src/adapters/opencode/commands/cf-stats.md +17 -7
  68. package/src/adapters/opencode/commands/cf-sync.md +21 -6
  69. package/src/adapters/opencode/commands/cf-task/align.md +25 -8
  70. package/src/adapters/opencode/commands/cf-task/archive.md +36 -42
  71. package/src/adapters/opencode/commands/cf-task/block.md +16 -2
  72. package/src/adapters/opencode/commands/cf-task/graph.md +14 -1
  73. package/src/adapters/opencode/commands/cf-task/note.md +14 -1
  74. package/src/adapters/opencode/commands/cf-task/plan.md +137 -23
  75. package/src/adapters/opencode/commands/cf-task/prd.md +13 -2
  76. package/src/adapters/opencode/commands/cf-task/start.md +53 -45
  77. package/src/adapters/opencode/commands/cf-task/status.md +13 -0
  78. package/src/adapters/opencode/commands/cf-task/verify-e2e.md +68 -35
  79. package/src/adapters/opencode/commands/cf-validate.md +35 -9
  80. package/src/adapters/opencode/plugins/code-flow/index.js +128 -179
  81. package/src/adapters/platform-bindings.json +370 -0
  82. package/src/cli.js +369 -316
  83. package/src/core/code-flow/config.yml +4 -1
  84. package/src/core/code-flow/runtime-commands.json +259 -0
  85. package/src/core/code-flow/scripts/cf_acceptance_manifest.py +5 -289
  86. package/src/core/code-flow/scripts/cf_acceptance_runner.py +5 -187
  87. package/src/core/code-flow/scripts/cf_audit.py +10 -0
  88. package/src/core/code-flow/scripts/cf_codex_hook.py +10 -0
  89. package/src/core/code-flow/scripts/cf_design_lint.py +10 -0
  90. package/src/core/code-flow/scripts/cf_evidence_import.py +10 -0
  91. package/src/core/code-flow/scripts/cf_feedback.py +5 -51
  92. package/src/core/code-flow/scripts/cf_opencode_event.py +10 -0
  93. package/src/core/code-flow/scripts/cf_post_hook.py +6 -191
  94. package/src/core/code-flow/scripts/cf_pre_tool_hook.py +6 -99
  95. package/src/core/code-flow/scripts/cf_scenarios.py +10 -0
  96. package/src/core/code-flow/scripts/cf_spec_context.py +5 -1741
  97. package/src/core/code-flow/scripts/cf_spec_gate.py +5 -259
  98. package/src/core/code-flow/scripts/cf_spec_session.py +5 -114
  99. package/src/core/code-flow/scripts/cf_stats.py +5 -501
  100. package/src/core/code-flow/scripts/cf_stop_hook.py +6 -376
  101. package/src/core/code-flow/scripts/cf_sync.py +5 -135
  102. package/src/core/code-flow/scripts/cf_task_index.py +5 -143
  103. package/src/core/code-flow/scripts/cf_task_parallel.py +5 -389
  104. package/src/core/code-flow/scripts/cf_task_workflow.py +6 -201
  105. package/src/core/code-flow/scripts/cf_user_prompt_hook.py +6 -173
  106. package/src/core/code-flow/scripts/cf_validation.py +6 -77
  107. package/src/core/code-flow/scripts/codeflow/__init__.py +0 -0
  108. package/src/core/code-flow/scripts/codeflow/acceptance/__init__.py +0 -0
  109. package/src/core/code-flow/scripts/codeflow/acceptance/cf_acceptance_evidence.py +347 -0
  110. package/src/core/code-flow/scripts/codeflow/acceptance/cf_acceptance_manifest_core.py +660 -0
  111. package/src/core/code-flow/scripts/codeflow/acceptance/cf_acceptance_plan.py +202 -0
  112. package/src/core/code-flow/scripts/codeflow/acceptance/cf_acceptance_registration.py +60 -0
  113. package/src/core/code-flow/scripts/codeflow/acceptance/cf_acceptance_risk.py +73 -0
  114. package/src/core/code-flow/scripts/codeflow/acceptance/cf_acceptance_schema.py +282 -0
  115. package/src/core/code-flow/scripts/codeflow/acceptance/cf_acceptance_selection.py +266 -0
  116. package/src/core/code-flow/scripts/codeflow/acceptance/cf_check_inputs.py +172 -0
  117. package/src/core/code-flow/scripts/codeflow/acceptance/cf_check_registration.py +326 -0
  118. package/src/core/code-flow/scripts/{cf_checks.py → codeflow/acceptance/cf_checks.py} +8 -11
  119. package/src/core/code-flow/scripts/codeflow/acceptance/cf_plan_quality.py +197 -0
  120. package/src/core/code-flow/scripts/codeflow/acceptance/cf_scenario_quality.py +174 -0
  121. package/src/core/code-flow/scripts/codeflow/acceptance/cf_scenario_source.py +300 -0
  122. package/src/core/code-flow/scripts/codeflow/acceptance/cf_traceability.py +502 -0
  123. package/src/core/code-flow/scripts/codeflow/cli/__init__.py +0 -0
  124. package/src/core/code-flow/scripts/codeflow/cli/cf_acceptance_manifest.py +347 -0
  125. package/src/core/code-flow/scripts/codeflow/cli/cf_acceptance_runner.py +1490 -0
  126. package/src/core/code-flow/scripts/codeflow/cli/cf_audit.py +533 -0
  127. package/src/core/code-flow/scripts/codeflow/cli/cf_design_lint.py +172 -0
  128. package/src/core/code-flow/scripts/codeflow/cli/cf_evidence_import.py +607 -0
  129. package/src/core/code-flow/scripts/codeflow/cli/cf_feedback.py +53 -0
  130. package/src/core/code-flow/scripts/codeflow/cli/cf_scenarios.py +58 -0
  131. package/src/core/code-flow/scripts/codeflow/cli/cf_spec_context.py +1175 -0
  132. package/src/core/code-flow/scripts/codeflow/cli/cf_spec_gate.py +60 -0
  133. package/src/core/code-flow/scripts/codeflow/cli/cf_spec_session.py +46 -0
  134. package/src/core/code-flow/scripts/codeflow/cli/cf_stats.py +540 -0
  135. package/src/core/code-flow/scripts/codeflow/cli/cf_sync.py +144 -0
  136. package/src/core/code-flow/scripts/codeflow/cli/cf_task_index.py +177 -0
  137. package/src/core/code-flow/scripts/codeflow/cli/cf_task_parallel.py +1142 -0
  138. package/src/core/code-flow/scripts/codeflow/cli/cf_task_workflow.py +742 -0
  139. package/src/core/code-flow/scripts/codeflow/cli/cf_validation.py +31 -0
  140. package/src/core/code-flow/scripts/codeflow/core/__init__.py +0 -0
  141. package/src/core/code-flow/scripts/codeflow/core/cf_context_cache.py +67 -0
  142. package/src/core/code-flow/scripts/{cf_core.py → codeflow/core/cf_core.py} +119 -297
  143. package/src/core/code-flow/scripts/codeflow/core/cf_exec_base.py +325 -0
  144. package/src/core/code-flow/scripts/codeflow/core/cf_fs.py +57 -0
  145. package/src/core/code-flow/scripts/codeflow/core/cf_git.py +92 -0
  146. package/src/core/code-flow/scripts/{cf_log.py → codeflow/core/cf_log.py} +13 -7
  147. package/src/core/code-flow/scripts/codeflow/core/cf_markdown.py +77 -0
  148. package/src/core/code-flow/scripts/codeflow/core/cf_native_paths.py +26 -0
  149. package/src/core/code-flow/scripts/codeflow/core/cf_patch.py +33 -0
  150. package/src/core/code-flow/scripts/codeflow/core/cf_runtime_install.py +32 -0
  151. package/src/core/code-flow/scripts/codeflow/core/cf_stop_guard.py +53 -0
  152. package/src/core/code-flow/scripts/codeflow/core/cf_task_state.py +9 -0
  153. package/src/core/code-flow/scripts/codeflow/core/cf_validators.py +408 -0
  154. package/src/core/code-flow/scripts/codeflow/core/cf_worktree_env.py +253 -0
  155. package/src/core/code-flow/scripts/codeflow/evidence/__init__.py +0 -0
  156. package/src/core/code-flow/scripts/codeflow/evidence/cf_evidence_store.py +527 -0
  157. package/src/core/code-flow/scripts/codeflow/hooks/__init__.py +0 -0
  158. package/src/core/code-flow/scripts/codeflow/hooks/cf_codex_hook.py +95 -0
  159. package/src/core/code-flow/scripts/codeflow/hooks/cf_opencode_event.py +98 -0
  160. package/src/core/code-flow/scripts/codeflow/hooks/cf_post_hook.py +30 -0
  161. package/src/core/code-flow/scripts/codeflow/hooks/cf_pre_tool_hook.py +41 -0
  162. package/src/core/code-flow/scripts/codeflow/hooks/cf_stop_hook.py +292 -0
  163. package/src/core/code-flow/scripts/codeflow/hooks/cf_user_prompt_hook.py +161 -0
  164. package/src/core/code-flow/scripts/codeflow/spec/__init__.py +0 -0
  165. package/src/core/code-flow/scripts/codeflow/spec/cf_active_task.py +412 -0
  166. package/src/core/code-flow/scripts/codeflow/spec/cf_context_refresh.py +301 -0
  167. package/src/core/code-flow/scripts/codeflow/spec/cf_edit_service.py +149 -0
  168. package/src/core/code-flow/scripts/codeflow/spec/cf_injection.py +111 -0
  169. package/src/core/code-flow/scripts/codeflow/spec/cf_rule_readiness.py +114 -0
  170. package/src/core/code-flow/scripts/{cf_scan.py → codeflow/spec/cf_scan.py} +93 -21
  171. package/src/core/code-flow/scripts/codeflow/spec/cf_spec_budget.py +55 -0
  172. package/src/core/code-flow/scripts/codeflow/spec/cf_spec_context_model.py +477 -0
  173. package/src/core/code-flow/scripts/codeflow/spec/cf_spec_impact.py +119 -0
  174. package/src/core/code-flow/scripts/{cf_spec_metadata.py → codeflow/spec/cf_spec_metadata.py} +5 -2
  175. package/src/core/code-flow/scripts/{cf_spec_migrate.py → codeflow/spec/cf_spec_migrate.py} +27 -10
  176. package/src/core/code-flow/scripts/codeflow/spec/cf_spec_optimize.py +193 -0
  177. package/src/core/code-flow/scripts/codeflow/spec/cf_spec_optimize_apply.py +187 -0
  178. package/src/core/code-flow/scripts/{cf_spec_resolver.py → codeflow/spec/cf_spec_resolver.py} +39 -17
  179. package/src/core/code-flow/scripts/{cf_spec_router.py → codeflow/spec/cf_spec_router.py} +96 -36
  180. package/src/core/code-flow/scripts/{cf_spec_verify.py → codeflow/spec/cf_spec_verify.py} +163 -35
  181. package/src/core/code-flow/scripts/codeflow/spec/cf_stage_gate.py +330 -0
  182. package/src/core/code-flow/scripts/codeflow/spec/cf_task_projection.py +83 -0
  183. package/src/core/code-flow/scripts/codeflow/spec/cf_workflow_service.py +755 -0
  184. package/src/core/code-flow/scripts/{cf_workflow_transaction.py → codeflow/spec/cf_workflow_transaction.py} +29 -15
  185. package/src/core/code-flow/scripts/codeflow/task/__init__.py +0 -0
  186. package/src/core/code-flow/scripts/codeflow/task/cf_requirement_verification.py +867 -0
  187. package/src/core/code-flow/scripts/codeflow/task/cf_task_doc.py +178 -0
  188. package/src/core/code-flow/scripts/codeflow/task/cf_task_merge.py +635 -0
  189. package/src/core/code-flow/scripts/codeflow/task/cf_task_plan.py +251 -0
  190. package/src/core/code-flow/scripts/codeflow/task/cf_task_runtime.py +570 -0
  191. package/src/core/code-flow/scripts/codeflow/task/cf_validation_service.py +73 -0
  192. package/src/core/code-flow/scripts/runtime-resources.json +29 -0
  193. package/src/core/code-flow/specs/shared/design/design-frontend.md +18 -15
  194. package/src/core/code-flow/specs/shared/design/design-full.md +33 -23
  195. package/src/core/code-flow/specs/shared/design/design-lite.md +23 -13
  196. package/src/core/code-flow/specs/shared/scenarios-template.md +55 -0
  197. package/src/core/code-flow/validation.yml +9 -3
  198. package/src/migrate/runtime.js +213 -0
  199. package/src/migrate/spec-workflow.js +16 -1
  200. package/src/runtime.js +219 -0
  201. package/src/core/code-flow/scripts/cf_acceptance_evidence.py +0 -100
  202. package/src/core/code-flow/scripts/cf_acceptance_schema.py +0 -83
  203. package/src/core/code-flow/scripts/cf_exec_base.py +0 -112
  204. package/src/core/code-flow/scripts/cf_task_runtime.py +0 -304
  205. package/src/core/code-flow/scripts/cf_task_state.py +0 -3
  206. package/src/core/code-flow/scripts/cf_workflow_service.py +0 -366
  207. /package/src/core/code-flow/scripts/{cf_session_state.py → codeflow/core/cf_session_state.py} +0 -0
@@ -2,7 +2,7 @@ version: 1
2
2
 
3
3
  spec_workflow:
4
4
  schema_version: 1
5
- enforcement: required # required=全门禁 | warn=门禁仅记录不阻断 | inject=只注入不门禁
5
+ enforcement: required # 推荐全门禁;历史 warn/inject 仅兼容部分验证降级,路由完整性错误始终阻断
6
6
  drift_check: true
7
7
  scope_expansion_check: true
8
8
  metrics: true
@@ -92,6 +92,9 @@ path_mapping:
92
92
  - path: "shared/_map.md"
93
93
  tags: ["*"]
94
94
  tier: 0
95
+ - path: "shared/scenarios-template.md"
96
+ tags: [] # 永不自动注入,仅 align 显式 Read
97
+ tier: 1
95
98
  - path: "shared/prd-template.md"
96
99
  tags: [] # 永不自动注入,仅 cf-task:prd 命令显式 Read
97
100
  tier: 1
@@ -0,0 +1,259 @@
1
+ {
2
+ "schema_version": 1,
3
+ "commands": {
4
+ "spec budget": {
5
+ "module": "cf_spec_context",
6
+ "prefix": ["budget"],
7
+ "example_args": "--help"
8
+ },
9
+ "spec impact": {
10
+ "module": "cf_spec_context",
11
+ "prefix": ["impact"],
12
+ "example_args": "--help"
13
+ },
14
+ "spec optimize": {
15
+ "module": "cf_spec_context",
16
+ "prefix": ["optimize"],
17
+ "example_args": "--help"
18
+ },
19
+ "spec catalog": {
20
+ "module": "cf_spec_context",
21
+ "prefix": [
22
+ "catalog"
23
+ ],
24
+ "example_args": "--help"
25
+ },
26
+ "spec decision": {
27
+ "module": "cf_spec_context",
28
+ "prefix": [
29
+ "decision"
30
+ ],
31
+ "example_args": "--help"
32
+ },
33
+ "spec bind": {
34
+ "module": "cf_spec_context",
35
+ "prefix": [
36
+ "bind"
37
+ ],
38
+ "example_args": "--help"
39
+ },
40
+ "spec validate": {
41
+ "module": "cf_spec_context",
42
+ "prefix": [
43
+ "validate"
44
+ ],
45
+ "example_args": "--task-dir \"<需求目录>\" --json"
46
+ },
47
+ "spec status": {
48
+ "module": "cf_spec_context",
49
+ "prefix": [
50
+ "status"
51
+ ],
52
+ "example_args": "--task-dir \"<需求目录>\" --root \"$PWD\" --json"
53
+ },
54
+ "spec doctor": {
55
+ "module": "cf_spec_context",
56
+ "prefix": [
57
+ "doctor"
58
+ ],
59
+ "example_args": "--task-dir \"<需求目录>\" --root \"$PWD\" --json"
60
+ },
61
+ "spec refresh": {
62
+ "module": "cf_spec_context",
63
+ "prefix": [
64
+ "refresh"
65
+ ],
66
+ "example_args": "--task-dir \"<需求目录>\" --root \"$PWD\" --json"
67
+ },
68
+ "spec refresh-missing": {
69
+ "module": "cf_spec_context",
70
+ "prefix": [
71
+ "refresh-missing"
72
+ ],
73
+ "example_args": "--help"
74
+ },
75
+ "spec active": {
76
+ "module": "cf_spec_context",
77
+ "prefix": [
78
+ "active"
79
+ ],
80
+ "example_args": "--help"
81
+ },
82
+ "task start": {
83
+ "module": "cf_spec_context",
84
+ "prefix": [
85
+ "start"
86
+ ],
87
+ "example_args": "--task-dir \"<需求目录>\" --task-file \"<任务文件>\" --task TASK-001 --root \"$PWD\" --json"
88
+ },
89
+ "task finish": {
90
+ "module": "cf_task_workflow",
91
+ "prefix": [
92
+ "finish"
93
+ ],
94
+ "example_args": "--task-dir \"<需求目录>\" --task TASK-001 --root \"$PWD\" --json"
95
+ },
96
+ "task block": {
97
+ "module": "cf_task_workflow",
98
+ "prefix": [
99
+ "block"
100
+ ],
101
+ "example_args": "--help"
102
+ },
103
+ "task resume": {
104
+ "module": "cf_task_workflow",
105
+ "prefix": [
106
+ "resume"
107
+ ],
108
+ "example_args": "--help"
109
+ },
110
+ "task verify": {
111
+ "module": "cf_task_workflow",
112
+ "prefix": [
113
+ "verify"
114
+ ],
115
+ "example_args": "--task-dir \"<需求目录>\" --tasks TASK-001,TASK-002 --layers validators,e2e --profile smoke --root \"$PWD\" --json"
116
+ },
117
+ "task verify-e2e": {
118
+ "module": "cf_task_workflow",
119
+ "prefix": [
120
+ "verify-e2e"
121
+ ],
122
+ "example_args": "--task-dir \"<需求目录>\" --root \"$PWD\" --json"
123
+ },
124
+ "task confirm-manual": {
125
+ "module": "cf_task_workflow",
126
+ "prefix": [
127
+ "confirm-manual"
128
+ ],
129
+ "example_args": "--help"
130
+ },
131
+ "task cleanup-session": {
132
+ "module": "cf_task_workflow",
133
+ "prefix": [
134
+ "cleanup-session"
135
+ ],
136
+ "example_args": "--help"
137
+ },
138
+ "audit": {
139
+ "module": "cf_audit",
140
+ "prefix": [],
141
+ "example_args": "--task-dir \"<需求目录>\" --root \"$PWD\" --json"
142
+ },
143
+ "spec gate": {
144
+ "module": "cf_spec_gate",
145
+ "prefix": [],
146
+ "example_args": "--help"
147
+ },
148
+ "spec session": {
149
+ "module": "cf_spec_session",
150
+ "prefix": [],
151
+ "example_args": "--help"
152
+ },
153
+ "task index": {
154
+ "module": "cf_task_index",
155
+ "prefix": [],
156
+ "example_args": "--help"
157
+ },
158
+ "task parallel": {
159
+ "module": "cf_task_parallel",
160
+ "prefix": [],
161
+ "example_args": "--help"
162
+ },
163
+ "acceptance manifest": {
164
+ "module": "cf_acceptance_manifest",
165
+ "prefix": [],
166
+ "example_args": "--task-file \"<任务文件>\" --output \"<需求目录>/.acceptance-manifest.json\""
167
+ },
168
+ "acceptance project": {
169
+ "module": "cf_acceptance_manifest",
170
+ "prefix": [
171
+ "--project"
172
+ ],
173
+ "example_args": "--task-file \"<任务文件>\" --output \"<需求目录>/.acceptance-manifest.json\""
174
+ },
175
+ "acceptance risk": {
176
+ "module": "cf_acceptance_manifest",
177
+ "prefix": [
178
+ "--accept-risk"
179
+ ],
180
+ "example_args": "--task-file \"<任务文件>\" --output \"<需求目录>/.acceptance-manifest.json\" --check-id CHK-... --risk-owner \"<owner>\" --risk-reason \"<reason>\" --expires-at 2026-12-31 --confirmed-by \"<user>\" --source \"<user reply>\""
181
+ },
182
+ "acceptance run": {
183
+ "module": "cf_acceptance_runner",
184
+ "prefix": [],
185
+ "example_args": "--manifest \"<需求目录>/.acceptance-manifest.json\" --root \"$PWD\" --write-evidence"
186
+ },
187
+ "evidence import": {
188
+ "module": "cf_evidence_import",
189
+ "prefix": [
190
+ "import"
191
+ ],
192
+ "example_args": "--format junit --file \"<report.xml>\" --commit \"$(git rev-parse HEAD)\" --run-url \"<ci-run-url>\" --json"
193
+ },
194
+ "validate": {
195
+ "module": "cf_validation",
196
+ "prefix": [],
197
+ "example_args": "--root \"$PWD\" --json"
198
+ },
199
+ "stats": {
200
+ "module": "cf_stats",
201
+ "prefix": [],
202
+ "example_args": "--help"
203
+ },
204
+ "feedback ignore": {
205
+ "module": "cf_feedback",
206
+ "prefix": [
207
+ "ignore"
208
+ ],
209
+ "example_args": "--help"
210
+ },
211
+ "sync check": {
212
+ "module": "cf_sync",
213
+ "prefix": [
214
+ "check"
215
+ ],
216
+ "example_args": "--help"
217
+ },
218
+ "sync apply": {
219
+ "module": "cf_sync",
220
+ "prefix": [
221
+ "sync"
222
+ ],
223
+ "example_args": "--help"
224
+ },
225
+ "task archive": {
226
+ "module": "cf_task_workflow",
227
+ "prefix": [
228
+ "archive"
229
+ ],
230
+ "example_args": "--task-dir \"<需求目录>\" --root \"$PWD\" --json"
231
+ },
232
+ "scenarios parse": {
233
+ "module": "cf_scenarios",
234
+ "prefix": [
235
+ "parse"
236
+ ],
237
+ "example_args": "--file \"<需求>.scenarios.md\" --quality --json"
238
+ },
239
+ "scenarios merge": {
240
+ "module": "cf_scenarios",
241
+ "prefix": [
242
+ "merge"
243
+ ],
244
+ "example_args": "--file a.scenarios.md --file b.scenarios.md --json"
245
+ },
246
+ "scenarios migrate": {
247
+ "module": "cf_scenarios",
248
+ "prefix": [
249
+ "migrate"
250
+ ],
251
+ "example_args": "--task-file \"<任务文件>\" --output \"<需求>.scenarios.md\" --json"
252
+ },
253
+ "design lint": {
254
+ "module": "cf_design_lint",
255
+ "prefix": [],
256
+ "example_args": "--file \"<需求>.design.md\" --scenarios-file \"<需求>.scenarios.md\" --json"
257
+ }
258
+ }
259
+ }
@@ -1,294 +1,10 @@
1
1
  #!/usr/bin/env python3
2
- """Lock and validate plan Acceptance Coverage entries."""
3
-
4
- from __future__ import annotations
5
-
6
- import argparse
7
- import hashlib
8
- import json
9
- import shlex
10
- from pathlib import Path
11
- import re
2
+ """Path-frozen entry shim: implementation lives in codeflow.cli.cf_acceptance_manifest."""
12
3
  import sys
13
- from typing import IO, Callable, Mapping, Optional, Sequence
14
-
15
- from cf_acceptance_schema import load_manifest, validate_scenarios
16
-
17
-
18
- _ROW_RE = re.compile(r"^\|\s*([SEB]-\d+)\s*\|(.+?)\|$")
19
-
20
- _MANIFEST_SCHEMA = 2
21
-
22
-
23
- def _kind(level: str) -> str:
24
- """Map test level to execution kind.
25
-
26
- - functional: auto-executed in Done Gate (unit, integration)
27
- - e2e: deferred until explicit --verify-e2e (needs external dependencies)
28
- - manual: user confirmation required, never auto-executed
29
-
30
- 层级单元格允许带注解(如 `E2E(Playwright)`、`e2e/chrome`、`manual(chrome)`),
31
- 取分隔符前的词元判定;避免带注解的 E2E 被误判为 functional 而每任务执行。
32
- """
33
- head = level.strip().lower()
34
- for separator in ("(", "(", "[", "/", ",", ",", " ", "—", "-"):
35
- head = head.split(separator, 1)[0]
36
- head = head.strip()
37
- if head in ("manual", "手工", "手动"):
38
- return "manual"
39
- if head in ("e2e", "端到端", "end"):
40
- return "e2e"
41
- return "functional"
42
-
43
-
44
- def _parse_command(cell: str, scenario_id: str) -> Optional[list[str]]:
45
- """Parse the optional 命令 column: argv JSON array preferred, else shell words.
46
-
47
- Empty / `-` / `planned` → None (not yet registered, not executable).
48
- """
49
- text = (cell or "").strip()
50
- if not text or text in {"-", "planned", "pending", "TBD", "tbd"}:
51
- return None
52
- if text.startswith("["):
53
- try:
54
- value = json.loads(text)
55
- except json.JSONDecodeError as exc:
56
- raise ValueError(f"{scenario_id} 命令列不是合法 argv JSON: {exc}") from exc
57
- if not isinstance(value, list) or not value or not all(isinstance(item, str) for item in value):
58
- raise ValueError(f"{scenario_id} 命令列必须是 argv 字符串数组")
59
- return list(value)
60
- return shlex.split(text)
61
-
62
-
63
- def _parse_float(cell: str, default: float) -> float:
64
- try:
65
- return float((cell or "").strip() or default)
66
- except (TypeError, ValueError):
67
- return default
68
-
69
-
70
- def _manifest_hash(rows: list[dict[str, object]]) -> str:
71
- immutable = [
72
- {key: item[key] for key in ("id", "source", "level", "kind", "boundary", "owner", "command", "cwd", "timeout", "depends_on")}
73
- for item in rows
74
- ]
75
- payload = json.dumps(immutable, ensure_ascii=False, sort_keys=True, separators=(",", ":")).encode("utf-8")
76
- return hashlib.sha256(payload).hexdigest()
77
-
78
-
79
- def _legacy_kind(level: str) -> str:
80
- """v1 精确匹配:仅用于识别旧版锁定的 manifest,不做新语义判定。"""
81
- normalized = level.strip().lower()
82
- if normalized == "manual":
83
- return "manual"
84
- if normalized == "e2e":
85
- return "e2e"
86
- return "functional"
87
-
88
-
89
- def _extract_rows(task_file: str, kind_fn: Callable[[str], str]) -> dict[str, object]:
90
- path = Path(task_file)
91
- text = path.read_text(encoding="utf-8")
92
- rows: list[dict[str, object]] = []
93
- in_table = False
94
- seen: set[str] = set()
95
- for line in text.splitlines():
96
- if line.startswith("## Acceptance Coverage"):
97
- in_table = True
98
- continue
99
- if in_table and line.startswith("## "):
100
- break
101
- if not in_table:
102
- continue
103
- match = _ROW_RE.match(line)
104
- if not match or set(match.group(2).replace("|", "").strip()) <= {"-", " "}:
105
- continue
106
- fields = [item.strip() for item in match.group(2).split("|")]
107
- if match.group(1) in seen:
108
- raise ValueError(f"duplicate scenario id: {match.group(1)}")
109
- if len(fields) < 5:
110
- raise ValueError(f"Acceptance Coverage missing columns: {match.group(1)}")
111
- seen.add(match.group(1))
112
- depends = [item.strip() for item in (fields[8] if len(fields) > 8 else "").split(",") if item.strip()]
113
- rows.append({
114
- "id": match.group(1),
115
- "source": fields[0],
116
- "level": fields[1],
117
- "kind": kind_fn(fields[1]),
118
- "boundary": fields[2],
119
- "owner": fields[3],
120
- "status": fields[4],
121
- "command": _parse_command(fields[5], match.group(1)) if len(fields) > 5 else None,
122
- "cwd": (fields[6] if len(fields) > 6 else "").strip() or ".",
123
- "timeout": _parse_float(fields[7], 60.0) if len(fields) > 7 else 60.0,
124
- "depends_on": depends,
125
- })
126
- if not rows:
127
- raise ValueError("Acceptance Coverage 缺失或为空")
128
- validate_scenarios(rows)
129
- return {"schema": _MANIFEST_SCHEMA, "task_file": str(path), "task_sha256": _manifest_hash(rows), "scenarios": rows}
130
-
131
-
132
- def extract_manifest(task_file: str, kind_fn: Callable[[str], str] = _kind) -> dict[str, object]:
133
- return _extract_rows(task_file, kind_fn)
134
-
135
-
136
- def write_manifest(task_file: str, output: str) -> dict[str, object]:
137
- manifest = extract_manifest(task_file)
138
- target = Path(output)
139
- try:
140
- manifest["task_file"] = str(Path(task_file).resolve().relative_to(target.parent.resolve()))
141
- except ValueError:
142
- manifest["task_file"] = str(Path(task_file).resolve())
143
- target.parent.mkdir(parents=True, exist_ok=True)
144
- target.write_text(json.dumps(manifest, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
145
- return manifest
146
-
147
-
148
- _IMMUTABLE_SCENARIO_FIELDS = (
149
- "id", "source", "level", "kind", "boundary", "owner", "command", "cwd", "timeout", "depends_on",
150
- )
151
-
152
-
153
- def _heal_legacy_kinds(manifest_file: Path, manifest: dict[str, object], expected: dict[str, object]) -> None:
154
- """旧版 `_kind` 精确匹配把带注解的 E2E 锁成 functional;任务内容未变时
155
- 原地升级 kind 并重锁哈希,保留 execution evidence 等运行态字段。"""
156
- by_id: dict[str, dict[str, object]] = {}
157
- scenarios = expected.get("scenarios")
158
- if isinstance(scenarios, list):
159
- for item in scenarios:
160
- if isinstance(item, dict) and isinstance(item.get("id"), str):
161
- by_id[item["id"]] = item
162
- current = manifest.get("scenarios")
163
- if isinstance(current, list):
164
- for item in current:
165
- if not isinstance(item, dict):
166
- continue
167
- target = by_id.get(item.get("id")) if isinstance(item.get("id"), str) else None
168
- if target is not None:
169
- item["kind"] = target.get("kind")
170
- manifest["task_sha256"] = expected["task_sha256"]
171
- manifest_file.write_text(json.dumps(manifest, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
172
-
173
-
174
- def validate_manifest(task_file: str, manifest_file: str) -> tuple[bool, str]:
175
- try:
176
- manifest = load_manifest(Path(manifest_file))
177
- expected = extract_manifest(task_file)
178
- except (OSError, ValueError, json.JSONDecodeError) as exc:
179
- return False, f"acceptance_manifest_invalid: {exc}"
180
- if manifest.get("schema") != _MANIFEST_SCHEMA:
181
- return False, "acceptance_manifest_drift"
182
- if manifest.get("task_sha256") != expected["task_sha256"]:
183
- legacy = extract_manifest(task_file, _legacy_kind)
184
- if manifest.get("task_sha256") != legacy["task_sha256"]:
185
- return False, "acceptance_manifest_drift"
186
- _heal_legacy_kinds(Path(manifest_file), manifest, expected)
187
- stored_task = manifest.get("task_file", "")
188
- if not stored_task or (Path(manifest_file).parent / stored_task).resolve() != Path(task_file).resolve():
189
- return False, "acceptance_manifest_task_mismatch"
190
- if not isinstance(manifest.get("scenarios"), list):
191
- return False, "acceptance_manifest_scenarios_changed"
192
- stored = {
193
- item.get("id"): item
194
- for item in manifest["scenarios"]
195
- if isinstance(item, Mapping) and isinstance(item.get("id"), str)
196
- }
197
- required = {item["id"] for item in expected["scenarios"]}
198
- if set(stored) != required:
199
- return False, "acceptance_manifest_scenarios_changed"
200
- for item in expected["scenarios"]:
201
- current = stored[item["id"]]
202
- for field in _IMMUTABLE_SCENARIO_FIELDS:
203
- if current.get(field) != item.get(field):
204
- return False, f"acceptance_manifest_field_changed:{item['id']}:{field}"
205
- text = Path(task_file).read_text(encoding="utf-8")
206
- for scenario in expected["scenarios"]:
207
- owner = scenario["owner"]
208
- section = re.search(rf"(?ms)^##\s+{re.escape(owner)}:.*?(?=^##\s+TASK-|\Z)", text)
209
- refs = re.search(r"(?m)^- \*\*Acceptance-Refs\*\*:[ \t]*([^\n]*)", section.group(0)) if section else None
210
- if not refs or scenario["id"] not in re.findall(r"\b[SEB]-\d+\b", refs.group(1)):
211
- return False, "acceptance_manifest_owner_missing"
212
- return True, ""
213
-
214
-
215
- def record_manual_evidence(manifest_file: str, scenario_id: str, confirmed_by: str, evidence: str) -> None:
216
- if not confirmed_by or confirmed_by.lower().split(":", 1)[0] in {"agent", "assistant", "codex", "claude", "opencode", "costrict"}:
217
- raise ValueError("manual evidence must be confirmed by a user")
218
- path = Path(manifest_file)
219
- data = load_manifest(path)
220
- scenarios = data.get("scenarios")
221
- if not isinstance(scenarios, list):
222
- raise ValueError("invalid manifest scenarios")
223
- found = False
224
- owner = ""
225
- task_file = data.get("task_file")
226
- for item in scenarios:
227
- if isinstance(item, dict) and item.get("id") == scenario_id:
228
- if item.get("kind") != "manual":
229
- raise ValueError("scenario is not manual")
230
- item["status"] = "verified"
231
- item["evidence"] = {"confirmed_by": confirmed_by, "evidence": evidence}
232
- found = True
233
- owner = item.get("owner") if isinstance(item.get("owner"), str) else ""
234
- if not found:
235
- raise ValueError("scenario not found")
236
- path.write_text(json.dumps(data, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
237
- if isinstance(task_file, str) and task_file:
238
- task_path = Path(task_file)
239
- if not task_path.is_absolute():
240
- task_path = path.parent / task_path
241
- _sync_task_evidence(task_path, scenario_id, confirmed_by, evidence, owner)
242
-
243
-
244
- def _sync_task_evidence(task_file: Path, scenario_id: str, confirmed_by: str, evidence: str, owner: str = "") -> None:
245
- from cf_acceptance_evidence import sync_task_evidence
246
- sync_task_evidence(task_file, scenario_id, confirmed_by, evidence, owner)
247
-
248
-
249
- def main(argv: Optional[Sequence[str]] = None, stdout: IO[str] = sys.stdout) -> int:
250
- parser = argparse.ArgumentParser()
251
- parser.add_argument("--task-file", required=True)
252
- parser.add_argument("--output", default="")
253
- parser.add_argument("--verify-plan", action="store_true")
254
- parser.add_argument("--task-dir", default="")
255
- parser.add_argument("--record-manual", action="store_true")
256
- parser.add_argument("--scenario-id", default="")
257
- parser.add_argument("--confirmed-by", default="")
258
- parser.add_argument("--evidence", default="")
259
- args = parser.parse_args(argv)
260
- try:
261
- if args.record_manual:
262
- if not args.output or not args.scenario_id or not args.evidence:
263
- raise ValueError("--record-manual requires --output, --scenario-id and --evidence")
264
- record_manual_evidence(args.output, args.scenario_id, args.confirmed_by, args.evidence)
265
- stdout.write(json.dumps({"ok": True, "scenario": args.scenario_id}, ensure_ascii=False))
266
- return 0
267
- if args.verify_plan:
268
- if not args.task_dir:
269
- raise ValueError("--verify-plan requires --task-dir")
270
- from cf_spec_context import load_context
271
- from cf_spec_gate import result_to_data, validate_plan_coverage, validate_stage
272
-
273
- context = load_context(str(Path(args.task_dir) / "spec-context.yml"))
274
- gate = validate_stage(context, "plan")
275
- coverage = validate_plan_coverage(context, args.task_file)
276
- result = {
277
- "decision": "block" if gate.decision == "block" or coverage.decision == "block" else "pass",
278
- "gate": result_to_data(gate),
279
- "coverage": result_to_data(coverage),
280
- }
281
- stdout.write(json.dumps(result, ensure_ascii=False))
282
- return 0 if result["decision"] == "pass" else 3
283
- if not args.output:
284
- raise ValueError("--output is required unless --verify-plan is used")
285
- manifest = write_manifest(args.task_file, args.output)
286
- stdout.write(json.dumps({"ok": True, "scenarios": len(manifest["scenarios"])}, ensure_ascii=False))
287
- return 0
288
- except (OSError, ValueError) as exc:
289
- stdout.write(json.dumps({"ok": False, "error": str(exc)}, ensure_ascii=False))
290
- return 2
291
4
 
5
+ from codeflow.cli import cf_acceptance_manifest as _impl
292
6
 
293
7
  if __name__ == "__main__":
294
- raise SystemExit(main())
8
+ raise SystemExit(_impl.main())
9
+ else:
10
+ sys.modules[__name__] = _impl