cowork-flow 0.0.42 → 0.0.43
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/README.md +1 -1
- package/package.json +1 -1
- package/template/.claude/agents/cowork-implement.md +2 -3
- package/template/.codex/agents/cowork-implement.toml +2 -2
- package/template/.cowork-flow/.version +1 -1
- package/template/.cowork-flow/scripts/application/batch_execution.py +21 -0
- package/template/.cowork-flow/scripts/commands/task_navigation.py +1 -1
- package/template/.cowork-flow/scripts/common/gates/anti_rationalization.py +1 -1
- package/template/.cowork-flow/scripts/common/gates/gates.py +1 -0
- package/template/.cowork-flow/scripts/common/gates/tdd_evidence.py +231 -71
- package/template/.cowork-flow/scripts/common/gates/test_intent.py +6 -30
- package/template/.cowork-flow/spec/contracts/decision-anchor.md +2 -2
- package/template/.cowork-flow/spec/protocols/tdd.md +3 -3
- package/template/.cowork-flow/spec/references/definition-of-done.md +1 -1
- package/template/.cowork-flow/spec/runtime/host-assets.json +0 -2
- package/template/.cowork-flow/spec/runtime/skill-registry.json +25 -2
- package/template/.cowork-flow/workflow.md +4 -3
- package/template/.opencode/agents/cowork-implement.md +1 -1
- package/template/.zcode/.zcode-plugin/plugin.json +1 -1
- package/template/.zcode/scaffold/.cowork-flow/workflow.md +4 -3
- package/template/skills/batch-mode/SKILL.md +72 -59
- package/template/skills/tdd/SKILL.md +44 -0
- package/template/skills/writing-plans/SKILL.md +44 -17
package/README.md
CHANGED
|
@@ -68,7 +68,7 @@ Skills 维护在 `template/skills/` 唯一源码,`init` 时按平台分发到
|
|
|
68
68
|
| `codex` / `opencode` | `.agents/skills/` |
|
|
69
69
|
| `claude-code` | `.claude/skills/` |
|
|
70
70
|
|
|
71
|
-
分发动作:`batch-mode`、`brainstorming`、`break-loop`、`cowork-flow`、`doubt-review`、`game-design`、`party-mode`、`writing-plans`
|
|
71
|
+
分发动作:`batch-mode`、`brainstorming`、`break-loop`、`cowork-flow`、`doubt-review`、`game-design`、`party-mode`、`tdd`、`writing-plans`
|
|
72
72
|
|
|
73
73
|
## CLI 命令
|
|
74
74
|
|
package/package.json
CHANGED
|
@@ -42,8 +42,7 @@ Load context before editing:
|
|
|
42
42
|
6. Read the plan file linked from this task (check `<task>/task.json` `relatedFiles` for a plan path, or search `.cowork-flow/plans/` for a plan file referencing this task directory). Follow the plan steps in order — each step has Files, Action, Verify, and Expected fields.
|
|
43
43
|
7. Read quality source entries from context; backend/frontend natural-language
|
|
44
44
|
specs are review checklists, not dynamic hard validators.
|
|
45
|
-
8. For behavior-change tasks,
|
|
46
|
-
implementation or record a documented exemption.
|
|
45
|
+
8. For behavior-change tasks, write the failing test before implementation and record any red/green evidence in `<task>/check.jsonl`, or record a documented exemption there.
|
|
47
46
|
9. Before reporting completion, write `<task>/quality-review.jsonl` with
|
|
48
47
|
checklist, machine warning, and Definition of Done evidence.
|
|
49
48
|
|
|
@@ -52,7 +51,7 @@ Authoritative internal protocols:
|
|
|
52
51
|
- `.cowork-flow/spec/protocols/decision-review.md`
|
|
53
52
|
- `.cowork-flow/spec/protocols/spec-maintenance.md`
|
|
54
53
|
|
|
55
|
-
Apply their output contracts exactly. Implementation reports and `
|
|
54
|
+
Apply their output contracts exactly. Implementation reports and TDD entries in `check.jsonl`
|
|
56
55
|
must preserve `acceptanceId`, `redCommand`, `redExitCode`, `greenCommand`,
|
|
57
56
|
`greenExitCode`, and `specUpdates`.
|
|
58
57
|
|
|
@@ -33,7 +33,7 @@ Load context before editing:
|
|
|
33
33
|
6. Read the plan file linked from this task (check `<task>/task.json` `relatedFiles` for a plan path, or search `.cowork-flow/plans/` for a plan file referencing this task directory). Follow the plan steps in order — each step has Files, Action, Verify, and Expected fields.
|
|
34
34
|
7. Read quality source entries from context; backend/frontend natural-language
|
|
35
35
|
specs are review checklists, not dynamic hard validators.
|
|
36
|
-
8. For behavior-change tasks,
|
|
36
|
+
8. For behavior-change tasks, write the failing test before implementation and record any red/green evidence in `<task>/check.jsonl`, or record a documented exemption there.
|
|
37
37
|
9. Before reporting completion, write `<task>/quality-review.jsonl` with
|
|
38
38
|
checklist, machine warning, and Definition of Done evidence.
|
|
39
39
|
|
|
@@ -42,7 +42,7 @@ Authoritative internal protocols:
|
|
|
42
42
|
- `.cowork-flow/spec/protocols/decision-review.md`
|
|
43
43
|
- `.cowork-flow/spec/protocols/spec-maintenance.md`
|
|
44
44
|
|
|
45
|
-
Apply their output contracts exactly. Implementation reports and `
|
|
45
|
+
Apply their output contracts exactly. Implementation reports and TDD entries in `check.jsonl`
|
|
46
46
|
must preserve `acceptanceId`, `redCommand`, `redExitCode`, `greenCommand`,
|
|
47
47
|
`greenExitCode`, and `specUpdates`.
|
|
48
48
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
0.0.
|
|
1
|
+
0.0.43
|
|
@@ -25,6 +25,7 @@ BATCH_STEPS = (
|
|
|
25
25
|
"init_check_context",
|
|
26
26
|
"await_check_result",
|
|
27
27
|
"complete_task",
|
|
28
|
+
"archive_task",
|
|
28
29
|
"commit_task",
|
|
29
30
|
)
|
|
30
31
|
STEP_PHASES = {
|
|
@@ -33,12 +34,14 @@ STEP_PHASES = {
|
|
|
33
34
|
"review_task": "review",
|
|
34
35
|
"await_check_result": "check",
|
|
35
36
|
"complete_task": "complete",
|
|
37
|
+
"archive_task": "archive",
|
|
36
38
|
"commit_task": "commit",
|
|
37
39
|
}
|
|
38
40
|
LIFECYCLE_STATUSES = {
|
|
39
41
|
"start_task": "in_progress",
|
|
40
42
|
"review_task": "review",
|
|
41
43
|
"complete_task": "completed",
|
|
44
|
+
"archive_task": "completed",
|
|
42
45
|
}
|
|
43
46
|
RUNTIME_ROLES = {
|
|
44
47
|
"init_implement_context": "implement",
|
|
@@ -322,6 +325,9 @@ class BatchExecutionService:
|
|
|
322
325
|
f"commit could not be verified: {commit_id}",
|
|
323
326
|
)
|
|
324
327
|
return
|
|
328
|
+
if action_type == "archive_task":
|
|
329
|
+
self._verify_archive_result(task_name, result)
|
|
330
|
+
return
|
|
325
331
|
raise BatchExecutionError(
|
|
326
332
|
"BATCH-ACTION-UNKNOWN-001",
|
|
327
333
|
f"unsupported Batch action: {action_type}",
|
|
@@ -364,6 +370,21 @@ class BatchExecutionService:
|
|
|
364
370
|
f"expected {expected_status}",
|
|
365
371
|
)
|
|
366
372
|
|
|
373
|
+
def _verify_archive_result(
|
|
374
|
+
self,
|
|
375
|
+
task_name: str,
|
|
376
|
+
result: dict,
|
|
377
|
+
) -> None:
|
|
378
|
+
destination = self._optional_text(result, "archive_destination")
|
|
379
|
+
if destination is None:
|
|
380
|
+
return
|
|
381
|
+
archive_path = self.repo_root / destination
|
|
382
|
+
if not archive_path.exists():
|
|
383
|
+
raise BatchExecutionError(
|
|
384
|
+
"BATCH-ARCHIVE-VERIFY-001",
|
|
385
|
+
f"archive destination does not exist: {destination}",
|
|
386
|
+
)
|
|
387
|
+
|
|
367
388
|
def _verify_runtime_initialized(
|
|
368
389
|
self,
|
|
369
390
|
state: dict,
|
|
@@ -96,7 +96,7 @@ def _print_blockers(blockers: list[str]) -> None:
|
|
|
96
96
|
|
|
97
97
|
def _implementation(task_path: str) -> None:
|
|
98
98
|
print("Next action: execute implementation plan")
|
|
99
|
-
print(f"TDD reminder: for behavior changes, write
|
|
99
|
+
print(f"TDD reminder: for behavior changes, write the failing test first and record any red/green evidence in {task_path}/check.jsonl.")
|
|
100
100
|
print(
|
|
101
101
|
f"Command: ./.cowork-flow/run subagent init --role implement "
|
|
102
102
|
f"--agent-type cowork-implement --execution-task-dir {task_path} "
|
|
@@ -43,7 +43,7 @@ def validate_exemption_rationalization(reason: str) -> list[dict]:
|
|
|
43
43
|
f"'{pattern}'. Replace with a concrete behavior test or "
|
|
44
44
|
f"specific verification command."
|
|
45
45
|
),
|
|
46
|
-
"file": "<task>/
|
|
46
|
+
"file": "<task>/check.jsonl",
|
|
47
47
|
"fix_hint": (
|
|
48
48
|
"Remove rationalization from exemption reason. "
|
|
49
49
|
"Either write a failing behavior test for this AC "
|
|
@@ -130,6 +130,7 @@ def _core_gate_definitions() -> tuple[GateDefinition, ...]:
|
|
|
130
130
|
validator_key="tdd_evidence",
|
|
131
131
|
required=True,
|
|
132
132
|
block_message="TDD evidence gate blocked lifecycle transition",
|
|
133
|
+
warning_message="TDD evidence advisories",
|
|
133
134
|
),
|
|
134
135
|
GateDefinition(
|
|
135
136
|
id="test_intent",
|
|
@@ -2,10 +2,9 @@
|
|
|
2
2
|
"""
|
|
3
3
|
TDD 红绿证据校验(task_review / task_complete 阶段)。
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
- validate_tdd_red_evidence: 仅检查 red 证据完整性(implement 入口提醒)
|
|
5
|
+
新任务从 <task>/check.jsonl 读取 TDD evidence 记录,旧任务已有
|
|
6
|
+
<task>/tdd.jsonl 时继续兼容读取并校验。普通行为变更缺少显式 red/green
|
|
7
|
+
证据只产生 warning;高风险行为变更仍要求显式 red/green evidence。
|
|
9
8
|
|
|
10
9
|
acceptanceId 的提取同时支持 AC-001、AC-1、验收标准:1 三种写法。
|
|
11
10
|
"""
|
|
@@ -16,7 +15,12 @@ import json
|
|
|
16
15
|
import re
|
|
17
16
|
from pathlib import Path
|
|
18
17
|
|
|
19
|
-
|
|
18
|
+
CHECK_EVIDENCE_FILE = "check.jsonl"
|
|
19
|
+
LEGACY_EVIDENCE_FILE = "tdd.jsonl"
|
|
20
|
+
EVIDENCE_FILE = CHECK_EVIDENCE_FILE
|
|
21
|
+
|
|
22
|
+
TDD_EVIDENCE_TYPES = frozenset({"tdd", "tdd_evidence"})
|
|
23
|
+
TDD_EXEMPTION_TYPES = frozenset({"tdd_exemption", "exemption"})
|
|
20
24
|
|
|
21
25
|
REQUIRED_EVIDENCE_FIELDS = (
|
|
22
26
|
"acceptanceId",
|
|
@@ -65,6 +69,25 @@ TDD_REQUIRED_MARKERS = (
|
|
|
65
69
|
"修改",
|
|
66
70
|
)
|
|
67
71
|
|
|
72
|
+
STRICT_TDD_MARKERS = (
|
|
73
|
+
"security",
|
|
74
|
+
"permission",
|
|
75
|
+
"auth",
|
|
76
|
+
"protocol",
|
|
77
|
+
"state machine",
|
|
78
|
+
"migration",
|
|
79
|
+
"public contract",
|
|
80
|
+
"file format",
|
|
81
|
+
"安全",
|
|
82
|
+
"权限",
|
|
83
|
+
"认证",
|
|
84
|
+
"协议",
|
|
85
|
+
"状态机",
|
|
86
|
+
"迁移",
|
|
87
|
+
"公共契约",
|
|
88
|
+
"文件格式",
|
|
89
|
+
)
|
|
90
|
+
|
|
68
91
|
NON_BEHAVIOR_MARKERS = (
|
|
69
92
|
"docs-only",
|
|
70
93
|
"documentation_only",
|
|
@@ -92,75 +115,134 @@ def validate_tdd_evidence(task_dir: Path) -> list[dict]:
|
|
|
92
115
|
"""Return TDD evidence violations for a task directory."""
|
|
93
116
|
task_dir = Path(task_dir)
|
|
94
117
|
decision_anchor_text = _read_text(task_dir / "decision-anchor.md")
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
if not evidence_path.is_file():
|
|
98
|
-
if _task_requires_tdd(decision_anchor_text):
|
|
99
|
-
return [
|
|
100
|
-
_violation(
|
|
101
|
-
"TDD-RED-001",
|
|
102
|
-
"TDD evidence file is missing for a behavior-change task",
|
|
103
|
-
evidence_path,
|
|
104
|
-
"Create tdd.jsonl with red/green evidence. For doc-only tasks, add an exemption: "
|
|
105
|
-
'{"acceptanceId":"AC-001","type":"exemption","exemptionType":"doc_only",'
|
|
106
|
-
'"reason":"why no behavior test","verificationCommand":"ls <expected files>"}',
|
|
107
|
-
)
|
|
108
|
-
]
|
|
109
|
-
return []
|
|
110
|
-
|
|
111
|
-
entries, parse_violations = _read_jsonl(evidence_path)
|
|
118
|
+
records, parse_violations, legacy_file_present, legacy_entry_count = _load_tdd_records(task_dir)
|
|
112
119
|
if parse_violations:
|
|
113
120
|
return parse_violations
|
|
114
121
|
|
|
115
|
-
if
|
|
122
|
+
if legacy_file_present and legacy_entry_count == 0:
|
|
116
123
|
return [
|
|
117
124
|
_violation(
|
|
118
125
|
"TDD-RED-002",
|
|
119
|
-
"TDD evidence file is empty",
|
|
120
|
-
|
|
121
|
-
"
|
|
126
|
+
"Legacy TDD evidence file is empty",
|
|
127
|
+
task_dir / LEGACY_EVIDENCE_FILE,
|
|
128
|
+
"Remove the empty legacy file or record TDD evidence in check.jsonl.",
|
|
122
129
|
)
|
|
123
130
|
]
|
|
124
131
|
|
|
132
|
+
if not records:
|
|
133
|
+
if _task_requires_explicit_tdd(decision_anchor_text):
|
|
134
|
+
return [_missing_red_evidence_violation(task_dir / CHECK_EVIDENCE_FILE)]
|
|
135
|
+
if _task_requires_tdd(decision_anchor_text):
|
|
136
|
+
return [_missing_red_evidence_warning(task_dir / CHECK_EVIDENCE_FILE)]
|
|
137
|
+
return []
|
|
138
|
+
|
|
125
139
|
acceptance_ids = _acceptance_ids(decision_anchor_text)
|
|
126
140
|
violations: list[dict] = []
|
|
127
|
-
for
|
|
128
|
-
if entry
|
|
129
|
-
violations.extend(_validate_exemption(entry, evidence_path,
|
|
141
|
+
for record_index, (evidence_path, _line_number, entry) in enumerate(records, start=1):
|
|
142
|
+
if is_tdd_exemption(entry):
|
|
143
|
+
violations.extend(_validate_exemption(entry, evidence_path, record_index, acceptance_ids))
|
|
130
144
|
else:
|
|
131
|
-
violations.extend(_validate_evidence(entry, evidence_path,
|
|
145
|
+
violations.extend(_validate_evidence(entry, evidence_path, record_index, acceptance_ids))
|
|
132
146
|
|
|
133
147
|
return violations
|
|
134
148
|
|
|
135
149
|
|
|
136
150
|
def validate_tdd_red_evidence(task_dir: Path) -> list[dict]:
|
|
137
|
-
"""Return violations
|
|
151
|
+
"""Return violations or warnings for missing red evidence before implementation."""
|
|
138
152
|
task_dir = Path(task_dir)
|
|
139
153
|
decision_anchor_text = _read_text(task_dir / "decision-anchor.md")
|
|
140
154
|
if not _task_requires_tdd(decision_anchor_text):
|
|
141
155
|
return []
|
|
142
156
|
|
|
143
|
-
|
|
144
|
-
if not evidence_path.is_file():
|
|
145
|
-
return [_missing_red_evidence_violation(evidence_path)]
|
|
146
|
-
|
|
147
|
-
entries, parse_violations = _read_jsonl(evidence_path)
|
|
157
|
+
records, parse_violations, _legacy_file_present, _legacy_entry_count = _load_tdd_records(task_dir)
|
|
148
158
|
if parse_violations:
|
|
149
159
|
return parse_violations
|
|
150
160
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
161
|
+
red_records = [
|
|
162
|
+
record
|
|
163
|
+
for record in records
|
|
164
|
+
if not is_tdd_exemption(record[2])
|
|
165
|
+
]
|
|
166
|
+
if not red_records:
|
|
167
|
+
if _task_requires_explicit_tdd(decision_anchor_text):
|
|
168
|
+
return [_missing_red_evidence_violation(task_dir / CHECK_EVIDENCE_FILE)]
|
|
169
|
+
return [_missing_red_evidence_warning(task_dir / CHECK_EVIDENCE_FILE)]
|
|
154
170
|
|
|
155
171
|
acceptance_ids = _acceptance_ids(decision_anchor_text)
|
|
156
172
|
violations: list[dict] = []
|
|
157
|
-
for
|
|
158
|
-
|
|
159
|
-
continue
|
|
160
|
-
violations.extend(_validate_red_evidence(entry, evidence_path, index, acceptance_ids))
|
|
173
|
+
for record_index, (evidence_path, _line_number, entry) in enumerate(red_records, start=1):
|
|
174
|
+
violations.extend(_validate_red_evidence(entry, evidence_path, record_index, acceptance_ids))
|
|
161
175
|
return violations
|
|
162
176
|
|
|
163
177
|
|
|
178
|
+
def tdd_evidence_records(task_dir: Path) -> list[tuple[Path, int, dict]]:
|
|
179
|
+
"""Return check.jsonl and legacy tdd.jsonl records that represent TDD evidence."""
|
|
180
|
+
records, _violations, _legacy_file_present, _legacy_entry_count = _load_tdd_records(Path(task_dir))
|
|
181
|
+
return records
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def migrate_legacy_tdd_records(task_dir: Path) -> dict:
|
|
185
|
+
"""Migrate legacy tdd.jsonl records to check.jsonl and remove the legacy file.
|
|
186
|
+
|
|
187
|
+
Returns a summary dict with:
|
|
188
|
+
- migrated: number of records moved
|
|
189
|
+
- skipped: number of duplicate acceptanceIds already in check.jsonl
|
|
190
|
+
- legacy_removed: whether the legacy file was deleted
|
|
191
|
+
"""
|
|
192
|
+
task_dir = Path(task_dir)
|
|
193
|
+
legacy_path = task_dir / LEGACY_EVIDENCE_FILE
|
|
194
|
+
check_path = task_dir / CHECK_EVIDENCE_FILE
|
|
195
|
+
|
|
196
|
+
if not legacy_path.is_file():
|
|
197
|
+
return {"migrated": 0, "skipped": 0, "legacy_removed": False}
|
|
198
|
+
|
|
199
|
+
migrated = 0
|
|
200
|
+
skipped = 0
|
|
201
|
+
|
|
202
|
+
# Load existing check.jsonl acceptanceIds for dedup
|
|
203
|
+
existing_ids: set[str] = set()
|
|
204
|
+
if check_path.is_file():
|
|
205
|
+
check_entries, _ = _read_jsonl(check_path, report_invalid=False)
|
|
206
|
+
for _, entry in check_entries:
|
|
207
|
+
aid = str(entry.get("acceptanceId", "")).strip()
|
|
208
|
+
if aid:
|
|
209
|
+
existing_ids.add(aid)
|
|
210
|
+
|
|
211
|
+
# Read legacy records
|
|
212
|
+
legacy_entries, _ = _read_jsonl(legacy_path, report_invalid=True)
|
|
213
|
+
new_records: list[dict] = []
|
|
214
|
+
|
|
215
|
+
for _, entry in legacy_entries:
|
|
216
|
+
aid = str(entry.get("acceptanceId", "")).strip()
|
|
217
|
+
if aid and aid in existing_ids:
|
|
218
|
+
skipped += 1
|
|
219
|
+
continue
|
|
220
|
+
new_records.append(entry)
|
|
221
|
+
if aid:
|
|
222
|
+
existing_ids.add(aid)
|
|
223
|
+
migrated += 1
|
|
224
|
+
|
|
225
|
+
# Append to check.jsonl
|
|
226
|
+
if new_records:
|
|
227
|
+
with check_path.open("a", encoding="utf-8") as stream:
|
|
228
|
+
for record in new_records:
|
|
229
|
+
stream.write(json.dumps(record, ensure_ascii=False) + "\n")
|
|
230
|
+
|
|
231
|
+
# Remove legacy file
|
|
232
|
+
legacy_path.unlink()
|
|
233
|
+
|
|
234
|
+
return {
|
|
235
|
+
"migrated": migrated,
|
|
236
|
+
"skipped": skipped,
|
|
237
|
+
"legacy_removed": True,
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def is_tdd_exemption(entry: dict) -> bool:
|
|
242
|
+
entry_type = str(entry.get("type") or "").strip()
|
|
243
|
+
return entry_type in TDD_EXEMPTION_TYPES and _has_value(entry, "exemptionType")
|
|
244
|
+
|
|
245
|
+
|
|
164
246
|
def _task_requires_tdd(decision_anchor_text: str) -> bool:
|
|
165
247
|
lower = decision_anchor_text.lower()
|
|
166
248
|
if not lower:
|
|
@@ -170,23 +252,82 @@ def _task_requires_tdd(decision_anchor_text: str) -> bool:
|
|
|
170
252
|
return any(marker.lower() in lower for marker in TDD_REQUIRED_MARKERS)
|
|
171
253
|
|
|
172
254
|
|
|
255
|
+
def _task_requires_explicit_tdd(decision_anchor_text: str) -> bool:
|
|
256
|
+
lower = decision_anchor_text.lower()
|
|
257
|
+
if not _task_requires_tdd(decision_anchor_text):
|
|
258
|
+
return False
|
|
259
|
+
return any(marker.lower() in lower for marker in STRICT_TDD_MARKERS)
|
|
260
|
+
|
|
261
|
+
|
|
173
262
|
from common.core.files import read_text_utf8 as _read_text
|
|
174
263
|
|
|
175
264
|
|
|
176
|
-
def
|
|
177
|
-
|
|
265
|
+
def _load_tdd_records(task_dir: Path) -> tuple[list[tuple[Path, int, dict]], list[dict], bool, int]:
|
|
266
|
+
records: list[tuple[Path, int, dict]] = []
|
|
267
|
+
violations: list[dict] = []
|
|
268
|
+
|
|
269
|
+
legacy_path = task_dir / LEGACY_EVIDENCE_FILE
|
|
270
|
+
legacy_entry_count = 0
|
|
271
|
+
if legacy_path.is_file():
|
|
272
|
+
legacy_entries, legacy_violations = _read_jsonl(legacy_path, report_invalid=True)
|
|
273
|
+
violations.extend(legacy_violations)
|
|
274
|
+
legacy_entry_count = len(legacy_entries)
|
|
275
|
+
records.extend((legacy_path, line_number, entry) for line_number, entry in legacy_entries)
|
|
276
|
+
violations.append(
|
|
277
|
+
{
|
|
278
|
+
"rule_id": "TDD-LEGACY-001",
|
|
279
|
+
"type": "tdd_legacy_detection",
|
|
280
|
+
"severity": "warn",
|
|
281
|
+
"passed": False,
|
|
282
|
+
"message": (
|
|
283
|
+
"Legacy tdd.jsonl detected. Migrate evidence to check.jsonl "
|
|
284
|
+
"using migrate_legacy_tdd_records(task_dir)."
|
|
285
|
+
),
|
|
286
|
+
"file": str(legacy_path),
|
|
287
|
+
"fix_hint": (
|
|
288
|
+
"Run migrate_legacy_tdd_records(task_dir) to move records "
|
|
289
|
+
"to check.jsonl and remove the legacy file."
|
|
290
|
+
),
|
|
291
|
+
}
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
check_path = task_dir / CHECK_EVIDENCE_FILE
|
|
295
|
+
if check_path.is_file():
|
|
296
|
+
check_entries, _check_violations = _read_jsonl(check_path, report_invalid=False)
|
|
297
|
+
records.extend(
|
|
298
|
+
(check_path, line_number, entry)
|
|
299
|
+
for line_number, entry in check_entries
|
|
300
|
+
if _is_tdd_check_record(entry)
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
return records, violations, legacy_path.is_file(), legacy_entry_count
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def _is_tdd_check_record(entry: dict) -> bool:
|
|
307
|
+
entry_type = str(entry.get("type") or "").strip()
|
|
308
|
+
if entry_type in TDD_EVIDENCE_TYPES or entry_type == "tdd_exemption":
|
|
309
|
+
return True
|
|
310
|
+
if is_tdd_exemption(entry):
|
|
311
|
+
return True
|
|
312
|
+
return any(field in entry for field in ("redCommand", "greenCommand", "redExitCode", "greenExitCode"))
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def _read_jsonl(path: Path, *, report_invalid: bool) -> tuple[list[tuple[int, dict]], list[dict]]:
|
|
316
|
+
entries: list[tuple[int, dict]] = []
|
|
178
317
|
violations: list[dict] = []
|
|
179
318
|
try:
|
|
180
319
|
lines = path.read_text(encoding="utf-8").splitlines()
|
|
181
320
|
except (OSError, UnicodeDecodeError):
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
321
|
+
if report_invalid:
|
|
322
|
+
violations.append(
|
|
323
|
+
_violation(
|
|
324
|
+
"TDD-READ-001",
|
|
325
|
+
"Failed to read TDD evidence as UTF-8",
|
|
326
|
+
path,
|
|
327
|
+
"Rewrite the evidence file with UTF-8 encoding.",
|
|
328
|
+
)
|
|
188
329
|
)
|
|
189
|
-
|
|
330
|
+
return entries, violations
|
|
190
331
|
|
|
191
332
|
for line_number, line in enumerate(lines, start=1):
|
|
192
333
|
if not line.strip():
|
|
@@ -194,26 +335,28 @@ def _read_jsonl(path: Path) -> tuple[list[dict], list[dict]]:
|
|
|
194
335
|
try:
|
|
195
336
|
data = json.loads(line)
|
|
196
337
|
except json.JSONDecodeError:
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
338
|
+
if report_invalid:
|
|
339
|
+
violations.append(
|
|
340
|
+
_violation(
|
|
341
|
+
"TDD-FORMAT-001",
|
|
342
|
+
f"Invalid JSON in TDD evidence line {line_number}",
|
|
343
|
+
path,
|
|
344
|
+
"Write one valid JSON object per line.",
|
|
345
|
+
)
|
|
203
346
|
)
|
|
204
|
-
)
|
|
205
347
|
continue
|
|
206
348
|
if not isinstance(data, dict):
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
349
|
+
if report_invalid:
|
|
350
|
+
violations.append(
|
|
351
|
+
_violation(
|
|
352
|
+
"TDD-FORMAT-002",
|
|
353
|
+
f"TDD evidence line {line_number} must be a JSON object",
|
|
354
|
+
path,
|
|
355
|
+
"Replace non-object entries with structured evidence records.",
|
|
356
|
+
)
|
|
213
357
|
)
|
|
214
|
-
)
|
|
215
358
|
continue
|
|
216
|
-
entries.append(data)
|
|
359
|
+
entries.append((line_number, data))
|
|
217
360
|
|
|
218
361
|
return entries, violations
|
|
219
362
|
|
|
@@ -316,9 +459,19 @@ def _validate_red_evidence(
|
|
|
316
459
|
def _missing_red_evidence_violation(evidence_path: Path) -> dict:
|
|
317
460
|
return _violation(
|
|
318
461
|
"TDD-RED-001",
|
|
319
|
-
"TDD red evidence is missing for a behavior-change task",
|
|
462
|
+
"TDD red-green evidence is missing for a high-risk behavior-change task",
|
|
463
|
+
evidence_path,
|
|
464
|
+
"Record a tdd evidence object in check.jsonl with redCommand, redExitCode, greenCommand, greenExitCode, and acceptanceId.",
|
|
465
|
+
)
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
def _missing_red_evidence_warning(evidence_path: Path) -> dict:
|
|
469
|
+
return _violation(
|
|
470
|
+
"TDD-RED-WARN-001",
|
|
471
|
+
"TDD red-green evidence is not recorded for this behavior-change task",
|
|
320
472
|
evidence_path,
|
|
321
|
-
"
|
|
473
|
+
"Prefer recording red/green evidence in check.jsonl when the change was developed TDD-first.",
|
|
474
|
+
severity="warn",
|
|
322
475
|
)
|
|
323
476
|
|
|
324
477
|
|
|
@@ -366,11 +519,18 @@ def _has_value(entry: dict, field: str) -> bool:
|
|
|
366
519
|
return True
|
|
367
520
|
|
|
368
521
|
|
|
369
|
-
def _violation(
|
|
522
|
+
def _violation(
|
|
523
|
+
rule_id: str,
|
|
524
|
+
message: str,
|
|
525
|
+
file_path: Path,
|
|
526
|
+
fix_hint: str,
|
|
527
|
+
*,
|
|
528
|
+
severity: str = "block",
|
|
529
|
+
) -> dict:
|
|
370
530
|
return {
|
|
371
531
|
"rule_id": rule_id,
|
|
372
532
|
"type": "tdd_evidence",
|
|
373
|
-
"severity":
|
|
533
|
+
"severity": severity,
|
|
374
534
|
"passed": False,
|
|
375
535
|
"message": message,
|
|
376
536
|
"file": str(file_path),
|
|
@@ -5,11 +5,10 @@ from __future__ import annotations
|
|
|
5
5
|
|
|
6
6
|
import ast
|
|
7
7
|
import io
|
|
8
|
-
import json
|
|
9
8
|
import tokenize
|
|
10
9
|
from pathlib import Path
|
|
11
10
|
|
|
12
|
-
from .tdd_evidence import
|
|
11
|
+
from .tdd_evidence import is_tdd_exemption, tdd_evidence_records
|
|
13
12
|
|
|
14
13
|
REPORT_FIELD = "test_intent_review"
|
|
15
14
|
|
|
@@ -47,15 +46,14 @@ BLOCK_MARKERS = (
|
|
|
47
46
|
|
|
48
47
|
|
|
49
48
|
def validate_test_intent(repo_root: Path, task_dir: Path) -> list[dict]:
|
|
50
|
-
"""Return test intent warnings or blocks for
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
if not entries:
|
|
49
|
+
"""Return test intent warnings or blocks for TDD evidence records."""
|
|
50
|
+
records = tdd_evidence_records(Path(task_dir))
|
|
51
|
+
if not records:
|
|
54
52
|
return []
|
|
55
53
|
|
|
56
54
|
violations: list[dict] = []
|
|
57
|
-
for index, entry in enumerate(
|
|
58
|
-
if entry
|
|
55
|
+
for index, (evidence_path, _line_number, entry) in enumerate(records, start=1):
|
|
56
|
+
if is_tdd_exemption(entry):
|
|
59
57
|
continue
|
|
60
58
|
|
|
61
59
|
test_file = entry.get("testFile")
|
|
@@ -121,28 +119,6 @@ def validate_test_intent(repo_root: Path, task_dir: Path) -> list[dict]:
|
|
|
121
119
|
return violations
|
|
122
120
|
|
|
123
121
|
|
|
124
|
-
def _read_tdd_entries(path: Path) -> list[dict]:
|
|
125
|
-
if not path.is_file():
|
|
126
|
-
return []
|
|
127
|
-
|
|
128
|
-
entries: list[dict] = []
|
|
129
|
-
try:
|
|
130
|
-
lines = path.read_text(encoding="utf-8").splitlines()
|
|
131
|
-
except (OSError, UnicodeDecodeError):
|
|
132
|
-
return []
|
|
133
|
-
|
|
134
|
-
for line in lines:
|
|
135
|
-
if not line.strip():
|
|
136
|
-
continue
|
|
137
|
-
try:
|
|
138
|
-
data = json.loads(line)
|
|
139
|
-
except json.JSONDecodeError:
|
|
140
|
-
continue
|
|
141
|
-
if isinstance(data, dict):
|
|
142
|
-
entries.append(data)
|
|
143
|
-
return entries
|
|
144
|
-
|
|
145
|
-
|
|
146
122
|
from common.core.files import read_text_utf8 as _read_text
|
|
147
123
|
|
|
148
124
|
|
|
@@ -50,9 +50,9 @@
|
|
|
50
50
|
|
|
51
51
|
## 产物压缩
|
|
52
52
|
|
|
53
|
-
合并前(6个文件):`prd.md` + `implement.jsonl` + `check.jsonl` + `debug.jsonl` + `
|
|
53
|
+
合并前(6个文件):`prd.md` + `implement.jsonl` + `check.jsonl` + `debug.jsonl` + `task.json`
|
|
54
54
|
|
|
55
|
-
合并后(5个文件):`decision-anchor.md` + `implement.jsonl` + `check.jsonl` + `debug.jsonl` + `
|
|
55
|
+
合并后(5个文件):`decision-anchor.md` + `implement.jsonl` + `check.jsonl` + `debug.jsonl` + `task.json`
|
|
56
56
|
|
|
57
57
|
**净减 1 个文件类型。相关文件追踪现在由 implement.jsonl 统一负责。**
|
|
58
58
|
|
|
@@ -20,7 +20,7 @@ This section is the authoritative red-green-refactor contract.
|
|
|
20
20
|
|
|
21
21
|
## Output
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
When red/green evidence is useful or required, write one JSON object per line to `<task>/check.jsonl` with `type: "tdd"` and:
|
|
24
24
|
|
|
25
25
|
- `acceptanceId`, `testFile`, `testName`
|
|
26
26
|
- `redCommand`, `redExitCode`, `redOutputExcerpt`, `failureReason`
|
|
@@ -31,12 +31,12 @@ Only docs/comment/format-only work may use a documented `exemption`.
|
|
|
31
31
|
|
|
32
32
|
## Anti-Rationalization
|
|
33
33
|
|
|
34
|
-
The following excuses do not exempt
|
|
34
|
+
The following excuses do not exempt meaningful behavior testing or required high-risk TDD evidence:
|
|
35
35
|
|
|
36
36
|
| Excuse | Why It Fails | Required Alternative |
|
|
37
37
|
|---|---|---|
|
|
38
38
|
| "This logic is simple, no test needed" | Simple logic still breaks on edge cases and later edits. | Write the smallest behavior test for the acceptance ID. |
|
|
39
|
-
| "Other tests already cover this" | Invisible coverage is not red-green evidence. | Point to the exact redCommand and acceptanceId in `
|
|
39
|
+
| "Other tests already cover this" | Invisible coverage is not red-green evidence. | Point to the exact redCommand and acceptanceId in `check.jsonl`. |
|
|
40
40
|
| "I'll add tests after implementation" | Post-implementation tests are not a red-green cycle. | Red first, then green; record both commands. |
|
|
41
41
|
| "It looks correct" | Visual inspection misses scenarios that tests can pin down. | Turn the expected behavior and edge case into assertions. |
|
|
42
42
|
| "I've written similar tests" | Similar structure is not the same behavior. | Re-run the redCommand for this acceptanceId. |
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
| # | 检查项 | 验证 |
|
|
8
8
|
|---|---|---|
|
|
9
9
|
| G1 | 代码实现符合 decision-anchor acceptance criteria | AC 全部为 verified |
|
|
10
|
-
| G2 |
|
|
10
|
+
| G2 | 行为变更有对应测试;高风险任务在 check.jsonl 有 red-green evidence | ./cowork-flow/run task validate |
|
|
11
11
|
| G3 | check phase 完成,无 unresolved blocker | check.jsonl status=pass |
|
|
12
12
|
| G4 | 无新引入 lint/type/build 警告 | lint / build 命令通过 |
|
|
13
13
|
| G5 | Git status snapshot 仅含任务相关变更,包括 staged、unstaged、untracked | git status --porcelain=v1 -uall |
|
|
@@ -194,7 +194,6 @@
|
|
|
194
194
|
".agents/skills/finish-work",
|
|
195
195
|
".agents/skills/party-mode-v2",
|
|
196
196
|
".agents/skills/start",
|
|
197
|
-
".agents/skills/tdd",
|
|
198
197
|
".agents/skills/update-spec",
|
|
199
198
|
".agents/skills/using-cowork-flow",
|
|
200
199
|
".claude/skills/before-dev",
|
|
@@ -204,7 +203,6 @@
|
|
|
204
203
|
".claude/skills/finish-work",
|
|
205
204
|
".claude/skills/party-mode-v2",
|
|
206
205
|
".claude/skills/start",
|
|
207
|
-
".claude/skills/tdd",
|
|
208
206
|
".claude/skills/update-spec",
|
|
209
207
|
".claude/skills/using-cowork-flow"
|
|
210
208
|
]
|
|
@@ -324,6 +324,29 @@
|
|
|
324
324
|
"source": ".cowork-flow/scripts/common/gates/gates.py",
|
|
325
325
|
"managedPaths": []
|
|
326
326
|
},
|
|
327
|
+
{
|
|
328
|
+
"id": "tdd",
|
|
329
|
+
"displayName": "TDD",
|
|
330
|
+
"aliases": [],
|
|
331
|
+
"kind": "protocol",
|
|
332
|
+
"visibility": "public",
|
|
333
|
+
"status": "active",
|
|
334
|
+
"statuses": [
|
|
335
|
+
"in_progress"
|
|
336
|
+
],
|
|
337
|
+
"intents": [
|
|
338
|
+
"apply_tdd"
|
|
339
|
+
],
|
|
340
|
+
"enforcement": "advisory",
|
|
341
|
+
"runtimeGate": null,
|
|
342
|
+
"runtimeCommand": null,
|
|
343
|
+
"evidenceArtifact": "check.jsonl",
|
|
344
|
+
"source": "skills/tdd/SKILL.md",
|
|
345
|
+
"managedPaths": [
|
|
346
|
+
".agents/skills/tdd/",
|
|
347
|
+
".claude/skills/tdd/"
|
|
348
|
+
]
|
|
349
|
+
},
|
|
327
350
|
{
|
|
328
351
|
"id": "tdd-protocol",
|
|
329
352
|
"displayName": "TDD Protocol",
|
|
@@ -340,7 +363,7 @@
|
|
|
340
363
|
"enforcement": "mandatory",
|
|
341
364
|
"runtimeGate": "tdd-evidence",
|
|
342
365
|
"runtimeCommand": null,
|
|
343
|
-
"evidenceArtifact": "
|
|
366
|
+
"evidenceArtifact": "check.jsonl",
|
|
344
367
|
"source": ".cowork-flow/spec/protocols/tdd.md",
|
|
345
368
|
"managedPaths": []
|
|
346
369
|
},
|
|
@@ -356,7 +379,7 @@
|
|
|
356
379
|
"enforcement": "runtime",
|
|
357
380
|
"runtimeGate": null,
|
|
358
381
|
"runtimeCommand": "task review",
|
|
359
|
-
"evidenceArtifact": "
|
|
382
|
+
"evidenceArtifact": "check.jsonl",
|
|
360
383
|
"source": ".cowork-flow/scripts/common/gates/tdd_evidence.py",
|
|
361
384
|
"managedPaths": []
|
|
362
385
|
},
|
|
@@ -21,7 +21,7 @@ changes -> brainstorming -> read spec -> plan -> tasks -> implement -> check ->
|
|
|
21
21
|
|
|
22
22
|
`cowork-flow` 只保存项目状态、任务上下文、宿主适配器契约和恢复线索;实际执行由主会话和固定 `cowork-*` 代理完成。宿主工具名只写在 `.cowork-flow/adapters/<host>/adapter.yaml`,不进入流程分支。
|
|
23
23
|
|
|
24
|
-
`task next` 是主会话的阶段导航器。进入任务阶段、恢复会话、派发实现、进入检查、完成收口前,先运行 `./.cowork-flow/run task next` 或 `.\.cowork-flow\run.cmd task next`,用当前任务和 `task.json.status`
|
|
24
|
+
`task next` 是主会话的阶段导航器。进入任务阶段、恢复会话、派发实现、进入检查、完成收口前,先运行 `./.cowork-flow/run task next` 或 `.\.cowork-flow\run.cmd task next`,用当前任务和 `task.json.status` 决定下一步。该命令只读,不推进状态,也不阻断固定代理派发;实现阶段会提示行为变更先写失败测试,并把必要的 red/green evidence 记录到 `check.jsonl`。
|
|
25
25
|
|
|
26
26
|
## 1.1 状态注入与入口分类
|
|
27
27
|
|
|
@@ -188,12 +188,12 @@ L2 任务在 `task start` 前必须通过 readiness gate;同一 blocker 列表
|
|
|
188
188
|
## 6. 实现阶段
|
|
189
189
|
|
|
190
190
|
1. 先运行 `task next` 确认当前状态和下一步命令。
|
|
191
|
-
2.
|
|
191
|
+
2. 行为变更任务先写失败测试;普通任务可把 red/green evidence 作为 `type: "tdd"` 记录写入 `<task>/check.jsonl`,高风险任务必须显式记录。`task next` 会在实现步骤给出非阻断提醒。
|
|
192
192
|
3. 默认通过宿主适配器派发 `cowork-implement`。派发必须使用新鲜子上下文,并遵守 `.cowork-flow/spec/contracts/subagent-dispatch.md`。
|
|
193
193
|
4. 派发内容应包含当前计划步骤、范围边界和期望验证命令。
|
|
194
194
|
5. 如果用户明确要求主会话内联执行,或当前任务正在修改子代理/运行时行为,可以不派发 `cowork-implement`,但必须说明原因,并仍按计划与测试循环推进。
|
|
195
195
|
6. 涉及行为变化时,先写失败测试,再实现,再验证变绿。
|
|
196
|
-
7. 实现完成并通过本阶段验证后运行 `./.cowork-flow/run task review [task-dir]`,由 review
|
|
196
|
+
7. 实现完成并通过本阶段验证后运行 `./.cowork-flow/run task review [task-dir]`,由 review 门禁验收测试意图、TDD/check evidence 和实现范围,再把任务推进到检查阶段。
|
|
197
197
|
|
|
198
198
|
## 7. 检查阶段
|
|
199
199
|
|
|
@@ -237,6 +237,7 @@ L2 任务在 `task start` 前必须通过 readiness gate;同一 blocker 列表
|
|
|
237
237
|
第二个提交是明确的 follow-up metadata commit;session 中的 `commit` 始终指向前一个真实实现提交,不使用永久占位值。
|
|
238
238
|
`task archive <task-name>` 会归档 task,并自动归档 `change.yaml.task`
|
|
239
239
|
指向该 task 的 active change;无法通过 `change validate` 的 change 不会被自动归档。
|
|
240
|
+
注:batch 模式(`task start <parent> --auto --approved`)已在 `complete_task` 与 `commit_task` 之间自动执行 `archive_task`,无需手动归档。
|
|
240
241
|
|
|
241
242
|
## 9. 恢复规则
|
|
242
243
|
|
|
@@ -43,7 +43,7 @@ Read and apply:
|
|
|
43
43
|
- `.cowork-flow/spec/protocols/decision-review.md`
|
|
44
44
|
- `.cowork-flow/spec/protocols/spec-maintenance.md`
|
|
45
45
|
|
|
46
|
-
Their contracts are Host-neutral. Implementation reports and `
|
|
46
|
+
Their contracts are Host-neutral. Implementation reports and TDD entries in `check.jsonl` must
|
|
47
47
|
preserve `acceptanceId`, `redCommand`, `redExitCode`, `greenCommand`,
|
|
48
48
|
`greenExitCode`, and `specUpdates`. A behavior change requires
|
|
49
49
|
red-green-refactor evidence or a valid documented exemption.
|
|
@@ -21,7 +21,7 @@ changes -> brainstorming -> read spec -> plan -> tasks -> implement -> check ->
|
|
|
21
21
|
|
|
22
22
|
`cowork-flow` 只保存项目状态、任务上下文、宿主适配器契约和恢复线索;实际执行由主会话和固定 `cowork-*` 代理完成。宿主工具名只写在 `.cowork-flow/adapters/<host>/adapter.yaml`,不进入流程分支。
|
|
23
23
|
|
|
24
|
-
`task next` 是主会话的阶段导航器。进入任务阶段、恢复会话、派发实现、进入检查、完成收口前,先运行 `./.cowork-flow/run task next` 或 `.\.cowork-flow\run.cmd task next`,用当前任务和 `task.json.status`
|
|
24
|
+
`task next` 是主会话的阶段导航器。进入任务阶段、恢复会话、派发实现、进入检查、完成收口前,先运行 `./.cowork-flow/run task next` 或 `.\.cowork-flow\run.cmd task next`,用当前任务和 `task.json.status` 决定下一步。该命令只读,不推进状态,也不阻断固定代理派发;实现阶段会提示行为变更先写失败测试,并把必要的 red/green evidence 记录到 `check.jsonl`。
|
|
25
25
|
|
|
26
26
|
## 1.1 状态注入与入口分类
|
|
27
27
|
|
|
@@ -188,12 +188,12 @@ L2 任务在 `task start` 前必须通过 readiness gate;同一 blocker 列表
|
|
|
188
188
|
## 6. 实现阶段
|
|
189
189
|
|
|
190
190
|
1. 先运行 `task next` 确认当前状态和下一步命令。
|
|
191
|
-
2.
|
|
191
|
+
2. 行为变更任务先写失败测试;普通任务可把 red/green evidence 作为 `type: "tdd"` 记录写入 `<task>/check.jsonl`,高风险任务必须显式记录。`task next` 会在实现步骤给出非阻断提醒。
|
|
192
192
|
3. 默认通过宿主适配器派发 `cowork-implement`。派发必须使用新鲜子上下文,并遵守 `.cowork-flow/spec/contracts/subagent-dispatch.md`。
|
|
193
193
|
4. 派发内容应包含当前计划步骤、范围边界和期望验证命令。
|
|
194
194
|
5. 如果用户明确要求主会话内联执行,或当前任务正在修改子代理/运行时行为,可以不派发 `cowork-implement`,但必须说明原因,并仍按计划与测试循环推进。
|
|
195
195
|
6. 涉及行为变化时,先写失败测试,再实现,再验证变绿。
|
|
196
|
-
7. 实现完成并通过本阶段验证后运行 `./.cowork-flow/run task review [task-dir]`,由 review
|
|
196
|
+
7. 实现完成并通过本阶段验证后运行 `./.cowork-flow/run task review [task-dir]`,由 review 门禁验收测试意图、TDD/check evidence 和实现范围,再把任务推进到检查阶段。
|
|
197
197
|
|
|
198
198
|
## 7. 检查阶段
|
|
199
199
|
|
|
@@ -237,6 +237,7 @@ L2 任务在 `task start` 前必须通过 readiness gate;同一 blocker 列表
|
|
|
237
237
|
第二个提交是明确的 follow-up metadata commit;session 中的 `commit` 始终指向前一个真实实现提交,不使用永久占位值。
|
|
238
238
|
`task archive <task-name>` 会归档 task,并自动归档 `change.yaml.task`
|
|
239
239
|
指向该 task 的 active change;无法通过 `change validate` 的 change 不会被自动归档。
|
|
240
|
+
注:batch 模式(`task start <parent> --auto --approved`)已在 `complete_task` 与 `commit_task` 之间自动执行 `archive_task`,无需手动归档。
|
|
240
241
|
|
|
241
242
|
## 9. 恢复规则
|
|
242
243
|
|
|
@@ -1,66 +1,64 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: batch-mode
|
|
3
|
-
description:
|
|
3
|
+
description: Use when the user approves a full plan and asks for continuous execution across the task graph; each task still goes through Gate, implementation, check, completion, and commit independently.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Batch Mode
|
|
7
7
|
|
|
8
|
-
##
|
|
8
|
+
## Core Contract
|
|
9
9
|
|
|
10
|
-
Batch Scheduler
|
|
10
|
+
Batch Scheduler is a persistent state machine. Do **not** simulate or check completion inside the CLI.
|
|
11
11
|
|
|
12
|
-
-
|
|
13
|
-
- `implement.jsonl
|
|
14
|
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
- 已完成的任务、阶段和提交不会在恢复时重复执行。
|
|
12
|
+
- Task ordering comes **only** from the topological order of leaf tasks in the change/task graph.
|
|
13
|
+
- `implement.jsonl` and `check.jsonl` serve **only** as current-task context and Gate evidence; they must **not** be used as the Batch task list.
|
|
14
|
+
- Only one host-neutral `next_action` is published at a time.
|
|
15
|
+
- After the host executes a real action, results must be written back; the state machine validates repository state before advancing.
|
|
16
|
+
- Any failure in Gate, binding, implementation, check, test, completion, or commit **pauses** the batch.
|
|
17
|
+
- Completed tasks, phases, and commits are never re-executed on resume.
|
|
19
18
|
|
|
20
|
-
##
|
|
19
|
+
## Startup Conditions
|
|
21
20
|
|
|
22
|
-
|
|
21
|
+
Start only when **all** conditions are met:
|
|
23
22
|
|
|
24
|
-
1.
|
|
25
|
-
2.
|
|
26
|
-
3.
|
|
27
|
-
4.
|
|
23
|
+
1. The user explicitly approves the plan and asks for automatic continuous execution.
|
|
24
|
+
2. A valid task graph links the parent task and all leaf tasks.
|
|
25
|
+
3. The current session is main/coordinator — not a worker or delegated subagent.
|
|
26
|
+
4. The host can perform task, subagent, and Git actions.
|
|
28
27
|
|
|
29
|
-
|
|
28
|
+
Startup command:
|
|
30
29
|
|
|
31
30
|
```bash
|
|
32
31
|
./.cowork-flow/run task start <parent-task> --auto --approved
|
|
33
32
|
```
|
|
34
33
|
|
|
35
|
-
|
|
34
|
+
The command returns the full Batch state, where `next_action` is the **only** allowed next step.
|
|
36
35
|
|
|
37
|
-
## Host Action
|
|
36
|
+
## Host Action Loop
|
|
38
37
|
|
|
39
|
-
|
|
38
|
+
Each cycle processes in this order:
|
|
40
39
|
|
|
41
|
-
1.
|
|
42
|
-
2.
|
|
43
|
-
3.
|
|
44
|
-
4.
|
|
40
|
+
1. Read `next_action`; verify `action_id`, `type`, `task`, and `task_dir`.
|
|
41
|
+
2. Execute **only** that action — do not advance to later phases prematurely.
|
|
42
|
+
3. Write the result to a UTF-8 JSON file.
|
|
43
|
+
4. Write back the result:
|
|
45
44
|
|
|
46
45
|
```bash
|
|
47
46
|
./.cowork-flow/run task batch-record-result <batch-id> --file <result.json>
|
|
48
47
|
```
|
|
49
48
|
|
|
50
|
-
5.
|
|
51
|
-
停止后续任务。
|
|
49
|
+
5. Read the new state. If `awaiting_host`, continue with the new `next_action`. If `paused`, halt all subsequent tasks.
|
|
52
50
|
|
|
53
|
-
## Action
|
|
51
|
+
## Action Types
|
|
54
52
|
|
|
55
53
|
### `start_task`
|
|
56
54
|
|
|
57
|
-
|
|
55
|
+
Run the real task startup command and pass through the readiness/spec/TDD Gate:
|
|
58
56
|
|
|
59
57
|
```bash
|
|
60
58
|
./.cowork-flow/run task start <task_dir>
|
|
61
59
|
```
|
|
62
60
|
|
|
63
|
-
|
|
61
|
+
A successful result must contain:
|
|
64
62
|
|
|
65
63
|
```json
|
|
66
64
|
{
|
|
@@ -71,11 +69,11 @@ Batch Scheduler 是持久化状态机,不在 CLI 内模拟实现或检查完
|
|
|
71
69
|
}
|
|
72
70
|
```
|
|
73
71
|
|
|
74
|
-
|
|
72
|
+
The state machine also reads `task.json` and trusts only the genuine `in_progress` status.
|
|
75
73
|
|
|
76
74
|
### `init_implement_context` / `init_check_context`
|
|
77
75
|
|
|
78
|
-
|
|
76
|
+
Run with `role`, `agent_type`, `task_dir`, and `title` from the action:
|
|
79
77
|
|
|
80
78
|
```bash
|
|
81
79
|
./.cowork-flow/run subagent init \
|
|
@@ -85,11 +83,9 @@ Batch Scheduler 是持久化状态机,不在 CLI 内模拟实现或检查完
|
|
|
85
83
|
--title "<title>"
|
|
86
84
|
```
|
|
87
85
|
|
|
88
|
-
|
|
89
|
-
`cowork_host_context_key` 派发对应 worker,并让 worker 首步执行
|
|
90
|
-
`subagent bind`。
|
|
86
|
+
The host must use the returned `cowork_runtime_context_id` and `cowork_host_context_key` to dispatch the matching worker, and the worker's first step must run `subagent bind`.
|
|
91
87
|
|
|
92
|
-
|
|
88
|
+
Write back the initialization result:
|
|
93
89
|
|
|
94
90
|
```json
|
|
95
91
|
{
|
|
@@ -101,39 +97,58 @@ Host 必须使用返回的 `cowork_runtime_context_id` 与
|
|
|
101
97
|
}
|
|
102
98
|
```
|
|
103
99
|
|
|
104
|
-
|
|
100
|
+
For the check phase, change `type` to `init_check_context`.
|
|
105
101
|
|
|
106
102
|
### `await_implement_result` / `await_check_result`
|
|
107
103
|
|
|
108
|
-
|
|
104
|
+
Wait for the corresponding worker to finish, and verify the output belongs to the current task and the runtime context is bound to the expected host.
|
|
109
105
|
|
|
110
|
-
|
|
111
|
-
`host_context_key`。状态机验证 `status=bound` 后关闭 runtime context;
|
|
112
|
-
如果关闭成功但 Batch checkpoint 尚未落盘,重复回写仍可恢复。
|
|
106
|
+
A successful result must contain the same `runtime_context_id` and `host_context_key` as the action. The state machine verifies `status=bound` before closing the runtime context. If close succeeds but the Batch checkpoint has not yet been flushed, repeating the write-back can still recover.
|
|
113
107
|
|
|
114
108
|
### `review_task`
|
|
115
109
|
|
|
116
|
-
|
|
110
|
+
Run:
|
|
117
111
|
|
|
118
112
|
```bash
|
|
119
113
|
./.cowork-flow/run task review <task_dir>
|
|
120
114
|
```
|
|
121
115
|
|
|
122
|
-
|
|
116
|
+
The successful result's `task_status` must be `review`; the state machine also reads the genuine `task.json`.
|
|
123
117
|
|
|
124
118
|
### `complete_task`
|
|
125
119
|
|
|
126
|
-
|
|
120
|
+
Run:
|
|
127
121
|
|
|
128
122
|
```bash
|
|
129
123
|
./.cowork-flow/run task complete <task_dir>
|
|
130
124
|
```
|
|
131
125
|
|
|
132
|
-
|
|
126
|
+
The successful result's `task_status` must be `completed`; the state machine also reads the genuine `task.json`.
|
|
127
|
+
|
|
128
|
+
### `archive_task`
|
|
129
|
+
|
|
130
|
+
Run the real task archive command to move the completed task to `archive/<year-month>/`:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
./.cowork-flow/run task archive <task_name>
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
The host must verify the task directory has been moved to the archive location. A successful result must contain:
|
|
137
|
+
|
|
138
|
+
```json
|
|
139
|
+
{
|
|
140
|
+
"action_id": "<action-id>",
|
|
141
|
+
"type": "archive_task",
|
|
142
|
+
"outcome": "success",
|
|
143
|
+
"archive_destination": ".cowork-flow/tasks/archive/<year-month>/<task_name>"
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
The state machine verifies the destination directory exists on disk. If the archive fails (task directory not found, move conflict, or linked-change validation failure), write back a failure result and the batch pauses. On resume, only `archive_task` is retried — `complete_task` is not re-executed.
|
|
133
148
|
|
|
134
149
|
### `commit_task`
|
|
135
150
|
|
|
136
|
-
|
|
151
|
+
Commit only the current task's verified changes, and write back the real commit id:
|
|
137
152
|
|
|
138
153
|
```json
|
|
139
154
|
{
|
|
@@ -144,12 +159,11 @@ Host 必须使用返回的 `cowork_runtime_context_id` 与
|
|
|
144
159
|
}
|
|
145
160
|
```
|
|
146
161
|
|
|
147
|
-
|
|
148
|
-
都会暂停。
|
|
162
|
+
The state machine uses Git to verify the object is genuinely a commit. Empty values, placeholder strings, or non-existent commits all cause a pause.
|
|
149
163
|
|
|
150
|
-
##
|
|
164
|
+
## Failure and Recovery
|
|
151
165
|
|
|
152
|
-
|
|
166
|
+
When the host is unavailable or any action fails, write back a non-success result:
|
|
153
167
|
|
|
154
168
|
```json
|
|
155
169
|
{
|
|
@@ -160,22 +174,21 @@ Host 不可用或任一动作失败时,回写非 success 结果:
|
|
|
160
174
|
}
|
|
161
175
|
```
|
|
162
176
|
|
|
163
|
-
|
|
177
|
+
The state becomes `paused`. The failed task and all subsequent tasks are **not** marked completed.
|
|
164
178
|
|
|
165
|
-
|
|
179
|
+
After fixing the cause, run:
|
|
166
180
|
|
|
167
181
|
```bash
|
|
168
182
|
./.cowork-flow/run task batch-resume <batch-id>
|
|
169
183
|
```
|
|
170
184
|
|
|
171
|
-
|
|
172
|
-
不要跳过失败任务,不要手工修改 Batch 状态文件。
|
|
185
|
+
Resume generates a new `action_id` but preserves completed tasks, phases, runtime evidence, and commits. Do **not** skip the failed task. Do **not** manually edit the Batch state file.
|
|
173
186
|
|
|
174
|
-
##
|
|
187
|
+
## Completion Verification
|
|
175
188
|
|
|
176
|
-
Batch
|
|
189
|
+
Once the Batch state becomes `completed`:
|
|
177
190
|
|
|
178
|
-
1.
|
|
179
|
-
2.
|
|
180
|
-
3.
|
|
181
|
-
4.
|
|
191
|
+
1. Run the project's full test and build commands.
|
|
192
|
+
2. Verify `completed_tasks`, `task_phases`, and `commits` correspond one-to-one.
|
|
193
|
+
3. Verify each leaf task has its own independent commit.
|
|
194
|
+
4. Run spec sync, doctor, and pre-release checks.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: tdd
|
|
3
|
+
description: Use when implementing behavior changes with red-green-refactor discipline. Guides test-first development and records optional or high-risk red/green evidence in check.jsonl without replacing cowork-flow routing.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# TDD
|
|
7
|
+
|
|
8
|
+
Use this Skill to practice test-first implementation inside the active cowork-flow task. This Skill does not start, review, complete, archive, dispatch, or route tasks; use `cowork-flow` for lifecycle state.
|
|
9
|
+
|
|
10
|
+
## Trigger
|
|
11
|
+
|
|
12
|
+
Use for behavior, state, protocol, CLI, data-format, permission, or error-handling changes. For docs/comment/format-only work, skip TDD and record the verification method in `check.jsonl`.
|
|
13
|
+
|
|
14
|
+
## Red-Green-Refactor
|
|
15
|
+
|
|
16
|
+
1. Map the behavior to a `decision-anchor.md` acceptance ID.
|
|
17
|
+
2. Write the smallest meaningful test that fails because the target behavior is missing or wrong.
|
|
18
|
+
3. Run the test and confirm the red failure is about target behavior, not setup/import/environment noise.
|
|
19
|
+
4. Implement the minimum change.
|
|
20
|
+
5. Run the same test to green, then run directly dependent tests.
|
|
21
|
+
6. Refactor only after the behavior is pinned green.
|
|
22
|
+
|
|
23
|
+
## Evidence
|
|
24
|
+
|
|
25
|
+
For ordinary behavior changes, recording red/green evidence is recommended when it helps review but is not a separate artifact requirement. For high-risk changes such as protocol, state-machine, permission, security, migration, public-contract, or file-format changes, record a `type: "tdd"` object in `<task>/check.jsonl` with:
|
|
26
|
+
|
|
27
|
+
- `acceptanceId`, `testFile`, `testName`
|
|
28
|
+
- `redCommand`, `redExitCode`, `redOutputExcerpt`, `failureReason`
|
|
29
|
+
- `whyThisTestMatters`
|
|
30
|
+
- `greenCommand`, `greenExitCode`, `broaderVerification`
|
|
31
|
+
|
|
32
|
+
Only docs/comment/format-only work may use a documented `type: "tdd_exemption"` record in `check.jsonl`.
|
|
33
|
+
|
|
34
|
+
## Anti-Rationalization
|
|
35
|
+
|
|
36
|
+
Do not use these excuses to skip meaningful behavior tests:
|
|
37
|
+
|
|
38
|
+
| Excuse | Why It Fails | Required Alternative |
|
|
39
|
+
|---|---|---|
|
|
40
|
+
| "This logic is simple, no test needed" | Simple logic still breaks on edge cases and later edits. | Write the smallest behavior test for the acceptance ID. |
|
|
41
|
+
| "Other tests already cover this" | Invisible coverage is not red-green evidence. | Point to the exact test command and acceptance ID in `check.jsonl`. |
|
|
42
|
+
| "I'll add tests after implementation" | Post-implementation tests are not a red-green cycle. | Red first, then green; record both commands when evidence is required. |
|
|
43
|
+
| "It looks correct" | Visual inspection misses scenarios that tests can pin down. | Turn expected behavior and edge cases into assertions. |
|
|
44
|
+
| "This is internal only" | Internal behavior still affects callers and workflow state. | Test through the public entry point, or directly test the narrow internal contract. |
|
|
@@ -29,11 +29,26 @@ Start with:
|
|
|
29
29
|
|
|
30
30
|
> For formal fixed-agent work: create a runtime context with `.cowork-flow/run subagent init`, pass `cowork_runtime_context_id: <runtime_context_id>` through the active Host Adapter, then dispatch `cowork-implement` or `cowork-check`. Close the runtime context after verification.
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
| 项 | 内容 |
|
|
33
|
+
|----|------|
|
|
34
|
+
| **目标** | <一句话> |
|
|
35
|
+
| **架构** | <2-3 句> |
|
|
36
|
+
| **验证** | `<命令>` → exit 0;`<命令>` → exit 0 |
|
|
37
|
+
| **策略** | 串行 / 并行(文件所有权:A 组 / B 组) |
|
|
38
|
+
| **范围** | N 步 / N 文件 / N AC |
|
|
39
|
+
| **风险** | 无显著风险 / 见尾部风险表 §高-N |
|
|
40
|
+
|
|
41
|
+
## 概览
|
|
42
|
+
|
|
43
|
+
| Step | AC | 标题 | 状态 |
|
|
44
|
+
|------|----|------|------|
|
|
45
|
+
| 1.1 | AC-001 | <标题> | ⬜ |
|
|
46
|
+
| 1.2 | AC-002 | <标题> | ⬜ |
|
|
47
|
+
| 2.1 | — | <标题> | ⬜ |
|
|
35
48
|
```
|
|
36
49
|
|
|
50
|
+
Then list acceptance criteria, then steps.
|
|
51
|
+
|
|
37
52
|
## Task Rules
|
|
38
53
|
|
|
39
54
|
- Each task names exact files to create, modify, or test.
|
|
@@ -41,25 +56,37 @@ Start with:
|
|
|
41
56
|
- **Every step MUST follow the executable format below** so subagents never guess:
|
|
42
57
|
|
|
43
58
|
```markdown
|
|
44
|
-
### Step N.M
|
|
59
|
+
### Step N.M [AC-XXX] ⬜: <一句话描述,中文>
|
|
60
|
+
|
|
61
|
+
- **改动**: `path/a.py`, `path/b.py`(×2 = template + root 两份)
|
|
62
|
+
- **做了什么**: <一句话具体变更>
|
|
63
|
+
- **验证**: `<命令>` → exit 0(实现前 exit 1 → 实现后 exit 0 | AC-XXX)
|
|
64
|
+
- **回滚**: <一句话或"仅增测试,无回滚成本">
|
|
65
|
+
```
|
|
45
66
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
-
|
|
67
|
+
For steps covering multiple AC:
|
|
68
|
+
|
|
69
|
+
```markdown
|
|
70
|
+
- **同时覆盖**: AC-002
|
|
50
71
|
```
|
|
51
72
|
|
|
52
|
-
For
|
|
73
|
+
For steps without an AC (e.g. integrated verification):
|
|
53
74
|
|
|
54
75
|
```markdown
|
|
55
|
-
|
|
56
|
-
- redCommand: `<command>`
|
|
57
|
-
- redExitCode: 1
|
|
58
|
-
- greenCommand: `<command>`
|
|
59
|
-
- greenExitCode: 0
|
|
60
|
-
- acceptanceId: AC-001
|
|
76
|
+
### Step N.M — ⬜: <一句话描述,中文>
|
|
61
77
|
```
|
|
62
78
|
|
|
79
|
+
In the overview table, fill AC column with `—` for no-AC steps.
|
|
80
|
+
|
|
81
|
+
- TDD evidence is inlined in Verify parentheses: `实现前 exit 1 → 实现后 exit 0`. When red and green commands differ, use two-line form:
|
|
82
|
+
```
|
|
83
|
+
- **验证**: `red command` → exit 1 | `green command` → exit 0 | AC-XXX
|
|
84
|
+
```
|
|
85
|
+
- Expected/断言 field is retired. Its responsibility moves into Verify parentheses — describe state changes that exit code alone cannot cover:
|
|
86
|
+
```
|
|
87
|
+
- **验证**: `pytest tests/test_archive.py -q` → exit 0(覆盖:归档成功的 JSONL 自路径规范化 + 失败路径的字节级回滚)
|
|
88
|
+
```
|
|
89
|
+
- Rollback is always local — write recovery directly in the step. No cross-reference to risk table.
|
|
63
90
|
- Include commands and expected results in every step.
|
|
64
91
|
- Include a failing test before implementation when behavior can be tested.
|
|
65
92
|
- Do not add shallow tests just to satisfy process. Avoid tests that only assert existence, mirror implementation details, count mocks without behavior, or snapshot empty structure.
|
|
@@ -73,7 +100,7 @@ Start with:
|
|
|
73
100
|
| Agent Rationalization | Rebuttal | Alternative |
|
|
74
101
|
|---|---|---|
|
|
75
102
|
| "This task is too small, let's merge it." | Task granularity is determined by acceptance criteria, not line count. Merge only when AC cannot be split; otherwise keep independent. | Split until each step has a distinct AC; keep them as independent steps even when short. |
|
|
76
|
-
| "Write a rough plan first, refine during implementation." | Rough plan = no constraints during implementation. Implementation without constraints = scope creep or rework. | Write the plan to the full executable format (
|
|
103
|
+
| "Write a rough plan first, refine during implementation." | Rough plan = no constraints during implementation. Implementation without constraints = scope creep or rework. | Write the plan to the full executable format (改动 / 做了什么 / 验证 / 回滚) before any code is touched. |
|
|
77
104
|
| "Skip the plan and start directly since we have decision-anchor." | decision-anchor.md defines "what to do"; the plan defines "how to do it and in what order." Tasks without a plan incur doubled rework cost when dependency errors surface midway. | Use decision-anchor.md to derive the AC list, then produce plan steps that map each AC to a concrete file + verify command. |
|
|
78
105
|
| "This task doesn't need a verify command." | No verify = no completion signal. Every task must have an executable verification command. | Add at least one automated check (typecheck, test, lint, build) as the Verify step so completion is observable. |
|
|
79
106
|
|
|
@@ -113,4 +140,4 @@ Before handoff:
|
|
|
113
140
|
1. Confirm every decision-anchor.md acceptance criterion maps to a plan step.
|
|
114
141
|
2. Search the plan for placeholders.
|
|
115
142
|
3. Check names, paths, command syntax, and expected outputs.
|
|
116
|
-
4. Record remaining risks or
|
|
143
|
+
4. Record remaining risks or blocklers.
|