Repository navigation
v0.2.5
TeamFlow v0.2.5
Release — 「编排可测 + 交付质量可客观判定」版本(19 个新提交 / 49 个文件 / +2484 −719 行,v0.2.4..HEAD 全量对比):① 把 1646 行的 executePipeline 拆成阶段模块、host/core 按职责分目录,并让真实编排行为可以被测试驱动;② 补「接口契约」三层与「跑得起来」要求;③ 新增三条 host 侧客观检查(能否打开 / 接口对不对 / 有没有真动)—— 它们不依赖模型能力,因此对本插件面向的任意模型(含本地小模型)都成立。无破坏性,宿主窗口不变。
Release — "testable orchestration + objectively verifiable delivery" (19 new commits / 49 files / +2484 −719, full v0.2.4..HEAD diff): ① splits the 1646-line executePipeline into stage modules, groups host/core by responsibility, and makes real orchestration behavior testable; ② adds the three-layer interface contract and the "must actually run" requirement; ③ adds three host-side objective checks (does it open / are interfaces consistent / does it actually move) — all independent of model capability, so they hold for any model this plugin is used with, including small local ones. No breaking changes; the host window is unchanged.
中文
升级要点(TL;DR)
- 无破坏性:宿主窗口不变(
>=0.1.7-alpha.1 <0.2.0);journal 格式、任务夹契约、teamflow_*工具参数全部向后兼容。 - 升完重启
dsh --profile web生效(本版改动包含 host 运行时代码)。 - 新增的 host 侧检查全部只记录、不阻断(warn/info 进 journal 日志),处于观察期,用来积累真实样本。
- 交付侧多了一处成本:架构蓝图(
api签名)现在也会注入 QA / 验收的上下文,单次约 +1.4k tokens。
为什么做这三件事
都来自实测事故,不是设计偏好:
- 编排不可测:
executePipeline单函数 1646 行,dev/qa/acceptance 揉在一起,改一处无法验证「行为没变」。 - 交付质量只能靠模型自觉:贪吃蛇那次产出的
main.js调用了GameEngine上不存在的move()/render(),首帧 TypeError 直接死机,而「文件存在 / 有 export」级别的检查全绿;另一次是把循环判定写反(lastTime - timestamp),不抛异常、控制台干净、画面全黑,QA 三轮全放行。 - 约束能不能生效取决于模型:同一个「请在报告里给出命令 + 退出码」的要求,强模型自发就做到了,弱模型三次全部做不到 ⇒ 靠模型自律的门禁只对一部分用户有效。
本次变更
① 编排可测:拆分与分目录
executePipeline1646 → 1159 行:dev / qa / acceptance 三块整体外移到host/core/stages/,函数体按行切片搬运、一字未改;共享状态装进PipelineCtx(三兄弟用import type反向引类型 ⇒ 运行时无回边、无循环依赖)。host/core按职责分组:stages/agent/workspace/domain/,pipeline/triage/locale/report留根。- 新增
test/orchestration.test.js:stub 子代理驱动真实executePipeline,覆盖档位护栏 / 波次调度 / 澄清闸门 / 提测门禁 / 取消 / 终态归一等 17 个场景。
② 交付质量:契约三层 + 跑得起来
- 接口契约三层:蓝图必须给
api精确签名 → dev 必须逐字调用、禁止自创成员名 → QA 必须做静态接口一致性核对(不匹配 = P1)。 - 冒烟运行(0a2):交付必须被证明「主路径产生可观测状态变化」,存在性不是证据;无浏览器时用 DOM/Canvas 桩但必须保留真实定时器语义。
- 运行证据探针(warn-only):交付含可执行入口时,QA 报告应给出「命令 + 结果」类证据。
- 措辞修正:明确「不得强行执行不该执行的交付」—— 需编译 / 依赖服务 / 纯库的形态走等价检查。
③ host 侧三条客观检查(与模型能力无关)
| 检查 | 判什么 | 实证 |
|---|---|---|
| 加载检查 | 真浏览器加载,抓未捕获异常(抓「打开就崩」) | 子代理起不了浏览器(受限令牌下 Chromium crashpad 恒失败),这件事只能 host 做 |
| 接口核对 | 解析产物真实导出 vs 跨模块调用点 | 同一把尺子:事故 run 命中 5 处(engine.move 等),健康 run 0 处 |
| 冒烟 | 有 canvas 却从未绘制 = 空转 | 事故 run(黑屏)→ no-motion;三个健康交付 → ok,4/4 判定正确 |
④ 一次公开撤回:浏览器配方
曾基于「本机 headless Chrome 可用」给 QA 写了启动配方(含 --no-sandbox)。用户在 dsh 宿主实测证明:加不加 --no-sandbox 输出逐字节相同,都失败 —— --no-sandbox 关的是 Chromium 自己的沙箱,管不到宿主沙箱对 crashpad 跨进程 OpenProcess 的拒绝。配方已删除,改为写明「agent 沙箱内不可用 + 具体证据 + 不要再试参数组合」。
⑤ 修两个真 bug
- 两项 host 检查静默空转:误用
root(=options.productRoot) 当文件系统路径,而它工作区模式下恒为 null(连查 5 个 run)⇒ 检查从未执行且 journal 零痕迹。改为经fsRootOf()解析,并让每种结局都留痕。 - 蓝图收不到:
stateSliceFor的角色白名单只有arch/tech/dev,而qaPrompt与acceptancePrompt都写着「若注入蓝图则依它核对」⇒ 那两句恒为假。已补qa/acceptance。
验证
- 30 个测试套件全绿,断言 2177 条(v0.2.4 为 2013)。
tsc0 错;oxlint --deny-warnings0 警告 0 错误(73 文件)。- CI(ubuntu-latest)全绿;host 冒烟在 Linux 上真跑了(不是环境跳过)。
- 编排测试对拆分前 / 拆分后两份代码各跑一遍均全绿 ⇒ 锁的是行为而非结构。
已知边界
- 三条 host 检查纯记录、不参与判定;判据(尤其冒烟的「有 canvas 却零绘制」)需更多真实样本校准后才考虑升级。
- 冒烟依赖本机 Chromium 可启动;起不来即
skip,绝不因此影响流水线。 - 接口核对是内部一致性(调用点 vs 产物导出);「蓝图声明 vs 产物实现」的契约一致性尚未覆盖。
- 缺陷闭环目前只有「整轮干净」的批量核销,没有逐条核销(已知问题,留待后续)。
English
Upgrade notes (TL;DR)
- No breaking changes: host window unchanged (
>=0.1.7-alpha.1 <0.2.0); journal format, task-folder contract andteamflow_*tool arguments all remain backward compatible. - Restart
dsh --profile webafter upgrading (this release touches host runtime code). - Every new host-side check is record-only and non-blocking (warn/info in the journal log) — it is in an observation period to collect real samples.
- One added cost: the architecture blueprint (
apisignatures) is now also injected into QA / acceptance context, roughly +1.4k tokens per call.
Why these three themes
All trace back to observed failures, not preferences:
- Orchestration was untestable: a single 1646-line
executePipelinemixing dev/qa/acceptance — there was no way to prove a change preserved behavior. - Delivery quality depended on the model's discipline: a snake game shipped a
main.jscallingmove()/render()that did not exist onGameEngine— instant TypeError, while "file exists / has exports" checks were all green. Another run inverted a loop comparison (lastTime - timestamp): no exception, clean console, black screen, and QA signed off across three rounds. - Whether a constraint works depends on the model: the same "record the command and its exit code" requirement was met spontaneously by a strong model and missed three times out of three by a weak one ⇒ model-discipline gates only work for part of the audience.
Changes
① Testable orchestration
executePipeline1646 → 1159 lines: dev/qa/acceptance moved tohost/core/stages/, bodies transferred line-by-line, unmodified; shared state collected intoPipelineCtx(the three modules import the type back withimport type, so there is no runtime back-edge).host/coregrouped by responsibility:stages/agent/workspace/domain/.- New
test/orchestration.test.js: stub subagents driving the realexecutePipelineacross 17 scenarios.
② Delivery quality: contract + "it must run"
- Three-layer interface contract: the blueprint must declare exact
apisignatures → dev must call them verbatim → QA must statically verify cross-module consistency (mismatch = P1). - Smoke run (0a2): the delivery must be shown to produce observable state change; existence is not evidence. Without a browser, use DOM/Canvas stubs but keep real timer semantics.
- Verification-evidence probe (warn-only) and a wording fix: never force-launch something not meant to be launched.
③ Three host-side objective checks (model-independent)
| Check | What it decides | Evidence |
|---|---|---|
| Load check | real-browser load, captures uncaught errors ("dead on arrival") | subagents cannot launch a browser at all (restricted token ⇒ Chromium crashpad always fails) |
| Interface check | real exports vs cross-module call sites | same ruler: 5 hits on the incident run (engine.move etc.), 0 on healthy ones |
| Smoke | canvas present but never drawn = inert | incident run (black screen) → no-motion; three healthy deliveries → ok — 4/4 correct |
④ One public retraction: the browser recipe
A launch recipe (including --no-sandbox) was added for QA based on "headless Chrome works here". The user's probe inside the dsh host proved identical byte-for-byte failure with and without --no-sandbox — that flag disables Chromium's own sandbox, not the host sandbox that denies crashpad's cross-process OpenProcess. The recipe was removed and replaced with "not launchable inside the agent sandbox + concrete evidence + do not retry flag combinations".
⑤ Two real bugs fixed
- Two host checks were silently no-ops:
root(=options.productRoot) was used as a filesystem path, but it is always null in workspace mode (checked across 5 runs) ⇒ the checks never ran and left no trace. Now resolved viafsRootOf(), and every outcome is logged. - The blueprint never reached QA/acceptance:
stateSliceFor's role whitelist had onlyarch/tech/dev, while bothqaPromptandacceptancePromptsaid "if the injected blueprint JSON is present…" ⇒ those two clauses were always false.qa/acceptanceadded.
Verification
- 30 test suites green, 2177 assertions (v0.2.4 had 2013).
tscclean;oxlint --deny-warnings0 warnings / 0 errors across 73 files.- CI (ubuntu-latest) green; the host smoke check actually ran on Linux (not skipped for environment).
- The orchestration suite passes against both pre-split and post-split code ⇒ it locks behavior, not structure.
Known limits
- All three host checks are record-only and non-blocking; thresholds (especially "canvas present but zero draws") need more real samples before being promoted.
- Smoke depends on a launchable local Chromium; otherwise it reports
skipand never affects the pipeline. - The interface check covers internal consistency (call sites vs exports); contract consistency (blueprint declaration vs implementation) is not covered yet.
- Defect closure is still all-or-nothing per clean round; per-defect closure is a known gap.