---
title: "安装和命令"
description: "「安装和命令」：yokoten 使用指南的一章。"
canonical: https://yokoten.app/zh/docs/guide/install-and-commands
lang: zh-CN
source: https://github.com/yokoten-labs/yokoten/blob/v0.1.2/docs/i18n/zh-CN/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.

# 安装和命令

需要 Node 22.23.4 或更高版本（用 Node 24 要 24.22.1 起，用 Node 26 要 26.12.1 起）：它能直接运行 TypeScript，不用先编译。
22.18 到 22.23.3 也能跑 TypeScript，但请别再用：这几个版本有安全漏洞，Node 在 2026 年 10 月 21 日已经修好了
（[官方公告](https://nodejs.org/en/blog/vulnerability/october-2026-security-releases)）。所以 npm 会警告，完整的 `doctor` 也会在 `machine:` 下提醒。没有任何依赖。
装法有两种：一种是 Claude Code 插件，一种是 `install` 命令（从 npm 跑，或者从 clone
下来的仓库跑）。选一种就行，别两种都装。yokoten 写进去的 hook 命令要在 POSIX shell 里跑，
Windows 上请用 Git Bash。

也有人只装了技能：`npx skills add yokoten-labs/yokoten` 只复制技能。技能只是给 agent 的
说明，真正拦住命令的是守卫。所以只装技能等于没有守卫：要跑 `npx yokoten install`，或者用插件，
守卫才会打开。

大多数人只需要一条命令：`npx yokoten`。它做了 `install`、`init` 和 `doctor` 的活：
补上缺的东西，再给出一屏：你在哪里，下一步做什么。`trust` 和 `update` 仍是各自独立的步骤。除它之外，`yokoten help` 只列四条命令：
`desk`、`kanban`、`check` 和 `tidy`（[记录 0043](https://github.com/yokoten-labs/yokoten/blob/v0.1.2/docs/records/0043-v012-three-commands.md)）。
旧命令照样能用，并会告诉你它搬到了哪里。`yokoten help --all` 把它们都列出来，下面「命令」里的表一条条讲清。

## 装哪个版本

有三个发布通道。Stable 是稳妥的默认选择。

| 通道 | 插件 | npm | 装到的是什么 | 适合谁 |
|---|---|---|---|---|
| Stable | `yokoten` | `latest` | 最新的正式发布标签，例如 v0.1.0，带更新日志 | 所有人 |
| Beta | `yokoten-beta` | `next` | 最新的预发布标签，例如 v0.2.0-beta.1 | 想早点用新功能、出了问题愿意报告的人 |
| Dev | `yokoten-dev` | 无 | 默认分支，每个合并的 pull request | 贡献者，以及我们自己的测试 |

`/plugin install yokoten@yokoten` 装的是 Stable。Beta 用 `yokoten-beta@yokoten`，Dev 用
`yokoten-dev@yokoten`。只装一个通道，不要同时装几个。要换通道，先运行
`/plugin uninstall yokoten@yokoten`，再装另一个名字。在终端里，`claude plugin uninstall`
和 `claude plugin install` 也一样。`doctor` 会在输出的最上面写出通道和版本。

预发布版可能比正式版先出问题。Dev 每次合并都会变，而且没有更新日志。

## 用 Claude Code 插件装

在 Claude Code 里输入：

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

第一条把这个仓库加成插件市场，第二条打开插件页面，选「Install for you」（给自己装，
所有项目都能用）。不进 Claude Code 也行，在终端里先跑
`claude plugin marketplace add yokoten-labs/yokoten`，再跑 `claude plugin install yokoten@yokoten`。

插件就是这个仓库本身。它用的文件和 `install` 复制的是同一份，所以两种装法不会越走越不一样：

| 插件带来的 | 来自 |
|---|---|
| 技能：`/yokoten:dispatch`、`/yokoten:inspect`、`/yokoten:merge`、`/yokoten:rca`、`/yokoten:record`、`/yokoten:release`、`/yokoten:yokoten` | `kit/user/skills/` |
| agent：`yokoten:worker`、`yokoten:reviewer`、`yokoten:verifier` | `kit/user/agents/` |
| `/yokoten:init`、`/yokoten:doctor` 和 `/yokoten:update`：直接用插件里的 yokoten 跑这几个命令 | `kit/plugin/skills/` |
| 会话开头跑 `doctor` 的 hook 和命令守卫，跟 `install` 加的两个一样 | `.claude-plugin/plugin.json` |

命令守卫在哪个文件夹都会跑，所以只管命令，只有一个例外：AI 改的文件要是在一个装了 yokoten 的
仓库里（有 `yokoten.json`），不管会话是从哪个文件夹开的，都按那个仓库管文件的规则来查。
所以 worker 就算在会话文件夹外面的 worktree 里干活，也改不了只有 lead 能改的文件。

Claude Code 用 `node` 直接跑插件里的 `.ts` 文件。之所以能跑，是因为插件不放在
`node_modules` 下面（Node 不处理 `node_modules` 里的 TypeScript）。想更新，就打开 `/plugin`，
在 **Installed** 里选 yokoten，点 **Update now**。别人做的插件市场默认不自动更新，要自己打开。在终端里跑
`claude plugin update yokoten@yokoten` 也一样；Beta 或 Dev 要用 `yokoten-beta@yokoten` 或
`yokoten-dev@yokoten`。下一次工具调用会跑更新后的守卫。还带着旧 hook 命令的会话需要重启一次。
会话还在跑的时候，要是旧的插件文件夹被删掉了，这次工具调用会被拒绝，并提示你重启。插件要是比跑 `doctor`
的这份 yokoten 旧，`doctor` 会提醒你。

插件只管 Claude Code。它不会帮你建私有词表文件：`doctor` 会告诉你文件在哪，你自己建。
插件在你的用户设置里开着的时候，`doctor` 就把它的 hook 和技能算作已经装好。

插件和 `install` 二选一，别两个都用：插件开着的时候，`install` 不会往全局文件夹里放自己的一份。
以前两个都装过的机器上，守卫和会话开头的 doctor 会跑两遍，每个技能也会出现两次，`doctor` 会提醒你，
插件开着的 `install` 会把自己写过的全局副本收回来。想从 `install` 换成插件，就跑 `yokoten uninstall`。
它会删掉 `install` 写的东西。`~/.config/yokoten` 里你自己的文件，比如你加的私有词，都会留着。

插件开着的时候，`install` 不写自己的守卫，守卫只留插件那一个。把插件关掉或者删掉，
这台机器就一个守卫都没有了：`doctor` 会以“no guard for Claude Code”报失败，
`yokoten install` 会写回 `install` 自己的守卫。

## 用 npx 装

用 Codex CLI、Gemini CLI，或者不想用插件，就这样装：

```sh
cd your-project
npx yokoten             # 一次就好：装好缺的东西，再看看走到哪一关
```

在任何项目里再运行一次，它只做还缺的部分。想一步一步来的话：

```sh
npx yokoten install      # 这台电脑：技能、agent、hook
npx yokoten init         # 这个项目：只加文件，不覆盖
npx yokoten doctor       # 看看走到哪一关，下一步做什么
```

`npx` 会先把包下载下来再运行。这个包没有任何依赖。`install` 和 `init` 会把 yokoten
的运行文件从包里拷出来，一份放到 `~/.local/share/yokoten`，一份放到项目的 `.yokoten/`。
hook 跑的是这两份拷贝，所以不用联网，也不用再经过 `npx`。

npm 上的包跑的是编译好的 JavaScript，因为 Node 不处理 `node_modules` 里的 TypeScript。
仓库本身还是不用编译，只有 `npm pack` 打包的时候才生成 `dist/`
（见[记录 0008](https://github.com/yokoten-labs/yokoten/blob/v0.1.2/docs/records/0008-npm-package.md)，英文）。

发布到 npm 是主人的事，跟打发版 tag 一样。tag 一推上去，发布 workflow 就开始跑：先跑检查，
然后发布那一步停下来，等主人在 `release` 环境里点批准，批准了才发到 npm。整个过程不存任何
npm token（见[记录 0009](https://github.com/yokoten-labs/yokoten/blob/v0.1.2/docs/records/0009-release-environment.md)，英文）。只有第一个版本例外，
由主人手动发布：npm 上还没有这个包的时候，没法设置信任哪个 workflow。第一个版本发布之前，先
[从 clone 下来的仓库装](https://yokoten.app/zh/docs/guide/install-and-commands.md#从-clone-下来的仓库装)。

## 从 clone 下来的仓库装

命令一样，只是从 clone 下来的仓库跑。npm 上还没有第一个版本的时候，就用这种装法：

```sh
git clone https://github.com/yokoten-labs/yokoten
node yokoten/bin/yokoten.ts install      # 这台电脑：技能、agent、hook
cd your-project
node ~/.local/share/yokoten/bin/yokoten.ts init     # 这个项目：只加文件，不覆盖
node ~/.local/share/yokoten/bin/yokoten.ts doctor   # 看看走到哪一关，下一步做什么
```

yokoten 的提示里写的都是 `yokoten init`、`yokoten doctor`。所以 `install` 会在
`~/.local/bin` 放一个小小的 `yokoten` 启动脚本（Windows 上是 `yokoten.cmd`）。大多数电脑的
PATH 里本来就有这个文件夹，所以新开一个终端，`yokoten` 就能用了。这个脚本跑的是
`bin/yokoten.mjs`，跟钩子用的是同一个入口，启动更快。旧版装的启动脚本，下次 `install` 会直接改写。

要是 PATH 里没有 `~/.local/bin`，`install` 会问你一次：
`Add ~/.local/bin to your PATH in ~/.bashrc? [Y/n]`（用 zsh 的话是 `~/.zshrc`，macOS 上的 bash 是 `~/.bash_profile`）。你答应了，
它就往这个文件里加下面这一行；不答应，它就把这一行打出来，你自己加：

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

只有你答应了，yokoten 才会改你的 shell 配置文件。没人在终端前的时候（管道、hook、CI 里），
它不问，也不改。`~/.local/bin/yokoten` 要是你自己的文件，不是 yokoten 写的，它就不动，
并告诉你是哪个文件。`uninstall` 会删掉启动脚本和带标记的那一行。PATH 里没有一个
`yokoten` 跑的是装好的这一份时，`doctor` 会提醒你。

这些命令（包括上面 npx 那几条）在 [docs/facts.json](https://github.com/yokoten-labs/yokoten/blob/v0.1.2/docs/facts.json) 里还有一份，网站读的是那份，所以改命令的时候两边都要改。

## 当成 GitHub Action 用

GitHub 上的任何仓库，加一个 workflow 文件，就能在 CI 里跑 yokoten 的检查。仓库里不用装
任何东西，数据也不会离开这次 CI：不用令牌，不连外面的服务。

```yaml
# .github/workflows/yokoten.yml
name: yokoten
on:
  pull_request:
    # 有了 edited、labeled、milestoned，改完标题或标签，check --pr 会再跑一遍。
    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@<完整的 commit SHA>
```

这个 action 先装好 Node，再跑 `check`；如果是 PR，还会接着跑 `check --pr`。查出来的问题
会标在 PR 页面上，跟 yokoten 自己的 CI 一样。

它认这三种事件：

- **`push`**：跑 `check`。
- **`pull_request`**：先跑 `check`，再跑 `check --pr`。
- **`issues`**：先跑 `check`，再跑 `check --issue` 查 issue 标题。要在 `on:` 下面加上
  `issues: types: [opened, edited]` 才会触发。

`pull_request_target` 拉下来的是目标分支，不是 PR 本身的代码，所以 action 不跑
`check --pr`，只留一条提示：PR 检查要用 `pull_request` 触发，不是 `pull_request_target`。

- **版本要钉死。** 写完整的 commit SHA。写 `@main` 这种分支名，哪天内容变了你都不知道。
  等发布了正式版本，也可以写版本标签。
- **checkout** 要加 `fetch-depth: 0`。`check --pr` 要比较 PR 的起点和最新提交，两个都得拉下来。
- **权限**只要 `contents: read`。这个 action 不要令牌。
- **参数**有三个：`pr-check`（默认 `true`）、`issue-check`（默认 `true`）和 `node-version`
  （默认 `^22.23.4`，也就是第一个带 2026 年 10 月安全修复的 Node 22）。不想查 PR 的格式，就写 `pr-check: false`；不想查 issue 标题，
  就写 `issue-check: false`。
- **Node**：action 装的 Node，这个 job 后面的步骤也会用到。

`check --pr` 按核心规则第 8 条检查 PR。还没按这条规则走的仓库，可以先写 `pr-check: false`。
Dependabot 开的 PR 只查标题，其他几条只提醒、不算失败，因为它的正文是 Dependabot 自己写的。
kit 里 `init` 放的 `check` workflow 先跑项目里 `.yokoten/` 的那份：它和提交钩子用的是同一个
yokoten，检查也一样。然后才跑这个 action，钉在第一个正式版本的 commit 上，这样改了 `.yokoten/`
的 PR 也得过一份它改不了的检查；Dependabot 会把这个 pin 更新到新版本。kit 的 `pr` 和
`issue` workflow 跑项目里 `.yokoten/` 的那份。

## 命令

`init` 会把 yokoten 运行要用的文件复制到项目的 `.yokoten/` 里。把它一起提交：这样不联网、
不全局安装，hook 和 CI 也能跑。

项目原来的设置，`init` 会保住。`AGENTS.md` 里没有 `## This project` 的，`init` 把核心规则加在最上面，
原来的内容整个挪到 `## This project` 下面，里面的标题各降一级，别的一个字不改。`init` 会打印它挪了哪些标题。
如果 git 里有原来那个文件，`init` 会打印把它找回来的命令；没有的话，就留一份副本在
`AGENTS.md.before-yokoten`。YAML 头信息（front matter）仍在最顶上。`AGENTS.md` 是链接的，就原样不动。有 `CLAUDE.md` 的，它在文件末尾加一行 `@AGENTS.md`：有
`CLAUDE.md` 时，Claude Code 只通过这一行才会读 `AGENTS.md`。`.claude/CLAUDE.md` 也算：
根目录没有 `CLAUDE.md` 的话，就在它末尾加 `@../AGENTS.md`。用 husky 或者别的
`core.hooksPath` 的，它不换掉那个目录，而是把提交检查加进那里的 `pre-commit`。项目自己的
hook 一行都不删，`init` 也会说改了哪个文件。git 自己提交合并时跑的是 `pre-merge-commit`，不是
`pre-commit`，所以 `init` 在那里也放同样的检查（`.githooks/pre-merge-commit`，用 husky 的是
`.husky/pre-merge-commit`）。项目自己写的 `.githooks/pre-commit` 也会加上检查。`.husky/` 下的
文件，文件权限不动：husky 9 是用 `sh` 去跑它们的。

有的仓库在 `.git/hooks` 里也有自己的 hook，可能是 lefthook 或 pre-commit 框架装的，也可能是
手写的。git 改成从 `.githooks` 找 hook 以后，这些 hook 全都不跑了。所以 `init` 写的
`.githooks/pre-commit` 和 `.githooks/pre-merge-commit` 先跑检查，再去跑原来那个 hook；别的有执行
权限的 hook，比如 `commit-msg`、`pre-push`，`init` 也在 `.githooks` 里各写一个文件，只管去跑原来
那个。参数和标准输入都原样传过去。原来的 hook 留在 `.git/hooks` 里不动，`init` 会一个个说保留了
哪些。没有执行权限的 hook，git 现在本来就不跑，所以 `init` 照常设好，并告诉你怎么留住它。要是接
不上（提交 hook 来自全局的 `core.hooksPath`，或者项目自己的 `.githooks` 文件把检查写在 `if` 里，
或者它和原来的 hook 都要读标准输入），`init` 就不设 `core.hooksPath`，并告诉你该怎么做。设了全局
`core.hooksPath` 的话，`.git/hooks` 里的文件本来就没跑过，`init` 不管它们。这样的 hook 会被丢掉的
时候，`doctor` 不会把提交检查算作做完。项目自己的 workflow，`init` 会原样留着，`doctor` 会告诉
你该往里加哪一步。

`init` 还会在 `.github/workflows/` 里放三个 workflow：`yokoten.yml`（跑提交检查；一个 lint
job 用 actionlint 和 zizmor 查 workflow 本身；还有一个 `ci` job，设成必过的检查就只要设它）、
`pr.yml` 和 `issue.yml`。每个 action 都固定到某一次提交，每个 job 都有超时，checkout 不留
token。在 `yokoten.yml` 里，同一个 PR 推了新提交，还在跑的那次会取消。推送到 `main`
不会取消运行：每一次提交都单独测一遍。`pr.yml` 什么都不取消。
它的几次运行开始的顺序不固定，所以每一次都用这个 job 的 token，去读 PR 现在的标题、正文、标签和
里程碑。重跑也是再读一遍现在的。你自己的测试，加成 `yokoten.yml` 里的一个 job，再把它写进 `ci`
的 `needs`。

`yokoten.yml` 和 `pr.yml` 还会在 `merge_group` 上跑，这样合并队列跑的必过检查和 PR 的一样。合并队列
的一次运行里没有 PR：`yokoten.yml` 把每个 job 都完整跑一遍，`pr` 的 job 跳过，跳过在队列里算作通
过。去仓库设置里打开合并队列。`issue.yml` 从不在那里跑：issue 不是合并且要过的检查。

`yokoten.yml` 里有个 `changes` job，会看 PR 改了哪些文件。每个文件都在 `docs/` 或 `changes/` 下，或者是
`README.md`、`CHANGELOG.md`，这个 PR 就算只改文档。你自己的 job 可以跳过只改文档的 PR；`ci` 上面的
注释写了要加的两行。`ci` 把这种跳过算作通过。提交检查和 lint job 一直会跑。往 main 推送时，所有 job 都跑。
读不到文件列表的 PR 也是所有 job 都跑，并给一条警告。yokoten 自己的 CI 里，只改文档的 PR 把测试只跑一遍（Ubuntu、Node 24），跳过 `typecheck` 和 Action 的 job。

如果项目里已经有这几个 workflow，`init` 会先看你有没有改过。kit 记着自己写出过的每一个版本。
文件和其中一个一模一样（换行是 Windows 的也算），就说明你没改过，`init` 会把它换成最新版，
并告诉你换了哪个。`init` 从来不提交，所以想要回旧文件，用 `git restore <文件>` 就行。
你改过的文件原样保留；`init` 和完整的 `doctor` 会给出一条命令，让你对比它和 kit 最新版的区别。
同一个 kit 版本，`doctor` 只提醒一次。符号链接也原样保留：`init` 从不顺着链接去写。

另外还在 `.github/` 里放三个文件，默认设置就是安全的：

- `dependabot.yml`：自动更新 CI 用到的 action，还有 `init` 在项目里找到的包（npm、cargo、pip、
  Go 等，看根目录和下一层文件夹）。每种一个月只开一个 PR；新版本发布满一周才会用。
  已经有这个文件的话，`init` 不动它，只告诉你哪些包它还没管。
- `SECURITY.md`：告诉别人发现漏洞要私下报告，不要发公开 issue。要让私下报告能用，得在仓库
  设置里打开 private vulnerability reporting（私下报告漏洞）。`doctor` 会把它和其他安全开关
  一起列出来。
- `CODEOWNERS`：默认只许 lead 改的那些路径，都写上主人：`AGENTS.md`、`yokoten.json`、
  `.yokoten/`、hook、agent 配置和 `.github/`。守卫在本机拦住 worker，这个文件在服务器上拦住
  同样的改动。主人取自 GitHub 上的 `origin` 远程仓库；没有的话，文件里先放一个占位符，
  `doctor` 会提醒你换成自己的 GitHub 用户名。GitHub 不认组织当主人，所以 `gh` 查出这个账号是
  组织时，`init` 也写占位符，并提醒你换成一个人，或者 `@组织/团队`。完整的 `doctor` 发现文件里
  只写了组织，也会提醒你。要真正起作用，得在分支保护里打开
  「Require review from Code Owners」（需要 code owner 审查）。项目里本来就有 CODEOWNERS 的
  （放在 `.github/`、根目录或 `docs/`），原样保留。

| 命令 | 做什么 |
|------|--------|
| `install [--yes] [--list]` | 把入口技能 `yokoten` 一个装进 `~/.claude/skills` 和 `~/.agents/skills`，agent 装进 `~/.claude/agents`，加上会话开头的 `doctor` hook 和用户级命令守卫，再建一个空的私有词表。在终端里先显示一屏将要改动的内容，问一次，然后再写；`--yes` 跳过这个问题。写之前，先列出要写的文件和设置键。这两个 hook 给 Claude Code 装一份；已经有 `~/.codex` 或 `~/.gemini` 文件夹的话，Codex 和 Gemini CLI 也各装一份。Claude Code 的插件开着的时候，它不写全局的技能、agent 和 hook，并收回旧 `install` 写过的那些。它还会把这台电脑上能找到的私有词加进词表，并写好 git 的 mailmap 文件。改 git 全局的 `mailmap.file` 之前，它会先问你。`--list` 逐行列出它写的或留下的每个文件。见「隐私」 |
| `init [dir] [--list]` | 把这套做法放进一个项目。`--list` 显示它写入或保留的每个文件；默认只显示一屏。只加文件；已有的文件保留，并告诉你保留了哪些。kit 的 workflow 你没改过的，会换成最新版。它还会用 `gh` 看一眼 GitHub，告诉你要补哪些标准标签和里程碑，但不会去改 GitHub。没装 `gh`、没登录，或者远程仓库不在 GitHub 上，就说一声跳过了 |
| `init --github --yes [--remove-default-labels] [--board]` | 补上缺的标准标签；没有打开的里程碑时，建一个叫 "First release" 的；要是已经有一个关掉的里程碑叫这个名字，就叫 "Next release"。关掉的里程碑不会被重新打开。已有的标准标签，颜色和说明改成标准的。`yokoten.json` 里的模块不超过 8 个时，每个模块建一个灰色标签。再跑一次什么也不会变。GitHub 自带的、不在这套里的标签（`question`、`wontfix` 等），只有加上 `--remove-default-labels` 才会删。加 `--board` 还会建一个关联到仓库的看板，要 gh 的令牌带 `project` 权限 |
| `init --no-ci` | 不用 CI：在 `yokoten.json` 的 `"notNow"` 里记下 `"ci"`，不写 workflow，也不写 Dependabot 配置；这些文件如果还是 kit 原样，就删掉。私有仓库的 Actions 按分钟收费，用这个省下来 |
| `trust [dir] [--undo]` | 给这台电脑上装了的 agent 打开 yokoten 的守卫：按它们各自的写法，只写 yokoten 自己的钩子需要的信任，支持 Codex、Grok CLI、Copilot CLI 和 Muse Code。这条命令由人在终端里跑；管道里什么都不写。只有 yokoten 自己的钩子在、而且项目的 Codex 沙箱是安全的，它才信任这个文件夹，因为信任文件夹会让项目自己的沙箱设置生效。在终端里跑 `init` 时会问一次；没有终端就什么都不写，只告诉你这条命令。`--undo` 只删 yokoten 写进去的条目，你后来改过的会留着 |
| `update [dir] [--write] [--all]` | 默认每条改动的规则一行；`--all` 列出每一行改动。看看这个版本的 yokoten 会怎么改项目 `AGENTS.md` 里的核心规则。加上 `--write` 才真的替换；「This project」那部分原样保留。见[更新规则](https://yokoten.app/zh/docs/guide/install-and-commands.md#更新规则) |
| `upgrade` | 把 yokoten 本身升到最新版。装了 Claude Code 插件时，先更新 marketplace，再更新插件，都走 HTTPS，不用 SSH 密钥。不会添加或删除任何东西。没装插件时，只告诉你运行 `npx yokoten@latest` |
| `doctor [--all] [--session] [--json]` | 项目走到了哪一步，一屏看完：做完了什么、缺什么、下一步做什么。`--all` 列出整条路：每一关、每一步，加上这台电脑还缺什么。`--session` 只说下一步，会话开头用。完整的 doctor 还会拿一条无关紧要的命令把守卫跑一次，报告 `guard: 90 ms per command (60 ms CPU)`：一次调用的墙钟时间和 CPU 时间（用户态加内核态）；会话开头不跑。再加 `--agent gemini`，就按 Gemini CLI 读得懂的 JSON 输出 |
| `adopt [--all] [--json]` | 默认只显示一屏；`--all` 显示整张表，每一行都附上理由。给老项目的采用表出一份草稿：项目现在做到了哪些，每一步建议怎么定。`--json` 输出每一行的数据。不改任何文件 |
| `check [--staged] [--all] [--json]` | 一屏：问题全部列出，提示折成一个数字；`--all` 逐条列出提示。检查目录层级、隐私和只有 lead 能改的文件；`--staged` 只查下一次提交。提交里不能有构建产物和二进制文件。`changes/` 里的文件按 `changelog` 的读法检查，格式不对，提交时就拦下。什么都没暂存时，`--staged` 提醒一句，算通过。查整个目录时，落后于英文的译文会给一条提醒，见[翻译](https://yokoten.app/zh/docs/guide/install-and-commands.md#翻译) |
| `check --pr` | 在 CI 里检查 PR：标题格式、第一行的 `tier:`、三个小节、关联的 issue、需要时的变更文件、milestone 和标签；最后一行写明做出这个 PR 的每个 AI 和模型：`🤖 Made with <agent> (<model>)`，可写一个或多个逗号分隔的组合；审阅的 AI 也可以写上：`🤖 Made with worker <X> (<模型>), reviewed by <Y> (<模型>) and <Z> (<模型>), lead Claude Code (<模型>)`（一个或两个审阅者，最多两个）。见下面的「更新日志」。改到 CODEOWNERS 里列出的文件，必须写 `tier: A`。这一行会和分支上的 desk 状态文件核对：行里没写出的 desk，或审阅者与写作者同属一个厂商，都算问题。改动的文档里句子太长、改动的代码里名字难读、改动超过 400 行，都只提醒，不算失败。`Made with` 里的模型和 worker 状态文件不一致时也只提醒，而且只在有 worker 工作副本的机器上检查：CI 看不到那个文件 |
| `check --pr <number>` | 同样的检查，但通过 `gh` 读这个编号的 PR，没有 CI 的仓库合并前自己跑 |
| `check --issue` | 在 CI 里检查 issue 的标题，见下面的「issue 标题」 |
| `check --anchor [--repo <owner/name>]` | 每个已经过去的 UTC 日，拿事件日志最后一行的哈希去 Sigstore Rekor 里对；对不上就点名是哪一天。发到 Rekor 的只有那 64 个十六进制字符，日志的行、路径和名字都不出这台电脑。`--repo` 指定给它签名的仓库的锚定工作流 |
| `ci-health [--repo <owner/name>] [--write] [--dry-run]` | 读最近 200 次 Actions 运行和打开的 PR 的检查，打印报告，什么都不写。加 `--write` 才更新那个固定的 issue「CI health」；`--dry-run` 只打印报告 |
| `provenance <ref> [--json]` | 给一个提交，或一个范围里的每个提交，写明写的人、审的人、批准的人、等级和 issue；从 squash 后的提交信息和宿主上最新的 Approve 读。`--json` 输出记录。见[来源记录](https://github.com/yokoten-labs/yokoten/blob/v0.1.2/docs/i18n/standard.md#来源) |
| `borrowed [--dry-run] [--json]` | 通过 `gh` 把 `src/borrowed/` 里的每个文件和上游默认分支对比，每个有变化的来源开一个 issue。安全修复会在标题里点出来。`--dry-run` 只显示报告，不开 issue；`--json` 以数据形式输出报告。见[借来的代码](https://yokoten.app/zh/docs/guide/install-and-commands.md#借来的代码) |
| `marks allow <mark> --here` | 让词表里的一个词只在这个仓库放行，不需要 yokoten.json。放行记在这台电脑上，不写进仓库。`--remove` 撤回。见「隐私」 |
| `redact` | 从标准输入读文字，把你的信息换成占位符再输出：`<owner>`、`<email>`、`<host>`、`<mark>`。命令守卫会让有风险的命令先过一遍它。见「隐私」 |
| `anchor [--json]` | 列出这个检出里记录的锚点，最新的在前。worker 的检查通过、工作区干净时记录一个。锚点是一个提交：用 `git switch -c try-2 <commit>` 回去，什么都不会被重置或删除。见下面的「停止检查」 |
| `changelog [--write]`、`changelog --notes <版本>` | 列出 `changes/` 里的文件会往 `[Unreleased]` 下面加哪些条目。加上 `--write` 就真的写进 `CHANGELOG.md`，再删掉这些文件；发版前跑。`--notes` 从 `CHANGELOG.md` 里这个版本的那一节直接生成发版说明，不用再手写一遍。见下面的「更新日志」 |
| `mcp [dir] [--command "<command>"] [--json]` | 启动 `.mcp.json`、`.vscode/mcp.json` 或 `mcp.json` 里的每个 stdio 服务器，按各个客户端的要求检查它的工具：schema、名字、注解、数量、结果格式和多余的 stdout。`--command` 检查没有配置条目的服务器；`--json` 输出检查结果。Windows 的提醒不会让它失败。见英文指南的「Shipping an MCP server that works in every agent」 |
| `learn <issue> [--write] [--all]` | 默认只显示一屏；`--all` 显示 issue 里没填的每一项，以及草稿会写哪些文件。从一个事故 issue 起草一条检查，留给人来审。加上 `--write` 才把文件写进 `lessons/`。见下面的「从事故里学」 |
| `learn --ship <id>` | 在 yokoten 里用：把审过的教训搬进 `kit/lessons/` 下的一个分组，以后用这份 yokoten 跑 `doctor`，每个项目都会查它。还有 `TODO(reviewer)` 没填，或者它的测试没过，就不让搬 |
| `learn --export <id> [--out <file>]` | 把审过的教训写成一个小的 JSON 文件，带着事故和阳性对照，不含私人细节。加 `--out` 写进文件，不加就打印出来 |
| `learn --import <file> [--write]` | 显示另一个项目来的教训文件。加 `--write` 才把它放进 `lessons/` 等审。跑 `learn --ship` 之前什么都不运行 |
| `learn --upstream "<发生了什么>" [--area <name>] [--fix <idea>]` | 记下 yokoten 自己的一个毛病，项目和你本人的信息都先藏掉。`--list` 列出这些记录，`--done <id>` 删掉一条。见下面的「问题出在 yokoten 自己身上」 |
| `check --history` | 对所有分支的每一个提交做隐私检查。私有仓库公开之前跑一遍。浅克隆看不到旧的提交，所以它不扫描、直接报错；在 CI 里要拉下全部历史（`fetch-depth: 0`）。文件里查到问题时，写的是加进这个文件的那次提交，和那次提交里的路径：`文件 @ <提交>`。`git replace` 藏不住提交，它照样读原来的提交；有 grafts 文件时同样不扫描、直接报错。 |
| `pr sync` | 每次合并之后，把每个打开的 PR 更新到它的基础分支：合并基础分支，只解决两种机械冲突（两边在同一处各加了一整块，或两边改了同一个 import 列表），每次合并（干净合并或解决冲突后）都先运行 typecheck 和改动文件对应的测试，然后以 bot 的身份推送。其他冲突会停下，并打印文件、冲突块和一份派活说明。每行报告会写明实际合并的基础分支；打开的 PR 列表最多 100 条，截断时会说明。它从不强制推送、从不 rebase，也不在 GitHub 上解决冲突。每次合并后都要跑。 |
| `hook [--agent <name>] [--commands-only] [--denial <mode>] [--json] [--protocol]` | 工具调用守卫，从标准输入读一次工具调用：默认读 Claude Code 的 PreToolUse；加 `--agent codex` 读 Codex 的 PreToolUse，加 `--agent gemini` 读 Gemini CLI 的 BeforeTool，加 `--agent antigravity` 读 Antigravity CLI 的 PreToolUse，加 `--agent muse` 读 Muse Code 的 PreToolUse，加 `--agent deepseek-harness` 读 DeepSeek Harness 的 PreToolUse，加 `--agent grok` 读 Grok CLI 的 PreToolUse。加 `--denial exit2` 时，拒绝仍以退出码 2 和标准错误上的原因返回，供只看退出码的宿主使用。加 `--protocol` 时，任何工具都能发送守卫协议并读取回答，见[守卫协议](https://github.com/yokoten-labs/yokoten/blob/v0.1.2/docs/records/0039-the-guard-protocol.md) |
| `hook --stop [--agent <name>]` | 收尾检查。从标准输入读这一轮结束的事件，检查改过的文件：默认读 Claude Code 的 Stop；加 `--agent codex` 读 Codex 的 Stop，加 `--agent gemini` 读 Gemini CLI 的 AfterAgent，加 `--agent grok` 读 Grok CLI 的 Stop。见[收尾检查](https://yokoten.app/zh/docs/guide/install-and-commands.md#收尾检查) |
| `test [--heavy] [--print] [-- <runner words>]` | 轻量地跑测试，给和别的工作共用的机器用（#461）：用 `nice -n 10` 跑 `node --test`（Windows 上不用 nice），`--test-concurrency` 最多取核数的一半、每 1 GB 空闲内存最多 1 个，至少为 1。`--` 后面的词交给测试运行器：测试文件，或者它自己的选项。默认就是开着的。`YOKOTEN_LIGHT_TESTS=0` 对每次调用关掉，`--heavy` 只关这一次。`--print` 只显示命令和原因，不运行 |
| `stops [off \| on]` | 显示 `~/.config/yokoten/stops.jsonl` 这个拦截记录，以及 yokoten 还在不在写它。`off` 停止写入并删掉记录，`on` 重新开始。记录只留在这台电脑上。见[收尾检查](https://yokoten.app/zh/docs/guide/install-and-commands.md#收尾检查) |
| `explain [<id>] [--json]` | 讲一条规则或者路上的一步：它是什么、为什么要有、yokoten 怎么检查 |
| `worker mark <目录>` | 把 `<目录>` 这个 worktree 标成 worker 的。之后在这里干活的 agent，护栏一律当 worker 看待，哪怕它的运行环境没把 `YOKOTEN_ROLE` 传给钩子（比如 Muse Code）。worker 用常见的办法删这个标记，护栏都会拦下；`.git` 读不出来的时候，也照样当 worker。护栏只看命令文字，真正的锁是服务器，还有别把推送和机器人的凭据交给 worker。`git worktree remove <目录>` 会连同 worktree 一起删掉标记。给的是子目录时，它标的是所在的 worktree，并说出是哪个。普通的 clone（比如 desk）也能标，标记放在它自己的 `.git` 里（#672）。lead 在自己的检出里给 clone 打标记。主检出（lead 自己在里面干活的 clone，或者挂着 worktree 的那个）和子模块永远不会被标记。worker 不能打标记，也不能去掉标记 |
| `worker env [运行环境]` | 列出 yokoten 给 agent 准备环境变量时，会从这个 shell 留下哪些变量名，并数出去掉了几个。加上运行环境名，会把它自己的登录变量也算进去。从不显示值。`desk` 就用它启动 agent。见 [agent 拿到的环境变量](https://yokoten.app/zh/docs/guide/privacy.md#agent-拿到的环境变量) |
| `msg send <收件人> [<文字>]`、`msg inbox`、`msg who`、`msg log` | 各家 agent 通过这台电脑上的文件互相发消息。只有 lead 能跑 `log`。见 [agent 之间发消息](https://yokoten.app/zh/docs/guide/managing-agents-like-a-team.md#agent-之间发消息) |
| `desk [<issue> \| "<任务>"] [--agent <名字>] [--pr] [--no-install] [--json]` | 不带参数：列出你的工位，什么都不写（加 `--json` 就把这份列表当数据打印）。带 issue 号或一句任务：建工位 `../<仓库>-desks/<n>/`（它自己的克隆，分支 `desk/<n>`），写好简报，在围栏里启动 AI（先选 Claude Code）。AI 退出后，在围栏里跑检查和测试，一屏给出证据。它从不 push；`--pr` 才推分支并开 PR（配了 bot 就用 bot）。中途停下的工位，下次启动时会列出来，`desk <n>` 从最后一次提交接着做。原生 Windows 上请在 WSL 里跑 |
| `desk <agent> <文件夹> [--name <名字>] [--read <路径>] [--host <主机>] [--marks] -- <命令>` | 把 worker 的 agent 放进一个围起来的工位里启动：只有它自己的克隆、自己的状态文件夹，除了模型的 API 不能上网。只有 lead 跑它。见 [工位](https://yokoten.app/zh/docs/guide/managing-agents-like-a-team.md#工位) |
| `uninstall [--list]` | 撤掉 `install` 装的东西：带 yokoten 管理标记的入口技能和 agent，Claude Code、Codex CLI、Gemini CLI、Antigravity CLI、Copilot CLI、Grok CLI、Muse Code 设置里的 hook，OpenCode 的插件，还有 `~/.local/share/yokoten`。私有词表和 mailmap 文件只剩 `install` 写的表头时，也一并删掉；`install` 建的空文件夹和 git 配置文件也删掉。`install` 之前就有的文件夹留着。你自己的文件、改动和设置都留着；拦截记录会被删掉。再跑一次什么也不会变。`--list` 逐行列出它删掉的或留下的每个文件 |
| `bot -- <命令>` | 以 bot 的身份运行一条命令（`gh`、`git push`），用一小时有效的令牌，令牌不会打印出来。如果命令是 `gh issue` 或 `gh pr` 的 `create`、`edit`，并且带了标题，它会先按 CI 的规则检查标题；标题不合格就直接拒绝，什么都不会发出去。用 `gh api` 改 issue 或 PR 时，标题也照样检查。`gh pr create` 和 `gh pr edit` 如果带了 `--body` 或 `--body-file`，正文也会先过一遍：规则和 `check --pr` 里检查正文的那部分一样，看的是这个 PR 的分支改了哪些文件，以及基础分支上的 CODEOWNERS。正文不合格同样直接拒绝，什么都不发出去。正文最后一行不是 `🤖 Made with <agent> (<model>)`（可写一个或多个逗号分隔的组合）的同样直接拒绝：每个 PR 都要写明做出它的每个 AI 和模型。这一行可以加一段 `reviewed by <agent> (<模型>)`，或用 `and` 连起两个审阅者，最多两个。评论（`gh pr comment`、`gh issue comment`）、带正文的审查（`gh pr review --body`）和新建的 issue（`gh issue create --body`）也要带上这一行：最后一行不是 `Made with` 行的正文同样直接拒绝。带 `--attach` 的正文会被拒绝：上传的文件会落在 `Made with` 行之后。只改正文的编辑（`gh pr edit --body`、`gh issue edit --body`）保持原样。`gh pr create` 没给类型标签或 tier 标签时，bot 会自动补上：类型看标题（`fix` 补 `bug`），tier 看正文第一行的 `tier:`。`gh pr edit` 换了标题或正文时也会这样补，并在正文给出新 tier 时移除旧的 tier 标签。你自己给的标签保留不动。你给的 tier 标签和正文对不上，就直接拒绝，提示和 `check --pr` 一样。仓库里没有的标签，bot 会说一声然后不加；它从不新建标签。`gh pr create` 没带 `--milestone` 时，bot 会用第一个关联 issue（`Closes #N`、`Part of #N`）的里程碑；这些 issue 都没有里程碑，就直接拒绝，什么都不发出去，请自己加上 `--milestone`。gh 读不到那个 issue 时，PR 照发，不带里程碑，只留一条提示。它只运行 `gh` 和 `git`，所以 `env gh …`、`sh -c …` 这类写法会被拒绝；gh 的别名（alias）它不认，别名里的标题不会被检查。设置方法见 [server.md](https://github.com/yokoten-labs/yokoten/blob/v0.1.2/docs/server.md#3-the-bot-a-github-app) |
| `train [<pr>...] [--wait <秒>] [--next]` | 按给出的顺序，以 bot 的身份合并一批已批准、检查全绿的 PR；第一个需要人来处理的 PR 就让它停下。不给 PR 编号时只按冲突地图给出的顺序打印计划，什么都不合并；要合并就写出编号。顺序是：先合并移动路径的改动，单独进行；再合并不与其他改动冲突的；然后合并被最多改动冲突的那个，最旧的优先。第一次合并之前先记下每个 PR 的基础分支，所以叠在被 squash 合并的分支上的 PR，会用它和那个分支真正共有的基础来合并。只涉及 import 行的冲突取并集；真正的冲突、测试失败、`check` 失败或缺少批准都会让列车停下。每次合并后的树先测试再推送；推送后的合并要等 CI 通过；`--wait` 设等多久，默认 1800 秒。`--next` 只说出下一个 PR 是哪个、为什么，不合并。只有 lead 能跑 |
| `kanban [--text] [--json] [--no-open]` | 旧名字 `board` 照样能用，先打一行告诉你现在叫 `kanban`。只在这台电脑上开一个只读页面：各个会话、等你处理的 PR，以及 Actions。页面上没有任何东西能启动、停掉或改动工作。启动后自己在浏览器里打开页面并登录好，按 Ctrl-C 停掉（加 `--no-open` 就只打印地址）。登录用的密钥只交给浏览器。加 `--text` 就在终端里用纯文本打印同一份快照，加 `--json` 就打印成数据。见「看板」 |
| `tidy [--local \| --empty [--yes]] [--json]` | 只打 `tidy`：列出旧东西，按你能做的事分组。旧东西指已合并或远端已删的分支、没活了的 worktree 和工位、工位已删但还留着的日志。每行写原因和大小，什么也不改（加 `--json` 就把列表当数据打印）。`--local`：先给每样东西存一份（分支打成 bundle，没提交的改动打成 patch，再写 `index.json`），然后搬进 `~/.local/state/yokoten/trash` 里按时间命名的文件夹。只要有一份没存成，就一样也不搬。正在用的工位和 worktree 不碰。GitHub 上的分支只列出来。`--empty`：问你一句，再删回收站里放了 7 天以上的文件夹；没有终端时要加 `--yes` |
| `conflicts [--json] [--rebuild <id>]` | 读取每个打开的 PR；没有代码托管时，读取每个本地分支。用平实的话说明哪些改动和 main 冲突、哪些改动互相冲突。它会说出只碰同一个文件的那几对，以及这一对里先合哪个。它不检出、不合并。结果放在 git 目录里，guard 不用跑 git 就能读到。加 `--json` 打印整张地图；加 `--rebuild` 打印一段提示，让 AI 在最新的 main 上重做某个改动 |

每个命令后面加 `--help` 或 `-h`，就会打出它自己的说明：做什么、有哪些选项、一个例子。
`yokoten help <命令>` 打出的也一样。`yokoten help` 只列开头要用的四个命令 `desk`、`kanban`、`check`、`tidy`，`help --all` 列出所有命令；搬走的命令照样能跑，先在 stderr 打一行说搬到了哪里。`yokoten version`（或者
`--version`、`-v`）打出版本号。看说明不会改任何东西，
退出码是 0。命令或选项打错了，yokoten 会提示最接近的那个，比如 `yokoten docter` 会说
`unknown command docter`，再给一行 `Next: \`yokoten doctor\`, if you meant it; \`yokoten help\` lists every command`。`bot --` 后面的词都交给要运行的命令，所以
`yokoten bot -- gh --help` 运行的是 `gh --help`。

`check`、`doctor`、`hook`、`explain`、`adopt` 都可以加 `--json`。加了以后，标准输出里只有 JSON，
别的什么都没有，方便 AI 和脚本读。出错时也一样：用法写错了或者程序出错，标准输出是
`{ command, ok: false, error }`，退出码是 1，错误信息照样打在标准错误里。每条结果的字段都一样：`id`、`what`、`why`、`fix`；
指南里讲到这条规则的，再加 `docs`（链接）；问题出在文件里的，再加 `file` 和 `line`。
拿 `id` 去跑 `yokoten explain` 就能看到详细说明。任何字段里都不会有隐私内容。

- `check --json`：`{ check, ok, findings, notes }`。`check` 是 `tree`、`staged`、`history`、
  `pr` 或 `issue`，说明查的是哪一种。`notes` 只是提醒，不会让检查失败。CI 这次跑的不是
  PR 或 issue 时，`check --pr` 和 `check --issue` 直接算通过，多一个 `skipped` 写明原因。
- `doctor --json`：`{ version, ok, selfTest, repo, stage, stages, next, findings, steps,
  notes, server, lessons }`。教训查出来的问题放在 `notes` 里，第一次见到的教训带 `isNew: true`。加 `--session` 时只给下一步、私有词表缺失的提醒，以及核心规则落后时的 `core-rules`；有只在这台机器上的活，或者会话刚听到的某一课查出的问题时，放在 `notes` 里。
- `hook --json`：拦下时是 `{ blocked: true, finding }`，放行时是 `{ blocked: false, toolCall: true }`；
  标准输入读到的不是工具调用时，是 `{ blocked: false, toolCall: false }`。AI 工具本身不用它：
  它们按各自的 hook 协议，看退出码，再从标准错误读原因。
- `hook --stop --json`：`{ blocked, stop, findings }`。`stop_hook_active` 为真，或者检查本身出错时，
  多一个 `skipped` 写明原因。读到的不是 Stop 事件时，是 `{ blocked: false, stop: false }`。
- `explain --json`：`{ found, id, kind, what, why, check, docs, stage, standard }`。id 不存在时是
  `{ id, found: false, ids }`，退出码 1；没写 id 时是 `{ ids }`，退出码 0。

输出的规矩只有一条：结果写到标准输出（stdout）；拒绝、出错和 `check` 查出的问题写到标准错误（stderr）；`doctor` 那条路上的一行（比如 MISSING）是结果的一部分，仍在 stdout。
不认识的选项会直接拒绝，不会悄悄忽略。

`init`、`update`、`doctor`、`check`、`adopt` 和 `bot` 都要在 git 仓库里跑。不在仓库里，
它们会直说，什么文件都不写，退出码是 1。`init <dir>` 碰到不存在的文件夹也会拒绝。
`doctor --session` 在仓库外不出声，免得打扰在别的文件夹里开的会话。

CI 没过，不用再翻日志找原因。`check` 在 GitHub Actions 里跑的时候，每个问题都会标在 PR
改动的那一行上；任务摘要（job summary）里也会列出来：在哪、哪条规则、出了什么问题、
为什么、怎么改。规则名可以点，点开就是指南里讲它的那一节。
只是提醒的，显示成黄色警告，不会让检查失败。和平时的输出一样，这里只说问题在哪，
不会把查到的内容打出来。

## 退出码

| 退出码 | 意思 |
|------|------|
| 0 | 一切正常。路线图上还没做的步骤只是待办，不算问题，`doctor` 照样给 0。`doctor --session` 永远是 0，保证会话总能开始 |
| 1 | 查出了问题：`check` 查出的，或者 `doctor` 查出的（自检失败，或者 core-rules、tree、本机这几项标着 MISSING）、id 不存在（`explain`）、命令用法不对，或者 yokoten 自己出错 |
| 2 | hook 拦下了这次工具调用，或者收尾检查把 AI 叫回来了，原因在标准错误里 |

这几个数字定下来就不改。yokoten 自己出错时给 1。可是三个 AI 工具都只认 2 才拦，守卫一崩，
调用就放过去了。所以 `init`、`install` 和插件写的工具调用 hook 会把别的退出码都改成 2：
守卫崩了、内存爆了、被杀掉了，这次调用都会被拒绝，标准错误里有一行
`` yokoten's guard failed (exit N), so the command is refused; run `yokoten doctor` ``。
标准错误里只有这一行，node 自己的报错和 shell 打的「Killed」都不显示。
有两种情况还是 1：运行时的文件夹没了，或者 AI 工具的 PATH 里找不到 `node`。这时 hook
说一行提示就放行，免得每次工具调用都被拦住，你还能跑 `yokoten doctor` 看是怎么回事。
收尾检查和会话开头的 doctor 照旧，出错就原样交出去。

AI 工具还有一条：hook 跑得比它的超时还久，工具就直接放行。所以守卫读一次调用超过 5 秒
（可以用 `YOKOTEN_GUARD_DEADLINE_MS` 改，最多 30 秒），或者读的时候出错，都会拒绝这次
调用，给 2（`unreadable-command`）。

## 本机提示

有些毛病出在电脑上：它一来，所有 AI 会话都停了，AI 自己却看不见。
完整的 `doctor` 把它们列在 `machine:` 下面，都是提示，不影响退出码。
查哪些、为什么这样查，见[记录 0012](https://github.com/yokoten-labs/yokoten/blob/v0.1.2/docs/records/0012-machine-checks.md)（英文）。

- **disk-space**：放仓库的那块盘快满了。盘一满，所有会话都会停。剩下不到 20 GB、
  而且不到整块盘的 10% 时，doctor 才提醒。想换个线，就设 `YOKOTEN_MIN_FREE_GB`；
  设成 `0` 就不提醒。会话开头也会说这一条。
- **host-disk**，只在 WSL 2 里查：装着 Linux 磁盘文件的那块 Windows 盘。在 WSL 里用 `df`
  看到的是磁盘文件的大小，不是那块 Windows 盘还剩多少。完整的 `doctor` 用 `reg.exe`
  找到那块盘再去读，最多等 3 秒。读到的结果存在 `~/.config/yokoten/machine.json`。
  会话开头不跑任何程序，只在上次读到的偏少、而且不超过 7 天时再说一遍。想重新读，就跑
  `yokoten doctor`。没有 `WSL_DISTRO_NAME` 时（比如用 `sudo` 跑），doctor 找不到那块盘，
  就不提它。虚拟机的磁盘文件只会变大；在虚拟机
  里删文件，Windows 盘上一点空间也空不出来。所以提示会说：先把工作备份到另一块盘，再清理
  Windows 盘，或者照[微软的说明](https://learn.microsoft.com/windows/wsl/disk-space)把磁盘
  文件压小、搬走。yokoten 不推荐 WSL 的稀疏模式（sparse），因为 WSL 自己把它标成了不安全。

- **agent-temp-dir**：Claude Code 的临时文件夹 `claude-<uid>` 归了别的用户。这个文件夹
  放在 `CLAUDE_CODE_TMPDIR` 里；没设的话放在 `TMPDIR` 里，默认是 `/tmp`。
  常见的情况是重启以后，某个 root 进程先把它建了出来。这时 Claude Code 会拒绝启动。
  提示里会给出 `sudo rm -rf '<文件夹>'`（路径带引号），你自己跑；doctor 从来不删东西。
- **leftover-temp-folders**：临时文件夹里有放了超过一天的 `yokoten-*` 文件夹。它们是跑测试
  剩下的，攒到几千个就会把盘占满。完整的 `doctor` 只数个数，不算大小，所以很快。提示里会给出
  一条 `find ... -exec rm -rf` 命令，只删超过一天的那些，你自己跑；doctor 从来不删东西。
- **node-security**：跑 yokoten 的 Node 没有 2026 年 10 月 21 日的安全修复。版本比所在系列的修复版
  （22.23.4、24.22.1、26.12.1）旧，就有已知漏洞；23、25 和 22 以下的系列已经不再修漏洞，所以不支持。
  提示会告诉你该装哪个版本。只有完整的 `doctor` 会看，也从不算失败。

## 出了问题怎么办

**AI 工具打不开。** 它打不开，也就没法告诉你为什么。在项目里开一个终端，跑
`npx yokoten doctor`，看 `machine:` 下面那几行。最常见的是盘满了，或者临时文件夹归了
root。每条提示都写着修好它的命令。

## 收尾检查

工具调用 hook 只看命令。AI 用 `sed` 或脚本改了文件，它看不到。提交检查看得到，但要等到
提交的时候。收尾检查更早：AI 说完这一轮、准备停下时就跑。在 Claude Code 和 Codex 里是
Stop 事件，在 Gemini CLI 里是 AfterAgent。

```mermaid
flowchart LR
  S["AI 准备停下"] --> A{"已经被叫回来过一次？"}
  A -- 是 --> E["停下"]
  A -- 否 --> C{"改过的文件有问题？"}
  C -- 没有 --> E
  C -- 有 --> B["AI 拿到问题清单，回去改"]
```

- 只查改过的文件：已暂存的、没暂存的，还有 git 没忽略的新文件。用的是 `check --staged`
  那一套：个人信息、密钥、不该改的文件。
- 只叫回来一次。叫回来以后 AI 工具会把 `stop_hook_active` 设成真，下次停下就一定放行，
  不会来回打转。
- 不读历史，不联网。
- yokoten 自己出错时直接放行，只在标准错误里写一行。
- 有个新文件你想留着、不想被查，就把它写进 `.gitignore`，或者写进 `yokoten.json` 的
  `privacy.skip`。
- yokoten 读不了的 `.yokoten-agent.json` 保持原样，收尾检查会说明原因。

每次拦截也会写进这台电脑上的一份记录。工具调用守卫拦下一次调用，或者收尾检查把 agent 叫回来时，
yokoten 往 `~/.config/yokoten/stops.jsonl` 写一行：UTC 时间、运行环境、角色、规则，还有结果
（`refused`；worker 试满次数后放行的 `allowed once`；守卫读不出这次调用的 `no answer`）。
命令只在隐私检查把它遮好之后才写，而且只写第一行；改文件的调用不写路径，也不写文件内容。
记录只留在这台电脑上，yokoten 不会发到任何地方。超过 5 MB 时先删最旧的行。`yokoten stops off`
停止写入并删掉记录，`yokoten stops on` 重新开始；`yokoten uninstall` 也会删掉它。

`init` 会把它写进 `.claude/settings.json`、`.codex/hooks.json` 和 `.gemini/settings.json`。
`install` 不在用户级加它：它要读文件，所以由
每个项目自己打开，跟工具调用 hook 里管文件的规则一样。

提交检查不让你提交 `dist/`、`build/`、`out/` 文件夹里的文件，也不让你提交二进制文件。
这些文件每次构建都会重新生成，二进制文件审查的人也看不懂。图片和字体（`png`、`jpg`、`gif`、
`webp`、`svg`、`ico`、`woff`、`woff2`）可以提交。某个文件确实要留在仓库里，就在 `yokoten.json`
里写上它的路径（可以用通配符）：`"allowBuild": ["docs/manual.pdf"]`。

## 禁用的命令

项目对技术栈的约定，比如「不用 Python」「不用 yarn」，agent 干到一半很容易就忘了。只写在
AGENTS.md 里，它就只是个愿望。把这些程序写进 `yokoten.json`，每个配上原因，工具调用守卫就会替你守住：

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

这样一来，不管是 lead 还是哪个 worker，只要命令里运行了 `python3`，守卫都会拦下来。提示里会写
是哪个程序、你写的原因，并让 agent 改用项目自己的技术栈。

- 守卫只看每条命令真正运行的程序，按名字认：`/usr/bin/python3` 和 `python3.exe` 都算 `python3`。
- 命令的每一段都会查：`a && b`、`a | b`、`a; b`、`$(...)`，还有交给 shell 运行的字符串：
  `bash -c '...'`、`ssh host '...'`、`flock file -c '...'`。
- 前面加了变量或重定向、套了一层或者放在流程语句里也藏不住：`X=1 python3`；`2>/dev/null python3`；
  `env`、`sudo`、`nice`、`timeout`、`xargs`、`setsid`、`stdbuf` 这类启动器；`if`、`for`、`while`、`!` 后面的命令；
  `find -exec` 运行的程序；`env -S` 里的字符串。
- 只是参数里出现这个名字的不拦：`echo python`、`cat python.md`、`pythonic`，
  还有 `case` 里的匹配项，比如 `python3)`。
  `command -v python3` 也不拦，它只是查程序在哪儿，并不运行。
- 名字里不能带路径，也不能有空格。`commands` 写错了格式，跟 [yokoten.json](https://yokoten.app/zh/docs/guide/install-and-commands.md#yokotenjson)
  里别的设置写错一样，算配置错误；守卫这时不拦任何程序，但会说一声，核心检查照常运行。

守卫读的是命令的文字。脚本、`make` 目标、npm 脚本或者 shell 函数在里面运行的程序，它看不到。
想拦哪个版本就写哪个：`python3.12` 不算 `python3`。

## yokoten.json

项目的设置都写在仓库根目录的 `yokoten.json` 里。每一项都可以不写。不写的项、写成 `null`
的项，或者整个文件不存在，都按默认值来。

| 设置 | 该怎么写 | 默认值 |
|------|----------|--------|
| `layout` | `{ "maxEntries": 7, "exempt": ["docs/records"] }`：一个大于 0 的整数，加一串文件夹或通配符 | 7，没有例外的文件夹 |
| `tests` | 一串通配符 | `*.test.*`、`*.spec.*`、`*_test.go`、`test/**`、`tests/**` |
| `leadOnly` | 一串通配符 | 规则、agent 配置、钩子和 CI |
| `privacy` | `{ "allow": [], "skip": [], "utc": true, "output": "run" }`：两串文字，一个 true 或 false，再加 `"run"` 或 `"stop"` | 跳过锁文件，要求 UTC，照常跑 |
| `notNow` | 每个步骤名配一个原因：`{ "ci": "no remote yet" }` | 没有 |
| `allowBuild` | 一串通配符 | 没有 |
| `areas` | 一串单词 | 任何小写单词都行 |
| `translations` | `{ "fail": true }` | `false` |
| `commands` | `{ "block": { "<程序>": "<原因>" } }` | 不拦任何程序 |

yokoten 拿这张表统一检查每一项：

- 某一项格式不对，比如 `"leadOnly": "x"`，就是配置错误。`check` 和 `doctor` 退出码是 1，
  提示里会把写错的每一项都列出来，并说明应该怎么写，跑一次就能看全。
- 工具调用钩子遇到这种情况，这一项就用默认值，在 stderr 上用一行字说明，核心检查照常运行。
  如果只是对象里的某个字段写错了，比如 `layout.maxEntries`，就只有这个字段用默认值。
  `commands` 写错了，整个禁用列表都不算。
- `yokoten.json` 本身写坏了，不是合法的 JSON，或者最外层不是一个 JSON 对象，`check` 和
  `doctor` 也会报配置错误；钩子这时全部按默认设置干活，用一行字说明，核心检查照常运行。
- yokoten 不认识的设置项，`check` 只给一条提醒，不算错误。这样给新版 yokoten 写的配置，
  在旧版上也能用。对象里不认识的字段也一样只给提醒，比如 `"commands": { "blok": ... }`。

## issue 标题

issue 标题先写改哪一块，再写要做什么。这样一排 issue 看下来，就像以后要合进来的一排提交。

- 任务：`模块: 动词开头的短语`，比如 `doctor: add the review step`。
- 事故（incident）：`模块: 看到的现象`，比如 `check: misses a key in a JSON file`。写你看到了什么，
  别写打算怎么修。
- 最多 60 个字符，结尾不加句号，标题里不写 `#N`，要提别的 issue 就写在正文里。
- 模块名全小写，一个词；要用几个词就用连字符连起来。

`init` 会加上 `.github/workflows/issue.yml`。有人开 issue 或改 issue 时，它跑一次
`check --issue`。它只读不写：不会去 issue 下面留言，也不会帮你改标题。跑红了，作者自己改标题就行。

agent 起的标题在发出去之前就查。在有 `yokoten.json` 的仓库里，用 `gh issue create|edit` 或
`gh pr create|edit` 带标题时，不管是直接用 gh 还是经过 `yokoten bot`，工具调用守卫都跑同一套检查。
标题不对，命令就被拦下，CI 不会看到它。用户级的 hook 不查标题：别的项目有自己的规则。

默认任何小写的模块名都能过。想限定只用哪几个，就在 `yokoten.json` 里列出来：
`"areas": ["doctor", "check", "docs"]`。列了以后，PR 标题 `type(area): summary` 里的模块名也只能用这几个。

## 更新日志

PR 改了用户能感觉到的东西，就加一个小文件：`changes/<issue>-<slug>.md`，不去改
`CHANGELOG.md`。要是每个 PR 都往同一个文件的开头加一行，合进去一个，别的 PR 就全冲突了。
每个 PR 各写各的文件，就不会冲突。

文件就是普通的 Markdown。第 1 行写分类：Added、Changed、Deprecated、Removed、Fixed、Security
或 Internal。第 2 行空着。从第 3 行开始写条目，写成它在更新日志里该有的样子。前面的 `- `
会自动加上。一个文件里写好几条也可以。条目里不能有标题；文件直接放在 `changes/` 下面，
不要放进子文件夹，也不要用链接。

```markdown
Fixed

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

`yokoten changelog` 只把这些文件会加什么列出来，不写任何东西。发版前跑
`yokoten changelog --write`：每一条放进 `[Unreleased]` 里对应的分类下面，用过的文件删掉。
已经发布的版本它不碰。有文件格式不对，它会说是哪个文件、错在哪，一个字都不写。

`check --pr` 把 `changes/` 里新加的文件当成这个 PR 的更新日志，还会检查格式。直接改
`CHANGELOG.md` 也照样能过，还没换过来的项目不受影响。`check` 也用同样的读法检查 `changes/`
里的文件，`check --staged` 查暂存的那些。这样格式不对的文件在提交时就被 git 钩子拦下，不用等到
CI。暂存的变更文件里有太长的句子，它也会提醒。还没加进 git 的变更文件也算，跟 `changelog` 一样。`init` 会加上 `changes/README.md`，
里面有格式说明。`changes/` 里的文件可以超过 7 个，因为每次发版都会清空。

## 翻译

英文是原文，译文跟着英文走。英文改了，译文就落后了，可往往没人发现。所以每个译文文件的第 1 行
都写明它是从哪个英文文件翻的，用 HTML 注释写，GitHub 上看不到。

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

路径是英文文件的路径，从仓库根目录算起。只有相对路径才算：像
`<!-- source: https://example.com/post -->` 这样注明出处的注释，或者写的是绝对路径，
`check` 都不管。这一行写一次就行，以后不用再改。改英文时，把译文也一起改了，放在同一个
commit 或同一个 PR 里。

`check` 会读每个 Markdown 和 HTML 文件的第 1 行，然后看当前分支的提交（只沿第一个父提交
往回找，找到加进译文的那次提交为止）。有一个提交改了英文、没改译文，译文就落后一个 commit。
只有后面一个只改译文的提交，才算跟上。英文和译文一起改的提交没问题，但它盖不住前面只改英文
的提交。所以一个 PR 只改了英文，另一个 PR 两个都改了，不管谁先合，前一个都会被提醒。译文改了
哪一行都算。

为什么不在这一行记下英文的版本？那样的话，每个改英文的 PR 都得改这一行，两个这样的 PR
一定会在这一行上冲突。这一行不变，几个 PR 同时改，也只会各改各翻的那几段。

代价有两个。一是只改译文的提交，算作跟上了它前面所有的英文改动。在译文里改个错字，可能就把
一处没人翻的英文改动盖过去了。所以改错字之前先跑一下 `check`，如果它说译文落后，先把译文
补上。二是用 squash 合并时，只改英文的 PR 会一直被提醒，直到有一个只改译文的提交。就算后面
的 PR 改自己那部分时顺手把它也翻了，提醒也还在。想消掉提醒，就单开一个只改译文的 PR。

工作区里还没提交的改动也这样算：只改了译文，就当作更晚的改动；只改了英文，译文算落后；两个
都改了，就和一个两个都改的提交一样。

译文落后时，`check` 给一条 `translation-behind` 提醒：落后了几个 commit，怎么看英文改了
什么（`git diff <commit> HEAD -- docs/guide.md`）。提醒不会让 CI 失败。想让它失败，就在
`yokoten.json` 里写 `"translations": { "fail": true }`。

旧的写法在第 1 行还多写了英文的 blob：
`<!-- source: docs/guide.md blob 3b18e512dba79e4c8300dd08aeb37f8e728b8dad -->`。
`check` 照样认。blob 和英文对得上，就算最新；对不上，就按上面的提交来判断。blob 随时可以删掉。

浅克隆里只有最近的几个提交。这些提交够用时，`check` 照常判断；看到的只改英文的提交，就报
「至少」落后几个 commit。不够用时，`check` 整次运行只给一条提醒，说有几份译文没检查，
不会每个文件都报一条。就算设了 `"fail": true`，这条提醒也不会让 CI 失败。`check` 从不
fetch。CI 里 checkout 时设 `fetch-depth: 0`，kit 里的 `yokoten.yml` 已经设好了。

## 借来的代码

记录 0024 说明 yokoten 什么时候可以复制别的项目的代码。复制来的一小块叫**轮子**。轮子
放在 `src/borrowed/`，永远不作为依赖。它很小：只放我们需要的那部分。轮子里不是本项目
自己的代码，所以读者必须能看出它从哪来。

只在宽松许可证下复制：MIT、BSD-2-Clause、BSD-3-Clause、ISC 或 Apache-2.0。其他许可证
走净室路线：一个 agent 只写代码做什么，另一个没看过源码的 agent 根据这份说明写代码。

每个轮子开头都有一段注释文件头，放在任何代码之前：

```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
```

| 字段 | 是否必需 | 内容 |
|------|----------|------|
| `source` | 总是 | 上游文件的网址，形如 `github.com/<owner>/<repo>/blob/...` |
| `commit` | 总是 | 取代码时上游提交的完整 40 位 sha |
| `license` | 总是 | SPDX 名称：`MIT`、`BSD-2-Clause`、`BSD-3-Clause`、`ISC` 或 `Apache-2.0` |
| `copyright` | 总是 | 上游的版权声明，逐字保留 |
| `notice` | Apache-2.0 | 上游的 NOTICE 文本 |
| `changes` | 否 | 我们改了什么，让读者分得清我们的修改和上游的代码 |

每个字段占一行：`// 字段: 值`。文件头必须在最前面，在代码之前。只有第一段注释算数。
同一个字段写两次时，第一个值生效。

`yokoten check` 会读 `src/borrowed/` 里每个文件的文件头，既查整棵树，也查即将提交的
改动。每个缺失的字段都会报告，并给出文件和要补的那一行。文件头不对，提交就过不去。
来源不是网址，或者提交不是完整 sha，也会被拒绝。

五种之外的许可证会被拒绝，即使文件头是完整的。修改办法是
`use the clean-room road in record 0024`（走记录 0024 的净室路线）。检查不认识的许可证
名称也一样。

`borrowed` 工作流每周一运行 `yokoten borrowed`。对每个轮子，命令问 GitHub：上游默认分支
上，哪些提交改动过这个源文件。如果有晚于我们所取提交的提交，上游就变了。

命令**每个来源开一个 issue**，不是每个文件一个。它会先找已经提到这个来源的未关闭 issue，
所以之后的运行不会重复开。issue 里列出本地文件、我们取的提交，以及上游更新的提交。提交
说明里提到安全修复时，标题是 `check: take a borrowed security fix`；其他变化用
`check: review borrowed code upstream`。

主机回答不了的来源是一个缺口，不是通过。任务会说明是哪个来源，并以失败结束，免得有人
把沉默当成「上游没变」。只想看报告、不开 issue，运行 `yokoten borrowed --dry-run`。

上游修了 bug，就取新的提交：更新 `source` 和 `commit`，重新应用我们的 `changes`，让测试
通过。如果变化没有碰到我们用的那部分，在 issue 里说明并关闭它。不再用某个轮子时，删掉
那个文件。退路也是出路：这段代码很小，测试说明了它必须做什么（记录 0014）。

## 更新规则

yokoten 每学到一个教训，核心规则就会改。`yokoten install` 会更新你电脑上的技能和 agent。
项目里的 `AGENTS.md` 是你自己的文件，你不开口，它就不会变；有了 `## This project` 以后，`init` 也不会再去动它。

`doctor` 会拿 `AGENTS.md` 里 `## This project` 上面的部分，跟你正在用的 yokoten
自带的核心规则比。两边不一样，它就报 `core-rules`，会话开头也会提一句。

`yokoten update` 只把要改的地方列出来，不写任何东西：哪几节变了，每一行是加（`+`）
还是删（`-`）。跑 `yokoten update --write` 才会替换核心规则那部分。从 `## This project`
往下的内容一个字节都不动。

核心规则里你自己手改过的行，也会显示成 `-`，因为更新会把它换掉。所以先把你自己的规则
挪到 `## This project` 下面。要是文件里没有这个标题，yokoten 分不清哪些是核心规则、
哪些是你的，它会直接告诉你，不比也不写。

`AGENTS.md` 第一行记着核心规则是哪个版本的 kit 写的，比如 `<!-- yokoten kit 0.1.0 -->`。
这是一行注释，GitHub 上看不到。`init` 会写上它，`update --write` 会换成新版本号。
要是项目里的版本比你正在用的 yokoten 还新，`update` 会拒绝，什么都不写，免得把规则
退回旧版。这时 `doctor` 会让你升级 yokoten 本身，而不是去改项目。没有这一行的文件，
就只按内容比。

`.yokoten/` 里那份也带着 kit 的 `AGENTS.md`，还有 kit 里的课，所以 CI 和钩子里跑 `doctor`，比的就是项目
当初拷进来的那版 kit。想换成更新的规则，就用装好的 yokoten、插件或者 clone 下来的仓库跑
`update`。用它们再跑一次 `init`，`.yokoten/` 也会一起更新。直接用 `.yokoten/` 里那份跑 `init`，
那份会原样留着，不会更新。

装了 Claude Code 插件的话，直接用 `/yokoten:update`。它跑的是插件自己那份 yokoten，
先给你看要改什么，你点头了才写。

## 从事故里学

出了一次事故，就是学到了一课。`yokoten learn` 把这一课写成一条检查，最后每个项目都会跑它：

```mermaid
flowchart LR
  A[事故 issue] --> B[yokoten learn]
  B --> C[检查草稿]
  C --> D[人来审]
  D --> E[learn --ship：放进 kit]
  E --> F[每个项目的 doctor]
```

后面跟 issue 编号就行；别的仓库的 issue，就给它的网址。它用 `gh` 读 issue，走的是你自己的登录。
它不调用任何 AI 模型，草稿里的内容全部照着 issue 里的几节来填：

| 草稿里的字段 | 从哪来 |
|---|---|
| `id` | `incident-<编号>` |
| `what` | 标题里 `area: ` 后面那部分 |
| `why` | 「Why it matters」那一节。没有这一节，就先拿「Why it got through」顶上，再留一个 `TODO(reviewer)` 让人改写 |
| `fix` | 「Fix」那一节 |

issue 里没写的那一节，对应的字段就留一个 `TODO(reviewer)`，绝不替你瞎编。issue 的原文也会放进注释里，
审的人不用来回翻。

`yokoten learn 138` 只把草稿打出来，什么都不写。`yokoten learn 138 --write` 会在
`lessons/incident-138/` 里加两个文件：

- `lesson.ts`：这条检查要报的内容，外加一个空的 `findProblems`，检查本身要人来写。
- `lesson.test.ts`：一个「阳性对照」测试：故意喂给检查一个有问题的例子，要求它能抓到。

检查没写好之前，这个测试一直是红的，所以草稿自己永远过不了。`--write` 只加文件，
已经有的文件不会动。

`findProblems` 拿到的是一个仓库的文件夹，在哪里发现问题，就为那个地方报一条。它只读文件：
不联网，也不改任何东西。要写得快一点，因为每个项目跑 `doctor` 都会跑它。

审完了，就把这一课发出去。在 yokoten 的仓库里跑 `yokoten learn --ship incident-138`，
它会把这一课搬进一个编了号的分组，比如 `kit/lessons/1/incident-138/`。分组是为了让每个文件夹
最多 7 项：一个分组放 5 课，再留 2 个位置给下一层分组，新的一课放进第一个还有空位的分组。
然后在教训表 `kit/lessons/README.md` 里加一行：编号、放在哪个文件夹、出自哪个事故、哪个 yokoten 版本加的。只要还有 `TODO(reviewer)` 没填，或者测试没过，
它就不搬，什么都不动。yokoten 自己也有一个测试，对 kit 里的每一课都查这几条。
之所以做成命令而不是让人手动 `git mv`，是因为搬文件、加表格、跑测试要一次做完，不会只做一半。

项目跑的是哪份 yokoten 的 `doctor`，就查哪份 yokoten 里的课。每份 yokoten 只读自己的
`kit/lessons/`，不下载，也不同步。所以发出去以后，各处什么时候能查到这一课：

| 在项目里怎么跑 `doctor` | 什么时候有这一课 |
|---|---|
| 用刚发课的这份：`node path/to/yokoten/bin/yokoten.ts doctor` | 马上就有 |
| 用这台电脑装的：`yokoten doctor` | 在这份里跑一次 `node bin/yokoten.ts install` 以后 |
| 用项目自带的：`node .yokoten/bin/yokoten.ts doctor` | 用这份再跑一次 `init`，再把 `.yokoten/` 提交上去以后 |
| 别人用 npm 或插件装的 | 等 yokoten 发下一个版本，他们升级以后 |

`learn --ship` 跑完也会打出这几条。查到问题，就在 `doctor` 的报告里给一行 note：

```
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
```

项目里第一次跑完整的 `doctor` 碰到一课新的，会单独说一次，哪怕没查出问题：
`note    new lesson: incident-138 (<what>); doctor now runs it in this project`。
见过哪些课记在 `.git/yokoten/state.json` 里，这个文件不会被提交。

会话开头也会听到新的一课，只说一次，前提是它在这里查出了问题。就一行，还写明用哪条命令看在哪里：
``yokoten: a new lesson learned in another project found a problem here: <what>. Run
`yokoten doctor` to see where.`` 之后的会话，或者同一个会话压缩上下文之后，都不会再说。
每一课只在某一次会话开头跑一回，跑过没有也记在同一个文件里，所以以后的会话开头不花时间在这上面。
之后才出现的问题，留给完整的 `doctor` 去报。

提醒不会让 `doctor` 失败。某个项目不想查这一课，就在 `yokoten.json` 的 "notNow" 里写上它的编号和理由，
比如 `"notNow": { "incident-138": "a setup file sets it" }`。

**自己走一遍。** 在 yokoten 的仓库里跑 `node scripts/demo/loop.ts`，大约一秒就把整个流程走完：
出事故、起草稿、没审就发被拒、人来审、发进 kit，最后另一个项目的 `doctor` 给出提醒。
全程在临时文件夹里做，不联网（用的是假的 `gh`），做完就删。想留下来看看，加 `--keep`，
它会打出文件夹在哪里。换成真的事故也是这几条命令：在 clone 下来的 yokoten 里，用你自己的
issue，写你自己的检查。

## 问题出在 yokoten 自己身上

有时候毛病不在你的项目，而在 yokoten：守卫漏了一种情况，或者某句提示看不懂。在哪儿发现的，就在哪儿记一笔：

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

这条记录存在你自己电脑上的 `~/.config/yokoten/upstream/`，不在任何仓库里。
`--area` 和 `--fix` 可以不写；不写 `--area`，就记成 `unknown`。存之前先清理一遍：

- 项目名换成 `<project>`，不管怎么写：`secret-proj`、`secret_proj`、`SecretProj`、`secretproj`
  都算。不到三个字母的名字不换，不然会误伤太多词；它要是能认出你，就把它加进私密标记
- 指向项目里的路径，开头换成 `<project>`，绕过软链接的也一样
- 你的主目录换成 `~`，一行字把路径截断了也一样
- 你的名字、邮箱、gh 登录名、Linux 和 Windows 的用户名、私有主机和私密标记换成占位符，跟 `yokoten redact` 一样；
  邮箱会变成 `<email>`

带着常见密钥格式的，不存：提交检查认得的 token、URL 里的密码、带值的 `password=`、`token=`、
`api_key=`、JWT，还有 OpenAI 的 key。清理完还带着 IP 地址或私密标记的，也不存。yokoten 只说是
哪一类，不会把内容打出来。整个过程不联网，什么都不往外发。没有主目录（HOME 是空的）就什么都不存。

在 yokoten 自己的仓库里，会话开头和 `doctor` 会说有几条在等：
`yokoten: 3 lessons from other projects wait to become issues: yokoten learn --upstream --list`。
别的仓库看不到这一行。每条开一个 issue，开完用 `yokoten learn --upstream --done <id>` 删掉。

## 设置

项目的设置写在仓库根目录的 `yokoten.json` 里，每一项都可以不写。列表类的设置，你写了
就整个换掉默认值，不会接在默认值后面。

| 键 | 默认值 | 管什么 |
|----|--------|--------|
| `layout.maxEntries` | `7` | 一个文件夹最多放几项（第 10 条核心规则） |
| `layout.exempt` | 无 | 可以超过上限的文件夹，见[可以变多的文件夹](https://yokoten.app/zh/docs/guide/install-and-commands.md#可以变多的文件夹) |
| `tests` | `*.test.*`、`*.spec.*`、`*_test.go`、`test/**`、`tests/**` | 哪些算测试文件，修复任务不能改 |
| `leadOnly` | 规则、agent 配置、`.githooks/**`、`.github/**`、项目里那份 yokoten 副本 `.yokoten/**`、`.yokotenignore` | worker 不能改的文件 |
| `privacy.allow` | 无 | 隐私检查放行的原样字符串，比如公开的客服邮箱，或者私密词表里这个项目自己的名字 |
| `privacy.skip` | 各种 lock 文件 | 隐私检查不读的文件 |
| `privacy.utc` | `true` | 提交时间必须用 UTC |
| `privacy.output` | `"run"` | 在 Claude Code 以外的 agent 里，会打印账号信息的命令照常跑；设成 `"stop"` 就拦下，告诉 agent 过滤后的命令 |
| `notNow` | 无 | 暂时不做的步骤，用 `doctor` 里的名字，每条写上原因：`{ "ci": "no remote yet" }` |
| `allowBuild` | 无 | 提交检查放行的构建产物和二进制文件 |
| `areas` | 不限 | 标题里能用的模块名，见 [issue 标题](https://yokoten.app/zh/docs/guide/install-and-commands.md#issue-标题) |
| `translations.fail` | `false` | 译文落后于原文时，检查直接失败 |

### 可以变多的文件夹

第 10 条核心规则说，一个文件夹最多放 7 项。有两种文件夹可以超过，`changes/` 也一直可以：

- **一项一项往下记的日志**，比如 `docs/records`。每条加进去就留着。
- **测试用的第三方样例文件**，沿用上游的文件名，旁边放着它的 `NOTICE`。测试、`NOTICE`
  和给上游报的 bug 都靠文件名找文件。再分文件夹，查一个文件、从上游更新一次都更麻烦。

这样的文件夹要一个一个手写进 `layout.exempt`。每一项是从根目录起的路径，或者一个通配符。
通配符里，`*` 代表一层文件夹，`**` 代表任意多层，零层也行；别的字符都按原样匹配。
Rust 的 workspace 里，一条就能管到每个 crate：

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

哪条通配符一个文件夹都没对上，`check` 和 `doctor` 会提醒一句，过时的条目就看得见了。

环境变量：

| 变量 | 管什么 |
|------|--------|
| `YOKOTEN_ROLE=worker` | 这个会话是 worker，不能改只有 lead 能改的文件 |
| `YOKOTEN_LEAD_ONLY_OK` | lead 允许这个 worker 改的那几个文件，从仓库根目录写起，用逗号隔开。见[支持哪些 agent](https://yokoten.app/zh/docs/guide/works-with.md) |
| `YOKOTEN_FIX_TASK=1` | 这个会话是修复任务，不能改测试文件 |
| `YOKOTEN_RELEASE_APPROVAL=1` | 主人允许这个会话发版。没见过 Muse Code 把它传给钩子 |
| `YOKOTEN_PRIVATE_MARKS` | 再加一个私有词表文件。里面的词跟 `~/.config/yokoten/private-marks` 里的合在一起用，不会顶替它。见「隐私」 |
| `YOKOTEN_MIN_FREE_GB` | 盘上剩的空间少于这么多 GB 时，`doctor` 提醒；`0` 就不提醒。见[本机提示](https://yokoten.app/zh/docs/guide/install-and-commands.md#本机提示) |
| `YOKOTEN_BOT_APP_ID`、`YOKOTEN_BOT_KEY`、`YOKOTEN_BOT_INSTALLATION` | bot 的设置，也可以写成 `~/.config/yokoten/keys.env` 里的几行。见 [server.md](https://github.com/yokoten-labs/yokoten/blob/v0.1.2/docs/server.md#3-the-bot-a-github-app) |
| `YOKOTEN_GITHUB_API` | 换一个 GitHub API 地址，比如自建的 GitHub Enterprise Server |
| `YOKOTEN_PR_LIVE=1` | `check --pr` 用 `GITHUB_TOKEN` 去读 PR 现在的标题、正文、标签和里程碑，不读触发时留下的事件文件。读不到这些就失败。kit 里的 `pr.yml` 会设上它 |
| `LLAMA_API_KEY` 或 `MODEL_API_KEY`、`LLAMA_API_BASE`、`LLAMA_MODEL` | `adopt` 用的模型，见[先拿一份草稿](https://yokoten.app/zh/docs/guide/four-layers.md#先拿一份草稿) |

---

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