cowork-flow 2.4.1 → 2.4.2
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/CHANGELOG.md +12 -1
- package/package.json +1 -1
- package/presets/claude-code/.claude-plugin/plugin.json +1 -1
- package/presets/codex/.codex-plugin/plugin.json +1 -1
- package/presets/kimi-code/.kimi-plugin/plugin.json +1 -1
- package/presets/qoder/.qoder-plugin/plugin.json +1 -1
- package/presets/zcode/.zcode-plugin/plugin.json +1 -1
- package/template/.cowork-flow/.version +1 -1
- package/template/.cowork-flow/scripts/adapters/cli/plan.py +218 -0
- package/template/.cowork-flow/scripts/run.py +3 -1
- package/template/.cowork-flow/scripts/services/plan_readiness.py +583 -0
- package/template/.cowork-flow/scripts/services/plan_scaffold.py +133 -0
- package/template/.cowork-flow/scripts/services/plan_schema.py +40 -0
- package/template/.cowork-flow/scripts/services/task_context.py +12 -0
- package/template/.cowork-flow/scripts/services/task_creation.py +23 -24
- package/template/.cowork-flow/spec/contracts/plan-binding.md +62 -4
- package/template/.cowork-flow/spec/runtime/contract-registry.json +2 -1
- package/template/skills/task-planning/SKILL.md +24 -0
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,16 @@
|
|
|
6
6
|
|
|
7
7
|
### Changed
|
|
8
8
|
|
|
9
|
+
## [2.4.2] - 2026-10-23
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **计划骨架生成与定稿门禁**:新增 `./.cowork-flow/run plan new --title ... --slug ...`,从已安装的本地化 `.cowork-flow/templates/plan-template.md` 生成计划骨架并替换标题占位符,默认拒绝覆盖已有计划;新增 `plan finalize [task-dir]`,校验计划模板形状、目标、AC、章节内容、任务简报字段、验证命令、anchor 的 AC/目标/规范核对和 `implement.jsonl`,通过后写入 `task.json.meta.planReady` 的 plan/anchor 摘要。模板中的角括号占位内容以及示例路径/符号(如 `path/a.py`、`function_name`)不算已填充,必须替换后才能定稿。任务创建会把计划 AC 自动填入 anchor 的验收标准。新 strict 非 Tiny 任务默认带 `planReadyRequired`,start 要求快照存在且摘要未变;人验收后修改计划或 anchor 会阻断 start,需重新 finalize 并重新验收。Tiny、legacy 与未标记旧任务保持原行为。
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- **计划流程改为“生成骨架 → 填充 → finalize → 人验收 → start”**:`task-planning` 技能、`plan-binding.md` 契约与 `plan finalize` 检查共同约束计划完整性;start 保留结构 backstop,并把新任务的完整性主检查前移到 finalize。契约摘要同步更新,未新增第二套计划模板。
|
|
18
|
+
|
|
9
19
|
## [2.4.1] - 2026-10-23
|
|
10
20
|
|
|
11
21
|
### Added
|
|
@@ -435,7 +445,8 @@
|
|
|
435
445
|
|
|
436
446
|
此版本未保留本地条目正文,详情见 [GitHub Release](https://github.com/fishlikewater/cowork-flow/releases/tag/v0.0.47)。
|
|
437
447
|
|
|
438
|
-
[Unreleased]: https://github.com/fishlikewater/cowork-flow/compare/v2.4.
|
|
448
|
+
[Unreleased]: https://github.com/fishlikewater/cowork-flow/compare/v2.4.2...HEAD
|
|
449
|
+
[2.4.2]: https://github.com/fishlikewater/cowork-flow/compare/v2.4.1...v2.4.2
|
|
439
450
|
[2.4.1]: https://github.com/fishlikewater/cowork-flow/compare/v2.4.0...v2.4.1
|
|
440
451
|
[2.4.0]: https://github.com/fishlikewater/cowork-flow/compare/v2.3.0...v2.4.0
|
|
441
452
|
[2.3.0]: https://github.com/fishlikewater/cowork-flow/compare/v2.2.0...v2.3.0
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
2.4.
|
|
1
|
+
2.4.2
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Plan skeleton generation and readiness finalization CLI."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import json
|
|
8
|
+
import sys
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from adapters.cli.execution_context_args import (
|
|
12
|
+
build_internal_execution_context_parser,
|
|
13
|
+
execution_context_from_namespace,
|
|
14
|
+
)
|
|
15
|
+
from adapters.cli.task_support import (
|
|
16
|
+
Colors,
|
|
17
|
+
colored,
|
|
18
|
+
resolve_task_dir,
|
|
19
|
+
slugify,
|
|
20
|
+
)
|
|
21
|
+
from infra.paths import FILE_TASK_JSON, get_repo_root
|
|
22
|
+
from runtime.session_state import (
|
|
23
|
+
PROVENANCE_PROCESS_FALLBACK,
|
|
24
|
+
get_active_task,
|
|
25
|
+
)
|
|
26
|
+
from services.plan_readiness import PlanReadinessError, PlanReadinessService
|
|
27
|
+
from services.plan_scaffold import PlanScaffoldError, PlanScaffoldService
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _build_parser() -> argparse.ArgumentParser:
|
|
31
|
+
parser = argparse.ArgumentParser(
|
|
32
|
+
description="Generate a plan skeleton or finalize planning artifacts",
|
|
33
|
+
parents=[build_internal_execution_context_parser()],
|
|
34
|
+
)
|
|
35
|
+
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
36
|
+
|
|
37
|
+
new_parser = subparsers.add_parser(
|
|
38
|
+
"new",
|
|
39
|
+
help="Create a plan skeleton from the installed localized template",
|
|
40
|
+
)
|
|
41
|
+
new_parser.add_argument("--title", required=True, help="Plan title")
|
|
42
|
+
new_parser.add_argument(
|
|
43
|
+
"--slug",
|
|
44
|
+
help="ASCII plan slug; defaults to the title when it slugifies",
|
|
45
|
+
)
|
|
46
|
+
new_parser.add_argument(
|
|
47
|
+
"--force",
|
|
48
|
+
action="store_true",
|
|
49
|
+
help="Overwrite an existing plan file",
|
|
50
|
+
)
|
|
51
|
+
new_parser.add_argument(
|
|
52
|
+
"--date",
|
|
53
|
+
help="Plan date prefix in YYYY-MM-DD form (default: today)",
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
finalize_parser = subparsers.add_parser(
|
|
57
|
+
"finalize",
|
|
58
|
+
help="Validate planning artifacts and record the plan-ready snapshot",
|
|
59
|
+
)
|
|
60
|
+
finalize_parser.add_argument(
|
|
61
|
+
"dir",
|
|
62
|
+
nargs="?",
|
|
63
|
+
help="Task directory or name; defaults to the active task",
|
|
64
|
+
)
|
|
65
|
+
finalize_parser.add_argument(
|
|
66
|
+
"--json",
|
|
67
|
+
action="store_true",
|
|
68
|
+
help="Print the plan-ready snapshot as JSON",
|
|
69
|
+
)
|
|
70
|
+
return parser
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _resolve_task(args: argparse.Namespace, repo_root: Path) -> Path | None:
|
|
74
|
+
target = getattr(args, "dir", None)
|
|
75
|
+
if target:
|
|
76
|
+
task_dir = resolve_task_dir(target, repo_root)
|
|
77
|
+
else:
|
|
78
|
+
active = get_active_task(repo_root)
|
|
79
|
+
if not active.context_key:
|
|
80
|
+
print(
|
|
81
|
+
colored(
|
|
82
|
+
"Error: Missing session context. Set "
|
|
83
|
+
"COWORK_FLOW_CONTEXT_ID or pass a task dir.",
|
|
84
|
+
Colors.RED,
|
|
85
|
+
),
|
|
86
|
+
file=sys.stderr,
|
|
87
|
+
)
|
|
88
|
+
return None
|
|
89
|
+
if active.task_path and active.provenance == PROVENANCE_PROCESS_FALLBACK:
|
|
90
|
+
print(
|
|
91
|
+
colored(
|
|
92
|
+
"Error: Session binding is process-fallback (shared); "
|
|
93
|
+
"set COWORK_FLOW_CONTEXT_ID to the session value shown "
|
|
94
|
+
"in the workflow-state header, or pass a task dir "
|
|
95
|
+
"explicitly.",
|
|
96
|
+
Colors.RED,
|
|
97
|
+
),
|
|
98
|
+
file=sys.stderr,
|
|
99
|
+
)
|
|
100
|
+
return None
|
|
101
|
+
if not active.task_path:
|
|
102
|
+
print(
|
|
103
|
+
colored(
|
|
104
|
+
"Error: No active task set for this session",
|
|
105
|
+
Colors.RED,
|
|
106
|
+
),
|
|
107
|
+
file=sys.stderr,
|
|
108
|
+
)
|
|
109
|
+
return None
|
|
110
|
+
task_dir = repo_root / active.task_path
|
|
111
|
+
|
|
112
|
+
if not task_dir.is_dir() or not (task_dir / FILE_TASK_JSON).is_file():
|
|
113
|
+
print(
|
|
114
|
+
colored(
|
|
115
|
+
f"Error: Task not found or task.json missing: {task_dir}",
|
|
116
|
+
Colors.RED,
|
|
117
|
+
),
|
|
118
|
+
file=sys.stderr,
|
|
119
|
+
)
|
|
120
|
+
return None
|
|
121
|
+
return task_dir
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _cmd_new(args: argparse.Namespace, repo_root: Path) -> int:
|
|
125
|
+
slug = args.slug or slugify(args.title)
|
|
126
|
+
if not slug:
|
|
127
|
+
print(
|
|
128
|
+
colored(
|
|
129
|
+
"Error: could not generate an ASCII slug from the title; "
|
|
130
|
+
"pass --slug <ascii-slug>",
|
|
131
|
+
Colors.RED,
|
|
132
|
+
),
|
|
133
|
+
file=sys.stderr,
|
|
134
|
+
)
|
|
135
|
+
return 1
|
|
136
|
+
try:
|
|
137
|
+
plan_path = PlanScaffoldService(repo_root).create(
|
|
138
|
+
args.title,
|
|
139
|
+
slug,
|
|
140
|
+
force=bool(args.force),
|
|
141
|
+
date_prefix=args.date,
|
|
142
|
+
)
|
|
143
|
+
except PlanScaffoldError as error:
|
|
144
|
+
print(
|
|
145
|
+
colored(
|
|
146
|
+
f"Error: Failed to create plan skeleton [{error.code}]: "
|
|
147
|
+
f"{error.detail}",
|
|
148
|
+
Colors.RED,
|
|
149
|
+
),
|
|
150
|
+
file=sys.stderr,
|
|
151
|
+
)
|
|
152
|
+
return 1
|
|
153
|
+
try:
|
|
154
|
+
display = plan_path.resolve().relative_to(repo_root.resolve()).as_posix()
|
|
155
|
+
except ValueError:
|
|
156
|
+
display = str(plan_path)
|
|
157
|
+
print(display)
|
|
158
|
+
return 0
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _cmd_finalize(args: argparse.Namespace, repo_root: Path) -> int:
|
|
162
|
+
task_dir = _resolve_task(args, repo_root)
|
|
163
|
+
if task_dir is None:
|
|
164
|
+
return 1
|
|
165
|
+
try:
|
|
166
|
+
snapshot = PlanReadinessService(repo_root).finalize(task_dir)
|
|
167
|
+
except PlanReadinessError as error:
|
|
168
|
+
print(
|
|
169
|
+
colored("Error: Plan is not ready for review", Colors.RED),
|
|
170
|
+
file=sys.stderr,
|
|
171
|
+
)
|
|
172
|
+
for blocker in error.blockers:
|
|
173
|
+
print(f" - {blocker}", file=sys.stderr)
|
|
174
|
+
print(
|
|
175
|
+
"Hint: fill the planning artifacts, then rerun "
|
|
176
|
+
"`./.cowork-flow/run plan finalize <task-dir>`.",
|
|
177
|
+
file=sys.stderr,
|
|
178
|
+
)
|
|
179
|
+
return 1
|
|
180
|
+
|
|
181
|
+
if getattr(args, "json", False):
|
|
182
|
+
print(json.dumps(snapshot, ensure_ascii=False, indent=2))
|
|
183
|
+
else:
|
|
184
|
+
print(colored("Plan is ready for human review.", Colors.GREEN))
|
|
185
|
+
print(f" planDigest: {snapshot['planDigest']}")
|
|
186
|
+
print(f" anchorDigest: {snapshot['anchorDigest']}")
|
|
187
|
+
print(f" readyAt: {snapshot['readyAt']}")
|
|
188
|
+
print(
|
|
189
|
+
" Next: review the plan and anchor, then start the task. "
|
|
190
|
+
"Start will reject any later edit to either file."
|
|
191
|
+
)
|
|
192
|
+
return 0
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def main(argv: list[str] | None = None) -> int:
|
|
196
|
+
args = _build_parser().parse_args(argv)
|
|
197
|
+
execution_context = execution_context_from_namespace(args)
|
|
198
|
+
if not execution_context.is_default:
|
|
199
|
+
print(
|
|
200
|
+
colored(
|
|
201
|
+
"Error: plan commands must run in the main session; "
|
|
202
|
+
"delegated contexts cannot finalize a task.",
|
|
203
|
+
Colors.RED,
|
|
204
|
+
),
|
|
205
|
+
file=sys.stderr,
|
|
206
|
+
)
|
|
207
|
+
return 2
|
|
208
|
+
|
|
209
|
+
repo_root = get_repo_root()
|
|
210
|
+
if args.command == "new":
|
|
211
|
+
return _cmd_new(args, repo_root)
|
|
212
|
+
if args.command == "finalize":
|
|
213
|
+
return _cmd_finalize(args, repo_root)
|
|
214
|
+
return 1
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
if __name__ == "__main__":
|
|
218
|
+
raise SystemExit(main())
|
|
@@ -23,6 +23,7 @@ configure_cli_encoding()
|
|
|
23
23
|
COMMAND_SCRIPTS = {
|
|
24
24
|
"resume": "adapters/cli/resume.py",
|
|
25
25
|
"task": "adapters/cli/task.py",
|
|
26
|
+
"plan": "adapters/cli/plan.py",
|
|
26
27
|
"get-context": "adapters/cli/get_context.py",
|
|
27
28
|
"get_context": "adapters/cli/get_context.py",
|
|
28
29
|
"get-developer": "adapters/cli/get_developer.py",
|
|
@@ -35,7 +36,7 @@ COMMAND_SCRIPTS = {
|
|
|
35
36
|
}
|
|
36
37
|
RESERVED_COMMAND_NAMES = (*COMMAND_SCRIPTS, "python", "help", "-h", "--help")
|
|
37
38
|
|
|
38
|
-
CONTEXT_AWARE_COMMANDS = {"resume", "task", "subagent"}
|
|
39
|
+
CONTEXT_AWARE_COMMANDS = {"resume", "task", "plan", "subagent"}
|
|
39
40
|
|
|
40
41
|
|
|
41
42
|
def print_usage() -> None:
|
|
@@ -49,6 +50,7 @@ def print_usage() -> None:
|
|
|
49
50
|
Common commands:
|
|
50
51
|
resume
|
|
51
52
|
task
|
|
53
|
+
plan
|
|
52
54
|
state
|
|
53
55
|
get-context
|
|
54
56
|
get-developer
|