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
|
@@ -5,24 +5,28 @@ description: Use when requirements are unclear, a requested change has multiple
|
|
|
5
5
|
|
|
6
6
|
# Brainstorming
|
|
7
7
|
|
|
8
|
-
Use this skill
|
|
8
|
+
Use this skill as the active clarification gate for new requirements. Turn an idea into a clear change request before PRD, planning, fixed-agent dispatch, or code changes begin.
|
|
9
9
|
|
|
10
10
|
## Flow
|
|
11
11
|
|
|
12
12
|
1. Read only the context needed to understand the project area: `AGENTS.md`, `.cowork-flow/workflow.md`, and relevant spec indexes.
|
|
13
|
-
2. State assumptions, success criteria, and the smallest useful scope.
|
|
14
|
-
3. Ask one high-value question only when the answer cannot be inferred safely.
|
|
15
|
-
4. Present 2-3 viable approaches when trade-offs matter, with a concrete
|
|
16
|
-
5.
|
|
17
|
-
6.
|
|
13
|
+
2. State the goal, non-goals, assumptions, scope boundary, success criteria, and the smallest useful scope.
|
|
14
|
+
3. Ask one high-value question only when the answer cannot be inferred safely; otherwise proceed with explicit assumptions.
|
|
15
|
+
4. Present 2-3 viable approaches when trade-offs matter, with a concrete recommended direction.
|
|
16
|
+
5. Do not write PRD, planning, or fixed-agent dispatch input until the direction and acceptance criteria are clear.
|
|
17
|
+
6. For L1/L2 work, create or update the change/task artifacts required by `.cowork-flow/workflow.md` after the clarification output is stable.
|
|
18
|
+
7. Hand off to `writing-plans` when the requested behavior and acceptance criteria are clear.
|
|
18
19
|
|
|
19
20
|
## Output
|
|
20
21
|
|
|
21
22
|
Keep the result practical:
|
|
22
23
|
|
|
23
|
-
- Goal
|
|
24
|
-
-
|
|
24
|
+
- Goal.
|
|
25
|
+
- Non-goals.
|
|
26
|
+
- Key assumptions.
|
|
27
|
+
- Scope boundary, including in-scope and out-of-scope work.
|
|
28
|
+
- Recommended direction and rejected alternatives.
|
|
25
29
|
- Acceptance criteria.
|
|
26
|
-
- Open risks or blockers.
|
|
30
|
+
- Open questions, risks, or blockers, with their impact on the next step.
|
|
27
31
|
|
|
28
32
|
Do not implement during brainstorming unless the user explicitly changes the request to a direct edit.
|
|
@@ -25,8 +25,8 @@ Before claiming completion, verify:
|
|
|
25
25
|
2. Run `git diff --check`.
|
|
26
26
|
3. Run focused tests and then broader project verification when appropriate.
|
|
27
27
|
4. Update the plan/task status.
|
|
28
|
-
5.
|
|
29
|
-
6.
|
|
30
|
-
7.
|
|
28
|
+
5. Archive completed task/change artifacts when requested or required by workflow.
|
|
29
|
+
6. Record the session with `.cowork-flow/run add-session --commit "-"` unless a prior commit hash is intentionally being recorded.
|
|
30
|
+
7. If commit policy allows, stage only expected files and commit once.
|
|
31
31
|
|
|
32
32
|
If the user asks not to commit, stop before staging and report the verified state.
|
|
@@ -18,7 +18,7 @@ Keep those copies aligned unless there is a documented reason not to.
|
|
|
18
18
|
|
|
19
19
|
## Rules
|
|
20
20
|
|
|
21
|
-
- Prefer the current fixed-agent model: main session coordinates, child agents execute bounded tasks
|
|
21
|
+
- Prefer the current fixed-agent model: main session coordinates through the active Host Adapter, child agents execute bounded tasks with fresh context.
|
|
22
22
|
- Do not add compatibility fallback paths for removed workflow models unless the user explicitly asks.
|
|
23
23
|
- Delete stale skills, prompts, tests, and docs when their behavior is no longer valid.
|
|
24
24
|
- Update tests that guard template contents, packaging, hooks, and task context generation.
|
|
@@ -5,27 +5,38 @@ description: Use when starting or resuming main-session work in a cowork-flow pr
|
|
|
5
5
|
|
|
6
6
|
# Start
|
|
7
7
|
|
|
8
|
-
This skill is for the main session.
|
|
8
|
+
This skill is for the main session only. Formal subagents are accepted only after runtime context binding is recorded and must not load this startup flow.
|
|
9
9
|
Main repository changes follow `Plan -> Implement -> Check -> Finish`.
|
|
10
|
-
Before loading state, classify
|
|
10
|
+
Before loading state, classify only whether the current request is a
|
|
11
|
+
main-session request, read-only question, command-only wrapper, or unclear
|
|
12
|
+
input. Do not infer subagent identity from prompt shape. If a child has
|
|
13
|
+
`cowork_runtime_context_id`, hook/plugin binding may run early; otherwise the
|
|
14
|
+
child must run the explicit shim bind before formal work.
|
|
11
15
|
|
|
12
16
|
## Load State
|
|
13
17
|
|
|
14
18
|
1. Read `AGENTS.md`.
|
|
15
19
|
2. Read `.cowork-flow/workflow.md`.
|
|
16
|
-
3.
|
|
17
|
-
4.
|
|
18
|
-
5. Read
|
|
20
|
+
3. Read `.cowork-flow/config.yaml` for Codex dispatch hints and lifecycle hooks.
|
|
21
|
+
4. Run `.cowork-flow/run resume` or `.\.cowork-flow\run.cmd resume` on Windows.
|
|
22
|
+
5. Read the active task PRD and JSONL indexes only when a task is active.
|
|
23
|
+
6. Read relevant `.cowork-flow/spec/*/index.md` files before code changes.
|
|
19
24
|
|
|
20
25
|
Report active task, workflow state, blockers, and the next phase.
|
|
21
26
|
|
|
22
27
|
## Route
|
|
23
28
|
|
|
24
|
-
Route in stages. Before state is loaded,
|
|
29
|
+
Route in stages. Before state is loaded, true question-only requests may bypass
|
|
30
|
+
Load State. Repository-changing main-session requests load state first. After
|
|
31
|
+
state is loaded, apply the requirement clarification gate from
|
|
32
|
+
`.cowork-flow/workflow.md`, then route to the next workflow phase; clear
|
|
33
|
+
multi-step implementation uses `writing-plans` before fixed-agent dispatch.
|
|
34
|
+
|
|
35
|
+
New requirements that are unclear, boundary-unclear, multi-approach, behavior-changing, or missing acceptance criteria use `brainstorming` before PRD, planning, or fixed-agent dispatch. Small repository changes proceed directly only when the goal, scope boundary, and acceptance criteria are already clear.
|
|
25
36
|
|
|
26
37
|
- Question-only work: answer directly.
|
|
27
|
-
- Small repository change: classify by `.cowork-flow/workflow.md`, create/start a task if required, then proceed.
|
|
28
|
-
- Unclear or multi-approach work: use `brainstorming`.
|
|
38
|
+
- Small repository change with clear goal/scope/acceptance: classify by `.cowork-flow/workflow.md`, create/start a task if required, then proceed.
|
|
39
|
+
- Unclear, boundary-unclear, behavior-changing, or multi-approach work: use `brainstorming`.
|
|
29
40
|
- Multi-step implementation: use `writing-plans`, then dispatch fixed agents where appropriate.
|
|
30
41
|
- Before coding: use `before-dev`.
|
|
31
42
|
- After implementation: use `check`, then `finish-work`.
|
|
@@ -41,14 +52,27 @@ Route in stages. Before state is loaded, only true question-only requests and bo
|
|
|
41
52
|
|
|
42
53
|
The main session owns coordination:
|
|
43
54
|
|
|
44
|
-
- Research: `
|
|
45
|
-
- Implementation: `
|
|
46
|
-
- Verification: `
|
|
55
|
+
- Research: dispatch `cowork-research` through the active Host Adapter.
|
|
56
|
+
- Implementation: dispatch `cowork-implement` through the active Host Adapter.
|
|
57
|
+
- Verification: dispatch `cowork-check` through the active Host Adapter.
|
|
47
58
|
|
|
48
|
-
Every dispatch
|
|
59
|
+
Every formal dispatch uses runtime-context dispatch:
|
|
49
60
|
|
|
50
61
|
```text
|
|
51
|
-
|
|
62
|
+
cowork_runtime_context_id: <runtime_context_id>
|
|
63
|
+
cowork_host_context_key: <host_context_key>
|
|
52
64
|
```
|
|
53
65
|
|
|
54
|
-
|
|
66
|
+
Before spawning a formal child, create a runtime context with
|
|
67
|
+
`.cowork-flow/run subagent init` and pass the returned prompt transport through
|
|
68
|
+
the active Host Adapter. The child's first step is
|
|
69
|
+
`.cowork-flow/run subagent bind <runtime_context_id> <host_context_key>`; if a
|
|
70
|
+
hook/plugin already bound the same key, this command is idempotent. The parent
|
|
71
|
+
must verify `status=bound` and `bound_context_key=<host_context_key>` before
|
|
72
|
+
accepting output. If binding is missing, closed, invalid, or mismatched, the
|
|
73
|
+
child fails closed and must not run start/resume/task activation/archive/commit
|
|
74
|
+
or coordinate other agents.
|
|
75
|
+
|
|
76
|
+
After dispatch, use adapter wait/list/cancel primitives, review the output,
|
|
77
|
+
verify deliverables, and close the runtime context with
|
|
78
|
+
`.cowork-flow/run subagent close <runtime_context_id>`.
|
|
@@ -27,7 +27,7 @@ Start with:
|
|
|
27
27
|
```markdown
|
|
28
28
|
# <Feature> Implementation Plan
|
|
29
29
|
|
|
30
|
-
> For
|
|
30
|
+
> For formal fixed-agent work: create a runtime context with `.cowork-flow/run subagent init`, pass `cowork_runtime_context_id: <runtime_context_id>` through the active Host Adapter, then dispatch `cowork-implement` or `cowork-check`. Close the runtime context after verification.
|
|
31
31
|
|
|
32
32
|
**Goal:** <one sentence>
|
|
33
33
|
**Architecture:** <2-3 sentences>
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cowork-check
|
|
3
|
+
description: Cowork-flow check fixed subagent.
|
|
4
|
+
tools: Read, Write, Edit, MultiEdit, Grep, Glob, LS, Bash
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
You are the `cowork-check` fixed subagent for Claude Code.
|
|
8
|
+
You are a leaf executor and must not invoke other agents.
|
|
9
|
+
|
|
10
|
+
Formal `cowork-check` work requires a bound runtime context. The prompt, host
|
|
11
|
+
metadata, or environment must provide:
|
|
12
|
+
|
|
13
|
+
```text
|
|
14
|
+
cowork_runtime_context_id: <runtime_context_id>
|
|
15
|
+
cowork_host_context_key: <host_context_key>
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
The hook may bind that id to
|
|
19
|
+
`.cowork-flow/.runtime/subagents/<runtime_context_id>.json` before workflow
|
|
20
|
+
state is injected. The first child step must still run:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
./.cowork-flow/run subagent bind <runtime_context_id> <host_context_key>
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
If the explicit bind fails, or if the bound context is missing, closed, invalid,
|
|
27
|
+
or names another agent type, report `needs_context` and stop. Do not use
|
|
28
|
+
`COWORK_ENTRY_CONTRACT_V1` to infer subagent identity; that contract classifies
|
|
29
|
+
main-session prompts only.
|
|
30
|
+
|
|
31
|
+
Rules:
|
|
32
|
+
|
|
33
|
+
- Read the task directory from the bound runtime context.
|
|
34
|
+
- Read `<task>/prd.md`, `<task>/check.jsonl`, `git diff`, and each JSONL `file`
|
|
35
|
+
entry.
|
|
36
|
+
- Check acceptance criteria, tests, scope, and spec sync.
|
|
37
|
+
- Fix in-scope issues directly when small and clear.
|
|
38
|
+
- Report findings, fixes, and exact verification commands.
|
|
39
|
+
- Do not use the Task tool or invoke subagents.
|
|
40
|
+
- Do not run task start, task finish, task archive, unscoped resume, commit, or
|
|
41
|
+
push.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cowork-implement
|
|
3
|
+
description: Cowork-flow implementation fixed subagent.
|
|
4
|
+
tools: Read, Write, Edit, MultiEdit, Grep, Glob, LS, Bash
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
You are the `cowork-implement` fixed subagent for Claude Code.
|
|
8
|
+
You are a leaf executor and must not invoke other agents.
|
|
9
|
+
|
|
10
|
+
Formal `cowork-implement` work requires a bound runtime context. The prompt,
|
|
11
|
+
host metadata, or environment must provide:
|
|
12
|
+
|
|
13
|
+
```text
|
|
14
|
+
cowork_runtime_context_id: <runtime_context_id>
|
|
15
|
+
cowork_host_context_key: <host_context_key>
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
The hook may bind that id to
|
|
19
|
+
`.cowork-flow/.runtime/subagents/<runtime_context_id>.json` before workflow
|
|
20
|
+
state is injected. The first child step must still run:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
./.cowork-flow/run subagent bind <runtime_context_id> <host_context_key>
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
If the explicit bind fails, or if the bound context is missing, closed, invalid,
|
|
27
|
+
or names another agent type, report `needs_context` and stop. Do not use
|
|
28
|
+
`COWORK_ENTRY_CONTRACT_V1` to infer subagent identity; that contract classifies
|
|
29
|
+
main-session prompts only.
|
|
30
|
+
|
|
31
|
+
Rules:
|
|
32
|
+
|
|
33
|
+
- Read the task directory from the bound runtime context.
|
|
34
|
+
- Read `<task>/prd.md`, `<task>/info.md` if present, `<task>/implement.jsonl`,
|
|
35
|
+
and each JSONL `file` entry.
|
|
36
|
+
- Keep edits inside the assigned scope.
|
|
37
|
+
- Report changed files and exact verification commands.
|
|
38
|
+
- Do not use the Task tool or invoke subagents.
|
|
39
|
+
- Do not run task start, task finish, task archive, unscoped resume, commit, or
|
|
40
|
+
push.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cowork-research
|
|
3
|
+
description: Cowork-flow research fixed subagent.
|
|
4
|
+
tools: Read, Grep, Glob, LS
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
You are the `cowork-research` fixed subagent for Claude Code.
|
|
8
|
+
You are a leaf executor and must not invoke other agents.
|
|
9
|
+
|
|
10
|
+
Formal `cowork-research` work requires a bound runtime context. The prompt,
|
|
11
|
+
host metadata, or environment must provide:
|
|
12
|
+
|
|
13
|
+
```text
|
|
14
|
+
cowork_runtime_context_id: <runtime_context_id>
|
|
15
|
+
cowork_host_context_key: <host_context_key>
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
The hook may bind that id to
|
|
19
|
+
`.cowork-flow/.runtime/subagents/<runtime_context_id>.json` before workflow
|
|
20
|
+
state is injected. The first child step must still run:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
./.cowork-flow/run subagent bind <runtime_context_id> <host_context_key>
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
If the explicit bind fails, or if the bound context is missing, closed, invalid,
|
|
27
|
+
or names another agent type, report `needs_context` and stop. Do not use
|
|
28
|
+
`COWORK_ENTRY_CONTRACT_V1` to infer subagent identity; that contract classifies
|
|
29
|
+
main-session prompts only.
|
|
30
|
+
|
|
31
|
+
Rules:
|
|
32
|
+
|
|
33
|
+
- Read the task directory and assignment from the bound runtime context.
|
|
34
|
+
- Read task context and prompt-named files only.
|
|
35
|
+
- Write research notes only under the assigned task `research/` directory when
|
|
36
|
+
explicitly asked.
|
|
37
|
+
- Report findings with file and line evidence.
|
|
38
|
+
- Do not use the Task tool or invoke subagents.
|
|
39
|
+
- Do not run task start, task finish, task archive, unscoped resume, commit, or
|
|
40
|
+
push.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Dispatch cowork-flow check fixed subagent.
|
|
3
|
+
argument-hint: "<task-dir>"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Use the `cowork-check` agent for `$ARGUMENTS`.
|
|
7
|
+
|
|
8
|
+
Before dispatch, create a runtime context:
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
./.cowork-flow/run subagent init --role check --agent-type cowork-check --execution-task-dir "$ARGUMENTS" --title "Check $ARGUMENTS" --host claude-code --adapter claude-code.subagent
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Pass the returned prompt transport to the subagent:
|
|
15
|
+
|
|
16
|
+
```text
|
|
17
|
+
cowork_runtime_context_id: <runtime_context_id>
|
|
18
|
+
cowork_host_context_key: <host_context_key>
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
The child must first bind the runtime context before role work:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
./.cowork-flow/run subagent bind <runtime_context_id> <host_context_key>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Missing, closed, invalid, mismatched, or unbound context is `needs_context`.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Dispatch cowork-flow implementation fixed subagent.
|
|
3
|
+
argument-hint: "<task-dir>"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Use the `cowork-implement` agent for `$ARGUMENTS`.
|
|
7
|
+
|
|
8
|
+
Before dispatch, create a runtime context:
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
./.cowork-flow/run subagent init --role implement --agent-type cowork-implement --execution-task-dir "$ARGUMENTS" --title "Implement $ARGUMENTS" --host claude-code --adapter claude-code.subagent
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Pass the returned prompt transport to the subagent:
|
|
15
|
+
|
|
16
|
+
```text
|
|
17
|
+
cowork_runtime_context_id: <runtime_context_id>
|
|
18
|
+
cowork_host_context_key: <host_context_key>
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
The child must first bind the runtime context before role work:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
./.cowork-flow/run subagent bind <runtime_context_id> <host_context_key>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Missing, closed, invalid, mismatched, or unbound context is `needs_context`.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Dispatch cowork-flow research fixed subagent.
|
|
3
|
+
argument-hint: "<task-dir>"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Use the `cowork-research` agent for `$ARGUMENTS`.
|
|
7
|
+
|
|
8
|
+
Before dispatch, create a runtime context:
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
./.cowork-flow/run subagent init --role research --agent-type cowork-research --execution-task-dir "$ARGUMENTS" --title "Research $ARGUMENTS" --host claude-code --adapter claude-code.subagent
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Pass the returned prompt transport to the subagent:
|
|
15
|
+
|
|
16
|
+
```text
|
|
17
|
+
cowork_runtime_context_id: <runtime_context_id>
|
|
18
|
+
cowork_host_context_key: <host_context_key>
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
The child must first bind the runtime context before role work:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
./.cowork-flow/run subagent bind <runtime_context_id> <host_context_key>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Missing, closed, invalid, mismatched, or unbound context is `needs_context`.
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Emit cowork-flow workflow context for Claude Code hooks."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import hashlib
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
import re
|
|
10
|
+
import sys
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
TAG_RE = re.compile(
|
|
15
|
+
r"\[workflow-state:([A-Za-z0-9_-]+)\]\s*\n(.*?)\n\s*\[/workflow-state:\1\]",
|
|
16
|
+
re.DOTALL,
|
|
17
|
+
)
|
|
18
|
+
DEFAULT_CONTRACT_REGISTRY = {
|
|
19
|
+
"contracts": [
|
|
20
|
+
{
|
|
21
|
+
"id": "COWORK_ENTRY_CONTRACT_V1",
|
|
22
|
+
"path": ".cowork-flow/spec/entry-contract.md",
|
|
23
|
+
"digest": [
|
|
24
|
+
"Classify main-session requests before task start, resume, archive, or commit.",
|
|
25
|
+
"Runtime context, not prompt labels, identifies formal subagent sessions.",
|
|
26
|
+
],
|
|
27
|
+
"readWhen": ["before task start/resume/archive", "when prompt and bootstrap text conflict"],
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"id": "RUNTIME_CONTEXT_DISPATCH_V2",
|
|
31
|
+
"path": ".cowork-flow/spec/subagent-dispatch.md",
|
|
32
|
+
"digest": [
|
|
33
|
+
"Formal subagent work is keyed by cowork_runtime_context_id.",
|
|
34
|
+
"Explicit shim bind records bound_context_key before formal output is accepted.",
|
|
35
|
+
],
|
|
36
|
+
"readWhen": ["before formal subagent dispatch", "when checking subagent health"],
|
|
37
|
+
},
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _find_repo_root(start: Path) -> Path | None:
|
|
43
|
+
current = start.resolve()
|
|
44
|
+
while True:
|
|
45
|
+
if (current / ".cowork-flow").is_dir():
|
|
46
|
+
return current
|
|
47
|
+
if current == current.parent:
|
|
48
|
+
return None
|
|
49
|
+
current = current.parent
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _read_hook_input() -> dict[str, Any]:
|
|
53
|
+
try:
|
|
54
|
+
raw = sys.stdin.buffer.read()
|
|
55
|
+
except AttributeError:
|
|
56
|
+
text = sys.stdin.read()
|
|
57
|
+
else:
|
|
58
|
+
if not raw.strip():
|
|
59
|
+
return {}
|
|
60
|
+
try:
|
|
61
|
+
text = raw.decode("utf-8-sig")
|
|
62
|
+
except UnicodeDecodeError:
|
|
63
|
+
encoding = getattr(sys.stdin, "encoding", None) or "utf-8"
|
|
64
|
+
text = raw.decode(encoding, errors="replace")
|
|
65
|
+
if not text.strip():
|
|
66
|
+
return {}
|
|
67
|
+
try:
|
|
68
|
+
data = json.loads(text)
|
|
69
|
+
except (json.JSONDecodeError, ValueError):
|
|
70
|
+
return {}
|
|
71
|
+
return data if isinstance(data, dict) else {}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _hook_event_name(hook_input: dict[str, Any]) -> str:
|
|
75
|
+
value = hook_input.get("hook_event_name") or hook_input.get("hookEventName")
|
|
76
|
+
if isinstance(value, str) and value.strip():
|
|
77
|
+
return value.strip()
|
|
78
|
+
return "UserPromptSubmit"
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _normalize_hook_input(hook_input: dict[str, Any]) -> dict[str, Any]:
|
|
82
|
+
normalized = dict(hook_input)
|
|
83
|
+
if "claude_session_id" not in normalized:
|
|
84
|
+
session_id = normalized.get("session_id")
|
|
85
|
+
if isinstance(session_id, str) and session_id.strip():
|
|
86
|
+
normalized["claude_session_id"] = session_id
|
|
87
|
+
return normalized
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _load_breadcrumbs(root: Path) -> dict[str, str]:
|
|
91
|
+
workflow = root / ".cowork-flow" / "spec" / "workflow-state-templates.md"
|
|
92
|
+
try:
|
|
93
|
+
text = workflow.read_text(encoding="utf-8")
|
|
94
|
+
except OSError:
|
|
95
|
+
return {}
|
|
96
|
+
return {match.group(1): match.group(2).strip() for match in TAG_RE.finditer(text)}
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _as_string_list(value: Any) -> list[str]:
|
|
100
|
+
if not isinstance(value, list):
|
|
101
|
+
return []
|
|
102
|
+
return [item for item in value if isinstance(item, str) and item.strip()]
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _load_contract_registry(root: Path) -> list[dict[str, Any]]:
|
|
106
|
+
registry_file = root / ".cowork-flow" / "spec" / "registry.json"
|
|
107
|
+
try:
|
|
108
|
+
data = json.loads(registry_file.read_text(encoding="utf-8"))
|
|
109
|
+
except (OSError, json.JSONDecodeError):
|
|
110
|
+
data = DEFAULT_CONTRACT_REGISTRY
|
|
111
|
+
contracts = data.get("contracts") if isinstance(data, dict) else None
|
|
112
|
+
if not isinstance(contracts, list):
|
|
113
|
+
contracts = DEFAULT_CONTRACT_REGISTRY["contracts"]
|
|
114
|
+
return [contract for contract in contracts if isinstance(contract, dict)]
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _contract_fingerprint(root: Path, contracts: list[dict[str, Any]]) -> str:
|
|
118
|
+
digest = hashlib.sha256()
|
|
119
|
+
digest.update(json.dumps(contracts, ensure_ascii=False, sort_keys=True).encode("utf-8"))
|
|
120
|
+
for contract in contracts:
|
|
121
|
+
path = contract.get("path")
|
|
122
|
+
if not isinstance(path, str) or not path.strip():
|
|
123
|
+
continue
|
|
124
|
+
try:
|
|
125
|
+
digest.update((root / path).read_bytes())
|
|
126
|
+
except OSError:
|
|
127
|
+
digest.update(f"missing:{path}".encode("utf-8"))
|
|
128
|
+
return digest.hexdigest()[:16]
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _build_contract_digest(root: Path) -> str:
|
|
132
|
+
contracts = _load_contract_registry(root)
|
|
133
|
+
fingerprint = _contract_fingerprint(root, contracts)
|
|
134
|
+
lines = [
|
|
135
|
+
'<cowork-runtime host="claude-code" adapter="claude-code.hooks">',
|
|
136
|
+
f'<contract-digest fingerprint="{fingerprint}">',
|
|
137
|
+
"policy: repeat this short digest every hook; read full spec files only before listed actions.",
|
|
138
|
+
]
|
|
139
|
+
for contract in contracts:
|
|
140
|
+
contract_id = contract.get("id")
|
|
141
|
+
path = contract.get("path")
|
|
142
|
+
if not isinstance(contract_id, str) or not contract_id.strip():
|
|
143
|
+
continue
|
|
144
|
+
path_text = path if isinstance(path, str) and path.strip() else "<missing-path>"
|
|
145
|
+
lines.append(f"- {contract_id}: {path_text}")
|
|
146
|
+
for item in _as_string_list(contract.get("digest"))[:2]:
|
|
147
|
+
lines.append(f" digest: {item}")
|
|
148
|
+
read_when = _as_string_list(contract.get("readWhen"))
|
|
149
|
+
if read_when:
|
|
150
|
+
lines.append(f" read_before: {'; '.join(read_when)}")
|
|
151
|
+
lines.extend(["</contract-digest>", "</cowork-runtime>"])
|
|
152
|
+
return "\n".join(lines)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def _load_common(root: Path) -> None:
|
|
156
|
+
scripts_dir = root / ".cowork-flow" / "scripts"
|
|
157
|
+
if str(scripts_dir) not in sys.path:
|
|
158
|
+
sys.path.insert(0, str(scripts_dir))
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _get_active_task(root: Path, hook_input: dict[str, Any]) -> tuple[str | None, str, str]:
|
|
162
|
+
_load_common(root)
|
|
163
|
+
try:
|
|
164
|
+
from common.active_task import get_active_task # type: ignore[import-not-found]
|
|
165
|
+
except Exception:
|
|
166
|
+
return None, "no_task", "unavailable"
|
|
167
|
+
|
|
168
|
+
active = get_active_task(root, hook_input)
|
|
169
|
+
if not active.task_path:
|
|
170
|
+
return None, "no_task", active.source
|
|
171
|
+
|
|
172
|
+
task_dir = root / active.task_path
|
|
173
|
+
task_json = task_dir / "task.json"
|
|
174
|
+
try:
|
|
175
|
+
data = json.loads(task_json.read_text(encoding="utf-8"))
|
|
176
|
+
except (OSError, json.JSONDecodeError):
|
|
177
|
+
return active.task_path, "stale", active.source
|
|
178
|
+
|
|
179
|
+
status = data.get("status")
|
|
180
|
+
if not isinstance(status, str) or not status.strip():
|
|
181
|
+
status = "unknown"
|
|
182
|
+
return active.task_path, status.strip(), active.source
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _resolve_runtime_context(root: Path, hook_input: dict[str, Any]) -> tuple[dict[str, Any] | None, str | None]:
|
|
186
|
+
_load_common(root)
|
|
187
|
+
try:
|
|
188
|
+
from common.active_task import ( # type: ignore[import-not-found]
|
|
189
|
+
bind_runtime_context,
|
|
190
|
+
read_runtime_context,
|
|
191
|
+
resolve_runtime_context_id,
|
|
192
|
+
)
|
|
193
|
+
except Exception:
|
|
194
|
+
return None, None
|
|
195
|
+
|
|
196
|
+
runtime_context_id = resolve_runtime_context_id(hook_input)
|
|
197
|
+
if not runtime_context_id:
|
|
198
|
+
return None, None
|
|
199
|
+
|
|
200
|
+
context = read_runtime_context(root, runtime_context_id)
|
|
201
|
+
if not context or context.get("scope") != "subagent" or context.get("status") == "closed":
|
|
202
|
+
return None, runtime_context_id
|
|
203
|
+
|
|
204
|
+
bound = bind_runtime_context(root, runtime_context_id, values=hook_input)
|
|
205
|
+
return bound or context, runtime_context_id
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def _subagent_runtime_lines(context: dict[str, Any]) -> list[str]:
|
|
209
|
+
assignment = context.get("assignment") if isinstance(context.get("assignment"), dict) else {}
|
|
210
|
+
lines = [
|
|
211
|
+
f"Runtime context: {context.get('runtime_context_id')}",
|
|
212
|
+
f"Agent: {context.get('agent_type') or 'unknown'}",
|
|
213
|
+
"Scope: subagent",
|
|
214
|
+
"Do not run start/resume/task start/archive/commit/spawn.",
|
|
215
|
+
]
|
|
216
|
+
goal = assignment.get("goal") if isinstance(assignment, dict) else None
|
|
217
|
+
if isinstance(goal, str) and goal.strip():
|
|
218
|
+
lines.append(f"Goal: {goal.strip()}")
|
|
219
|
+
return lines
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def _build_context(
|
|
223
|
+
root: Path,
|
|
224
|
+
task_path: str | None,
|
|
225
|
+
status: str,
|
|
226
|
+
source: str,
|
|
227
|
+
breadcrumbs: dict[str, str],
|
|
228
|
+
extra_lines: list[str] | None = None,
|
|
229
|
+
) -> str:
|
|
230
|
+
body = breadcrumbs.get(status) or "Refer to .cowork-flow/workflow.md for the current step."
|
|
231
|
+
if extra_lines:
|
|
232
|
+
body = "\n".join([body, *extra_lines])
|
|
233
|
+
if task_path is None:
|
|
234
|
+
header = f"Status: {status}\nSource: {source}"
|
|
235
|
+
else:
|
|
236
|
+
header = f"Task: {task_path}\nStatus: {status}\nSource: {source}"
|
|
237
|
+
|
|
238
|
+
return "\n\n".join(
|
|
239
|
+
[
|
|
240
|
+
"<claude-code-runtime>\nhooks: UserPromptSubmit, SessionStart\n</claude-code-runtime>",
|
|
241
|
+
_build_contract_digest(root),
|
|
242
|
+
f"<workflow-state>\n{header}\n{body}\n</workflow-state>",
|
|
243
|
+
]
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def main() -> int:
|
|
248
|
+
if os.environ.get("COWORK_FLOW_HOOKS") == "0" or os.environ.get("COWORK_FLOW_DISABLE_HOOKS") == "1":
|
|
249
|
+
return 0
|
|
250
|
+
|
|
251
|
+
hook_input = _normalize_hook_input(_read_hook_input())
|
|
252
|
+
cwd_value = hook_input.get("cwd")
|
|
253
|
+
cwd = Path(cwd_value) if isinstance(cwd_value, str) and cwd_value.strip() else Path.cwd()
|
|
254
|
+
root = _find_repo_root(cwd)
|
|
255
|
+
if root is None:
|
|
256
|
+
return 0
|
|
257
|
+
|
|
258
|
+
event_name = _hook_event_name(hook_input)
|
|
259
|
+
breadcrumbs = _load_breadcrumbs(root)
|
|
260
|
+
runtime_context, runtime_context_id = (
|
|
261
|
+
_resolve_runtime_context(root, hook_input) if event_name == "UserPromptSubmit" else (None, None)
|
|
262
|
+
)
|
|
263
|
+
extra_lines: list[str] | None = None
|
|
264
|
+
if runtime_context is not None:
|
|
265
|
+
task_dir = runtime_context.get("task_dir")
|
|
266
|
+
task_path = task_dir.strip() if isinstance(task_dir, str) and task_dir.strip() else None
|
|
267
|
+
status = "delegated_subtask"
|
|
268
|
+
source = f"runtime-context:{runtime_context.get('runtime_context_id')}"
|
|
269
|
+
extra_lines = _subagent_runtime_lines(runtime_context)
|
|
270
|
+
elif runtime_context_id:
|
|
271
|
+
task_path = None
|
|
272
|
+
status = "delegated_subtask"
|
|
273
|
+
source = f"runtime-context-invalid:{runtime_context_id}"
|
|
274
|
+
extra_lines = [
|
|
275
|
+
f"Runtime context: {runtime_context_id}",
|
|
276
|
+
"Scope: subagent",
|
|
277
|
+
"Runtime context is missing, closed, or invalid. Do not run start/resume/task start/archive/commit/spawn.",
|
|
278
|
+
]
|
|
279
|
+
else:
|
|
280
|
+
task_path, status, source = _get_active_task(root, hook_input)
|
|
281
|
+
additional_context = _build_context(root, task_path, status, source, breadcrumbs, extra_lines)
|
|
282
|
+
|
|
283
|
+
print(
|
|
284
|
+
json.dumps(
|
|
285
|
+
{
|
|
286
|
+
"hookSpecificOutput": {
|
|
287
|
+
"hookEventName": event_name,
|
|
288
|
+
"additionalContext": additional_context,
|
|
289
|
+
}
|
|
290
|
+
},
|
|
291
|
+
ensure_ascii=False,
|
|
292
|
+
)
|
|
293
|
+
)
|
|
294
|
+
return 0
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
if __name__ == "__main__":
|
|
298
|
+
raise SystemExit(main())
|