cowork-flow 0.0.45 → 0.0.47
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/README.md +11 -3
- package/package.json +1 -1
- package/src/cli.js +1 -1
- package/src/commands/install-zcode-plugin.js +165 -21
- package/template/.cowork-flow/.version +1 -1
- package/template/.cowork-flow/scripts/adapters/cli/subagent.py +3 -0
- package/template/.cowork-flow/scripts/adapters/cli/task.py +24 -185
- package/template/.cowork-flow/scripts/adapters/cli/task_navigation.py +70 -1
- package/template/.cowork-flow/scripts/adapters/cli/task_next_runner.py +229 -0
- package/template/.cowork-flow/scripts/adapters/cli/task_parser.py +1 -1
- package/template/.cowork-flow/scripts/adapters/cli/task_tree_commands.py +68 -0
- package/template/.cowork-flow/scripts/infra/skill_manifest.py +25 -2
- package/template/.cowork-flow/scripts/runtime/session_state.py +2 -0
- package/template/.cowork-flow/scripts/services/task_routing.py +11 -0
- package/template/.cowork-flow/spec/contracts/skill-owned-actions.md +14 -5
- package/template/.zcode/.zcode-plugin/plugin.json +3 -1
- package/template/.zcode/agents/cowork-check.md +27 -0
- package/template/.zcode/agents/cowork-implement.md +28 -0
- package/template/.zcode/agents/cowork-research.md +21 -0
- package/template/.zcode/hooks/hooks.json +12 -6
- package/template/.zcode/hooks/inject-context.js +40 -13
- package/template/.zcode/hooks/inject-context.selfcheck.mjs +8 -2
- package/template/skills/adversarial-review/SKILL.md +17 -0
- package/template/skills/runtime-health/SKILL.md +6 -1
- package/template/skills/runtime-health/scripts/doctor.py +174 -8
- package/template/skills/task-review/SKILL.md +5 -0
- package/template/skills/task-review/manifest.json +4 -2
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Delivery runner for deterministic `task next --run` actions."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import sys
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Callable
|
|
11
|
+
|
|
12
|
+
from adapters.cli import task_navigation
|
|
13
|
+
from adapters.cli.task_support import Colors, colored, resolve_task_dir
|
|
14
|
+
from infra.paths import get_repo_root
|
|
15
|
+
from runtime.session_state import get_active_task
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
LifecycleCommand = Callable[[argparse.Namespace], int]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass(frozen=True)
|
|
22
|
+
class NextActionHandlers:
|
|
23
|
+
create: LifecycleCommand
|
|
24
|
+
start: LifecycleCommand
|
|
25
|
+
review: LifecycleCommand
|
|
26
|
+
complete: LifecycleCommand
|
|
27
|
+
archive: LifecycleCommand
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def namespace_with(args: argparse.Namespace, **overrides) -> argparse.Namespace:
|
|
31
|
+
values = vars(args).copy()
|
|
32
|
+
values.update(overrides)
|
|
33
|
+
return argparse.Namespace(**values)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _display_task_path(repo_root: Path, task_dir: Path) -> str:
|
|
37
|
+
try:
|
|
38
|
+
return task_dir.resolve().relative_to(repo_root.resolve()).as_posix()
|
|
39
|
+
except ValueError:
|
|
40
|
+
return str(task_dir)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _next_target_for_run(args: argparse.Namespace, repo_root: Path):
|
|
44
|
+
target_input = getattr(args, "dir", None)
|
|
45
|
+
if target_input:
|
|
46
|
+
task_dir = resolve_task_dir(target_input, repo_root)
|
|
47
|
+
return task_dir, _display_task_path(repo_root, task_dir), False
|
|
48
|
+
|
|
49
|
+
active = get_active_task(repo_root)
|
|
50
|
+
if active.task_path:
|
|
51
|
+
task_dir = repo_root / active.task_path
|
|
52
|
+
return task_dir, active.task_path, True
|
|
53
|
+
return None, None, False
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _next_payload_for_run(args: argparse.Namespace, repo_root: Path) -> dict[str, object]:
|
|
57
|
+
task_dir, task_path, active_target = _next_target_for_run(args, repo_root)
|
|
58
|
+
if task_dir is None or task_path is None:
|
|
59
|
+
return task_navigation.build_navigation_payload(
|
|
60
|
+
args=args,
|
|
61
|
+
status="no_task",
|
|
62
|
+
blockers=[],
|
|
63
|
+
active_target=False,
|
|
64
|
+
task_path=None,
|
|
65
|
+
)
|
|
66
|
+
if not task_dir.is_dir():
|
|
67
|
+
return task_navigation.build_navigation_payload(
|
|
68
|
+
args=args,
|
|
69
|
+
status="stale",
|
|
70
|
+
blockers=[f"task directory not found: {task_path}"],
|
|
71
|
+
active_target=active_target,
|
|
72
|
+
task_path=task_path,
|
|
73
|
+
)
|
|
74
|
+
status = task_navigation._status(repo_root, task_dir)
|
|
75
|
+
blockers = task_navigation._blockers(repo_root, task_dir) if status == "planning" else []
|
|
76
|
+
return task_navigation.build_navigation_payload(
|
|
77
|
+
args=args,
|
|
78
|
+
status=status,
|
|
79
|
+
blockers=blockers,
|
|
80
|
+
active_target=active_target,
|
|
81
|
+
task_path=task_path,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _run_create_action(
|
|
86
|
+
args: argparse.Namespace,
|
|
87
|
+
action: dict[str, object],
|
|
88
|
+
handlers: NextActionHandlers,
|
|
89
|
+
) -> int:
|
|
90
|
+
if not getattr(args, "title", None):
|
|
91
|
+
print(
|
|
92
|
+
colored("Error: create_task requires --title", Colors.RED),
|
|
93
|
+
file=sys.stderr,
|
|
94
|
+
)
|
|
95
|
+
print(f"Command: {action.get('command')}", file=sys.stderr)
|
|
96
|
+
return 1
|
|
97
|
+
return handlers.create(
|
|
98
|
+
namespace_with(
|
|
99
|
+
args,
|
|
100
|
+
title=args.title,
|
|
101
|
+
slug=getattr(args, "slug", None),
|
|
102
|
+
assignee=getattr(args, "assignee", None),
|
|
103
|
+
priority=getattr(args, "priority", "P2"),
|
|
104
|
+
description=getattr(args, "description", None),
|
|
105
|
+
parent=getattr(args, "parent", None),
|
|
106
|
+
from_plan=getattr(args, "from_plan", None),
|
|
107
|
+
)
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _create_input_names(args: argparse.Namespace) -> list[str]:
|
|
112
|
+
names = (
|
|
113
|
+
"title",
|
|
114
|
+
"slug",
|
|
115
|
+
"assignee",
|
|
116
|
+
"description",
|
|
117
|
+
"parent",
|
|
118
|
+
"from_plan",
|
|
119
|
+
)
|
|
120
|
+
return [name for name in names if getattr(args, name, None)]
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _validated_next_action(args: argparse.Namespace) -> tuple[dict[str, object] | None, int]:
|
|
124
|
+
payload = _next_payload_for_run(args, get_repo_root())
|
|
125
|
+
action = payload.get("action")
|
|
126
|
+
if not isinstance(action, dict):
|
|
127
|
+
print(
|
|
128
|
+
colored("Error: task next did not return an action", Colors.RED),
|
|
129
|
+
file=sys.stderr,
|
|
130
|
+
)
|
|
131
|
+
return None, 1
|
|
132
|
+
create_inputs = _create_input_names(args)
|
|
133
|
+
action_id = action.get("id")
|
|
134
|
+
if create_inputs and action_id != "create_task":
|
|
135
|
+
print(
|
|
136
|
+
colored(
|
|
137
|
+
f"Error: create_task inputs cannot run {action_id}",
|
|
138
|
+
Colors.RED,
|
|
139
|
+
),
|
|
140
|
+
file=sys.stderr,
|
|
141
|
+
)
|
|
142
|
+
print(
|
|
143
|
+
"Hint: archive or finish the current task first, then rerun "
|
|
144
|
+
"`task next --run --title ... --slug ... --assignee <name>`.",
|
|
145
|
+
file=sys.stderr,
|
|
146
|
+
)
|
|
147
|
+
return None, 1
|
|
148
|
+
if payload.get("blockers") or action.get("blockers"):
|
|
149
|
+
print(colored("Error: next action is blocked", Colors.RED), file=sys.stderr)
|
|
150
|
+
for blocker in payload.get("blockers", []) or action.get("blockers", []):
|
|
151
|
+
print(f" - {blocker}", file=sys.stderr)
|
|
152
|
+
return None, 1
|
|
153
|
+
if not action.get("runnable"):
|
|
154
|
+
action_id = action.get("id")
|
|
155
|
+
print(
|
|
156
|
+
colored(f"Error: next action is not executable: {action_id}", Colors.RED),
|
|
157
|
+
file=sys.stderr,
|
|
158
|
+
)
|
|
159
|
+
if action.get("command"):
|
|
160
|
+
print(f"Command: {action['command']}", file=sys.stderr)
|
|
161
|
+
return None, 1
|
|
162
|
+
return action, 0
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _resolved_next_action_task(args: argparse.Namespace, action_id: object) -> str | None:
|
|
166
|
+
if action_id == "create_task":
|
|
167
|
+
return None
|
|
168
|
+
_, task_path_value, _ = _next_target_for_run(args, get_repo_root())
|
|
169
|
+
if task_path_value is None:
|
|
170
|
+
print(colored("Error: no task target for action", Colors.RED), file=sys.stderr)
|
|
171
|
+
return None
|
|
172
|
+
return task_path_value
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _dispatch_next_lifecycle_action(
|
|
176
|
+
args: argparse.Namespace,
|
|
177
|
+
action_id: object,
|
|
178
|
+
task_path: str | None,
|
|
179
|
+
action: dict[str, object],
|
|
180
|
+
handlers: NextActionHandlers,
|
|
181
|
+
) -> int:
|
|
182
|
+
if action_id == "create_task":
|
|
183
|
+
return _run_create_action(args, action, handlers)
|
|
184
|
+
if task_path is None:
|
|
185
|
+
return 1
|
|
186
|
+
if action_id == "start_task":
|
|
187
|
+
return handlers.start(
|
|
188
|
+
namespace_with(
|
|
189
|
+
args,
|
|
190
|
+
dir=task_path,
|
|
191
|
+
auto=bool(getattr(args, "auto", False)),
|
|
192
|
+
approved=bool(getattr(args, "approved", False)),
|
|
193
|
+
)
|
|
194
|
+
)
|
|
195
|
+
if action_id == "request_review":
|
|
196
|
+
return handlers.review(namespace_with(args, dir=task_path))
|
|
197
|
+
if action_id == "complete_task":
|
|
198
|
+
return handlers.complete(namespace_with(args, dir=task_path))
|
|
199
|
+
if action_id == "archive_task":
|
|
200
|
+
return handlers.archive(
|
|
201
|
+
namespace_with(
|
|
202
|
+
args,
|
|
203
|
+
name=Path(str(task_path)).name,
|
|
204
|
+
commit=bool(getattr(args, "commit", False)),
|
|
205
|
+
)
|
|
206
|
+
)
|
|
207
|
+
if action_id == "batch_execute":
|
|
208
|
+
return handlers.start(
|
|
209
|
+
namespace_with(
|
|
210
|
+
args,
|
|
211
|
+
dir=task_path,
|
|
212
|
+
auto=True,
|
|
213
|
+
approved=bool(getattr(args, "approved", False)),
|
|
214
|
+
)
|
|
215
|
+
)
|
|
216
|
+
print(
|
|
217
|
+
colored(f"Error: unsupported next action: {action_id}", Colors.RED),
|
|
218
|
+
file=sys.stderr,
|
|
219
|
+
)
|
|
220
|
+
return 1
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def run_next_action(args: argparse.Namespace, handlers: NextActionHandlers) -> int:
|
|
224
|
+
action, error_code = _validated_next_action(args)
|
|
225
|
+
if action is None:
|
|
226
|
+
return error_code
|
|
227
|
+
action_id = action.get("id")
|
|
228
|
+
task_path = _resolved_next_action_task(args, action_id)
|
|
229
|
+
return _dispatch_next_lifecycle_action(args, action_id, task_path, action, handlers)
|
|
@@ -14,7 +14,7 @@ USAGE_TEXT = """Task workflow router for cowork-flow
|
|
|
14
14
|
Usage:
|
|
15
15
|
./.cowork-flow/run task next [dir] [--json] [--intent I]
|
|
16
16
|
./.cowork-flow/run task next [dir] --validate
|
|
17
|
-
./.cowork-flow/run task next --list [--mine] [--status S]
|
|
17
|
+
./.cowork-flow/run task next --list [--json] [--mine] [--status S]
|
|
18
18
|
./.cowork-flow/run task next [dir] --run [--intent I]
|
|
19
19
|
|
|
20
20
|
Public contract:
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
from __future__ import annotations
|
|
5
5
|
|
|
6
|
+
import json
|
|
6
7
|
import sys
|
|
7
8
|
|
|
8
9
|
from services.task_tree import TaskTreeError, TaskTreeService
|
|
@@ -140,6 +141,73 @@ def cmd_list(args) -> int:
|
|
|
140
141
|
return 0
|
|
141
142
|
|
|
142
143
|
|
|
144
|
+
def _list_task_records(repo_root, *, mine: bool, status: str | None):
|
|
145
|
+
active_task = get_active_task(repo_root).task_path
|
|
146
|
+
developer = get_developer(repo_root)
|
|
147
|
+
if mine and not developer:
|
|
148
|
+
return None, "No developer set. Run init_developer.py first"
|
|
149
|
+
|
|
150
|
+
service = TaskTreeService(repo_root)
|
|
151
|
+
all_tasks = service.active_nodes()
|
|
152
|
+
records: list[dict[str, object]] = []
|
|
153
|
+
|
|
154
|
+
def append_task(dir_name: str, depth: int = 0) -> None:
|
|
155
|
+
info = all_tasks[dir_name]
|
|
156
|
+
if mine and info.assignee != developer:
|
|
157
|
+
return
|
|
158
|
+
if status and info.status != status:
|
|
159
|
+
return
|
|
160
|
+
relative_path = f"{DIR_WORKFLOW}/{DIR_TASKS}/{dir_name}"
|
|
161
|
+
done, total = service.children_progress(info.children, all_tasks)
|
|
162
|
+
records.append(
|
|
163
|
+
{
|
|
164
|
+
"name": dir_name,
|
|
165
|
+
"path": relative_path,
|
|
166
|
+
"status": info.status,
|
|
167
|
+
"assignee": info.assignee,
|
|
168
|
+
"parent": info.parent,
|
|
169
|
+
"children": list(info.children),
|
|
170
|
+
"childrenDone": done,
|
|
171
|
+
"childrenTotal": total,
|
|
172
|
+
"depth": depth,
|
|
173
|
+
"active": relative_path == active_task,
|
|
174
|
+
}
|
|
175
|
+
)
|
|
176
|
+
for child_name in info.children:
|
|
177
|
+
if child_name in all_tasks:
|
|
178
|
+
append_task(child_name, depth + 1)
|
|
179
|
+
|
|
180
|
+
for dir_name in service.root_names(all_tasks):
|
|
181
|
+
append_task(dir_name)
|
|
182
|
+
return records, None
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def cmd_list_json(args) -> int:
|
|
186
|
+
repo_root = get_repo_root()
|
|
187
|
+
records, error = _list_task_records(
|
|
188
|
+
repo_root,
|
|
189
|
+
mine=bool(getattr(args, "mine", False)),
|
|
190
|
+
status=getattr(args, "status", None),
|
|
191
|
+
)
|
|
192
|
+
if error:
|
|
193
|
+
print(colored(f"Error: {error}", Colors.RED), file=sys.stderr)
|
|
194
|
+
return 1
|
|
195
|
+
print(
|
|
196
|
+
json.dumps(
|
|
197
|
+
{
|
|
198
|
+
"tasks": records or [],
|
|
199
|
+
"count": len(records or []),
|
|
200
|
+
"filters": {
|
|
201
|
+
"mine": bool(getattr(args, "mine", False)),
|
|
202
|
+
"status": getattr(args, "status", None),
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
ensure_ascii=False,
|
|
206
|
+
)
|
|
207
|
+
)
|
|
208
|
+
return 0
|
|
209
|
+
|
|
210
|
+
|
|
143
211
|
def cmd_list_archive(args) -> int:
|
|
144
212
|
repo_root = get_repo_root()
|
|
145
213
|
month = args.month
|
|
@@ -27,6 +27,7 @@ class SkillAction:
|
|
|
27
27
|
lifecycle_check: str | None
|
|
28
28
|
mutates_state: bool
|
|
29
29
|
command: str | None
|
|
30
|
+
diagnostics_command: str | None
|
|
30
31
|
|
|
31
32
|
|
|
32
33
|
@dataclass(frozen=True)
|
|
@@ -142,7 +143,14 @@ def _parse_manifest(path: Path, raw: dict[str, Any]) -> SkillManifest:
|
|
|
142
143
|
_reject_unknown_fields(
|
|
143
144
|
item,
|
|
144
145
|
allowed=frozenset(
|
|
145
|
-
{
|
|
146
|
+
{
|
|
147
|
+
"id",
|
|
148
|
+
"label",
|
|
149
|
+
"lifecycleCheck",
|
|
150
|
+
"mutatesState",
|
|
151
|
+
"command",
|
|
152
|
+
"diagnosticsCommand",
|
|
153
|
+
}
|
|
146
154
|
),
|
|
147
155
|
scope="manifest action",
|
|
148
156
|
path=path,
|
|
@@ -162,6 +170,9 @@ def _parse_manifest(path: Path, raw: dict[str, Any]) -> SkillManifest:
|
|
|
162
170
|
command = item.get("command")
|
|
163
171
|
if command is not None and not isinstance(command, str):
|
|
164
172
|
raise SkillManifestError(f"manifest command is invalid: {path}")
|
|
173
|
+
diagnostics_command = item.get("diagnosticsCommand")
|
|
174
|
+
if diagnostics_command is not None and not isinstance(diagnostics_command, str):
|
|
175
|
+
raise SkillManifestError(f"manifest diagnosticsCommand is invalid: {path}")
|
|
165
176
|
actions.append(
|
|
166
177
|
SkillAction(
|
|
167
178
|
skill=skill,
|
|
@@ -170,6 +181,11 @@ def _parse_manifest(path: Path, raw: dict[str, Any]) -> SkillManifest:
|
|
|
170
181
|
lifecycle_check=lifecycle_check.strip() if isinstance(lifecycle_check, str) else None,
|
|
171
182
|
mutates_state=mutates_state,
|
|
172
183
|
command=command.strip() if isinstance(command, str) else None,
|
|
184
|
+
diagnostics_command=(
|
|
185
|
+
diagnostics_command.strip()
|
|
186
|
+
if isinstance(diagnostics_command, str)
|
|
187
|
+
else None
|
|
188
|
+
),
|
|
173
189
|
)
|
|
174
190
|
)
|
|
175
191
|
|
|
@@ -333,7 +349,14 @@ def load_skill_manifests(repo_root: Path) -> tuple[SkillManifest, ...]:
|
|
|
333
349
|
def _manifest_signature(manifest: SkillManifest) -> tuple[object, ...]:
|
|
334
350
|
return (
|
|
335
351
|
tuple(
|
|
336
|
-
(
|
|
352
|
+
(
|
|
353
|
+
item.action_id,
|
|
354
|
+
item.label,
|
|
355
|
+
item.lifecycle_check,
|
|
356
|
+
item.mutates_state,
|
|
357
|
+
item.command,
|
|
358
|
+
item.diagnostics_command,
|
|
359
|
+
)
|
|
337
360
|
for item in manifest.actions
|
|
338
361
|
),
|
|
339
362
|
tuple(
|
|
@@ -69,6 +69,7 @@ def _resolve_env_context_key() -> str | None:
|
|
|
69
69
|
return _sanitize(explicit)
|
|
70
70
|
|
|
71
71
|
for prefix, env_name in (
|
|
72
|
+
("zcode", "ZCODE_SESSION_ID"),
|
|
72
73
|
("opencode", "OPENCODE_SESSION_ID"),
|
|
73
74
|
("claude", "CLAUDE_SESSION_ID"),
|
|
74
75
|
("claude", "CLAUDE_CODE_SESSION_ID"),
|
|
@@ -90,6 +91,7 @@ def _resolve_input_context_key(values: Mapping[str, object] | None) -> str | Non
|
|
|
90
91
|
return _sanitize(explicit)
|
|
91
92
|
|
|
92
93
|
for prefix, names in (
|
|
94
|
+
("zcode", ("ZCODE_SESSION_ID", "zcode_session_id")),
|
|
93
95
|
("opencode", ("OPENCODE_SESSION_ID", "opencode_session_id", "sessionID", "sessionId")),
|
|
94
96
|
("claude", ("CLAUDE_SESSION_ID", "claude_session_id", "CLAUDE_CODE_SESSION_ID", "claude_code_session_id")),
|
|
95
97
|
("codex", ("CODEX_SESSION_ID", "codex_session_id", "session_id")),
|
|
@@ -47,6 +47,12 @@ def _action_command(action_id: str, task_path: str | None, template: str | None)
|
|
|
47
47
|
return None
|
|
48
48
|
|
|
49
49
|
|
|
50
|
+
def _diagnostics_command(task_path: str | None, template: str | None) -> str | None:
|
|
51
|
+
if template:
|
|
52
|
+
return template.replace("<task-dir>", task_path or "<task-dir>")
|
|
53
|
+
return None
|
|
54
|
+
|
|
55
|
+
|
|
50
56
|
def _action_contract(
|
|
51
57
|
*,
|
|
52
58
|
status: str,
|
|
@@ -90,6 +96,10 @@ def _action_contract(
|
|
|
90
96
|
"label": owner.label if owner is not None else action_id,
|
|
91
97
|
"activatedSkill": owner.skill if owner is not None else None,
|
|
92
98
|
"command": _action_command(action_id, task_path, owner.command if owner else None),
|
|
99
|
+
"diagnosticsCommand": _diagnostics_command(
|
|
100
|
+
task_path,
|
|
101
|
+
owner.diagnostics_command if owner else None,
|
|
102
|
+
),
|
|
93
103
|
"mutatesState": action["mutatesState"],
|
|
94
104
|
"lifecycleCheck": lifecycle_check,
|
|
95
105
|
"runtimeGate": lifecycle_check,
|
|
@@ -141,6 +151,7 @@ def route_request(
|
|
|
141
151
|
"nextAction": action["id"],
|
|
142
152
|
"activatedSkill": action["activatedSkill"],
|
|
143
153
|
"actionCommand": action["command"],
|
|
154
|
+
"diagnosticsCommand": action["diagnosticsCommand"],
|
|
144
155
|
"mutatesState": action["mutatesState"],
|
|
145
156
|
"lifecycleCheck": action["lifecycleCheck"],
|
|
146
157
|
"runtimeGate": action["runtimeGate"],
|
|
@@ -7,7 +7,7 @@ not contain Skill ids, prompt wording, CLI commands, or script paths.
|
|
|
7
7
|
Each Skill may declare its own `manifest.json` with:
|
|
8
8
|
|
|
9
9
|
- `actions`: action id, display label, lifecycle check, mutation fact, and an
|
|
10
|
-
optional command description
|
|
10
|
+
optional command description plus optional `diagnosticsCommand`;
|
|
11
11
|
- `context`: implement/check/debug activation rules by dev type or path;
|
|
12
12
|
- `commands`: Skill-owned runtime entrypoints.
|
|
13
13
|
|
|
@@ -28,7 +28,9 @@ resume and result recording are also Skill commands rather than task CLI
|
|
|
28
28
|
handlers.
|
|
29
29
|
|
|
30
30
|
Review-oriented Skill commands may provide advisory facts, such as changed-file
|
|
31
|
-
coverage, applicable spec sources, and test-intent signals.
|
|
31
|
+
coverage, applicable spec sources, and test-intent signals. An action-level
|
|
32
|
+
`diagnosticsCommand` may expose these helpers in text and JSON navigation, but
|
|
33
|
+
it does not make the helper runnable as a lifecycle action. They must remain
|
|
32
34
|
read-only helpers: no task-local review evidence file, lifecycle state mutation,
|
|
33
35
|
natural-language spec hard gate, or pass/fail completion verdict.
|
|
34
36
|
|
|
@@ -41,6 +43,13 @@ Skill-id priority tables.
|
|
|
41
43
|
|
|
42
44
|
Runtime Health has two scopes. An installed project validates its installed
|
|
43
45
|
runtime, detected host platforms, and installed Skill manifests without
|
|
44
|
-
requiring a `template/` directory. A cowork-flow source checkout
|
|
45
|
-
|
|
46
|
-
|
|
46
|
+
requiring a `template/` directory. A cowork-flow source checkout distinguishes
|
|
47
|
+
tracked source checkout bootstrap files, ignored local live runtime, and the
|
|
48
|
+
template distribution source. Source checkout health must not force-track the whole `.cowork-flow/` tree: tracked bootstrap files must match the template,
|
|
49
|
+
ignored local live runtime is compared only when present, and Skill replica
|
|
50
|
+
parity is checked for every detected installed Skill target.
|
|
51
|
+
|
|
52
|
+
Runtime Health also reports stale task hygiene read-only. It may warn about
|
|
53
|
+
completed-unarchived tasks, active-unbound tasks, and missing task context, and
|
|
54
|
+
it may include command hints. These warnings must not mutate task lifecycle
|
|
55
|
+
state, write evidence files, or become completion gates.
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cowork-flow",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.47",
|
|
4
4
|
"description": "AI 协作工作流引擎:任务生命周期、子代理调度、门禁与状态注入。将 cowork-flow 的完整工作流带入 ZCode。",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "fisklikewater"
|
|
7
7
|
},
|
|
8
8
|
"license": "MIT",
|
|
9
|
+
"hooks": "hooks/hooks.json",
|
|
10
|
+
"agents": "agents",
|
|
9
11
|
"skills": "skills"
|
|
10
12
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cowork-check
|
|
3
|
+
description: Cowork-flow check fixed subagent for independent implementation review with runtime context binding.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
You are the `cowork-check` fixed subagent for ZCode.
|
|
7
|
+
|
|
8
|
+
Read and apply these plugin Skills before review work:
|
|
9
|
+
|
|
10
|
+
- `skills/agent-dispatch/SKILL.md`
|
|
11
|
+
- `skills/task-review/SKILL.md`
|
|
12
|
+
- `skills/decision-audit/SKILL.md`
|
|
13
|
+
- `skills/spec-sync/SKILL.md`
|
|
14
|
+
|
|
15
|
+
Execution:
|
|
16
|
+
|
|
17
|
+
1. Follow `agent-dispatch` to bind runtime context. If binding fails, report `needs_context` and stop.
|
|
18
|
+
2. Read the task directory, `decision-anchor.md`, linked plan, the `check.jsonl` context index, every referenced `file` entry, and current `git diff`; do not write review conclusions to JSONL.
|
|
19
|
+
3. Apply `task-review` to verify scope, tests, specs, advisory facts, lifecycle facts, and Definition of Done coverage.
|
|
20
|
+
4. Fix only clearly in-scope issues; otherwise report findings with acceptance IDs, resolutions, `test_intent_review` (test intent review), and verification commands.
|
|
21
|
+
|
|
22
|
+
Rules:
|
|
23
|
+
|
|
24
|
+
- Do not invoke subagents.
|
|
25
|
+
- Do not run task start, finish, archive, unscoped resume, commit, or push.
|
|
26
|
+
- Treat backend/frontend natural-language specs as checklist context, not dynamic hard validators.
|
|
27
|
+
- Do not accept shallow tests that would still pass when target behavior breaks.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cowork-implement
|
|
3
|
+
description: Cowork-flow implementation fixed subagent for bounded code changes with runtime context binding.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
You are the `cowork-implement` fixed subagent for ZCode.
|
|
7
|
+
|
|
8
|
+
Read and apply these plugin Skills before task work:
|
|
9
|
+
|
|
10
|
+
- `skills/agent-dispatch/SKILL.md`
|
|
11
|
+
- `skills/decision-audit/SKILL.md`
|
|
12
|
+
- `skills/spec-sync/SKILL.md`
|
|
13
|
+
- `skills/test-first/SKILL.md` for behavior changes
|
|
14
|
+
|
|
15
|
+
Execution:
|
|
16
|
+
|
|
17
|
+
1. Follow `agent-dispatch` to bind runtime context. If binding fails, report `needs_context` and stop.
|
|
18
|
+
2. Read the task directory, `decision-anchor.md`, optional `info.md`, `implement.jsonl`, every JSONL `file` entry, and the linked plan.
|
|
19
|
+
3. Follow the plan in order, keeping edits inside assigned scope.
|
|
20
|
+
4. Use project specs and quality sources as checklist context; runtime gates remain kernel-owned commands.
|
|
21
|
+
5. Run planned verification and report changed files, acceptance IDs, verification commands, and spec updates or why none were needed.
|
|
22
|
+
|
|
23
|
+
Rules:
|
|
24
|
+
|
|
25
|
+
- Do not invoke subagents.
|
|
26
|
+
- Do not run task start, finish, archive, unscoped resume, commit, or push.
|
|
27
|
+
- Do not create task-local review artifacts, `tdd.jsonl`, or TDD evidence records in `check.jsonl`.
|
|
28
|
+
- Do not claim completion without current verification.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cowork-research
|
|
3
|
+
description: Cowork-flow research-only fixed subagent for sourced investigation with runtime context binding.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
You are the `cowork-research` fixed subagent for ZCode.
|
|
7
|
+
|
|
8
|
+
Read and apply `skills/agent-dispatch/SKILL.md` before research work.
|
|
9
|
+
|
|
10
|
+
Execution:
|
|
11
|
+
|
|
12
|
+
1. Follow `agent-dispatch` to bind runtime context. If binding fails, report `needs_context` and stop.
|
|
13
|
+
2. Read the task directory, assignment, and prompt-named context.
|
|
14
|
+
3. Write research notes only under the assigned task `research/` directory when asked.
|
|
15
|
+
4. Report sourced findings, uncertainty, and recommended next action.
|
|
16
|
+
|
|
17
|
+
Rules:
|
|
18
|
+
|
|
19
|
+
- Do not invoke subagents.
|
|
20
|
+
- Do not edit code, specs, task state, or git state.
|
|
21
|
+
- Do not run task start, finish, archive, unscoped resume, commit, or push.
|
|
@@ -5,9 +5,12 @@
|
|
|
5
5
|
"matcher": "startup|clear|compact",
|
|
6
6
|
"hooks": [
|
|
7
7
|
{
|
|
8
|
-
"type": "
|
|
9
|
-
"command": "node
|
|
10
|
-
"
|
|
8
|
+
"type": "process",
|
|
9
|
+
"command": "node",
|
|
10
|
+
"args": [
|
|
11
|
+
"${ZCODE_PLUGIN_ROOT}/hooks/inject-context.js"
|
|
12
|
+
],
|
|
13
|
+
"timeoutMs": 5000
|
|
11
14
|
}
|
|
12
15
|
]
|
|
13
16
|
}
|
|
@@ -16,9 +19,12 @@
|
|
|
16
19
|
{
|
|
17
20
|
"hooks": [
|
|
18
21
|
{
|
|
19
|
-
"type": "
|
|
20
|
-
"command": "node
|
|
21
|
-
"
|
|
22
|
+
"type": "process",
|
|
23
|
+
"command": "node",
|
|
24
|
+
"args": [
|
|
25
|
+
"${ZCODE_PLUGIN_ROOT}/hooks/inject-context.js"
|
|
26
|
+
],
|
|
27
|
+
"timeoutMs": 5000
|
|
22
28
|
}
|
|
23
29
|
]
|
|
24
30
|
}
|