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
package/CHANGELOG.md CHANGED
@@ -4,6 +4,27 @@
4
4
 
5
5
  ### Added
6
6
 
7
+ ## [2.0.0] - 2026-10-16
8
+
9
+ ### Added
10
+
11
+ - **编辑期越界警告覆盖 6 宿主(抽共享模块)**:`spec-check` 删除后编辑期只剩 zcode 的 scope 越界提示(其余宿主 `unsupported`),现在把同一语义接到 claude-code / codex / qoder / opencode / dsh——编辑工具落盘后,若活动任务为 `in_progress`/`review` 且被改文件不在其 `implement.jsonl` 白名单内,回**一行** `⚠️ <rel> is outside the task's declared scope. If intended, declare it in the task's `implement.jsonl` (agent-mutable) or revert the edit.`;在范围、无任务、planning/completed、子代理会话、任何异常或超时一律静默——非阻塞、fail-open、全宿主 exit 0。判定逻辑从 `zcode_policy.py` 抽到共享模块 `adapters/host/edit_scope.py`(新增 `extract_edited_paths`:`file_path`/`filePath`/`path` 加 codex `apply_patch` 的 `*** Update File:` 文本,去重保序),zcode 改为引用后对 zcode 实际载荷输出逐字节相同(共享模块语义上是超集:额外识别 `path` 键与 apply_patch 文本路径,zcode 载荷不触发;其既有测试零改动全绿)。**通道**:claude-code / codex / qoder / opencode 走 `inject.py` 的 policy `post_tool_use` 槽(前三个回 `hookSpecificOutput.additionalContext` 信封,opencode 因 `emit_text` 得裸文本并追加到 `tool.execute.after` 的工具输出),dsh 在其 preset 插件的 `tools/post-execute` 里经 Python heredoc 直调共享模块,以 plugin notice 并入 `additionalContexts`。能力声明:`host-assets.json` 六个宿主 `editScopeWarning: plugin`,kimi-code 保持 `unsupported`(宿主丢弃 PostToolUse stdout);`context-injection.md` 的 Per-edit warning 段同步改写。**迁移**:宿主侧载荷需 `cwf host add <host> --force` 重装(claude-code 的 `.claude/settings.json`、codex 的 `.codex/hooks.json`、qoder 的机器级插件 hooks.json、opencode 与 dsh 的插件文件都在载荷内)。Bash 重定向/tee 写文件仍不在覆盖范围(既有残余缺口)。
12
+
13
+ ### Changed
14
+
15
+ - **末段裁剪抽成 `_cut_scope_rows`,体积按 Scope 行条数分摊,弱断言用例补强**:`_fit_stage_contract` 回到软预算以内(63 → 45 行),末段裁剪有了独立函数名与边界说明;`room` 由 `budget - len(fixed) - 1` 改为 `(budget - len(fixed) - scope_count) // scope_count`,多条 `Scope:` 前缀行不再"同时被截断又超预算"(单条时与旧式逐字节等价:2050 次逐预算渲染对照、其中 668 次走到末段裁剪,零差异)。复核曾判 `room < len("Scope: ") + 1` 下限与标签守卫行为重叠、可删——实测该结论只对 `0 ≤ room < 8` 成立:`room < 0` 时切片是 `line[:-k]`,从尾部切、前端保留,长条目的标签存活会让守卫放行一个被腰斩且仍超预算的块,下限是唯一拦截者,故保留并补了只能由它单独拦住的红证用例。契约与 docstring 的阈值措辞精确到 `len(fixed) + 9`。测试面:新增多条 Scope 行体积不变量用例,并把 6 条 `shallow_assertion_signal` 软提示一并处置(新用例补 `assertRegex` 钉消费者契约、`SpecEntriesForTaskDevTypeTest` 补 `assertEqual(resolved, …)` 与按 dev-type 所有权过滤后的精确列表),补强后 `review-check` 在该文件零告警。
16
+ - **stage-contract 末段裁剪修复:任务身份与编辑策略优先于预算**:预算极小时末段裁剪原先对整块正文做码点截断,会把开标签一起截掉(实测产出 `<` 或 `<stage-contract task="` 这类半截标签,消费者正则匹配不到,块等于消失),或把 `Gates:` 编辑策略行切成半句(预算 400 实测只剩 `Gates: edit`)。现在末段裁剪只缩短 Scope 行:开标签、闭合标签与 Gates 行在任何预算下完整存活,超长部分由 Scope 行承担;截断若会把 Scope 行的 `Scope: ` 标签本身磨掉(切点落在条目首部空白处),则改回返回整块——宁可超预算,也不产出消费者正则匹配不到的 Scope 行。预算小到连这些固定行都装不下时同样返回整块、允许超过预算(保护行与可解析性优先于预算)。出厂 `budget = 3400` 下该末段不可达,对下游零可见变化——字节对比确认现有全部夹具在该预算下逐字节相同;`context-injection.md` 的 Budget 段与 `_fit_stage_contract` docstring 已写明三条不变量与下限,并新增扫预算可解析性用例与下限用例(各带红证)。
17
+ - **stage-contract 注入容量放宽(Verify 行 10 条 / 块预算 3400 字)**:`Verify:` 行从"渲染 3 条 / 解析 5 条 / 全块 1200 字"放宽到"渲染 10 条 / 解析 10 条 / 全块 3400 字",让 `decision-anchor.md` 的 `## 验证命令` 段放得下"先读规范再逐条核对改动"的强提醒加真实校验命令,不再静默丢弃第 4 条之后的命令(单条仍截断在 120 字)。三处预算来源同改并互锁:模板 `spec/runtime/scope-rules.json`、`context_paths.DEFAULT_SCOPE_RULES`、`fact_view.STAGE_CONTRACT_VERIFY_LIMIT`/`STAGE_CONTRACT_BUDGET`,新增"默认值↔常量"一致性测试补上第三条断链。`scope-rules.json` 进 `syncPolicy.safeFiles`,已有项目一次普通 `cwf project sync` 即拿到新默认值;代价是该文件的本地调优会在下次 sync 被覆盖(`context-injection.md` 与 `spec-checks.md` 已写明)。降级测试改为钉住自选预算并额外断言输入确实超预算,不再随生产常量漂移(原先升预算会让它们静默恒真)。
18
+ - **kimi-code 裸命令一次装齐(组合组件)**:`kimi-code` 新增组合组件 `all` 并把 default 从 `plugin` 改指它——`cwf host add kimi-code` 一条命令先装上下文注入 hook(config.toml 托管块 + shim + marker),再装插件(sources/managed 两份载荷 + 宿主注册表记录),`cwf host remove kimi-code` 一次卸两者。组合器只做定向旗标剥除:`--force` 自动只达插件段(hook 无该旗标),未知旗标仍原样透传给 installer 报错;显式 `--component plugin` / `--component hook` 的单装语义不变(plugin 单装不写 config.toml)。`host list` 的组件列因此显示 `plugin, hook, all`。`docs/hosts.md` 的宿主表与 Kimi 段改为裸命令主推,升级路径写明 `cwf host add kimi-code --force`。
19
+
20
+ ### Removed
21
+
22
+ - **spec-check 机制整体删除(破坏性)**:下游正式项目不会手写 `checks:` 声明,规范声明命令 / 编辑期提示 / 收口硬门禁 / doctor 体检被判定为冗余设计,整条链删除。**删除面**:`run spec-check` 命令与两个实现文件(573+178 行)、lifecycle 的 `SPEC-CHECK-PARSE-ERROR` / `SPEC-CHECK-EXECUTOR-ERROR` / `SPEC-CHECK-VIOLATION` / `SPEC-CHECK-UNCHECKED` 四个错误码与豁免构造(`meta.specCheckExempt` / `meta.specCheckSummary` 遥测随之消失)、`--allow-unchecked` 与 `allow_unchecked_specs`、全部宿主的编辑期 spec 提示(zcode 合并提示、claude-code / codex / qoder 的 `PostToolUse` 槽与 hooks.json 声明、opencode `tool.execute.after`、dsh `tools/post-execute`)、doctor 的 `specChecks` 体检、契约 `spec/contracts/spec-checks.md` 与 `SPEC_CHECKS_V1` 契约登记。**保留不动**:zcode 编辑期 scope 越界警告(`edit_scope_warning` 原样,PostToolUse 现在只回这条)、stage-contract 的 Specs 行与 h2 digest 注入、`--allow-missing-evidence` 的 AC 证据豁免链(含 `meta.evidenceExempt` 与打印/归档展示)、`allow_spec_file_modifications`,以及 review 结论的 `clause → command → negativeProof` triple(只删依赖 `checks:` 的措辞)。**契约搬迁**:`spec-checks.md` 的 digest 注入附录并入 `context-injection.md` 的 Specs 段,`spec/` 同步保护前缀与 `safeFiles` 具名例外说明并入 `docs/architecture.md`。**迁移**:`.cowork-flow/scripts/services/spec_check.py` 与 `.cowork-flow/scripts/adapters/cli/spec_check.py` 已登记 `obsoleteFiles`,一次普通 `cwf project sync` 即删除;已装项目里的 `spec/contracts/spec-checks.md` 受 `spec/` 保护前缀保护,sync 会明确报告 `Kept … (protected-path); delete manually if unwanted`,需手动清理;宿主侧载荷需 `cwf host add <host> --force` 重装。已声明 `checks:` 的项目从此失去该门禁——替代信号是 review 阶段既有的"逐条核对用户规范"清单项与 stage-contract 的 Specs 行。
23
+
24
+ ### Fixed
25
+
26
+ - **注入文本与策略提示指向不存在的命令**:`<stage-contract>` 的 Gates 行尾句写 `self-declared via task context add`、zcode 越界编辑提示写 "add it with `task context add`",但 CLI 只有 `{next, scope, specs}`(实测 `invalid choice: 'context'`);`plan-binding.md` 与 `task-adopt.md` 同样引用 `task create` / `task create --from-plan`。这些内部适配器是有意不公开的(`test_context_commands_are_not_public_parser_commands` 锁定,`task_context_commands.py` 写明 public workflow 走 `task next` + 直编 JSONL),所以改文字到真实机制而非接线:Gates 尾句改为 `scope is agent-mutable (declare more in implement.jsonl)`,zcode 提示改为 "declare it in the task's `implement.jsonl`",契约正文改为 `task next --title … --slug …` 与 `task next --from-plan <path>`。`.cowork-flow/scripts/**` 在 `syncPolicy.safePrefixes` 内,下游一次普通 `cwf project sync` 即生效;受 `spec/` 前缀保护的契约正文仍只对新装项目生效(既有取舍)。
27
+
7
28
  ## [1.9.2] - 2026-10-14
8
29
 
9
30
  ### Added
@@ -344,7 +365,8 @@
344
365
 
345
366
  此版本未保留本地条目正文,详情见 [GitHub Release](https://github.com/fishlikewater/cowork-flow/releases/tag/v0.0.47)。
346
367
 
347
- [Unreleased]: https://github.com/fishlikewater/cowork-flow/compare/v1.9.1...HEAD
368
+ [Unreleased]: https://github.com/fishlikewater/cowork-flow/compare/v2.0.0...HEAD
369
+ [2.0.0]: https://github.com/fishlikewater/cowork-flow/compare/v1.9.2...v2.0.0
348
370
  [1.9.2]: https://github.com/fishlikewater/cowork-flow/compare/v1.9.1...v1.9.2
349
371
  [1.9.1]: https://github.com/fishlikewater/cowork-flow/compare/v1.9.0...v1.9.1
350
372
  [1.9.0]: https://github.com/fishlikewater/cowork-flow/compare/v1.8.1...v1.9.0
@@ -71,40 +71,7 @@
71
71
 
72
72
  `config.yaml`、`.developer`、规范、任务和计划按同步策略保护。`--force` 会扩大覆盖范围,因此已有项目应先用 `--dry-run` 查看变更。
73
73
 
74
- ## 规范挂命令
75
-
76
- 项目可以在 `.cowork-flow/spec/` 下的规范文件中声明检查命令。执行器只读取声明,不解析规范正文。
77
-
78
- ```markdown
79
- ---
80
- checks:
81
- - cmd: npm run lint --silent
82
- files: "src/"
83
- timeout: 60
84
- when: both
85
- ---
86
- ```
87
-
88
- 可用字段:
89
-
90
- | 字段 | 含义 |
91
- |---|---|
92
- | `cmd` | 要执行的命令 |
93
- | `files` | 目录前缀或扩展名,如 `src/`、`*.ts`;多个值用逗号分隔 |
94
- | `timeout` | 秒数,默认 30,最长 120 |
95
- | `when` | `edit`、`lifecycle` 或 `both`,默认 `both` |
96
-
97
- 唯一执行入口是 `./.cowork-flow/run spec-check`:
98
-
99
- | 结果 | 含义 | 是否阻断完成 |
100
- |---|---|---|
101
- | `pass` | 命令执行成功 | 否 |
102
- | `violation` | 命令执行失败且有违规 | 是 |
103
- | `unchecked` | 命令缺失、解释器缺失或超时 | 是,需显式放行 |
104
-
105
- `when: edit` 是编辑期提示,超时上限为 2.5 秒,不代替收口检查。收口阶段会执行全部声明,并把结果写入任务记录。
106
-
107
- 模板不预置生效的项目检查命令,因为模板无法知道目标项目使用什么工具。需要检查时,在项目自己的 `spec/` 文件中声明。完整契约见 `.cowork-flow/spec/contracts/spec-checks.md`。
74
+ `.cowork-flow/spec/` 前缀整体受同步保护:既有项目里的规范文件不会被 `sync` 覆盖,模板侧的规范更新只对全新 `init` 生效。具名例外是 `spec/runtime/host-assets.json` 中 `syncPolicy.safeFiles` 列出的文件——`spec/contracts/workflow-state-templates.md` 与 `spec/runtime/scope-rules.json`——它们随 `sync` 更新,使契约勘误与预算默认值能到达已装项目;代价是本地调过 `scope-rules.json` 的项目会在下一次 `sync` 丢掉这些改动。
108
75
 
109
76
  ## 扩展规则
110
77
 
package/docs/direction.md CHANGED
@@ -45,7 +45,7 @@
45
45
 
46
46
  ## 5. 版本线
47
47
 
48
- - **1.8.x 已发布**:证据化完成(AC 事实化、`evidence.jsonl`、完成门禁)与 spec-check 采纳闭环。
48
+ - **1.8.x 已发布**:证据化完成(AC 事实化、`evidence.jsonl`、完成门禁)。
49
49
  - **1.9.0 主线已实现,待发布**:多执行者协作事实——看板归属可见、依赖边、交接凭证。详见 [CHANGELOG](../CHANGELOG.md) 的 `[Unreleased]` 段。
50
50
  - 各版本的交付明细一律以 CHANGELOG 为准,本文不重复。
51
51
 
package/docs/hosts.md CHANGED
@@ -19,7 +19,7 @@
19
19
  | ZCode | 需要 | `cwf host add zcode` |
20
20
  | Qoder | 需要 | `cwf host add qoder` |
21
21
  | DeepSeek Harness | 需要 | `cwf host add dsh` |
22
- | Kimi Code | 工作流注入需要 hook;全局插件可选 | `cwf host add kimi-code --component hook` |
22
+ | Kimi Code | 需要 | `cwf host add kimi-code`(一条命令装齐 hook 与插件) |
23
23
 
24
24
  ## 通用命令
25
25
 
@@ -159,23 +159,27 @@ hook 行是否进入 agent 提示按版本分线:实测 DSH 0.1.1-rc.1 不收
159
159
 
160
160
  ## Kimi Code
161
161
 
162
- 工作流状态注入使用 hook 组件:
162
+ 裸命令一次装齐两部分:先装上下文注入 hook(必需),再装插件:
163
163
 
164
164
  ```bash
165
- cwf host add kimi-code --component hook
166
- cwf host remove kimi-code --component hook
165
+ cwf host add kimi-code
166
+ cwf host remove kimi-code
167
167
  ```
168
168
 
169
- 安装或更新后重载配置或新开会话。该组件总是重写自己的 hook 区块,不接受 `--force`。
169
+ 安装或更新后重载配置或新开会话。hook 组件总是重写自己的 hook 区块,不写插件;插件组件在未初始化项目中提供全局引导技能。单独安装某一半时用 `--component`:
170
170
 
171
- 如果还需要在未初始化项目中提供全局引导技能,可以另外安装插件:
171
+ ```bash
172
+ cwf host add kimi-code --component plugin
173
+ cwf host add kimi-code --component hook
174
+ ```
175
+
176
+ hook 组件不接受 `--force`(它总是接管重写自己的区块);裸命令里的 `--force` 只作用于插件段:
172
177
 
173
178
  ```bash
174
- cwf host add kimi-code
175
- cwf host remove kimi-code
179
+ cwf host add kimi-code --force
176
180
  ```
177
181
 
178
- 命令会物化到 `$KIMI_CODE_HOME/plugins/sources/cowork-flow/`(稳定重装源)与 `$KIMI_CODE_HOME/plugins/managed/cowork-flow/`(宿主加载的副本),并直接写入宿主的插件注册表 `$KIMI_CODE_HOME/plugins/installed.json`,与 Kimi Code 自身的安装落盘格式一致。重启 Kimi Code 即生效,无需在 TUI 中手工安装。卸载会反向清理两份目录与注册记录。
182
+ 插件会物化到 `$KIMI_CODE_HOME/plugins/sources/cowork-flow/`(稳定重装源)与 `$KIMI_CODE_HOME/plugins/managed/cowork-flow/`(宿主加载的副本),并直接写入宿主的插件注册表 `$KIMI_CODE_HOME/plugins/installed.json`,与 Kimi Code 自身的安装落盘格式一致。重启 Kimi Code 即生效,无需在 TUI 中手工安装。卸载会反向清理两份目录与注册记录。
179
183
 
180
184
  ## MCP 客户端接入
181
185
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cowork-flow",
3
- "version": "1.9.2",
3
+ "version": "2.0.0",
4
4
  "description": "CLI for installing and maintaining cowork-flow collaboration templates.",
5
5
  "keywords": [
6
6
  "ai-agent",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cowork-flow",
3
- "version": "1.9.2",
3
+ "version": "2.0.0",
4
4
  "description": "AI 协作工作流引擎:任务生命周期、子代理调度、门禁与状态注入。将 cowork-flow 的完整工作流带入 Claude Code。",
5
5
  "displayName": "Cowork Flow",
6
6
  "author": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cowork-flow",
3
- "version": "1.9.2",
3
+ "version": "2.0.0",
4
4
  "description": "AI 协作工作流引擎:任务生命周期、子代理调度、门禁与状态注入。将 cowork-flow 的完整工作流带入 Codex。",
5
5
  "author": {
6
6
  "name": "fishlikewater",
@@ -17,6 +17,11 @@
17
17
  // the text is byte-stable, so the static prompt prefix
18
18
  // stays cacheable.
19
19
  //
20
+ // It also carries the editor-phase scope advisory on the finished tool
21
+ // result (`tools/post-execute`): one line when an edited file is outside
22
+ // the active task's declared scope, silent otherwise. The tool already
23
+ // ran, so the notice only decorates the next request and never blocks.
24
+ //
20
25
  // Degradation is silent: no `.cowork-flow` root, a missing
21
26
  // Python, a broken runtime, or the `COWORK_FLOW_HOOKS=0` /
22
27
  // `COWORK_FLOW_DISABLE_HOOKS=1` switches all yield an
@@ -97,14 +102,19 @@ const EDIT_EXEC_OPTIONS = {
97
102
  maxBuffer: 1024 * 1024,
98
103
  };
99
104
 
100
- // Editor-phase spec-check protocol: same root discovery,
101
- // then the shared edit-check entry so DSH sessions receive
102
- // the single-line violation hint the other hosts get from
103
- // their PostToolUse hook. Always exits 0.
104
- const PYTHON_EDIT_PROTOCOL = `\
105
+ // Editor-phase scope protocol: same root discovery, then the shared
106
+ // edit-scope entry the other hosts reach through their PostToolUse
107
+ // hook, so DSH sessions get the identical single-line advisory. Prints
108
+ // at most one line; exits 0 either way.
109
+ const PYTHON_EDIT_SCOPE_PROTOCOL = `\
105
110
  import sys
106
111
  from pathlib import Path
107
112
 
113
+ # The advisory line starts with a character outside the Windows ANSI
114
+ # codepage (the warning sign), so stdout must be UTF-8 regardless of the
115
+ # console: otherwise the print raises and the warning is swallowed.
116
+ sys.stdout.reconfigure(encoding="utf-8", errors="replace", newline="")
117
+
108
118
  cwd = sys.argv[1]
109
119
  file_path = sys.argv[2]
110
120
  current = Path(cwd).resolve()
@@ -120,8 +130,13 @@ if root is None:
120
130
  raise SystemExit(0)
121
131
  sys.path.insert(0, str(root / ".cowork-flow" / "scripts"))
122
132
  try:
123
- from services.spec_check import run_edit_checks
124
- print(run_edit_checks(root, file_path))
133
+ from adapters.host.edit_scope import edit_scope_warning
134
+ print(edit_scope_warning(root, {
135
+ "cwd": cwd,
136
+ "hook_event_name": "PostToolUse",
137
+ "tool_name": "Write",
138
+ "tool_input": {"file_path": file_path},
139
+ }))
125
140
  except Exception:
126
141
  pass
127
142
  `;
@@ -305,12 +320,12 @@ export function editedFilePath(exec) {
305
320
 
306
321
 
307
322
  /**
308
- * Editor-phase spec check for one edited file. Returns the
323
+ * Editor-phase scope check for one edited file. Returns the
309
324
  * single-line warning, or '' when there is nothing to say:
310
- * no root, no interpreter, no declaration, a clean run, or
325
+ * no root, no interpreter, no active task, a clean run, or
311
326
  * a timeout. Never throws.
312
327
  */
313
- export async function runEditSpecCheck(cwd, filePath) {
328
+ export async function runEditScopeCheck(cwd, filePath) {
314
329
  if (hooksDisabled() || !cwd || !filePath) {
315
330
  return '';
316
331
  }
@@ -321,7 +336,7 @@ export async function runEditSpecCheck(cwd, filePath) {
321
336
  const attempt = (candidate) => new Promise((resolvePromise) => {
322
337
  execFile(
323
338
  candidate.command,
324
- [...candidate.args, '-c', PYTHON_EDIT_PROTOCOL, cwd, filePath],
339
+ [...candidate.args, '-c', PYTHON_EDIT_SCOPE_PROTOCOL, cwd, filePath],
325
340
  EDIT_EXEC_OPTIONS,
326
341
  (error, stdout) => {
327
342
  if (error) {
@@ -357,7 +372,7 @@ export async function runEditSpecCheck(cwd, filePath) {
357
372
  * preset deliberately does not import, so the shape is
358
373
  * built here with node's uuid.
359
374
  */
360
- function specNoticeMessage(text) {
375
+ function noticeMessage(text) {
361
376
  return {
362
377
  id: randomUUID(),
363
378
  role: 'user',
@@ -366,7 +381,7 @@ function specNoticeMessage(text) {
366
381
  kind: 'plugin',
367
382
  plugin: 'cowork-flow',
368
383
  form: 'notice',
369
- summary: 'spec-check',
384
+ summary: 'edit-scope',
370
385
  },
371
386
  };
372
387
  }
@@ -430,7 +445,7 @@ export function apply(ctx) {
430
445
  refresh(agent, false);
431
446
  });
432
447
 
433
- // Editor-phase spec-check: the tool already ran, so this
448
+ // Editor-phase scope check: the tool already ran, so this
434
449
  // only adds context for the next request and never blocks
435
450
  // the call. The waterfall awaits next() first.
436
451
  ctx.on('tools/post-execute', async (exec, _result, next) => {
@@ -442,7 +457,7 @@ export function apply(ctx) {
442
457
  }
443
458
  let warning = '';
444
459
  try {
445
- warning = await runEditSpecCheck(agent.cwd, filePath);
460
+ warning = await runEditScopeCheck(agent.cwd, filePath);
446
461
  } catch {
447
462
  warning = '';
448
463
  }
@@ -450,7 +465,7 @@ export function apply(ctx) {
450
465
  return downstream;
451
466
  }
452
467
  const contexts = [
453
- specNoticeMessage(warning),
468
+ noticeMessage(warning),
454
469
  ...((downstream && downstream.additionalContexts) || []),
455
470
  ];
456
471
  if (downstream && downstream.kind === 'block') {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cowork-flow",
3
- "version": "1.9.2",
3
+ "version": "2.0.0",
4
4
  "description": "AI 协作工作流引擎:任务生命周期、子代理调度、门禁与状态注入。将 cowork-flow 的完整工作流带入 Kimi Code。",
5
5
  "author": {
6
6
  "name": "fishlikewater",
@@ -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
@@ -175,8 +175,9 @@ function runInject(candidate, script, payload) {
175
175
  // inject.py and hand back whatever it printed. The payload spreads the
176
176
  // host input so every session/runtime-context key the Python identity
177
177
  // table knows arrives unchanged; hook_event_name and cwd are the two
178
- // fields the hook contract requires.
179
- async function renderViaInject(input) {
178
+ // fields the hook contract requires. `overrides` swaps the event name
179
+ // and adds the tool fields the PostToolUse contract needs.
180
+ async function renderViaInject(input, overrides = null) {
180
181
  if (
181
182
  process.env.COWORK_FLOW_HOOKS === "0" ||
182
183
  process.env.COWORK_FLOW_DISABLE_HOOKS === "1"
@@ -188,9 +189,11 @@ async function renderViaInject(input) {
188
189
  if (!script) {
189
190
  return ""
190
191
  }
192
+ const extra = overrides && typeof overrides === "object" ? overrides : {}
191
193
  const payload = {
192
194
  ...(input && typeof input === "object" ? input : {}),
193
- hook_event_name: "UserPromptSubmit",
195
+ ...extra,
196
+ hook_event_name: extra.hook_event_name || "UserPromptSubmit",
194
197
  cwd: inputCwd(input) || (root === pluginRoot ? null : root) || process.cwd(),
195
198
  }
196
199
  for (const candidate of pythonCandidates()) {
@@ -203,6 +206,34 @@ async function renderViaInject(input) {
203
206
  return ""
204
207
  }
205
208
 
209
+ // Editor-phase scope warning: opencode sessions get the same
210
+ // single-line out-of-scope advisory the other hosts receive on
211
+ // PostToolUse. Best-effort — a missing runtime, a timeout, or any
212
+ // error stays silent and never blocks the edit that already happened.
213
+ const EDIT_TOOL_NAMES = new Set(["edit", "write"])
214
+
215
+ function editedFilePath(input) {
216
+ const tool = typeof input?.tool === "string" ? input.tool : ""
217
+ if (!EDIT_TOOL_NAMES.has(tool)) {
218
+ return ""
219
+ }
220
+ const args = input?.args
221
+ const candidate = args && typeof args === "object" ? args.filePath : null
222
+ return typeof candidate === "string" && candidate.trim() ? candidate.trim() : ""
223
+ }
224
+
225
+ async function renderEditScopeWarning(input) {
226
+ const filePath = editedFilePath(input)
227
+ if (!filePath) {
228
+ return ""
229
+ }
230
+ return renderViaInject(input, {
231
+ hook_event_name: "PostToolUse",
232
+ tool_name: typeof input?.tool === "string" ? input.tool : "edit",
233
+ tool_input: { file_path: filePath },
234
+ })
235
+ }
236
+
206
237
  function sanitize(value) {
207
238
  return String(value ?? "")
208
239
  .trim()
@@ -262,82 +293,6 @@ function injectShellEnv(input, output) {
262
293
  }
263
294
  }
264
295
 
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
296
  // The payload's own skills directory, located from this
342
297
  // module's file path and not from the repository root: the
343
298
  // machine install puts this file in
@@ -382,11 +337,11 @@ export function registerPayloadSkills(config) {
382
337
 
383
338
  // The plugin adapter imports the hook helpers; these are
384
339
  // the unit-test surface (transport, fail-open, shell env,
385
- // edit-phase delegation). opencode never reads this module.
340
+ // edit-scope delegation). opencode never reads this module.
386
341
  export {
387
342
  renderViaInject,
388
343
  editedFilePath,
344
+ renderEditScopeWarning,
389
345
  findRepoRoot,
390
346
  injectShellEnv,
391
- runEditSpecCheck,
392
347
  }
@@ -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
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cowork-flow",
3
- "version": "1.9.2",
3
+ "version": "2.0.0",
4
4
  "description": "AI 协作工作流引擎:任务生命周期、子代理调度、门禁与状态注入。将 cowork-flow 的完整工作流带入 Qoder。",
5
5
  "displayName": "Cowork Flow",
6
6
  "author": {
@@ -41,7 +41,7 @@
41
41
  "${QODER_PLUGIN_ROOT}/hooks/inject-context.mjs"
42
42
  ],
43
43
  "timeout": 30,
44
- "statusMessage": "cowork-flow: 规范自查"
44
+ "statusMessage": "cowork-flow: 编辑越界自查"
45
45
  }
46
46
  ]
47
47
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cowork-flow",
3
- "version": "1.9.2",
3
+ "version": "2.0.0",
4
4
  "description": "AI 协作工作流引擎:任务生命周期、子代理调度、门禁与状态注入。将 cowork-flow 的完整工作流带入 ZCode。",
5
5
  "author": {
6
6
  "name": "fishlikewater",
@@ -13,6 +13,8 @@ import { runInstallOpenCodePlugin, FLAGS as OPENCODE_PLUGIN_FLAGS } from './inst
13
13
  import { runInstallQoderPlugin, FLAGS as QODER_PLUGIN_FLAGS } from './install-qoder-plugin.js';
14
14
  import { runInstallZCodePlugin, FLAGS as ZCODE_PLUGIN_FLAGS } from './install-zcode-plugin.js';
15
15
 
16
+ const KIMI_ALL_FLAGS = [...new Set([...KIMI_HOOK_FLAGS, ...KIMI_PLUGIN_FLAGS])].sort();
17
+
16
18
 
17
19
  // Which machine-level integrations a host has.
18
20
  // Deliberately not part of host-assets.json: that manifest
@@ -58,10 +60,11 @@ export const HOST_COMPONENTS = {
58
60
  }
59
61
  },
60
62
  'kimi-code': {
61
- default: 'plugin',
63
+ default: 'all',
62
64
  components: {
63
65
  plugin: { summary: 'Kimi Code plugin: the bootstrap Skill it loads at session start', flags: KIMI_PLUGIN_FLAGS, run: runInstallKimiPlugin },
64
- hook: { summary: 'Kimi Code context-injection hook as a user-level config.toml [[hooks]] row', flags: KIMI_HOOK_FLAGS, run: runInstallKimiHook }
66
+ hook: { summary: 'Kimi Code context-injection hook as a user-level config.toml [[hooks]] row', flags: KIMI_HOOK_FLAGS, run: runInstallKimiHook },
67
+ all: { summary: 'Kimi Code hook plus plugin, installed in one command (the default)', flags: KIMI_ALL_FLAGS, run: runInstallKimiAll }
65
68
  }
66
69
  }
67
70
  };
@@ -78,6 +81,24 @@ export const HOST_FLAGS = [...new Set(
78
81
  )].sort();
79
82
 
80
83
 
84
+ function withoutForceFlag(args) {
85
+ return args.filter((arg) => arg !== '--force' && !arg.startsWith('--force='));
86
+ }
87
+
88
+
89
+ // kimi-code's default component: the hook (injection, the
90
+ // required half) first, then the plugin, in one command.
91
+ // Both installers stay the only validators — unknown flags
92
+ // pass through untouched and are rejected by whichever
93
+ // installer reaches them — but `--force` cannot: the hook
94
+ // has no such flag, so the combined path strips it for the
95
+ // hook segment only, and the plugin still receives it.
96
+ async function runInstallKimiAll(args) {
97
+ await runInstallKimiHook(withoutForceFlag(args));
98
+ await runInstallKimiPlugin(args);
99
+ }
100
+
101
+
81
102
  function resolveHost(token) {
82
103
  const id = hostRegistry.platformIdFor(token);
83
104
  if (id === null) {
@@ -41,9 +41,11 @@ const HOST_NOTE = `Hosts: ${Object.entries(HOST_COMPONENTS)
41
41
  .join('; ')}.`;
42
42
  // The union across hosts contains one gap: kimi-code's
43
43
  // hook has no --force, so the help names it instead of
44
- // letting the user hit a usage error.
44
+ // letting the user hit a usage error. kimi-code's default
45
+ // component routes --force to the plugin segment only.
45
46
  const HOST_FORCE_NOTE = '`--force` applies to every component except kimi-code\'s hook, '
46
- + 'which overwrites unconditionally and therefore does not accept the flag.';
47
+ + 'which overwrites unconditionally and therefore does not accept the flag; '
48
+ + 'kimi-code\'s default component consumes it for the plugin only.';
47
49
 
48
50
  export const COMMANDS = [
49
51
  {
@@ -1 +1 @@
1
- 1.9.2
1
+ 2.0.0
@@ -13,7 +13,7 @@ capabilities:
13
13
  runtimeContextDispatch: native
14
14
  runtimeContextBinding: shim
15
15
  runtimeContextCleanup: native
16
- editScopeWarning: unsupported
16
+ editScopeWarning: plugin
17
17
  dispatch:
18
18
  primitive: subagent
19
19
  agentsPath: .claude/agents
@@ -13,7 +13,7 @@ capabilities:
13
13
  runtimeContextDispatch: native
14
14
  runtimeContextBinding: shim
15
15
  runtimeContextCleanup: native
16
- editScopeWarning: unsupported
16
+ editScopeWarning: plugin
17
17
  dispatch:
18
18
  primitive: spawn_agent
19
19
  forkTurns: none
@@ -13,7 +13,7 @@ capabilities:
13
13
  runtimeContextDispatch: native
14
14
  runtimeContextBinding: shim
15
15
  runtimeContextCleanup: shim
16
- editScopeWarning: unsupported
16
+ editScopeWarning: plugin
17
17
  dispatch:
18
18
  primitive: subagent
19
19
  memoryPath: AGENTS.md
@@ -13,7 +13,7 @@ capabilities:
13
13
  runtimeContextDispatch: native
14
14
  runtimeContextBinding: shim
15
15
  runtimeContextCleanup: native
16
- editScopeWarning: unsupported
16
+ editScopeWarning: plugin
17
17
  dispatch:
18
18
  primitive: task
19
19
  agentsPath: .opencode/agents