cowork-flow 1.9.2 → 2.0.0

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 (61) hide show
  1. package/CHANGELOG.md +23 -1
  2. package/docs/architecture.md +1 -34
  3. package/docs/direction.md +1 -1
  4. package/docs/hosts.md +13 -9
  5. package/package.json +1 -1
  6. package/presets/claude-code/.claude-plugin/plugin.json +1 -1
  7. package/presets/codex/.codex-plugin/plugin.json +1 -1
  8. package/presets/dsh/plugins/workflow-state.js +31 -16
  9. package/presets/kimi-code/.kimi-plugin/plugin.json +1 -1
  10. package/presets/opencode/cowork-flow/plugin-core.js +42 -87
  11. package/presets/opencode/plugins/cowork-flow.js +7 -9
  12. package/presets/qoder/.qoder-plugin/plugin.json +1 -1
  13. package/presets/qoder/hooks/hooks.json +1 -1
  14. package/presets/zcode/.zcode-plugin/plugin.json +1 -1
  15. package/src/commands/host.js +23 -2
  16. package/src/commands/registry.js +4 -2
  17. package/template/.cowork-flow/.version +1 -1
  18. package/template/.cowork-flow/adapters/claude-code/adapter.yaml +1 -1
  19. package/template/.cowork-flow/adapters/codex/adapter.yaml +1 -1
  20. package/template/.cowork-flow/adapters/dsh/adapter.yaml +1 -1
  21. package/template/.cowork-flow/adapters/opencode/adapter.yaml +1 -1
  22. package/template/.cowork-flow/adapters/qoder/adapter.yaml +1 -1
  23. package/template/.cowork-flow/scripts/adapters/cli/task_lifecycle_commands.py +0 -1
  24. package/template/.cowork-flow/scripts/adapters/cli/task_parser.py +0 -7
  25. package/template/.cowork-flow/scripts/adapters/host/claude_code_policy.py +6 -4
  26. package/template/.cowork-flow/scripts/adapters/host/codex_policy.py +8 -6
  27. package/template/.cowork-flow/scripts/adapters/host/edit_scope.py +113 -0
  28. package/template/.cowork-flow/scripts/adapters/host/opencode_policy.py +12 -0
  29. package/template/.cowork-flow/scripts/adapters/host/qoder_policy.py +7 -5
  30. package/template/.cowork-flow/scripts/adapters/host/workflow_state_hook.py +0 -91
  31. package/template/.cowork-flow/scripts/adapters/host/zcode_policy.py +15 -77
  32. package/template/.cowork-flow/scripts/run.py +0 -2
  33. package/template/.cowork-flow/scripts/services/context_paths.py +2 -2
  34. package/template/.cowork-flow/scripts/services/fact_view.py +49 -21
  35. package/template/.cowork-flow/scripts/services/lifecycle_checks.py +1 -216
  36. package/template/.cowork-flow/scripts/services/lifecycle_policy.py +0 -5
  37. package/template/.cowork-flow/scripts/services/task_gate_profiles.py +1 -1
  38. package/template/.cowork-flow/scripts/services/task_lifecycle.py +14 -31
  39. package/template/.cowork-flow/spec/backend/error-handling.md +0 -14
  40. package/template/.cowork-flow/spec/contracts/context-injection.md +49 -22
  41. package/template/.cowork-flow/spec/contracts/decision-anchor.md +3 -3
  42. package/template/.cowork-flow/spec/contracts/error-codes.md +1 -3
  43. package/template/.cowork-flow/spec/contracts/evidence-completion.md +6 -8
  44. package/template/.cowork-flow/spec/contracts/index.md +0 -1
  45. package/template/.cowork-flow/spec/contracts/plan-binding.md +3 -2
  46. package/template/.cowork-flow/spec/contracts/subagent-dispatch.md +6 -21
  47. package/template/.cowork-flow/spec/contracts/task-adopt.md +1 -1
  48. package/template/.cowork-flow/spec/runtime/contract-registry.json +0 -13
  49. package/template/.cowork-flow/spec/runtime/host-assets.json +10 -7
  50. package/template/.cowork-flow/spec/runtime/scope-rules.json +2 -2
  51. package/template/.opencode/cowork-flow/plugin-core.js +42 -87
  52. package/template/.opencode/plugins/cowork-flow.js +7 -9
  53. package/template/AGENTS.md +0 -1
  54. package/template/skills/agent-dispatch/SKILL.md +0 -15
  55. package/template/skills/runtime-health/scripts/doctor.py +0 -49
  56. package/template/skills/spec-sync/SKILL.md +0 -1
  57. package/template/skills/task-planning/SKILL.md +1 -1
  58. package/template/skills/task-review/SKILL.md +3 -3
  59. package/template/.cowork-flow/scripts/adapters/cli/spec_check.py +0 -178
  60. package/template/.cowork-flow/scripts/services/spec_check.py +0 -573
  61. package/template/.cowork-flow/spec/contracts/spec-checks.md +0 -281
@@ -1,281 +0,0 @@
1
- # Spec Check Contract (spec-declared commands)
2
-
3
- ## What it does
4
-
5
- User specs (the `backend/` and `frontend/` directories under `.cowork-flow/spec/`,
6
- plus any project-owned directory) may declare check commands in frontmatter under
7
- `checks:`. The mechanism only executes declarations and never parses spec prose —
8
- so a rule change is a command change in the same file, and the two stay in sync by
9
- construction.
10
-
11
- ## When to attach a declaration
12
-
13
- A clause is worth a `checks:` declaration whenever a command exit code can decide
14
- it (lint / tests / type check / schema validation). Two hard constraints:
15
-
16
- - **A declared command that does not exist = `unchecked` = blocks `complete`**:
17
- confirm the command runs in the current repository before declaring it.
18
- - **edit-only is not a gate**: `when: edit` is a best-effort edit-time hint only —
19
- Bash writes bypass PostToolUse, and hosts without an edit-time fast path
20
- (see Appendix A) receive none of this feedback; the closeout phase does not
21
- re-run it either. A check that must hard-block has to declare
22
- `when: lifecycle`, or the default `both`.
23
-
24
- ## Syntax
25
-
26
- ```markdown
27
- ---
28
- checks:
29
- - cmd: npm run lint --silent
30
- files: "src/" # optional: scope, a directory prefix or an extension ("*.ts"), comma-separated
31
- timeout: 60 # optional: seconds, default 30, hard cap 120
32
- when: both # optional: edit | lifecycle | both (default both)
33
- cmd.win: npm run lint # optional: Windows override command
34
- ---
35
-
36
- # Spec prose (the mechanism never reads it)
37
- ```
38
-
39
- - `files` accepts exactly two forms: a directory prefix (`src/`, ending in `/` or
40
- auto-completed to it) and an extension (`*.ts`). Full glob forms (`**`, `[]`)
41
- parse as an error, the declaration does not run, and doctor reports it — never a
42
- fallback to "match everything"; for multi-value declarations
43
- (`files: "src/", "lib/"`) each token is unquoted under the same quoting rule
44
- before it takes effect.
45
- - Quoting rule: a value is unquoted only when the whole value is a single quoted
46
- pair; command values carrying several quoted pairs, such as
47
- `"python" -c "..."`, are kept verbatim and split by the executor under shell
48
- rules.
49
- - `when: edit` runs at edit time (PostToolUse) on the changed files that hit
50
- `files`, with the timeout clamped to 2.5 seconds (spawn budget for interpreter
51
- startup and the cmd wrapper); slow commands should declare `when: lifecycle`.
52
- - **The file must be UTF-8 without BOM**: frontmatter starting with a BOM is
53
- silently ignored by the parser (zero declarations, no parse error) — the
54
- declaration fails without a word. After declaring, run `spec-check` once to
55
- confirm it was parsed.
56
-
57
- ### From zero to the first check (copy-paste)
58
-
59
- 1. Create a spec file under `.cowork-flow/spec/` (a project-owned subdirectory, or
60
- directly at the root, e.g. `spec/team-gate.md`). `contracts/`, `runtime/`, and
61
- `schemas/` are machine directories and are not scanned; every other level and
62
- subdirectory is. Save the file as UTF-8 without BOM.
63
- 2. Copy the block below into it, replace `cmd` with a command that really runs in
64
- this repository, and narrow `files` to the layer that spec governs:
65
-
66
- ```markdown
67
- ---
68
- checks:
69
- - cmd: npm run lint --silent
70
- files: "src/"
71
- when: both
72
- ---
73
-
74
- # Team spec prose (the mechanism never reads it)
75
- ```
76
-
77
- 3. Run `./.cowork-flow/run spec-check`: you should see
78
- `1 passed, 0 violations, 0 unchecked` instead of `no checks declared`. Then
79
- break the rule once on purpose and confirm it really reports a `violation` — a
80
- declaration that always passes is decoration, not a check.
81
-
82
- **The command prints violations only; the framework adds the
83
- `spec-check[<spec>] violation:` prefix**: a `cmd` that prints that prefix itself
84
- stacks on top of the framework's, producing
85
- `spec-check[team-gate.md] violation: spec-check[team-gate.md] violation: ...`.
86
- A command's only duties are an exit code (0 = pass, non-zero = violation) and
87
- readable output; the prefix, the classification, the three-state verdict, and the
88
- throttle all live on the framework side.
89
-
90
- ## Three states and the gate
91
-
92
- | State | Meaning | complete gate |
93
- | --- | --- | --- |
94
- | `pass` | exit code 0 | passes |
95
- | `violation` | non-zero exit code | **blocks** (status does not advance) |
96
- | `unchecked` | command missing / interpreter missing / timeout | **blocks**; an explicit `--allow-unchecked` lets it through and records the exemption in task.json `meta.specCheckExempt` (item by item, see below) |
97
-
98
- `unchecked` never masquerades as `pass` ("did not run" is not "passed"); malformed
99
- frontmatter treats that spec as having no checks and lands in the doctor report
100
- without breaking the flow. A parse failure is a signal separate from the three
101
- states: declaration parse errors such as an illegal `files` form belong to
102
- `parseErrors`, are not folded into `pass`/`unchecked`, and exit with code 2 like
103
- `unchecked`. Text mode and `--json` must use the same exit code, and text mode
104
- must print the parse-error lines (`spec-check: N parse error(s): <spec>: <error>`)
105
- — printing only `0 passed, 0 violations, 0 unchecked` would make a malformed
106
- declaration look like a clean pass.
107
-
108
- Exit code contract: `0` everything passed; `1` a violation exists; `2` unchecked
109
- entries, `parseErrors`, or an executor failure exist. `--allow-unchecked` waives
110
- only the `unchecked` block, never a parse error.
111
-
112
- **A whole-executor failure is a separate block**: when the spec-check module fails
113
- to import or raises, the closeout gate degrades to `summary.executorError` set plus
114
- `classification: executor-failure` plus exit code 2, and blocks `complete` with
115
- `SPEC-CHECK-EXECUTOR-ERROR`. It does **not** accept `--allow-unchecked` — that flag
116
- waives "a declared command could not run", not "the checker itself broke"; the
117
- synthetic `unchecked: 1` in the degraded report is only a counting placeholder, is
118
- not reported as its own entry, and the block message does not mention the flag.
119
-
120
- **An exemption record must name what it waived**: `meta.specCheckExempt` =
121
- `{"at", "flag": "--allow-unchecked", "by": <actor or null>, "unchecked": N,
122
- "uncheckedItems": [{"spec", "cmd"}…], "truncated"?}`; `meta.evidenceExempt` =
123
- `{"at", "flag": "--allow-missing-evidence", "by": <actor or null>,
124
- "missing": [<AC id>…]}`. The item list is capped at 10 entries; beyond that
125
- `truncated: true` is set. A single command is clipped at 80 characters and **never
126
- splits the first token** (that token is the program name — which is precisely the
127
- answer to "what went unverified"). `by` is the resolved session actor, or `null`
128
- when none resolves — **not** a stand-in borrowed from `creator`/`assignee`. The
129
- completion output and the archive output print **the same rows of the same record**
130
- (the completion output reads the `task.json` just written), so "what was recorded"
131
- and "what was printed" cannot tell two different stories; with no exemption, no
132
- exemption rows are printed at all.
133
-
134
- **Every run carries one classification fact**: `summary.classification` ∈
135
- `parse-error` / `executor-failure` / `zero-declaration` / `violation` /
136
- `unchecked` / `pass` / `empty`, queryable directly through `--json`. `empty` is a
137
- legitimate state: declarations exist but none ran in this phase (for example
138
- `when: edit` declarations under a lifecycle run). Reading a report older than that
139
- field goes through `report_classification()`, which returns `legacy` — a missing
140
- field is itself a fact, and is never reverse-engineered from the counts (that
141
- would promote an old report into a guess).
142
-
143
- **Zero declarations is a legitimate state, but it must be visible**: a repository
144
- with no effective declarations exits with code `0`, and text mode prints the
145
- standalone fact line `spec-check: no checks declared (<N> spec files scanned)`,
146
- distinguishable from `spec-check: <n> passed, 0 violations, 0 unchecked`; `--json`
147
- carries `specFiles` at the top level and `declarations` in the summary. Zero
148
- declarations ≠ clean pass — it means the spec chain currently holds no
149
- machine-checkable constraint, a fact that needs to be seen rather than a green
150
- that can be claimed.
151
-
152
- ## Timing and output
153
-
154
- - **Edit time** (PostToolUse): runs only for `when: edit`/`both` when the change
155
- hits `files`, printing a single
156
- `spec-check[<spec>] violation: <first violation line>`; a missing executor or a
157
- timeout stays silent and never blocks the edit stream. Host differences and
158
- throttle details are in Appendix A.
159
- - **Closeout** (task complete): full run, results recorded in the
160
- `meta.specCheckSummary` telemetry; the block message lists only a summary of the
161
- failing entries. Exemptions must also be **visible**: while
162
- `--allow-unchecked` / `--allow-missing-evidence` apply, the completion output
163
- prints that record item by item (an `Exempted: …` header line plus
164
- ` - <spec>: <cmd>` / ` - <AC id>` detail rows, naming
165
- `meta.specCheckExempt` / `meta.evidenceExempt` and the actor), and the archive
166
- output prints the same rows again — an exemption written only into task.json is a
167
- clean pass to whoever is watching the command.
168
- - **Manual query**: `./.cowork-flow/run spec-check [--file <path>] [--json]
169
- [--verbose]` (exit codes as above).
170
-
171
- ## Declarations belong to the consumer
172
-
173
- - **The template ships no declarations**: the spec files the template carries
174
- contain no effective `checks:` declaration — the template cannot know a
175
- project's commands, and a missing command lands in `unchecked` and blocks
176
- `complete`. Declare them in project-owned spec files (e.g. `spec/team-xxx.md` or
177
- a project-owned subdirectory); sync never touches those, and the declarations are
178
- maintained by the consumer alongside the spec prose.
179
- - **sync-protected prefix and the named exception**: `spec/` as a whole is a
180
- sync-protected prefix, so existing spec files are not overwritten by `sync`
181
- (spec updates in the template only take effect for a fresh `init`); the single
182
- named exception is the entries under `spec/contracts/` listed in
183
- `spec/runtime/host-assets.json` `syncPolicy.safeFiles` (this file and
184
- `workflow-state-templates.md`), which do track sync so contract corrections can
185
- reach installed projects.
186
- - **Recognizing clauses and attaching declarations is the main-session AI's job**:
187
- declarations belong to the project. During review / spec maintenance, the main
188
- session AI identifies machine-checkable clauses and adds the `checks:`
189
- declaration (it does not rewrite the clause prose on its own — a conflict between
190
- a clause and the implementation goes through the review flow for the user to
191
- decide); only the main session writes specs, and a delegated subagent only
192
- reports candidates without exceeding its scope. The full duty is in the
193
- `task-review` and `spec-sync` Skills.
194
- - **Four quality disciplines**: ① produce a `pass` on a clean tree; ② prove the
195
- check really can fail with one negative case (not an always-passing decoration);
196
- ③ narrow the coverage with `files`; ④ run `spec-check` once after declaring to
197
- confirm it was parsed (guards against silent BOM / `files`-form failures). The
198
- review report lists the `clause → command → negativeProof` triple: `clause` (the
199
- clause), `command` (the deciding command), `negativeProof` (what the command
200
- reports when that clause is broken) — the same key set `task-review`'s Review
201
- Report Format and `subagent check-review --finding` use. A clause no command can
202
- decide must state why (`command`/`negativeProof` are `null`) instead of leaving
203
- the row blank; a clause that is machine-checkable yet yields no command is itself
204
- a finding.
205
-
206
- ## Appendix A: host capability matrix
207
-
208
- | Host | digest injection | edit-time fast path | closeout enforcement |
209
- | --- | --- | --- | --- |
210
- | zcode | yes (Specs line h2 digest) | yes (PostToolUse short path; **main session only**) | yes |
211
- | claude-code | yes (Python single source) | yes (PostToolUse, exit 2 feedback) | yes |
212
- | codex | yes | yes (PostToolUse, `apply_patch\|Write\|Edit` → exit 2 stderr) | yes |
213
- | opencode | yes | yes (plugin `tool.execute.after` appends one line to the tool result) | yes |
214
- | dsh | yes (preset injection) | yes (preset plugin `tools/post-execute` → `additionalContexts`) | yes |
215
- | kimi-code | yes (UserPromptSubmit hook → `inject.py --host kimi-code`) | no (no PostToolUse registered; observational-event stdout is discarded) | yes |
216
- | qoder | yes (plugin hook → `inject.py --host qoder`) | yes (`PostToolUse` non-blocking event, warning via `additionalContext`, exit 0) | yes |
217
-
218
- zcode / codex / claude-code share `run_edit_checks` through the Python single
219
- source; opencode pulls the same executor from inside its plugin via the CLI
220
- (`run spec-check --phase edit --throttled`), dsh calls the same Python protocol
221
- through its preset plugin's `tools/post-execute`, and qoder calls the same executor
222
- through its plugin hook's `PostToolUse` (that event is not blockable on Qoder, so
223
- the warning comes back as `additionalContext` with exit 0 rather than
224
- claude-code/codex's stderr + exit 2) — the six hosts with an edit-time fast path
225
- share one throttle state and one three-state semantics instead of implementing
226
- check logic each; kimi-code only gets closeout enforcement and the subagent
227
- self-check.
228
-
229
- Edit-time throttle: the same file is reported at most once per 10 seconds; the
230
- throttle state lives in `.cowork-flow/.runtime/spec-edit-throttle.json`, and the
231
- window is written only after the check ran to completion — a crashing executor
232
- does not consume the window and a retry still executes. Delegated subagent sessions
233
- receive spec-violation feedback too (subagents are the main code writers); scope
234
- warnings stay main-only. The zcode main session's hook identity (the conversation's
235
- own session id) is never bound — activation happens under an explicit identity on
236
- the Bash side — so display and edit-time warnings fall back to the latest main
237
- session; claude/codex keep a strict session identity (their Bash env carries the
238
- same session id). zcode forwards through a shim to the single-source entrypoint
239
- (`inject.py`), merging scope warnings and spec warnings into one additionalContext
240
- payload; claude uses exit 2 stderr feedback.
241
-
242
- ## Appendix B: the delegated edit-time gap and its compensation
243
-
244
- **Delegated subagent edit-time gap (measured on zcode, two rounds on 2026-09-09)**:
245
- the ZCode plugin hook only dispatches on the main-session tool stream (all seven
246
- event types attach to the main session; a subagent is a separate internal session
247
- stream, `sess_subagent_agent_*`), so an Agent child's Edit/Write never receives the
248
- `spec-check[...] violation` line — runtime binding and check logic are both fine;
249
- what is missing is the host dispatch. The compensating mechanisms (frozen in the
250
- subagent-dispatch.md contract):
251
-
252
- 1. Self-check before a subagent finishes: run `./.cowork-flow/run spec-check` in
253
- full and fix everything (the pull path; verified to work);
254
- 2. A second check before the parent session accepts: violations count as acceptance
255
- blockers (feedback moves from closeout up to return);
256
- 3. Implementation tasks' Verify commands include the spec-check self-check by
257
- default;
258
- 4. The closeout hard gate still backstops everything unchanged (a violation blocks
259
- `complete`).
260
-
261
- Governance direction: ask the host to extend hook dispatch to the subagent tool
262
- stream; once delivered, item 1 degrades automatically into a redundant safety net.
263
-
264
- ## Appendix C: digest injection
265
-
266
- After entering `in_progress`, the stage-contract Specs line upgrades to
267
- `Specs: <spec>(<h2 heading tree>); …`: at most 6 h2 entries, each truncated to 24
268
- characters with `();` characters stripped; a missing file adds no annotation. The
269
- digest is an entry-name index, not the prose, and is hard-capped by
270
- scope-rules.json `stageContract.budget`; over budget, the whole line yields
271
- (Scope/Gates lines are never dropped). The python/zcode output comes from a single
272
- Python entrypoint, locked by the `test/stage-contract.test.js` matrix; opencode
273
- transports that same entrypoint's output, so byte-exact equality holds by
274
- construction.
275
-
276
- ## Appendix D: open decision
277
-
278
- **Closeout aggregation budget**: a closeout runs everything with no aggregation
279
- budget — repositories with many slow declarations get linearly slower at
280
- `complete`. Whether to give closeout parallelism or a budget ceiling is left to a
281
- later product decision; today it stays simple and serial.