mcpField
I
isitdone

isitdone

本地服务
github.com开发工具12 周前更新

关于 isitdone

isitdone 是一款不使用 LLM 的 Stop 钩子和命令行工具,会在编码 Agent 结束前运行仓库测试、类型检查和 lint。它还可检测测试被削弱的改动、适配多种 Agent 钩子,并在拉取请求上执行相同检查。

主要功能

  1. 01在 Agent 停止前运行仓库检查
  2. 02检测削弱测试的改动
  3. 03在相关编辑后向 Agent 发出警告
  4. 04通过 GitHub Action 检查拉取请求
  5. 05生成与 Git 状态绑定的验证凭证

适用场景

  1. 01检查失败时阻止 Agent 声称完成
  2. 02在 Agent 编辑过程中发现测试被削弱
  3. 03将验证结果附加到拉取请求

MCP 相关

README 提到 isitdone 可作为没有停止钩子的 Agent 所用的 MCP 服务,但现有内容未说明其 MCP 工具。

配置

本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。

mcp.json · 通用配置
{
"mcpServers": {
"raimondasl-isitdone": {
"command": "npx",
"args": [
"-y",
"@aivolution/isitdone"
]
}
}
}
启动命令npx -y @aivolution/isitdone

本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。

安装包
npm@aivolution/isitdone0.9.0

工具

3

收录的工具列表 · 更新于 2 周前

参数类型必填说明
cwdstring否仓库的绝对路径,或仓库内某个目录的绝对路径。默认值:客户端的第一个工作区根目录;若无,则为服务器启动时所在的目录。
basestring否用于测试完整性扫描的 Git ref。默认值:HEAD,即未提交的更改。
claimstring否你即将提出的完成声明,需加引号;该声明会记录在凭证中。
strictboolean否即使检查通过,如果更改弱化了测试(发现高危或严重问题),也回答 NOT DONE。
profileenum否full(默认)会运行所有检查。lite 只运行类型检查和 lint,且绝不会报告 done: true。lite | full

详细介绍

isitdone

不要让你的编码 Agent 在测试真正通过前说“完成”。

isitdone 拦截虚假的“完成”:Stop 钩子运行真实测试,测试失败,Agent 修复问题,收据显示 DONE

根据一次真实运行(npm run demo)生成:Agent 的语句是旁白;钩子和 CLI 输出按原样捕获,仅缩短了临时路径。

isitdone 是一个零 LLM、零依赖的 Stop 钩子和 CLI,适用于 Claude Code、Codex CLI、Cursor、Gemini CLI、GitHub Copilot CLI、Qwen Code、Goose、Factory Droid、Devin、Augment、OpenCode 和 Junie CLI。当 Agent 试图以工作已完成为由结束本轮时,isitdone 会在完全相同的工作树上运行仓库的真实测试、类型检查和 lint 命令,扫描差异以检查测试是否被削弱,并在检查通过前拒绝停止。它还会在本轮中途告知 Agent 刚刚进行的编辑是否削弱了测试;作为 GitHub Action 在拉取请求上运行相同的验证;向没有 Stop 钩子的 Agent 提供它作为 MCP 服务;并生成一份与 Git 绑定的收据,供你粘贴到 PR 中。

npx isitdone init

无需 API 密钥。无需网络。无需遥测。只有退出代码。

它存在的意义

Claude Code 结束本轮并说:

完成。全部 4 项测试均已通过,身份验证重构也已完成。

Stop 钩子会在允许本轮结束前运行 isitdone。Agent 收到的却是:

isitdone: NOT DONE. 1 check failed on the current working tree (attempt 1/3).
You claimed: "All 4 tests pass and the auth refactor is complete."

  npm run typecheck  PASS
  npm run lint       PASS
  npm test           FAIL  1 failed, 3 passed

--- npm test (last 24 lines) ---
✖ failing tests:

test at test/auth.test.js:14:1
✖ is case-insensitive about the scheme (1.498ms)
  AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:

  null !== 'abc'
  ...

Fix the failures, then run `npx isitdone` and paste its output before claiming completion.
Do not skip, delete or weaken tests to make this pass; if a check is wrong for this repo,
say so explicitly to the user.

Agent 会继续工作。真正完成后:

$ npx isitdone
isitdone  main@2429c82  dirty (1 file)

  npm run typecheck  PASS  428ms
  npm run lint       PASS  411ms
  npm test           PASS  532ms

  DONE   receipt -> PASS (tree e5e47d9, 3 checks, 1.8s)
$ npx isitdone receipt --md        # paste into the PR
| check | result | time |
|---|---|---|
| `npm run typecheck` | PASS | 428ms |
| `npm run lint` | PASS | 411ms |
| `npm test` | PASS | 532ms |

Tree e5e47d9 on main@2429c82 (+1 uncommitted). Receipt: **PASS** · isitdone 0.1.0

再编辑一个文件,收据就会变为 STALE,直到再次运行检查。手动编辑的收据会显示 NONE。(npm 上的 isitdone 是 @aivolution/isitdone 的简短别名;两个命令运行的是同一个程序。)

这种情况到底有多常见?

在你自己的机器上测量。isitdone history 会读取磁盘上已有的对话记录:Claude Code(~/.claude/projects)、Codex CLI(~/.codex/sessions:包括旧版、分页版和 0.40 之前的 rollout,并遵循 /undo 回滚)、Gemini CLI(~/.gemini/tmp/<project>/chats,包括 .json 和 0.39+ 的 .jsonl 两种布局)、Qwen Code(~/.qwen/projects/<cwd>/chats)和 Cursor(IDE 的 state.vscdb 气泡存储,会在原处以只读方式打开,需要 Node 22.13+ 或 24 才能使用 node:sqlite;在 Node 20 上则会改为读取 agent-transcripts JSONL,其中不包含退出代码,报告会说明这一点)。它会找出 Agent 修改文件后声称已完成的每个轮次,并检查最后一次修改之后是否确实有测试命令通过。任何数据都不会离开你的机器;只会打印计数。

$ npx isitdone history --until 2026-09-07 --exclude <private project>
isitdone history  ~/.claude/projects  until 2026-09-07
  scanned 596 sessions in 6 projects; 1320 turns edited files; 495 of those ended with a completion claim; 1 project dir excluded
  claude-code  596 sessions  495 claims  35%   verified

  VERIFIED   35%   (173)   a test command passed after the last edit
  STALE      35%   (174)   tests passed, then more edits, no re-run
  FAILED     1%    (3)     the last test run failed, "done" claimed anyway
  NEVER RAN  29%   (145)   no test command in the turn at all

  65% of "done" claims (322 of 495) had no passing test run behind them.
  21 of the verified claims had a failed test run earlier in the same turn; only the last command passed, and it may have been a narrower one (--verbose marks them).

这是作者的真实结果,由 isitdone 0.8.2 于 2026-09-27 对 gate 安装前磁盘上已有的 Claude Code 会话进行统计:声称完成的日期为 2026-06-20 至 2026-09-07,使用 --until 2026-09-07 即可复现。一个私人项目通过 --exclude 排除,所有已发布的运行结果也都如此处理;除此之外,只将主目录缩写为 ~,并删除了末尾的逐项目统计行。首个已发布结果来自 isitdone 0.2.0,日期为 2026-09-07:516 条声称中有 69%(31% 已验证、37% 已过时、31% 从未运行;失败运行一行显示为 0%;按 0.2.0 的普通四舍五入方式计算,最多代表 2 条声称)。后续版本在相同对话记录上更严格地检测声称和轮次。自 0.8.1 起,history 会在每个占比旁显示计数(自 0.8.2 起,标题数字也会显示计数),并且任何非空行都不会被四舍五入为 0%;因此,现在显示 0% 就表示数量为零。欢迎发布你的结果。

安装 gate 后,仅靠这项统计会低估实际情况:验证是在 Stop hook 内完成的,而对话记录不会显示 hook 的运行是 Agent 执行的测试。因此,hook 会在本地记录自己的决策(.isitdone/decisions.jsonl:每次停止占一行,包含结果、最终消息是否声称已完成以及检查计数;不包含消息文本或路径,只有经过哈希处理的会话 ID),而 history 会补充其扫描项目中 gate 的执行结果。布局如下,数字仅作示例:

  gate  the isitdone Stop hook decided 132 stops in 2 projects (.isitdone/decisions.jsonl)
        97 passed; 21 turns blocked: 18 fixed and then passed, 2 gave up after the cap, 1 released
        64 stops claimed done; 12 of them were refused (19%)

关于 gate 为什么放在这个位置的详细说明,见 docs/why.md(也发布在 DEV,欢迎发表评论)。

安装

每种宿主运行一条命令。在仓库内运行。

宿主命令写入位置
Claude Codenpx isitdone init.claude/settings.json(Stop)
Codex CLInpx isitdone init --agent codex.codex/hooks.json(Stop),然后在 Codex 中运行 /hooks 并信任它
Cursornpx isitdone init --agent cursor.cursor/hooks.json(stop)
Gemini CLInpx isitdone init --agent gemini.gemini/settings.json(AfterAgent);或运行 gemini extensions install https://github.com/raimondasl/isitdone-gemini
GitHub Copilot CLInpx isitdone init --agent copilot.github/hooks/isitdone.json(agentStop);重启 Copilot
Qwen Codenpx isitdone init --agent qwen.qwen/settings.json(Stop)
Goosenpx isitdone init --agent goose.agents/plugins/isitdone/hooks/hooks.json(Stop)
Factory Droidnpx isitdone init --agent droid.factory/hooks.json(Stop)
Devinnpx isitdone init --agent devin.devin/hooks.v1.json(Stop);如果存在 Claude Code hook,则会跳过,因为 Devin 也会加载该 hook
Augment (Auggie)npx isitdone init --agent augment.augment/settings.json(Stop),指向 .augment/hooks/isitdone-hook.sh/.cmd,因为 Auggie 运行脚本文件
OpenCodenpx isitdone init --agent opencode.opencode/plugins/isitdone.js(一个插件:OpenCode 没有阻塞型 hook,因此检查失败时会在同一会话中以可见的 [isitdone] 后续消息返回)
Junie CLI(早期访问)npx isitdone init --agent junie --user~/.junie/config.json(Stop)
全部npx isitdone init --agent all仓库中适用的所有配置

init 会检测检查项、写入 Stop hook(对于 Claude Code、Codex、Gemini CLI、Qwen Code、Devin 和 OpenCode,还会写入仅警告的编辑后 hook)、将 .isitdone/ 添加到 .gitignore,并运行 doctor。doctor 会通过 hook 输入一个合成的“所有测试通过”停止事件,验证它能阻止操作;还会在当前代码树上运行一次检测到的检查项,这样错误的猜测(例如 CI 运行 mypy src/pkg,却猜成 mypy .)或已经存在失败项的代码树就会在安装时立即显现,而不是每次停止时都显示 NOT DONE:

$ npx isitdone init
isitdone init  ~/work/demo-app
  detected   npm run typecheck, npm run lint, npm test
  profile    claim-gated  (lite checks on every stop, full checks when the agent claims done)
  gitignore  added .isitdone/
  Claude Code added           .claude/settings.json

isitdone doctor  ~/work/demo-app
  ok  git            main@2429c82, 0 dirty file(s), tree 8d11d2f
  ok  config         no .isitdone.json (auto-detect only)
  ok  checks         typecheck: npm run typecheck [lite, 60s]; lint: npm run lint [lite, 60s]; test: npm test [full, 120s]
  ok  baseline       all 3 checks pass on the current tree (npm run typecheck 428ms, npm run lint 411ms, npm test 532ms)
  ok  hook:claude    Claude Code (project) .claude/settings.json -> npx -y @aivolution/isitdone hook --host claude
  ok  probe:claude   synthetic "tests pass" stop was blocked in 1.9s (decision block with reason)

  OK   the agent cannot claim done with failing checks in this repo

Claude Code 用户也可以将其安装为插件:/plugin marketplace add raimondasl/isitdone,然后运行 /plugin install isitdone@isitdone。Gemini CLI 用户可以将其安装为扩展:gemini extensions install https://github.com/raimondasl/isitdone-gemini(isitdone-gemini)。可粘贴给 Agent 的这些说明版本见 docs/install.md。这些 Agent 的回合结束 hook 如何工作(配置文件、负载、如何阻止操作、循环标志)已在 docs/agent-hooks.md 中以不依赖供应商的方式说明;相同的表格数据也见 docs/agent-hooks.json。

添加 --user 可将其安装到用户级设置中,而不是项目中。运行 npx isitdone uninstall 可将其移除。通过 npx skills add raimondasl/isitdone 教 Agent 自行运行它(SKILL.md 位于仓库根目录)。

代码位于 @aivolution/isitdone 包中;npm 上的 isitdone 是使用相同命令的短别名,已安装的 hook 始终调用规范包。运行 npm i -D @aivolution/isitdone 可让 hook 在本地解析,无需查询注册表,也可离线使用。

更新

hook 通过 npx 运行,npx 使用自己的安装缓存,不会自行更新未固定版本的包。要将 hook 更新到最新版本:

npx isitdone update          # clears the cached copies of both packages and re-warms them
npx isitdone update --check  # only reports whether a newer release exists

doctor 会报告 hook 实际运行的版本,并在有更新版本可用时提示。将 @aivolution/isitdone 安装为开发依赖的项目,应改用 npm update @aivolution/isitdone 进行更新。

回合中警告

Stop hook 是门禁;编辑后 hook 是提醒。在 Claude Code、Codex、Gemini CLI、Qwen Code、Devin 和 OpenCode 上,init 还会注册一个 hook,在每次 Edit/Write 后运行(Codex 和 Devin:apply_patch;Gemini 和 Qwen:write_file/replace;OpenCode:edit/write/apply_patch)。它会针对 HEAD 检查该文件(因此会看到文件中所有未提交的更改,而不只是此次编辑涉及的行),如果测试变弱了,就会在工具结果旁添加一条简短、客观的说明:

isitdone: after your edit, src/auth.test.ts has weaker tests than HEAD (Tests 4 -> 4   Assertions 6 -> 4   Skipped 0 -> 1):
  - line 12: test skipped or marked expected-failure [high]
  - line 30: assertion weakened: toEqual -> existence check [medium]
If this is intentional, or the change was already there before your edit, tell the user why; otherwise restore the tests. The Stop hook will run the full checks before you can finish.

它从不阻止操作,而且速度很快(只检查一个文件,不运行测试)。Cursor 没有可供 Agent 在编辑后查看的通道,因此在那里,警告会随 Stop hook 一起到达。使用 init --no-edit-hook 可跳过;在配置中将 "integrity": "off" 也会将其关闭。

GitHub Action

在每个拉取请求上执行相同的验证,从干净的检出开始,绝不信任本地回执:

on: { pull_request: {} }
permissions:
  contents: read
  pull-requests: write      # for the sticky PR comment
jobs:
  isitdone:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with: { fetch-depth: 0 }   # exact merge-base for the test-integrity diff
      - uses: raimondasl/isitdone@v0

已在 GitHub Marketplace 上列出,名称为 isitdone verify。它会运行仓库的检查,扫描与 PR 基准版本相比的差异,查找被削弱的测试(默认采用严格模式:即使检查通过,高危/严重问题也会导致任务失败),将回执写入任务摘要,在 PR 上保留一条持续更新的评论,并可将发现作为 SARIF 上传(with: { sarif: 'true' },推送事件需要 security-events: write 权限)。对于来自分支仓库的拉取请求,默认令牌为只读,因此会跳过评论,任务摘要会包含回执;传入具有写入权限的 token(PAT 或 GitHub App 令牌),或使用 pull_request_target 运行,也可以在那里发表评论。base 接受分支(默认值:PR 基准分支)、提交 sha、标签或限定引用。输入:version、base、strict、comment、sarif、token、args、command;输出:status、report、json(--json-file 结果的路径)。

MCP 服务

对于没有 stop hook 的 Agent 和 IDE(VS Code Copilot agent mode、Cline、Windsurf Cascade、Kiro、Zed、JetBrains AI Assistant、Claude Desktop、Amp、Crush、Roo Code),程序无法阻止它们结束任务;同样的验证也可以通过 Model Context Protocol 服务提供:

npx -y @aivolution/isitdone mcp

这是两种集成方式中较弱的一种,而且本来就应该如此。 Hook 由宿主强制执行:无论 Agent 是否愿意,在检查通过之前,它都无法结束本轮对话。MCP 工具只有在模型决定调用时才会运行;如果模型跳过调用,仍会像之前一样声称“完成了”。如果你的 Agent 出现在安装表格中,请使用 hook(也可以两种都用:它们共享验证记录)。没有 hook 的情况下,告诉模型要调用某个工具,仍胜过一句无人核验的话。

它提供三个工具,服务说明会告诉模型:在说工作已完成之前调用 isitdone_verify,粘贴结果,并且绝不通过弱化测试来让检查通过:

工具参数返回内容
isitdone_verifycwd?、profile?(lite | full)、claim?、base?、strict?npx isitdone 的运行结果:done 为 true/false、每项检查的状态、失败输出的末尾内容、测试完整性检查结果以及验证记录状态。NOT DONE 是正常结果(isError 仍为 false),因此模型会读取结果并继续工作。
isitdone_receiptcwd?当前代码树的 PASS、FAIL、STALE 或 NONE。不会运行任何检查。
isitdone_detectcwd?即将运行的检查及每项检查的检测位置。不会运行任何检查。

每个结果都是纯文本报告,并通过 structuredContent(带有 outputSchema)提供相同的信息,供能够读取这些内容的客户端使用。如果没有 cwd 参数,服务会使用客户端的第一个工作区根目录(roots),然后才使用服务的启动目录。

VS Code,在 .vscode/mcp.json 中:

{
  "servers": {
    "isitdone": { "type": "stdio", "command": "npx", "args": ["-y", "@aivolution/isitdone", "mcp"] }
  }
}

Cursor(.cursor/mcp.json)、Claude Desktop(claude_desktop_config.json,位于 Settings、Developer 下)、Cline(MCP Servers、Configure:cline_mcp_settings.json)和 Windsurf(~/.codeium/windsurf/mcp_config.json)使用相同的配置格式:

{
  "mcpServers": {
    "isitdone": { "command": "npx", "args": ["-y", "@aivolution/isitdone", "mcp"] }
  }
}

Claude Code:

claude mcp add isitdone -- npx -y @aivolution/isitdone mcp

Cursor 和 Claude Code 有真正的 hook,因此在这些客户端中,MCP 服务只是一个便捷工具(Agent 可以在任务中途查询验证记录),而不是门槛:请保留 npx isitdone init。在 Windows 上,无法直接启动 npx 的客户端需要使用 "command": "cmd", "args": ["/c", "npx", "-y", "@aivolution/isitdone", "mcp"]。Claude Desktop 没有工作区,因此模型必须传入 cwd。由于没有任何机制强制调用该工具,也要在 Agent 的规则文件(.github/copilot-instructions.md、.clinerules、.windsurfrules、AGENTS.md)中说明这一点:"在告诉我工作已完成之前,请调用 isitdone_verify 工具并粘贴其结果。"

实践中值得注意的细节:每个代码库同一时间只运行一次验证;第二次相同的调用会获得正在进行的运行结果;当客户端因测试套件运行缓慢而自行超时并放弃等待(在其自身的工具超时后发送 notifications/cancelled)时,运行仍会继续完成并留下验证记录,因此模型重试时会立即收到结果,而不会再次超时;对于请求进度通知的客户端,每项检查都会发送进度通知;当客户端关闭 stdin 时,正在运行的检查会被终止,服务也会退出。检查通过管道运行,因此测试输出的任何内容都不会进入协议流。该服务直接基于线格式编写(以换行符分隔的 JSON-RPC 2.0,不使用 SDK,仍然零依赖),支持握手修订版本 2024-11-05、2025-03-26、2025-06-18 和 2025-11-25,以及无状态修订版本 2026-07-28(server/discover、每个请求的 _meta、通过 input_required 获取 roots)。server.json 将其描述为官方 MCP Registry 中的 io.github.raimondasl/isitdone;发布工作流会将其发布到该 Registry。

决策方式

  1. **检测。**读取 package.json 中的脚本(test、typecheck、lint、build;npm、pnpm、yarn、bun、deno)、pyproject.toml/pytest.ini/requirements.txt(pytest、ruff、flake8、mypy、pyright;uv/poetry/pipenv 运行器)、go.mod(go vet、go test ./...)、Cargo.toml(cargo check、cargo test)、.NET 解决方案、Gradle/Maven 和 Makefile 目标。任何内容都可在 .isitdone.json 中覆盖。
  2. **根据声明触发门禁。**默认的 claim-gated 配置会在每次停止时运行快速的 lite 检查(typecheck、lint),仅当 Agent 的最终消息包含完成声明时,才运行 full 检查(tests、build),例如:“测试通过”“完成”“已实现”“已验证”“可以开始审核”等。提问或进度更新不会触发耗时两分钟的测试运行。无法传递最终消息的宿主(Cursor、Copilot CLI、Factory Droid、Devin、Junie)会使用完整配置,并按工作树缓存。
  3. **按工作树缓存。**使用临时 git 索引对工作树(已跟踪的更改和未跟踪的文件,包括子模块和嵌入式仓库的内容)进行哈希,不会向 .git/objects 写入任何 blob。相同工作树哈希和相同检查配置的 PASS 回执会被复用;不会无谓地重复运行。
  4. **运行并决策。**检查会在全新的子进程中运行,设置 CI=true,并为每项检查设置超时,同时捕获最后 30 行输出。如果有任何检查失败,钩子会按宿主要求的阻止格式返回一条长度受限的纯文本原因,其中引用声明和失败输出。如果全部通过,则写入回执,Agent 可以停止。
  5. **扫描差异,检查测试是否被削弱。**检查被删除的测试文件、新增的 .skip/.only/xfail、被删减的断言、被降级的匹配器(toStrictEqual 改为 toEqual、toThrow("msg") 改为 toThrow()、assertEqual 改为 assertTrue)、放宽的容差、空的 catch/except: pass,以及被废除作用的配置(|| true、--passWithNoTests、continue-on-error、testPathIgnorePatterns、-DskipTests、ignoreFailures、cargo test -- --skip、被删除的 CI 测试步骤)。涵盖 JS/TS、Python、Go、Rust(#[ignore]、被放宽的 #[should_panic]、assert_eq! 改为 is_ok()、按内容查找内联 #[cfg(test)] 模块)、Java/Kotlin(JUnit 4/5、TestNG、AssertJ、Hamcrest;@Disabled、assumeTrue(false)、assertEquals 改为 assertNotNull;Kotlin 对 JUnit 和 kotlin.test 的支持为尽力而为)以及 C#(xUnit、NUnit、MSTest;Skip =、[Ignore]、Assert.Equal 改为 Assert.NotNull);扫描的构建配置包括 Cargo/nextest、Maven/Gradle、csproj/runsettings/xunit.runner.json 和常见 CI 文件。发现项会以更改前后的行记录(Tests 47 -> 44 Assertions 112 -> 104 Skipped 0 -> 1),并写入回执;设置 "integrity": "strict"(或使用 --strict)时,即使检查通过,高危/严重发现项也会阻止停止。使用 // isitdone: allow <reason> 忽略某一行;忽略项会被报告,不会隐藏,而 --ci 会将新增的忽略项视为发现项。
  6. **绝不无限循环。**钩子会遵循各宿主的 stop_hook_active / loop_count(对于两者都不提供的宿主,则自行统计尝试次数),并按会话统计自身的尝试次数(默认上限为 3);达到上限后,会显示警告并允许 Agent 停止。stdin 格式错误、配置损坏或内部错误始终会允许停止:isitdone 绝不能让 Agent 无法运行。

同一工作树中的两个会话

检查会查看整个工作树。当两个 Agent 会话在同一目录中工作时,工作树也包含另一个会话尚未完成的文件;如果门禁不考虑这一点,先停止的会话就会被归咎,并被要求“修复”不属于自己的工作。isitdone 按一条规则将两个会话区分开来:**单独运行的会话仍按原方式触发门禁。**任何更宽松的处理都需要有证据证明另一个会话在此处有尚未完成的工作。

  • 编辑后钩子会记录哪个会话编辑了哪个文件(位于仓库顶层的 .isitdone/sessions/:路径和时间戳,仅本地保存,绝不提交)。
  • 正在进行的工作证明必须同时满足以下条件:另一个会话在本会话首次编辑之后、且在最近一小时内有钩子活动;它记录的编辑发生在它自己上一次检查通过的 stop 之后(该 stop 必须验证整个代码树:运行每项检查,而不是只运行 lint 或什么都没运行的暂停);并且这些文件仍未提交。因此,在本会话开始前就已结束的对话(/clear、重启、昨天的会话)不算“另一个会话”;而已在检查通过后结束本轮工作的会话(或通过检查后退出的一次性辅助程序)没有正在进行的工作:它离开时文件状态良好,因此现在出现的失败是之后更改造成的,做出该更改的会话须对此承担全部责任。
  • 有了上述证明,每条阻止原因都会先指出另一个会话的文件,并明确要求:不要编辑、还原或“修复”这些文件,也不要运行会丢弃这些文件的 git 命令(checkout、restore、reset、stash、clean)。
  • 如果失败输出中提及本会话编辑过的文件,则按常规阻止,最多重试 maxAttempts 次。
  • 如果失败输出中没有提及本会话编辑过的任何文件,则阻止一次。系统会要求 Agent 判断是否由其更改导致(例如,更改类型或测试夹具破坏了它从未打开的文件),若是则修复自己的更改;到下一个 stop 时会再次运行检查,如果输出仍未提及本会话的文件,Agent 可以停止。系统会告知用户,回执会标记为 FAIL,因为代码树未经验证。在该轮中,如果没有触发阻止,且检查未在该 stop 时运行,则不会释放任何内容。(已包含上述指令的阻止消息也算作这次告知。)
  • 完全没有编辑记录的会话(宿主没有编辑后钩子,或编辑仅通过 shell 完成)不会获得任何宽限,无论是检查失败还是严格的测试完整性问题都一样:由于无法证明它改动了什么,系统会像只有它一个会话那样对其进行门控。系统会告知它另一个会话正在编辑哪些文件,并要求它不要丢弃该会话的更改。
  • 否则,另一个会话文件中的测试完整性问题由该会话处理,stop 消息会说明问题数量。
  • 有并发会话时,即使轻量检查失败,也会运行完整检查,以免另一个会话文件中的 lint 错误掩盖本会话的测试失败。
  • 每个项目的检查运行都会串行化,无论来自 Stop 钩子、CLI 还是 MCP 服务:如果已有检查正在运行,新运行会等待(最长三分钟;从钩子启动时,如果检查本身已占用钩子的大部分时限,等待时间会更短),而不会同时再次运行整套检查。缓存的 PASS 无需等待。系统会如实记录检查期间发生的编辑:等待结束后,仅当另一个检查在当前这棵代码树上启动时,才会复用其 PASS;如果某次检查运行期间有会话记录了编辑,或现有文件被修改或删除,则该次检查的回执会绑定到它启动时的代码树(并附带警告);会话也绝不会采用在其最近一次已记录编辑之前启动的检查结果作为缓存 PASS。

“提及某个文件”指:相对于仓库的路径、仓库内的绝对路径,或检查从子目录或工作区运行时打印的较短路径(前提是仓库中只有一个文件以该路径结尾);单独的 Name.java:17 只有在该名称在仓库中具有唯一性时才算。首先会剥除颜色代码;在 Windows 和 macOS 上,比较时不区分大小写。 值得了解的限制。记录来自编辑后钩子,因此在 Claude Code、Codex、Gemini CLI、Qwen Code、Devin 和 OpenCode 中都能确定归属(子 Agent 的编辑归于其父 Agent;如果插件是在 0.6.1 之前生成的,请重新运行一次 init --agent opencode 以刷新);通过 shell 命令更改的文件则不归属于任何会话。中途被终止的会话不会道别:在最多一小时内,其未完成的文件仍会放宽检查门槛,对于未提及剩余会话任何文件的失败,只需阻止一次,而不是三次。反过来的空档是:新会话的第一个钩子触发之前,其他正在运行的会话对它不可见。归属依据的是失败报告的位置,而不是导致失败的原因:如果本会话的更改导致另一个会话仍在编辑的文件出错,那么唯一的防护就是这次阻止及其提问,而另一个会话会因其文件中的失败而受阻。检查运行期间由没有编辑后钩子的会话或通过 shell 新增的文件,无法与检查自身的输出区分,因此回执仍可能在未发现的情况下涵盖这些文件。失败的测试通常会指出测试文件,而不是被编辑的源文件,因此除非该会话也编辑了测试文件,否则此类失败会被视为“未提及其任何文件”。检查输出中的 ../ 相对路径不会被解析。释放消息会显示在 Claude Code、Codex、Gemini CLI 和 Qwen Code 中;Devin 和 OpenCode 没有显示消息的渠道,因此在阻止一次后会静默释放。运行锁尽力而为:等待超时或竞争激烈时,运行仍可能重叠,就像之前一直那样。"otherSessions": "ignore" 会关闭这条指令、单次阻止和运行锁(编辑仍会被记录;只有回执绑定会读取这些记录)。

以这种方式共享目录可行,但并不理想:另一个会话负责的部分目录树出现问题时,整个代码仓库的检查就无法通过。要进行长时间并行工作,请为每个会话提供独立的 git worktree;每个会话都会有自己的 .isitdone/ 和回执。

暂停门禁

有时机器必须保持安静:运行基准测试、执行封存运行,或在其他地方进行漫长的构建。暂停会暂时关闭 Stop hook,然后自动重新开启。在你自己的终端中运行:

npx isitdone pause                          # this repository (all its worktrees), for an hour
npx isitdone pause --for 3h                 # 30m, 3h, 2d, 1w; at most 30 days
npx isitdone pause --global --until 2026-11-02T11:00Z   # every repository of this user
npx isitdone resume                         # end this repository's pause early (--global: the user-wide one)
  • hook 会在每次停止时读取暂停状态,并在运行检查期间每隔几秒检查一次:已在运行的会话会在下次停止时读取暂停状态,已经开始的检查也会被停止(不会写入收据)。只要 hook 运行的是 0.9.0 或更高版本的 isitdone,就无需重启任何东西:如果 pause 发现仍可能有 hook 使用较旧的副本(缓存于 npx 的副本:npx isitdone update;固定版本或已安装的副本:重新运行 init,或运行 npm update @aivolution/isitdone,然后重启这些会话),它会告知你。
  • 暂停期间,停止操作不会运行任何检查,也不会向项目中写入任何新内容。每次暂停期间的停止操作,只要最终消息声称已完成(或可能已完成:有些宿主不会传递消息),以及每次让阻止过程提前结束的暂停,都会告知用户门禁已关闭以及关闭到何时;进度停止则保持静默。此消息会发送至 Claude Code、Codex、Gemini CLI、Qwen Code、Factory Droid 和 Augment(并作为 Copilot CLI 的允许原因);Cursor、Goose、Devin、OpenCode 和 Junie 没有可用的消息通道,因此只有 doctor 和 history 会显示暂停状态。暂停期间的停止操作会记录日志,history 会单独统计这些操作,包括暂停允许在阻止过程后继续的轮次。
  • 只有自动门禁会暂停。手动运行的 npx isitdone、MCP 工具和 GitHub Action 仍会执行检查;npx isitdone 和 doctor 会告知你 hook 已暂停。
  • 暂停状态保存在项目之外的 ~/.isitdone/pauses/ 中,因此“此用户的每个仓库”指的是使用此主目录运行的每个 Agent:WSL、容器和其他账户都有各自的暂停状态文件。过期或无法读取的暂停文件会被忽略:有疑问时,门禁会运行。若需暂停超过 30 天,请运行 npx isitdone uninstall。
  • 这是你的开关,不是 Agent 的。 如果 shell 看起来属于 Agent,pause 会拒绝运行:Claude Code、Codex、Cursor、Gemini CLI 和 Qwen Code 会在它们运行命令的 shell 中设置变量,从这些应用内部打开的终端(或 Claude Code 的 ! 前缀)也会带上这些变量。这只是增加了一道门槛,并非锁定:Agent 可以清除这些变量,也可以像修改自己的 hook 设置一样,在工作区之外自行写入暂停文件。每次 Agent 在暂停期间声称“已完成”时显示的消息、doctor 和决策日志,可以让这类行为不被忽视。

CLI

npx isitdone                      run all checks, write the receipt, exit 0 (DONE) or 1 (NOT DONE)
npx isitdone --profile lite       typecheck + lint only
npx isitdone --json               {"ok", "done", "checks": [...]} for scripts and orchestrators (--json-file <path> writes it)
npx isitdone --no-cache           re-run even if a PASS receipt exists for this tree
npx isitdone --all                keep running tests even if typecheck failed
npx isitdone --strict             block when the change weakened tests (high/critical findings)
npx isitdone --ci                 strict, plus new "isitdone: allow" suppressions count as findings
npx isitdone --base main          scan the diff against a branch instead of HEAD (pull requests)
npx isitdone --report out.md      write a markdown report; --sarif out.sarif writes SARIF 2.1.0 for code scanning
npx isitdone history [--since 30d] [--exclude <substr>] [--verbose] [--min <pct>]
                                  share of past "done" claims backed by a passing test run
                                  (Claude Code, Codex, Gemini CLI, Qwen Code and Cursor transcripts, local only)
npx isitdone receipt [--md|--json] state of the current tree: PASS | FAIL | STALE | NONE (exit 0 only on full PASS)
npx isitdone detect [--json]      which checks would run and where they came from
npx isitdone init [--agent ...]   install the Stop hook (claude | codex | cursor | gemini | copilot | qwen | goose | droid |
                                  devin | augment | opencode | junie | all | auto)
npx isitdone doctor               prove the installed hook blocks; run the checks once on the current tree (--no-baseline skips)
npx isitdone uninstall            remove the hook(s)
npx isitdone pause [--for 3h | --until <time>] [--global]
                                  let agents' stops through unchecked for a while (see "Pausing the gate"); resume [--global] ends it
npx isitdone mcp                  MCP server on stdio for agents without a stop hook (started by the IDE, see "MCP server")

对于编排器和 Agent 循环,npx isitdone --json 是一个完成判定条件:只有当前树的所有完整检查都通过时,done 才为 true。退出代码:0 表示已完成,1 表示未完成,3 表示用法错误或内部错误。

配置

可选。在仓库根目录的 .isitdone.json 中配置,或在 package.json 中添加 "isitdone" 键:

{
  "checks": {
    "test": "npm run test:unit",                    // replace a detected command
    "lint": false,                                  // disable one
    "e2e": { "cmd": "playwright test", "kind": "full", "timeout": 600 },  // add one
    "smoke": { "cmd": "./smoke.sh", "kind": "lite" }
  },
  "profile": "claim-gated",   // claim-gated (default) | lite | full
  "timeout": 120,             // seconds per full check
  "liteTimeout": 60,          // seconds per lite check
  "maxAttempts": 3,           // consecutive blocks before the agent may stop anyway
  "build": false,             // include the build script even when tests exist
  "claimPatterns": ["ship it"], // extra completion-claim regexes
  "integrity": "warn",        // warn (default) | strict | off
  "otherSessions": "respect", // respect (default) | ignore: see "Two sessions in one working tree"
  "history": { "exclude": ["client-x"] }  // project paths to skip in `isitdone history`
}

kind: "lite" 表示每次停止时都会运行检查;kind: "full" 表示 Agent 声称完成时才会运行检查。test 和 build 默认属于 full;typecheck、lint、check 和 vet 默认属于 lite。

它不是什么

  • 不是测谎仪。 它不会评判 Agent 的句子,而是运行命令并读取退出代码。声明只决定运行多少检查。
  • 不是对抗性安全机制。 回执经过 HMAC 签名,因此手动编辑的文件会显示为 NONE;树哈希也能让过期回执显而易见。但有权限编辑设置的 Agent 可以移除钩子。isitdone 防范的是诚实的失误,而几乎所有“测试通过”的虚假说法都源于此。
  • 不是 AST。 测试完整性扫描会针对差异进行逐行正则匹配,使用一小份公开的检测器列表。它能发现 Agent 不修复问题却让红变绿的常见手法;但会漏掉巧妙的手法(例如把测试的预期值改成与回归问题一致),偶尔也会误报合理的重构,所以默认只发出警告,且每项发现都会显示其证据。它在 bench/ 中的标注语料上评估精确率和召回率(运行 npm run bench 会打印运行所用的版本和提交)。截至 2026-09-24,在 isitdone 0.7.0 中,语料包含 192 个手动标注的案例:89 个合理案例中没有误报,103 个篡改案例中发现了 102 个;每个参与评分的检测器都至少由 3 个仅针对该检测器的案例覆盖;运行结果会分别打印每个检测器和每个案例的召回率。每次报告错误后,语料都会扩充。
  • 不是大模型。 这里没有任何内容会调用模型、向外发送数据或需要密钥。

相关工具

工具功能关系
oh-my-agent多 Agent 框架,包含一个运行类型检查、测试和 lint 的停止钩子门禁isitdone 是面向任意宿主的独立门禁原语,带有回执和声明门控
taskmaster在出现完成令牌前阻止停止基于令牌;不会运行检查
gutcheck面向差异的变异探测器,带有 Claude Code 停止钩子互补的第二道门禁;isitdone 验证测试套件是否通过,gutcheck 验证测试套件是否有意义
checkwash, testseal用于扫描弱化测试的差异扫描器仅扫描差异;isitdone 会运行检查(差异扫描已列入路线图)
ProofRun绑定代码树的 PASS/FAIL/STALE 回执,使用手写配置回执理念相似;isitdone 会自动检测并接入各类 Agent

路线图

  • v0.5 增加 --related 模式,为运行缓慢的测试套件仅运行涉及已更改文件的测试;增加 Cline(在 attempt_completion 上使用 PreToolUse 门禁)和 Amp(agent.end 插件)适配器;待 session.stopping 发布后增加原生 OpenCode 钩子,待 Windsurf/Cascade 钩子支持阻止操作后增加相应适配器;增加用于 OpenCode 数据库的 history;增加 Kotest/Spek DSL;增加用于检测将预期值改成与回归问题一致的检测器。
  • 暂不计划提供钩子适配器:Roo Code 和 Kilo Code(没有钩子)、Kiro(其 Stop 触发器无法阻止操作)、Crush(仅支持 PreToolUse);这些工具可改用 MCP 服务。Aider 没有钩子,但 aider --auto-test --test-cmd "npx isitdone" 会在每次编辑后将相同的判定结果反馈回去。

开发

npm ci
npm test            # vitest, runs in a throwaway HOME so it never touches your real agent settings
npm run typecheck
npm run build       # esbuild -> dist/isitdone.js (CLI) and dist/index.js (library), no dependencies
npm run bench       # precision/recall of the test-integrity scanner over bench/cases/
node dist/isitdone.js

本仓库也用自身验证自身:CI 会在每次推送时运行 node dist/isitdone.js --json,拉取请求则会运行 GitHub Action。此库也可导入:import { verify, scanIntegrity } from '@aivolution/isitdone'。

许可证

MIT

评论

支持 Markdown

第一次接触 MCP?

从协议是什么,到在客户端里装上第一个服务,这几条回答覆盖了大多数入门问题。

一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。