cowork-flow 0.0.39 → 0.0.41
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/.cowork-flow/.version +1 -1
- package/template/.cowork-flow/scripts/application/task_archive.py +148 -21
- package/template/.cowork-flow/scripts/application/task_lifecycle.py +3 -1
- package/template/.cowork-flow/scripts/commands/add_session.py +137 -124
- package/template/.cowork-flow/scripts/commands/task.py +1 -0
- package/template/.cowork-flow/scripts/commands/task_archive_commands.py +135 -49
- package/template/.cowork-flow/scripts/commands/task_navigation.py +145 -81
- package/template/.cowork-flow/scripts/commands/task_parser.py +1 -0
- package/template/.cowork-flow/scripts/common/core/paths.py +4 -1
- package/template/.cowork-flow/scripts/common/gates/gates.py +114 -108
- package/template/.cowork-flow/scripts/common/gates/validate_implementation.py +50 -28
- package/template/.cowork-flow/scripts/common/git/git_snapshot.py +14 -0
- package/template/.cowork-flow/spec/protocols/decision-review.md +28 -1
- package/template/.cowork-flow/spec/protocols/spec-maintenance.md +19 -0
- package/template/.cowork-flow/spec/protocols/tdd.md +13 -0
- 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 +29 -1
- package/template/.cowork-flow/workflow.md +6 -2
- package/template/.zcode/.zcode-plugin/plugin.json +1 -1
- package/template/.zcode/scaffold/.cowork-flow/workflow.md +6 -2
- package/template/skills/doubt-review/SKILL.md +125 -0
|
@@ -28,6 +28,7 @@ STAGE_GATES = {
|
|
|
28
28
|
"complexity",
|
|
29
29
|
),
|
|
30
30
|
"task_complete": (
|
|
31
|
+
"implementation",
|
|
31
32
|
"tdd_evidence",
|
|
32
33
|
"test_intent",
|
|
33
34
|
"runtime_rules",
|
|
@@ -37,9 +38,8 @@ STAGE_GATES = {
|
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
|
|
40
|
-
def
|
|
41
|
-
|
|
42
|
-
for binding in (
|
|
41
|
+
def _validator_bindings() -> tuple[ValidatorBinding, ...]:
|
|
42
|
+
return (
|
|
43
43
|
ValidatorBinding(
|
|
44
44
|
key="runtime_rules",
|
|
45
45
|
module="validate_rules",
|
|
@@ -79,10 +79,11 @@ def build_default_registry() -> GateRegistry:
|
|
|
79
79
|
function="validate_complexity_signals",
|
|
80
80
|
positional=("repo_root", "task_dir"),
|
|
81
81
|
),
|
|
82
|
-
)
|
|
83
|
-
|
|
82
|
+
)
|
|
83
|
+
|
|
84
84
|
|
|
85
|
-
|
|
85
|
+
def _gate_definitions() -> tuple[GateDefinition, ...]:
|
|
86
|
+
return (
|
|
86
87
|
GateDefinition(
|
|
87
88
|
id="runtime_rules",
|
|
88
89
|
validator_key="runtime_rules",
|
|
@@ -94,7 +95,7 @@ def build_default_registry() -> GateRegistry:
|
|
|
94
95
|
id="implementation",
|
|
95
96
|
validator_key="implementation",
|
|
96
97
|
required=True,
|
|
97
|
-
block_message="Implementation gate blocked
|
|
98
|
+
block_message="Implementation gate blocked lifecycle transition",
|
|
98
99
|
warning_message="Implementation violations detected",
|
|
99
100
|
),
|
|
100
101
|
GateDefinition(
|
|
@@ -124,7 +125,13 @@ def build_default_registry() -> GateRegistry:
|
|
|
124
125
|
warning_message="Complexity review warnings",
|
|
125
126
|
),
|
|
126
127
|
)
|
|
127
|
-
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def build_default_registry() -> GateRegistry:
|
|
131
|
+
registry = GateRegistry()
|
|
132
|
+
for binding in _validator_bindings():
|
|
133
|
+
registry.register_validator(binding)
|
|
134
|
+
for definition in _gate_definitions():
|
|
128
135
|
registry.register_gate(definition)
|
|
129
136
|
return registry
|
|
130
137
|
|
|
@@ -151,61 +158,57 @@ def _pipeline_violation(
|
|
|
151
158
|
).to_dict()
|
|
152
159
|
|
|
153
160
|
|
|
161
|
+
def _normalize_violation(
|
|
162
|
+
raw_violation: object,
|
|
163
|
+
context: GateContext,
|
|
164
|
+
definition: GateDefinition,
|
|
165
|
+
) -> dict:
|
|
166
|
+
if not isinstance(raw_violation, Mapping):
|
|
167
|
+
return _pipeline_violation(
|
|
168
|
+
rule_id="GATE-PROTOCOL-001",
|
|
169
|
+
context=context,
|
|
170
|
+
definition=definition,
|
|
171
|
+
message=f"Gate {definition.id} returned a non-object violation",
|
|
172
|
+
fix_hint="Return violation dictionaries with stable metadata.",
|
|
173
|
+
)
|
|
174
|
+
try:
|
|
175
|
+
return Violation.from_mapping(
|
|
176
|
+
raw_violation,
|
|
177
|
+
scope=context.scope,
|
|
178
|
+
gate_id=definition.id,
|
|
179
|
+
).to_dict()
|
|
180
|
+
except ValueError as error:
|
|
181
|
+
return _pipeline_violation(
|
|
182
|
+
rule_id="GATE-PROTOCOL-001",
|
|
183
|
+
context=context,
|
|
184
|
+
definition=definition,
|
|
185
|
+
message=f"Gate {definition.id} returned invalid metadata: {error}",
|
|
186
|
+
fix_hint="Return violations with rule_id, severity, and message.",
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
|
|
154
190
|
def _normalize_violations(
|
|
155
191
|
raw_result: object,
|
|
156
192
|
context: GateContext,
|
|
157
193
|
definition: GateDefinition,
|
|
158
194
|
) -> list[dict]:
|
|
159
|
-
if
|
|
195
|
+
if isinstance(raw_result, list):
|
|
160
196
|
return [
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
context=context,
|
|
164
|
-
definition=definition,
|
|
165
|
-
message=(
|
|
166
|
-
f"Gate {definition.id} returned "
|
|
167
|
-
f"{type(raw_result).__name__}; expected a list of violations"
|
|
168
|
-
),
|
|
169
|
-
fix_hint="Return list[dict] from the registered gate validator.",
|
|
170
|
-
)
|
|
197
|
+
_normalize_violation(item, context, definition)
|
|
198
|
+
for item in raw_result
|
|
171
199
|
]
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
fix_hint="Return violation dictionaries with stable metadata.",
|
|
185
|
-
)
|
|
186
|
-
)
|
|
187
|
-
continue
|
|
188
|
-
try:
|
|
189
|
-
normalized.append(
|
|
190
|
-
Violation.from_mapping(
|
|
191
|
-
raw_violation,
|
|
192
|
-
scope=context.scope,
|
|
193
|
-
gate_id=definition.id,
|
|
194
|
-
).to_dict()
|
|
195
|
-
)
|
|
196
|
-
except ValueError as error:
|
|
197
|
-
normalized.append(
|
|
198
|
-
_pipeline_violation(
|
|
199
|
-
rule_id="GATE-PROTOCOL-001",
|
|
200
|
-
context=context,
|
|
201
|
-
definition=definition,
|
|
202
|
-
message=f"Gate {definition.id} returned invalid metadata: {error}",
|
|
203
|
-
fix_hint=(
|
|
204
|
-
"Return violations with rule_id, severity, and message."
|
|
205
|
-
),
|
|
206
|
-
)
|
|
207
|
-
)
|
|
208
|
-
return normalized
|
|
200
|
+
return [
|
|
201
|
+
_pipeline_violation(
|
|
202
|
+
rule_id="GATE-PROTOCOL-001",
|
|
203
|
+
context=context,
|
|
204
|
+
definition=definition,
|
|
205
|
+
message=(
|
|
206
|
+
f"Gate {definition.id} returned "
|
|
207
|
+
f"{type(raw_result).__name__}; expected a list of violations"
|
|
208
|
+
),
|
|
209
|
+
fix_hint="Return list[dict] from the registered gate validator.",
|
|
210
|
+
)
|
|
211
|
+
]
|
|
209
212
|
|
|
210
213
|
|
|
211
214
|
class GatePipeline:
|
|
@@ -214,62 +217,65 @@ class GatePipeline:
|
|
|
214
217
|
def __init__(self, registry: GateRegistry | None = None) -> None:
|
|
215
218
|
self.registry = registry or build_default_registry()
|
|
216
219
|
|
|
217
|
-
def
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
fix_hint=(
|
|
234
|
-
"Restore the required validator module and function."
|
|
235
|
-
),
|
|
236
|
-
)
|
|
237
|
-
]
|
|
238
|
-
except Exception as error:
|
|
239
|
-
violations = [
|
|
240
|
-
_pipeline_violation(
|
|
241
|
-
rule_id="GATE-EXEC-001",
|
|
242
|
-
context=context,
|
|
243
|
-
definition=definition,
|
|
244
|
-
message=f"Gate {definition.id} failed during execution: {error}",
|
|
245
|
-
fix_hint=(
|
|
246
|
-
"Fix the validator exception; lifecycle gates fail closed."
|
|
247
|
-
),
|
|
248
|
-
)
|
|
249
|
-
]
|
|
250
|
-
else:
|
|
251
|
-
violations = _normalize_violations(
|
|
252
|
-
raw_result,
|
|
253
|
-
context,
|
|
254
|
-
definition,
|
|
220
|
+
def _execute_gate(
|
|
221
|
+
self,
|
|
222
|
+
context: GateContext,
|
|
223
|
+
gate_id: str,
|
|
224
|
+
) -> GateExecution:
|
|
225
|
+
definition = self.registry.gate(gate_id)
|
|
226
|
+
try:
|
|
227
|
+
raw_result = self.registry.invoke(definition, context)
|
|
228
|
+
except GateLoadError as error:
|
|
229
|
+
violations = [
|
|
230
|
+
_pipeline_violation(
|
|
231
|
+
rule_id="GATE-LOAD-001",
|
|
232
|
+
context=context,
|
|
233
|
+
definition=definition,
|
|
234
|
+
message=f"Gate {definition.id} could not load: {error}",
|
|
235
|
+
fix_hint="Restore the required validator module and function.",
|
|
255
236
|
)
|
|
256
|
-
|
|
257
|
-
|
|
237
|
+
]
|
|
238
|
+
except Exception as error:
|
|
239
|
+
violations = [
|
|
240
|
+
_pipeline_violation(
|
|
241
|
+
rule_id="GATE-EXEC-001",
|
|
242
|
+
context=context,
|
|
243
|
+
definition=definition,
|
|
244
|
+
message=f"Gate {definition.id} failed during execution: {error}",
|
|
245
|
+
fix_hint="Fix the validator exception; lifecycle gates fail closed.",
|
|
246
|
+
)
|
|
247
|
+
]
|
|
248
|
+
else:
|
|
249
|
+
violations = _normalize_violations(raw_result, context, definition)
|
|
250
|
+
return GateExecution(
|
|
251
|
+
definition=definition,
|
|
252
|
+
result=GateResult.from_violations(
|
|
258
253
|
context.scope,
|
|
259
254
|
violations,
|
|
260
255
|
context.task_dir,
|
|
261
|
-
)
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
256
|
+
),
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
def run(self, context: GateContext) -> GateResult:
|
|
260
|
+
executions = [
|
|
261
|
+
self._execute_gate(context, gate_id)
|
|
262
|
+
for gate_id in STAGE_GATES[context.scope]
|
|
263
|
+
]
|
|
264
|
+
violations = [
|
|
265
|
+
violation
|
|
266
|
+
for execution in executions
|
|
267
|
+
for violation in execution.result.violations
|
|
268
|
+
]
|
|
269
|
+
priority_violations = [
|
|
270
|
+
violation
|
|
271
|
+
for violation in violations
|
|
272
|
+
if str(violation.get("rule_id", "")).startswith("GATE-")
|
|
273
|
+
]
|
|
274
|
+
ordinary_violations = [
|
|
275
|
+
violation
|
|
276
|
+
for violation in violations
|
|
277
|
+
if not str(violation.get("rule_id", "")).startswith("GATE-")
|
|
278
|
+
]
|
|
273
279
|
|
|
274
280
|
return GateResult(
|
|
275
281
|
scope=context.scope,
|
|
@@ -18,6 +18,8 @@ import subprocess
|
|
|
18
18
|
import sys
|
|
19
19
|
from pathlib import Path
|
|
20
20
|
|
|
21
|
+
from common.git.git_snapshot import collect_changed_paths
|
|
22
|
+
|
|
21
23
|
try:
|
|
22
24
|
from .validate_rules import (
|
|
23
25
|
config_violation,
|
|
@@ -71,7 +73,15 @@ def validate_implementation(
|
|
|
71
73
|
)
|
|
72
74
|
)
|
|
73
75
|
violations.extend(_check_premature_abstraction(repo_root, diff_output, rule_index))
|
|
74
|
-
violations.extend(
|
|
76
|
+
violations.extend(
|
|
77
|
+
_check_unrequested_features(
|
|
78
|
+
repo_root,
|
|
79
|
+
diff_output,
|
|
80
|
+
task_dir,
|
|
81
|
+
rule_index,
|
|
82
|
+
modified_files=modified_files,
|
|
83
|
+
)
|
|
84
|
+
)
|
|
75
85
|
|
|
76
86
|
return violations
|
|
77
87
|
|
|
@@ -86,38 +96,44 @@ def _missing_rule(repo_root: Path, rule_id: str) -> dict:
|
|
|
86
96
|
|
|
87
97
|
|
|
88
98
|
def _get_modified_files(repo_root: Path) -> list[str]:
|
|
89
|
-
"""Get
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
return
|
|
102
|
-
|
|
103
|
-
|
|
99
|
+
"""Get staged, unstaged, and untracked files under repo_root."""
|
|
100
|
+
changed_files = collect_changed_paths(repo_root)
|
|
101
|
+
return [
|
|
102
|
+
file_path
|
|
103
|
+
for file_path in changed_files
|
|
104
|
+
if _is_implementation_relevant(file_path)
|
|
105
|
+
]
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _is_implementation_relevant(file_path: str) -> bool:
|
|
109
|
+
normalized = file_path.replace("\\", "/")
|
|
110
|
+
if not normalized.startswith(".cowork-flow/"):
|
|
111
|
+
return True
|
|
112
|
+
return (
|
|
113
|
+
normalized.startswith(".cowork-flow/spec/")
|
|
114
|
+
or normalized == ".cowork-flow/workflow.md"
|
|
115
|
+
)
|
|
104
116
|
|
|
105
117
|
|
|
106
118
|
def _get_git_diff(repo_root: Path, files: list[str]) -> str:
|
|
107
|
-
"""Get
|
|
119
|
+
"""Get unstaged and staged diff output for changed files."""
|
|
108
120
|
if not files:
|
|
109
121
|
return ""
|
|
110
122
|
|
|
111
123
|
try:
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
124
|
+
outputs = []
|
|
125
|
+
for extra_args in ((), ("--cached",)):
|
|
126
|
+
diff_result = subprocess.run(
|
|
127
|
+
["git", "diff", *extra_args, "--", *files],
|
|
128
|
+
cwd=repo_root,
|
|
129
|
+
capture_output=True,
|
|
130
|
+
text=True,
|
|
131
|
+
encoding="utf-8",
|
|
132
|
+
errors="replace",
|
|
133
|
+
)
|
|
134
|
+
if diff_result.returncode == 0:
|
|
135
|
+
outputs.append(diff_result.stdout)
|
|
136
|
+
return "\n".join(outputs)
|
|
121
137
|
except Exception:
|
|
122
138
|
return ""
|
|
123
139
|
|
|
@@ -229,6 +245,8 @@ def _check_unrequested_features(
|
|
|
229
245
|
diff_output: str,
|
|
230
246
|
task_dir: Path,
|
|
231
247
|
rule_index: dict[str, dict],
|
|
248
|
+
*,
|
|
249
|
+
modified_files: list[str] | None = None,
|
|
232
250
|
) -> list[dict]:
|
|
233
251
|
"""R-AG-005: Require modified files to be explicitly listed."""
|
|
234
252
|
del diff_output
|
|
@@ -242,8 +260,12 @@ def _check_unrequested_features(
|
|
|
242
260
|
return violations
|
|
243
261
|
|
|
244
262
|
# Get modified project files (exclude .cowork-flow/ metadata)
|
|
245
|
-
|
|
246
|
-
|
|
263
|
+
changed_files = (
|
|
264
|
+
modified_files
|
|
265
|
+
if modified_files is not None
|
|
266
|
+
else _get_modified_files(repo_root)
|
|
267
|
+
)
|
|
268
|
+
for file_path in changed_files:
|
|
247
269
|
# Skip .cowork-flow metadata files and template files
|
|
248
270
|
if ".cowork-flow/" in file_path:
|
|
249
271
|
continue
|
|
@@ -19,6 +19,14 @@ class ChangedFile:
|
|
|
19
19
|
|
|
20
20
|
def collect_changed_files(repo_root: Path) -> list[ChangedFile]:
|
|
21
21
|
"""Return modified, staged, and untracked files from git status."""
|
|
22
|
+
prefix_rc, prefix_stdout, _ = _run_git_command(
|
|
23
|
+
["rev-parse", "--show-prefix"],
|
|
24
|
+
cwd=repo_root,
|
|
25
|
+
)
|
|
26
|
+
if prefix_rc != 0:
|
|
27
|
+
return []
|
|
28
|
+
repo_prefix = prefix_stdout.strip().replace("\\", "/")
|
|
29
|
+
|
|
22
30
|
rc, stdout, _ = _run_git_command(
|
|
23
31
|
["status", "--porcelain=v1", "-uall", "--", "."],
|
|
24
32
|
cwd=repo_root,
|
|
@@ -32,6 +40,12 @@ def collect_changed_files(repo_root: Path) -> list[ChangedFile]:
|
|
|
32
40
|
if parsed is None:
|
|
33
41
|
continue
|
|
34
42
|
path, statuses = parsed
|
|
43
|
+
if repo_prefix:
|
|
44
|
+
if not path.startswith(repo_prefix):
|
|
45
|
+
continue
|
|
46
|
+
path = path[len(repo_prefix):]
|
|
47
|
+
if not path:
|
|
48
|
+
continue
|
|
35
49
|
changed.setdefault(path, set()).update(statuses)
|
|
36
50
|
|
|
37
51
|
return [
|
|
@@ -2,18 +2,39 @@
|
|
|
2
2
|
|
|
3
3
|
> Internal protocol enforced by workflow readiness; it is not a public Skill.
|
|
4
4
|
|
|
5
|
+
## Purpose
|
|
6
|
+
|
|
7
|
+
`decision-review` is the mandatory L2 evidence gate for non-trivial decisions.
|
|
8
|
+
It preserves the adversarial doubt cycle from `doubt-review` while keeping the
|
|
9
|
+
runtime authority in `decision-review.jsonl`.
|
|
10
|
+
|
|
5
11
|
## Enforcement
|
|
6
12
|
|
|
7
13
|
- L2 tasks: mandatory before task start.
|
|
8
14
|
- L0/L1 tasks: advisory and never blocked by missing decision review evidence.
|
|
9
15
|
- The runtime authority is `decision-review.jsonl`; prose notes do not satisfy the gate.
|
|
10
16
|
|
|
17
|
+
## Doubt Cycle
|
|
18
|
+
|
|
19
|
+
Every accepted L2 decision review must be derived from this bounded cycle:
|
|
20
|
+
|
|
21
|
+
1. CLAIM: state the decision and why being wrong would be costly.
|
|
22
|
+
2. EXTRACT: isolate the minimal reviewable artifact or design statement.
|
|
23
|
+
3. DOUBT: ask a fresh context to find problems in ARTIFACT + CONTRACT, not CLAIM.
|
|
24
|
+
4. RECONCILE: classify each finding as contract misunderstanding, actionable,
|
|
25
|
+
accepted trade-off, or noise.
|
|
26
|
+
5. STOP: stop after trivial or already-considered findings, three cycles, or the
|
|
27
|
+
user explicitly says enough.
|
|
28
|
+
|
|
29
|
+
The reviewer receives ARTIFACT + CONTRACT, not CLAIM. CLAIM is recorded for
|
|
30
|
+
traceability, but it must not bias the adversarial review prompt.
|
|
31
|
+
|
|
11
32
|
## Evidence
|
|
12
33
|
|
|
13
34
|
Write one UTF-8 JSON object per line to `<task>/decision-review.jsonl`:
|
|
14
35
|
|
|
15
36
|
```json
|
|
16
|
-
{"acceptanceId":"AC-007A","claim":"The selected direction is safe.","contract":"The invariant that must hold.","reviewerContext":"fresh","findings":[],"resolution":"accepted"}
|
|
37
|
+
{"acceptanceId":"AC-007A","claim":"The selected direction is safe.","contract":"The invariant that must hold.","reviewerContext":"fresh","findings":[],"resolution":"accepted","artifact":"Focused diff or design statement reviewed via ARTIFACT + CONTRACT.","reconciliation":[],"cycleCount":1}
|
|
17
38
|
```
|
|
18
39
|
|
|
19
40
|
Required fields:
|
|
@@ -25,4 +46,10 @@ Required fields:
|
|
|
25
46
|
- `findings`: JSON array
|
|
26
47
|
- `resolution`: exactly `accepted`
|
|
27
48
|
|
|
49
|
+
Recommended fields:
|
|
50
|
+
|
|
51
|
+
- `artifact`: minimal EXTRACT artifact reviewed.
|
|
52
|
+
- `reconciliation`: how each finding was classified.
|
|
53
|
+
- `cycleCount`: number of completed doubt cycles, never greater than 3.
|
|
54
|
+
|
|
28
55
|
Missing, malformed, stale-context, or unaccepted records block L2 task start.
|
|
@@ -8,6 +8,25 @@ Update `.cowork-flow/spec/` when a change introduces or changes a reusable comma
|
|
|
8
8
|
|
|
9
9
|
Do not add one-off implementation narration. Prefer the narrowest existing spec and update its index when a new topic is introduced.
|
|
10
10
|
|
|
11
|
+
## Choose Location
|
|
12
|
+
|
|
13
|
+
- `backend/` or `frontend/`: implementation contracts, signatures, examples, validation behavior, and test points.
|
|
14
|
+
- `guides/`: short thinking checklists and pointers to deeper specs.
|
|
15
|
+
- `contracts/`: cross-agent, runtime, host-adapter, or persisted-state contracts.
|
|
16
|
+
- `runtime/` or `schemas/`: machine-readable rules, registries, and schema-owned metadata.
|
|
17
|
+
- `workflow.md` or `AGENTS.md`: process rules, phase gates, collaboration rules, or task routing behavior.
|
|
18
|
+
|
|
19
|
+
## Write
|
|
20
|
+
|
|
21
|
+
Keep each update concrete:
|
|
22
|
+
|
|
23
|
+
- State the trigger and scope.
|
|
24
|
+
- Show the contract or command shape.
|
|
25
|
+
- Include good/bad cases when useful.
|
|
26
|
+
- State tests or checks that protect the behavior.
|
|
27
|
+
- Update the matching `index.md` when adding a new topic.
|
|
28
|
+
- Check for duplicate guidance and remove stale wording before finishing.
|
|
29
|
+
|
|
11
30
|
## Output
|
|
12
31
|
|
|
13
32
|
Every Host reports `specUpdates` as one of:
|
|
@@ -28,3 +28,16 @@ Write one JSON object per line to `<task>/tdd.jsonl` with:
|
|
|
28
28
|
- `greenCommand`, `greenExitCode`, `broaderVerification`
|
|
29
29
|
|
|
30
30
|
Only docs/comment/format-only work may use a documented `exemption`.
|
|
31
|
+
|
|
32
|
+
## Anti-Rationalization
|
|
33
|
+
|
|
34
|
+
The following excuses do not exempt the TDD evidence requirement:
|
|
35
|
+
|
|
36
|
+
| Excuse | Why It Fails | Required Alternative |
|
|
37
|
+
|---|---|---|
|
|
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`. |
|
|
40
|
+
| "I'll add tests after implementation" | Post-implementation tests are not a red-green cycle. | Red first, then green; record both commands. |
|
|
41
|
+
| "It looks correct" | Visual inspection misses scenarios that tests can pin down. | Turn the expected behavior and edge case into assertions. |
|
|
42
|
+
| "I've written similar tests" | Similar structure is not the same behavior. | Re-run the redCommand for this acceptanceId. |
|
|
43
|
+
| "This is internal only" | Internal behavior still affects callers and workflow state. | Test through the public entry point, or directly test the internal contract when that is the narrowest stable surface. |
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
| G2 | L1/L2: tdd.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
|
-
| G5 |
|
|
13
|
+
| G5 | Git status snapshot 仅含任务相关变更,包括 staged、unstaged、untracked | git status --porcelain=v1 -uall |
|
|
14
14
|
|
|
15
15
|
## L0 专属(docs/format/comments only)
|
|
16
16
|
|
|
@@ -190,7 +190,6 @@
|
|
|
190
190
|
".agents/skills/before-dev",
|
|
191
191
|
".agents/skills/check",
|
|
192
192
|
".agents/skills/continue",
|
|
193
|
-
".agents/skills/doubt-review",
|
|
194
193
|
".agents/skills/entry-boundary",
|
|
195
194
|
".agents/skills/finish-work",
|
|
196
195
|
".agents/skills/party-mode-v2",
|
|
@@ -201,7 +200,6 @@
|
|
|
201
200
|
".claude/skills/before-dev",
|
|
202
201
|
".claude/skills/check",
|
|
203
202
|
".claude/skills/continue",
|
|
204
|
-
".claude/skills/doubt-review",
|
|
205
203
|
".claude/skills/entry-boundary",
|
|
206
204
|
".claude/skills/finish-work",
|
|
207
205
|
".claude/skills/party-mode-v2",
|
|
@@ -149,12 +149,40 @@
|
|
|
149
149
|
"source": ".cowork-flow/spec/protocols/decision-review.md",
|
|
150
150
|
"managedPaths": []
|
|
151
151
|
},
|
|
152
|
+
{
|
|
153
|
+
"id": "doubt-review",
|
|
154
|
+
"displayName": "Doubt Review",
|
|
155
|
+
"aliases": [
|
|
156
|
+
"adversarial-review"
|
|
157
|
+
],
|
|
158
|
+
"kind": "protocol",
|
|
159
|
+
"visibility": "public",
|
|
160
|
+
"status": "active",
|
|
161
|
+
"statuses": [
|
|
162
|
+
"planning",
|
|
163
|
+
"in_progress",
|
|
164
|
+
"review",
|
|
165
|
+
"delegated_subtask"
|
|
166
|
+
],
|
|
167
|
+
"intents": [
|
|
168
|
+
"doubt_review"
|
|
169
|
+
],
|
|
170
|
+
"enforcement": "advisory",
|
|
171
|
+
"runtimeGate": null,
|
|
172
|
+
"runtimeCommand": null,
|
|
173
|
+
"evidenceArtifact": "decision-review.jsonl",
|
|
174
|
+
"source": "skills/doubt-review/SKILL.md",
|
|
175
|
+
"managedPaths": [
|
|
176
|
+
".agents/skills/doubt-review/",
|
|
177
|
+
".claude/skills/doubt-review/"
|
|
178
|
+
]
|
|
179
|
+
},
|
|
152
180
|
{
|
|
153
181
|
"id": "game-design",
|
|
154
182
|
"displayName": "Game Design",
|
|
155
183
|
"aliases": [],
|
|
156
184
|
"kind": "domain",
|
|
157
|
-
"visibility": "
|
|
185
|
+
"visibility": "public",
|
|
158
186
|
"status": "active",
|
|
159
187
|
"statuses": [
|
|
160
188
|
"planning",
|
|
@@ -216,7 +216,7 @@ L2 任务在 `task start` 前必须通过 readiness gate;同一 blocker 列表
|
|
|
216
216
|
- 所有声明通过的验证都有命令输出依据。
|
|
217
217
|
- 规格已更新,或明确判断无需更新。
|
|
218
218
|
- 计划状态、任务状态、`change` 元数据不冲突。
|
|
219
|
-
-
|
|
219
|
+
- 先归档并提交实现,再用真实实现提交哈希记录会话;会话元数据使用 follow-up metadata commit 提交。
|
|
220
220
|
- 不纳入无关脏改。
|
|
221
221
|
|
|
222
222
|
2. 顺序:
|
|
@@ -226,11 +226,15 @@ L2 任务在 `task start` 前必须通过 readiness gate;同一 blocker 列表
|
|
|
226
226
|
git diff --check
|
|
227
227
|
npm run test:all
|
|
228
228
|
./.cowork-flow/run task archive <task-name>
|
|
229
|
-
./.cowork-flow/run add-session --title "<title>" --commit "-" --summary "<summary>"
|
|
230
229
|
git status --short
|
|
231
230
|
git add <expected files>
|
|
232
231
|
git commit -m "<message>"
|
|
232
|
+
implementation_commit="$(git rev-parse HEAD)"
|
|
233
|
+
./.cowork-flow/run add-session --title "<title>" --commit "$implementation_commit" --summary "<summary>"
|
|
234
|
+
git add .cowork-flow/workspace/<developer>/
|
|
235
|
+
git commit -m "docs(session): record <title>"
|
|
233
236
|
```
|
|
237
|
+
第二个提交是明确的 follow-up metadata commit;session 中的 `commit` 始终指向前一个真实实现提交,不使用永久占位值。
|
|
234
238
|
`task archive <task-name>` 会归档 task,并自动归档 `change.yaml.task`
|
|
235
239
|
指向该 task 的 active change;无法通过 `change validate` 的 change 不会被自动归档。
|
|
236
240
|
|
|
@@ -216,7 +216,7 @@ L2 任务在 `task start` 前必须通过 readiness gate;同一 blocker 列表
|
|
|
216
216
|
- 所有声明通过的验证都有命令输出依据。
|
|
217
217
|
- 规格已更新,或明确判断无需更新。
|
|
218
218
|
- 计划状态、任务状态、`change` 元数据不冲突。
|
|
219
|
-
-
|
|
219
|
+
- 先归档并提交实现,再用真实实现提交哈希记录会话;会话元数据使用 follow-up metadata commit 提交。
|
|
220
220
|
- 不纳入无关脏改。
|
|
221
221
|
|
|
222
222
|
2. 顺序:
|
|
@@ -226,11 +226,15 @@ L2 任务在 `task start` 前必须通过 readiness gate;同一 blocker 列表
|
|
|
226
226
|
git diff --check
|
|
227
227
|
npm run test:all
|
|
228
228
|
./.cowork-flow/run task archive <task-name>
|
|
229
|
-
./.cowork-flow/run add-session --title "<title>" --commit "-" --summary "<summary>"
|
|
230
229
|
git status --short
|
|
231
230
|
git add <expected files>
|
|
232
231
|
git commit -m "<message>"
|
|
232
|
+
implementation_commit="$(git rev-parse HEAD)"
|
|
233
|
+
./.cowork-flow/run add-session --title "<title>" --commit "$implementation_commit" --summary "<summary>"
|
|
234
|
+
git add .cowork-flow/workspace/<developer>/
|
|
235
|
+
git commit -m "docs(session): record <title>"
|
|
233
236
|
```
|
|
237
|
+
第二个提交是明确的 follow-up metadata commit;session 中的 `commit` 始终指向前一个真实实现提交,不使用永久占位值。
|
|
234
238
|
`task archive <task-name>` 会归档 task,并自动归档 `change.yaml.task`
|
|
235
239
|
指向该 task 的 active change;无法通过 `change validate` 的 change 不会被自动归档。
|
|
236
240
|
|