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.
- package/CHANGELOG.md +45 -1
- package/README.en.md +1 -1
- package/README.md +1 -1
- package/docs/architecture.md +1 -34
- package/docs/direction.md +4 -3
- package/docs/hosts.md +14 -10
- package/docs/index.md +1 -1
- package/docs/release.md +1 -1
- package/package.json +6 -6
- package/presets/claude-code/.claude-plugin/plugin.json +1 -1
- package/presets/codex/.codex-plugin/plugin.json +1 -1
- package/presets/dsh/plugins/workflow-state.js +72 -24
- package/presets/kimi-code/.kimi-plugin/plugin.json +1 -1
- package/presets/kimi-code/hooks/cowork-flow-inject.mjs +28 -2
- package/presets/opencode/cowork-flow/plugin-core.js +70 -88
- package/presets/opencode/plugins/cowork-flow.js +7 -9
- package/presets/qoder/.qoder-plugin/plugin.json +1 -1
- package/presets/qoder/hooks/hooks.json +1 -1
- package/presets/qoder/hooks/inject-context.mjs +28 -2
- package/presets/zcode/.zcode-plugin/plugin.json +1 -1
- package/presets/zcode/hooks/inject-context.mjs +28 -3
- package/src/commands/host.js +23 -2
- package/src/commands/install-dsh-hook.js +1 -6
- package/src/commands/install-dsh-preset.js +1 -6
- package/src/commands/install-zcode-plugin.js +2 -3
- package/src/commands/registry.js +4 -2
- package/src/lib/asset-plan.js +1 -1
- package/src/lib/dsh-home.js +6 -0
- package/src/lib/plan-applier.js +1 -6
- package/template/.claude/agents/cowork-check.md +1 -1
- package/template/.claude/agents/cowork-implement.md +1 -1
- package/template/.claude/agents/cowork-research.md +1 -1
- package/template/.codex/agents/cowork-check.toml +1 -1
- package/template/.codex/agents/cowork-implement.toml +1 -1
- package/template/.codex/agents/cowork-research.toml +1 -1
- package/template/.codex/agents/default.toml +1 -1
- package/template/.codex/agents/explorer.toml +1 -1
- package/template/.codex/agents/worker.toml +1 -1
- package/template/.cowork-flow/.version +1 -1
- package/template/.cowork-flow/adapters/claude-code/adapter.yaml +1 -1
- package/template/.cowork-flow/adapters/codex/adapter.yaml +1 -1
- package/template/.cowork-flow/adapters/dsh/adapter.yaml +1 -1
- package/template/.cowork-flow/adapters/opencode/adapter.yaml +1 -1
- package/template/.cowork-flow/adapters/qoder/adapter.yaml +1 -1
- package/template/.cowork-flow/run +2 -2
- package/template/.cowork-flow/run.cmd +3 -3
- package/template/.cowork-flow/scripts/adapters/cli/task_lifecycle_commands.py +0 -1
- package/template/.cowork-flow/scripts/adapters/cli/task_parser.py +0 -7
- package/template/.cowork-flow/scripts/adapters/host/claude_code_policy.py +6 -4
- package/template/.cowork-flow/scripts/adapters/host/codex_policy.py +8 -6
- package/template/.cowork-flow/scripts/adapters/host/edit_scope.py +113 -0
- package/template/.cowork-flow/scripts/adapters/host/opencode_policy.py +12 -0
- package/template/.cowork-flow/scripts/adapters/host/qoder_policy.py +7 -5
- package/template/.cowork-flow/scripts/adapters/host/workflow_state_hook.py +0 -91
- package/template/.cowork-flow/scripts/adapters/host/zcode_policy.py +15 -77
- package/template/.cowork-flow/scripts/run.py +0 -2
- package/template/.cowork-flow/scripts/services/context_paths.py +2 -2
- package/template/.cowork-flow/scripts/services/fact_view.py +49 -21
- package/template/.cowork-flow/scripts/services/lifecycle_checks.py +1 -216
- package/template/.cowork-flow/scripts/services/lifecycle_policy.py +0 -5
- package/template/.cowork-flow/scripts/services/task_gate_profiles.py +1 -1
- package/template/.cowork-flow/scripts/services/task_lifecycle.py +14 -31
- package/template/.cowork-flow/spec/backend/error-handling.md +0 -14
- package/template/.cowork-flow/spec/contracts/context-injection.md +53 -26
- package/template/.cowork-flow/spec/contracts/decision-anchor.md +3 -3
- package/template/.cowork-flow/spec/contracts/error-codes.md +1 -3
- package/template/.cowork-flow/spec/contracts/evidence-completion.md +6 -8
- package/template/.cowork-flow/spec/contracts/index.md +0 -1
- package/template/.cowork-flow/spec/contracts/plan-binding.md +3 -2
- package/template/.cowork-flow/spec/contracts/subagent-dispatch.md +6 -21
- package/template/.cowork-flow/spec/contracts/task-adopt.md +1 -1
- package/template/.cowork-flow/spec/runtime/contract-registry.json +0 -13
- package/template/.cowork-flow/spec/runtime/host-assets.json +10 -7
- package/template/.cowork-flow/spec/runtime/scope-rules.json +2 -2
- package/template/.kimi-code/agents/cowork-check.md +1 -1
- package/template/.kimi-code/agents/cowork-implement.md +1 -1
- package/template/.kimi-code/agents/cowork-research.md +1 -1
- package/template/.opencode/agents/cowork-check.md +1 -1
- package/template/.opencode/agents/cowork-implement.md +1 -1
- package/template/.opencode/agents/cowork-research.md +1 -1
- package/template/.opencode/cowork-flow/plugin-core.js +70 -88
- package/template/.opencode/plugins/cowork-flow.js +7 -9
- package/template/AGENTS.md +0 -1
- package/template/skills/agent-dispatch/SKILL.md +0 -15
- package/template/skills/runtime-health/scripts/doctor.py +83 -58
- package/template/skills/spec-sync/SKILL.md +0 -1
- package/template/skills/task-planning/SKILL.md +1 -1
- package/template/skills/task-review/SKILL.md +3 -3
- package/template/.cowork-flow/scripts/adapters/cli/spec_check.py +0 -178
- package/template/.cowork-flow/scripts/services/spec_check.py +0 -573
- package/template/.cowork-flow/spec/contracts/spec-checks.md +0 -281
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,48 @@
|
|
|
4
4
|
|
|
5
5
|
### Added
|
|
6
6
|
|
|
7
|
+
## [2.1.0] - 2026-10-19
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **发布前漂移门禁接入 release.sh,测试范围与超时收敛**:发布 pre-flight 在 `source:refresh`(会覆写活副本)之前强制跑 `node scripts/release-gates.js`,自实例漂移即中止并打印修复指引;新增 `pytest.ini`(`testpaths = tests`)把 `python -m pytest -q` 的收集范围钉在 `tests/`,所有 node 测试脚本统一 `--test-timeout=600000`,`test:windows:core` 补上 Windows 全量 `init/sync` 套件(此前只跑两条具名用例)。
|
|
12
|
+
- **发布门禁补"活副本资产漂移"verdict**:`release-gates` 新增 `self-instance-asset-drift`——以 `node bin/cowork-flow.js project sync . --dry-run` 的 readiness 为准,`wouldCopy`/`wouldRemoveObsolete` 非空即 fail 并点名文件(`wouldSkipProtected`/`wouldKeep` 只报告不阻断)。覆盖 `.claude/**`、`.codex/**`、skills 等 `dev refresh` 看不到的活副本,避免发布第 5 步 `sync --force` 静默覆写;`scripts/release.sh` 失败提示补 `node bin/cowork-flow.js project sync .` 修复指引。
|
|
13
|
+
- **doctor 增补 zcode 插件新鲜度检查**:`check_zcode_plugin` 以 payload 盖章清单 `.zcode-plugin/plugin.json` 的版本对比项目 `.cowork-flow/.version` → `PLUGIN-STALE`(hint `cwf host add zcode --force`);未声明 zcode 静默,声明未安装报 `PLUGIN-NOT-INSTALLED`,清单不可读报 `PLUGIN-UNKNOWN-VERSION`。与 opencode/qoder 同为 advisory,不进 error 级。
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- **Python 解释器下限统一 3.10,五个宿主 shim 加解释器探针守卫**:`template/.cowork-flow/run`、`run.cmd` 的版本判据与提示从 3.9 升到 3.10(与运行时协议一致);`presets/{opencode,zcode,qoder,kimi-code,dsh}` 的注入 shim 逐个候选加同源版本探针(`sys.version_info >= (3, 10)`),不可用即跳过、全不可用则 stderr 一行说明并 fail-open(exit 0),探针超时视为无裁决、不跳过。**迁移**:shim 在宿主载荷内,需 `cwf host add <host> --force` 重装。
|
|
18
|
+
- **清理 spec-check 删除后的文档与宿主提示残留**:README(中英)、`docs/{index,hosts,direction}.md`、`context-injection.md` 里"规范检查/规范门禁"的过时措辞改为"同步策略与受保护文件",并订正机器级 shim 的 fail-open 描述(现为四个机器级 shim;项目级 Edit 期 hook 自 2.0.0 起同样 advisory、恒 exit 0);15 处宿主 agent 提示(`.claude`/`.codex`/`.kimi-code`/`.opencode` 的 `cowork-*` 与 codex 的 `default`/`explorer`/`worker`)把点名已删子命令的禁令改为"禁止执行任务生命周期转换"。
|
|
19
|
+
- **`assetDriftVerdict` 缺键改为 fail-closed**:readiness 少 `wouldCopy`/`wouldRemoveObsolete`(或给非数组)时不再静默判"无漂移",改判门禁失效并点名缺失键(`cannot judge drift`),并把逐 key 单钉、e2e fail 断言补进测试。
|
|
20
|
+
- **dsh 候选可用性映射抽为 `candidateUnavailable` 并恢复 Windows 覆盖**:`ENOENT`/退出码 3 → 换下一个候选;其余非零退出(含超时)→ 候选按"可用但协议失败、不注入"处理(语义不变,仅抽函数加注释);新增跨平台单测钉住四种输入,`test/dsh-hook.test.js` 两条过期的 Windows skip 移除(取证:node 探测 WindowsApps `python3` 别名得 `ENOENT`、回落到真实 `python` 3.12)。
|
|
21
|
+
- **重复工具函数收敛**:`sha256` 只留 `src/lib/asset-plan.js` 的导出,`src/lib/plan-applier.js` 改为复用;`getDshHome` 抽到新增的 `src/lib/dsh-home.js`,两个 dsh 安装器共用。
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
|
|
25
|
+
- **dsh 完整协议 stdout 固定 UTF-8,恢复非 CJK 控制台下的工作流注入**:`<workflow-state>` 块含中文文案,Windows 默认 ANSI 代码页(CI runner 为 cp1252)下 `print()` 抛 `UnicodeEncodeError`,调用方按既有语义读作"解释器能跑但协议失败 → 不注入",dsh 会话静默丢失整块注入——上面移除 Windows skip 后两条用例在 runner 上暴露的正是这个缺陷(原 skip 注释归因于 WindowsApps `python3` 桩,实测不符)。修法对齐同文件 edit-scope 协议与 `inject.py` 的既有写法,在完整协议开头补 `sys.stdout.reconfigure(encoding="utf-8", errors="replace", newline="")`;新增回归用例在 `PYTHONIOENCODING=ascii` 下钉住该契约(红证:改动前该用例失败)。顺带把 `newline=""` 的 LF 语义带进来,块字节在 Windows 与 POSIX 上一致。
|
|
26
|
+
- **release 编排用例补 `scripts/release-gates.js` 桩**:`0c81b29` 给 `release.sh` 加的首道漂移门禁在 Linux 上必飞——夹具只复制 `release.sh`,`node scripts/release-gates.js` 报 `Cannot find module` 被读作漂移阻断,3 条用例红(was: 该门禁已无任何用例钉住其接线)。夹具补同款 `process.exit(0)` 桩,并在"defaults to patch"用例断言门禁先于 `source:refresh` 运行。
|
|
27
|
+
|
|
28
|
+
## [2.0.0] - 2026-10-16
|
|
29
|
+
|
|
30
|
+
### Added
|
|
31
|
+
|
|
32
|
+
- **编辑期越界警告覆盖 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 写文件仍不在覆盖范围(既有残余缺口)。
|
|
33
|
+
|
|
34
|
+
### Changed
|
|
35
|
+
|
|
36
|
+
- **末段裁剪抽成 `_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` 在该文件零告警。
|
|
37
|
+
- **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 已写明三条不变量与下限,并新增扫预算可解析性用例与下限用例(各带红证)。
|
|
38
|
+
- **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` 已写明)。降级测试改为钉住自选预算并额外断言输入确实超预算,不再随生产常量漂移(原先升预算会让它们静默恒真)。
|
|
39
|
+
- **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`。
|
|
40
|
+
|
|
41
|
+
### Removed
|
|
42
|
+
|
|
43
|
+
- **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 行。
|
|
44
|
+
|
|
45
|
+
### Fixed
|
|
46
|
+
|
|
47
|
+
- **注入文本与策略提示指向不存在的命令**:`<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/` 前缀保护的契约正文仍只对新装项目生效(既有取舍)。
|
|
48
|
+
|
|
7
49
|
## [1.9.2] - 2026-10-14
|
|
8
50
|
|
|
9
51
|
### Added
|
|
@@ -344,7 +386,9 @@
|
|
|
344
386
|
|
|
345
387
|
此版本未保留本地条目正文,详情见 [GitHub Release](https://github.com/fishlikewater/cowork-flow/releases/tag/v0.0.47)。
|
|
346
388
|
|
|
347
|
-
[Unreleased]: https://github.com/fishlikewater/cowork-flow/compare/
|
|
389
|
+
[Unreleased]: https://github.com/fishlikewater/cowork-flow/compare/v2.1.0...HEAD
|
|
390
|
+
[2.1.0]: https://github.com/fishlikewater/cowork-flow/compare/v2.0.0...v2.1.0
|
|
391
|
+
[2.0.0]: https://github.com/fishlikewater/cowork-flow/compare/v1.9.2...v2.0.0
|
|
348
392
|
[1.9.2]: https://github.com/fishlikewater/cowork-flow/compare/v1.9.1...v1.9.2
|
|
349
393
|
[1.9.1]: https://github.com/fishlikewater/cowork-flow/compare/v1.9.0...v1.9.1
|
|
350
394
|
[1.9.0]: https://github.com/fishlikewater/cowork-flow/compare/v1.8.1...v1.9.0
|
package/README.en.md
CHANGED
|
@@ -169,7 +169,7 @@ The detailed documentation is written in Chinese.
|
|
|
169
169
|
| Document | Contents |
|
|
170
170
|
|---|---|
|
|
171
171
|
| [Documentation index](docs/index.md) | Entry point for every document |
|
|
172
|
-
| [Architecture and extension](docs/architecture.md) | How the project files are organized, how
|
|
172
|
+
| [Architecture and extension](docs/architecture.md) | How the project files are organized, how the sync policy and protected files work |
|
|
173
173
|
| [Host integration](docs/hosts.md) | Install, restart, troubleshoot, and uninstall for each host |
|
|
174
174
|
| [Release and maintenance](docs/release.md) | Tests, CI, and maintainer commands |
|
|
175
175
|
| [Contributing](CONTRIBUTING.md) | Local setup, commit conventions, and PR checks |
|
package/README.md
CHANGED
|
@@ -149,7 +149,7 @@ cwf project sync .
|
|
|
149
149
|
| 文档 | 内容 |
|
|
150
150
|
|---|---|
|
|
151
151
|
| [文档索引](docs/index.md) | 全部文档入口 |
|
|
152
|
-
| [架构与扩展](docs/architecture.md) |
|
|
152
|
+
| [架构与扩展](docs/architecture.md) | 项目文件如何组织、同步策略与受保护文件 |
|
|
153
153
|
| [宿主接入](docs/hosts.md) | 各宿主的安装、重启、排障和卸载方式 |
|
|
154
154
|
| [发布与维护](docs/release.md) | 测试、CI 和维护者命令 |
|
|
155
155
|
| [参与开发](CONTRIBUTING.md) | 本地开发、提交和 PR 检查 |
|
package/docs/architecture.md
CHANGED
|
@@ -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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# cowork-flow 方向规划:Agent 运行时上下文与协作事实层
|
|
2
2
|
|
|
3
|
-
> 状态:**已批准** · 创建:2026-08-27 · 最近更新:2026-10-
|
|
3
|
+
> 状态:**已批准** · 创建:2026-08-27 · 最近更新:2026-10-19
|
|
4
4
|
> 本文档是后续阶段立项的权威输入。任何方向的修订必须先更新本文档,再立项。
|
|
5
5
|
> 已完成阶段的实施细节与提交记录见 [CHANGELOG](../CHANGELOG.md);本文只保留仍然生效的定位、取舍、边界和待决项。
|
|
6
6
|
|
|
@@ -45,8 +45,9 @@
|
|
|
45
45
|
|
|
46
46
|
## 5. 版本线
|
|
47
47
|
|
|
48
|
-
- **1.8.x 已发布**:证据化完成(AC 事实化、`evidence.jsonl
|
|
49
|
-
- **1.9.
|
|
48
|
+
- **1.8.x 已发布**:证据化完成(AC 事实化、`evidence.jsonl`、完成门禁)。
|
|
49
|
+
- **1.9.x 已发布**:多执行者协作事实——看板归属可见、依赖边、交接凭证。
|
|
50
|
+
- **2.0.0 已发布**:spec-check 机制破坏性删除、编辑期越界警告覆盖各宿主、注入容量放宽(Verify 10 条 / 块预算 3400)。详见 [CHANGELOG](../CHANGELOG.md) 的 `[2.0.0]` 段。
|
|
50
51
|
- 各版本的交付明细一律以 CHANGELOG 为准,本文不重复。
|
|
51
52
|
|
|
52
53
|
## 6. 待决项与缓行
|
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 |
|
|
22
|
+
| Kimi Code | 需要 | `cwf host add kimi-code`(一条命令装齐 hook 与插件) |
|
|
23
23
|
|
|
24
24
|
## 通用命令
|
|
25
25
|
|
|
@@ -51,7 +51,7 @@ cwf host remove <host>
|
|
|
51
51
|
|
|
52
52
|
`--force` 只在确认需要覆盖已有安装时使用。安装器遇到不属于自己的文件时默认拒绝覆盖或删除;`--dry-run` 会执行同样的归属检查。
|
|
53
53
|
|
|
54
|
-
机器级 hook
|
|
54
|
+
机器级 hook 与项目运行时各自升级,而它会在任何工作区触发——包括运行时比它旧的项目。四个机器级注入 shim(Qoder、ZCode、Kimi Code、OpenCode)因此是 **fail-open** 的:项目运行时失败时只在宿主运行日志留原因并返回 0,不注入,也不把「hook 失败」变成「拒绝你的输入」;项目级 Edit 期 hook(Claude Code / Codex)自 2.0.0 起同样不再返回非零退出,一律 advisory。旧项目上注入会静默不生效,跑一次 `cwf project sync .` 即可恢复。
|
|
55
55
|
|
|
56
56
|
## Codex
|
|
57
57
|
|
|
@@ -159,23 +159,27 @@ hook 行是否进入 agent 提示按版本分线:实测 DSH 0.1.1-rc.1 不收
|
|
|
159
159
|
|
|
160
160
|
## Kimi Code
|
|
161
161
|
|
|
162
|
-
|
|
162
|
+
裸命令一次装齐两部分:先装上下文注入 hook(必需),再装插件:
|
|
163
163
|
|
|
164
164
|
```bash
|
|
165
|
-
cwf host add kimi-code
|
|
166
|
-
cwf host remove kimi-code
|
|
165
|
+
cwf host add kimi-code
|
|
166
|
+
cwf host remove kimi-code
|
|
167
167
|
```
|
|
168
168
|
|
|
169
|
-
|
|
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
|
-
|
|
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/docs/index.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|---|---|---|
|
|
9
9
|
| 安装、初始化项目或开始一个任务 | [README](../README.md) | 最短使用路径、日常命令和常见问题 |
|
|
10
10
|
| 把 cowork-flow 接到某个 AI 编码宿主 | [宿主接入](hosts.md) | 各宿主的安装组件、重启要求、升级、卸载和排障方式 |
|
|
11
|
-
| 了解项目文件如何组织 | [架构与扩展](architecture.md) | 运行时、`template/`、`presets
|
|
11
|
+
| 了解项目文件如何组织 | [架构与扩展](architecture.md) | 运行时、`template/`、`presets/`、同步策略和扩展边界 |
|
|
12
12
|
| 参与项目开发 | [参与开发](../CONTRIBUTING.md) | 本地环境、代码约定、测试和 PR 检查 |
|
|
13
13
|
| 了解测试与发布流程 | [发布与维护](release.md) | 测试分层、CI、版本发布和维护者命令 |
|
|
14
14
|
| 了解项目方向与版本规划 | [方向规划](direction.md) | 定位声明、已拍板取舍、版本主线与待决项 |
|
package/docs/release.md
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
| `npm run test:windows:core` | 涉及 Windows、路径、换行或发布行为 |
|
|
18
18
|
| `python -m pytest -q` | 改动 `template/.cowork-flow/`、`tests/` 或 Python 契约 |
|
|
19
19
|
| `npm run test:all` | 准备发布时的完整 Node、模板和打包检查 |
|
|
20
|
-
| `npm run release:check` | 发布信心门禁:`test:all`
|
|
20
|
+
| `npm run release:check` | 发布信心门禁:`test:all` 加上测试表达不了的检查——自实例与活副本资产(`.claude/**`、`.codex/**`、skills)相对 `template/` 的漂移,以及 `doctor --all` 的 error 级问题 |
|
|
21
21
|
|
|
22
22
|
平台前提不满足时,测试可能明确跳过。跳过项必须原样记录,不能当作通过。
|
|
23
23
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cowork-flow",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "CLI for installing and maintaining cowork-flow collaboration templates.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai-agent",
|
|
@@ -48,10 +48,10 @@
|
|
|
48
48
|
],
|
|
49
49
|
"scripts": {
|
|
50
50
|
"test": "npm run test:fast",
|
|
51
|
-
"test:smoke": "node --test test/smoke.test.js test/tarball-install.test.js",
|
|
52
|
-
"test:fast": "node --test test/asset-plan.test.js test/cli.test.js test/cli-registry.test.js test/claude-code-plugin.test.js test/codex-plugin.test.js test/compliance-e2e.test.js test/contract-fingerprint.test.js test/copy-template.obsolete-safety.test.js test/docs.test.js test/host-assets.test.js test/host-command.test.js test/kimi-hook.test.js test/kimi-installed-registry.test.js test/kimi-plugin.test.js test/opencode-plugin-installer.test.js test/opencode-plugin.test.js test/package.test.js test/plugin-metadata.test.js test/plugin-payload.test.js test/qoder-plugin.test.js test/smoke.test.js test/stage-contract.test.js test/template-runner.test.js test/update.test.js test/zcode-plugin.test.js",
|
|
53
|
-
"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",
|
|
54
|
-
"test:node:full": "node --test",
|
|
51
|
+
"test:smoke": "node --test --test-timeout=600000 test/smoke.test.js test/tarball-install.test.js",
|
|
52
|
+
"test:fast": "node --test --test-timeout=600000 test/asset-plan.test.js test/cli.test.js test/cli-registry.test.js test/claude-code-plugin.test.js test/codex-plugin.test.js test/compliance-e2e.test.js test/contract-fingerprint.test.js test/copy-template.obsolete-safety.test.js test/docs.test.js test/host-assets.test.js test/host-command.test.js test/kimi-hook.test.js test/kimi-installed-registry.test.js test/kimi-plugin.test.js test/opencode-plugin-installer.test.js test/opencode-plugin.test.js test/package.test.js test/plugin-metadata.test.js test/plugin-payload.test.js test/qoder-plugin.test.js test/smoke.test.js test/stage-contract.test.js test/template-runner.test.js test/update.test.js test/zcode-plugin.test.js",
|
|
53
|
+
"test:integration": "node --test --test-timeout=600000 --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",
|
|
54
|
+
"test:node:full": "node --test --test-timeout=600000",
|
|
55
55
|
"test:template": "node scripts/run-template-tests.js --suite core",
|
|
56
56
|
"test:template:full": "node scripts/run-template-tests.js --suite full",
|
|
57
57
|
"pack:check": "node scripts/pack-check.js",
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
"test:all": "npm run test:node:full && npm run test:template:full && npm run pack:check",
|
|
62
62
|
"release:check": "npm run test:all && node scripts/release-gates.js",
|
|
63
63
|
"release": "sh scripts/release.sh",
|
|
64
|
-
"test:windows:core": "npm run test:fast && npm run test:integration && node --test test/dsh-home-patch.test.js test/dsh-hook.test.js test/dsh-preset.test.js test/dsh-preset-bundle.test.js test/mcp-client-matrix.test.js test/mcp-state-command.test.js test/tarball-install.test.js && npm run pack:check && npm run test:template"
|
|
64
|
+
"test:windows:core": "npm run test:fast && npm run test:integration && node --test --test-timeout=600000 test/dsh-home-patch.test.js test/dsh-hook.test.js test/dsh-preset.test.js test/dsh-preset-bundle.test.js test/mcp-client-matrix.test.js test/mcp-state-command.test.js test/tarball-install.test.js && node --test --test-timeout=600000 test/init.test.js test/sync.test.js && npm run pack:check && npm run test:template"
|
|
65
65
|
},
|
|
66
66
|
"engines": {
|
|
67
67
|
"node": ">=20"
|
|
@@ -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
|
|
@@ -47,14 +52,28 @@ const NO_ROOT_MARKER = '__COWORK_FLOW_NO_ROOT__';
|
|
|
47
52
|
const NO_ROOT_TTL_MS = 30_000;
|
|
48
53
|
const noRootCache = new Map();
|
|
49
54
|
|
|
50
|
-
// Python 3.
|
|
55
|
+
// Python 3.10+ protocol reused from the host hook adapters.
|
|
51
56
|
// It resolves the `.cowork-flow` root from the given cwd,
|
|
52
57
|
// loads the shared workflow_state_hook module, and prints
|
|
53
|
-
// the full hook context block.
|
|
58
|
+
// the full hook context block. The version guard runs first
|
|
59
|
+
// and exits 3 so the caller can tell "interpreter below the
|
|
60
|
+
// floor" (try the next candidate) apart from "the runtime
|
|
61
|
+
// failed" (contribute nothing).
|
|
54
62
|
const PYTHON_PROTOCOL = `\
|
|
55
63
|
import sys
|
|
64
|
+
|
|
65
|
+
if sys.version_info < (3, 10):
|
|
66
|
+
raise SystemExit(3)
|
|
67
|
+
|
|
56
68
|
from pathlib import Path
|
|
57
69
|
|
|
70
|
+
# The block carries CJK copy, so stdout must be UTF-8 whatever the console
|
|
71
|
+
# codepage says: on a cp1252 console (the CI runner's default) print()
|
|
72
|
+
# raises, and the caller reads the traceback as "interpreter ran, protocol
|
|
73
|
+
# failed" and injects nothing. newline="" keeps the payload bytes identical
|
|
74
|
+
# across platforms, like the edit-scope protocol below.
|
|
75
|
+
sys.stdout.reconfigure(encoding="utf-8", errors="replace", newline="")
|
|
76
|
+
|
|
58
77
|
cwd = sys.argv[1]
|
|
59
78
|
full_digest = len(sys.argv) > 2 and sys.argv[2] != "0"
|
|
60
79
|
current = Path(cwd).resolve()
|
|
@@ -97,14 +116,25 @@ const EDIT_EXEC_OPTIONS = {
|
|
|
97
116
|
maxBuffer: 1024 * 1024,
|
|
98
117
|
};
|
|
99
118
|
|
|
100
|
-
// Editor-phase
|
|
101
|
-
//
|
|
102
|
-
//
|
|
103
|
-
//
|
|
104
|
-
|
|
119
|
+
// Editor-phase scope protocol: same root discovery, then the shared
|
|
120
|
+
// edit-scope entry the other hosts reach through their PostToolUse
|
|
121
|
+
// hook, so DSH sessions get the identical single-line advisory. Prints
|
|
122
|
+
// at most one line; exits 0 either way. The version guard exits 3 (an
|
|
123
|
+
// interpreter below the floor, not a runtime verdict), which the
|
|
124
|
+
// caller treats like a missing binary: try the next candidate.
|
|
125
|
+
const PYTHON_EDIT_SCOPE_PROTOCOL = `\
|
|
105
126
|
import sys
|
|
127
|
+
|
|
128
|
+
if sys.version_info < (3, 10):
|
|
129
|
+
raise SystemExit(3)
|
|
130
|
+
|
|
106
131
|
from pathlib import Path
|
|
107
132
|
|
|
133
|
+
# The advisory line starts with a character outside the Windows ANSI
|
|
134
|
+
# codepage (the warning sign), so stdout must be UTF-8 regardless of the
|
|
135
|
+
# console: otherwise the print raises and the warning is swallowed.
|
|
136
|
+
sys.stdout.reconfigure(encoding="utf-8", errors="replace", newline="")
|
|
137
|
+
|
|
108
138
|
cwd = sys.argv[1]
|
|
109
139
|
file_path = sys.argv[2]
|
|
110
140
|
current = Path(cwd).resolve()
|
|
@@ -120,8 +150,13 @@ if root is None:
|
|
|
120
150
|
raise SystemExit(0)
|
|
121
151
|
sys.path.insert(0, str(root / ".cowork-flow" / "scripts"))
|
|
122
152
|
try:
|
|
123
|
-
from
|
|
124
|
-
print(
|
|
153
|
+
from adapters.host.edit_scope import edit_scope_warning
|
|
154
|
+
print(edit_scope_warning(root, {
|
|
155
|
+
"cwd": cwd,
|
|
156
|
+
"hook_event_name": "PostToolUse",
|
|
157
|
+
"tool_name": "Write",
|
|
158
|
+
"tool_input": {"file_path": file_path},
|
|
159
|
+
}))
|
|
125
160
|
except Exception:
|
|
126
161
|
pass
|
|
127
162
|
`;
|
|
@@ -175,6 +210,16 @@ function discoverCandidates() {
|
|
|
175
210
|
}
|
|
176
211
|
|
|
177
212
|
|
|
213
|
+
// ENOENT = the interpreter does not exist, exit code 3 = it is older than
|
|
214
|
+
// the protocol's floor; both mean "try the next candidate". Any other
|
|
215
|
+
// failure — a different non-zero exit or a timeout — means the interpreter
|
|
216
|
+
// ran but the protocol failed: the candidate works, the runtime does not,
|
|
217
|
+
// so it is not skipped. `attempt`'s edit-scope variant shares this mapping.
|
|
218
|
+
export function candidateUnavailable(error) {
|
|
219
|
+
return error.code === 'ENOENT' || error.code === 3;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
|
|
178
223
|
function tryPython(candidate, cwd, full = true) {
|
|
179
224
|
return new Promise((resolve) => {
|
|
180
225
|
execFile(
|
|
@@ -183,11 +228,7 @@ function tryPython(candidate, cwd, full = true) {
|
|
|
183
228
|
EXEC_OPTIONS,
|
|
184
229
|
(error, stdout) => {
|
|
185
230
|
if (error) {
|
|
186
|
-
|
|
187
|
-
// the next one). Any other failure means the
|
|
188
|
-
// interpreter ran but the protocol failed — the
|
|
189
|
-
// candidate works, the runtime does not.
|
|
190
|
-
resolve({ missing: error.code === 'ENOENT', output: '' });
|
|
231
|
+
resolve({ missing: candidateUnavailable(error), output: '' });
|
|
191
232
|
return;
|
|
192
233
|
}
|
|
193
234
|
resolve({ missing: false, output: stdout.trim() });
|
|
@@ -281,6 +322,13 @@ export async function runWorkflowState(cwd, full = true) {
|
|
|
281
322
|
workingPython = candidate;
|
|
282
323
|
return normalize(result.output);
|
|
283
324
|
}
|
|
325
|
+
// Nothing in the candidate list could run the protocol: contribute
|
|
326
|
+
// nothing, but say why once — a Python below the floor (or none at
|
|
327
|
+
// all) is a misconfiguration the session owner can fix, and total
|
|
328
|
+
// silence hides it.
|
|
329
|
+
process.stderr.write(
|
|
330
|
+
'cowork-flow dsh hook: no Python 3.10+ interpreter found; skipping workflow-state injection\n'
|
|
331
|
+
);
|
|
284
332
|
return '';
|
|
285
333
|
}
|
|
286
334
|
|
|
@@ -305,12 +353,12 @@ export function editedFilePath(exec) {
|
|
|
305
353
|
|
|
306
354
|
|
|
307
355
|
/**
|
|
308
|
-
* Editor-phase
|
|
356
|
+
* Editor-phase scope check for one edited file. Returns the
|
|
309
357
|
* single-line warning, or '' when there is nothing to say:
|
|
310
|
-
* no root, no interpreter, no
|
|
358
|
+
* no root, no interpreter, no active task, a clean run, or
|
|
311
359
|
* a timeout. Never throws.
|
|
312
360
|
*/
|
|
313
|
-
export async function
|
|
361
|
+
export async function runEditScopeCheck(cwd, filePath) {
|
|
314
362
|
if (hooksDisabled() || !cwd || !filePath) {
|
|
315
363
|
return '';
|
|
316
364
|
}
|
|
@@ -321,11 +369,11 @@ export async function runEditSpecCheck(cwd, filePath) {
|
|
|
321
369
|
const attempt = (candidate) => new Promise((resolvePromise) => {
|
|
322
370
|
execFile(
|
|
323
371
|
candidate.command,
|
|
324
|
-
[...candidate.args, '-c',
|
|
372
|
+
[...candidate.args, '-c', PYTHON_EDIT_SCOPE_PROTOCOL, cwd, filePath],
|
|
325
373
|
EDIT_EXEC_OPTIONS,
|
|
326
374
|
(error, stdout) => {
|
|
327
375
|
if (error) {
|
|
328
|
-
resolvePromise({ missing: error
|
|
376
|
+
resolvePromise({ missing: candidateUnavailable(error), output: '' });
|
|
329
377
|
return;
|
|
330
378
|
}
|
|
331
379
|
resolvePromise({ missing: false, output: stdout.trim() });
|
|
@@ -357,7 +405,7 @@ export async function runEditSpecCheck(cwd, filePath) {
|
|
|
357
405
|
* preset deliberately does not import, so the shape is
|
|
358
406
|
* built here with node's uuid.
|
|
359
407
|
*/
|
|
360
|
-
function
|
|
408
|
+
function noticeMessage(text) {
|
|
361
409
|
return {
|
|
362
410
|
id: randomUUID(),
|
|
363
411
|
role: 'user',
|
|
@@ -366,7 +414,7 @@ function specNoticeMessage(text) {
|
|
|
366
414
|
kind: 'plugin',
|
|
367
415
|
plugin: 'cowork-flow',
|
|
368
416
|
form: 'notice',
|
|
369
|
-
summary: '
|
|
417
|
+
summary: 'edit-scope',
|
|
370
418
|
},
|
|
371
419
|
};
|
|
372
420
|
}
|
|
@@ -430,7 +478,7 @@ export function apply(ctx) {
|
|
|
430
478
|
refresh(agent, false);
|
|
431
479
|
});
|
|
432
480
|
|
|
433
|
-
// Editor-phase
|
|
481
|
+
// Editor-phase scope check: the tool already ran, so this
|
|
434
482
|
// only adds context for the next request and never blocks
|
|
435
483
|
// the call. The waterfall awaits next() first.
|
|
436
484
|
ctx.on('tools/post-execute', async (exec, _result, next) => {
|
|
@@ -442,7 +490,7 @@ export function apply(ctx) {
|
|
|
442
490
|
}
|
|
443
491
|
let warning = '';
|
|
444
492
|
try {
|
|
445
|
-
warning = await
|
|
493
|
+
warning = await runEditScopeCheck(agent.cwd, filePath);
|
|
446
494
|
} catch {
|
|
447
495
|
warning = '';
|
|
448
496
|
}
|
|
@@ -450,7 +498,7 @@ export function apply(ctx) {
|
|
|
450
498
|
return downstream;
|
|
451
499
|
}
|
|
452
500
|
const contexts = [
|
|
453
|
-
|
|
501
|
+
noticeMessage(warning),
|
|
454
502
|
...((downstream && downstream.additionalContexts) || []),
|
|
455
503
|
];
|
|
456
504
|
if (downstream && downstream.kind === 'block') {
|
|
@@ -34,6 +34,27 @@ const INJECT_RELATIVE = join(
|
|
|
34
34
|
);
|
|
35
35
|
const HOST = "kimi-code";
|
|
36
36
|
const SPAWN_TIMEOUT_MS = 20000; // config.toml grants the hook 30s
|
|
37
|
+
// The .cowork-flow/run floor, reused here: a candidate that cannot run
|
|
38
|
+
// Python 3.10+ is skipped like a missing binary, so a stale `python3`
|
|
39
|
+
// never hides a working `python` further down the list.
|
|
40
|
+
const MIN_PYTHON_CHECK = "import sys; raise SystemExit(0 if sys.version_info >= (3, 10) else 1)";
|
|
41
|
+
|
|
42
|
+
function interpreterIsUsable(candidate) {
|
|
43
|
+
try {
|
|
44
|
+
const probe = spawnSync(
|
|
45
|
+
candidate[0],
|
|
46
|
+
[...candidate.slice(1), "-c", MIN_PYTHON_CHECK],
|
|
47
|
+
{ timeout: SPAWN_TIMEOUT_MS, windowsHide: true }
|
|
48
|
+
);
|
|
49
|
+
// A probe killed by its own timeout reached no verdict — a loaded machine,
|
|
50
|
+
// not a bad interpreter — so let the real spawn decide; it carries the
|
|
51
|
+
// same timeout and its own diagnostics.
|
|
52
|
+
if (probe.error && probe.error.code === "ETIMEDOUT") return true;
|
|
53
|
+
return !probe.error && probe.status === 0;
|
|
54
|
+
} catch {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
37
58
|
|
|
38
59
|
function readHookInput() {
|
|
39
60
|
if (process.stdin.isTTY) return { raw: "", parsed: {} };
|
|
@@ -89,6 +110,7 @@ function main() {
|
|
|
89
110
|
|
|
90
111
|
const injectScript = join(root, INJECT_RELATIVE);
|
|
91
112
|
for (const candidate of pythonCandidates()) {
|
|
113
|
+
if (!interpreterIsUsable(candidate)) continue;
|
|
92
114
|
let result;
|
|
93
115
|
try {
|
|
94
116
|
result = spawnSync(
|
|
@@ -117,8 +139,12 @@ function main() {
|
|
|
117
139
|
}
|
|
118
140
|
process.exit(0);
|
|
119
141
|
}
|
|
120
|
-
// No
|
|
121
|
-
//
|
|
142
|
+
// No candidate could run the entry: stay fail-open, but say why once
|
|
143
|
+
// instead of hiding a misconfiguration (a Python below the floor, or
|
|
144
|
+
// none at all).
|
|
145
|
+
process.stderr.write(
|
|
146
|
+
"cowork-flow kimi-code hook: no Python 3.10+ interpreter found; skipping injection\n"
|
|
147
|
+
);
|
|
122
148
|
process.exit(0);
|
|
123
149
|
}
|
|
124
150
|
|