@michaelxwb/code-flow 0.5.1 → 0.5.2
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/package.json +1 -1
- package/src/adapters/claude/commands/cf-task/align.md +6 -2
- package/src/adapters/claude/commands/cf-task/archive.md +25 -4
- package/src/adapters/claude/commands/cf-task/plan.md +38 -1
- package/src/adapters/claude/commands/cf-task/start.md +47 -8
- package/src/adapters/codex/skills/cf-task-align/SKILL.md +6 -2
- package/src/adapters/codex/skills/cf-task-archive/SKILL.md +25 -4
- package/src/adapters/codex/skills/cf-task-plan/SKILL.md +38 -1
- package/src/adapters/codex/skills/cf-task-start/SKILL.md +47 -8
- package/src/adapters/costrict/commands/cf-task/align.md +6 -2
- package/src/adapters/costrict/commands/cf-task/archive.md +25 -4
- package/src/adapters/costrict/commands/cf-task/plan.md +38 -1
- package/src/adapters/costrict/commands/cf-task/start.md +47 -8
- package/src/adapters/opencode/commands/cf-task/align.md +6 -2
- package/src/adapters/opencode/commands/cf-task/archive.md +25 -4
- package/src/adapters/opencode/commands/cf-task/plan.md +38 -1
- package/src/adapters/opencode/commands/cf-task/start.md +47 -8
- package/src/core/code-flow/scripts/cf_stop_hook.py +85 -5
- package/src/core/code-flow/specs/shared/design/design-frontend.md +10 -10
- package/src/core/code-flow/specs/shared/design/design-full.md +26 -24
- package/src/core/code-flow/specs/shared/design/design-lite.md +11 -9
|
@@ -29,6 +29,11 @@ from cf_core import (
|
|
|
29
29
|
|
|
30
30
|
TOTAL_BUDGET_SECONDS = 30.0
|
|
31
31
|
_BRACE_RE = re.compile(r"\{([^{}]+)\}")
|
|
32
|
+
_TASK_SECTION_RE = re.compile(
|
|
33
|
+
r"(?ms)^## (TASK-\d+):.*?(?=^## TASK-\d+:|\Z)"
|
|
34
|
+
)
|
|
35
|
+
_SCENARIO_RE = re.compile(r"\b[SEB]-\d+\b")
|
|
36
|
+
_UNVERIFIED_RE = re.compile(r"\b(?:planned|pending|tbd)\b", re.IGNORECASE)
|
|
32
37
|
|
|
33
38
|
|
|
34
39
|
def expand_braces(pattern: str) -> list:
|
|
@@ -83,6 +88,79 @@ def session_edited_files(project_root: str, sid: str) -> list:
|
|
|
83
88
|
return files
|
|
84
89
|
|
|
85
90
|
|
|
91
|
+
def _is_active_task_file(rel_path: str) -> bool:
|
|
92
|
+
path = normalize_path(rel_path)
|
|
93
|
+
if not path.startswith(".code-flow/tasks/") or "/archived/" in path:
|
|
94
|
+
return False
|
|
95
|
+
return path.endswith(".md") and not path.endswith((".design.md", ".prd.md"))
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _subsection(section: str, heading: str) -> str:
|
|
99
|
+
match = re.search(
|
|
100
|
+
rf"(?ms)^### {re.escape(heading)}\s*$\n(.*?)(?=^### |^## |\Z)",
|
|
101
|
+
section,
|
|
102
|
+
)
|
|
103
|
+
return match.group(1) if match else ""
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _acceptance_gap(task_id: str, section: str, coverage: str) -> str:
|
|
107
|
+
status = re.search(r"(?m)^- \*\*Status\*\*: ([^\n]+)", section)
|
|
108
|
+
if not status or status.group(1).strip() != "done":
|
|
109
|
+
return ""
|
|
110
|
+
refs_match = re.search(r"(?m)^- \*\*Acceptance-Refs\*\*: ([^\n]+)", section)
|
|
111
|
+
if not refs_match:
|
|
112
|
+
return f"{task_id} 缺少 Acceptance-Refs"
|
|
113
|
+
refs = refs_match.group(1).strip()
|
|
114
|
+
if refs.upper().startswith("N/A"):
|
|
115
|
+
return ""
|
|
116
|
+
scenarios = sorted(set(_SCENARIO_RE.findall(refs)))
|
|
117
|
+
if not scenarios:
|
|
118
|
+
return f"{task_id} Acceptance-Refs 未引用 S/E/B 场景"
|
|
119
|
+
contract = _subsection(section, "Acceptance Contract")
|
|
120
|
+
evidence = _subsection(section, "Acceptance Evidence")
|
|
121
|
+
if not contract or not evidence:
|
|
122
|
+
return f"{task_id} 缺少 Acceptance Contract 或 Acceptance Evidence"
|
|
123
|
+
if _UNVERIFIED_RE.search(contract) or _UNVERIFIED_RE.search(evidence):
|
|
124
|
+
return f"{task_id} 验收契约仍有 planned/pending/TBD"
|
|
125
|
+
for scenario in scenarios:
|
|
126
|
+
contract_ok = any(scenario in line and "verified" in line.lower()
|
|
127
|
+
for line in contract.splitlines())
|
|
128
|
+
evidence_ok = any(scenario in line and "verified" in line.lower()
|
|
129
|
+
for line in evidence.splitlines())
|
|
130
|
+
coverage_ok = any(scenario in line and "verified" in line.lower()
|
|
131
|
+
for line in coverage.splitlines())
|
|
132
|
+
if not contract_ok or not evidence_ok or not coverage_ok:
|
|
133
|
+
return f"{task_id} 的 {scenario} 未在覆盖表、契约和证据中全部 verified"
|
|
134
|
+
return ""
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def task_acceptance_failures(project_root: str, files: list) -> list:
|
|
138
|
+
"""Check new-format task files; legacy tasks without coverage are ignored."""
|
|
139
|
+
failures = []
|
|
140
|
+
for rel_path in files:
|
|
141
|
+
if not _is_active_task_file(rel_path):
|
|
142
|
+
continue
|
|
143
|
+
path = os.path.join(project_root, normalize_path(rel_path))
|
|
144
|
+
try:
|
|
145
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
146
|
+
text = f.read()
|
|
147
|
+
except OSError as exc:
|
|
148
|
+
_log(f"cf_stop_hook task read failed: {exc}")
|
|
149
|
+
continue
|
|
150
|
+
if "## Acceptance Coverage" not in text:
|
|
151
|
+
continue
|
|
152
|
+
coverage = text.split("## Acceptance Coverage", 1)[1].split("## TASK-", 1)[0]
|
|
153
|
+
for match in _TASK_SECTION_RE.finditer(text):
|
|
154
|
+
detail = _acceptance_gap(match.group(1), match.group(0), coverage)
|
|
155
|
+
if detail:
|
|
156
|
+
failures.append({
|
|
157
|
+
"name": "任务验收契约",
|
|
158
|
+
"on_fail": "补齐设计场景的测试映射与执行证据后再完成任务",
|
|
159
|
+
"detail": detail,
|
|
160
|
+
})
|
|
161
|
+
return failures
|
|
162
|
+
|
|
163
|
+
|
|
86
164
|
def run_validators(
|
|
87
165
|
project_root: str, validators: list, files: list, sid: str,
|
|
88
166
|
total_budget: float = TOTAL_BUDGET_SECONDS,
|
|
@@ -144,7 +222,7 @@ def run_validators(
|
|
|
144
222
|
|
|
145
223
|
|
|
146
224
|
def _reason_text(failures: list, truncated: bool) -> str:
|
|
147
|
-
lines = ["收尾校验未通过(cf-stop
|
|
225
|
+
lines = ["收尾校验未通过(cf-stop):"]
|
|
148
226
|
for item in failures:
|
|
149
227
|
lines.append(f"✗ {item['name']}:{item['on_fail']}")
|
|
150
228
|
if item["detail"]:
|
|
@@ -174,12 +252,14 @@ def main() -> None:
|
|
|
174
252
|
files = session_edited_files(project_root, sid)
|
|
175
253
|
if not files:
|
|
176
254
|
return
|
|
255
|
+
acceptance_failures = task_acceptance_failures(project_root, files)
|
|
177
256
|
validators = load_validators(project_root)
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
257
|
+
failures, truncated = run_validators(
|
|
258
|
+
project_root, validators, files, sid
|
|
259
|
+
) if validators else ([], False)
|
|
260
|
+
failures = acceptance_failures + failures
|
|
181
261
|
if not failures:
|
|
182
|
-
return #
|
|
262
|
+
return # 全过或无 validation.yml 且无验收缺口时静默
|
|
183
263
|
payload = {"decision": "block", "reason": _reason_text(failures, truncated)}
|
|
184
264
|
sys.stdout.write(json.dumps(payload, ensure_ascii=False))
|
|
185
265
|
except Exception as exc:
|
|
@@ -90,19 +90,19 @@
|
|
|
90
90
|
|
|
91
91
|
### 2.4 验收条件 [必填]
|
|
92
92
|
|
|
93
|
-
> 交互层面的可验证场景,写到可转
|
|
93
|
+
> 交互层面的可验证场景,写到可转 E2E / 组件交互测试断言的粒度(明确操作与可观测 UI 结果)。场景 ID 即测试用例来源。测试层级只能填 `unit` / `integration` / `E2E` / `manual`;跨路由、服务请求、状态更新和最终 UI 的用户流程必须标为 `E2E`。关键真实边界列出不得 mock 的浏览器、路由、状态或服务边界。
|
|
94
94
|
|
|
95
95
|
**正常场景**
|
|
96
96
|
|
|
97
|
-
| 场景ID | 功能ID | 优先级 | 操作步骤 | 预期 UI 结果 |
|
|
98
|
-
|
|
99
|
-
| S-01 | FEAT-01 | P0 | 1. <操作> | <可见结果> |
|
|
97
|
+
| 场景ID | 功能ID | 优先级 | 测试层级 | 关键真实边界 | 操作步骤 | 预期 UI 结果 |
|
|
98
|
+
|--------|--------|--------|---------|-------------|---------|-------------|
|
|
99
|
+
| S-01 | FEAT-01 | P0 | E2E | Browser → Router → Service → UI | 1. <操作> | <可见结果> |
|
|
100
100
|
|
|
101
101
|
**异常场景**
|
|
102
102
|
|
|
103
|
-
| 场景ID | 功能ID | 触发条件 | UI 表现 |
|
|
104
|
-
|
|
105
|
-
| E-01 | FEAT-01 | (接口失败 / 空数据 / 校验不通过) | (错误提示 / 空态 / 字段定位) |
|
|
103
|
+
| 场景ID | 功能ID | 测试层级 | 关键真实边界 | 触发条件 | UI 表现 |
|
|
104
|
+
|--------|--------|---------|-------------|---------|---------|
|
|
105
|
+
| E-01 | FEAT-01 | integration | Service → UI | (接口失败 / 空数据 / 校验不通过) | (错误提示 / 空态 / 字段定位) |
|
|
106
106
|
|
|
107
107
|
**非功能指标** [按需]
|
|
108
108
|
|
|
@@ -227,9 +227,9 @@
|
|
|
227
227
|
|
|
228
228
|
## 4. 风险与依赖 [按需]
|
|
229
229
|
|
|
230
|
-
| 风险ID | 描述 | 影响 | 应对 |
|
|
231
|
-
|
|
232
|
-
| | | | |
|
|
230
|
+
| 风险ID | 描述 | 影响 | 应对 | 验证场景 |
|
|
231
|
+
|--------|------|------|------|---------|
|
|
232
|
+
| | | | | S-/E-/B- 或手动验证理由 |
|
|
233
233
|
|
|
234
234
|
---
|
|
235
235
|
|
|
@@ -133,32 +133,34 @@
|
|
|
133
133
|
|
|
134
134
|
#### 2.5.1 业务规则与约束
|
|
135
135
|
|
|
136
|
-
| ID | 类型 | 描述 |
|
|
137
|
-
|
|
138
|
-
| RULE-01 | 业务规则 | |
|
|
139
|
-
| RULE-02 | 系统约束 | 单次批量导入上限≤10000条 |
|
|
136
|
+
| ID | 类型 | 描述 | 验证场景 |
|
|
137
|
+
|----|------|------|---------|
|
|
138
|
+
| RULE-01 | 业务规则 | | S-01 |
|
|
139
|
+
| RULE-02 | 系统约束 | 单次批量导入上限≤10000条 | B-01 |
|
|
140
140
|
|
|
141
141
|
#### 2.5.2 功能验收场景
|
|
142
142
|
|
|
143
|
-
> 场景写到可转自动化测试断言的粒度(明确前置、输入、可观测输出)。场景 ID(S-/E-/B-)即测试用例来源,§6
|
|
143
|
+
> 场景写到可转自动化测试断言的粒度(明确前置、输入、可观测输出)。场景 ID(S-/E-/B-)即测试用例来源,§6 追溯矩阵直接引用这些场景 ID。
|
|
144
|
+
>
|
|
145
|
+
> **测试层级**只能填 `unit` / `integration` / `E2E` / `manual`。跨 API、存储、运行时生成、渲染或用户可见结果等多个边界的场景必须标为 `E2E`;`manual` 仅限无法自动化的外部条件,并记录原因。**关键真实边界**列出测试中不得 mock 的组件或最终可观测面,编码阶段不得自行降级。
|
|
144
146
|
|
|
145
147
|
**正常场景**
|
|
146
148
|
|
|
147
|
-
| 场景ID | 功能ID | 优先级 | 前置条件 | 操作步骤 | 预期结果 |
|
|
148
|
-
|
|
149
|
-
| S-01 | FEAT-01 | P0 | | | |
|
|
149
|
+
| 场景ID | 功能ID | 优先级 | 测试层级 | 关键真实边界 | 前置条件 | 操作步骤 | 预期结果 |
|
|
150
|
+
|--------|--------|--------|---------|-------------|---------|---------|---------|
|
|
151
|
+
| S-01 | FEAT-01 | P0 | E2E | API → Store → 最终输出 | | | |
|
|
150
152
|
|
|
151
153
|
**异常场景**
|
|
152
154
|
|
|
153
|
-
| 场景ID | 功能ID | 触发条件 | 系统行为 | 用户感知 |
|
|
154
|
-
|
|
155
|
-
| E-01 | FEAT-01 | | | |
|
|
155
|
+
| 场景ID | 功能ID | 测试层级 | 关键真实边界 | 触发条件 | 系统行为 | 用户感知 |
|
|
156
|
+
|--------|--------|---------|-------------|---------|---------|---------|
|
|
157
|
+
| E-01 | FEAT-01 | integration | Service → Store | | | |
|
|
156
158
|
|
|
157
159
|
**边界场景** [按需]
|
|
158
160
|
|
|
159
|
-
| 场景ID | 字段/条件 | 边界值 | 预期行为 |
|
|
160
|
-
|
|
161
|
-
| B-01 | | | |
|
|
161
|
+
| 场景ID | 测试层级 | 关键真实边界 | 字段/条件 | 边界值 | 预期行为 |
|
|
162
|
+
|--------|---------|-------------|----------|--------|---------|
|
|
163
|
+
| B-01 | unit | 校验函数 | | | |
|
|
162
164
|
|
|
163
165
|
#### 2.5.3 非功能指标 [按需]
|
|
164
166
|
|
|
@@ -386,9 +388,9 @@ flowchart TD
|
|
|
386
388
|
|
|
387
389
|
**风险 / 失效应对**(默认轻量表)
|
|
388
390
|
|
|
389
|
-
| 风险ID | 失效模式 | 影响 | 应对措施 |
|
|
390
|
-
|
|
391
|
-
| RISK-01 | | | |
|
|
391
|
+
| 风险ID | 失效模式 | 影响 | 应对措施 | 验证场景 |
|
|
392
|
+
|--------|---------|------|---------|---------|
|
|
393
|
+
| RISK-01 | | | | E-01 |
|
|
392
394
|
|
|
393
395
|
> 仅安全关键 / 高并发 / 强一致系统才升级为 FMEA:追加 S/O/D(1-10)与 RPN=S×O×D 三列,RPN≥200 上线前必须解决。普通功能用上表即可,不要为打分而打分。
|
|
394
396
|
|
|
@@ -457,19 +459,19 @@ flowchart TD
|
|
|
457
459
|
|
|
458
460
|
### 5.2 风险识别
|
|
459
461
|
|
|
460
|
-
| 风险ID | 类型 | 描述 | 概率 | 影响 | 应对措施 |
|
|
461
|
-
|
|
462
|
-
| | | | | | |
|
|
462
|
+
| 风险ID | 类型 | 描述 | 概率 | 影响 | 应对措施 | 验证场景 |
|
|
463
|
+
|--------|------|------|------|------|---------|---------|
|
|
464
|
+
| | | | | | | S-/E-/B- 或手动验证理由 |
|
|
463
465
|
|
|
464
466
|
---
|
|
465
467
|
|
|
466
468
|
## 6. 需求追溯矩阵
|
|
467
469
|
|
|
468
|
-
| 用户故事 | 功能ID | 接口ID | 测试用例ID | 状态 |
|
|
469
|
-
|
|
470
|
-
| US-01 | FEAT-01 | API-01 |
|
|
470
|
+
| 用户故事 | 功能ID | 接口ID | 测试用例ID | 测试层级 | 状态 |
|
|
471
|
+
|---------|--------|--------|-----------|---------|------|
|
|
472
|
+
| US-01 | FEAT-01 | API-01 | S-01 | E2E | 待实现 |
|
|
471
473
|
|
|
472
|
-
> 用户故事一列引用 PRD 中的 US-XX(无 PRD 时填"需求描述");测试用例 ID 列引用 §2.5.2 的场景 ID(S-/E-/B-),不要另造编号。
|
|
474
|
+
> 用户故事一列引用 PRD 中的 US-XX(无 PRD 时填"需求描述");测试用例 ID 列引用 §2.5.2 的场景 ID(S-/E-/B-),不要另造编号。RULE 与高影响 RISK 必须映射到场景;无法自动化时写明手动验证原因,不得留空。
|
|
473
475
|
|
|
474
476
|
---
|
|
475
477
|
|
|
@@ -109,18 +109,20 @@
|
|
|
109
109
|
#### 2.4.1 功能验收场景
|
|
110
110
|
|
|
111
111
|
> 每个 P0/P1 FEAT 至少 1 条正常 + 1 条异常场景;"操作步骤/预期结果"写到可直接转为自动化测试断言的粒度(明确输入与可观测输出)。场景 ID(S-/E-/B-)即测试用例编号,供 plan 拆解引用。
|
|
112
|
+
>
|
|
113
|
+
> **测试层级**只能填 `unit` / `integration` / `E2E` / `manual`。跨 API、存储、运行时生成、渲染或用户可见结果等多个边界的场景必须标为 `E2E`;`manual` 仅限无法自动化的外部条件,并记录原因。**关键真实边界**列出测试中不得 mock 的组件或最终可观测面,编码阶段不得自行降级。
|
|
112
114
|
|
|
113
115
|
**正常场景**
|
|
114
116
|
|
|
115
|
-
| 场景ID | 功能ID | 优先级 | 操作步骤 | 预期结果 |
|
|
116
|
-
|
|
117
|
-
| S-01 | FEAT-01 | P0 | 1. <步骤><br>2. <步骤> | <结果> |
|
|
117
|
+
| 场景ID | 功能ID | 优先级 | 测试层级 | 关键真实边界 | 操作步骤 | 预期结果 |
|
|
118
|
+
|--------|--------|--------|---------|-------------|---------|---------|
|
|
119
|
+
| S-01 | FEAT-01 | P0 | E2E | API → Store → 最终输出 | 1. <步骤><br>2. <步骤> | <结果> |
|
|
118
120
|
|
|
119
121
|
**异常场景**
|
|
120
122
|
|
|
121
|
-
| 场景ID | 功能ID | 触发条件 | 系统行为 |
|
|
122
|
-
|
|
123
|
-
| E-01 | FEAT-01 | | |
|
|
123
|
+
| 场景ID | 功能ID | 测试层级 | 关键真实边界 | 触发条件 | 系统行为 |
|
|
124
|
+
|--------|--------|---------|-------------|---------|---------|
|
|
125
|
+
| E-01 | FEAT-01 | integration | Service → Store | | |
|
|
124
126
|
|
|
125
127
|
#### 2.4.2 非功能指标 [按需]
|
|
126
128
|
|
|
@@ -274,9 +276,9 @@ flowchart TD
|
|
|
274
276
|
|
|
275
277
|
### 4.2 风险识别
|
|
276
278
|
|
|
277
|
-
| 风险ID | 描述 | 影响 | 应对措施 |
|
|
278
|
-
|
|
279
|
-
| | | | |
|
|
279
|
+
| 风险ID | 描述 | 影响 | 应对措施 | 验证场景 |
|
|
280
|
+
|--------|------|------|---------|---------|
|
|
281
|
+
| | | | | S-/E-/B- 或手动验证理由 |
|
|
280
282
|
|
|
281
283
|
---
|
|
282
284
|
|