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
@@ -1,36 +1,27 @@
1
1
  #!/usr/bin/env python3
2
- """Emit cowork-flow workflow context for Codex UserPromptSubmit hooks."""
2
+ """Emit cowork-flow workflow context for Codex hooks."""
3
3
 
4
4
  from __future__ import annotations
5
5
 
6
6
  import json
7
- import hashlib
8
7
  import os
9
- import re
10
8
  import sys
11
9
  from pathlib import Path
12
10
  from typing import Any
13
11
 
14
12
 
15
- TAG_RE = re.compile(
16
- r"\[workflow-state:([A-Za-z0-9_-]+)\]\s*\n(.*?)\n\s*\[/workflow-state:\1\]",
17
- re.DOTALL,
18
- )
19
- DEFAULT_CONTRACT_REGISTRY = {
20
- "contracts": [
21
- {
22
- "id": "RUNTIME_CONTEXT_DISPATCH_V2",
23
- "path": ".cowork-flow/spec/contracts/subagent-dispatch.md",
24
- "digest": [
25
- "Formal subagent work is keyed by cowork_runtime_context_id.",
26
- "Explicit shim bind records bound_context_key before formal output is accepted.",
27
- ],
28
- "readWhen": ["before formal subagent dispatch", "when checking subagent health"],
29
- },
30
- ]
31
- }
13
+ def _read_input() -> dict[str, Any]:
14
+ raw = sys.stdin.buffer.read()
15
+ if not raw.strip():
16
+ return {}
17
+ try:
18
+ data = json.loads(raw.decode("utf-8-sig"))
19
+ except (UnicodeDecodeError, json.JSONDecodeError, ValueError):
20
+ return {}
21
+ return data if isinstance(data, dict) else {}
22
+
32
23
 
33
- def _find_repo_root(start: Path) -> Path | None:
24
+ def _find_root(start: Path) -> Path | None:
34
25
  current = start.resolve()
35
26
  while True:
36
27
  if (current / ".cowork-flow").is_dir():
@@ -40,263 +31,53 @@ def _find_repo_root(start: Path) -> Path | None:
40
31
  current = current.parent
41
32
 
42
33
 
43
- def _read_hook_input() -> dict[str, Any]:
44
- try:
45
- raw = sys.stdin.buffer.read()
46
- except AttributeError:
47
- text = sys.stdin.read()
48
- else:
49
- if not raw.strip():
50
- return {}
51
- try:
52
- text = raw.decode("utf-8-sig")
53
- except UnicodeDecodeError:
54
- encoding = getattr(sys.stdin, "encoding", None) or "utf-8"
55
- text = raw.decode(encoding, errors="replace")
56
- if not text.strip():
57
- return {}
58
- try:
59
- data = json.loads(text)
60
- except (json.JSONDecodeError, ValueError):
61
- return {}
62
- return data if isinstance(data, dict) else {}
63
-
64
-
65
- def _load_breadcrumbs(root: Path) -> dict[str, str]:
66
- workflow = root / ".cowork-flow" / "spec" / "contracts" / "workflow-state-templates.md"
67
- try:
68
- text = workflow.read_text(encoding="utf-8")
69
- except OSError:
70
- return {}
71
- return {match.group(1): match.group(2).strip() for match in TAG_RE.finditer(text)}
72
-
73
-
74
- def _as_string_list(value: Any) -> list[str]:
75
- if not isinstance(value, list):
76
- return []
77
- return [item for item in value if isinstance(item, str) and item.strip()]
78
-
79
-
80
- def _load_contract_registry(root: Path) -> tuple[list[dict[str, Any]], str | None]:
81
- registry_file = root / ".cowork-flow" / "spec" / "runtime" / "contract-registry.json"
82
- try:
83
- data = json.loads(registry_file.read_text(encoding="utf-8"))
84
- except OSError:
85
- data = DEFAULT_CONTRACT_REGISTRY
86
- warning = f"contract registry unavailable at {registry_file}; using fallback digest"
87
- except json.JSONDecodeError:
88
- data = DEFAULT_CONTRACT_REGISTRY
89
- warning = f"contract registry invalid at {registry_file}; using fallback digest"
90
- else:
91
- warning = None
92
- contracts = data.get("contracts") if isinstance(data, dict) else None
93
- if not isinstance(contracts, list):
94
- contracts = DEFAULT_CONTRACT_REGISTRY["contracts"]
95
- warning = warning or f"contract registry has no contracts array at {registry_file}; using fallback digest"
96
- return [contract for contract in contracts if isinstance(contract, dict)], warning
97
-
98
-
99
- def _contract_fingerprint(root: Path, contracts: list[dict[str, Any]]) -> str:
100
- digest = hashlib.sha256()
101
- digest.update(json.dumps(contracts, ensure_ascii=False, sort_keys=True).encode("utf-8"))
102
- for contract in contracts:
103
- path = contract.get("path")
104
- if not isinstance(path, str) or not path.strip():
105
- continue
106
- try:
107
- digest.update((root / path).read_bytes())
108
- except OSError:
109
- digest.update(f"missing:{path}".encode("utf-8"))
110
- return digest.hexdigest()[:16]
111
-
112
-
113
- def _build_contract_digest(root: Path) -> str:
114
- contracts, warning = _load_contract_registry(root)
115
- fingerprint = _contract_fingerprint(root, contracts)
116
- lines = [
117
- '<cowork-runtime host="codex" adapter="codex.spawn_agent">',
118
- f'<contract-digest fingerprint="{fingerprint}">',
119
- "policy: repeat this short digest every hook; read full spec files only before listed actions.",
120
- ]
121
- if warning:
122
- lines.append(f"warning: {warning}")
123
- for contract in contracts:
124
- contract_id = contract.get("id")
125
- path = contract.get("path")
126
- if not isinstance(contract_id, str) or not contract_id.strip():
127
- continue
128
- path_text = path if isinstance(path, str) and path.strip() else "<missing-path>"
129
- lines.append(f"- {contract_id}: {path_text}")
130
- for item in _as_string_list(contract.get("digest"))[:2]:
131
- lines.append(f" digest: {item}")
132
- read_when = _as_string_list(contract.get("readWhen"))
133
- if read_when:
134
- lines.append(f" read_before: {'; '.join(read_when)}")
135
- lines.extend(["</contract-digest>", "</cowork-runtime>"])
136
- return "\n".join(lines)
137
-
138
-
139
- def _load_common(root: Path) -> None:
140
- scripts_dir = root / ".cowork-flow" / "scripts"
141
- if str(scripts_dir) not in sys.path:
142
- sys.path.insert(0, str(scripts_dir))
143
-
144
-
145
- def _get_dispatch_mode(root: Path) -> str:
146
- _load_common(root)
147
- try:
148
- from common.core.config import get_codex_dispatch_mode # type: ignore[import-not-found]
149
- except Exception:
150
- return "sub-agent"
151
- try:
152
- return get_codex_dispatch_mode(root)
153
- except Exception:
154
- return "sub-agent"
155
-
156
-
157
- def _get_active_task(root: Path, hook_input: dict[str, Any]) -> tuple[str | None, str, str]:
158
- _load_common(root)
159
- try:
160
- from common.task.active_task import get_active_task # type: ignore[import-not-found]
161
- except Exception:
162
- return None, "no_task", "unavailable"
163
-
164
- active = get_active_task(root, hook_input)
165
- if not active.task_path:
166
- return None, "no_task", active.source
167
-
168
- task_dir = root / active.task_path
169
- task_json = task_dir / "task.json"
170
- try:
171
- data = json.loads(task_json.read_text(encoding="utf-8"))
172
- except (OSError, json.JSONDecodeError):
173
- return active.task_path, "stale", active.source
174
-
175
- status = data.get("status")
176
- if not isinstance(status, str) or not status.strip():
177
- status = "unknown"
178
- return active.task_path, status.strip(), active.source
179
-
180
-
181
- def _resolve_runtime_context(root: Path, hook_input: dict[str, Any]) -> tuple[dict[str, Any] | None, str | None]:
182
- _load_common(root)
183
- try:
184
- from common.task.active_task import ( # type: ignore[import-not-found]
185
- bind_runtime_context,
186
- read_runtime_context,
187
- resolve_runtime_context_id,
188
- )
189
- except Exception:
190
- return None, None
191
-
192
- runtime_context_id = resolve_runtime_context_id(hook_input)
193
- if not runtime_context_id:
194
- return None, None
195
-
196
- context = read_runtime_context(root, runtime_context_id)
197
- if not context or context.get("scope") != "subagent" or context.get("status") == "closed":
198
- return None, runtime_context_id
199
-
200
- bound = bind_runtime_context(root, runtime_context_id, values=hook_input)
201
- return bound or context, runtime_context_id
202
-
203
-
204
- def _subagent_runtime_lines(context: dict[str, Any]) -> list[str]:
205
- assignment = context.get("assignment") if isinstance(context.get("assignment"), dict) else {}
206
- lines = [
207
- f"Runtime context: {context.get('runtime_context_id')}",
208
- f"Agent: {context.get('agent_type') or 'unknown'}",
209
- "Scope: subagent",
210
- "Do not run start/resume/task start/archive/commit/spawn.",
211
- ]
212
- goal = assignment.get("goal") if isinstance(assignment, dict) else None
213
- if isinstance(goal, str) and goal.strip():
214
- lines.append(f"Goal: {goal.strip()}")
215
- return lines
216
-
217
-
218
- def _build_context(
219
- root: Path,
220
- task_path: str | None,
221
- status: str,
222
- source: str,
223
- breadcrumbs: dict[str, str],
224
- dispatch_mode: str,
225
- extra_lines: list[str] | None = None,
226
- ) -> str:
227
- body = breadcrumbs.get(status) or "Refer to .cowork-flow/workflow.md for the current step."
228
- if extra_lines:
229
- body = "\n".join([body, *extra_lines])
230
- if task_path is None:
231
- header = f"Status: {status}\nSource: {source}"
232
- else:
233
- header = f"Task: {task_path}\nStatus: {status}\nSource: {source}"
234
-
235
- return "\n\n".join(
236
- [
237
- f"<codex-dispatch-mode>{dispatch_mode}</codex-dispatch-mode>",
238
- (
239
- "<codex-runtime>\n"
240
- "dispatch_mode_meaning: workflow dispatch hint, not current thread role\n"
241
- "runtime_context_identity: formal subagent sessions bind before workflow-state injection\n"
242
- "</codex-runtime>"
243
- ),
244
- (
245
- _build_contract_digest(root)
246
- ),
247
- f"<workflow-state>\n{header}\n{body}\n</workflow-state>",
248
- ]
249
- )
250
-
251
-
252
34
  def main() -> int:
253
- if os.environ.get("COWORK_FLOW_HOOKS") == "0" or os.environ.get("COWORK_FLOW_DISABLE_HOOKS") == "1":
35
+ if (
36
+ os.environ.get("COWORK_FLOW_HOOKS") == "0"
37
+ or os.environ.get("COWORK_FLOW_DISABLE_HOOKS") == "1"
38
+ ):
254
39
  return 0
255
-
256
- hook_input = _read_hook_input()
40
+ hook_input = _read_input()
257
41
  cwd_value = hook_input.get("cwd")
258
- cwd = Path(cwd_value) if isinstance(cwd_value, str) and cwd_value.strip() else Path.cwd()
259
- root = _find_repo_root(cwd)
42
+ cwd = (
43
+ Path(cwd_value)
44
+ if isinstance(cwd_value, str) and cwd_value.strip()
45
+ else Path.cwd()
46
+ )
47
+ root = _find_root(cwd)
260
48
  if root is None:
261
49
  return 0
50
+ scripts_dir = root / ".cowork-flow" / "scripts"
51
+ sys.path.insert(0, str(scripts_dir))
52
+ from common.host.workflow_state_hook import (
53
+ build_hook_context,
54
+ codex_dispatch_mode,
55
+ )
262
56
 
263
- breadcrumbs = _load_breadcrumbs(root)
264
- dispatch_mode = _get_dispatch_mode(root)
265
- runtime_context, runtime_context_id = _resolve_runtime_context(root, hook_input)
266
- extra_lines: list[str] | None = None
267
- if runtime_context is not None:
268
- task_dir = runtime_context.get("task_dir")
269
- task_path = task_dir.strip() if isinstance(task_dir, str) and task_dir.strip() else None
270
- status = "delegated_subtask"
271
- source = f"runtime-context:{runtime_context.get('runtime_context_id')}"
272
- extra_lines = _subagent_runtime_lines(runtime_context)
273
- elif runtime_context_id:
274
- task_path = None
275
- status = "delegated_subtask"
276
- source = f"runtime-context-invalid:{runtime_context_id}"
277
- extra_lines = [
278
- f"Runtime context: {runtime_context_id}",
279
- "Scope: subagent",
280
- "Runtime context is missing, closed, or invalid. Do not run start/resume/task start/archive/commit/spawn.",
281
- ]
282
- else:
283
- task_path, status, source = _get_active_task(root, hook_input)
284
- additional_context = _build_context(
57
+ dispatch_mode = codex_dispatch_mode(root)
58
+ context = build_hook_context(
285
59
  root,
286
- task_path,
287
- status,
288
- source,
289
- breadcrumbs,
290
- dispatch_mode,
291
- extra_lines,
60
+ hook_input,
61
+ host="codex",
62
+ adapter="codex.spawn_agent",
63
+ preamble=(
64
+ f"<codex-dispatch-mode>{dispatch_mode}</codex-dispatch-mode>",
65
+ (
66
+ "<codex-runtime>\n"
67
+ "dispatch_mode_meaning: workflow dispatch hint, not current "
68
+ "thread role\n"
69
+ "runtime_context_identity: formal subagent sessions bind "
70
+ "before workflow-state injection\n"
71
+ "</codex-runtime>"
72
+ ),
73
+ ),
292
74
  )
293
-
294
75
  print(
295
76
  json.dumps(
296
77
  {
297
78
  "hookSpecificOutput": {
298
79
  "hookEventName": "UserPromptSubmit",
299
- "additionalContext": additional_context,
80
+ "additionalContext": context,
300
81
  }
301
82
  },
302
83
  ensure_ascii=False,
@@ -1 +1 @@
1
- 0.0.37
1
+ 0.0.39
@@ -0,0 +1 @@
1
+ """Application services for cowork-flow commands."""