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.
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/commands/install-zcode-plugin.js +11 -0
- package/template/.claude/agents/cowork-check.md +13 -2
- package/template/.claude/agents/cowork-implement.md +15 -4
- package/template/.codex/agents/cowork-check.toml +12 -7
- package/template/.codex/agents/cowork-implement.toml +13 -6
- package/template/.cowork-flow/.version +1 -1
- package/template/.cowork-flow/scripts/application/batch_execution.py +21 -0
- package/template/.cowork-flow/scripts/application/task_context.py +18 -8
- package/template/.cowork-flow/scripts/commands/task_navigation.py +1 -1
- package/template/.cowork-flow/scripts/common/core/quality_sources.py +140 -0
- package/template/.cowork-flow/scripts/common/gates/anti_rationalization.py +1 -1
- package/template/.cowork-flow/scripts/common/gates/gates.py +55 -2
- package/template/.cowork-flow/scripts/common/gates/quality_review.py +332 -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/scripts/common/gates/validate_coding_standards.py +13 -5
- package/template/.cowork-flow/spec/contracts/decision-anchor.md +2 -2
- package/template/.cowork-flow/spec/protocols/review.md +20 -1
- 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-check.md +6 -0
- package/template/.opencode/agents/cowork-implement.md +15 -3
- 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/template/.zcode/scaffold/.cowork-flow/spec/backend/database-guidelines.md +0 -23
- package/template/.zcode/scaffold/.cowork-flow/spec/backend/directory-structure.md +0 -27
- package/template/.zcode/scaffold/.cowork-flow/spec/backend/encoding-guidelines.md +0 -27
- package/template/.zcode/scaffold/.cowork-flow/spec/backend/error-handling.md +0 -20
- package/template/.zcode/scaffold/.cowork-flow/spec/backend/index.md +0 -40
- package/template/.zcode/scaffold/.cowork-flow/spec/backend/logging-guidelines.md +0 -20
- package/template/.zcode/scaffold/.cowork-flow/spec/backend/quality-guidelines.md +0 -25
- package/template/.zcode/scaffold/.cowork-flow/spec/contracts/capabilities.md +0 -34
- package/template/.zcode/scaffold/.cowork-flow/spec/contracts/decision-anchor.md +0 -74
- package/template/.zcode/scaffold/.cowork-flow/spec/contracts/index.md +0 -11
- package/template/.zcode/scaffold/.cowork-flow/spec/contracts/party-mode-v2-board.md +0 -68
- package/template/.zcode/scaffold/.cowork-flow/spec/contracts/subagent-dispatch.md +0 -83
- package/template/.zcode/scaffold/.cowork-flow/spec/contracts/workflow-state-templates.md +0 -72
- package/template/.zcode/scaffold/.cowork-flow/spec/frontend/component-guidelines.md +0 -19
- package/template/.zcode/scaffold/.cowork-flow/spec/frontend/directory-structure.md +0 -18
- package/template/.zcode/scaffold/.cowork-flow/spec/frontend/hook-guidelines.md +0 -17
- package/template/.zcode/scaffold/.cowork-flow/spec/frontend/index.md +0 -39
- package/template/.zcode/scaffold/.cowork-flow/spec/frontend/quality-guidelines.md +0 -25
- package/template/.zcode/scaffold/.cowork-flow/spec/frontend/state-management.md +0 -18
- package/template/.zcode/scaffold/.cowork-flow/spec/frontend/type-safety.md +0 -18
- package/template/.zcode/scaffold/.cowork-flow/spec/game/asset-pipeline.md +0 -53
- package/template/.zcode/scaffold/.cowork-flow/spec/game/engine-guidelines.md +0 -43
- package/template/.zcode/scaffold/.cowork-flow/spec/game/index.md +0 -23
- package/template/.zcode/scaffold/.cowork-flow/spec/game/multiplayer-guidelines.md +0 -44
- package/template/.zcode/scaffold/.cowork-flow/spec/game/performance-guidelines.md +0 -58
- package/template/.zcode/scaffold/.cowork-flow/spec/guides/code-reuse-thinking-guide.md +0 -26
- package/template/.zcode/scaffold/.cowork-flow/spec/guides/cross-layer-thinking-guide.md +0 -21
- package/template/.zcode/scaffold/.cowork-flow/spec/guides/index.md +0 -31
- package/template/.zcode/scaffold/.cowork-flow/spec/guides/pre-implementation-checklist.md +0 -15
- package/template/.zcode/scaffold/.cowork-flow/spec/index.md +0 -15
- package/template/.zcode/scaffold/.cowork-flow/spec/runtime/contract-registry.json +0 -68
- package/template/.zcode/scaffold/.cowork-flow/spec/runtime/index.md +0 -10
- package/template/.zcode/scaffold/.cowork-flow/spec/runtime/rules.json +0 -176
- package/template/.zcode/scaffold/.cowork-flow/spec/schemas/adapter.schema.json +0 -128
- package/template/.zcode/scaffold/.cowork-flow/spec/schemas/index.md +0 -10
- package/template/.zcode/scaffold/.cowork-flow/spec/schemas/party-mode-v2-actions.schema.json +0 -90
- 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
|
+
}
|