cowork-flow 0.0.33 → 0.0.35
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/package.json +1 -1
- package/template/.claude/agents/cowork-check.md +1 -1
- package/template/.claude/agents/cowork-implement.md +2 -2
- package/template/.cowork-flow/.version +1 -1
- package/template/.cowork-flow/scripts/__init__.py +1 -0
- package/template/.cowork-flow/scripts/commands/task.py +50 -18
- package/template/.cowork-flow/scripts/common/core/execution_context.py +2 -2
- package/template/.cowork-flow/scripts/common/gates/tdd_evidence.py +16 -4
- package/template/.cowork-flow/scripts/common/gates/validate_implementation.py +47 -17
- package/template/.cowork-flow/scripts/common/git/git_context.py +4 -4
- package/template/.cowork-flow/scripts/common/task/readiness.py +7 -7
- package/template/.cowork-flow/spec/contracts/decision-anchor.md +68 -0
- package/template/.cowork-flow/spec/contracts/workflow-state-templates.md +1 -1
- package/template/.cowork-flow/spec/references/definition-of-done.md +1 -1
- package/template/.cowork-flow/spec/runtime/rules.json +1 -1
- package/template/.cowork-flow/spec/schemas/rules.schema.json +1 -1
- package/template/.cowork-flow/workflow.md +4 -4
- package/template/.opencode/agents/cowork-check.md +1 -1
- package/template/.opencode/agents/cowork-implement.md +2 -2
- package/template/.opencode/agents/cowork-research.md +1 -1
- package/template/.zcode/.zcode-plugin/plugin.json +1 -1
- package/template/.zcode/hooks/runtime/scripts/__init__.py +1 -0
- package/template/.zcode/hooks/runtime/scripts/commands/task.py +51 -19
- package/template/.zcode/hooks/runtime/scripts/common/core/execution_context.py +2 -2
- package/template/.zcode/hooks/runtime/scripts/common/gates/tdd_evidence.py +16 -4
- package/template/.zcode/hooks/runtime/scripts/common/gates/validate_implementation.py +47 -17
- package/template/.zcode/hooks/runtime/scripts/common/git/git_context.py +4 -4
- package/template/.zcode/hooks/runtime/scripts/common/task/readiness.py +25 -7
- package/template/.zcode/scaffold/.cowork-flow/run +79 -0
- package/template/.zcode/scaffold/.cowork-flow/run.cmd +88 -0
- package/template/.zcode/scaffold/.cowork-flow/spec/contracts/decision-anchor.md +68 -0
- package/template/.zcode/scaffold/.cowork-flow/spec/contracts/workflow-state-templates.md +1 -1
- package/template/.zcode/scaffold/.cowork-flow/spec/runtime/rules.json +60 -0
- package/template/.zcode/scaffold/.cowork-flow/spec/schemas/rules.schema.json +1 -1
- package/template/.zcode/scaffold/.cowork-flow/workflow.md +9 -4
- package/template/skills/before-dev/SKILL.md +3 -3
- package/template/skills/continue/SKILL.md +1 -1
package/package.json
CHANGED
|
@@ -35,7 +35,7 @@ the task.
|
|
|
35
35
|
|
|
36
36
|
Load context before checking:
|
|
37
37
|
1. Read the task directory from the bound runtime context.
|
|
38
|
-
2. Read `<task>/
|
|
38
|
+
2. Read `<task>/decision-anchor.md`.
|
|
39
39
|
3. 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). Use the plan's Verify commands and Expected results as the checklist for each step.
|
|
40
40
|
4. Read `<task>/check.jsonl`.
|
|
41
41
|
5. Read `git diff`.
|
|
@@ -35,8 +35,8 @@ the task.
|
|
|
35
35
|
|
|
36
36
|
Load context before editing:
|
|
37
37
|
1. Read the task directory from the bound runtime context.
|
|
38
|
-
2. Read `<task>/
|
|
39
|
-
3. Read
|
|
38
|
+
2. Read `<task>/decision-anchor.md`.
|
|
39
|
+
3. Read .
|
|
40
40
|
4. Read `<task>/implement.jsonl`.
|
|
41
41
|
5. Read each JSONL `file` entry.
|
|
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.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
0.0.
|
|
1
|
+
0.0.35
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Cowork Flow runtime scripts."""
|
|
@@ -39,6 +39,7 @@ else:
|
|
|
39
39
|
import _bootstrap # noqa: F401
|
|
40
40
|
from common.core.files import (
|
|
41
41
|
read_json_file as _read_json_file,
|
|
42
|
+
read_text_utf8 as _read_text,
|
|
42
43
|
write_json_file as _write_json_file,
|
|
43
44
|
)
|
|
44
45
|
from common.gates.gates import GateResult, GateRunner
|
|
@@ -466,17 +467,6 @@ def _write_jsonl(path: Path, entries: list[dict]) -> None:
|
|
|
466
467
|
path.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
|
467
468
|
|
|
468
469
|
|
|
469
|
-
def _read_text_if_present(path: Path) -> str:
|
|
470
|
-
"""读取文本文件并返回去空白后的内容;不存在或读取失败时返回空字符串。"""
|
|
471
|
-
if not path.is_file():
|
|
472
|
-
return ""
|
|
473
|
-
|
|
474
|
-
try:
|
|
475
|
-
return path.read_text(encoding="utf-8").strip()
|
|
476
|
-
except (OSError, IOError):
|
|
477
|
-
return ""
|
|
478
|
-
|
|
479
|
-
|
|
480
470
|
def _task_start_blockers(task_dir: Path) -> list[str]:
|
|
481
471
|
"""返回启动任务前必须处理的准备阻塞项。"""
|
|
482
472
|
blockers: list[str] = []
|
|
@@ -485,13 +475,17 @@ def _task_start_blockers(task_dir: Path) -> list[str]:
|
|
|
485
475
|
if not task_json.is_file():
|
|
486
476
|
blockers.append("task.json is missing")
|
|
487
477
|
|
|
488
|
-
|
|
489
|
-
if
|
|
490
|
-
|
|
478
|
+
# 只检查存在性 — R-WF-008 负责章节内容完整性
|
|
479
|
+
if _read_text(task_dir / "prd.md"):
|
|
480
|
+
# 旧格式:提示需要迁移(cmd_start 会自动迁移)
|
|
481
|
+
if not _read_text(task_dir / "decision-anchor.md"):
|
|
482
|
+
blockers.append("prd.md found; task start will auto-migrate to decision-anchor.md")
|
|
483
|
+
elif not _read_text(task_dir / "decision-anchor.md"):
|
|
484
|
+
blockers.append("decision-anchor.md is missing or empty")
|
|
491
485
|
|
|
492
486
|
for jsonl_name in CONTEXT_JSONL_FILES:
|
|
493
487
|
jsonl_file = task_dir / jsonl_name
|
|
494
|
-
if not
|
|
488
|
+
if not _read_text(jsonl_file):
|
|
495
489
|
blockers.append(f"{jsonl_name} is missing or empty")
|
|
496
490
|
|
|
497
491
|
return blockers
|
|
@@ -512,7 +506,7 @@ def _task_context_validation_issues(
|
|
|
512
506
|
|
|
513
507
|
for jsonl_name in CONTEXT_JSONL_FILES:
|
|
514
508
|
jsonl_file = task_dir / jsonl_name
|
|
515
|
-
if not
|
|
509
|
+
if not _read_text(jsonl_file):
|
|
516
510
|
continue
|
|
517
511
|
|
|
518
512
|
error_count = _validate_jsonl(jsonl_file, repo_root, quiet=quiet)
|
|
@@ -661,10 +655,29 @@ def cmd_create(args: argparse.Namespace) -> int:
|
|
|
661
655
|
|
|
662
656
|
print(colored(f"Linked as child of: {parent_dir.name}", Colors.GREEN), file=sys.stderr)
|
|
663
657
|
|
|
658
|
+
# --from-plan: 自动生成 decision-anchor.md 骨架
|
|
659
|
+
if getattr(args, "from_plan", None):
|
|
660
|
+
plan_path = Path(args.from_plan)
|
|
661
|
+
if plan_path.is_file():
|
|
662
|
+
plan_text = plan_path.read_text(encoding="utf-8")
|
|
663
|
+
goal = next(
|
|
664
|
+
(line.split(":", 1)[1].strip() for line in plan_text.splitlines()
|
|
665
|
+
if line.startswith("**Goal:**") or line.startswith("**目标:**")),
|
|
666
|
+
None,
|
|
667
|
+
)
|
|
668
|
+
if goal:
|
|
669
|
+
anchor_file = task_dir / "decision-anchor.md"
|
|
670
|
+
if not anchor_file.exists():
|
|
671
|
+
anchor_file.write_text(
|
|
672
|
+
f"## 目标\n\n{goal}\n\n## 验收标准\n- [ ] \n",
|
|
673
|
+
encoding="utf-8",
|
|
674
|
+
)
|
|
675
|
+
print(f" {colored('[OK]', Colors.GREEN)} Generated decision-anchor.md from plan")
|
|
676
|
+
|
|
664
677
|
print(colored(f"Created task: {dir_name}", Colors.GREEN), file=sys.stderr)
|
|
665
678
|
print("", file=sys.stderr)
|
|
666
679
|
print(colored("Next steps:", Colors.BLUE), file=sys.stderr)
|
|
667
|
-
print(" 1. Create
|
|
680
|
+
print(" 1. Create decision-anchor.md with requirements", file=sys.stderr)
|
|
668
681
|
print(" 2. Run: ./.cowork-flow/run task init-context <dir> <dev_type>", file=sys.stderr)
|
|
669
682
|
print(" 3. Run: ./.cowork-flow/run task start <dir>", file=sys.stderr)
|
|
670
683
|
print("", file=sys.stderr)
|
|
@@ -961,11 +974,29 @@ def cmd_start(args: argparse.Namespace) -> int:
|
|
|
961
974
|
for blocker in blockers:
|
|
962
975
|
print(f" - {blocker}", file=sys.stderr)
|
|
963
976
|
print(
|
|
964
|
-
"Hint: write
|
|
977
|
+
"Hint: write decision-anchor.md, run ./.cowork-flow/run task init-context <dir> <dev_type>, then retry",
|
|
965
978
|
file=sys.stderr,
|
|
966
979
|
)
|
|
967
980
|
return 1
|
|
968
981
|
|
|
982
|
+
# 自动迁移旧 prd.md 到 decision-anchor.md
|
|
983
|
+
prd_file = full_path / "prd.md"
|
|
984
|
+
anchor_file = full_path / "decision-anchor.md"
|
|
985
|
+
if prd_file.exists() and not anchor_file.exists():
|
|
986
|
+
content = prd_file.read_text(encoding="utf-8").strip()
|
|
987
|
+
if not content:
|
|
988
|
+
content = "(empty legacy prd.md)"
|
|
989
|
+
if "## 目标" not in content and "## Goal" not in content:
|
|
990
|
+
content = "## 目标\n\n" + content
|
|
991
|
+
if "## 验收标准" not in content and "## Acceptance" not in content:
|
|
992
|
+
content += "\n\n## 验收标准\n- [ ] \n"
|
|
993
|
+
anchor_file.write_text(content, encoding="utf-8")
|
|
994
|
+
prd_file.unlink()
|
|
995
|
+
print(
|
|
996
|
+
colored(" [迁移] prd.md → decision-anchor.md", Colors.YELLOW),
|
|
997
|
+
file=sys.stderr,
|
|
998
|
+
)
|
|
999
|
+
|
|
969
1000
|
validation_issues = _task_context_validation_issues(full_path, repo_root)
|
|
970
1001
|
if validation_issues:
|
|
971
1002
|
print(colored("Error: Task context validation failed", Colors.RED), file=sys.stderr)
|
|
@@ -1937,6 +1968,7 @@ def main() -> int:
|
|
|
1937
1968
|
p_create.add_argument("--priority", "-p", default="P2", help="Priority (P0-P3)")
|
|
1938
1969
|
p_create.add_argument("--description", "-d", help="Task description")
|
|
1939
1970
|
p_create.add_argument("--parent", help="Parent task directory (establishes subtask link)")
|
|
1971
|
+
p_create.add_argument("--from-plan", "-f", help="Path to plan file (auto-generate decision-anchor skeleton)")
|
|
1940
1972
|
|
|
1941
1973
|
# init-context
|
|
1942
1974
|
p_init = subparsers.add_parser("init-context", help="Initialize context files")
|
|
@@ -268,8 +268,8 @@ def build_worker_resume_text(
|
|
|
268
268
|
if isinstance(file_value, str) and file_value.strip():
|
|
269
269
|
suffix = f" - {reason}" if isinstance(reason, str) and reason.strip() else ""
|
|
270
270
|
lines.append(f"- {file_value}{suffix}")
|
|
271
|
-
if context.task_dir and (repo_root / context.task_dir / "
|
|
272
|
-
lines.append(f"- Read task PRD: {context.task_dir}/
|
|
271
|
+
if context.task_dir and (repo_root / context.task_dir / "decision-anchor.md").is_file():
|
|
272
|
+
lines.append(f"- Read task PRD: {context.task_dir}/decision-anchor.md")
|
|
273
273
|
lines.append("- Follow only the files, steps, and commands named in the worker brief.")
|
|
274
274
|
|
|
275
275
|
forbidden_actions = context_data.get("forbiddenActions")
|
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
2
|
+
"""
|
|
3
|
+
TDD 红绿证据校验(task_review / task_complete 阶段)。
|
|
4
|
+
|
|
5
|
+
读取 <task>/tdd.jsonl,验证每条 evidence 或 exemption 记录的结构完整
|
|
6
|
+
性与业务意图。验证结果分两类出口:
|
|
7
|
+
- validate_tdd_evidence: 全量校验(review 阶段)
|
|
8
|
+
- validate_tdd_red_evidence: 仅检查 red 证据完整性(implement 入口提醒)
|
|
9
|
+
|
|
10
|
+
acceptanceId 的提取同时支持 AC-001、AC-1、验收标准:1 三种写法。
|
|
11
|
+
"""
|
|
3
12
|
|
|
4
13
|
from __future__ import annotations
|
|
5
14
|
|
|
@@ -82,7 +91,7 @@ SETUP_FAILURE_MARKERS = (
|
|
|
82
91
|
def validate_tdd_evidence(task_dir: Path) -> list[dict]:
|
|
83
92
|
"""Return TDD evidence violations for a task directory."""
|
|
84
93
|
task_dir = Path(task_dir)
|
|
85
|
-
prd_text = _read_text(task_dir / "
|
|
94
|
+
prd_text = _read_text(task_dir / "decision-anchor.md")
|
|
86
95
|
evidence_path = task_dir / EVIDENCE_FILE
|
|
87
96
|
|
|
88
97
|
if not evidence_path.is_file():
|
|
@@ -127,7 +136,7 @@ def validate_tdd_evidence(task_dir: Path) -> list[dict]:
|
|
|
127
136
|
def validate_tdd_red_evidence(task_dir: Path) -> list[dict]:
|
|
128
137
|
"""Return violations when implementation would start without red evidence."""
|
|
129
138
|
task_dir = Path(task_dir)
|
|
130
|
-
prd_text = _read_text(task_dir / "
|
|
139
|
+
prd_text = _read_text(task_dir / "decision-anchor.md")
|
|
131
140
|
if not _task_requires_tdd(prd_text):
|
|
132
141
|
return []
|
|
133
142
|
|
|
@@ -210,7 +219,10 @@ def _read_jsonl(path: Path) -> tuple[list[dict], list[dict]]:
|
|
|
210
219
|
|
|
211
220
|
|
|
212
221
|
def _acceptance_ids(prd_text: str) -> set[str]:
|
|
213
|
-
|
|
222
|
+
"""提取验收标准 ID,支持 AC-001、AC-1、验收标准:1 等格式。"""
|
|
223
|
+
ids = set(re.findall(r"\bAC-(\d{1,4})\b", prd_text, re.IGNORECASE))
|
|
224
|
+
ids |= set(re.findall(r"验收标准[::]\s*(\d+)", prd_text))
|
|
225
|
+
return {f"AC-{n}" for n in ids}
|
|
214
226
|
|
|
215
227
|
|
|
216
228
|
def _validate_evidence(
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
"""
|
|
3
|
-
|
|
3
|
+
R-AG-002 到 R-AG-009 的门禁检查器。
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
在 task_review 阶段运行:通过 git diff 获取工作树的未提交改动,逐项
|
|
6
|
+
检查 forbidden_action 规则。每项规则以 (repo_root, modified_files, diff_output,
|
|
7
|
+
rule_index) 为入参,返回 violation dict 列表。
|
|
8
|
+
|
|
9
|
+
结果汇总到 GateResult:存在 severity=block 的 violation 时阻断 review 流程,
|
|
10
|
+
warn 级别仅输出警告。
|
|
6
11
|
"""
|
|
7
12
|
|
|
8
13
|
from __future__ import annotations
|
|
@@ -186,29 +191,54 @@ def _check_unrequested_features(
|
|
|
186
191
|
task_dir: Path,
|
|
187
192
|
rule_index: dict[str, dict],
|
|
188
193
|
) -> list[dict]:
|
|
189
|
-
"""R-AG-005: Check if unrequested
|
|
194
|
+
"""R-AG-005: Check if unrequested files were modified (diff files must be in implement.jsonl)."""
|
|
190
195
|
violations = []
|
|
191
196
|
|
|
192
|
-
#
|
|
193
|
-
|
|
194
|
-
if not
|
|
197
|
+
# Collect allowed files from implement.jsonl
|
|
198
|
+
implement_jsonl = task_dir / "implement.jsonl"
|
|
199
|
+
if not implement_jsonl.exists():
|
|
195
200
|
return violations
|
|
196
201
|
|
|
197
|
-
|
|
202
|
+
allowed_files: set[str] = set()
|
|
203
|
+
try:
|
|
204
|
+
for line in implement_jsonl.read_text(encoding="utf-8").splitlines():
|
|
205
|
+
line = line.strip()
|
|
206
|
+
if not line:
|
|
207
|
+
continue
|
|
208
|
+
try:
|
|
209
|
+
entry = json.loads(line)
|
|
210
|
+
except json.JSONDecodeError:
|
|
211
|
+
continue
|
|
212
|
+
file_path = entry.get("file")
|
|
213
|
+
if isinstance(file_path, str) and file_path:
|
|
214
|
+
# Normalize paths (strip repo root prefix if present)
|
|
215
|
+
allowed_files.add(file_path)
|
|
216
|
+
# Also add stripped versions for matching
|
|
217
|
+
if file_path.startswith("./"):
|
|
218
|
+
allowed_files.add(file_path[2:])
|
|
219
|
+
else:
|
|
220
|
+
allowed_files.add(f"./{file_path}")
|
|
221
|
+
except (OSError, UnicodeDecodeError):
|
|
222
|
+
return violations
|
|
198
223
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
feature_indicators = [
|
|
202
|
-
r"Added.*feature",
|
|
203
|
-
r"New.*functionality",
|
|
204
|
-
r"Implemented.*module",
|
|
205
|
-
]
|
|
224
|
+
if not allowed_files:
|
|
225
|
+
return violations
|
|
206
226
|
|
|
207
|
-
|
|
208
|
-
|
|
227
|
+
# Get modified project files (exclude .cowork-flow/ metadata)
|
|
228
|
+
modified_files = _get_modified_files(repo_root)
|
|
229
|
+
for file_path in modified_files:
|
|
230
|
+
# Skip .cowork-flow metadata files and template files
|
|
231
|
+
if ".cowork-flow/" in file_path:
|
|
232
|
+
continue
|
|
233
|
+
normalized = file_path.replace("\\", "/")
|
|
234
|
+
# Check if file is in allowed list
|
|
235
|
+
if normalized not in allowed_files and f"./{normalized}" not in allowed_files:
|
|
209
236
|
rule = rule_index.get("R-AG-005")
|
|
210
237
|
violations.append(
|
|
211
|
-
rule_violation(
|
|
238
|
+
rule_violation(
|
|
239
|
+
rule, file_path,
|
|
240
|
+
detail=f"modified file not in implement.jsonl allowed list"
|
|
241
|
+
)
|
|
212
242
|
if rule
|
|
213
243
|
else _missing_rule(repo_root, "R-AG-005")
|
|
214
244
|
)
|
|
@@ -189,7 +189,7 @@ def _get_active_task_snapshot(repo_root: Path) -> ActiveTaskSnapshot:
|
|
|
189
189
|
return ActiveTaskSnapshot(
|
|
190
190
|
path=active_task,
|
|
191
191
|
data=data,
|
|
192
|
-
has_prd=(active_task_dir / "
|
|
192
|
+
has_prd=(active_task_dir / "decision-anchor.md").is_file(),
|
|
193
193
|
)
|
|
194
194
|
|
|
195
195
|
|
|
@@ -271,7 +271,7 @@ def _build_resume_checklist(
|
|
|
271
271
|
commands.append(f"./{DIR_WORKFLOW}/run task list-context {active_task}")
|
|
272
272
|
|
|
273
273
|
if snapshot.has_prd:
|
|
274
|
-
read_files.append(f"{active_task}/
|
|
274
|
+
read_files.append(f"{active_task}/decision-anchor.md")
|
|
275
275
|
|
|
276
276
|
read_files.extend(_task_plan_references(repo_root, snapshot))
|
|
277
277
|
notes.append("Read current plan status only when continuing implementation.")
|
|
@@ -314,7 +314,7 @@ def _append_resume_checklist(
|
|
|
314
314
|
return
|
|
315
315
|
|
|
316
316
|
active_task = snapshot.path
|
|
317
|
-
prd_path = f"{active_task}/
|
|
317
|
+
prd_path = f"{active_task}/decision-anchor.md"
|
|
318
318
|
if prd_path in read_files:
|
|
319
319
|
lines.append(f"- Read active task PRD: {prd_path}")
|
|
320
320
|
else:
|
|
@@ -362,7 +362,7 @@ def _append_active_task(
|
|
|
362
362
|
|
|
363
363
|
if include_prd_hint and snapshot.has_prd:
|
|
364
364
|
lines.append("")
|
|
365
|
-
lines.append("[!] This task has
|
|
365
|
+
lines.append("[!] This task has decision-anchor.md - read it for task details")
|
|
366
366
|
lines.append("")
|
|
367
367
|
|
|
368
368
|
|
|
@@ -11,13 +11,13 @@ from .task_utils import find_task_by_name
|
|
|
11
11
|
|
|
12
12
|
REQUIRED_L2_MARKERS = {
|
|
13
13
|
"goal and user value": (
|
|
14
|
-
("## goal", "## problem", "目标"),
|
|
14
|
+
("## goal", "## problem", "## 目标", "目标"),
|
|
15
15
|
("## benefits", "user value", "用户价值", "价值"),
|
|
16
16
|
),
|
|
17
|
-
"non-goals": (("## non-goals", "non-goals", "非目标"),),
|
|
18
|
-
"key assumptions": (("assumption", "assumptions", "关键假设"),),
|
|
19
|
-
"scope boundary": (("## scope", "scope boundary", "范围边界", "scope:"),),
|
|
20
|
-
"acceptance criteria": (("acceptance criteria", "## acceptance", "验收标准", "验收"),),
|
|
17
|
+
"non-goals": (("## non-goals", "non-goals", "## 非目标", "非目标"),),
|
|
18
|
+
"key assumptions": (("assumption", "assumptions", "## 关键假设", "关键假设"),),
|
|
19
|
+
"scope boundary": (("## scope", "scope boundary", "## 范围边界", "范围边界", "scope:"),),
|
|
20
|
+
"acceptance criteria": (("acceptance criteria", "## acceptance", "## 验收标准", "验收标准", "验收"),),
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
VERIFICATION_COMMAND_MARKERS = (
|
|
@@ -167,10 +167,10 @@ def _read_l2_context(repo_root: Path, change_dir: Path, plan_path: Path | None,
|
|
|
167
167
|
_read_text(change_dir / "proposal.md"),
|
|
168
168
|
_read_text(change_dir / "spec.md"),
|
|
169
169
|
_read_text(change_dir / "design.md"),
|
|
170
|
-
_read_text(task_dir / "
|
|
170
|
+
_read_text(task_dir / "decision-anchor.md"),
|
|
171
171
|
]
|
|
172
172
|
for ancestor in _task_ancestry(repo_root, task_dir)[1:]:
|
|
173
|
-
parts.append(_read_text(ancestor / "
|
|
173
|
+
parts.append(_read_text(ancestor / "decision-anchor.md"))
|
|
174
174
|
if plan_path is not None:
|
|
175
175
|
parts.append(_read_text(plan_path))
|
|
176
176
|
return "\n\n".join(part for part in parts if part)
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Decision Anchor Contract
|
|
2
|
+
|
|
3
|
+
**每个 task 必须有 `decision-anchor.md`,它是执行时防止漂移的唯一权威决策来源。**
|
|
4
|
+
|
|
5
|
+
它合并并替代了原来的 `prd.md`,同时承载"做什么"和"为什么不这么做"两个职责,压缩任务产物数量。
|
|
6
|
+
|
|
7
|
+
## Schema
|
|
8
|
+
|
|
9
|
+
```markdown
|
|
10
|
+
# Decision Anchor
|
|
11
|
+
|
|
12
|
+
## 目标
|
|
13
|
+
<一句话>
|
|
14
|
+
|
|
15
|
+
## 验收标准
|
|
16
|
+
- [ ] AC-001: <可验证>
|
|
17
|
+
- [ ] AC-002: <可验证>
|
|
18
|
+
|
|
19
|
+
## 被拒方案(可选)
|
|
20
|
+
- **方案B**: <简述> — 拒绝原因: <为什么>
|
|
21
|
+
|
|
22
|
+
## 关键假设(可选)
|
|
23
|
+
- <假设1>
|
|
24
|
+
|
|
25
|
+
## 范围边界(可选)
|
|
26
|
+
- 范围内: <列出>
|
|
27
|
+
- 范围外: <列出>
|
|
28
|
+
|
|
29
|
+
## 验证命令(可选)
|
|
30
|
+
- `<command>` → <expected>
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## 章节要求
|
|
34
|
+
|
|
35
|
+
| 章节 | 必填 | 原因 |
|
|
36
|
+
|------|------|------|
|
|
37
|
+
| `## 目标` | 必备 | 没有目标就没有验收基准 |
|
|
38
|
+
| `## 验收标准` | 必备 | 没有验收标准 task_start 会被阻断 |
|
|
39
|
+
| `## 被拒方案` | 可选 | brainstorming 任务必填,防止执行时重新考虑被拒方案 |
|
|
40
|
+
| `## 关键假设` | 可选 | 复杂任务填写,暴露隐性约束 |
|
|
41
|
+
| `## 范围边界` | 可选 | 防止范围蔓延 |
|
|
42
|
+
| `## 验证命令` | 可选 | 复杂任务填写,作为自动验证依据 |
|
|
43
|
+
|
|
44
|
+
## 场景
|
|
45
|
+
|
|
46
|
+
| 场景 | 来源 | 填充内容 |
|
|
47
|
+
|------|------|---------|
|
|
48
|
+
| 有 brainstorming | 从讨论结论提取 | 全部章节 |
|
|
49
|
+
| 无 brainstorming(需求明确) | 从需求描述和 plan 提取 | 最小:目标 + 验收标准 |
|
|
50
|
+
|
|
51
|
+
## 产物压缩
|
|
52
|
+
|
|
53
|
+
合并前(6个文件):`prd.md` + `implement.jsonl` + `check.jsonl` + `debug.jsonl` + `tdd.jsonl` + `task.json`
|
|
54
|
+
|
|
55
|
+
合并后(5个文件):`decision-anchor.md` + `implement.jsonl` + `check.jsonl` + `debug.jsonl` + `tdd.jsonl` + `task.json`
|
|
56
|
+
|
|
57
|
+
**净减 1 个文件类型。相关文件追踪现在由 implement.jsonl 统一负责。**
|
|
58
|
+
|
|
59
|
+
## Gate 联动
|
|
60
|
+
|
|
61
|
+
- `R-WF-008`: task_start 阶段阻断,检查 `## 目标` 和 `## 验收标准` 章节存在
|
|
62
|
+
- `cowork-implement` 读取:见 .cowork-flow/spec/contracts/subagent-dispatch.md
|
|
63
|
+
- `cowork-check` 读取:同上
|
|
64
|
+
- `debug.jsonl` 引用:可选,偏差诊断时写入 anchor 锚点差异
|
|
65
|
+
|
|
66
|
+
## 迁移
|
|
67
|
+
|
|
68
|
+
已有 `prd.md` 的任务:首次 `task start` 时自动搬运内容,加默认章节标题,删除 `prd.md`。
|
|
@@ -50,7 +50,7 @@ are constraints only; they are not the task itself.
|
|
|
50
50
|
## planning
|
|
51
51
|
|
|
52
52
|
[workflow-state:planning]
|
|
53
|
-
活动任务处于计划阶段。先完成
|
|
53
|
+
活动任务处于计划阶段。先完成 decision-anchor.md,整理带有规格/调研文件的 implement.jsonl 和 check.jsonl,再运行 task start,之后才派发 cowork-implement。
|
|
54
54
|
[/workflow-state:planning]
|
|
55
55
|
|
|
56
56
|
## in_progress
|
|
@@ -221,7 +221,7 @@
|
|
|
221
221
|
"id": "R-WF-011",
|
|
222
222
|
"type": "phase_gate",
|
|
223
223
|
"severity": "warn",
|
|
224
|
-
"scope": "
|
|
224
|
+
"scope": "all",
|
|
225
225
|
"condition": "Archive should happen before final commit to avoid committing unreviewed code",
|
|
226
226
|
"message": "Uncommitted changes detected. Archive the task first, then commit the archived result.",
|
|
227
227
|
"fix_hint": "Run './.cowork-flow/run task archive <name>' first. Then commit the archive result.",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
},
|
|
39
39
|
"scope": {
|
|
40
40
|
"type": "string",
|
|
41
|
-
"enum": ["task_start", "task_review", "task_complete", "check", "implement", "all"],
|
|
41
|
+
"enum": ["task_start", "task_review", "task_complete", "check", "implement", "task_archive", "all"],
|
|
42
42
|
"description": "Scope where rule applies"
|
|
43
43
|
},
|
|
44
44
|
"condition": {
|
|
@@ -47,7 +47,7 @@ changes -> brainstorming -> read spec -> plan -> tasks -> implement -> check ->
|
|
|
47
47
|
| --- | --- |
|
|
48
48
|
| 开发者身份 | `.cowork-flow/.developer` |
|
|
49
49
|
| 当前会话任务 | `.cowork-flow/.runtime/sessions/<context-key>.json` |
|
|
50
|
-
| 任务目标 | `.cowork-flow/tasks/<task>/
|
|
50
|
+
| 任务目标 | `.cowork-flow/tasks/<task>/decision-anchor.md` |
|
|
51
51
|
| 实现上下文 | `.cowork-flow/tasks/<task>/implement.jsonl` |
|
|
52
52
|
| 检查上下文 | `.cowork-flow/tasks/<task>/check.jsonl` |
|
|
53
53
|
| 调试上下文 | `.cowork-flow/tasks/<task>/debug.jsonl` |
|
|
@@ -77,8 +77,8 @@ changes -> brainstorming -> read spec -> plan -> tasks -> implement -> check ->
|
|
|
77
77
|
| 代理 | 读取 | 允许 | 禁止 | 输出 |
|
|
78
78
|
| --- | --- | --- | --- | --- |
|
|
79
79
|
| `cowork-research` | 任务上下文和调研输入 | 只做调研,只写入 `<task>/research/` | 改代码、改规格、改任务状态、操作 Git | 调研结论和证据 |
|
|
80
|
-
| `cowork-implement` | `<task>/
|
|
81
|
-
| `cowork-check` | `<task>/
|
|
80
|
+
| `cowork-implement` | `<task>/decision-anchor.md`、`<task>/implement.jsonl` 和 JSONL 指向的文件 | 按任务范围实现 | 启动其他代理、提交、归档、运行 `task start`、`task finish` 或 `task archive` | 改动文件和验证命令 |
|
|
81
|
+
| `cowork-check` | `<task>/decision-anchor.md`、`<task>/check.jsonl` 和 `git diff` | 检查行为、测试、规格同步和遗漏;范围内问题直接修复 | 提交、归档、启动其他代理 | 检查结论、修复内容和验证结果 |
|
|
82
82
|
|
|
83
83
|
## 3.1 固定代理派发入口
|
|
84
84
|
|
|
@@ -160,7 +160,7 @@ L2 任务在 `task start` 前必须通过 readiness gate;同一 blocker 列表
|
|
|
160
160
|
```bash
|
|
161
161
|
./.cowork-flow/run task create "<title>" --slug <task-name>
|
|
162
162
|
```
|
|
163
|
-
6. 对创建的每项任务写 `
|
|
163
|
+
6. 对创建的每项任务写 `decision-anchor.md`,至少包含 `## 目标` 和 `## 验收标准` 两个章节。相关文件通过 `implement.jsonl` 跟踪(运行 `task add-context` 添加)。
|
|
164
164
|
7. 初始化上下文:
|
|
165
165
|
```bash
|
|
166
166
|
./.cowork-flow/run task init-context <task-dir> <type>
|
|
@@ -36,7 +36,7 @@ signal.
|
|
|
36
36
|
Rules:
|
|
37
37
|
|
|
38
38
|
- Read the task directory from the bound runtime context.
|
|
39
|
-
- Read `<task>/
|
|
39
|
+
- Read `<task>/decision-anchor.md`, the plan file linked from this task (check `<task>/task.json` `relatedFiles` for a plan path, or search `.cowork-flow/plans/`), `<task>/check.jsonl`, each JSONL `file` entry, and
|
|
40
40
|
current `git diff`.
|
|
41
41
|
- Check behavior, tests, spec sync, and scope.
|
|
42
42
|
- Fix only in-scope issues.
|
|
@@ -175,7 +175,7 @@ You MUST NOT do any of the following:
|
|
|
175
175
|
|
|
176
176
|
1. Bind runtime context: `./.cowork-flow/run subagent bind <runtime_context_id> <host_context_key>`
|
|
177
177
|
2. Read task directory from bound runtime context
|
|
178
|
-
3. Read `<task>/
|
|
178
|
+
3. Read `<task>/decision-anchor.md`, `<task>/implement.jsonl`, and each JSONL `file` entry
|
|
179
179
|
4. 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.
|
|
180
180
|
5. Read pre-implementation checklist: `.cowork-flow/spec/guides/pre-implementation-checklist.md`
|
|
181
181
|
6. Read relevant spec directory (backend or frontend) and ALL linked documents
|
|
@@ -190,7 +190,7 @@ You MUST NOT do any of the following:
|
|
|
190
190
|
Rules:
|
|
191
191
|
|
|
192
192
|
- Read the task directory from the bound runtime context.
|
|
193
|
-
- Read `<task>/
|
|
193
|
+
- Read `<task>/decision-anchor.md`, `<task>/implement.jsonl`,
|
|
194
194
|
and each JSONL `file` entry.
|
|
195
195
|
- Read the plan file linked from this task. Follow the plan steps in order.
|
|
196
196
|
- Keep edits inside the assigned scope.
|
|
@@ -38,7 +38,7 @@ signal.
|
|
|
38
38
|
Rules:
|
|
39
39
|
|
|
40
40
|
- Read the task directory and assignment from the bound runtime context.
|
|
41
|
-
- Read `<task>/
|
|
41
|
+
- Read `<task>/decision-anchor.md`, and prompt-named context.
|
|
42
42
|
- Write research only under `<task>/research/`.
|
|
43
43
|
- Do not edit code, specs, task state, or git state.
|
|
44
44
|
- Do not use the `task` tool or invoke subagents.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Cowork Flow zcode plugin runtime scripts."""
|