Default 七阶段工作流
Default workflow 的重型轨道(pm、frontend、backend)是 Tenon 的完整治理路径。每个阶段都有允许的回边和出口条件,输入与产出由 Tenon 在运行时记录。lite 轨道只有四步,见轻量轨道。
Tenon 自有的 skill 只有一个 tenon:它读任务冻结的工作流计划,按 tenon status <change> --json 的 step.next 执行当前步骤。每一步加载哪些技能全部写在 templates/workflows/default.yaml 里,按轨道分支,也只写在那里。
每步声明的技能(default,按轨道):
| Step | pm | frontend | backend |
|---|---|---|---|
open | openspec-propose | openspec-propose | openspec-propose |
explore | brainstorming · grilling · domain-modeling | openspec-explore · brainstorming · grilling · domain-modeling | openspec-explore · brainstorming · grilling · domain-modeling · codebase-design |
spec | openspec-propose · brainstorming · writing-plans · grilling · domain-modeling | openspec-propose · writing-plans | openspec-propose · writing-plans |
build | prototype · frontend-design | test-driven-development · frontend-design | test-driven-development |
verify | browser-qa · web-design-guidelines · design-taste-frontend · verification-before-completion | verification-before-completion · e2e-testing · browser-qa · web-design-guidelines · design-taste-frontend | verification-before-completion |
ship | — | finishing-a-development-branch | finishing-a-development-branch |
archive | — | — | — |
离开条件
流程图、每条转换的 guard 和动作都写在工作流 YAML 里并按它执行,代码里没有以工作流名为键的写死规则。离开一个步骤由它的离开条件决定,其中三个是通用的:
| 离开条件 | 写在哪 | 满足时 |
|---|---|---|
tasks-through-step | 步骤与前进边 | tasks.md 里截至本步的任务都已勾选 |
not-afk-queued | build-complete 边 | 任务没有在 AFK 队列里排队 |
dependencies-ended | build-complete 边 | depends_on 里的任务都已结束 |
此外 YAML 里还有各步自己的条件:build-complete 的 pre_verify_review_result=pass、下面的必须产出、规格步至少 3 条任务、verify-pass 的未变构建修订、交付步的交付值(pr_url,pm 是 prd_path)与规格迁移。不带 event 的 tenon check <change> 列出「步骤 guards ∪ 前进边 guards」,一次列出全部未满足的条件,退出码 2。tenon advance 只自动走唯一的前进边,不走回退边(requirements-changed、verify-fail)和放弃边(scope-expanded)。原来的实现方式、隔离方式、直接实现确认和分支状态这几项决定已从新任务里去掉(升级前创建的任务照旧)。Dashboard 工作流页在门禁段下只读列出该步的离开条件,必须产出显示为「必须产出 <path>」。
默认工作流不为任何一步声明输入、输出文档。每一步写出了什么在离开步骤时记录,下一步必须读完直接上游步骤产出的 Markdown 文档(见文档、Skill 与证据链)。templates/workflows/default.yaml 里与文档有关的条件只有下面这些 required-output 守卫({change} 是任务名):
| 步骤 | 必须产出 | 轨道 |
|---|---|---|
open | openspec/changes/{change}/proposal.md | pm、frontend、backend |
explore | docs/superpowers/specs/*.md | pm、frontend、backend |
spec | openspec/changes/{change}/specs/*/spec.md | pm、frontend、backend |
spec | docs/superpowers/plans/*.md | frontend、backend |
verify(verify-pass 边) | docs/superpowers/reports/{change}-verify.md | pm、frontend、backend |
lite 轨道一条都不声明。仓库里在这个任务之前就存在的文件,除非本步改过,否则满足不了必须产出。frontend 轨道不再要求立项前 DESIGN.md 就绪;它的有效性由交付步的 design-system 测试检查。
Open
创建独立 Change,由 openspec-propose 写 proposal;tenon init 只铺 tasks.md 骨架(Todo 的来源)。这里不提前实现代码,也不把未来任务伪装成已确认计划。
Explore
读完 Open 交接来的文档,进行外部研究、现有能力检查、方案比较和技术设计。出口必须写出 docs/superpowers/specs/*.md 的设计文档并取得 review receipt。ADR 由 domain-modeling 按它自己的规则写(难回退、脱离上下文会意外、真实取舍);工作流不规定谁产出 ADR。
Spec
把设计转成 OpenSpec delta requirement/scenario 和可执行计划。计划必须列文件、行为、测试、回滚和上下文切分;前端/后端 full 任务先设计纵向 tracer bullet。
Build
真实实现遵循项目规则和测试先行。每完成一个任务立即运行窄测试,再扩大验证范围。Build 完成后冻结 build:v1:<git|workspace>:<revision_hash>:<repository_hash>:<worktree_hash> token;它同时绑定 revision、 物理 repository 和 worktree。旧裸 Git SHA/裸 workspace baseline 不会自动回填,必须回 Build 重新捕获。 缺失、非法、陈旧或无法证明来源时,Verify、readiness、HTTP/SSE 与 AFK 统一返回 verify-build-revision-untrusted,修复标识为 return-to-build-and-capture-current-revision。不要手动 set build_sha。
token 绑定什么取决于任务的 isolation:branch 绑 Git 对象;worktree 与 in-place 绑工作区内容指纹(不含 .git、依赖、OpenSpec 与文档证据、 pipeline 控制文件和测试缓存),因为任务 worktree 里的实现在 Build 出口时通常还没提交,Build 也从不要求提交。身份仍绑定物理 repository 与物理 worktree, 所以在别的检出里验证一个 worktree 任务会得到 worktree-mismatch;token 类别与任务的 isolation 不符(例如 worktree 任务带着 Git 对象类 token, 来自升级前手动把 isolation 设成 worktree 的旧任务)得到 isolation-mismatch。两者都是回 Build 重新捕获。CLI、Dashboard 流转和 Dashboard 快照 都从同一个函数(expectedBuildRevisionKind)取类别,不会各说各话。详见仓库里的 docs/CONTRACT.md。
pre_verify_review_result=pass 是 Build 的通过结论,tenon set 写入前核对本步声明的就绪证据。 每条重型轨道的 Build 都声明了可核对的证据:frontend、backend 要求项目测试目录里的单测类套件通过(Build 的 test: changed 档位),pm 要求必需评审者 spec-consistency(block_at: medium,逐条比对实现与 proposal / design / delta spec / tasks,语言无关)。证据不齐时写 pass 被拒,并点名缺的测试或评审者。
每条重型轨道都挂了智能体:调研步骤的执行者是 researcher;实现步骤的执行者是 builder (按独立任务各起一个子代理,合成一份报告),先于该步的评审者运行;验证步骤声明 code-size 测试 (tenon test code-size --json)并挂必需评审者 code-size 读取它的结果。这条测试默认没有上限:它记录 files_changed、lines_added、lines_deleted、largest_added_lines,退出码 0 即通过,不挡出口,也不需要豁免; 规模是否合理由评审者按实测值和改动性质判断。想要上限的项目在自己的工作流里给该测试写 pass.metrics,例如 pass: { metrics: [{ name: lines_added, max: 3000 }] },超限的运行以 metric-threshold 失败。启用上限之后, 用户决定放行时才用 tenon test waive <change> --test code-size --reason … 登记;评审确认这道门时一并批准, 批准前不放行。执行者没有 done 之前不能离开步骤。
评审者按风险挂载。security 声明了 attach_on: [auth, dependency, contract](见智能体),所以在验证要求它的轨道上, 只有任务的改动碰到鉴权、依赖清单或接口契约路径时才挂上;一个都没碰到的任务不会等它、也不会派发它。同一个任务也可以先走更轻的 lite 轨道,改动长大了再升级到重型轨道。
步骤自己的必需测试(tests)命令是 npm 脚本而项目里没有这个脚本时,这条测试是「未配置」,不是失败:tenon test run 拒跑、不落记录,并给出配置方式;step.next 在本步或上一步(配置是一次工作区改动,要赶在 Build 冻结候选版本之前)入口 就以 fix(test-unconfigured)提出。默认工作流里仍是必需的步骤测试只有 code-size(pm、frontend、backend 的验证)、轻量轨道实现步的 diff-risk 探针和设计体系校验(frontend 的交付);项目自己的测试来自测试目录,所以一个普通项目不会为了满足工作流去补 typecheck 或 test:integration 脚本。
步骤用一个键 test: 声明自己对测试体系的要求,取值是一个档位:none(不要求,与不写相同)、plan(本任务有测试计划, 且项目目录里有每个必需类的套件)、changed(跑本任务改动碰到的套件,只跑改动范围)、full(必需类的套件全量跑,被改动碰到的其他类也全量跑)。 项目的 .tenon/tests/catalog.yaml 说明测试怎么跑、每个档位是什么含义,任务的 test-plan.yaml 只记你手工做的选择(加跑的套件、用例映射、豁免), 工作流只写档位名。套件被「碰到」,是指本任务改动的某个文件匹配该套件的 files 或 covers glob;没写 covers 的套件只有它自己的测试文件改了才算被碰到, 改动只含源码文件时给出不阻断的提示 suites-without-covers(见 CLI 参考)。较早的多字段 test_policy 是旧写法,冻结了它的任务照旧可读(同一步不能同时写两个键)。步骤自己的 tests 与档位并存可用。
档位的含义写在目录里,不写在工作流里。.tenon/tests/catalog.yaml 的可选顶层键 levels 覆盖 plan、changed、full 三档的内建缺省; 每档只接受六个键:required(目录里必须有套件的测试类)、touched(只在被改动碰到时才跑的类)、scenarios(off | required | passing)、 coverage(lines、branches、functions、statements、changed_lines,各 0–100)、flaky({ max, fail_on_new })、require_baseline; plan 档只接受 required 与 scenarios。没写的键取内建缺省:
levels:
plan: { required: [unit] }
changed: { required: [unit], touched: [static] }
full: { required: [unit], touched: [integration, e2e, static, benchmark] }内建缺省是 scenarios: off、不设覆盖率与不稳定用例门槛、require_baseline: false,普通任务不会被要求超出改动所需的测试。需要更多的项目自己写回: levels.full.coverage: { lines: 80 } 让每个 test: full 的步骤对声明了覆盖率报告的套件检查行覆盖率。未知的档、键或类,或超出 0–100 的覆盖率, 都会让目录无效(tenon test catalog validate 给出行号),测试相关的出口一律阻断直到修好。levels 计入目录摘要,目录仍是受保护的测试配置: 任务里改它要经评审确认。
测试类把套件种类归成五类(外加只在点名时运行的 custom):unit(unit、coverage)、integration(integration、regression、contract、smoke)、 e2e(e2e、playwright、browser、a11y、visual)、static(typecheck、lint、code-size、design-system)和 benchmark。 套件的 kind 可以写类名,也可以写旧种类名(别名仍有效);档位按类匹配,冻结的旧策略仍按种类名逐字匹配,要求不会因此变宽。
改动过的测试文件由套件认领,不逐个登记(冻结的旧 files: registered 也按同样的方式判定,不再要求逐个登记)。本任务新增或修改的测试文件, 只要被某个套件的 files glob 匹配,就归入该套件,并随档位要跑的套件一起运行。认领在求值时现算,不写进 test-plan.yaml,所以新增测试文件 不会让已有运行过期。套件跑完后,认领的文件必须在报告里至少有一个执行过的用例,否则出口以 registered-test-not-executed 阻断。 像测试却没有任何套件认领的文件是孤儿(test-file-orphan),阻断到执行 tenon test register <change> --auto,它把该文件的精确路径追加进合适套件。 openspec/、.tenon/、.pipeline/ 下的路径从来不算测试改动,测试文件名规则只对源码扩展名(js、ts、py、go、rs、java 等)成立, 所以 fixtures/data.spec.json 或归档里的 .spec.report 都不是测试。
测试完整性报告把任务和它的起点对比,指出全量运行的用例数下降与跳过数上升、 被删或被跳过的测试、被删的断言、被改写的快照、被改的基线、新增的已知失败和降低的覆盖率门槛(十种信号见 CLI 参考的「测试完整性」,命令是 tenon test integrity <change>)。档位没有完整性设置,所以默认工作流的 每个实现与验证步骤都按 notice 运行:信号在 status、test status 和 Dashboard 测试页签里汇成一条 test-integrity 提示,从不阻塞。 只有冻结了 integrity: block 的旧策略仍会让这些信号成为该步骤的阻塞(读不出 diff 时也阻塞,files-diff-unavailable);没有逐条豁免, 有意删除就还原测试。
各轨道默认档位(在 Dashboard 工作流页按步骤选档;每档的含义来自目录):
| 轨道 | spec | build | verify |
|---|---|---|---|
| lite | (没有规格步) | changed | full |
| pm | none(不要求映射场景) | 无 | changed |
| frontend | plan | changed | full |
| backend | plan | changed | full |
按内建档位定义,这意味着:只有单测类套件是必需类;changed 的步骤只跑本任务碰到的套件(typecheck 这类静态检查也只在碰到时才跑), full 的步骤全量跑每个单测类套件,再加被碰到的集成、端到端、静态与基准类套件。changed 下改动没碰到任何套件时零运行, tenon test run <change> --stage 如实说明并以 0 退出。默认工作流不再写覆盖率门槛:想要的项目在目录里写 levels.full.coverage, 它只对目录里声明了 coverage 的套件生效。
OpenSpec 场景映射默认关闭,只在档位要求时才有(scenarios: required | passing)。关闭时没有任何场景会挡出口,step.next 不为它们下发 test-plan-map,验证报告的追溯矩阵只列已映射的行,另加一行「另有 N 条场景 / 任务未映射(本步不要求映射)」。
某个测试类对整个项目都不适用(纯 JavaScript 项目没有类型检查)时,在目录里声明一次,不必每个任务各豁免一次: tenon test catalog not-applicable typecheck --reason '<原因>' 会在 .tenon/tests/catalog.yaml 写入一条 not_applicable, 它同时满足该种类所属的测试类。它要经一次人工确认才生效:tenon review request 把它和计划里的豁免一起列出(键 not-applicable:<kind>), 用户的确认(tenon review acknowledge,不含 --delegated)批准它,approved_by 记录批准人;在此之前档位仍要求这个类, 报 waiver-unapproved。只有单个任务不适用时仍用 tenon test waive。
项目还没有测试目录时,tenon init 会自己跑 tenon test discover --write 并在 stderr 说明; 请审阅生成的 .tenon/tests/catalog.yaml 后提交。discover 认领 src/、test/、tests/、__tests__/ 和项目根目录下的单测文件 (*.test.*、*.spec.*)。tenon test register <change> --auto 一条命令做完剩下的:缺目录先识别,再把本任务改动里没有套件认领的测试文件 的精确路径追加进合适的套件,不放宽任何 glob,也不碰本任务没改动的文件。所有写目录的命令(catalog add|set|rm|not-applicable、register --auto) 只改实际变化的行,保留注释、空行和排版;唯一丢失的是被改键上的行内注释。新建的目录整份写出。
阻塞带稳定的码(test-catalog-missing、test-plan-missing、test-kind-missing、test-file-orphan、test-not-run、 test-failed、test-stale、no-tests-ran、coverage-below、scenario-uncovered 等)、短标签、完整原因和修复命令。 (test-file-unregistered 不再产生,只留在词表里供旧报告显示。)
tenon status 的 step.next 按固定顺序推进测试体系:test-discover(无目录)→ test-plan-seed → test-plan-map (只在缺必需测试类,或档位要求场景映射时)→ test-register-files(只在出现孤儿测试文件时,一条 tenon test register <change> --auto 全部认领)→ run-tests(tenon test run <change> --stage) → test-report(把追溯矩阵写进验证报告)。它们排在本步文档之后、评审者之前;已经运行过却不满足策略的阻塞变成 fix,评审门上有回退边则走回退边(轮次内直接 tenon transition)。豁免与目录里的 not_applicable 要人工批准:tenon review request 列出待批准的项,用户的确认 (tenon review acknowledge,不含 --delegated)恰好批准这些。测试计划、基线、已知失败清单和运行记录只经 tenon test … 写入,写门拒绝直接编辑和 shell 重定向,tenon 命令与普通 git 操作放行。声明 reads_tests 的 评审者,提示词还附带最新的失败用例、flaky 用例、覆盖率对照门槛与基准变化。
Verify
独立检查测试、类型、构建、浏览器、安装和安全边界。Verify 不修改实现;失败走 verify-fail 返回 Build,返工后重新冻结基线。
Ship
tenon spec apply <change> 把已验证的 delta spec 应用进主规格并写下 applied spec 回执, 再准备真实交付。若 Change 带主规格迁移 receipt, 必须先生成身份和摘要绑定的机器应用结果;tenon check 与 ship-complete 的运行时 typed guard 都会复核它。代码合并、push、Pages 上线等外部动作必须以实际成功为准。
Archive
读完 Ship 交接来的文档,确认任务清零后归档。Archive 之后自动更新不得改写历史字节。
归档由 tenon task finish <change> 完成:step.next 的 finish-change 给出这条命令,Tenon 把任务目录移进 openspec/changes/archive/(OpenSpec 轨道由它调用 openspec archive <change> --skip-specs --yes --json)并记下任务结果「完结」,提交照 step.next 的 commit 做。中途放弃用 tenon task terminate <change>,结果记为「已终止」,只提交任务目录。
两条回边
requirements-changed:Build → Spec,用于需求或设计语义改变;verify-fail:Verify → Build,用于实现或验收失败(验证轮次用完后这条边被拒,见验证轮次上限)。
Review 边界
Explore、Spec、Verify 的 review 必须绑定 exact phase 和 exact event。同一份确认不能同时授权两个不同的 event(例如已批准的 verify-pass 不能拿去放行别的 event)。持续授权只改变确认记录方式,不改变 guard。
受轮次上限约束的步骤(评审门且有回退边)上,轮次还没用完时走回退边不需要评审回执,直接 tenon transition(例如 tenon transition <change> verify-fail);前进边、剩余阻断与测试豁免仍只认人工确认。评审待确认时用户要求回去修改,同样直接 tenon transition <change> verify-fail,转换会清掉已过期的评审标记。
Verify 的轮次有上限,见下面的验证轮次上限。Build 中的 TDD、单元测试、类型检查、lint 和窄集成测试属于实现反馈,不算一轮。
如果 Verify 报告构建修订不可信,沿 verify-fail → build 回边重新实现并运行 build-complete;新的 canonical transition record 会提供 token 的来源证明。worktree 任务要在它自己的 worktree 里验证,换一个检出会得到 worktree-mismatch。
回边不要求本步智能体的技能。这些技能(例如归 builder 的 test-driven-development)只能在智能体运行期间加载,要求它们等于逼你为了退回去先开始一次 builder:所以 Build 上的 requirements-changed 在本次访问里还没有任何 builder 运行时也能走。主线自己加载的技能在回边上仍然要求,前进边则要求全部。 tenon status、tenon check 与 Dashboard 流转对此一致,见智能体。
验证轮次上限
Verify 最多验证 2 轮。templates/workflows/default.yaml 里每条轨道的验证步都声明了 max_rounds: 2。一轮 = 进入验证步一次:第一次进入是第 1 轮,每次 verify-fail 回到 Build、再经 build-complete 进入验证,就多一轮。
轮次还没用完时回退边很轻:必需测试或必需评审者不通过,step.next 直接给 transition verify-fail,tenon transition <change> verify-fail 不需要评审请求与用户确认就能成功(agent 用一句话告诉用户为什么回退)。前进边、剩余阻断与测试豁免仍只认人工确认。
第 2 轮仍有必需测试或必需评审者不通过时,Tenon 不再自动回退。step.next 不再给 verify-fail 这条边:它要么 stop(rounds-exhausted),要么请用户接受剩余阻断;tenon transition <change> verify-fail 也会被拒。之后由用户决定:
- 用人工确认接受剩余的评审者阻断(失败的必需测试要改走步骤测试豁免);
- 用
tenon set <change> max_rounds <N>调高上限,再回到 Build; - 经
requirements-changed回到规格。这条边声明在 Build 上而不是 Verify 上,所以从 Verify 出发要分两步,第一步同样要用户先决定:用tenon set <change> max_rounds <N>调高上限,经verify-fail回到 Build,再在 Build 上执行tenon transition <change> requirements-changed。任务落到规格之后,计数从第 1 轮重新开始,用户可以再把max_rounds调回去; - 终止任务。
agent 不会自行调高上限,也不会绕过它。命令、step.rounds 和剩余阻断的接受见 CLI 参考。
上限是工作流步骤的 max_rounds 键,写在 gate 旁边:
- id: verify
gate: review
max_rounds: 2这个键只能声明在设了 gate: review、且至少有一条回退边(回到更早步骤的边,如 verify-fail)的步骤上。声明在别处会让工作流解析失败,错误点名步骤和原因(Build 没有评审门,不受上限约束,它的 requirements-changed 始终可用)。取值是 1 到 20 的整数,0、21、two、1.5 同样被拒。受限步骤没有声明这个键时(自定义工作流的步骤,或这个键出现之前就已冻结的任务),按 2 处理。上限随工作流计划冻结,之后再改工作流文件不影响在途任务;tenon set <change> max_rounds <N> 只覆盖单个任务。Dashboard 工作流页读写定义时会保留这个键,但没有编辑控件。
任务起点
“自任务开始以来改动了哪些文件”在 Tenon 里只有一个答案:测试文件的登记与同步、改动文件清单、受保护测试配置的批准、测试完整性报告、diff-risk 探针、按 attach_on 挂载的评审者、改动行覆盖率、证据导出、流转门(CLI 与 HTTP)、Dashboard 快照和 tenon verify --ci,比对的都是同一个起点提交。Tenon 在建任务时记下它, 并让它跟随上游。
tenon init把检出时的HEAD写进任务字段start_sha(AFK 分诊同样写)。仓库还没有提交、或不是 Git 仓库时不写。值存在任务状态旁的一份小记录里 (.pipeline-run/start-sha/),不写进.pipeline.yaml,所以这个字段出现之前发布的版本仍读得了、也续写得了这个任务。起点取下面这些候选里最新的一个:
start_sha,当它是HEAD的祖先、且提交者时间不晚于任务created_at加 60 秒时(真实的值是建任务时的HEAD,提交时间不可能更晚; 校验不过、读不到提交时间、或created_at不可用,这个候选就被丢弃);HEAD与base_branch上游(<base_branch>@{upstream})的 merge-base,当这个上游能解析时;HEAD与base_branch的 merge-base,当它不等于HEAD本身时(任务分支或 worktree 分支有自己的提交,它就是分叉点)。
候选两两比较:一方是另一方的祖先就取后代;不成链就取二者的 merge-base。所以清单可能多算,但不会少算。一个候选都没有时,仍按原来的规则:取
created_at之前 的最后一个提交。不读提交信息,也不读作者。起点只能前移到 Tenon 记下的
HEAD,或本地上游引用可达的提交,改写提交信息藏不住任何改动。start_sha、base_branch、created_at决定起点,所以只有tenon init能写它们;tenon set、set-many、cas拒写(exit1,状态不变)。base_branch与created_at以前可以被改写。.pipeline-run/start-sha/下的那份小记录不在状态摘要的保护范围里,所以才有上面的时间校验: 它被改写成更晚的提交或被删除,起点只会前移、清单只会多出文件,不会少。你会看到的变化:别人合入基线分支、你又拉取进来的提交,不再被算成本任务的改动(不再冒出一堆继承来的测试文件和被改过的目录)。
限制。上游是本地引用,没有
git fetch时可能偏旧,那只会多算文件。原地任务在基线分支上提交了自己的改动并推送到它的上游之后,起点会前移过这些提交,它们不再算本任务的 改动。worktree 任务不受影响:它的base_branch是出发的分支,任务分支的提交在合并前不在那个分支的上游里。兼容。没有
start_sha又没有上游的任务行为不变。这个字段出现之前发布的运行时读得了带start_sha的任务;如果那个旧运行时之后又往任务里写了内容(例如回滚之后), 新版本再读到的start_sha是空值,起点退回上游和基线分支候选,与「字段出现之前创建的任务」完全一样(见仓库里的docs/CONTRACT.md)。
新任务建在自己的 worktree 里,也正是这点避免了两个任务共用一个起点,见任务在哪里。
阶段产物总览
| Phase | 典型产物(技能写出,Tenon 记录) | 主要出口条件 |
|---|---|---|
| open | proposal | 身份与范围明确 |
| explore | 技术设计、研究 | 取舍完成并 review |
| spec | delta spec、实施计划 | 需求可验证并 review |
| build | 实现、测试、冻结基线 | 当前任务完成 |
| verify | 独立验证报告 | exact-event review |
| ship | applied spec、交付准备 | 已验证规格已应用 |
| archive | 不可变历史 | 全部证据可读取 |
Open 创建状态和 tasks.md 骨架,不铺 proposal、design 骨架:这些文件由对应技能自己写出。
Explore 的研究结论必须回到产品事实、仓库实现或一手资料。方案比较要写清选择、拒绝项、风险和验证方法。
Spec 使用稳定英文 OpenSpec 操作词,但 requirement、scenario 和说明正文默认中文。设计语义变化必须回到 Spec 修订并重新 review。
Build 可以修改实现和测试,但不能在 Verify 中边验边改。Build 改动了交接来的文档,改动记为 Build 的产出;需求语义变了走 requirements-changed。
Verify 至少回答:运行了什么、结果是什么、基线是哪一版、失败如何复现。前端交付必须使用真实浏览器。
Ship 不重新解释需求,也不引入新实现。应用 delta 时发现主 spec 冲突,应返回受控修订路径。
Archive 保留最终状态与证据链。插件自动更新不能批量翻译或格式化历史归档。
Review 顺序
check → review request --event <event> → 用户确认 → transition这是前进边的流程。受轮次上限约束的步骤上,轮次内的回退边不走它:直接 tenon transition <change> <回退事件>。
确认只来自用户:回复放行语(“确认继续”“继续执行”等),由 hook 写入回执;在 Dashboard 确认(tenon dashboard --open); 或在没有 UserPromptSubmit hook 的宿主上,由用户本人在自己的终端运行 tenon review acknowledge <change>。agent 不得代为执行 这种手动形式,宿主 gate 会拒绝。
持续授权允许 CLI 写入 delegated acknowledgement(--delegated),但前提仍是产物、Skill 证据、文档读取和 guard 全部真实通过, 且它不批准待批准的测试豁免。
回边不是删除失败记录。旧报告、receipt 和 transition history 应保留,新一轮基线和结果追加到同一个 Change 的历史。
Todo 与阶段所有权
tasks.md 是唯一 Todo 真相源。一级标题对应七个 phase,未来阶段任务可以展示,但不会反向阻塞当前出口。每个 phase 只能勾选自己的任务。
查看当前事实
tenon status <change> --json
tenon document status <change>
tenon check <change>三条命令分别回答状态、本步的输入产出与必须产出、出口 guard;任何一条通过都不等于自动推进。