---
title: "Install and commands"
description: "「Install and commands」：yokoten 使用指南的一章。"
canonical: https://yokoten.app/zh-tw/docs/guide/install-and-commands
lang: en
source: https://github.com/yokoten-labs/yokoten/blob/v0.1.2/docs/guide.md
ref: "v0.1.2"
---

> For AI agents: the documentation index is at https://yokoten.app/llms.txt. To set up yokoten in a project, follow https://yokoten.app/agents.md.

> 這一頁還沒有繁體中文版，下面是英文原文。

# Install and commands

Requires Node 22.23.4 or later (24.22.1 or later on Node 24, 26.12.1 or later on Node 26),
which runs TypeScript as it is. Releases 22.18 to 22.23.3 run TypeScript too, but do not
use them: they have security holes that Node fixed on 2026-10-21
([advisory](https://nodejs.org/en/blog/vulnerability/october-2026-security-releases)). So npm warns about them, and
full `doctor` notes them under `machine:`. No dependencies. There
are two ways to install: the Claude Code plugin, or the `install` command, from npm or
from a clone. Use one, not both. The hook commands that yokoten writes need a POSIX shell:
on Windows, use Git Bash.

Some people arrive with skills alone: `npx skills add yokoten-labs/yokoten` copies the
skills only. A skill is advice to the agent; the guard is what stops a command. So skills
alone guard nothing: run `npx yokoten install` too, or use the plugin, to turn the guard on.

Most people need one command: `npx yokoten`. It does the work of `install`, `init`
and `doctor`: it sets up what is missing, then shows where you are and the next step. `trust`
and `update` stay their own steps. `yokoten help` shows four commands and plain `yokoten` ([Start here](https://yokoten.app/zh-tw/docs/guide/start-here.md)),
and `yokoten help --all` lists the rest in groups; the tables under [Commands](https://yokoten.app/zh-tw/docs/guide/install-and-commands.md#commands) explain each.

## Which version to install

There are three release channels. Stable is the safe default.

| Channel | Plugin | npm | What you get | For |
|---|---|---|---|---|
| Stable | `yokoten` | `latest` | The latest release tag, such as v0.1.0, with a changelog | Everyone |
| Beta | `yokoten-beta` | `next` | The latest pre-release tag, such as v0.2.0-beta.1 | People who want new features early and can report problems |
| Dev | `yokoten-dev` | none | The default branch, every merged pull request | Contributors, and our own tests |

`/plugin install yokoten@yokoten` gets Stable. Use `yokoten-beta@yokoten` for Beta, or
`yokoten-dev@yokoten` for Dev. Install one channel, not several. To switch, run
`/plugin uninstall yokoten@yokoten`, then install the other name. From a shell,
`claude plugin uninstall` and `claude plugin install` do the same. `doctor` names the
channel and version at the top of its output.

A pre-release can break before the stable release. Dev changes on every merge and has
no changelog.

## The Claude Code plugin

In a Claude Code session:

```text
/plugin marketplace add yokoten-labs/yokoten
/plugin install yokoten@yokoten
```

The first command adds this repo as a plugin marketplace. The second opens the plugin.
Choose "Install for you" (user scope). From your shell, `claude plugin marketplace add
yokoten-labs/yokoten` and then `claude plugin install yokoten@yokoten` do the same.

The plugin is this repo. It uses the same files that `install` copies, so the two ways
can't drift apart:

| The plugin adds | From |
|---|---|
| The skills: `/yokoten:dispatch`, `/yokoten:inspect`, `/yokoten:merge`, `/yokoten:rca`, `/yokoten:record`, `/yokoten:release`, `/yokoten:yokoten` | `kit/user/skills/` |
| The agents: `yokoten:worker`, `yokoten:reviewer`, `yokoten:verifier` | `kit/user/agents/` |
| `/yokoten:init`, `/yokoten:doctor` and `/yokoten:update`, which run yokoten from the plugin | `kit/plugin/skills/` |
| The SessionStart `doctor` hook and the command guard: the same two hooks that `install` adds | `.claude-plugin/plugin.json` |

The command guard runs in every folder, so it keeps to the rules about commands, with one
exception: an edit to a file inside a yokoten repo (one with `yokoten.json`) gets that
repo's file rules, wherever the session started. A worker in a worktree outside the
session's folder can't change a lead-only file either.

Claude Code runs the plugin's `.ts` files with `node`. Node can do this because the
plugin folder is not under `node_modules`. To get a newer yokoten, open `/plugin`, select
yokoten on the **Installed** tab, and choose **Update now**. A third-party marketplace
does not update on its own until you turn on auto-update for it. From your shell,
`claude plugin update yokoten@yokoten` does the same; for Beta or Dev, use
`yokoten-beta@yokoten` or `yokoten-dev@yokoten`. The next tool call runs the updated
guard. A session which still has the old hook command needs one restart. If Claude Code
removes the old plugin folder while a session still runs, the tool call is refused and
asks for a restart. When the plugin is older than the yokoten that runs `doctor`,
`doctor` says so in a note.

The plugin works in Claude Code only. It does not make the private-marks file: `doctor`
names the file, and you make it. When the plugin is on in your user settings, `doctor`
counts its hooks and skills as installed.

Run `install`, or turn on the plugin: with the plugin on, `install` keeps its own copies
out of your global folders. On a machine that had both before, the guard and the session
doctor run twice and every skill shows up twice: `doctor` says so in a note, and `install`
with the plugin on takes back the global copies it wrote. To move from `install` to the
plugin, run `yokoten uninstall`. It removes what `install` wrote. Your own files in
`~/.config/yokoten`, such as the private marks you added, stay.

With the plugin on, `install` writes no guard of its own, because the plugin's guard is
the one guard. Turn the plugin off or remove it, and the machine has no guard at all:
`doctor` then fails with "no guard for Claude Code", and `yokoten install` writes
install's own guard.

## Install with npx

For Codex CLI and Gemini CLI, or without the plugin:

```sh
cd your-project
npx yokoten             # once: installs what is missing, then the road
```

Run it again in any project and it does only what is still missing. One step at a time:

```sh
npx yokoten install      # this machine: skills, agents, hooks
npx yokoten init         # this project: adds files, never overwrites
npx yokoten doctor       # the road: where you are, what's next
```

`npx` downloads the package and runs it. The package has no dependencies. `install`
and `init` copy yokoten's runtime out of the package: to `~/.local/share/yokoten` and
to `.yokoten/`. The hooks run these copies, so they need no network and no `npx`.

The package runs built JavaScript, because Node does not run TypeScript under
`node_modules`. The repo has no build; `npm pack` makes the package's `dist/`
([record 0008](https://github.com/yokoten-labs/yokoten/blob/v0.1.2/docs/records/0008-npm-package.md)).

The owner publishes the package, as the owner creates a release tag. The tag starts the
release workflow. It runs the checks, then the publish job waits for the owner's
approval in the `release` environment. Only then does it publish to npm, with no npm
token stored anywhere ([record 0009](https://github.com/yokoten-labs/yokoten/blob/v0.1.2/docs/records/0009-release-environment.md)). The first
release is the one exception: the owner publishes it by hand, because npm can't trust a
workflow for a package it does not have yet. Until the
first release is on npm, [install from a clone](https://yokoten.app/zh-tw/docs/guide/install-and-commands.md#install-from-a-clone).

## Install from a clone

The same commands, from a copy of this repo. Use this way until the first release is
on npm:

```sh
git clone https://github.com/yokoten-labs/yokoten
node yokoten/bin/yokoten.ts install      # this machine: skills, agents, hooks
cd your-project
node ~/.local/share/yokoten/bin/yokoten.ts init     # this project: adds files, never overwrites
node ~/.local/share/yokoten/bin/yokoten.ts doctor   # the road: where you are, what's next
```

yokoten's messages say `yokoten init` and `yokoten doctor`. So `install` puts a small
`yokoten` launcher in `~/.local/bin` (`yokoten.cmd` on Windows). That folder is on the
PATH on most setups, so `yokoten` works in the next shell you open. The launcher runs
`bin/yokoten.mjs`, the same fast entry the hooks run. The next `install` rewrites a
launcher that an older install wrote.

When `~/.local/bin` is not on your PATH, `install` asks once:
`Add ~/.local/bin to your PATH in ~/.bashrc? [Y/n]` (`~/.zshrc` for zsh, `~/.bash_profile` for bash on macOS). On yes, it adds
this one line to that file. On no, it prints the line for you to add:

```sh
export PATH="$HOME/.local/bin:$PATH" # added by yokoten
```

yokoten edits your shell profile only after that yes. It never asks, and never edits,
when no one is at the terminal: in a pipe, a hook or CI. A `~/.local/bin/yokoten` that
yokoten did not write stays as it is, and `install` names it. `uninstall` removes the
launcher and the marked line. `doctor` notes when no `yokoten` on your PATH runs the
installed copy.

These commands, and the npx ones, are also in [docs/facts.json](https://github.com/yokoten-labs/yokoten/blob/v0.1.2/docs/facts.json), which the
website reads, so a change to them goes in both places.

## Use it as a GitHub Action

Any repo on GitHub can run yokoten's checks in CI with one workflow file. Nothing is
installed in the repo, and nothing leaves the job: no token, no outside service.

```yaml
# .github/workflows/yokoten.yml
name: yokoten
on:
  pull_request:
    # `edited`, `labeled` and `milestoned` run `check --pr` again after a fix.
    types: [opened, edited, synchronize, reopened, labeled, unlabeled, milestoned, demilestoned]
  push:
    branches: [main]
permissions:
  contents: read
jobs:
  yokoten:
    runs-on: ubuntu-latest
    timeout-minutes: 10
    steps:
      - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
        with:
          fetch-depth: 0
          persist-credentials: false
      - uses: yokoten-labs/yokoten@<full commit SHA>
```

The action sets up Node, then runs `check`. On a pull request it also runs `check
--pr`. The findings show on the pull request page, as in yokoten's own CI.

It handles three events:

- **`push`**: `check`.
- **`pull_request`**: `check`, then `check --pr`.
- **`issues`**: `check`, then `check --issue` on the issue's title. To run it, add
  `issues:` with `types: [opened, edited]` under `on:`.

On `pull_request_target` the checkout holds the base branch, not the pull request, so the
action skips `check --pr` and leaves a notice: the pull request check runs on
`pull_request`, not `pull_request_target`.

- **Pin it.** Use a full commit SHA. A branch name like `@main` changes under you. Once
  there is a release, a release tag works too.
- **The checkout** needs `fetch-depth: 0`. `check --pr` lists the files between the base
  and the head commit, so it needs both.
- **Permissions**: `contents: read` is enough. The action takes no token.
- **Inputs**: `pr-check` (default `true`), `issue-check` (default `true`) and
  `node-version` (default `^22.23.4`, the first Node 22 with the October 2026 security
  fixes). Set `pr-check: false` to skip the pull
  request standard, `issue-check: false` to skip the issue title standard.
- **Node**: the action sets up Node for the rest of the job too.

`check --pr` holds a pull request to the standard of core rule 8. A repo that does not
follow it yet can start with `pr-check: false`. A pull request Dependabot opens gets
the title check, and the other rules show as notes, because Dependabot writes its own
body. The kit's `check` workflow runs the project's copy in `.yokoten/` first: the same
yokoten, and so the same check, as the commit hook. It then runs the action, pinned to
the first release's commit, so a pull request that changes `.yokoten/` still meets a
check it cannot edit; Dependabot keeps that pin current. The kit's `pr` and `issue`
workflows run the project's copy.

## A private repo without CI

A private repo on a free plan pays for its GitHub Actions minutes. Such a project can
keep CI off until the repo goes public. Then the commit hook and `yokoten check` are
the gate.

`init` reads the repo's visibility through `gh`. On a private repo it says that the
workflows use Actions minutes. It still writes them: `init` never makes this choice
for you. To keep CI off, run:

```sh
yokoten init --no-ci
```

`--no-ci` does three things:

- It adds `"ci"` to `"notNow"` in `yokoten.json`, with the reason.
- It leaves out the three workflows and `.github/dependabot.yml`. Dependabot in the kit
  updates only the actions that CI runs.
- It removes each of those files that is still the kit's copy. A file you changed, or
  one reached through a symlink, is kept, and `init` names it.

With `"notNow": { "ci": "<reason>" }`, the road counts the commit hook and `yokoten
check` as the gate. The steps that need CI wait with it: `tests`, `ci` and `titles`.
The `security-files` step does not ask for Dependabot, and full `doctor` does not ask
for a required CI check. A later `init`, or `init --github --yes`, writes no workflow
again.

Without CI, some things are not checked. Full `doctor` says so once, in a `no-ci` note:

- nothing on the server checks pull requests, issue titles or the workflows;
- no code scanning, such as CodeQL, runs;
- a commit made with the hook off reaches `main` unchecked.

So check each pull request before you merge it:

```sh
yokoten check --pr 12
```

`check --pr <number>` reads that pull request through `gh`. It checks the same things
as in CI, with the commits in your clone. When a commit is missing, it says what to
fetch.

From stage 5, the note also reminds you to end the choice when the repo goes public.
When full `doctor` reads that the repo is public, it says so. To take CI up again,
remove `"ci"` from `"notNow"` and run `yokoten init`. It writes the workflows, and the
steps count CI again.

## Commands

`init` copies the runtime into `.yokoten/` in your project. Commit it: the hooks and CI
then run with no network and no global install.

`init` keeps a project's own setup working. An `AGENTS.md` without `## This project` gets
the core rules on top. Its own text moves under `## This project`, and its headings go one
level down; nothing else in it changes. `init` prints each heading it moved. When git has
the old file as it was, `init` prints the command that gives it back. Otherwise it keeps a
copy in `AGENTS.md.before-yokoten`. YAML front matter stays at the very top. An `AGENTS.md` that is a link stays as it is. With a `CLAUDE.md`, it adds an `@AGENTS.md`
line at the end, because Claude Code reads `AGENTS.md` only through that import. A
`.claude/CLAUDE.md` counts too, and gets `@../AGENTS.md` when there is no root one. With
husky or another `core.hooksPath`, it keeps that folder and adds the commit check to its
`pre-commit`. The project's own hook lines stay, and `init` says which file it changed.
git runs `pre-merge-commit`, not `pre-commit`, for a merge it commits itself, so `init` adds
the same check there too (`.githooks/pre-merge-commit`, or husky's `.husky/pre-merge-commit`).
A `.githooks/pre-commit` of the project's own gets the check too. A file under `.husky/`
keeps its mode, because husky 9 runs it with `sh`.

A clone can also have its own hooks in `.git/hooks`: from lefthook, the pre-commit
framework, or written by hand. Pointing git at `.githooks` would switch all of them off,
so `init` writes `.githooks/pre-commit` and `.githooks/pre-merge-commit` to run the check
first and then the old hook, and for each other executable hook, such as `commit-msg` or
`pre-push`, a `.githooks` file that only runs the old one. Each gets the same arguments
and stdin. The old hooks stay in `.git/hooks`, and `init` names each one it kept. A hook
that is not executable is skipped by git today, so `init` goes ahead and says how to keep
it. When it can't hand off (a commit hook comes from a global `core.hooksPath`, or the
project's own `.githooks` file has its check inside an `if`, or reads stdin like the old
hook), `init` leaves `core.hooksPath` unset and prints what to do. With a global
`core.hooksPath`, the files in `.git/hooks` never ran, so `init` leaves them out. `doctor`
does not count the commit check as done while such a hook would be dropped. For a
workflow of the project's own that `init` keeps, `doctor` gives the step to add to it.

`init` also writes four workflows into `.github/workflows/`: `yokoten.yml` (the
commit check, a lint job with actionlint and zizmor, and one `ci` job to make the required
check), `pr.yml`, `issue.yml` and `actions-health.yml`. Each action is pinned to a commit,
each job has a time limit, checkout keeps no token, and in `yokoten.yml` a new push cancels
the old run on the same pull request. A push to `main` cancels nothing: every commit is
tested on its own. `pr.yml` cancels nothing. Its runs can start in any order, so each one
reads the pull request's title, body, labels and milestone as they are now, with the job's
token, and a rerun reads them again. Add your tests as a job in `yokoten.yml` and to the `needs` list of `ci`. A
private repo can keep CI off: see [A private repo without CI](https://yokoten.app/zh-tw/docs/guide/install-and-commands.md#a-private-repo-without-ci).

`yokoten.yml` and `pr.yml` also run on `merge_group`, so a merge queue runs the same
required checks as a pull request. A merge group run has no pull request. `yokoten.yml`
runs every job in full, and the `pr` job skips, which counts as green for the queue.
Turn the queue on in the repo settings. `issue.yml` never runs there: an issue is not
a check on a merge.

`actions-health.yml` runs every 30 minutes. It reads the last 200 Actions runs and the
checks of the open pull requests, then updates one pinned issue, `CI health`. The issue
lists failed or cancelled runs on the default branch, failed or cancelled checks on open
pull requests, slow workflows, queue waits, and branches that both passed and failed. It only
writes that one issue. Delete the file to stop it, or run `yokoten init --no-ci`.
`yokoten ci-health` prints the same report and writes nothing; the workflow runs it with `--write`.

When a workflow is already there, `init` checks whether you changed it. The kit keeps a
list of every version it wrote. A file that matches one of them, Windows line ends or not,
is a copy you never changed. `init` then updates it to the newest version and says so.
`init` never commits, so `git restore <file>` brings the old one back. A file with your
changes is kept as it is. `init` and full `doctor` show the command that compares it with
the kit's newest copy; `doctor` says it once for each kit version. A symlink is kept too:
`init` never writes through one.

In `yokoten.yml`, a `changes` job reads which files a pull request changes. The pull
request is docs-only when each file is under `docs/` or `changes/`, or is a `README.md`
or `CHANGELOG.md`. A job of yours can then skip a docs-only pull request; the comment
above `ci` shows the two lines to add. `ci` counts that skip as green. The commit check
and the lint job always run. A push to main runs every job. So does a pull request whose
file list can't be read: a warning says so. In yokoten's own CI, a docs-only pull request
runs the tests once, on Ubuntu with Node 24, and skips `typecheck` and the Action job.

It puts three safe defaults in `.github/` too:

- `dependabot.yml` updates the actions that CI runs, and the packages `init` finds (npm,
  cargo, pip, Go and more, at the root and one folder down). It opens one pull request a
  month for each, and takes a release only when it is a week old. A file you already have
  is kept; `init` names the packages it does not update.
- `SECURITY.md` asks people to report a vulnerability privately, not in a public issue.
  For this, turn on private vulnerability reporting. `doctor` lists it with the other
  security switches.
- `CODEOWNERS` names the owner for every lead-only path in the defaults: `AGENTS.md`,
  `yokoten.json`, `.yokoten/`, the hooks, the agent config and `.github/`. The guard
  stops a worker on the machine; this file stops the same change on the server. The
  owner is the account in a GitHub `origin` remote. Without one, the file has a
  placeholder: make the repo, add the remote and push, then `doctor` asks you to put your
  GitHub handle in it. GitHub takes no
  organization as an owner, so when `gh` says the account is one, the file has the
  placeholder too, and `init` asks for a person or `@org/team`. Full `doctor` flags a
  file whose only owner is the organization. It bites only when
  branch protection has "Require review from Code Owners" on. A CODEOWNERS file the
  project already has, in `.github/`, at the root or in `docs/`, is kept as it is.

| Command | Group | What it does |
|---------|-------|--------------|
| `install [--yes] [--list]` | Start | One entry skill, `yokoten`, into `~/.claude/skills` and `~/.agents/skills`; agents into `~/.claude/agents`; a SessionStart `doctor` hook; a user-level command guard; and an empty private-marks file. At a terminal it shows one screen of what changes, asks once, and then writes; `--yes` skips the question. It prints the files and settings keys it writes before it writes them. The two hooks go to Claude Code, and to Codex and Gemini CLI when `~/.codex` or `~/.gemini` already exists. With the Claude Code plugin on, it writes no global skill, agent or hook, and takes back what an older install wrote there. It adds the marks it finds on this machine, and writes a git mailmap. It asks before it sets git's global `mailmap.file`. `--list` names every file it writes or keeps. See [Privacy](https://yokoten.app/zh-tw/docs/guide/privacy.md) |
| `init [dir] [--list]` | Start | Puts the practices into a project. `--list` shows every file it wrote or kept; by default one screen does. Adds files; keeps any that are already there and says so. A kit workflow you never changed gets the newest version. It also reads GitHub through `gh` and says which standard labels and milestone it would add. It never changes GitHub. Without `gh`, a sign-in or a GitHub remote, it says what it skipped |
| `init --github --yes [--remove-default-labels] [--board]` | Start | Adds the missing standard labels, and a milestone "First release" when no milestone is open. When a closed milestone already has that title, the new one is "Next release". A closed milestone is never reopened. A standard label that is there gets the standard colour and description. With 8 areas or fewer in `yokoten.json`, it adds one grey label per area. A second run changes nothing. GitHub's default labels outside the set (`question`, `wontfix` and others) are deleted only with `--remove-default-labels`. `--board` also makes a board linked to the repo; it needs the gh token's `project` scope |
| `init --no-ci` | Start | Keeps CI off: adds `"ci"` to `"notNow"` in `yokoten.json`, writes no workflow and no Dependabot config, and removes those files when they are still the kit's copy. See [A private repo without CI](https://yokoten.app/zh-tw/docs/guide/install-and-commands.md#a-private-repo-without-ci) |
| `trust [dir] [--undo]` | Start | Turns on yokoten's guard in the agents installed here, by writing their own trust for yokoten's hooks: Codex, Grok CLI, Copilot CLI and Muse Code. A person runs it at a terminal; a pipe writes nothing. It trusts a folder only when yokoten's own hook is there and the project's Codex sandbox is safe, because trusting a folder is what lets its sandbox apply. `init` asks once on a terminal; with no terminal it writes nothing and names this command. `--undo` removes only the entries yokoten wrote, and leaves one you changed since. See [Safe by default](https://github.com/yokoten-labs/yokoten/blob/v0.1.2/docs/records/0014-safe-by-default-with-a-way-back.md) |
| `doctor [--all] [--session] [--json]` | Start | Where the project is on the road, in one screen: what is done, what is missing, and the next step. `--all` shows every stage and step, and what this machine is missing. `--session` prints only the next step, for the start of a session. Full doctor also runs the guard once on a trivial command and reports `guard: 90 ms per command (60 ms CPU)`: the wall time and the CPU time (user plus system) of one call. A session skips it. With `--agent gemini`, it prints the step as the JSON that Gemini CLI reads |
| `adopt [--all] [--json]` | Start | One screen by default; `--all` shows the whole table, with a reason for every row. A draft of the adoption table for an old project: what it does today, and a proposed decision for each step. `--json` prints the rows. Writes nothing |
| `explain [<id>] [--json]` | Start | A rule or a step on the road: what it is, why, and how yokoten checks it |
| `worker mark <dir>` | Work | Marks the worktree at `<dir>` as a worker's. The guard then treats every agent there as a worker, even one whose runtime drops `YOKOTEN_ROLE` (Muse Code). The guard refuses a worker's plain ways to remove it, and a `.git` link it can't read counts as marked; the server and credentials kept from the worker are the real lock. `git worktree remove <dir>` removes the mark. Given a subfolder, it marks the worktree's top folder and says which. A plain clone, such as a desk, is marked too: the mark goes in its own `.git` (#672). The lead marks a clone from its own checkout. The main checkout (the clone the lead runs in, or one with worktrees) and a submodule are never marked. A worker can't mark or unmark. See [Works with](https://yokoten.app/zh-tw/docs/guide/works-with.md) |
| `worker env [runtime]` | Work | Lists the names that yokoten's agent environment would keep from this shell, and counts the names it drops. With a runtime, its own login name counts too. It never shows a value. `desk` starts an agent with it. See [What an agent's environment holds](https://yokoten.app/zh-tw/docs/guide/privacy.md#what-an-agents-environment-holds) |
| `msg send <to> [<text>]`, `msg inbox`, `msg who`, `msg log` | Work | Agents of every vendor message each other through files on this machine. Only the lead runs `log`. See [Messages between agents](https://yokoten.app/zh-tw/docs/guide/managing-agents-like-a-team.md#messages-between-agents) |
| `desk [<issue> \| "<task>"] [--agent <name>] [--pr] [--no-install] [--json]` | Work | With no words, lists your desks and writes nothing (`--json` prints that list as data). With an issue or a task, makes the desk `../<repo>-desks/<n>/` (its own clone, branch `desk/<n>`), writes a brief, and starts the AI (Claude Code first) inside the fence. When the AI exits, the check and the tests run in the fence, and one screen shows the evidence. It never pushes; `--pr` pushes the branch and opens the pull request, as the bot when it is set up. A desk that stopped mid-task shows on the next start, and `desk <n>` goes on from its last commit. On native Windows, run it in WSL |
| `desk <agent> <folder> [--name <name>] [--read <paths>] [--host <hosts>] [--marks] -- <command>` | Work | Starts a worker's agent in a fenced desk: its own clone, its own state folder, and no network but its model's API. Only the lead runs it. See [The desk](https://yokoten.app/zh-tw/docs/guide/managing-agents-like-a-team.md#the-desk) |
| `kanban [--text] [--json] [--no-open]` | Work | Its old name `board` still runs it, after one line that names `kanban`. Serves a read-only page on this machine only: the sessions, the pull requests that wait on you, and Actions. Nothing on it starts, stops or changes work. It opens the page in your browser, signed in, and runs until you stop it (`--no-open` prints the address only). The sign-in key goes only to the browser. `--text` prints the same snapshot as plain text, and `--json` prints it as data. See [The kanban](https://yokoten.app/zh-tw/docs/guide/managing-agents-like-a-team.md#the-kanban) |
| `check [--staged] [--all] [--json]` | Guard | One screen: the problems in full, the notes folded into a count; `--all` shows each note. Layout, privacy and lead-only files, for the tree or the next commit. A commit may not add build output or binaries. Each change file is read the way `changelog` reads it, so a wrong one fails at commit. With nothing staged, `--staged` notes it and is ok. For the tree, a translation behind its English source is a note. See [Translations](https://yokoten.app/zh-tw/docs/guide/install-and-commands.md#translations) |
| `check --pr` | Guard | In CI: the pull request's title, `tier:` line, sections, linked issue, change file, milestone and labels. See [The changelog](https://yokoten.app/zh-tw/docs/guide/install-and-commands.md#the-changelog). The last line names each AI and model: `🤖 Made with <agent> (<model>)`, one or more comma-separated pairs. Reviewers may be added: `🤖 Made with worker <X> (<model>), reviewed by <Y> (<model>) and <Z> (<model>), lead Claude Code (<model>)` (one or two reviewers, never more). A file that CODEOWNERS owns needs `tier: A`. The line is checked against the desk status files on the branch: a desk the line does not name, or reviewers who are all the writer's own model, is a finding. One reviewer of another model is enough (record 0045): Claude Sonnet reviewing Claude Opus passes, and so does another vendor. A `Made with` model that disagrees with the worker's status file is a note on the machine with its desk. CI cannot see that file, so CI never notes it. Long sentences on the lines a pull request adds to Markdown, hard-to-read names on the lines it adds to code, and a diff over 400 changed lines are notes |
| `check --pr <number>` | Guard | The same check for one pull request, read through `gh`, for a repo without CI. See [A private repo without CI](https://yokoten.app/zh-tw/docs/guide/install-and-commands.md#a-private-repo-without-ci) |
| `check --issue` | Guard | In CI: the issue's title and labels. See [Issue titles](https://yokoten.app/zh-tw/docs/guide/install-and-commands.md#issue-titles) |
| `check --anchor [--repo <owner/name>]` | Guard | Each past UTC day of the event log against its hash in Sigstore Rekor, and names the day when the log no longer matches. Only the day's 64 hex characters go to Rekor; no line, path or name leaves the machine. `--repo` names the repo whose anchor workflow signed the entries |
| `provenance <ref> [--json]` | Guard | For a commit, or each commit of a range: the writer, reviewers, approver, tier and issue, from the squashed message and the host's latest Approve. `--json` prints the records. See [Provenance](https://github.com/yokoten-labs/yokoten/blob/v0.1.2/docs/standard.md#provenance) |
| `borrowed [--dry-run] [--json]` | Guard | Compares each file in `src/borrowed/` with upstream's default branch, through `gh`, and opens one issue per source that changed. A security fix is called out in the title. `--dry-run` shows the report and opens nothing; `--json` prints the report as data. See [Borrowed code](https://yokoten.app/zh-tw/docs/guide/install-and-commands.md#borrowed-code) |
| `redact` | Guard | Reads text on stdin and writes it with your details hidden: `<owner>`, `<email>`, `<host>`, `<mark>`. The tool-call guard sends risky commands through it. See [Privacy](https://yokoten.app/zh-tw/docs/guide/privacy.md) |
| `marks allow <mark> --here` | Guard | Lets one of your private marks through in this repo only, with no yokoten.json. The allow is kept on this machine, never in the repo. `--remove` takes it back. See [Privacy](https://yokoten.app/zh-tw/docs/guide/privacy.md) |
| `anchor [--json]` | Guard | Lists the anchors recorded in this checkout, newest first. A worker records one when its checks pass and its tree is clean. An anchor is a commit: go back with `git switch -c try-2 <commit>`, and nothing is reset or deleted. See [The stop check](https://yokoten.app/zh-tw/docs/guide/install-and-commands.md#the-stop-check) |
| `mcp [dir] [--command "<command>"] [--json]` | Guard | Starts each stdio server in `.mcp.json`, `.vscode/mcp.json` or `mcp.json` and checks its tools for every client: schema, names, annotations, count, result shape and stray stdout. `--command` checks a server with no config entry; `--json` prints the findings. A Windows warning does not fail it. See [Shipping an MCP server](https://yokoten.app/zh-tw/docs/guide/shipping-an-mcp-server-that-works-in-every-agent.md) |
| `check --history` | Guard | The privacy check over every commit on every branch. Run it before a private repo goes public. A shallow clone can't see old commits, so it fails without scanning; in CI, check out the whole history (`fetch-depth: 0`). A hit in a file names the commit that added it, and the path in that commit: `file @ <commit>`. It reads commits past `git replace`, and fails the same way when a grafts file can hide commits. |
| `hook [--agent <name>] [--commands-only] [--denial <mode>] [--json] [--protocol]` | Guard | The tool-call guard. Reads one tool call on stdin: a Claude Code PreToolUse call by default, a Codex PreToolUse call with `--agent codex`, a Gemini CLI BeforeTool call with `--agent gemini`, an Antigravity CLI PreToolUse call with `--agent antigravity`, a Muse Code PreToolUse call with `--agent muse`, a DeepSeek Harness PreToolUse call with `--agent deepseek-harness`, a Grok CLI PreToolUse call with `--agent grok`, an OpenCode tool call with `--agent opencode`. With `--denial exit2`, a refusal keeps exit 2 and the reason on stderr, for a host that reads the exit code alone. With `--protocol`, any tool sends the guard protocol and reads its answer: see [the guard protocol](https://github.com/yokoten-labs/yokoten/blob/v0.1.2/docs/records/0039-the-guard-protocol.md) |
| `hook --stop [--agent <name>]` | Guard | The stop check. Reads the end-of-turn event on stdin and checks the changed files: a Claude Code Stop or SubagentStop event by default, a Codex Stop event with `--agent codex`, a Muse Code Stop event with `--agent muse`, a Gemini CLI AfterAgent event with `--agent gemini`, a Grok CLI Stop event with `--agent grok`. A worker also runs the task's done-when command. See [The stop check](https://yokoten.app/zh-tw/docs/guide/install-and-commands.md#the-stop-check) |
| `test [--heavy] [--print] [-- <runner words>]` | Guard | Runs the tests light, for a machine other work shares (#461): `node --test` under `nice -n 10` (not on Windows), with `--test-concurrency` at most half the cores and one per GB of free memory, and 1 at least. Words after `--` go to the runner: test files, or its options. It is on by default. `YOKOTEN_LIGHT_TESTS=0` turns it off for every call, `--heavy` for one. `--print` shows the command and why, and runs nothing |
| `stops [off \| on]` | Guard | Shows the stop log at `~/.config/yokoten/stops.jsonl`, and whether yokoten writes it. `off` stops the writing and deletes the log; `on` starts it again. The log stays on this computer. See [The stop check](https://yokoten.app/zh-tw/docs/guide/install-and-commands.md#the-stop-check) |
| `ci-health [--repo <owner/name>] [--write] [--dry-run]` | Merge and CI | Reads the last 200 Actions runs and the checks of the open pull requests, and prints the report. It writes nothing. `--write` updates one pinned issue, `CI health`; `--dry-run` prints the report alone |
| `pr sync` | Merge and CI | Brings each open pull request up to date with its base branch after a merge. It merges the base in, resolves only two mechanical conflicts (both sides added a whole block, or both changed the same import list), runs typecheck and the tests of the touched files after every merge, clean or resolved, then pushes as the bot. Anything else stops with the files, the hunks and a dispatch brief. Each report line names the base it merged. The open list stops at 100 pull requests, and the report says when it did. It never force-pushes, rebases or resolves on GitHub. Run it after every merge. See [Managing agents like a team](https://yokoten.app/zh-tw/docs/guide/managing-agents-like-a-team.md) |
| `bot -- <command>` | Merge and CI | Runs one command (`gh`, `git push`) as the bot, with a one-hour token it never prints. Before `gh issue` or `gh pr` `create` or `edit` with a title, it checks the title by the CI rules. A bad title is refused, and nothing is sent. `gh api` on an issue or pull request path is checked the same way. With `--body` or `--body-file`, `gh pr create` and `gh pr edit` also check the body by the body rules of `check --pr`, against the pull request's branch and the base branch's CODEOWNERS. A bad body is refused too, and nothing is sent. A body whose last line is not `🤖 Made with <agent> (<model>)`, one or more comma-separated pairs, is refused too: each pull request names each AI and model that made it. The line may add one `reviewed by <agent> (<model>)` part, or two reviewers joined by `and`; never more than two. A comment (`gh pr comment`, `gh issue comment`), a review with a body (`gh pr review --body`), and a new issue (`gh issue create --body`) carry the same line: a body whose last line is not the `Made with` line is refused too. A note with `--attach` is refused: the upload lands past the `Made with` line. An edit (`gh pr edit --body`, `gh issue edit --body`) stays as it is. `gh pr create` without a type or tier label gets them added: the type from the title (`bug` for `fix`), the tier from the body's `tier:` line. `gh pr edit` does the same for a new title or body, and removes the old tier label when the body sets a new tier. A label you pass is kept. A tier label that does not match the body is refused, with the message of `check --pr`. A label the repo does not have is named in a note and left out; the bot never creates labels. `gh pr create` without `--milestone` gets the milestone of the first linked issue (`Closes #N`, `Part of #N`) that has one. When no linked issue has one, it is refused, and nothing is sent; add `--milestone`. When gh can't read the issue, the pull request is sent without a milestone, with a note. It runs `gh` and `git` only, so `env gh …` or `sh -c …` is refused. It does not read a gh alias, so a title set through one is not checked. Setup: [server.md](https://github.com/yokoten-labs/yokoten/blob/v0.1.2/docs/server.md#3-the-bot-a-github-app) |
| `train [<pr>...] [--wait <seconds>] [--next]` | Merge and CI | Merges an approved, green queue in the order given, as the bot, and stops at the first pull request that needs a person. With no numbers it only prints the plan from the conflict map, in that map's order, and merges nothing; name the numbers to merge them. The order is: a change that moves a path first and alone, then a change that collides with nothing, then the one more others collide with, oldest first. It records every base before the first merge, so a pull request stacked on a squash-merged branch merges from the base it truly shares with that branch. A conflict made only of import lines takes the union; a real conflict, a failing test, a failing `check` or a missing approval stops the train. Each merged tree is tested before the push, and CI must pass on the pushed merge; `--wait` sets how long to wait (default 1800 seconds). `--next` says which pull request is next and why, and merges nothing. Only the lead runs it |
| `conflicts [--json] [--rebuild <id>]` | Merge and CI | Reads every open pull request, or every local branch when there is no host. It says in plain words which changes collide with main and with each other. It names the pairs that touch one file, and which of the two to merge first. It checks nothing out and merges nothing. It keeps the answer in the git folder, so the guard reads it without running git. `--json` prints the whole map, and `--rebuild` prints a prompt that rebuilds one change on the current main |
| `update [dir] [--write] [--all]` | Maintain | One row per changed rule by default; `--all` shows every changed line. Shows what this yokoten changes in the core rules of the project's `AGENTS.md`. `--write` replaces them, and keeps the "This project" part as it is. See [Updating the rules](https://yokoten.app/zh-tw/docs/guide/install-and-commands.md#updating-the-rules) |
| `upgrade` | Maintain | Brings yokoten itself to the newest version. With the Claude Code plugin on, it updates the marketplace, then the plugin, over HTTPS, so no SSH key is needed. It adds and removes nothing. Without the plugin, it names `npx yokoten@latest` |
| `changelog [--write]`, `changelog --notes <version>` | Maintain | Shows the entries the files in `changes/` add under `[Unreleased]`. `--write` puts them into `CHANGELOG.md` and deletes the files; run it before a release. `--notes` prints a version's release notes from its `CHANGELOG.md` section, so they are never written twice. See [The changelog](https://yokoten.app/zh-tw/docs/guide/install-and-commands.md#the-changelog) |
| `learn <issue> [--write] [--all]` | Maintain | One screen by default; `--all` shows every field the issue left open and the files the draft writes. Drafts a check from an incident issue, for a person to review. `--write` adds the files in `lessons/`. See [Learning from an incident](https://yokoten.app/zh-tw/docs/guide/install-and-commands.md#learning-from-an-incident) |
| `learn --ship <id>` | Maintain | In yokoten: moves a reviewed lesson into a group in `kit/lessons/`, where `doctor` from that yokoten runs it in every project. Refused while a `TODO(reviewer)` is left or its test fails |
| `learn --export <id> [--out <file>]` | Maintain | Writes a reviewed lesson as one small JSON file, with its incident and its positive control, and no private detail. `--out` writes the file instead of printing it |
| `learn --import <file> [--write]` | Maintain | Shows a lesson file from another project. `--write` adds it to `lessons/` for review. Nothing runs before `learn --ship` |
| `learn --upstream "<what happened>" [--area <name>] [--fix <idea>]` | Maintain | Keeps a note about a gap in yokoten itself, with the project and you hidden. `--list` shows the notes; `--done <id>` removes one. See [Lessons for yokoten itself](https://yokoten.app/zh-tw/docs/guide/install-and-commands.md#lessons-for-yokoten-itself) |
| `uninstall [--list]` | Maintain | Takes back what `install` wrote: the entry skill and agents marked as managed by yokoten, its hooks in the settings of Claude Code, Codex CLI, Gemini CLI, Antigravity CLI, Copilot CLI, Grok CLI and Muse Code, its OpenCode plugin, and `~/.local/share/yokoten`. It removes the private-marks and mailmap files when they hold only install's header, and the folders and git config file it made when they are empty. A folder that was there before `install` stays. Your own files, edits and settings stay; the stop log is removed. A second run changes nothing. `--list` names every file it removes or keeps |
| `tidy [--local \| --empty [--yes]] [--json]` | Maintain | Alone, it lists what is old, grouped by what you can do: merged or gone branches, worktrees and desks with no work left, and the logs of desks that are gone. Each row says why, and its size. It changes nothing (`--json` prints the list as data). `--local` saves each one first (a bundle of its branches, a patch of its unsaved changes, and `index.json`), then moves it into a dated folder in `~/.local/state/yokoten/trash`. If a save fails, nothing moves. A desk or worktree in use is never touched. Branches on GitHub are only listed. `--empty` deletes trash folders older than 7 days, after one question; with no terminal it needs `--yes` |

Each command prints its own help with `--help` or `-h`: what it does, its options and
one example. `yokoten help <command>` prints the same. `yokoten help` lists the four commands to
start with (`desk`, `kanban`, `check` and `tidy`; plain `yokoten` is the fifth), and `help --all` lists every command in groups by job. `yokoten board`, the old name of
`kanban`, still runs it after one line that says so. `yokoten version` (or `--version`, `-v`) prints the version. Help changes nothing
and exits 0. For a typo, yokoten names the closest command
or option: `yokoten docter` says `unknown command docter`, then
``Next: `yokoten doctor`, if you meant it; `yokoten help` lists every command``. After `bot --`, every word
belongs to the command it runs, so `yokoten bot -- gh --help` runs `gh --help`.

`check`, `doctor`, `hook`, `explain` and `adopt` take `--json`. Then stdout holds JSON and
nothing else, for an agent or a script. An error is JSON too: a usage error or a crash
prints `{ command, ok: false, error }` with exit 1, and the message still goes to stderr. Each finding has the same fields: `id`, `what`,
`why`, `fix`, `docs` (the link) when the guide explains the rule, and `file` and `line`
when it is in a file. `yokoten explain <id>` takes the `id`. No field holds a private
value.

- `check --json`: `{ check, ok, findings, notes }`. `check` is `tree`, `staged`,
  `history`, `pr` or `issue`. A note never fails the check. When the CI run has no
  pull request or issue, `check --pr` and `check --issue` are ok and add `skipped`, the reason.
- `doctor --json`: `{ version, ok, selfTest, repo, stage, stages, next, findings, steps,
  notes, server, lessons }`. `selfTest` is `{ ok, failed, ran, skipped, reason? }`: `ok` means no check failed; `ran` is false, with `skipped` and `reason`, when a control could not make a temp folder and did not run. The findings of a lesson are in `notes`; a new lesson has `isNew: true`. With `--session`, only the next step, the private-marks finding, and `core-rules` when the rules are behind. Then `notes` holds the work only on this machine and the findings of a lesson the session just heard about, if there are any.
- `hook --json`: `{ blocked: true, finding }` when it blocks, `{ blocked: false, toolCall: true }`
  when it allows, and `{ blocked: false, toolCall: false }` when stdin is not a tool call.
  The runtimes do not use it: they read the exit code and the reason on stderr, as their
  hook protocols say.
- `hook --stop --json`: `{ blocked, stop, findings }`. For a lead, `stop_hook_active`
  adds `skipped`, the reason. A worker is still checked when that field is set.
  When the check itself fails, `skipped` names that too. When a worker's done-when
  check fails, also `doneWhen`. After the try limit, `skipped` is
  `stopped with failing checks`. That stop writes `checks` as `failing` in
  `.yokoten-agent.json` in a linked worktree. A pass there sets `checks` to `passing`.
  When stdin is not a Stop event: `{ blocked: false, stop: false }`.
- `explain --json`: `{ found, id, kind, what, why, check, docs, stage, standard }`. For an
  unknown id: `{ id, found: false, ids }`, exit 1. With no id: `{ ids }`, exit 0.

Output for people follows one rule. Results go to stdout. Refusals, errors and the
problems `check` finds go to stderr; a row of the `doctor` road, such as MISSING, is part
of its result and stays on stdout. An unknown option is refused, never ignored.

`init`, `update`, `doctor`, `check`, `adopt` and `bot` need a git repo. Outside one, they
say so, write nothing, and exit 1. `init <dir>` also refuses a folder that does not exist.
`doctor --session` stays quiet outside a repo, so a session there gets no noise.

In CI, you don't need to open the log to see what failed. When `check` runs in GitHub
Actions, each finding is an annotation on the changed line of the pull request. The job
summary lists each finding with its place, rule, what, why and the fix. The rule links to
its section of this guide. A note shows as a warning and never fails the check. Like the rest of the output, these name a place,
never what matched.

## Exit codes

| Code | Means |
|------|-------|
| 0 | All is well. Steps left on the road are to do, not problems, so `doctor` gives 0 with them. `doctor --session` always gives 0, so a session always starts |
| 1 | Problems found: by `check`, or by `doctor` (a failed self-test, or a core-rules, tree or machine line marked MISSING), an unknown id (`explain`), a usage error, or an error in yokoten itself |
| 2 | The hook blocked the tool call, or the stop check sent the agent back. The reason is on stderr |

These codes do not change. yokoten itself gives 1 when it breaks. But only exit 2 blocks
a call in all three agents, so a guard that crashed would let the call through. The
tool-call hook that `init`, `install` and the plugin write turns any other exit into 2:
when the guard crashes, runs out of memory or is killed, the call is refused with one
line, `` yokoten's guard failed (exit N), so the command is refused; run `yokoten doctor` ``.
That line is all of stderr: node's own error and the shell's "Killed" line are dropped.
Two cases stay at 1: when the runtime is gone, or when `node` is not on the agent's
PATH, the hook says so in one line and the call goes on. A deleted folder or a missing
node does not block every tool call, so you can still run `yokoten doctor`. The stop
check and the session doctor pass a failure on as it is.

The runtimes also let a call through when a hook runs past their timeout, so when reading
a call takes the guard longer than 5 seconds (`YOKOTEN_GUARD_DEADLINE_MS` changes it, up
to 30 seconds), or fails, the guard refuses the call with 2 (`unreadable-command`).

## Machine notes

Some problems on the machine stop every agent session, and the agent can't see them.
Full `doctor` lists them under `machine:` as notes. A note never changes the exit code.
[Record 0012](https://github.com/yokoten-labs/yokoten/blob/v0.1.2/docs/records/0012-machine-checks.md) says which problems doctor looks for, and why.

- **disk-space**: the disk that holds the repo is low on space. A full disk stops every
  session. Doctor warns below 20 GB, when that is also under 10% of the disk. Set
  `YOKOTEN_MIN_FREE_GB` to use another limit, or `0` to turn the warning off. A session
  hears this note too.
- **host-disk**, in WSL 2 only: the Windows drive that holds the Linux disk file. Inside
  WSL, `df` shows the size of the disk file, not the free space on that drive. Full
  `doctor` reads the drive through `reg.exe`, and waits at most 3 seconds. It keeps the
  reading in `~/.config/yokoten/machine.json`. A session runs no program: it repeats the
  last reading when that was low and is at most 7 days old. Run `yokoten doctor` to read
  the drive again. Without `WSL_DISTRO_NAME`, as under `sudo`, doctor can't find the drive
  and says nothing about it. The VM's disk file only grows. Deleting files inside
  the VM frees nothing on the Windows drive. So the note says: back up your work to
  another drive first. Then free space on the Windows drive, or shrink or move the disk
  file with [Microsoft's guide](https://learn.microsoft.com/windows/wsl/disk-space).
  yokoten does not suggest WSL's sparse mode: WSL marks it unsafe. When the kept reading is
  low, the tool-call guard asks before a build, a test suite, a package install or a
  container build starts. Claude Code shows the question; the other agents get the same
  words as a note. The guard never refuses this, and reads only the kept reading.

- **agent-temp-dir**: another user owns Claude Code's temp folder, `claude-<uid>`. The
  folder is in `CLAUDE_CODE_TMPDIR`, else in `TMPDIR`, by default `/tmp`.
  This happens when a root process makes the folder first, for example after a restart.
  Claude Code then refuses to start. The note prints `sudo rm -rf '<folder>'`, quoted. Run it
  yourself: doctor never deletes anything.
- **leftover-temp-folders**: `yokoten-*` folders older than a day in the temp folder.
  Test runs leave them, and thousands of them fill the disk. Full `doctor` counts them.
  It does not add up their size, so it stays fast. The note prints a `find ... -exec rm -rf`
  command that removes only the old ones. Run it yourself: doctor never deletes anything.
- **node-security**: the Node that runs yokoten lacks the security fixes of 2026-10-21.
  A release older than the fix in its line (22.23.4, 24.22.1, 26.12.1) has known holes.
  A line that gets no more fixes, such as 23, 25 or anything below 22, is not supported.
  The note names the version to install. Full `doctor` only, and never a failure.

## The stop check

The tool-call hook reads commands. It does not see a file that `sed` or a script
changed. The commit check sees it, but only at the commit. The stop check runs earlier:
when the agent tries to end its turn. That is the Stop event in Claude Code, Codex,
Muse Code and Grok CLI, SubagentStop in Claude Code, and AfterAgent in Gemini CLI.

```mermaid
flowchart LR
  S["agent ends its turn"] --> U{"subagent, and its type is not a worker?"}
  U -- yes --> N["turn ends, no check"]
  U -- no --> W{"a worker?"}
  W -- no --> A{"already sent back once?"}
  A -- yes --> E["turn ends"]
  A -- no --> C{"findings in changed files?"}
  C -- no --> E
  C -- yes --> B["agent gets the findings and fixes them"]
  W -- yes --> D{"done-when check passes?"}
  D -- yes --> E
  D -- no --> K{"try count saved?"}
  K -- no --> H{"stop_hook_active, or refused in the last 5 minutes?"}
  H -- yes --> X["stop allowed; checks still failing"]
  H -- no --> F["one refusal; the next stop is let through"]
  K -- yes --> T{"this try is 1, 2 or 3?"}
  T -- yes --> F2["worker gets the output and fixes it"]
  T -- no --> X
```

- It checks the changed files only: staged, not staged, and new files that git does not
  ignore. It uses the privacy, secret and file rules of `check --staged`.
- A subagent is checked only when its own type is a worker.
  The parent session's role does not count. A reviewer started by a worker is not checked.
  Its stop exits 0. A type is a worker only when it is `worker`, or it ends with `:worker`.
  `coworker` and `code-worker` are not workers. Mark such an agent as `worker`,
  or use a name that ends with `:worker`. A reviewer, Explore agent or verifier is not checked.
- For a lead, it sends the agent back once. The agent then sets `stop_hook_active`, and
  the next stop always ends the turn. There is no loop.
- For a worker, it also runs the task's done-when command. The command comes from
  `YOKOTEN_DONE_WHEN`, or from `yokoten-done-when` in the worktree's git folder.
  The environment variable wins when both are set. When the brief has none, the
  changed-file check is the project's check.
- The command uses the platform shell. That is `sh` on Linux and macOS, and `cmd.exe`
  on Windows. It may run for two minutes. When it ends, or the time runs out, yokoten
  stops the command and the programs it started.
- On Windows, `cmd.exe` runs in a job when Windows PowerShell is there. Closing the
  job stops those programs after `cmd.exe` has exited. The command reaches the host
  with `-EncodedCommand`, so the check's own text and quotes stay as they are. A
  security policy on the machine can still block `Add-Type`, which compiles the short
  job script. If PowerShell is missing, blocked, stops before it starts the check, or
  cannot make the job, the check runs `cmd.exe` alone and says why on stderr. A program
  left behind can then keep running. A host that stops after it started the check is
  not detected: its exit code is read as the check's own. The two-minute budget
  includes starting that host.
- A program that has already started its own session is not stopped. `setsid` starts
  one. A new process group in the same session is stopped on Linux. On macOS, `ps` gives no
  session id, so only the check's own process group is stopped.
- A failing check sends the worker back, with the output in short and what to fix.
  The worker gets three tries. On the third, the hook says the next stop is let through.
  The worker's final message to the lead must start with `NOT DONE: checks failing`.
  It must quote the failing output. The task is not done.
- The allowed stop writes `.yokoten-agent.json` at the root of a linked worktree.
  It does not write that file in the main checkout. It sets `checks` to `failing`
  and `updated` to the time in UTC. A pass sets `checks` to `passing`.
  Other fields stay. The lead reads that file. Its `/api/state` shows it as the commit check.
  The allowed stop's stderr reaches nobody.
  A status file yokoten cannot read is left unchanged, and the stop check says why.
- When the try count cannot be saved, one refusal says the next stop is let through.
  The same worker is let through when it was refused in the last 5 minutes.
  This holds even when the runtime never sets `stop_hook_active`. A worker is never trapped.
  The count is cleared when the check passes, and again when the worker is let through.
  A resumed worker is checked from try 1. Count files older than a day are removed
  when a new count is written.
- A subagent worker shares the lead's folder and environment. It does not get its own
  `YOKOTEN_DONE_WHEN`. The check runs in the event's folder. It runs in the worker's
  worktree only when the stop event names that worktree.
- DeepSeek Harness, Antigravity CLI and OpenCode have no stop hook. The lead runs the check.
- Claude Code, Codex, Muse Code and Grok CLI each wait 600 seconds for this hook.
  Gemini CLI reads `timeout` in milliseconds, so its hook waits 600000 milliseconds.
  Each of those is 10 minutes. That covers the two-minute command.
  Muse Code settings hooks, including `.muse/hooks.json`, also read `timeout` in seconds.
- It reads no history and uses no network.
- On an error in yokoten, the turn ends and stderr gets one line.
- A new file you want to keep out of the check goes in `.gitignore`, or in
  `privacy.skip` in `yokoten.json`.

Each stop is also written to a log on this computer. yokoten writes one line to
`~/.config/yokoten/stops.jsonl` when the guard stops a tool call, or the stop check sends
an agent back. The line holds the time in UTC, the agent's runtime, its role, the rule, and
the answer: `refused`, `allowed once` (the worker was let through after its tries), or
`no answer` (the guard could not read the call). A command is written only after the
privacy filter has masked it, and only its first line; an edit writes no path and no file
text. The file stays on this computer, and yokoten sends it nowhere. Old lines go first
when it passes 5 MB. `yokoten stops off` stops the writing and deletes the log;
`yokoten stops on` starts it again. `yokoten uninstall` removes the log too.

`init` adds the hook to `.claude/settings.json` (Stop and SubagentStop),
`.codex/hooks.json` (Stop), `.muse/hooks.json` (Stop), `.gemini/settings.json`
(AfterAgent) and `.grok/hooks/yokoten.json` (Stop). Claude Code, Codex, Muse Code
and Grok CLI wait 600 seconds. Gemini CLI waits 600000 milliseconds.
`install` does not add it at user level: it
reads files, so each project turns it on, as with the file rules of the tool-call hook.

The commit check refuses a file in a `dist/`, `build/` or `out/` folder, and a binary file.
A build makes such files again, and a reviewer can't read a binary. Images and fonts
(`png`, `jpg`, `gif`, `webp`, `svg`, `ico`, `woff`, `woff2`) pass. To keep a file
anyway, list it in `yokoten.json` as a glob: `"allowBuild": ["docs/manual.pdf"]`.

## Blocked commands

A project's stack rule, such as "no Python" or "no yarn", is easy for an agent to forget
in the middle of a task. Written only in AGENTS.md, it is a wish. List the programs in
`yokoten.json`, each with its reason, and the tool-call guard holds the rule:

```json
"commands": {
  "block": {
    "python": "this project is TypeScript only",
    "python3": "this project is TypeScript only"
  }
}
```

The guard then stops a command that runs `python3`, for the lead and for every worker.
The message names the program, gives your reason, and tells the agent to use the
project's own stack.

- The guard looks at the program of each command, by its name: `/usr/bin/python3` and
  `python3.exe` count as `python3`.
- Every part of the command counts: `a && b`, `a | b`, `a; b`, `$(...)`, and the
  string a shell runs: `bash -c '...'`, `ssh host '...'`, `flock file -c '...'`.
- Assignments, redirects, launchers and shell keywords do not hide the program:
  `X=1 python3`; `2>/dev/null python3`;
  launchers such as `env`, `sudo`, `nice`, `timeout`, `xargs`, `setsid` and `stdbuf`;
  a command after `if`, `for`, `while` or `!`; a program `find -exec` runs; and the
  string in `env -S`.
- A word that only holds the name passes: `echo python`, `cat python.md`, `pythonic`,
  and a `case` pattern such as `python3)`.
  `command -v python3` passes too: it only says where the program is.
- A name has no folder and no space. A wrong shape in `"commands"` is a config error,
  as for any key in [yokoten.json](https://yokoten.app/zh-tw/docs/guide/install-and-commands.md#yokotenjson). The guard then blocks no program and
  says so, and the core checks still run.

The guard reads command text. It does not see a program that a script, a `make` target,
an npm script or a shell function runs. Name each version you want to stop:
`python3.12` is not `python3`.

The safeguard's own refusals are fixed, not a setting: a wipe of home or the system,
a disk command, and root. Two of them can surprise you.

- A wipe of a target the guard cannot read is refused, such as `$OUT_DIR` or `$(pwd)`.
  Name the folder with a plain path instead.
- A disk tool is refused even when it only reads: `fdisk -l`, `parted -l`,
  `diskutil list` and `gpt -r show disk0`. Ask the owner to run it.

The folder the command runs in counts as the project, so `rm -rf dist` passes there.

## yokoten.json

`yokoten.json` at the repo root holds the project's settings. Every key is optional. A
missing key, a key set to `null`, or a missing file gives the default.

| Key | Must be | Default |
|-----|---------|---------|
| `layout` | `{ "maxEntries": 7, "exempt": ["docs/records"] }`: a whole number above 0, and a list of folders or globs | 7, no folder exempt |
| `tests` | a list of globs | `*.test.*`, `*.spec.*`, `*_test.go`, `test/**`, `tests/**` |
| `leadOnly` | a list of globs | the rules, agent config, hooks and CI |
| `privacy` | `{ "allow": [], "skip": [], "utc": true, "output": "run", "skipOwnName": true }`: two lists of text, true or false, `"run"` or `"stop"`, and true or false | lockfiles skipped, UTC on, output run, own name skipped |
| `notNow` | reasons by step name: `{ "ci": "no remote yet" }` | none |
| `allowBuild` | a list of globs | none |
| `areas` | a list of words | any lowercase word |
| `translations` | `{ "fail": true }` | `false` |
| `commands` | `{ "block": { "<program>": "<reason>" } }` | no program blocked |
| `worker` | `{ "timeLimitMinutes": 180, "settings": ["SCREENS_OUT"] }`: a whole number above 0, and a list of setting names | 180 minutes, no extra setting |

yokoten checks each key against this table, in one place:

- A key with the wrong shape, such as `"leadOnly": "x"`, is a config error. `check` and
  `doctor` exit 1. The message names each wrong key and says what it must look like, so
  one run shows them all.
- The tool-call hook then uses that key's default. It says so in one line on stderr, and
  the core checks still run. A wrong field in an object, such as `layout.maxEntries`,
  gives only that field's default. A wrong `"commands"` drops the whole block list.
- When `yokoten.json` is not valid JSON, or is not one JSON object, `check` and `doctor`
  report a config error. The hook then uses all the defaults, says so in one line, and
  still runs the core checks.
- A key that yokoten does not know is a note from `check`, never an error. A config
  written for a newer yokoten then still works with an older one. A field it does not
  know inside an object, such as `"commands": { "blok": ... }`, is a note too.

## Issue titles

An issue title names the area, then the work. A list of issues then reads like a list of
commits to come.

- A task: `area: verb phrase`, for example `doctor: add the review step`.
- An incident: `area: symptom`, for example `check: misses a key in a JSON file`. Name
  what you saw, not the fix.
- At most 60 characters. No full stop at the end. No `#N`: link other issues in the body.
- The area is one lowercase word, or words joined by hyphens.
- One type label (`feat`, `bug`, `docs` and the like) and one tier label (`tier A`,
  `tier B` or `tier C`), when the repo has those labels. A repo without type or tier
  labels gets no line.

`init` adds `.github/workflows/issue.yml`. It runs `check --issue` when an issue is
opened or edited. It checks the title and the labels: one type label and one tier label,
when the repo has those labels. It only reads: it never comments on the issue or renames
it. A red run tells the author to fix the title or the labels. To know which kinds the
repo has, the check lists its labels through `gh`; when `gh` cannot list them, only the
title is checked.

An agent's title is checked before it is sent. In a repo with a `yokoten.json`, the
tool-call guard runs the same check on `gh issue create|edit` and `gh pr create|edit`
with a title, plain or through `yokoten bot`. A bad title stops the command, so CI never
sees it. `yokoten bot` also refuses a `gh issue create` with a missing type or tier label
the repo has, so a bad issue is not sent. The user-level hook does not check titles:
another project has its own rules.

Any lowercase area passes. To limit the areas, list them in `yokoten.json`:
`"areas": ["doctor", "check", "docs"]`. Pull request titles then use the same list in
`type(area): summary`.

## The changelog

A pull request that changes what users notice adds one small file,
`changes/<issue>-<slug>.md`. It does not edit `CHANGELOG.md`. When every pull request
adds a line at the top of the same file, each merge makes the others conflict. Separate
files never conflict.

The file is plain Markdown. Line 1 is the section: Added, Changed, Deprecated, Removed,
Fixed, Security or Internal. Line 2 is blank. From line 3, write the entry as it should
read in the changelog. The `- ` is added for you. Several bullets in one file are fine.
The entry has no headings, and the file is a plain file right in `changes/`, not in a
folder or behind a link.

```markdown
Fixed

`doctor` no longer stops when the repo has no remote (#42).
```

`yokoten changelog` shows what the files would add, and writes nothing. Before a release,
`yokoten changelog --write` puts each entry under its section in `[Unreleased]`, and
deletes the files it used. It never touches released versions. If a file is wrong,
it says which file and why, and writes nothing.

`check --pr` takes a new file in `changes/` as the pull request's changelog entry, and
checks its form. A line in `CHANGELOG.md` still passes, for a project that has not moved
yet. `check` reads the files in `changes/` the same way, and `check --staged` reads the
staged ones. So the git hook stops a wrong file at commit, before CI does. It also notes
long sentences in a staged change file. A change file not yet added to git counts too, as
it does for `changelog`. `init` adds `changes/README.md`, which shows the form.
`changes/` may hold more than 7 files, since each release empties it.

## Translations

English is the source; a translation follows it. When the English text changes, the
translation falls behind, and nobody sees it. So each translated file names its source
on line 1, in an HTML comment. GitHub does not show it.

```markdown
<!-- source: docs/guide.md -->
```

The path is the English file, from the repo root. Only a relative path makes a marker:
an attribution comment such as `<!-- source: https://example.com/post -->`, or an
absolute path, is left alone. Write this line once; it never changes. When you change
the English, update the translation in the same commit or pull request.

`check` reads line 1 of each Markdown and HTML file. Then it reads the commits on the
current branch, first parent only, back to the commit that added the translation. A
commit that changes the English but not the translation puts the translation one commit
behind. Only a later commit that changes only the translation catches up. A commit that
changes both files is fine, but it does not cover an English-only commit before it. So
when one pull request changes only the English and another changes both, the first is
noted, whichever merges first. Any line of the translation counts.

Why not a line that records the English version? Every pull request that changes the
English would then edit that line. Two such pull requests would always conflict on it.
With a line that never changes, parallel pull requests touch only the paragraphs they
translate.

The costs. First, a commit that changes only the translation catches up with every
English change before it. A typo fix in the translation can hide an English change that
nobody translated. So run `check` before a typo fix. If it notes the translation as
behind, update the translation first. Second, with squash merges, an English-only pull
request stays noted until a commit changes only the translation. This is true even when
a later pull request translated it along with its own change. To clear the note, update
the translation in a pull request of its own.

In the work tree, an edit to the translation alone counts as a later change. An edit to
the English alone makes the translation behind. An edit to both counts as a commit that
changes both.

When a translation is behind, `check` gives a `translation-behind` note. The note says
how many commits behind, and how to see the change: `git diff <commit> HEAD -- docs/guide.md`.
A note never fails CI. To make it fail, put `"translations": { "fail": true }` in
`yokoten.json`.

An older form of line 1 adds the English blob:
`<!-- source: docs/guide.md blob 3b18e512dba79e4c8300dd08aeb37f8e728b8dad -->`.
`check` still reads it. A blob equal to the English text is current; otherwise the
commits decide. You can drop the blob at any time.

A shallow clone holds only the newest commits. When they are enough to tell, `check`
answers as usual; an English-only commit it sees is "at least" that many commits behind.
When they are not enough, `check` gives one note for the whole run. The note says how
many translations weren't checked, and there is no note for each file. That note never
fails, even with `"fail": true`. `check` never fetches. In CI, use `fetch-depth: 0`; the
kit's `yokoten.yml` does.

## Borrowed code

Record 0024 says when yokoten may copy code from another project. A copied piece is a
**wheel**. A wheel lives in `src/borrowed/`, never as a dependency. It is small: only
the part we need. The code under it is not the project's own, so a reader must see
where it came from.

Copy only under a permissive license: MIT, BSD-2-Clause, BSD-3-Clause, ISC or
Apache-2.0. For any other license, use the clean-room road: one agent writes what the
code does, and another agent, which has not seen the source, writes the code.

Every wheel starts with a comment header, before any code:

```ts
// source: https://github.com/example/upstream/blob/3f2a1c9d0e8b7a6f5e4d3c2b1a0f9e8d7c6b5a4f/src/thing.ts
// commit: 3f2a1c9d0e8b7a6f5e4d3c2b1a0f9e8d7c6b5a4f
// license: MIT
// copyright: Copyright (c) 2026 Example Authors
// changes: renamed readThing to parseThing
```

| Field | Needed | What it holds |
|-------|--------|---------------|
| `source` | always | The upstream file URL, as a `github.com/<owner>/<repo>/blob/...` link |
| `commit` | always | The full 40-character commit sha the code was taken from |
| `license` | always | The SPDX name: `MIT`, `BSD-2-Clause`, `BSD-3-Clause`, `ISC` or `Apache-2.0` |
| `copyright` | always | The upstream copyright notice, word for word |
| `notice` | Apache-2.0 | The upstream NOTICE text |
| `changes` | no | What we changed, so a reader can tell our edits from upstream's |

Each field is one line: `// field: value`. The header must come first, before the code.
Only the first comment block counts. When a field is written twice, the first one wins.

`yokoten check` reads the header of every file in `src/borrowed/`, in the whole tree and
in the commit about to be made. It reports each field that is missing, with the file and
the line to add. A commit with a bad header does not pass. A source that is not a GitHub
file link, or a commit that is not a full sha, is refused too. The watch reads only
`github.com/<owner>/<repo>/blob/...`, so the check refuses any other host with a fix.

A license outside the five above is refused, even when the header is complete. The fix
is `use the clean-room road in record 0024`. The same applies to a license name the
check does not know.

The `borrowed` workflow runs `yokoten borrowed` every Monday. For each wheel, the command
asks GitHub for the commits on upstream's default branch that touched the source file. If
a commit comes after the one we took, upstream has moved.

The command opens **one issue per source**, not one per file. It looks for an open issue
that already names the source, so a later run adds nothing. If the host cannot list the
open issues, the run stops with an error; it never opens a second issue. The issue lists
the local files, the commit we took, and the newer upstream commits. A commit message
that names a security fix gets the title `check: take a borrowed security fix`; other
changes get `check: review borrowed code upstream`.

When files that share a source took different commits, the command compares each one. If
the commit we took is not in the list the host gives, it compares dates instead of
calling every listed commit newer: a snapshot commit that never touched the file opens
no issue. When the host can give neither the list nor that date, the source is a gap.

A source the host cannot answer for is a gap, not a pass. The job says which source, and
fails, so nobody reads silence as "upstream did not change". To see the report without
opening issues, run `yokoten borrowed --dry-run`.

When upstream fixes a bug, take the new commit: update `source` and `commit`, re-apply
our `changes`, and let the tests pass. When the change does not touch the part we use,
say so in the issue and close it. To stop using a wheel, delete the file. The way back
is also the way out: the code was small, and the tests show what it must do (record
0014).

Some commands run another project's program, as they run `git` or `gh`. yokoten copies none
of its code and does not list it as a dependency. The version is pinned in the code.

| Program | Used by | Version | License | Where it comes from |
|---------|---------|---------|---------|---------------------|
| [sandbox-runtime](https://github.com/anthropics/sandbox-runtime) (`srt`), Anthropic | `yokoten desk`, the fence | 0.0.81, in `src/agents/desk/fence/srt.ts` | Apache-2.0 | `srt` on PATH at that version, else `npx` |

## Updating the rules

When yokoten learns a lesson, its core rules change. `yokoten install` updates the
skills and agents on your machine. Your project's `AGENTS.md` is your file, so it
changes only when you ask. Once it has `## This project`, `init` never touches it again.

`doctor` compares the part of `AGENTS.md` above `## This project` with the core rules
of the yokoten you run. When they differ, it reports `core-rules`, and a session hears
it too.

`yokoten update` shows the change and writes nothing. It names the sections that
changed, and lists each line it adds (`+`) or removes (`-`). `yokoten update --write`
replaces the core part. Everything from `## This project` down stays byte for byte.

A line you edited by hand in the core part shows as `-`, because the update replaces
it. Move your own rules under `## This project` first. Without that heading, yokoten
can't tell the core rules from yours. Then it says so, and compares and writes nothing.

The first line of `AGENTS.md` names the kit that wrote the core rules, as a comment
that GitHub does not show: `<!-- yokoten kit 0.1.0 -->`. `init` writes it, and
`update --write` sets the new version. When the project's kit is newer than the
yokoten you run, `update` refuses and writes nothing, so the rules never go back to
an older kit. `doctor` then says to update yokoten itself, not the project. A file
without that line compares by its text only.

The copy in `.yokoten/` keeps the kit's `AGENTS.md` and its lessons, so `doctor` in CI and the hooks
compares with the kit your project vendored. To take newer rules, run `update` from an
installed yokoten, the plugin or a clone. Run `init` again from there to refresh `.yokoten/` too.
Run from `.yokoten/` itself, `init` keeps that copy as it is.

With the Claude Code plugin, run `/yokoten:update`. It runs the plugin's own yokoten,
shows the change, and writes only after you agree.

## Learning from an incident

An incident is a lesson. `yokoten learn` turns it into a check that every project runs:

```mermaid
flowchart LR
  A[incident issue] --> B[yokoten learn]
  B --> C[check draft]
  C --> D[a person reviews it]
  D --> E[learn --ship: in the kit]
  E --> F[doctor in every project]
```

Give it the issue's number, or its URL for an issue in another repo. It reads the issue
through `gh`, with your own sign-in. It calls no AI model: the draft comes from the
issue's sections.

| Draft field | Comes from |
|---|---|
| `id` | `incident-<number>` |
| `what` | the title, after `area: ` |
| `why` | the "Why it matters" section. With none, "Why it got through" and a `TODO(reviewer)` to rewrite it |
| `fix` | the "Fix" section |

A field with no section in the issue gets a `TODO(reviewer)`, never a guess. The draft
also copies the issue's sections into a comment, for the reviewer.

`yokoten learn 138` prints the draft and writes nothing. `yokoten learn 138 --write`
adds two files in `lessons/incident-138/`:

- `lesson.ts`: the finding, and an empty `findProblems` for the check.
- `lesson.test.ts`: a positive control. It feeds the check a known-bad case and
  expects a catch.

The test fails until a person writes the check. So a draft never passes on its own.
`--write` adds only: it keeps a file that is already there.

`findProblems` gets the folder of a repo. It gives one finding for each place with the
problem. It reads files only: no network, no writes. Keep it fast, because `doctor` runs
it in every project.

When the review is done, ship the lesson. In a copy of yokoten, run
`yokoten learn --ship incident-138`. It moves the lesson into a numbered group, for example
`kit/lessons/1/incident-138/`, so no folder holds more than 7 entries: a group takes 5
lessons and 2 more groups, and a lesson goes in the first group with room. It adds a row to
the lessons table, `kit/lessons/README.md`: the id, the folder, the incident, and the
yokoten version. While a `TODO(reviewer)` is left or the test fails, it refuses and moves
nothing. A test in yokoten checks the same for every lesson in the kit. It is a command,
not a `git mv`, so the move, the table row and the test run are one step.

A project runs the lessons of the yokoten that runs its `doctor`. Each copy of yokoten reads
its own `kit/lessons/`: nothing is downloaded or synced. So after the ship:

| Who runs `doctor` in the project | Has the lesson |
|---|---|
| This copy: `node path/to/yokoten/bin/yokoten.ts doctor` | at once |
| This machine: `yokoten doctor` | after `node bin/yokoten.ts install` from this copy |
| The project's own copy: `node .yokoten/bin/yokoten.ts doctor` | after `init` again from this copy; commit `.yokoten/` |
| Everyone else, through npm or the plugin | when their yokoten updates, at the next release |

`learn --ship` prints the same list. A lesson that finds the problem gives a note, a row of
the `doctor` report:

```
Project:
  note        lesson incident-138: test/api.test.ts: a test calls a server on this machine and does not set NO_PROXY; set NO_PROXY and no_proxy to 127.0.0.1,localhost while the test runs, then put the old values back
```

The first time a project's full `doctor` runs a new lesson, it says so once. A lesson
that finds nothing says `note    a new check ran: ok (incident-138); doctor now runs it
in this project`. One that finds a problem says `note    new lesson: incident-138
(<what>); doctor now runs it in this project`, then the problem. It keeps the lessons
it has seen in `.git/yokoten/state.json`, so the file is never committed.

The session start hears a new lesson too, once, when it finds a problem here. It is one
line, and it names the command that shows where:
``yokoten: a new lesson learned in another project found a problem here: <what>. Run
`yokoten doctor` to see where.`` A later session, or the same one after a compaction, does
not hear it again. The session runs each new lesson at one start only, and keeps which in
the same file, so a later start spends no time on it. A problem that comes later is for
full `doctor` to report.

A note never fails `doctor`. To stop a lesson in one project, add its id to "notNow" in
`yokoten.json`, with the reason: `"notNow": { "incident-138": "a setup file sets it" }`.

**Try the loop.** In a clone of yokoten, run `node scripts/demo/loop.ts`. In about a second, it
walks the whole loop in a temp folder: an incident, the draft, a refused ship, the review,
the ship, and the note in a second project. It runs offline, with a fake `gh`, and removes
the folder after. `--keep` keeps the folder and prints where it is. The real loop is the same
commands, with your own issue and your own check, in a clone of yokoten.

## Lessons for yokoten itself

Sometimes the problem is in yokoten, not in your project: a guard missed a case, or a
message was hard to follow. Write it down where you found it:

```
yokoten learn --upstream "the guard let git push --tags through" --area hook --fix "match the flag too"
```

The note goes to `~/.config/yokoten/upstream/`, on your machine and outside every repo.
`--area` and `--fix` are optional; without `--area`, the part is `unknown`. Before it is
kept, the note is cleaned:

- the project's name becomes `<project>`, in any spelling: `secret-proj`, `secret_proj`,
  `SecretProj` and `secretproj` alike. A name of under three letters is left alone, as it
  would hide too many words; add it to your private marks if it points at you
- a path into the project starts with `<project>`, also when it goes through a symlink
- your home folder becomes `~`, also when a line cuts its path short
- your name, emails, gh login, Linux and Windows user names, private hosts and private
  marks become placeholders, as in `yokoten redact`; an email address becomes `<email>`

A note with a known secret shape is not kept: a token the commit check knows, a password in
a URL, `password=`, `token=` or `api_key=` with a value, a JSON web token, or an OpenAI
key. A note that still holds an IP address or a private mark after the cleaning is not kept
either. yokoten says which kind of detail it found, never the value. Nothing is sent
anywhere. Without a home folder (an empty HOME), nothing is kept.

In the yokoten repo, the session start and `doctor` say how many notes wait:
`yokoten: 3 lessons from other projects wait to become issues: yokoten learn --upstream --list`.
Other repos never see this line. Open an issue for each note, then remove it with
`yokoten learn --upstream --done <id>`.

## Settings

`yokoten.json` at the repo root holds the project's settings. Every key is optional.
A list you set replaces the default list; it does not add to it.

| Key | Default | What it does |
|-----|---------|--------------|
| `layout.maxEntries` | `7` | The most entries a folder may hold (core rule 10) |
| `layout.exempt` | none | Folders that may grow past it. See [Folders that may grow](https://yokoten.app/zh-tw/docs/guide/install-and-commands.md#folders-that-may-grow) |
| `tests` | `*.test.*`, `*.spec.*`, `*_test.go`, `test/**`, `tests/**` | The test files a fix task may not change |
| `leadOnly` | the rules, agent config, `.githooks/**`, `.github/**`, the vendored runtime `.yokoten/**`, `.yokotenignore` | The files a worker may not change |
| `privacy.allow` | none | Exact strings the privacy check lets through, such as a public support address, or this project's own name from your private marks |
| `privacy.skip` | lock files | Files the privacy check does not read |
| `privacy.utc` | `true` | Commit times must be in UTC |
| `privacy.output` | `"run"` | In an agent other than Claude Code, a command that prints account details runs as it is. `"stop"` stops it and shows the filtered command |
| `privacy.skipOwnName` | `true` | A private mark that is this repo's name is not looked for in this repo. `false` looks for it too |
| `notNow` | none | Steps not taken up yet, by `doctor`'s name, each with a reason: `{ "ci": "no remote yet" }` |
| `allowBuild` | none | Build output and binaries the commit check lets through |
| `areas` | any | The areas a title may name. See [Issue titles](https://yokoten.app/zh-tw/docs/guide/install-and-commands.md#issue-titles) |
| `translations.fail` | `false` | A translation behind its source fails the check |
| `worker.timeLimitMinutes` | `180` | A worker that a runner or a desk starts stops after this many minutes |
| `worker.settings` | none | Environment settings a worker may put before a command, such as a test's output file: `["SCREENS_OUT"]`. A name that moves a folder or runs other code (`PATH`, `HOME`, `GIT_*`, `LD_*`, `NODE_OPTIONS` and the like) is a config error, and the list then gives no extra names |

### Folders that may grow

Core rule 10 keeps each folder at 7 entries or fewer. Two kinds of folder may grow past it,
and `changes/` and `.worktrees/` always may:

- **A log**, such as `docs/records`. Each entry is added once and kept.
- **A third-party fixture set** that tests read, kept under its upstream names, with its
  `NOTICE` beside it. Tests, the `NOTICE` and upstream bug reports find a file by its
  name. Folders would make each lookup and each refresh from upstream harder.

List each such folder in `layout.exempt` by hand. An entry is a path from the root, or a
glob. In a glob, `*` is one folder and `**` is any number of folders, none too. No other
character is special. One glob covers every crate of a Rust workspace:

```json
{ "layout": { "exempt": ["docs/records", "crates/*/tests/data"] } }
```

`check` and `doctor` note a glob that matches no folder, so a stale entry shows.

Environment variables:

| Variable | What it does |
|----------|--------------|
| `YOKOTEN_ROLE=worker` | The session is a worker. It may not change lead-only files |
| `YOKOTEN_DONE_WHEN` | The command the stop hook runs when this worker ends the task. It wins over `yokoten-done-when` in the worktree's git folder. A subagent shares the lead's environment and does not get its own. See [The stop check](https://yokoten.app/zh-tw/docs/guide/install-and-commands.md#the-stop-check) |
| `YOKOTEN_LEAD_ONLY_OK` | The lead-only paths this worker may change, comma-separated, from the repo root. See [Works with](https://yokoten.app/zh-tw/docs/guide/works-with.md) |
| `YOKOTEN_FIX_TASK=1` | The session is a fix task. It may not change test files |
| `YOKOTEN_RELEASE_APPROVAL=1` | The owner allows a release from this session. Muse Code is not known to pass it to a hook |
| `YOKOTEN_PRIVATE_MARKS` | A second private-marks file. Its marks are added to the ones in `~/.config/yokoten/private-marks`; it never takes that file's place. See [Privacy](https://yokoten.app/zh-tw/docs/guide/privacy.md) |
| `YOKOTEN_MIN_FREE_GB` | `doctor` notes a disk with less free space than this, in GB. `0` turns the note off. See [Machine notes](https://yokoten.app/zh-tw/docs/guide/install-and-commands.md#machine-notes) |
| `YOKOTEN_BOT_APP_ID`, `YOKOTEN_BOT_KEY`, `YOKOTEN_BOT_INSTALLATION` | The bot. They can also be lines in `~/.config/yokoten/keys.env`. See [server.md](https://github.com/yokoten-labs/yokoten/blob/v0.1.2/docs/server.md#3-the-bot-a-github-app) |
| `YOKOTEN_GITHUB_API` | Another GitHub API address, such as GitHub Enterprise Server |
| `YOKOTEN_PR_LIVE=1` | `check --pr` reads the pull request's title, body, labels and milestone as they are now, with `GITHUB_TOKEN`, not from the event file. It fails when it can't read them. The kit's `pr.yml` sets it |
| `LLAMA_API_KEY` or `MODEL_API_KEY`, `LLAMA_API_BASE`, `LLAMA_MODEL` | The model for `adopt`. See [A first draft of the table](https://yokoten.app/zh-tw/docs/guide/four-layers.md#a-first-draft-of-the-table) |

---

上一章: [Works with](https://yokoten.app/zh-tw/docs/guide/works-with.md)
下一章: [Shipping an MCP server that works in every agent](https://yokoten.app/zh-tw/docs/guide/shipping-an-mcp-server-that-works-in-every-agent.md)
