tenon · git:20261014.16afa51 · 2026-10-14 · sha256 575c1fbf311e02ab
tenon git:20261014.16afa51A
Immutable. This exact content never changes and is served at /api/v1/blob/575c1fbf311e02ab.
---
name: tenon
description: "按任务冻结的工作流逐步执行:路由、恢复、每步照 `tenon status --json` 的下一步做。"
---
# /tenon — 工作流执行入口
每一步做什么全部来自工作流数据。本文件只讲怎么读那份数据、怎么照做;default 与自定义工作流
一视同仁,这里不出现任何轨道名或阶段名。
## 何时用
- `<tenon-dispatch> action: invoke-skill skill: tenon` 或用户输入 `/tenon` 时进入。
- 纯聊天不创建任务,直接回答。
- 面向用户的回复(进度、暂停说明、最终总结)使用用户所用的语言:用户用中文下指令就用中文回复;
命令、字段名、动作名与路径保持原样。
## Codex 的技能读取(硬规则)
- 插件技能一律按 `tenon:<id>` 加载,让宿主读插件 cache 里的 `skills/<id>/SKILL.md`;
同名的 `~/.agents`、`~/.claude`、项目副本一概不读(已激活任务时 hook 会拒绝)。
技能不可用就跑 `tenon setup --codex` 或 `tenon update --codex`,不用同名外部技能代替。
- 每个技能一次完整读取,并把内部结果整体转发给宿主,使 `exit_code` 可审计:
```javascript
const result = await tools.exec_command({ cmd: "<读取 SKILL.md 的命令>", workdir: "<绝对项目目录>" });
text(result);
```
等价的 `text(await tools.exec_command({ ... }));` 也接受。不要用 `text(result.output)`:
它丢内部退出码。`max_output_tokens` 要装得下全文;被截断的读取不算证据,之后
`tenon document record` 会报 `lacks exact host confirmation`。
## 模式(取 `step.mode`)
| 模式 | 来源 | 规则 |
| --- | --- | --- |
| `interactive` | 缺省 | 有 `allowed` 的字段用 AskUserQuestion 问(Codex:一个纯文本问题后结束回合)。每次 `transition` / `complete` 前展示产出、测试结果与评审发现,等用户说「继续」(受轮次约束的评审步上轮次内的回退除外:直接 `transition`,用一句话告诉用户为什么回退)。评审门:`request-review` 后展示并结束回合;能确认这道门的回复是「确认继续」「继续执行」「同意继续」,以及简短同意「继续」「可以」「同意」「好的」「按推荐」「按你的推荐」(拒绝或带条件的回复不算)。 |
| `continuous` | 本任务已有交互授权(`tenon session activate --continuous`) | 有 `recommended` 的选择不停;评审门在 `request-review` 后用 `tenon review acknowledge <c> --delegated`。必需测试或评审者不通过时走回退边修问题,绝不「接受偏差」。 |
| `afk` | `TENON_AFK=1` | 同 continuous;`await-review` 直接以当前步骤状态结束本轮。 |
三种模式都一样:技能、文档、测试、评审者、门禁、读取回执,一项都不跳过;推送、PR、部署等
外部动作需要本次任务里已有明确授权;交互式技能自己跑它的对话。
评审确认由 hook 在用户回复时写入回执:本轮上下文出现 `<tenon-review-confirmed>`,或
`step.review.status` 已是 `approved`,就是用户已经确认了这道门——直接照 `next` 做(`transition`),
不要再让用户说「确认继续」,也不要说「这道门只认某句话」。
「按推荐」在评审门上只表示确认这道门,不是采纳你自己的建议。之后要设置字段时仍以 `next` 给的
`recommended` 为准;你自己的偏好与 CLI 的推荐不同(例如 `next` 推荐了某个取值而你想用另一个)
时,先明确问用户,得到回答再设,不要把一句「按推荐」当成授权。
## 进入
1. `command -v tenon`,没有就停下并给出重装提示。
2. dispatch `intent: new` → 起一个 kebab-case 名;`intent: select` 或 `selection_required: true`
→ 要用户给出**一对**确切的候选(名字 + 工作流),然后结束回合;`intent: resume` → 只接手
点名的那个任务。没有 dispatch 的手动 `/tenon`:`tenon list --json`,多于一个就问。
选通道以 dispatch 为准,不自己按关键词定轻重:`track_basis: user-named` 表示 `track` 是用户在消息里
点名的轨道,建任务时照用,不评估;`score` 是按内容评分的推测,用户另有说法以用户为准。
dispatch 带 `assess: required` 时,`candidate: workflow=<w>;track=<t>` 各行是可选的轨道(轻量一条、
领域重型一条),轻重由预计改动范围决定:建任务前先读相关代码,列出预计要改的文件,运行
`tenon assess --files <文件…> [--json]`——退出码 0 = 未超轻量阈值(推荐轻量候选),3 = 超限(输出列出
被突破的阈值,如 `auth_files 1 > 0`,推荐领域重型候选),1 = 出错(如路径越出仓库,改正后重跑)。
把评估结果与推荐摆给用户确认:interactive 结束回合等回复;continuous / afk 直接采用推荐。确认之后才
`tenon init`;选了轻量候选时加 `--domain <另一个 candidate 的轨道>`,升级时 `next` 就能直接给出领域轨道。
以 `/` 开头的提示词是显式命令,路由钩子不处理,所以 `/tenon <请求>` 要新建任务时由你选通道,选法与钩子一致:
用户点名了工作流或轨道就照用,不评估;请求带重型信号(架构、跨模块、迁移、schema、鉴权、依赖升级、
发布、全栈)就直接选对应领域的重型轨道;其余实现类请求同样先评估——读代码、列出预计要改的文件、
`tenon assess --files <文件…>`(不带 `--workflow` / `--track` 时评估默认工作流的轻量轨道),用
`tenon tracks list` 看有哪些轨道,再按上一段把评估与推荐摆给用户确认。
项目自定义的工作流只在用户点名时用,不要自己挑一个。
3. 新建:`tenon init <c> --workflow <w> --track <t> --preset full`(用户可以点别的 preset;dispatch 的
`track` 与 `workflow` 同名时是平铺工作流,省略 `--track`);
随后 `tenon session activate <c> --host-session <host_session_id> [--continuous]`。恢复只跑同一条
activate。`<host_session_id>` 取 dispatch 里的 `host_session_id`;dispatch 带了它就必须传,
否则下一轮的「确认继续」认不出本会话的任务,会被当成新任务。dispatch 没有它时才省略该参数。
activate 会生成 `.pipeline/.gitignore`、`.tenon/.gitignore`(带 `--host-session` 时还有
`openspec/.gitignore`):它们只把本机状态挡在 git 外,本身是项目文件,随本任务的第一次提交一起入库。
4. 新建或恢复之后立即重新加载 tenon(这是新的步骤访问):第一次读 `next` 时就不会只剩一条 `load-tenon`。
5. 用 `tenon workflow plan <c> --json` 的步骤标签建 Todo,当前项取 `current_step`。
6. 进入循环。
## 循环
```
repeat:
S = tenon status <c> --json | .step # 或 tenon step run <c> --json | .step(见下)
S.next[0].action == stop → 报告 message,结束(已完结的任务也有 step:next 是 stop,code finished)
把 S.next 里与 next[0] 同 action 的项一起做完(一波),按下面的动作表
做完 transition / complete → 立即重新加载 tenon(新的步骤访问,不必先 status 一次才知道),按模式继续或暂停
做完 request-review → 把产出摆给用户,结束本轮等「确认继续」(不要再 status 一次去看 await-review)
做完 finish-change → 任务结束,不必再 status
```
`tenon step run <c> --json` 读的是同一份 `step`,并先把 `next` 里确定性的部分一次做完——读取回执、生成测试计划初稿
(升级前创建的任务还会铺文档骨架、登记已写好的文档)——再给出最新的 `next`(`did` 是它做了什么,`stopped` 是它为什么停)。它能代替循环开头的 `status`,
文档类步骤上一次调用顶过去逐份 read 的好几次(升级前创建的任务还有 scaffold / record)。它不加载技能、不派 agent、不写文档内容、不跑测试:这些仍照动作表做。
## 动作表(闭集,与 `step.next` 一一对应)
| `action` | 做什么 |
| --- | --- |
| `stop` | 报告 `message` 后结束。`code: rounds-exhausted`(验证轮次用完,必需测试仍失败)时,把失败的测试、剩余阻断与 `message` 里的出路摆给用户,结束回合等用户决定,不要自行选一条出路。 |
| `load-tenon` | 重新加载本技能(Claude 用 Skill 工具;Codex 按上面的读取规则整读一次)。 |
| `read-documents` | 逐个读完 `documents` 列出的文件,把内容读进上下文(不得丢弃输出:`cat … >/dev/null` 这类读取不算读过),再 `tenon document read <c> all`。新任务里它们是直接上游步骤的产出:本步可以改,改动会记为本步产出;升级前创建的任务里它们是已登记的输入,只有 `editable` 里的 kind 本步可以改(改完照 `next` 重新登记),需求语义变了走 `requirements-changed` 回到规格步。tasks.md 只勾当前步骤标题下的复选框。 |
| `run-agent` | 逐项:`tenon agent prompt <c> <agent> --host <host> --json`(`<host>` 是本宿主 id:Claude Code 用 `claude`,Codex 用 `codex`;agent 声明了 `hosts` 且不含本宿主时 exit 2,照报错告诉用户,不换宿主硬跑)→ 用返回的 `subagent_type` 派发子代理跑返回的 `prompt`(Claude 用 Agent 工具;Codex 用子任务或 `codex exec`;没有子代理的宿主就在主线顺序跑)→ 把报告写到返回的 `report_path`(正文末尾一个 `tenon-result` 代码块)→ `tenon agent record <c> <run_id>`。`subagent_type` 是 `tenon-<agent>` 时,Claude Code 按该 agent 的 `tools` 白名单执行;Codex 没有按 agent 的工具白名单,只在工具里没有写或执行能力时设只读沙箱,其余限制就是 agent 正文里的指令;宿主说找不到这个子代理(会话中途才生成的),改用通用子代理跑同一份提示词,登记时加 `--subagent <实际用的类型>`。返回的 `host.run_on` 非空(`action` 里带 `host` 的评审者)时,这个评审者必须在另一个宿主上跑(跨厂商评审:Claude 写、Codex 审):不要在本宿主里自己审;从仓库根目录执行 `run_on.command`(它读 `run_on.prompt_file`;这是另一家的 CLI,由你执行,Tenon 不会替你起),评审写完报告并登记后继续;对方登记不了 Tenon 状态时,在本宿主执行 `run_on.record`(带 `--host`)。在不符的宿主上登记会被拒(exit 2),台账里宿主不符的结论无效。`host.run_on` 为空照常派发。同一波并行。执行者的正文只做「一个 task」(如 `builder`)时,本步未勾的独立任务各起一个同类型子代理并行,提示词后面写明各自的任务;全部回来后主线合成一份报告写到 `report_path`,只 record 一次。带 `status: running` 与 `run_id` 的项是已经开始的那次运行:不要重新 prompt,等它跑完把报告写到给出的 `report_path`,再 `tenon agent record <c> <run_id>`。评审者在同一份代码(候选没变)上已经有结论时,不能靠重跑换结论:`prompt` 会 exit 2——改代码换候选后再跑;确有需要(例如上一轮提示缺上下文)加 `--rerun-reason <原因>`,原因与重跑次数会展示给用户,没写原因的同候选重跑按最严结论判定。子代理还在跑的时候,主线不要去加载会向用户提问的技能(`brainstorming`、`grilling`、`domain-modeling` 等):它们落下的待处理交互会让主线停下等用户回复;等执行者返回、报告登记之后再加载。 |
| `load-skill` | 加载本波每个技能,按下面的「上游技能怎么用」执行。带 `review_bar` 时按下面的「下一步评审口径」审查。 |
| `scaffold-document` | 只出现在升级前创建的任务:文件不存在时先 `tenon document scaffold <c> <kind> [--capability <cap>]`,再动笔写内容:骨架里的 `[待填写…]` / `[pending…]` 占位要全部替换成真内容,留着占位符登记会被拒。`tenon step run <c>` 会替你铺。 |
| `record-document` | 只出现在升级前创建的任务:`tenon document record <c> <kind> <path> --producer <producer>`;本步有好几份文档都写好了,用 `tenon document record <c> --all` 一次登记(没写完的它会列出并跳过)。 |
| `register-field` | 只出现在升级前创建的任务:`tenon artifact register <c> <field> <path> --producer <producer>`。 |
| `set-field` | 先按下面的「决定」定值,再 `tenon set <c> <field> <value>`。 |
| `validate-spec` | `tenon spec apply <c> --dry-run`;退出码 2 就按报错改 delta spec 再跑。 |
| `apply-spec` | `tenon spec apply <c>`。 |
| `run-test` | `tenon test run <c> <test>`;`fail` 先改代码再重跑,不改就重跑没有意义。报「未配置(test-unconfigured)」不是失败:按提示配置(见 `fix`)。报「还没有得到你的信任」按 `run-tests` 处理。 |
| `test-discover` | 项目还没有测试目录(`tenon init` 通常已经自动识别过;没识别到就是没找到测试工具):跑 `command`(`tenon test discover --write`),核对 `.tenon/tests/catalog.yaml`(每个套件的命令、报告路径、种类、测试文件 glob;种类写 `unit` / `integration` / `e2e` / `static` / `benchmark` / `custom` 这几类,旧种类名是别名);glob 要覆盖测试放的位置,改动过的测试文件按它自动认领,不用逐个登记;缺的套件补进目录(`tenon test catalog add`)。项目确实没有某类测试就在目录里声明不适用:`tenon test catalog not-applicable <kind> --reason <原因>`——它要经评审确认一次才生效。 |
| `test-plan-seed` | 跑 `command`(`tenon test plan <c> --seed`)生成本任务的测试计划初稿。按档位(`test:`)要求测试的步骤,初稿不写套件和测试文件——运行集由档位、目录和本任务改动在求值时算出,测试文件按套件 glob 自动认领,只报告缺的必需测试类。计划、基线、已知失败清单只经 `tenon test …` 写入,不要手改 `test-plan.yaml`(会被判 `test-plan-tampered`),hook 也会拒绝。 |
| `test-plan-map` | 只在缺必需测试类(目录里没有该类套件),或档位要求场景映射(`scenarios: required` / `passing`)时下发;默认不要求映射场景。逐条处理 `items`,每条自带 `fix` 命令:缺的类照 `fix` 补套件(`tenon test catalog add`)或声明不适用,旧 `test_policy` 缺的种类 `tenon test register <c> --suite <id>`,没映射的场景(仅档位要求时)`--case "<covers>" --test "<文件> › <用例名>"`(用例要真的存在)。确实不适用的种类:整个项目都不适用用 `tenon test catalog not-applicable <k> --reason <原因>`(评审确认一次后对所有任务生效),只有本任务不适用才 `tenon test waive <c> --kind <k> --reason <原因>`——两者都要评审批准,不是绕过。`show` 是查看当前计划的命令。 |
| `test-register-files` | 只为孤儿下发:`files` 是本任务新增或修改、像测试却没有任何套件的 glob 认领的文件(`test-file-orphan`)。执行一条 `fix`(`tenon test register <c> --auto`)认领全部:只把各文件的精确路径追加进合适套件的 `files`,不放宽 glob,也不动本任务没改过的文件;没有合适的套件才先补目录。被套件 glob 认领的改动文件不用登记,也不会下发这个动作。 |
| `run-tests` | 动作带 `trust` 时先请用户在自己的终端运行 `trust.command`(把 `trust.commands` 列给用户看;它只列本次要执行、尚未信任的命令,信任在同一仓库的所有 worktree 共享),得到答复后再执行,不要先撞一次拒绝。执行 `command`(`tenon test run <c> --stage`):一条命令跑完本阶段按档位要求的全部套件;改动没碰到任何套件时它说明没有要运行的套件并以 0 退出,不是失败。跑完用例失败、覆盖率不足之类不是「没跑」,`next` 会改发 `fix`(评审门上有回退边就走回退边;受轮次约束的步骤上,轮次内直接 `transition`,不需要评审回执)。命令以 exit 1 报「还没有得到你的信任」时,是本次要执行的命令里有尚未得到用户本人确认的:把它列出的命令摆给用户,请用户在自己的终端运行 `tenon test trust`,然后再重跑。不要自己运行 `tenon test trust`,也不要设置 `TENON_TEST_TRUST`(hook 会拒绝)。 |
| `test-report` | 执行 `command`:把追溯矩阵写进验证报告。档位不要求场景映射时,矩阵只列已映射的行,另加一行「另有 N 条场景 / 任务未映射(本步不要求映射)」,那不是缺陷。报告是已登记文档,写完会变过期,照 `next` 重新登记。 |
| `fix` | 逐条解决 `blockers[]`(改代码或文档),然后回到循环。`source: tasks` 的 blocker 带 `items`(截至本步仍未勾的任务原文):把这些任务真的做完,再在 tasks.md 里勾上。`code: test-unconfigured`:项目没有这条必需测试要的 npm 脚本——在 package.json 加上运行本项目真正这类测试的脚本,不要复制别的测试命令凑数;在计划步提出时,把「写这类测试」列进本步的计划与 tasks,并把新增的测试脚本与测试同步写进 proposal(What Changes / Impact)与 design,删掉与之矛盾的表述(如「不改 package.json」);在之后的步骤提出时只补 package.json 的脚本(及它要跑的测试代码),不要改已登记的规格文档(proposal / design / plan)。项目不用 npm 时停下告诉用户去改工作流的测试命令。 |
| `request-review` | `tenon check <c>` → `tenon review request <c> --event <event>` → 把产出与结论摆给用户。动作带 `waivers` 时,里面是待用户批准的项:计划里的测试豁免(`kind:<种类>`)、目录里项目级的「不适用」声明(`not-applicable:<种类>`),以及本任务改动了的测试配置文件(目录、基线、已知失败清单、项目工作流的路径)。把 request 输出逐条列出的这些项连同理由、文件摘要一并摆给用户:用户的确认同时批准它们,确认之后配置文件再变要重新确认。委托确认(持续授权)不批准它们,只能等用户回复「确认继续」。动作带 `residual`(验证轮次已用完,必需评审者在当前候选上仍不通过)时,还要把 `review request` 输出里逐条列出的剩余阻断(评审者、阻断级发现)连同 `alternatives`(另外的出路)摆给用户:只有用户回复放行语(「确认继续」等)的人工确认才接受这些剩余阻断;持续授权的委托确认会被拒、评审保持待确认,如实告诉用户,不要自己批准也不要绕开。受轮次约束的评审步上,轮次内的回退边不走这个动作:`next` 直接给 `transition`,不需要评审回执。 |
| `await-review` | interactive:结束回合等人。continuous:`tenon review acknowledge <c> --delegated`(计划里有待批准的测试豁免或待接受的剩余阻断时委托确认会被拒、评审仍待确认:如实告诉用户,等用户回复「确认继续」人工批准,不要自己批准也不要绕开)。afk:结束本轮。 |
| `commit` | 交付物提交(交付步有未勾任务时它排在勾选之前:先提交,再勾「提交代码」这类任务):`git add -A -- <commit.paths…>`;`commit.untrack` 非空时接着 `git rm --cached -q --ignore-unmatch -- <commit.untrack…>`;最后 `git commit -m "<commit.message>"`。paths / untrack 原样用、不增不减(`:(exclude)…` 是挡住仓库根门禁标记的 pathspec,照抄)。宿主不让写 `.git` 时如实告诉用户这一步留给他,不要说已提交。 |
| `choose-exit` | 按下面的「出口」挑一条边。 |
| `transition` | `tenon transition <c> <event>`。带 `escalate` 的 `transition`(`event: scope-expanded`)是风险升级:改动风险探针没通过,`escalate.reasons` 是被突破的阈值——照做这条转换(它不要求其余证据),然后照 `escalate.then` 新建重型轨道任务并 `tenon set <新任务> depends_on <本任务>`(`then` 直接给出 `tenon init …` 命令;它没有领域、只列出可选轨道时,把可选项摆给用户选一个再建);不要为了让探针通过去拆改动或藏文件。当 `next` 里只剩这一条 `transition` 时直接执行,不问用户,interactive 也一样(不等「继续」,不给「升级 / 调探针阈值 / 不走流程」这类选择题):执行前用一句话告诉用户为什么升级(`escalate.reasons`)、接下来要做什么,然后动手。 |
| `complete` | `tenon transition <c> <event>`——走完终态自边,状态机到此结束。归档由下一条 `finish-change` 单独下发,不要在这里抢跑 `tenon task finish`。 |
| `finish-change` | `command` 不是 `null` 时照原样跑(`tenon task finish <c>`):Tenon 把 change 目录搬进 `openspec/changes/archive/` 并记下任务结果,不要自己执行 `openspec` 命令。`commit` 不是 `null` 时再提交:`git add -A -- <commit.paths…>`;`commit.untrack` 非空时接着 `git rm --cached -q --ignore-unmatch -- <commit.untrack…>`;最后 `git commit -m "<commit.message>"`。paths / untrack 原样用、不增不减(宿主不让写 `.git` 时如实告诉用户这一步留给他,不要说已提交);`commit` 为 `null`(不是 git 仓)就不提交。 |
## 决定、字段、出口
- 带 `allowed` 的字段是一次决定:interactive 把 `recommended` 排在第一位问;continuous / afk
直接用 `recommended`。决定在动手之前下发,按你接下来真的要用的方式填,之后照它执行。
- `kind: outcome` 的字段只在本步必需测试与评审者都过了之后才出现在 `next` 里;它们没有 `recommended`,填 `required` 给的值。`pre_verify_review_result` / `verify_result` 是通过结论:CLI 写入前核对本步证据,被拒就按错误里点名的测试或 agent 去补,不要换个写法绕过。
- 下一步评审口径:动作带 `review_bar`(下一步声明的评审者,每项 `step` / `agent` / `required` /
`block_at` / `focus`)时,本步的实现与自审——技能自带的代码评审、子代理审查、写
`pre_verify_review_result` 之前的就绪审查——按同一口径判级:落在这些评审者关注点上、级别达到其
`block_at`(`medium` 即 medium 及以上)的问题就是阻断,在本步修完,不要判成「建议」留给下一步;
下一步的评审者会以同一级别打回,多走一轮回退。派发审查子代理时把 `review_bar` 原样写进提示。
- `pr_url`、`prd_path` 和各类文件路径只填真值,绝不编造。`pr_url` 是真实的 http(s) PR 地址;仓库没有 远端时 `next` 会推荐 `no-remote`(本地交付、没有 PR,CLI 会复核确实没有远端)。有远端却开不了 PR 就停下说明。
- 暂停等用户(评审门、interactive 的「继续」)之前如实报告工作区:以 `git status --short` 为准。
交付步照 `next` 做完、走出口之前,工作区应当是干净的(`set pr_url` 写下的状态文件已由 `next` 点名的
提交入库);不干净就照实列出。`transition` 自己还会改 change 目录里的状态文件(`.pipeline.yaml`、
`.pipeline-history.jsonl` 等),之后暂停时说「只剩流转写下的状态文件未提交,完结时由 `finish-change`
提交」——不要说工作区是干净的。
- 不要自己建分支或 worktree。提交只照 `next` 的 `commit` / `finish-change` 做(交付步在交付值之前
点名提交交付物,完结后点名提交归档),`next` 没点名就不提交;技能自带的提交步骤同样不做。
- 出口:`ready` 的前进边直接走;回退边只在它的含义成立时走(必需测试或评审者不通过 → 回到实现
的那条边;已确认的需求变了 → 回到规格的那条边)。受轮次上限约束的评审步(评审门且有回退边)上,
轮次还没用完时走回退边不需要评审回执:`next` 会直接给 `transition`,任何模式都直接走,用一句话
告诉用户为什么回退;评审待确认时用户要求回去修改,同样直接
`tenon transition <c> <回退事件>`,不要等确认。前进边、剩余阻断与测试豁免仍只认人工确认。没有轮次约束的步骤上的
回退边,interactive 先问;轮次用完后回退边会被拒(见下)。走到终态的
`scope-expanded` 表示目标超出了这条轨道:之后按 `escalate.then` 新建重型轨道任务,并
`tenon set <new> depends_on <old>`(由改动风险探针触发时,`next` 会直接给出它;它是 `next` 里唯一的 `transition` 时不用问,见动作表的 `transition`)。
- 验证轮次上限 `max_rounds` 是用户的决定:不要自行执行 `tenon set <c> max_rounds <N>`,只在用户明确指示时执行;设置会改变任务状态,已发起或已批准的评审请求随之失效,执行后重读 `next` 并重新 `tenon review request`。上限用完时回退边会被拒,不要换事件、改状态或删 marker 绕过,照 `stop` / `request-review` 把出路摆给用户。
## 上游技能怎么用
- 任务已经存在且已绑定:不要 `openspec new change`,也不要另选或另建 change。
- 技能按它自己的 SKILL.md 写文件,路径由技能决定。Tenon 在离开步骤时自动记录本步新增、修改、删除的文件与
来源技能,不需要登记。
- 步骤要求的「必须产出」(`required-output`)没写出时,出口阻断(`fix`)会点名路径:让技能把文件写出来。
- 技能自带的「提交」「归档」「同步规格」「推送」「建 PR」等收尾动作,只有 `step.next` 点名时才做。
- tasks 只勾当前步骤标题下的复选框。
- 升级前创建的任务(`step.documents.mode` 为 `contract`)照旧:文档写在 `step.documents` 给的 `path` 上,
缺结构先 `scaffold-document`;`path` 为 `null` = 这一条的路径还要你拍板,按 `path_template` 里剩下的占位符定值再 scaffold
(delta-spec 缺 `{capability}`,即 `tenon document scaffold <c> delta-spec --capability <x>`);只经 `record-document`
登记;tasks 的重新登记会出现在 `step.documents.updates` 里。
## 出错时
| 情况 | 怎么办 |
| --- | --- |
| 没有 `tenon` | 停下,给重装提示 |
| 技能加载不了 | 停下,提示 `tenon setup --<host>` / `tenon update --<host>` |
| `lacks exact host confirmation` | 按上面的读取规则重新加载产出者技能,再登记一次 |
| `stop` 且 `code: retired-skills` | 告诉用户新建任务;旧任务可以终止(`tenon task terminate`)或删除 |
| 评审待确认、回复不是放行语 | 继续等;hook 会打印解锁提示。等待期间只读工具与只读命令不受拦截,由 `\|` `&&` `\|\|` `;` 连接、每段都只读的组合命令(如 `git status --short \| grep x`)也放行 |
| 回退边被拒、报「验证轮次已用完」 | 不要换事件或调高上限绕过;把已用轮次、上限与出路摆给用户,等用户决定 |