cowork-flow 0.0.34 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cowork-flow",
3
- "version": "0.0.34",
3
+ "version": "0.0.35",
4
4
  "description": "CLI for installing and maintaining cowork-flow collaboration templates.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1 +1 @@
1
- 0.0.34
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] = []
@@ -486,16 +476,16 @@ def _task_start_blockers(task_dir: Path) -> list[str]:
486
476
  blockers.append("task.json is missing")
487
477
 
488
478
  # 只检查存在性 — R-WF-008 负责章节内容完整性
489
- if _read_text_if_present(task_dir / "prd.md"):
479
+ if _read_text(task_dir / "prd.md"):
490
480
  # 旧格式:提示需要迁移(cmd_start 会自动迁移)
491
- if not _read_text_if_present(task_dir / "decision-anchor.md"):
481
+ if not _read_text(task_dir / "decision-anchor.md"):
492
482
  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"):
483
+ elif not _read_text(task_dir / "decision-anchor.md"):
494
484
  blockers.append("decision-anchor.md is missing or empty")
495
485
 
496
486
  for jsonl_name in CONTEXT_JSONL_FILES:
497
487
  jsonl_file = task_dir / jsonl_name
498
- if not _read_text_if_present(jsonl_file):
488
+ if not _read_text(jsonl_file):
499
489
  blockers.append(f"{jsonl_name} is missing or empty")
500
490
 
501
491
  return blockers
@@ -516,7 +506,7 @@ def _task_context_validation_issues(
516
506
 
517
507
  for jsonl_name in CONTEXT_JSONL_FILES:
518
508
  jsonl_file = task_dir / jsonl_name
519
- if not _read_text_if_present(jsonl_file):
509
+ if not _read_text(jsonl_file):
520
510
  continue
521
511
 
522
512
  error_count = _validate_jsonl(jsonl_file, repo_root, quiet=quiet)
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cowork-flow",
3
- "version": "0.0.34",
3
+ "version": "0.0.35",
4
4
  "description": "AI 协作工作流引擎:任务生命周期、子代理调度、门禁与状态注入。将 cowork-flow 的完整工作流带入 ZCode。",
5
5
  "author": {
6
6
  "name": "fisklikewater"
@@ -0,0 +1 @@
1
+ """Cowork Flow zcode plugin 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] = []
@@ -486,16 +476,16 @@ def _task_start_blockers(task_dir: Path) -> list[str]:
486
476
  blockers.append("task.json is missing")
487
477
 
488
478
  # 只检查存在性 — R-WF-008 负责章节内容完整性
489
- if _read_text_if_present(task_dir / "prd.md"):
479
+ if _read_text(task_dir / "prd.md"):
490
480
  # 旧格式:提示需要迁移(cmd_start 会自动迁移)
491
- if not _read_text_if_present(task_dir / "decision-anchor.md"):
481
+ if not _read_text(task_dir / "decision-anchor.md"):
492
482
  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"):
483
+ elif not _read_text(task_dir / "decision-anchor.md"):
494
484
  blockers.append("decision-anchor.md is missing or empty")
495
485
 
496
486
  for jsonl_name in CONTEXT_JSONL_FILES:
497
487
  jsonl_file = task_dir / jsonl_name
498
- if not _read_text_if_present(jsonl_file):
488
+ if not _read_text(jsonl_file):
499
489
  blockers.append(f"{jsonl_name} is missing or empty")
500
490
 
501
491
  return blockers
@@ -516,7 +506,7 @@ def _task_context_validation_issues(
516
506
 
517
507
  for jsonl_name in CONTEXT_JSONL_FILES:
518
508
  jsonl_file = task_dir / jsonl_name
519
- if not _read_text_if_present(jsonl_file):
509
+ if not _read_text(jsonl_file):
520
510
  continue
521
511
 
522
512
  error_count = _validate_jsonl(jsonl_file, repo_root, quiet=quiet)
@@ -1520,7 +1510,7 @@ def cmd_next(args: argparse.Namespace) -> int:
1520
1510
  return 0
1521
1511
 
1522
1512
  if status in DONE_STATUSES:
1523
- print("Next action: finalize, commit, archive, and record session")
1513
+ print("Next action: finalize, archive, commit, and record session")
1524
1514
  print("Command: git status --short")
1525
1515
  linked_changes = _linked_active_changes_for_task(repo_root, task_dir)
1526
1516
  print(f"Then: ./.cowork-flow/run task archive {Path(task_path).name}")
@@ -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
@@ -246,4 +246,22 @@ def task_readiness_blockers(repo_root: Path, task_dir: Path) -> list[str]:
246
246
  if not _has_verification_command(plan_text):
247
247
  blockers.append(f"L2 readiness ({slug}): linked plan is missing verification commands")
248
248
 
249
+ blockers.extend(_check_doubt_review(task_dir, change_dir))
250
+
249
251
  return blockers
252
+
253
+
254
+ def _check_doubt_review(task_dir: Path, change_dir: Path) -> list[str]:
255
+ """L2 optional check: doubt-review 记录是否存在 (warn 级别, 不阻断)."""
256
+ doubt_file = Path(task_dir) / "doubt-review.md"
257
+ if not doubt_file.is_file():
258
+ return [
259
+ f"L2 doubt-review 建议: 在 {doubt_file} 中记录关键决策的 "
260
+ f"CLAIM+CONTRACT(非强制,但强烈推荐)"
261
+ ]
262
+ text = _read_text(doubt_file)
263
+ if "CLAIM:" not in text:
264
+ return [
265
+ f"L2 doubt-review: {doubt_file} 存在但没有 CLAIM 记录"
266
+ ]
267
+ return []
@@ -0,0 +1,79 @@
1
+ #!/bin/sh
2
+ set -u
3
+
4
+ MIN_PYTHON_LABEL="Python 3.9+"
5
+ 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
+
9
+ candidate_is_valid() {
10
+ candidate_cmd=$1
11
+ candidate_arg=${2:-}
12
+
13
+ command -v "$candidate_cmd" >/dev/null 2>&1 || return 1
14
+
15
+ if [ -n "$candidate_arg" ]; then
16
+ "$candidate_cmd" "$candidate_arg" -c "$VERSION_CHECK" >/dev/null 2>&1
17
+ else
18
+ "$candidate_cmd" -c "$VERSION_CHECK" >/dev/null 2>&1
19
+ fi
20
+ }
21
+
22
+ select_explicit_python() {
23
+ env_name=$1
24
+ env_value=$2
25
+
26
+ if candidate_is_valid "$env_value"; then
27
+ SELECTED_PYTHON=$env_value
28
+ SELECTED_PYTHON_ARG=
29
+ return 0
30
+ fi
31
+
32
+ echo "Error: $env_name does not point to a usable ${MIN_PYTHON_LABEL} interpreter: $env_value" >&2
33
+ echo "Set $env_name to an executable ${MIN_PYTHON_LABEL} interpreter path." >&2
34
+ exit 127
35
+ }
36
+
37
+ select_python() {
38
+ SELECTED_PYTHON=
39
+ SELECTED_PYTHON_ARG=
40
+
41
+ if [ -n "${COWORK_FLOW_PYTHON:-}" ]; then
42
+ select_explicit_python "COWORK_FLOW_PYTHON" "$COWORK_FLOW_PYTHON"
43
+ return 0
44
+ fi
45
+
46
+ if [ -n "${PYTHON:-}" ]; then
47
+ select_explicit_python "PYTHON" "$PYTHON"
48
+ return 0
49
+ fi
50
+
51
+ if candidate_is_valid "python3"; then
52
+ SELECTED_PYTHON=python3
53
+ return 0
54
+ fi
55
+
56
+ if candidate_is_valid "python"; then
57
+ SELECTED_PYTHON=python
58
+ return 0
59
+ fi
60
+
61
+ if candidate_is_valid "py" "-3"; then
62
+ SELECTED_PYTHON=py
63
+ SELECTED_PYTHON_ARG=-3
64
+ return 0
65
+ fi
66
+
67
+ echo "Error: ${MIN_PYTHON_LABEL} is required, but no usable interpreter was found." >&2
68
+ echo "Tried: COWORK_FLOW_PYTHON, PYTHON, python3, python, py -3." >&2
69
+ echo "Set COWORK_FLOW_PYTHON=/path/to/python3 and retry." >&2
70
+ exit 127
71
+ }
72
+
73
+ select_python
74
+
75
+ if [ -n "$SELECTED_PYTHON_ARG" ]; then
76
+ exec "$SELECTED_PYTHON" "$SELECTED_PYTHON_ARG" "$RUNNER_SCRIPT" "$@"
77
+ fi
78
+
79
+ exec "$SELECTED_PYTHON" "$RUNNER_SCRIPT" "$@"
@@ -0,0 +1,88 @@
1
+ @echo off
2
+ setlocal EnableExtensions
3
+
4
+ set "MIN_PYTHON_LABEL=Python 3.9+"
5
+ set "VERSION_CHECK=import sys; raise SystemExit(0 if sys.version_info >= (3, 9) else 1)"
6
+ set "WORKFLOW_DIR=%~dp0"
7
+ set "RUNNER_SCRIPT=%WORKFLOW_DIR%scripts\run.py"
8
+ set "SELECTED_PYTHON="
9
+ set "SELECTED_PYTHON_ARG="
10
+
11
+ call :select_python
12
+ if errorlevel 1 exit /b %ERRORLEVEL%
13
+
14
+ if not "%SELECTED_PYTHON_ARG%"=="" (
15
+ "%SELECTED_PYTHON%" "%SELECTED_PYTHON_ARG%" "%RUNNER_SCRIPT%" %*
16
+ ) else (
17
+ "%SELECTED_PYTHON%" "%RUNNER_SCRIPT%" %*
18
+ )
19
+ exit /b %ERRORLEVEL%
20
+
21
+ :candidate_is_valid
22
+ set "CANDIDATE_CMD=%~1"
23
+ set "CANDIDATE_ARG=%~2"
24
+
25
+ where "%CANDIDATE_CMD%" >nul 2>nul
26
+ if errorlevel 1 (
27
+ if not exist "%CANDIDATE_CMD%" exit /b 1
28
+ )
29
+
30
+ if not "%CANDIDATE_ARG%"=="" (
31
+ "%CANDIDATE_CMD%" "%CANDIDATE_ARG%" -c "%VERSION_CHECK%" >nul 2>nul
32
+ ) else (
33
+ "%CANDIDATE_CMD%" -c "%VERSION_CHECK%" >nul 2>nul
34
+ )
35
+ exit /b %ERRORLEVEL%
36
+
37
+ :select_explicit_python
38
+ set "ENV_NAME=%~1"
39
+ set "ENV_VALUE=%~2"
40
+
41
+ call :candidate_is_valid "%ENV_VALUE%" ""
42
+ if not errorlevel 1 (
43
+ set "SELECTED_PYTHON=%ENV_VALUE%"
44
+ set "SELECTED_PYTHON_ARG="
45
+ exit /b 0
46
+ )
47
+
48
+ echo Error: %ENV_NAME% does not point to a usable %MIN_PYTHON_LABEL% interpreter: %ENV_VALUE% 1>&2
49
+ echo Set %ENV_NAME% to an executable Python 3.9+ interpreter path. 1>&2
50
+ exit /b 127
51
+
52
+ :select_python
53
+ set "SELECTED_PYTHON="
54
+ set "SELECTED_PYTHON_ARG="
55
+
56
+ if not "%COWORK_FLOW_PYTHON%"=="" (
57
+ call :select_explicit_python "COWORK_FLOW_PYTHON" "%COWORK_FLOW_PYTHON%"
58
+ exit /b %ERRORLEVEL%
59
+ )
60
+
61
+ if not "%PYTHON%"=="" (
62
+ call :select_explicit_python "PYTHON" "%PYTHON%"
63
+ exit /b %ERRORLEVEL%
64
+ )
65
+
66
+ call :candidate_is_valid "python3" ""
67
+ if not errorlevel 1 (
68
+ set "SELECTED_PYTHON=python3"
69
+ exit /b 0
70
+ )
71
+
72
+ call :candidate_is_valid "python" ""
73
+ if not errorlevel 1 (
74
+ set "SELECTED_PYTHON=python"
75
+ exit /b 0
76
+ )
77
+
78
+ call :candidate_is_valid "py" "-3"
79
+ if not errorlevel 1 (
80
+ set "SELECTED_PYTHON=py"
81
+ set "SELECTED_PYTHON_ARG=-3"
82
+ exit /b 0
83
+ )
84
+
85
+ echo Error: %MIN_PYTHON_LABEL% is required, but no usable interpreter was found. 1>&2
86
+ echo Tried: COWORK_FLOW_PYTHON, PYTHON, python3, python, py -3. 1>&2
87
+ echo Set COWORK_FLOW_PYTHON=C:\Path\To\python.exe and retry. 1>&2
88
+ exit /b 127