cowork-flow 1.7.2 → 1.8.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 (34) hide show
  1. package/CHANGELOG.md +17 -1
  2. package/docs/direction.md +179 -0
  3. package/docs/index.md +1 -0
  4. package/package.json +1 -1
  5. package/presets/claude-code/.claude-plugin/plugin.json +1 -1
  6. package/presets/codex/.codex-plugin/plugin.json +1 -1
  7. package/presets/kimi-code/.kimi-plugin/plugin.json +1 -1
  8. package/presets/opencode/cowork-flow/plugin-core.js +51 -3
  9. package/presets/qoder/.qoder-plugin/plugin.json +1 -1
  10. package/presets/zcode/.zcode-plugin/plugin.json +1 -1
  11. package/presets/zcode/hooks/hooks.json +3 -3
  12. package/presets/zcode/hooks/{inject-context.js → inject-context.mjs} +11 -6
  13. package/template/.cowork-flow/.version +1 -1
  14. package/template/.cowork-flow/scripts/adapters/cli/spec_check.py +10 -1
  15. package/template/.cowork-flow/scripts/adapters/cli/task_lifecycle_commands.py +3 -0
  16. package/template/.cowork-flow/scripts/adapters/cli/task_parser.py +1 -0
  17. package/template/.cowork-flow/scripts/adapters/host/workflow_state_hook.py +21 -0
  18. package/template/.cowork-flow/scripts/adapters/host/zcode_policy.py +1 -1
  19. package/template/.cowork-flow/scripts/services/ac_evidence.py +107 -0
  20. package/template/.cowork-flow/scripts/services/fact_view.py +16 -2
  21. package/template/.cowork-flow/scripts/services/lifecycle_checks.py +84 -0
  22. package/template/.cowork-flow/scripts/services/lifecycle_policy.py +5 -0
  23. package/template/.cowork-flow/scripts/services/spec_check.py +11 -1
  24. package/template/.cowork-flow/scripts/services/task_lifecycle.py +12 -0
  25. package/template/.cowork-flow/spec/contracts/context-injection.md +6 -4
  26. package/template/.cowork-flow/spec/contracts/decision-anchor.md +1 -0
  27. package/template/.cowork-flow/spec/contracts/evidence-completion.md +48 -0
  28. package/template/.cowork-flow/spec/contracts/index.md +1 -0
  29. package/template/.cowork-flow/spec/contracts/spec-checks.md +49 -82
  30. package/template/.cowork-flow/spec/runtime/contract-registry.json +13 -0
  31. package/template/.opencode/cowork-flow/plugin-core.js +51 -3
  32. package/template/skills/spec-sync/SKILL.md +1 -0
  33. package/template/skills/task-review/SKILL.md +3 -2
  34. package/template/skills/test-first/SKILL.md +4 -0
package/CHANGELOG.md CHANGED
@@ -5,6 +5,20 @@
5
5
  ### Added
6
6
 
7
7
 
8
+ ## [1.8.0] - 2026-10-08
9
+
10
+ ### Added
11
+
12
+ - **spec-check 采纳闭环(1.8.x 主线)**:让 spec-check 机制从"存在但沉默"变为可遵循。① 零声明可见性:仓库无生效声明时 `spec-check` 文本模式输出显式事实行 `spec-check: no checks declared (<N> spec files scanned)`(与"有声明且全过"可区分),`--json` 顶层新增 `specFiles`、summary 新增 `declarations`——"没挂声明"不再与"干净通过"同形(零声明保持退出码 0:不挂命令是合法状态,但必须被看见)。② 注入契约 `SPEC_CHECKS_V1`:`contract-registry.json` 新增第 10 条(digest 2 条 + readWhen 3 条),python/zcode/opencode 三线渲染在 digest 块输出 `- SPEC_CHECKS_V1: .cowork-flow/spec/contracts/spec-checks.md`。③ `spec-checks.md` 重排为使用者核心区(何时挂 / 语法含 UTF-8 无 BOM 注意点 / 三态与门禁含零声明语义 / 时机与输出 / 归属)+ 维护者附录(宿主能力矩阵 / delegated 缺口与补偿 / digest 注入 / 开放决策),零信息丢失由独立复核逐项清点确认。④ AI 挂命令职责三处一致:契约归属段、`task-review`(User specs 逐条问"可机检吗、已挂吗" + `user_spec_review` 列「条款 → 命令」清单)、`spec-sync`(同次维护挂/刷新声明);质量纪律四条(干净树跑出 pass / 反例能报错 / `files` 限定 / 挂后跑 spec-check 确认解析)获测试锁(`test_review_contract.py` markers)。不新增硬门禁——放权语义:主会话 AI 主动挂声明,review 报告留痕。
13
+ - **证据化完成(1.8.0 主线)**:任务完成从"流程走完"升级为"证据齐全"。decision-anchor 的验收标准(AC)成为机器可查询事实——`services/ac_evidence.py` 解析 `## 验收标准` 的勾选状态并读取任务目录 `evidence.jsonl`(追加式,`{ac, kind: test|command|manual, ref, note?, recordedAt, by}`);完成动作新增两条稳定门禁 code:存在未勾选 AC 阻断(`LIFECYCLE-AC-001`)、已声明 AC 缺有效证据记录阻断(`LIFECYCLE-AC-002`),新旗标 `--allow-missing-evidence` 豁免后者并把 `meta.evidenceExempt`(含 `at` 与 `missing`)留在任务记录;decision-anchor 注入块在 `Acceptance:` 行后追加 `AC evidence: n/m`(缺失时附 `missing=` 列表),三线(Python / zcode shim / opencode 插件)逐字节同格式并由矩阵测试锁定;`run state --json` 与 MCP 事实视图新增 `evidenceCoverage`。无 AC 的存量任务零影响(兼容路径有测试锁定),契约见 `.cowork-flow/spec/contracts/evidence-completion.md`。
14
+
15
+ ## [1.7.3] - 2026-10-06
16
+
17
+ ### Fixed
18
+
19
+ - ZCode 插件 hook shim 由 `.js` 改为 **`.mjs`**,并修复项目根向上查找只覆盖两层的缺陷(1.7.2 遗留说明的收口)。修前两个缺陷叠加:shim 安装到宿主插件缓存目录后无 `package.json`,`.js` 按 CommonJS 解析,ESM `import` 只靠 Node ≥23 的语法探测兜住——仓库声明最低 Node 20,这些机器上 shim 直接语法错误,SessionStart / UserPromptSubmit / PostToolUse 注入从未发生;同时 `findWorkflowRoot` 在循环外缓存 `dirname(current)`,向上查找只覆盖起始目录与其父,hook 事件的 cwd 在项目根下两级及以上(例如从子目录打开会话)时定位不到项目运行时,注入同样为空。现在对齐 qoder / kimi 先例:`.mjs` 扩展名 + `resolve` 起步 + 逐级向上直到文件系统根,其余传输行为零变化。升级动作:升级 CLI 后运行 `cwf host add zcode --force` 刷新插件缓存,并重启 ZCode。
20
+
21
+
8
22
  ## [1.7.2] - 2026-10-04
9
23
 
10
24
  ### Added
@@ -282,7 +296,9 @@
282
296
 
283
297
  此版本未保留本地条目正文,详情见 [GitHub Release](https://github.com/fishlikewater/cowork-flow/releases/tag/v0.0.47)。
284
298
 
285
- [Unreleased]: https://github.com/fishlikewater/cowork-flow/compare/v1.7.2...HEAD
299
+ [Unreleased]: https://github.com/fishlikewater/cowork-flow/compare/v1.8.0...HEAD
300
+ [1.8.0]: https://github.com/fishlikewater/cowork-flow/compare/v1.7.3...v1.8.0
301
+ [1.7.3]: https://github.com/fishlikewater/cowork-flow/compare/v1.7.2...v1.7.3
286
302
  [1.7.2]: https://github.com/fishlikewater/cowork-flow/compare/v1.7.1...v1.7.2
287
303
  [1.7.1]: https://github.com/fishlikewater/cowork-flow/compare/v1.7.0...v1.7.1
288
304
  [1.7.0]: https://github.com/fishlikewater/cowork-flow/compare/v1.6.0...v1.7.0
@@ -0,0 +1,179 @@
1
+ # cowork-flow 方向规划:Agent 运行时上下文与协作事实层
2
+
3
+ > 状态:**已批准(Approved)** · 创建:2026-08-27 · 决策记录:注入格式取舍选**方案 A**(结构化事实头 + 人读摘要体,分级降级)
4
+ > 本文档是后续阶段立项的权威输入。任何方向的修订必须先更新本文档再立项。
5
+
6
+ ---
7
+
8
+ ## 0. 状态追踪(防遗忘,后续会话先看这里)
9
+
10
+ | 阶段 | 状态 | 关联任务 / 提交 | 下一动作 |
11
+ |---|---|---|---|
12
+ | 阶段 0:定位与协议收敛 | ✅ 完成 | 08-27-stage0-positioning-protocol(0c7ae64) | — |
13
+ | 阶段 1:事实层 API 化 + 注入结构化 | ✅ 完成 | 1a: 08-28-stage1a-state-fact-view(60753fa);1b: 08-28-stage1b-structured-fact-header(5cb25ea) | — |
14
+ | 阶段 2:多执行者语义 | ✅ 完成 | 08-28-stage2-multi-executor(fd4d6af):executor 归属 / 冲突拦截 / --takeover / 无会话 start / evidence 证据位 | — |
15
+ | 阶段 3:协议生态适配 | ✅ 完成(观察期持续) | 08-29-stage3-mcp-fact-access(a88474e):`run mcp-state` 无依赖 MCP 只读服务 + `fact-layer-access.md` 接入契约 | 观察 AGNTCY 等协议收敛;写工具默认拒绝,需重开契约 |
16
+ | 本规划文档落库 | ✅ 完成 | 08-27-direction-doc | — |
17
+ | 1.5→1.7 宿主广度 + 发布链(七宿主) | ✅ 完成 | CHANGELOG 1.5.0-1.7.2 | — |
18
+ | 1.7.3 维护:ZCode shim .mjs + 项目根查找修复 | ✅ 完成 | 10-04-zcode-shim-mjs(d0716a9) | — |
19
+ | 1.8.0 立项:证据化完成 | 🔄 立项完成,待启动 | 10-05-direction-1-8-0 / 10-05-evidence-completion | 在宿主会话启动主线任务 |
20
+ | 1.8.0 主线实现:证据化完成 | ✅ 完成 | 10-05-evidence-completion(baa386d,CI test + windows-core 全绿) | 真实宿主冒烟后发布 1.8.0 |
21
+ | 1.8.x 收尾:Skill 证据补课 | 🔄 进行中 | 10-06-skill-evidence-training | 合入后与冒烟共同构成 1.8.0 发布判据 |
22
+ | 1.9.0 前置:task adopt 命令 | 📋 立项 | 10-06-task-adopt-command | 下一会话实现(agent-teams 就绪的地基) |
23
+
24
+ **当前结论**:方向路线图阶段 0-3 全部落地。项目当前形态:「多主机 Agent 协作的运行时上下文与协作事实层」——事实视图(CLI + MCP)、结构化注入、跨宿主一致性治理、执行者归属与证据位均已就绪并有测试锁定。后续演进见「不做什么」边界与观察期事项。
25
+
26
+ ---
27
+
28
+ ## 1. 定位(一句话)
29
+
30
+ > **cowork-flow 不约束模型怎么想;它保证任何主机上的任何 agent,在任何会话、任何时刻,从同一份权威事实起步。**
31
+
32
+ | | 旧定位 | 新定位 |
33
+ |---|---|---|
34
+ | 叙事 | 帮你建流程(流程模板) | 运行时上下文与协作事实层 |
35
+ | 手段 | 任务流、门禁、规格文档 | 状态注入、事实一致性、跨宿主治理 |
36
+ | 目的 | 让协作"守规矩" | 让协作"从正确状态起步" |
37
+ | 模型能力上升时 | 约束类价值递减(模型自己会守规矩) | 事实类价值递增(状态是模型编不出来的) |
38
+
39
+ **为什么必须现在调整**:项目的消费方正在从"人"变成"agent"。注入格式还停留在给人看的文本(`<workflow-state>` 的 label:value 行 + 散文,全五宿主一致;唯一严格机器可读的是 fingerprint 属性行)。事实层(task.json+revision、state-snapshot、operations 日志、implement.jsonl、contract-registry、host-assets)已经建起来,但散落成文件约定,没有统一机器出口;决策事实(decision-anchor)完全不在注入体系内。
40
+
41
+ ## 2. 背景:大模型发展对项目的五个冲击
42
+
43
+ 1. **模型能力上升** → "约束模型"价值递减(门禁、防呆、状态机防漂移——模型越强越不需要被管),"喂状态"价值递增(上下文质量成为 agent 质量的第一杠杆)。
44
+ 2. **人机对话 → agent 集群执行**(远程/后台/并行/批处理/CI 触发)→ 会话模型需支持无会话执行者、多执行者协作、委派与收归。
45
+ 3. **上下文工程成为学科**(token 预算、注入时机、摘要压缩、状态新鲜度)→ 项目已有的五宿主 hook 注入、digest 指纹、快照单源正是"运行时上下文中枢",应提升为产品主线。
46
+ 4. **协议标准化**(MCP 成事实标准、hooks.json 类扩展点演化、agent 间协议涌现)→ 契约格式自持、host 适配保持薄层;不自创跨 agent 协议。
47
+ 5. **模型质量波动** → 门禁仍是安全网(每个新版本可能有行为回归),保留但不作为卖点投资。
48
+
49
+ ## 3. 现状资产盘点(2026-08-27 探查实录)
50
+
51
+ ### 3.1 注入层
52
+
53
+ | Host | 注入机制 | 触发时机 | 格式 |
54
+ |---|---|---|---|
55
+ | zcode | 插件 process hook(JS,inject-context.js) | SessionStart 全量 / 每条消息单行指纹 / PostToolUse 生命周期命令刷新 | stdout JSON,内部文本块 |
56
+ | codex | hooks.json 命令(Python) | 仅 UserPromptSubmit | 全量 digest(无 slim) |
57
+ | claude-code | settings.json hook(Python) | SessionStart + 每条消息 | 全量 digest(无 slim) |
58
+ | opencode | 插件(JS) | 每次 prompt 组装 + shell.env | 全量 digest(无 slim) |
59
+ | dsh | 预设插件(内嵌 Python 协议) | session-start / inbox-claimed / 生命周期命令工具结果 | systemPrompt section 文本 |
60
+
61
+ **已知债**:三线零代码共享(zcode JS / opencode JS / python 线)——指纹序列化策略不一致(`JSON.stringify` 不排序 vs `sort_keys` vs `stableStringify`,跨 host 指纹有分叉风险);slim 指纹形态只有 zcode 有;codex 事件名硬编码丢弃事件维度;state-snapshot 只有 zcode 消费(Python 线仍 TAG_RE 解析 templates.md,是已确认的漂移面)。
62
+
63
+ ### 3.2 事实层(任务状态模型)
64
+
65
+ - **权威事实(生命周期内不变)**:id/name、title、creator、assignee、createdAt、priority、description;决策事实 decision-anchor.md(目标/AC/被拒方案/假设/范围);绑定事实 `task.json.meta.planFile`;写入痕迹 `_state.revision`+`operation_id` 与 operations/ 操作日志(追加式)。
66
+ - **流程状态(有明确写者)**:status(planning→in_progress→review→completed,单向禁回退)、completedAt、children/parent、会话绑定(sessions/*.json、subagents/*.json)、state-snapshot.json、归档位置。
67
+ - **死字段待清理**:`commit`(声明存在、无写入方)、`scope`、`relatedFiles`(已被 implement.jsonl 取代)、`subtasks`(无写入方)。
68
+ - **模型**:单主人同步推进(start 必须会话身份;review/complete 已支持无会话);子代理为受缚叶执行者(authority 五项全 false,证据生产不推进状态);并发正确性靠 CAS+锁+UoW 恢复,已成熟。
69
+ - **机器出口现状**:JSON 有 task.json、snapshot、implement.jsonl、sessions/subagents、operations、`task next --json`、contract-registry、host-assets;**仅人读无解析**:decision-anchor.md(只查章节字符串)、plans/*.md(v1 显式不解析)。
70
+
71
+ ### 3.3 治理层
72
+
73
+ - 分层哲学已验证:**机器只裁决事实,判断交给 review 双人证**。
74
+ - 硬契约:host-assets.json(能力矩阵,doctor 交叉校验)、contract-registry.json(指纹清单)、schemas/、lifecycle 文件范围白名单、plan-binding gate、发布链(release:check + CHANGELOG + 三处版本同步)。
75
+ - 软规范:backend/frontend/game(review 强审查、非机器 gate)、references/(loadWhen 驱动)。
76
+ - 模型能力上升时:纯防呆类(guides、error-output-as-data 类)价值递减;跨宿主一致性类(host-assets 校验、分布漂移检测、syncPolicy 保护、registry 指纹)价值递增。
77
+
78
+ ## 4. 为什么这是最优方向(候选否决)
79
+
80
+ | 候选 | 否决理由 |
81
+ |---|---|
82
+ | AgentOps / 可观测性 | 无运行遥测资产,从头建等于换赛道,与工作流引擎定位断裂 |
83
+ | 任务编排引擎 | 与主机自带编排重叠;状态模型翻成分布式系统复杂度爆炸 |
84
+ | 纯治理合规层 | 纯门禁价值随模型自觉性上升递减;无独特性 |
85
+ | ✅ 事实层 + 注入 | 全仓最厚资产都在这;跨主机一致性是任何单主机工具给不了的**结构性差异**;与上下文工程趋势同向且落在可执行的事实层 |
86
+
87
+ ## 5. 三支柱
88
+
89
+ | 支柱 | 内涵 | 已有资产 | 缺口 |
90
+ |---|---|---|---|
91
+ | **事实层** | 权威状态与决策的可查询存储 | task.json+revision、snapshot、operations、implement.jsonl、registry、host-assets | 无统一机器出口;decision-anchor 无解析;死字段 |
92
+ | **注入协议** | 按时机/角色/token 预算把事实注入任意 host | 五宿主注入齐备、digest 指纹、快照信任链、会话隔离 | 人读文本格式;三线拷贝;slim 不对称;决策事实不注入 |
93
+ | **一致性治理** | 跨宿主/跨会话/跨模型的事实一致性 | doctor 交叉校验、同步保护、文件范围门禁、发布链 | 指纹序列化不一致;快照单宿主消费;无跨 host 注入行为断言 |
94
+
95
+ ## 6. 演进路线
96
+
97
+ ### 阶段 0:定位与协议收敛(低风险、高杠杆)
98
+ - 交付:README 定位改写;新增 `spec/contracts/context-injection.md`(冻结块结构、事件时机矩阵、slim 策略、序列化规范,三线按规范对齐);修一致性债(指纹序列化统一 sort_keys、Python 线补 slim、codex 补事件名)。
99
+ - 验收:新增跨 host 注入行为测试——同一仓库五宿主指纹一致、slim 触发一致。
100
+
101
+ ### 阶段 1:事实层 API 化 + 注入结构化
102
+ - 交付:`cowork-flow state <task> --json`(task.json+snapshot+sessions+plan 绑定+registry 汇总为一份事实视图);workflow-state 块升级为**结构化事实头 + 紧凑人读体**(协议允许分级降级,纯文本通道退化为属性行);决策事实条件注入(planning/in_progress 时注入 decision-anchor 要点,完成后收缩)。
103
+ - 验收:任何 host 的 agent 可经上下文与 CLI 拿到"任务是什么、为什么、边界在哪",且机器可解析。
104
+
105
+ ### 阶段 2:多执行者语义
106
+ - 交付:owner/executor 归属字段;无会话推进放开(CLI/CI 触发);多会话协作同一任务的协调(任务级锁/合并协议,复用 CAS+UoW);子代理证据位独立于状态位。
107
+ - 验收:两会话可显式接管同一任务不踩踏;子代理证据可独立推进"证据位"。
108
+
109
+ ### 阶段 3:协议生态适配(观察期,不承诺)
110
+ - 交付:MCP 服务暴露事实层(只读查询先行,`state --json` 的薄壳);观察 AGNTCY 等,不自创协议;保持 adapter 薄。
111
+ - 验收:任何 MCP 客户端可查任务状态;生态协议出现时 2 天内可接入。
112
+
113
+ ## 7. 已拍板取舍
114
+
115
+ - **注入格式(已决策:方案 A)**:结构化事实头 + 人读摘要体双格式,分级降级——结构化优先,通道不允许时退化为属性行/纯摘要。
116
+ - 否决 B(全 JSON 注入):dsh/codex 纯文本通道浪费 token,破坏现有紧凑散文面包屑可读性。
117
+ - 否决 C(保持文本只加 --json 出口):核心价值主张落不到注入本身。
118
+ - **事实层出口(推荐)**:CLI + JSON 协议先行(零依赖),MCP 为阶段 3 的薄适配,不先建。
119
+ - **三线收敛(推荐)**:先"协议规范单源 + 三线按规范实现",代码级共享后议(成本高、收益在协议一致性先兑现)。
120
+
121
+ ## 8. 明确不做什么
122
+
123
+ 1. 不做"更聪明的提示词/AGENTS.md 写作术"——模型能力会追平,且无数据资产。
124
+ 2. 不自创跨 agent 通信标准——协议博弈期,等收敛。
125
+ 3. 不做任务执行引擎——不替代 agent 干活,只保证它从正确状态起步。
126
+ 4. 不深化纯流程门禁——保留为安全网,停止投资。
127
+
128
+ ## 9. 度量指标
129
+
130
+ 1. **跨宿主行为一致**:五宿主指纹/slim 触发有测试断言,0 漂移。
131
+ 2. **新 host 接入成本**:从数天降到小时级(协议规范完备度直接度量)。
132
+ 3. **注入 token 成本**:长会话注入体积不随消息数线性增长(slim 全覆盖)。
133
+ 4. **事实新鲜度事故**:多会话任务污染/踩踏事故率趋零。
134
+
135
+ ## 10. 2026-10 修订:1.8.0 方向(证据化完成)
136
+
137
+ > 修订背景:阶段 0-3 之后,1.5→1.7 三条版本线投入宿主广度(七宿主)与发布链,广度路线边际收益递减;生态出现确定性信号,方向回到事实层纵深。本节为 1.8.0 立项的权威依据。
138
+
139
+ ### 10.1 生态输入(2026-10 复核)
140
+
141
+ 1. **spec-driven development 主流化**:GitHub Spec Kit、AWS Kiro、OpenSpec、Tessl 押同一注——spec 与代码作为可跟踪制品共同演化;市场口径是"builder 们正从自由式 vibe coding 走向结构化工作流"。cowork-flow 的 decision-anchor + plan-binding 在此赛道上,结构性差异是宿主中立、状态在仓库内——定位为这些 SDD 工具**之下的状态层**,不与其竞争。
142
+ 2. **agent 团队 / 后台 agent 兴起**:Claude Code Agent Teams 官方口径 teammates ephemeral by design,靠 git 与文件协调;"持久共享状态"成为公认缺口。cowork-flow 的任务状态在仓库、跨会话可续、executor 归属与冲突拦截(阶段 2 资产)正是该缺口的产品化。
143
+ 3. **质量与隐藏 bug 是 vibe coding 头号风险叙事**:"验证证据"正在成为完成的定义。这与分层哲学(机器裁决事实、review 双人证)同向,但 AC 目前只是 decision-anchor 里的 markdown 勾选框,机器不解析。
144
+
145
+ ### 10.2 1.8.0 主线:证据化完成
146
+
147
+ - **定义**:任务完成从"流程走完"升级为"证据齐全"——decision-anchor 的 AC 成为机器可查询事实,完成门禁校验每个 AC 关联到可验证证据(测试 / 命令输出 / evidence 记录),缺失时阻断或显式放行(对齐 spec-check 的 unchecked 语义)。
148
+ - **最小设计立场**:不发明新格式——AC 沿用 decision-anchor 现有 `- [ ] AC-xxx:` checklist 形态做解析;证据落在任务目录 `evidence.jsonl`(每行 `{ac, kind, ref, recordedAt}`,追加式);无 decision-anchor 或无 AC 的旧任务自动跳过(兼容路径,不产生虚假阻断)。
149
+ - **注入面**:review / 完成阶段注入 AC 覆盖事实行(n/m 有证据);contract digest 随之变化,需同步跨宿主一致性测试与 `spec/contracts/context-injection.md` 契约。
150
+ - **为什么先做它**:自洽(不依赖宿主团队功能节奏)、直接回应"AI 生成代码怎么算完成"的行业焦虑、是候选 B 的前置(evidence 事实格式先定,团队协调才有可信的交接凭证)。
151
+
152
+ ### 10.3 候选与缓行
153
+
154
+ - **候选 B(1.9.0 候选主线):agent-teams 就绪**——把 executor / evidence / takeover 语义补成团队协调原语(任务认领、交接、依赖边),让多 agent 场景把 `.cowork-flow/tasks/` 当共享看板。延后理由:生态节奏不完全可控,且需要 1.8.0 的证据格式做地基。
155
+ - **维持缓行**:MCP 写工具(重开 fact-layer-access 契约才可议)、新宿主接入、自创跨 agent 协议。
156
+ - **新增待决项(维护者拍板,不并入 1.8.0)**:文档英文化 / 双语——npm 周下载约 273(2026-10-03 口径),英文社区是 SDD 讨论主场;与 AGENTS.md「文档语言:中文」约定冲突,单独立项时再议。
157
+
158
+ ### 10.4 度量补充
159
+
160
+ 5. **采用**:npm 周下载与激活项目数——1.8.0 之后用数字验证"深度路线"是否兑现。
161
+
162
+ ### 10.5 2026-10-06 增补:1.8.x 收尾与 1.9.0 排期
163
+
164
+ - **1.8.0 主线已实现**(10-05-evidence-completion,baa386d):AC 事实化、`evidence.jsonl`、完成门禁 `LIFECYCLE-AC-001/002`、三线注入覆盖行、`evidenceCoverage` 事实视图;主线任务自身以新门禁完成(四 AC 证据齐全,无豁免)。
165
+ - **1.8.0 发布判据**:CI 双平台(已绿)+ 真实宿主冒烟(维护者:带 AC 任务在 ZCode/Qoder 实跑一轮,观察覆盖行与门禁体验)+ Skill 证据补课合入。
166
+ - **1.9.0 主线候选:agent-teams 就绪**,按 10.3 排期;前置小任务 = **task adopt 命令**(孤儿任务收养:显式重绑执行者 + 陈旧基线降级 + 审计留痕)。需求证据:2026-10-06 归档两个滞留任务时,takeover 只重绑执行者,陈旧基线需手改 task.json meta 才能走降级归档——该操作目前无运行时命令承载。10.5 增补时立项(10-06-task-adopt-command),设计注意"基线永不覆盖"不变量与显式收养语义的边界。
167
+ - **Skill 证据补课**(10-06-skill-evidence-training):1.8.0 只改了运行时与契约,教 agent 写证据的目前只有门禁报错文案;task-review 等 Skill 正文需补 evidence.jsonl 约定。
168
+ - **维持**:文档英文化待维护者拍板(与 AGENTS.md 文档语言约定冲突);缓行清单不变。**观察项**:1.8.0 的机器可解析 AC 是与 Spec Kit / OpenSpec 互操作的第一块砖(spec 制品 ↔ decision-anchor 映射),符合"SDD 工具之下的状态层"定位,待 agent-teams 排期落定后议。
169
+
170
+ ## 11. 修订记录
171
+
172
+ - 2026-08-27:创建。基于三路只读探查(注入层/状态模型/治理层)+ 大模型趋势分析;用户拍板方案 A;落库任务 08-27-direction-doc。
173
+ - 2026-08-27:阶段 0 完成并更新状态追踪。交付:README 定位改写、`spec/contracts/context-injection.md` 协议契约、指纹序列化三线统一(跨 host 一致性测试锁定)、Python 线/opencode/dsh slim、codex 事件名。任务 08-27-stage0-positioning-protocol(0c7ae64)。
174
+ - 2026-08-28:阶段 1 完成并更新状态追踪。1a:`run state [task] --json` 事实视图(60753fa)。1b:`<workflow-state>` 属性事实头 + `<decision-anchor>` 决策要点注入,三线一致,协议契约同步(5cb25ea)。
175
+ - 2026-08-28:阶段 2 完成并更新状态追踪。executor 归属(start 写入会话 key / 显式 `--executor`)、`LIFECYCLE-EXECUTOR-001` 冲突拦截(幂等重跑同样拦截)、`--takeover` 接管(含已激活任务)、无会话 CI start、`subagent evidence` 证据位、`run state` Executor 行(fd4d6af)。
176
+ - 2026-08-29:阶段 3 完成并更新状态追踪——路线图阶段 0-3 全部落地。`run mcp-state` 无依赖 MCP stdio 只读服务(task_state / task_list)、`spec/contracts/fact-layer-access.md` 接入契约(只读保证 + 薄 adapter 立场)(a88474e)。观察期事项:AGNTCY 等跨 agent 协议收敛后按薄壳接入;MCP 写工具默认拒绝,提案须先重开 fact-layer-access 契约。
177
+ - 2026-08-29:MCP 全局入口补充——npm CLI 新增 `cowork-flow mcp-state` 透传命令(cwd 向上定位项目运行时、stdio inherit、退出码透传),MCP 客户端全局注册一次即可服务所有项目,消除逐项目 `.mcp.json` 配置(9a1fa86)。
178
+ - 2026-08-29:后续方向 P0-P3 执行完毕。**P0**:1.1.0 发布——dev→master 合并、CHANGELOG 1.1.0 段(含 1.0.0 载体错位注明)、tag v1.1.0 与 GitHub Release 创建;publish CI 首跑曝光 node:full 存量问题(dsh-hook 依赖 gitignored 运行时、Windows 无法 exec shebang 脚本、test runner IPC 崩溃)并全部修复——双平台门禁现已全绿。**发布确认:npm registry `cowork-flow@1.1.0` 已上线(dist-tag latest),AC-004 闭合,发版任务归档。** **P1**:`docs/mcp-client-setup.md`(codex/opencode/claude-code/zcode 真实注册格式)+ `test/mcp-client-matrix.test.js` 客户端等效启动验证。**P1'**:publish.yml 链路实测核对并写入 README 发布节。**P2**:flaky 组合复跑 5 轮未复现(记录为单次现象);doctor 会话卫生无残留。**P3**:观察期边界维持——MCP 写工具默认拒绝,AGNTCY 等协议收敛后按薄壳接入。- 2026-10-05:2026-10 修订落库(任务 10-05-direction-1-8-0)。1.7.3 维护完成(10-04-zcode-shim-mjs,d0716a9:ZCode shim 改 .mjs、findWorkflowRoot off-by-one 修复);1.8.0 主线定为**证据化完成**,主线任务 10-05-evidence-completion 立项(planning 就绪待启动);候选 B(agent-teams 就绪)列为 1.9.0 候选主线;文档英文化列为待决项,待维护者拍板。方向文档自本次起恢复版本控制(087ef72 untrack 后首次入库)。
179
+ - 2026-10-06:10.5 增补落库(任务 10-06-direction-post-1-8-0)。1.8.0 主线实现完成(10-05-evidence-completion,baa386d,CI 双平台全绿);立项 10-06-skill-evidence-training(1.8.0 收尾)与 10-06-task-adopt-command(1.9.0 前置);1.8.0 发布判据 = CI + 真实宿主冒烟 + Skill 补课。
package/docs/index.md CHANGED
@@ -11,6 +11,7 @@
11
11
  | 了解项目文件如何组织 | [工作原理与扩展点](architecture.md) | 运行时、`template/`、`presets/`、规范检查和扩展边界 |
12
12
  | 参与项目开发 | [贡献指南](../CONTRIBUTING.md) | 本地环境、代码约定、测试和 PR 检查 |
13
13
  | 了解测试与发布流程 | [开发与发布](release.md) | 测试分层、CI、版本发布和维护者命令 |
14
+ | 了解项目方向与版本规划 | [方向规划](direction.md) | 定位声明、演进路线、已拍板取舍和版本主线 |
14
15
  | 查某个版本改了什么 | [更新日志](../CHANGELOG.md) | 按版本记录的功能变化、修复和迁移说明 |
15
16
 
16
17
  ## 常见阅读路径
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cowork-flow",
3
- "version": "1.7.2",
3
+ "version": "1.8.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.7.2",
3
+ "version": "1.8.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.7.2",
3
+ "version": "1.8.0",
4
4
  "description": "AI 协作工作流引擎:任务生命周期、子代理调度、门禁与状态注入。将 cowork-flow 的完整工作流带入 Codex。",
5
5
  "author": {
6
6
  "name": "fishlikewater",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cowork-flow",
3
- "version": "1.7.2",
3
+ "version": "1.8.0",
4
4
  "description": "AI 协作工作流引擎:任务生命周期、子代理调度、门禁与状态注入。将 cowork-flow 的完整工作流带入 Kimi Code。",
5
5
  "author": {
6
6
  "name": "fishlikewater",
@@ -574,8 +574,14 @@ function parseDecisionAnchor(text) {
574
574
  if (section === "目标") {
575
575
  if (line) goalLines.push(line)
576
576
  } else if (section === "验收标准") {
577
- const match = raw.match(/^\s*-\s*\[[ xX]?\]\s*(AC-[A-Za-z0-9-]+)\s*[::]\s*(.+?)\s*$/)
578
- if (match) result.acceptanceCriteria.push({ id: match[1], text: match[2] })
577
+ const match = raw.match(/^\s*-\s*(?:\[([ xX])\]?\s*)?(AC-[A-Za-z0-9-]+)\s*[::]\s*(.+?)\s*$/)
578
+ if (match) {
579
+ result.acceptanceCriteria.push({
580
+ id: match[2],
581
+ text: match[3],
582
+ checked: match[1] === 'x' || match[1] === 'X',
583
+ })
584
+ }
579
585
  } else if (section === "被拒方案") {
580
586
  const match = raw.match(/^\s*-\s*\*\*(.+?)\*\*/)
581
587
  if (match) result.rejectedOptions.push(match[1].trim())
@@ -836,6 +842,36 @@ function withStageFacts(block, root, taskPath, status, readonly = false) {
836
842
  return parts.length > 0 ? `${parts.join("\n\n")}\n\n${block}` : block
837
843
  }
838
844
 
845
+ // Evidence coverage for the anchor block: one read of the task's
846
+ // evidence.jsonl, tolerating noise exactly like the python line. Returns
847
+ // null when the file is absent so the coverage line degrades silently.
848
+ function readEvidenceCoverage(root, taskPath) {
849
+ let text = ''
850
+ try {
851
+ text = readFileSync(resolve(root, taskPath, 'evidence.jsonl'), 'utf8')
852
+ } catch {
853
+ return null
854
+ }
855
+ const covered = new Set()
856
+ for (const raw of text.split('\n')) {
857
+ const line = raw.trim()
858
+ if (!line) continue
859
+ let record
860
+ try {
861
+ record = JSON.parse(line)
862
+ } catch {
863
+ continue
864
+ }
865
+ if (!record || typeof record !== 'object' || Array.isArray(record)) continue
866
+ const ac = typeof record.ac === 'string' ? record.ac.trim() : ''
867
+ const ref = typeof record.ref === 'string' ? record.ref.trim() : ''
868
+ if (ac && ref && ['test', 'command', 'manual'].includes(record.kind)) {
869
+ covered.add(ac)
870
+ }
871
+ }
872
+ return covered
873
+ }
874
+
839
875
  function decisionAnchorBlock(root, taskPath, status) {
840
876
  if (!taskPath) {
841
877
  return null
@@ -868,6 +904,17 @@ function decisionAnchorBlock(root, taskPath, status) {
868
904
  lines.push(
869
905
  `Acceptance: ${parsed.acceptanceCriteria.slice(0, 8).map((item) => `${item.id} ${item.text.slice(0, 80)}`).join("; ")}`
870
906
  )
907
+ const covered = readEvidenceCoverage(root, taskPath)
908
+ if (covered) {
909
+ const total = parsed.acceptanceCriteria.length
910
+ const missing = parsed.acceptanceCriteria
911
+ .map((item) => item.id)
912
+ .filter((id) => !covered.has(id))
913
+ const detail = missing.length
914
+ ? ` missing=${missing.slice(0, 5).join(',')}`
915
+ : ''
916
+ lines.push(`AC evidence: ${total - missing.length}/${total}${detail}`)
917
+ }
871
918
  }
872
919
  if (parsed.rejectedOptions.length > 0) {
873
920
  lines.push(`Rejected: ${parsed.rejectedOptions.slice(0, 6).join("; ")}`)
@@ -1073,5 +1120,6 @@ export {
1073
1120
  runEditSpecCheck,
1074
1121
  contractFingerprint,
1075
1122
  stableStringify,
1076
- stageContractBlock
1123
+ stageContractBlock,
1124
+ decisionAnchorBlock
1077
1125
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cowork-flow",
3
- "version": "1.7.2",
3
+ "version": "1.8.0",
4
4
  "description": "AI 协作工作流引擎:任务生命周期、子代理调度、门禁与状态注入。将 cowork-flow 的完整工作流带入 Qoder。",
5
5
  "displayName": "Cowork Flow",
6
6
  "author": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cowork-flow",
3
- "version": "1.7.2",
3
+ "version": "1.8.0",
4
4
  "description": "AI 协作工作流引擎:任务生命周期、子代理调度、门禁与状态注入。将 cowork-flow 的完整工作流带入 ZCode。",
5
5
  "author": {
6
6
  "name": "fishlikewater",
@@ -8,7 +8,7 @@
8
8
  "type": "process",
9
9
  "command": "node",
10
10
  "args": [
11
- "${ZCODE_PLUGIN_ROOT}/hooks/inject-context.js"
11
+ "${ZCODE_PLUGIN_ROOT}/hooks/inject-context.mjs"
12
12
  ],
13
13
  "timeoutMs": 5000
14
14
  }
@@ -22,7 +22,7 @@
22
22
  "type": "process",
23
23
  "command": "node",
24
24
  "args": [
25
- "${ZCODE_PLUGIN_ROOT}/hooks/inject-context.js"
25
+ "${ZCODE_PLUGIN_ROOT}/hooks/inject-context.mjs"
26
26
  ],
27
27
  "timeoutMs": 5000
28
28
  }
@@ -37,7 +37,7 @@
37
37
  "type": "process",
38
38
  "command": "node",
39
39
  "args": [
40
- "${ZCODE_PLUGIN_ROOT}/hooks/inject-context.js"
40
+ "${ZCODE_PLUGIN_ROOT}/hooks/inject-context.mjs"
41
41
  ],
42
42
  "timeoutMs": 5000
43
43
  }
@@ -25,7 +25,7 @@
25
25
  */
26
26
 
27
27
  import { existsSync, readFileSync } from "fs";
28
- import { dirname, join } from "path";
28
+ import { dirname, join, resolve } from "path";
29
29
  import { fileURLToPath } from "url";
30
30
  import { spawnSync } from "child_process";
31
31
 
@@ -54,13 +54,18 @@ function readHookInput() {
54
54
 
55
55
  function findWorkflowRoot(startDir) {
56
56
  if (typeof startDir !== "string" || !startDir.trim()) return null;
57
- let current = startDir;
58
- const root = dirname(current);
59
- while (current !== root) {
57
+ let current;
58
+ try {
59
+ current = resolve(startDir);
60
+ } catch {
61
+ return null;
62
+ }
63
+ for (;;) {
60
64
  if (existsSync(join(current, DIR_WORKFLOW))) return current;
61
- current = dirname(current);
65
+ const parent = dirname(current);
66
+ if (parent === current) return null;
67
+ current = parent;
62
68
  }
63
- return existsSync(join(current, DIR_WORKFLOW)) ? current : null;
64
69
  }
65
70
 
66
71
  function findProjectRoot(parsedInput) {
@@ -1 +1 @@
1
- 1.7.2
1
+ 1.8.0
@@ -108,13 +108,22 @@ def _print_text_report(report: dict, *, verbose: bool) -> None:
108
108
  print(_parse_error_line(parse_errors))
109
109
  return
110
110
 
111
+ summary = report.get("summary", {})
112
+ if not parse_errors and summary.get("declarations") == 0:
113
+ # Zero declarations is a fact, not a pass: name it so a missing or
114
+ # silently ignored declaration block cannot look like a clean run.
115
+ print(
116
+ "spec-check: no checks declared "
117
+ f"({report.get('specFiles', 0)} spec files scanned)"
118
+ )
119
+ return
120
+
111
121
  from services.spec_check import normalized_one_line
112
122
 
113
123
  line = normalized_one_line(report)
114
124
  if line:
115
125
  print(line)
116
126
  return
117
- summary = report.get("summary", {})
118
127
  print(
119
128
  "spec-check: {pass} passed, {violation} violations, "
120
129
  "{unchecked} unchecked".format(
@@ -336,6 +336,9 @@ def cmd_complete(args: argparse.Namespace) -> int:
336
336
  task_dir,
337
337
  execution_context=execution_context,
338
338
  allow_unchecked_specs=bool(getattr(args, "allow_unchecked", False)),
339
+ allow_missing_evidence=bool(
340
+ getattr(args, "allow_missing_evidence", False)
341
+ ),
339
342
  )
340
343
  if not result.ok:
341
344
  if result.code == "LIFECYCLE-TRANSITION-001":
@@ -86,6 +86,7 @@ def _add_next_output_options(parser: argparse.ArgumentParser) -> None:
86
86
  )
87
87
  parser.add_argument(
88
88
  "--allow-unchecked",
89
+ "--allow-missing-evidence",
89
90
  dest="allow_unchecked",
90
91
  action="store_true",
91
92
  help="With task completion: allow unchecked spec-check commands "
@@ -382,12 +382,33 @@ def _decision_anchor_block(
382
382
  for item in parsed["acceptanceCriteria"][:8]
383
383
  )
384
384
  lines.append(f"Acceptance: {items}")
385
+ coverage = _acceptance_evidence_coverage(root, task_path)
386
+ if coverage:
387
+ detail = ""
388
+ if coverage["missing"]:
389
+ detail = f" missing={','.join(coverage['missing'][:5])}"
390
+ lines.append(
391
+ f"AC evidence: {coverage['withEvidence']}/{coverage['total']}{detail}"
392
+ )
385
393
  if parsed["rejectedOptions"]:
386
394
  lines.append("Rejected: " + "; ".join(parsed["rejectedOptions"][:6]))
387
395
  lines.append("</decision-anchor>")
388
396
  return "\n".join(lines)
389
397
 
390
398
 
399
+ def _acceptance_evidence_coverage(root: Path, task_path: str | None):
400
+ """AC evidence coverage facts for the anchor block; a supplementary fact
401
+ that degrades to absence on any failure instead of degrading injection."""
402
+ if not task_path:
403
+ return None
404
+ try:
405
+ from services.ac_evidence import coverage_summary
406
+
407
+ return coverage_summary(root / task_path)
408
+ except Exception:
409
+ return None
410
+
411
+
391
412
  def _stage_contract_block(
392
413
  root: Path, task_path: str | None, status: str
393
414
  ) -> str | None:
@@ -33,7 +33,7 @@ ZCODE_DIGEST_POLICY = (
33
33
  "read full spec files only before listed actions."
34
34
  )
35
35
 
36
- # The zcode shim (hooks/inject-context.js) pre-filters Bash
36
+ # The zcode shim (hooks/inject-context.mjs) pre-filters Bash
37
37
  # events with the same rule; this entry keeps the transport
38
38
  # contract correct standalone.
39
39
  LIFECYCLE_BASH_RE = re.compile(r"\brun(?:\.cmd)?\s+(?:task|subagent|resume)\b")
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Acceptance-criteria evidence facts for task completion.
4
+
5
+ Single read entry for the completion gate, the fact view, and injection:
6
+ which AC ids the decision anchor declares, which are checked, and which
7
+ have at least one evidence record in the task's evidence.jsonl. Judgment
8
+ about whether a record really proves an AC stays with review; the machine
9
+ only decides existence facts.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import json
15
+ from pathlib import Path
16
+ from typing import Any
17
+
18
+ from services.fact_view import parse_decision_anchor
19
+
20
+ EVIDENCE_FILE = "evidence.jsonl"
21
+ EVIDENCE_KINDS = ("test", "command", "manual")
22
+
23
+
24
+ def read_decision_anchor(task_dir: Path) -> str | None:
25
+ path = Path(task_dir) / "decision-anchor.md"
26
+ try:
27
+ text = path.read_text(encoding="utf-8")
28
+ except (OSError, UnicodeDecodeError):
29
+ return None
30
+ return text or None
31
+
32
+
33
+ def load_acceptance_criteria(task_dir: Path) -> list[dict[str, Any]]:
34
+ """AC rows (id / text / checked) declared by the task's decision anchor.
35
+
36
+ Empty list when the anchor is missing or declares no AC rows — the
37
+ compatibility path that keeps pre-existing tasks exempt from the gate."""
38
+ text = read_decision_anchor(task_dir)
39
+ if not text:
40
+ return []
41
+ return list(parse_decision_anchor(text)["acceptanceCriteria"])
42
+
43
+
44
+ def read_evidence(task_dir: Path) -> list[dict[str, Any]]:
45
+ """Valid evidence records appended by the implementer.
46
+
47
+ Bad lines and malformed records are skipped, never fatal: the gate
48
+ blocks on missing coverage, not on file noise."""
49
+ path = Path(task_dir) / EVIDENCE_FILE
50
+ try:
51
+ text = path.read_text(encoding="utf-8")
52
+ except (OSError, UnicodeDecodeError):
53
+ return []
54
+ records: list[dict[str, Any]] = []
55
+ for line in text.splitlines():
56
+ line = line.strip()
57
+ if not line:
58
+ continue
59
+ try:
60
+ record = json.loads(line)
61
+ except json.JSONDecodeError:
62
+ continue
63
+ if isinstance(record, dict) and _valid_record(record):
64
+ records.append(record)
65
+ return records
66
+
67
+
68
+ def _valid_record(record: dict[str, Any]) -> bool:
69
+ ac = record.get("ac")
70
+ ref = record.get("ref")
71
+ kind = record.get("kind")
72
+ return (
73
+ isinstance(ac, str)
74
+ and bool(ac.strip())
75
+ and isinstance(ref, str)
76
+ and bool(ref.strip())
77
+ and kind in EVIDENCE_KINDS
78
+ )
79
+
80
+
81
+ def evidence_coverage(
82
+ acceptance: list[dict[str, Any]],
83
+ evidence: list[dict[str, Any]],
84
+ ) -> dict[str, Any]:
85
+ """Coverage facts over declared AC ids: total, covered, missing ids."""
86
+ covered = {
87
+ record["ac"].strip()
88
+ for record in evidence
89
+ }
90
+ ids = [item["id"] for item in acceptance]
91
+ missing = [ac_id for ac_id in ids if ac_id not in covered]
92
+ return {
93
+ "total": len(ids),
94
+ "withEvidence": len(ids) - len(missing),
95
+ "missing": missing,
96
+ }
97
+
98
+
99
+ def coverage_summary(task_dir: Path) -> dict[str, Any] | None:
100
+ """End-to-end coverage facts for one task.
101
+
102
+ None when the task declares no acceptance criteria — consumers treat
103
+ that as "gate not applicable" instead of a fake zero."""
104
+ acceptance = load_acceptance_criteria(task_dir)
105
+ if not acceptance:
106
+ return None
107
+ return evidence_coverage(acceptance, read_evidence(task_dir))