cowork-flow 0.0.37 → 0.0.39

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 (111) hide show
  1. package/README.md +27 -14
  2. package/package.json +1 -1
  3. package/src/commands/init.js +39 -28
  4. package/src/commands/sync.js +2 -2
  5. package/src/lib/asset-plan.js +127 -0
  6. package/src/lib/copy-template.js +101 -141
  7. package/src/lib/host-assets.js +231 -0
  8. package/src/lib/plan-applier.js +329 -0
  9. package/src/lib/platforms.js +15 -76
  10. package/src/lib/skill-registry.js +503 -0
  11. package/template/.claude/agents/cowork-check.md +10 -1
  12. package/template/.claude/agents/cowork-implement.md +11 -2
  13. package/template/.claude/hooks/inject-workflow-state.py +51 -254
  14. package/template/.codex/agents/cowork-check.toml +12 -3
  15. package/template/.codex/agents/cowork-implement.toml +11 -2
  16. package/template/.codex/agents/default.toml +3 -2
  17. package/template/.codex/agents/explorer.toml +3 -2
  18. package/template/.codex/agents/worker.toml +2 -1
  19. package/template/.codex/hooks/inject-workflow-state.py +46 -265
  20. package/template/.cowork-flow/.version +1 -1
  21. package/template/.cowork-flow/scripts/application/__init__.py +1 -0
  22. package/template/.cowork-flow/scripts/application/batch_execution.py +743 -0
  23. package/template/.cowork-flow/scripts/application/runtime_context_service.py +365 -0
  24. package/template/.cowork-flow/scripts/application/task_archive.py +220 -0
  25. package/template/.cowork-flow/scripts/application/task_context.py +765 -0
  26. package/template/.cowork-flow/scripts/application/task_creation.py +201 -0
  27. package/template/.cowork-flow/scripts/application/task_lifecycle.py +532 -0
  28. package/template/.cowork-flow/scripts/application/task_tree.py +179 -0
  29. package/template/.cowork-flow/scripts/commands/doctor.py +78 -57
  30. package/template/.cowork-flow/scripts/commands/party_mode_v2.py +671 -425
  31. package/template/.cowork-flow/scripts/commands/subagent.py +38 -26
  32. package/template/.cowork-flow/scripts/commands/task.py +535 -1953
  33. package/template/.cowork-flow/scripts/commands/task_archive_commands.py +195 -0
  34. package/template/.cowork-flow/scripts/commands/task_context_commands.py +240 -0
  35. package/template/.cowork-flow/scripts/commands/task_create_command.py +164 -0
  36. package/template/.cowork-flow/scripts/commands/task_navigation.py +407 -0
  37. package/template/.cowork-flow/scripts/commands/task_parser.py +364 -0
  38. package/template/.cowork-flow/scripts/commands/task_support.py +76 -0
  39. package/template/.cowork-flow/scripts/commands/task_tree_commands.py +160 -0
  40. package/template/.cowork-flow/scripts/common/core/execution_context.py +161 -85
  41. package/template/.cowork-flow/scripts/common/core/host_manifest.py +393 -0
  42. package/template/.cowork-flow/scripts/common/core/skill_registry.py +568 -0
  43. package/template/.cowork-flow/scripts/common/gates/coding_standards.py +58 -7
  44. package/template/.cowork-flow/scripts/common/gates/gates.py +323 -46
  45. package/template/.cowork-flow/scripts/common/gates/models.py +196 -0
  46. package/template/.cowork-flow/scripts/common/gates/registry.py +79 -0
  47. package/template/.cowork-flow/scripts/common/gates/tdd_evidence.py +13 -13
  48. package/template/.cowork-flow/scripts/common/gates/validate_coding_standards.py +104 -62
  49. package/template/.cowork-flow/scripts/common/gates/validate_implementation.py +39 -22
  50. package/template/.cowork-flow/scripts/common/gates/validate_rules.py +223 -40
  51. package/template/.cowork-flow/scripts/common/git/git_context.py +216 -182
  52. package/template/.cowork-flow/scripts/common/host/__init__.py +1 -0
  53. package/template/.cowork-flow/scripts/common/host/workflow_state_hook.py +303 -0
  54. package/template/.cowork-flow/scripts/common/storage/__init__.py +1 -0
  55. package/template/.cowork-flow/scripts/common/storage/operation_log.py +72 -0
  56. package/template/.cowork-flow/scripts/common/storage/state_store.py +279 -0
  57. package/template/.cowork-flow/scripts/common/storage/unit_of_work.py +214 -0
  58. package/template/.cowork-flow/scripts/common/task/active_task.py +93 -135
  59. package/template/.cowork-flow/scripts/common/task/batch_mode.py +33 -120
  60. package/template/.cowork-flow/scripts/common/task/decision_review.py +85 -0
  61. package/template/.cowork-flow/scripts/common/task/readiness.py +9 -15
  62. package/template/.cowork-flow/scripts/common/task/task_repository.py +156 -0
  63. package/template/.cowork-flow/scripts/run.py +24 -0
  64. package/template/.cowork-flow/spec/contracts/decision-anchor.md +8 -2
  65. package/template/.cowork-flow/spec/contracts/subagent-dispatch.md +2 -4
  66. package/template/.cowork-flow/spec/contracts/workflow-state-templates.md +1 -1
  67. package/template/.cowork-flow/spec/protocols/decision-review.md +28 -0
  68. package/template/.cowork-flow/spec/protocols/review.md +34 -0
  69. package/template/.cowork-flow/spec/protocols/spec-maintenance.md +16 -0
  70. package/template/.cowork-flow/spec/protocols/tdd.md +30 -0
  71. package/template/.cowork-flow/spec/references/definition-of-done.md +4 -4
  72. package/template/.cowork-flow/spec/references/orchestration-patterns.md +1 -1
  73. package/template/.cowork-flow/spec/runtime/contract-registry.json +25 -0
  74. package/template/.cowork-flow/spec/runtime/host-assets.json +214 -0
  75. package/template/.cowork-flow/spec/runtime/index.md +8 -0
  76. package/template/.cowork-flow/spec/runtime/rules.json +61 -10
  77. package/template/.cowork-flow/spec/runtime/skill-registry.json +397 -0
  78. package/template/.cowork-flow/spec/schemas/host-assets.schema.json +173 -0
  79. package/template/.cowork-flow/spec/schemas/rules.schema.json +10 -1
  80. package/template/.cowork-flow/spec/schemas/skill-registry.schema.json +202 -0
  81. package/template/.cowork-flow/workflow.md +18 -13
  82. package/template/.opencode/agents/cowork-check.md +11 -0
  83. package/template/.opencode/agents/cowork-implement.md +16 -1
  84. package/template/.zcode/.zcode-plugin/plugin.json +1 -1
  85. package/template/.zcode/hooks/inject-context.js +3 -85
  86. package/template/.zcode/hooks/inject-context.selfcheck.mjs +6 -6
  87. package/template/.zcode/scaffold/.cowork-flow/spec/contracts/decision-anchor.md +8 -2
  88. package/template/.zcode/scaffold/.cowork-flow/spec/contracts/subagent-dispatch.md +2 -4
  89. package/template/.zcode/scaffold/.cowork-flow/spec/runtime/rules.json +61 -10
  90. package/template/.zcode/scaffold/.cowork-flow/spec/schemas/rules.schema.json +10 -1
  91. package/template/.zcode/scaffold/.cowork-flow/workflow.md +18 -13
  92. package/template/.zcode/scaffold/AGENTS.md +18 -8
  93. package/template/AGENTS.md +1 -1
  94. package/template/skills/batch-mode/SKILL.md +140 -76
  95. package/template/skills/brainstorming/SKILL.md +2 -2
  96. package/template/skills/cowork-flow/SKILL.md +42 -0
  97. package/template/skills/party-mode/SHARED-BOUNDARIES.md +1 -1
  98. package/template/skills/party-mode/SKILL.md +48 -119
  99. package/template/.cowork-flow/scripts/common/gates/spec_validator.py +0 -53
  100. package/template/.cowork-flow/scripts/common/gates/validate_jsonl.py +0 -32
  101. package/template/skills/before-dev/SKILL.md +0 -41
  102. package/template/skills/check/SKILL.md +0 -53
  103. package/template/skills/check/simplification-guide.md +0 -47
  104. package/template/skills/continue/SKILL.md +0 -29
  105. package/template/skills/doubt-review/SKILL.md +0 -118
  106. package/template/skills/finish-work/SKILL.md +0 -32
  107. package/template/skills/party-mode-v2/SKILL.md +0 -75
  108. package/template/skills/start/SKILL.md +0 -78
  109. package/template/skills/tdd/SKILL.md +0 -74
  110. package/template/skills/update-spec/SKILL.md +0 -39
  111. package/template/skills/using-cowork-flow/SKILL.md +0 -80
@@ -0,0 +1,407 @@
1
+ #!/usr/bin/env python3
2
+ """Read-only task workflow navigation command."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import json
7
+ from pathlib import Path
8
+
9
+ from application.task_context import TaskContextService
10
+ from commands.task_archive_commands import linked_active_changes_for_task
11
+ from commands.task_support import resolve_task_dir
12
+ from common.core.execution_context import execution_context_from_namespace
13
+ from common.core.paths import get_repo_root
14
+ from common.core.skill_registry import SkillRegistry, load_skill_registry
15
+ from common.gates.gates import GateRunner
16
+ from common.task.active_task import get_active_task
17
+ from common.task.readiness import task_readiness_blockers
18
+ from common.task.task_repository import TaskRepository, TaskRepositoryError
19
+
20
+
21
+ CHECK_STATUSES = ("review",)
22
+ DONE_STATUSES = ("completed", "done")
23
+ USER_INTENTS = (
24
+ "question",
25
+ "clarify",
26
+ "plan",
27
+ "implement",
28
+ "review",
29
+ "debug",
30
+ "discuss",
31
+ "batch",
32
+ )
33
+ INTENT_REGISTRY_KEYS = {
34
+ "question": None,
35
+ "clarify": "clarify_requirement",
36
+ "plan": "write_plan",
37
+ "implement": "route_workflow",
38
+ "review": "route_workflow",
39
+ "debug": "analyze_repeated_failure",
40
+ "discuss": "discuss_options",
41
+ "batch": "batch_execute_plan",
42
+ }
43
+ INTENT_OPERATIONS = {
44
+ "question": {"answer_questions"},
45
+ "clarify": {"edit_planning_artifacts"},
46
+ "plan": {"edit_planning_artifacts"},
47
+ "implement": {
48
+ "create_task",
49
+ "start_task",
50
+ "implement_change",
51
+ "execute_delegated_work",
52
+ },
53
+ "review": {"request_review", "verify_change", "complete_task"},
54
+ "debug": {"debug_failure"},
55
+ "discuss": {"discuss_options"},
56
+ "batch": {"batch_execute"},
57
+ }
58
+
59
+
60
+ def _display(repo_root: Path, task_dir: Path) -> str:
61
+ try:
62
+ return task_dir.resolve().relative_to(repo_root.resolve()).as_posix()
63
+ except ValueError:
64
+ return str(task_dir)
65
+
66
+
67
+ def _status(repo_root: Path, task_dir: Path) -> str:
68
+ try:
69
+ data = TaskRepository(repo_root).load(task_dir)
70
+ except TaskRepositoryError:
71
+ return "stale"
72
+ status = data.get("status")
73
+ return status.strip() if isinstance(status, str) and status.strip() else "unknown"
74
+
75
+
76
+ def _blockers(repo_root: Path, task_dir: Path) -> list[str]:
77
+ blockers = list(TaskContextService(repo_root).start_blockers(task_dir))
78
+ blockers.extend(task_readiness_blockers(repo_root, task_dir))
79
+ result = GateRunner(repo_root).run("task_start", task_dir)
80
+ blockers.extend(
81
+ f"{item.get('rule_id') or item.get('id')}: {item.get('message') or 'Gate blocked'}"
82
+ for item in result.blockers
83
+ )
84
+ return blockers
85
+
86
+
87
+ def _print_blockers(blockers: list[str]) -> None:
88
+ if not blockers:
89
+ print("Blockers: none")
90
+ return
91
+ print("Blockers:")
92
+ for blocker in blockers:
93
+ print(f" - {blocker}")
94
+
95
+
96
+ def _implementation(task_path: str) -> None:
97
+ print("Next action: execute implementation plan")
98
+ print(f"TDD reminder: for behavior changes, write a failing test and record red evidence in {task_path}/tdd.jsonl before modifying code.")
99
+ print(
100
+ f"Command: ./.cowork-flow/run subagent init --role implement "
101
+ f"--agent-type cowork-implement --execution-task-dir {task_path} "
102
+ f"--title \"Implement {Path(task_path).name}\""
103
+ )
104
+ print("Then: pass cowork_runtime_context_id and cowork_host_context_key through the active Host Adapter")
105
+ print("Then: child first step runs ./.cowork-flow/run subagent bind <runtime_context_id> <host_context_key>")
106
+ print("Then: verify status=bound and bound_context_key before accepting output")
107
+ print(f"Then: wait, verify output, close runtime context, then ./.cowork-flow/run task review {task_path}")
108
+
109
+
110
+ def _main_operations(
111
+ status: str,
112
+ blockers: tuple[str, ...],
113
+ active_target: bool,
114
+ ) -> list[str]:
115
+ operations = ["answer_questions", "debug_failure", "discuss_options"]
116
+ if status == "no_task":
117
+ operations.extend(["create_task", "edit_planning_artifacts"])
118
+ elif status == "planning":
119
+ operations.append("edit_planning_artifacts")
120
+ if not blockers:
121
+ if active_target:
122
+ operations.extend(["implement_change", "request_review"])
123
+ else:
124
+ operations.append("start_task")
125
+ operations.append("batch_execute")
126
+ elif status == "in_progress":
127
+ operations.extend(
128
+ ["implement_change", "request_review", "batch_execute"]
129
+ )
130
+ elif status in CHECK_STATUSES:
131
+ operations.extend(
132
+ ["verify_change", "apply_review_fix", "complete_task"]
133
+ )
134
+ elif status in DONE_STATUSES:
135
+ operations.extend(["archive_task", "create_task"])
136
+ elif status == "delegated_subtask":
137
+ operations.extend(["execute_delegated_work", "report_result"])
138
+ else:
139
+ operations.append("repair_workflow_state")
140
+ return operations
141
+
142
+
143
+ def _allowed_operations(
144
+ status: str,
145
+ context: str,
146
+ blockers: tuple[str, ...],
147
+ active_target: bool,
148
+ ) -> list[str]:
149
+ if context == "delegated" and status != "delegated_subtask":
150
+ return [
151
+ "answer_questions",
152
+ "debug_failure",
153
+ "discuss_options",
154
+ "report_needs_context",
155
+ ]
156
+ return _main_operations(status, blockers, active_target)
157
+
158
+
159
+ def _required_artifacts(status: str) -> list[str]:
160
+ if status in {"no_task", "planning"}:
161
+ return ["decision-anchor.md", "implement.jsonl"]
162
+ if status == "in_progress":
163
+ return ["decision-anchor.md", "implement.jsonl"]
164
+ if status in CHECK_STATUSES:
165
+ return ["decision-anchor.md", "check.jsonl"]
166
+ if status in DONE_STATUSES:
167
+ return ["check.jsonl"]
168
+ if status == "delegated_subtask":
169
+ return ["runtime-context"]
170
+ return []
171
+
172
+
173
+ def _intent_is_allowed(intent: str, operations: list[str]) -> bool:
174
+ return bool(INTENT_OPERATIONS[intent].intersection(operations))
175
+
176
+
177
+ def _matching_public_skills(
178
+ registry: SkillRegistry,
179
+ status: str,
180
+ intent: str,
181
+ ) -> list[str]:
182
+ registry_intent = INTENT_REGISTRY_KEYS[intent]
183
+ if registry_intent is None:
184
+ return []
185
+ return [
186
+ entry.id
187
+ for entry in registry.public_entries
188
+ if status in entry.statuses and registry_intent in entry.intents
189
+ ]
190
+
191
+
192
+ def route_request(
193
+ registry: SkillRegistry,
194
+ *,
195
+ status: str,
196
+ intent: str,
197
+ context: str,
198
+ blockers: tuple[str, ...] | list[str],
199
+ active_target: bool,
200
+ ) -> dict[str, object]:
201
+ """Return the stable state, intent, and execution-context route."""
202
+ if intent not in USER_INTENTS:
203
+ raise ValueError(f"unsupported workflow intent: {intent}")
204
+ if context not in {"main", "delegated"}:
205
+ raise ValueError(f"unsupported workflow context: {context}")
206
+
207
+ route_blockers = [str(blocker) for blocker in blockers]
208
+ blocker_tuple = tuple(route_blockers)
209
+ operations = _allowed_operations(
210
+ status,
211
+ context,
212
+ blocker_tuple,
213
+ active_target,
214
+ )
215
+ intent_allowed = _intent_is_allowed(intent, operations)
216
+ matches = (
217
+ _matching_public_skills(registry, status, intent)
218
+ if intent_allowed
219
+ else []
220
+ )
221
+
222
+ if context == "delegated" and status != "delegated_subtask":
223
+ route_blockers.append(
224
+ "delegated context cannot operate main-session workflow state"
225
+ )
226
+ if not intent_allowed:
227
+ route_blockers.append(
228
+ f"intent {intent} is not allowed while status is {status}"
229
+ )
230
+ if len(matches) > 1:
231
+ route_blockers.append(
232
+ "multiple active public Skills match the same routing cell"
233
+ )
234
+ if intent == "batch" and not matches:
235
+ route_blockers.append("batch-mode is disabled")
236
+
237
+ protocols: list[str] = []
238
+ if intent_allowed and intent == "implement" and status in {
239
+ "in_progress",
240
+ "delegated_subtask",
241
+ }:
242
+ protocols.append("tdd")
243
+ elif intent_allowed and intent == "review" and status in CHECK_STATUSES:
244
+ protocols.append("check")
245
+
246
+ return {
247
+ "status": status,
248
+ "allowedOperations": operations,
249
+ "requiredArtifacts": _required_artifacts(status),
250
+ "recommendedSkill": matches[0] if len(matches) == 1 else None,
251
+ "internalProtocols": protocols,
252
+ "blockers": route_blockers,
253
+ }
254
+
255
+
256
+ def _default_intent(
257
+ status: str,
258
+ blockers: list[str],
259
+ active_target: bool,
260
+ ) -> str:
261
+ if status == "no_task":
262
+ return "clarify"
263
+ if status == "planning":
264
+ return "plan" if blockers else "implement"
265
+ if status == "in_progress":
266
+ return "implement"
267
+ if status in CHECK_STATUSES:
268
+ return "review"
269
+ if status == "delegated_subtask":
270
+ return "implement"
271
+ if active_target:
272
+ return "implement"
273
+ return "question"
274
+
275
+
276
+ def _routing_context(args) -> str:
277
+ execution_context = execution_context_from_namespace(args)
278
+ if execution_context.is_worker or execution_context.is_subagent:
279
+ return "delegated"
280
+ return "main"
281
+
282
+
283
+ def _load_navigation_registry() -> SkillRegistry:
284
+ runtime_root = Path(__file__).resolve().parents[3]
285
+ return load_skill_registry(runtime_root, validate_sources=False)
286
+
287
+
288
+ def _print_json_route(
289
+ *,
290
+ args,
291
+ status: str,
292
+ blockers: list[str],
293
+ active_target: bool,
294
+ ) -> None:
295
+ intent = getattr(args, "intent", None) or _default_intent(
296
+ status,
297
+ blockers,
298
+ active_target,
299
+ )
300
+ payload = route_request(
301
+ _load_navigation_registry(),
302
+ status=status,
303
+ intent=intent,
304
+ context=_routing_context(args),
305
+ blockers=blockers,
306
+ active_target=active_target,
307
+ )
308
+ print(json.dumps(payload, ensure_ascii=False, sort_keys=False))
309
+
310
+
311
+ def cmd_next(args) -> int:
312
+ repo_root = get_repo_root()
313
+ target = getattr(args, "dir", None)
314
+ active_target = False
315
+ structured = bool(getattr(args, "json", False))
316
+ if not structured:
317
+ print("Workflow Next")
318
+ if target:
319
+ task_dir = resolve_task_dir(target, repo_root)
320
+ task_path = _display(repo_root, task_dir)
321
+ source = "argument"
322
+ else:
323
+ active = get_active_task(repo_root)
324
+ source = f"{active.source}:{active.context_key or '-'}"
325
+ if not active.task_path:
326
+ if structured:
327
+ _print_json_route(
328
+ args=args,
329
+ status="no_task",
330
+ blockers=[],
331
+ active_target=False,
332
+ )
333
+ return 0
334
+ print("Status: no_task")
335
+ print(f"Source: {source}")
336
+ print("Next action: create or start a task before repository changes")
337
+ print('Command: ./.cowork-flow/run task create "<title>" --slug <task-name>')
338
+ print("Then: ./.cowork-flow/run task start <task-dir>")
339
+ print("Runtime-context subagent state is injected by hook/plugin; do not infer it from prompt text.")
340
+ return 0
341
+ task_path = active.task_path
342
+ task_dir = repo_root / task_path
343
+ active_target = True
344
+
345
+ if not structured:
346
+ print(f"Task: {task_path}")
347
+ if not task_dir.is_dir():
348
+ if structured:
349
+ _print_json_route(
350
+ args=args,
351
+ status="stale",
352
+ blockers=[f"task directory not found: {task_path}"],
353
+ active_target=active_target,
354
+ )
355
+ return 0
356
+ print("Status: stale")
357
+ print(f"Source: {source}")
358
+ print("Next action: clear or replace the missing active task")
359
+ print("Command: ./.cowork-flow/run task list")
360
+ print("Blockers:")
361
+ print(f" - task directory not found: {task_path}")
362
+ return 0
363
+
364
+ status = _status(repo_root, task_dir)
365
+ blockers = _blockers(repo_root, task_dir)
366
+ if structured:
367
+ _print_json_route(
368
+ args=args,
369
+ status=status,
370
+ blockers=blockers,
371
+ active_target=active_target,
372
+ )
373
+ return 0
374
+
375
+ print(f"Status: {status}")
376
+ print(f"Source: {source}")
377
+ if status == "planning":
378
+ if blockers:
379
+ print("Next action: finish planning prerequisites before starting task")
380
+ print(f"Command: ./.cowork-flow/run task init-context {task_path} <dev_type>")
381
+ print(f"Then: ./.cowork-flow/run task start {task_path}")
382
+ elif active_target:
383
+ _implementation(task_path)
384
+ else:
385
+ print("Next action: start task")
386
+ print(f"Command: ./.cowork-flow/run task start {task_path}")
387
+ elif status == "in_progress":
388
+ _implementation(task_path)
389
+ elif status in CHECK_STATUSES:
390
+ print("Next action: verify implementation")
391
+ print(f"Command: ./.cowork-flow/run subagent init --role check --agent-type cowork-check --execution-task-dir {task_path} --title \"Check {Path(task_path).name}\"")
392
+ print("Then: pass cowork_runtime_context_id and cowork_host_context_key through the active Host Adapter or run equivalent inline check")
393
+ print("Then: child first step runs ./.cowork-flow/run subagent bind <runtime_context_id> <host_context_key>")
394
+ print("Then: verify status=bound and bound_context_key before accepting output")
395
+ print(f"Then: ./.cowork-flow/run task complete {task_path}")
396
+ elif status in DONE_STATUSES:
397
+ print("Next action: finalize, archive, commit, and record session")
398
+ print("Command: git status --short")
399
+ changes = linked_active_changes_for_task(repo_root, task_dir)
400
+ print(f"Then: ./.cowork-flow/run task archive {Path(task_path).name}")
401
+ for slug in changes:
402
+ print(f"Then: ./.cowork-flow/run change archive {slug} (handled by task archive)")
403
+ else:
404
+ print("Next action: inspect task status and repair workflow state")
405
+ print(f"Command: ./.cowork-flow/run task validate {task_path}")
406
+ _print_blockers(blockers)
407
+ return 0