cowork-flow 0.0.34 → 0.0.36

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/package.json +1 -1
  2. package/src/commands/install-zcode-plugin.js +14 -0
  3. package/template/.codex/agents/cowork-check.toml +1 -1
  4. package/template/.codex/agents/cowork-implement.toml +1 -1
  5. package/template/.cowork-flow/.version +1 -1
  6. package/template/.cowork-flow/scripts/__init__.py +1 -0
  7. package/template/.cowork-flow/scripts/commands/task.py +35 -29
  8. package/template/.cowork-flow/scripts/common/gates/tdd_evidence.py +10 -1
  9. package/template/.cowork-flow/scripts/common/gates/validate_implementation.py +7 -2
  10. package/template/.cowork-flow/spec/runtime/rules.json +17 -125
  11. package/template/.cowork-flow/spec/schemas/rules.schema.json +2 -2
  12. package/template/.zcode/.zcode-plugin/plugin.json +1 -1
  13. package/template/.zcode/hooks/inject-context.js +32 -2
  14. package/template/.zcode/scaffold/.cowork-flow/.version +1 -1
  15. package/template/.zcode/scaffold/.cowork-flow/run +98 -0
  16. package/template/.zcode/scaffold/.cowork-flow/run.cmd +111 -0
  17. package/template/.zcode/scaffold/.cowork-flow/spec/runtime/rules.json +17 -125
  18. package/template/.zcode/scaffold/.cowork-flow/spec/schemas/rules.schema.json +2 -2
  19. package/template/skills/check/SKILL.md +2 -2
  20. package/template/skills/game-design/SKILL.md +1 -1
  21. package/template/skills/start/SKILL.md +2 -2
  22. package/template/skills/tdd/SKILL.md +2 -2
  23. package/template/skills/writing-plans/SKILL.md +4 -4
  24. package/template/.zcode/hooks/runtime/run +0 -58
  25. package/template/.zcode/hooks/runtime/run.cmd +0 -37
  26. package/template/.zcode/hooks/runtime/scripts/commands/__init__.py +0 -1
  27. package/template/.zcode/hooks/runtime/scripts/commands/_bootstrap.py +0 -10
  28. package/template/.zcode/hooks/runtime/scripts/commands/add_session.py +0 -452
  29. package/template/.zcode/hooks/runtime/scripts/commands/change.py +0 -412
  30. package/template/.zcode/hooks/runtime/scripts/commands/doctor.py +0 -471
  31. package/template/.zcode/hooks/runtime/scripts/commands/get_context.py +0 -20
  32. package/template/.zcode/hooks/runtime/scripts/commands/get_developer.py +0 -30
  33. package/template/.zcode/hooks/runtime/scripts/commands/init_developer.py +0 -55
  34. package/template/.zcode/hooks/runtime/scripts/commands/party_mode_v2.py +0 -1166
  35. package/template/.zcode/hooks/runtime/scripts/commands/resume.py +0 -62
  36. package/template/.zcode/hooks/runtime/scripts/commands/subagent.py +0 -321
  37. package/template/.zcode/hooks/runtime/scripts/commands/task.py +0 -2082
  38. package/template/.zcode/hooks/runtime/scripts/common/__init__.py +0 -79
  39. package/template/.zcode/hooks/runtime/scripts/common/core/__init__.py +0 -1
  40. package/template/.zcode/hooks/runtime/scripts/common/core/config.py +0 -242
  41. package/template/.zcode/hooks/runtime/scripts/common/core/developer.py +0 -205
  42. package/template/.zcode/hooks/runtime/scripts/common/core/execution_context.py +0 -385
  43. package/template/.zcode/hooks/runtime/scripts/common/core/files.py +0 -45
  44. package/template/.zcode/hooks/runtime/scripts/common/core/paths.py +0 -239
  45. package/template/.zcode/hooks/runtime/scripts/common/gates/__init__.py +0 -1
  46. package/template/.zcode/hooks/runtime/scripts/common/gates/anti_rationalization.py +0 -55
  47. package/template/.zcode/hooks/runtime/scripts/common/gates/coding_standards.py +0 -210
  48. package/template/.zcode/hooks/runtime/scripts/common/gates/gates.py +0 -71
  49. package/template/.zcode/hooks/runtime/scripts/common/gates/tdd_evidence.py +0 -369
  50. package/template/.zcode/hooks/runtime/scripts/common/gates/test_intent.py +0 -262
  51. package/template/.zcode/hooks/runtime/scripts/common/gates/validate_coding_standards.py +0 -342
  52. package/template/.zcode/hooks/runtime/scripts/common/gates/validate_implementation.py +0 -268
  53. package/template/.zcode/hooks/runtime/scripts/common/gates/validate_rules.py +0 -462
  54. package/template/.zcode/hooks/runtime/scripts/common/git/__init__.py +0 -1
  55. package/template/.zcode/hooks/runtime/scripts/common/git/git_context.py +0 -765
  56. package/template/.zcode/hooks/runtime/scripts/common/git/git_snapshot.py +0 -83
  57. package/template/.zcode/hooks/runtime/scripts/common/task/__init__.py +0 -1
  58. package/template/.zcode/hooks/runtime/scripts/common/task/active_task.py +0 -403
  59. package/template/.zcode/hooks/runtime/scripts/common/task/archive_utils.py +0 -72
  60. package/template/.zcode/hooks/runtime/scripts/common/task/readiness.py +0 -249
  61. package/template/.zcode/hooks/runtime/scripts/common/task/state_machine.py +0 -56
  62. package/template/.zcode/hooks/runtime/scripts/common/task/task_utils.py +0 -187
  63. package/template/.zcode/hooks/runtime/scripts/run.py +0 -138
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cowork-flow",
3
- "version": "0.0.34",
3
+ "version": "0.0.36",
4
4
  "description": "CLI for installing and maintaining cowork-flow collaboration templates.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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>/prd.md`.
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>/prd.md`.
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.34
1
+ 0.0.36
@@ -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
@@ -432,22 +433,38 @@ def _skill_path(name: str, repo_root: Path | None = None) -> str:
432
433
  return f"{DIR_AGENTS}/skills/{name}/SKILL.md"
433
434
 
434
435
 
435
- def get_check_context(dev_type: str) -> list[dict]:
436
- """Get check context entries. Injects spec guides per dev_type so the check agent
437
- can verify compliance against project conventions."""
436
+ def _discover_spec_files(repo_root: Path, dev_type: str) -> list[str]:
437
+ """动态发现 spec/<dev_type>/ 下的所有 .md 文件。
438
+
439
+ 返回形如 ".cowork-flow/spec/backend/error-handling.md" 的相对路径列表,
440
+ 可直接写入 check.jsonl 条目。dev_type="spec" 时返回 spec/index.md。
441
+ """
442
+ if dev_type == "spec":
443
+ return [f"{DIR_WORKFLOW}/{DIR_SPEC}/index.md"]
444
+
445
+ spec_dir = Path(repo_root) / DIR_WORKFLOW / DIR_SPEC / dev_type
446
+ if not spec_dir.is_dir():
447
+ return []
448
+ return sorted(
449
+ f"{DIR_WORKFLOW}/{DIR_SPEC}/{dev_type}/{p.name}"
450
+ for p in spec_dir.glob("*.md")
451
+ if p.is_file()
452
+ )
453
+
454
+
455
+ def get_check_context(repo_root: Path, dev_type: str) -> list[dict]:
456
+ """Get check context entries.
457
+
458
+ Injects skill guides and **所有** spec 子文件,使 check agent 可以对照
459
+ .cowork-flow/spec/<dev_type>/ 的完整规范集逐项验证实现。
460
+ spec 文件通过 _discover_spec_files 动态发现,新增/删除 spec 文件无需改代码。
461
+ """
438
462
  base = [
439
463
  {"file": _skill_path("check"), "reason": "Quality, contract, and template consistency check"},
440
464
  {"file": _skill_path("finish-work"), "reason": "Finish, archive, and session recording gate"},
441
465
  ]
442
- if dev_type in ("backend", "test"):
443
- base.append({"file": f"{DIR_WORKFLOW}/{DIR_SPEC}/backend/index.md", "reason": "Verify backend spec compliance"})
444
- elif dev_type == "frontend":
445
- base.append({"file": f"{DIR_WORKFLOW}/{DIR_SPEC}/frontend/index.md", "reason": "Verify frontend spec compliance"})
446
- elif dev_type == "fullstack":
447
- base.append({"file": f"{DIR_WORKFLOW}/{DIR_SPEC}/backend/index.md", "reason": "Verify backend spec compliance"})
448
- base.append({"file": f"{DIR_WORKFLOW}/{DIR_SPEC}/frontend/index.md", "reason": "Verify frontend spec compliance"})
449
- elif dev_type == "spec":
450
- base.append({"file": f"{DIR_WORKFLOW}/{DIR_SPEC}/index.md", "reason": "Verify spec compliance"})
466
+ for spec_file in _discover_spec_files(repo_root, dev_type):
467
+ base.append({"file": spec_file, "reason": f"Verify {Path(spec_file).name} compliance"})
451
468
  return base
452
469
 
453
470
 
@@ -466,17 +483,6 @@ def _write_jsonl(path: Path, entries: list[dict]) -> None:
466
483
  path.write_text("\n".join(lines) + "\n", encoding="utf-8")
467
484
 
468
485
 
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
486
  def _task_start_blockers(task_dir: Path) -> list[str]:
481
487
  """返回启动任务前必须处理的准备阻塞项。"""
482
488
  blockers: list[str] = []
@@ -486,16 +492,16 @@ def _task_start_blockers(task_dir: Path) -> list[str]:
486
492
  blockers.append("task.json is missing")
487
493
 
488
494
  # 只检查存在性 — R-WF-008 负责章节内容完整性
489
- if _read_text_if_present(task_dir / "prd.md"):
495
+ if _read_text(task_dir / "prd.md"):
490
496
  # 旧格式:提示需要迁移(cmd_start 会自动迁移)
491
- if not _read_text_if_present(task_dir / "decision-anchor.md"):
497
+ if not _read_text(task_dir / "decision-anchor.md"):
492
498
  blockers.append("prd.md found; task start will auto-migrate to decision-anchor.md")
493
- elif not _read_text_if_present(task_dir / "decision-anchor.md"):
499
+ elif not _read_text(task_dir / "decision-anchor.md"):
494
500
  blockers.append("decision-anchor.md is missing or empty")
495
501
 
496
502
  for jsonl_name in CONTEXT_JSONL_FILES:
497
503
  jsonl_file = task_dir / jsonl_name
498
- if not _read_text_if_present(jsonl_file):
504
+ if not _read_text(jsonl_file):
499
505
  blockers.append(f"{jsonl_name} is missing or empty")
500
506
 
501
507
  return blockers
@@ -516,7 +522,7 @@ def _task_context_validation_issues(
516
522
 
517
523
  for jsonl_name in CONTEXT_JSONL_FILES:
518
524
  jsonl_file = task_dir / jsonl_name
519
- if not _read_text_if_present(jsonl_file):
525
+ if not _read_text(jsonl_file):
520
526
  continue
521
527
 
522
528
  error_count = _validate_jsonl(jsonl_file, repo_root, quiet=quiet)
@@ -750,7 +756,7 @@ def cmd_init_context(args: argparse.Namespace) -> int:
750
756
  _report_jsonl_skip(check_file, "already exists, skipping")
751
757
  else:
752
758
  print(colored("Creating check.jsonl...", Colors.CYAN))
753
- check_entries = get_check_context(dev_type)
759
+ check_entries = get_check_context(repo_root, dev_type)
754
760
  _write_jsonl(check_file, check_entries)
755
761
  print(f" {colored('[OK]', Colors.GREEN)} {len(check_entries)} entries")
756
762
 
@@ -1,5 +1,14 @@
1
1
  #!/usr/bin/env python3
2
- """TDD evidence validation for task review gates."""
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
 
@@ -1,8 +1,13 @@
1
1
  #!/usr/bin/env python3
2
2
  """
3
- Implementation validation script.
3
+ R-AG-002 到 R-AG-009 的门禁检查器。
4
4
 
5
- Validates code changes against forbidden action rules after task implementation.
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
@@ -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-AG-001",
90
- "type": "forbidden_action",
77
+ "id": "R-WF-008",
78
+ "type": "phase_gate",
91
79
  "severity": "block",
92
- "scope": "all",
93
- "condition": "子代理不派发其他代理",
94
- "message": "Subagent attempted to dispatch another agent",
95
- "fix_hint": "子代理是叶子执行者,不能派发、等待、列出或取消其他代理",
96
- "source_file": "AGENTS.md",
97
- "source_excerpt": "cowork-* 固定代理是叶子执行者",
98
- "enforcement": "host_contract"
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": "all",
105
- "condition": "子代理不修改规格文件",
92
+ "scope": "implement",
93
+ "condition": "不修改规格文件",
106
94
  "message": "Subagent attempted to modify spec files",
107
95
  "fix_hint": "规格文件只能由主会话修改,子代理应专注于实现任务",
108
- "source_file": "AGENTS.md",
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": "Unrequested feature introduced",
143
- "fix_hint": "只实现 PRD 中明确要求的功能,不添加额外功能",
144
- "source_file": "AGENTS.md",
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", "tdd_evidence", "host_contract", "metadata_only"],
71
- "description": "Runtime or contract surface responsible for enforcing or carrying this rule"
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cowork-flow",
3
- "version": "0.0.34",
3
+ "version": "0.0.36",
4
4
  "description": "AI 协作工作流引擎:任务生命周期、子代理调度、门禁与状态注入。将 cowork-flow 的完整工作流带入 ZCode。",
5
5
  "author": {
6
6
  "name": "fisklikewater"
@@ -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(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.30
1
+ 0.0.35
@@ -0,0 +1,98 @@
1
+ #!/bin/sh
2
+ # cowork-flow run launcher (ZCode plugin edition)
3
+ # Delegates to the plugin cache runtime — no local scripts/ required.
4
+ set -u
5
+
6
+ MIN_PYTHON_LABEL="Python 3.9+"
7
+ VERSION_CHECK='import sys; raise SystemExit(0 if sys.version_info >= (3, 9) else 1)'
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 ----------------------------------------
28
+ candidate_is_valid() {
29
+ candidate_cmd=$1
30
+ candidate_arg=${2:-}
31
+
32
+ command -v "$candidate_cmd" >/dev/null 2>&1 || return 1
33
+
34
+ if [ -n "$candidate_arg" ]; then
35
+ "$candidate_cmd" "$candidate_arg" -c "$VERSION_CHECK" >/dev/null 2>&1
36
+ else
37
+ "$candidate_cmd" -c "$VERSION_CHECK" >/dev/null 2>&1
38
+ fi
39
+ }
40
+
41
+ select_explicit_python() {
42
+ env_name=$1
43
+ env_value=$2
44
+
45
+ if candidate_is_valid "$env_value"; then
46
+ SELECTED_PYTHON=$env_value
47
+ SELECTED_PYTHON_ARG=
48
+ return 0
49
+ fi
50
+
51
+ echo "Error: $env_name does not point to a usable ${MIN_PYTHON_LABEL} interpreter: $env_value" >&2
52
+ echo "Set $env_name to an executable ${MIN_PYTHON_LABEL} interpreter path." >&2
53
+ exit 127
54
+ }
55
+
56
+ select_python() {
57
+ SELECTED_PYTHON=
58
+ SELECTED_PYTHON_ARG=
59
+
60
+ if [ -n "${COWORK_FLOW_PYTHON:-}" ]; then
61
+ select_explicit_python "COWORK_FLOW_PYTHON" "$COWORK_FLOW_PYTHON"
62
+ return 0
63
+ fi
64
+
65
+ if [ -n "${PYTHON:-}" ]; then
66
+ select_explicit_python "PYTHON" "$PYTHON"
67
+ return 0
68
+ fi
69
+
70
+ if candidate_is_valid "python3"; then
71
+ SELECTED_PYTHON=python3
72
+ return 0
73
+ fi
74
+
75
+ if candidate_is_valid "python"; then
76
+ SELECTED_PYTHON=python
77
+ return 0
78
+ fi
79
+
80
+ if candidate_is_valid "py" "-3"; then
81
+ SELECTED_PYTHON=py
82
+ SELECTED_PYTHON_ARG=-3
83
+ return 0
84
+ fi
85
+
86
+ echo "Error: ${MIN_PYTHON_LABEL} is required, but no usable interpreter was found." >&2
87
+ echo "Tried: COWORK_FLOW_PYTHON, PYTHON, python3, python, py -3." >&2
88
+ echo "Set COWORK_FLOW_PYTHON=/path/to/python3 and retry." >&2
89
+ exit 127
90
+ }
91
+
92
+ select_python
93
+
94
+ if [ -n "$SELECTED_PYTHON_ARG" ]; then
95
+ exec "$SELECTED_PYTHON" "$SELECTED_PYTHON_ARG" "$RUNNER" "$@"
96
+ fi
97
+
98
+ exec "$SELECTED_PYTHON" "$RUNNER" "$@"