cowork-flow 1.9.2 → 2.1.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 (91) hide show
  1. package/CHANGELOG.md +45 -1
  2. package/README.en.md +1 -1
  3. package/README.md +1 -1
  4. package/docs/architecture.md +1 -34
  5. package/docs/direction.md +4 -3
  6. package/docs/hosts.md +14 -10
  7. package/docs/index.md +1 -1
  8. package/docs/release.md +1 -1
  9. package/package.json +6 -6
  10. package/presets/claude-code/.claude-plugin/plugin.json +1 -1
  11. package/presets/codex/.codex-plugin/plugin.json +1 -1
  12. package/presets/dsh/plugins/workflow-state.js +72 -24
  13. package/presets/kimi-code/.kimi-plugin/plugin.json +1 -1
  14. package/presets/kimi-code/hooks/cowork-flow-inject.mjs +28 -2
  15. package/presets/opencode/cowork-flow/plugin-core.js +70 -88
  16. package/presets/opencode/plugins/cowork-flow.js +7 -9
  17. package/presets/qoder/.qoder-plugin/plugin.json +1 -1
  18. package/presets/qoder/hooks/hooks.json +1 -1
  19. package/presets/qoder/hooks/inject-context.mjs +28 -2
  20. package/presets/zcode/.zcode-plugin/plugin.json +1 -1
  21. package/presets/zcode/hooks/inject-context.mjs +28 -3
  22. package/src/commands/host.js +23 -2
  23. package/src/commands/install-dsh-hook.js +1 -6
  24. package/src/commands/install-dsh-preset.js +1 -6
  25. package/src/commands/install-zcode-plugin.js +2 -3
  26. package/src/commands/registry.js +4 -2
  27. package/src/lib/asset-plan.js +1 -1
  28. package/src/lib/dsh-home.js +6 -0
  29. package/src/lib/plan-applier.js +1 -6
  30. package/template/.claude/agents/cowork-check.md +1 -1
  31. package/template/.claude/agents/cowork-implement.md +1 -1
  32. package/template/.claude/agents/cowork-research.md +1 -1
  33. package/template/.codex/agents/cowork-check.toml +1 -1
  34. package/template/.codex/agents/cowork-implement.toml +1 -1
  35. package/template/.codex/agents/cowork-research.toml +1 -1
  36. package/template/.codex/agents/default.toml +1 -1
  37. package/template/.codex/agents/explorer.toml +1 -1
  38. package/template/.codex/agents/worker.toml +1 -1
  39. package/template/.cowork-flow/.version +1 -1
  40. package/template/.cowork-flow/adapters/claude-code/adapter.yaml +1 -1
  41. package/template/.cowork-flow/adapters/codex/adapter.yaml +1 -1
  42. package/template/.cowork-flow/adapters/dsh/adapter.yaml +1 -1
  43. package/template/.cowork-flow/adapters/opencode/adapter.yaml +1 -1
  44. package/template/.cowork-flow/adapters/qoder/adapter.yaml +1 -1
  45. package/template/.cowork-flow/run +2 -2
  46. package/template/.cowork-flow/run.cmd +3 -3
  47. package/template/.cowork-flow/scripts/adapters/cli/task_lifecycle_commands.py +0 -1
  48. package/template/.cowork-flow/scripts/adapters/cli/task_parser.py +0 -7
  49. package/template/.cowork-flow/scripts/adapters/host/claude_code_policy.py +6 -4
  50. package/template/.cowork-flow/scripts/adapters/host/codex_policy.py +8 -6
  51. package/template/.cowork-flow/scripts/adapters/host/edit_scope.py +113 -0
  52. package/template/.cowork-flow/scripts/adapters/host/opencode_policy.py +12 -0
  53. package/template/.cowork-flow/scripts/adapters/host/qoder_policy.py +7 -5
  54. package/template/.cowork-flow/scripts/adapters/host/workflow_state_hook.py +0 -91
  55. package/template/.cowork-flow/scripts/adapters/host/zcode_policy.py +15 -77
  56. package/template/.cowork-flow/scripts/run.py +0 -2
  57. package/template/.cowork-flow/scripts/services/context_paths.py +2 -2
  58. package/template/.cowork-flow/scripts/services/fact_view.py +49 -21
  59. package/template/.cowork-flow/scripts/services/lifecycle_checks.py +1 -216
  60. package/template/.cowork-flow/scripts/services/lifecycle_policy.py +0 -5
  61. package/template/.cowork-flow/scripts/services/task_gate_profiles.py +1 -1
  62. package/template/.cowork-flow/scripts/services/task_lifecycle.py +14 -31
  63. package/template/.cowork-flow/spec/backend/error-handling.md +0 -14
  64. package/template/.cowork-flow/spec/contracts/context-injection.md +53 -26
  65. package/template/.cowork-flow/spec/contracts/decision-anchor.md +3 -3
  66. package/template/.cowork-flow/spec/contracts/error-codes.md +1 -3
  67. package/template/.cowork-flow/spec/contracts/evidence-completion.md +6 -8
  68. package/template/.cowork-flow/spec/contracts/index.md +0 -1
  69. package/template/.cowork-flow/spec/contracts/plan-binding.md +3 -2
  70. package/template/.cowork-flow/spec/contracts/subagent-dispatch.md +6 -21
  71. package/template/.cowork-flow/spec/contracts/task-adopt.md +1 -1
  72. package/template/.cowork-flow/spec/runtime/contract-registry.json +0 -13
  73. package/template/.cowork-flow/spec/runtime/host-assets.json +10 -7
  74. package/template/.cowork-flow/spec/runtime/scope-rules.json +2 -2
  75. package/template/.kimi-code/agents/cowork-check.md +1 -1
  76. package/template/.kimi-code/agents/cowork-implement.md +1 -1
  77. package/template/.kimi-code/agents/cowork-research.md +1 -1
  78. package/template/.opencode/agents/cowork-check.md +1 -1
  79. package/template/.opencode/agents/cowork-implement.md +1 -1
  80. package/template/.opencode/agents/cowork-research.md +1 -1
  81. package/template/.opencode/cowork-flow/plugin-core.js +70 -88
  82. package/template/.opencode/plugins/cowork-flow.js +7 -9
  83. package/template/AGENTS.md +0 -1
  84. package/template/skills/agent-dispatch/SKILL.md +0 -15
  85. package/template/skills/runtime-health/scripts/doctor.py +83 -58
  86. package/template/skills/spec-sync/SKILL.md +0 -1
  87. package/template/skills/task-planning/SKILL.md +1 -1
  88. package/template/skills/task-review/SKILL.md +3 -3
  89. package/template/.cowork-flow/scripts/adapters/cli/spec_check.py +0 -178
  90. package/template/.cowork-flow/scripts/services/spec_check.py +0 -573
  91. package/template/.cowork-flow/spec/contracts/spec-checks.md +0 -281
@@ -52,7 +52,7 @@ translated heading would simply stop being read.
52
52
  fact view (`services/fact_view.parse_decision_anchor`). Line-format conventions:
53
53
 
54
54
  - `## 验证命令`: one command per line (an optional `- ` prefix), truncated to 120
55
- characters, at most 5 lines; the implementing agent runs them itself
55
+ characters, at most 10 lines; the implementing agent runs them itself
56
56
  (self-declared, self-checked) and the reviewer verifies the execution evidence.
57
57
  - `## 范围边界`: only the first non-empty line is taken, truncated to 160
58
58
  characters.
@@ -110,14 +110,14 @@ After merging (5 files): `decision-anchor.md` + `implement.jsonl` + `check.jsonl
110
110
  component.
111
111
  A **consequence** (expected cost, not a defect): **any byte change in
112
112
  `implement.jsonl`** (appending or removing a scope entry, rewording a `reason`,
113
- a domain-guide entry auto-appended by `task context add`) **or a rewrite of a
113
+ a domain-guide entry) **or a rewrite of a
114
114
  referenced spec's prose invalidates an existing receipt**, requiring one more
115
115
  review run against the final state. Appending evidence, ticking ACs, and runtime
116
116
  metadata are not in that set.
117
117
  - **Light profile `meta.taskType: "tiny"`**: applies to `strict` tasks only
118
118
  (`legacy` + `tiny` stays a compatibility path). A light task **is still strict**
119
119
  — what it waives are three things: the review receipt, AC evidence, and
120
- `dev_type`. It does **not** waive spec-check, the file-scope gate, or "AC rows
120
+ `dev_type`. It does **not** waive the file-scope gate or "AC rows
121
121
  are non-empty" (`LIFECYCLE-AC-003`/`004` still apply). Plan-file binding is not
122
122
  among the three, but the pre-existing field-level exemption for tiny applies, so
123
123
  a light task does not require `planFile` either. `tiny` **can only be declared at
@@ -44,6 +44,4 @@ to them:
44
44
  ## Exemptions
45
45
 
46
46
  `LIFECYCLE-AC-002` is the only code in this set a flag can waive
47
- (`--allow-missing-evidence`); spec-check's `--allow-unchecked` waives `unchecked`
48
- declarations, not a `LIFECYCLE-*` code. Details are in `evidence-completion.md` and
49
- `spec-checks.md`.
47
+ (`--allow-missing-evidence`). Details are in `evidence-completion.md`.
@@ -40,7 +40,7 @@ direct lifecycle checks:
40
40
  | `LIFECYCLE-AC-001` | an unchecked AC row exists | none — do the work, or tick the completed rows |
41
41
  | `LIFECYCLE-AC-002` | a declared AC has no valid evidence record | `--allow-missing-evidence` (recorded in task.json `meta.evidenceExempt`, with `at`, `flag`, `by`, and the `missing` detail list) |
42
42
  | `LIFECYCLE-AC-003` | `gateProfile=strict` and decision-anchor yields no AC row | none — a strict task was already required to declare ACs at start, so reading none at completion has only three explanations: the anchor was rewritten, the task was created wrongly, or the anchor is undecodable (corrupted encoding) |
43
- | `LIFECYCLE-AC-004` | `gateProfile=strict` and the ac_evidence module cannot be imported | none — same family as `SPEC-CHECK-EXECUTOR-ERROR`: a broken module is not "nothing to check"; the message carries the original exception, and you rerun once the runtime install is fixed |
43
+ | `LIFECYCLE-AC-004` | `gateProfile=strict` and the ac_evidence module cannot be imported | none — a broken module is not "nothing to check"; the message carries the original exception, and you rerun once the runtime install is fixed |
44
44
 
45
45
  - The first two codes can appear together; `--allow-missing-evidence` waives
46
46
  `LIFECYCLE-AC-002` only, never `003`/`004`.
@@ -51,8 +51,8 @@ direct lifecycle checks:
51
51
  - **Strict path**: a `gateProfile=strict` task re-verifies AC existence and
52
52
  evidence-module availability at completion — the start-time AC declaration
53
53
  requirement does not lapse because completion cannot read it.
54
- - The `meta.evidenceExempt` trace has the same shape as spec-check's
55
- `specCheckExempt`: retries do not accumulate, the latest completion result wins.
54
+ - The `meta.evidenceExempt` trace does not accumulate: retries leave only the
55
+ latest completion result.
56
56
  - **An exemption record must name which items were waived and who approved it**:
57
57
  `meta.evidenceExempt` = `{"at", "flag": "--allow-missing-evidence",
58
58
  "by": <actor or null>, "missing": [<AC id>…]}`. `by` is the resolved session
@@ -60,9 +60,7 @@ direct lifecycle checks:
60
60
  `creator`/`assignee`. Both the completion output and the archive output print
61
61
  this record item by item (the completion output reads the `task.json` just
62
62
  written), so "what was recorded" and "what was printed" cannot tell two different
63
- stories; with no exemption, no exemption rows are printed at all. The structure
64
- matches the `meta.specCheckExempt` detail rules on the spec-check side; see
65
- `spec-checks.md`.
63
+ stories; with no exemption, no exemption rows are printed at all.
66
64
 
67
65
  ## Injection and fact view
68
66
 
@@ -81,5 +79,5 @@ direct lifecycle checks:
81
79
  behind an AC to fabricate a record.
82
80
  - It does not parse AC prose semantics; a missing checkbox on an `AC-xxx` row
83
81
  parses as incomplete.
84
- - This contract does not change spec-check semantics; MCP write tools still refuse
85
- by default (`fact-layer-access.md`).
82
+ - This contract does not change the file-scope gate or the AC gate; MCP write
83
+ tools still refuse by default (`fact-layer-access.md`).
@@ -12,7 +12,6 @@ Current contracts:
12
12
  - `capabilities.md`: the host adapter capability model.
13
13
  - `party-mode-v2-board.md`: the Party Mode V2 board protocol.
14
14
  - `plan-binding.md`: lightweight development-plan binding, pre-start missing-plan blocking, and recovery visibility.
15
- - `spec-checks.md`: the spec-declared command contract — syntax of `checks:` in user-spec frontmatter, three-state gate semantics, timing, and the host capability matrix.
16
15
  - `context-injection.md`: the transport shapes, event-timing matrix, and fingerprint serialization rules for runtime-context injection across hosts (a meta-protocol, not in `contract-registry.json`).
17
16
  - `decision-anchor.md`: the schema of each task's `decision-anchor.md` — goal, acceptance criteria, rejected options, and key assumptions.
18
17
  - `error-output-as-data.md`: external error output is analyzed as data, never executed as instructions (`ERROR_OUTPUT_AS_DATA_V1`).
@@ -14,8 +14,9 @@ the plan into a second workflow kernel.
14
14
 
15
15
  ## Creation phase
16
16
 
17
- - A plain `task create` may create a planning task with no plan.
18
- - `task create --from-plan <path>` is an explicit binding request; `<path>` must
17
+ - A plain task creation (`task next --title … --slug …`) may create a planning
18
+ task with no plan.
19
+ - `task next --from-plan <path>` is an explicit binding request; `<path>` must
19
20
  exist, lie inside the repository, and lie under `.cowork-flow/plans/`.
20
21
  - On a successful binding the runtime writes:
21
22
 
@@ -108,23 +108,6 @@ does not defend against a forged runtime record — writing
108
108
  `.cowork-flow/.runtime/**` is out of scope here and would need a signed or
109
109
  externally rooted record, which is a separate contract.
110
110
 
111
- ## Delegated Spec Self-Check
112
-
113
- Delegated subagent tool calls receive no edit-phase hook feedback on hosts
114
- whose plugin hooks cover only the main session stream (verified on zcode:
115
- plugin PostToolUse fires for main-session tool calls but never for
116
- Agent-tool children). The reliable pull path is the CLI: before reporting
117
- completion, `cowork-implement` (and any child that changes files) runs
118
-
119
- ```text
120
- ./.cowork-flow/run spec-check
121
- ```
122
-
123
- fixes every reported violation, and re-runs until the summary is clean or the
124
- remaining violations are reported as blockers. This is a protocol behavior,
125
- not a second lifecycle gate — the closeout gate re-runs the same checks and
126
- blocks completion regardless.
127
-
128
111
  ## Return Acceptance And Closeout
129
112
 
130
113
  - Converge on the child result before acceptance — via the wait primitive or
@@ -133,10 +116,12 @@ blocks completion regardless.
133
116
  - Confirm no stray running children with the adapter list primitive.
134
117
  - Verify the child report by checking files, commands, and results; do not
135
118
  trust completed text alone.
136
- - Before accepting delegated output, run `./.cowork-flow/run spec-check` in
137
- the main session and treat every violation as an acceptance blocker: the
138
- child receives no edit-phase hook feedback, so acceptance-time checking is
139
- the earliest reliable checkpoint for delegated work.
119
+ - Before accepting delegated output, verify the child's changed paths against
120
+ the task scope and the applicable `spec/` requirements from the diff
121
+ (`git diff --stat` / `task next <dir> --validate`): child sessions receive no
122
+ edit-phase hook feedback on hosts whose plugin hooks cover only the main
123
+ session stream, so acceptance-time review is the earliest reliable checkpoint
124
+ for delegated work.
140
125
  - After completion or failure, close the child with the adapter cancel/close
141
126
  primitive and clean the runtime context sessions.
142
127
  - The child remains a leaf executor and must not dispatch, wait for, list, or
@@ -74,7 +74,7 @@ that does not advance status); `check_result` is `null` (no check is exempted).
74
74
  ## Session binding
75
75
 
76
76
  On successful adoption the CLI binds the adopter's session to that task (the same
77
- shape as a rebind by `task next <dir>` or the binding after `task create`), so a
77
+ shape as a rebind by `task next <dir>` or the binding at task creation), so a
78
78
  later `task next <dir> --run` can omit the directory and continue the
79
79
  review/complete/archive chain.
80
80
 
@@ -115,19 +115,6 @@
115
115
  "when changing task start readiness",
116
116
  "when changing resume context for active tasks"
117
117
  ]
118
- },
119
- {
120
- "id": "SPEC_CHECKS_V1",
121
- "path": ".cowork-flow/spec/contracts/spec-checks.md",
122
- "digest": [
123
- "Specs may declare `checks:` commands in frontmatter; the mechanism runs declarations, never prose.",
124
- "A declared command that cannot be found counts as unchecked and blocks completion."
125
- ],
126
- "readWhen": [
127
- "before adding or editing spec files",
128
- "when a spec clause can be judged by a command exit code",
129
- "when spec-check reports violations or unchecked"
130
- ]
131
118
  }
132
119
  ],
133
120
  "references": [
@@ -155,7 +155,7 @@
155
155
  "runtimeContextDispatch": "native",
156
156
  "runtimeContextBinding": "shim",
157
157
  "runtimeContextCleanup": "native",
158
- "editScopeWarning": "unsupported"
158
+ "editScopeWarning": "plugin"
159
159
  },
160
160
  "commandTargets": [
161
161
  {
@@ -204,7 +204,7 @@
204
204
  "runtimeContextDispatch": "native",
205
205
  "runtimeContextBinding": "shim",
206
206
  "runtimeContextCleanup": "native",
207
- "editScopeWarning": "unsupported"
207
+ "editScopeWarning": "plugin"
208
208
  },
209
209
  "commandTargets": [],
210
210
  "payload": {
@@ -251,7 +251,7 @@
251
251
  "runtimeContextDispatch": "native",
252
252
  "runtimeContextBinding": "shim",
253
253
  "runtimeContextCleanup": "native",
254
- "editScopeWarning": "unsupported"
254
+ "editScopeWarning": "plugin"
255
255
  },
256
256
  "commandTargets": [
257
257
  {
@@ -300,7 +300,7 @@
300
300
  "runtimeContextDispatch": "native",
301
301
  "runtimeContextBinding": "shim",
302
302
  "runtimeContextCleanup": "shim",
303
- "editScopeWarning": "unsupported"
303
+ "editScopeWarning": "plugin"
304
304
  },
305
305
  "commandTargets": [],
306
306
  "payload": null
@@ -431,7 +431,7 @@
431
431
  "runtimeContextDispatch": "native",
432
432
  "runtimeContextBinding": "shim",
433
433
  "runtimeContextCleanup": "native",
434
- "editScopeWarning": "unsupported"
434
+ "editScopeWarning": "plugin"
435
435
  },
436
436
  "commandTargets": [],
437
437
  "payload": {
@@ -460,8 +460,8 @@
460
460
  ".cowork-flow/.version",
461
461
  ".cowork-flow/run",
462
462
  ".cowork-flow/run.cmd",
463
- ".cowork-flow/spec/contracts/spec-checks.md",
464
- ".cowork-flow/spec/contracts/workflow-state-templates.md"
463
+ ".cowork-flow/spec/contracts/workflow-state-templates.md",
464
+ ".cowork-flow/spec/runtime/scope-rules.json"
465
465
  ],
466
466
  "safePrefixes": [
467
467
  ".cowork-flow/"
@@ -515,6 +515,7 @@
515
515
  ".cowork-flow/scripts/application",
516
516
  ".cowork-flow/scripts/services/batch_execution.py",
517
517
  ".cowork-flow/scripts/services/decision_review.py",
518
+ ".cowork-flow/scripts/services/spec_check.py",
518
519
  ".cowork-flow/scripts/commands",
519
520
  ".cowork-flow/scripts/common",
520
521
  ".cowork-flow/scripts/common/__init__.py",
@@ -567,6 +568,7 @@
567
568
  ".cowork-flow/scripts/subagent.py",
568
569
  ".cowork-flow/scripts/task.py",
569
570
  ".cowork-flow/spec/contracts/entry-contract.md",
571
+ ".cowork-flow/spec/contracts/spec-checks.md",
570
572
  ".cowork-flow/spec/runtime/rules.json",
571
573
  ".cowork-flow/spec/schemas/rules.schema.json",
572
574
  ".cowork-flow/spec/runtime/skill-registry.json",
@@ -575,6 +577,7 @@
575
577
  ".cowork-flow/scripts/adapters/cli/add_session.py",
576
578
  ".cowork-flow/workspace/index.md",
577
579
  ".cowork-flow/scripts/adapters/cli/change.py",
580
+ ".cowork-flow/scripts/adapters/cli/spec_check.py",
578
581
  ".cowork-flow/changes/",
579
582
  ".cowork-flow/skills/adversarial-review",
580
583
  ".cowork-flow/skills/agent-dispatch",
@@ -8,9 +8,9 @@
8
8
  "trailingSlashRejectedTypes": ["planned-file", "deleted-file"]
9
9
  },
10
10
  "stageContract": {
11
- "budget": 1200,
11
+ "budget": 3400,
12
12
  "scopeLimit": 8,
13
13
  "specLimit": 4,
14
- "verifyLimit": 3
14
+ "verifyLimit": 10
15
15
  }
16
16
  }
@@ -24,6 +24,6 @@ Execution:
24
24
  Rules:
25
25
 
26
26
  - Do not invoke subagents.
27
- - Do not run task start, finish, archive, unscoped resume, commit, or push.
27
+ - Do not run task lifecycle transitions, unscoped resume, commit, or push.
28
28
  - Treat backend/frontend natural-language specs as checklist context, not dynamic hard validators.
29
29
  - Do not accept shallow tests that would still pass when target behavior breaks.
@@ -25,6 +25,6 @@ Execution:
25
25
  Rules:
26
26
 
27
27
  - Do not invoke subagents.
28
- - Do not run task start, finish, archive, unscoped resume, commit, or push.
28
+ - Do not run task lifecycle transitions, unscoped resume, commit, or push.
29
29
  - Do not create task-local review artifacts, `tdd.jsonl`, or TDD evidence records in `check.jsonl`.
30
30
  - Do not claim completion without current verification.
@@ -20,4 +20,4 @@ Rules:
20
20
 
21
21
  - Do not invoke subagents.
22
22
  - Do not edit code, specs, task state, or git state.
23
- - Do not run task start, finish, archive, unscoped resume, commit, or push.
23
+ - Do not run task lifecycle transitions, unscoped resume, commit, or push.
@@ -28,6 +28,6 @@ Execution:
28
28
  Rules:
29
29
 
30
30
  - Do not use the `task` tool or invoke subagents.
31
- - Do not run task start, finish, archive, unscoped resume, commit, or push.
31
+ - Do not run task lifecycle transitions, unscoped resume, commit, or push.
32
32
  - Treat backend/frontend natural-language specs as checklist context, not dynamic hard validators.
33
33
  - Do not accept shallow tests that would still pass when target behavior breaks.
@@ -29,6 +29,6 @@ Execution:
29
29
  Rules:
30
30
 
31
31
  - Do not use the `task` tool or invoke subagents.
32
- - Do not run task start, finish, archive, unscoped resume, commit, or push.
32
+ - Do not run task lifecycle transitions, unscoped resume, commit, or push.
33
33
  - Do not create task-local review artifacts, `tdd.jsonl`, or TDD evidence records in `check.jsonl`.
34
34
  - Do not claim completion without current verification.
@@ -26,4 +26,4 @@ Rules:
26
26
 
27
27
  - Do not use the `task` tool or invoke subagents.
28
28
  - Do not edit code, specs, task state, or git state.
29
- - Do not run task start, finish, archive, unscoped resume, commit, or push.
29
+ - Do not run task lifecycle transitions, unscoped resume, commit, or push.
@@ -1,11 +1,11 @@
1
1
  // Transport shim behind the opencode plugin. Every workflow fact
2
2
  // (contract digest, workflow-state, decision anchor, stage contract,
3
- // runtime-context bind) is rendered by the Python source
4
- // <scripts>/adapters/host/inject.py --host opencode; this module moves
5
- // bytes only. What stays here is what the Python side cannot reach:
6
- // the config-hook skills registration, the shell.env context-key
7
- // export, and the edit-phase spec-check the tool.execute.after hook
8
- // delegates to the project runtime.
3
+ // runtime-context bind, edit-scope warning) is rendered by the Python
4
+ // source <scripts>/adapters/host/inject.py --host opencode; this module
5
+ // moves bytes only. What stays here is what the Python side cannot
6
+ // reach: the config-hook skills registration, the shell.env context-key
7
+ // export, and the tool-shape translation for the tool.execute.after
8
+ // edit-scope delegation.
9
9
  //
10
10
  // This module must NOT live under plugins/: opencode
11
11
  // treats every file directly inside plugins/ as a plugin
@@ -16,7 +16,7 @@
16
16
  // install, so the adapter's relative import resolves in
17
17
  // both.
18
18
 
19
- import { spawn } from "node:child_process"
19
+ import { spawn, spawnSync } from "node:child_process"
20
20
  import { existsSync } from "node:fs"
21
21
  import { dirname, resolve } from "node:path"
22
22
  import { fileURLToPath } from "node:url"
@@ -24,6 +24,27 @@ import { fileURLToPath } from "node:url"
24
24
  const DIR_WORKFLOW = ".cowork-flow"
25
25
  // One prompt assembly must not wait longer than a host hook event.
26
26
  const INJECT_TIMEOUT_MS = 4800
27
+ // The .cowork-flow/run floor, reused here: a candidate that cannot run
28
+ // Python 3.10+ is skipped like a missing binary, so a stale `python3`
29
+ // never hides a working `python` further down the list.
30
+ const MIN_PYTHON_CHECK =
31
+ "import sys; raise SystemExit(0 if sys.version_info >= (3, 10) else 1)"
32
+
33
+ function interpreterIsUsable(candidate) {
34
+ try {
35
+ const probe = spawnSync(candidate[0], [...candidate.slice(1), "-c", MIN_PYTHON_CHECK], {
36
+ timeout: INJECT_TIMEOUT_MS,
37
+ windowsHide: true,
38
+ })
39
+ // A probe killed by its own timeout reached no verdict — a loaded machine,
40
+ // not a bad interpreter — so let the real spawn decide; it carries the
41
+ // same timeout and its own diagnostics.
42
+ if (probe.error && probe.error.code === "ETIMEDOUT") return true
43
+ return !probe.error && probe.status === 0
44
+ } catch {
45
+ return false
46
+ }
47
+ }
27
48
 
28
49
  // Last-resort repository root: this module lives at
29
50
  // <payload>/cowork-flow/plugin-core.js, next to plugins/,
@@ -175,8 +196,9 @@ function runInject(candidate, script, payload) {
175
196
  // inject.py and hand back whatever it printed. The payload spreads the
176
197
  // host input so every session/runtime-context key the Python identity
177
198
  // table knows arrives unchanged; hook_event_name and cwd are the two
178
- // fields the hook contract requires.
179
- async function renderViaInject(input) {
199
+ // fields the hook contract requires. `overrides` swaps the event name
200
+ // and adds the tool fields the PostToolUse contract needs.
201
+ async function renderViaInject(input, overrides = null) {
180
202
  if (
181
203
  process.env.COWORK_FLOW_HOOKS === "0" ||
182
204
  process.env.COWORK_FLOW_DISABLE_HOOKS === "1"
@@ -188,21 +210,57 @@ async function renderViaInject(input) {
188
210
  if (!script) {
189
211
  return ""
190
212
  }
213
+ const extra = overrides && typeof overrides === "object" ? overrides : {}
191
214
  const payload = {
192
215
  ...(input && typeof input === "object" ? input : {}),
193
- hook_event_name: "UserPromptSubmit",
216
+ ...extra,
217
+ hook_event_name: extra.hook_event_name || "UserPromptSubmit",
194
218
  cwd: inputCwd(input) || (root === pluginRoot ? null : root) || process.cwd(),
195
219
  }
196
220
  for (const candidate of pythonCandidates()) {
221
+ if (!interpreterIsUsable(candidate)) {
222
+ continue
223
+ }
197
224
  const result = await runInject(candidate, script, payload)
198
225
  if (!result.ran) {
199
226
  continue
200
227
  }
201
228
  return result.stdout.trim()
202
229
  }
230
+ console.error(
231
+ "cowork-flow opencode plugin: no Python 3.10+ interpreter found; skipping injection"
232
+ )
203
233
  return ""
204
234
  }
205
235
 
236
+ // Editor-phase scope warning: opencode sessions get the same
237
+ // single-line out-of-scope advisory the other hosts receive on
238
+ // PostToolUse. Best-effort — a missing runtime, a timeout, or any
239
+ // error stays silent and never blocks the edit that already happened.
240
+ const EDIT_TOOL_NAMES = new Set(["edit", "write"])
241
+
242
+ function editedFilePath(input) {
243
+ const tool = typeof input?.tool === "string" ? input.tool : ""
244
+ if (!EDIT_TOOL_NAMES.has(tool)) {
245
+ return ""
246
+ }
247
+ const args = input?.args
248
+ const candidate = args && typeof args === "object" ? args.filePath : null
249
+ return typeof candidate === "string" && candidate.trim() ? candidate.trim() : ""
250
+ }
251
+
252
+ async function renderEditScopeWarning(input) {
253
+ const filePath = editedFilePath(input)
254
+ if (!filePath) {
255
+ return ""
256
+ }
257
+ return renderViaInject(input, {
258
+ hook_event_name: "PostToolUse",
259
+ tool_name: typeof input?.tool === "string" ? input.tool : "edit",
260
+ tool_input: { file_path: filePath },
261
+ })
262
+ }
263
+
206
264
  function sanitize(value) {
207
265
  return String(value ?? "")
208
266
  .trim()
@@ -262,82 +320,6 @@ function injectShellEnv(input, output) {
262
320
  }
263
321
  }
264
322
 
265
- // Editor-phase spec-check: opencode sessions get the same
266
- // single-line violation hint zcode and claude-code
267
- // receive. Best-effort — a missing runtime, a timeout, or
268
- // any error stays silent and never blocks the edit that
269
- // already happened.
270
- const EDIT_TOOL_NAMES = new Set(["edit", "write"])
271
- const EDIT_CHECK_TIMEOUT_MS = 2500
272
-
273
- function editedFilePath(input) {
274
- const tool = typeof input?.tool === "string" ? input.tool : ""
275
- if (!EDIT_TOOL_NAMES.has(tool)) {
276
- return ""
277
- }
278
- const args = input?.args
279
- const candidate = args && typeof args === "object" ? args.filePath : null
280
- return typeof candidate === "string" && candidate.trim() ? candidate.trim() : ""
281
- }
282
-
283
- function runEditSpecCheck(root, filePath) {
284
- return new Promise((resolvePromise) => {
285
- let settled = false
286
- const finish = (value) => {
287
- if (!settled) {
288
- settled = true
289
- resolvePromise(value)
290
- }
291
- }
292
- const entry = resolve(
293
- root,
294
- DIR_WORKFLOW,
295
- process.platform === "win32" ? "run.cmd" : "run"
296
- )
297
- if (!existsSync(entry)) {
298
- finish("")
299
- return
300
- }
301
- let child
302
- try {
303
- child = spawn(
304
- entry,
305
- ["spec-check", "--phase", "edit", "--file", filePath, "--throttled"],
306
- {
307
- cwd: root,
308
- stdio: ["ignore", "pipe", "ignore"],
309
- shell: process.platform === "win32",
310
- }
311
- )
312
- } catch {
313
- finish("")
314
- return
315
- }
316
- const timer = setTimeout(() => {
317
- try {
318
- child.kill()
319
- } catch {
320
- // the edit must not be held up by a stuck checker
321
- }
322
- finish("")
323
- }, EDIT_CHECK_TIMEOUT_MS)
324
- let stdout = ""
325
- if (child.stdout) {
326
- child.stdout.on("data", (chunk) => {
327
- stdout += String(chunk)
328
- })
329
- }
330
- child.on("error", () => {
331
- clearTimeout(timer)
332
- finish("")
333
- })
334
- child.on("close", () => {
335
- clearTimeout(timer)
336
- finish(stdout.trim())
337
- })
338
- })
339
- }
340
-
341
323
  // The payload's own skills directory, located from this
342
324
  // module's file path and not from the repository root: the
343
325
  // machine install puts this file in
@@ -382,11 +364,11 @@ export function registerPayloadSkills(config) {
382
364
 
383
365
  // The plugin adapter imports the hook helpers; these are
384
366
  // the unit-test surface (transport, fail-open, shell env,
385
- // edit-phase delegation). opencode never reads this module.
367
+ // edit-scope delegation). opencode never reads this module.
386
368
  export {
387
369
  renderViaInject,
388
370
  editedFilePath,
371
+ renderEditScopeWarning,
389
372
  findRepoRoot,
390
373
  injectShellEnv,
391
- runEditSpecCheck,
392
374
  }
@@ -7,12 +7,10 @@
7
7
  // re-exported helper would be called as a plugin and break
8
8
  // host bootstrap.
9
9
  import {
10
- editedFilePath,
11
- findRepoRoot,
12
10
  injectShellEnv,
13
11
  registerPayloadSkills,
14
- renderViaInject,
15
- runEditSpecCheck
12
+ renderEditScopeWarning,
13
+ renderViaInject
16
14
  } from "../cowork-flow/plugin-core.js"
17
15
 
18
16
  export const CoworkFlowPlugin = async () => {
@@ -37,12 +35,12 @@ export const CoworkFlowPlugin = async () => {
37
35
  output.system.push(text)
38
36
  }
39
37
  },
38
+ // Editor-phase scope advisory: the tool already ran, so
39
+ // the single out-of-scope line is appended to its output
40
+ // and the call is never blocked. Non-edit tools and a
41
+ // missing/failing runtime stay silent.
40
42
  "tool.execute.after": async (input, output) => {
41
- const filePath = editedFilePath(input)
42
- if (!filePath) {
43
- return
44
- }
45
- const warning = await runEditSpecCheck(findRepoRoot(input), filePath)
43
+ const warning = await renderEditScopeWarning(input)
46
44
  if (warning) {
47
45
  output.output = [output.output, warning].filter(Boolean).join("\n")
48
46
  }
@@ -144,5 +144,4 @@
144
144
 
145
145
  <!-- COWORK-FLOW:START -->
146
146
  项目流程以 `task next`、runtime gate、Skill command manifests 和 `.cowork-flow/spec/` 硬契约为准;不要维护第二套流程文档或 Skill 注册控制面。
147
- 规范声明的检查以 `.cowork-flow/spec/contracts/spec-checks.md` 为准;交付或收口前运行 `./.cowork-flow/run spec-check`。
148
147
  <!-- COWORK-FLOW:END -->
@@ -30,21 +30,6 @@ Use this Skill only inside fixed subagents such as `cowork-implement`,
30
30
  - Treat runtime gates as CLI adapters backed by kernel services; this Skill may
31
31
  explain when to run or report them, but does not enforce them.
32
32
 
33
- ## Spec Self-Check
34
-
35
- Delegated subagent tool calls receive no edit-phase hook feedback on hosts
36
- whose plugin hooks cover only the main session stream — a `spec-check[...]
37
- violation` line never reaches this session, even when the edit violates a
38
- declared check. Before reporting completion after any file change, run:
39
-
40
- ```text
41
- ./.cowork-flow/run spec-check
42
- ```
43
-
44
- Fix every reported violation and re-run until the summary is clean, or report
45
- the remaining violations as blockers. Honor any hook feedback the host does
46
- deliver; the self-check exists because delivery is not guaranteed.
47
-
48
33
  ## Role Outputs
49
34
 
50
35
  - `cowork-implement`: report changed files, acceptance IDs, verification