Skip to content

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>.md for Claude Code and <Codex config dir>/agents/tenon-<name>.toml for Codex. The config dir is $CLAUDE_CONFIG_DIR (Claude Code) or $CODEX_HOME (Codex) when that variable is an absolute path, otherwise ~/.claude or ~/.codex. --host defaults to the host detected from the environment, and to claude in a plain terminal. It exits 0 when done (files it must leave alone are WARN lines and count as done) and 1 for a usage error, an unassembled library or a failed write.
  • Before, tenon init and tenon agent prompt wrote .claude/agents/tenon-<name>.md into 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|--codex and tenon update --claude|--codex run the same install after they succeed (update also when it finds the release already current) and print what was written. A failed install only warns: the exit code of setup or update does not change, and you can run tenon agent install --host <host> yourself. --dry-run installs 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|rm change the library only and print a reminder to run tenon agent install and 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 a WARN; 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. If host-owned.json cannot 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 as modified. tenon uninstall works on the project's .pipeline-owned.json and does not touch them, so run tenon 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 prompt writes no host file. With --json it returns subagent_type: tenon-<name> and native: true only 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-purpose for Claude Code, default for Codex) with native: false, a reason (not-installed, definition-mismatch, shadowed or project-agent) and a one-line hint, 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 none records 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.jsonl as subagent_reason, and the ledger row keeps its three subagent keys.
  • 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.json lists 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 a WARN naming 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_DIR or CODEX_HOME set, run the install with the same variable, or the files land in ~/.claude/agents and 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 claude uses the session's environment. See Host subagents and the install command.

Step progress in the terminal ​

  • After tenon init, tenon session activate, tenon transition or tenon step run <name> succeeds, the PostToolUse hook step-progress prints 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 example verify-fail to 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 exits 1 with 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 tenon skill fills the host's Todo list from tenon workflow plan <name> --json only 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 run tenon 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 init creates the task in <project>/.worktrees/<change> on a new branch tenon/<change>, cut from the current HEAD and tracking nothing. The task records base_branch (the branch you were on), start_sha (that HEAD) and isolation=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/exclude when no ignore rule covers it, so git status in the main checkout does not change. Creation is all or nothing: if init fails the worktree and the branch are removed again, and an existing path or branch of that name makes init exit 1 and changes nothing.
  • In place instead. tenon init creates 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, HEAD is detached, the directory is itself a linked worktree, or you pass --in-place (use it only when you want that; the tenon skill passes it only if you ask). TENON_INIT_IN_PLACE=1 has 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. --json prints one line on stdout with change, path, worktree (the absolute path, or null) and worktree_reason (null for a worktree, otherwise not-git, no-commits, detached-head, linked-worktree or requested).
  • Switching in and resuming. In Claude Code the tenon skill switches into the worktree with EnterWorktree and then runs tenon 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 carries worktree_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 list has a final WORKTREE column (- for a task of the current root) and its --json rows carry worktree; 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) exits 1 for 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-level setup string, such as npm ci, and tenon init runs 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: a setup you already trusted runs at once, and one that is not trusted yet is not run: init prints it and asks you to run tenon test trust yourself (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 of setup in the main checkout does not count. A failing setup only warns, with the command to rerun it, and init still exits 0; a missing or invalid catalog, or no setup key, is reported and skipped. tenon test trust lists setup with the suites, and tenon test run neither runs nor checks it. setup can take minutes: give the shell call a generous timeout (the skill uses 600000 ms); a timeout only stops setup, 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, as cleanup on the finish-change action and on the stop with code: 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 -d refuses a branch that is not merged, so no commit is lost. finish-change always has the key (null for a task created in place); the stop has 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=branch binds a Git object. The identity stays bound to the physical repository and worktree, so verifying a worktree task from another checkout gives worktree-mismatch, and a token of the wrong kind for the task's isolation gives isolation-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 init records the HEAD it started from as the task field start_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 of HEAD and committed no later than the task's created_at plus 60 seconds), the merge-base of HEAD and the upstream of base_branch, and the merge-base of HEAD and base_branch (when that is not HEAD itself). 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 before created_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-risk probe, reviewers scoped by attach_on, changed-line coverage, evidence export, the transition gates (CLI and HTTP), the Dashboard snapshot and tenon verify --ci all compare against this one start. In a CI checkout tenon verify --ci takes origin/<base> as the base when that reference exists, and the local branch otherwise.
  • start_sha, base_branch and created_at are written only by tenon init: tenon set, set-many and cas refuse them (exit 1, nothing changes). base_branch and created_at used to be writable.
  • start_sha is 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 against created_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 fetch it 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 than created_at passes 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-changed from Build or verify-fail from Verify) no longer asks for the skills of the agents the step declares: those skills can only be loaded while the agent runs, so requirements-changed from 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 transition and 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 went revision-stale, and every fingerprint, so every tenon 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 init creates a worktree in the main checkout of a Git repository. Scripts and CI that run tenon init and then keep working in the same directory pass --in-place (the CI bench step of this repository does) or change into the path that --json returns. 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 prompt returns the generic subagent with reason: "not-installed" and the install hint, and the agent runs without its tools allowlist and model.
  • A trusted setup runs without asking. Trust binds the command text, not the repository files the command reads when it runs, such as the package.json lifecycle scripts or the scripts they call, the same as for test suites. After setup is trusted, a later commit that changes those files makes tenon init run the changed version in the new worktree. To avoid that, create the task with tenon init --in-place or remove setup from the catalog.
  • tenon set, set-many and cas refuse start_sha, base_branch and created_at (exit 1). A script that wrote base_branch or created_at fails now.
  • tenon list has a new last column, WORKTREE, and its --json rows have a worktree key. A script that reads the table by column position or compares rows exactly needs an update.
  • tenon uninstall leaves the user-level subagent files. Run tenon 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 init in a temporary git repository and expects the task in that directory must export TENON_INIT_IN_PLACE=1, and a fixture that must not touch the host configuration must unset CLAUDE_CONFIG_DIR and CODEX_HOME: the user-level subagents and AGENTS.md resolve from them. The vitest global setup and the shell test scripts of this repository do both.
  • This repository's .tenon/tests/catalog.yaml declares setup: npm ci && npm run build:packages.

What you need to do ​

Run tenon update --claude (or --codex), then:

  1. Install the host subagents in the terminal with tenon agent install --host claude (or --codex), and open a new host session. The tenon update you just ran is the code of the release you updated from and has no install step; from now on tenon setup and tenon update run it, and a second tenon update does it now. If Claude Code or Codex starts with CLAUDE_CONFIG_DIR or CODEX_HOME set, 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.
  2. Scripts and CI that run tenon init and then work in the same directory: add --in-place, or read worktree from --json.
  3. To have new worktrees prepared, declare setup (for example npm ci) in .tenon/tests/catalog.yaml and run tenon test trust once in your own terminal. Without it a new worktree has no installed dependencies, and the tests do not run there (tenon init says so).
  4. Remove writes to base_branch and created_at from scripts that used tenon set.
  5. On a machine whose project has an untracked .claude/worktrees/, a task in flight can show revision-stale once on its first check: return to Build, capture the current revision again, and run the reviewers again (see Compatibility).
  6. When a task in a worktree is finished, run the cleanup command that step.next reports, in the main checkout.
  7. Codex: the hook files changed, so Codex may ask you to trust them again. Run /hooks, trust tenon, and start a new session (see Codex hook trust).
  8. 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 setup from .tenon/tests/catalog.yaml, and run tenon 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 by TENON_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-level setup is reported with an unknown key. Before rolling back, remove setup from .tenon/tests/catalog.yaml.
  • A task that tenon init created in a worktree has isolation=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 its tenon list does not list those tasks, and a command that reads one by name says change 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 (reasons not-installed, definition-mismatch, shadowed, project-agent) and runs recorded with --subagent none. Finish or terminate such tasks before rolling back.
  • start_sha is 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-level tenon-<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 at tenon init and 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-time revision-stale on 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 install and uninstall, tenon workflow progress, tenon init --in-place and --json, tenon agent record --subagent none, the WORKTREE column of tenon list and worktree in its --json rows, reason and hint in tenon agent prompt --json, the cleanup key of the finish-change action, and the catalog key setup. Changed: tenon set, set-many and cas refuse start_sha, base_branch and created_at. Removed: the project-level host agent files that tenon init and tenon agent prompt wrote.

Verify ​

bash
tenon --version
tenon doctor
tenon runtime status
tenon agent install --host claude
tenon list --json

tenon --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 standard and simple Workflows: open → build → verify → done with one review gate and a change-risk probe in Build. When the probe fails the task escalates: scope-expanded ends it as Escalated and names the command that opens the heavy Track task and links it with depends_on (the domain recorded by tenon 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 (exit 0 within them, 3 over a limit with the breached limits listed, 1 error), and the tenon skill 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 (openspec or none), route (keywords, exclude, priority, fallback) and automation (eligible, enqueue_after_spec). A Workflow without tracks: 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 list lists the Tracks of every Workflow.
  • default runs from its YAML. Its rules are written there as leave conditions, three of them general: tasks-through-step, not-afk-queued and dependencies-ended. tenon check <name> without an event lists the unmet conditions of the step and of its forward edges at once and exits 2.
  • Every task ends in openspec/changes/archive/. tenon task finish <name> and tenon task terminate <name> move the task there with the result Finished, Escalated or Terminated, and tenon list --finished lists 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 HEAD and 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 (unknown when 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.next lists the unread ones in read-documents, and tenon 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 (exit 1). tenon document status <name> prints the inputs, outputs and required outputs of the step.
  • The required-output guard, { 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 (exit 2). {change} is the task name, * matches inside one path segment, ** any number of segments and ? one character. default uses 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.md and tasks.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 | full instead of the multi-field test_policy (a step cannot carry both, and a frozen test_policy is still read). default uses changed at Build and full at Verify on the lite, frontend and backend Tracks, plan at Spec of frontend and backend, and none at Spec of pm (its Verify is changed). The default full runs 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-level levels key that defines plan, changed and full with 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 changes levels, the change summary in tenon review request marks each loosened or tightened key per level.
  • Five test classes: unit, integration, e2e, static and benchmark (plus custom, run only when named) group the 17 kinds. A suite's kind may 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 files glob matches it and is never written to test-plan.yaml. A file no suite claims is an orphan (test-file-orphan), and tenon test register <change> --auto appends its exact path to a suitable suite; it no longer widens a glob or scans the whole repository. Paths under openspec/, .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 its files or covers glob. When a change edits only source files, changed and full skip a suite that has no covers and print the non-blocking notice suites-without-covers naming it; write covers with tenon test catalog set <id> --cover-glob "<glob>" to have source changes run it.
  • tenon test sync <change> --prune deletes from the plan's files the registrations the catalog no longer claims, which clears registered-test-not-executed.
  • Catalog writes keep your comments: catalog add|set|rm|not-applicable and register --auto rewrite 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 trust stores 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 a test-trust/ path.
  • code-size has no ceiling by default. The 2000-line lines_added limit is removed from the default workflow and the code-size direction; the probe still reports its metrics, exits 0 and records the measured values, and the code-size reviewer reads and judges them. A project that wants a limit writes pass.metrics under the test in its workflow. A test:<id> waiver shown in review request now 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/ or coverage/ 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: run tenon transition directly. step.next hands you transition for the only back edge instead of request-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/null and 2>&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-diff and --textconv, rg --pre, -z and --search-zip, sed -n with anything but a line-range print, and file -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-fail in default) allows at most two rounds, a round being one entry into the step; the count starts again after requirements-changed back to Spec. A step declares its limit with max_rounds (1 to 20), a limited step that declares nothing gets 2, and tenon 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.next asks you to accept the remaining reviewer blockers at the forward review, or stops with rounds-exhausted, and lists the other ways out (raise the limit, return to Spec, end the task). The CLI and the Dashboard (HTTP 409, code: "rounds-exhausted") refuse the back edge. Only a human confirmation accepts remaining blockers, bound to the reviewer's run and the code candidate; --delegated and AFK mode never do, and the Dashboard's Approve refuses them (HTTP 409, 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 as code-size through. Before, nothing could: step.next stayed on run-test and the task never reached the review gate. The waiver is listed by tenon review request and 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-build when the build is current, --dry-run to preview). It never poses as a release: /api/health reports channel: dev and <version>+dev.<sha7>, tenon doctor shows identity:release as a warning, auto-update is off and tenon update refuses 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, and tenon doctor reports source:drift. --from-source runs 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 ​

  • standard and simple are gone, and so are the chat and free Tracks. tenon init <name> --workflow standard and --workflow simple exit 1 and print --workflow default --track lite. A retired Track id given to --track exits 1 and lists the Tracks the Workflow offers. tenon init <name> --workflow design-system takes no --track. A task created before the upgrade finishes on the snapshot it froze, whatever its Workflow or Track was.
  • .pipeline/tracks.yaml and 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 the route of the Workflow branches.
  • "Hiding" is removed. tenon task archive and tenon task unarchive, the Workspace "Archived" toggle and the per-user archived.json are gone; the two subcommands exit 1 and point to tenon task terminate and tenon task finish. Tasks you had hidden show again in the Workspace.
  • New tasks carry fewer fields and checks. build_mode, isolation, direct_override and branch_status and 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 of default exits 2, not 1.
  • Workflow definitions reject the document-contract keys. document_contract (top level or on a Track branch) and the step keys inputs, outputs and artifacts are removed, and so is the nonempty-output guard. Saving a Workflow, creating a task and compiling a plan reject them and name the replacement: a file condition is a required-output guard, a field condition is field-nonempty or field-equals. Empty inputs: [], outputs: [] and artifacts: [] still load. A gate: auto step is no longer derived from its outputs, so write those checks as guards. A new custom Workflow that freezes a build revision writes freeze-build-sha on the build-complete edge and build-head-unchanged on the verify-pass edge itself, as default does. See Keys that were removed.
  • New tasks get no proposal or design skeleton and no document ledger (.pipeline-documents.json); a Track with spec: openspec gets only the tasks.md skeleton. tenon document scaffold, record, init and migrate-delta exit 2 and tenon artifact register exits 1 on a new task and say that outputs are recorded automatically. The frontend Track no longer requires a ready DESIGN.md before Open; the design-system test still checks it at Ship. The Workflow API no longer returns effectiveIo, and the io field of GET /api/change/:name/orchestration is now stepIo. 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.eligible on its Track. The pm, frontend and backend Tracks of default are eligible; the lite Track and any Track without an automation block, every flat Workflow included, are not (tenon afk enqueue exits 3, and --json reports reason: "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_rounds on the step to change it.
  • Test settings change what a pass asks for. The default code-size test no longer blocks on 2000 added lines, the default full no longer runs every integration and e2e suite, and scenario mapping no longer blocks an exit. The 80% line-coverage threshold that default asked of frontend and backend Verify is gone: a project that wants it writes levels.full.coverage in 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 run says to fix git first and tenon test trust refuses to write.
  • An agent cannot confirm a review for you. The write gate refuses an agent's tenon review acknowledge without --delegated. Confirm with an approval phrase as your whole reply (the hook writes the receipt), in the Dashboard (tenon dashboard --open), or by running tenon review acknowledge <change-name> in your own terminal.
  • A source development install replaces the release until you go back. tenon update refuses it; use tenon update --claude --to-stable (or --codex).

Development ​

  • A test or acceptance script that starts a tenon child in an isolated runtime must drop the launcher's runtime roots (TENON_RUNTIME_ROOTS and TENON_RUNTIME_DATA_ROOT, _STATE_ROOT, _CONFIG_ROOT): use isolatedEnv, or pass the environment through withoutInheritedRuntimeRoots from tools/lib/runtime-roots.mjs. Otherwise a suite run through tenon test run reads 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:

  1. Replace --workflow standard / --workflow simple in scripts and notes with --workflow default --track lite, and drop --track from design-system tasks.
  2. Replace tenon task archive <name> with tenon task terminate <name> (give up a task) or tenon task finish <name> (a task that reached its end).
  3. Move rules from .pipeline/tracks.yaml into the route of the Workflow branches.
  4. If you saved your own default in the global store, it still loads, but a copy that lacks the lite branch makes tenon assess report "no change-risk thresholds" instead of guessing. Add a lite branch with a diff-risk test to use assessment.
  5. If a Workflow of yours fails to save and names document_contract, inputs, outputs or artifacts, delete those keys; write required-output for a file a step must produce and field-nonempty or field-equals for a field. Scripts that read effectiveIo from the Workflow API or io from GET /api/change/:name/orchestration read stepIo instead.
  6. In each git repository, run tenon test trust once in your own terminal before the first test run.
  7. 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-fail succeeds on its own. Nothing changes for forward edges.
  8. Codex: the hook files changed, so Codex may ask you to trust them again. Run /hooks, trust tenon, and start a new session (see Codex hook trust). Run tenon sync --migrate if tenon update says the block in AGENTS.md is stale.
  9. Before rolling back to an earlier release, finish or terminate the tasks you created after upgrading, and remove levels from .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 by TENON_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 of default with 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 test key, the step max_rounds key or the catalog levels key: reading a Workflow with one of the step keys fails, and a catalog with levels is reported with an unknown key. Before rolling back, finish or terminate the tasks you created with those keys, and remove levels from .tenon/tests/catalog.yaml.
  • v0.3.2 does not know the three general leave conditions or the required-output guard. For a task this release created on a heavy Track of default, tenon status shows 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 without tracks: (design-system or 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-time revision-stale on 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 list and show, tenon task finish and terminate, tenon list --finished, tenon init --domain, tenon review revoke, tenon test waive --test, tenon test sync --prune, tenon set <change> max_rounds, tenon setup --from-source and tenon update --to-stable. Removed: tenon task archive and unarchive, POST /api/changes, the router preview and the effectiveIo and io response fields.

Verify ​

bash
tenon --version
tenon doctor
tenon runtime status
tenon tracks list
tenon init demo --workflow standard

tenon --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 --rollback after a tenon update failed 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 update and tenon setup settle 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.sh of v0.3.2 once for the host. See Rollback, update and setup all refuse on a leftover rollback.
  • A not_applicable entry 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.yaml counted as an unapproved protected change (protected-file-unapproved), and review request refused a second request.
  • A task already stuck this way is not repaired by the update. Archive it with tenon task archive <name> --yes and 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 doctor shows identity:release as a warning, not a failure.
  • The warning names both ways forward: tenon update returns to the newer release, tenon setup rebinds 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.json now 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_FILE and similar variables, which the check now ignores.
  • tenon test discover writes 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_ms and .hz metrics.
  • Discover writes no suite, and prints a note, when a vitest 5 project's bench files still import the removed module-level bench.
  • More agent and review output follows TENON_LANG: the agent next lines, the other agent prompt and agent record errors, and the review request and review acknowledge messages. 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-verify action has an optional sarif-checkout-path input, for repositories whose commit lives in a sub-checkout, and a sarif-id output (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 discover writes no benchmark suite when it cannot determine the vitest major version, and prints both catalog add commands instead. That is the case when vitest is not installed and package.json declares no range that fixes one (latest, workspace:*, >=3), or when the installed vitest package.json cannot be read.

Development ​

  • Tenon's own tests run on vitest 4.1.11 (was 3.2.6); the tinypool override is gone.
  • CI now runs, as blocking steps of the verify job, an update and rollback acceptance (npm run test:update-rollback, including the recovery from a stuck rollback) and a real npm install vitest@5 bench check.
  • The tenon-verify action 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 or verify --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:

bash
/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 -- --claude

Compatibility ​

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-verify action 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_RETENTION chains, discover --write catalogs 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 ​

bash
tenon --version
tenon doctor
tenon runtime status

tenon --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 --ci with candidate-mismatch because of host-local files that are never committed: .claude/settings.local.json, CLAUDE.local.md in 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-mismatch message 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-expanded edge (for example a standard task that escalated to default) is no longer judged on test evidence by verify --ci; the report carries the note change-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: escalated is judged as before.
  • tenon evidence export is 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 prompt routing, the host refusals of agent record) and the review acknowledge refusals follow TENON_LANG; they were always Chinese.
  • The selected track tab stays fully visible in the English Dashboard: a page opened with track=backend no 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 --open as ––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 tenon skill picks the lane for a manual /tenon <request> (a prompt that starts with / skips the routing hook): the workflow or Track you name, else standard for an implementation request, default only for heavy or cross-domain work (architecture, auth, migrations, dependencies, contracts), and a project-defined workflow only when you name it.
  • When step.next offers a single transition and it is the scope-expanded escalation, 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 --ci adds the note finished-judged-at-head when 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) or xargs, 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 --ci has two new note-level finding codes, change-abandoned and finished-judged-at-head. No command, option, project file or Dashboard API changed apart from --version.

Verify ​

bash
tenon --version
tenon doctor
tenon runtime status

tenon --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 ​

  • standard is 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 reviewers code-review and security (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 on default. 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.next one scope-expanded transition whose escalate payload says to open a default task.
  • tenon step run <change> [--json] does the deterministic part of step.next in 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> --all records 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 official security reviewer (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 playwright step test is gone and the e2e reviewer (1.1.0) no longer reruns it.
  • An integration test drives a three-file bug fix through the lane and asserts at most 25 tenon calls 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. Exit 0 passes, 2 has an error finding, 1 is a usage error.
  • Formats are text, json, sarif (2.1.0) and markdown (job summary), written with --format, --out and --also. Every report lists what CI cannot prove without the local key.
  • Test integrity uses the step's integrity policy: block turns a signal into the error test-integrity (exit 2; on a shallow clone the diff cannot be read and it fails with files-diff-unavailable), and the default notice gives a note that never fails the run.
  • The output follows the CLI language (TENON_LANG=en|zh, then LC_ALL, LC_MESSAGES, LANG; Chinese when none is set): text, Markdown, SARIF messages, JSON message strings and usage errors. Finding codes, levels, exit codes and JSON field names do not change.
  • The composite GitHub Action .github/actions/tenon-verify runs it on pull requests with the CLI bundle of the pinned release, appends the job summary and uploads SARIF to code scanning. Its language input (zh or en) sets the report language. See CI verification.
  • tenon evidence export <change> --format agent-trace|otel|git-notes|trailer writes an Agent Trace v0.1 record, OTLP/JSON GenAI spans (nothing is sent), a git note in refs/notes/tenon, or Tenon-Change: / Tenon-Evidence: trailers. It prints by default; --apply writes the note or amends HEAD.
  • --anchor (git-notes) copies the record chain head into the note; verify --ci reports anchor-mismatch or anchor-behind, and --require-anchor makes a missing or lagging anchor an error.

Test integrity and the trust root of test evidence ​

  • test_policy.integrity: notice | block (default notice) and tenon 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.
  • notice shows a note in status and the Dashboard; block blocks 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 is record-unsealed; the next run starts a new one.
  • Changes to .tenon/tests/catalog.yaml, baselines/**, known-failures.yaml and .pipeline/workflows/*.yaml in a task's diff block every gate: review step until you confirm them; tenon review request lists them and --delegated is refused. A known failure names one case and expires within 30 days.
  • tenon test trust [<change>] [--yes] [--status] [--json]: tenon test run refuses catalog and inline-test commands you have not trusted on this machine. CI declares TENON_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 contains tenon 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 prompt refuses (exit 2) unless you pass --rerun-reason <text>, and the most severe verdict on that candidate counts. The Dashboard shows reruns.

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 with host: (tenon agent new --host). The step's declaration binds, the agent's only routes.
  • From another host or a plain terminal, tenon agent prompt writes the prompt to openspec/changes/<change>/.pipeline-agent-reports/<run-id>.prompt.md and prints the codex exec … or claude -p … command and the tenon agent record line. 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 (detected or declared). 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 (exit 2).
  • 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 copyable tenon 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, benchmark and smoke run when the project has them; coverage thresholds apply only to a suite whose catalog entry declares coverage; a unit suite run in full satisfies regression.
  • tenon test catalog not-applicable <kind> --reason <text> | --rm declares a kind that does not apply to the project; it takes effect after one human review acknowledge (not --delegated).
  • tenon init runs tenon test discover --write when the project has no catalog, and tenon test register <change> --auto claims unclaimed test files, seeds the plan and registers them. JavaScript unit globs now cover src/, test/, tests/, __tests__/ and root *.test.*.

Dashboard ​

  • Sign-in: without a session, GET / and every /api/* request answer 401, except /api/health and /assets/*. tenon dashboard --open mints 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-Language prefers 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 acknowledge in 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-motion shows 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=list and /api/stream?view=list (a full frame, then snapshot-delta) and a task's evidence from GET /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, state and count and 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.ts runs axe-core over the main pages in light and dark and fails on any serious or critical violation.
  • 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 (mode 0600, 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 with tenon logs [--follow] [--lines <n>].
  • TENON_LANG=en|zh (then LC_ALL, LC_MESSAGES, LANG) picks the CLI language for help and the common errors; JSON fields and exit codes do not change. An illegal tenon transition event now lists the legal events for the step.

CI and platform ​

  • CI blocks on verify (Node 22, Chromium), a node-matrix job and a separate WebKit job. npm test runs with TENON_E2E=1.
  • node-matrix runs the test-system, reporter and parser suites, the tools/ node:test scripts 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 own node:test on 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 plus profiles_env (for example linux-x64-4c-node22-1a2b3c4d), so hosted runners of one size share a benchmark baseline.
  • tenon doctor reports env: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 run puts the running tenon first on PATH for test processes, so tenon test code-size --json no longer exits 127.

Fixed ​

  • The 2000-line code-size limit of v0.2.0 never reached the frozen plan, so a 2036-line candidate passed. New tasks now fail the test when lines_added exceeds 2000.
  • tenon test catalog add --report-format exit-code works, 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 root coverage/; deleting or archiving a task removes its generated host agent files; a finished design-system task commits DESIGN.md.
  • Agent files name skills with the tenon: prefix (a bare deep-research was refused); tenon agent validate rejects the skeleton placeholders. A case without a file shows "No file reported", not (unknown).
  • tenon update tells a project with a stale Tenon block in AGENTS.md to run tenon sync --migrate.

Behaviour changes and upgrade impact ​

  • Review confirmation is owner-only. A non-owner running tenon review acknowledge is refused. Use --as reviewer (the history records as=reviewer owner=<id>) or tenon 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. Run tenon dashboard --open; read with the CLI instead of curl; with no browser, run tenon dashboard --port <port> in a terminal to get the link.
  • A catalog with profile: is rejected by older Tenon, and so is one with not_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's test_policy. If discover cannot identify your npm test, register a unit suite (tenon test catalog add) or declare unit not applicable, or Spec stops with test-kind-missing.
  • The code-size limit is enforced for new tasks (see Fixed): split the task or raise pass.metrics in the workflow.
  • scope-expanded is an abandon edge in simple and standard: it needs no test, reviewer, document or skill evidence and leaves the workspace uncommitted for the new default task. Nothing to do.
  • Routing sends small implementation prompts to standard, not the seven-phase default. To stay on default, name the Track in the prompt (for example "use the backend track").
  • The security reviewer attaches only when the diff touches auth, dependency or contract paths. To keep it on every task, tenon agent copy security <name>, delete the attach_on line and use the copy in your workflow.
  • The first tenon test run on a machine needs your trust. It stops with exit 1 and lists the commands; run tenon test trust in 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 acknowledge or 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, run tenon test run <change> --stage once.
  • 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 as LANG=en_US.UTF-8 now gives English help and messages. TENON_LANG=zh pins 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 trust once in your own terminal; for a task started earlier run tenon test run <change> --stage again.
  • Codex projects: run tenon sync --migrate if tenon update says the block in AGENTS.md is stale.
  • Optional: add the tenon-verify Action to your pull requests (CI verification) and set TENON_TEST_TRUST=1 in CI jobs that run tenon test run.

If every command already fails with Node identity changed, run the versioned installer once for each host you use:

bash
/usr/bin/curl -fsSL https://raw.githubusercontent.com/jefferysha/tenon/v0.3.0/install.sh | /bin/bash -s -- --claude
/usr/bin/curl -fsSL https://raw.githubusercontent.com/jefferysha/tenon/v0.3.0/install.sh | /bin/bash -s -- --codex

A 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 --rollback or 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). Run tenon test run <change> --stage once after updating.
  • v0.3 counts undeclared coverage/, test-results/ and playwright-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: coarse or not_applicable: in the catalog; integrity: notice|block in a workflow test_policy; host: on a workflow reviewer; attach_on or host in a custom agent file (including tenon agent copy security <name>); and TENON_RECORD_RETENTION.
  • Standard-lane tasks (track: standard) are v0.3-only. v0.2.1 can list them and show their status, but check, test, agent and document refuse them (未注册的 track 'standard') and status --json has no step projection. Finish or archive them before rolling back.
  • The Dashboard's full snapshot keeps its shape; ?view=list and GET /api/change/:name/snapshot are additions, and every route except health and assets needs a session.

Verify ​

bash
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-open

tenon 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/tenon and tenon-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, doctor and runtime still run, so tenon 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 your PATH: env TENON_RUNTIME_ROOTS=… node …/bootstrap/active.mjs cli setup --claude (--codex for 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 update run by v0.2.0 writes the old format again, so the first Tenon command or session start afterwards rewrites both launchers with the writer setup uses, 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 naming tenon setup --claude / --codex if it could not. Hooks stay silent and never wait for it. Anything else is left untouched.
  • tenon doctor has 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:

bash
/usr/bin/curl -fsSL https://raw.githubusercontent.com/jefferysha/tenon/v0.2.1/install.sh | /bin/bash -s -- --claude
/usr/bin/curl -fsSL https://raw.githubusercontent.com/jefferysha/tenon/v0.2.1/install.sh | /bin/bash -s -- --codex

If 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 ​

bash
tenon runtime status
tenon doctor
grep -c '%d' ~/.local/bin/tenon

The 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, rm and export. Every agent declares a role (executor or reviewer) and an optional semver version. new asks for what is missing in an interactive terminal and runs non-interactively when all flags are given. rm refuses 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 in templates/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-author skill 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 a tenon-result block), validates it and registers it with tenon 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>.md or .codex/agents/tenon-<name>.toml. tenon agent prompt then returns subagent_type: tenon-<name>, so the host runs the agent as its own subagent (Claude Code enforces its tools allowlist; 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), and tenon uninstall removes only files it generated and you did not edit.
  • The default workflow now attaches executors: every Explore runs researcher, every Build runs builder (one subagent per independent task, merged into one report) before its reviewers, and every Verify declares the code-size test (tenon test code-size --json, passing while lines_added stays within 2000) and the required code-size reviewer. Chat and free also run security as 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_on runs after the skills it names; skills in one wave run in parallel and waves run in series; skills without depends_on stay serial in declaration order, as before. The skill gate, tenon status and 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= and GET /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: null and an omitted gate mean 自动, so the workflow page offers two options. The outputs check applies to forward edges only; return edges such as verify-fail and requirements-changed are 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 --write adds the new ones.
  • Each change has a test plan, openspec/changes/<change>/test-plan.yaml, written only by tenon test commands: the suites the change uses, the test files it added or changed, and the mapping from OpenSpec scenarios and tasks.md items to test cases. A hand-edited plan becomes test-plan-tampered.
  • Every added or modified test file must be registered. tenon test sync and the step exit read the change's diff since it started (staged, unstaged and untracked files included) and block with test-file-unregistered; when the diff cannot be read they block with files-diff-unavailable instead of passing. Only scenarios and the tasks in the implementation section of tasks.md need a case; other tasks are optional.
  • Workflows declare a test_policy per stage and track: which kinds must be registered, which must run (changed or full scope), coverage thresholds, benchmark baseline requirement, flaky limit, required browsers, and scenario coverage. default ships defaults for every track (for example frontend registers unit and playwright at spec, and Verify runs unit, regression and playwright on chromium and webkit with 80% line coverage). A stage without a policy and the old inline tests[] 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 status walks the flow in step.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 discover use 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 is flaky, counted, and limited by the step policy.
  • Coverage reads istanbul summary, lcov and cobertura and is judged against the policy; changed_lines comes 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>.json and 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 request lists them and the user's confirmation approves exactly those.
  • tenon test report writes a traceability matrix (scenario or task → case → latest result) into the verification report. Reviewers that declare reads_tests also 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 the tenon status and snapshot benchmarks against the CI profile.

Forms ​

  • New workflow: pick a start point (blank, a copy of built-in default or simple, 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.md is never overwritten. The default frontend spec prompt points at tenon 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.md described per-phase skills that were removed. It now describes the single tenon skill. tenon sync reports the block as absent, current, stale or invalid, and tenon sync --migrate refreshes 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 an apply_patch).
  • The hosts list of an agent was never enforced because the tenon skill did not pass --host; it does now.
  • Test runs that inherit TENON_USER, TENON_RUNTIME_HOME, TENON_BASE_BRANCH or TENON_CHANGE_NAME (for example npm test inside 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 as ENOTEMPTY. Vitest workers are capped at min(8, cores), so full npm test runs 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 user and tenon owner are documented in the CLI reference and the contract; the AFK fail-closed error names the real gap (the caller did not inject deps.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:

bash
/usr/bin/curl -fsSL https://raw.githubusercontent.com/jefferysha/tenon/v0.2.0/install.sh | /bin/bash -s -- --claude
/usr/bin/curl -fsSL https://raw.githubusercontent.com/jefferysha/tenon/v0.2.0/install.sh | /bin/bash -s -- --codex

Then, for every project that uses the default workflow:

  • The default workflow now requires tests. Run tenon test discover --write once in the project, review .tenon/tests/catalog.yaml and commit it. Until a catalog exists, a new task is blocked at its first stage that has a test policy with test-catalog-missing, and tenon status shows the command.
  • For Codex projects, run tenon sync --migrate once to refresh the managed block in AGENTS.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 the tenon:agent-author skill) and attach them to a step on the workflow page.

Compatibility ​

  • The CLI additions are new commands and new optional fields. Agent files without role still load (the role is inferred from the tools and tenon agent validate asks you to add it); v1 test records and steps that declare inline tests[] stay readable and in force; gate: null keeps working and means 自动.
  • New project files: .tenon/tests/ (catalog, baselines, known failures), .tenon/agents/, and per change openspec/changes/<change>/test-plan.yaml. Host agent files .claude/agents/tenon-*.md and .codex/agents/tenon-*.toml are 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 ​

bash
tenon doctor
tenon runtime status
tenon test catalog validate

Both 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.md receives the full text; CLAUDE.md / GEMINI.md get a single @AGENTS.md import. 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.md can be created as an @AGENTS.md import.
  • 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 workspace and workflow; old progress / workbench links 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:

bash
/usr/bin/curl -fsSL https://raw.githubusercontent.com/jefferysha/tenon/v0.1.9/install.sh | /bin/bash -s -- --claude
/usr/bin/curl -fsSL https://raw.githubusercontent.com/jefferysha/tenon/v0.1.9/install.sh | /bin/bash -s -- --codex

Compatibility ​

  • Dashboard visuals and interaction only; the CLI, state and data formats are unchanged.

Verify ​

bash
tenon doctor
tenon runtime status

Both 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/snapshot supports ETag / 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, track and step.

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 modelInvocable no 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:

bash
/usr/bin/curl -fsSL https://raw.githubusercontent.com/jefferysha/tenon/v0.1.8/install.sh | /bin/bash -s -- --claude
/usr/bin/curl -fsSL https://raw.githubusercontent.com/jefferysha/tenon/v0.1.8/install.sh | /bin/bash -s -- --codex

Compatibility ​

  • 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, wf and track; in the aggregate view change carries a project tag, and old links still match by name.

Verify ​

bash
tenon doctor
tenon runtime status

Both 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:

bash
/usr/bin/curl -fsSL https://raw.githubusercontent.com/jefferysha/tenon/v0.1.7/install.sh | /bin/bash -s -- --claude
/usr/bin/curl -fsSL https://raw.githubusercontent.com/jefferysha/tenon/v0.1.7/install.sh | /bin/bash -s -- --codex

Verify ​

bash
tenon doctor
tenon runtime status

Both 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_result field carry review_bar: the reviewers the next step (verify) declares, with their block_at and 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 first feat(<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:

bash
/usr/bin/curl -fsSL https://raw.githubusercontent.com/jefferysha/tenon/v0.1.6/install.sh | /bin/bash -s -- --claude
/usr/bin/curl -fsSL https://raw.githubusercontent.com/jefferysha/tenon/v0.1.6/install.sh | /bin/bash -s -- --codex

Compatibility ​

  • build actions in next gain an optional review_bar field.

Verify ​

bash
tenon doctor
tenon runtime status

Both 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's recommended, 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-consistency reviewer 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-documents action names the inputs this step may edit (editable) with a note; everything else is read-only and requirement changes go through requirements-changed. Document content must be read into context, not discarded.
  • Workflows without OpenSpec report "finished" instead of "archived", and the list --finished column is FINISHED_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:

bash
/usr/bin/curl -fsSL https://raw.githubusercontent.com/jefferysha/tenon/v0.1.5/install.sh | /bin/bash -s -- --claude
/usr/bin/curl -fsSL https://raw.githubusercontent.com/jefferysha/tenon/v0.1.5/install.sh | /bin/bash -s -- --codex

Compatibility ​

  • Human-readable output changes: the finished line of check for non-OpenSpec workflows, the status lines finished / finished_at, and the list column FINISHED_AT. JSON output is unchanged.

Verify ​

bash
tenon doctor
tenon runtime status

Both 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_url or prd_path), next adds a commit action before pr_url that commits every deliverable: code, docs, the applied main specs, test records under .tenon/users/<user>/tests and the state-directory .gitignore files. 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 status is empty apart from ignored files.
  • The entry skill commits only when next names commit or finish-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.json script without editing the recorded spec documents, so it no longer forces a return to spec.
  • build asks for build_mode / isolation before the test-configuration fix.
  • status --json always includes step for a finished change, with the same shape as an active one and next set to stop (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:

bash
/usr/bin/curl -fsSL https://raw.githubusercontent.com/jefferysha/tenon/v0.1.4/install.sh | /bin/bash -s -- --claude
/usr/bin/curl -fsSL https://raw.githubusercontent.com/jefferysha/tenon/v0.1.4/install.sh | /bin/bash -s -- --codex

Compatibility ​

  • 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-archived to finished.

Verify ​

bash
tenon doctor
tenon runtime status

Both 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: unchecked tasks.md items come before apply-spec and document writes as a fix action listing every item, and ticking one no longer hides the rest.
  • The finish-change commit succeeds first time: it lists only paths git accepts (an untracked original change directory is left out), adds .pipeline/.gitignore, .tenon/.gitignore and openspec/.gitignore when they exist untracked, and untracks heartbeat files committed by older releases. Outside a git repo commit is null.
  • Workflows without OpenSpec, such as simple, also get a finish-change commit after verify-pass when changes are uncommitted.
  • Choice fields such as build_mode and isolation appear in next after the input documents are read and before implementation starts.
  • The build step of the chat, pm and free tracks declares the required spec-consistency reviewer, so pre_verify_review_result can be pass only 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 run reports it as not configured rather than failed and writes no record; step.tests[].status is unconfigured, flagged already in build.
  • Finished changes have one status --json shape, and tenon test status lists each step's last record for them.
  • tenon spec apply fills a new capability's main-spec Purpose from the proposal instead of leaving TBD, and fails with purpose-missing while 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/.gitignore ignores .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:

bash
/usr/bin/curl -fsSL https://raw.githubusercontent.com/jefferysha/tenon/v0.1.3/install.sh | /bin/bash -s -- --claude
/usr/bin/curl -fsSL https://raw.githubusercontent.com/jefferysha/tenon/v0.1.3/install.sh | /bin/bash -s -- --codex

Compatibility ​

  • next order changed: choice fields and test configuration come first. finish-change.commit adds untrack, and command and commit may be null.
  • Main specs already written with TBD are not rewritten.

Verify ​

bash
tenon doctor
tenon runtime status

Both 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 Code AskUserQuestion is a deferred tool that must be loaded through ToolSearch, 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 free track 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/.gitignore ignores local-only state: cache/, terminal-sessions/ and codex-skill-receipts.jsonl.

Data-driven flow (step.next) ​

  • pre_verify_review_result can be set to pass only once the step's declared tests, executors and required reviewers have real results. next no longer recommends pass, nor a build_mode that then demands direct_override.
  • phase_status, verified_at and updated_at are managed by transitions and refused by tenon set.
  • When a required reviewer fails, next goes straight to the fix or verify-fail exit.
  • Parallel agents are numbered by dependency layer; next reports an agent that is still running, and tenon agent next no longer claims everything is done when it is not.
  • ship: unchecked tasks.md items appear in next as the exit blocker. With no git remote, pr_url accepts no-remote (the CLI checks there is no remote); any other value must be an http(s) URL.
  • Finished changes are listed under finished_changes, and tenon check reports them as finished with exit 0. finish-change carries the paths and message to commit the archive move, so no uncommitted changes are left.
  • document record refuses 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_contract after steps now parses).
  • --preset accepts only full|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-size counts 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 plan lists each step's inputs, outputs, tests and agents; doctor and last-update.json agree 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:

bash
/usr/bin/curl -fsSL https://raw.githubusercontent.com/jefferysha/tenon/v0.1.2/install.sh | /bin/bash -s -- --claude
/usr/bin/curl -fsSL https://raw.githubusercontent.com/jefferysha/tenon/v0.1.2/install.sh | /bin/bash -s -- --codex

Compatibility ​

  • tenon status <change> --json adds finished_changes; set-field and agent next JSON gain fields.
  • An unknown --preset and agent prompt for an undeclared agent now exit 1.
  • Documents already recorded with placeholders in changes in progress are unaffected; re-recording requires filling them first.

Verify ​

bash
tenon doctor
tenon runtime status

Both 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, frontend and backend tracks could not leave explore, and pm was also stuck at spec, verify and ship. Their mandatory skills included upstream skills whose SKILL.md carries disable-model-invocation: true. The host refused to invoke them, no receipt was written, and every transition failed with step-skills-incomplete.
  • Replacements: grill-with-docs becomes grilling + domain-modeling, the two skills it delegates to, and improve-codebase-architecture becomes codebase-design, also in the builtin architecture agent. pm no longer requires handoff (use tenon handoff <change> [--bundle]), to-spec or to-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 doctor reports skills:invocable, red when every alternative of a mandatory skill is proven non-invocable and yellow when its SKILL.md cannot be read; and the Dashboard refuses to save a workflow that makes such a skill mandatory.

No more false greens ​

  • tenon check, tenon transition and tenon status share one judgement of a step's skills and of its exit rules. In v0.1.0 they could disagree on the same state: at pm ship, check failed while status showed the exit ready and transition let it through. A task can no longer ship without its deliverable, such as a pm task with an empty prd_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: update slot 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 (the archived transition), then finish-change with the exact openspec archive command;
  • build_sha is 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-many and cas require an identity and refuse a non-owner, like every other mutation, and refuse a finished task.
  • 完结 is the transition tenon transition <change> archived, which stamps archived_at; writing archived or archived_at by hand is refused.
  • A finished task stays readable with tenon status, and appears in tenon list --finished as 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 apply creates the main-spec directory of a capability that does not exist yet.
  • tenon doctor attributes 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.md bytes, 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:

bash
/usr/bin/curl -fsSL https://raw.githubusercontent.com/jefferysha/tenon/v0.1.1/install.sh | /bin/bash -s -- --claude
/usr/bin/curl -fsSL https://raw.githubusercontent.com/jefferysha/tenon/v0.1.1/install.sh | /bin/bash -s -- --codex

Compatibility ​

  • 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. next asks for it again as record-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-modeling and codebase-design (explore); brainstorming (pm spec); test-driven-development, frontend-design and prototype (build); browser-qa, web-design-guidelines, design-taste-frontend and e2e-testing (verify); finishing-a-development-branch (ship). Binding them to an output needs a document-contract decision in a later release.

Verify ​

bash
tenon doctor
tenon runtime status

skills: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 update names 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:

bash
/usr/bin/curl -fsSL https://raw.githubusercontent.com/jefferysha/tenon/v0.1.0/install.sh | /bin/bash -s -- --claude
/usr/bin/curl -fsSL https://raw.githubusercontent.com/jefferysha/tenon/v0.1.0/install.sh | /bin/bash -s -- --codex

tenon 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> and tenon list --archived.

Workflows are data ​

  • openspec: true is 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 tenon skill drives every step from tenon status <change> --json, whose step block names what to do next, including pending agent runs and required tests.
  • tenon spec apply applies 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, with tenon design and 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 ​

bash
tenon doctor
tenon runtime status

Both 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 SHALL or MUST in every requirement. The scaffold prompt (both locales) and the tenon-spec skill now state the rule, and the skill runs openspec validate <change> --strict before 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-fail the agent offered 「修复」 as an answer; the gate ignored it silently.
  • tenon-verify tells the agent to name 「确认继续」 when it pauses at verify-fail.

Upgrade ​

Install the new release for each host you use, then open a new host session:

bash
/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 -- --codex

v1.1.4 · 2026-09-15 ​

Found while running a real Codex task on v1.1.3.

Codex skill evidence ​

  • A complete SKILL.md read now counts as skill evidence in both exec program forms Codex writes. Tenon recognised only const r = await tools.exec_command({...}); text(r);, so a read written as text(await tools.exec_command({...})); produced no evidence: the first tenon document record after it failed with current 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 cat of its SKILL.md with a max_output_tokens large enough for the whole file, because a truncated read is not evidence), then retry the record. A Codex agent read the 20 KB tenon-explore skill 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:

bash
/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 -- --codex

v1.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-remote and a shallow git fetch once, so a single ETIMEDOUT or TLS reset (SSL_ERROR_SYSCALL) failed install.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:

bash
/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 -- --codex

v1.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 an InteractionConfirmed history 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:

bash
/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 -- --codex

v1.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.json automatically and refused a manifest that referenced it a second time, so no Tenon skill or hook was available. The Claude manifest no longer declares hooks.
  • Documents can be recorded from Claude Code. Claude Code reports plugin skills as tenon:<skill>; the Skill receipt rejected that name, so every tenon document record failed with current StepVisit lacks exact host confirmation and the default workflow could not leave open.
  • Tenon runs inside the Codex sandbox. Codex's workspace-write sandbox denies /bin/ps; the state lock now records a pid-only owner instead of failing with withLock: current process start identity is unavailable.
  • tenon doctor turns 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 show explains 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:

bash
/usr/bin/curl -fsSL https://raw.githubusercontent.com/jefferysha/tenon/v1.1.1/install.sh | /bin/bash -s -- --claude
/usr/bin/curl -fsSL https://raw.githubusercontent.com/jefferysha/tenon/v1.1.1/install.sh | /bin/bash -s -- --codex

Verify 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 review or auto, 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 acknowledge exits 2, 3, 4 or 1 for 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/cas can no longer move phase, and tenon state import-legacy keeps 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-uri is 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/v2 and 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 an artifact:// 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-installed and 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-remote and 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 doctor release-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 update Release 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 ETIMEDOUT instead 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.2 prebuilt assets; it never installs from main or compiles source.
  • tenon update --codex resolves 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.json now declares entrySkill: "tenon" as the only public entry.
  • Codex normal chat invokes tenon:tenon; no secondary entry alias is retained.
  • Root AGENTS.md and the Codex static adapter consume one generated managed block.
  • tenon doctor verifies the entry Skill and reports an enabled conflicting workflow plugin as a red finding.
  • tenon setup --codex -y removes 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-CN by 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 → done and 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.txt indexes 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 --codex or tenon 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:18765 by default.

Upgrade checklist ​

  1. Inspect the repository working tree.
  2. Run the update command for the selected host.
  3. Run tenon runtime status.
  4. Run tenon doctor.
  5. Run tenon list --json in the project.
  6. Confirm Dashboard uses 127.0.0.1:18765.

Verification ​

  • tenon --help lists the command families.
  • tenon runtime status reports the active runtime.
  • tenon doctor reports no missing bundled Skills.
  • Repeated tenon setup --codex remains 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.

Local-first · Evidence-driven · Portable