cowork-flow 0.0.47 → 0.0.49
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 +41 -0
- package/README.md +102 -23
- package/package.json +8 -3
- package/src/cli.js +6 -0
- package/src/commands/init.js +0 -83
- package/src/commands/source-refresh.js +40 -0
- package/src/commands/sync.js +6 -0
- package/src/commands/update.js +59 -6
- package/src/lib/copy-template.js +200 -50
- package/src/lib/host-assets.js +367 -64
- package/src/lib/plan-applier.js +47 -0
- package/template/.claude/hooks/inject-workflow-state.py +7 -0
- package/template/.codex/hooks/inject-workflow-state.py +7 -0
- package/template/.codex/hooks.json +1 -1
- package/template/.cowork-flow/.version +1 -1
- package/template/.cowork-flow/config.yaml +0 -11
- package/template/.cowork-flow/scripts/adapters/cli/init_developer.py +0 -1
- package/template/.cowork-flow/scripts/adapters/cli/subagent.py +7 -4
- package/template/.cowork-flow/scripts/adapters/cli/task.py +8 -484
- package/template/.cowork-flow/scripts/adapters/cli/task_archive_commands.py +15 -134
- package/template/.cowork-flow/scripts/adapters/cli/task_context_commands.py +7 -1
- package/template/.cowork-flow/scripts/adapters/cli/task_lifecycle_commands.py +388 -0
- package/template/.cowork-flow/scripts/adapters/cli/task_navigation.py +163 -2
- package/template/.cowork-flow/scripts/adapters/git/git_context.py +16 -193
- package/template/.cowork-flow/scripts/adapters/host/host_manifest.py +170 -84
- package/template/.cowork-flow/scripts/infra/config.py +0 -17
- package/template/.cowork-flow/scripts/infra/developer_profile.py +0 -95
- package/template/.cowork-flow/scripts/infra/paths.py +0 -79
- package/template/.cowork-flow/scripts/infra/skill_manifest.py +23 -3
- package/template/.cowork-flow/scripts/infra/storage/operation_log.py +40 -0
- package/template/.cowork-flow/scripts/infra/storage/state_store.py +356 -1
- package/template/.cowork-flow/scripts/kernel/workflow_route.py +15 -15
- package/template/.cowork-flow/scripts/run.py +0 -5
- package/template/.cowork-flow/scripts/services/context_discovery.py +156 -0
- package/template/.cowork-flow/scripts/services/context_jsonl.py +119 -0
- package/template/.cowork-flow/scripts/services/context_paths.py +186 -0
- package/template/.cowork-flow/scripts/services/lifecycle_checks.py +186 -69
- package/template/.cowork-flow/scripts/services/lifecycle_policy.py +128 -0
- package/template/.cowork-flow/scripts/services/plan_binding.py +146 -0
- package/template/.cowork-flow/scripts/services/readiness.py +3 -170
- package/template/.cowork-flow/scripts/services/task_archive.py +115 -24
- package/template/.cowork-flow/scripts/services/task_context.py +103 -365
- package/template/.cowork-flow/scripts/services/task_creation.py +40 -5
- package/template/.cowork-flow/scripts/services/task_lifecycle.py +155 -18
- package/template/.cowork-flow/scripts/services/task_routing.py +80 -67
- package/template/.cowork-flow/scripts/services/task_utils.py +0 -146
- package/template/.cowork-flow/scripts/services/workflow_runtime.py +1 -1
- package/template/.cowork-flow/spec/contracts/capabilities.md +23 -0
- package/template/.cowork-flow/spec/contracts/decision-anchor.md +2 -2
- package/template/.cowork-flow/spec/contracts/error-output-as-data.md +8 -0
- package/template/.cowork-flow/spec/contracts/index.md +1 -0
- package/template/.cowork-flow/spec/contracts/party-mode-v2-board.md +20 -0
- package/template/.cowork-flow/spec/contracts/plan-binding.md +50 -0
- package/template/.cowork-flow/spec/contracts/skill-owned-actions.md +31 -7
- package/template/.cowork-flow/spec/guides/index.md +3 -1
- package/template/.cowork-flow/spec/index.md +5 -4
- package/template/.cowork-flow/spec/references/definition-of-done.md +2 -3
- package/template/.cowork-flow/spec/references/orchestration-patterns.md +1 -1
- package/template/.cowork-flow/spec/runtime/contract-registry.json +14 -0
- package/template/.cowork-flow/spec/runtime/host-assets.json +73 -3
- package/template/.cowork-flow/spec/runtime/index.md +1 -1
- package/template/.cowork-flow/spec/schemas/host-assets.schema.json +150 -8
- package/template/.zcode/.zcode-plugin/plugin.json +1 -1
- package/template/skills/batch-execution/SKILL.md +37 -3
- package/template/skills/batch-execution/scripts/batch_action.py +4 -0
- package/template/skills/batch-execution/scripts/batch_execution.py +120 -499
- package/template/skills/batch-execution/scripts/batch_recovery.py +94 -0
- package/template/skills/batch-execution/scripts/batch_state_machine.py +201 -0
- package/template/skills/batch-execution/scripts/batch_verification.py +326 -0
- package/template/skills/brainstorming/SKILL.md +8 -7
- package/template/skills/cowork-flow/SKILL.md +3 -0
- package/template/skills/party-mode/scripts/party_action_contract.py +113 -0
- package/template/skills/party-mode/scripts/party_actions.py +95 -0
- package/template/skills/party-mode/scripts/party_board_store.py +148 -0
- package/template/skills/party-mode/scripts/party_mode_v2.py +345 -317
- package/template/skills/party-mode/scripts/party_reports.py +177 -0
- package/template/skills/party-mode/scripts/party_state_machine.py +189 -0
- package/template/skills/runtime-health/SKILL.md +14 -2
- package/template/skills/runtime-health/scripts/doctor.py +278 -41
- package/template/skills/task-planning/SKILL.md +105 -61
- package/template/skills/task-review/SKILL.md +4 -4
- package/template/skills/task-review/scripts/review_check.py +277 -20
- package/template/skills/test-first/SKILL.md +18 -3
- package/template/skills/test-first/manifest.json +8 -4
- package/template/.cowork-flow/changes/archive/.gitkeep +0 -1
- package/template/.cowork-flow/scripts/adapters/cli/add_session.py +0 -465
- package/template/.cowork-flow/scripts/adapters/cli/change.py +0 -412
- package/template/.cowork-flow/workspace/index.md +0 -102
- package/template/.zcode/scaffold/AGENTS.md +0 -145
- package/template/.zcode/scaffold/CLAUDE.md +0 -5
|
@@ -98,6 +98,9 @@ def _host_context_prefix(host: str) -> str:
|
|
|
98
98
|
def _suggest_host_context_key(host: str, runtime_context_id: str) -> str:
|
|
99
99
|
return f"{_host_context_prefix(host)}_{runtime_context_id}"
|
|
100
100
|
|
|
101
|
+
def _print_runtime_error(error: RuntimeContextError) -> None:
|
|
102
|
+
print(f"Error: {error.code}: {error.detail}", file=sys.stderr)
|
|
103
|
+
|
|
101
104
|
def _resolve_agent_type(role: str, agent_type: str | None) -> tuple[str, str]:
|
|
102
105
|
normalized_role = role.strip()
|
|
103
106
|
requested = agent_type.strip() if isinstance(agent_type, str) else ""
|
|
@@ -178,7 +181,7 @@ def cmd_init(args: argparse.Namespace) -> int:
|
|
|
178
181
|
context,
|
|
179
182
|
)
|
|
180
183
|
except RuntimeContextError as error:
|
|
181
|
-
|
|
184
|
+
_print_runtime_error(error)
|
|
182
185
|
return 1
|
|
183
186
|
logical_context_key = initialized.logical_context_key
|
|
184
187
|
host_context_key = _suggest_host_context_key(host, runtime_context_id)
|
|
@@ -244,7 +247,7 @@ def cmd_update(args: argparse.Namespace) -> int:
|
|
|
244
247
|
note=args.note,
|
|
245
248
|
)
|
|
246
249
|
except RuntimeContextError as error:
|
|
247
|
-
|
|
250
|
+
_print_runtime_error(error)
|
|
248
251
|
return 1
|
|
249
252
|
if context is None:
|
|
250
253
|
print(f"Error: subagent not found: {args.subagent_id}", file=sys.stderr)
|
|
@@ -261,7 +264,7 @@ def cmd_bind(args: argparse.Namespace) -> int:
|
|
|
261
264
|
args.context_key,
|
|
262
265
|
)
|
|
263
266
|
except RuntimeContextError as error:
|
|
264
|
-
|
|
267
|
+
_print_runtime_error(error)
|
|
265
268
|
return 1
|
|
266
269
|
if context is None:
|
|
267
270
|
print(f"Error: cannot bind runtime context: {args.subagent_id}", file=sys.stderr)
|
|
@@ -275,7 +278,7 @@ def cmd_close(args: argparse.Namespace) -> int:
|
|
|
275
278
|
try:
|
|
276
279
|
closed = RuntimeContextService(repo_root).close(args.subagent_id)
|
|
277
280
|
except RuntimeContextError as error:
|
|
278
|
-
|
|
281
|
+
_print_runtime_error(error)
|
|
279
282
|
return 1
|
|
280
283
|
if not closed:
|
|
281
284
|
print(f"Error: subagent not found: {args.subagent_id}", file=sys.stderr)
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
# -*- coding: utf-8 -*-
|
|
3
|
-
"""Task workflow entry point and
|
|
3
|
+
"""Task workflow entry point and CLI command composition."""
|
|
4
4
|
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
|
|
7
7
|
import argparse
|
|
8
8
|
import sys
|
|
9
|
-
from datetime import datetime
|
|
10
|
-
from pathlib import Path
|
|
11
9
|
|
|
12
10
|
if __package__:
|
|
13
11
|
from . import _bootstrap as _bootstrap # noqa: F401
|
|
@@ -15,8 +13,6 @@ else:
|
|
|
15
13
|
import _bootstrap # noqa: F401
|
|
16
14
|
|
|
17
15
|
from services.task_context import (
|
|
18
|
-
PLANNED_FILE_HINT,
|
|
19
|
-
TaskContextService,
|
|
20
16
|
detect_installed_platforms as _detect_installed_platforms,
|
|
21
17
|
discover_spec_files as _discover_spec_files,
|
|
22
18
|
get_check_context,
|
|
@@ -31,11 +27,6 @@ from services.task_context import (
|
|
|
31
27
|
use_claude_skill_context as _use_claude_skill_context,
|
|
32
28
|
write_jsonl as _write_jsonl,
|
|
33
29
|
)
|
|
34
|
-
from services.task_lifecycle import (
|
|
35
|
-
LifecyclePreflightFailure,
|
|
36
|
-
LifecycleResult,
|
|
37
|
-
TaskLifecycleService,
|
|
38
|
-
)
|
|
39
30
|
from adapters.cli.task_archive_commands import cmd_archive
|
|
40
31
|
from adapters.cli.task_context_commands import (
|
|
41
32
|
cmd_add_planned_file,
|
|
@@ -46,6 +37,13 @@ from adapters.cli.task_context_commands import (
|
|
|
46
37
|
)
|
|
47
38
|
from adapters.cli.task_create_command import cmd_create, ensure_tasks_dir
|
|
48
39
|
from adapters.cli import task_navigation
|
|
40
|
+
from adapters.cli.task_lifecycle_commands import (
|
|
41
|
+
cmd_complete,
|
|
42
|
+
cmd_current,
|
|
43
|
+
cmd_finish,
|
|
44
|
+
cmd_review,
|
|
45
|
+
cmd_start,
|
|
46
|
+
)
|
|
49
47
|
from adapters.cli.task_parser import build_parser, show_usage
|
|
50
48
|
from adapters.cli.task_next_runner import (
|
|
51
49
|
NextActionHandlers,
|
|
@@ -56,7 +54,6 @@ from adapters.cli.task_support import (
|
|
|
56
54
|
Colors,
|
|
57
55
|
colored,
|
|
58
56
|
resolve_task_dir as _resolve_task_dir,
|
|
59
|
-
run_hooks as _run_hooks,
|
|
60
57
|
)
|
|
61
58
|
from adapters.cli.task_tree_commands import (
|
|
62
59
|
cmd_add_subtask,
|
|
@@ -67,479 +64,6 @@ from adapters.cli.task_tree_commands import (
|
|
|
67
64
|
)
|
|
68
65
|
from adapters.cli.execution_context_args import execution_context_from_namespace
|
|
69
66
|
from adapters.cli.execution_resume import worker_command_block_message
|
|
70
|
-
from infra.paths import (
|
|
71
|
-
DIR_WORKFLOW,
|
|
72
|
-
FILE_TASK_JSON,
|
|
73
|
-
get_repo_root,
|
|
74
|
-
)
|
|
75
|
-
from runtime.session_state import clear_active_task, get_active_task, is_main_session
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
def _allow_spec_file_modifications(repo_root: Path, execution_context) -> bool:
|
|
79
|
-
if execution_context.is_worker or execution_context.is_subagent:
|
|
80
|
-
return False
|
|
81
|
-
return is_main_session(repo_root)
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
def _report_check_block(title: str, blockers: tuple[str, ...]) -> int:
|
|
85
|
-
print(colored(f"Error: {title}", Colors.RED), file=sys.stderr)
|
|
86
|
-
for blocker in blockers:
|
|
87
|
-
print(f" - {blocker}", file=sys.stderr)
|
|
88
|
-
return 1
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
def _print_transition_blockers(blockers: list[str]) -> None:
|
|
92
|
-
print(
|
|
93
|
-
colored("Error: Task state transition blocked", Colors.RED),
|
|
94
|
-
file=sys.stderr,
|
|
95
|
-
)
|
|
96
|
-
for blocker in blockers:
|
|
97
|
-
print(f" - {blocker}", file=sys.stderr)
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
def _display_task_path(repo_root: Path, task_dir: Path) -> str:
|
|
101
|
-
try:
|
|
102
|
-
return task_dir.resolve().relative_to(repo_root.resolve()).as_posix()
|
|
103
|
-
except ValueError:
|
|
104
|
-
return str(task_dir)
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
def _resolve_status_task_dir(
|
|
108
|
-
args: argparse.Namespace,
|
|
109
|
-
repo_root: Path,
|
|
110
|
-
) -> Path | None:
|
|
111
|
-
target_input = getattr(args, "dir", None)
|
|
112
|
-
if target_input:
|
|
113
|
-
task_dir = _resolve_task_dir(target_input, repo_root)
|
|
114
|
-
else:
|
|
115
|
-
active = get_active_task(repo_root)
|
|
116
|
-
if not active.context_key:
|
|
117
|
-
print(
|
|
118
|
-
colored(
|
|
119
|
-
"Error: Missing session context. Set "
|
|
120
|
-
"COWORK_FLOW_CONTEXT_ID or pass a task dir.",
|
|
121
|
-
Colors.RED,
|
|
122
|
-
),
|
|
123
|
-
file=sys.stderr,
|
|
124
|
-
)
|
|
125
|
-
return None
|
|
126
|
-
if not active.task_path:
|
|
127
|
-
print(
|
|
128
|
-
colored(
|
|
129
|
-
"Error: No active task set for this session",
|
|
130
|
-
Colors.RED,
|
|
131
|
-
),
|
|
132
|
-
file=sys.stderr,
|
|
133
|
-
)
|
|
134
|
-
return None
|
|
135
|
-
task_dir = repo_root / active.task_path
|
|
136
|
-
|
|
137
|
-
if not task_dir.is_dir():
|
|
138
|
-
print(
|
|
139
|
-
colored(
|
|
140
|
-
f"Error: Task not found: {target_input or task_dir}",
|
|
141
|
-
Colors.RED,
|
|
142
|
-
),
|
|
143
|
-
file=sys.stderr,
|
|
144
|
-
)
|
|
145
|
-
return None
|
|
146
|
-
if not (task_dir / FILE_TASK_JSON).is_file():
|
|
147
|
-
print(
|
|
148
|
-
colored(
|
|
149
|
-
f"Error: task.json not found: {task_dir}",
|
|
150
|
-
Colors.RED,
|
|
151
|
-
),
|
|
152
|
-
file=sys.stderr,
|
|
153
|
-
)
|
|
154
|
-
return None
|
|
155
|
-
return task_dir
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
def _task_start_blockers(task_dir: Path) -> list[str]:
|
|
159
|
-
return list(
|
|
160
|
-
TaskContextService(get_repo_root(task_dir)).start_blockers(task_dir)
|
|
161
|
-
)
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
def _task_context_validation_issues(
|
|
165
|
-
task_dir: Path,
|
|
166
|
-
repo_root: Path,
|
|
167
|
-
quiet: bool = False,
|
|
168
|
-
) -> list[str]:
|
|
169
|
-
del quiet
|
|
170
|
-
return list(
|
|
171
|
-
TaskContextService(repo_root).validation_issue_summaries(task_dir)
|
|
172
|
-
)
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
def _refresh_task_artifact_placeholders(
|
|
176
|
-
task_dir: Path,
|
|
177
|
-
repo_root: Path,
|
|
178
|
-
) -> list[str]:
|
|
179
|
-
service = TaskContextService(repo_root)
|
|
180
|
-
service.ensure_task_artifact_placeholders(task_dir)
|
|
181
|
-
return _task_context_validation_issues(task_dir, repo_root)
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
def _optional_readiness_blockers(
|
|
185
|
-
repo_root: Path,
|
|
186
|
-
task_dir: Path,
|
|
187
|
-
) -> list[str]:
|
|
188
|
-
try:
|
|
189
|
-
from services.readiness import task_readiness_blockers
|
|
190
|
-
except Exception:
|
|
191
|
-
return []
|
|
192
|
-
try:
|
|
193
|
-
blockers = task_readiness_blockers(repo_root, task_dir)
|
|
194
|
-
except Exception:
|
|
195
|
-
return [
|
|
196
|
-
"readiness check failed; run task next <dir> --validate and inspect "
|
|
197
|
-
"linked change"
|
|
198
|
-
]
|
|
199
|
-
return [str(blocker) for blocker in blockers if str(blocker).strip()]
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
def _start_preflight(
|
|
203
|
-
task_dir: Path,
|
|
204
|
-
repo_root: Path,
|
|
205
|
-
) -> LifecyclePreflightFailure | None:
|
|
206
|
-
blockers = _task_start_blockers(task_dir)
|
|
207
|
-
if blockers:
|
|
208
|
-
return LifecyclePreflightFailure(
|
|
209
|
-
code="TASK-START-001",
|
|
210
|
-
title="Task is not ready to start yet",
|
|
211
|
-
blockers=tuple(blockers),
|
|
212
|
-
hint=(
|
|
213
|
-
"write decision-anchor.md and required context JSONL files, "
|
|
214
|
-
"then retry `task next <dir> --run`"
|
|
215
|
-
),
|
|
216
|
-
)
|
|
217
|
-
|
|
218
|
-
validation_issues = _task_context_validation_issues(
|
|
219
|
-
task_dir,
|
|
220
|
-
repo_root,
|
|
221
|
-
)
|
|
222
|
-
if validation_issues:
|
|
223
|
-
validation_issues = _refresh_task_artifact_placeholders(task_dir, repo_root)
|
|
224
|
-
if validation_issues:
|
|
225
|
-
return LifecyclePreflightFailure(
|
|
226
|
-
code="TASK-CONTEXT-001",
|
|
227
|
-
title="Task context validation failed",
|
|
228
|
-
blockers=tuple(validation_issues),
|
|
229
|
-
hint=(
|
|
230
|
-
"run ./.cowork-flow/run task next <dir> --validate and fix the "
|
|
231
|
-
f"reported issues. {PLANNED_FILE_HINT}"
|
|
232
|
-
),
|
|
233
|
-
)
|
|
234
|
-
|
|
235
|
-
readiness_blockers = _optional_readiness_blockers(
|
|
236
|
-
repo_root,
|
|
237
|
-
task_dir,
|
|
238
|
-
)
|
|
239
|
-
if readiness_blockers:
|
|
240
|
-
return LifecyclePreflightFailure(
|
|
241
|
-
code="TASK-READINESS-001",
|
|
242
|
-
title="Task readiness failed",
|
|
243
|
-
blockers=tuple(readiness_blockers),
|
|
244
|
-
hint=(
|
|
245
|
-
"run ./.cowork-flow/run task next <dir> and complete the "
|
|
246
|
-
"reported readiness artifacts"
|
|
247
|
-
),
|
|
248
|
-
)
|
|
249
|
-
return None
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
def _report_lifecycle_preflight(
|
|
253
|
-
result: LifecyclePreflightFailure | LifecycleResult,
|
|
254
|
-
) -> int:
|
|
255
|
-
print(colored(f"Error: {result.title}", Colors.RED), file=sys.stderr)
|
|
256
|
-
for blocker in result.blockers:
|
|
257
|
-
print(f" - {blocker}", file=sys.stderr)
|
|
258
|
-
if result.hint:
|
|
259
|
-
print(f"Hint: {result.hint}", file=sys.stderr)
|
|
260
|
-
return 1
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
def _report_lifecycle_repository_error(
|
|
264
|
-
result: LifecycleResult,
|
|
265
|
-
) -> int:
|
|
266
|
-
error = result.repository_error
|
|
267
|
-
action = (
|
|
268
|
-
"read"
|
|
269
|
-
if error is not None and error.code.startswith("TASK-LOAD-")
|
|
270
|
-
else "write"
|
|
271
|
-
)
|
|
272
|
-
task_json = result.task_dir / FILE_TASK_JSON
|
|
273
|
-
print(
|
|
274
|
-
colored(
|
|
275
|
-
f"Error: Failed to {action} task metadata: {task_json}",
|
|
276
|
-
Colors.RED,
|
|
277
|
-
),
|
|
278
|
-
file=sys.stderr,
|
|
279
|
-
)
|
|
280
|
-
return 1
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
def _resolve_start_task(
|
|
284
|
-
task_input: str,
|
|
285
|
-
repo_root: Path,
|
|
286
|
-
) -> Path | None:
|
|
287
|
-
full_path = _resolve_task_dir(task_input, repo_root)
|
|
288
|
-
if full_path.is_dir():
|
|
289
|
-
return full_path
|
|
290
|
-
print(
|
|
291
|
-
colored(
|
|
292
|
-
f"Error: Task not found: {task_input}",
|
|
293
|
-
Colors.RED,
|
|
294
|
-
)
|
|
295
|
-
)
|
|
296
|
-
print(
|
|
297
|
-
"Hint: Use task name (e.g., 'my-task') or full path "
|
|
298
|
-
f"(e.g., '{DIR_WORKFLOW}/tasks/01-31-my-task')"
|
|
299
|
-
)
|
|
300
|
-
return None
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
def _run_auto_start(
|
|
304
|
-
args: argparse.Namespace,
|
|
305
|
-
full_path: Path,
|
|
306
|
-
preflight,
|
|
307
|
-
) -> int:
|
|
308
|
-
failure = preflight(full_path)
|
|
309
|
-
if failure is not None:
|
|
310
|
-
return _report_lifecycle_preflight(failure)
|
|
311
|
-
from adapters.cli.batch_mode import run_batch_entry
|
|
312
|
-
|
|
313
|
-
return run_batch_entry(get_repo_root(), full_path, args)
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
def _report_start_failure(
|
|
317
|
-
result: LifecycleResult,
|
|
318
|
-
service: TaskLifecycleService,
|
|
319
|
-
) -> int:
|
|
320
|
-
del service
|
|
321
|
-
if result.title:
|
|
322
|
-
return _report_lifecycle_preflight(result)
|
|
323
|
-
if result.code == "LIFECYCLE-TRANSITION-001":
|
|
324
|
-
_print_transition_blockers(list(result.blockers))
|
|
325
|
-
return 1
|
|
326
|
-
if result.code == "LIFECYCLE-CHECK-001":
|
|
327
|
-
return _report_check_block(
|
|
328
|
-
"Lifecycle checks blocked start_task action",
|
|
329
|
-
result.blockers,
|
|
330
|
-
)
|
|
331
|
-
if result.code == "LIFECYCLE-CONTEXT-001":
|
|
332
|
-
return _report_missing_session_context()
|
|
333
|
-
if result.repository_error is not None:
|
|
334
|
-
return _report_lifecycle_repository_error(result)
|
|
335
|
-
print(
|
|
336
|
-
colored("Error: Task lifecycle failed", Colors.RED),
|
|
337
|
-
file=sys.stderr,
|
|
338
|
-
)
|
|
339
|
-
return 1
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
def _report_missing_session_context() -> int:
|
|
343
|
-
print(
|
|
344
|
-
colored(
|
|
345
|
-
"Error: Missing session context. Set "
|
|
346
|
-
"COWORK_FLOW_CONTEXT_ID or run inside a "
|
|
347
|
-
"supported host session.",
|
|
348
|
-
Colors.RED,
|
|
349
|
-
),
|
|
350
|
-
file=sys.stderr,
|
|
351
|
-
)
|
|
352
|
-
return 1
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
def _report_start_success(
|
|
356
|
-
result: LifecycleResult,
|
|
357
|
-
repo_root: Path,
|
|
358
|
-
full_path: Path,
|
|
359
|
-
) -> None:
|
|
360
|
-
task_dir = result.active_task_path or _display_task_path(
|
|
361
|
-
repo_root,
|
|
362
|
-
full_path,
|
|
363
|
-
)
|
|
364
|
-
print(
|
|
365
|
-
colored(
|
|
366
|
-
f"[OK] Active session task set to: {task_dir}",
|
|
367
|
-
Colors.GREEN,
|
|
368
|
-
)
|
|
369
|
-
)
|
|
370
|
-
print()
|
|
371
|
-
print(
|
|
372
|
-
colored(
|
|
373
|
-
"Fixed agents will load context from this task's jsonl files.",
|
|
374
|
-
Colors.BLUE,
|
|
375
|
-
)
|
|
376
|
-
)
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
def cmd_start(args: argparse.Namespace) -> int:
|
|
380
|
-
"""Set the active task for this session."""
|
|
381
|
-
repo_root = get_repo_root()
|
|
382
|
-
task_input = args.dir
|
|
383
|
-
if not task_input:
|
|
384
|
-
print(
|
|
385
|
-
colored(
|
|
386
|
-
"Error: task directory or name required",
|
|
387
|
-
Colors.RED,
|
|
388
|
-
)
|
|
389
|
-
)
|
|
390
|
-
return 1
|
|
391
|
-
|
|
392
|
-
full_path = _resolve_start_task(task_input, repo_root)
|
|
393
|
-
if full_path is None:
|
|
394
|
-
return 1
|
|
395
|
-
|
|
396
|
-
def preflight(
|
|
397
|
-
task_dir: Path,
|
|
398
|
-
) -> LifecyclePreflightFailure | None:
|
|
399
|
-
return _start_preflight(task_dir, repo_root)
|
|
400
|
-
|
|
401
|
-
if getattr(args, "auto", False):
|
|
402
|
-
return _run_auto_start(args, full_path, preflight)
|
|
403
|
-
|
|
404
|
-
service = TaskLifecycleService(repo_root)
|
|
405
|
-
result = service.start(full_path, preflight=preflight)
|
|
406
|
-
if not result.ok:
|
|
407
|
-
return _report_start_failure(result, service)
|
|
408
|
-
|
|
409
|
-
_report_start_success(result, repo_root, full_path)
|
|
410
|
-
_run_hooks("after_start", full_path / FILE_TASK_JSON, repo_root)
|
|
411
|
-
return 0
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
def cmd_review(args: argparse.Namespace) -> int:
|
|
415
|
-
"""Mark a task ready for check."""
|
|
416
|
-
repo_root = get_repo_root()
|
|
417
|
-
execution_context = execution_context_from_namespace(args)
|
|
418
|
-
task_dir = _resolve_status_task_dir(args, repo_root)
|
|
419
|
-
if task_dir is None:
|
|
420
|
-
return 1
|
|
421
|
-
|
|
422
|
-
service = TaskLifecycleService(repo_root)
|
|
423
|
-
result = service.review(
|
|
424
|
-
task_dir,
|
|
425
|
-
allow_spec_file_modifications=_allow_spec_file_modifications(
|
|
426
|
-
repo_root,
|
|
427
|
-
execution_context,
|
|
428
|
-
),
|
|
429
|
-
)
|
|
430
|
-
if not result.ok:
|
|
431
|
-
if result.code == "LIFECYCLE-TRANSITION-001":
|
|
432
|
-
_print_transition_blockers(list(result.blockers))
|
|
433
|
-
return 1
|
|
434
|
-
if result.code == "LIFECYCLE-CHECK-001":
|
|
435
|
-
return _report_check_block(
|
|
436
|
-
"Lifecycle checks blocked review",
|
|
437
|
-
result.blockers,
|
|
438
|
-
)
|
|
439
|
-
if result.repository_error is not None:
|
|
440
|
-
return _report_lifecycle_repository_error(result)
|
|
441
|
-
return 1
|
|
442
|
-
|
|
443
|
-
task_path = _display_task_path(repo_root, task_dir)
|
|
444
|
-
print(
|
|
445
|
-
colored(
|
|
446
|
-
f"[OK] Task marked for check: {task_path}",
|
|
447
|
-
Colors.GREEN,
|
|
448
|
-
)
|
|
449
|
-
)
|
|
450
|
-
print(f"Next: ./.cowork-flow/run task next {task_path}")
|
|
451
|
-
return 0
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
def cmd_complete(args: argparse.Namespace) -> int:
|
|
455
|
-
"""Mark a task completed after final check."""
|
|
456
|
-
repo_root = get_repo_root()
|
|
457
|
-
task_dir = _resolve_status_task_dir(args, repo_root)
|
|
458
|
-
if task_dir is None:
|
|
459
|
-
return 1
|
|
460
|
-
|
|
461
|
-
service = TaskLifecycleService(repo_root)
|
|
462
|
-
result = service.complete(
|
|
463
|
-
task_dir,
|
|
464
|
-
completed_at=datetime.now().strftime("%Y-%m-%d"),
|
|
465
|
-
allow_spec_file_modifications=is_main_session(repo_root),
|
|
466
|
-
)
|
|
467
|
-
if not result.ok:
|
|
468
|
-
if result.code == "LIFECYCLE-TRANSITION-001":
|
|
469
|
-
_print_transition_blockers(list(result.blockers))
|
|
470
|
-
print(
|
|
471
|
-
"Hint: run ./.cowork-flow/run task next <task-dir> "
|
|
472
|
-
"--run --intent review to mark the task ready for check",
|
|
473
|
-
file=sys.stderr,
|
|
474
|
-
)
|
|
475
|
-
return 1
|
|
476
|
-
if result.code == "LIFECYCLE-CHECK-001":
|
|
477
|
-
return _report_check_block(
|
|
478
|
-
"Lifecycle checks blocked completion",
|
|
479
|
-
result.blockers,
|
|
480
|
-
)
|
|
481
|
-
if result.repository_error is not None:
|
|
482
|
-
return _report_lifecycle_repository_error(result)
|
|
483
|
-
return 1
|
|
484
|
-
|
|
485
|
-
task_path = _display_task_path(repo_root, task_dir)
|
|
486
|
-
print(
|
|
487
|
-
colored(
|
|
488
|
-
f"[OK] Task marked completed: {task_path}",
|
|
489
|
-
Colors.GREEN,
|
|
490
|
-
)
|
|
491
|
-
)
|
|
492
|
-
print(f"Next: ./.cowork-flow/run task next {task_path}")
|
|
493
|
-
return 0
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
def cmd_finish(args: argparse.Namespace) -> int:
|
|
497
|
-
"""Clear the active task for this session."""
|
|
498
|
-
repo_root = get_repo_root()
|
|
499
|
-
active = get_active_task(repo_root)
|
|
500
|
-
if not active.task_path:
|
|
501
|
-
print(
|
|
502
|
-
colored(
|
|
503
|
-
"No active task set for this session",
|
|
504
|
-
Colors.YELLOW,
|
|
505
|
-
)
|
|
506
|
-
)
|
|
507
|
-
return 0
|
|
508
|
-
|
|
509
|
-
task_json_path = repo_root / active.task_path / FILE_TASK_JSON
|
|
510
|
-
clear_active_task(repo_root)
|
|
511
|
-
print(
|
|
512
|
-
colored(
|
|
513
|
-
f"[OK] Cleared active session task (was: {active.task_path})",
|
|
514
|
-
Colors.GREEN,
|
|
515
|
-
)
|
|
516
|
-
)
|
|
517
|
-
if task_json_path.is_file():
|
|
518
|
-
_run_hooks("after_finish", task_json_path, repo_root)
|
|
519
|
-
return 0
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
def cmd_current(args: argparse.Namespace) -> int:
|
|
523
|
-
"""Show the active task for this session."""
|
|
524
|
-
repo_root = get_repo_root()
|
|
525
|
-
active = get_active_task(repo_root)
|
|
526
|
-
if not active.context_key:
|
|
527
|
-
print(
|
|
528
|
-
colored(
|
|
529
|
-
"Error: Missing session context. Set "
|
|
530
|
-
"COWORK_FLOW_CONTEXT_ID or run inside a "
|
|
531
|
-
"supported host session.",
|
|
532
|
-
Colors.RED,
|
|
533
|
-
),
|
|
534
|
-
file=sys.stderr,
|
|
535
|
-
)
|
|
536
|
-
return 1
|
|
537
|
-
if not active.task_path:
|
|
538
|
-
print("Active task: (none)")
|
|
539
|
-
return 0
|
|
540
|
-
print(f"Active task: {active.task_path}")
|
|
541
|
-
print(f"Source: {active.source}:{active.context_key}")
|
|
542
|
-
return 0
|
|
543
67
|
|
|
544
68
|
|
|
545
69
|
def cmd_next(args: argparse.Namespace) -> int:
|