cowork-flow 0.0.19 → 0.0.21
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 +65 -20
- package/package.json +1 -1
- package/src/cli.js +109 -2
- package/src/commands/init.js +61 -4
- package/src/commands/sync.js +10 -1
- package/src/lib/copy-template.js +38 -7
- package/src/lib/platforms.js +73 -0
- package/template/.agent/skills/brainstorming/SKILL.md +13 -9
- package/template/.agent/skills/finish-work/SKILL.md +3 -3
- package/template/.agent/skills/meta/SKILL.md +1 -1
- package/template/.agent/skills/start/SKILL.md +38 -14
- package/template/.agent/skills/writing-plans/SKILL.md +1 -1
- package/template/.claude/agents/cowork-check.md +41 -0
- package/template/.claude/agents/cowork-implement.md +40 -0
- package/template/.claude/agents/cowork-research.md +40 -0
- package/template/.claude/commands/cowork-check.md +27 -0
- package/template/.claude/commands/cowork-implement.md +27 -0
- package/template/.claude/commands/cowork-research.md +27 -0
- package/template/.claude/hooks/inject-workflow-state.py +298 -0
- package/template/.claude/settings.json +24 -0
- package/template/.claude/skills/before-dev/SKILL.md +31 -0
- package/template/.claude/skills/brainstorming/SKILL.md +32 -0
- package/template/.claude/skills/break-loop/SKILL.md +26 -0
- package/template/.claude/skills/check/SKILL.md +29 -0
- package/template/.claude/skills/continue/SKILL.md +29 -0
- package/template/.claude/skills/finish-work/SKILL.md +32 -0
- package/template/.claude/skills/meta/SKILL.md +30 -0
- package/template/.claude/skills/python-design/SKILL.md +27 -0
- package/template/.claude/skills/start/SKILL.md +78 -0
- package/template/.claude/skills/update-spec/SKILL.md +39 -0
- package/template/.claude/skills/writing-plans/SKILL.md +70 -0
- package/template/.codex/agents/cowork-check.toml +17 -22
- package/template/.codex/agents/cowork-implement.toml +18 -23
- package/template/.codex/agents/cowork-research.toml +14 -19
- package/template/.codex/agents/default.toml +27 -0
- package/template/.codex/agents/explorer.toml +28 -0
- package/template/.codex/agents/worker.toml +29 -0
- package/template/.codex/hooks/inject-workflow-state.py +172 -90
- package/template/.cowork-flow/.version +1 -1
- package/template/.cowork-flow/adapters/claude-code/adapter.yaml +35 -0
- package/template/.cowork-flow/adapters/codex/adapter.yaml +35 -0
- package/template/.cowork-flow/adapters/opencode/adapter.yaml +33 -0
- package/template/.cowork-flow/config.yaml +4 -1
- package/template/.cowork-flow/scripts/add_session.py +20 -7
- package/template/.cowork-flow/scripts/common/active_task.py +212 -1
- package/template/.cowork-flow/scripts/common/entry_classifier.py +122 -0
- package/template/.cowork-flow/scripts/common/execution_context.py +10 -17
- package/template/.cowork-flow/scripts/common/readiness.py +249 -0
- package/template/.cowork-flow/scripts/doctor.py +417 -29
- package/template/.cowork-flow/scripts/project_context.py +230 -0
- package/template/.cowork-flow/scripts/run.py +3 -0
- package/template/.cowork-flow/scripts/subagent.py +152 -238
- package/template/.cowork-flow/scripts/task.py +330 -17
- package/template/.cowork-flow/spec/adapter.schema.json +132 -0
- package/template/.cowork-flow/spec/capabilities.md +34 -0
- package/template/.cowork-flow/spec/entry-contract.md +45 -0
- package/template/.cowork-flow/spec/registry.json +54 -0
- package/template/.cowork-flow/spec/subagent-dispatch.md +77 -0
- package/template/.cowork-flow/spec/workflow-state-templates.md +66 -0
- package/template/.cowork-flow/workflow.md +154 -190
- package/template/.opencode/agents/cowork-check.md +46 -0
- package/template/.opencode/agents/cowork-implement.md +45 -0
- package/template/.opencode/agents/cowork-research.md +46 -0
- package/template/.opencode/commands/cowork-check.md +26 -0
- package/template/.opencode/commands/cowork-implement.md +26 -0
- package/template/.opencode/commands/cowork-research.md +26 -0
- package/template/.opencode/plugins/cowork-flow.js +317 -0
- package/template/CLAUDE.md +5 -0
- package/template/.agent/skills/entry-boundary/SKILL.md +0 -81
|
@@ -8,51 +8,181 @@ import argparse
|
|
|
8
8
|
import sys
|
|
9
9
|
from pathlib import Path
|
|
10
10
|
|
|
11
|
+
try:
|
|
12
|
+
import tomllib
|
|
13
|
+
except ModuleNotFoundError: # pragma: no cover - Python < 3.11
|
|
14
|
+
tomllib = None # type: ignore[assignment]
|
|
15
|
+
|
|
11
16
|
from common.paths import get_repo_root
|
|
12
17
|
|
|
13
18
|
|
|
19
|
+
ENTRY_BOUNDARY_DIR = "entry" + "-boundary"
|
|
20
|
+
|
|
14
21
|
REQUIRED_START_SNIPPETS = [
|
|
15
22
|
"This skill is for the main session",
|
|
16
|
-
"
|
|
23
|
+
"accepted only after runtime context binding is recorded",
|
|
17
24
|
"Main repository changes follow `Plan -> Implement -> Check -> Finish`",
|
|
25
|
+
"Host Adapter",
|
|
26
|
+
".cowork-flow/run subagent init",
|
|
27
|
+
"cowork_runtime_context_id",
|
|
28
|
+
"cowork_host_context_key",
|
|
29
|
+
".cowork-flow/run subagent bind <runtime_context_id> <host_context_key>",
|
|
30
|
+
".cowork-flow/run subagent close",
|
|
18
31
|
]
|
|
19
32
|
|
|
20
|
-
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"If project bootstrap says to create/start/resume",
|
|
28
|
-
"Active task: <task-dir>",
|
|
29
|
-
"Follow the delegated prompt first",
|
|
30
|
-
"Do not create or activate a project task",
|
|
31
|
-
"Do not run unscoped `.cowork-flow/run resume`",
|
|
32
|
-
"Do not spawn or manage more agents",
|
|
33
|
+
REQUIRED_FIXED_AGENT_SNIPPETS = [
|
|
34
|
+
"cowork_runtime_context_id: <runtime_context_id>",
|
|
35
|
+
"bound runtime context",
|
|
36
|
+
"report needs_context",
|
|
37
|
+
"MUST NOT spawn",
|
|
38
|
+
"multi_agent = false",
|
|
39
|
+
"enabled = false",
|
|
33
40
|
]
|
|
34
41
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
+
REQUIRED_FIXED_AGENT_DESCRIPTION_SNIPPET = "runtime-context"
|
|
43
|
+
|
|
44
|
+
FORBIDDEN_FIXED_AGENT_DESCRIPTION_SNIPPETS = [
|
|
45
|
+
"Active task",
|
|
46
|
+
"active task",
|
|
47
|
+
"self-loads task context",
|
|
48
|
+
"self-loads",
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
FORBIDDEN_README_DISPATCH_SNIPPETS = [
|
|
52
|
+
'message="Active task: <task-dir>\\n\\n<assignment>"',
|
|
53
|
+
"提示词首行使用 `Active task: <task-dir>`",
|
|
42
54
|
]
|
|
43
55
|
|
|
44
56
|
REQUIRED_WORKFLOW_DISPATCH_SNIPPETS = [
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"
|
|
57
|
+
"宿主适配器契约",
|
|
58
|
+
".cowork-flow/spec/subagent-dispatch.md",
|
|
59
|
+
"新鲜子上下文",
|
|
60
|
+
"runtime context",
|
|
61
|
+
"适配器等待原语",
|
|
62
|
+
"适配器列表原语",
|
|
63
|
+
"适配器取消/关闭原语",
|
|
64
|
+
"advisory work",
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
REQUIRED_SUBAGENT_DISPATCH_SNIPPETS = [
|
|
68
|
+
"Runtime-context subagent dispatch",
|
|
69
|
+
"cowork_runtime_context_id",
|
|
70
|
+
"cowork_host_context_key",
|
|
71
|
+
".cowork-flow/.runtime/subagents/<runtime_context_id>.json",
|
|
72
|
+
".cowork-flow/.runtime/sessions/subagent_<runtime_context_id>.json",
|
|
73
|
+
"Verified binding is the formal dispatch acceptance event",
|
|
74
|
+
"subagent bind <runtime_context_id> <host_context_key>",
|
|
75
|
+
'bound_context_key: "<host_context_key>"',
|
|
76
|
+
"fail-closed subagent state",
|
|
77
|
+
"Generic `worker`, `default`, or `explorer` dispatch is advisory only",
|
|
50
78
|
]
|
|
51
79
|
|
|
52
80
|
REQUIRED_HOOK_SNIPPETS = [
|
|
53
81
|
".cowork-flow/run python .codex/hooks/inject-workflow-state.py",
|
|
54
82
|
]
|
|
55
83
|
|
|
84
|
+
REQUIRED_RUNTIME_HOOK_SNIPPETS = [
|
|
85
|
+
"resolve_runtime_context_id",
|
|
86
|
+
"bind_runtime_context",
|
|
87
|
+
"runtime-context-invalid",
|
|
88
|
+
'status = "delegated_subtask"',
|
|
89
|
+
"workflow-state-templates.md",
|
|
90
|
+
]
|
|
91
|
+
|
|
92
|
+
REQUIRED_WORKFLOW_STATE_TEMPLATE_SNIPPETS = [
|
|
93
|
+
"[workflow-state:no_task]",
|
|
94
|
+
"[workflow-state:delegated_subtask]",
|
|
95
|
+
"[workflow-state:planning]",
|
|
96
|
+
"[workflow-state:in_progress]",
|
|
97
|
+
"[workflow-state:review]",
|
|
98
|
+
"[workflow-state:checking]",
|
|
99
|
+
"[workflow-state:completed]",
|
|
100
|
+
"runtime context",
|
|
101
|
+
"UNKNOWN is not a delegated",
|
|
102
|
+
]
|
|
103
|
+
|
|
104
|
+
REQUIRED_ENTRY_CLASSIFIER_SNIPPETS = [
|
|
105
|
+
"classify_entry",
|
|
106
|
+
"EntryKind.MAIN_SESSION",
|
|
107
|
+
"EntryKind.READ_ONLY",
|
|
108
|
+
"EntryKind.COMMAND_ONLY",
|
|
109
|
+
"EntryKind.UNKNOWN",
|
|
110
|
+
]
|
|
111
|
+
|
|
112
|
+
REQUIRED_ENTRY_CONTRACT_SNIPPETS = [
|
|
113
|
+
"COWORK_ENTRY_CONTRACT_V1",
|
|
114
|
+
"main-session prompts",
|
|
115
|
+
"resolved by runtime context binding",
|
|
116
|
+
"`UNKNOWN` is not subagent evidence",
|
|
117
|
+
"Runtime context binding overrides project bootstrap text",
|
|
118
|
+
]
|
|
119
|
+
|
|
120
|
+
REQUIRED_CONTRACT_REGISTRY_SNIPPETS = [
|
|
121
|
+
'"schemaVersion": 1',
|
|
122
|
+
'"COWORK_ENTRY_CONTRACT_V1"',
|
|
123
|
+
'"RUNTIME_CONTEXT_DISPATCH_V2"',
|
|
124
|
+
'"HOST_ADAPTER_CAPABILITIES_V1"',
|
|
125
|
+
'"HOST_ADAPTER_SCHEMA_V1"',
|
|
126
|
+
'"readWhen"',
|
|
127
|
+
'".cowork-flow/spec/entry-contract.md"',
|
|
128
|
+
'".cowork-flow/spec/subagent-dispatch.md"',
|
|
129
|
+
'".cowork-flow/spec/capabilities.md"',
|
|
130
|
+
'".cowork-flow/spec/adapter.schema.json"',
|
|
131
|
+
]
|
|
132
|
+
|
|
133
|
+
REQUIRED_ADAPTER_SNIPPETS = [
|
|
134
|
+
"schemaVersion: 1",
|
|
135
|
+
"capabilities:",
|
|
136
|
+
"dispatchSubagent:",
|
|
137
|
+
"freshChildContext:",
|
|
138
|
+
"waitChild:",
|
|
139
|
+
"listChildren:",
|
|
140
|
+
"cancelChild:",
|
|
141
|
+
"runtimeContextDispatch:",
|
|
142
|
+
"runtimeContextBinding:",
|
|
143
|
+
"runtimeContextCleanup:",
|
|
144
|
+
"runtimeContext:",
|
|
145
|
+
"promptKey: cowork_runtime_context_id",
|
|
146
|
+
"envKey: COWORK_FLOW_RUNTIME_CONTEXT_ID",
|
|
147
|
+
"metadataKey: cowork_runtime_context_id",
|
|
148
|
+
"dispatch: RUNTIME_CONTEXT_DISPATCH_V2",
|
|
149
|
+
"leafExecutor: true",
|
|
150
|
+
"whenRuntimeContextMissing: fail_closed",
|
|
151
|
+
]
|
|
152
|
+
|
|
153
|
+
REQUIRED_OPENCODE_AGENT_SNIPPETS = [
|
|
154
|
+
"mode: subagent",
|
|
155
|
+
"task: deny",
|
|
156
|
+
"cowork_runtime_context_id: <runtime_context_id>",
|
|
157
|
+
"bound runtime context",
|
|
158
|
+
"leaf executor",
|
|
159
|
+
]
|
|
160
|
+
|
|
161
|
+
REQUIRED_CLAUDE_AGENT_SNIPPETS = [
|
|
162
|
+
"name:",
|
|
163
|
+
"cowork_runtime_context_id: <runtime_context_id>",
|
|
164
|
+
"bound runtime context",
|
|
165
|
+
"leaf executor",
|
|
166
|
+
"Do not use the Task tool or invoke subagents",
|
|
167
|
+
]
|
|
168
|
+
|
|
169
|
+
REQUIRED_RUNTIME_COMMAND_SNIPPETS = [
|
|
170
|
+
".cowork-flow/run subagent init",
|
|
171
|
+
"cowork_runtime_context_id: <runtime_context_id>",
|
|
172
|
+
"needs_context",
|
|
173
|
+
]
|
|
174
|
+
|
|
175
|
+
REQUIRED_CLAUDE_SKILL_SNIPPETS = [
|
|
176
|
+
"name:",
|
|
177
|
+
"description:",
|
|
178
|
+
]
|
|
179
|
+
|
|
180
|
+
REQUIRED_CLAUDE_HOOK_SETTINGS_SNIPPETS = [
|
|
181
|
+
".cowork-flow/run python .claude/hooks/inject-workflow-state.py",
|
|
182
|
+
"UserPromptSubmit",
|
|
183
|
+
"SessionStart",
|
|
184
|
+
]
|
|
185
|
+
|
|
56
186
|
|
|
57
187
|
def _check_file_contains(path: Path, snippets: list[str], errors: list[str]) -> None:
|
|
58
188
|
if not path.is_file():
|
|
@@ -64,6 +194,219 @@ def _check_file_contains(path: Path, snippets: list[str], errors: list[str]) ->
|
|
|
64
194
|
errors.append(f"{path} missing snippet: {snippet}")
|
|
65
195
|
|
|
66
196
|
|
|
197
|
+
def _check_file_omits(path: Path, snippets: list[str], errors: list[str]) -> None:
|
|
198
|
+
if not path.is_file():
|
|
199
|
+
errors.append(f"missing file: {path}")
|
|
200
|
+
return
|
|
201
|
+
text = path.read_text(encoding="utf-8")
|
|
202
|
+
for snippet in snippets:
|
|
203
|
+
if snippet in text:
|
|
204
|
+
errors.append(f"{path} contains forbidden snippet: {snippet}")
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def _check_file_absent(path: Path, errors: list[str]) -> None:
|
|
208
|
+
if path.exists():
|
|
209
|
+
errors.append(f"unexpected file: {path}")
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def _check_toml_parseable(path: Path, errors: list[str]) -> dict | None:
|
|
213
|
+
if not path.is_file():
|
|
214
|
+
errors.append(f"missing file: {path}")
|
|
215
|
+
return None
|
|
216
|
+
if tomllib is None:
|
|
217
|
+
return None
|
|
218
|
+
try:
|
|
219
|
+
data = tomllib.loads(path.read_text(encoding="utf-8"))
|
|
220
|
+
except Exception as exc:
|
|
221
|
+
errors.append(f"{path} is not valid TOML: {exc}")
|
|
222
|
+
return None
|
|
223
|
+
return data if isinstance(data, dict) else None
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def _check_common_contracts(repo_root: Path, errors: list[str]) -> None:
|
|
227
|
+
for rel in (
|
|
228
|
+
".cowork-flow/spec/entry-contract.md",
|
|
229
|
+
"template/.cowork-flow/spec/entry-contract.md",
|
|
230
|
+
):
|
|
231
|
+
_check_file_contains(repo_root / rel, REQUIRED_ENTRY_CONTRACT_SNIPPETS, errors)
|
|
232
|
+
for rel in (
|
|
233
|
+
".cowork-flow/spec/registry.json",
|
|
234
|
+
"template/.cowork-flow/spec/registry.json",
|
|
235
|
+
):
|
|
236
|
+
_check_file_contains(repo_root / rel, REQUIRED_CONTRACT_REGISTRY_SNIPPETS, errors)
|
|
237
|
+
for rel in (
|
|
238
|
+
".cowork-flow/spec/subagent-dispatch.md",
|
|
239
|
+
"template/.cowork-flow/spec/subagent-dispatch.md",
|
|
240
|
+
):
|
|
241
|
+
_check_file_contains(repo_root / rel, REQUIRED_SUBAGENT_DISPATCH_SNIPPETS, errors)
|
|
242
|
+
for rel in (
|
|
243
|
+
".cowork-flow/spec/workflow-state-templates.md",
|
|
244
|
+
"template/.cowork-flow/spec/workflow-state-templates.md",
|
|
245
|
+
):
|
|
246
|
+
_check_file_contains(repo_root / rel, REQUIRED_WORKFLOW_STATE_TEMPLATE_SNIPPETS, errors)
|
|
247
|
+
for rel in (
|
|
248
|
+
".cowork-flow/scripts/common/entry_classifier.py",
|
|
249
|
+
"template/.cowork-flow/scripts/common/entry_classifier.py",
|
|
250
|
+
):
|
|
251
|
+
_check_file_contains(repo_root / rel, REQUIRED_ENTRY_CLASSIFIER_SNIPPETS, errors)
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def _check_host_adapters(repo_root: Path, errors: list[str]) -> None:
|
|
255
|
+
for rel in (
|
|
256
|
+
".cowork-flow/spec/adapter.schema.json",
|
|
257
|
+
"template/.cowork-flow/spec/adapter.schema.json",
|
|
258
|
+
".cowork-flow/spec/capabilities.md",
|
|
259
|
+
"template/.cowork-flow/spec/capabilities.md",
|
|
260
|
+
):
|
|
261
|
+
_check_file_contains(
|
|
262
|
+
repo_root / rel,
|
|
263
|
+
["dispatchSubagent", "freshChildContext", "runtimeContextDispatch", "unsupported"],
|
|
264
|
+
errors,
|
|
265
|
+
)
|
|
266
|
+
for rel in (
|
|
267
|
+
".cowork-flow/adapters/codex/adapter.yaml",
|
|
268
|
+
".cowork-flow/adapters/opencode/adapter.yaml",
|
|
269
|
+
".cowork-flow/adapters/claude-code/adapter.yaml",
|
|
270
|
+
"template/.cowork-flow/adapters/codex/adapter.yaml",
|
|
271
|
+
"template/.cowork-flow/adapters/opencode/adapter.yaml",
|
|
272
|
+
"template/.cowork-flow/adapters/claude-code/adapter.yaml",
|
|
273
|
+
):
|
|
274
|
+
_check_file_contains(repo_root / rel, REQUIRED_ADAPTER_SNIPPETS, errors)
|
|
275
|
+
for rel in (
|
|
276
|
+
".cowork-flow/adapters/claude-code/adapter.yaml",
|
|
277
|
+
"template/.cowork-flow/adapters/claude-code/adapter.yaml",
|
|
278
|
+
):
|
|
279
|
+
_check_file_contains(
|
|
280
|
+
repo_root / rel,
|
|
281
|
+
[
|
|
282
|
+
"skillsPath: .claude/skills",
|
|
283
|
+
"settingsPath: .claude/settings.json",
|
|
284
|
+
"hooksPath: .claude/hooks",
|
|
285
|
+
],
|
|
286
|
+
errors,
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def cmd_entry_contract(_: argparse.Namespace) -> int:
|
|
291
|
+
repo_root = get_repo_root()
|
|
292
|
+
errors: list[str] = []
|
|
293
|
+
_check_common_contracts(repo_root, errors)
|
|
294
|
+
for rel in (
|
|
295
|
+
f".agent/skills/{ENTRY_BOUNDARY_DIR}/SKILL.md",
|
|
296
|
+
f"template/.agent/skills/{ENTRY_BOUNDARY_DIR}/SKILL.md",
|
|
297
|
+
):
|
|
298
|
+
_check_file_absent(repo_root / rel, errors)
|
|
299
|
+
if errors:
|
|
300
|
+
for error in errors:
|
|
301
|
+
print(f"ERROR: {error}", file=sys.stderr)
|
|
302
|
+
return 1
|
|
303
|
+
print("entry contract checks passed")
|
|
304
|
+
return 0
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def cmd_host_adapters(_: argparse.Namespace) -> int:
|
|
308
|
+
repo_root = get_repo_root()
|
|
309
|
+
errors: list[str] = []
|
|
310
|
+
_check_host_adapters(repo_root, errors)
|
|
311
|
+
for rel in (
|
|
312
|
+
".opencode/agents/cowork-research.md",
|
|
313
|
+
".opencode/agents/cowork-implement.md",
|
|
314
|
+
".opencode/agents/cowork-check.md",
|
|
315
|
+
"template/.opencode/agents/cowork-research.md",
|
|
316
|
+
"template/.opencode/agents/cowork-implement.md",
|
|
317
|
+
"template/.opencode/agents/cowork-check.md",
|
|
318
|
+
):
|
|
319
|
+
_check_file_contains(repo_root / rel, REQUIRED_OPENCODE_AGENT_SNIPPETS, errors)
|
|
320
|
+
for rel in (
|
|
321
|
+
".claude/agents/cowork-research.md",
|
|
322
|
+
".claude/agents/cowork-implement.md",
|
|
323
|
+
".claude/agents/cowork-check.md",
|
|
324
|
+
"template/.claude/agents/cowork-research.md",
|
|
325
|
+
"template/.claude/agents/cowork-implement.md",
|
|
326
|
+
"template/.claude/agents/cowork-check.md",
|
|
327
|
+
):
|
|
328
|
+
_check_file_contains(repo_root / rel, REQUIRED_CLAUDE_AGENT_SNIPPETS, errors)
|
|
329
|
+
for rel in (
|
|
330
|
+
".claude/commands/cowork-research.md",
|
|
331
|
+
".claude/commands/cowork-implement.md",
|
|
332
|
+
".claude/commands/cowork-check.md",
|
|
333
|
+
".opencode/commands/cowork-research.md",
|
|
334
|
+
".opencode/commands/cowork-implement.md",
|
|
335
|
+
".opencode/commands/cowork-check.md",
|
|
336
|
+
"template/.claude/commands/cowork-research.md",
|
|
337
|
+
"template/.claude/commands/cowork-implement.md",
|
|
338
|
+
"template/.claude/commands/cowork-check.md",
|
|
339
|
+
"template/.opencode/commands/cowork-research.md",
|
|
340
|
+
"template/.opencode/commands/cowork-implement.md",
|
|
341
|
+
"template/.opencode/commands/cowork-check.md",
|
|
342
|
+
):
|
|
343
|
+
_check_file_contains(repo_root / rel, REQUIRED_RUNTIME_COMMAND_SNIPPETS, errors)
|
|
344
|
+
for rel in (
|
|
345
|
+
".claude/skills/start/SKILL.md",
|
|
346
|
+
".claude/skills/check/SKILL.md",
|
|
347
|
+
"template/.claude/skills/start/SKILL.md",
|
|
348
|
+
"template/.claude/skills/check/SKILL.md",
|
|
349
|
+
):
|
|
350
|
+
_check_file_contains(repo_root / rel, REQUIRED_CLAUDE_SKILL_SNIPPETS, errors)
|
|
351
|
+
for rel in (
|
|
352
|
+
f".claude/skills/{ENTRY_BOUNDARY_DIR}/SKILL.md",
|
|
353
|
+
f"template/.claude/skills/{ENTRY_BOUNDARY_DIR}/SKILL.md",
|
|
354
|
+
):
|
|
355
|
+
_check_file_absent(repo_root / rel, errors)
|
|
356
|
+
for rel in (
|
|
357
|
+
".claude/settings.json",
|
|
358
|
+
"template/.claude/settings.json",
|
|
359
|
+
):
|
|
360
|
+
_check_file_contains(repo_root / rel, REQUIRED_CLAUDE_HOOK_SETTINGS_SNIPPETS, errors)
|
|
361
|
+
for rel in (
|
|
362
|
+
".claude/hooks/inject-workflow-state.py",
|
|
363
|
+
"template/.claude/hooks/inject-workflow-state.py",
|
|
364
|
+
".codex/hooks/inject-workflow-state.py",
|
|
365
|
+
"template/.codex/hooks/inject-workflow-state.py",
|
|
366
|
+
):
|
|
367
|
+
_check_file_contains(repo_root / rel, REQUIRED_RUNTIME_HOOK_SNIPPETS, errors)
|
|
368
|
+
for rel in (
|
|
369
|
+
"CLAUDE.md",
|
|
370
|
+
"template/CLAUDE.md",
|
|
371
|
+
):
|
|
372
|
+
_check_file_contains(
|
|
373
|
+
repo_root / rel,
|
|
374
|
+
[
|
|
375
|
+
"@AGENTS.md",
|
|
376
|
+
"<!-- COWORK-FLOW:START -->",
|
|
377
|
+
".cowork-flow/run subagent init",
|
|
378
|
+
"cowork_runtime_context_id: <runtime_context_id>",
|
|
379
|
+
".claude/agents/cowork-implement.md",
|
|
380
|
+
".claude/skills/",
|
|
381
|
+
],
|
|
382
|
+
errors,
|
|
383
|
+
)
|
|
384
|
+
for rel in (
|
|
385
|
+
".opencode/plugins/cowork-flow.js",
|
|
386
|
+
"template/.opencode/plugins/cowork-flow.js",
|
|
387
|
+
):
|
|
388
|
+
_check_file_contains(
|
|
389
|
+
repo_root / rel,
|
|
390
|
+
[
|
|
391
|
+
"experimental.chat.system.transform",
|
|
392
|
+
".cowork-flow\", \"spec\", \"registry.json",
|
|
393
|
+
"<contract-digest fingerprint=",
|
|
394
|
+
"read_before",
|
|
395
|
+
"RUNTIME_CONTEXT_DISPATCH_V2",
|
|
396
|
+
"resolveRuntimeContextId",
|
|
397
|
+
"bindRuntimeContext",
|
|
398
|
+
"runtime-context-invalid",
|
|
399
|
+
],
|
|
400
|
+
errors,
|
|
401
|
+
)
|
|
402
|
+
if errors:
|
|
403
|
+
for error in errors:
|
|
404
|
+
print(f"ERROR: {error}", file=sys.stderr)
|
|
405
|
+
return 1
|
|
406
|
+
print("host adapter checks passed")
|
|
407
|
+
return 0
|
|
408
|
+
|
|
409
|
+
|
|
67
410
|
def cmd_subagent_safety(_: argparse.Namespace) -> int:
|
|
68
411
|
repo_root = get_repo_root()
|
|
69
412
|
errors: list[str] = []
|
|
@@ -73,10 +416,10 @@ def cmd_subagent_safety(_: argparse.Namespace) -> int:
|
|
|
73
416
|
):
|
|
74
417
|
_check_file_contains(repo_root / rel, REQUIRED_START_SNIPPETS, errors)
|
|
75
418
|
for rel in (
|
|
76
|
-
".agent/skills/
|
|
77
|
-
"template/.agent/skills/
|
|
419
|
+
f".agent/skills/{ENTRY_BOUNDARY_DIR}/SKILL.md",
|
|
420
|
+
f"template/.agent/skills/{ENTRY_BOUNDARY_DIR}/SKILL.md",
|
|
78
421
|
):
|
|
79
|
-
|
|
422
|
+
_check_file_absent(repo_root / rel, errors)
|
|
80
423
|
for rel in (
|
|
81
424
|
".codex/agents/cowork-research.toml",
|
|
82
425
|
".codex/agents/cowork-implement.toml",
|
|
@@ -86,21 +429,60 @@ def cmd_subagent_safety(_: argparse.Namespace) -> int:
|
|
|
86
429
|
"template/.codex/agents/cowork-check.toml",
|
|
87
430
|
):
|
|
88
431
|
_check_file_contains(repo_root / rel, REQUIRED_FIXED_AGENT_SNIPPETS, errors)
|
|
432
|
+
data = _check_toml_parseable(repo_root / rel, errors)
|
|
433
|
+
if data is not None and data.get("name") != Path(rel).stem:
|
|
434
|
+
errors.append(f"{rel} name must match filename")
|
|
435
|
+
description = data.get("description") if data is not None else None
|
|
436
|
+
if isinstance(description, str):
|
|
437
|
+
if REQUIRED_FIXED_AGENT_DESCRIPTION_SNIPPET not in description:
|
|
438
|
+
errors.append(
|
|
439
|
+
f"{rel} description must mention {REQUIRED_FIXED_AGENT_DESCRIPTION_SNIPPET}"
|
|
440
|
+
)
|
|
441
|
+
for snippet in FORBIDDEN_FIXED_AGENT_DESCRIPTION_SNIPPETS:
|
|
442
|
+
if snippet in description:
|
|
443
|
+
errors.append(f"{rel} description contains stale dispatch marker: {snippet}")
|
|
444
|
+
elif data is not None:
|
|
445
|
+
errors.append(f"{rel} missing description")
|
|
446
|
+
if (repo_root / "README.md").is_file():
|
|
447
|
+
_check_file_omits(repo_root / "README.md", FORBIDDEN_README_DISPATCH_SNIPPETS, errors)
|
|
448
|
+
for rel in (
|
|
449
|
+
".codex/agents/worker.toml",
|
|
450
|
+
".codex/agents/default.toml",
|
|
451
|
+
".codex/agents/explorer.toml",
|
|
452
|
+
"template/.codex/agents/worker.toml",
|
|
453
|
+
"template/.codex/agents/default.toml",
|
|
454
|
+
"template/.codex/agents/explorer.toml",
|
|
455
|
+
):
|
|
456
|
+
_check_file_contains(repo_root / rel, ["bootstrap", "start", "resume", "advisory"], errors)
|
|
457
|
+
data = _check_toml_parseable(repo_root / rel, errors)
|
|
458
|
+
if data is not None and data.get("name") != Path(rel).stem:
|
|
459
|
+
errors.append(f"{rel} name must match filename")
|
|
89
460
|
for rel in (
|
|
90
461
|
".cowork-flow/workflow.md",
|
|
91
462
|
"template/.cowork-flow/workflow.md",
|
|
92
463
|
):
|
|
93
464
|
_check_file_contains(repo_root / rel, REQUIRED_WORKFLOW_DISPATCH_SNIPPETS, errors)
|
|
465
|
+
_check_common_contracts(repo_root, errors)
|
|
466
|
+
_check_host_adapters(repo_root, errors)
|
|
94
467
|
for rel in (
|
|
95
468
|
".codex/hooks.json",
|
|
96
469
|
"template/.codex/hooks.json",
|
|
97
470
|
):
|
|
98
471
|
_check_file_contains(repo_root / rel, REQUIRED_HOOK_SNIPPETS, errors)
|
|
472
|
+
for rel in (
|
|
473
|
+
".codex/hooks/inject-workflow-state.py",
|
|
474
|
+
"template/.codex/hooks/inject-workflow-state.py",
|
|
475
|
+
".claude/hooks/inject-workflow-state.py",
|
|
476
|
+
"template/.claude/hooks/inject-workflow-state.py",
|
|
477
|
+
):
|
|
478
|
+
_check_file_contains(repo_root / rel, REQUIRED_RUNTIME_HOOK_SNIPPETS, errors)
|
|
99
479
|
for rel in (
|
|
100
480
|
".cowork-flow/scripts/subagent.py",
|
|
101
481
|
"template/.cowork-flow/scripts/subagent.py",
|
|
102
482
|
".cowork-flow/scripts/common/execution_context.py",
|
|
103
483
|
"template/.cowork-flow/scripts/common/execution_context.py",
|
|
484
|
+
".cowork-flow/scripts/common/active_task.py",
|
|
485
|
+
"template/.cowork-flow/scripts/common/active_task.py",
|
|
104
486
|
):
|
|
105
487
|
if not (repo_root / rel).is_file():
|
|
106
488
|
errors.append(f"missing file: {rel}")
|
|
@@ -115,6 +497,8 @@ def cmd_subagent_safety(_: argparse.Namespace) -> int:
|
|
|
115
497
|
def build_parser() -> argparse.ArgumentParser:
|
|
116
498
|
parser = argparse.ArgumentParser(description="cowork-flow diagnostics")
|
|
117
499
|
parser.add_argument("--subagent-safety", action="store_true", help="Check subagent safety wiring")
|
|
500
|
+
parser.add_argument("--entry-contract", action="store_true", help="Check entry classification contract")
|
|
501
|
+
parser.add_argument("--host-adapters", action="store_true", help="Check host adapter declarations")
|
|
118
502
|
return parser
|
|
119
503
|
|
|
120
504
|
|
|
@@ -123,6 +507,10 @@ def main() -> int:
|
|
|
123
507
|
args = parser.parse_args()
|
|
124
508
|
if args.subagent_safety:
|
|
125
509
|
return cmd_subagent_safety(args)
|
|
510
|
+
if args.entry_contract:
|
|
511
|
+
return cmd_entry_contract(args)
|
|
512
|
+
if args.host_adapters:
|
|
513
|
+
return cmd_host_adapters(args)
|
|
126
514
|
parser.print_help()
|
|
127
515
|
return 0
|
|
128
516
|
|