Release notes
Tenon release notes explain what changed, what users need to do, and how to verify an upgrade.
Only capabilities included in a public distribution belong here. Plans, internal ADRs, and unmerged experiments are not presented as shipped work.
v0.3.4 · 2026-10-14
v0.3.4 makes the agents of a task dependable and lets several tasks in one repository run side by side. Tenon's agents are installed once, from the terminal, as user-level host subagents, so a session no longer meets "subagent not found" at its first task. A new task is created in its own Git worktree, and the start point that decides which changes belong to it is recorded when the task is created and follows the upstream. A line in the terminal shows which step a task is on, and going back from a step no longer needs an agent to be started first. Read "Behaviour changes and upgrade impact" before you upgrade a project.
Host subagents installed from the terminal
tenon agent install [--host claude|codex]writes the official and custom agents that apply to the host as user-level subagent files:<Claude config dir>/agents/tenon-<name>.mdfor Claude Code and<Codex config dir>/agents/tenon-<name>.tomlfor Codex. The config dir is$CLAUDE_CONFIG_DIR(Claude Code) or$CODEX_HOME(Codex) when that variable is an absolute path, otherwise~/.claudeor~/.codex.--hostdefaults to the host detected from the environment, and toclaudein a plain terminal. It exits0when done (files it must leave alone areWARNlines and count as done) and1for a usage error, an unassembled library or a failed write.- Before,
tenon initandtenon agent promptwrote.claude/agents/tenon-<name>.mdinto the project from inside the session, and a subagent file added in the middle of a session is picked up at a time the host does not specify, so the first task created in a session could dispatch a subagent that was "not found". Only files that exist before the session starts are dependable, so no Tenon command that runs inside a session writes them any more. - When it runs.
tenon setup --claude|--codexandtenon update --claude|--codexrun the same install after they succeed (updatealso when it finds the release already current) and print what was written. A failed install only warns: the exit code ofsetuporupdatedoes not change, and you can runtenon agent install --host <host>yourself.--dry-runinstalls nothing, adapter hosts have no subagent directory and skip the step, and the once-a-day automatic update (--auto) speaks only when the install fails.tenon agent new|add|copy|rmchange the library only and print a reminder to runtenon agent installand open a new session. - Ownership. A content hash per file is kept in
<config>/agents/host-owned.json, grouped by the directory the files were written to, so installing or uninstalling in one config dir never deletes, rewrites or claims the files Tenon wrote in another. A file is written only when it does not exist yet, already has exactly the rendered content (Tenon then records that it owns it), or Tenon wrote it earlier and nobody has edited it since. A same-named file that is yours, or one you edited, is left alone and listed as aWARN; a target file that is itself a symlink is refused (a symlinked parent directory, such as a dotfiles-managed~/.claude, is fine). When an agent has left the library, its file is removed if Tenon wrote it and it is unedited. Ifhost-owned.jsoncannot be parsed, no existing file is changed or deleted and only missing files are written. tenon agent uninstall [--host claude|codex]deletes the files Tenon wrote that are unedited; edited ones are kept and listed asmodified.tenon uninstallworks on the project's.pipeline-owned.jsonand does not touch them, so runtenon agent uninstall --host <host>first if you want them gone.- Project agents (
.tenon/agents/) are never installed, so the result is the same in whichever directory you run the install. A project agent always runs as the host's generic subagent, and its tool restriction is only the instruction in its body. tenon agent promptwrites no host file. With--jsonit returnssubagent_type: tenon-<name>andnative: trueonly when the installed file is byte-for-byte what the task's frozen definition renders for the host and the project has no different same-named file (.claude/agents/tenon-<name>.md,.codex/agents/tenon-<name>.toml) that would shadow it. Otherwise it returns the host's generic subagent (general-purposefor Claude Code,defaultfor Codex) withnative: false, areason(not-installed,definition-mismatch,shadowedorproject-agent) and a one-linehint, always in Chinese, that names the directory it checked. A task keeps the definition it froze, so a task that froze an older version of an agent stays on the generic subagent instead of silently using a newer one.tenon agent record <change> <run-id> --subagent nonerecords a run for which no subagent was dispatched (reason: not-dispatched). The reason of a run that did not use the dedicated subagent is kept in the run side note.pipeline-agent-run-meta.jsonlassubagent_reason, and the ledger row keeps its threesubagentkeys.- Files that earlier versions wrote into the project outrank the user-level ones, so Tenon removes them: at
tenon init, and when a task is finished, terminated or deleted (CLI and Dashboard). A leftover is deleted when the project's.pipeline-owned.jsonlists it and its content still matches the recorded hash, or when its content equals what the current library renders for that agent. Anything else (edited files, files that match neither rule, unreadable ones) is kept and reported as aWARNnaming the file, and a failure never changes the exit code. - Open a new session after installing: the host loads subagent files when a session starts. Install in the environment the host starts in. If your Claude Code or Codex starts with
CLAUDE_CONFIG_DIRorCODEX_HOMEset, run the install with the same variable, or the files land in~/.claude/agentsand the host never sees them. The first line of the output,目标目录:<dir>, shows where the files went, and inside a session! tenon agent install --host claudeuses the session's environment. See Host subagents and the install command.
Step progress in the terminal
- After
tenon init,tenon session activate,tenon transitionortenon step run <name>succeeds, the PostToolUse hookstep-progressprints one line taken from the task's frozen plan:✓for steps already passed,●for the current one,○for those not reached, and✓everywhere once the task is archived. Steps are joined by→and labelled as the workflow declares them (立项 ✓ → 调研 ● → 规格 ○ → …). After a back edge (for exampleverify-failto Build) the later steps show○again. tenon workflow progress <name>prints the same line, and finds a task that lives in a worktree from the main checkout too. When it cannot read the task (invalid name, unreadable state, no plan, no steps) it exits1with an empty stdout.- The line does not depend on the host's Todo tool, which recent Claude Code models do not offer by default; the
tenonskill fills the host's Todo list fromtenon workflow plan <name> --jsononly when the host has one. The hook runs only after the host reports the command as successful, so a failed command prints no line, and any hook problem prints nothing rather than an error. It recognizes task names made of lowercase letters, digits and hyphens; for another name runtenon workflow progress <name>yourself. See Step progress in the terminal.
Each new task in its own worktree
- In the main checkout of a Git repository,
tenon initcreates the task in<project>/.worktrees/<change>on a new branchtenon/<change>, cut from the currentHEADand tracking nothing. The task recordsbase_branch(the branch you were on),start_sha(thatHEAD) andisolation=worktree. Tasks in one repository run side by side, and none sees another's changed files, test registrations or build fingerprint..worktrees/is added to.git/info/excludewhen no ignore rule covers it, sogit statusin the main checkout does not change. Creation is all or nothing: ifinitfails the worktree and the branch are removed again, and an existing path or branch of that name makesinitexit1and changes nothing. - In place instead.
tenon initcreates the task in the current directory, as before, and says why on stderr, when the directory is not a Git repository (or not its root), the repository has no commit,HEADis detached, the directory is itself a linked worktree, or you pass--in-place(use it only when you want that; thetenonskill passes it only if you ask).TENON_INIT_IN_PLACE=1has the same effect without printing a reason and exists for Tenon's own tests and scripts. A task created by AFK triage is created in place, and a task created before the upgrade stays in its checkout. - Output. Text output keeps stdout empty; its last two stderr lines are
worktree: <absolute path>and how to switch in.--jsonprints one line on stdout withchange,path,worktree(the absolute path, ornull) andworktree_reason(nullfor a worktree, otherwisenot-git,no-commits,detached-head,linked-worktreeorrequested). - Switching in and resuming. In Claude Code the
tenonskill switches into the worktree withEnterWorktreeand then runstenon session activate; from then on commands, review confirmations and hooks use the worktree as the project root. Codex has no equivalent: the skill asks you to open a new session in that directory and ends the turn. Resuming works the same way: when the router names a task that exists only under.worktrees/, its dispatch carriesworktree_path. When you say "continue" in the main checkout and only worktree tasks can be continued, the dispatch lists them with their paths (change_candidate) and asks you to pick one. Worktree tasks are not counted when the router decides between creating a task and selecting one. - Finding the task.
tenon listhas a finalWORKTREEcolumn (-for a task of the current root) and its--jsonrows carryworktree; run in the main checkout it also lists the unarchived tasks under.worktrees/. A command that reads a task by name in the main checkout (status,get,check,workflow plan) exits1for a task that lives in a worktree and names the worktree path in the error. - Preparing the worktree. A new worktree has no installed dependencies or build output. The test catalog (
.tenon/tests/catalog.yaml) can declare one top-levelsetupstring, such asnpm ci, andtenon initruns it in the new worktree, with its output on stderr, when the command is trusted. Trust is per user, repository and command, so every worktree of the repository shares it: asetupyou already trusted runs at once, and one that is not trusted yet is not run:initprints it and asks you to runtenon test trustyourself (an agent cannot confirm it for you), and then to run the command by hand. The worktree holds the committed catalog, so an uncommitted edit ofsetupin the main checkout does not count. A failingsetuponly warns, with the command to rerun it, andinitstill exits0; a missing or invalid catalog, or nosetupkey, is reported and skipped.tenon test trustlistssetupwith the suites, andtenon test runneither runs nor checks it.setupcan take minutes: give the shell call a generous timeout (the skill uses 600000 ms); a timeout only stopssetup, and the task already exists. - Finishing. Delivery and finishing happen on the
tenon/<change>branch. When the task is archived Tenon leaves the worktree and the branch in place and only reports the command that removes them, ascleanupon thefinish-changeaction and on thestopwithcode: finished:git worktree remove '<path>' && git branch -d 'tenon/<change>'. Run it yourself, in the main checkout and not inside the worktree it removes;git branch -drefuses a branch that is not merged, so no commit is lost.finish-changealways has the key (nullfor a task created in place); thestophas it only for a worktree task. - The build revision of a worktree task binds a content fingerprint of the workspace, as for an in-place task, because the implementation in a task worktree is usually not committed when Build ends and Build never requires a commit; only
isolation=branchbinds a Git object. The identity stays bound to the physical repository and worktree, so verifying a worktree task from another checkout givesworktree-mismatch, and a token of the wrong kind for the task'sisolationgivesisolation-mismatch. Both mean: return to Build and capture again. - Each new task costs a worktree, a branch and a dependency install, and Codex needs a new session. The Dashboard registers each worktree as its own project and has no view across worktrees. See Where the task lives.
A recorded start point
tenon initrecords theHEADit started from as the task fieldstart_sha(AFK triage does the same; nothing is recorded when the repository has no commit). The start of "changed since the task started" is the newest of three candidates:start_sha(when it is an ancestor ofHEADand committed no later than the task'screated_atplus 60 seconds), the merge-base ofHEADand the upstream ofbase_branch, and the merge-base ofHEADandbase_branch(when that is notHEADitself). Candidates that do not form a chain fall back to their common ancestor, so the list can show too much but never too little; with no candidate at all it is the last commit beforecreated_at, as before. No commit message or author is read.- What you see: commits that others merged into the base branch and that you then fetched or pulled stop counting as this task's changes, instead of appearing as inherited test files and a changed catalog. Test registration and sync, the changed-files list, the approval of protected test configuration, the test-integrity report, the
diff-riskprobe, reviewers scoped byattach_on, changed-line coverage, evidence export, the transition gates (CLI and HTTP), the Dashboard snapshot andtenon verify --ciall compare against this one start. In a CI checkouttenon verify --citakesorigin/<base>as the base when that reference exists, and the local branch otherwise. start_sha,base_branchandcreated_atare written only bytenon init:tenon set,set-manyandcasrefuse them (exit1, nothing changes).base_branchandcreated_atused to be writable.start_shais kept in a record next to the task state (.pipeline-run/start-sha/), not in.pipeline.yaml. That record is not covered by the state digest, which is why its commit time is checked againstcreated_at: rewriting or deleting it can only move the start earlier and show more files, never fewer.- Limits. The upstream is a local reference, so without a
git fetchit can be stale, which only counts more files. An in-place task that commits on the base branch and pushes to its upstream moves its start past those commits, and they stop counting as its changes; a worktree task is not affected. A commit made with a forged commit time earlier thancreated_atpasses the time check. See Task start point.
Fixed
- Going back from a step no longer requires starting an agent. A back edge (such as
requirements-changedfrom Build orverify-failfrom Verify) no longer asks for the skills of the agents the step declares: those skills can only be loaded while the agent runs, sorequirements-changedfrom Build needed a builder run started first. Skills the main thread loads itself are still required on a back edge, and a forward edge requires everything, as before.tenon status,tenon check,tenon transitionand the Dashboard transition apply the same rule. - An untracked
.claude/worktrees/no longer moves the workspace fingerprint that build revisions and reviewer verdicts bind. Claude Code creates that directory for the checkouts of its subagents and sessions, another session of the same repository keeps changing it, and it can hold a very large number of files: a task in the main checkout wentrevision-stale, and every fingerprint, so everytenon status, was slow. The traversal no longer descends into it. A file in it that git tracks still counts, so a pull request cannot hide code there. Test records already bound a fingerprint that leaves it out.
Behaviour changes and upgrade impact
tenon initcreates a worktree in the main checkout of a Git repository. Scripts and CI that runtenon initand then keep working in the same directory pass--in-place(the CI bench step of this repository does) or change into the path that--jsonreturns. A command that reads a task by name runs in the task's own root.- Dedicated subagents need
tenon agent install. Until you have run it and opened a new session,tenon agent promptreturns the generic subagent withreason: "not-installed"and the install hint, and the agent runs without itstoolsallowlist andmodel. - A trusted
setupruns without asking. Trust binds the command text, not the repository files the command reads when it runs, such as thepackage.jsonlifecycle scripts or the scripts they call, the same as for test suites. Aftersetupis trusted, a later commit that changes those files makestenon initrun the changed version in the new worktree. To avoid that, create the task withtenon init --in-placeor removesetupfrom the catalog. tenon set,set-manyandcasrefusestart_sha,base_branchandcreated_at(exit1). A script that wrotebase_branchorcreated_atfails now.tenon listhas a new last column,WORKTREE, and its--jsonrows have aworktreekey. A script that reads the table by column position or compares rows exactly needs an update.tenon uninstallleaves the user-level subagent files. Runtenon agent uninstall --host <host>first if you want them gone.
Development
- An external task tool used only in Tenon's own repository is removed: its project hooks used relative paths from a gitignored directory, so they failed inside git worktrees and blocked prompts. Nothing changes for users.
- A test or script that runs
tenon initin a temporary git repository and expects the task in that directory must exportTENON_INIT_IN_PLACE=1, and a fixture that must not touch the host configuration must unsetCLAUDE_CONFIG_DIRandCODEX_HOME: the user-level subagents andAGENTS.mdresolve from them. The vitest global setup and the shell test scripts of this repository do both. - This repository's
.tenon/tests/catalog.yamldeclaressetup: npm ci && npm run build:packages.
What you need to do
Run tenon update --claude (or --codex), then:
- Install the host subagents in the terminal with
tenon agent install --host claude(or--codex), and open a new host session. Thetenon updateyou just ran is the code of the release you updated from and has no install step; from now ontenon setupandtenon updaterun it, and a secondtenon updatedoes it now. If Claude Code or Codex starts withCLAUDE_CONFIG_DIRorCODEX_HOMEset, run the install with the same variable (inside a session:! tenon agent install --host claude) and check the目标目录:<dir>line. Run it again after you add, edit or remove a custom agent. - Scripts and CI that run
tenon initand then work in the same directory: add--in-place, or readworktreefrom--json. - To have new worktrees prepared, declare
setup(for examplenpm ci) in.tenon/tests/catalog.yamland runtenon test trustonce in your own terminal. Without it a new worktree has no installed dependencies, and the tests do not run there (tenon initsays so). - Remove writes to
base_branchandcreated_atfrom scripts that usedtenon set. - On a machine whose project has an untracked
.claude/worktrees/, a task in flight can showrevision-staleonce on its first check: return to Build, capture the current revision again, and run the reviewers again (see Compatibility). - When a task in a worktree is finished, run the
cleanupcommand thatstep.nextreports, in the main checkout. - Codex: the hook files changed, so Codex may ask you to trust them again. Run
/hooks, trusttenon, and start a new session (see Codex hook trust). - Before rolling back to an earlier release, finish or terminate the tasks you created in a worktree and the tasks that have a run on a generic subagent, remove
setupfrom.tenon/tests/catalog.yaml, and runtenon agent uninstall --host <host>if you want the user-level subagent files gone (see Compatibility).
Compatibility
N-1 is v0.3.3. The N-1 gate (tools/test-bundle.sh) crosses it with this release in both directions on every run.
- The gate crosses the test record chain, the agent run ledger with its sidecar, the test plan, catalogs written by
discover --write, chains pruned byTENON_RECORD_RETENTION, and records written beside an untracked.claude/settings.local.json. It creates its tasks in place (TENON_INIT_IN_PLACE=1). The other limits below are known and not crossed by the gate. - v0.3.3 cannot read the catalog key
setup: a catalog with a top-levelsetupis reported with an unknown key. Before rolling back, removesetupfrom.tenon/tests/catalog.yaml. - A task that
tenon initcreated in a worktree hasisolation=worktree. v0.3.3 binds that value to a Git object where this release binds a workspace fingerprint, so a build revision captured by one version is refused by the other at Verify (isolation-mismatch). Finish or terminate the tasks you created in a worktree before rolling back (see Updates, recovery, and uninstall). v0.3.3 does not know.worktrees/either: in the main checkout itstenon listdoes not list those tasks, and a command that reads one by name sayschange not found; run it inside the worktree. - v0.3.3 cannot read the agent run side note of a run that did not use the dedicated subagent: the row carries
subagent_reason, which v0.3.3 does not know, so it reports the task's agent run ledger as corrupt (runs-corrupt). That covers runs dispatched to a generic subagent (reasonsnot-installed,definition-mismatch,shadowed,project-agent) and runs recorded with--subagent none. Finish or terminate such tasks before rolling back. start_shais kept in a companion record, so v0.3.3 reads a task that has one and can continue it. The record is not carried into the revisions v0.3.3 writes: back on this release the start of that task is computed from the upstream and base-branch candidates only, as for a task created before the field existed.- v0.3.3 does not know the user-level subagent files or
host-owned.json. From inside a host session it writes the project-leveltenon-<name>files again (tenon init,tenon agent prompt --host), and a project-level file outranks a user-level one of the same name; this release removes them again attenon initand when a task ends. - Fingerprint rule change: on a machine whose project has an untracked
.claude/worktrees/, v0.3.3 counts that directory in the fingerprint that build revisions and reviewer verdicts bind, and this release does not. A task in flight across the upgrade (or across a rollback) can show a one-timerevision-staleon its first check, and its reviewers have to run again. The remedy is the usual one for a stale build revision: return to Build and capture the current revision again. Test records bind a fingerprint that never counted that directory and are not affected, and neither are tasks in a project without it. - Commands and options added:
tenon agent installanduninstall,tenon workflow progress,tenon init --in-placeand--json,tenon agent record --subagent none, theWORKTREEcolumn oftenon listandworktreein its--jsonrows,reasonandhintintenon agent prompt --json, thecleanupkey of thefinish-changeaction, and the catalog keysetup. Changed:tenon set,set-manyandcasrefusestart_sha,base_branchandcreated_at. Removed: the project-level host agent files thattenon initandtenon agent promptwrote.
Verify
tenon --version
tenon doctor
tenon runtime status
tenon agent install --host claude
tenon list --jsontenon --version prints 0.3.4. In tenon doctor, identity:release lists 0.3.4 for the host plugin, the runtime and the Dashboard server. tenon runtime status shows the active release with valid=yes. tenon agent install prints 目标目录:<dir> first and exits 0; on a repeated run each file is reported as 未变化 (unchanged). In a new session, tenon agent prompt <change> <agent> --host claude --json for an agent of the task's current step returns subagent_type: tenon-<agent> with native: true. Every row of tenon list --json has a worktree key, the absolute path of the worktree or null for a task of the current root.
v0.3.3 · 2026-10-14
v0.3.3 makes a task lighter to start, to configure and to confirm. Only two Workflows are built in now: default, whose Tracks are lightweight (lite), product (pm), frontend (frontend) and backend (backend), and the flat design-system. A Workflow no longer declares the documents of a step, because Tenon records what each step wrote. A step declares its testing with one level, and the default code-size test has no line ceiling. A back edge within the round limit and a read-only command chain no longer wait for a confirmation, and parallel sessions in one project no longer block each other. Read "Behaviour changes and upgrade impact" before you upgrade a project.
One default Workflow
- The lightweight Track replaces the
standardandsimpleWorkflows:open → build → verify → donewith one review gate and a change-risk probe in Build. When the probe fails the task escalates:scope-expandedends it as Escalated and names the command that opens the heavy Track task and links it withdepends_on(the domain recorded bytenon init --domain, otherwise a list of Tracks for you to choose from). - A development request is assessed before a task exists. Routing decides only the domain. Then
tenon assess --files <path>...checks the files you expect to change against the lightweight limits (exit0within them,3over a limit with the breached limits listed,1error), and thetenonskill shows you the result and its recommendation and creates the task only after you confirm. A request with a heavy signal (by default words such as architecture, migration, schema, authentication or release) goes to the domain's heavy Track directly.tenon init --domain <track>records the heavy Track a lightweight task escalates to. - A Track is a branch (
tracks.<id>) of a Workflow YAML with its own steps and three optional settings:spec(openspecornone),route(keywords,exclude,priority,fallback) andautomation(eligible,enqueue_after_spec). A Workflow withouttracks:is its own single Track. Routing is generated from the branches, so a Workflow saved on the Dashboard routes the next message by its new rules.tenon tracks listlists the Tracks of every Workflow. defaultruns from its YAML. Its rules are written there as leave conditions, three of them general:tasks-through-step,not-afk-queuedanddependencies-ended.tenon check <name>without an event lists the unmet conditions of the step and of its forward edges at once and exits2.- Every task ends in
openspec/changes/archive/.tenon task finish <name>andtenon task terminate <name>move the task there with the result Finished, Escalated or Terminated, andtenon list --finishedlists ended tasks. The Workspace categories are All · Needs you · Running · Ended, and an Ended card shows its result. - The workflow page edits a Track's spec and routing keywords and lists each step's leave conditions read-only.
Documents recorded at runtime
- A Workflow no longer declares the input and output documents of a step. Entering a step takes a checkpoint (git
HEADand the files that differ from it, with their SHA-256); leaving it compares the workspace with the checkpoint and records the files added, modified and deleted, with the Skill that wrote each (unknownwhen Tenon cannot tell), in.pipeline-step-outputs.json. Shell and subagent writes count. Outside a git repository nothing is recorded and no transition is blocked. - The next step receives only the Markdown documents its direct upstream step produced.
step.nextlists the unread ones inread-documents, andtenon document read <name> all(or one path) writes the read receipt. No edge except the abandon edge can be left while an input is unread (exit1).tenon document status <name>prints the inputs, outputs and required outputs of the step. - The
required-outputguard,{ type: required-output, path: <pattern> }, on a step or an edge: the step must have written a matching file in this task before it can be left (exit2).{change}is the task name,*matches inside one path segment,**any number of segments and?one character.defaultuses it for the proposal, the design, the delta spec, the plan and the verification report. - Observation, handoff, read receipts and required outputs work the same on every Track and Workflow and do not depend on OpenSpec. A Spec Kit style flow is a plain Workflow that requires
specs/*/spec.md,plan.mdandtasks.md. See Documents, Skills, and evidence. - The Workspace shows the real inputs of each step (file, source stage, source skill, read or unread) and outputs (file, added, modified or deleted, source skill). The workflow page has no input or output tables any more and lists "Required output
<path>" under the gate.
Simpler test settings
- One level per step. A workflow step declares
test: none | plan | changed | fullinstead of the multi-fieldtest_policy(a step cannot carry both, and a frozentest_policyis still read).defaultuseschangedat Build andfullat Verify on the lite, frontend and backend Tracks,planat Spec of frontend and backend, andnoneat Spec of pm (its Verify ischanged). The defaultfullruns the unit class in full and the other classes the change touches, not every integration and e2e suite. levels. The catalog (.tenon/tests/catalog.yaml) takes an optional top-levellevelskey that definesplan,changedandfullwith six keys (required,touched,scenarios,coverage,flaky,require_baseline); what you leave out keeps the built-in default (required: [unit], no coverage threshold,scenarios: off). When a task changeslevels, the change summary intenon review requestmarks each loosened or tightened key per level.- Five test classes:
unit,integration,e2e,staticandbenchmark(pluscustom, run only when named) group the 17 kinds. A suite'skindmay be a class or an old kind name. - Claiming, not registering. A test file the change adds or modifies is claimed by the suite whose
filesglob matches it and is never written totest-plan.yaml. A file no suite claims is an orphan (test-file-orphan), andtenon test register <change> --autoappends its exact path to a suitable suite; it no longer widens a glob or scans the whole repository. Paths underopenspec/,.tenon/and.pipeline/, and files without a source extension (for example.spec.report), are never test files. - Suites without
covers. A suite is touched when a changed file matches itsfilesorcoversglob. When a change edits only source files,changedandfullskip a suite that has nocoversand print the non-blocking noticesuites-without-coversnaming it; writecoverswithtenon test catalog set <id> --cover-glob "<glob>"to have source changes run it. tenon test sync <change> --prunedeletes from the plan'sfilesthe registrations the catalog no longer claims, which clearsregistered-test-not-executed.- Catalog writes keep your comments:
catalog add|set|rm|not-applicableandregister --autorewrite only the lines that changed. Scenario mapping is off by default, so no scenario blocks an exit and the traceability matrix lists the mapped rows plus one "N more ... not mapped" line. - Test trust is per user, repository and command.
tenon test truststores the decision in your user state directory, every worktree of the same repository shares it, and editing one command asks again for that command only. The hook refuses an agent writing under atest-trust/path. code-sizehas no ceiling by default. The 2000-linelines_addedlimit is removed from the default workflow and thecode-sizedirection; the probe still reports its metrics, exits0and records the measured values, and thecode-sizereviewer reads and judges them. A project that wants a limit writespass.metricsunder the test in its workflow. Atest:<id>waiver shown inreview requestnow carries the latest measured value next to your reason.- Run records carry relative paths: the repository root and your home directory (
~/) are rewritten before the digest is computed. Existing records are not rewritten. - Test output no longer invalidates the candidate. An untracked file with an evidence extension (log, txt, xml, md, csv, png, jpg, jpeg, gif, webp, webm, mp4, lcov, info) under
test-results/,playwright-report/orcoverage/is not part of the candidate fingerprint, so an e2e server writing a log does not make the frozen build stale. Tracked files, scripts, files without an extension and formats that code or a browser can load or unpack (json, html, htm, zip, gz, har) still count. - Dashboard: the workflow page picks a step's level with one choice, the project Tests page shows the effective level definitions, and the task Tests tab marks claimed files and collapses the scenario trace.
Fewer confirmations
- Back edges within the round limit. On a step limited by verification rounds (a review gate with a back edge, such as Verify with
verify-fail), taking the back edge while rounds remain needs no review receipt: runtenon transitiondirectly.step.nexthands youtransitionfor the only back edge instead ofrequest-review, and the Dashboard transition endpoint behaves the same. Forward edges, residual blockers and test waivers still need a human confirmation, and once the rounds are used up the back edge is refused as before. - Every successful transition deletes the review marker (
.pipeline-pending-review) bound to that task, so a review request that was open before the upgrade, or a forward review that was waiting when you asked to go back, no longer leaves a stale marker that blocks writes for up to 30 minutes. A failed delete only prints a warning. - Read-only command chains. While a review, interaction or confirmation is pending, the write gate lets through commands joined by
|,&&,||or;when every segment is read-only (git status --short | grep x,cd sub && ls). Redirections other than>/dev/null,2>/dev/null,&>/dev/nulland2>&1, a background&, command substitution or variable expansion (including$IFS), unclosed quotes and commands over 2048 characters are never read-only. Some read-only commands are refused in their writing or program-running forms:git diff|log|show --output,--ext-diffand--textconv,rg --pre,-zand--search-zip,sed -nwith anything but a line-range print, andfile -C. - While a review waits, the gate also lets exactly
tenon transition <change> <event>for the task that owns the marker through and leaves the decision to the CLI, so an agent can take a back edge when you ask to go back (the CLI still refuses a forward edge without its approved receipt). - The block message depends on the marker kind. A review block names the task and the event, lists the approval phrases and the Dashboard confirmation, and no longer mentions AskUserQuestion.
Fixed
- Parallel sessions in one project no longer block or confirm each other. Before, a review waiting in one session stopped the writes, skill loads and subagents of another session working on a different task, a reply in the other session could write the first one's review receipt, and skill evidence landed on the wrong task. The hooks that block, confirm or record evidence now use the task bound to their own host session. A session whose task is bound to another session is neither blocked nor able to confirm it, and a host that gives no session id behaves as before.
tenon session activate <name> --host-session <id>binds a task to one session at a time: it removes any other session's binding to that task and names the old session on stderr. After a conversation is resumed with a new session id, the hooks of that session neither block nor confirm until the task is activated again with the new id.- A pending interaction marker (the question a skill waits for you to answer) belongs to the session that raised it. The gate blocks only that session, only its AskUserQuestion answer or approval phrase clears it, and sessions that wait at the same time no longer overwrite each other's marker. A marker in the old format is deleted when it is seen.
- A pasted approval phrase no longer approves a review. An approval phrase counts only when it is the whole short reply (for example "确认继续。"); inside a longer message, a quote or a paste it is ignored, and for AskUserQuestion the chosen answer is judged, not the text of the question. Before, a phrase inside a paste was matched as a substring and could record a review as approved.
Added
- Verification round limit. A step with a review gate and a back edge (Verify with
verify-failindefault) allows at most two rounds, a round being one entry into the step; the count starts again afterrequirements-changedback to Spec. A step declares its limit withmax_rounds(1 to 20), a limited step that declares nothing gets 2, andtenon set <change> max_rounds <N>overrides it for one task. When the rounds are used up and a required test or reviewer still fails, Tenon stops going back by itself:step.nextasks you to accept the remaining reviewer blockers at the forward review, or stops withrounds-exhausted, and lists the other ways out (raise the limit, return to Spec, end the task). The CLI and the Dashboard (HTTP409,code: "rounds-exhausted") refuse the back edge. Only a human confirmation accepts remaining blockers, bound to the reviewer's run and the code candidate;--delegatedand AFK mode never do, and the Dashboard's Approve refuses them (HTTP409,code: "residual-pending"), so confirm in the terminal. An agent raises the limit only when you say so. See Verification round limit. - Step-test waiver.
tenon test waive <change> --test <step-test-id> --reason <text>lets a failing required step test such ascode-sizethrough. Before, nothing could:step.nextstayed onrun-testand the task never reached the review gate. The waiver is listed bytenon review requestand approved by your confirmation, it covers only a failure that is fresh on the current candidate, and the approval is bound to that code candidate, so changed code that fails again needs a new approval. Reviewers see the reason marked as the registrant's own words. tenon review revoke <change> --reason <text>withdraws an approval given by mistake: it turns the current step's approved, not yet consumed receipt back into a pending one for the same event, restores the review marker and records the revocation in the change history.- Source development install, for working inside a Tenon checkout.
tenon setup --claude --from-source <repo>(or--codex) builds the checkout, fetches the upstream skills it is missing and installs the checkout's plugin and managed runtime in place of the release, so the skills, hooks and CLI your agent loads are the checkout's. Rerun it after editing the checkout to sync (--skip-buildwhen the build is current,--dry-runto preview). It never poses as a release:/api/healthreportschannel: devand<version>+dev.<sha7>,tenon doctorshowsidentity:releaseas a warning, auto-update is off andtenon updaterefuses it.tenon update --claude --to-stable(or--codex) goes back to the latest stable release. A new session started in the checkout warns when the installed files no longer match it, andtenon doctorreportssource:drift.--from-sourceruns the build of the checkout you give it and installs that checkout's hooks for good, so use it only on a checkout you trust. See Run your checkout as the installed plugin.
Behaviour changes and upgrade impact
standardandsimpleare gone, and so are thechatandfreeTracks.tenon init <name> --workflow standardand--workflow simpleexit1and print--workflow default --track lite. A retired Track id given to--trackexits1and lists the Tracks the Workflow offers.tenon init <name> --workflow design-systemtakes no--track. A task created before the upgrade finishes on the snapshot it froze, whatever its Workflow or Track was..pipeline/tracks.yamland the built-in Track registry are no longer read. Tenon prints one line when the file exists. Move the routing words you kept there into therouteof the Workflow branches.- "Hiding" is removed.
tenon task archiveandtenon task unarchive, the Workspace "Archived" toggle and the per-userarchived.jsonare gone; the two subcommands exit1and point totenon task terminateandtenon task finish. Tasks you had hidden show again in the Workspace. - New tasks carry fewer fields and checks.
build_mode,isolation,direct_overrideandbranch_statusand their checks, and the checks for a statefile, literal proposal/tasks/design paths and the spec coverage matrix, are gone; a task created before the upgrade keeps its fields. The manifest skill matrix (mandatory_skills),POST /api/changes(the Dashboard no longer creates tasks) and the router preview are removed, so skills come only from the Workflow YAML. A failed leave condition ofdefaultexits2, not1. - Workflow definitions reject the document-contract keys.
document_contract(top level or on a Track branch) and the step keysinputs,outputsandartifactsare removed, and so is thenonempty-outputguard. Saving a Workflow, creating a task and compiling a plan reject them and name the replacement: a file condition is arequired-outputguard, a field condition isfield-nonemptyorfield-equals. Emptyinputs: [],outputs: []andartifacts: []still load. Agate: autostep is no longer derived from itsoutputs, so write those checks as guards. A new custom Workflow that freezes a build revision writesfreeze-build-shaon thebuild-completeedge andbuild-head-unchangedon theverify-passedge itself, asdefaultdoes. See Keys that were removed. - New tasks get no proposal or design skeleton and no document ledger (
.pipeline-documents.json); a Track withspec: openspecgets only thetasks.mdskeleton.tenon document scaffold,record,initandmigrate-deltaexit2andtenon artifact registerexits1on a new task and say that outputs are recorded automatically. The frontend Track no longer requires a readyDESIGN.mdbefore Open; thedesign-systemtest still checks it at Ship. The Workflow API no longer returnseffectiveIo, and theiofield ofGET /api/change/:name/orchestrationis nowstepIo. A task created before the upgrade finishes under the plan it froze, former document contract included. - A Track enters AFK only if it says so. Whether a task may be queued reads only
automation.eligibleon its Track. Thepm,frontendandbackendTracks ofdefaultare eligible; theliteTrack and any Track without anautomationblock, every flat Workflow included, are not (tenon afk enqueueexits3, and--jsonreportsreason: "track-not-afk-eligible"). - A step with a review gate and a back edge is limited to two verification rounds, a step of a custom Workflow that declares nothing included. Write
max_roundson the step to change it. - Test settings change what a pass asks for. The default
code-sizetest no longer blocks on 2000 added lines, the defaultfullno longer runs every integration and e2e suite, and scenario mapping no longer blocks an exit. The 80% line-coverage threshold thatdefaultasked of frontend and backend Verify is gone: a project that wants it writeslevels.full.coveragein its catalog. - Test trust is asked once more in each git repository. Trust stored in a checkout's seal counts only in a project that is not a git repository (new trust is written there too); in a git repository the old seal is ignored, tracked or not, because the seal and its key can arrive with the repository or an archive. A checkout counts as "not a git repository" only when neither it nor any parent has a
.git; one whose repository identity cannot be read (git fails or times out, or a forged pointer) is untrusted,tenon test runsays to fix git first andtenon test trustrefuses to write. - An agent cannot confirm a review for you. The write gate refuses an agent's
tenon review acknowledgewithout--delegated. Confirm with an approval phrase as your whole reply (the hook writes the receipt), in the Dashboard (tenon dashboard --open), or by runningtenon review acknowledge <change-name>in your own terminal. - A source development install replaces the release until you go back.
tenon updaterefuses it; usetenon update --claude --to-stable(or--codex).
Development
- A test or acceptance script that starts a
tenonchild in an isolated runtime must drop the launcher's runtime roots (TENON_RUNTIME_ROOTSandTENON_RUNTIME_DATA_ROOT,_STATE_ROOT,_CONFIG_ROOT): useisolatedEnv, or pass the environment throughwithoutInheritedRuntimeRootsfromtools/lib/runtime-roots.mjs. Otherwise a suite run throughtenon test runreads and writes your real Tenon state.
Documentation
- The installation page says that the retired 1.x releases and tags were deleted on 2026-10-07 and that the maintainers keep an archive.
What you need to do
Run tenon update --codex (or --claude) and open a new host session, then:
- Replace
--workflow standard/--workflow simplein scripts and notes with--workflow default --track lite, and drop--trackfromdesign-systemtasks. - Replace
tenon task archive <name>withtenon task terminate <name>(give up a task) ortenon task finish <name>(a task that reached its end). - Move rules from
.pipeline/tracks.yamlinto therouteof the Workflow branches. - If you saved your own
defaultin the global store, it still loads, but a copy that lacks thelitebranch makestenon assessreport "no change-risk thresholds" instead of guessing. Add alitebranch with adiff-risktest to use assessment. - If a Workflow of yours fails to save and names
document_contract,inputs,outputsorartifacts, delete those keys; writerequired-outputfor a file a step must produce andfield-nonemptyorfield-equalsfor a field. Scripts that readeffectiveIofrom the Workflow API oriofromGET /api/change/:name/orchestrationreadstepIoinstead. - In each git repository, run
tenon test trustonce in your own terminal before the first test run. - Scripts and notes that request a review of the back edge before taking it can drop that step while rounds remain:
tenon transition <name> verify-failsucceeds on its own. Nothing changes for forward edges. - Codex: the hook files changed, so Codex may ask you to trust them again. Run
/hooks, trusttenon, and start a new session (see Codex hook trust). Runtenon sync --migrateiftenon updatesays the block inAGENTS.mdis stale. - Before rolling back to an earlier release, finish or terminate the tasks you created after upgrading, and remove
levelsfrom.tenon/tests/catalog.yaml(see Compatibility).
Compatibility
N-1 is v0.3.2. The N-1 gate (tools/test-bundle.sh) crosses it with this release in both directions on every run.
- The gate crosses the test record chain, the agent run ledger with its sidecar, the test plan, catalogs written by
discover --write, chains pruned byTENON_RECORD_RETENTION, and records written beside an untracked.claude/settings.local.json. It also checks that v0.3.2 reads a task this release created on a heavy Track ofdefaultwith only the warning described in the third bullet. The other limits below are known and not crossed by the gate. - v0.3.2 cannot read the step
testkey, the stepmax_roundskey or the cataloglevelskey: reading a Workflow with one of the step keys fails, and a catalog withlevelsis reported with an unknown key. Before rolling back, finish or terminate the tasks you created with those keys, and removelevelsfrom.tenon/tests/catalog.yaml. - v0.3.2 does not know the three general leave conditions or the
required-outputguard. For a task this release created on a heavy Track ofdefault,tenon statusshows a one-line warning instead of the step projection. v0.3.2 rejects a task on a Track it does not know:lite, or a Workflow withouttracks:(design-systemor your own flat Workflow), whose Track is recorded under the Workflow's name. Finish or terminate such tasks before rolling back (see Updates, recovery, and uninstall). - Fingerprint rule change: a task in flight whose workspace holds an undeclared test output (for example an untracked
test-results/**/*.log) can show a one-timerevision-staleon its first check after the upgrade, because the file is no longer counted. The remedy is the usual one for a stale build revision: return to Build and capture the current revision again. Tasks without such files are unaffected. - Commands and options added:
tenon assess,tenon tracks listandshow,tenon task finishandterminate,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-sourceandtenon update --to-stable. Removed:tenon task archiveandunarchive,POST /api/changes, the router preview and theeffectiveIoandioresponse fields.
Verify
tenon --version
tenon doctor
tenon runtime status
tenon tracks list
tenon init demo --workflow standardtenon --version prints 0.3.3. In tenon doctor, identity:release lists 0.3.3 for the host plugin, the runtime and the Dashboard server. tenon runtime status shows the active release with valid=yes. tracks list shows the lite, pm, frontend and backend Tracks of default, design-system and your own Workflows. The last command exits 1 and prints --workflow default --track lite (demo is just a sample name, nothing is created). On a task created after the upgrade, tenon document status <name> --json reports mode: "observed" with its inputs, outputs and required; on one created before with the former document contract, tenon status <name> --json shows step.documents.mode: "contract".
v0.3.2 · 2026-10-07
A fix release for v0.3.1, from its supplementary acceptance. tenon runtime repair --rollback no longer leaves an install stuck, a not_applicable entry committed before a task no longer blocks it after approval, and tenon test discover writes a benchmark command that runs on vitest 5.
Fixed
- Runtime rollback wedge. On every release from v0.1.0 through v0.3.1,
tenon runtime repair --rollbackafter atenon updatefailed with "rollback refuses a third-party launcher checkpoint" and left the install stuck. Rollback, update and setup were then all refused. - Rollback now proves the launcher pair before it moves the selection, so a refused rollback changes nothing.
tenon updateandtenon setupsettle a rollback that an older bootstrap left half done instead of refusing it. - To recover an install that is already stuck, run the versioned
install.shof v0.3.2 once for the host. See Rollback, update and setup all refuse on a leftover rollback. - A
not_applicableentry that the test catalog already held, unapproved, before the task started no longer blocks the task after you approve it (affected v0.3.0 and v0.3.1). - Before, the approval's rewrite of
catalog.yamlcounted as an unapproved protected change (protected-file-unapproved), andreview requestrefused a second request. - A task already stuck this way is not repaired by the update. Archive it with
tenon task archive <name> --yesand start a new task: it asks you to confirm the catalog rewrite once. See A task is stuck on protected-file-unapproved. - After a rollback,
tenon doctorshowsidentity:releaseas a warning, not a failure. - The warning names both ways forward:
tenon updatereturns to the newer release,tenon setuprebinds the host plugin to the runtime you rolled back to. - The warning shows only while the rollback is the latest runtime event and the host plugin is the release you rolled back from. Any other mismatch stays a failure.
- Leaving host-local files out of the test-record candidate now fails closed in more cases (the file is counted unless git shows it is untracked):
- a broken
.git(empty, corrupt, unreadable, or a gitfile whose target is gone); - a case-insensitive file system, where a tracked
.Claude/Settings.local.jsonnow counts as.claude/settings.local.json; - a repository above the project answering in place of the project's own
.git; - inherited
GIT_DIR,GIT_INDEX_FILEand similar variables, which the check now ignores.
- a broken
tenon test discoverwrites a benchmark suite that runs on vitest 5 (--reporter=json, since vitest 5 removed--outputJson).- The benchmark parser reads the vitest 5 JSON report into the same
<name>.mean_ms,.p99_msand.hzmetrics. - Discover writes no suite, and prints a note, when a vitest 5 project's bench files still import the removed module-level
bench. - More
agentandreviewoutput followsTENON_LANG: theagent nextlines, the otheragent promptandagent recorderrors, and thereview requestandreview acknowledgemessages. Strings not yet in the catalog stay in Chinese. - The new-project wizard's confirm-step precheck has a 15 s timeout. A timeout or network error shows an error with Retry, Back stays usable while the precheck runs, and a hung request can no longer lock the wizard.
Added
- The
tenon-verifyaction has an optionalsarif-checkout-pathinput, for repositories whose commit lives in a sub-checkout, and asarif-idoutput (the ID of the code scanning upload). Leaving the input empty keeps the old behaviour.
Behaviour changes
- A vitest 4 or earlier project that gives two benchmarks one name (or names that differ only in punctuation) now fails the bench report parse and names the duplicate. Before, the last result silently replaced the first.
tenon test discoverwrites no benchmark suite when it cannot determine the vitest major version, and prints bothcatalog addcommands instead. That is the case when vitest is not installed andpackage.jsondeclares no range that fixes one (latest,workspace:*,>=3), or when the installed vitestpackage.jsoncannot be read.
Development
- Tenon's own tests run on vitest 4.1.11 (was 3.2.6); the
tinypooloverride is gone. - CI now runs, as blocking steps of the
verifyjob, an update and rollback acceptance (npm run test:update-rollback, including the recovery from a stuck rollback) and a realnpm install vitest@5bench check. - The
tenon-verifyaction has its own self-test workflow (verify-action-selftest.yml). It runs the action on real runners and verifies that the SARIF upload reached code scanning (skipped for fork pull requests, which have no write token). It runs on pull requests that touch the action orverify --ci, and on manual dispatch.
What you need to do
Run tenon update --codex (or --claude) and open a new host session.
If you have not run tenon runtime repair --rollback, that is all: tenon update is enough, and later rollbacks work on v0.3.2. The versioned install.sh is only for installs that are already stuck.
If an earlier rollback left you stuck (tenon runtime repair --rollback, tenon update and tenon setup all refuse with "an unfinished runtime rollback exists"), tenon update cannot help. Run the versioned install.sh once for the host:
/usr/bin/curl -fsSL https://raw.githubusercontent.com/jefferysha/tenon/v0.3.2/install.sh | /bin/bash -s -- --codex
# or
/usr/bin/curl -fsSL https://raw.githubusercontent.com/jefferysha/tenon/v0.3.2/install.sh | /bin/bash -s -- --claudeCompatibility
N-1 is v0.3.1. The N-1 gate (tools/test-bundle.sh) crosses it with this release in both directions on every run.
- No record, state or ledger schema changed, and no CLI command, CLI option or Dashboard API was added or changed; the only new interface is the optional
tenon-verifyaction input and output under Added. In an ordinary workspace both versions bind the same portable fingerprint, so each reads the other's test records as fresh, also beside an untracked.claude/settings.local.json. The gate checks that against the real v0.3.1 CLI. - The gate also crosses the agent run ledger,
TENON_RECORD_RETENTIONchains,discover --writecatalogs and a tree with a symlink and 664/775 modes. - In the host-local cases above, where 0.3.2 now counts a file that 0.3.1 left out, the two compute different fingerprints for that workspace. The gate does not cross those cases.
Verify
tenon --version
tenon doctor
tenon runtime statustenon --version prints 0.3.2. In tenon doctor, identity:release lists 0.3.2 for the host plugin, the runtime and the Dashboard server. tenon runtime status shows the active release with valid=yes. After a tenon update, tenon runtime repair --rollback exits 0 and tenon runtime status shows the release you rolled back to as active.
v0.3.1 · 2026-10-07
A fix release for v0.3.0, from its real-host acceptance. tenon verify --ci now reproduces your test records on a clean clone when your workspace held host-local files, and no longer fails a task that was abandoned on purpose. The CLI messages of the cross-vendor review, the English track tab and the sign-in command are fixed, and tenon --version exists.
Fixed
- A clean clone no longer fails
tenon verify --ciwithcandidate-mismatchbecause of host-local files that are never committed:.claude/settings.local.json,CLAUDE.local.mdin the project root and.claude/worktrees/. See Candidate mismatch. - Test records written by 0.3.1 bind a fingerprint that leaves those files out, and only while git does not track them: a tracked file under those paths still counts. Nothing else is left out by name.
- Test records written on one platform now verify on another (macOS and Linux CI). The fingerprint a 0.3.1 record binds records permission bits the way git does: a regular file is 644 or 755 by its owner executable bit, a directory carries no mode, a symlink only its target.
- Before, the fingerprint recorded the raw bits, which differ for the same committed content: a symlink is 0755 on macOS and always 0777 on Linux, and files and directories follow the umask. A repository with a symlink, or written under a umask other than 022, could never be reproduced by a runner on the other platform.
- The
candidate-mismatchmessage names the paths it can establish: the candidate files changed after the first commit that followed the run, the git-ignored or untracked files this checkout holds in the candidate scope, and tracked host-local paths. The delivery commit's own files are no longer blamed. - A task abandoned through the
scope-expandededge (for example astandardtask that escalated todefault) is no longer judged on test evidence byverify --ci; the report carries the notechange-abandoned. Protected-file approvals are still checked on it at normal severity. - The abandon only counts when the task's committed transition chain shows the edge; a state that merely says
phase: escalatedis judged as before. tenon evidence exportis deterministic: every timestamp comes from the evidence (the finish time of the latest record in the chain), not from the clock, so exporting twice prints the same bytes.- The cross-vendor review lines (
agent promptrouting, the host refusals ofagent record) and thereview acknowledgerefusals followTENON_LANG; they were always Chinese. - The selected track tab stays fully visible in the English Dashboard: a page opened with
track=backendno longer cuts the tab off in the fade, and it scrolls back into view when its name widens (a language switch) or the strip narrows. A tab you scroll to, click or focus yourself is not pulled back by a resize. - The sign-in page and the Dashboard no longer draw
--openas––open: commands, paths and ids in monospace turn font ligatures off. The text was always ASCII. tenon --version(and-V) prints the version of the plugin payload the command runs from.
Changed
- The
tenonskill picks the lane for a manual/tenon <request>(a prompt that starts with/skips the routing hook): the workflow or Track you name, elsestandardfor an implementation request,defaultonly for heavy or cross-domain work (architecture, auth, migrations, dependencies, contracts), and a project-defined workflow only when you name it. - When
step.nextoffers a singletransitionand it is thescope-expandedescalation, the skill takes it directly, in interactive mode too: it tells you in one line why it escalates and what comes next, then runs it. tenon verify --ciadds the notefinished-judged-at-headwhen a finished task fails because CI judges it against the checked-out tree, not the commit it finished on. The verdict is the same as before.
Documentation
- Reading test records is supported with plain read utilities (
cat,ls,jq,grep,head,tail,wc,find -name) or the editor's Read tool. A read through inline interpreter code (node -e,python -c) orxargs, or chained with any write, is refused by the write gate, which did not change. See Security model.
What you need to do
Run tenon update --codex (or --claude) and open a new host session. Nothing else is required.
If verify --ci already reports candidate-mismatch for records written by 0.3.0 or earlier in a workspace that had one of the host-local files, run tenon test run <change> --stage once with 0.3.1 and commit the new records.
Compatibility
N-1 is v0.3.0. The N-1 gate (tools/test-bundle.sh) crosses it with this release in both directions on every run.
- No record schema changed. A v0.3.1 record binds one workspace fingerprint, as before. For an ordinary tree (no untracked host-local file, directories 755, files 644 or 755, no symlink on Linux) the portable and the full form are equal and v0.3.0 reads the record as fresh.
- The full form, which v0.3.0 and earlier computed, is unchanged byte for byte on every platform; the N-1 gate checks it against the real v0.3.0 CLI on a tree with a symlink and 664/775 modes.
- With an untracked host-local file, a symlink on Linux, or modes other than 755/644 (for example under umask 002) in the workspace that wrote it, v0.3.0 reads a v0.3.1 record as stale ("code changed", exit
2), never as damaged. That follows from the full form it computes, which the N-1 gate checks (previous bullet). - v0.2.1 computes the same full fingerprint as v0.3.0, so it behaves the same; the gate does not run v0.2.1. Run the suite again with the version you went back to.
- v0.3.0 records stay valid: v0.3.1 accepts both fingerprint forms, so a record written by v0.3.0 is fresh on the machine that wrote it.
verify --cihas two new note-level finding codes,change-abandonedandfinished-judged-at-head. No command, option, project file or Dashboard API changed apart from--version.
Verify
tenon --version
tenon doctor
tenon runtime statustenon --version prints 0.3.1. In tenon doctor, identity:release lists 0.3.1 for the host plugin, the runtime and the Dashboard server. tenon runtime status shows the active release with valid=yes. In a clean clone of a project whose records 0.3.1 wrote, tenon verify --ci --change <name> no longer reports candidate-mismatch for the host-local files above.
v0.3.0 · 2026-10-06
v0.3 makes Tenon usable on an ordinary project and harder to fool. A small change now goes through a four-step standard lane, and the default test flow requires only unit. The test evidence a task commits can be re-checked in CI from the repository alone (tenon verify --ci and a GitHub Action), is guarded by a local trust root and an integrity report, and a reviewer can be required to run on the other vendor's CLI. The Dashboard needs a sign-in, draws the workflow on a banded canvas with the Signal flow animation, loads a 30-project snapshot in under 1 MB, labels blockers in English without parsing Chinese text, and has an axe accessibility check in CI. tenon support bundle and tenon logs cover diagnostics. The release includes the v0.2.1 launcher fix. Read "Behaviour changes and upgrade impact" before you upgrade a project.
Standard lane
standardis a built-in workflow and track:open → build → verify → done, with no Explore stage and no OpenSpec documents. Build runs the unit tests and the risk probe; Verify has one review gate and the reviewerscode-reviewandsecurity(which attaches only on auth, dependency or contract paths).- Implementation-shaped requests (fix, add, refactor, in English and Chinese) route to
standard; heavy signals (architecture, schema, auth, dependency upgrade, release) stay ondefault. See the lightweight Track, which later replaced the standard lane. - A prompt that looks like code work but matches no Track gets a one-line "not governed" notice.
tenon test diff-risk [<change>] [--json]is the risk probe Build requires. It fails on more than 8 source files, a contract, auth, dependency or migration path, a deleted test or a changed test configuration (limits:pass.metrics).- A failed probe leaves
step.nextonescope-expandedtransition whoseescalatepayload says to open adefaulttask. tenon step run <change> [--json]does the deterministic part ofstep.nextin one call (scaffold and record documents, read them, seed the test plan) and stops at the first action that needs the host or the author.tenon document record <change> --allrecords every document of the step whose file is already written; unfinished ones are listed and skipped.- New official reviewer
code-review: it reads the diff against the goal and acceptance check the host appends to its prompt, with no spec documents. There are ten official agents now. - Reviewers can declare
attach_on(auth,dependency,contract,migration) and join a step only when the task touches such a path. The officialsecurityreviewer (1.1.0) attaches only for[auth, dependency, contract]; that default lives in the CLI, not in the agent file. - Verify runs the project's Playwright suite once: the default workflow's inline
playwrightstep test is gone and thee2ereviewer (1.1.0) no longer reruns it. - An integration test drives a three-file bug fix through the lane and asserts at most 25
tenoncalls and 2 user replies.
CI verification and evidence export
tenon verify --ci (--change <name> | --all-open | --since <ref>)re-checks committed evidence without your local key: the record chain, plan, records and catalog agreement, case-level verdicts, the candidate tree, approval lines for changed test configuration, anchors and test integrity. Exit0passes,2has an error finding,1is a usage error.- Formats are
text,json,sarif(2.1.0) andmarkdown(job summary), written with--format,--outand--also. Every report lists what CI cannot prove without the local key. - Test integrity uses the step's
integritypolicy:blockturns a signal into the errortest-integrity(exit2; on a shallow clone the diff cannot be read and it fails withfiles-diff-unavailable), and the defaultnoticegives a note that never fails the run. - The output follows the CLI language (
TENON_LANG=en|zh, thenLC_ALL,LC_MESSAGES,LANG; Chinese when none is set): text, Markdown, SARIF messages, JSONmessagestrings and usage errors. Finding codes, levels, exit codes and JSON field names do not change. - The composite GitHub Action
.github/actions/tenon-verifyruns it on pull requests with the CLI bundle of the pinned release, appends the job summary and uploads SARIF to code scanning. Itslanguageinput (zhoren) sets the report language. See CI verification. tenon evidence export <change> --format agent-trace|otel|git-notes|trailerwrites an Agent Trace v0.1 record, OTLP/JSON GenAI spans (nothing is sent), a git note inrefs/notes/tenon, orTenon-Change:/Tenon-Evidence:trailers. It prints by default;--applywrites the note or amendsHEAD.--anchor(git-notes) copies the record chain head into the note;verify --cireportsanchor-mismatchoranchor-behind, and--require-anchormakes a missing or lagging anchor an error.
Test integrity and the trust root of test evidence
test_policy.integrity: notice | block(defaultnotice) andtenon test integrity <change>report ten signals that evidence got weaker since the task started: fewer cases, more skips, deleted tests, removed assertions, rewritten snapshots, changed baselines, lower coverage thresholds.noticeshows a note instatusand the Dashboard;blockblocks the step.- A report must be newer than the run start and is copied with its digest into the run's artifact directory, or the run fails with
report-untrusted. The workspace fingerprint ignores only output paths the catalog or workflow declares. - A local seal (
<user-dir>/local/test-seal.json, HMAC key beside it, git-ignored) holds the head of each record chain, your approvals and trust decisions. A chain whose head is not sealed isrecord-unsealed; the next run starts a new one. - Changes to
.tenon/tests/catalog.yaml,baselines/**,known-failures.yamland.pipeline/workflows/*.yamlin a task's diff block everygate: reviewstep until you confirm them;tenon review requestlists them and--delegatedis refused. A known failure names one case and expires within 30 days. tenon test trust [<change>] [--yes] [--status] [--json]:tenon test runrefuses catalog and inline-test commands you have not trusted on this machine. CI declaresTENON_TEST_TRUST=1.- The write gate recognises 13 shell write shapes into evidence files (
python -c,curl -o,tar -x,git apply,xargs sh -c,tee, redirects and more) and refuses an agent call that containstenon test trust. The match is static and best effort; the seal catches the rest. - A reviewer with a verdict on unchanged code is not rerun into a pass:
tenon agent promptrefuses (exit2) unless you pass--rerun-reason <text>, and the most severe verdict on that candidate counts. The Dashboard showsreruns.
Cross-vendor review
- A workflow step can require a reviewer on
host: codex | claude | any(Claude writes, Codex reviews); an agent file may suggest one withhost:(tenon agent new --host). The step's declaration binds, the agent's only routes. - From another host or a plain terminal,
tenon agent promptwrites the prompt toopenspec/changes/<change>/.pipeline-agent-reports/<run-id>.prompt.mdand prints thecodex exec …orclaude -p …command and thetenon agent recordline. Tenon never starts the other CLI: you, or the agent in the current host, run it. tenon agent record <change> <run-id> --host <host>stores the host (detectedordeclared). The verdict is bound to the reviewed code's content hash and to the host: a record from the wrong or an unknown host is refused (exit2).- The workflow page edits a reviewer's
host. The agent run drawer shows the recorded host next to the required one, the bound candidate and, until a valid run exists on the required host, a copyabletenon agent prompt <change> <agent>command for the current and later steps of an open task. The Dashboard never runs it.
Test flow
- The default policy requires only
unit.typecheck,integration,regression,e2e,playwright,a11y,visual,benchmarkandsmokerun when the project has them; coverage thresholds apply only to a suite whose catalog entry declarescoverage; aunitsuite run in full satisfiesregression. tenon test catalog not-applicable <kind> --reason <text> | --rmdeclares a kind that does not apply to the project; it takes effect after one humanreview acknowledge(not--delegated).tenon initrunstenon test discover --writewhen the project has no catalog, andtenon test register <change> --autoclaims unclaimed test files, seeds the plan and registers them. JavaScript unit globs now coversrc/,test/,tests/,__tests__/and root*.test.*.
Dashboard
- Sign-in: without a session,
GET /and every/api/*request answer401, except/api/healthand/assets/*.tenon dashboard --openmints a one-time link (2 minutes) and opens your browser signed in; no token is written to disk. See Signing in. - The sign-in page is styled with the Dashboard's tokens in light and dark. It is in one language (English when the browser's
Accept-Languageprefers it over Chinese, otherwise Chinese) and shows the command with a copy button. - A used, invalid or expired link gets its own page in the same style, with the same command and a Continue link.
- Approving a review needs a person present: a second click requests a single-use nonce (30 seconds) bound to your session, the change and the review revision.
tenon review acknowledgein a terminal is unchanged. - Signal replaces the per-edge pulse: one constant-speed conveyor with a four-layer streak runs from the current node and stops at a review gate.
prefers-reduced-motionshows a static highlight; the animation pauses off screen and in a hidden tab. - The overview has borderless columns fitted to the width, one spine per column, a bracket per parallel wave, semantic zoom and click-to-focus column headers; nodes are 40 px with state glyphs, and the stage rail is 3 px.
- Pages: the workspace Next step merges blockers of one kind into 40 px rows; the Tests tab has four tabular figures, an Integrity section and "N optional"; the Skills page reads references from the orchestration; counts roll vertically.
- The Tests tab keeps non-blocking notices (a fixed known failure, a noisy benchmark, an unchecked file) in a Notices section of their own. The Blockers table and its count hold real blockers only.
- English count labels agree with the number: the Tests tab summary reads Suite or Suites, Case or Cases.
- The track tab strip fades on the side that still has hidden tabs, so a long English name is not cut mid-word; the selected and the keyboard-focused tab are scrolled clear of the fade.
- Scale: each project has its own snapshot cache. The Dashboard reads
GET /api/snapshot?view=listand/api/stream?view=list(a full frame, thensnapshot-delta) and a task's evidence fromGET /api/change/:name/snapshot. - At 30 projects × 30 tasks the list body is about 0.6 MB where the single snapshot was 11 MB, and a write plus rebuild stays under 1.5 s at p95.
- English: step-exit blockers carry
code,subject,stateandcountand are labelled from those, not parsed from the Chinese sentence (kept as a tooltip).<html lang>follows the chosen language before first paint. - In English, the stage, track and test-kind names of the built-in workflows (
default,standard,design-system) are shown in English while the stored label is still the shipped Chinese one; a name you edited or created is shown as stored, and nothing is written back. Tables are sized by content, so words such as "Integration" are not truncated. e2e/dashboard/a11y.spec.tsruns axe-core over the main pages in light and dark and fails on anyseriousorcriticalviolation.- Accessibility fixes: the focus ring of a keyboard-focused track tab is no longer clipped by the tab strip; placed rows in the skill composer are disabled, not only dimmed; the enabled Done button of the skill and agent composers (and the other solid accent fills) had a contrast of 1.96:1 in dark mode and now uses the primary-button colours (4.84:1).
Support, logs and language
tenon support bundle [--out <path>] [--json]writes a local.tar.gz(mode0600, at most 5 MiB): versions,doctor,runtime status, a configuration summary, the recent Dashboard log. Tokens, cookies, login codes, emails, home paths and user names are removed first; the command lists what it holds. Nothing is uploaded.- The Dashboard server writes
<state>/logs/dashboard.log(three rotated files of 1 MiB, credentials redacted); read it withtenon logs [--follow] [--lines <n>]. TENON_LANG=en|zh(thenLC_ALL,LC_MESSAGES,LANG) picks the CLI language for help and the common errors; JSON fields and exit codes do not change. An illegaltenon transitionevent now lists the legal events for the step.
CI and platform
- CI blocks on
verify(Node 22, Chromium), anode-matrixjob and a separate WebKit job.npm testruns withTENON_E2E=1. node-matrixruns the test-system, reporter and parser suites, thetools/node:testscripts and the Dashboard Chromium e2e on Node 20, 22 and 24, not the whole suite. Tenon needs Node 22 or newer; Node 20 is there because a project under test may still run its ownnode:teston it, and the reporter and the parsers must read that output.- A catalog can set
profile: coarse: the machine profile is OS, architecture, core count and Node major version plusprofiles_env(for examplelinux-x64-4c-node22-1a2b3c4d), so hosted runners of one size share a benchmark baseline. tenon doctorreportsenv:platform(green on macOS, Linux and WSL, red on native Windows with a pointer to WSL 2, yellow elsewhere); see the support matrix. Node 20 is not supported for running Tenon.tenon test runputs the runningtenonfirst onPATHfor test processes, sotenon test code-size --jsonno longer exits 127.
Fixed
- The 2000-line
code-sizelimit of v0.2.0 never reached the frozen plan, so a 2036-line candidate passed. New tasks now fail the test whenlines_addedexceeds 2000. tenon test catalog add --report-format exit-codeworks, and a service readiness failure says why.- Dashboard agent detail and delete see references from project-level workflows; a referenced agent cannot be deleted.
- A pending interaction or confirm marker no longer blocks the tools of a running subagent.
- Delivery commits no longer carry
.pipeline-owned.json,test-results/,playwright-report/or a rootcoverage/; deleting or archiving a task removes its generated host agent files; a finished design-system task commitsDESIGN.md. - Agent files name skills with the
tenon:prefix (a baredeep-researchwas refused);tenon agent validaterejects the skeleton placeholders. A case without a file shows "No file reported", not(unknown). tenon updatetells a project with a stale Tenon block inAGENTS.mdto runtenon sync --migrate.
Behaviour changes and upgrade impact
- Review confirmation is owner-only. A non-owner running
tenon review acknowledgeis refused. Use--as reviewer(the history recordsas=reviewer owner=<id>) ortenon owner take <change>. The Dashboard's Approve is unchanged. - The Dashboard requires sign-in. Anonymous requests get
401, except health and assets, and a server restart signs you out. Runtenon dashboard --open; read with the CLI instead ofcurl; with no browser, runtenon dashboard --port <port>in a terminal to get the link. - A catalog with
profile:is rejected by older Tenon, and so is one withnot_applicable:. Add them after everyone who reads the repository has updated; a catalog without them is read as before. - The default test flow requires only
unit. A task started before the update keeps its frozen plan. To require another kind, declare it in your workflow'stest_policy. If discover cannot identify yournpm test, register aunitsuite (tenon test catalog add) or declareunitnot applicable, or Spec stops withtest-kind-missing. - The
code-sizelimit is enforced for new tasks (see Fixed): split the task or raisepass.metricsin the workflow. scope-expandedis an abandon edge insimpleandstandard: it needs no test, reviewer, document or skill evidence and leaves the workspace uncommitted for the newdefaulttask. Nothing to do.- Routing sends small implementation prompts to
standard, not the seven-phasedefault. To stay ondefault, name the Track in the prompt (for example "use the backend track"). - The
securityreviewer attaches only when the diff touches auth, dependency or contract paths. To keep it on every task,tenon agent copy security <name>, delete theattach_online and use the copy in your workflow. - The first
tenon test runon a machine needs your trust. It stops with exit1and lists the commands; runtenon test trustin your own terminal (an agent cannot). A changed command asks again. - What counts as a pass needs your confirmation. Catalog, baseline, known-failure or project-workflow changes block the review gate until you approve them (
tenon review acknowledgeor the Dashboard). Known-failure entries must name one case, and an expiry beyond 30 days is not honored. A reviewer is not rerun on unchanged code without--rerun-reason. - Records written before the update count as not run (
record-unsealed). For a task in flight, runtenon test run <change> --stageonce. - Undeclared output directories count toward the candidate. Declare report, coverage and artifact paths in the catalog, or a run that writes
coverage/makes its own record stale. - The CLI language follows the locale. With no locale signal, or
C/POSIX, output stays Chinese; a system locale such asLANG=en_US.UTF-8now gives English help and messages.TENON_LANG=zhpins Chinese. Scripts should read--json.
What you need to do
Run tenon update --codex (or --claude), open a new host session, then:
- Sign in to the Dashboard with
tenon dashboard --open. - In each project run
tenon test trustonce in your own terminal; for a task started earlier runtenon test run <change> --stageagain. - Codex projects: run
tenon sync --migrateiftenon updatesays the block inAGENTS.mdis stale. - Optional: add the
tenon-verifyAction to your pull requests (CI verification) and setTENON_TEST_TRUST=1in CI jobs that runtenon test run.
If every command already fails with Node identity changed, run the versioned installer once for each host you use:
/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 -- --codexA fresh install of v0.3.0 needs only the first two steps.
Compatibility
N-1 is v0.2.1. The N-1 gate (tools/test-bundle.sh) crosses it with this release in both directions on every run.
- Whatever v0.3 writes during normal use stays readable by v0.2.1, so you can roll back with
tenon runtime repair --rollbackor work next to a teammate who has not updated. Readable does not mean valid: a run recorded by one version can still show as stale in the other (see the undeclared output bullet). - Test run records are not pruned unless you set
TENON_RECORD_RETENTION=<n>. v0.2.1 reports such a capped chain as tampered, so set it only when everyone is on v0.3 or later. - Agent run host and rerun reason are stored in
.pipeline-agent-run-meta.jsonl, beside the run ledger, and v0.2.1 ignores it. - Records written by v0.2.1 read as an intact chain in v0.3 but have no local seal (
record-unsealed). Runtenon test run <change> --stageonce after updating. - v0.3 counts undeclared
coverage/,test-results/andplaywright-report/as part of the workspace. A run recorded by one version may show as stale in the other. Rerun, or declare the output in the catalog. - What needs v0.3 when you opt in (v0.2.1 reports the file as invalid or ignores the setting):
profile: coarseornot_applicable:in the catalog;integrity: notice|blockin a workflowtest_policy;host:on a workflow reviewer;attach_onorhostin a custom agent file (includingtenon agent copy security <name>); andTENON_RECORD_RETENTION. - Standard-lane tasks (
track: standard) are v0.3-only. v0.2.1 can list them and show their status, butcheck,test,agentanddocumentrefuse them (未注册的 track 'standard') andstatus --jsonhas no step projection. Finish or archive them before rolling back. - The Dashboard's full snapshot keeps its shape;
?view=listandGET /api/change/:name/snapshotare additions, and every route except health and assets needs a session.
Verify
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 shows the active release with valid=yes. Doctor has no red check: identity:release lists 0.3.0 for the host plugin, the runtime and the Dashboard server, env:platform is green on macOS, Linux and WSL, and env:path-tenon is yellow while the launcher directory is not on PATH. With the Dashboard running, the curl prints 401, because the request carries no session; tenon dashboard --open opens the page signed in. The support bundle lists the files it holds and the number of redactions, and has mode 0600. verify --ci exits 0 when no task is in scope or every task passes, and 2 with a finding for the record, approval or candidate that does not match.
v0.2.1 · 2026-10-01
A hotfix for v0.2.0. After a restart of macOS every tenon command and every hook failed with tenon runtime Node identity changed; rerun tenon setup --codex or tenon setup --claude, and the command that message named was refused by the same check, so the install could not repair itself.
Fixed
- The stable launchers (
~/.local/bin/tenonandtenon-hook) pinned the device number (st_dev) of the Node binary and of each parent directory. macOS gives the same volume a new device number at every restart, so the pin never survived one. Launchers no longer store it. They still refuse a symlink on the Node path, pin the inode, mode, owner and size of the binary and the inode, mode and owner of its parent directories, and compare the Node's SHA-256 with the digest taken at setup. Linux gets the same change. - A launcher that fails its Node check no longer locks you out. If the Node bytes are unchanged and only their identity moved,
tenon setup,update,doctorandruntimestill run, sotenon setup --claude(or--codex) re-pins; any other command prints one line naming that repair. If the Node was replaced or removed, the line is a complete command that runs the bootstrap with the Node on yourPATH:env TENON_RUNTIME_ROOTS=… node …/bootstrap/active.mjs cli setup --claude(--codexfor Codex). - Hooks print that message at most once every 30 minutes and otherwise exit 0 without output. They never block the host.
- The release repairs v0.2.0-format launchers itself.
tenon updaterun by v0.2.0 writes the old format again, so the first Tenon command or session start afterwards rewrites both launchers with the writersetupuses, but only when they are Tenon's own files (not symlinks), export the same roots, and pin the Node that is running with its recorded SHA-256. A command prints one line when it did so, or one line namingtenon setup --claude/--codexif it could not. Hooks stay silent and never wait for it. Anything else is left untouched. tenon doctorhas a new check,runtime:launcher: WARN while a launcher still pins a device number, PASS otherwise.- The payload digest cache no longer keys on the device number, so the first dispatch after a restart does not re-hash the whole payload.
What you need to do
If every command already fails with Node identity changed, run the versioned installer once for each host you use. It does not go through the broken 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 -- --codexIf v0.2.0 still works for you, run tenon update --codex (or --claude). The updater that runs is still v0.2.0 and writes the old launchers once more; the first Tenon command or new session after it switches them to the restart-safe format automatically. tenon setup --codex (or --claude) does the same by hand. A fresh install of v0.2.1 needs nothing.
Compatibility
No public command, option, project file or Dashboard API changed. The additions are the runtime:launcher doctor check and an internal repair command; the launcher text changes (no device number, new failure messages). Projects and runtime state are untouched.
Verify
tenon runtime status
tenon doctor
grep -c '%d' ~/.local/bin/tenonThe runtime reports the active release and doctor is green, including runtime:launcher. The grep prints 0. After the next restart tenon runtime status still works.
v0.2.0 · 2026-09-30
A capability release. Agents are now written and registered in the terminal, the whole workflow can be seen as one canvas, and every task registers and runs all of its tests. The default workflow changes with it: agents are attached to its steps and tests are required, so read "What you need to do" before you upgrade a project.
Agents
- Agents are registered in the terminal with
tenon agent list,show,new,add,validate,copy,rmandexport. Every agent declares arole(executororreviewer) and an optional semverversion.newasks for what is missing in an interactive terminal and runs non-interactively when all flags are given.rmrefuses an agent that a workflow step still uses and lists the steps. The Dashboard no longer creates agents; it displays them. - Agents come from three sources. Official agents ship with the plugin (
builder,researcher,architecture,backend-quality,code-size,e2e,frontend-quality,security,spec-consistency; name, version, role and digest are recorded intemplates/agents/manifest.json) and are read-only. Custom agents live in the user config. Project agents live in.tenon/agents/, are committed, and are shared with the team. A project agent wins over a custom one of the same name; no source can reuse an official name, and such a conflict is listed and never used. - The
tenon:agent-authorskill drafts an agent in Claude Code or Codex from your description (responsibility, what it does and does not do, method, self-check, report ending in atenon-resultblock), validates it and registers it withtenon agent add. It writes no Tenon state. - When a task freezes its agents, Tenon generates the host's native subagent files:
.claude/agents/tenon-<name>.mdor.codex/agents/tenon-<name>.toml.tenon agent promptthen returnssubagent_type: tenon-<name>, so the host runs the agent as its own subagent (Claude Code enforces itstoolsallowlist; Codex has no per-agent tool allowlist, only a read-only sandbox for agents that cannot write or run commands). If a file cannot be generated, the host falls back to a general subagent and the run record says so. The files are listed in.pipeline-owned.json, are removed when no running task uses them (also when the task is archived from the Dashboard), andtenon uninstallremoves only files it generated and you did not edit. - The default workflow now attaches executors: every Explore runs
researcher, every Build runsbuilder(one subagent per independent task, merged into one report) before its reviewers, and every Verify declares thecode-sizetest (tenon test code-size --json, passing whilelines_addedstays within 2000) and the requiredcode-sizereviewer. Chat and free also runsecurityas an advisory Verify reviewer. - The Library page lists agents grouped by role with source and version, shows the fields, the rendered body, the steps that use the agent and its recent runs, edits the body of custom and project agents, and copies an official agent as a custom one.
Orchestration
- Skills run in waves. A skill that declares
depends_onruns after the skills it names; skills in one wave run in parallel and waves run in series; skills withoutdepends_onstay serial in declaration order, as before. The skill gate,tenon statusand the canvas read the same order. - The kernel projects a workflow (or a task's frozen plan) into an orchestration: per stage, the executors, skills, tests and reviewers in the order the runner takes them, the gate, return edges and the document flow. OpenSpec-injected and manifest-overlay skills are computed there instead of guessed in the page. Read-only endpoints:
GET /api/workflows/:name/orchestration?track=andGET /api/change/:c/orchestration?root=. - The workflow page has a 总览 row as the first step of the left column. Its right side is a full-width canvas with one column per stage (executor → skill → test → reviewer, gate in the column head, return edges dashed, document flow on hover) with zoom, fit and full screen. The workbench task detail has the same canvas as a 总览 tab, with each node's run state, the current stage highlighted, read from the task's frozen plan.
- The right pane of a single stage has four sections: 输入 → 技能 → 输出 → 门禁. The 技能 canvas shows executors, skills, tests and reviewers in lanes; "back to" moved into the gate section.
- A gate is 评审 (a person confirms) or 自动 (the declared outputs must all be set).
gate: nulland an omitted gate mean 自动, so the workflow page offers two options. The outputs check applies to forward edges only; return edges such asverify-failandrequirements-changedare never blocked by it, and outputs the engine writes itself (build_sha,archived) are not part of it.
Test system
- Every task registers all of its tests. The project has one catalog,
.tenon/tests/catalog.yaml(tracked, human editable): each suite has a kind, runner, command, report format and path, optional coverage, artifact paths, services, retries, benchmark settings and a way to select changed files.tenon test discover [--write]recognises vitest, jest, mocha, node:test, Playwright, tsc, eslint, pytest and go, prints suggested suites with reporter flags that produce parseable reports, and--writeadds the new ones. - Each change has a test plan,
openspec/changes/<change>/test-plan.yaml, written only bytenon testcommands: the suites the change uses, the test files it added or changed, and the mapping from OpenSpec scenarios andtasks.mditems to test cases. A hand-edited plan becomestest-plan-tampered. - Every added or modified test file must be registered.
tenon test syncand the step exit read the change's diff since it started (staged, unstaged and untracked files included) and block withtest-file-unregistered; when the diff cannot be read they block withfiles-diff-unavailableinstead of passing. Only scenarios and the tasks in the implementation section oftasks.mdneed a case; other tasks are optional. - Workflows declare a
test_policyper stage and track: which kinds must be registered, which must run (changedorfullscope), coverage thresholds, benchmark baseline requirement, flaky limit, required browsers, and scenario coverage.defaultships defaults for every track (for example frontend registersunitandplaywrightat spec, and Verify runsunit,regressionandplaywrighton chromium and webkit with 80% line coverage). A stage without a policy and the old inlinetests[]behave as before. Blockers have stable codes (test-catalog-missing,test-plan-missing,test-kind-missing,test-not-run,test-failed,test-stale,no-tests-ran,coverage-below,scenario-uncovered, …) with the fix command. - Commands:
tenon test discover,catalog,plan,register,unregister,waive,sync,run,status,baseline,known,report.tenon test run <change>runs by suite, kind, stage, changed files or everything.tenon statuswalks the flow instep.next: discover → seed the plan → map scenarios → register files → run the stage's tests → write the report. - Results are parsed per case: JUnit (also pytest and surefire), Playwright JSON, Vitest JSON, Jest JSON,
go test -json, TAP; benchmarks from benchmark JSON, hyperfine, k6 and Lighthouse. A run fails when there is no report, the report is unreadable, no case ran or all were skipped (no-tests-ran, so"test": "exit 0"no longer passes), the exit code and the report disagree, or a registered file or mapped case never ran. - node:test suites from
tenon test discoveruse a Tenon-shipped JUnit reporter (materialised in the run's artifact directory, nothing is written to the project), so every case has its file on Node 20, 22 and 24. When a report has no file, the case's file is recorded as unknown, and a registered case matches it by name only when that name is unique in the run. - Suites can declare services. They start once per run in their own process group, are probed by URL, port or log text, and are stopped afterwards including grandchild processes. A URL or port that already answers before start is refused, because the tests would hit an old server.
- Failed cases are retried per suite (
retries); a case that passes on retry isflaky, counted, and limited by the step policy. - Coverage reads istanbul summary, lcov and cobertura and is judged against the policy;
changed_linescomes from the diff. - Benchmarks run warmup then N runs and keep the median, p95 and spread. Baselines are stored per machine profile in
.tenon/tests/baselines/<suite>/<profile>.jsonand are committed. A regression beyond the threshold blocks on the same profile; without a baseline for the profile the run passes with a notice and the command to create one, unless the policy requires one; profiles never compare with each other. - Known failures,
.tenon/tests/known-failures.yaml(reason, optional link, expiry date): a listed case that still fails does not block, a new failure blocks, a case that now passes is reported with the command to remove the entry, and an expired entry counts as an ordinary failure. - Playwright is the only browser evidence that counts. Screenshots, traces, videos and HTML reports are copied per file into the run, indexed with size and digest, and opened or downloaded one by one.
- Records are v2 (suite runs, cases, coverage, metrics, services, artifact index) and are bound to the code fingerprint, catalog entries, plan and workflow. They are written only by
tenon test run, in a hash chain (.tenon/users/<slug>/tests/<change>/<run-id>.json,prev_digest). The gate hook refuses writes and shell redirects into the records, plans, baselines and known failures. Editing a record breaks the chain and the run counts as not run. Waivers for a missing kind or case need a person:tenon review requestlists them and the user's confirmation approves exactly those. tenon test reportwrites a traceability matrix (scenario or task → case → latest result) into the verification report. Reviewers that declarereads_testsalso get the failing and flaky cases, coverage and benchmark deltas.- Dashboard 测试 views (read-only, no test is run or registered there): the project page lists the catalog's suites, baselines per machine profile and known failures; the workbench task has a 测试 tab (policy matrix, scenario trace, unregistered files, case-level failures) and a run drawer (failed cases, artifacts, coverage, benchmark against the baseline, logs); the workflow page edits each stage's test policy as a form; the Library shows test templates by runner. Endpoints:
GET /api/tests/catalog,baselines,plan,records,record,artifact. - Tenon's own repository registers its suites in
.tenon/tests/catalog.yaml; CI runs the Dashboard browser tests on Chromium and thetenon statusand snapshot benchmarks against the CI profile.
Forms
- New workflow: pick a start point (blank, a copy of built-in
defaultorsimple, an existing workflow, or an imported YAML), a name and the OpenSpec switch; the right side previews the tracks and stages; name uniqueness and validity are checked as you type. - New and edit template: name, category, frameworks (multi-select) and a Markdown body with an edit/render switch; placeholder variables are explained in a tooltip. Test templates in the Library are shown by runner, read-only.
- New project has a fifth step, 资源, between templates and clients: choose a component library, an icon set and a DESIGN.md. It works like the templates step (click a row to preview what will be written, add or remove from the preview head, filtered by the frameworks chosen before, switchable to all). The DESIGN.md is written by a streamed design step and an existing
DESIGN.mdis never overwritten. The default frontend spec prompt points attenon resources. - The skills page lists OpenSpec-injected and manifest-overlay skills in its references.
- All create dialogs share one behaviour: fixed height, Enter submits, Esc closes (asking first when there is input), validation under the field, help in tooltips.
Fixes
- Codex: the managed block in
AGENTS.mddescribed per-phase skills that were removed. It now describes the singletenonskill.tenon syncreports the block asabsent,current,staleorinvalid, andtenon sync --migraterefreshes a stale one in place, touching only the lines between the markers. - Playwright and other directory outputs were recorded as one directory and the Dashboard refused them (403). Every file is indexed and opens on its own; a saved artifact keeps its original file name instead of
artifact.zip. - The write-gate hook matched the whole tool input, so writing a document that merely mentioned a test-record path was refused. It now looks only at the write target (
file_path,notebook_path, or the file headers of anapply_patch). - The
hostslist of an agent was never enforced because thetenonskill did not pass--host; it does now. - Test runs that inherit
TENON_USER,TENON_RUNTIME_HOME,TENON_BASE_BRANCHorTENON_CHANGE_NAME(for examplenpm testinside a Tenon session) no longer fail on identity assertions or write into the real runtime root; Vitest isolates all six variables per test file. Temp-directory cleanup retries, so a late write no longer surfaces asENOTEMPTY. Vitest workers are capped atmin(8, cores), so fullnpm testruns alike locally and in CI. - Documentation: the retired 1.x releases and tags are still published and stay until the v0.x real-host acceptance;
tenon userandtenon ownerare documented in the CLI reference and the contract; the AFK fail-closed error names the real gap (the caller did not injectdeps.preparation).
What you need to do
From v0.1.10: run tenon update --codex (or --claude), open a new host session and reload the Dashboard. The N-1 gate reads and writes this release's data with the published v0.1.10 in both directions.
From 1.x: run the versioned installer once for each host you use:
/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 -- --codexThen, for every project that uses the default workflow:
- The default workflow now requires tests. Run
tenon test discover --writeonce in the project, review.tenon/tests/catalog.yamland commit it. Until a catalog exists, a new task is blocked at its first stage that has a test policy withtest-catalog-missing, andtenon statusshows the command. - For Codex projects, run
tenon sync --migrateonce to refresh the managed block inAGENTS.md. - Tasks that are already started keep the plan they froze: they do not get the new executors, test policies or gate behaviour, and finish under the rules they started with. New tasks get the new rules.
- To use your own agents, run
tenon agent new(or thetenon:agent-authorskill) and attach them to a step on the workflow page.
Compatibility
- The CLI additions are new commands and new optional fields. Agent files without
rolestill load (the role is inferred from the tools andtenon agent validateasks you to add it); v1 test records and steps that declare inlinetests[]stay readable and in force;gate: nullkeeps working and means 自动. - New project files:
.tenon/tests/(catalog, baselines, known failures),.tenon/agents/, and per changeopenspec/changes/<change>/test-plan.yaml. Host agent files.claude/agents/tenon-*.mdand.codex/agents/tenon-*.tomlare generated and removed by Tenon; they are not part of a task's delivery commit. - The Dashboard never runs or registers tests and never calls a model; registration happens in the terminal.
Verify
tenon doctor
tenon runtime status
tenon test catalog validateBoth hosts' inventory, the active managed runtime and the Dashboard report 0.2.0. tenon test catalog validate is for a project that has a catalog. Open the Dashboard: the workflow page shows 总览 as its first step, the Library lists nine official agents grouped by role, and a project with a catalog shows its suites under 测试.
v0.1.10 · 2026-09-25
A Dashboard usability release, driven by walking through real user tasks end to end.
New project
- Creating a project is now a four-step wizard: Location → Templates → Clients → Confirm.
- Folders are chosen with the system folder dialog (
POST /api/fs/choose-folder); when no dialog is available an in-page folder browser is used (GET /api/fs/list). Typing a path remains a secondary option. - The location step checks as you go: missing folder, not a git repository (with an "Initialize git" switch), already registered (with Open), and existing instruction files.
- Only
AGENTS.mdreceives the full text;CLAUDE.md/GEMINI.mdget a single@AGENTS.mdimport. Existing files can be appended to, replaced or skipped. - Creation reports every step live (
POST /api/projects/create/stream) with rollback and retry on failure, and stays on the result until you choose Open project.
Projects
- Projects are organised by client: only the clients enabled for a project are listed, clients sharing one file are merged, and more are added from "+". The enabled set is stored in the project's
.tenon/clients.json(GET/POST /api/projects/clients). - Project level and user level are a switch inside the selected client; nothing is written just by opening or previewing; a missing
CLAUDE.mdcan be created as an@AGENTS.mdimport. - A project can be removed from the list (files are kept); load failures show an inline error with retry.
Workspace
- Task readiness in the Dashboard now uses the same exit judgement as
tenon status(skills, documents, tests, unchecked tasks). "Needs you" counts only pending reviews and is identical across all views. - Each task shows a Next step block: one short line per blocker, a copyable status command and a copyable
/tenon 继续 <change>prompt to resume it in the agent chat. - Inputs, outputs and tests are tables; the stage bar is plain segments; filters fit one line.
- Deleting an untracked task says it cannot be recovered and asks for the name; archiving can be undone.
Workflow, Library, Skills
- Every built-in workflow (including
simple) is listed; OpenSpec-contributed skills appear on the canvas; new stages need only a name; deleting a workflow used by tasks is refused with the list of tasks. - Skill canvases pulse continuously, each edge taking at least 480ms so short canvases pulse visibly.
- Library copies are named "… 副本" and open in edit; every list opens its first item instead of a blank pane.
- A failed skill source says why (for example, no installed release payload).
- View ids are
workspaceandworkflow; oldprogress/workbenchlinks redirect. The top-bar project switcher is gone on desktop; the connection dot only shows when disconnected.
v0.1.9 · 2026-09-24
A Dashboard design release. After an in-depth aesthetic review (visual language, colour, typography, component finish, interaction, motion) and research into top-tier developer products, the foundations, overlays, motion and pages were brought up together.
Foundations
- The accent is the prototype's pine
#236a50; dark mode has four surface steps and a mid-green primary button with white text; selection is one neutral-green fill with a 2px inset edge, with no blue selection or second accent. - Four radii: controls 8, lists and menus 10, dialogs and drawers 14, inline code 4; fully rounded shapes are kept for count badges, avatars and status dots only.
- Three shadow levels: a hairline for cards, a two-layer shadow for menus and popovers, a deep shadow for dialogs and drawers; overlays no longer draw a currentColor border.
- Latin text uses Inter (latin subset only, about 48 KB) while Chinese keeps the system fonts; numbers are tabular; weights are limited to 400 / 500 / 600.
Motion
- One set of durations and curves: press 80ms, hover 140ms, selection and menus 160–180ms, dialogs and drawers 240ms, exits 120ms.
- Menus, popovers and selects fade in from 0.98; dialogs scale from 0.97 and rise; drawers slide in from the right; toasts fade out before unmounting; the selected background of the nav, filter chips and segmented controls slides to its new place.
- The canvas pulse is one timeline: every edge at the same speed (420 px/s), passed on in order, fading at both ends, flashing each node on arrival and ringing the solid end dot; switching stages no longer sweeps the canvas.
- With reduced motion only 100ms fades and colour changes remain.
Pages
- The top-bar nav follows the project switcher; the pending count sits on the workbench tab's corner; connection status is a dot.
- The rail drops nested frames and collapses to icons below 1360px.
- Workflow page: the gate is a segmented control with help in a tooltip; "back to" uses the shared select; the save bar slides in only with unsaved changes; composers are one surface with "+" and the drag handle shown on hover and a one-line file list; back arcs carry an arrow.
- The workbench empty state shows a one-line command with copy; stage progress fills from the left.
- The projects page aligns the host and file tables; the instruction editor grows with its content and switches edit/render with a segmented control.
- Library and skills use lighter weights and fewer decorations; skill links colour on hover only; empty details show no text.
Upgrade
From v0.1.8: run tenon update --codex (or --claude), open a new host session and reload the Dashboard. The N-1 gate reads and writes this release's data with the published v0.1.8 in both directions.
From 1.x: run the versioned installer once for each host you use:
/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 -- --codexCompatibility
- Dashboard visuals and interaction only; the CLI, state and data formats are unchanged.
Verify
tenon doctor
tenon runtime statusBoth hosts' inventory, the active managed runtime and the Dashboard report 0.1.9.
v0.1.8 · 2026-09-24
A Dashboard experience release: a full overhaul after a professional UI/UX evaluation (visuals, components, graphics, motion, task flows, smoothness, redundancy, information architecture, responsiveness, accessibility), plus a server snapshot performance fix.
Faster
- The server builds one shared snapshot for
/api/snapshot, the stream's first frame, poll broadcasts and the AFK views, reuses it while the fingerprint is unchanged, and drops it after every server write./api/snapshotsupportsETag/304. A page load builds the snapshot once; with the cache warm, pages show their full content in about 0.1 s (3–5 s before); idle event-loop load falls from about 76% to about 1%. - The workflow, library and skills pages no longer wait for the snapshot; the workbench and projects pages show skeletons on first load instead of a false "no projects".
Workbench
- Filters stay on one line and never wrap: status chips (all / needs you / running / in review / done) plus owner, workflow, track and stage dropdowns, with whatever does not fit under "more"; a facet with one value is hidden. The top-bar pending badge and "needs you" share one count, and the badge filters to it.
- Archive, take over and delete move into "⋯" menus beside the title and on cards, also in the aggregate view; the bottom action bar is gone.
- Stages show their labels instead of ids; ready states use amber; single-stage workflows draw no bar; the selected task is written to the URL with a project tag, so same-name tasks are no longer confused.
Workflow page and canvases
- Canvases render at 1:1 and grow with the number of parallel lanes instead of shrinking text, re-fit on resize, and align overflowing content to the left for panning. Zoom controls are no longer white in dark mode. The pulse plays only while a skill runs or right after an edit.
- The built-in default workflow no longer shows a warning on every stage (all were false positives); warnings are an icon with a tooltip.
- Gate help opens from the keyboard; the save bar shows the number of unsaved changes; composers use "done", preview a row on click and add it with "+".
- Deep links support
wf,trackandstep.
Projects, library and skills
- The host list is an even table; instruction files are applied from the diff drawer after "preview changes"; delete moves into the "⋯" menu; Markdown previews no longer show HTML comments.
- The library shows names only (ids in tooltips); "copy" is "copy as custom"; built-ins are marked with a lock; agents are grouped as executors and reviewers; loading no longer looks empty.
- The skills page has search and row details, and marks only changed and failed skills. A server row carrying
modelInvocableno longer makes the whole page report an invalid response.
Design system and accessibility
- Secondary text in the light theme meets WCAG AA; disabled buttons change colour instead of only fading; buttons and chips have 40px hit areas.
- One Radix-based dialog; destructive confirmations are alertdialogs that start on "cancel".
- Duplicate breadcrumbs, eyebrows and names are removed; settings use two segmented rows for theme and language; the offline banner is fully visible.
Upgrade
From v0.1.7: run tenon update --codex (or --claude), open a new host session and reload the Dashboard. The N-1 gate reads and writes this release's data with the published v0.1.7 in both directions.
From 1.x: run the versioned installer once for each host you use:
/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 -- --codexCompatibility
- Changes made outside the server that the snapshot fingerprint does not cover (such as editing a workflow file directly or changing identity with
git config) show up within 30 s; writes through the Dashboard or the CLI show immediately. - New URL parameters
status,step,wfandtrack; in the aggregate viewchangecarries a project tag, and old links still match by name.
Verify
tenon doctor
tenon runtime statusBoth hosts' inventory, the active managed runtime and the Dashboard report 0.1.8.
v0.1.7 · 2026-09-24
A sixth real-session acceptance release. On v0.1.6 all three tracks finished with no verify-fail and every review gate opened on a single confirmation; this release fixes the last flow issue that run found.
Flow
- When the user replied "继续" after the delivery step's turn, the resume itself appended to the interaction, skill-invocation and skill-confirmation ledgers in the change directory, and v0.1.6 asked for another delivery commit with the same title; the model skipped it and transitioned on an uncommitted tree. The "anything left to deliver" check now excludes all four hook-appended ledgers (history, interactions, skill invocations, skill confirmations); they go in with the next commit.
Upgrade
From v0.1.6: run tenon update --codex (or --claude) and open a new host session. The N-1 gate reads and writes this release's data with the published v0.1.6 in both directions.
From 1.x: run the versioned installer once for each host you use:
/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 -- --codexVerify
tenon doctor
tenon runtime statusBoth hosts' inventory, the active managed runtime and the Dashboard report 0.1.7.
v0.1.6 · 2026-09-24
A fifth real-session acceptance release. On v0.1.5 all three tracks finished and every review gate opened on a single confirmation; this release closes the remaining small issues.
Flow
- build's skill, executor and reviewer actions and the
pre_verify_review_resultfield carryreview_bar: the reviewers the next step (verify) declares, with theirblock_atand focus. Self-review and sub-agent reviews in build use the same bar and fix blockers there, instead of being stopped at verify and sent back. - The second delivery commit is titled
chore(<change>): update deliverables, distinct from the firstfeat(<change>): deliver. - A delivery value already known (such as
pr_url=no-remote) is written before the last delivery commit; when a commit is needed first to get a PR URL, one more commit follows the write. The workspace is clean before the delivery step's transition. - The entry skill replies in the user's language, keeping commands, field names and paths unchanged.
Upgrade
From v0.1.5: run tenon update --codex (or --claude) and open a new host session. The N-1 gate reads and writes this release's data with the published v0.1.5 in both directions.
From 1.x: run the versioned installer once for each host you use:
/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 -- --codexCompatibility
- build actions in
nextgain an optionalreview_barfield.
Verify
tenon doctor
tenon runtime statusBoth hosts' inventory, the active managed runtime and the Dashboard report 0.1.6.
v0.1.5 · 2026-09-24
A fourth real-session acceptance release. On v0.1.4 all three tracks finished with a clean working tree; this release fixes the guidance and confirmation issues that run found.
Review confirmation
- When a reply such as "按推荐" confirms a review, the hook now tells the model the receipt was written for which change and event, to follow
next, and not to ask for "确认继续" again. In v0.1.4 the model claimed the confirmation had not taken effect and waited a turn. - The resume context only reports review gates that still exist and are unconfirmed; a confirmed review awaiting its transition shows as confirmed.
- The entry skill lists every reply that confirms a review gate. "按推荐" only confirms the gate: field values follow
next'srecommended, and the model must ask before using a different value.
Flow
- When the planning step flags a missing test script, it asks for the script and tests to be written into the proposal and design too, and the
spec-consistencyreviewer no longer treats a script added only to satisfy a required test as a spec mismatch. In v0.1.4 this caused a verify-fail and a return to spec. - The delivery step commits before ticking tasks, and later changes such as the applied spec get a second commit before
pr_url. - The
read-documentsaction names the inputs this step may edit (editable) with a note; everything else is read-only and requirement changes go throughrequirements-changed. Document content must be read into context, not discarded. - Workflows without OpenSpec report "finished" instead of "archived", and the
list --finishedcolumn isFINISHED_AT.
Upgrade
From v0.1.4: run tenon update --codex (or --claude) and open a new host session. The N-1 gate reads and writes this release's data with the published v0.1.4 in both directions.
From 1.x: run the versioned installer once for each host you use:
/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 -- --codexCompatibility
- Human-readable output changes: the finished line of
checkfor non-OpenSpec workflows, thestatuslinesfinished/finished_at, and thelistcolumnFINISHED_AT. JSON output is unchanged.
Verify
tenon doctor
tenon runtime statusBoth hosts' inventory, the active managed runtime and the Dashboard report 0.1.5.
v0.1.4 · 2026-09-24
A third real-session acceptance release. v0.1.3 was driven through the backend, free and simple tracks in real Claude Code sessions; this release fixes what that found.
Committing deliverables
- On the delivery step (a step whose fields include
pr_urlorprd_path),nextadds acommitaction beforepr_urlthat commits every deliverable: code, docs, the applied main specs, test records under.tenon/users/<user>/testsand the state-directory.gitignorefiles. In v0.1.3 a default-workflow change finished with all of these left in the working tree. - The commit covers the whole working tree but excludes the local gate markers at the repository root (
.pipeline-pending-*) and local files left by older releases, and no commit is issued when only those remain. After the backend and free tracks finish,git statusis empty apart from ignored files. - The entry skill commits only when
nextnamescommitorfinish-change.
Flow
- When a later step's required test lacks its npm script, the planning step (spec) already raises it so it goes into the plan; if it is only found in build, the message says to add the
package.jsonscript without editing the recorded spec documents, so it no longer forces a return to spec. - build asks for
build_mode/isolationbefore the test-configuration fix. status --jsonalways includesstepfor a finished change, with the same shape as an active one andnextset tostop(code: finished).- Main specs keep a blank line between Purpose and Requirements, and the unlock notice is printed once per prompt.
Upgrade
From v0.1.3: run tenon update --codex (or --claude) and open a new host session. The N-1 gate reads and writes this release's data with the published v0.1.3 in both directions.
From 1.x: run the versioned installer once for each host you use:
/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 -- --codexCompatibility
- The delivery commit includes every uncommitted change in the working tree; deal with unrelated uncommitted files in the same repository first.
- The finished-change stop code changes from
run-archivedtofinished.
Verify
tenon doctor
tenon runtime statusBoth hosts' inventory, the active managed runtime and the Dashboard report 0.1.4.
v0.1.3 · 2026-09-24
A second real-session acceptance release. v0.1.2 was driven through the backend and free tracks in real Claude Code sessions and the Dashboard was re-checked at 1440 px; this release fixes what that found.
Data-driven flow (step.next)
ship: uncheckedtasks.mditems come beforeapply-specand document writes as afixaction listing every item, and ticking one no longer hides the rest.- The
finish-changecommit succeeds first time: it lists only paths git accepts (an untracked original change directory is left out), adds.pipeline/.gitignore,.tenon/.gitignoreandopenspec/.gitignorewhen they exist untracked, and untracks heartbeat files committed by older releases. Outside a git repocommitisnull. - Workflows without OpenSpec, such as
simple, also get afinish-changecommit afterverify-passwhen changes are uncommitted. - Choice fields such as
build_modeandisolationappear innextafter the input documents are read and before implementation starts. - The
buildstep of thechat,pmandfreetracks declares the requiredspec-consistencyreviewer, sopre_verify_review_resultcan bepassonly once that review has a real verdict. Changes in progress keep their frozen plans. - When a required test's npm script is missing,
tenon test runreports it as not configured rather than failed and writes no record;step.tests[].statusisunconfigured, flagged already inbuild. - Finished changes have one
status --jsonshape, andtenon test statuslists each step's last record for them. tenon spec applyfills a new capability's main-spec Purpose from the proposal instead of leavingTBD, and fails withpurpose-missingwhile the proposal is still a scaffold.
Host and hooks
- When the user names a known track ("走 free 轨道", "track=free", "use the backend track", ...), routing uses it and the dispatch says
track_basis: user-named; negated, unknown or multiple names fall back to scoring with a note. - The unlock hints list the replies that actually unlock (including "按推荐" and "好的") and those that do not, and a test checks every phrase against the classifier.
- A new
openspec/.gitignoreignores.pipeline-terminal-activity.*heartbeats, so they no longer dirty the tree. - UserPromptSubmit hooks no longer time out on pasted text: v0.1.2 exceeded 30 s on a 64 KB log, and 1 MB now takes under 0.5 s. Prompts over 64 KiB are routed on their first and last 8 KiB.
Dashboard
- The 含已完结 / 已归档 / 未提交删除 toggles sit on their own row, so workflow chips no longer collapse into a column.
- Each resource-catalog facet row starts with its group name (category, framework, styling, license).
Upgrade
From v0.1.2: run tenon update --codex (or --claude) and open a new host session. The N-1 gate reads and writes this release's data with the published v0.1.2 in both directions.
From 1.x: run the versioned installer once for each host you use:
/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 -- --codexCompatibility
nextorder changed: choice fields and test configuration come first.finish-change.commitaddsuntrack, andcommandandcommitmay benull.- Main specs already written with
TBDare not rewritten.
Verify
tenon doctor
tenon runtime statusBoth hosts' inventory, the active managed runtime and the Dashboard report 0.1.3.
v0.1.2 · 2026-09-24
An acceptance-fix release. v0.1.1 was self-tested end to end in real use: a real Claude Code session through all seven phases, every CLI capability, and every Dashboard page in a browser. This release fixes what that found.
Host and hooks
- The interaction gate no longer blocks
ToolSearch. In Claude CodeAskUserQuestionis a deferred tool that must be loaded throughToolSearch, so v0.1.1 deadlocked whenever a skill required a question. The block message now explains how to ask. - Hook JSON parsing is linear. On macOS's bash 3.2, v0.1.1 took quadratic time on long commands: a 21 KB heredoc kept the test-nudge hook busy for 67 s and the host cancelled it. A 166 KB input now takes about 1 s, and commands over 64 KiB are never treated as read-only.
- The test reminder only matches tests declared on the current step and track; the
freetrack is no longer told to run tests it does not have. - The entry skill activates the session with the host session id, and "确认继续" / "继续执行" resume the current change while a review is pending instead of being routed as a new task.
.pipeline/.gitignoreignores local-only state:cache/,terminal-sessions/andcodex-skill-receipts.jsonl.
Data-driven flow (step.next)
pre_verify_review_resultcan be set topassonly once the step's declared tests, executors and required reviewers have real results.nextno longer recommendspass, nor abuild_modethat then demandsdirect_override.phase_status,verified_atandupdated_atare managed by transitions and refused bytenon set.- When a required reviewer fails,
nextgoes straight to the fix orverify-failexit. - Parallel agents are numbered by dependency layer;
nextreports an agent that is still running, andtenon agent nextno longer claims everything is done when it is not. ship: uncheckedtasks.mditems appear innextas the exit blocker. With no git remote,pr_urlacceptsno-remote(the CLI checks there is no remote); any other value must be an http(s) URL.- Finished changes are listed under
finished_changes, andtenon checkreports them as finished with exit 0.finish-changecarries the paths and message to commit the archive move, so no uncommitted changes are left. document recordrefuses scaffolds that still contain template placeholders and lists the lines.
CLI
- Top-level workflow YAML keys may appear in any order (the documented example with
document_contractafterstepsnow parses). --presetaccepts onlyfull|hotfix|tweak; custom workflows no longer need one.- Unknown change names report "change 不存在" instead of raw file errors, and the CLI exits quietly on a closed pipe.
tenon test code-sizecounts source files only; reviewer verdicts bind to the workspace fingerprint like test records, so a code edit makes an old review stale.- The human-readable
tenon workflow planlists each step's inputs, outputs, tests and agents;doctorandlast-update.jsonagree on the changed-skill count.
Dashboard
- The reviewer/executor editor keeps its draft across parent re-renders (in v0.1.1 added reviewers could be lost).
- First load shows a loading state instead of "no projects"; middle-column search boxes keep their height and filter rows wrap.
- Previews no longer render YAML frontmatter as body text; deleting a custom template, agent or test direction asks for confirmation; toasts report the outcome.
- The offline banner is fully visible below the top bar; the projects page no longer re-reads instruction files every 5 seconds.
- The resource catalog adds v0 templates, Skiper UI (links only) and coss ui.
Upgrade
From v0.1.1: run tenon update --codex (or --claude) and open a new host session. The N-1 gate reads and writes this release's data with the published v0.1.1 in both directions.
From 1.x: run the versioned installer once for each host you use:
/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 -- --codexCompatibility
tenon status <change> --jsonaddsfinished_changes;set-fieldandagent nextJSON gain fields.- An unknown
--presetandagent promptfor an undeclared agent now exit 1. - Documents already recorded with placeholders in changes in progress are unaffected; re-recording requires filling them first.
Verify
tenon doctor
tenon runtime statusBoth hosts' inventory, the active managed runtime and the Dashboard report 0.1.2.
v0.1.1 · 2026-09-23
A correctness release. Accepting v0.1.0 on real hosts found the default workflow unusable on three of its five tracks, and several places where Tenon reported a step as done without checking it.
Blocker fixed: mandatory skills the host refused to run
- In v0.1.0 the
pm,frontendandbackendtracks could not leaveexplore, andpmwas also stuck atspec,verifyandship. Their mandatory skills included upstream skills whoseSKILL.mdcarriesdisable-model-invocation: true. The host refused to invoke them, no receipt was written, and every transition failed withstep-skills-incomplete. - Replacements:
grill-with-docsbecomesgrilling+domain-modeling, the two skills it delegates to, andimprove-codebase-architecturebecomescodebase-design, also in the builtinarchitectureagent.pmno longer requireshandoff(usetenon handoff <change> [--bundle]),to-specorto-tickets: the applied-spec document and the spec step's task guard already cover what they did. The removed skills still ship as manual guides. - This cannot ship again. The release candidate's skill verification fails with
mandatory-skill-not-invocable;tenon doctorreportsskills:invocable, red when every alternative of a mandatory skill is proven non-invocable and yellow when itsSKILL.mdcannot be read; and the Dashboard refuses to save a workflow that makes such a skill mandatory.
No more false greens
tenon check,tenon transitionandtenon statusshare one judgement of a step's skills and of its exit rules. In v0.1.0 they could disagree on the same state: at pmship,checkfailed whilestatusshowed the exit ready andtransitionlet it through. A task can no longer ship without its deliverable, such as a pm task with an emptyprd_path.- A spec rehearsal no longer satisfies the applied-spec obligation, on any track.
- A mandatory skill that the step's document contract names as a producer is done only when a document it produced is recorded in the current step visit. An invocation receipt alone no longer counts for it.
A task finishes by following next alone
tenon status <change> --json now emits in next only actions the commands accept, so a runner that follows it takes a default task from open to tenon list --finished:
- an input document edited after it was recorded is recorded again, instead of being re-read forever;
- a scaffolded document is recorded in the same wave, so the scaffold step settles;
- an unrecorded
role: updateslot is a permission, not an obligation, and asks for nothing; - a single rollback edge behind a review gate goes through a review request, like a forward edge;
- the archive step offers
complete(thearchivedtransition), thenfinish-changewith the exactopenspec archivecommand; build_shais written by the Build exit transition instead of being asked of the runner;- the coverage guard's remediation names what its parser actually reads.
Ownership and finished tasks
tenon set,set-manyandcasrequire an identity and refuse a non-owner, like every other mutation, and refuse a finished task.- 完结 is the transition
tenon transition <change> archived, which stampsarchived_at; writingarchivedorarchived_atby hand is refused. - A finished task stays readable with
tenon status, and appears intenon list --finishedas soon as it is marked archived, before its directory moves. - A task hidden with 归档 and a finished task give different messages: the first points at
tenon task unarchive, the second says the task is finished and a new task is needed.
Install and upgrade
tenon spec applycreates the main-spec directory of a capability that does not exist yet.tenon doctorattributes its findings to the host in use, and contacts the remote only with--verify-release.- The upstream skill lock keeps exactly the v0.1.0 format, so a v0.1.0 verifier reads a lock written by v0.1.1 and the reverse. Whether a skill can be invoked is read from its
SKILL.mdbytes, not from the lock. - The installation guide explains raising
CLAUDE_CODE_PLUGIN_GIT_TIMEOUT_MS: Claude Code clones the marketplace with a 120 s default, and a timed-out clone leaves that host without the plugin.
Upgrade
From v0.1.0, run tenon update --codex (or --claude), then open a new host session. The N-1 compatibility gate runs the published v0.1.0 against this release's writes in both directions.
From 1.x, run the versioned installer once for each host you use:
/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 -- --codexCompatibility
- In a task already in progress, a document that a producing mandatory skill recorded in an earlier step visit no longer counts for the current visit.
nextasks for it again asrecord-document, naming the skill. - These mandatory skills are still satisfied by invocation alone, because no document slot names them as its producer at that step:
openspec-explore,grilling,domain-modelingandcodebase-design(explore);brainstorming(pm spec);test-driven-development,frontend-designandprototype(build);browser-qa,web-design-guidelines,design-taste-frontendande2e-testing(verify);finishing-a-development-branch(ship). Binding them to an output needs a document-contract decision in a later release.
Verify
tenon doctor
tenon runtime statusskills:invocable is green, and both hosts' inventories, the active managed runtime and the Dashboard report 0.1.1.
v0.1.0 · 2026-09-22
Version numbering restarts at 0.1.0. Tenon is young, and a 1.x number claimed a maturity it did not have. This release also delivers the capabilities built after v1.1.5.
Version reset
- Numbering restarts at 0.1.0 and continues 0.1.x, 0.x. The retired v1.0.0–v1.0.9 and v1.1.0–v1.1.5 releases and tags are removed, and those 16 numbers are never published again: the release candidate rejects them.
- Install order ranks every retired 1.x number below every other stable version, so 0.1.0 is an upgrade from 1.1.5, while a newer 0.x is never silently replaced by an older one.
tenon updatenames the retired version it migrates away from, so the change is never silent.
Upgrade from 1.x
Run the versioned installer once for each host you use, then open a new host session:
/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 -- --codextenon update on a 1.x installation reports a downgrade to 0.1.0 and changes nothing. That refusal ships inside the published 1.x code and cannot be fixed retroactively, so the one-liner above is the migration path. The daily automatic update on a 1.x machine logs the same refusal. After migrating, tenon update --codex (or --claude) is again the routine upgrade.
More than one person per repository
- Tenon resolves a declared identity from
TENON_USER, then<config>/user.json, then the repository's own configured user email, with no login. New records carry the actor who wrote them. - Every task has a creator and an owner; another user takes over with 接手. Advancing a task you do not own is refused.
- Per-user state lives under
.tenon/users/<slug>/, so two people in one repository no longer overwrite each other's active task or authority state. The Dashboard shows the current user and filters the workspace list by owner.
Task archive and deletion
- 归档 / 取消归档 hides a task for one user only, and is reversible.
- 删除 removes a task from the working tree without committing, and the Dashboard says so before you confirm.
tenon task delete|archive|unarchive <name>andtenon list --archived.
Workflows are data
openspec: trueis the single OpenSpec switch in a workflow.- Each track declares its own
document_contract: which documents a step produces, updates or requires. The kernel's fixed per-phase document tables are gone, and the global default workflow spells the tables out. - The Dashboard workflow page edits the inputs, skills, executors, outputs, reviewers and gate of every step, and a workflow's steps, tracks and gates (评审 or 自动) are stored globally rather than per project.
One skill instead of seven
- A single
tenonskill drives every step fromtenon status <change> --json, whosestepblock names what to do next, including pending agent runs and required tests. tenon spec applyapplies a delta spec to the main spec.
Executors and reviewers per step
- A step declares executors and reviewers:
tenon agent next|prompt|record. Nine builtin agents ship with the release (builder, researcher, architecture, frontend-quality, backend-quality, code-size, security, spec-consistency, e2e). - Tenon computes a reviewer's verdict from its findings and the severity that blocks the step, records every run as evidence, and freezes the agent set for a task when the task is created.
- Agents run in the host (Claude Code's Agent tool, or a Codex subagent). The Dashboard never calls a model.
Test evidence per step
- Steps declare tests with a command, inputs and outputs, run through
tenon test run <change> <test-id>, and the transition gate refuses to advance while a required test record is missing or stale. - Nine builtin test directions ship as a library: unit, integration, e2e, playwright, typecheck, regression, benchmark, code-size and design-system. Run logs, traces and screenshots are kept per run for the acting user.
Instruction files, templates and projects
- Tenon writes project and user instruction files (AGENTS.md, CLAUDE.md, GEMINI.md) from 32 builtin template blocks, and reports a conflict instead of overwriting a file that changed outside Tenon.
- The library page collects agents, templates, the resource catalog and test directions; builtin items are read-only and can be copied before editing. A single 新建项目 button creates or adopts a project.
Design system and resource catalog
- A project's design system lives in
DESIGN.md, withtenon designand a design-system workflow to produce it. Creating a frontend task is refused until the design system is ready. - The resource catalog ships 163 builtin entries (
tenon resources), so a step can name the libraries and references it is allowed to use.
Upstream skills
- Setup and update install 53 upstream skills, declared in
skills/sources.yaml, into the plugin root. A lock file records each skill's commit, tree digest and license, and a failed fetch leaves the active release untouched. - The installed payload therefore grows to roughly 48 MB. A digest cache keyed on file stat keeps the measured hook dispatch overhead within about 7 ms of v1.1.5.
Compatibility
- v0.1.0 has no earlier 0.x release, so the N-1 compatibility gate is skipped for this release only, and reported rather than silent: the fixture names v0.1.0 and the tooling exits with a documented skip code. v0.1.1 pins v0.1.0 as its baseline.
- Because of that, v0.1.0 is not proven to read tasks created by 1.x. Finish or archive an in-flight 1.x task before migrating, or expect to recreate it.
Verify
tenon doctor
tenon runtime statusBoth hosts' inventories, the active managed runtime and the Dashboard all report 0.1.0, and a repeated tenon update --codex reports that the release is already in effect.
v1.1.5 · 2026-09-15
Found while continuing the same real Codex task on v1.1.4.
Specs
- A Chinese delta spec no longer reaches Verify before learning that OpenSpec strict validation needs the English
SHALLorMUSTin every requirement. The scaffold prompt (both locales) and thetenon-specskill now state the rule, and the skill runsopenspec validate <change> --strictbefore requesting the spec review when the official CLI is available. The Codex task wrote requirements with 「必须」, passed the spec review, and was sent back from Verify.
Review gate
- A reply that does not confirm a pending review now says which reply does, the same as for interactions. At
verify-failthe agent offered 「修复」 as an answer; the gate ignored it silently. tenon-verifytells the agent to name 「确认继续」 when it pauses atverify-fail.
Upgrade
Install the new release for each host you use, then open a new host session:
/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
Found while running a real Codex task on v1.1.3.
Codex skill evidence
- A complete
SKILL.mdread now counts as skill evidence in both exec program forms Codex writes. Tenon recognised onlyconst r = await tools.exec_command({...}); text(r);, so a read written astext(await tools.exec_command({...}));produced no evidence: the firsttenon document recordafter it failed withcurrent StepVisit lacks exact host confirmation, and the agent had to read the skill again. Both forms must still forward the complete result of exactly one awaited call;.output, unawaited calls, wrapped results and extra statements are rejected as before. - That error now says how to recover: invoke the producer skill again in the current step (Claude Code: the Skill tool; Codex: one standalone
catof itsSKILL.mdwith amax_output_tokenslarge enough for the whole file, because a truncated read is not evidence), then retry the record. A Codex agent read the 20 KBtenon-exploreskill with a 1,000–2,000 token budget twice before guessing a larger one.
Upgrade
Install the new release for each host you use, then open a new host session:
/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
Found while installing v1.1.2 for Codex on a slow proxy connection.
Installer
- A dropped or slow connection to GitHub no longer aborts the install. The stable release proof ran
git ls-remoteand a shallowgit fetchonce, so a singleETIMEDOUTor TLS reset (SSL_ERROR_SYSCALL) failedinstall.sh. Both calls now retry transport failures up to three times with a short backoff. A missing tag or ref still fails immediately, and every result is validated exactly as before.
Interaction gate
- An approval that releases a pending interaction now says so in the conversation. A Codex agent that had been blocked earlier assumed the gate still held after the user replied 「确认继续」 and stopped without retrying the blocked action.
Upgrade
Install the new release for each host you use, then open a new host session:
/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
Fixes found by continuing real Claude Code and Codex tasks on v1.1.1.
Interaction gate
- An interactive skill (
brainstorming,grilling,grill-with-docs,prototype,huashu-design) asks the user once per step visit. Codex records a document only after re-reading its producer skill; for an interactive producer that re-read locked the same question again, so Explore could not record its design or ADR in Codex. An approval now leaves anInteractionConfirmedhistory row, and the gate does not lock that skill again until the step is entered again. - The gate names the reply that unlocks it (「确认继续」, 「继续执行」 or 「同意继续」). A reply that is not recognized as approval while a question is pending now tells the agent so instead of being ignored.
Upgrade
Install the new release for each host you use, then open a new host session:
/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
Fixes found by running real tasks on the published v1.1.0 in Claude Code and Codex.
Host compatibility
- Claude Code loads the plugin again. Claude Code 2.1 loads the standard
hooks/hooks.jsonautomatically and refused a manifest that referenced it a second time, so no Tenon skill or hook was available. The Claude manifest no longer declareshooks. - Documents can be recorded from Claude Code. Claude Code reports plugin skills as
tenon:<skill>; the Skill receipt rejected that name, so everytenon document recordfailed withcurrent StepVisit lacks exact host confirmationand the default workflow could not leaveopen. - Tenon runs inside the Codex sandbox. Codex's
workspace-writesandbox denies/bin/ps; the state lock now records a pid-only owner instead of failing withwithLock: current process start identity is unavailable. tenon doctorturns red when the host reports that the Tenon plugin failed to load.
Workflows and Dashboard
- Tracks created in the Dashboard work end to end. Skills no longer check a workflow branch track with
tenon tracks show, which only knows the project Track registry;tenon tracks showexplains branch tracks when an id is not registered. - An archived run shows no current stage.
- A stage without runtime artifacts returns an empty catalog instead of a failed request on every refresh.
Upgrade
Install the new release for each host you use, then open a new host session:
/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 -- --codexVerify with claude plugin list (Tenon enabled, no errors) and tenon doctor.
v1.1.0 · 2026-09-15
Workflow editor and workspace
- The Dashboard is reduced to two views: the workspace (projects, Changes, stage rail, per-stage Skill flow with run status, input/output files, runtime artifacts) and the workflow editor.
- Workflows are stored per user and are not bound to a project. Each workflow owns Track branches; stages are reordered by drag, gates are
revieworauto, and a stage may declare where it returns to. Skills are composed on a canvas where dropping on a column runs them in parallel and dropping to the right runs them serially. - Runtime artifacts keep producer, version and lineage; document and field outputs share one submission path.
Review decisions and gate safety
- A pending review can be approved from the terminal or from the workspace right column. Both use one shared acknowledgement application, so the canonical state, interaction record and history line are identical apart from the entry channel (
review_acknowledged_via). - Every rejected acknowledgement writes nothing. Dashboard requests carry an expected revision and an idempotency key; stale or conflicting requests return 409 with a stable code, and unexpected errors return 500 without internal details.
tenon review acknowledgeexits2,3,4or1for review-required, revision, idempotency and invalid-command failures. - Transitions out of a review step require an exact receipt plus a matching binding on every entry point.
tenon set/set-many/cascan no longer movephase, andtenon state import-legacykeeps transition-controlled fields and reports any it ignored. - The local HTTP transition route can never satisfy a loop human gate. While a review is pending, reads of the Dashboard token and loopback API calls are recorded as redacted self-approval observations, including in AFK mode.
Security
fast-uriis updated to a release without the high-severity advisories.
Upgrade
Install the immutable v1.1.0 entrypoint, or run tenon update --codex (or --claude). Open a new host session afterwards so the updated Skills and hooks are loaded. v1.0.9 remains immutable for rollback.
v1.0.9 · 2026-09-02
Dashboard reliability and Pipeline clarity
- Finite adapter-install event streams now close deterministically after completion, surface malformed or unavailable transport errors, and never leave the install action stuck in a busy state. Switching projects detaches the previous stream so late events cannot leak into the new project.
- Definition-catalog SSE requests clean up even when the browser closes during the initial load; slow catalog projections are serialized so revisions cannot arrive out of order or regress the visible definition.
- Change creation waits for catalog reconciliation and explains loading, unavailable, and empty states. The selected Pipeline now previews every Stage's serial/parallel mode, Skill order, and declared dependencies before the first run.
- Adapter request booleans are type-checked instead of coercing string values, keeping dry-run and confirmation semantics explicit for API clients.
Compatibility and scope
- Existing Changes keep their frozen Workflow/Track/Pipeline identity; the GUI continues to expose the canonical Workflow/Track-derived Pipeline. Independent named Pipeline blueprints remain available through planner-v2 until a persisted Pipeline Registry is introduced.
Upgrade
Install the immutable v1.0.9 entrypoint, then use tenon update --codex (or --claude) for routine upgrades. The previous v1.0.8 release remains immutable and can be used for rollback.
v1.0.8 · 2026-09-02
Orchestration input and execution runtime
- Skill dependencies are materialized into a versioned
skill-input-manifest/v2and bounded input bundle before execution. Executors and validators receive the same digest-checked inputs, while rejected input delivery fails closed without invoking the Skill. - Canonical Skill output is atomically persisted under
.tenon-artifacts/, registered with anartifact://reference, schema, byte count, and SHA-256 digest, and resolved by downstream Skills through the manifest contract. - Custom Workflow, Track, Pipeline, Stage, and Skill dependencies honor declared serial/parallel modes and resource claims; overlapping write claims are never run concurrently.
Security and dependency maintenance
- The Browserslist dependency is pinned to the first patched release (
4.28.7) through the repository override and lockfile, removing the high-severity advisory without weakening the audit gate.
Upgrade
Install the immutable v1.0.8 entrypoint, then use tenon update --codex (or --claude) for routine upgrades. Existing Changes retain their frozen Workflow/Track/Pipeline identity until an explicit replan.
v1.0.7 · 2026-08-11
Cross-version installer bridge recovery
- The public installer can take over a durable WAL from an older stable release only after that transaction reached the completed host phase
plugin-installedand the installed plugin plus Marketplace still exactly match the older stable version, tag, commit, official origin, ref, and clean checkout. - WALs in any other phase, with malformed or unknown data, a same-version target whose tag/commit is not the exact current proven target, any newer target, or any host inventory drift fail closed before host mutation and remain available for diagnosis or recovery.
- An exact same-target WAL keeps the existing same-target recovery semantics.
- The old transaction is atomically replaced by a current-target transaction whose before-snapshot is the verified host state; existing exact provenance, trusted-host, lock, atomicity, and packaged setup checks remain unchanged. There are no retries, fallbacks, or weakened stable Release/object proof checks.
Upgrade
Install the immutable v1.0.7 entrypoint, then use tenon update --codex (or --claude) for routine upgrades.
v1.0.6 · 2026-08-11
Stable Git proof budget
- Real slow-link measurements showed that the public stable tag/object proof can exceed the previous 30-second Git budget: proxy
ls-remote/fetch took 6.9s/11.3s, while direct fetch reached 22.9s and formal transactions still observed occasional longer phases. - Git remote
ls-remoteand fetch now use a bounded 60-second budget. GitHub Release API metadata and the npm bootstrap raw installer download remain bounded at 30 seconds; local init/rev-parse/cat-file proof remains 10 seconds, and host observation keeps its default 5 seconds. - Exact stable tag/object/commit, digest, trusted executable, official HTTPS host, size, and atomicity checks are unchanged. There are no retries or source/branch/cache fallbacks, and failures remain closed before mutation.
Upgrade
Install the immutable v1.0.6 entrypoint, then use tenon update --codex (or --claude) for routine upgrades.
v1.0.5 · 2026-08-11
Doctor release identity proof
- The
tenon doctorrelease-identity probe now propagates the bounded 30-second budget for remote Git tag/object proof and the 10-second budget for local proof commands. - Host observation commands retain their default 5-second timeout; no retry, source/branch/cache fallback, or trusted-executable/security validation is weakened.
Upgrade
Install the immutable v1.0.5 entrypoint, then use tenon update --codex (or --claude) for routine upgrades.
v1.0.4 · 2026-08-11
Public installation and update network budget
- The shell installer’s GitHub Release metadata/tag proof,
tenon updateRelease metadata fetch, and the npm bootstrap installer download now share a bounded 30-second network budget. - Exact stable Release, tag/object, digest, host trust, HTTPS host, size, and atomicity checks are unchanged.
- There are still no retries or source/branch/cache fallbacks; failures remain closed before mutation.
Upgrade
Install the immutable v1.0.4 entrypoint, then use tenon update --codex (or --claude) for routine upgrades.
v1.0.3 · 2026-08-11
Stable Release proof diagnostics
- Remote tag and object proof now keeps a bounded 30-second network budget instead of the previous 10-second budget; local proof commands remain on a 10-second budget.
- Timeout failures now preserve actionable stderr diagnostics such as
ETIMEDOUTinstead of returning an empty error detail. - Security validation, atomic publication, and the no-retry/no-fallback behavior are unchanged.
Upgrade
Routine upgrades still use tenon update --codex (or --claude) and remain bound to the verified stable Release tag.
v1.0.2 · 2026-08-08
Versioned installation and updates
- The public one-line installer is pinned to immutable
v1.0.2prebuilt assets; it never installs frommainor compiles source. tenon update --codexresolves the official latest stable GitHub Release, freezes its tag and commit, and rebinds the Codex Marketplace through host-owned commands.- Exact same-version host/runtime/Dashboard state is a zero-mutation no-op; downgrade attempts and unverifiable Release metadata fail before mutation.
- Setup always waits for Dashboard readiness. Piped/CI installs and all updates keep the browser closed and print the verified URL plus
tenon dashboard --open.
Upgrade
Existing v1.0.1 users run the immutable v1.0.2/install.sh one-liner once; the v1.0.1 launcher cannot safely self-rebind the new tag in one old-updater invocation. From v1.0.2 onward, run one tenon update --codex. Open a new Codex session to load the released Skills and hooks, then run tenon doctor --json.
v1.0.1 · 2026-07-26
Normal-chat entry contract
product/identity.jsonnow declaresentrySkill: "tenon"as the only public entry.- Codex normal chat invokes
tenon:tenon; no secondary entry alias is retained. - Root
AGENTS.mdand the Codex static adapter consume one generated managed block. tenon doctorverifies the entry Skill and reports an enabled conflicting workflow plugin as a red finding.tenon setup --codex -yremoves that exact retired registration through the official Codex plugin manager before activating Tenon.
Repository and release hygiene
- CI and Release scan every tracked path and text file for restricted external reference identities.
- Matching is case-insensitive, has no exemptions, and diagnostics never echo a restricted identity.
- The same checks run before the release payload is built.
Upgrade
Run tenon update --codex, then tenon setup --codex --auto-update -y. Open a new Codex session and run tenon doctor --json.
v1.0.0 · 2026-07-26
Governed document locale
- New Changes pin governed documents to
zh-CNby default. tenon init,tenon document scaffold, and the default OpenSpec fallback share one Document Presentation Registry.- Users can explicitly select
--document-locale en. - A pinned Change cannot silently switch locale.
- Historical Changes infer locale from the writing system used by existing H1 headings.
- Mixed or ambiguous signals fail loudly and require an explicit locale.
Execution modes
- Discussion handles ordinary conversation without a state machine.
- Simple uses
change → verify → doneand does not create the full OpenSpec chain. - Default uses
open → explore → spec ⇄ build ⇄ verify → ship → archive. - Free binds an explicit workflow without adding a domain Track.
- Custom follows only its declared DAG, Skills, gates, and document contract.
Documentation site
- The repository README defaults to Chinese and links to
README.en.md. - The site exposes Chinese at the root and the English mirror under
/en/. - Local search is built from the public content manifest.
- GitHub Pages deploys only from
main. - Pull requests build and verify but do not deploy.
- The artifact is checked against a closed allowlist and scanned for sensitive material.
llms.txtindexes only public pages.- Internal ADRs, Superpowers plans, review receipts, and local control-plane state are excluded.
Installation and updates
- Install for Codex with
tenon setup --codex. - Install for Claude with
tenon setup --claude. - Update with the matching
tenon update --codexortenon update --claude. - The managed runtime is content-addressed and the stable launcher targets a verified release.
- A failed update preserves the previous release for
tenon runtime repair --rollback. - Dashboard listens on
127.0.0.1:18765by default.
Upgrade checklist
- Inspect the repository working tree.
- Run the update command for the selected host.
- Run
tenon runtime status. - Run
tenon doctor. - Run
tenon list --jsonin the project. - Confirm Dashboard uses
127.0.0.1:18765.
Verification
tenon --helplists the command families.tenon runtime statusreports the active runtime.tenon doctorreports no missing bundled Skills.- Repeated
tenon setup --codexremains idempotent. - Updating does not rewrite canonical Change state.
- A new test Change creates Chinese proposal, design, and tasks files.
- An explicitly English Change keeps newly scaffolded documents in English.
Compatibility
The canonical Change codec does not gain a locale field. Locale lives in the rollback-compatible .pipeline-document-locale.json sidecar.
Rollback
Run tenon runtime status, then use tenon runtime repair --rollback to return to the previous verified content-addressed release.
Runtime rollback does not remove project Changes, OpenSpec documents, or evidence ledgers.
Next action
Read Updates, recovery, and uninstall for the complete maintenance and recovery workflow.