cowork-flow 0.0.35 → 0.0.37
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/src/commands/install-zcode-plugin.js +14 -0
- package/template/.codex/agents/cowork-check.toml +1 -1
- package/template/.codex/agents/cowork-implement.toml +1 -1
- package/template/.cowork-flow/.version +1 -1
- package/template/.cowork-flow/scripts/commands/task.py +74 -37
- package/template/.cowork-flow/spec/runtime/rules.json +17 -125
- package/template/.cowork-flow/spec/schemas/rules.schema.json +2 -2
- package/template/.zcode/.zcode-plugin/plugin.json +1 -1
- package/template/.zcode/hooks/inject-context.js +32 -2
- package/template/.zcode/scaffold/.cowork-flow/.version +1 -1
- package/template/.zcode/scaffold/.cowork-flow/run +23 -4
- package/template/.zcode/scaffold/.cowork-flow/run.cmd +27 -4
- package/template/.zcode/scaffold/.cowork-flow/spec/runtime/rules.json +17 -125
- package/template/.zcode/scaffold/.cowork-flow/spec/schemas/rules.schema.json +2 -2
- package/template/skills/batch-mode/SKILL.md +60 -60
- package/template/skills/before-dev/SKILL.md +23 -135
- package/template/skills/break-loop/SKILL.md +26 -25
- package/template/skills/check/SKILL.md +19 -64
- package/template/skills/check/simplification-guide.md +47 -0
- package/template/skills/doubt-review/SKILL.md +73 -73
- package/template/skills/game-design/SKILL.md +1 -1
- package/template/skills/party-mode/SHARED-BOUNDARIES.md +8 -0
- package/template/skills/party-mode/SKILL.md +4 -4
- package/template/skills/party-mode-v2/SKILL.md +4 -4
- package/template/skills/start/SKILL.md +2 -2
- package/template/skills/tdd/SKILL.md +10 -10
- package/template/skills/using-cowork-flow/SKILL.md +80 -0
- package/template/skills/writing-plans/SKILL.md +18 -18
- package/template/.zcode/hooks/runtime/run +0 -58
- package/template/.zcode/hooks/runtime/run.cmd +0 -37
- package/template/.zcode/hooks/runtime/scripts/__init__.py +0 -1
- package/template/.zcode/hooks/runtime/scripts/commands/__init__.py +0 -1
- package/template/.zcode/hooks/runtime/scripts/commands/_bootstrap.py +0 -10
- package/template/.zcode/hooks/runtime/scripts/commands/add_session.py +0 -452
- package/template/.zcode/hooks/runtime/scripts/commands/change.py +0 -412
- package/template/.zcode/hooks/runtime/scripts/commands/doctor.py +0 -471
- package/template/.zcode/hooks/runtime/scripts/commands/get_context.py +0 -20
- package/template/.zcode/hooks/runtime/scripts/commands/get_developer.py +0 -30
- package/template/.zcode/hooks/runtime/scripts/commands/init_developer.py +0 -55
- package/template/.zcode/hooks/runtime/scripts/commands/party_mode_v2.py +0 -1166
- package/template/.zcode/hooks/runtime/scripts/commands/resume.py +0 -62
- package/template/.zcode/hooks/runtime/scripts/commands/subagent.py +0 -321
- package/template/.zcode/hooks/runtime/scripts/commands/task.py +0 -2072
- package/template/.zcode/hooks/runtime/scripts/common/__init__.py +0 -79
- package/template/.zcode/hooks/runtime/scripts/common/core/__init__.py +0 -1
- package/template/.zcode/hooks/runtime/scripts/common/core/config.py +0 -242
- package/template/.zcode/hooks/runtime/scripts/common/core/developer.py +0 -205
- package/template/.zcode/hooks/runtime/scripts/common/core/execution_context.py +0 -385
- package/template/.zcode/hooks/runtime/scripts/common/core/files.py +0 -45
- package/template/.zcode/hooks/runtime/scripts/common/core/paths.py +0 -239
- package/template/.zcode/hooks/runtime/scripts/common/gates/__init__.py +0 -1
- package/template/.zcode/hooks/runtime/scripts/common/gates/anti_rationalization.py +0 -55
- package/template/.zcode/hooks/runtime/scripts/common/gates/coding_standards.py +0 -210
- package/template/.zcode/hooks/runtime/scripts/common/gates/gates.py +0 -71
- package/template/.zcode/hooks/runtime/scripts/common/gates/tdd_evidence.py +0 -378
- package/template/.zcode/hooks/runtime/scripts/common/gates/test_intent.py +0 -262
- package/template/.zcode/hooks/runtime/scripts/common/gates/validate_coding_standards.py +0 -342
- package/template/.zcode/hooks/runtime/scripts/common/gates/validate_implementation.py +0 -273
- package/template/.zcode/hooks/runtime/scripts/common/gates/validate_rules.py +0 -462
- package/template/.zcode/hooks/runtime/scripts/common/git/__init__.py +0 -1
- package/template/.zcode/hooks/runtime/scripts/common/git/git_context.py +0 -765
- package/template/.zcode/hooks/runtime/scripts/common/git/git_snapshot.py +0 -83
- package/template/.zcode/hooks/runtime/scripts/common/task/__init__.py +0 -1
- package/template/.zcode/hooks/runtime/scripts/common/task/active_task.py +0 -403
- package/template/.zcode/hooks/runtime/scripts/common/task/archive_utils.py +0 -72
- package/template/.zcode/hooks/runtime/scripts/common/task/readiness.py +0 -267
- package/template/.zcode/hooks/runtime/scripts/common/task/state_machine.py +0 -56
- package/template/.zcode/hooks/runtime/scripts/common/task/task_utils.py +0 -187
- package/template/.zcode/hooks/runtime/scripts/run.py +0 -138
package/package.json
CHANGED
|
@@ -105,6 +105,20 @@ export async function runInstallZCodePlugin(args = []) {
|
|
|
105
105
|
// Copy plugin runtime (.zcode-plugin/, hooks/, runtime/, scaffold/) from .zcode/
|
|
106
106
|
await cp(pluginSrc, destDir, { recursive: true });
|
|
107
107
|
|
|
108
|
+
// Sync package.json version to scaffold/.version so the plugin-only launcher
|
|
109
|
+
// can resolve the correct versioned plugin cache path at runtime.
|
|
110
|
+
const scaffoldVersionPath = join(destDir, "scaffold", ".cowork-flow", ".version");
|
|
111
|
+
await writeFile(scaffoldVersionPath, `${version}\n`, "utf8");
|
|
112
|
+
|
|
113
|
+
// Sync canonical scripts from main template (single source of truth).
|
|
114
|
+
// The .zcode/hooks/runtime/scripts/ copy is stale; overwrite with the
|
|
115
|
+
// authoritative version from template/.cowork-flow/scripts/.
|
|
116
|
+
const mainScriptsSrc = join(templateRoot, ".cowork-flow", "scripts");
|
|
117
|
+
const pluginScriptsDest = join(destDir, "hooks", "runtime", "scripts");
|
|
118
|
+
if (await pathExists(mainScriptsSrc)) {
|
|
119
|
+
await cp(mainScriptsSrc, pluginScriptsDest, { recursive: true, force: true });
|
|
120
|
+
}
|
|
121
|
+
|
|
108
122
|
// Copy canonical skills into the plugin cache so plugin.json "skills" resolves.
|
|
109
123
|
await cp(skillsSrc, join(destDir, "skills"), { recursive: true, force: true });
|
|
110
124
|
|
|
@@ -26,7 +26,7 @@ the task.
|
|
|
26
26
|
|
|
27
27
|
Load context before checking:
|
|
28
28
|
1. Read the task directory from the bound runtime context.
|
|
29
|
-
2. Read `<task>/
|
|
29
|
+
2. Read `<task>/decision-anchor.md`.
|
|
30
30
|
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.
|
|
31
31
|
4. Read `<task>/check.jsonl`.
|
|
32
32
|
5. Read `git diff`.
|
|
@@ -26,7 +26,7 @@ the task.
|
|
|
26
26
|
|
|
27
27
|
Load context before editing:
|
|
28
28
|
1. Read the task directory from the bound runtime context.
|
|
29
|
-
2. Read `<task>/
|
|
29
|
+
2. Read `<task>/decision-anchor.md`.
|
|
30
30
|
3. Read `<task>/info.md` if present.
|
|
31
31
|
4. Read `<task>/implement.jsonl`.
|
|
32
32
|
5. Read each JSONL `file` entry.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
0.0.
|
|
1
|
+
0.0.37
|
|
@@ -433,22 +433,47 @@ def _skill_path(name: str, repo_root: Path | None = None) -> str:
|
|
|
433
433
|
return f"{DIR_AGENTS}/skills/{name}/SKILL.md"
|
|
434
434
|
|
|
435
435
|
|
|
436
|
-
def
|
|
437
|
-
"""
|
|
438
|
-
|
|
436
|
+
def _is_skill_path(file_path: str) -> bool:
|
|
437
|
+
"""Check if a file path is a skill file (injected by plugin runtime, not a project file)."""
|
|
438
|
+
return (
|
|
439
|
+
file_path.startswith(f"{DIR_AGENTS}/skills/") or file_path.startswith(f".claude/skills/")
|
|
440
|
+
# Also match the legacy pattern for the skills directory
|
|
441
|
+
or ".skills/" in file_path or "/skills/" in file_path
|
|
442
|
+
)
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
def _discover_spec_files(repo_root: Path, dev_type: str) -> list[str]:
|
|
446
|
+
"""动态发现 spec/<dev_type>/ 下的所有 .md 文件。
|
|
447
|
+
|
|
448
|
+
返回形如 ".cowork-flow/spec/backend/error-handling.md" 的相对路径列表,
|
|
449
|
+
可直接写入 check.jsonl 条目。dev_type="spec" 时返回 spec/index.md。
|
|
450
|
+
"""
|
|
451
|
+
if dev_type == "spec":
|
|
452
|
+
return [f"{DIR_WORKFLOW}/{DIR_SPEC}/index.md"]
|
|
453
|
+
|
|
454
|
+
spec_dir = Path(repo_root) / DIR_WORKFLOW / DIR_SPEC / dev_type
|
|
455
|
+
if not spec_dir.is_dir():
|
|
456
|
+
return []
|
|
457
|
+
return sorted(
|
|
458
|
+
f"{DIR_WORKFLOW}/{DIR_SPEC}/{dev_type}/{p.name}"
|
|
459
|
+
for p in spec_dir.glob("*.md")
|
|
460
|
+
if p.is_file()
|
|
461
|
+
)
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
def get_check_context(repo_root: Path, dev_type: str) -> list[dict]:
|
|
465
|
+
"""Get check context entries.
|
|
466
|
+
|
|
467
|
+
Injects skill guides and **所有** spec 子文件,使 check agent 可以对照
|
|
468
|
+
.cowork-flow/spec/<dev_type>/ 的完整规范集逐项验证实现。
|
|
469
|
+
spec 文件通过 _discover_spec_files 动态发现,新增/删除 spec 文件无需改代码。
|
|
470
|
+
"""
|
|
439
471
|
base = [
|
|
440
472
|
{"file": _skill_path("check"), "reason": "Quality, contract, and template consistency check"},
|
|
441
473
|
{"file": _skill_path("finish-work"), "reason": "Finish, archive, and session recording gate"},
|
|
442
474
|
]
|
|
443
|
-
|
|
444
|
-
base.append({"file":
|
|
445
|
-
elif dev_type == "frontend":
|
|
446
|
-
base.append({"file": f"{DIR_WORKFLOW}/{DIR_SPEC}/frontend/index.md", "reason": "Verify frontend spec compliance"})
|
|
447
|
-
elif dev_type == "fullstack":
|
|
448
|
-
base.append({"file": f"{DIR_WORKFLOW}/{DIR_SPEC}/backend/index.md", "reason": "Verify backend spec compliance"})
|
|
449
|
-
base.append({"file": f"{DIR_WORKFLOW}/{DIR_SPEC}/frontend/index.md", "reason": "Verify frontend spec compliance"})
|
|
450
|
-
elif dev_type == "spec":
|
|
451
|
-
base.append({"file": f"{DIR_WORKFLOW}/{DIR_SPEC}/index.md", "reason": "Verify spec compliance"})
|
|
475
|
+
for spec_file in _discover_spec_files(repo_root, dev_type):
|
|
476
|
+
base.append({"file": spec_file, "reason": f"Verify {Path(spec_file).name} compliance"})
|
|
452
477
|
return base
|
|
453
478
|
|
|
454
479
|
|
|
@@ -467,8 +492,34 @@ def _write_jsonl(path: Path, entries: list[dict]) -> None:
|
|
|
467
492
|
path.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
|
468
493
|
|
|
469
494
|
|
|
495
|
+
def _migrate_prd_to_anchor(task_dir: Path) -> bool:
|
|
496
|
+
"""将旧 prd.md 迁移到 decision-anchor.md。返回是否执行了迁移。"""
|
|
497
|
+
prd_file = task_dir / "prd.md"
|
|
498
|
+
anchor_file = task_dir / "decision-anchor.md"
|
|
499
|
+
if not prd_file.exists() or anchor_file.exists():
|
|
500
|
+
return False
|
|
501
|
+
content = prd_file.read_text(encoding="utf-8").strip()
|
|
502
|
+
if not content:
|
|
503
|
+
content = "(empty legacy prd.md)"
|
|
504
|
+
if "## 目标" not in content and "## Goal" not in content:
|
|
505
|
+
content = "## 目标\n\n" + content
|
|
506
|
+
if "## 验收标准" not in content and "## Acceptance" not in content:
|
|
507
|
+
content += "\n\n## 验收标准\n- [ ] \n"
|
|
508
|
+
anchor_file.write_text(content, encoding="utf-8")
|
|
509
|
+
prd_file.unlink()
|
|
510
|
+
print(
|
|
511
|
+
colored(" [迁移] prd.md → decision-anchor.md", Colors.YELLOW),
|
|
512
|
+
file=sys.stderr,
|
|
513
|
+
)
|
|
514
|
+
return True
|
|
515
|
+
|
|
516
|
+
|
|
470
517
|
def _task_start_blockers(task_dir: Path) -> list[str]:
|
|
471
|
-
"""返回启动任务前必须处理的准备阻塞项。
|
|
518
|
+
"""返回启动任务前必须处理的准备阻塞项。
|
|
519
|
+
|
|
520
|
+
注意:调用前应先执行 _migrate_prd_to_anchor(),
|
|
521
|
+
本函数不再检查 prd.md(已被迁移处理)。
|
|
522
|
+
"""
|
|
472
523
|
blockers: list[str] = []
|
|
473
524
|
|
|
474
525
|
task_json = task_dir / FILE_TASK_JSON
|
|
@@ -476,11 +527,7 @@ def _task_start_blockers(task_dir: Path) -> list[str]:
|
|
|
476
527
|
blockers.append("task.json is missing")
|
|
477
528
|
|
|
478
529
|
# 只检查存在性 — R-WF-008 负责章节内容完整性
|
|
479
|
-
if _read_text(task_dir / "
|
|
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"):
|
|
530
|
+
if not _read_text(task_dir / "decision-anchor.md"):
|
|
484
531
|
blockers.append("decision-anchor.md is missing or empty")
|
|
485
532
|
|
|
486
533
|
for jsonl_name in CONTEXT_JSONL_FILES:
|
|
@@ -740,7 +787,7 @@ def cmd_init_context(args: argparse.Namespace) -> int:
|
|
|
740
787
|
_report_jsonl_skip(check_file, "already exists, skipping")
|
|
741
788
|
else:
|
|
742
789
|
print(colored("Creating check.jsonl...", Colors.CYAN))
|
|
743
|
-
check_entries = get_check_context(dev_type)
|
|
790
|
+
check_entries = get_check_context(repo_root, dev_type)
|
|
744
791
|
_write_jsonl(check_file, check_entries)
|
|
745
792
|
print(f" {colored('[OK]', Colors.GREEN)} {len(check_entries)} entries")
|
|
746
793
|
|
|
@@ -877,6 +924,10 @@ def _validate_jsonl(jsonl_file: Path, repo_root: Path, quiet: bool = False) -> i
|
|
|
877
924
|
errors += 1
|
|
878
925
|
continue
|
|
879
926
|
|
|
927
|
+
# 技能文件由插件运行时注入,不在项目目录中校验存在性
|
|
928
|
+
if _is_skill_path(file_path):
|
|
929
|
+
continue
|
|
930
|
+
|
|
880
931
|
full_path = repo_root / file_path
|
|
881
932
|
if entry_type == "directory":
|
|
882
933
|
if not full_path.is_dir():
|
|
@@ -968,6 +1019,10 @@ def cmd_start(args: argparse.Namespace) -> int:
|
|
|
968
1019
|
print(f"Hint: Use task name (e.g., 'my-task') or full path (e.g., '{DIR_WORKFLOW}/tasks/01-31-my-task')")
|
|
969
1020
|
return 1
|
|
970
1021
|
|
|
1022
|
+
# 1. 先迁移旧 prd.md(在 blocker 检查之前,确保迁移后 blocker 校验正确)
|
|
1023
|
+
_migrate_prd_to_anchor(full_path)
|
|
1024
|
+
|
|
1025
|
+
# 2. 再检查准备阻塞项
|
|
971
1026
|
blockers = _task_start_blockers(full_path)
|
|
972
1027
|
if blockers:
|
|
973
1028
|
print(colored("Error: Task is not ready to start yet", Colors.RED), file=sys.stderr)
|
|
@@ -979,24 +1034,6 @@ def cmd_start(args: argparse.Namespace) -> int:
|
|
|
979
1034
|
)
|
|
980
1035
|
return 1
|
|
981
1036
|
|
|
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
|
-
|
|
1000
1037
|
validation_issues = _task_context_validation_issues(full_path, repo_root)
|
|
1001
1038
|
if validation_issues:
|
|
1002
1039
|
print(colored("Error: Task context validation failed", Colors.RED), file=sys.stderr)
|
|
@@ -61,18 +61,6 @@
|
|
|
61
61
|
"source_excerpt": "L2(结构性)",
|
|
62
62
|
"enforcement": "validate_rules"
|
|
63
63
|
},
|
|
64
|
-
{
|
|
65
|
-
"id": "R-WF-006",
|
|
66
|
-
"type": "phase_gate",
|
|
67
|
-
"severity": "warn",
|
|
68
|
-
"scope": "implement",
|
|
69
|
-
"condition": "实现前必须有失败测试(行为变化场景)",
|
|
70
|
-
"message": "Implementation without failing test for behavior change",
|
|
71
|
-
"fix_hint": "先编写失败测试,再实现功能,确保测试在实现后通过",
|
|
72
|
-
"source_file": ".cowork-flow/workflow.md",
|
|
73
|
-
"source_excerpt": "先写能失败的测试",
|
|
74
|
-
"enforcement": "tdd_evidence"
|
|
75
|
-
},
|
|
76
64
|
{
|
|
77
65
|
"id": "R-WF-007",
|
|
78
66
|
"type": "phase_gate",
|
|
@@ -86,63 +74,39 @@
|
|
|
86
74
|
"enforcement": "validate_rules"
|
|
87
75
|
},
|
|
88
76
|
{
|
|
89
|
-
"id": "R-
|
|
90
|
-
"type": "
|
|
77
|
+
"id": "R-WF-008",
|
|
78
|
+
"type": "phase_gate",
|
|
91
79
|
"severity": "block",
|
|
92
|
-
"scope": "
|
|
93
|
-
"condition": "
|
|
94
|
-
"message": "
|
|
95
|
-
"fix_hint": "
|
|
96
|
-
"source_file": "
|
|
97
|
-
"source_excerpt": "
|
|
98
|
-
"enforcement": "
|
|
80
|
+
"scope": "task_start",
|
|
81
|
+
"condition": "任务必须有 decision-anchor.md 且包含目标和验收标准章节",
|
|
82
|
+
"message": "Drift prevention: decision-anchor.md is missing or incomplete",
|
|
83
|
+
"fix_hint": "创建 <task-dir>/decision-anchor.md,至少包含 ## 目标和 ## 验收标准",
|
|
84
|
+
"source_file": ".cowork-flow/spec/contracts/decision-anchor.md",
|
|
85
|
+
"source_excerpt": "每个任务必须有决策锚点",
|
|
86
|
+
"enforcement": "validate_rules"
|
|
99
87
|
},
|
|
100
88
|
{
|
|
101
89
|
"id": "R-AG-002",
|
|
102
90
|
"type": "forbidden_action",
|
|
103
91
|
"severity": "block",
|
|
104
|
-
"scope": "
|
|
105
|
-
"condition": "
|
|
92
|
+
"scope": "implement",
|
|
93
|
+
"condition": "不修改规格文件",
|
|
106
94
|
"message": "Subagent attempted to modify spec files",
|
|
107
95
|
"fix_hint": "规格文件只能由主会话修改,子代理应专注于实现任务",
|
|
108
|
-
"source_file": "
|
|
96
|
+
"source_file": ".cowork-flow/spec/contracts/subagent-dispatch.md",
|
|
109
97
|
"source_excerpt": "cowork-* 固定代理是叶子执行者",
|
|
110
98
|
"enforcement": "validate_implementation"
|
|
111
99
|
},
|
|
112
|
-
{
|
|
113
|
-
"id": "R-AG-003",
|
|
114
|
-
"type": "forbidden_action",
|
|
115
|
-
"severity": "block",
|
|
116
|
-
"scope": "all",
|
|
117
|
-
"condition": "子代理不操作 Git",
|
|
118
|
-
"message": "Subagent attempted to operate Git",
|
|
119
|
-
"fix_hint": "Git 操作只能由主会话执行,子代理应专注于代码实现",
|
|
120
|
-
"source_file": "AGENTS.md",
|
|
121
|
-
"source_excerpt": "cowork-* 固定代理是叶子执行者",
|
|
122
|
-
"enforcement": "host_contract"
|
|
123
|
-
},
|
|
124
|
-
{
|
|
125
|
-
"id": "R-AG-004",
|
|
126
|
-
"type": "forbidden_action",
|
|
127
|
-
"severity": "block",
|
|
128
|
-
"scope": "all",
|
|
129
|
-
"condition": "子代理不提交/归档",
|
|
130
|
-
"message": "Subagent attempted to commit or archive",
|
|
131
|
-
"fix_hint": "提交和归档只能由主会话执行,子代理应专注于代码实现",
|
|
132
|
-
"source_file": "AGENTS.md",
|
|
133
|
-
"source_excerpt": "cowork-* 固定代理是叶子执行者",
|
|
134
|
-
"enforcement": "host_contract"
|
|
135
|
-
},
|
|
136
100
|
{
|
|
137
101
|
"id": "R-AG-005",
|
|
138
102
|
"type": "forbidden_action",
|
|
139
103
|
"severity": "block",
|
|
140
104
|
"scope": "implement",
|
|
141
|
-
"condition": "
|
|
142
|
-
"message": "
|
|
143
|
-
"fix_hint": "
|
|
144
|
-
"source_file": "
|
|
145
|
-
"source_excerpt": "
|
|
105
|
+
"condition": "不修改 implement.jsonl 允许范围外的文件",
|
|
106
|
+
"message": "Modified file not in implement.jsonl allowed list",
|
|
107
|
+
"fix_hint": "通过 ./.cowork-flow/run task add-context <dir> implement <path> <reason> 登记所有需要修改的文件",
|
|
108
|
+
"source_file": ".cowork-flow/spec/contracts/decision-anchor.md",
|
|
109
|
+
"source_excerpt": "implement.jsonl 是任务的权威文件白名单",
|
|
146
110
|
"enforcement": "validate_implementation"
|
|
147
111
|
},
|
|
148
112
|
{
|
|
@@ -156,78 +120,6 @@
|
|
|
156
120
|
"source_file": "AGENTS.md",
|
|
157
121
|
"source_excerpt": "不为一次性代码做抽象",
|
|
158
122
|
"enforcement": "validate_implementation"
|
|
159
|
-
},
|
|
160
|
-
{
|
|
161
|
-
"id": "R-AG-007",
|
|
162
|
-
"type": "forbidden_action",
|
|
163
|
-
"severity": "block",
|
|
164
|
-
"scope": "implement",
|
|
165
|
-
"condition": "不顺手重构无关模块",
|
|
166
|
-
"message": "Unrelated module refactored during implementation",
|
|
167
|
-
"fix_hint": "只修改与当前任务直接相关的代码,不扩大改动范围",
|
|
168
|
-
"source_file": "AGENTS.md",
|
|
169
|
-
"source_excerpt": "不顺手重构无关模块",
|
|
170
|
-
"enforcement": "host_contract"
|
|
171
|
-
},
|
|
172
|
-
{
|
|
173
|
-
"id": "R-WF-008",
|
|
174
|
-
"type": "phase_gate",
|
|
175
|
-
"severity": "warn",
|
|
176
|
-
"scope": "task_start",
|
|
177
|
-
"condition": "L2 任务的关键决策应有 doubt-review 记录",
|
|
178
|
-
"message": "Missing doubt-review for L2 non-trivial decision",
|
|
179
|
-
"fix_hint": "在 <task>/doubt-review.md 中为每个非平凡决策完成 5-step doubt cycle",
|
|
180
|
-
"source_file": ".cowork-flow/spec/contracts/subagent-dispatch.md",
|
|
181
|
-
"source_excerpt": "doubt-review is a mandatory sub-step of before-dev gate for L2",
|
|
182
|
-
"enforcement": "validate_rules"
|
|
183
|
-
},
|
|
184
|
-
{
|
|
185
|
-
"id": "R-WF-009",
|
|
186
|
-
"type": "phase_gate",
|
|
187
|
-
"severity": "warn",
|
|
188
|
-
"scope": "check",
|
|
189
|
-
"condition": "Bug fix must have regression test that fails without the fix",
|
|
190
|
-
"message": "Bug fix lacks regression test or modifies existing test to pass",
|
|
191
|
-
"fix_hint": "Record red-green evidence in tdd.jsonl; test must fail without the fix and pass with the fix",
|
|
192
|
-
"source_file": ".cowork-flow/workflow.md",
|
|
193
|
-
"source_excerpt": "先写能失败的测试",
|
|
194
|
-
"enforcement": "tdd_evidence"
|
|
195
|
-
},
|
|
196
|
-
{
|
|
197
|
-
"id": "R-WF-010",
|
|
198
|
-
"type": "phase_gate",
|
|
199
|
-
"severity": "block",
|
|
200
|
-
"scope": "all",
|
|
201
|
-
"condition": "Batch mode requires explicit user approval before starting",
|
|
202
|
-
"message": "Batch mode started without explicit user approval",
|
|
203
|
-
"fix_hint": "用户必须在 writing-plans 结束时明确说 'approved' + 'auto' 才能进入批模式",
|
|
204
|
-
"source_file": ".cowork-flow/workflow.md",
|
|
205
|
-
"source_excerpt": "用户是流程的最终决策者",
|
|
206
|
-
"enforcement": "host_contract"
|
|
207
|
-
},
|
|
208
|
-
{
|
|
209
|
-
"id": "R-AG-008",
|
|
210
|
-
"type": "forbidden_action",
|
|
211
|
-
"severity": "warn",
|
|
212
|
-
"scope": "check",
|
|
213
|
-
"condition": "Simplification must preserve observable behavior",
|
|
214
|
-
"message": "Simplification appears to change observable behavior",
|
|
215
|
-
"fix_hint": "简化不应改变测试结果。如果测试需要修改才能通过,行为已变——还原或重写测试",
|
|
216
|
-
"source_file": ".cowork-flow/workflow.md",
|
|
217
|
-
"source_excerpt": "外科手术式改动: 只改必须改的",
|
|
218
|
-
"enforcement": "metadata_only"
|
|
219
|
-
},
|
|
220
|
-
{
|
|
221
|
-
"id": "R-WF-011",
|
|
222
|
-
"type": "phase_gate",
|
|
223
|
-
"severity": "warn",
|
|
224
|
-
"scope": "all",
|
|
225
|
-
"condition": "Archive should happen before final commit to avoid committing unreviewed code",
|
|
226
|
-
"message": "Uncommitted changes detected. Archive the task first, then commit the archived result.",
|
|
227
|
-
"fix_hint": "Run './.cowork-flow/run task archive <name>' first. Then commit the archive result.",
|
|
228
|
-
"source_file": ".cowork-flow/workflow.md",
|
|
229
|
-
"source_excerpt": "归档后再提交",
|
|
230
|
-
"enforcement": "validate_rules"
|
|
231
123
|
}
|
|
232
124
|
]
|
|
233
125
|
}
|
|
@@ -67,8 +67,8 @@
|
|
|
67
67
|
},
|
|
68
68
|
"enforcement": {
|
|
69
69
|
"type": "string",
|
|
70
|
-
"enum": ["validate_rules", "validate_implementation"
|
|
71
|
-
"description": "Runtime
|
|
70
|
+
"enum": ["validate_rules", "validate_implementation"],
|
|
71
|
+
"description": "Runtime surface responsible for enforcing this rule. Must have a corresponding validator module."
|
|
72
72
|
}
|
|
73
73
|
},
|
|
74
74
|
"additionalProperties": false
|
|
@@ -5,14 +5,39 @@
|
|
|
5
5
|
* Output: ZCode/Claude Code hook format (stdout JSON).
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { readFileSync, existsSync, readdirSync, mkdirSync, copyFileSync } from "fs";
|
|
9
|
-
import { join, dirname } from "path";
|
|
8
|
+
import { readFileSync, writeFileSync, existsSync, readdirSync, mkdirSync, copyFileSync } from "fs";
|
|
9
|
+
import { join, dirname, basename } from "path";
|
|
10
10
|
import { createHash } from "crypto";
|
|
11
11
|
|
|
12
12
|
const SCAFFOLD_DIR = join(import.meta.dirname, "..", "scaffold");
|
|
13
13
|
const DIR_WORKFLOW = ".cowork-flow";
|
|
14
14
|
const FILE_TASK_JSON = "task.json";
|
|
15
15
|
|
|
16
|
+
// Derive the installed plugin version from this hook's location in the cache.
|
|
17
|
+
// Path: hooks/inject-context.js -> .. = <cache>/<version>/
|
|
18
|
+
const PLUGIN_VERSION = basename(dirname(import.meta.dirname));
|
|
19
|
+
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// Version sync
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
function syncPluginVersion(targetDir) {
|
|
24
|
+
if (!PLUGIN_VERSION) return;
|
|
25
|
+
const versionFile = join(targetDir, DIR_WORKFLOW, ".version");
|
|
26
|
+
let current = "";
|
|
27
|
+
try {
|
|
28
|
+
current = readFileSync(versionFile, "utf8").trim();
|
|
29
|
+
} catch {
|
|
30
|
+
current = "";
|
|
31
|
+
}
|
|
32
|
+
if (current === PLUGIN_VERSION) return;
|
|
33
|
+
try {
|
|
34
|
+
mkdirSync(dirname(versionFile), { recursive: true });
|
|
35
|
+
writeFileSync(versionFile, `${PLUGIN_VERSION}\n`, "utf8");
|
|
36
|
+
} catch {
|
|
37
|
+
/* skip — non-fatal */
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
16
41
|
const TAG_RE = /\[workflow-state:([A-Za-z0-9_-]+)\]\s*\n(.*?)\n\s*\[\/workflow-state:\1\]/gs;
|
|
17
42
|
const RUNTIME_CONTEXT_PROMPT_RE = /^\s*cowork_runtime_context_id\s*:\s*([A-Za-z0-9._-]+)\s*$/gim;
|
|
18
43
|
const DEFAULT_CONTRACT_REGISTRY = {
|
|
@@ -338,6 +363,11 @@ function main() {
|
|
|
338
363
|
const copied = envDir ? scaffoldProject(envDir) : [];
|
|
339
364
|
const effectiveRoot = repoRoot || (envDir && existsSync(join(envDir, DIR_WORKFLOW)) ? envDir : null);
|
|
340
365
|
|
|
366
|
+
// Always refresh .version so plugin upgrades propagate to existing projects
|
|
367
|
+
if (effectiveRoot) {
|
|
368
|
+
syncPluginVersion(effectiveRoot);
|
|
369
|
+
}
|
|
370
|
+
|
|
341
371
|
if (!effectiveRoot) {
|
|
342
372
|
context = `<workflow-state>
|
|
343
373
|
Status: not_initialized
|
|
@@ -1 +1 @@
|
|
|
1
|
-
0.0.
|
|
1
|
+
0.0.35
|
|
@@ -1,11 +1,30 @@
|
|
|
1
1
|
#!/bin/sh
|
|
2
|
+
# cowork-flow run launcher (ZCode plugin edition)
|
|
3
|
+
# Delegates to the plugin cache runtime — no local scripts/ required.
|
|
2
4
|
set -u
|
|
3
5
|
|
|
4
6
|
MIN_PYTHON_LABEL="Python 3.9+"
|
|
5
7
|
VERSION_CHECK='import sys; raise SystemExit(0 if sys.version_info >= (3, 9) else 1)'
|
|
6
|
-
WORKFLOW_DIR=$(CDPATH= cd "$(dirname "$0")" && pwd) || exit 1
|
|
7
|
-
RUNNER_SCRIPT="$WORKFLOW_DIR/scripts/run.py"
|
|
8
8
|
|
|
9
|
+
VERSION_FILE=$(dirname "$0")/.version
|
|
10
|
+
VERSION="$(cat "$VERSION_FILE" 2>/dev/null | tr -d '\r')"
|
|
11
|
+
|
|
12
|
+
if [ -z "$VERSION" ]; then
|
|
13
|
+
echo "Error: cannot determine cowork-flow version (.version missing)" >&2
|
|
14
|
+
exit 127
|
|
15
|
+
fi
|
|
16
|
+
|
|
17
|
+
ZCODE_BASE="${ZCODE_HOME:-$HOME/.zcode}"
|
|
18
|
+
RUNNER="$ZCODE_BASE/cli/plugins/cache/zcode-plugins-official/cowork-flow/$VERSION/hooks/runtime/scripts/run.py"
|
|
19
|
+
|
|
20
|
+
if [ ! -f "$RUNNER" ]; then
|
|
21
|
+
echo "Error: cowork-flow plugin runtime not found at:" >&2
|
|
22
|
+
echo " $RUNNER" >&2
|
|
23
|
+
echo "Install the ZCode plugin or run 'cowork-flow init' for standalone." >&2
|
|
24
|
+
exit 127
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
# --- Python interpreter discovery ----------------------------------------
|
|
9
28
|
candidate_is_valid() {
|
|
10
29
|
candidate_cmd=$1
|
|
11
30
|
candidate_arg=${2:-}
|
|
@@ -73,7 +92,7 @@ select_python() {
|
|
|
73
92
|
select_python
|
|
74
93
|
|
|
75
94
|
if [ -n "$SELECTED_PYTHON_ARG" ]; then
|
|
76
|
-
exec "$SELECTED_PYTHON" "$SELECTED_PYTHON_ARG" "$
|
|
95
|
+
exec "$SELECTED_PYTHON" "$SELECTED_PYTHON_ARG" "$RUNNER" "$@"
|
|
77
96
|
fi
|
|
78
97
|
|
|
79
|
-
exec "$SELECTED_PYTHON" "$
|
|
98
|
+
exec "$SELECTED_PYTHON" "$RUNNER" "$@"
|
|
@@ -1,10 +1,33 @@
|
|
|
1
1
|
@echo off
|
|
2
|
+
rem cowork-flow run launcher (ZCode plugin edition)
|
|
3
|
+
rem Delegates to the plugin cache runtime — no local scripts/ required.
|
|
2
4
|
setlocal EnableExtensions
|
|
3
5
|
|
|
4
6
|
set "MIN_PYTHON_LABEL=Python 3.9+"
|
|
5
7
|
set "VERSION_CHECK=import sys; raise SystemExit(0 if sys.version_info >= (3, 9) else 1)"
|
|
6
|
-
|
|
7
|
-
set "
|
|
8
|
+
|
|
9
|
+
set "VERSION_FILE=%~dp0.version"
|
|
10
|
+
set "VERSION="
|
|
11
|
+
for /f "usebackq delims=" %%A in ("%VERSION_FILE%") do set "VERSION=%%A"
|
|
12
|
+
|
|
13
|
+
if "%VERSION%"=="" (
|
|
14
|
+
echo Error: cannot determine cowork-flow version (.version missing) >&2
|
|
15
|
+
exit /b 127
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
set "ZCODE_BASE=%ZCODE_HOME%"
|
|
19
|
+
if "%ZCODE_BASE%"=="" set "ZCODE_BASE=%USERPROFILE%\.zcode"
|
|
20
|
+
|
|
21
|
+
set "RUNNER=%ZCODE_BASE%\cli\plugins\cache\zcode-plugins-official\cowork-flow\%VERSION%\hooks\runtime\scripts\run.py"
|
|
22
|
+
|
|
23
|
+
if not exist "%RUNNER%" (
|
|
24
|
+
echo Error: cowork-flow plugin runtime not found at: >&2
|
|
25
|
+
echo %RUNNER% >&2
|
|
26
|
+
echo Install the ZCode plugin or run 'cowork-flow init' for standalone. >&2
|
|
27
|
+
exit /b 127
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
rem --- Python interpreter discovery ----------------------------------------
|
|
8
31
|
set "SELECTED_PYTHON="
|
|
9
32
|
set "SELECTED_PYTHON_ARG="
|
|
10
33
|
|
|
@@ -12,9 +35,9 @@ call :select_python
|
|
|
12
35
|
if errorlevel 1 exit /b %ERRORLEVEL%
|
|
13
36
|
|
|
14
37
|
if not "%SELECTED_PYTHON_ARG%"=="" (
|
|
15
|
-
"%SELECTED_PYTHON%" "%SELECTED_PYTHON_ARG%" "%
|
|
38
|
+
"%SELECTED_PYTHON%" "%SELECTED_PYTHON_ARG%" "%RUNNER%" %*
|
|
16
39
|
) else (
|
|
17
|
-
"%SELECTED_PYTHON%" "%
|
|
40
|
+
"%SELECTED_PYTHON%" "%RUNNER%" %*
|
|
18
41
|
)
|
|
19
42
|
exit /b %ERRORLEVEL%
|
|
20
43
|
|