← 返回列表
未验证
让编码代理的绿色通过声明保持诚实的钩子:每一次验证运行都被不加掩饰地记录下来,而当证据过期、失败或被掩盖时,“完成”会被…
尚未跑自动兼容性验证,可查看页面内的依赖与入口分析。 · 最近上游提交 2026/9/5 · 已提供中文文档
保持编码代理的绿色声明诚实可信:验证运行以未屏蔽的方式记录,当证据过期、失败或被屏蔽时,完成会被阻止。
综合分
30.6
GitHub 分
30.6
用户评分
—
★ Stars
3
周下载量
—
安装插件(需先安装 dsh CLI 引擎:npm install -g @deepseek-ai/dsh)
dsh plugin --profile web add pavangupta352/stalegreen该插件未发布到 npm,走 GitHub 源安装(pnpm 若拦截 prepare 脚本,按其提示在 pnpm-workspace.yaml 的 allowBuilds 中放行后重跑)
数据截至 2026/9/20(元数据每日更新 · 实装验证按队列轮转,单条结论的验证时间见上方)
用户评分
还没有人投票,来当第一个
订阅周报,不错过优质插件更新
每周一封 · 高评分插件 + 新用户活动
README
stalegreen
ci
npm
license
让编码代理的绿色通过声明保持诚实的钩子:每一次验证运行都被不加掩饰地记录下来,而当证据过期、失败或被掩盖时,“完成”会被阻止。
零 token、零网络、零遥测。每一个判定都是确定性的,并引用一份凭据。
一个代理运行测试、编辑文件、声称所有测试通过,然后被凭据和已编辑文件阻止;它重新运行,看到失败,修复它,并以绿色结束
问题
编码代理经常说“所有测试通过”。这句话有两个问题,二者都在真实会话日志中被测量到:
1. 过期的绿色。 代理引用的测试运行发生在它自己后续编辑之前。没有任何东西被重新运行。
2. 被掩盖的结果。 代理通过 tail -5、grep、|| true 或 ; echo done 管道处理运行器,因此退出状态和摘要行从未到达记录中。这次运行看起来没问题,因为没有任何东西能看起来有问题。
明显的矛盾,即最后一次运行失败而代理仍然报告绿色,很少见。过期和被掩盖的证据则不然。stalegreen 是证据卫生:它检查声明背后的证据,而不是声明背后的意图。
它做什么
揭示。 在验证命令运行之前(pytest、pnpm test、tsc、eslint、cargo test、go test、next build 以及大约八十个其他命令),PreToolUse 钩子会重写它,以便完整输出被捕获到日志中,最后几行显示给代理,打印一行显式的 [stalegreen] exit= receipt=,并保留退出状态。退出状态也会写在日志旁边,而钩子信任该文件而不是打印的行,因此仅凭输出文本永远无法创建或刷新凭据。吞掉结果的管道和后缀再也无法隐藏失败。无法安全包装的命令(heredoc、后台运行、进程替换、sudo、现有重定向、监视模式)保持原样。
凭据。 命令完成后,运行器自己的摘要行和退出状态成为一份凭据:命令、运行器、类别、通过或失败、计数、时间戳和工作树指纹。此后每一次文件编辑都是一个编辑事件。
新鲜度门。 当代理试图以诸如“所有 41 个测试通过”、“tsc 干净”、“lint 通过”或“构建成功”之类的声明结束其回合时,Stop 钩子会查找该类别的最新凭据:
| 证据 | 判定 | 操作 |
| --- | --- | --- |
| 通过的运行,工作树自那以后未更改 | FRESH | 允许 |
| 通过的运行,自那以后文件已更改 | STALE | 阻止一次,要求重新运行 |
| 失败的运行 | FAILED | 阻止一次 |
| 结果未被记录(被掩码)的运行 | MASKED | 阻止一次,要求进行未掩码的重跑 |
| 完全没有运行 | NONE | 允许(在严格模式下阻止) |
| 后台运行仍在进行中 | DEFERRED | 允许 |
一次阻止看起来像这样:
stalegreen: “全部 41 个测试通过”已过期。凭据 r-0017(pnpm test,vitest,41 个通过,14:02:11)早于之后的 3 次编辑:
src/routes/pay.ts (14:05:40), src/lib/hold.ts (14:06:02), src/lib/hold.test.ts (14:06:31)
重新运行 pnpm test 并报告结果,或明确说明在这些编辑之后未重新运行测试。
同一类别每轮最多被阻止一次。第二次停止会被允许并记录,因此 agent 永远不会卡住。
安装
作为 Claude Code 插件,在 Claude Code 内部执行:
/plugin marketplace add pavangupta352/stalegreen
/plugin install stalegreen@stalegreen
这会从插件自带的 hook 副本注册 PreToolUse、PostToolUse、Stop 和 SubagentStop。除仓库本身外不会下载任何内容,安装时也不会运行任何东西;该 hook 没有依赖项。
或者使用 CLI,用于 Claude Code、Codex 或两者:
npx stalegreen install --all # 或 --claude,或 --codex
npx stalegreen doctor
install 会将编译后的 hook 复制到 ~/.stalegreen/bin/hook.js,并注册相同的四个事件:Claude Code 在 ~/.claude/settings.json 中,Codex 在 ~/.codex/hooks.json 中。添加 --project 以用于仓库的 .claude/settings.json 或 .codex/hooks.json,添加 --advisory 以记录判定而不阻止。uninstall 会移除它们。Claude Code 请使用这两种方式之一,不要同时使用,否则每次运行都会被记录两次。
对于 DeepSeek Harness,有一个原生 Cordis 插件 dsh-plugin-stalegreen,列在 awesome-dsh-plugin 中:
dsh plugin add dsh-plugin-stalegreen
它监听 tools/pre-execute、tools/post-execute 和 agent/turn-stopping,将相同的凭据写入相同的存储,并引导一个纠正步骤而不是阻止。该 harness 无法重写工具调用,因此被掩码的运行在那里只会被拒绝(在严格模式下),永远不会被解除掩码。
Claude Code。 权限仍归你所有:只有当你的权限规则已经允许原始命令时,该 hook 才会为包装后的命令返回 allow 决策,因此它绝不会扩大 Claude Code 可以运行的范围。在配置中设置 "permission": "allow" 可跳过每次验证运行的提示,或设置 "ask" 以永不返回决策。
Codex。 Codex 只审查一次新钩子:打开 Codex,运行 /hooks 并信任 stalegreen 条目(codex exec 则改用 --dangerously-bypass-hook-trust)。Codex 只在带有 allow 决定时才接受重写后的命令,因此该决定只针对验证命令本身返回,绝不针对其他任何内容;设置 "permission": "ask" 可在那里关闭重写。Codex 钩子完全不会报告 shell 命令的退出状态,这使得重写成为了解一次运行如何结束的唯一方式;没有它时,仍由运行器自身的摘要行决定,而 Script failed 标题算作失败。Stop 处的阻止会以 {"decision":"block","reason":...} 返回,Codex 会将其转换为继续提示。
如何判定新鲜度
指纹是仓库中每个已跟踪和未跟踪文件内容的哈希,未更改的文件从 git 索引读取,已修改的文件从磁盘读取,并排除文档和其他非代码路径。当任何文件内容发生变化时它就会改变,而在 git add 或 git commit 时不会改变,因此提交永远不会使新鲜证据变旧。通过 heredoc、sed -i、子代理或任何其他转录未显示的编辑方式所做的修改仍会被捕获。当指纹不可用时(在 git 之外,或超出其 150 毫秒预算),门控会回退到回执之后记录的编辑事件。在后台完成的运行以其开始时间计时,因此在其运行期间所做的编辑算作其后的编辑。
什么算作掩盖
当已识别的验证命令的运行器片段属于以下情况时,该命令被掩盖:
- 通过管道传给 tail、head、grep、wc、cut、awk、sed、less、cat 或 tee(没有 set -o pipefail 时退出状态会丢失;tail 会保留摘要,过滤器则不会),
- 后接 || true、|| echo ...、; echo done 或任何 ; 链,
- 发送到 /dev/null 或文件,
- 以 ! 为前缀。
静默标志(-q、--silent、--reporter=dot)会被记录,但本身不会掩盖。
运行器目录
| 类别 | 已识别的运行器 |
| --- | --- |
| test | pytest, unittest, manage.py test, jest, vitest, mocha, ava, node --test, bun test, deno test, playwright, cypress, jasmine, tap, go test, cargo test, cargo nextest, dotnet test, mvn test, gradle test, sbt test, rspec, minitest, rails test, phpunit, pest, artisan test, mix test, swift test, xcodebuild test, flutter test, dart test, ctest, bazel test, elm-test, npm/pnpm/yarn/bun test scripts, make test |
| typecheck | tsc, vue-tsc, svelte-check, astro check, mypy, pyright, pyre, flow, cargo check, deno check, phpstan, psalm, sorbet, mix dialyzer, typecheck scripts |
| lint | eslint, biome, oxlint, stylelint, prettier --check, ruff, flake8, pylint, black --check, isort --check, golangci-lint, go vet, cargo clippy, cargo fmt --check, rubocop, phpcs, mix credo, swiftlint, dart analyze, shellcheck, hadolint, yamllint, ktlint, detekt, lint scripts |
| build | next build、vite build、webpack、rollup、esbuild、tsup、parcel、turbo、nx、cargo build、go build、make、gradle、maven、dotnet build、xcodebuild、swift build、docker build、cmake、ninja、mix compile、python -m build、elm make、构建脚本 |
保持判定诚实的规则:
- 一次运行只有在运行器自身给出肯定性摘要且已知退出状态为 0 时,才算 pass。fail 需要失败信号或已知的非零退出。其他一切情况都是 inconclusive,绝不是 fail。
- 0 failed、0 errors 和 No issues found 都算通过。
- 子集运行(-k、-t、单个文件路径、工作区中的 -p)满足“测试通过”,但不满足“所有测试通过”。
- --collect-only、--list、--help、试运行和监视模式都不是验证运行。
- 复合命令会在引号外按 &&、||、; 和 | 拆分;开头的 cd 所在目录会被记录,这样某个包中失败的 tsc 就不会对另一个包的断言做过期检查。
什么是断言
该门禁只读取最终助手消息。代码围栏、行内代码和引号内的文本永远不算断言。问题、对用户的指示、带保留的及未来的陈述,以及归属于他人的陈述都会被忽略:
| 句子 | 视为 |
| --- | --- |
| All 41 tests pass. | 断言,测试,全部 |
| tsc --noEmit is clean. | 断言,类型检查 |
| Lint and typecheck both pass. | 两个断言 |
| The tests should pass now. | 带保留,忽略 |
| Let me run the tests to confirm they pass. | 未来,忽略 |
| 40 passed, 1 failed. | 否定,忽略 |
| According to the README, all tests pass. | 转述,忽略 |
| Apart from the flaky snapshot test, all tests pass. | 限定,报告但从不阻止 |
| Do all tests pass? | 问题,忽略 |
该提取器会针对一个包含 350 个句子的标注语料库进行测试,并且在断言性声明上必须保持精确率不低于 0.98、召回率不低于 0.90。
配置
~/.stalegreen/config.json,可在每个仓库中通过 .stalegreen.json 覆盖:
{
"policy": "block",
"mode": "rewrite",
"strictNoEvidence": false,
"tailLines": 40,
"categories": { "test": true, "typecheck": true, "lint": true, "build": true },
"ignoreCommands": ["make lint-fast"],
"extraRunners": [{ "match": "^make check", "category": "test", "pass": "^OK", "fail": "FAILED" }],
"fingerprintIgnore": [".md", "docs/*"],
"fingerprintBudgetMs": 150,
"deferredTtlMinutes": 10,
"maxLogBytes": 5242880,
"permission": "inherit",
"prune": "30d"
}
- policy:block(默认)或 advisory,后者记录判定但不阻止。
- mode:rewrite(默认)、strict(无法被包装的掩码命令会被拒绝,并给出重新运行指令)或 off(不重写)。
- strictNoEvidence:阻止完全没有运行记录的断言。
- tailLines:智能体能看到被包装运行的多少行;当智能体请求至少 20 行时,其自身的 tail -n 会被保留。
- categories、ignoreCommands、extraRunners:哪些类别被门控、哪些命令永远不会被视为运行,以及你自己的运行器(match 是对命令的正则表达式,pass 和 fail 针对输出)。
- fingerprintIgnore、fingerprintBudgetMs:从工作树哈希中排除的路径,以及超过多长时间后哈希被标记为不可用并由编辑事件决定。
- deferredTtlMinutes:后台运行被视为进行中的时长。
- maxLogBytes:超过此大小的日志会被截断并带有标记。
- permission:inherit(默认,仅当你自己的规则已允许该命令时才做出允许决定)、allow(每次验证运行)或 ask(从不做出决定;在 Codex 上这会关闭重写)。
- prune:stalegreen doctor --prune 保留的时间窗口;更早的会话会从存储中移除。
输出语法
来自钩子和 CLI 的每个判定都会序列化为相同的结构,以便其他工具可以消费它:
{
"claim": { "category": "test", "text": "all 41 tests pass", "scope": "all", "qualified": false },
"evidence": { "receipt": "r-0017", "cmd": "pnpm test", "runner": "vitest", "verdict": "pass", "counts": { "passed": 41 }, "ts": "2026-09-02T14:02:11.318Z", "cwd": "/repo", "scope": "all", "masked": false },
"freshness": { "fingerprintMatch": false, "editsAfter": [{ "path": "src/lib/hold.ts", "ts": "2026-09-02T14:06:02.000Z", "kind": "Edit" }] },
"verdict": "STALE",
"action": "blocked"
}
回执、编辑事件和判定以仅追加的 JSONL 形式存放在 ~/.stalegreen/sessions// 下,每个会话一个文件,外加每次运行一个日志。
在真实会话上测量
stalegreen stats 会通过同一门控重放过去的 Claude Code 会话,并报告绿色声明有多频繁地是过期的、失败的、被掩盖的或无依据的,以及验证运行有多频繁地隐藏其退出状态。这是作者自己机器上的输出,180 天的会话,按打印原样粘贴:
$ stalegreen stats --since 180d
Green claims 375 in 16 sessions, 10 repeated status lines counted once
fresh 155 41% a passing run and no edits since
stale 98 26% a passing run, then edits, no rerun
failed 48 13% the last matching run failed
masked 23 6% the exit status was hidden and nothing readable was left
no run 51 14% nothing matching ran in the session
Verification runs 9,011
exit hidden 8,794 98% piped, redirected, chained or sent to /dev/null
hid a failure 1,372 15% exit hidden, failure marker in the visible output
no result 1,502 17% exit hidden and no summary line either
26% of green claims were stale (98 of 375); 98% of verification runs hid their exit status (8794 of 9011).
那些数字背后的每一条判定都经过人工复核。过期的那些确实是引用运行之后的真实编辑。失败的那些是连续数月以退出码 1 结束的构建,而每份摘要都写着“构建通过”。该命令在本地读取记录,只打印聚合结果;stalegreen history --explain 会列出各条声明及其凭据。
CLI
stalegreen check [--session ] [--json] 当前或最近一次会话的声明与证据
stalegreen receipt 一次运行的凭据及其日志尾部
stalegreen doctor [--prune] 钩子、存储健康状况及最近判定;清理旧会话
stalegreen history [--since 30d] [--include-none] [--explain] [--json]
重放过往会话:过期、失败和被掩盖的声明
stalegreen stats [--since 90d] [--json] 上述比率,按执行框架、模型和会话类型分组
stalegreen redact [--session ] [--out f] 用于缺陷报告的会话可分享副本
redact 将路径缩短为相对于仓库的形式,遮蔽命令和输出中的机密,并用匹配到的声明替换智能体的叙述,因此误拦截可以连同证据一起报告,而无需附带对话内容。
history 和 stats 以流式方式读取 ~/.claude/projects(或 $CLAUDE_CONFIG_DIR/projects)和 ~/.codex/sessions(或 $CODEX_HOME/sessions),因此数 GB 的记录也没问题;--harness claude|codex|all 用于选择来源。Codex 子线程会合并到其父线程中,Codex 的 JavaScript exec 单元格内的 shell 命令会从该单元格中读出。它们从不写入那里,也从不离开本机。
原则
- 确定性且可引用。 每条消息都指明凭据 id、命令、时间以及之后编辑过的文件。
- 失败开放。 钩子崩溃、超时或解析失败绝不会中断智能体。该失败会被计数,操作则被放行。
- 保守。 无法识别的命令不予处理。无法解析的输出视为不确定,绝不视为失败。同一类别每轮最多拦截一次。
- 快速。 钩子进程从启动到退出的耗时在 CI 中测量;整个引擎是一个编译后的文件,没有运行时依赖。
这不是什么
- 不是意图的评判者。它从不给任何东西打分;它检查的是某项声明背后的证据是否新鲜且完整。
- 不是会话分析器或仪表盘。
- 不是文件存在性检查器。Heredoc 和 sed -i 编辑在记录中不可见,因此新鲜度来自工作树,而不是来自智能体声称它编辑了什么。
先前工作
- backcheck(Vector Institute,Apache-2.0):一个面向 Claude Code 的事后审计器,带有大量运行器解析器、模糊措辞列表和测试完整性检查。是与本引擎最接近的引擎;其模糊措辞列表和误报案例为这里的声明语法提供了参考。
- tycho(Apache-2.0):一个确定性的跨 harness 完成钩子,带有 attestation 尾部。最接近的实时门禁。
- red-handed、nuhuh、truthguard、redpen、groundtruth、verify-gate 和 proof:面向 Claude Code 的 Stop-hook 门禁与审计,从“agent 是否说了真话”的角度攻击同一个痛点。
- superpowers 的 verification-before-completion 规则:没有新鲜证据就不允许声称完成。stalegreen 是让该规则可检查的确定性层。
- Claude Code 的 /goal 和 /verify、Codex 的 /goal 和 Guardian、Grok Build 的 verifier:由模型判断的完成检查。它们信任进入 transcript 的内容;stalegreen 确保进入其中的内容是真实且新鲜的。
- @letta-ai/trajectory:适用于多种 harness 的 transcript 读取器。
许可证
MIT。版权所有 (c) 2026 Pavan Gupta。扫码进群