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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cowork-flow",
3
- "version": "0.0.42",
3
+ "version": "0.0.43",
4
4
  "description": "CLI for installing and maintaining cowork-flow collaboration templates.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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, create or verify `<task>/tdd.jsonl` before
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 `tdd.jsonl`
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, create or verify `<task>/tdd.jsonl` before implementation or record a documented exemption.
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 `tdd.jsonl`
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.42
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 a failing test and record red evidence in {task_path}/tdd.jsonl before modifying code.")
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>/tdd.jsonl",
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
- 读取 <task>/tdd.jsonl,验证每条 evidence 或 exemption 记录的结构完整
6
- 性与业务意图。验证结果分两类出口:
7
- - validate_tdd_evidence: 全量校验(review 阶段)
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
- EVIDENCE_FILE = "tdd.jsonl"
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
- evidence_path = task_dir / EVIDENCE_FILE
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 not entries:
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
- evidence_path,
121
- "Add at least one evidence or exemption record.",
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 index, entry in enumerate(entries, start=1):
128
- if entry.get("type") == "exemption":
129
- violations.extend(_validate_exemption(entry, evidence_path, index, acceptance_ids))
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, index, acceptance_ids))
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 when implementation would start without red evidence."""
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
- evidence_path = task_dir / EVIDENCE_FILE
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
- red_entries = [entry for entry in entries if entry.get("type") != "exemption"]
152
- if not red_entries:
153
- return [_missing_red_evidence_violation(evidence_path)]
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 index, entry in enumerate(entries, start=1):
158
- if entry.get("type") == "exemption":
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 _read_jsonl(path: Path) -> tuple[list[dict], list[dict]]:
177
- entries: list[dict] = []
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
- return entries, [
183
- _violation(
184
- "TDD-READ-001",
185
- "Failed to read TDD evidence as UTF-8",
186
- path,
187
- "Rewrite tdd.jsonl with UTF-8 encoding.",
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
- violations.append(
198
- _violation(
199
- "TDD-FORMAT-001",
200
- f"Invalid JSON in tdd.jsonl line {line_number}",
201
- path,
202
- "Write one valid JSON object per line.",
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
- violations.append(
208
- _violation(
209
- "TDD-FORMAT-002",
210
- f"tdd.jsonl line {line_number} must be a JSON object",
211
- path,
212
- "Replace non-object entries with structured evidence records.",
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
- "Write a failing behavior test and record its red command before implementation.",
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(rule_id: str, message: str, file_path: Path, fix_hint: str) -> dict:
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": "block",
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 EVIDENCE_FILE
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 the task's TDD evidence."""
51
- evidence_path = Path(task_dir) / EVIDENCE_FILE
52
- entries = _read_tdd_entries(evidence_path)
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(entries, start=1):
58
- if entry.get("type") == "exemption":
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` + `tdd.jsonl` + `task.json`
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` + `tdd.jsonl` + `task.json`
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
- Write one JSON object per line to `<task>/tdd.jsonl` with:
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 the TDD evidence requirement:
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 `tdd.jsonl`. |
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 | L1/L2: tdd.jsonl 有 red-green evidence | ./cowork-flow/run task validate |
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": "tdd.jsonl",
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": "tdd.jsonl",
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` 决定下一步。该命令只读,不推进状态,也不阻断固定代理派发;实现阶段会提示行为变更先补 TDD red evidence。
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. 行为变更任务先写失败测试并记录 `<task>/tdd.jsonl` 的 red evidence;`task next` 会在实现步骤给出非阻断提醒。
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 门禁验收完整 `tdd.jsonl`,再把任务推进到检查阶段。
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 `tdd.jsonl` must
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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cowork-flow",
3
- "version": "0.0.42",
3
+ "version": "0.0.43",
4
4
  "description": "AI 协作工作流引擎:任务生命周期、子代理调度、门禁与状态注入。将 cowork-flow 的完整工作流带入 ZCode。",
5
5
  "author": {
6
6
  "name": "fisklikewater"
@@ -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` 决定下一步。该命令只读,不推进状态,也不阻断固定代理派发;实现阶段会提示行为变更先补 TDD red evidence。
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. 行为变更任务先写失败测试并记录 `<task>/tdd.jsonl` 的 red evidence;`task next` 会在实现步骤给出非阻断提醒。
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 门禁验收完整 `tdd.jsonl`,再把任务推进到检查阶段。
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: 用户批准完整计划并要求按任务图连续执行时使用;每个任务仍独立经过 Gate、实现、检查、完成和提交。
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 是持久化状态机,不在 CLI 内模拟实现或检查完成。
10
+ Batch Scheduler is a persistent state machine. Do **not** simulate or check completion inside the CLI.
11
11
 
12
- - 任务顺序只来自 change/task graph 的叶子任务拓扑序。
13
- - `implement.jsonl`、`check.jsonl` 和 `tdd.jsonl` 只作为当前任务上下文与 Gate 证据,
14
- 不能作为 Batch 任务列表。
15
- - 每次只发布一个 host-neutral `next_action`。
16
- - Host 执行真实动作后,必须回写结果;状态机验证仓库状态后才推进。
17
- - 任何 Gate、绑定、实现、检查、测试、完成或提交失败都会暂停。
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. parent task 与所有叶子任务已建立有效 task graph。
26
- 3. 当前会话是 main/coordinator,不是 worker 或 delegated subagent。
27
- 4. Host 能执行 task、subagent 和 Git 动作。
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
- 命令返回完整 Batch 状态,其中 `next_action` 是唯一允许执行的下一步。
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. 读取 `next_action`,核对 `action_id`、`type`、`task` 和 `task_dir`。
42
- 2. 只执行该动作,不提前执行后续阶段。
43
- 3. 将结果写入 UTF-8 JSON 文件。
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. 读取新状态;若为 `awaiting_host`,继续新的 `next_action`;若为 `paused`,
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
- 运行真实任务启动命令并通过 readiness/spec/TDD Gate:
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
- 状态机同时读取 `task.json`,仅相信真实的 `in_progress` 状态。
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
- 按 action 中的 `role`、`agent_type`、`task_dir` 和 `title` 运行:
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
- Host 必须使用返回的 `cowork_runtime_context_id` 与
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
- 检查阶段将 `type` 改为 `init_check_context`。
100
+ For the check phase, change `type` to `init_check_context`.
105
101
 
106
102
  ### `await_implement_result` / `await_check_result`
107
103
 
108
- 等待对应 worker 完成,验证输出属于当前任务且 runtime context 已绑定到预期 Host。
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
- 成功结果必须包含 action 中相同的 `runtime_context_id` 和
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
- 成功结果的 `task_status` 必须为 `review`,状态机还会读取真实 `task.json`。
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
- 成功结果的 `task_status` 必须为 `completed`,状态机还会读取真实 `task.json`。
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
- 只提交当前任务已验证的改动,并回写真实 commit id:
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
- 状态机使用 Git 验证该对象确实是 commit;空值、占位字符串或不存在的 commit
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
- Host 不可用或任一动作失败时,回写非 success 结果:
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
- 状态会变为 `paused`,失败任务与所有后续任务不会被标记 completed。
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
- 恢复会生成新的 `action_id`,但保留已完成任务、阶段、runtime 证据和 commit。
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 状态变为 `completed` 后:
189
+ Once the Batch state becomes `completed`:
177
190
 
178
- 1. 运行项目完整测试与构建命令。
179
- 2. 核对 `completed_tasks`、`task_phases` 与 `commits` 一一对应。
180
- 3. 核对每个叶子任务都有独立 commit。
181
- 4. 运行 spec sync、doctor 和发布前检查。
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
- **Goal:** <one sentence>
33
- **Architecture:** <2-3 sentences>
34
- **Verification:** <commands or checks>
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: <short description>
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
- - **Files**: <exact file paths, one per line or comma-separated>
47
- - **Action**: <one sentence describing the concrete change>
48
- - **Verify**: `<command>` → `<expected output or exit code>`
49
- - **Expected**: <what the file/state/system looks like after the step succeeds>
67
+ For steps covering multiple AC:
68
+
69
+ ```markdown
70
+ - **同时覆盖**: AC-002
50
71
  ```
51
72
 
52
- For behavior-change steps, add TDD evidence:
73
+ For steps without an AC (e.g. integrated verification):
53
74
 
54
75
  ```markdown
55
- - **TDD**: RED → GREEN
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 (Files / Action / Verify / Expected) before any code is touched. |
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 blockers.
143
+ 4. Record remaining risks or blocklers.