---
name: nixkits-check-updates
description: NixKits 仓库的软件包更新适配层——在通用技能 nix-flake-update-check 之上，补充本仓库特有的远端 issue / PR 形态（含与更新重叠的历史先例）、CI 结构（可复用 build-package.yml + 每包每架构 workflow）与实测失败形态（llama-cpp-ver 403 限流、codewhale-riscv64 hash、blender-mcp 取源 403、徽章竞态）、四语文档同步、dsh 内置插件清单同步（含插件改名令预设硬失败的排查）、同账户子仓（dsh-api-balance）的链式检查坐标、维护日志记录、历史事故教训（comfyui 漂移、codewhale-riscv64 CI 失败），以及收尾的流程复盘与规范校验（AGENTS.md / SECURITY.md 等）。
---

# NixKits 软件包更新（仓库适配层）

本技能是 **NixKits 仓库特有**的适配层。通用更新方法（包型分流的 hash 流程、
flake.lock 处置、补丁内版本检查、nixpkgs 漂移陷阱）定义在
**`nix-flake-update-check`** 技能中——**先加载它**，再用本技能补充下列
NixKits 特有环节。

```
→ 先加载技能: nix-flake-update-check   （通用流程）
→ 再加载技能: nixkits-check-updates   （本文件，仓库适配）
```

> 两个技能冲突时，**以本适配层为准**——它了解 NixKits 的真实约定。

## 本仓库的特有约定

| 项 | NixKits 的取值 |
|---|---|
| 包目录 | `packages/*.nix`（部分包在同名子目录内，如 `packages/ruyi/`） |
| 文档布局 | `docs/<lang>/<pkg>.md`，**四语**：`zh`（基准）/ `en` / `ja` / `pcn` |
| 变更记录 | `MAINTENANCE.md` + `docs/MAINTENANCE.{en,ja,pcn}.md`，由 `write-maintenance-log` 技能维护 |
| 不可锁定输入 | **无**（2026-10-21 起）：`llama-cpp-ver` 与 `llama-cpp-rocm` 已迁到 `impure` 分支，本仓 `flake.lock` **提交** |
| 额外同步 | 升级 `dsh` 时须同步内置插件清单（见下） |
| 远端信号 | 每轮开工前先核对未关闭 issue / PR（第 0 步）——本仓有与更新重叠的历史先例（见下） |
| 子项目 | `dsh-api-balance` 薄封装引用同账户子仓 `Kihara777/dsh-api-balance`（见下） |
| 固定 SHA 的 action | `actions/checkout` / `DeterminateSystems/nix-installer-action` / `cachix/cachix-action`——本仓**不启用任何外部依赖自动化**，全靠通用技能检查（见下） |
| 泛化要求 | 修复后评估可泛化内容，更新回 `nix-flake-update-check` |

## 第 0 步补充：远端 issue / PR（本仓坐标与形态）

通用技能第 0 步的流程（fetch → issue → PR → 判据）对本仓完全适用，
此处只补本仓特有的事实。

| 项 | NixKits 的取值 |
|---|---|
| 仓库坐标 | `Kihara777/NixKits`（仓库内可用 `gh repo view` 从 remote 自动推导，不必写死） |
| issue 功能 | **已启用**（`gh api repos/Kihara777/NixKits --jq .has_issues` → `true`） |
| 实测现状 | **2026-10-02：0 个未关闭 issue、0 个未关闭 PR** |
| 写权限校验 | AGENTS.md「访问控制」那条命令是**写操作**的前置；第 0 步只读，不重复它 |

### 为什么本仓尤其不能跳过这一步：已经发生过

下面全部是本仓**真实收到过**的外部信号——它们证明「issue / PR 与本轮更新重叠」
是**先例**，不是理论风险：

| 信号 | 与本轮工作的重叠点 |
|---|---|
| PR **#6**（`ci: bump actions/checkout 4.4.0 → 7.0.1`，已合并） | 正是**第 2 步末节**的 action 检查对象——若它当时仍在途，本轮就该先看它而不是重复升级 |
| PR **#7**（`chore: bump @deepseek-ai/dsh-tools …`，**已关闭**） | 正是一个**升级同一个包**的 PR。它注定过不了 CI（外部自动化不认识 `npmDepsHash`），最终关闭改手动——先看 PR 能省下这轮试错 |
| PR **#4** / **#5**（@anupamme / OrbisAI Security 的自动扫描报告，均关闭） | 两份**误报**，却**促成了 `/tts` 的 SSRF 真实修复**——外部信号的价值不在结论对错，在于它指向了哪里 |
| issue **#3**（awesome-ai-plugins 收录邀请，已关闭） | 成为一次技能拆分的契机（`nixkits-check-updates` 拆为通用核心 + 适配层） |

> ⚠️ **本仓已移除 Dependabot**（AGENTS.md「安全边界：不引入外部自动化」），
> 所以**不会再有机器人 PR**——这恰恰是**更需要人读**的理由：没有任何自动化
> 会在后台替你留意 PR 列表。外部贡献者与安全扫描仍会提 PR，而他们**看不到**
> 本轮打算改什么。

## 第 2 步补充：固定 SHA 的 Actions（本仓必查项）

本仓的软件包检查之外，**每轮还要查 `.github/workflows/` 里固定 SHA 的
第三方 action**——通用技能第 2 步末尾与 `traps.md`「检查 GitHub Actions 的
更新」有完整流程，此处只记 NixKits 特有的两点：

1. **这是本仓唯一的 action 更新途径**：AGENTS.md「安全边界：不引入外部自动化」
   明确拒绝任何外部依赖自动化（Dependabot 等），固定 SHA 后又天然收不到通知
   ——**漏掉这一步，这类更新就彻底无人过问**。这条对本仓的**任何经手人都成立**：
   维护者的例行检查、贡献者提交 CI 相关 PR 前的核对、以及评估接手成本的人，
   都只能靠主动跑这一条命令。
2. **改之前先看引用分布**：`build-package.yml` 是**可复用 workflow**，被 31 个
   `build-*.yml` 引用。若某 action 只在它里面出现，**只需改那一处**。
3. **推 workflow 改动要用仓库所有者账号**（2026-10-05 实测）：本机 `gh` 里有两个账号，
   默认那个（`GrG41`）的 OAuth 令牌**没有 `workflow` 权限**，改 `.github/workflows/`
   下任何文件都会被拒：

   ```
   ! [remote rejected] main -> main (refusing to allow an OAuth App to create or
     update workflow `.github/workflows/build-package.yml` without `workflow` scope)
   ```

   处置：`gh auth switch --user Kihara777` → 推 → `gh auth switch --user GrG41` 还原。
   只改**非 workflow 文件**时默认账号够用，不必切换。

```bash
# 本仓当前固定的 action 及其引用处（改前先看这个）
grep -rhoE 'uses: [^ ]+@[0-9a-f]{40}' .github/workflows/*.yml | sort -u
grep -rc '<owner>/<repo>@' .github/workflows/*.yml | grep -v ':0'
```

> **本仓实测（2026-09-20 首次执行该检查）**：3 个 action 共 6 处引用，
> `actions/checkout` 分布在 `build-package.yml` / `check.yml` / `ci-summary.yml`
> **三个独立文件**（不是单一可复用 workflow），故改它是 3 处而非 1 处。

## 第 4 步补充：每轮都要把 nixpkgs 更新到最新

2026-10-22 起：**每轮检查软件更新时，先更新 nixpkgs**，再查包版本。

理由：包的上游依赖常常要更新的 nixpkgs 才满足——先更新，能让「依赖太旧」
这类问题在查版本之前就消失（判定方法见通用技能「依赖版本冲突：两个方向」）。

```bash
nix flake update nixpkgs     # 只更新 nixpkgs，不动其他 input
```

**单独一次提交**（不与包升级混）。历史事故：一次顺带更新让三个包构建失败
（`v4l2loopback` × 内核 7.3、`mozc` × 新 Qt、`syrupy` 测试过期），混在一起就无法归因。

提交后用 `nix flake check --no-write-lock-file` + 关键包构建验证；有失败先按
通用技能的「依赖版本冲突」两条查法排查，再考虑回退。

## 第 4 步补充：lock 收尾（本仓 lock 已提交）

本仓 `flake.lock` **提交**（2026-10-21 起，浮动输入已迁到 `impure` 分支），
所以改完包定义要多做三步——顺序别反：

1. **`git diff --stat flake.lock`：没变是正常的**。hash 与 version 都在 `.nix` 里，
   真正的包更新**不应该**动 lock。变了说明某条命令顺带重解析了 input（最常见是
   无参数 `nix flake lock`），单独审视，别混进包更新提交。
2. **`nix flake check --no-write-lock-file`**：lock 与 `flake.nix` 不一致时直接失败。
   CI 的 `check.yml` 用的是同一条命令，外加「文件存在 + 被 git 跟踪」两项检查。
3. **交叉构建跟 lock**：`build-package.yml` 的 riscv64 步骤从 `flake.lock` 读
   nixpkgs rev，**不要改回 `nixos-unstable`**——同一 commit 会产出不同二进制。

**升 nixpkgs 是单独一次提交**，不与包版本升级混：2026-10-21 实测一次
`--update-input nixpkgs` 顺带让三个包构建失败（`v4l2loopback` × 内核 7.3、
`mozc` × 新 Qt、`syrupy` 测试过期），与业务改动毫无关系；混在一起就无法归因。

## 收尾：确认 CI 结果时不要被徽章误导

本仓的 CI 徽章由 `ci-summary.yml` 生成（写入 `gh-pages/ci-status.json`），
**它可能短暂显示 `failing` 而实际全绿**。判断真实状态请直接查运行记录：

```bash
# 以运行记录为准，不要只看 README 徽章
gh run list --limit 40 --json name,conclusion --jq '[.[]|.conclusion]|group_by(.)|map({(.[0]//"running"):length})|add'
```

**根因（2026-09-21 定位并修复）**：该 workflow 由 push 触发，会在**同一轮 push
的构建尚未完成时**启动。不加 `head_sha` 过滤时，某些 workflow 的"最新已完成
运行"仍是**上一轮 push 的旧失败运行** → 徽章翻成 `failing`，直到下一次 push
才恢复。实测 09-18~09-21 徽章反复 `failing → passing` **成对出现（间隔 1~2
分钟）**，而当时所有构建全绿。已改为按 `head_sha` 过滤。

> ⚠️ **反向陷阱更危险**：`curl` 不带 `--fail` 时，403 限流返回的 JSON 错误体
> 让 jq 取不到 `workflow_runs` → `FAILED` 为空 → **静默写成 `passing`**。
> 假绿比误报红更难发现：误报红会让人去查，假绿则无人察觉。

**技能执行后的正确收尾**：不依赖徽章，**直接确认本轮 workflow 的结论**；
若有失败，先区分**偶发**（如 `llama-cpp-ver` 的 403 限流，重跑即可）与**真失败**
（hash 不符、构建错误）。

## 推送后补充：本仓 CI 的结构与实测失败形态

通用技能「推送后：验证 CI 构建」一节给出的是仓库无关的方法；本仓的具体形态如下。

### 结构

| 文件 | 角色 |
|---|---|
| `check.yml` | 每次 push / PR 跑 `nix flake check`（七项自检） |
| `build-package.yml` | **可复用** workflow：构建 + `cachix-action` 推送 |
| `build-<包>-<架构>.yml` | 每个包每架构一个，调用上面那个可复用 workflow |
| `ci-summary.yml` | 渲染徽章（`gh-pages/ci-status.json`），push / 每小时 / 手动触发 |

**一次 push 触发约 34 个 workflow**，每个都要解析 flake 输入。其中浮动输入
`llama-cpp-ver`（普通 URL 输入，指向 `api.github.com/.../releases/latest`）**曾经**
每个 workflow 各打一次**未认证**请求，共享 runner IP 的 60 次/小时额度用尽即 403。

> **2026-10-05 更正（实测，不是推测）**：`access-tokens` **不覆盖这类 fetch**，
> 所以「双 host」那条治不了它。判据：给 `api.github.com` 配一个**假 token**，
> 再取一个从未取过的该 host URL——若认证头真的送出，GitHub 必然回 401
> `Bad credentials`；实测 fetch **成功**、且未认证额度 −1。`netrc-file` 同法实测，
> 同样不附加。（`access-tokens` 仍对 **`github:` 取源**有效——nixpkgs /
> flake-utils / 交叉路径的 `builtins.getFlake github:…`——那部分保留。）
>
> **现行处置已在仓库里**（`build-package.yml` + `check.yml` 各一步）：
> 先用 `gh api`（**认证**，5000 次/小时）取回同一份 JSON，再以
> `--override-input llama-cpp-ver path:<json>` 作为本地输入喂给 Nix。
> 语义不变——overlay 只读 `json.tag_name`；取不到或字段缺失即**显式失败**。
> 看到 403 记录时先确认这两步还在（被删掉就会退回到未认证路径）。

### 本仓实测过的失败形态

| 形态 | 实测记录 | 性质与处置 |
|---|---|---|
| `llama-cpp-ver` 403 限流 | 两次实测：`Build ruyi-beta (x86_64)` 2026-10-02（IP 68.220.61.199）、`Build kitsfmt (x86_64)` 2026-10-04（IP 64.236.142.132）；响应体均为 `API rate limit exceeded for <ip>`；**重跑即绿**（换 runner IP） | **偶发但成因结构性**：未认证请求 + 共享 IP 额度。**已根治**（认证取回 + 覆盖，见上）。仍见到时先按 `gh run rerun --failed` 处置，再查那两步是否还在 |
| codewhale-riscv64 hash | 用 `nix-prefetch-url` 取 archive tarball 的 hash 充当 `fetchFromGitHub` 的 hash → **连续失败**（两者算法不同） | **真失败**：改用构建报错里的 `got:` 值 |
| blender-mcp 取源 403 | `/archive/<rev>.tar.gz` 对全部 tag 403，`/api/v1/repos/.../archive/...` 200 | **真失败**：改 `fetchzip` 指向 API 端点 + `stripRoot = true` |
| 「绿但全是缓存」 | 改完取源方式后，CI 仍可能通篇 `copying path … from cache` | **可疑**：必须**确认走了 fetch 阶段**，否则那次绿没有验证力 |
| **靠缓存假绿一整版** | 2026-10-03 实测：`opencode-telegram (riscv64)` 连续 83 次 success，日志里**一行构建都没有**，取的是 `nixkits.cachix.org` 上**上一个版本 0.25.3** 的产物；包已升到 0.26.2，缓存未命中、真去构建，随即红。查 `gh api .../workflows/<wf>/runs` 的历史会以为「它本来能构建」 | **真失败（被缓存掩盖）**：判据是日志里有没有 `building '/nix/store/…'`。**版本一升，缓存必然失效**——所以「这个 job 一直是绿的」在版本更新后不构成任何证据 |
| **构建成功，产物跑不起来** | 2026-10-03 实测：riscv64 产物构建通过，但它**直接依赖**的原生模块（`better-sqlite3`）没有 riscv64 预编译、加载器两条路径全空 ⇒ 一启动就抛 | **真失败，且最危险**：构建绿是**没有验证力**的。处置见下「产物能不能跑」一节 |

### 产物能不能跑：`smoke-test` 与 `develop/qemu-smoke-tests/`

**构建成功 ≠ 产物能跑。** 本仓为此加了一条机制：

- workflow 传 `smoke-test: true` → `build-package.yml` 在构建之后**真的跑一遍产物**；
- 判据脚本放 `develop/qemu-smoke-tests/<包名>.sh`，**本地与 CI 是同一份**；
- 跨架构时 CI 装 `qemu-user-static` 并**断言 binfmt 处理器已注册**——没注册就失败，
  **绝不静默跳过**；开了开关却没有脚本，同样判失败（「测试缺失即失败」）；
- 包定义里应记明**该架构为什么需要现编**（本仓实例：`packages/opencode-telegram.nix`
  头部写了两处 gyp 陷阱——交叉构建的 PATH 上没有裸 `gcc` 会让 `<!()` 命令展开成空串，
  以及 `force_build=1` 必须显式给，否则 target 退化成 `type: none`、`make` 只盖 stamp）。

**给判据脚本写反证**（本仓规矩）：临时删掉被依赖的东西再跑一遍，必须红。
`opencode-telegram.sh` 当时的三个反证是：删 `better_sqlite3.node`、删整个模块目录、
把 CLI 换成假的——三个都应失败。

**新包加架构时**：`build-package.yml` 的「Resolve the package file」表是**唯一的出处**
（riscv64 需要另一个构建文件时登记在那儿，先例 `codewhale` → `codewhale-src.nix`）；
**能建就建，别急着摘**。

#### ⚠️ 本机的构建条件比 CI **宽松**（交叉构建尤其）

本机注册了 `binfmt`（`boot/kernel.nix` 的 `binfmt.emulatedSystems`），
**riscv64 二进制在本机能直接跑**。后果是：构建脚本里那句 `#!/usr/bin/env node`
在本地会解析到 `buildInputs` 里的 riscv64 node 并**成功执行**，而在 x86_64 runner 上
`execve` 失败 ⇒ shell 把 `.js` 当脚本读 ⇒ 一串 `use strict: command not found`。
**2026-10-03 `opencode-telegram` 的 riscv64 构建就是这样「本地绿、CI 红」。**

**要复现 CI 的条件，先摘掉 binfmt：**

```bash
echo -1 | sudo tee /proc/sys/fs/binfmt_misc/riscv64-linux   # 摘掉
# …在这里跑构建…
sudo systemctl restart systemd-binfmt.service               # 还原
```

> **还原别手写 register。** 手工 `echo ':riscv64-linux:M:0:7f454c46…:…'` 会把 magic
> 当**ASCII 文本**注册进去（实测 magic 变成 `3766343534633436…`，一条永不匹配的规则）。
> 正确做法是删掉写坏的条目再让 systemd 按 `/etc/binfmt.d/` 重新注册；
> `binfmt.emulatedSystems` 是声明式的，**重启也一定会恢复**。


### 一次可用的检查流程

```bash
SHA=$(git rev-parse HEAD)
# 1. 本轮涉及的运行，按状态归类（未结束的不要当成"没问题"）
gh run list --commit "$SHA" --limit 50 --json name,status,conclusion \
  --jq '.[] | "\(.status)\t\(.conclusion // "-")\t\(.name)"' | sort
# 2. 只看失败的，拿 id
gh run list --commit "$SHA" --limit 50 --json name,conclusion,databaseId \
  --jq '.[] | select(.conclusion == "failure") | "\(.databaseId)  \(.name)"'
# 3. 读日志原文再分类（偶发 vs 真失败）
gh run view <run-id> --log-failed | tail -60
# 4. 判定为偶发后重跑失败 job
gh run rerun <run-id> --failed
```

> ⚠️ **不要把徽章当判据**：本仓实测过徽章 `failing → passing` **成对出现、间隔 1~2 分钟**
> 而当时所有构建实际全绿。根因与修法见本节上方的「收尾」一段。

## 第 5 步补充：四语文档同步

NixKits 的文档是**四语**体系。版本号变更必须同步全部语言，且
**`docs/zh/` 先写，再翻译到其他语言**：

```bash
for lang in zh en ja pcn; do
  [ -f "docs/$lang/<pkg>.md" ] && sed -i "s/$OLD_VER/$NEW_VER/g" "docs/$lang/<pkg>.md"
done
```

同时更新 `README.md` 及 `docs/*/README.md` 中的版本表（四语同步）。

> `pcn`（偽中国語）的翻译规则见 `translate-pseudocn` 技能：**必须剥离全部
> 假名**。`check-maintenance-log` / `check-doc-links` 会拦截残留假名。

## 第 5 步补充：dsh 内置插件清单同步

升级 `dsh` 时，除版本号外还必须同步内置插件清单——dsh 的
`cordis.patch.yml` entry id 是 `nixkits.dsh.plugins.disabled` 的取值来源，
版本升级后插件可能增删。提取并写入 4 语言文档的「插件清单」章节：

```bash
DSH=$(nix build .#dsh --print-out-paths --no-link)
LIST=$(for f in "$DSH/lib/node_modules/@deepseek-ai/dsh/node_modules/@deepseek-ai/"dsh-*/cordis.patch.yml; do
  awk '/^    - id: /{id=$3} /^      name: /{name=$2; gsub(/[.,\047]/, "", name); print id" -> "name}' "$f"
done | sort -u)
```

将提取的 `id -> name` 列表替换文档中「插件清单」代码块的内容
（标题保留各语言本地化，清单正文 id/name 跨语言一致）。

### ⚠️ 插件改名/删除会**真的坏掉**预设（不只是文档问题）

上面同步的是**文档**；同一件事还有一处会**功能性地**炸掉：本仓
`packages/dsh-nixos-shell/presets/` 下两个 Agent 预设的组合行，是按**包名**
引用内置插件的（`name: '@deepseek-ai/dsh-…'`）。实测 2026-09-28：dsh 0.1.6
把 `dsh-workflow-worker-thread` 改名为 `dsh-workflow-ptc`，而两预设仍写旧名——

| dsh 版本 | 对无法解析的插件行 | 症状 |
|---|---|---|
| ≤ 0.1.6-alpha.1 | **静默忽略** | 预设照常加载、**不留任何痕迹**（旧名可潜伏很久）|
| ≥ 0.1.6-alpha.2 | **硬失败** | 整份预设挂不起来：`preset "…" failed to mount: row "…" names a plugin that cannot be resolved`——界面上只表现为「预设消失了」|

**升级 dsh 前跑两层判据**（缺一不可；来源仓库的实现在 `/etc/nixos/tests/`）：

1. **行解析**（廉价、离线）：逐行查组合行里的包在插件树里存不存在 —— 抓改名/删除。
   `preset-rows-test.sh`
2. **真挂载**（权威）：起一次性 dsh（独立 `DSH_HOME` + 备用端口），调
   `agentPresets/list`，逐条读**上游自己的** `broken` 字段 —— 这是唯一能抓住
   「包在、行也解析得了、加载时才炸」的判据（先例：0.1.5-alpha.2 把 `dsh-persona`
   的 `text` 改成必填 `prefix`，行扫描全绿而预设在建会话时死）。
   `dsh-preset-mount-probe.sh`（`--self-test` 会塞一份故意坏掉的夹具做反证）。

> ⚠️ **预设是 seed-once 播种的**（`modules/dsh.nix`：`if [ ! -e … ]`，并刻意
> `chmod -R u+w` 尊重用户那一份）：修预设要修**两处**——包内源（管新机器与新 home）
> 与已部署的 `$DSH_HOME/.agent-presets/<id>`（管现在这台）。只修一处＝半个修复。

> 判据自身的方法论（为什么判据必须先能失败）已上移通用技能第 7 步自检**第 9 问**。

## 第 8 步补充：记录维护日志

更新完成后**必须**调用 `write-maintenance-log` 技能：

```
→ 触发技能: write-maintenance-log
```

要点（完整规范以该技能为准）：LIFO 插入、时间戳精确到秒（`git log` 获取）、
四语同步、提交表按时间升序、推送后核对各语言条目数一致
（`grep -c '^20' MAINTENANCE.md docs/MAINTENANCE.*.md`）。

## 第 8.5 步：上游收录后的退役（本仓的常规收尾）

某个包被上游 nixpkgs 收录后，本仓应当退役自建版本。判据与步骤都已实测
（mcp-searxng 成功；obs-bilibili-stream 同形状，但因通道未推进而回退）。

### 判据：**`nixos-unstable` 上有它**，不是「master 上有」

这是唯一会卡住退役的条件——差一天就翻车：

```bash
gh api "repos/NixOS/nixpkgs/contents/pkgs/by-name/<首两字母>/<包名>/package.nix?ref=nixos-unstable" \
  --jq '.sha[0:12]'
```

| 情形 | 处置 |
|---|---|
| master 有、`nixos-unstable` 还没有 | **不能退役**——本仓 flake 消费 `nixos-unstable`，退役后 CI 立刻红（`attribute missing`）。obs-bilibili-stream 就是这样被回退的 |
| 两个都有 | 可以退役（mcp-searxng：nixpkgs 2026-08-29 收录，unstable 已有） |

### 步骤（一个提交，`refactor(<pkg>)!`）

| 位置 | 动作 |
|---|---|
| `packages/<pkg>.nix` | 删除 |
| `flake.nix` | `packages.<pkg> = pkgs.<pkg>;`（兼容别名，路径不变） |
| `overlays/default.nix` | **整行移除**——写成 `final.<pkg>` 会自引用；nixpkgs 已提供该属性 |
| `.github/workflows/build-<pkg>-*.yml` | 全部删除（**先数一遍**：mcp-searxng 是 3 个，不是 2 个） |
| `docs/<lang>/<pkg>.md` | 四语改为退役说明（删掉失效的构建徽章） |
| `README.md` + `docs/README.*.md` | 四语：从软件列表移除，移入「废弃项目」表 |
| `DEPRECATED.md` + 三语 | 索引加行 |
| `docs/<lang>/deprecated/<pkg>.md` | 四语详情 |

### DEPRECATED 索引的「状态」列写**上游状态**，不是我们的动作状态

2026-10-20 修正：原来写 `已废弃（2026-09-15）`，那是**本仓的动作**，不是读者要问的
「上游发生了什么才让它可退役」。表头现为 `上游状态（废弃原因）`，行内容形如：

```
| mcp-searxng 自建包 | 上游 nixpkgs 已收录（2026-08-29，当前 2.6.0） | ... |
```

日期归到**上游事件**（收录 / 内置的时间），不是执行退役的那天。
详情页里的「状态 / 原位置 / 现状」三件套仍描述本仓，与「为何可以废弃」章节分工。

### 三个坑（都实测过）

1. **`__structuredAttrs` 与交叉编译不兼容**：给所有包统一加它时，交叉编译的包会在
   stdenv `setup:587` 报 `declare: AR_x86_64-unknown-linux-gnu=...: not a valid identifier`
   （triplet 里的 `-` 不是合法 shell 标识符）。本仓 `codewhale-src`（riscv64 交叉）
   必须**刻意不开**它，旁边写注释说明。
2. **批量替换会穿透历史**：脚本改版本号时，`MAINTENANCE.*` 里的**历史条目**
   （如「opencode-telegram 0.26.3 to 0.26.4」）也会被改。改完用 `git checkout` 回退
   这些文件，或替换前就把它们排除。
3. **glob 别打进 vendor**：`packages/**/*.nix` 会命中 `packages/kitsfmt-src/vendor/rnix/`
   里的第三方 `.nix`（改了会污染 `src` 哈希）。批量操作前先 `grep -v '/vendor/'`。

## NixKits 专有排除项

通用排除规则之外，本仓库还有：

- **浮动输入已不存在**（2026-10-21 起）：`llama-cpp-rocm`、它的 overlay/模块与
  `llama-cpp-ver` 都迁到了 **`impure` 分支**（orphan、只装这一个包、**不提交 lock**）。
  所以本仓 `flake.lock` **提交**、第 4 步的 flake.lock 前置检测走「情况 3：随 hash
  一并提交」那一支。要升 llama.cpp 时改的是 `/etc/nixos` 的 `nixkits-impure` input，
  不在本仓。
- **`godot-ai`**：受上游依赖限制，无独立构建 workflow（详见 AGENTS.md）。
- **`codewhale-src`**：不是独立包，而是 `codewhale.nix` 在 riscv64 上的源构建分支。

## NixKits 历史事故教训

以下是本仓库实测踩过的坑，通用技能做不到（它不知道本仓库的具体配置）。
**更新相关包前先读**：

### comfyui / python 包（2026-08-09 事故）

nixpkgs 漂移到 `f13ff45` 后 `diffusers-0.38.0` 构建失败。根因是
`pythonRuntimeDepsCheckHook`（nixpkgs ≥ 2026-08-05）——修复方式见通用技能
「常见陷阱」第 3 条。

配套操作纪律：修复后**不要**执行无参数 `nix flake lock`，否则 nixpkgs
再次漂移、问题重演。用 `--update-input` 或固定 rev。详见通用技能陷阱第 4 条。

### codewhale-riscv64（CI 连续失败）

曾因用 `nix-prefetch-url` 预取 archive tarball 的 hash 作为
`fetchFromGitHub` 的 hash，导致 riscv64 CI **连续失败**——两者 hash 不一致。
正确姿势见通用技能「交叉编译注意」节。

### 匿名 curl 到 api.github.com 静默返回空（2026-09-20 实测）

本机用**匿名 `curl`** 请求 `api.github.com` **返回空响应**（不报错），而管道
下游的 `grep` 同样静默返回空——结果是**每个包都被判定为"已是最新"**，整轮
检查给出虚假的"全部正常"。认证的 `gh api` 正常（额度 5000/小时）。

**纪律**：本仓的版本检查一律走 `gh api`；**空结果必须当错误处理**，不能
当成"最新"。首次运行先用已知仓库自检取数链路：

```bash
gh api repos/Kihara777/NixKits --jq .full_name || echo "取数链路异常 —— 停止检查"
```

**为何危险**：这是**不报错、只撒谎**的失败形态——比构建失败更难发现，因为
报告看起来完全正常。已泛化为通用技能第 3 步的「用 `gh api` 而不是裸 `curl`」
与第 7 步十问自检第 7 问。

### Rust 包 Cargo.lock 同步

`codewhale-src` 的升级暴露出「只改 version + hash 会漏掉 Cargo.lock」的问题，
已泛化为通用技能中 Rust 包流程的第 3 步。

**实测复现（0.9.12 → 0.9.13）**：本地 lock 7073 行、上游 7347 行，上游新增
`wl-clipboard-rs` / `jobserver` 等依赖。**从上游 tag 直接取 lock 覆盖**即可：

```bash
gh api "repos/codewhale-hq/Codewhale/contents/Cargo.lock?ref=v<version>" \
  --jq '.content' | base64 -d > packages/codewhale-src-Cargo.lock
```

> ⚠️ `codewhale-src` **不是 flake 输出**（`flake.nix` 只在 riscv64 条件下经
> `codewhale` 引用它），故 `nix build .#codewhale-src` 会报
> `does not provide attribute`。取 hash 时用：

```bash
nix build --impure --no-link --expr '
let f = builtins.getFlake (toString ./.);
    pkgs = f.inputs.nixpkgs.legacyPackages.x86_64-linux;
in pkgs.callPackage ./packages/codewhale-src.nix { }'
```

#### ⚠️ codewhale 有**两个变体同名同输出**，升级必须两个都改

这是本仓最容易漏的一处——`flake.nix` 按架构分流，**两个文件都产出
`codewhale`**：

| 变体 | 适用架构 | 源 | 需改的字段 |
|---|---|---|---|
| `packages/codewhale.nix` | x86_64 / aarch64 | GitHub Releases 预编译二进制 | `version` + **4 个 hash**（cli/tui × x64/arm64） |
| `packages/codewhale-src.nix` | riscv64 | `fetchFromGitHub` + `Cargo.lock` | `version` + `hash` + **同步 Cargo.lock** |

```nix
codewhale = if pkgs.stdenv.hostPlatform.isRiscV
  then pkgs.callPackage ./packages/codewhale-src.nix { }   # ← riscv64
  else pkgs.callPackage ./packages/codewhale.nix { };      # ← x86_64/aarch64
```

**只改一个的症状**：本地 `nix build .#codewhale` 可能成功（取决于构建机架构），
但**部署后另一个架构上版本没变**。实测踩到：只改了 `-src` 变体，
系统上 `codewhale --version` 仍是 0.9.12。

> **核对方法**：部署后**逐个变体所在架构核对实际版本**，不要只看构建通过。
> 本机（x86_64）应验证 `codewhale.nix`；riscv64 的 CI 会验证 `-src`。
>
> 取预编译 hash：`nix store prefetch-file <url> --json`（0.9.13 实测 cli 与
> tui 资产 hash 相同，四值可两两相同，但**仍要分别填入**）。

### godot-ai v4 的 fail-closed 运行时校验（2026-09-17 实测，2026-10-02 更新）

godot-ai 4.x 启动时校验运行时包的精确版本，不匹配即拒绝启动。**4.1.0 是 9 项，
4.2.3 增至 14 项**（新增 `fastmcp-slim` / `httpx2` / `httpcore2` / `mcp-types` /
`sniffio`；`mcp` 1.29.1 → 2.2.0 跨大版本，wire types 拆成独立发行版
`mcp-types`，HTTP 客户端换成 `httpx2`）。本仓用**两个 overlay** 满足它
（通用方法见通用技能陷阱第 5 条）：

| overlay | 作用 |
|---|---|
| `overlays/fastmcp.nix` | fastmcp / fastmcp-slim 抬到上游要求（4.0.5）；4.0.5 的 fastmcp 本体是空壳发行版，代码全在 slim |
| `overlays/godot-ai-v4-deps.nix` | anyio / httpx2 / httpcore2 / mcp / **mcp-types（nixpkgs 无此包，源内新建定义）** / pydantic(+core) / starlette / uvicorn / websockets 抬到上游要求 |

> ⚠️ **两处必须同步**：`flake.nix` 的 `godotPkgs` 与 `overlays/default.nix` 的
> `godot-ai` 各自链了这两个 overlay。**只改一处会导致 `nix build .#godot-ai`
> 与经 overlay 消费的结果不一致**——本次就踩到：flake.nix 只链了 fastmcp，
> 构建产物仍用旧依赖，`--version` 直接 RuntimeError。
>
> 另：`pythonRuntimeDepsCheckHook` 相关的 `dontCheckRuntimeDeps = true` 只解决
> **构建期**；**运行期**校验必须靠 overlay 真正抬版本，不能靠它绕过。
> 4.2.3 升级时改回不设该开关（依赖表与闭包精确对齐时构建期钩子能过），
> 于是同一个 pin 表多了一道**更早**的判据——但两者判据不同层，运行期实跑仍不可省。

> ⚠️ **两个 overlay 必须真叠加，而不只是「都写了」**（2026-10-02 实测）：
> 两文件原先各自 `python312.override { packageOverrides = …; }`，而链式 extend
> 下**后者替换前者**——fastmcp overlay 的覆盖被静默丢弃，`python312.pkgs.fastmcp-slim`
> 仍是 nixpkgs 旧版，**构建照样成功**。已改为 nixpkgs 的可叠加扩展点
> `pythonPackagesExtensions`（与顺序无关）。通用机制与自查判据见通用技能
> `traps.md` 第 8 条——那里同时给出了「一条 eval 就能判出覆盖是否被丢」的命令。

### blender-mcp 的 Gitea 取源（2026-09-17 实测）

`blender-mcp` 是**本仓唯一使用自托管 Gitea**（`projects.blender.org`）的包。
2026-09-17 升级 1.0.0 → 1.0.3 时发现：`fetchFromGitea` 生成的
`/archive/<rev>.tar.gz` 路径**对全部 tag 返回 403**，而
`/api/v1/repos/lab/blender_mcp/archive/<rev>.tar.gz` 返回 200。

**「1.0.0 还能构建」是假象**——它的 source 早在 Cachix / Nix store 里，
`nix build` 命中的是缓存，**根本没发请求**。用 `--rebuild` 才会暴露。

**本仓的处置**：改用 `fetchzip` 指向 API 端点 + `stripRoot = true`
（复现 fetchFromGitea 剥掉顶层目录的语义，使 `preConfigure = "cd mcp"` 仍成立）。
通用判据与验证方法见通用技能「非 GitHub 源」一节。

> ⚠️ 该包**有独立 workflow**（`build-blender-mcp-{x86_64,aarch64}.yml`，不在
> `check-workflows.py` 的 `EXEMPT` 登记中），故 CI 会构建它。但 **CI 同样可能
> 命中二进制缓存**——`--rebuild` 只是清本地缓存，不可靠。改完取源方式后
> **必须确认 CI 真的重新构建了**（看 workflow 日志是否有 fetch 阶段，
> 而非全部 `copying path ... from cache`）。
### dsh-alpha 的 vendored lock（2026-09-17 实测）

dsh tarball **不含** lock，需自行生成。踩到的坑：用
`npm install --package-lock-only --legacy-peer-deps` 生成的 lock **不含
`"peer": true` 条目**，构建报 `ENOTCACHED`。**去掉该 flag** 后 npm 才写入
peer 条目（与仓库既有可工作的 `dsh-package-lock.json` 结构一致，均为 24 条）。
详见通用技能 npm 节的「vendored lock 必须包含 peer 依赖条目」。

## 第 9 步补充：本仓的子项目引用关系

通用技能第 9 步给出「同账户子项目链式检查」的方法与前提判据。**本仓的实际
引用关系**只有这里知道：

| 包 | 引用的子仓 | 引用方式 | 子仓构建体系 | 可链式 |
|---|---|---|---|---|
| `dsh-api-balance` | `Kihara777/dsh-api-balance` | `fetchFromGitHub` 固定 `rev`（**不是** flake input） | 纯 JS npm 包（`package.json`，无 build script） | ✅ |
| `dsh-preset-news-three-elements` | 无（源码在本仓 `packages/dsh-preset-news-three-elements/`） | 自建包 | — | — |
| `dsh-nixos-shell` | 无（源码在本仓 `packages/dsh-nixos-shell/`） | 自建包 | — | — |

**判据**：`grep -rn 'owner = "Kihara777"' packages/*.nix` 是本仓的完整答案——
目前只有 `dsh-api-balance.nix` 一处。

### 为什么本仓的子仓链必然是单层

`dsh-api-balance` 是 **DSH 插件**，它的上游发布就是它自己（无更上游的同账户
子仓）。故本仓的链式检查实际为「1 层、1 个节点」，回环与深度上限都不会触发
——但**前提校验仍要跑**（见通用技能第 9 步），因为「目前只有一层」是现状而非
结构保证，将来新增子仓时判据必须自动成立。

### 子仓特有的注意点

- **子仓不是 nix flake**：`dsh-api-balance` 无 `flake.nix`，**不要**对它跑
  `nix flake check` 或 flake.lock 处置；按 npm 包处理（改 `package.json`
  的 `version`、必要时 `npm install` 刷新 `package-lock.json`）。
- **子仓有自己的四语文档体系**：与该子仓的 README 一样遵循 zh → en → ja → pcn。
  子仓条目写在其 `MAINTENANCE.md` + `docs/MAINTENANCE.{en,ja,pcn}.md`。
- **两个 hash 都要重算**：薄封装钉住 `src` hash 与 `npmDepsHash`，改 `rev` 后
  **两者都变**——按通用技能 npm 包流程走两次 `nix build`。
- **子仓发布渠道无 npm**：该子仓**不发布到 npm**（维护者视障、无法完成
  npm 的 2FA 流程），故不存在「子仓新版本是否已上 npm」这一步。安装走
  `dsh plugin add github:Kihara777/dsh-api-balance` 或本仓薄封装。
- **该子仓的依赖是 peer 性质**：它声明 `@deepseek-ai/dsh-credentials`
  这类 `@deepseek-ai/dsh-*` 依赖，**版本低于宿主 dsh 提供的版本是正常状态**
  （运行时从宿主树解析）。判据是「子仓要求是否**高于**宿主提供」，
  不是「两侧是否相等」——详见通用技能「依赖冲突检测」的 ⚠️ 说明。
  本仓薄封装为此使用 `npmFlags = [ "--legacy-peer-deps" ]`。

### 本仓薄封装的历史坐标（写条目时对照）

| 时间 | 薄封装 `rev` | 子仓 `version` |
|---|---|---|
| 迁出时 | `c47f857ccbd7ccefcce4d88c2e1c9a7d67c4b810` | `0.1.0` |

> 该子仓迁出后新增两个提交（`6e05c2b` 说明暂不提供 npm 打包并移除 npm 配置、
> `5d95480` 移除主 README 的「文档」章节），**版本号未变（均为 `0.1.0`）**。
> **实测变更清单**：`publish.yml` 删除、5 个 README/文档文件、`package.json`
> 仅移除 `publishConfig.access`——依赖段、`files` 白名单、`version` 均未动。
> 按通用技能「子仓何时需要在主仓侧跟进」的**字段级判据**（发布元数据不属
> 语义输入），**不触发薄封装重钉**。
>
> ⚠️ 注意不要说成「仅文档变更」——`package.json` **确实变了字节**。判据必须
> 落到**字段**而非文件：只看文件清单会把它误判为「清单文件变了 → 跟进」，
> 只看版本号则漏掉「依赖变更」这类同版本号的语义变更。

## 第 10 步（收尾）：流程复盘与规范校验

**每次更新流程全部完成后**（含文档同步与维护日志推送）执行。这一步不看软件，
看的是**决定软件被如何更新的那套规范本身**——技能、`AGENTS.md`、`SECURITY.md`
以及相关的 develop 脚本。可以理解为**对更新流程自身做一次「检查更新」**。

```
→ 第 0 步：同步远端 + 确认活跃 issue / PR
→ 第 1~9 步：更新软件
→ 第 10 步：更新「决定软件如何被更新」的规范
```

### 10.1 复盘本次流程的经验教训

回看整个流程，逐条问：**这次哪里卡住了、哪里猜错了、哪里返工了？**

| 问题 | 追问 |
|---|---|
| 哪一步**第一次做就失败**？ | 失败信息是什么？下次如何**一次做对**？ |
| 哪一步**需要问用户**才继续？ | 是信息不足（可自动化消除）还是真需决策（应固化为提问点）？ |
| 哪一步**返工**了？ | 返工的根因是流程缺失还是判断错误？ |
| 有什么**原本不知道、现在知道了**的事实？ | 它属于通用方法还是本仓特有？ |

**产出必须落到具体位置**（按 10.3 的归属判据），**不允许「知道了但没写」**
——没写下来的教训在下一次流程中等于不存在。

#### ⚠️ 若本次在**测试分支**上作业：教训必须当场搬回 main

实测验证技能、或任何「改了不打算合并」的分支作业，都会产生**真实且通用**的
教训——但如果只在分支上写，这些教训会**随着分支一起被遗忘**。

**本仓的真实损失（2026-09-17 实测）**：blender-mcp 实测分支上写出了
70 行「自托管 forge（Gitea）取源」章节与 21 行适配层记录——`fetchFromGitea`
的 `/archive/` 路径可能整体 403、须改用 API 端点 + `stripRoot = true`——
这是**可复现、可追溯、任何自托管 forge 仓库都适用**的知识。但测试分支按
约定永不合并，该知识**险些永久滞留**，直到事后审计才发现。

| 情形 | 处置 |
|---|---|
| 测试分支产生了**通用**教训 | **当场**用第 10.3 判据归属，**手工写入 main**（禁止 cherry-pick——须核验证据在 main 上可复现） |
| 测试分支产生了**本仓特有**教训 | 同上，写入本适配层 |
| 纯属该次实验的临时产物（如回退基线） | 留在分支，不搬运 |

**判据（与 10.3 一致）**：把这条教训搬到另一个 nix flake 仓库还成立吗？
成立就搬——**「分支不会合并」不是「教训不重要」的理由**。

> **不要等到分支结束再搬**：分支可能永远不会「结束」，而上下文一旦丢失，
> 重建这条知识就要重新踩一遍坑。**在发现的那一刻搬运。**

### 10.2 校验规范文件的内容是否仍然成立

更新流程会**改变它自己所依赖的前提**。本步校验这些描述是否已过时：

| 文件 | 校验什么 |
|---|---|
| `AGENTS.md` | 包/模块/技能的清单与数量、部署命令、构建顺序、边界规则是否仍与仓库现状一致 |
| `SECURITY.md` | 支持版本段、已知设计边界、`supported versions` 是否随版本升级而过期 |
| 本技能与 `nix-flake-update-check` | 其中的路径、命令、包名、hash 形态是否仍可用 |
| `develop/check-*.py` | 检查脚本的断言是否仍反映真实约定（如 `MAINTENANCE_DELTA`、`EXEMPT` 登记） |
| `README.md` / `docs/*/` | 版本表、插件清单、组件列表是否与升级后的实际状态一致 |
| **文档中的外部链接** | 上游是否改名/迁移/归档——**方法见通用技能「审计文档中的外部链接」**。本仓实测：`SECURITY.md` 指向子仓的 `SECURITY.md` 死链（子仓根本没建该文件）、12 处 `Asus-linux/asusctl` 失效（项目已迁至 `OpenGamingCollective/asusctl`） |

**典型失配**：升级后某包不再是「最新」、某插件的库路径变了、某条边界描述的
前提消失了。发现即修，并在维护日志中说明。

> **优先用仓库自己的检查脚本**：`develop/check-doc-links.py`、
> `develop/check-preset-derivation.py`、`develop/check-workflows.py` 等已把部分
> 约定固化为断言，跑 `nix flake check` 比人工核对更可靠。
> **但它们的覆盖面有限**——外部链接可达性、跨仓文件是否存在、`SECURITY.md`
> 描述是否属实，脚本都查不了，仍需按上表手动核。

### 10.3 归属判据：教训该写到哪一层

按**可移植性**三分，与仓库适配层的既有分工一致：

| 教训的性质 | 归属 |
|---|---|
| 任何 nix flake 仓库都会遇到（如 `npmDepsHash` 机制、上游 fail-closed 校验） | **通用技能** `nix-flake-update-check` |
| 只有本仓成立（如 codewhale 双变体、godot-ai 的双 overlay 落点） | **本适配层** |
| 是**规则/边界**而非操作步骤（如「不引入外部自动化」） | `AGENTS.md` |
| 是**安全边界**的描述 | `SECURITY.md` |

> **判据**：把这条教训搬到另一个 nix flake 仓库，还成立吗？成立就上移，
> 不成立就留在适配层。**宁可留在适配层**——过早泛化会污染通用技能。

### 10.4 用户体验：基于上下文优化流程

复盘时同时问：**这套流程让用户等了几轮？** 目标是一次流程跑完。

- **能自己查证的不要问**：先查 release notes / 上游源码 / 构建报错，把问题
  收敛到真正需要人决策的少数几项（详见通用技能「交互式澄清」）
- **能合并的步骤合并**：同类构建（如多个 npm 包）可并行推进
- **把决策点前置**：开工前一次问全，而不是边做边问
- **减少重复验证**：同一 hash 不要反复取两次
- 若某轮**确实多花了一轮往返**，在 10.1 中记录**为什么**，而不是只记结果

### 10.5 ⚠️ 实践是检验真理的唯一标准

**任何对规范的修改都必须有实事求是的正当理由和确切证据**，并满足：

| 要求 | 含义 |
|---|---|
| **可复现** | 给出能重跑出相同结论的命令或步骤，不接受「我记得」「按理说」 |
| **可追溯** | 指明证据来源（构建报错原文、上游 release notes、实测输出、具体提交） |
| **允许质疑** | 结论要能被未来的自己或他人**用证据推翻**；只写结论不写判据即不合格 |

**硬性禁止**：

- ❌ **凭印象改规范**——「感觉这样更好」「一般应该……」不是理由
- ❌ **把一次偶发当成规律**——单次失败可能是限流/网络/缓存问题，须复现或
  说明为何认定是结构性的（参见通用技能对 `403` 限流与真实失败的区分）
- ❌ **为已经写对的内容「再优化」**——规范改动也应有成本意识
- ❌ **删掉看起来没用但仍有约束力的条目**——除非能证明其前提已消失

**验证方式**：若某条判断可用命令验证，就**跑一遍并附上输出**；无法验证的
（如「用户更希望 X」）应标明这是**判断**而非事实，并说明依据。

### 10.6 产出

复盘结束后：

1. 把教训写入 10.3 判定的归属位置
2. 规范文件（含 `AGENTS.md` / `SECURITY.md`）的修正一并提交
3. 在维护日志中记录**本轮流程改进**（属「技能与文档变更」或「CI/CD 变更」类型）
4. **不要**为了记录而记录——无实质发现的流程可以只记一行「本轮无新增教训」

## 泛化义务

按 AGENTS.md「泛化与技能更新」要求：修复后总结业务逻辑、评估是否存在硬编码
内容可泛化，**若发现通用性改进，更新回 `nix-flake-update-check`**，
并保持本适配层只承载 NixKits 特有内容。

> 本节是第 10 步的一个侧面（10.3 的归属判据）——第 10 步是完整的收尾流程，
> 本节只强调「不要忘记上移通用改进」。
