cowork-flow 0.0.41 → 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.
Files changed (69) hide show
  1. package/README.md +1 -1
  2. package/package.json +1 -1
  3. package/src/commands/install-zcode-plugin.js +11 -0
  4. package/template/.claude/agents/cowork-check.md +13 -2
  5. package/template/.claude/agents/cowork-implement.md +15 -4
  6. package/template/.codex/agents/cowork-check.toml +12 -7
  7. package/template/.codex/agents/cowork-implement.toml +13 -6
  8. package/template/.cowork-flow/.version +1 -1
  9. package/template/.cowork-flow/scripts/application/batch_execution.py +21 -0
  10. package/template/.cowork-flow/scripts/application/task_context.py +18 -8
  11. package/template/.cowork-flow/scripts/commands/task_navigation.py +1 -1
  12. package/template/.cowork-flow/scripts/common/core/quality_sources.py +140 -0
  13. package/template/.cowork-flow/scripts/common/gates/anti_rationalization.py +1 -1
  14. package/template/.cowork-flow/scripts/common/gates/gates.py +55 -2
  15. package/template/.cowork-flow/scripts/common/gates/quality_review.py +332 -0
  16. package/template/.cowork-flow/scripts/common/gates/tdd_evidence.py +231 -71
  17. package/template/.cowork-flow/scripts/common/gates/test_intent.py +6 -30
  18. package/template/.cowork-flow/scripts/common/gates/validate_coding_standards.py +13 -5
  19. package/template/.cowork-flow/spec/contracts/decision-anchor.md +2 -2
  20. package/template/.cowork-flow/spec/protocols/review.md +20 -1
  21. package/template/.cowork-flow/spec/protocols/tdd.md +3 -3
  22. package/template/.cowork-flow/spec/references/definition-of-done.md +1 -1
  23. package/template/.cowork-flow/spec/runtime/host-assets.json +0 -2
  24. package/template/.cowork-flow/spec/runtime/skill-registry.json +25 -2
  25. package/template/.cowork-flow/workflow.md +4 -3
  26. package/template/.opencode/agents/cowork-check.md +6 -0
  27. package/template/.opencode/agents/cowork-implement.md +15 -3
  28. package/template/.zcode/.zcode-plugin/plugin.json +1 -1
  29. package/template/.zcode/scaffold/.cowork-flow/workflow.md +4 -3
  30. package/template/skills/batch-mode/SKILL.md +72 -59
  31. package/template/skills/tdd/SKILL.md +44 -0
  32. package/template/skills/writing-plans/SKILL.md +44 -17
  33. package/template/.zcode/scaffold/.cowork-flow/spec/backend/database-guidelines.md +0 -23
  34. package/template/.zcode/scaffold/.cowork-flow/spec/backend/directory-structure.md +0 -27
  35. package/template/.zcode/scaffold/.cowork-flow/spec/backend/encoding-guidelines.md +0 -27
  36. package/template/.zcode/scaffold/.cowork-flow/spec/backend/error-handling.md +0 -20
  37. package/template/.zcode/scaffold/.cowork-flow/spec/backend/index.md +0 -40
  38. package/template/.zcode/scaffold/.cowork-flow/spec/backend/logging-guidelines.md +0 -20
  39. package/template/.zcode/scaffold/.cowork-flow/spec/backend/quality-guidelines.md +0 -25
  40. package/template/.zcode/scaffold/.cowork-flow/spec/contracts/capabilities.md +0 -34
  41. package/template/.zcode/scaffold/.cowork-flow/spec/contracts/decision-anchor.md +0 -74
  42. package/template/.zcode/scaffold/.cowork-flow/spec/contracts/index.md +0 -11
  43. package/template/.zcode/scaffold/.cowork-flow/spec/contracts/party-mode-v2-board.md +0 -68
  44. package/template/.zcode/scaffold/.cowork-flow/spec/contracts/subagent-dispatch.md +0 -83
  45. package/template/.zcode/scaffold/.cowork-flow/spec/contracts/workflow-state-templates.md +0 -72
  46. package/template/.zcode/scaffold/.cowork-flow/spec/frontend/component-guidelines.md +0 -19
  47. package/template/.zcode/scaffold/.cowork-flow/spec/frontend/directory-structure.md +0 -18
  48. package/template/.zcode/scaffold/.cowork-flow/spec/frontend/hook-guidelines.md +0 -17
  49. package/template/.zcode/scaffold/.cowork-flow/spec/frontend/index.md +0 -39
  50. package/template/.zcode/scaffold/.cowork-flow/spec/frontend/quality-guidelines.md +0 -25
  51. package/template/.zcode/scaffold/.cowork-flow/spec/frontend/state-management.md +0 -18
  52. package/template/.zcode/scaffold/.cowork-flow/spec/frontend/type-safety.md +0 -18
  53. package/template/.zcode/scaffold/.cowork-flow/spec/game/asset-pipeline.md +0 -53
  54. package/template/.zcode/scaffold/.cowork-flow/spec/game/engine-guidelines.md +0 -43
  55. package/template/.zcode/scaffold/.cowork-flow/spec/game/index.md +0 -23
  56. package/template/.zcode/scaffold/.cowork-flow/spec/game/multiplayer-guidelines.md +0 -44
  57. package/template/.zcode/scaffold/.cowork-flow/spec/game/performance-guidelines.md +0 -58
  58. package/template/.zcode/scaffold/.cowork-flow/spec/guides/code-reuse-thinking-guide.md +0 -26
  59. package/template/.zcode/scaffold/.cowork-flow/spec/guides/cross-layer-thinking-guide.md +0 -21
  60. package/template/.zcode/scaffold/.cowork-flow/spec/guides/index.md +0 -31
  61. package/template/.zcode/scaffold/.cowork-flow/spec/guides/pre-implementation-checklist.md +0 -15
  62. package/template/.zcode/scaffold/.cowork-flow/spec/index.md +0 -15
  63. package/template/.zcode/scaffold/.cowork-flow/spec/runtime/contract-registry.json +0 -68
  64. package/template/.zcode/scaffold/.cowork-flow/spec/runtime/index.md +0 -10
  65. package/template/.zcode/scaffold/.cowork-flow/spec/runtime/rules.json +0 -176
  66. package/template/.zcode/scaffold/.cowork-flow/spec/schemas/adapter.schema.json +0 -128
  67. package/template/.zcode/scaffold/.cowork-flow/spec/schemas/index.md +0 -10
  68. package/template/.zcode/scaffold/.cowork-flow/spec/schemas/party-mode-v2-actions.schema.json +0 -90
  69. package/template/.zcode/scaffold/.cowork-flow/spec/schemas/rules.schema.json +0 -89
@@ -0,0 +1,332 @@
1
+ #!/usr/bin/env python3
2
+ """Validate task-local quality review evidence."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import json
7
+ from json import JSONDecodeError
8
+ from pathlib import Path
9
+ from typing import Iterable
10
+
11
+ from common.gates.validate_coding_standards import collect_machine_checks
12
+
13
+
14
+ QUALITY_REVIEW_FILE = "quality-review.jsonl"
15
+ REQUIRED_FIELDS = frozenset(
16
+ {"id", "source", "type", "status", "files", "evidence", "verification"}
17
+ )
18
+ ALLOWED_TYPES = frozenset({"checklist", "machine_warning", "dod"})
19
+ ALLOWED_STATUSES = frozenset(
20
+ {"pass", "fail", "not_applicable", "acknowledged_warning"}
21
+ )
22
+ VAGUE_EVIDENCE = frozenset(
23
+ {
24
+ "checked",
25
+ "done",
26
+ "ok",
27
+ "pass",
28
+ "passed",
29
+ "n/a",
30
+ "na",
31
+ "已检查",
32
+ "已确认",
33
+ "通过",
34
+ "无问题",
35
+ }
36
+ )
37
+
38
+
39
+ def validate_quality_review(
40
+ repo_root: Path,
41
+ task_dir: Path | None,
42
+ ) -> list[dict]:
43
+ """Return blocking violations for missing or incomplete quality evidence."""
44
+ if task_dir is None:
45
+ return [
46
+ _violation(
47
+ "QUALITY-REVIEW-TASK-001",
48
+ "",
49
+ "Quality review gate requires a task directory",
50
+ "Run task complete with an explicit or active task directory.",
51
+ )
52
+ ]
53
+
54
+ evidence_path = Path(task_dir) / QUALITY_REVIEW_FILE
55
+ entries, violations = _read_entries(evidence_path)
56
+ if violations and not entries:
57
+ return violations
58
+
59
+ violations.extend(_validate_entries(entries, evidence_path))
60
+ if not violations:
61
+ violations.extend(_validate_machine_warning_coverage(repo_root, entries))
62
+ return violations
63
+
64
+
65
+ def _read_entries(evidence_path: Path) -> tuple[list[dict], list[dict]]:
66
+ if not evidence_path.is_file():
67
+ return [], [
68
+ _violation(
69
+ "QUALITY-REVIEW-MISSING-001",
70
+ str(evidence_path),
71
+ "quality-review.jsonl is required before task complete",
72
+ "Record quality checklist, machine warning, and DoD evidence in quality-review.jsonl.",
73
+ )
74
+ ]
75
+
76
+ entries: list[dict] = []
77
+ violations: list[dict] = []
78
+ try:
79
+ lines = evidence_path.read_text(encoding="utf-8").splitlines()
80
+ except UnicodeDecodeError:
81
+ return [], [
82
+ _violation(
83
+ "QUALITY-REVIEW-UTF8-001",
84
+ str(evidence_path),
85
+ "quality-review.jsonl must be valid UTF-8",
86
+ "Rewrite quality-review.jsonl as UTF-8.",
87
+ )
88
+ ]
89
+ except OSError as error:
90
+ return [], [
91
+ _violation(
92
+ "QUALITY-REVIEW-READ-001",
93
+ str(evidence_path),
94
+ f"quality-review.jsonl could not be read: {error}",
95
+ "Restore a readable quality-review.jsonl file.",
96
+ )
97
+ ]
98
+
99
+ for index, raw_line in enumerate(lines, start=1):
100
+ line = raw_line.strip()
101
+ if not line:
102
+ continue
103
+ try:
104
+ value = json.loads(line)
105
+ except JSONDecodeError as error:
106
+ violations.append(
107
+ _line_violation(
108
+ "QUALITY-REVIEW-JSONL-001",
109
+ evidence_path,
110
+ index,
111
+ f"quality-review.jsonl line is invalid JSON: {error.msg}",
112
+ "Write one JSON object per line.",
113
+ )
114
+ )
115
+ continue
116
+ if not isinstance(value, dict):
117
+ violations.append(
118
+ _line_violation(
119
+ "QUALITY-REVIEW-JSONL-001",
120
+ evidence_path,
121
+ index,
122
+ "quality-review.jsonl line must be a JSON object",
123
+ "Write one JSON object per evidence record.",
124
+ )
125
+ )
126
+ continue
127
+ entries.append(value)
128
+
129
+ if not entries and not violations:
130
+ violations.append(
131
+ _violation(
132
+ "QUALITY-REVIEW-EMPTY-001",
133
+ str(evidence_path),
134
+ "quality-review.jsonl must contain at least one evidence record",
135
+ "Add checklist and DoD evidence records before completing the task.",
136
+ )
137
+ )
138
+ return entries, violations
139
+
140
+
141
+ def _validate_entries(entries: list[dict], evidence_path: Path) -> list[dict]:
142
+ violations: list[dict] = []
143
+ has_dod = False
144
+ for index, entry in enumerate(entries, start=1):
145
+ missing = sorted(field for field in REQUIRED_FIELDS if field not in entry)
146
+ if missing:
147
+ violations.append(
148
+ _line_violation(
149
+ "QUALITY-REVIEW-SCHEMA-001",
150
+ evidence_path,
151
+ index,
152
+ f"quality-review record is missing fields: {', '.join(missing)}",
153
+ "Include id, source, type, status, files, evidence, and verification.",
154
+ )
155
+ )
156
+ continue
157
+
158
+ entry_type = str(entry.get("type") or "").strip()
159
+ status = str(entry.get("status") or "").strip()
160
+ if entry_type not in ALLOWED_TYPES:
161
+ violations.append(
162
+ _line_violation(
163
+ "QUALITY-REVIEW-TYPE-001",
164
+ evidence_path,
165
+ index,
166
+ f"unsupported quality-review type: {entry_type}",
167
+ "Use checklist, machine_warning, or dod.",
168
+ )
169
+ )
170
+ if status not in ALLOWED_STATUSES:
171
+ violations.append(
172
+ _line_violation(
173
+ "QUALITY-REVIEW-STATUS-002",
174
+ evidence_path,
175
+ index,
176
+ f"unsupported quality-review status: {status}",
177
+ "Use pass, fail, not_applicable, or acknowledged_warning.",
178
+ )
179
+ )
180
+ if status == "fail":
181
+ violations.append(
182
+ _line_violation(
183
+ "QUALITY-REVIEW-STATUS-001",
184
+ evidence_path,
185
+ index,
186
+ "quality-review record is still failing",
187
+ "Resolve the finding or keep the task out of complete.",
188
+ )
189
+ )
190
+ if entry_type == "dod" and status in {"pass", "not_applicable"}:
191
+ has_dod = True
192
+
193
+ violations.extend(_validate_files(entry, evidence_path, index))
194
+ violations.extend(_validate_evidence(entry, evidence_path, index))
195
+ violations.extend(_validate_verification(entry, evidence_path, index))
196
+
197
+ if not has_dod:
198
+ violations.append(
199
+ _violation(
200
+ "QUALITY-REVIEW-DOD-001",
201
+ str(evidence_path),
202
+ "quality-review.jsonl must include Definition of Done evidence",
203
+ "Add a dod record for .cowork-flow/spec/references/definition-of-done.md.",
204
+ )
205
+ )
206
+ return violations
207
+
208
+
209
+ def _validate_files(entry: dict, evidence_path: Path, index: int) -> list[dict]:
210
+ files = entry.get("files")
211
+ if _non_empty_string_list(files):
212
+ return []
213
+ return [
214
+ _line_violation(
215
+ "QUALITY-REVIEW-FILES-001",
216
+ evidence_path,
217
+ index,
218
+ "quality-review files must be a non-empty list of paths",
219
+ "List the reviewed changed files for this evidence record.",
220
+ )
221
+ ]
222
+
223
+
224
+ def _validate_evidence(entry: dict, evidence_path: Path, index: int) -> list[dict]:
225
+ evidence = str(entry.get("evidence") or "").strip()
226
+ normalized = evidence.casefold()
227
+ if len(evidence) >= 24 and normalized not in VAGUE_EVIDENCE:
228
+ return []
229
+ return [
230
+ _line_violation(
231
+ "QUALITY-REVIEW-EVIDENCE-001",
232
+ evidence_path,
233
+ index,
234
+ "quality-review evidence is too vague",
235
+ "Describe the concrete file, rule, finding, or verification result.",
236
+ )
237
+ ]
238
+
239
+
240
+ def _validate_verification(entry: dict, evidence_path: Path, index: int) -> list[dict]:
241
+ verification = entry.get("verification")
242
+ if _non_empty_string_list(verification):
243
+ return []
244
+ return [
245
+ _line_violation(
246
+ "QUALITY-REVIEW-VERIFICATION-001",
247
+ evidence_path,
248
+ index,
249
+ "quality-review verification must be a non-empty list of commands or reasons",
250
+ "Record executed commands, or an explicit reason a command was not applicable.",
251
+ )
252
+ ]
253
+
254
+
255
+ def _validate_machine_warning_coverage(
256
+ repo_root: Path,
257
+ entries: list[dict],
258
+ ) -> list[dict]:
259
+ warnings = collect_machine_checks(Path(repo_root))
260
+ if not warnings:
261
+ return []
262
+
263
+ acknowledgements = _machine_warning_acknowledgements(entries)
264
+ violations: list[dict] = []
265
+ for warning in warnings:
266
+ rule_id = str(warning.get("rule_id") or "")
267
+ file_path = str(warning.get("file") or "")
268
+ if (rule_id, file_path) in acknowledgements:
269
+ continue
270
+ violations.append(
271
+ _violation(
272
+ "QUALITY-REVIEW-MACHINE-WARNING-001",
273
+ file_path,
274
+ f"machine warning {rule_id} is not addressed in quality-review.jsonl",
275
+ "Fix the warning or add a machine_warning record with status acknowledged_warning.",
276
+ )
277
+ )
278
+ return violations
279
+
280
+
281
+ def _machine_warning_acknowledgements(entries: Iterable[dict]) -> set[tuple[str, str]]:
282
+ acknowledgements: set[tuple[str, str]] = set()
283
+ for entry in entries:
284
+ if entry.get("type") != "machine_warning":
285
+ continue
286
+ if entry.get("status") != "acknowledged_warning":
287
+ continue
288
+ source = str(entry.get("source") or entry.get("rule_id") or "").strip()
289
+ files = entry.get("files")
290
+ if not source or not isinstance(files, list):
291
+ continue
292
+ for file_path in files:
293
+ if isinstance(file_path, str) and file_path.strip():
294
+ acknowledgements.add((source, file_path.strip()))
295
+ return acknowledgements
296
+
297
+
298
+ def _non_empty_string_list(value: object) -> bool:
299
+ return (
300
+ isinstance(value, list)
301
+ and bool(value)
302
+ and all(isinstance(item, str) and bool(item.strip()) for item in value)
303
+ )
304
+
305
+
306
+ def _line_violation(
307
+ rule_id: str,
308
+ path: Path,
309
+ line: int,
310
+ message: str,
311
+ fix_hint: str,
312
+ ) -> dict:
313
+ violation = _violation(rule_id, str(path), message, fix_hint)
314
+ violation["line"] = line
315
+ return violation
316
+
317
+
318
+ def _violation(
319
+ rule_id: str,
320
+ file_path: str,
321
+ message: str,
322
+ fix_hint: str,
323
+ ) -> dict:
324
+ return {
325
+ "rule_id": rule_id,
326
+ "type": "quality_review",
327
+ "severity": "block",
328
+ "passed": False,
329
+ "message": message,
330
+ "file": file_path,
331
+ "fix_hint": fix_hint,
332
+ }