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.
Files changed (90) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/README.md +102 -23
  3. package/package.json +8 -3
  4. package/src/cli.js +6 -0
  5. package/src/commands/init.js +0 -83
  6. package/src/commands/source-refresh.js +40 -0
  7. package/src/commands/sync.js +6 -0
  8. package/src/commands/update.js +59 -6
  9. package/src/lib/copy-template.js +200 -50
  10. package/src/lib/host-assets.js +367 -64
  11. package/src/lib/plan-applier.js +47 -0
  12. package/template/.claude/hooks/inject-workflow-state.py +7 -0
  13. package/template/.codex/hooks/inject-workflow-state.py +7 -0
  14. package/template/.codex/hooks.json +1 -1
  15. package/template/.cowork-flow/.version +1 -1
  16. package/template/.cowork-flow/config.yaml +0 -11
  17. package/template/.cowork-flow/scripts/adapters/cli/init_developer.py +0 -1
  18. package/template/.cowork-flow/scripts/adapters/cli/subagent.py +7 -4
  19. package/template/.cowork-flow/scripts/adapters/cli/task.py +8 -484
  20. package/template/.cowork-flow/scripts/adapters/cli/task_archive_commands.py +15 -134
  21. package/template/.cowork-flow/scripts/adapters/cli/task_context_commands.py +7 -1
  22. package/template/.cowork-flow/scripts/adapters/cli/task_lifecycle_commands.py +388 -0
  23. package/template/.cowork-flow/scripts/adapters/cli/task_navigation.py +163 -2
  24. package/template/.cowork-flow/scripts/adapters/git/git_context.py +16 -193
  25. package/template/.cowork-flow/scripts/adapters/host/host_manifest.py +170 -84
  26. package/template/.cowork-flow/scripts/infra/config.py +0 -17
  27. package/template/.cowork-flow/scripts/infra/developer_profile.py +0 -95
  28. package/template/.cowork-flow/scripts/infra/paths.py +0 -79
  29. package/template/.cowork-flow/scripts/infra/skill_manifest.py +23 -3
  30. package/template/.cowork-flow/scripts/infra/storage/operation_log.py +40 -0
  31. package/template/.cowork-flow/scripts/infra/storage/state_store.py +356 -1
  32. package/template/.cowork-flow/scripts/kernel/workflow_route.py +15 -15
  33. package/template/.cowork-flow/scripts/run.py +0 -5
  34. package/template/.cowork-flow/scripts/services/context_discovery.py +156 -0
  35. package/template/.cowork-flow/scripts/services/context_jsonl.py +119 -0
  36. package/template/.cowork-flow/scripts/services/context_paths.py +186 -0
  37. package/template/.cowork-flow/scripts/services/lifecycle_checks.py +186 -69
  38. package/template/.cowork-flow/scripts/services/lifecycle_policy.py +128 -0
  39. package/template/.cowork-flow/scripts/services/plan_binding.py +146 -0
  40. package/template/.cowork-flow/scripts/services/readiness.py +3 -170
  41. package/template/.cowork-flow/scripts/services/task_archive.py +115 -24
  42. package/template/.cowork-flow/scripts/services/task_context.py +103 -365
  43. package/template/.cowork-flow/scripts/services/task_creation.py +40 -5
  44. package/template/.cowork-flow/scripts/services/task_lifecycle.py +155 -18
  45. package/template/.cowork-flow/scripts/services/task_routing.py +80 -67
  46. package/template/.cowork-flow/scripts/services/task_utils.py +0 -146
  47. package/template/.cowork-flow/scripts/services/workflow_runtime.py +1 -1
  48. package/template/.cowork-flow/spec/contracts/capabilities.md +23 -0
  49. package/template/.cowork-flow/spec/contracts/decision-anchor.md +2 -2
  50. package/template/.cowork-flow/spec/contracts/error-output-as-data.md +8 -0
  51. package/template/.cowork-flow/spec/contracts/index.md +1 -0
  52. package/template/.cowork-flow/spec/contracts/party-mode-v2-board.md +20 -0
  53. package/template/.cowork-flow/spec/contracts/plan-binding.md +50 -0
  54. package/template/.cowork-flow/spec/contracts/skill-owned-actions.md +31 -7
  55. package/template/.cowork-flow/spec/guides/index.md +3 -1
  56. package/template/.cowork-flow/spec/index.md +5 -4
  57. package/template/.cowork-flow/spec/references/definition-of-done.md +2 -3
  58. package/template/.cowork-flow/spec/references/orchestration-patterns.md +1 -1
  59. package/template/.cowork-flow/spec/runtime/contract-registry.json +14 -0
  60. package/template/.cowork-flow/spec/runtime/host-assets.json +73 -3
  61. package/template/.cowork-flow/spec/runtime/index.md +1 -1
  62. package/template/.cowork-flow/spec/schemas/host-assets.schema.json +150 -8
  63. package/template/.zcode/.zcode-plugin/plugin.json +1 -1
  64. package/template/skills/batch-execution/SKILL.md +37 -3
  65. package/template/skills/batch-execution/scripts/batch_action.py +4 -0
  66. package/template/skills/batch-execution/scripts/batch_execution.py +120 -499
  67. package/template/skills/batch-execution/scripts/batch_recovery.py +94 -0
  68. package/template/skills/batch-execution/scripts/batch_state_machine.py +201 -0
  69. package/template/skills/batch-execution/scripts/batch_verification.py +326 -0
  70. package/template/skills/brainstorming/SKILL.md +8 -7
  71. package/template/skills/cowork-flow/SKILL.md +3 -0
  72. package/template/skills/party-mode/scripts/party_action_contract.py +113 -0
  73. package/template/skills/party-mode/scripts/party_actions.py +95 -0
  74. package/template/skills/party-mode/scripts/party_board_store.py +148 -0
  75. package/template/skills/party-mode/scripts/party_mode_v2.py +345 -317
  76. package/template/skills/party-mode/scripts/party_reports.py +177 -0
  77. package/template/skills/party-mode/scripts/party_state_machine.py +189 -0
  78. package/template/skills/runtime-health/SKILL.md +14 -2
  79. package/template/skills/runtime-health/scripts/doctor.py +278 -41
  80. package/template/skills/task-planning/SKILL.md +105 -61
  81. package/template/skills/task-review/SKILL.md +4 -4
  82. package/template/skills/task-review/scripts/review_check.py +277 -20
  83. package/template/skills/test-first/SKILL.md +18 -3
  84. package/template/skills/test-first/manifest.json +8 -4
  85. package/template/.cowork-flow/changes/archive/.gitkeep +0 -1
  86. package/template/.cowork-flow/scripts/adapters/cli/add_session.py +0 -465
  87. package/template/.cowork-flow/scripts/adapters/cli/change.py +0 -412
  88. package/template/.cowork-flow/workspace/index.md +0 -102
  89. package/template/.zcode/scaffold/AGENTS.md +0 -145
  90. 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
- print(f"Error: {error.detail}", file=sys.stderr)
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
- print(f"Error: {error.detail}", file=sys.stderr)
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
- print(f"Error: {error.detail}", file=sys.stderr)
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
- print(f"Error: {error.detail}", file=sys.stderr)
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 lifecycle adapters."""
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: