发布说明
Tenon 的发布说明用于回答三个问题:这一版改变了什么、用户需要做什么、如何确认升级成功。
本页只记录已经进入公开发行包的能力,不把规划、内部 ADR 或尚未合并的实验写成已交付事实。
阅读方式
每个版本条目都按“新增、变化、修复、升级动作、验证、兼容性”组织。
命令、事件名、phase id、配置 key 和文件路径保留英文,以便与 CLI 输出逐字对应。
面向用户的解释、影响与操作步骤默认使用中文。
v0.3.4 · 2026-10-14
v0.3.4 让任务的 agent 更可靠,也让同一仓库里的多个任务可以并排进行。Tenon 的 agent 在终端里一次性装成用户级的宿主子代理,会话里的第一个任务不再撞上「找不到子代理」。新任务建在自己的 Git worktree 里,决定哪些改动算本任务的那个起点,在建任务时记下并跟随上游。终端里有一行显示任务走到了哪一步,从某一步回退也不再要求先启动一个 agent。升级项目前先读「行为变化与升级影响」。
终端里安装宿主子代理
tenon agent install [--host claude|codex]把适用于该宿主的官方与自定义 agent 写成用户级子代理文件:Claude Code 是<Claude 配置目录>/agents/tenon-<name>.md,Codex 是<Codex 配置目录>/agents/tenon-<name>.toml。配置目录取$CLAUDE_CONFIG_DIR(Claude Code)或$CODEX_HOME(Codex),变量值是绝对路径时才采用,否则是~/.claude或~/.codex。--host缺省取进程环境判出的宿主,普通终端里是claude。完成时退出码0(必须原样不动的文件是WARN行,也算完成),用法错误、agent 库未装配或有文件写失败时退出码1。- 以前
tenon init与tenon agent prompt在会话里把.claude/agents/tenon-<name>.md写进项目,而会话中途新增的子代理文件什么时候被宿主加载没有保证,所以会话里建的第一个任务可能派发出一个「找不到」的子代理。只有会话启动前就存在的文件才可靠,所以会话里运行的 Tenon 命令一概不再写它们。 - 什么时候执行。
tenon setup --claude|--codex与tenon update --claude|--codex成功后会执行同一套安装(update发现已是最新版时也执行),并打印写了哪些文件。安装失败只给 WARN:setup/update的退出码不变,你可以自己再执行tenon agent install --host <host>。--dry-run不安装,适配器宿主没有子代理目录因而跳过这一步,每天一次的自动更新(--auto)只在安装失败时出声。tenon agent new|add|copy|rm只改库,只打印提醒:执行tenon agent install并开新会话。 - 所有权。按文件记内容摘要,存在
<config>/agents/host-owned.json,按写入的目录分组:在一个配置目录上安装或卸载,不会删除、改写或认领另一个目录里 Tenon 写的文件。文件只在安全时才写:它还不存在;内容已经和渲染结果完全一样(Tenon 补记它归自己);或 Tenon 之前写过、此后没人改过。同名但归你的文件、或你改过的文件原样不动,列成WARN;目标文件本身是符号链接则拒绝写(父目录是符号链接,例如 dotfiles 管理的~/.claude,没有问题)。agent 已不在库里时,它的文件如果是 Tenon 写的且没被改过就删除。host-owned.json解析不了时,已有文件一律不改不删,只写还不存在的文件。 tenon agent uninstall [--host claude|codex]删除 Tenon 写入且没被改过的文件;改过的保留,在结果里列为modified。tenon uninstall作用于项目的.pipeline-owned.json,不碰这些文件,想清掉它们请先执行tenon agent uninstall --host <host>。- 项目层 agent(
.tenon/agents/)从不安装,所以在哪个目录执行安装结果都一样。项目层 agent 一律走宿主的通用子代理,它的工具限制只剩正文里的指令。 tenon agent prompt不写任何宿主文件。带--json时,只有已安装的文件与任务冻结的定义为该宿主渲染出的内容逐字节一致、并且项目里没有内容不同的同名文件(.claude/agents/tenon-<name>.md、.codex/agents/tenon-<name>.toml)把它遮住时,才返回subagent_type: tenon-<name>与native: true。其余情况返回宿主的通用子代理(Claude Code 是general-purpose,Codex 是default),native: false,并带reason(not-installed、definition-mismatch、shadowed或project-agent)与一行hint;hint一律是中文,并点出它检查的目录。任务沿用它冻结的定义,所以冻结了旧版本 agent 的任务会留在通用子代理上,而不是悄悄换成新版本。tenon agent record <change> <run-id> --subagent none记下没有派发任何子代理的运行(reason: not-dispatched)。没用专属子代理的运行,原因存在运行旁注.pipeline-agent-run-meta.jsonl的subagent_reason里,台账行里的subagent仍是三个键。- 旧版本写进项目的文件优先级高于用户级的,所以 Tenon 会清理它们:
tenon init时,以及任务完结、终止或删除时(CLI 与 Dashboard 都一样)。项目的.pipeline-owned.json记着该文件且内容仍与记录的摘要一致的,或者内容与当前库为该 agent 渲染的结果完全一致的,会被删除。其余一律保留(被改过的、两条规则都不符合的、读不了的),并用WARN点名文件;清理失败从不改变退出码。 - 装完要开新会话:宿主在会话启动时加载子代理文件。要在宿主启动时的环境里安装。如果你的 Claude Code 或 Codex 是带着
CLAUDE_CONFIG_DIR或CODEX_HOME启动的,安装也要在带同样变量的环境里执行,否则文件落到~/.claude/agents,宿主看不到。输出第一行目标目录:<dir>告诉你装到了哪里,在会话里执行! tenon agent install --host claude用的就是会话的环境。见宿主子代理与安装命令。
终端里的步骤清单
tenon init、tenon session activate、tenon transition或tenon step run <name>成功后,PostToolUse hookstep-progress打印一行,数据来自任务冻结的计划:已过的步骤是✓,当前是●,没到的是○,任务归档后全是✓。步骤之间用→连接,标签就是工作流声明的标签(立项 ✓ → 调研 ● → 规格 ○ → …)。遇到回退边(例如verify-fail回到实现)时,后面的步骤会重新显示为○。tenon workflow progress <name>打印同一行,在主检出里也能找到住在 worktree 里的任务。读不了任务(名字非法、状态读不了、没有计划、没有步骤)时退出码1且 stdout 为空。- 这一行不依赖宿主的 Todo 工具(较新的 Claude Code 模型默认不提供);宿主有 Todo 工具时,
tenon技能还会按tenon workflow plan <name> --json建 Todo。hook 只在宿主报告命令成功之后运行,所以失败的命令不会打印;hook 出任何问题都只是不打印而不是报错。它只认由小写字母、数字和连字符组成的任务名,别的名字请自己执行tenon workflow progress <name>。见终端里的步骤清单。
每个新任务建在自己的 worktree
- 在 Git 仓库的主检出里,
tenon init把任务建在<项目>/.worktrees/<change>,分支是新建的tenon/<change>,从当前HEAD分出,不跟踪任何上游。任务里记下base_branch(你出发时所在的分支)、start_sha(那个HEAD)和isolation=worktree。同一个仓库里的多个任务并排进行,谁也看不到别的任务改了哪些文件、登记了哪些测试,构建指纹也互不相干。没有任何忽略规则覆盖.worktrees/时,它会被追加进.git/info/exclude,所以主检出的git status不变。创建要么全成要么全不成:init失败时 worktree 与分支会被撤掉,同名路径或分支已存在时init退出码1且什么都不改。 - 改为原地建立。下面这些情形里
tenon init像以前一样把任务建在当前目录,并在 stderr 说明原因:当前目录不是 Git 仓库(或不是仓库根目录)、仓库还没有任何提交、HEAD处于分离状态、当前目录本身已经是 linked worktree,或者你传了--in-place(确实要原地时才用;用户不要求时tenon技能不会传)。TENON_INIT_IN_PLACE=1效果相同但不打印原因,是给 Tenon 自己的测试和脚本用的。AFK 分诊建的任务一律原地建立,升级前创建的任务留在它原来的检出里。 - 输出。文本输出时 stdout 为空,stderr 的最后两行是
worktree: <绝对路径>和切进去的做法。--json时 stdout 只有一行,带change、path、worktree(绝对路径或null)和worktree_reason(建了 worktree 时是null,否则是not-git、no-commits、detached-head、linked-worktree、requested之一)。 - 切进去与恢复。Claude Code 里
tenon技能用EnterWorktree切进 worktree,再执行tenon session activate;此后的命令、评审确认与 hook 都以该 worktree 为项目根。Codex 没有对应能力:技能请你在该目录开新会话,并结束本轮。恢复同理:路由器解析到的点名任务只存在于.worktrees/下时,dispatch 会带worktree_path。在主检出里泛泛说「继续」、而能继续的只有 worktree 任务时,dispatch 把它们连同路径列出来(change_candidate),请你点名一个。worktree 任务不计入路由器判断「新建还是选择」时的在途任务数。 - 再次找到任务。
tenon list末尾有WORKTREE列(当前根自己的任务是-),--json的每一行带worktree;在主检出里执行时,它还会列出.worktrees/下未归档的任务。在主检出里按名字读取住在 worktree 里的任务的命令(status、get、check、workflow plan)以退出码1失败,报错点出 worktree 路径。 - 准备 worktree。新 worktree 里没有装好的依赖,也没有构建产物。测试目录(
.tenon/tests/catalog.yaml)可以声明一个顶层的setup字符串,例如npm ci,这条命令已受信任时,tenon init会在新 worktree 里执行它,输出转到 stderr。信任按用户、仓库、单条命令记录,同一仓库的所有 worktree 共享:这条setup你已经信任过就立即执行;还没信任的不会执行,init打印该命令,并请你自己运行tenon test trust确认(agent 不能替你确认),再手动执行它。worktree 里是已提交的目录,主检出里对setup的未提交改动不算。setup失败只给 WARN 和重跑的命令,init的退出码仍是0;没有目录、目录无效或没有setup键,都会说明并跳过。tenon test trust把setup连同各套件一起列出,tenon test run既不执行也不校验它。setup可能要几分钟:执行 init 的 shell 调用要给足超时(技能用 600000 ms);超时只会中断setup,任务已经建好。 - 完结。交付与完结都在
tenon/<change>分支上进行。任务归档后,Tenon 把 worktree 与分支留在原处,只报告移除它们的命令,字段是cleanup,出现在finish-change动作和code: finished的stop里:git worktree remove '<路径>' && git branch -d 'tenon/<change>'。要自己在主检出里执行(不能在要删的 worktree 里执行);git branch -d会拒绝还没合并的分支,所以不会丢提交。finish-change恒有这一键(原地建立的任务为null),stop只在 worktree 任务上才有。 - worktree 任务的构建版本和原地任务一样绑工作区内容指纹,因为任务 worktree 里的实现在 Build 出口时通常还没提交,Build 也从不要求提交;只有
isolation=branch绑 Git 对象。身份仍绑定物理 repository 与物理 worktree,所以在别的检出里验证一个 worktree 任务会得到worktree-mismatch,token 类别与任务的isolation不符则得到isolation-mismatch。两者都是回 Build 重新捕获。 - 每个新任务多一个 worktree、一个分支和一次依赖安装,Codex 要开新会话。Dashboard 把每个 worktree 登记为单独的项目,没有跨 worktree 的统一视图。见任务在哪里。
记录下来的任务起点
tenon init把检出时的HEAD写进任务字段start_sha(AFK 分诊同样写;仓库还没有提交时不写)。「自任务开始以来改动了哪些文件」的起点取三个候选里最新的一个:start_sha(当它是HEAD的祖先、且提交时间不晚于任务created_at加 60 秒时)、HEAD与base_branch上游的 merge-base、HEAD与base_branch的 merge-base(不等于HEAD本身时)。候选不成链时退到它们的共同祖先,所以清单可能多算,但不会少算;一个候选都没有时,仍取created_at之前的最后一个提交。不读提交信息,也不读作者。- 你会看到的变化:别人合入基线分支、你又拉取进来的提交,不再被算成本任务的改动,不再冒出一堆继承来的测试文件和被改过的目录。测试文件的登记与同步、改动文件清单、受保护测试配置的批准、测试完整性报告、
diff-risk探针、按attach_on挂载的评审者、改动行覆盖率、证据导出、流转门(CLI 与 HTTP)、Dashboard 快照和tenon verify --ci,比对的都是这同一个起点。在 CI 检出里,tenon verify --ci的基线优先取origin/<base>,它不存在才取本地分支。 start_sha、base_branch、created_at只由tenon init写入:tenon set、set-many、cas拒写(退出码1,状态不变)。base_branch与created_at以前可以被改写。start_sha存在任务状态旁的一份记录里(.pipeline-run/start-sha/),不写进.pipeline.yaml。这份记录不在状态摘要的保护范围里,所以才要拿它的提交时间对照created_at:它被改写或删除,起点只会前移、清单只会多出文件,不会少。- 限制。上游是本地引用,没有
git fetch时可能偏旧,那只会多算文件。原地任务在基线分支上提交了自己的改动并推送到它的上游之后,起点会前移过这些提交,它们不再算本任务的改动;worktree 任务不受影响。伪造了早于created_at的提交时间的提交能通过时间校验。见任务起点。
修复
- 从某一步回退不再要求先启动 agent。回退边(例如实现步的
requirements-changed、验证步的verify-fail)不再要求本步声明的 agent 的技能:这些技能只能在 agent 运行期间加载,所以实现步的requirements-changed以前要先开始一次 builder 运行。主线自己加载的技能在回退边上仍然要求,前进边照旧要求全部。tenon status、tenon check、tenon transition与 Dashboard 的流转用的是同一条规则。 - 未被跟踪的
.claude/worktrees/不再让工作区指纹变化,构建版本和评审结论绑的就是这个指纹。Claude Code 为它的子代理与会话在那里建检出,同一仓库的另一个会话随时在改它,它还可能非常大:主检出里的任务因此变成revision-stale,每次算指纹(也就是每次tenon status)都很慢。遍历现在不再进入这个目录。其中被 git 跟踪的文件照算,所以 PR 不能把代码藏进去。测试记录绑的指纹本来就不含它。
行为变化与升级影响
tenon init在 Git 仓库的主检出里建 worktree。 先tenon init、再留在同一个目录里继续工作的脚本和 CI,要加--in-place(本仓库 CI 的基准步骤就是这样),或者切到--json返回的路径里。按名字读取任务的命令要在任务自己的根里执行。- 专属子代理需要
tenon agent install。 在你执行它并开新会话之前,tenon agent prompt返回带reason: "not-installed"和安装提示的通用子代理,agent 在没有它自己的tools白名单与model的情况下运行。 - 已信任的
setup不再询问就执行。 信任绑定的是命令文本,不绑定命令执行时读取的仓库文件,例如package.json的生命周期脚本、它们调用的脚本,与测试套件相同。setup一旦被信任,之后的提交改了这些文件,tenon init会在新 worktree 里执行改过的版本。不想这样,就用tenon init --in-place建任务,或从目录里去掉setup。 tenon set、set-many、cas拒写start_sha、base_branch、created_at(退出码1)。写过base_branch或created_at的脚本现在会失败。tenon list末尾多了一列WORKTREE,--json的每一行多了worktree键。按列位置读表或逐字比对行的脚本要更新。tenon uninstall不碰用户级子代理文件。 想清掉它们,先执行tenon agent uninstall --host <host>。
开发
- Tenon 自己的仓库删除了一个只在本仓库使用的外部任务工具:它的项目 hook 用相对路径指向被 git 忽略的目录,在 git worktree 里会失败并拦住提示。对用户没有影响。
- 在临时 git 仓库里执行
tenon init并期望任务建在该目录的测试或脚本,必须导出TENON_INIT_IN_PLACE=1;不能碰宿主配置的夹具要去掉CLAUDE_CONFIG_DIR和CODEX_HOME:用户级子代理与AGENTS.md都据它们解析。本仓库的 vitest 全局 setup 与 shell 测试脚本两件事都做了。 - 本仓库的
.tenon/tests/catalog.yaml声明了setup: npm ci && npm run build:packages。
升级动作
运行 tenon update --claude(或 --codex),然后:
- 在终端里执行
tenon agent install --host claude(或--codex)安装宿主子代理,再新开宿主会话。你刚运行的tenon update是升级前那个版本的代码,没有安装这一步;之后的tenon setup与tenon update会自己执行它,现在再运行一次tenon update也能装上。如果 Claude Code 或 Codex 是带着CLAUDE_CONFIG_DIR或CODEX_HOME启动的,安装要在带同样变量的环境里执行(会话里用! tenon agent install --host claude),并核对目标目录:<dir>那一行。新增、修改或删除自定义 agent 之后也要再执行一次。 - 先
tenon init、再在同一个目录里继续工作的脚本和 CI:加--in-place,或者从--json读worktree。 - 想让新 worktree 自动做好准备,就在
.tenon/tests/catalog.yaml里声明setup(例如npm ci),并在自己的终端里运行一次tenon test trust。不声明的话新 worktree 里没有装好的依赖,测试跑不起来(tenon init会提示)。 - 用
tenon set写过base_branch或created_at的脚本,去掉这些写入。 - 项目里有未跟踪的
.claude/worktrees/的机器上,在途任务第一次校验可能出现一次revision-stale:回到实现步重新捕获当前版本,并重新运行评审者(见「兼容性」)。 - 建在 worktree 里的任务完结后,在主检出里执行
step.next报告的cleanup命令。 - Codex:hook 文件变了,Codex 可能要求你重新信任。运行
/hooks,信任tenon,再新开会话(见安装)。 - 回滚到更早的版本之前,先完结或终止建在 worktree 里的任务,以及有运行落在通用子代理上的任务,把
setup从.tenon/tests/catalog.yaml里删掉;想清掉用户级子代理文件就先执行tenon agent uninstall --host <host>(见「兼容性」)。
兼容性
N-1 是 v0.3.3。N-1 兼容门禁(tools/test-bundle.sh)每次运行都在两个方向上让它与本版本互相读写。
- 门禁交叉读写测试记录链、agent 运行台账及其旁注、测试计划、
discover --write写的目录、被TENON_RECORD_RETENTION清理过的记录链,以及旁边有未跟踪的.claude/settings.local.json时写下的记录。它的任务都是原地建立的(TENON_INIT_IN_PLACE=1)。下面其余的限制是已知的,门禁没有交叉测。 - v0.3.3 读不了目录的
setup键:读带顶层setup的目录会报未知键。回滚之前,先把setup从.tenon/tests/catalog.yaml里删掉。 tenon init建在 worktree 里的任务带isolation=worktree。v0.3.3 对这个值绑 Git 对象,本版本绑工作区指纹,所以一个版本捕获的构建版本,在验证步会被另一个版本拒绝(isolation-mismatch)。回滚之前先完结或终止建在 worktree 里的任务(见更新、恢复与卸载)。v0.3.3 也不认识.worktrees/:在主检出里它的tenon list不列这些任务,按名字读取它们的命令报change not found,要在 worktree 里执行。- v0.3.3 读不了没用专属子代理的运行的旁注:那一行带
subagent_reason,v0.3.3 不认识,于是把该任务的 agent 运行台账判为损坏(runs-corrupt)。这包括派发到通用子代理的运行(原因not-installed、definition-mismatch、shadowed、project-agent)和用--subagent none登记的运行。回滚之前先完结或终止这样的任务。 start_sha存在伴随记录里,所以 v0.3.3 读得了带它的任务,也续写得了。这份记录不会带进 v0.3.3 写下的 revision:回到本版本后,那个任务的起点只由上游与基线分支两个候选算出,与字段出现之前创建的任务一样。- v0.3.3 不认识用户级子代理文件和
host-owned.json。在宿主会话里它会把项目级的tenon-<name>文件重新写出来(tenon init、tenon agent prompt --host),而项目级文件的优先级高于同名的用户级文件;本版本会在tenon init和任务结束时再把它们清掉。 - 指纹规则变化:项目里有未跟踪的
.claude/worktrees/的机器上,v0.3.3 把这个目录算进构建版本和评审结论绑的指纹,本版本不算。跨过升级(或跨过回滚)的在途任务,第一次校验可能出现一次revision-stale,评审者也要重新运行。按冻结版本过期的惯常办法处理:回到实现步重新捕获当前版本。测试记录绑的指纹从来不含这个目录,不受影响,项目里没有这个目录的任务也不受影响。 - 新增的命令与选项:
tenon agent install与uninstall、tenon workflow progress、tenon init --in-place与--json、tenon agent record --subagent none、tenon list的WORKTREE列和--json行里的worktree、tenon agent prompt --json里的reason与hint、finish-change动作的cleanup键、目录的setup键。有变化的:tenon set、set-many、cas拒写start_sha、base_branch、created_at。已删除:tenon init与tenon agent prompt写进项目的宿主 agent 文件。
验证
tenon --version
tenon doctor
tenon runtime status
tenon agent install --host claude
tenon list --jsontenon --version 打印 0.3.4。tenon doctor 里 identity:release 列出宿主插件、runtime 和 Dashboard server 都是 0.3.4。tenon runtime status 显示当前 release 为 valid=yes。tenon agent install 首行打印 目标目录:<dir> 并退出码 0;重复执行时每个文件都报「未变化」。新会话里,对任务当前步骤里的 agent 执行 tenon agent prompt <change> <agent> --host claude --json,返回 subagent_type: tenon-<agent> 与 native: true。tenon list --json 的每一行都有 worktree 键,值是 worktree 的绝对路径,当前根自己的任务为 null。
v0.3.3 · 2026-10-14
v0.3.3 让任务更轻地创建、配置和确认。内建工作流只剩两个:default,它的轨道是轻量(lite)、产品(pm)、前端(frontend)、后端(backend),以及平铺的 design-system。工作流不再声明每一步的文档,Tenon 改为记录每一步实际写出了什么。步骤用一个档位声明测试要求,默认的 code-size 测试没有行数上限。轮次内的回退边和只读组合命令不再等待确认,同一项目里并行的会话也不再互相拦截。升级项目前先读「行为变化与升级影响」。
一个默认工作流
- 轻量轨道取代了
standard与simple工作流:open → build → verify → done,一个评审门,实现步里有改动风险探针。探针失败时任务升级:scope-expanded把它记为已升级,并给出开重型轨道任务、用depends_on关联的命令(用tenon init --domain记下的领域;没记就列出可选的轨道让你选)。 - 开发请求在任务建立之前先评估。路由只决定领域。然后
tenon assess --files <路径>...按轻量轨道的阈值评估预计要改的文件(退出码0未超限,3超限并列出被突破的阈值,1出错),tenon技能把结果与推荐摆给你,你确认后才创建任务。带重型信号的请求(默认的词有架构、迁移、schema、鉴权、发布等)直接进领域的重型轨道。tenon init --domain <track>记下轻量任务将升级到的重型轨道。 - 轨道是工作流 YAML 里的分支(
tracks.<id>),有自己的步骤和三个可选设置:spec(openspec或none)、route(keywords、exclude、priority、fallback)、automation(eligible、enqueue_after_spec)。没有tracks:的工作流本身就是唯一的轨道。路由数据从这些分支生成,所以在 Dashboard 保存工作流后,下一条消息就按新规则路由。tenon tracks list列出全部工作流的轨道。 default按 YAML 执行。它的规则写成离开条件,其中三个是通用的:tasks-through-step、not-afk-queued、dependencies-ended。不带 event 的tenon check <name>一次列出本步和前进边上全部未满足的条件,退出码2。- 所有任务都在
openspec/changes/archive/里结束。tenon task finish <name>与tenon task terminate <name>把任务移到那里并记下结果:完结、已升级或已终止;tenon list --finished列出已结束的任务。工作台分类为 全部 · 需要你 · 进行中 · 已结束,已结束的卡片带结果标记。 - 工作流页可以改轨道的规范与路由关键词,并只读列出每步的离开条件。
运行时记录文档
- 工作流不再声明每一步的输入、输出文档。进入步骤时记下检查点(git
HEAD和与之不同的文件及其 SHA-256);离开步骤时与检查点比较,把本步新增、修改、删除的文件连同写出它们的技能(Tenon 判断不出时记unknown)记进.pipeline-step-outputs.json。shell 与子代理写的文件也算。不在 git 仓库里时什么都不记,也不阻断任何转换。 - 下一步只拿到直接上游步骤产出的 Markdown 文档。
step.next用read-documents列出未读的,tenon document read <name> all(或一个路径)写读取回执;输入没读完时,除放弃边外的任何边都走不了(退出码1)。tenon document status <name>打印本步的输入、产出与必须产出。 required-output守卫,{ type: required-output, path: <模式> },写在步骤或边上:本步必须在这个任务里写出过匹配的文件才能离开(退出码2)。{change}是任务名,*匹配一段路径内的字符,**匹配任意多段,?匹配一个字符。default用它要求 proposal、设计、delta spec、计划和验证报告。- 观察、交接、读取回执和必须产出对每条轨道、每个工作流同样生效,不依赖 OpenSpec。Spec Kit 风格的流程就是一个要求
specs/*/spec.md、plan.md、tasks.md的普通工作流。见文档、Skill 与证据链。 - 工作台显示每一步真实的输入(文件、来源阶段、来源技能、已读 / 未读)和输出(文件、新增 / 修改 / 删除、来源技能);工作流页不再有输入、输出表,门禁下列出「必须产出
<path>」。
测试设置简化
- 每步一个档位。工作流步骤接受
test: none | plan | changed | full,取代多字段test_policy(同一步不能两个都写;冻结的test_policy仍可读取)。default在 lite、frontend、backend 的实现步用changed、验证步用full,frontend 与 backend 的规格步用plan,pm 的规格步用none(它的验证步是changed)。默认的full不再把全部集成与端到端套件都跑一遍:全量跑单测类,其他类只跑改动碰到的。 levels。目录(.tenon/tests/catalog.yaml)接受可选顶层键levels,用六个键(required、touched、scenarios、coverage、flaky、require_baseline)定义plan、changed、full;没写的取内建缺省(required: [unit],不设覆盖率门槛,scenarios: off)。任务改了levels,tenon review request的改动摘要逐档逐键标出放宽或收紧。- 五个测试类。
unit、integration、e2e、static、benchmark(外加只在点名时运行的custom)把 17 个种类归类;套件的kind可写类名或旧种类名。 - 认领,不登记。本任务新增或修改的测试文件,由
filesglob 匹配它的套件认领,从不写进test-plan.yaml。没有套件认领的文件是孤儿(test-file-orphan),tenon test register <change> --auto把该文件的精确路径追加进合适的套件,不再放宽 glob,也不再扫整个仓库。openspec/、.tenon/、.pipeline/下的路径和没有源码扩展名的文件(例如.spec.report)从来不是测试文件。 - 没写
covers的套件。改动文件匹配套件的files或coversglob 才算碰到它。改动只含源码文件时,changed与full档不跑没写covers的套件,并给出不阻断的提示suites-without-covers点名它;要让源码改动触发它,用tenon test catalog set <id> --cover-glob "<glob>"写上covers。 tenon test sync <change> --prune从计划的files里删掉目录不再认领的登记,用来解除registered-test-not-executed。- 目录写回保留注释:
catalog add|set|rm|not-applicable与register --auto只改变化的行。场景映射默认关闭,没有场景会挡出口,追溯矩阵列出已映射的行,另加一行「另有 N 条…未映射」。 - 测试信任按用户、仓库、命令记录。
tenon test trust把决定存在你的用户状态目录,同一仓库的所有 worktree 共享,改一条命令只让这一条重新问。hook 拒绝 agent 写入test-trust/路径。 code-size默认没有上限。默认工作流和code-size方向里 2000 行的lines_added限制已删除;探针仍报指标、退出码0,实测值照样记进运行记录,code-size评审者读到它并据此判断。想要上限的项目在工作流里给该测试写pass.metrics。review request里列出的test:<id>豁免,现在在你的理由旁边显示最新实测值。- 运行记录带相对路径:算摘要之前,仓库根改写成相对路径,用户主目录改写成
~/。已有记录不回写。 - 测试输出不再让候选失效。
test-results/、playwright-report/、coverage/之下未跟踪、扩展名是证据格式(log、txt、xml、md、csv、png、jpg、jpeg、gif、webp、webm、mp4、lcov、info)的文件不计入候选指纹,所以 e2e 服务写日志不会让冻结的版本过期。已跟踪的文件、脚本、没有扩展名的文件,以及能被代码或浏览器加载、解包的格式(json、html、htm、zip、gz、har)仍然计入。 - Dashboard:工作流页用一个单选为步骤选档位,项目测试页显示生效的档位定义,任务测试页标出被认领的文件并收起场景追溯。
更少的确认
- 轮次内的回退边。受轮次上限约束的步骤(评审门且有回退边,例如带
verify-fail的验证)上,轮次还没用完时走回退边不需要评审回执,直接tenon transition。step.next对唯一的回退边直接给transition,不再给request-review;Dashboard 的转换接口行为一致。前进边、剩余阻断与测试豁免仍只认人工确认,轮次用完后回退边照旧被拒。 - 每次成功转换都会删除绑定本任务的评审标记(
.pipeline-pending-review),所以升级前已发起的评审请求、或前进评审待确认时你要求回去修改,都不会再留下一个最长 30 分钟内拦住写入的过期标记;删除失败只输出一行警告。 - 只读组合命令。评审、交互或确认待处理期间,写门放行由
|、&&、||、;连接、每一段都只读的命令(git status --short | grep x、cd sub && ls)。>/dev/null、2>/dev/null、&>/dev/null、2>&1以外的重定向、后台&、命令替换与变量展开(含$IFS)、引号不闭合、超过 2048 个字符的命令一律不算只读。部分只读命令的写文件或执行程序形态现在被拒:git diff|log|show --output、--ext-diff、--textconv,rg --pre、-z、--search-zip,sed -n非行号区间打印,file -C。 - 评审待确认时,门还恰好放行本任务(标记所属任务)的
tenon transition <change> <event>这一形态并交给 CLI 判定,所以你要求回去修改时 agent 走得了回退边(缺已批准回执的前进边仍由 CLI 拒绝)。 - 拦截提示按标记种类区分:评审提示写明任务与事件,列出放行语与 Dashboard 确认,不再提 AskUserQuestion。
修复
- 同一项目里并行的会话不再互相拦截、互相确认。以前,一个会话的评审待确认,会拦住另一个会话(做的是别的任务)的写入、技能加载和子代理;在另一个会话里回复一句话,就能替前一个会话写下评审回执;技能证据还会记到别人的任务上。现在拦截、代为确认、记证据的 hook 都用绑定在自己宿主会话上的任务。任务绑在别的会话上时,本会话既不被拦,也确认不了它;宿主不给会话 id 时行为与以前一致。
tenon session activate <name> --host-session <id>让一个任务同一时间只绑一个会话:删除其他会话对该任务的绑定,并在 stderr 点出旧会话。对话恢复后会话 id 会变,用新 id 重新激活该任务之前,这个会话的 hook 既不拦截也不确认。- 待处理的交互标记(技能在等你回答的问题)属于发起它的会话。门只拦这个会话,只有它的 AskUserQuestion 答案或放行语能解开,同时等待的多个会话也不再互相覆盖标记。旧格式的标记见到即删除。
- 粘贴进来的放行语不再批准评审。放行语只有在整条短回复就是它时才算(例如「确认继续。」);出现在更长的消息、引用或粘贴里不算,AskUserQuestion 看的是选中的答案,而不是问题文字。以前放行语在粘贴内容里会被子串匹配,可能让一次评审被记成已通过。
新增
- 验证轮次上限。设了评审门且有回退边的步骤(
default里是带verify-fail的验证)最多验证两轮,一轮就是进入这一步一次;经requirements-changed回到规格后重新计数。步骤用max_rounds(1 到 20)声明上限,没有声明的受限步骤取 2,tenon set <change> max_rounds <N>为单个任务覆盖。轮次用完、必需的测试或评审者仍不通过时,Tenon 不再自己回退:step.next请你在前进评审门接受剩余的评审者阻断,或以rounds-exhausted停下,并列出其他出路(调高上限、回到规格、终止任务)。CLI 与 Dashboard(HTTP409,code: "rounds-exhausted")都拒绝回退边。剩余阻断只认人工确认,绑定评审者的这次运行和当时的代码候选;--delegated与 AFK 模式永远接受不了,Dashboard 的「批准」也拒绝它们(HTTP409,code: "residual-pending"),请在终端确认。上限由你决定调高,agent 只在你明说时才改。见验证轮次上限。 - 步骤测试豁免。
tenon test waive <change> --test <step-test-id> --reason <text>放行失败的必需步骤测试,例如code-size。以前没有办法:step.next一直停在run-test,任务到不了评审门。豁免由tenon review request列出,经你的确认批准;它只覆盖在当前候选上仍然新鲜的失败,批准绑定该代码候选,所以代码变了再失败要重新批准。评审者看到的理由会标注为登记者自述。 tenon review revoke <change> --reason <text>撤回误给的批准:把当前步已批准、未被消费的回执撤回为同一 event 的「待确认」,恢复评审标记,并在任务历史里留一条撤回记录。- 源码开发安装,用于在 Tenon 检出里工作。
tenon setup --claude --from-source <repo>(Codex 用--codex)构建该检出、获取缺失的上游技能,并用该检出的插件与托管运行时取代发布版,所以 agent 加载的技能、hooks 与 CLI 都是检出里的。改完检出后重跑同一条命令即可同步(构建已是最新时可加--skip-build,--dry-run先预览)。它不冒充发布版:/api/health报告channel: dev和<version>+dev.<sha7>,tenon doctor把identity:release显示为警告,自动更新关闭,tenon update拒绝它;tenon update --claude --to-stable(Codex 用--codex)切回最新的正式版。在检出里新开会话时,若已装内容与它不一致会有提示,tenon doctor报告source:drift。--from-source会运行所给检出的构建,并把该检出的 hooks 长期装进宿主,只对你信任的检出使用。见贡献者开发。
行为变化与升级影响
standard、simple已删除,chat、free轨道也一样。tenon init <name> --workflow standard与--workflow simple退出码1,并提示改用--workflow default --track lite。把已退役的轨道 id 传给--track退出码1,并列出该工作流可选的轨道。tenon init <name> --workflow design-system不写--track。升级前创建的任务,不论属于哪个工作流或轨道,都按它冻结的快照走完。.pipeline/tracks.yaml与内建轨道登记表不再读取。 文件还在时 Tenon 只打印一行提示。把你留在里面的路由词写进工作流分支的route。- 「隐藏」已删除。
tenon task archive、tenon task unarchive、工作台「已归档」开关和每用户的archived.json都不再存在;两个旧子命令退出码1并指向tenon task terminate与tenon task finish。此前被隐藏的任务重新出现在工作台里。 - 新任务的字段和检查变少。
build_mode、isolation、direct_override、branch_status及其检查,statefile、立项字面路径、规格 coverage 矩阵的检查都已删除;升级前创建的任务保留自己的字段。manifest 的技能矩阵(mandatory_skills)、POST /api/changes(Dashboard 不再新建任务)和路由预览已删除,技能只来自工作流 YAML。default的离开条件不满足时退出码是2,不再是1。 - 工作流定义拒绝文档契约的键。
document_contract(顶层或轨道分支下)以及步骤的inputs、outputs、artifacts已删除,nonempty-output守卫也一并删除。保存工作流、创建任务和编译新计划时都会报错并点名替代写法:文件条件写required-output守卫,字段条件写field-nonempty或field-equals。空的inputs: []、outputs: []、artifacts: []仍然能加载。gate: auto不再由步骤的outputs推导,要把那些检查改写成守卫。要为验证步冻结构建修订的新自定义工作流,要像default一样自己写build-complete边上的freeze-build-sha和verify-pass边上的build-head-unchanged。见自定义 Workflow 与 Track。 - 新任务没有 proposal、设计骨架,也没有文档账本(
.pipeline-documents.json);spec: openspec的轨道只铺tasks.md骨架。tenon document scaffold、record、init、migrate-delta(退出码2)和tenon artifact register(退出码1)在新任务上被拒,并告知产出由 Tenon 自动记录。frontend 轨道不再要求立项前DESIGN.md就绪;DESIGN.md的有效性仍由交付步的design-system测试检查。工作流 API 不再返回effectiveIo,GET /api/change/:name/orchestration的io字段换成stepIo。升级前创建的任务按它冻结的计划走完,旧文档契约也一样。 - 轨道只有自己声明了才能进 AFK。 任务能否入队只看它所在轨道的
automation.eligible。default的pm、frontend、backend轨道可以;lite轨道和没写automation的轨道(平铺工作流都算)不可以(tenon afk enqueue退出码3,带--json时reason: "track-not-afk-eligible")。 - 设了评审门且有回退边的步骤最多验证两轮,自定义工作流里没有声明的步骤也一样。要改就在该步骤上写
max_rounds。 - 测试设置改变了「通过」的要求。 默认的
code-size测试不再因为新增 2000 行而不通过,默认的full不再把全部集成与端到端套件都跑一遍,场景映射也不再挡出口。default原来在 frontend、backend 验证步要求的 80% 行覆盖率门槛已删除:想要它的项目在目录里写levels.full.coverage。 - 每个 git 仓库的测试信任要再问一次。 写在检出封存里的信任只在不是 git 仓库的项目里继续有效(那里新的信任也写进检出封存);git 仓库里一律不认检出里的旧封存,不论它是否被 git 跟踪,因为封存和它的密钥可能随仓库或压缩包带来。只有检出目录及其所有上级都没有
.git才算「不是 git 仓库」;有.git却读不出仓库身份(git 出错、超时或伪造的指针)一律未信任:tenon test run提示先修好 git,tenon test trust拒绝写入。 - agent 不能替你确认评审。 写门拒绝 agent 执行不带
--delegated的tenon review acknowledge。请用整条回复就是放行语的方式确认(由 hook 写入回执)、在 Dashboard(tenon dashboard --open)里确认,或在自己的终端里运行tenon review acknowledge <change-name>。 - 源码开发安装会取代发布版,直到你切回去。
tenon update拒绝它;用tenon update --claude --to-stable(Codex 用--codex)切回。
开发
- 在隔离 runtime 里启动
tenon子进程的测试或验收脚本,必须去掉启动器导出的本机运行时根(TENON_RUNTIME_ROOTS以及TENON_RUNTIME_DATA_ROOT、_STATE_ROOT、_CONFIG_ROOT):用isolatedEnv,或让环境经过tools/lib/runtime-roots.mjs的withoutInheritedRuntimeRoots。否则经tenon test run运行的套件会读写你真实的 Tenon 状态。
文档
- 安装页写明:已退役的 1.x 版本与标签已于 2026-10-07 删除,维护者保留归档。
升级动作
运行 tenon update --codex(或 --claude),新开宿主会话,然后:
- 把脚本和笔记里的
--workflow standard/--workflow simple换成--workflow default --track lite,design-system任务去掉--track。 - 把
tenon task archive <name>换成tenon task terminate <name>(放弃任务)或tenon task finish <name>(已走到终点的任务)。 - 把
.pipeline/tracks.yaml里的规则写进工作流分支的route。 - 如果你在全局存储里保存过自己的
default,它照常加载,但缺lite分支的副本会让tenon assess报「没有改动风险阈值」而不是去猜;要用评估,就加一条带diff-risk测试的lite分支。 - 如果你的工作流保存失败并点名
document_contract、inputs、outputs或artifacts,删掉这些键;步骤必须产出的文件写required-output,字段条件写field-nonempty或field-equals。读工作流 API 的effectiveIo或GET /api/change/:name/orchestration的io的脚本,改读stepIo。 - 在每个 git 仓库里,首次运行测试之前,在你自己的终端里运行一次
tenon test trust。 - 脚本和笔记里“先为回退边请求评审、再走回退边”的步骤,轮次没用完时可以去掉:
tenon transition <name> verify-fail自己就能成功。前进边不变。 - Codex:hook 文件变了,Codex 可能要求你重新信任。运行
/hooks,信任tenon,再新开会话(见安装)。tenon update若提示AGENTS.md里的块已过期,运行tenon sync --migrate。 - 回滚到更早的版本之前,先完结或终止升级后新建的任务,并把
levels从.tenon/tests/catalog.yaml里删掉(见「兼容性」)。
兼容性
N-1 是 v0.3.2。N-1 兼容门禁(tools/test-bundle.sh)每次运行都在两个方向上让它与本版本互相读写。
- 门禁交叉读写测试记录链、agent 运行台账及其旁注、测试计划、
discover --write写的目录、被TENON_RECORD_RETENTION清理过的记录链,以及旁边有未跟踪的.claude/settings.local.json时写下的记录。它还核对 v0.3.2 读本版本在default重型轨道上创建的任务时,只出现第三条所说的那一行警告。下面其余的限制是已知的,门禁没有交叉测。 - v0.3.2 读不了步骤的
test键、步骤的max_rounds键和目录的levels键:读带这些步骤键的工作流会失败,读带levels的目录会报未知键。回滚之前,先完结或终止用这些键建的任务,并把levels从.tenon/tests/catalog.yaml里删掉。 - v0.3.2 不认识三个通用离开条件,也不认识
required-output守卫。本版本在default重型轨道上创建的任务,tenon status里只显示一行警告来代替 step 投影。v0.3.2 会直接拒绝它不认识的轨道上的任务:lite,以及没有tracks:的工作流(design-system或你自己的平铺工作流,轨道记的是工作流名)。回滚之前先完结或终止这些任务(见更新、恢复与卸载)。 - 指纹规则变化:工作区里有未声明测试输出(例如未跟踪的
test-results/**/*.log)的在途任务,升级后第一次校验可能出现一次revision-stale,因为那个文件不再计入。按冻结版本过期的惯常办法处理:回到实现步重新捕获当前版本。没有这类文件的任务不受影响。 - 新增的命令与选项:
tenon assess、tenon tracks list与show、tenon task finish与terminate、tenon list --finished、tenon init --domain、tenon review revoke、tenon test waive --test、tenon test sync --prune、tenon set <change> max_rounds、tenon setup --from-source、tenon update --to-stable。已删除:tenon task archive与unarchive、POST /api/changes、路由预览,以及接口响应里的effectiveIo与io字段。
验证
tenon --version
tenon doctor
tenon runtime status
tenon tracks list
tenon init demo --workflow standardtenon --version 打印 0.3.3。tenon doctor 里 identity:release 列出宿主插件、runtime 和 Dashboard server 都是 0.3.3。tenon runtime status 显示当前 release 为 valid=yes。tracks list 列出 default 的 lite、pm、frontend、backend、design-system 和你自己的工作流的轨道。最后一条退出码 1 并提示 --workflow default --track lite(demo 只是示例名,没有创建任何东西)。升级后创建的任务,tenon document status <name> --json 报告 mode: "observed" 以及 inputs、outputs、required;升级前创建、带旧文档契约的任务,tenon status <name> --json 里是 step.documents.mode: "contract"。
v0.3.2 · 2026-10-07
v0.3.1 的修复版,来自它的补充验收。tenon runtime repair --rollback 不再让安装卡死,任务开始之前就已提交的 not_applicable 条目在批准之后不再挡住任务,tenon test discover 写出的基准命令能在 vitest 5 上运行。
修复
- runtime 回滚卡死。从 v0.1.0 到 v0.3.1 的每个版本里,
tenon update之后运行tenon runtime repair --rollback都会失败,报 “rollback refuses a third-party launcher checkpoint”,并让安装卡住:之后回滚、更新、setup 都被拒绝。 - 回滚现在先证明 launcher 一对,再翻转选择,所以被拒绝的回滚不会改动任何东西。
tenon update和tenon setup会收尾旧 bootstrap 留下的半截回滚,而不是拒绝。 - 已经卡住的安装,为该宿主运行一次 v0.3.2 的版本化
install.sh。见回滚 更新 setup 都因未完成的回滚被拒绝。 - 任务开始之前,测试目录里就已有、还没批准的
not_applicable条目,在你批准之后不再挡住任务(影响 v0.3.0 和 v0.3.1)。 - 以前批准会把批准人写回
catalog.yaml,这次改写被算成没批准的受保护文件改动(protected-file-unapproved),review request又拒绝第二次请求。 - 已经卡住的任务不会被这次更新修好:用
tenon task archive <name> --yes归档它,再新建一个任务,新任务里确认一次目录改写即可。见批准不适用声明之后任务卡在 protected-file-unapproved。 - 回滚之后,
tenon doctor把identity:release显示为警告而不是失败,并给出两条出路:tenon update回到较新的 release,tenon setup把宿主插件绑到当前 runtime。 - 只有回滚是最近一次 runtime 事件、且宿主插件正是被回滚掉的那份时才是警告,其他不一致仍是失败。
- 把宿主本地文件排除在测试记录的候选之外,现在在更多情况下失败关闭(除非 git 证明它没被跟踪,否则这个文件照算):
- 损坏的
.git(空的、损坏的、读不了的,或者 gitfile 指向的目标已不在); - 大小写不敏感的文件系统:被跟踪的
.Claude/Settings.local.json现在按被跟踪的.claude/settings.local.json算; - 项目上层的仓库代替项目自己的
.git作答; - 继承来的
GIT_DIR、GIT_INDEX_FILE等变量,现在检查时会忽略它们。
- 损坏的
tenon test discover写出能在 vitest 5 上运行的基准套件(用--reporter=json,因为 vitest 5 删掉了--outputJson)。- 基准解析器把 vitest 5 的 JSON 报告读成同样的
<名字>.mean_ms、.p99_ms、.hz指标。 - vitest 5 工程的 bench 文件还在从
vitest导入已被删掉的模块级bench时,discover 只给提示、不写套件。 - 更多
agent和review的输出跟随TENON_LANG:agent next的各行、其他agent prompt与agent record的错误,以及review request、review acknowledge的信息。目录里还没有的字符串仍是中文。 - 新建项目向导确认步的预检有了 15 秒的期限。超时或网络错误会显示错误并给「重试」,预检进行时「上一步」仍可用,卡住的请求不再锁死向导。
新增
tenon-verifyaction 有了可选输入sarif-checkout-path,给提交在子检出里的仓库用,另有输出sarif-id(code scanning 上传的 ID)。输入留空时行为不变。
行为变化
- vitest 4 及更早的工程里,两个基准重名(或名字只有标点不同)现在让基准报告解析失败,并点出重名的那个。以前后一个结果会悄悄盖掉前一个。
- 读不出 vitest 主版本时,
tenon test discover不再写基准套件,改为打印两条catalog add命令。读不出的情况有:vitest 没装且package.json里没有能定出主版本的范围(latest、workspace:*、>=3),或者已安装的 vitest 的package.json读不出来。
开发
- Tenon 自己的测试跑在 vitest 4.1.11 上(之前是 3.2.6),
tinypool的 override 已去掉。 - CI 现在在
verify作业里以阻塞步骤运行更新与回滚验收(npm run test:update-rollback,含从卡死的回滚中恢复),以及真装npm install vitest@5的基准检查。 tenon-verifyaction 有了自己的自测工作流(verify-action-selftest.yml):在真实运行器上运行 action,并验证 SARIF 上传确实到了 code scanning(fork 的 PR 没有写令牌,跳过这一步)。改动 action 或verify --ci代码的 PR 会触发它,也可以手动触发。
升级动作
运行 tenon update --codex(或 --claude),新开宿主会话。
如果你没有运行过 tenon runtime repair --rollback,只需要 tenon update,之后的回滚在 v0.3.2 上可用。版本化的 install.sh 只用于已经卡住的安装。
如果之前的回滚把你卡住了(tenon runtime repair --rollback、tenon update、tenon setup 都报「存在未完成的 runtime rollback」而拒绝),tenon update 帮不上忙。为该宿主运行一次版本化的 install.sh:
/usr/bin/curl -fsSL https://raw.githubusercontent.com/jefferysha/tenon/v0.3.2/install.sh | /bin/bash -s -- --codex
# 或
/usr/bin/curl -fsSL https://raw.githubusercontent.com/jefferysha/tenon/v0.3.2/install.sh | /bin/bash -s -- --claude兼容性
N-1 是 v0.3.1。N-1 兼容门禁(tools/test-bundle.sh)每次运行都在两个方向上让它与本版本互相读写。
- 没有改任何记录、状态或台账的 schema,也没有新增或改动 CLI 命令、CLI 选项和 Dashboard API;唯一新增的接口是上面「新增」里
tenon-verifyaction 的可选输入与输出。普通工作区里两个版本绑的是同一个可移植指纹,所以互相读对方写下的测试记录都是新鲜的,旁边有未跟踪的.claude/settings.local.json时也一样。门禁拿真正的 v0.3.1 CLI 核对过。 - 门禁还交叉读写 agent 运行台账、
TENON_RECORD_RETENTION的记录链、discover --write写的目录,以及带符号链接和 664/775 权限位的树。 - 上面宿主本地文件的几种情况里,0.3.2 现在会计入 0.3.1 排除掉的文件,两个版本对那个工作区算出的指纹不同。门禁没有交叉测这些情况。
验证
tenon --version
tenon doctor
tenon runtime statustenon --version 打印 0.3.2。tenon doctor 里 identity:release 列出宿主插件、runtime 和 Dashboard server 都是 0.3.2。tenon runtime status 显示当前 release 为 valid=yes。tenon update 之后运行 tenon runtime repair --rollback 退出码为 0,tenon runtime status 显示你回滚到的那份 release 为 active。
v0.3.1 · 2026-10-07
v0.3.0 的修复版,来自它的真实宿主验收。作者的工作区里有宿主本地文件时,tenon verify --ci 现在能在干净克隆上复现你的测试记录,也不再判定被有意放弃的任务失败。跨厂商评审的 CLI 提示、英文轨道标签和登录命令修好了,新增 tenon --version。
修复
- 干净克隆不再因为从不提交的宿主本地文件让
tenon verify --ci报candidate-mismatch:.claude/settings.local.json、项目根目录的CLAUDE.local.md和.claude/worktrees/。见候选代码不一致。 - 0.3.1 写下的测试记录绑的指纹不含这些文件,而且只在 git 没有跟踪它们时才不含:被跟踪的路径仍然计入。除此之外不按名称排除任何东西。
- 在一个平台上写下的测试记录现在能在另一个平台上验证(macOS 与 Linux CI)。0.3.1 的记录绑的指纹按 git 的方式记录权限位:普通文件按属主的可执行位记成 644 或 755,目录没有权限位,符号链接只记目标。
- 以前记的是原始权限位,而同样的提交内容这些位并不相同:符号链接在 macOS 是 0755、在 Linux 恒为 0777,文件和目录跟着 umask。所以带符号链接、或在 umask 不是 022 下写出的仓库,另一个平台的 runner 永远复现不了它的指纹。
candidate-mismatch的提示会写出它能确定的路径:记录之后的第一个提交之后又改过的候选文件,本次检出里候选范围内被 gitignore 或未跟踪的文件,以及被跟踪的宿主本地路径。交付提交自己带的文件不再被怪到头上。- 沿
scope-expanded放弃边离开的任务(例如升级到default的standard任务)不再被verify --ci判定测试证据,报告里留一条change-abandoned提示。受保护文件的批准照查,按正常级别报告。 - 只有任务提交的转换链里真的有这条放弃边才算;状态里只写着
phase: escalated的任务照旧判定。 tenon evidence export是确定性的:每个时间戳都取自证据本身(记录链里最晚一条记录的完成时间),不取时钟,所以导出两次打印的字节相同。- 跨厂商评审的几行(
agent prompt的路由说明、agent record对宿主的拒绝)和review acknowledge的拒绝信息跟随TENON_LANG;之前它们一直是中文。 - 英文 Dashboard 里选中的轨道标签保持完整可见:用
track=backend直接打开页面不再把标签截在渐隐里,名称变宽(切换语言)或页签条变窄时它会重新滚进可见范围。你自己滚动、点击或聚焦过的标签,不会被尺寸变化拉回去。 - 登录页和 Dashboard 里的
--open不再被画成––open:等宽字体里的命令、路径和 id 关掉了字体连字。文本本身一直是 ASCII。 tenon --version(及-V)打印命令所在插件载荷的版本号。
变化
- 手动
/tenon <请求>(以/开头的提示词不经路由钩子)由tenon技能选通道:用户点名的工作流或轨道优先,实现类请求用standard,只有重型或跨领域的工作(架构、鉴权、迁移、依赖、契约)用default,项目自定义的工作流只在用户点名时用。 step.next里只剩一条transition、且它是scope-expanded升级时,技能直接执行,interactive 模式也一样:先用一句话告诉你为什么升级、接下来做什么,然后动手。- 已完结的任务因为 CI 对它的判定对象是本次检出的树、而不是它完结时的提交而失败时,
tenon verify --ci会多给一条finished-judged-at-head提示。判定结果与之前相同。
文档
- 读测试记录的受支持办法是普通读取工具(
cat、ls、jq、grep、head、tail、wc、find -name)或编辑器的 Read 工具。经内联解释器代码(node -e、python -c)或xargs的读取,以及后面链着任何写入的读取,都会被写入门拒绝;这道门本身没有改。见安全模型。
升级动作
运行 tenon update --codex(或 --claude),新开宿主会话。无需其他操作。
如果 verify --ci 对 0.3.0 或更早版本写下的记录已经报 candidate-mismatch,而写它们的工作区里有上述某个宿主本地文件,用 0.3.1 对该任务运行一次 tenon test run <change> --stage,再提交新记录。
兼容性
N-1 是 v0.3.0。N-1 兼容门禁(tools/test-bundle.sh)每次运行都在两个方向上让它与本版本互相读写。
- 记录 schema 没有改:v0.3.1 的记录仍然只绑一个工作区指纹。普通的树(没有未跟踪的宿主本地文件、目录 755、文件 644 或 755、Linux 上没有符号链接)上,可移植指纹与完整指纹相等,v0.3.0 把这条记录读作新鲜。
- v0.3.0 及更早版本算的完整指纹在每个平台上逐字节不变;N-1 兼容门禁拿真正的 v0.3.0 CLI 在带符号链接和 664/775 权限位的树上核对过。
- 写它的工作区里有未跟踪的宿主本地文件、Linux 上有符号链接、或权限位不是 755/644(例如 umask 002)时,v0.3.0 会把 v0.3.1 的记录读作过期(「代码已变化」,退出码
2),不会读作损坏。这源自它算的完整指纹,N-1 兼容门禁核对过(见上一条)。 - v0.2.1 算的完整指纹与 v0.3.0 相同,所以行为一样;兼容门禁不运行 v0.2.1。用你回退到的版本重跑套件即可。
- v0.3.0 的记录继续有效:v0.3.1 认两种指纹,所以 v0.3.0 写下的记录在写它的那台机器上是新鲜的。
verify --ci多了两个提示级的发现代码:change-abandoned和finished-judged-at-head。除--version外,没有改变任何命令、选项、项目文件或 Dashboard API。
验证
tenon --version
tenon doctor
tenon runtime statustenon --version 打印 0.3.1。tenon doctor 里 identity:release 列出宿主插件、runtime 和 Dashboard server 都是 0.3.1。tenon runtime status 显示当前 release 为 valid=yes。在一个由 0.3.1 写下记录的项目的干净克隆里,tenon verify --ci --change <name> 不再因为上述宿主本地文件报 candidate-mismatch。
v0.3.0 · 2026-10-06
v0.3 让 Tenon 在普通项目里能用,也更难被糊弄。小改动现在走四步的 standard 标准通道,默认测试流程只要求 unit。任务提交的测试证据可以在 CI 里只凭仓库重新校验(tenon verify --ci 与一个 GitHub Action),由本机信任根和完整性报告守着,评审者还可以被要求在另一家厂商的 CLI 上运行。Dashboard 需要登录,工作流画在分色带的画布上并带 Signal 流动动画,30 个项目的快照小于 1 MB,英文界面的阻断标签不再解析中文文本,CI 里还有 axe 无障碍检查。tenon support bundle 与 tenon logs 负责诊断。本版本包含 v0.2.1 的 launcher 修复。升级项目前请先读「行为变化与升级影响」。
标准通道
standard是内建的工作流与轨道:open → build → verify → done,没有 Explore 阶段,也没有 OpenSpec 文档。Build 跑单测和改动风险探针,Verify 只有一道评审门,评审者是code-review和security(后者只在碰到鉴权、依赖或契约路径时挂载)。- 实现类请求(修、加、重构,中英文都认)路由到
standard;带重型信号的请求(架构、schema、鉴权、依赖升级、发布)仍走default。见轻量轨道,它后来取代了标准通道。 - 看上去在改代码、却没有任何轨道命中的提示,会收到一行「未被治理」的说明。
tenon test diff-risk [<change>] [--json]是 Build 要求的改动风险探针:源码文件多于 8 个、动了契约、鉴权、依赖或迁移路径、删了测试、改了测试配置,任一项都不通过(阈值是pass.metrics)。- 探针不过时,
step.next只剩一条带escalate载荷的scope-expanded转换,提示改开default任务。 tenon step run <change> [--json]一次做完step.next里确定性的部分(铺文档骨架、登记已写好的文档、读取回执、生成测试计划初稿),遇到需要宿主或作者的动作就停下。tenon document record <change> --all登记当前步骤所有文件已写好的文档;没写完的会列出并跳过。- 新增官方评审者
code-review:对照宿主附在提示末尾的目标与验收标准看 diff,不需要规格文档。官方智能体现在是十个。 - 评审者可以声明
attach_on(auth、dependency、contract、migration),任务碰到这些路径类才挂到步骤上。官方security(1.1.0)只在[auth, dependency, contract]时挂载,这个默认值写在 CLI 里,不在 agent 文件里。 - Verify 只跑一遍项目的 Playwright 套件:默认工作流里内联的
playwright步骤测试没有了,e2e评审者(1.1.0)也不再重跑。 - 一条集成测试把三个文件的缺陷修复从头走到尾,断言最多 25 次
tenon调用、2 次用户回复。
CI 校验与证据导出
tenon verify --ci (--change <name> | --all-open | --since <ref>)不用你的本机密钥就能重新校验已提交的证据:记录链、计划与记录与目录一致、用例级判定、候选代码、变更过的测试配置的批准行、锚点和测试完整性。退出码0通过,2有错误级发现,1是用法错误。- 格式有
text、json、sarif(2.1.0)和markdown(作业摘要),用--format、--out、--also输出。每份报告都列出没有本机密钥时 CI 证明不了什么。 - 测试完整性按步骤的
integrity策略判定:block把信号变成错误test-integrity(退出码2;浅克隆读不出改动行,则以files-diff-unavailable失败),缺省的notice只给一条永远不让运行失败的提示。 - 输出语言跟随 CLI 语言(
TENON_LANG=en|zh,其次LC_ALL、LC_MESSAGES、LANG;都没设时是中文):文本、Markdown、SARIF 消息、JSON 的message字符串和用法错误。发现码、级别、退出码和 JSON 字段名不变。 - 复合 GitHub Action
.github/actions/tenon-verify在 Pull Request 上运行它,用所固定发布里的 CLI bundle,追加作业摘要并把 SARIF 上传到 code scanning。它的language输入(zh或en)决定报告语言。见 CI 校验。 tenon evidence export <change> --format agent-trace|otel|git-notes|trailer输出 Agent Trace v0.1 记录、OTLP/JSON GenAI span(不会发往任何地方)、refs/notes/tenon里的 git note,或Tenon-Change:/Tenon-Evidence:提交尾注。默认只打印,--apply才写 note 或 amendHEAD。--anchor(git-notes)把记录链头抄进 note;verify --ci报告anchor-mismatch或anchor-behind,--require-anchor让缺失或落后的锚点变成错误。
测试完整性与证据可信根
test_policy.integrity: notice | block(默认notice)与tenon test integrity <change> [--step <id>] [--json]报告任务开始以来证据变弱的十种信号(用例变少、跳过变多、测试被删或被缩短、断言被删、快照被改写、基线被改、新增已知失败、覆盖率门槛调低)。notice在status和 Dashboard 测试页签里给一条test-integrity提示;block在该步骤把它变成阻断。- 报告必须比本次运行开始得更晚,并连同摘要复制进该次运行的产物目录,否则这次运行以
report-untrusted失败。工作区指纹只忽略目录或工作流里声明的输出路径。 - 本机封存(
<用户目录>/local/test-seal.json,旁边是 HMAC 密钥,gitignore)保存每条记录链的链头、你的批准和信任决定。链头没被封存的链是record-unsealed,下一次运行另起新链。 - 任务 diff 里出现
.tenon/tests/catalog.yaml、baselines/**、known-failures.yaml、.pipeline/workflows/*.yaml的改动,会挡住每个gate: review步骤,直到你确认;tenon review request逐项列出,--delegated被拒绝。已知失败必须指向一个用例,期限不超过 30 天。 tenon test trust [<change>] [--yes] [--status] [--json]:tenon test run拒绝运行你还没有在本机信任的目录命令与内联测试命令。CI 用TENON_TEST_TRUST=1声明信任。- 写入门识别 13 种写入证据文件的 shell 写法(
python -c、curl -o、tar -x、git apply、xargs sh -c、tee、重定向等),并拒绝 agent 的命令里出现tenon test trust。匹配是静态、尽力而为的,其余由封存兜住。 - 在没改的代码上已有结论的评审者,不能靠重跑换成通过:
tenon agent prompt会拒绝(退出2),除非加--rerun-reason <text>,该候选上最严的结论算数。Dashboard 显示reruns。
跨厂商评审
- 工作流步骤可以要求评审者在
host: codex | claude | any上运行(Claude 写、Codex 审);智能体文件可以用host:建议一个(tenon agent new --host)。步骤的声明有约束力,智能体的只负责路由。 - 在另一个宿主或纯终端里,
tenon agent prompt把提示写到openspec/changes/<change>/.pipeline-agent-reports/<run-id>.prompt.md,打印codex exec …或claude -p …命令和tenon agent record那一行。Tenon 从不启动另一家的 CLI:由你,或当前宿主里的 agent 去运行。 tenon agent record <change> <run-id> --host <host>记录宿主(detected或declared)。裁决绑定被评审代码的内容哈希和宿主:宿主不对或未知的登记被拒绝(退出2)。- 工作流页可以编辑评审者的
host。智能体运行抽屉并排显示记录的宿主和要求的宿主、绑定的候选;要求的宿主上还没有有效运行时,对任务当前及之后的步骤给出可复制的tenon agent prompt <change> <agent>命令。Dashboard 从不运行它。
测试流程
- 默认策略只要求
unit。typecheck、integration、regression、e2e、playwright、a11y、visual、benchmark、smoke在项目里有时才运行;覆盖率门槛只作用于目录条目声明了coverage的套件;全量运行的unit套件满足regression。 tenon test catalog not-applicable <kind> --reason <text> | --rm声明整个项目都不适用的种类;经过一次人工review acknowledge(不能--delegated)才生效。- 项目没有目录时
tenon init会运行tenon test discover --write,tenon test register <change> --auto认领无主测试文件、生成计划初稿并登记。JavaScript 单测的 glob 现在覆盖src/、test/、tests/、__tests__/和根目录的*.test.*。
Dashboard
- 登录:没有会话时,
GET /和所有/api/*请求都返回401,只有/api/health与/assets/*例外。tenon dashboard --open铸一条一次性登录链接(2 分钟、只能用一次)并由 server 自己打开浏览器,不往磁盘写任何 token。见登录。 - 登录页用 Dashboard 的设计 token 画成,浅色、暗色都有。它只用一种语言:浏览器的
Accept-Language把英文排在中文之前时是英文,否则是中文,并带一个可复制的命令。 - 用过、无效或过期的链接有自己的页面,样式相同,同样给出这条命令和一个「继续」链接。
- 批准评审需要有人在场:第二次点击才会申请一枚一次性 nonce(30 秒),它绑定你的会话、任务和评审修订号。终端里的
tenon review acknowledge不变。 - Signal 取代逐边脉冲:一条恒速传送带,四层彗星从当前节点出发,停在评审门前。
prefers-reduced-motion时只显示静态高亮;画布离屏或标签页隐藏时动画暂停。 - 总览是无框的列,按宽度适配,每列一根脊线,每个并行波次一道括线,语义缩放,点列头聚焦到该阶段;节点高 40 px,带状态符号,阶段条 3 px。
- 页面:工作台的「下一步」把同类阻断合并成 40 px 的行;测试页签有四个等宽数字、「完整性」区块和「N 可选」;技能页的引用来自编排接口;计数变化纵向滚动。
- 测试页签把不阻塞的提示(已修好的已知失败、基准波动、未检查的文件)放进单独的「提示」区块,「阻塞」表和它的计数只含真正的阻塞。
- 英文的计数词随数量变化:测试页签的汇总写 Suite 或 Suites、Case 或 Cases。
- 轨道页签条在还有隐藏页签的一侧渐隐,英文长名字不会被从中间截断;选中的和键盘聚焦的页签会被滚到渐隐区之外。
- 规模:每个项目一个快照缓存单元,写请求只清掉它点名的项目。Dashboard 读
GET /api/snapshot?view=list与/api/stream?view=list(先发完整帧,之后只发snapshot-delta),任务的证据从GET /api/change/:name/snapshot读。 - 30 个项目 × 30 个任务时列表体约 0.6 MB,原来的单份快照是 11 MB;写入后重建列表的 p95 保持在 1.5 s 以内(
bench-snapshot-large把关)。 - 英文:step-exit 阻断带
code、subject、state、count,据此生成标签,不再解析中文整句(整句留作 tooltip)。<html lang>在首帧前就跟随所选语言。 - 英文界面里,内建工作流(
default、standard、design-system)的阶段、轨道和测试种类名,在存下来的名字仍是出厂中文名时显示英文;你改过的、自建的名字原样显示,也不会写回。表格按内容定宽,所以「Integration」这类词不再被截断。 e2e/dashboard/a11y.spec.ts在亮、暗两种主题下用 axe-core 扫主要页面,出现任何serious或critical违规即失败。- 无障碍修复:键盘聚焦的轨道页签的焦点环不再被页签条裁掉;技能编排器里已放置的行现在是禁用的,而不只是变淡;技能和智能体编排器里可用的「完成」按钮(以及其他实心强调色底)在暗色下对比度只有 1.96:1,现在改用主按钮配色(4.84:1)。
支持、日志与语言
tenon support bundle [--out <path>] [--json]写出本地.tar.gz(权限0600,不超过 5 MiB):版本、doctor、runtime status、配置摘要、最近的 Dashboard 日志。token、cookie、登录码、邮箱、home 路径和用户名会先被抹掉;命令列出包里有什么。什么都不会上传。- Dashboard server 写
<state>/logs/dashboard.log(轮转,三个文件各 1 MiB,凭证已脱敏),用tenon logs [--follow] [--lines <n>]读取。 TENON_LANG=en|zh(其次LC_ALL、LC_MESSAGES、LANG)决定 CLI 帮助和常见错误的语言;JSON 字段与退出码不变。非法的tenon transition事件现在会列出当前步骤的合法事件。
CI 与平台
- CI 在三处阻塞:
verify(Node 22,Chromium)、node-matrix作业和独立的 WebKit 作业。npm test带TENON_E2E=1。 node-matrix在 Node 20、22、24 上运行测试体系、reporter 与解析器套件、tools/下的node:test脚本和 Dashboard 的 Chromium e2e,不是整套测试。Tenon 本身需要 Node 22 或更新;Node 20 在矩阵里,是因为被测项目可能仍在 Node 20 上跑自己的node:test,reporter 与解析器必须读得懂它的输出。- 测试目录可以设置
profile: coarse:机器画像是 OS、架构、核数、Node 主版本加profiles_env(例如linux-x64-4c-node22-1a2b3c4d),同规格的托管运行器共用一份基准基线。 tenon doctor报告env:platform(macOS、Linux、WSL 为绿,原生 Windows 为红并指向 WSL 2,其他为黄),见支持矩阵。Node 20 不支持运行 Tenon。tenon test run把正在运行的tenon放到测试进程PATH的最前,所以tenon test code-size --json不再以 127 退出。
修复
- v0.2.0 的 2000 行
code-size上限从未进入冻结计划,2036 行的候选也能通过。现在新任务在lines_added超过 2000 时测试失败。 tenon test catalog add --report-format exit-code可以用了,服务就绪失败会说明原因。- Dashboard 的智能体详情与删除能看到项目级工作流的引用,仍被引用的智能体不能删。
- 等待中的 interaction 或 confirm 标记不再挡住运行中子代理的工具。
- 交付提交不再带
.pipeline-owned.json、test-results/、playwright-report/和根目录的coverage/;删除或归档任务会清掉它生成的宿主智能体文件;完结的 design-system 任务会提交DESIGN.md。 - 智能体文件里的技能带
tenon:前缀(裸的deep-research曾被拒绝);tenon agent validate拒绝骨架占位符。没有文件归属的用例显示「未报告文件」,不再是(unknown)。 tenon update会提示AGENTS.md里 Tenon 受管块已过期的项目运行tenon sync --migrate。
行为变化与升级影响
- 确认评审只认负责人。 非负责人运行
tenon review acknowledge会被拒绝。用--as reviewer(历史里记as=reviewer owner=<id>)或tenon owner take <change>。Dashboard 的「批准」不变。 - Dashboard 需要登录。 匿名请求得到
401,只有 health 和静态资源例外,server 重启会让你退出登录。运行tenon dashboard --open;原来用curl的地方改用 CLI 读取;没有浏览器时在终端运行tenon dashboard --port <端口>取登录链接。 - 带
profile:的目录会被旧版 Tenon 拒绝,带not_applicable:的也一样。等所有读这个仓库的人都更新之后再加;不带它们的目录读法和以前一样。 - 默认测试流程只要求
unit。 更新前已开始的任务沿用冻结的计划。想要求别的种类,在你的工作流test_policy里声明。discover 认不出你的npm test时,登记一个unit套件(tenon test catalog add)或声明unit不适用,否则 Spec 会以test-kind-missing停住。 code-size上限开始生效,对新任务而言(见「修复」):拆分任务,或调高工作流里的pass.metrics。scope-expanded是放弃边,simple与standard都是:从它离开不要求测试、评审者、文档或技能证据,工作区保持未提交,交给新的default任务。无需操作。- 路由把小的实现类提示送到
standard,不再启动七阶段的default。要留在default,在提示里点名轨道(例如「用 backend 轨道」)。 security评审者只在 diff 碰到鉴权、依赖或契约路径时才挂载。 想让它每个任务都在:tenon agent copy security <名字>,删掉attach_on那一行,在你的工作流里用这个副本。- 一台机器上第一次
tenon test run需要你信任。 它以退出码1停下并列出命令;请在你自己的终端运行tenon test trust(agent 不能代你)。命令一变就会再问。 - 什么算通过需要你确认。 目录、基线、已知失败或项目工作流的改动会挡住评审门,直到你用
tenon review acknowledge或 Dashboard 批准。已知失败的条目必须指向一个用例,超过 30 天的到期日不被承认。代码没变时,评审者不加--rerun-reason就不能重跑。 - 更新之前写下的记录视为未运行(
record-unsealed)。进行中的任务运行一次tenon test run <change> --stage。 - 没有声明的输出目录计入候选代码。 在目录里声明报告、覆盖率和产物路径,否则运行时写出
coverage/会让它自己的记录过期。 - CLI 语言跟随 locale。 没有 locale 信号,或是
C/POSIX时输出仍是中文;LANG=en_US.UTF-8这类系统 locale 现在得到英文的帮助和消息。TENON_LANG=zh可以固定中文。脚本应该读--json。
升级动作
运行 tenon update --codex(或 --claude),新开宿主会话,然后:
- 用
tenon dashboard --open登录 Dashboard。 - 在每个项目里,在你自己的终端运行一次
tenon test trust;更新前已开始的任务,再运行一次tenon test run <change> --stage。 - Codex 项目:
tenon update提示AGENTS.md里的受管块已过期时,运行tenon sync --migrate。 - 可选:把
tenon-verifyAction 加到 Pull Request 上(CI 校验),并在运行tenon test run的 CI 作业里设置TENON_TEST_TRUST=1。
如果每条命令已经报 Node identity changed,对使用的每个宿主各运行一次版本化安装命令:
/usr/bin/curl -fsSL https://raw.githubusercontent.com/jefferysha/tenon/v0.3.0/install.sh | /bin/bash -s -- --claude
/usr/bin/curl -fsSL https://raw.githubusercontent.com/jefferysha/tenon/v0.3.0/install.sh | /bin/bash -s -- --codex全新安装 v0.3.0 只需要前两步。
兼容性
N-1 是 v0.2.1。N-1 兼容门禁(tools/test-bundle.sh)每次运行都在两个方向上让它与本版本互相读写。
- v0.3 在正常使用中写下的数据都能被 v0.2.1 读取,所以可以用
tenon runtime repair --rollback回滚,也可以和还没更新的同事在同一个仓库里继续工作。能读不等于有效:一个版本写下的运行在另一个版本里仍可能显示为过期(见下面未声明输出那一条)。 - 除非设了
TENON_RECORD_RETENTION=<n>,测试运行记录不会被清理。v0.2.1 会把这样被限制条数的链报告为被改动,所以只在所有人都已是 v0.3 或更新时才设它。 - agent 运行的宿主与重跑原因记在运行台账旁边的
.pipeline-agent-run-meta.jsonl里,v0.2.1 不读这个文件。 - v0.2.1 写下的记录在 v0.3 里读作完好的链,但没有本机封存(
record-unsealed)。更新之后运行一次tenon test run <change> --stage。 - v0.3 把没有声明过的
coverage/、test-results/、playwright-report/算进工作区。一个版本写下的运行在另一个版本里可能显示为过期。重跑,或在目录里声明这些输出。 - 你主动选用时需要 v0.3 的能力(v0.2.1 会把文件判为非法或忽略该设置):目录里的
profile: coarse或not_applicable:;工作流test_policy里的integrity: notice|block;工作流评审者上的host:;自定义 agent 文件里的attach_on或host(tenon agent copy security <名字>写出的副本也带它);以及TENON_RECORD_RETENTION。 - standard 通道的任务(
track: standard)只有 v0.3 能用。v0.2.1 能列出它们、显示状态,但check、test、agent、document会拒绝(未注册的 track 'standard'),status --json也没有 step 投影。回滚之前先完结或归档它们。 - Dashboard 的完整快照形状不变;
?view=list与GET /api/change/:name/snapshot是新增,除 health 和静态资源外所有路由都需要会话。
验证
tenon runtime status
tenon doctor
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:18765/api/snapshot
tenon support bundle --out /tmp/tenon-support.tar.gz
tenon verify --ci --all-opentenon runtime status 显示当前 release 为 valid=yes。doctor 没有红灯:identity:release 列出宿主插件、runtime 和 Dashboard server 都是 0.3.0,env:platform 在 macOS、Linux、WSL 上为绿,launcher 目录不在 PATH 上时 env:path-tenon 为黄。Dashboard 在运行时,curl 打印 401,因为请求没有带会话;tenon dashboard --open 会打开已登录的页面。support bundle 列出包含的文件和脱敏次数,权限为 0600。没有任务在范围内或每个任务都通过时,verify --ci 退出 0;记录、批准或候选代码对不上时退出 2 并给出发现。
v0.2.1 · 2026-10-01
v0.2.0 的热修复版。macOS 重启之后,每条 tenon 命令和每个 hook 都报 tenon runtime Node identity changed; rerun tenon setup --codex or tenon setup --claude,而这条提示让你运行的命令又被同一道检查拒绝,安装无法自己修复。
修复
- 稳定 launcher(
~/.local/bin/tenon与tenon-hook)把 Node 可执行文件及其每一级父目录的设备号(st_dev)钉了进去。macOS 在每次重启后会给同一个卷分配新的设备号,所以这个值撑不过一次重启。launcher 不再保存它。它仍然拒绝 Node 路径上的符号链接,仍然钉住二进制及其父目录的 inode、权限位、属主(二进制还有大小),也仍然拿 Node 的 SHA-256 与 setup 时记录的摘要比对。Linux 同样修改。 - launcher 的 Node 检查不通过时不再把你锁在外面。Node 字节没变、只是身份信息变了:
tenon setup、update、doctor、runtime仍能运行,tenon setup --claude(或--codex)即可重新钉住,其他命令只打印一行写明这条修复。Node 被替换或删除:那一行是一条完整命令,用你PATH上的 Node 运行 bootstrap:env TENON_RUNTIME_ROOTS=… node …/bootstrap/active.mjs cli setup --claude(Codex 用--codex)。 - hook 最多每 30 分钟打印一次这条消息,其余时候无输出地以 0 退出,从不阻断宿主。
- 新版本会自己修复 v0.2.0 格式的 launcher。v0.2.0 运行的
tenon update会把旧格式再写一遍,所以更新后的第一条 Tenon 命令或第一次会话启动,会用setup同一个写入器重写两个 launcher。前提是:它们是 Tenon 自己写的普通文件(不是符号链接)、导出相同的 roots,并且钉的是当前运行的 Node 及其记录的 SHA-256。命令行在修复时打印一行说明,修复失败则打印一行提示运行tenon setup --claude/--codex;hook 全程静默,也不会等待它。其他情况一律不改动。 tenon doctor新增检查runtime:launcher:launcher 仍钉着设备号时为 WARN,否则 PASS。- 载荷摘要缓存也不再用设备号作键,所以重启后的第一次分发不会重新哈希整个载荷。
升级动作
如果每条命令都已经报 Node identity changed,对使用的每个宿主各运行一次版本化安装命令。它不经过出问题的 launcher:
/usr/bin/curl -fsSL https://raw.githubusercontent.com/jefferysha/tenon/v0.2.1/install.sh | /bin/bash -s -- --claude
/usr/bin/curl -fsSL https://raw.githubusercontent.com/jefferysha/tenon/v0.2.1/install.sh | /bin/bash -s -- --codex如果 v0.2.0 对你还能用,运行 tenon update --codex(或 --claude)即可。正在运行的更新程序仍是 v0.2.0,它会把旧 launcher 再写一遍;更新之后的第一条 Tenon 命令或新会话会自动把它们切换成重启安全格式。也可以手动运行 tenon setup --codex(或 --claude)。全新安装 v0.2.1 无需额外操作。
兼容性
没有改变任何公开命令、选项、项目文件或 Dashboard API。新增的只有 runtime:launcher 检查和一个内部修复命令;launcher 文本变了(不含设备号、失败提示更明确)。项目和 runtime 状态不受影响。
验证
tenon runtime status
tenon doctor
grep -c '%d' ~/.local/bin/tenonruntime 报告 active release,doctor 为绿,包括 runtime:launcher。grep 输出 0。下次重启后 tenon runtime status 仍然可用。
v0.2.0 · 2026-09-30
能力补齐版。智能体改为在终端编写并注册,整条工作流可以在一张画布上看全,每个任务都要登记并运行自己的全部测试。默认工作流随之改变: 步骤挂上了执行者,并且要求测试,升级项目前请先读「升级动作」。
智能体
- 智能体在终端注册:
tenon agent list、show、new、add、validate、copy、rm、export。每个智能体声明role(executor或reviewer)和可选的 semverversion。new在交互终端里补问缺项,参数齐全时可完全非交互;rm拒绝删除仍被工作流步骤使用的智能体并列出这些步骤。Dashboard 不再新建智能体,只展示。 - 来源分三层。官方智能体随插件发行(
builder、researcher、architecture、backend-quality、code-size、e2e、frontend-quality、security、spec-consistency,名称、版本、角色与摘要记录在templates/agents/manifest.json),只读。自定义智能体在用户配置里。项目智能体在.tenon/agents/,进 git,团队共享。同名时项目级优先于自定义;任何来源都不能占用官方名称,这类冲突会被列出且不会被使用。 - 技能
tenon:agent-author:在 Claude Code 或 Codex 里按你的描述起草智能体(职责、只做与不做、方法、自检、以tenon-result块结尾的报告),校验后用tenon agent add注册。它不写任何 Tenon 状态。 - 任务冻结智能体时,Tenon 为当前宿主生成原生子代理文件:
.claude/agents/tenon-<name>.md或.codex/agents/tenon-<name>.toml。tenon agent prompt随后返回subagent_type: tenon-<name>,宿主用该专属子代理执行(Claude Code 强制执行tools白名单;Codex 没有按 agent 的工具白名单,只为写不了文件、跑不了命令的 agent 设只读沙箱)。无法生成文件时回退为通用子代理,并在运行记录里标明。这些文件登记在.pipeline-owned.json;没有运行中任务再使用时被清理(在 Dashboard 里归档任务同样清理);tenon uninstall只删除自己生成且你没改过的文件。 - 默认工作流现在挂了执行者:每个 Explore 运行
researcher,每个 Build 在评审者之前运行builder(每个相互独立的任务一个子代理,合并为一份报告),每个 Verify 声明code-size测试(tenon test code-size --json,lines_added不超过 2000 即通过)和必需的code-size评审者。chat 与 free 的 Verify 另外运行security作为建议性评审者。 - 库页面按角色分组列出智能体,行内显示来源与版本;详情显示字段、渲染后的正文、使用它的步骤和最近运行;自定义与项目智能体可编辑正文,官方智能体可「复制为自定义」。
编排
- 技能按波次执行。声明了
depends_on的技能在其依赖之后运行,同一波内并行,波次之间串行;未声明depends_on的技能保持按声明顺序串行,与之前一致。技能门、tenon status与画布读取同一份顺序。 - 内核把一个工作流(或任务冻结的计划)投影成编排:每个阶段按 runner 实际顺序列出执行者、技能、测试、评审者,以及门禁、回流边和文档流向。OpenSpec 注入的技能与 manifest 叠加的技能在内核计算,不再由页面推算。只读接口:
GET /api/workflows/:name/orchestration?track=与GET /api/change/:c/orchestration?root=。 - 工作流页左栏第一行是「总览」,右栏是全宽画布:每个阶段一列(执行者 → 技能 → 测试 → 评审者),门禁图标在列头,回流为虚线弧,悬停显示文档流向;可缩放、适应视图、全屏。工作台任务详情有同一张画布作为「总览」页签,节点带运行状态,当前阶段高亮,读的是任务冻结的计划。
- 单个阶段的右栏只有四段:输入 → 技能 → 输出 → 门禁。「技能」画布用泳道同时显示执行者、技能、测试与评审者;「退回」并入门禁段。
- 门禁只有「评审」(由人确认)和「自动」(声明的输出全部设置后才能离开)两种。
gate: null与省略 gate 都是「自动」,所以工作流页只提供两档。输出检查只作用于前进边;verify-fail、requirements-changed等回流边不受它阻塞;引擎自己写入的输出(build_sha、archived)不在检查范围内。
测试体系
- 每个任务都要登记自己的全部测试。项目有一份测试目录
.tenon/tests/catalog.yaml(进 git,人可编辑):每个套件有 kind、runner、命令、报告格式与路径、可选覆盖率、产物路径、服务、重试、基准设置,以及如何只选变更文件。tenon test discover [--write]识别 vitest、jest、mocha、node:test、Playwright、tsc、eslint、pytest、go,给出带可解析报告参数的建议套件,--write追加尚未在目录里的套件。 - 每个 change 有一份测试计划
openspec/changes/<change>/test-plan.yaml,只由tenon test命令写入:本任务用到的套件、新增或修改的测试文件,以及 OpenSpec 场景和tasks.md条目到测试用例的映射。手改计划会变成test-plan-tampered。 - 新增或修改的测试文件必须全部登记。
tenon test sync与阶段出口读取 change 自开始以来的 diff(含暂存、未暂存与未跟踪文件),未登记即以test-file-unregistered阻塞;读不到 diff 时以files-diff-unavailable阻塞而不是放行。只有场景和tasks.md中实现(build)段的任务需要映射到用例,其他任务是可选的。 - 工作流按轨道、按阶段声明
test_policy:必须登记哪些种类、必须运行哪些(changed或full范围)、覆盖率门槛、是否要求基准基线、flaky 上限、必需浏览器以及场景覆盖。default给每条轨道提供默认值(例如 frontend 在 spec 登记unit与playwright,Verify 运行unit、regression与 chromium、webkit 的playwright,并要求 80% 行覆盖率)。没有策略的阶段与旧的内联tests[]行为不变。阻塞项有稳定的代码(test-catalog-missing、test-plan-missing、test-kind-missing、test-not-run、test-failed、test-stale、no-tests-ran、coverage-below、scenario-uncovered等)并带修复命令。 - 命令:
tenon test discover、catalog、plan、register、unregister、waive、sync、run、status、baseline、known、report。tenon test run <change>可按套件、种类、阶段、变更文件或全部运行。tenon status在step.next里依序给出:发现 → 生成计划 → 映射场景 → 登记文件 → 运行本阶段测试 → 写报告。 - 结果按用例解析:JUnit(含 pytest 与 surefire)、Playwright JSON、Vitest JSON、Jest JSON、
go test -json、TAP;基准读取 benchmark JSON、hyperfine、k6 与 Lighthouse。没有报告、报告不可读、0 个用例或全部跳过(no-tests-ran,所以"test": "exit 0"不再算通过)、退出码与报告不一致、已登记文件或已映射用例没有出现在报告里,一律判失败。 tenon test discover给出的 node:test 套件使用 Tenon 随附的 JUnit reporter(落在本次运行的产物目录,不往项目里写任何文件),Node 20、22、24 上每个用例都带文件归属。报告里没有文件时,用例的文件记为未知,已登记的用例只在该名称于本次运行中唯一时按名称匹配。- 套件可声明服务。服务每次运行只启动一次,在自己的进程组里,按 URL、端口或日志文本探测就绪,结束后连同孙进程一起回收。启动前 URL 或端口已经有响应会被拒绝,因为测试会打到旧服务上。
- 失败用例按套件的
retries重试;重试后通过的用例标记为flaky并计数,受步骤策略的上限约束。 - 覆盖率读取 istanbul summary、lcov、cobertura,按策略门槛判定;
changed_lines来自 diff。 - 基准先预热再运行 N 次,保留中位数、p95 与离散度。基线按机器画像存于
.tenon/tests/baselines/<套件>/<画像>.json并提交。同画像下退化超过阈值即阻塞;该画像没有基线时通过并提示建立基线的命令(策略要求基线时则阻塞);不同画像之间从不比较。 - 已知失败清单
.tenon/tests/known-failures.yaml(原因、可选链接、到期日):清单里仍失败的用例不阻塞,新失败阻塞,已修好的用例会提示移出命令,过期条目按普通失败处理。 - 只有 Playwright 脚本计入浏览器证据。截图、trace、视频与 HTML 报告按文件复制进本次运行,建立带大小与摘要的索引,可逐个打开或下载。
- 记录为 v2(套件运行、用例、覆盖率、指标、服务、产物索引),并与代码指纹、目录条目、计划、工作流绑定。记录只由
tenon test run写入,按哈希链串联(.tenon/users/<slug>/tests/<change>/<run-id>.json,prev_digest)。gate hook 拒绝对记录、计划、基线与已知失败的写入和 shell 重定向。改动记录会让链断裂,该次运行视为未运行。对缺失种类或用例的豁免需要人:tenon review request会列出,用户的确认只批准这些项。 tenon test report把追溯矩阵(场景或任务 → 用例 → 最近结果)写进验证报告。声明了reads_tests的评审者还会拿到失败与 flaky 用例、覆盖率和基准差异。- Dashboard「测试」视图(只读,页面不运行也不登记测试):项目页列出目录套件、各机器画像的基线与已知失败;工作台任务有「测试」页签(策略矩阵、场景追溯、未登记文件、用例级失败)和运行抽屉(失败用例、产物、覆盖率、对比基线的基准、日志);工作流页用表单编辑每个阶段的测试策略;库里按 runner 展示测试模板。接口:
GET /api/tests/catalog、baselines、plan、records、record、artifact。 - Tenon 自己的仓库把全部套件登记在
.tenon/tests/catalog.yaml;CI 在 Chromium 上跑 Dashboard 浏览器测试,并对照 CI 画像跑tenon status与快照生成的基准。
表单
- 新建工作流:选择起点(空白、复制内建
default或simple、已有工作流、或导入 YAML)、名称与 OpenSpec 开关;右侧预览轨道与阶段;名称唯一性与合法性即时校验。 - 新建 / 编辑模板:名称、分类、适用框架(多选)与带「编辑 / 渲染」切换的 Markdown 正文;占位变量说明放在 Tooltip。库里的测试模板按 runner 只读展示。
- 新建项目在模板与客户端之间多了第五步「资源」:可选组件库、图标集与 DESIGN.md。它与模板步骤同构(点行在右侧预览将写入的内容,预览头部加入或移除,按上一步所选框架过滤,可切到全部)。DESIGN.md 由流式的 design 步骤写入,已有的
DESIGN.md不会被覆盖。默认前端 spec 提示词指向tenon resources。 - 技能页的「引用」列包含 OpenSpec 注入与 manifest 叠加的技能。
- 所有新建类对话框行为一致:固定高度、Enter 提交、Esc 关闭(有输入时先确认)、字段下方即时校验、说明进 Tooltip。
修复
- Codex:
AGENTS.md的受管块仍在描述已删除的各 phase 技能,现改为描述单一的tenon技能。tenon sync会把该块报告为absent、current、stale或invalid,tenon sync --migrate就地刷新过时的块,只改动标记之间的行。 - Playwright 等目录产物原先被记成一个目录,Dashboard 拒绝打开(403)。现在逐个文件建立索引、逐个打开;下载的产物保留原文件名,而不是
artifact.zip。 - 写入门 hook 原先按整段工具输入匹配,写一份只是提到测试记录路径的文档也被拒绝。现在只看写入目标(
file_path、notebook_path,或apply_patch的文件头)。 - 智能体的
hosts限制原先从不生效,因为tenon技能没有传--host;现已补上。 - 继承了
TENON_USER、TENON_RUNTIME_HOME、TENON_BASE_BRANCH或TENON_CHANGE_NAME的测试运行(例如在 Tenon 会话里跑npm test)不再因身份断言失败,也不再写入真实运行时目录;Vitest 对每个测试文件隔离这六个变量。临时目录清理带重试,迟到的写入不再表现为ENOTEMPTY。Vitest worker 上限为min(8, cores),完整的npm test在本机与 CI 表现一致。 - 文档:已退役的 1.x Release 与标签仍在发布,保留到 v0.x 真实宿主验收之后;
tenon user与tenon owner补进 CLI 参考与契约;AFK 的 fail-closed 报错写明真实缺口(调用方没有注入deps.preparation)。
升级动作
从 v0.1.10 升级:运行 tenon update --codex(或 --claude),然后新开宿主会话并刷新 Dashboard。N-1 兼容门禁在两个方向上用已发布的 v0.1.10 读写本版本的数据。
从 1.x 迁移:为使用的每个宿主各运行一次版本化安装命令:
/usr/bin/curl -fsSL https://raw.githubusercontent.com/jefferysha/tenon/v0.2.0/install.sh | /bin/bash -s -- --claude
/usr/bin/curl -fsSL https://raw.githubusercontent.com/jefferysha/tenon/v0.2.0/install.sh | /bin/bash -s -- --codex然后对每个使用默认工作流的项目:
- 默认工作流现在要求测试。在项目里运行一次
tenon test discover --write,检查.tenon/tests/catalog.yaml并提交。没有目录时,新任务会在第一个带测试策略的阶段以test-catalog-missing被挡住,tenon status会给出这条命令。 - Codex 项目运行一次
tenon sync --migrate,刷新AGENTS.md里的受管块。 - 已经开始的任务保留冻结时的计划:不会获得新的执行者、测试策略或门禁行为,按开始时的规则完成。新任务使用新规则。
- 要使用自己的智能体:运行
tenon agent new(或用tenon:agent-author技能),再到工作流页把它挂到某个步骤。
兼容性
- CLI 的新增是新命令和新的可选字段。没有
role的智能体文件照常读取(角色按工具推断,tenon agent validate会提示补上);v1 测试记录与声明了内联tests[]的步骤仍可读、仍生效;gate: null继续可用,含义是「自动」。 - 新增的项目文件:
.tenon/tests/(目录、基线、已知失败)、.tenon/agents/,以及每个 change 的openspec/changes/<change>/test-plan.yaml。宿主智能体文件.claude/agents/tenon-*.md与.codex/agents/tenon-*.toml由 Tenon 生成和清理,不属于任务的交付提交。 - Dashboard 不运行也不登记测试,也不调用模型;登记在终端完成。
验证
tenon doctor
tenon runtime status
tenon test catalog validate两个宿主的 inventory、active managed runtime 与 Dashboard 都报告 0.2.0。tenon test catalog validate 用于已有目录的项目。打开 Dashboard:工作流页第一步是「总览」,库里按角色分组列出九个官方智能体,已有目录的项目在「测试」下显示其套件。
v0.1.10 · 2026-09-25
以真实用户任务逐条走查驱动的 Dashboard 可用性版本。
新建项目
- 新建项目改为四步向导:位置 → 模板 → 客户端 → 确认。
- 目录用系统文件夹对话框选择(
POST /api/fs/choose-folder);没有对话框时改用页面内目录浏览(GET /api/fs/list),手输路径只是次要入口。 - 位置步骤即时校验:目录不存在、不是 git 仓库(带「初始化 git」开关)、已在列表中(可直接打开)、已有指令文件。
- 只有
AGENTS.md写入正文;CLAUDE.md/GEMINI.md只写一行@AGENTS.md;已有文件可追加、覆盖或跳过。 - 创建过程逐步回报(
POST /api/projects/create/stream),失败回滚并可重试;完成后停在结果页,由你点「打开项目」。
项目
- 项目页以客户端为中心:只列该项目已启用的客户端,读同一文件的客户端合并为一行,其余从「+」添加;启用集合存在项目的
.tenon/clients.json(GET/POST /api/projects/clients)。 - 项目级 / 用户级是所选客户端内的切换;打开或预览都不写文件;缺少
CLAUDE.md时可一键用@AGENTS.md引用创建。 - 可把项目移出列表(不删文件);加载失败显示内联错误与重试。
工作台
- 任务是否可进入下一阶段与
tenon status出口同一判定(技能、文档、测试、tasks 勾选);「需要你」只统计待评审,各视图一致。 - 每个任务有「下一步」:每个阻塞一行短标签、可复制的状态命令,以及在 agent 对话里恢复任务的
/tenon 继续 <change>。 - 输入、输出、测试改为表格;阶段条为纯色分段;筛选保持一行。
- 删除未被 git 跟踪的任务会明确「不可恢复」并要求输入名称;归档可撤销。
工作流、库、技能
- 列出全部内建工作流(含
simple);OpenSpec 注入的技能在画布上可见;添加阶段只需名称;被任务使用的工作流拒绝删除并列出任务。 - 技能画布持续脉冲,每条边至少 480ms,短画布也能看清。
- 库里复制得到「… 副本」并直接进入编辑;所有列表默认打开第一项,不再留空白。
- 技能来源失败会写明原因(例如没有已安装的发布包)。
- 视图 id 改为
workspace/workflow,旧的progress/workbench链接自动跳转;桌面端去掉顶栏项目切换器,连接点只在断线时显示。
v0.1.9 · 2026-09-24
Dashboard 审美版。一次审美层面的深度评估(视觉语言、色彩、排版、组件质感、交互、动效)与一流开发者产品的参考调研后, 对设计基础、浮层、动效和各页面做了统一提升。
设计基础
- 强调色采用原型的墨绿
#236a50;暗色表面分四档明度,主按钮改为中绿底白字;选中统一为中性偏绿底 + 左侧 2px 内边条, 不再有蓝色选中与第二强调色。 - 圆角四档:控件 8、列表与菜单 10、对话框与抽屉 14、行内代码 4;药丸只留给计数徽标、头像与状态点。
- 阴影三级:卡片发丝阴影、菜单与弹层双层阴影、对话框与抽屉深阴影;浮层不再有 currentColor 黑边。
- 拉丁字体改为 Inter(仅 latin 子集,约 48 KB),中文继续使用系统字体;数字等宽对齐;字重收敛为 400 / 500 / 600。
动效
- 统一时长与缓动:按下 80ms、悬停 140ms、选中与菜单 160–180ms、对话框与抽屉 240ms、退场 120ms。
- 菜单、弹层、选择框淡入并从 0.98 放大;对话框从 0.97 放大并上浮;抽屉从右侧滑入;Toast 先淡出再消失; 导航、筛选芯片与分段控件的选中底色以滑块滑到新位置。
- 画布脉冲改为一条时间线:各边同速(420px/s)依次传递,两端淡入淡出,到达节点时节点闪光,终点实心圆点出现光环; 切换阶段时画布不再横扫。
- 减少动效时只保留 100ms 的淡入淡出与颜色变化。
页面
- 顶栏导航紧跟项目切换器;待决策数字挂在「工作台」角上;连接状态只留状态点。
- 侧栏去掉框中框;1360px 以下折叠为图标栏。
- 工作流页:门禁为分段控件、说明在提示中;「退回」为统一的下拉;保存条只在有改动时滑出;编辑浮层合为一个表面, 「+」与拖拽柄悬停才出现,文件列表单行;回流弧带方向箭头。
- 工作台空态命令单行可复制;阶段推进时进度段从左填充。
- 项目页宿主与文件两表列宽统一;指令编辑区随内容增高,「编辑 / 渲染」为分段控件。
- 库与技能页减轻字重与装饰;技能页链接仅悬停着色;空白详情不再显示文字。
升级动作
从 v0.1.8 升级:运行 tenon update --codex(或 --claude),然后新开宿主会话并刷新 Dashboard。N-1 兼容门禁在两个方向 上用已发布的 v0.1.8 读写本版本的数据。
从 1.x 迁移:为使用的每个宿主各运行一次版本化安装命令:
/usr/bin/curl -fsSL https://raw.githubusercontent.com/jefferysha/tenon/v0.1.9/install.sh | /bin/bash -s -- --claude
/usr/bin/curl -fsSL https://raw.githubusercontent.com/jefferysha/tenon/v0.1.9/install.sh | /bin/bash -s -- --codex兼容性
- 仅 Dashboard 视觉与交互变化;CLI、状态与数据格式不变。
验证
tenon doctor
tenon runtime status两个宿主的 inventory、active managed runtime 与 Dashboard 都报告 0.1.9。
v0.1.8 · 2026-09-24
Dashboard 体验版。一次专业 UI/UX 评测(视觉、组件、图形、动效、动线、流畅性、冗余、信息架构、响应式、可访问性) 后的全面优化,外加服务端快照性能优化。
更快
- 服务端只构建一份共享快照,供
/api/snapshot、实时推送首帧、轮询广播与 AFK 视图共用;指纹不变时直接复用, 服务端的每次写操作后立即失效。/api/snapshot支持ETag/304。页面打开时只构建一次快照;缓存命中后各页面 约 0.1 秒内显示完整内容(此前 3–5 秒);空闲时服务端事件循环占用从约 76% 降到约 1%。 - 工作流、库与技能页不再等待快照;工作台与项目页首次加载显示骨架,不再误显示「还没有项目」。
工作台
- 筛选保持单行、不换行:状态芯片(全部 / 需要你 / 进行中 / 待复核 / 已完成)与负责人、工作流、轨道、阶段下拉, 放不下的收进「更多」;只有一个取值的维度隐藏。顶部「待决策」徽标与「需要你」使用同一计数,点击直接筛出。
- 归档、接手、删除移到标题旁与卡片上的「⋯」菜单,聚合视图也可用;底部动作条移除。
- 阶段显示名称而不是 id;「可进入下一步」用琥珀色;单阶段工作流不画进度条;选中的任务写入地址(含项目标识, 同名任务不再选错)。
工作流页与画布
- 画布按 1:1 显示、高度随并行数增长,不再把字缩小;尺寸变化后重新取景;内容放不下时从左对齐可平移。 深色主题下缩放控件不再是白块。脉冲只在技能运行中或刚编辑后播放。
- 内建 default 工作流不再每个阶段都显示警告(此前全部是误报);警告改为图标 + 提示。
- 门禁说明可用键盘打开;保存条显示「未保存 N 处」;编辑浮层主按钮为「完成」,点行预览、点「+」加入。
- 地址支持
wf、track、step深链。
项目、库与技能页
- 宿主表改为等分表;指令文件「预览变更」后在差异抽屉里「应用」;删除进入「⋯」菜单;Markdown 预览不再显示 HTML 注释。
- 库只显示名称(id 放提示);「复制」改为「复制为自定义」;内建项只用锁图标;智能体按执行者 / 评审者分组; 加载时不再误显示为空。
- 技能页支持搜索、点行查看详情,状态只标变化与失败;修复服务端返回
modelInvocable时整页报「响应格式无效」。
设计系统与可访问性
- 浅色主题辅助文字对比度达到 WCAG AA;禁用按钮换底色而不只是变淡;按钮与芯片点击区不小于 40px。
- 只保留一套基于 Radix 的对话框;删除类确认为 alertdialog,初始焦点在「取消」。
- 去掉重复的面包屑、页眉小标题与名称;设置改为主题 / 语言两行分段控件;断线横幅完整显示。
升级动作
从 v0.1.7 升级:运行 tenon update --codex(或 --claude),然后新开宿主会话并刷新 Dashboard。N-1 兼容门禁在两个方向 上用已发布的 v0.1.7 读写本版本的数据。
从 1.x 迁移:为使用的每个宿主各运行一次版本化安装命令:
/usr/bin/curl -fsSL https://raw.githubusercontent.com/jefferysha/tenon/v0.1.8/install.sh | /bin/bash -s -- --claude
/usr/bin/curl -fsSL https://raw.githubusercontent.com/jefferysha/tenon/v0.1.8/install.sh | /bin/bash -s -- --codex兼容性
- 在服务端之外改动、且快照指纹覆盖不到的内容(例如直接编辑工作流文件、用
git config改身份),Dashboard 最多 30 秒后可见;经 Dashboard 或 CLI 的写入立即可见。 - 地址参数新增
status、step、wf、track;聚合视图的change带项目标识,旧链接仍按名称匹配。
验证
tenon doctor
tenon runtime status两个宿主的 inventory、active managed runtime 与 Dashboard 都报告 0.1.8。
v0.1.7 · 2026-09-24
第六轮真实会话验收修复版。v0.1.6 上三条轨道全部走完、没有 verify-fail、每个评审门一次确认即放行;本版修复其中 发现的最后一处流程问题。
流程
- 交付步收尾后用户回复「继续」时,续轮本身会在 change 目录追加交互、技能调用与技能确认台账;v0.1.6 据此再次要求 一次同名的交付提交,模型跳过后在未提交的工作区上流转。判定「还有没有待提交的交付物」现在排除 hook 追加的全部 四本台账(历史、交互、技能调用、技能确认),它们随下一次提交入库。
升级动作
从 v0.1.6 升级:运行 tenon update --codex(或 --claude),然后新开宿主会话。N-1 兼容门禁在两个方向上用已发布的 v0.1.6 读写本版本的数据。
从 1.x 迁移:为使用的每个宿主各运行一次版本化安装命令:
/usr/bin/curl -fsSL https://raw.githubusercontent.com/jefferysha/tenon/v0.1.7/install.sh | /bin/bash -s -- --claude
/usr/bin/curl -fsSL https://raw.githubusercontent.com/jefferysha/tenon/v0.1.7/install.sh | /bin/bash -s -- --codex验证
tenon doctor
tenon runtime status两个宿主的 inventory、active managed runtime 与 Dashboard 都报告 0.1.7。
v0.1.6 · 2026-09-24
第五轮真实会话验收修复版。v0.1.5 上三条轨道全部走完、每个评审门一次确认即放行;本版收尾剩余的小问题。
流程
- build 步的技能加载、执行者与评审者动作以及
pre_verify_review_result字段带上review_bar:下一步(verify) 声明的评审者及其block_at与关注点。build 内的自审与子代理评审按同一口径修完阻断项,避免到 verify 才被拦下 而回退。 - 交付步第二次提交使用
chore(<change>): update deliverables,与首次feat(<change>): deliver区分。 - 交付值已知时(如
pr_url=no-remote)先写入再做最后一次交付提交;需要先提交才能得到 PR 地址时,写入后补一次 提交。交付步流转前工作区干净。 - 入口技能以用户使用的语言回复(命令、字段名与路径保持原样)。
升级动作
从 v0.1.5 升级:运行 tenon update --codex(或 --claude),然后新开宿主会话。N-1 兼容门禁在两个方向上用已发布的 v0.1.5 读写本版本的数据。
从 1.x 迁移:为使用的每个宿主各运行一次版本化安装命令:
/usr/bin/curl -fsSL https://raw.githubusercontent.com/jefferysha/tenon/v0.1.6/install.sh | /bin/bash -s -- --claude
/usr/bin/curl -fsSL https://raw.githubusercontent.com/jefferysha/tenon/v0.1.6/install.sh | /bin/bash -s -- --codex兼容性
next的 build 动作新增可选字段review_bar。
验证
tenon doctor
tenon runtime status两个宿主的 inventory、active managed runtime 与 Dashboard 都报告 0.1.6。
v0.1.5 · 2026-09-24
第四轮真实会话验收修复版。在 v0.1.4 上三条轨道都已走完并保持工作区干净,本版修复其中发现的引导与确认问题。
评审确认
- 用户回复「按推荐」等确认语后,hook 写入评审回执时明确告知模型:已记录对哪个任务、哪个事件的确认,照
next推进,不要再要求「确认继续」。v0.1.4 中模型会误称确认未生效并空等一轮。 - 会话恢复上下文只报告仍存在且未确认的评审门;已确认未流转的显示「评审已确认」。
- 入口技能列出全部能确认评审门的回复;「按推荐」只表示确认该门,字段取值以
next的recommended为准, 想用其他取值必须先问用户。
流程
- 规划步提示缺少测试脚本时,要求把测试脚本与测试同步写进提案与设计;
spec-consistency评审不再把仅为满足 必需测试而补的脚本视为规格不一致。v0.1.4 中这会导致 verify-fail 并回退 spec。 - 交付步先提交再勾选任务;应用规格等后续改动在
pr_url之前再提交一次。 read-documents动作标明本步可编辑的输入文档(editable)与说明;其余只读,需求变化走requirements-changed。文档内容必须读入上下文,不能丢弃输出。- 不使用 OpenSpec 的工作流完结后显示「已完结」,不再声称「已归档」;
list --finished列名改为FINISHED_AT。
升级动作
从 v0.1.4 升级:运行 tenon update --codex(或 --claude),然后新开宿主会话。N-1 兼容门禁在两个方向上用已发布的 v0.1.4 读写本版本的数据。
从 1.x 迁移:为使用的每个宿主各运行一次版本化安装命令:
/usr/bin/curl -fsSL https://raw.githubusercontent.com/jefferysha/tenon/v0.1.5/install.sh | /bin/bash -s -- --claude
/usr/bin/curl -fsSL https://raw.githubusercontent.com/jefferysha/tenon/v0.1.5/install.sh | /bin/bash -s -- --codex兼容性
- 人读输出变化:非 OpenSpec 工作流的
check完结文案、status的finished/finished_at行、list的FINISHED_AT列。JSON 输出不变。
验证
tenon doctor
tenon runtime status两个宿主的 inventory、active managed runtime 与 Dashboard 都报告 0.1.5。
v0.1.4 · 2026-09-24
第三轮真实会话验收修复版。在 v0.1.3 上用真实 Claude Code 会话走完 backend、free 与 simple 轨道,本版修复其中发现的问题。
交付物提交
- 交付步(字段含
pr_url或prd_path的步骤)的next新增commit动作,在pr_url之前提交全部交付物:代码、 文档、已应用的主规格、.tenon/users/<用户>/tests下的测试记录与状态目录.gitignore。v0.1.3 中默认工作流走完后 这些改动都留在工作区。 - 提交范围是整个工作区,但排除仓库根的本机门禁标记(
.pipeline-pending-*)与旧版本遗留的本机文件; 只剩这些文件时不再发出提交。走完 backend 与 free 后git status为空(被忽略文件除外)。 - 入口技能只在
next给出commit或finish-change时提交。
流程
- 后续步骤的必需测试缺少 npm 脚本时,在规划步(spec)就提示,写进计划;build 中才发现时,提示只需补
package.json脚本、不要改已登记的规格文档,不再引发回退 spec。 - build 步先要求
build_mode/isolation,再提示测试配置。 - 已完结任务的
status --json总是带step,结构与活跃任务一致,next为stop(code: finished)。 - 主规格的 Purpose 与 Requirements 之间保留空行;解封提示同一轮只输出一次。
升级动作
从 v0.1.3 升级:运行 tenon update --codex(或 --claude),然后新开宿主会话。N-1 兼容门禁在两个方向上用已发布的 v0.1.3 读写本版本的数据。
从 1.x 迁移:为使用的每个宿主各运行一次版本化安装命令:
/usr/bin/curl -fsSL https://raw.githubusercontent.com/jefferysha/tenon/v0.1.4/install.sh | /bin/bash -s -- --claude
/usr/bin/curl -fsSL https://raw.githubusercontent.com/jefferysha/tenon/v0.1.4/install.sh | /bin/bash -s -- --codex兼容性
- 交付提交包含工作区中的全部未提交改动;在同一仓库里有无关的未提交文件时,请先自行处理。
- 已完结任务的 stop 代码由
run-archived改为finished。
验证
tenon doctor
tenon runtime status两个宿主的 inventory、active managed runtime 与 Dashboard 都报告 0.1.4。
v0.1.3 · 2026-09-24
第二轮真实会话验收修复版。在 v0.1.2 上用真实 Claude Code 会话完整走完 backend 与 free 轨道,并按 1440 宽度复查 Dashboard,本版修复其中发现的问题。
数据驱动流程(step.next)
ship:tasks.md未完成项排在apply-spec与文档写入之前,以fix动作出现并列出每一项;勾选一项后剩余项 不再被隐藏。finish-change的提交命令一次成功:只列 git 接受的路径(未被跟踪的原 change 目录不再列入),存在且未跟踪的.pipeline/.gitignore、.tenon/.gitignore、openspec/.gitignore一并提交,旧版本已提交的心跳文件在提交中 取消跟踪。非 git 仓库时commit为null。simple等不使用 OpenSpec 的工作流在verify-pass后如有未提交改动,同样给出finish-change提交动作。build_mode、isolation等选择字段在读完输入文档后、动手实现前就出现在next中。chat、pm、free轨道的build步声明必需评审者spec-consistency:pre_verify_review_result必须在 评审有真实结论后才能写成pass。进行中的任务保留原冻结计划。- 必需测试的 npm 脚本在项目中不存在时,
tenon test run报「未配置」而不是失败,不写记录;step.tests[].status为unconfigured,并在build步就提示配置。 - 已完结任务的
status --json形态一致;tenon test status对已完结任务列出各步最后一次记录。 tenon spec apply新建能力主规格时从提案写入 Purpose,不再留下TBD;提案仍是骨架时报purpose-missing。
宿主与 hook
- 用户在消息中点名已知轨道(「走 free 轨道」「track=free」「use the backend track」等)时,路由以点名为准, dispatch 标注
track_basis: user-named;否定、未知或多个点名时回退评分并提示。 - 解封提示列出实际能解封的回复(含「按推荐」「好的」)与不能解封的回复,并由测试逐句核对与分类器一致。
- 新增
openspec/.gitignore忽略.pipeline-terminal-activity.*心跳,工作区不再因心跳变脏。 - 粘贴超长内容时 UserPromptSubmit hook 不再超时:v0.1.2 在 64 KB 日志上即超过 30 秒,现在 1 MB 也在 0.5 秒内; 超过 64 KiB 的 prompt 只保留首尾各 8 KiB 用于路由判定。
Dashboard
- 工作台的「含已完结 / 已归档 / 未提交删除」单独一行,工作流筛选不再被挤成竖排。
- 资源目录每组筛选行首显示组名(类别 / 框架 / 样式 / 许可)。
升级动作
从 v0.1.2 升级:运行 tenon update --codex(或 --claude),然后新开宿主会话。N-1 兼容门禁在两个方向上用已发布的 v0.1.2 读写本版本的数据。
从 1.x 迁移:为使用的每个宿主各运行一次版本化安装命令:
/usr/bin/curl -fsSL https://raw.githubusercontent.com/jefferysha/tenon/v0.1.3/install.sh | /bin/bash -s -- --claude
/usr/bin/curl -fsSL https://raw.githubusercontent.com/jefferysha/tenon/v0.1.3/install.sh | /bin/bash -s -- --codex兼容性
next动作顺序变化:选择字段与测试配置提前。finish-change.commit新增untrack,command与commit可能为null。- 已经写成
TBD的主规格不会被改写。
验证
tenon doctor
tenon runtime status两个宿主的 inventory、active managed runtime 与 Dashboard 都报告 0.1.3。
v0.1.2 · 2026-09-24
验收修复版。对 v0.1.1 做了系统性真实场景自测(真实 Claude Code 会话完整走完 7 个阶段、逐项命令行能力覆盖、 Dashboard 逐页浏览器操作),本版修复其中发现的缺陷。
宿主与 hook
- 交互门不再拦截
ToolSearch。Claude Code 中AskUserQuestion是延迟加载工具,必须先经ToolSearch载入; v0.1.1 在技能要求提问时会形成「必须先问、却不能发问」的死锁。拦截提示改为给出可执行的提问方式。 - hook 的 JSON 解析改为线性:v0.1.1 在 macOS 自带 bash 3.2 上对长命令是平方级耗时,21 KB 的 heredoc 就让测试 提醒 hook 耗时 67 秒并被宿主以超时取消。现在 166 KB 输入约 1 秒;超过 64 KiB 的命令不再判为只读。
- 测试提醒只匹配当前步骤、当前轨道声明的测试,
free轨道不再被提醒运行并不存在的测试。 - 入口技能激活会话时携带宿主会话 id;有待确认评审时,「确认继续」「继续执行」会接续当前任务,而不被路由成新任务。
.pipeline/.gitignore忽略cache/、terminal-sessions/与codex-skill-receipts.jsonl等纯本地状态。
数据驱动流程(step.next)
pre_verify_review_result只有在本步骤声明的测试、执行者与必需评审者都有真实结果时才能写成pass;next不再推荐pass,也不再推荐一个接受后又要求direct_override的build_mode。phase_status、verified_at、updated_at由流转管理,tenon set拒绝直接写入。- 必需评审者失败时,
next直接指向修复或verify-fail,不再要求填写结果字段。 - 并行 agent 按依赖分层编号波次;有 agent 仍在运行时
next如实提示,tenon agent next不再误报「全部完成」。 ship:tasks.md未完成项作为出口阻塞出现在next中;仓库没有远端时pr_url可填no-remote(CLI 复核确实无远端),其余取值必须是 http(s) 地址。- 已完结任务列在
finished_changes;tenon check对其报告「已完结,无需检查」并以 0 退出。finish-change附带提交归档搬移的路径与提交信息,工作区不再留下未提交改动。 document record拒绝仍含模板占位符的骨架文档,并列出所在行。
命令行
- 工作流 YAML 顶层键顺序无关(文档示例中
document_contract写在steps之后现在可解析)。 --preset只接受full|hotfix|tweak;自定义工作流不再要求 preset。- 不存在的任务名统一报「change 不存在」,不再泄漏原始文件错误;输出被管道提前关闭时安静退出。
tenon test code-size只统计源代码;评审结论与测试记录一样绑定工作区指纹,改码后旧评审会过期。- 人读的
tenon workflow plan列出每步的输入、输出、测试与 agent;doctor与last-update.json对变化技能的 计数一致。
Dashboard
- 评审者 / 执行者编辑器在父组件重渲染时不再清空草稿(v0.1.1 中添加的评审者可能保存不下来)。
- 首次加载显示加载中,而不是「还没有项目」;中栏搜索框不再被压扁,筛选行换行显示完整。
- 预览不再把 YAML frontmatter 当正文渲染;删除自定义模板、智能体与测试方向前需要确认;提示改为报告结果。
- 断线横幅完整显示在页头下方;项目页不再每 5 秒重读指令文件。
- 资源目录新增 v0 模板、Skiper UI(仅链接)与 coss ui。
升级动作
从 v0.1.1 升级:运行 tenon update --codex(或 --claude),然后新开宿主会话。N-1 兼容门禁在两个方向上用已发布的 v0.1.1 读写本版本的数据。
从 1.x 迁移:为使用的每个宿主各运行一次版本化安装命令:
/usr/bin/curl -fsSL https://raw.githubusercontent.com/jefferysha/tenon/v0.1.2/install.sh | /bin/bash -s -- --claude
/usr/bin/curl -fsSL https://raw.githubusercontent.com/jefferysha/tenon/v0.1.2/install.sh | /bin/bash -s -- --codex兼容性
tenon status <change> --json新增finished_changes;set-field与agent next的 JSON 新增字段。--preset传入未知值、agent prompt指定未声明的 agent 现在以 1 退出。- 进行中任务里仍含占位符的已登记文档不受影响;再次登记时需先填写。
验证
tenon doctor
tenon runtime status两个宿主的 inventory、active managed runtime 与 Dashboard 都报告 0.1.2。
v0.1.1 · 2026-09-23
正确性修复版。在真实宿主上验收 v0.1.0 时发现:默认工作流五条轨道里有三条走不通,另有几处 Tenon 没有核实就把步骤报成了完成。
阻断修复:宿主拒绝执行的强制技能
- v0.1.0 的
pm、frontend、backend三条轨道都出不了explore,pm还卡在spec、verify与ship。 这些步骤的强制技能里有上游技能在SKILL.md中声明了disable-model-invocation: true:宿主拒绝代模型调用, 留不下回执,每次流转都报step-skills-incomplete。 - 替换:
grill-with-docs换成它自己委托的grilling+domain-modeling;improve-codebase-architecture换成codebase-design,内建architectureagent 同步更换。pm不再强制handoff(改用tenon handoff <change> [--bundle])、to-spec与to-tickets:applied-spec 文档与 spec 步骤的任务门禁已经覆盖 它们原本的作用。被移出的技能仍随包分发,作为人工指引。 - 此类缺陷不会再发版:发布候选的技能校验以
mandatory-skill-not-invocable失败;tenon doctor新增skills:invocable,强制技能的全部备选都被证明不可调用时为红,读不到其SKILL.md时为黄;Dashboard 拒绝保存 把这类技能设为强制的工作流。
不再误报通过
tenon check、tenon transition与tenon status对步骤技能与出口规则共用同一份判定。v0.1.0 中三者会对同一份 状态给出不同结论:在 pm 的ship,check失败,status却显示出口就绪,transition也放行。现在任务不能在缺少 交付物时完成,例如prd_path为空的 pm 任务。- 任何轨道上,spec 预演都不再满足 applied-spec 义务。
- 被步骤文档契约点名为产出者的强制技能,只有在本次步骤访问中登记了它产出的文档才算完成,仅有调用回执不再算数。
只按 next 就能走完任务
tenon status <change> --json 的 next 现在只给出命令真正接受的动作,只按它执行的 runner 可以把默认任务从 open 走到 tenon list --finished:
- 已登记后又被修改的输入文档会重新登记,不再无限重复读取;
- 脚手架生成的文档在同一波里登记,脚手架步骤会收敛;
- 未登记的
role: update槽是许可而非义务,不再索要任何动作; - 评审门后的单条回退边与前进边一样先发起评审请求;
- 归档步骤先给出
complete(即archived流转),再给出带完整openspec archive命令的finish-change; build_sha由 Build 的出口流转写入,不再要求 runner 填写;- 覆盖率门禁的修复提示与其解析器实际读取的内容一致。
负责人与已完结任务
tenon set、set-many与cas与其他写操作一样要求身份、拒绝非负责人,并拒绝已完结的任务。- 完结就是流转
tenon transition <change> archived,它同时写入archived_at;手动写archived或archived_at会被拒绝。 - 已完结的任务仍可用
tenon status查看,并在标记完结的那一刻就出现在tenon list --finished,不必等目录移动。 - 被「归档」隐藏的任务与已完结的任务给出不同提示:前者指向
tenon task unarchive,后者说明任务已完结、需要新建任务。
安装与升级
tenon spec apply会为尚不存在的能力创建主规格目录。tenon doctor把检查结论归属到当前使用的宿主,只有加--verify-release时才访问远端。- 上游技能 lock 与 v0.1.0 的格式逐字节一致:v0.1.0 的校验器能读 v0.1.1 写出的 lock,反之亦然。技能能否被调用 改为从其
SKILL.md字节读取,不再经过 lock。 - 安装文档说明如何放宽
CLAUDE_CODE_PLUGIN_GIT_TIMEOUT_MS:Claude Code clone 插件市场的默认超时为 120 秒, clone 超时会让该宿主处于未装插件状态。
升级动作
从 v0.1.0 升级:运行 tenon update --codex(或 --claude),然后新开宿主会话。N-1 兼容门禁在两个方向上用已发布的 v0.1.0 读写本版本的数据。
从 1.x 迁移:为使用的每个宿主各运行一次版本化安装命令:
/usr/bin/curl -fsSL https://raw.githubusercontent.com/jefferysha/tenon/v0.1.1/install.sh | /bin/bash -s -- --claude
/usr/bin/curl -fsSL https://raw.githubusercontent.com/jefferysha/tenon/v0.1.1/install.sh | /bin/bash -s -- --codex兼容性
- 进行中的任务:产出型强制技能在更早一次步骤访问中登记的文档,不再计入当前访问;
next会以record-document(带技能名)要求重新登记。 - 以下强制技能仍以调用即算完成,因为在该步骤没有文档槽把它们点名为产出者:
openspec-explore、grilling、domain-modeling、codebase-design(explore);brainstorming(pm 的 spec);test-driven-development、frontend-design、prototype(build);browser-qa、web-design-guidelines、design-taste-frontend、e2e-testing(verify);finishing-a-development-branch(ship)。把它们绑定到产物,需要在后续版本中对文档契约 另行决策。
验证
tenon doctor
tenon runtime statusskills:invocable 为绿;两个宿主的 inventory、active managed runtime 与 Dashboard 都报告 0.1.1。
v0.1.0 · 2026-09-22
版本号从 0.1.0 重新开始。Tenon 还不成熟,1.x 这个号码宣称了它并不具备的稳定度。 本版同时交付 v1.1.5 之后完成的能力。
版本号重置
- 版本号从 0.1.0 重新开始,之后按 0.1.x / 0.x 递增。已发布的 v1.0.0–v1.0.9 与 v1.1.0–v1.1.5 的 Release 与标签已经删除,这 16 个号码永不复用:release candidate 会直接拒绝它们。
- 安装顺序把已退役的 1.x 排在其他所有正式版本之下:0.1.0 相对 1.1.5 是升级,而更新的 0.x 不会被更旧的 0.x 静默覆盖。
tenon update会写明自己正在离开的已退役版本,迁移不会静默发生。
升级动作
为使用的每个宿主各运行一次版本化安装命令,然后新开宿主会话:
/usr/bin/curl -fsSL https://raw.githubusercontent.com/jefferysha/tenon/v0.1.0/install.sh | /bin/bash -s -- --claude
/usr/bin/curl -fsSL https://raw.githubusercontent.com/jefferysha/tenon/v0.1.0/install.sh | /bin/bash -s -- --codex在 1.x 上运行 tenon update 只会报告降级到 0.1.0,并且不做任何改动:那段拒绝逻辑属于已经发布的 1.x, 无法事后修补,所以上面的一行命令才是迁移路径;1.x 机器上的每日自动更新记录的也是同一条拒绝。 迁移完成后,tenon update --codex(或 --claude)重新成为常规升级入口。
一个仓库多个用户
- Tenon 依次从
TENON_USER、<config>/user.json、仓库自身配置的用户邮箱解析声明式身份,不需要登录; 新写入的记录都带上执行者。 - 每个任务都有创建者与负责人,其他用户通过「接手」成为负责人;推进不属于自己的任务会被拒绝。
- 每个用户的状态存放在
.tenon/users/<slug>/,同一仓库里的两个人不再互相覆盖当前任务与授权状态。 Dashboard 顶栏显示当前用户,工作区列表可按负责人筛选。
任务归档与删除
- 归档 / 取消归档 只对当前用户隐藏任务,随时可逆。
- 删除只动工作区、不自动提交,Dashboard 会在确认前说明这是「未提交删除」。
- 对应命令:
tenon task delete|archive|unarchive <name>与tenon list --archived。
工作流即数据
openspec: true是工作流里唯一的 OpenSpec 开关。- 每条轨道自己声明
document_contract:哪个步骤产出、更新或要求哪份文档。内核里固定的按阶段文档表已删除, 全局默认工作流把这些表完整写出来。 - Dashboard 工作流页面可编辑每个步骤的输入、技能、执行者、输出、评审者与门禁(评审或自动);工作流的步骤、 轨道与门禁存在全局,不再绑定单个项目。
一个技能取代七个
- 单个
tenon技能按tenon status <change> --json的step块驱动每一步,其中直接给出待执行的 agent 与必需测试。 tenon spec apply把 delta spec 应用到主规格。
每步的执行者与评审者
- 步骤可声明执行者与评审者:
tenon agent next|prompt|record。随包提供九个内建 agent(builder、researcher、 architecture、frontend-quality、backend-quality、code-size、security、spec-consistency、e2e)。 - 评审结论由 Tenon 依据发现项与阻断级别计算,每次运行都留存证据;任务创建时冻结该任务的 agent 集合。
- agent 一律在宿主内运行(Claude Code 的 Agent 工具或 Codex 子 agent),Dashboard 从不调用模型。
每步的测试证据
- 步骤可声明测试的命令、输入与输出,统一通过
tenon test run <change> <test-id>执行;必需测试记录缺失或 过期时,流转门禁拒绝推进。 - 随包提供九个内建测试方向:unit、integration、e2e、playwright、typecheck、regression、benchmark、 code-size、design-system。运行日志、trace 与截图按运行留存在当前用户的本地目录。
指令文件、模板与项目
- Tenon 依据 32 个内建模板块写项目级与用户级指令文件(AGENTS.md、CLAUDE.md、GEMINI.md);文件在 Tenon 之外 被改动时报告冲突,不覆盖。
- 库页面集中管理 agent、模板、资源目录与测试方向,内建条目只读、可复制后编辑;「新建项目」一个按钮完成新建或接入。
设计体系与资源目录
- 项目的设计体系写在
DESIGN.md,由tenon design与设计体系工作流产出;设计体系未就绪时,拒绝创建前端任务。 - 资源目录随包提供 163 条内建条目(
tenon resources),供步骤声明可用的库与参考。
上游技能
- setup 与 update 按
skills/sources.yaml安装 53 个上游技能到插件根目录;lock 文件记录每个技能的 commit、 目录摘要与许可证,抓取失败不改变当前发行版。 - 因此安装后的 payload 增长到约 48 MB。以文件 stat 为键的摘要缓存把实测 hook 派发开销控制在相对 v1.1.5 约 7 ms 以内。
兼容性
- v0.1.0 之前没有更早的 0.x 正式版本,因此本次发布显式跳过 N-1 兼容门禁,并且是可见的跳过:fixture 点名 v0.1.0,工具以约定的退出码报告跳过。v0.1.1 起以 v0.1.0 为基线。
- 也因此,v0.1.0 没有验证过读取 1.x 创建的任务。迁移前请先完结或归档进行中的 1.x 任务,否则可能需要重建。
验证
tenon doctor
tenon runtime status两个宿主的 inventory、active managed runtime 与 Dashboard 都报告 0.1.0;再次运行 tenon update --codex 会提示当前发行版已精确生效。
v1.1.5 · 2026-09-15
在 v1.1.4 上继续同一个真实 Codex 任务时发现并修复的问题。
规格
- 中文 delta spec 不再拖到 Verify 才发现 OpenSpec strict validate 要求每条 requirement 含英文
SHALL或MUST。 两种语言的 scaffold 提示与tenon-spec技能现在都写明这条规则;官方 CLI 可用时,技能在请求 spec 评审前先运行openspec validate <change> --strict。此前 Codex 任务用「必须」写 requirement,通过了 spec 评审,却在 Verify 被退回。
评审门
- 待确认评审时,未被识别为确认的回复会提示应当如何回复,与交互门一致。此前在
verify-fail,agent 给出的选项 「修复」被门静默忽略。 tenon-verify要求 agent 在verify-fail暂停时写明回复「确认继续」。
升级动作
为使用的每个宿主安装新版本,然后新开宿主会话:
/usr/bin/curl -fsSL https://raw.githubusercontent.com/jefferysha/tenon/v1.1.5/install.sh | /bin/bash -s -- --claude
/usr/bin/curl -fsSL https://raw.githubusercontent.com/jefferysha/tenon/v1.1.5/install.sh | /bin/bash -s -- --codexv1.1.4 · 2026-09-15
在 v1.1.3 上用 Codex 执行真实任务时发现并修复的问题。
Codex 技能证据
- Codex 生成的两种 exec 程序写法中,完整读取
SKILL.md都会被认作技能证据。此前 Tenon 只识别const r = await tools.exec_command({...}); text(r);,写成text(await tools.exec_command({...}));的读取 不产生证据:之后第一次tenon document record必然报current StepVisit lacks exact host confirmation, agent 只能重新读取技能。两种写法都必须完整转发恰好一个 awaited 调用的结果;.output、未 await、 包裹转换和附加语句仍然拒绝。 - 该错误现在会说明恢复方法:在当前阶段重新调用产出技能(Claude Code 用 Skill 工具;Codex 用单独一条
cat读取其SKILL.md,max_output_tokens要足够容纳整个文件,被截断的读取不算证据),再重试登记。 此前 Codex agent 两次用 1000–2000 token 的输出上限读取 20 KB 的tenon-explore技能,之后才猜到要调大。
升级动作
为使用的每个宿主安装新版本,然后新开宿主会话:
/usr/bin/curl -fsSL https://raw.githubusercontent.com/jefferysha/tenon/v1.1.4/install.sh | /bin/bash -s -- --claude
/usr/bin/curl -fsSL https://raw.githubusercontent.com/jefferysha/tenon/v1.1.4/install.sh | /bin/bash -s -- --codexv1.1.3 · 2026-09-15
在慢速代理网络上为 Codex 安装 v1.1.2 时发现并修复的问题。
安装器
- 与 GitHub 的连接中断或变慢不再导致安装失败。稳定版本证明原本只执行一次
git ls-remote与浅克隆git fetch,一次ETIMEDOUT或 TLS 重置(SSL_ERROR_SYSCALL)就会让install.sh失败。现在两个调用在 传输失败时最多重试三次并短暂退避;标签或引用不存在时仍立即失败,每个结果的校验与之前完全相同。
交互门
- 用户确认解封待确认的交互后,会在对话中明确告知。此前在 Codex 中,先被拦截过的 agent 在用户回复「确认继续」 后仍以为门禁未解,没有重试被拦截的操作就停下。
升级动作
为使用的每个宿主安装新版本,然后新开宿主会话:
/usr/bin/curl -fsSL https://raw.githubusercontent.com/jefferysha/tenon/v1.1.3/install.sh | /bin/bash -s -- --claude
/usr/bin/curl -fsSL https://raw.githubusercontent.com/jefferysha/tenon/v1.1.3/install.sh | /bin/bash -s -- --codexv1.1.2 · 2026-09-15
在 v1.1.1 上继续用 Claude Code 与 Codex 执行真实任务时发现并修复的问题。
交互门
- 交互式技能(
brainstorming、grilling、grill-with-docs、prototype、huashu-design)在每次进入阶段后只向用户 确认一次。Codex 需要重新读取产出技能才能登记文档;对交互式产出技能,这次重读会再次锁住同一个问题, 导致 Codex 中 explore 无法登记设计文档与 ADR。现在用户确认后会写入InteractionConfirmed历史行, 在重新进入该阶段之前,门禁不再为同一技能加锁。 - 门禁提示会写明可解封的回复(「确认继续」「继续执行」或「同意继续」)。有待确认的问题时,未被识别为确认的 回复会明确告知 agent,不再被静默忽略。
升级动作
为使用的每个宿主安装新版本,然后新开宿主会话:
/usr/bin/curl -fsSL https://raw.githubusercontent.com/jefferysha/tenon/v1.1.2/install.sh | /bin/bash -s -- --claude
/usr/bin/curl -fsSL https://raw.githubusercontent.com/jefferysha/tenon/v1.1.2/install.sh | /bin/bash -s -- --codexv1.1.1 · 2026-09-15
在 Claude Code 与 Codex 中用已发布的 v1.1.0 真实执行任务时发现并修复的问题。
宿主兼容
- Claude Code 能重新加载插件。Claude Code 2.1 会自动加载标准
hooks/hooks.json,清单再次引用同一文件 会被拒绝,导致 Tenon 的技能与 hook 全部不可用。Claude 清单不再声明hooks。 - Claude Code 中可以登记文档。Claude Code 以
tenon:<skill>报告插件技能,Skill 回执拒绝了这种名字, 所有tenon document record都报current StepVisit lacks exact host confirmation,default 工作流 无法离开open。 - Tenon 可以在 Codex 沙箱内运行。Codex 的
workspace-write沙箱禁止执行/bin/ps;状态锁改为只记录 pid 的持有者,不再报withLock: current process start identity is unavailable。 - 宿主报告 Tenon 插件加载失败时,
tenon doctor显示红灯。
工作流与 Dashboard
- Dashboard 新建的轨道可以完整执行。技能不再用只认识项目轨道注册表的
tenon tracks show校验工作流 分支轨道;轨道 id 未注册时,tenon tracks show会说明分支轨道的查看方式。 - 已归档的运行不再显示进行中的阶段。
- 没有运行时产物的阶段返回空目录,不再在每次刷新时请求失败。
升级动作
为使用的每个宿主安装新版本,然后新开宿主会话:
/usr/bin/curl -fsSL https://raw.githubusercontent.com/jefferysha/tenon/v1.1.1/install.sh | /bin/bash -s -- --claude
/usr/bin/curl -fsSL https://raw.githubusercontent.com/jefferysha/tenon/v1.1.1/install.sh | /bin/bash -s -- --codex用 claude plugin list(Tenon 已启用且无错误)与 tenon doctor 验证。
v1.1.0 · 2026-09-15
工作流编辑器与工作台
- Dashboard 收敛为两个视图:工作台(项目、Change、阶段轨、带运行状态的阶段技能流、输入/输出文件、 运行时产物)与工作流编辑器。
- 工作流按用户全局存储,不再绑定项目。每个工作流包含自己的轨道分支;阶段可拖拽排序,门禁只有
review与auto,并可声明退回到哪个阶段。技能在画布上编排:落在同一列为并行,落在右侧为串行。 - 运行时产物保留产出者、版本与血缘;文档与字段产出走同一提交路径。
评审决策与门禁安全
- 待决 review 可以在终端或工作台右栏批准。两端共用同一个确认应用,除入口渠道 (
review_acknowledged_via)外,canonical 状态、交互记录与 history 完全一致。 - 所有被拒绝的确认都不写入任何数据。Dashboard 请求携带 expected revision 与幂等 key;过期或冲突 请求返回带稳定 code 的 409,意外错误返回不含内部细节的 500。
tenon review acknowledge对缺少复核 请求、revision 冲突、幂等冲突、无效命令分别以2、3、4、1退出。 - 所有入口离开 review 阶段都必须具备精确 receipt 与匹配 binding。
tenon set/set-many/cas不能再修改phase;tenon state import-legacy保留受 transition 控制的字段并报告被忽略的字段。 - 本地 HTTP transition 入口永远不能满足 loop human gate。review 待决期间,读取 Dashboard token 与调用 本机 API 会被记录为脱敏的自审批观测,AFK 模式下同样记录。
安全
fast-uri升级到不含高危公告的版本。
升级动作
安装不可变的 v1.1.0 入口,或运行 tenon update --codex(或 --claude)。完成后新开宿主会话以加载 更新后的 Skills 与 hooks。v1.0.9 保持不可变,可用于回滚。
v1.0.9 · 2026-09-02
Dashboard 稳定性与 Pipeline 可见性
- 适配器安装的有限事件流会在完成后确定性关闭;malformed、传输不可用会明确报错,安装按钮 不会永久停留在忙碌状态。切换项目时会解绑旧流,旧项目的迟到事件不会串到新项目。
- Definition catalog 的 SSE 在浏览器于初始加载期间离开时也会清理;慢速投影会串行化,避免 revision 乱序到达或回退页面上已经看到的定义。
- Change 创建会等待 catalog 对账,并明确展示 loading、unavailable、empty 状态。创建前会预览 选定 Pipeline 的每个 Stage、串行/并行模式、Skill 顺序和声明的依赖关系。
- 适配器请求的布尔字段现在严格校验类型,不再把字符串隐式当作值,保证 dry-run 与确认语义 对 API 客户端保持明确。
兼容性与范围
- 已有 Change 继续使用冻结的 Workflow/Track/Pipeline 身份;GUI 当前展示由 Workflow/Track 派生的 canonical Pipeline。独立命名的 Pipeline blueprint 仍通过 planner-v2 使用,待持久化 Pipeline Registry 契约落地后再进入编辑器。
升级动作
安装不可变的 v1.0.9 入口,日常更新继续运行 tenon update --codex(或 --claude)。之前的 v1.0.8 发布保持不可变,可作为回滚版本。
v1.0.8 · 2026-09-02
编排输入与 Pipeline Runtime
- Skill 依赖会在执行前物化为版本化的
skill-input-manifest/v2和受限输入包;Executor 与 Validator 接收同一个经过摘要校验的输入。输入投递被拒绝时会 fail-closed,绝不会调用 Skill。 - Skill 的规范化产物会原子写入
.tenon-artifacts/,并登记artifact://引用、Schema、字节数 和 SHA-256 摘要;下游 Skill 通过 Manifest 契约读取,而不是依赖模型自行寻找文件。 - 自定义 Workflow、Track、Pipeline、Stage 和 Skill 依赖遵守声明的串行/并行模式及资源声明; 重叠的写入资源不会并发执行。
安全与依赖维护
- Browserslist 通过仓库 override 和锁文件固定到首个修复版本
4.28.7,消除高危公告,未放宽审计门禁。
升级动作
安装不可变的 v1.0.8 入口,日常更新继续运行 tenon update --codex(或 --claude)。已存在的 Change 会继续使用冻结的 Workflow/Track/Pipeline,只有显式 replan 才会切换定义。
v1.0.7 · 2026-08-11
跨版本 installer bridge 恢复
- 公开 installer 只有在旧稳定版本的 durable WAL 已精确到达宿主阶段
plugin-installed,并且当前插件与 Marketplace 仍逐项匹配旧版本的 plugin version、稳定 tag、commit、官方 source/type/origin、ref 与 clean checkout 时,才会接管它。 - 任何其他 phase、malformed/unknown 数据、同版但 tag/commit 不等于当前已证明 target 的 WAL、更高目标版本,或任意宿主 inventory drift,都会在 host mutation 前 fail-closed,并保留原 WAL 供诊断或恢复;精确匹配当前 target 的同版 WAL 仍沿用既有恢复路径。
- 旧事务会被原子替换为 current-target 事务,并以已验证的宿主状态作为 before 快照;现有 exact provenance、trusted host、锁、原子性与 packaged setup 校验保持不变。不增加 retry/fallback,也不弱化 stable Release/object proof。
升级动作
当前公开入口使用不可变 v1.0.7;日常升级仍运行 tenon update --codex(或 --claude)。
v1.0.6 · 2026-08-11
Stable Git proof 网络预算
- 慢链路实测显示公开 stable tag/object proof 可能超过此前 30 秒 Git 预算:proxy
ls-remote/fetch 为 6.9 秒/11.3 秒,直连 fetch 达到 22.9 秒,而正式事务中仍偶发更长阶段。 - Git 远端
ls-remote与 fetch 现在采用有界 60 秒预算;GitHub Release API metadata 和 npm bootstrap raw installer 下载仍为 30 秒;本地 init/rev-parse/cat-file proof 仍为 10 秒,宿主 observation 默认仍为 5 秒。 - exact stable tag/object/commit、digest、trusted executable、官方 HTTPS host、大小限制与原子性校验保持不变;不重试、不使用 source/branch/cache fallback,失败继续在 mutation 前 fail-closed。
升级动作
当前公开入口使用不可变 v1.0.6;日常升级仍运行 tenon update --codex(或 --claude)。
v1.0.5 · 2026-08-11
Doctor 发布身份证明
tenon doctor的发布身份探针现在会传递远端 Git tag/object proof 的有界 30 秒预算,以及本地证明命令的 10 秒预算。- 宿主 observation 命令仍保留默认 5 秒超时;不增加 retry、不使用 source/branch/cache fallback,也不弱化 trusted-executable 或其他安全校验。
升级动作
当前公开入口使用不可变 v1.0.5;日常升级仍运行 tenon update --codex(或 --claude)。
v1.0.4 · 2026-08-11
公开安装与更新网络预算
- shell installer 的 GitHub Release metadata/tag proof、
tenon update的 Release metadata 请求,以及 npm bootstrap 的 installer 下载,统一采用有界 30 秒网络预算。 - exact stable Release、tag/object、digest、host trust、官方 HTTPS host、大小限制与原子性校验保持不变。
- 仍然不重试、不使用 source/branch/cache fallback;失败继续在 mutation 前 fail-closed。
升级动作
当前公开入口使用不可变 v1.0.4;日常升级仍运行 tenon update --codex(或 --claude)。
v1.0.3 · 2026-08-11
Stable Release 证明诊断
- 远端 tag/object proof 的网络预算从 10 秒提升为有界 30 秒;本地证明命令仍保持 10 秒预算。
- timeout 失败现在保留
ETIMEDOUT等可诊断的 stderr 信息,不再返回空错误详情。 - 安全验证、原子发布以及无 retry、无 fallback 语义保持不变。
升级动作
日常升级仍使用 tenon update --codex(或 --claude),并继续绑定经过验证的稳定 Release tag。
v1.0.2 · 2026-08-08
版本化安装与更新
- 公开一键安装固定使用不可变
v1.0.2预构建资产,不从main安装,也不编译源码。 tenon update --codex解析官方最新稳定 GitHub Release,冻结 tag 与 commit,再通过宿主官方命令重绑定 Codex Marketplace。- 宿主、managed runtime 与 Dashboard 精确同版时零 mutation;降级或无法验证 Release 身份时在 mutation 前失败。
- setup 始终等待 Dashboard readiness;curl/CI 安装和所有更新不自动打开浏览器,并打印已验证 URL 与
tenon dashboard --open。
升级动作
v1.0.1 用户先一次性运行不可变的 v1.0.2/install.sh 一行命令;旧 launcher 无法在一次旧 updater 调用中安全自重绑新 tag。从 v1.0.2 起,每次只运行一条 tenon update --codex。新开 Codex 会话 加载已发布 Skills/hooks 后,运行 tenon doctor --json。
v1.0.1 · 2026-07-26
正常对话入口契约
product/identity.json新增entrySkill: "tenon",它是唯一公开入口。- Codex 正常对话统一调用
tenon:tenon,不保留第二入口别名。 - 根
AGENTS.md与 Codex 静态 adapter 消费同一份生成 managed block。 tenon doctor会验证入口 Skill,并把仍启用的冲突工作流插件报告为红灯。tenon setup --codex -y会先通过 Codex 官方插件管理器移除该精确旧登记,再激活 Tenon。
仓库与发布卫生
- CI 与 Release 对所有受版本控制路径和文本执行外部参考项目身份扫描。
- 扫描不区分大小写、没有豁免,诊断信息也不会回显受限名称。
- Release payload 构建前执行同一门禁,避免源码干净但发行包污染。
升级动作
运行 tenon update --codex,随后运行 tenon setup --codex --auto-update -y。新开 Codex 会话后执行 tenon doctor --json。
v1.0.0 · 2026-07-26
中文治理文档
- 新 Change 的治理文档默认固定为
zh-CN。 tenon init、tenon document scaffold与 default OpenSpec fallback 使用同一 Document Presentation Registry。- 用户可以在创建时显式选择
--document-locale en。 - 已固定 locale 的 Change 不允许在中途静默切换语言。
- 历史 Change 会从现有 H1 文字信号推断语言。
- 语言信号混合或不足时命令失败并要求显式选择,不会猜测覆盖。
执行模式
- Discussion 用于不需要状态机的普通问答。
- Simple 使用
change → verify → done,不生成完整 OpenSpec 文档链。 - Default 使用
open → explore → spec ⇄ build ⇄ verify → ship → archive。 - Free 显式绑定 workflow,不叠加 PM、前端或后端 Track。
- Custom 完全遵守自身声明的 DAG、Skill、gate 与 document contract。
文档站
- 仓库首页 README 默认中文,并提供
README.en.md。 - 文档站提供中文根路由与
/en/英文镜像。 - 本地搜索基于公开 content manifest 构建。
- GitHub Pages 只从
main分支部署。 - Pull Request 只构建和检查,不执行生产部署。
- 发布 artifact 经过闭集 allowlist、敏感信息扫描和 project base 检查。
llms.txt只索引公开页面。- 内部 ADR、Superpowers 计划、review receipt 与本地控制面状态不会进入公开站点。
安装与更新
- Codex 使用
tenon setup --codex。 - Claude 使用
tenon setup --claude。 - 更新使用对应宿主的
tenon update --codex或tenon update --claude。 - 托管 runtime 以内容摘要发布,稳定 launcher 指向已验证版本。
- 更新失败时保留上一版,可用
tenon runtime repair --rollback恢复。 - Dashboard 默认监听
127.0.0.1:18765。
升级动作
- 在现有仓库确认工作区状态。
- 运行对应宿主的
tenon update命令。 - 运行
tenon runtime status查看活动版本。 - 运行
tenon doctor检查安装、Skill 与宿主适配。 - 在项目中运行
tenon list --json验证 CLI 可读状态。 - 打开 Dashboard 时确认地址为
127.0.0.1:18765。
验证
tenon --help能显示命令族。tenon runtime status能显示活动 runtime。tenon doctor不报告缺失的内建 Skill。tenon setup --codex重复运行保持幂等。tenon update --codex不修改项目的 canonical Change 状态。- 新建测试 Change 时 proposal、design 与 tasks 默认中文。
- 显式英文 Change 的新文档保持英文。
兼容性
canonical Change codec 不因文档 locale 增加新字段。
locale 固定信息保存在 .pipeline-document-locale.json sidecar,因此旧版 runtime 仍可读取 canonical state。
发行资产继续包含 default、simple、free 与 custom workflow 所需的模板和 Skill。
已知边界
GitHub Pages 的真实公开 URL 只有在 main workflow 成功部署后才能确认。
本地预览通过不等于远程部署成功;应以 Actions 的 deploy job 和 Pages environment 为准。
Dashboard 的界面语言与治理文档 locale 是两个独立边界,不互相覆盖。
回滚
如果升级后的 runtime 无法启动,先运行 tenon runtime status 收集版本信息。
随后运行 tenon runtime repair --rollback 切回上一份已验证内容摘要。
回滚 runtime 不会删除项目中的 Change、OpenSpec 文档或证据账本。
版本记录规范
未来发布必须在本页增加中文条目,并同步英文镜像。
条目必须对应真实提交、构建和验证证据。
未验证的规划只能写入 roadmap,不得提前进入发布说明。
每次发布还应检查安装命令、更新命令、Dashboard 端口和 Pages 路径是否与源码真相一致。
下一步
继续阅读更新、恢复与卸载,了解完整的运行时维护与恢复流程。