cowork-flow 1.4.0 → 1.6.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.
- package/CHANGELOG.md +81 -1
- package/README.md +85 -9
- package/package.json +2 -2
- package/presets/dsh/plugins/workflow-state.js +152 -0
- package/presets/kimi-code/hooks/cowork-flow-inject.mjs +113 -0
- package/src/cli.js +6 -0
- package/src/commands/install-dsh-preset.js +36 -2
- package/src/commands/install-kimi-hook.js +343 -0
- package/src/lib/host-assets.js +1 -10
- package/template/.codex/hooks.json +12 -0
- package/template/.cowork-flow/.version +1 -1
- package/template/.cowork-flow/adapters/kimi-code/adapter.yaml +31 -0
- package/template/.cowork-flow/scripts/adapters/cli/spec_check.py +1 -1
- package/template/.cowork-flow/scripts/adapters/cli/state.py +1 -1
- package/template/.cowork-flow/scripts/adapters/cli/subagent.py +38 -23
- package/template/.cowork-flow/scripts/adapters/cli/task.py +0 -3
- package/template/.cowork-flow/scripts/adapters/cli/task_lifecycle_commands.py +5 -2
- package/template/.cowork-flow/scripts/adapters/host/claude_code_policy.py +3 -14
- package/template/.cowork-flow/scripts/adapters/host/host_manifest.py +1 -2
- package/template/.cowork-flow/scripts/adapters/host/inject.py +14 -8
- package/template/.cowork-flow/scripts/adapters/host/kimi_code_policy.py +21 -0
- package/template/.cowork-flow/scripts/adapters/host/workflow_state_hook.py +61 -20
- package/template/.cowork-flow/scripts/adapters/host/zcode_policy.py +0 -17
- package/template/.cowork-flow/scripts/adapters/mcp/state_server.py +1 -1
- package/template/.cowork-flow/scripts/adapters/review/test_intent.py +0 -31
- package/template/.cowork-flow/scripts/infra/paths.py +0 -8
- package/template/.cowork-flow/scripts/run.py +1 -0
- package/template/.cowork-flow/scripts/runtime/host_identity.py +260 -0
- package/template/.cowork-flow/scripts/runtime/session_state.py +48 -53
- package/template/.cowork-flow/scripts/services/context_discovery.py +0 -35
- package/template/.cowork-flow/scripts/services/fact_view.py +1 -2
- package/template/.cowork-flow/scripts/services/lifecycle_checks.py +0 -20
- package/template/.cowork-flow/scripts/services/spec_check.py +1 -1
- package/template/.cowork-flow/scripts/services/task_context.py +0 -2
- package/template/.cowork-flow/spec/contracts/context-injection.md +91 -5
- package/template/.cowork-flow/spec/contracts/fact-layer-access.md +2 -1
- package/template/.cowork-flow/spec/contracts/spec-checks.md +16 -5
- package/template/.cowork-flow/spec/runtime/host-assets.json +49 -0
- package/template/.cowork-flow/spec/runtime/index.md +1 -1
- package/template/.cowork-flow/spec/schemas/host-assets.schema.json +1 -8
- package/template/.cowork-flow/spec/schemas/index.md +1 -0
- package/template/.kimi-code/agents/cowork-check.md +29 -0
- package/template/.kimi-code/agents/cowork-implement.md +30 -0
- package/template/.kimi-code/agents/cowork-research.md +23 -0
- package/template/.opencode/plugins/cowork-flow.js +86 -0
- package/template/.zcode/.zcode-plugin/plugin.json +1 -1
- package/template/AGENTS.md +1 -0
- package/template/skills/runtime-health/scripts/doctor.py +186 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,85 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.6.0 - 2026-09-20
|
|
4
|
+
|
|
5
|
+
### 门禁诚实化(收集一致 / 平台 skip / CI 同门禁)
|
|
6
|
+
|
|
7
|
+
- **收集一致**:`tests/test_agents_managed_block.py` 的两条断言原为模块级函数,`python -m unittest`(发布门禁与 `test:template` 用的收集器)收集不到它们(实测 `Ran 0 tests`),只有 pytest 能看到。现移入 `unittest.TestCase`,并进入 `CORE_TEMPLATE_TEST_MODULES`(core 套件 176 → 178)。新增 `tests/test_test_collection.py` 守卫:`tests/` 下再出现对 unittest 不可见的模块级 `def test_*` 即失败。
|
|
8
|
+
- **Windows 静默通过**:`test/dsh-home-patch.test.js` 此前在 win32 顶层 `process.exit(0)`,`node --test` 把整个文件记为**通过文件**(不进 skipped、不进 fail)。现改为 7 个用例带 `{ skip: <具体原因> }`,Windows 上如实计入 skipped。
|
|
9
|
+
- **恒真断言**:`tests/test_host_adapters.py` 三处「断言自己刚写死的字面量长度」(`assertEqual(2, len(surfaces))` 等)改为非空断言——不再恒真,也不引入需要同步维护的数字。
|
|
10
|
+
- **CI 与发布同门禁**:ubuntu job 不再分步跑 fast / integration / core,合并为一步 `npm run release:check`(与 publish.yml 完全相同的命令,`setup-python` 前置到它之前);`test/package.test.js` 新增断言锁定该一致性。此前 `test:node:full` 与 `test:template:full` 两层只在发布时首次执行,PR 侧从不运行。
|
|
11
|
+
|
|
12
|
+
### 宿主必需集合单源化
|
|
13
|
+
|
|
14
|
+
- 删除 Python `host_manifest.REQUIRED_CAPABILITY_MATRIX_HOSTS` 与 JS 镜像常量:`capabilityMatrix.hosts` 的必需宿主集合改由 manifest 的 `platforms` 派生(`set(platform_ids)` / `new Set([...platformIds])`)。
|
|
15
|
+
- 删除 `host-assets.schema.json` 中第三处宿主名枚举——它处于漂移状态(要求 5 家、含 `dsh`、缺 `kimi-code`,而数据是 6 家)。schema 只约束结构,宿主覆盖由运行时按数据校验。
|
|
16
|
+
- 新增共享 fixture `tests/fixtures/host-manifest/invalid-missing-matrix-host.json`(平台已声明但矩阵缺条目),Python 与 JS 两侧都拒绝(`capability matrix missing host: codex`),证明常量删除后该规则仍生效。
|
|
17
|
+
- `spec/runtime/index.md` 记录派生规则。
|
|
18
|
+
|
|
19
|
+
### 死代码与文档漂移清理
|
|
20
|
+
|
|
21
|
+
- 删除仅测试可达的 skill-path 链:`context_discovery` 的 `detect_installed_platforms`(手写 3 宿主)、`SKILL_REPLICA_DIR_BY_PLATFORM`、`skill_root`、`skill_path`,以及 `adapters/cli/task.py` 的三个未调用别名。该链在生产代码零调用,真实契约由 `infra/skill_manifest` 与 manifest 的 `skillTarget` 承担。
|
|
22
|
+
- 删除 `infra/paths.py` 中 `return` 之后的孤儿 try 块(引用未定义的 `file_path`,为 2026-08-05 移除 journal 流程时遗留)。
|
|
23
|
+
- 删除 `fact_view.STAGE_CONTRACT_STATES`(文件内零引用;活定义在 `workflow_state_hook`)、`lifecycle_checks` 两个零调用的 `*_blockers` 包装、`adapters/review/test_intent` 的恒空校验器 `validate_test_intent` 与三个未被读取的常量,以及断言其返回空的同义用例。
|
|
24
|
+
- 文档对齐:README 保护文件描述与 `syncPolicy` 实际一致(两个 spec 例外文件 + `.developer`);两处 spec-check 文案 3s → 2.5s;`spec/schemas/index.md` 补列 `host-assets.schema.json`;清理指向已 untrack `docs/` 的历史死链。
|
|
25
|
+
|
|
26
|
+
### DSH 注入接线覆盖
|
|
27
|
+
|
|
28
|
+
- `presets/dsh/plugins/workflow-state.js` 的 `apply(ctx)` 此前零测试。新增用例覆盖:四个刷新事件的接线、section 注册(name/order)、无缓存时返回空文本、非编辑调用下 `tools/post-execute` 原样透传 downstream、非 cowork-flow 根目录静默降级——全部在 Windows 上真实执行。「刷新替换而非累积」用例按既有惯例在 Windows skip(宿主解释器限制)。
|
|
29
|
+
|
|
30
|
+
### Windows 检出与行尾契约
|
|
31
|
+
|
|
32
|
+
- 根因:`presets/kimi-code/hooks/cowork-flow-inject.mjs` 落在 `* text=auto` 下且未被 `.gitattributes` 钉行尾,Windows 全新检出(Git for Windows 默认 `core.autocrlf=true`,与 `windows-latest` 一致)把它写成 CRLF;`install-kimi-hook` 原样拷贝为全局 hook shim,于是 `test/kimi-hook.test.js` 的 `/^#!\/usr\/bin\/env node\n/` 断言失败,`test:fast` → `windows-core` 确定性红。
|
|
33
|
+
- 修复:`.gitattributes` 为 `*.js`/`*.mjs` 声明 `text eol=lf`(与已钉的 `.py`/`.md`/`.json` 等一致);`test/package.test.js` 新增行尾契约断言锁定该声明。
|
|
34
|
+
- 为什么只在 Windows CI 可见:开发者工作区与 ubuntu 检出都是 LF,只有 Windows 全新检出是 CRLF。断言保持严格——CRLF shebang 的脚本在 POSIX 上无法直接执行,要修的是检出契约而不是判据。
|
|
35
|
+
- 证据:全新检出实测踩中同一断言失败(`test:fast`);仅把该文件规范化为 LF → 整条 `npm run test:windows:core` exit 0;Node 20.12.0 / 20.20.2 / 24.14.1 结果一致,与 Node 版本无关。
|
|
36
|
+
|
|
37
|
+
### 净变化
|
|
38
|
+
|
|
39
|
+
- 运行时代码(`template/.cowork-flow/scripts` + `src`):**+4 / −114,净 −110 行**;runtime 模块数 80 不变(行尾修复不涉及运行时代码)。
|
|
40
|
+
- 本次门禁工作:`e262196` **+365 / −200**(含 2 个新文件 +162 行)、`7fa3f8b` +3 / −2,行尾修复为小改动(行尾声明 + 契约守卫 + 记账);新增集中在 `tests/` 与 `test/`。**AC-006「净行数 ≤ 0」未达成**——三条口径都是新增大于删除。
|
|
41
|
+
- 版本口径参考:自 1.5.0 发版提交 `a4b0bfc` 起,1.6.0 全部提交的净变化由本版本更早的宿主特性主导,不属本次门禁工作。
|
|
42
|
+
- 契约指纹:registry 登记的契约文件与 `spec/runtime/host-assets.json` 均未改动,指纹不变。
|
|
43
|
+
|
|
44
|
+
### 平台差异与远端确认
|
|
45
|
+
|
|
46
|
+
- Windows 上 `test:node:full` 的 skipped 明细新增 `dsh-home-patch` 的 7 条;这些用例在 ubuntu 发布门禁中真实执行。
|
|
47
|
+
- POSIX-only:`a session refresh replaces the cached block instead of accumulating` 与既有 DSH 内容用例在本机(Windows)skip,未在本机执行。
|
|
48
|
+
- 远端确认(提交 `e262196`,CI run 35503939861):ubuntu job 首次执行 `release:check` **通过**——17 条此前只在发布时运行的 `release.test.js` POSIX 用例在 CI 上真实执行并全部通过。
|
|
49
|
+
- 同一 run 的 `windows-core` 失败(步骤 `Run Windows core verification`,exit 1)。该 job 在改动前的 `aa0e34e` 上即为同样的失败,不是本版本的功能改动引入;根因是行尾契约缺失,已在本版本修复(见「Windows 检出与行尾契约」),远端日志无需仓库权限即可复现——全新 `git clone` 即可稳定踩中。
|
|
50
|
+
|
|
51
|
+
## 1.5.0 - 2026-09-18
|
|
52
|
+
|
|
53
|
+
### 发版开关 `--no-publish`
|
|
54
|
+
|
|
55
|
+
- `scripts/release.sh` 新增 `--no-publish`:刷新、self-instance 镜像、双套测试门禁、版本 bump、changelog 校验、提交、tag 全部照常执行,只跳过最后的 `npm publish`,并提示 tag 仍是本地(后续走 `npm publish` 或 `gh release create v<v>` 触发 CI 通道)。不带该 flag 时行为逐项不变。
|
|
56
|
+
- 参数解析改为逐个消费:`--no-publish` 位置无关且可重复;release-type 与 `--version` 仍互斥、最多出现一次,重复被拒。
|
|
57
|
+
- 动机是既有的 CI 发布通道——tag 落地后由 `gh release create` 触发 publish.yml,而脚本此前只能一路 publish 到底。
|
|
58
|
+
|
|
59
|
+
### 编辑期覆盖扩展(codex / opencode / dsh)
|
|
60
|
+
|
|
61
|
+
- **codex**:`hooks.json` 注册 `PostToolUse`(matcher `apply_patch|Write|Edit`),编辑后违规经既有 `spec_only_post_tool_use` 通道(stderr + exit 2)反馈;运行时无需新增分支。
|
|
62
|
+
- **opencode**:插件新增 `tool.execute.after` 钩子——`edit`/`write` 命中时调用 `run spec-check --phase edit --file <path> --throttled`,把非空单行结果追加到工具结果文本;2.5 秒超时,运行时不存在的静默且不阻断编辑。
|
|
63
|
+
- **dsh**:预设插件注册 `tools/post-execute` waterfall——`write`/`edit` 命中时调同一 Python 协议(`run_edit_checks`),把单行告警作为 `additionalContexts` 附件送入下一轮请求;2.5 秒超时,无根/无解释器/协议异常一律静默。DSH 的 `tools/result` 只能观察不能回写,故走 post-execute 而非复用刷新监听。
|
|
64
|
+
- **路径提取多形态**:PostToolUse 的路径提取兼容 `file_path`/`filePath`/`path` 与 codex `apply_patch` 补丁文本(`*** Update/Add/Delete File:` 行);同一路径去重,避免重复消耗节流窗口。
|
|
65
|
+
- **契约诚实化**:`spec-checks.md` 矩阵与 README 从"opencode 降级:无"改为准确表述——五个宿主全部覆盖,其中 zcode 仅主会话、dsh 经预设注入;此前的"降级"是项目未接线,不是宿主能力缺失。
|
|
66
|
+
- **实弹验收项(未在本地执行)**:codex 会话编辑后 stderr 反馈可见(需宿主 hook 信任批准);opencode 会话 edit 后工具结果含单行警告;dsh 会话编辑后下一轮上下文含 spec-check 通知。
|
|
67
|
+
|
|
68
|
+
### DSH 预设过期检测
|
|
69
|
+
|
|
70
|
+
- **版本标记**:`install-dsh-preset` 安装后在预设目录写入 `.cowork-flow-preset.json`(`version` + `installedAt`);已安装且版本不同时,不带 `--force` 的重复执行输出过期提示与更新命令,而不是仅报 "already installed"。
|
|
71
|
+
- **doctor 检测**:新增只读 advisory 检查 `check_dsh_preset`——预设缺失时不报告;无标记或标记不可读 → `PRESET-UNKNOWN-VERSION`;与项目 `.cowork-flow/.version` 不一致 → `PRESET-STALE`;两者都附 `install-dsh-preset --force` 提示,且不进入 doctor 的 error 集合(`ok` 不受影响)。
|
|
72
|
+
- **背景**:预设不随 `sync`/npm 更新,此前升级路径只存在于 README 散文,用户可能长期停留在旧注入逻辑且没有任何提示;协议失败时该宿主的注入会静默降级为空输出。
|
|
73
|
+
|
|
74
|
+
### 下游可发现性与文档权威性(spec-check)
|
|
75
|
+
|
|
76
|
+
- **发现路径补齐**:`run` 帮助列出 `spec-check`;`AGENTS.md` 的 managed block(模板与根同块)加指针行;README 新增「规范挂命令」与「MCP 客户端接入」两节,能力表与常用命令同步。
|
|
77
|
+
- **契约修正**:`spec/contracts/spec-checks.md` 的「归属与 sync 策略」改为与实际一致——`spec/` 是 sync 保护前缀,已安装项目不会被同步改写;具名例外是 `syncPolicy.safeFiles` 中的契约文件。该文件加入 `safeFiles`(同 `workflow-state-templates.md` 先例),使契约修正能到达已安装下游。
|
|
78
|
+
- **fallback 自救路径**:`FALLBACK_BINDING_BLOCKER` 与两处 lifecycle 拒绝文案改为指向 workflow-state 头部的 `session` 属性(`COWORK_FLOW_CONTEXT_ID`),不再给出无任务可传的 `pass <task-dir>` 单一出路。
|
|
79
|
+
- **悬空引用清理**:README 与模板脚本/spec 中指向已 untrack 的 `docs/` 的引用全部改写或内联(MCP 客户端配置要点保留在 README)。
|
|
80
|
+
- **测试**:新增根/模板 managed block 字节相等与指针断言、fallback blocker 文案可执行性断言。
|
|
81
|
+
- **已知合法副作用**:contract fingerprint 由 `d1d0e2536e8fa150` 更新为 `2dc34375d28f8103`(内容派生;`host-assets.json` 的 `safeFiles` 条目变更)。
|
|
82
|
+
|
|
3
83
|
## 1.4.0 - 2026-09-14
|
|
4
84
|
|
|
5
85
|
### Hook 会话身份绑定(fix(runtime))
|
|
@@ -107,7 +187,7 @@
|
|
|
107
187
|
|
|
108
188
|
> **版本内容错位说明**:npm registry 上的 1.0.0 tarball 发布于 2026-08-26(仅含发版脚本修复之前的代码)。1.0.0 段下述的里程碑描述以本 1.1.0 为其实际发布载体——阶段 0-3 的全部内容自本版本起进入 npm 分发。
|
|
109
189
|
|
|
110
|
-
### 方向落地(阶段 0-3,详见 1.0.0
|
|
190
|
+
### 方向落地(阶段 0-3,详见 1.0.0 段)
|
|
111
191
|
|
|
112
192
|
- 阶段 0:README 定位改写为「运行时上下文与协作事实层」;注入协议契约 `spec/contracts/context-injection.md`;契约指纹序列化三线统一 + slim 全覆盖 + 跨 host 一致性测试。
|
|
113
193
|
- 阶段 1:`run state [task] --json` 事实视图;`<workflow-state>` 属性事实头 + `<decision-anchor>` 决策要点注入(三线一致)。
|
package/README.md
CHANGED
|
@@ -12,8 +12,9 @@
|
|
|
12
12
|
|---|---|
|
|
13
13
|
| 任务流程 | `task next --json` 给出下一步 action,`task next --run` 只执行当前 action。 |
|
|
14
14
|
| 事实接入 | `run state [task] --json` 事实视图;`run mcp-state` 无依赖 MCP 只读服务(`task_state` / `task_list` / `task_scope` / `task_specs`)供任何 MCP 客户端查询;`task scope` / `task specs` 为 CLI 同源事实命令。 |
|
|
15
|
+
| 规范挂命令 | spec 首部 frontmatter 声明 `checks:` 命令,按编辑期/收口期执行;`run spec-check` 输出三态门禁(pass / violation / unchecked)。 |
|
|
15
16
|
| 运行健康 | `doctor` 诊断 runtime、host assets、Skill replica 和任务 hygiene,不推进生命周期。 |
|
|
16
|
-
| Host 分发 | Host Asset Manifest 驱动 Codex / OpenCode / Claude Code / ZCode / DeepSeek Harness 资产和 obsolete 清理。 |
|
|
17
|
+
| Host 分发 | Host Asset Manifest 驱动 Codex / OpenCode / Claude Code / ZCode / Kimi Code / DeepSeek Harness 资产和 obsolete 清理。 |
|
|
17
18
|
| 批处理与讨论 | Batch 发布 Host action;Party Mode 只输出 advisory final facts。 |
|
|
18
19
|
| 发布准备 | `release:check`、`CHANGELOG.md`、`pack:check` 固定发布前证据。 |
|
|
19
20
|
|
|
@@ -25,7 +26,7 @@
|
|
|
25
26
|
| 理解任务如何流转 | [任务流程](#任务流程) |
|
|
26
27
|
| 处理故障或漂移 | [支持与故障诊断](#支持与故障诊断) |
|
|
27
28
|
| 做发布前检查 | [发布](#发布)、[`CHANGELOG.md`](CHANGELOG.md) |
|
|
28
|
-
|
|
|
29
|
+
| 了解项目演进与版本内容 | [`CHANGELOG.md`](CHANGELOG.md) |
|
|
29
30
|
| 接入到自己项目 | [接入原则](#接入原则) |
|
|
30
31
|
|
|
31
32
|
## 适用 / 不适用
|
|
@@ -68,14 +69,14 @@ cowork-flow install-dsh-hook
|
|
|
68
69
|
|
|
69
70
|
# MCP 客户端接入(可选):全局注册一次,任意项目查询任务事实;
|
|
70
71
|
# 项目级 opt-in(如 claude-code 的 .mcp.json)与注册健康检测见
|
|
71
|
-
# `run doctor
|
|
72
|
+
# `run doctor`;各客户端配置样例见下方「MCP 客户端接入」一节
|
|
72
73
|
cowork-flow mcp-state
|
|
73
74
|
|
|
74
75
|
# 维护者发布前检查
|
|
75
76
|
npm run release:check
|
|
76
77
|
```
|
|
77
78
|
|
|
78
|
-
平台选项:`codex` / `opencode` / `claude-code` / `dsh` / `zcode` / `all`(逗号分隔)
|
|
79
|
+
平台选项:`codex` / `opencode` / `claude-code` / `dsh` / `zcode` / `kimi-code` / `all`(逗号分隔)
|
|
79
80
|
|
|
80
81
|
## 仓库结构
|
|
81
82
|
|
|
@@ -89,6 +90,7 @@ template/
|
|
|
89
90
|
├── .opencode/ # OpenCode agents / commands / plugins
|
|
90
91
|
├── .dsh/ # DeepSeek Harness 标记(sync 检测 + 说明)
|
|
91
92
|
├── .zcode/ # ⭐ ZCode 插件(hooks + skills + agents + scaffold instructions)
|
|
93
|
+
├── .kimi-code/ # Kimi Code fixed agents(cowork-implement / check / research)
|
|
92
94
|
└── .cowork-flow/
|
|
93
95
|
├── config.yaml # 项目配置
|
|
94
96
|
├── scripts/ # Python 运行时
|
|
@@ -115,6 +117,7 @@ Skills 维护在 `template/skills/` 唯一源码,`init` / `sync` 时按目录
|
|
|
115
117
|
|---|---|
|
|
116
118
|
| `codex` / `opencode` | `.agents/skills/` |
|
|
117
119
|
| `dsh` | `.agents/skills/` |
|
|
120
|
+
| `kimi-code` | `.agents/skills/` |
|
|
118
121
|
| `claude-code` | `.claude/skills/` |
|
|
119
122
|
| `zcode` | `.cowork-flow/skills/`(内核 owner 解析用;系统提示层仍由 ZCode 插件单源提供,不重复加载) |
|
|
120
123
|
|
|
@@ -130,6 +133,7 @@ Skills 维护在 `template/skills/` 唯一源码,`init` / `sync` 时按目录
|
|
|
130
133
|
| `install-zcode-plugin` | 安装 ZCode 插件到全局缓存 |
|
|
131
134
|
| `install-dsh-preset` | 安装 DSH agent 预设到 `~/.dsh/.agent-presets/cowork-flow/`(整套 agent,可选) |
|
|
132
135
|
| `install-dsh-hook` | 机器级注册 workflow-state hook 组合行到 `$DSH_HOME/cordis.patch.yml`(当前 DSH 的 agent 提示不收集 host 层 section,实时注入请用预设方式) |
|
|
136
|
+
| `install-kimi-hook [--dry-run] [--force] [--uninstall]` | 机器级注册 UserPromptSubmit hook 到 `$KIMI_CODE_HOME/config.toml`(默认 `~/.kimi-code/`),向每个 Kimi Code 会话实时注入工作流上下文 |
|
|
133
137
|
| `update [--dry-run]` | 升级 CLI 本身 |
|
|
134
138
|
| `mcp-state` | 全局 MCP 事实入口:从 cwd 向上定位项目运行时并透传 `run mcp-state`(全局注册一次,所有 cowork-flow 项目通用) |
|
|
135
139
|
|
|
@@ -137,7 +141,7 @@ Skills 维护在 `template/skills/` 唯一源码,`init` / `sync` 时按目录
|
|
|
137
141
|
|
|
138
142
|
| 选项 | 说明 |
|
|
139
143
|
|---|---|
|
|
140
|
-
| `--platform <p>` | 平台:`codex` / `opencode` / `claude-code` / `dsh` / `zcode` / `all` |
|
|
144
|
+
| `--platform <p>` | 平台:`codex` / `opencode` / `claude-code` / `dsh` / `zcode` / `kimi-code` / `all` |
|
|
141
145
|
| `--developer <n>` | 开发者名称 |
|
|
142
146
|
| `--force` | 覆盖已有文件 |
|
|
143
147
|
| `--dry-run` | 预览不写入 |
|
|
@@ -146,7 +150,7 @@ Skills 维护在 `template/skills/` 唯一源码,`init` / `sync` 时按目录
|
|
|
146
150
|
|
|
147
151
|
- **自动识别**已安装 host 目录,只同步对应平台资产
|
|
148
152
|
- **Skills** 从 `template/skills/` 按平台分发
|
|
149
|
-
- **保护文件**:`config.yaml`、`spec
|
|
153
|
+
- **保护文件**:`config.yaml`、`.developer`、`spec/`(例外:`spec/contracts/workflow-state-templates.md` 与 `spec/contracts/spec-checks.md` 两个契约文件)、任务、计划
|
|
150
154
|
- **正式版旧资产清理**:旧脚本位置、旧 adapter 资产和已废弃文件按 Host Asset Manifest 的 `obsoleteFiles` 清理;用户保护文件保持不变
|
|
151
155
|
- **事务恢复**:上次未完成事务会在新一轮 sync 前恢复;事务元数据缺失或损坏时 fail-closed,不在未知状态上继续写入
|
|
152
156
|
- **Dry-run readiness**:`sync --dry-run` 只构建计划并输出 `readiness=<json>`,不写文件或事务状态;字段包含 `wouldCopy`、`wouldSkipProtected`、`wouldRemoveObsolete`、`hostAssetRefresh`、`pendingRecovery`、`warnings`
|
|
@@ -164,6 +168,32 @@ Skills 维护在 `template/skills/` 唯一源码,`init` / `sync` 时按目录
|
|
|
164
168
|
- 默认查询 npm latest,发现新版本时执行 `npm install -g cowork-flow@latest`
|
|
165
169
|
- `--dry-run` 只输出当前版本、最新版本和 `readiness=<json>`,其中 `update.wouldInstall` 表示是否会执行全局安装,不调用安装命令
|
|
166
170
|
|
|
171
|
+
## MCP 客户端接入
|
|
172
|
+
|
|
173
|
+
事实层以只读 MCP 服务(工具 `task_state` / `task_list`)提供给任意客户端。两种入口:
|
|
174
|
+
|
|
175
|
+
- **全局(推荐)**:`cowork-flow mcp-state`(npm 全局 CLI 透传)。注册一次,所有 cowork-flow 项目通用——项目根由客户端启动时的 cwd 向上解析。
|
|
176
|
+
- **项目级**:`<project>/.cowork-flow/run mcp-state`(不依赖全局安装,每项目一份配置)。
|
|
177
|
+
|
|
178
|
+
stdio 注册样例:
|
|
179
|
+
|
|
180
|
+
```toml
|
|
181
|
+
# Codex(~/.codex/config.toml)
|
|
182
|
+
[mcp_servers.cowork-flow]
|
|
183
|
+
command = "cowork-flow"
|
|
184
|
+
args = ["mcp-state"]
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
```json
|
|
188
|
+
// OpenCode(~/.config/opencode/opencode.json)
|
|
189
|
+
{"mcp": {"cowork-flow": {"type": "local", "command": ["cowork-flow", "mcp-state"], "enabled": true}}}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
- **Claude Code**:`claude mcp add -s user cowork-flow -- cowork-flow mcp-state`(项目级 `.mcp.json` 写同构条目)。
|
|
193
|
+
- **ZCode**:客户端设置的 MCP 服务器中添加同构 stdio 条目(命令 `cowork-flow`、参数 `mcp-state`)。
|
|
194
|
+
|
|
195
|
+
项目级注册的健康检查由 `./.cowork-flow/run doctor` 报告。
|
|
196
|
+
|
|
167
197
|
## ZCode 插件
|
|
168
198
|
|
|
169
199
|
```bash
|
|
@@ -195,7 +225,7 @@ cowork-flow init ./my-project --platform dsh # 项目资产:AGENTS.md + .age
|
|
|
195
225
|
cowork-flow install-dsh-hook # 机器级:注册 hook 组合行(实时注入见下方说明)
|
|
196
226
|
```
|
|
197
227
|
|
|
198
|
-
`install-dsh-hook` 把 `workflow-state.js` 插件作为 `insert:` patch 注册到 `$DSH_HOME/cordis.patch.yml`(未设置 `DSH_HOME` 时默认 `~/.dsh`),组合层面可被 `dsh --dump-config` 验证。经实测(DSH 0.1.1-rc.1),**agent 提示组装不收集 host 层 section**:该组合行不会在会话系统提示中产生 `<workflow-state>` 块。当前 DSH 版本下实时注入仍需预设方式(`install-dsh-preset`);本命令保留为组合层面的幂等注册能力(卸载见下),待 DSH 支持 agent-scope patch / workspace
|
|
228
|
+
`install-dsh-hook` 把 `workflow-state.js` 插件作为 `insert:` patch 注册到 `$DSH_HOME/cordis.patch.yml`(未设置 `DSH_HOME` 时默认 `~/.dsh`),组合层面可被 `dsh --dump-config` 验证。经实测(DSH 0.1.1-rc.1),**agent 提示组装不收集 host 层 section**:该组合行不会在会话系统提示中产生 `<workflow-state>` 块。当前 DSH 版本下实时注入仍需预设方式(`install-dsh-preset`);本命令保留为组合层面的幂等注册能力(卸载见下),待 DSH 支持 agent-scope patch / workspace 级组合后可直接生效。
|
|
199
229
|
|
|
200
230
|
在未安装 cowork-flow 的项目里 hook 完全无感:JS 侧根目录预检直接短路——不注入内容、不启动 Python 进程。全局开关(环境变量):`COWORK_FLOW_HOOKS=0` / `COWORK_FLOW_DISABLE_HOOKS=1`。卸载:`cowork-flow install-dsh-hook --uninstall`(`--force` 同时删除插件文件)。
|
|
201
231
|
|
|
@@ -213,10 +243,28 @@ cowork-flow install-dsh-preset --dry-run # 预览不写入
|
|
|
213
243
|
|
|
214
244
|
安装到 `~/.dsh/.agent-presets/cowork-flow/`(`DSH_HOME` 存在时以其为准):`agent.cordis.yml` + `preset.yml` + 全部流程技能。安装后在 DeepSeek Harness 中新建会话并选择 **Cowork Flow** 预设即可使用:persona 携带流程门禁规则,技能目录随预设挂载,不依赖项目本地副本。
|
|
215
245
|
|
|
246
|
+
预设是**一次性安装的机器级资产**:它不随 `sync` 或 npm 更新。升级 cowork-flow 后需要重跑 `cowork-flow install-dsh-preset --force` 才会刷新(不带 `--force` 的重复执行是空操作,安装器会在版本不同时给出提示)。安装时会在预设目录写入 `.cowork-flow-preset.json` 版本标记;`./.cowork-flow/run doctor` 比对标记与项目 runtime 版本,过期或缺失时输出 warning 与更新命令。
|
|
247
|
+
|
|
216
248
|
预设组合是部署 `standard` 预设的拷贝 + 最小改动(persona 流程规则、`skill-filesystem` 指向预设自带 `skills/`);`cowork-flow init --platform dsh` 仍负责项目级资产(`AGENTS.md`、`.agents/skills/`、`.dsh/` 标记)。
|
|
217
249
|
|
|
218
250
|
预设内置 **workflow-state hook**(`plugins/workflow-state.js`):DSH 原生等效于 Codex/Claude hook,向系统提示末尾注入与其它宿主同构的 `<workflow-state>` 块,每条用户消息刷新一次,并在生命周期命令(`task`/`subagent`/`resume`)执行完成后立即轮内刷新(替换语义,不累积)。项目无 `.cowork-flow` 根、缺少 Python 或设 `COWORK_FLOW_HOOKS=0` / `COWORK_FLOW_DISABLE_HOOKS=1` 时静默降级,由 AGENTS.md 门禁的运行导航器兜底。
|
|
219
251
|
|
|
252
|
+
## Kimi Code hook
|
|
253
|
+
|
|
254
|
+
```bash
|
|
255
|
+
cowork-flow install-kimi-hook # 安装(无条件覆盖);--force 与不带旗标同义
|
|
256
|
+
cowork-flow install-kimi-hook --dry-run # 预览将写入的托管块,不写文件
|
|
257
|
+
cowork-flow install-kimi-hook --uninstall # 卸载托管块、shim 与版本标记
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
安装写入用户级 Kimi Code home(`KIMI_CODE_HOME`,未设置时默认 `~/.kimi-code/`):hook 脚本 `hooks/cowork-flow-inject.mjs`、版本标记 `hooks/.cowork-flow-kimi-hook.json`,并在 `config.toml` 追加一段由注释标记包裹的托管块——一条 `[[hooks]]`,`event = "UserPromptSubmit"`、`command`、`timeout = 30`,不写 `matcher`(即匹配每条提交的提示)。配置文件按文本编辑、不做 TOML 重排,托管块以外的用户内容原样保留;卸载只移除这段托管块和上面两个文件,`config.toml` 因此变空时一并删除。
|
|
261
|
+
|
|
262
|
+
Kimi Code 只注册 `UserPromptSubmit` 一个事件:`SessionStart` / `PostToolUse` 等观察型事件的 stdout 会被宿主丢弃,无法注入内容。hook 的 stdout 就是注入正文,宿主把它追加进提示上下文;shim 在项目根目录预检失败(非 cowork-flow 项目)时直接短路,不启动 Python 进程,全局开关为 `COWORK_FLOW_HOOKS=0` / `COWORK_FLOW_DISABLE_HOOKS=1`。
|
|
263
|
+
|
|
264
|
+
> 安装或更新后需要**重启 Kimi Code 会话**(或重新加载配置)才会加载 hook。
|
|
265
|
+
|
|
266
|
+
Kimi Code 的 Bash 工具不导出会话标识环境变量,CLI 侧身份只能取自注入头里的 `session="kimi_<id>"`,需要显式传 `COWORK_FLOW_CONTEXT_ID`(或 `COWORK_FLOW_HOST=kimi-code`)。`./.cowork-flow/run doctor` 把该 hook 的注册情况作为 warning 级项报告(`HOOK-NOT-INSTALLED` / `HOOK-SHIM-MISSING` / `HOOK-UNKNOWN-VERSION` / `HOOK-STALE`),不计入 errors。
|
|
267
|
+
|
|
220
268
|
## 任务流程
|
|
221
269
|
|
|
222
270
|
```
|
|
@@ -254,6 +302,29 @@ flowchart TD
|
|
|
254
302
|
|
|
255
303
|
Batch 使用任务图和持久化 Host action:运行 `task next <parent-task> --run --intent batch --auto --approved` 获取 `next_action`;Host 完成真实生命周期动作后继续通过 `task next` 导航,不暴露独立 batch 子命令。
|
|
256
304
|
|
|
305
|
+
## 规范挂命令(spec-check)
|
|
306
|
+
|
|
307
|
+
`.cowork-flow/spec/` 下的规范可在文件首部 frontmatter 声明检查命令;机制只执行声明、不解析规范正文——规则随规范同文件更新,天然同步。
|
|
308
|
+
|
|
309
|
+
```markdown
|
|
310
|
+
---
|
|
311
|
+
checks:
|
|
312
|
+
- cmd: npm run lint --silent
|
|
313
|
+
files: "src/" # 可选:目录前缀或扩展名("*.ts"),逗号分隔
|
|
314
|
+
timeout: 60 # 可选:秒,默认 30,硬顶 120
|
|
315
|
+
when: both # 可选:edit | lifecycle | both(默认 both)
|
|
316
|
+
---
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
唯一执行器是 `./.cowork-flow/run spec-check`:
|
|
320
|
+
|
|
321
|
+
- **三态语义**:`pass`(退出码 0);`violation` 阻断 `task complete`;`unchecked`(命令缺失、解释器缺失、超时)同样阻断,需显式 `--allow-unchecked` 放行,豁免留痕进 `task.json`。unchecked 永不冒充 pass。
|
|
322
|
+
- **两个相位**:`when: edit` 在编辑期就地执行(超时钳制 2.5 秒,违规输出单行提示;具备编辑期 hook 的宿主均已覆盖,其中 zcode 仅主会话),是 best-effort 提示不是门禁;收口期全量执行,未过项阻断状态推进。
|
|
323
|
+
- **扫描范围**:`.cowork-flow/spec/` 下的 markdown;`contracts/`、`runtime/`、`schemas/` 三个机器自有子树不参与。
|
|
324
|
+
- **模板不带生效声明**:模板无法预知项目命令,而命令缺失会归 `unchecked` 并阻断收口;请把声明写进自建 spec 文件(如 `spec/team-xxx.md`)。
|
|
325
|
+
|
|
326
|
+
`task start` 后,绑定 spec 的章节索引(h2 标题树)随 stage-contract 注入,规范条目名常驻注意力。完整契约见 `.cowork-flow/spec/contracts/spec-checks.md`。
|
|
327
|
+
|
|
257
328
|
## 常用命令
|
|
258
329
|
|
|
259
330
|
```bash
|
|
@@ -274,6 +345,10 @@ Batch 使用任务图和持久化 Host action:运行 `task next <parent-task>
|
|
|
274
345
|
./.cowork-flow/run task next <dir> --run --intent review
|
|
275
346
|
./.cowork-flow/run task next <dir> --run --intent archive
|
|
276
347
|
|
|
348
|
+
# 规范检查
|
|
349
|
+
./.cowork-flow/run spec-check
|
|
350
|
+
./.cowork-flow/run spec-check --phase lifecycle --json
|
|
351
|
+
|
|
277
352
|
# 子代理
|
|
278
353
|
./.cowork-flow/run subagent init --role implement --agent-type cowork-implement --execution-task-dir <dir> --title "<title>"
|
|
279
354
|
./.cowork-flow/run subagent bind <runtime_context_id> <host_context_key>
|
|
@@ -323,6 +398,7 @@ npm run release:check # 发布信心门禁;当前等价于 test:all
|
|
|
323
398
|
npm run release # patch
|
|
324
399
|
npm run release -- minor # minor
|
|
325
400
|
npm run release -- --version 0.1.0 # 精确发布指定版本(跳过自动 bump)
|
|
401
|
+
npm run release -- minor --no-publish # 完整流程但跳过 npm publish(tag 留在本地)
|
|
326
402
|
```
|
|
327
403
|
|
|
328
404
|
**发布流程:**
|
|
@@ -331,9 +407,9 @@ npm run release -- --version 0.1.0 # 精确发布指定版本(跳过自动 bu
|
|
|
331
407
|
3. `npm version` 升级版本
|
|
332
408
|
4. 同步版本到 `template/.cowork-flow/.version` 和 `template/.zcode/.zcode-plugin/plugin.json`
|
|
333
409
|
5. `git commit` + `git tag`
|
|
334
|
-
6. `npm publish`
|
|
410
|
+
6. `npm publish`——走 CI 发布通道时改用 `--no-publish` 在此止步,交由下一步触发
|
|
335
411
|
|
|
336
|
-
**CI 发布通道(推荐):** `scripts/release.sh --
|
|
412
|
+
**CI 发布通道(推荐):** `scripts/release.sh <release-type> --no-publish` 完成提交与打 tag(不本地 publish)后,先 `git push` 分支并 `git push origin v<v>` 把 tag 推上远端,再 `gh release create v<v>` 触发 `.github/workflows/publish.yml`——远端尚无该 tag 时,`gh release create` 会从默认分支最新提交自动建 tag,使门禁与发布落在错误的提交上。Ubuntu/Windows 双平台全量门禁通过后自动 `npm publish`(需仓库 secret `NPM_TOKEN`,权限:publish)。`--no-publish` 只是跳过最后一步,前置的镜像、门禁与版本同步与默认路径完全一致。
|
|
337
413
|
|
|
338
414
|
- 发布说明维护在 `CHANGELOG.md`;发布前更新当前版本段落,并保留 `release:check` 和 `git diff --check` 证据。
|
|
339
415
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cowork-flow",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "CLI for installing and maintaining cowork-flow collaboration templates.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
],
|
|
20
20
|
"scripts": {
|
|
21
21
|
"test": "npm run test:fast",
|
|
22
|
-
"test:fast": "node --test test/asset-plan.test.js test/cli.test.js test/host-assets.test.js test/opencode-plugin.test.js test/package.test.js test/template-runner.test.js test/update.test.js test/zcode-plugin.test.js",
|
|
22
|
+
"test:fast": "node --test test/asset-plan.test.js test/cli.test.js test/host-assets.test.js test/kimi-hook.test.js test/opencode-plugin.test.js test/package.test.js test/template-runner.test.js test/update.test.js test/zcode-plugin.test.js",
|
|
23
23
|
"test:integration": "node --test --test-name-pattern=\"^(init copies the template into a new target directory|sync updates safe template files and preserves protected files)$\" test/init.test.js test/sync.test.js",
|
|
24
24
|
"test:node:full": "node --test",
|
|
25
25
|
"test:template": "node scripts/run-template-tests.js --suite core",
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
// falls back to running the navigator manually.
|
|
23
23
|
|
|
24
24
|
import { execFile } from 'node:child_process';
|
|
25
|
+
import { randomUUID } from 'node:crypto';
|
|
25
26
|
import { stat } from 'node:fs/promises';
|
|
26
27
|
import { dirname, join, resolve } from 'node:path';
|
|
27
28
|
|
|
@@ -82,6 +83,44 @@ print(build_hook_context(
|
|
|
82
83
|
))
|
|
83
84
|
`;
|
|
84
85
|
|
|
86
|
+
// The edit path sits on the finished tool result, so it gets a short leash:
|
|
87
|
+
// a slow checker is dropped rather than delaying the edit the model already
|
|
88
|
+
// made.
|
|
89
|
+
const EDIT_TIMEOUT_MS = 2500;
|
|
90
|
+
const EDIT_EXEC_OPTIONS = {
|
|
91
|
+
timeout: EDIT_TIMEOUT_MS,
|
|
92
|
+
windowsHide: true,
|
|
93
|
+
maxBuffer: 1024 * 1024,
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
// Editor-phase spec-check protocol: same root discovery, then the shared
|
|
97
|
+
// edit-check entry so DSH sessions receive the single-line violation hint
|
|
98
|
+
// the other hosts get from their PostToolUse hook. Always exits 0.
|
|
99
|
+
const PYTHON_EDIT_PROTOCOL = `\
|
|
100
|
+
import sys
|
|
101
|
+
from pathlib import Path
|
|
102
|
+
|
|
103
|
+
cwd = sys.argv[1]
|
|
104
|
+
file_path = sys.argv[2]
|
|
105
|
+
current = Path(cwd).resolve()
|
|
106
|
+
root = None
|
|
107
|
+
while True:
|
|
108
|
+
if (current / ".cowork-flow").is_dir():
|
|
109
|
+
root = current
|
|
110
|
+
break
|
|
111
|
+
if current == current.parent:
|
|
112
|
+
break
|
|
113
|
+
current = current.parent
|
|
114
|
+
if root is None:
|
|
115
|
+
raise SystemExit(0)
|
|
116
|
+
sys.path.insert(0, str(root / ".cowork-flow" / "scripts"))
|
|
117
|
+
try:
|
|
118
|
+
from services.spec_check import run_edit_checks
|
|
119
|
+
print(run_edit_checks(root, file_path))
|
|
120
|
+
except Exception:
|
|
121
|
+
pass
|
|
122
|
+
`;
|
|
123
|
+
|
|
85
124
|
// The first interpreter that ran the protocol successfully; a missing one is
|
|
86
125
|
// dropped so a later refresh can rediscover (e.g. after a PATH change).
|
|
87
126
|
let workingPython = null;
|
|
@@ -234,6 +273,90 @@ export async function runWorkflowState(cwd, full = true) {
|
|
|
234
273
|
}
|
|
235
274
|
|
|
236
275
|
|
|
276
|
+
const EDIT_TOOL_NAMES = new Set(['write', 'edit']);
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Path of the file a finished tool call edited, or '' for anything else.
|
|
281
|
+
* DSH's fs tools expose `file_path`; other tool surfaces are ignored.
|
|
282
|
+
*/
|
|
283
|
+
export function editedFilePath(exec) {
|
|
284
|
+
const name = exec && typeof exec.name === 'string' ? exec.name : '';
|
|
285
|
+
if (!EDIT_TOOL_NAMES.has(name)) {
|
|
286
|
+
return '';
|
|
287
|
+
}
|
|
288
|
+
const args = exec && exec.arguments;
|
|
289
|
+
const value = args && typeof args === 'object' ? args.file_path : null;
|
|
290
|
+
return typeof value === 'string' && value.trim() ? value.trim() : '';
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Editor-phase spec check for one edited file. Returns the single-line
|
|
296
|
+
* warning, or '' when there is nothing to say: no root, no interpreter, no
|
|
297
|
+
* declaration, a clean run, or a timeout. Never throws.
|
|
298
|
+
*/
|
|
299
|
+
export async function runEditSpecCheck(cwd, filePath) {
|
|
300
|
+
if (hooksDisabled() || !cwd || !filePath) {
|
|
301
|
+
return '';
|
|
302
|
+
}
|
|
303
|
+
const resolved = resolve(cwd);
|
|
304
|
+
if ((await findCoworkRoot(resolved)) === null) {
|
|
305
|
+
return '';
|
|
306
|
+
}
|
|
307
|
+
const attempt = (candidate) => new Promise((resolvePromise) => {
|
|
308
|
+
execFile(
|
|
309
|
+
candidate.command,
|
|
310
|
+
[...candidate.args, '-c', PYTHON_EDIT_PROTOCOL, cwd, filePath],
|
|
311
|
+
EDIT_EXEC_OPTIONS,
|
|
312
|
+
(error, stdout) => {
|
|
313
|
+
if (error) {
|
|
314
|
+
resolvePromise({ missing: error.code === 'ENOENT', output: '' });
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
resolvePromise({ missing: false, output: stdout.trim() });
|
|
318
|
+
},
|
|
319
|
+
);
|
|
320
|
+
});
|
|
321
|
+
if (workingPython !== null) {
|
|
322
|
+
const result = await attempt(workingPython);
|
|
323
|
+
if (!result.missing) {
|
|
324
|
+
return result.output;
|
|
325
|
+
}
|
|
326
|
+
workingPython = null;
|
|
327
|
+
}
|
|
328
|
+
for (const candidate of discoverCandidates()) {
|
|
329
|
+
const result = await attempt(candidate);
|
|
330
|
+
if (result.missing) {
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
workingPython = candidate;
|
|
334
|
+
return result.output;
|
|
335
|
+
}
|
|
336
|
+
return '';
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* One identified user message carrying a plugin notice. DSH's own
|
|
342
|
+
* createUserMessage lives in a package this preset deliberately does not
|
|
343
|
+
* import, so the shape is built here with node's uuid.
|
|
344
|
+
*/
|
|
345
|
+
function specNoticeMessage(text) {
|
|
346
|
+
return {
|
|
347
|
+
id: randomUUID(),
|
|
348
|
+
role: 'user',
|
|
349
|
+
content: [{ type: 'text', text }],
|
|
350
|
+
source: {
|
|
351
|
+
kind: 'plugin',
|
|
352
|
+
plugin: 'cowork-flow',
|
|
353
|
+
form: 'notice',
|
|
354
|
+
summary: 'spec-check',
|
|
355
|
+
},
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
|
|
237
360
|
export function apply(ctx) {
|
|
238
361
|
// One cache entry per agent: { text, inflight, queued, full }.
|
|
239
362
|
// `full` records the digest shape the next refresh should use: session
|
|
@@ -291,6 +414,35 @@ export function apply(ctx) {
|
|
|
291
414
|
refresh(agent, false);
|
|
292
415
|
});
|
|
293
416
|
|
|
417
|
+
// Editor-phase spec-check: the tool already ran, so this only attaches
|
|
418
|
+
// context for the next request and never blocks the call. Waterfall order
|
|
419
|
+
// requires awaiting next() before merging downstream decisions.
|
|
420
|
+
ctx.on('tools/post-execute', async (exec, _result, next) => {
|
|
421
|
+
const downstream = await next();
|
|
422
|
+
const agent = exec && exec.agent;
|
|
423
|
+
const filePath = editedFilePath(exec);
|
|
424
|
+
if (!agent || !agent.cwd || !filePath) {
|
|
425
|
+
return downstream;
|
|
426
|
+
}
|
|
427
|
+
let warning = '';
|
|
428
|
+
try {
|
|
429
|
+
warning = await runEditSpecCheck(agent.cwd, filePath);
|
|
430
|
+
} catch {
|
|
431
|
+
warning = '';
|
|
432
|
+
}
|
|
433
|
+
if (!warning) {
|
|
434
|
+
return downstream;
|
|
435
|
+
}
|
|
436
|
+
const contexts = [
|
|
437
|
+
specNoticeMessage(warning),
|
|
438
|
+
...((downstream && downstream.additionalContexts) || []),
|
|
439
|
+
];
|
|
440
|
+
if (downstream && downstream.kind === 'block') {
|
|
441
|
+
return { ...downstream, additionalContexts: contexts };
|
|
442
|
+
}
|
|
443
|
+
return { ...(downstream || { kind: 'accept' }), additionalContexts: contexts };
|
|
444
|
+
});
|
|
445
|
+
|
|
294
446
|
ctx.effect(() => ctx.systemPrompt.section({
|
|
295
447
|
name: SECTION_NAME,
|
|
296
448
|
order: SECTION_ORDER,
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* cowork-flow Kimi Code hook — transport shim.
|
|
4
|
+
*
|
|
5
|
+
* Kimi Code appends a UserPromptSubmit hook's stdout to the prompt context
|
|
6
|
+
* verbatim, so all workflow facts are rendered by the single Python source
|
|
7
|
+
* (inject.py --host kimi-code, emit_text policy). This shim keeps only
|
|
8
|
+
* transport duties:
|
|
9
|
+
* 1. cheap project-root pre-check — a cwd outside any cowork-flow project
|
|
10
|
+
* exits 0 without spawning anything, so the user-level hook never
|
|
11
|
+
* touches unrelated repositories;
|
|
12
|
+
* 2. interpreter location (COWORK_FLOW_PYTHON → python3 → python → py -3);
|
|
13
|
+
* 3. stdin passthrough to inject.py, stdout/exit forwarding.
|
|
14
|
+
*
|
|
15
|
+
* Fail-open by construction: a missing interpreter, a missing runtime, or a
|
|
16
|
+
* broken project copy all exit 0 with empty stdout instead of blocking the
|
|
17
|
+
* user's prompt (a non-zero exit on UserPromptSubmit aborts the submit).
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
21
|
+
import { dirname, join } from "node:path";
|
|
22
|
+
import { spawnSync } from "node:child_process";
|
|
23
|
+
|
|
24
|
+
const WORKFLOW_DIR = ".cowork-flow";
|
|
25
|
+
const INJECT_RELATIVE = join(
|
|
26
|
+
WORKFLOW_DIR,
|
|
27
|
+
"scripts",
|
|
28
|
+
"adapters",
|
|
29
|
+
"host",
|
|
30
|
+
"inject.py"
|
|
31
|
+
);
|
|
32
|
+
const HOST = "kimi-code";
|
|
33
|
+
const SPAWN_TIMEOUT_MS = 20000; // config.toml grants the hook 30s
|
|
34
|
+
|
|
35
|
+
function readHookInput() {
|
|
36
|
+
if (process.stdin.isTTY) return { raw: "", parsed: {} };
|
|
37
|
+
let raw = "";
|
|
38
|
+
try {
|
|
39
|
+
raw = readFileSync(0, "utf8").trim();
|
|
40
|
+
} catch {
|
|
41
|
+
return { raw: "", parsed: {} };
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
const parsed = JSON.parse(raw);
|
|
45
|
+
return { raw, parsed: parsed && typeof parsed === "object" ? parsed : {} };
|
|
46
|
+
} catch {
|
|
47
|
+
return { raw: "", parsed: {} };
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function findProjectRoot(startDir) {
|
|
52
|
+
if (typeof startDir !== "string" || !startDir.trim()) return null;
|
|
53
|
+
let current = startDir.trim();
|
|
54
|
+
const root = dirname(current);
|
|
55
|
+
while (current !== root) {
|
|
56
|
+
if (existsSync(join(current, INJECT_RELATIVE))) return current;
|
|
57
|
+
current = dirname(current);
|
|
58
|
+
}
|
|
59
|
+
return existsSync(join(current, INJECT_RELATIVE)) ? current : null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function pythonCandidates() {
|
|
63
|
+
const list = [];
|
|
64
|
+
const env = (process.env.COWORK_FLOW_PYTHON || "").trim();
|
|
65
|
+
if (env) list.push([env]);
|
|
66
|
+
list.push(["python3"], ["python"]);
|
|
67
|
+
if (process.platform === "win32") list.push(["py", "-3"]);
|
|
68
|
+
return list;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function main() {
|
|
72
|
+
if (
|
|
73
|
+
process.env.COWORK_FLOW_HOOKS === "0" ||
|
|
74
|
+
process.env.COWORK_FLOW_DISABLE_HOOKS === "1"
|
|
75
|
+
) {
|
|
76
|
+
process.exit(0);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const { raw, parsed } = readHookInput();
|
|
80
|
+
const cwd =
|
|
81
|
+
typeof parsed?.cwd === "string" && parsed.cwd.trim()
|
|
82
|
+
? parsed.cwd
|
|
83
|
+
: process.cwd();
|
|
84
|
+
const root = findProjectRoot(cwd);
|
|
85
|
+
if (!root) process.exit(0);
|
|
86
|
+
|
|
87
|
+
const injectScript = join(root, INJECT_RELATIVE);
|
|
88
|
+
for (const candidate of pythonCandidates()) {
|
|
89
|
+
let result;
|
|
90
|
+
try {
|
|
91
|
+
result = spawnSync(
|
|
92
|
+
candidate[0],
|
|
93
|
+
[...candidate.slice(1), injectScript, "--host", HOST],
|
|
94
|
+
{
|
|
95
|
+
input: raw,
|
|
96
|
+
timeout: SPAWN_TIMEOUT_MS,
|
|
97
|
+
encoding: "utf8",
|
|
98
|
+
windowsHide: true,
|
|
99
|
+
}
|
|
100
|
+
);
|
|
101
|
+
} catch {
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (result.error || result.status === null) continue;
|
|
105
|
+
if (result.stdout) process.stdout.write(result.stdout);
|
|
106
|
+
process.exit(result.status);
|
|
107
|
+
}
|
|
108
|
+
// No interpreter could run the entry: stay silent rather than break the
|
|
109
|
+
// user's submit (fail-open, same contract as the other host shims).
|
|
110
|
+
process.exit(0);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
main();
|
package/src/cli.js
CHANGED
|
@@ -5,6 +5,7 @@ import { createInterface } from 'node:readline/promises';
|
|
|
5
5
|
import { runInit } from './commands/init.js';
|
|
6
6
|
import { runInstallDshHook } from './commands/install-dsh-hook.js';
|
|
7
7
|
import { runInstallDshPreset } from './commands/install-dsh-preset.js';
|
|
8
|
+
import { runInstallKimiHook } from './commands/install-kimi-hook.js';
|
|
8
9
|
import { runInstallZCodePlugin } from './commands/install-zcode-plugin.js';
|
|
9
10
|
import { runMcpState } from './commands/mcp-state.js';
|
|
10
11
|
import { runSourceRefresh } from './commands/source-refresh.js';
|
|
@@ -19,6 +20,7 @@ Usage:
|
|
|
19
20
|
cowork-flow install-zcode-plugin [--dry-run] [--force] [--prune-old]
|
|
20
21
|
cowork-flow install-dsh-preset [--dry-run] [--force]
|
|
21
22
|
cowork-flow install-dsh-hook [--dry-run] [--force] [--uninstall]
|
|
23
|
+
cowork-flow install-kimi-hook [--dry-run] [--force] [--uninstall]
|
|
22
24
|
cowork-flow update
|
|
23
25
|
cowork-flow sync [target] [--dry-run] [--force]
|
|
24
26
|
cowork-flow source-refresh [target] [--dry-run]
|
|
@@ -201,6 +203,10 @@ export async function main(argv = process.argv.slice(2), options = {}) {
|
|
|
201
203
|
return await runInstallDshHook(args);
|
|
202
204
|
}
|
|
203
205
|
|
|
206
|
+
if (command === 'install-kimi-hook') {
|
|
207
|
+
return await runInstallKimiHook(args);
|
|
208
|
+
}
|
|
209
|
+
|
|
204
210
|
if (command === 'mcp-state') {
|
|
205
211
|
return await runMcpState(args);
|
|
206
212
|
}
|