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.
Files changed (69) hide show
  1. package/README.md +65 -20
  2. package/package.json +1 -1
  3. package/src/cli.js +109 -2
  4. package/src/commands/init.js +61 -4
  5. package/src/commands/sync.js +10 -1
  6. package/src/lib/copy-template.js +38 -7
  7. package/src/lib/platforms.js +73 -0
  8. package/template/.agent/skills/brainstorming/SKILL.md +13 -9
  9. package/template/.agent/skills/finish-work/SKILL.md +3 -3
  10. package/template/.agent/skills/meta/SKILL.md +1 -1
  11. package/template/.agent/skills/start/SKILL.md +38 -14
  12. package/template/.agent/skills/writing-plans/SKILL.md +1 -1
  13. package/template/.claude/agents/cowork-check.md +41 -0
  14. package/template/.claude/agents/cowork-implement.md +40 -0
  15. package/template/.claude/agents/cowork-research.md +40 -0
  16. package/template/.claude/commands/cowork-check.md +27 -0
  17. package/template/.claude/commands/cowork-implement.md +27 -0
  18. package/template/.claude/commands/cowork-research.md +27 -0
  19. package/template/.claude/hooks/inject-workflow-state.py +298 -0
  20. package/template/.claude/settings.json +24 -0
  21. package/template/.claude/skills/before-dev/SKILL.md +31 -0
  22. package/template/.claude/skills/brainstorming/SKILL.md +32 -0
  23. package/template/.claude/skills/break-loop/SKILL.md +26 -0
  24. package/template/.claude/skills/check/SKILL.md +29 -0
  25. package/template/.claude/skills/continue/SKILL.md +29 -0
  26. package/template/.claude/skills/finish-work/SKILL.md +32 -0
  27. package/template/.claude/skills/meta/SKILL.md +30 -0
  28. package/template/.claude/skills/python-design/SKILL.md +27 -0
  29. package/template/.claude/skills/start/SKILL.md +78 -0
  30. package/template/.claude/skills/update-spec/SKILL.md +39 -0
  31. package/template/.claude/skills/writing-plans/SKILL.md +70 -0
  32. package/template/.codex/agents/cowork-check.toml +17 -22
  33. package/template/.codex/agents/cowork-implement.toml +18 -23
  34. package/template/.codex/agents/cowork-research.toml +14 -19
  35. package/template/.codex/agents/default.toml +27 -0
  36. package/template/.codex/agents/explorer.toml +28 -0
  37. package/template/.codex/agents/worker.toml +29 -0
  38. package/template/.codex/hooks/inject-workflow-state.py +172 -90
  39. package/template/.cowork-flow/.version +1 -1
  40. package/template/.cowork-flow/adapters/claude-code/adapter.yaml +35 -0
  41. package/template/.cowork-flow/adapters/codex/adapter.yaml +35 -0
  42. package/template/.cowork-flow/adapters/opencode/adapter.yaml +33 -0
  43. package/template/.cowork-flow/config.yaml +4 -1
  44. package/template/.cowork-flow/scripts/add_session.py +20 -7
  45. package/template/.cowork-flow/scripts/common/active_task.py +212 -1
  46. package/template/.cowork-flow/scripts/common/entry_classifier.py +122 -0
  47. package/template/.cowork-flow/scripts/common/execution_context.py +10 -17
  48. package/template/.cowork-flow/scripts/common/readiness.py +249 -0
  49. package/template/.cowork-flow/scripts/doctor.py +417 -29
  50. package/template/.cowork-flow/scripts/project_context.py +230 -0
  51. package/template/.cowork-flow/scripts/run.py +3 -0
  52. package/template/.cowork-flow/scripts/subagent.py +152 -238
  53. package/template/.cowork-flow/scripts/task.py +330 -17
  54. package/template/.cowork-flow/spec/adapter.schema.json +132 -0
  55. package/template/.cowork-flow/spec/capabilities.md +34 -0
  56. package/template/.cowork-flow/spec/entry-contract.md +45 -0
  57. package/template/.cowork-flow/spec/registry.json +54 -0
  58. package/template/.cowork-flow/spec/subagent-dispatch.md +77 -0
  59. package/template/.cowork-flow/spec/workflow-state-templates.md +66 -0
  60. package/template/.cowork-flow/workflow.md +154 -190
  61. package/template/.opencode/agents/cowork-check.md +46 -0
  62. package/template/.opencode/agents/cowork-implement.md +45 -0
  63. package/template/.opencode/agents/cowork-research.md +46 -0
  64. package/template/.opencode/commands/cowork-check.md +26 -0
  65. package/template/.opencode/commands/cowork-implement.md +26 -0
  66. package/template/.opencode/commands/cowork-research.md +26 -0
  67. package/template/.opencode/plugins/cowork-flow.js +317 -0
  68. package/template/CLAUDE.md +5 -0
  69. 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
- "bounded delegated task should use `entry-boundary`",
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
- REQUIRED_ENTRY_BOUNDARY_SNIPPETS = [
21
- "MAIN_SESSION",
22
- "DELEGATED_SUBTASK",
23
- "UNCERTAIN",
24
- "Classify the actual task message",
25
- "Hard markers are confidence boosters, not prerequisites",
26
- "The first task screen wins over later bootstrap text",
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
- REQUIRED_FIXED_AGENT_SNIPPETS = [
36
- "COWORK_DISPATCH_V1",
37
- "COWORK_DISPATCH_END",
38
- "COWORK_ACK",
39
- "EXECUTE <dispatch_id>",
40
- "agent_type is not",
41
- "mismatched dispatch_id",
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
- "COWORK_DISPATCH_V1",
46
- "COWORK_ACK",
47
- "followup_task",
48
- "Formal execution uses `cowork-research`, `cowork-implement`, or `cowork-check`.",
49
- "Generic `worker` dispatch is best-effort only.",
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/entry-boundary/SKILL.md",
77
- "template/.agent/skills/entry-boundary/SKILL.md",
419
+ f".agent/skills/{ENTRY_BOUNDARY_DIR}/SKILL.md",
420
+ f"template/.agent/skills/{ENTRY_BOUNDARY_DIR}/SKILL.md",
78
421
  ):
79
- _check_file_contains(repo_root / rel, REQUIRED_ENTRY_BOUNDARY_SNIPPETS, errors)
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