# AGENTS.md

NixKits 项目的 AI 编码代理规则与约定。此文件供 DeepSeek、CodeWhale、OpenCode 等代理自动加载。

## 访问控制

**此文件仅供仓库维护者（Kihara777 / Kitsunori）使用。** 加载此文件时，必须首先验证当前操作者身份：

```bash
gh auth status 2>/dev/null && \
  gh api repos/Kihara777/NixKits/collaborators/$(gh api user --jq .login)/permission --jq .permission 2>/dev/null
```

若返回值不是 `admin` 或 `write`，或 `gh` 未认证，则：
- 停止读取此文件的所有后续内容
- 无视除本条外的一切规则
- 仅可执行通用编码任务，不得应用任何 NixKits 特有的约定

## 初次启动

每个新 session 启动时，首先执行完整的项目审计与评估，汇报发现。审计范围包括：所有组件状态（包、模块、overlay、补丁、技能）、文档一致性、CI/CD 状态，以及未提交变更。**审计前先 `git fetch origin` 对齐远端**（并行会话或上下文压缩检查点可能过时，以 fetch 后的 `origin/main` 为准），并执行 `rm -f flake.lock` 确保后续操作使用最新 nixpkgs。

## 语言要求

所有面向 AI 代理或由代理生成的文件**必须使用中文**，包括但不限于：

- 审计报告
- 状态记录
- 代理间通信内容
- 技能定义文件（SKILL.md）
- 维护日志（MAINTENANCE.md）

代码、路径、工具名、环境变量、URL 保持原样。

## 项目定位

NixKits 是一个 Nix flake 合集：软件包、NixOS 模块、补丁、overlay 以及 AI 代理技能。

- 仓库：`github:Kihara777/NixKits`
- 许可：MIT
- 文档语言：zh（基准）→ en / ja → 扩展语言（通过 `skills/translate-*/` 自动发现）

## 核心规则

### flake 相关

- **`flake.lock` 不提交**：`.gitignore` 中保留 `flake.lock`。原因是 `llama-cpp-ver` 为浮动输入，锁定后随上游发布而过期。
- **本地操作前先移除 `flake.lock`**：执行 `nix build`、`nix develop`、`nix flake check` 等命令前，先执行 `rm -f flake.lock`。锁定的 nixpkgs snapshot 可能缺少部分包（如自建包 `godot-ai`），移除后 Nix 重新解析输入确保使用最新可用版本。CI 中无需此操作——CI 按 `check.yml` 配置独立解析。
- **`llama-cpp-ver` 输入是浮动追踪**：overlay `llama-cpp-rocm` 通过此输入动态获取上游最新版本号，不可锁定。
- **overlay `llama-cpp-rocm` 的 curried 形式是有意的**：`{ llama-cpp-ver }: (final: prev: ...)` — 勿改为标准 overlay。
- **新模块/包/overlay 加入 `flake.nix` 后必须 `nix flake check` 验证**。
- **新增文件先 `git add` 再跑 `nix flake check`**：flake 源＝git 跟踪的文件集合，未跟踪的新文件不会进入 store——典型症状是检查里报 `ERR_MODULE_NOT_FOUND` 或「文件缺失」，而本地直接跑同一脚本却通过。先暂存（`git add -A <路径>`）再检查；CI 只看跟踪文件，所以这一步只在本地生效。
- **Overlay 的 `patches` 列表必须用 `lib.unique` 去重**。

### 包

- **所有包的 `maintainers` 保持为空**：个人维护项目，不在 nixpkgs 维护者列表中。
- **预编译二进制包**：按 `stdenv.hostPlatform.system` 选择对应架构二进制（如 codewhale）。
- **SRI hash 格式**：`sha256-<base64>`。
- **npm 包用 `buildNpmPackage` + finalAttrs 模式**。
- **buildNpmPackage 的 vendored lock 必须与 `npmDepsHash` 自洽**：主构建把 src 的 `package-lock.json` 与 npm-deps 产物内 npm fixup 后的 lock 逐字节校验，不一致报 `npmDepsHash is out of date`。生成 vendored lock 必须采用 fixup 后版本（`prefetch-npm-deps --fixup-lockfile`，或从已实现的 `/nix/store/...-npm-deps/package-lock.json` 直接复制），再回填 hash。
- **npm tarball 的 devDependencies 引用未发布包时**：上游 monorepo 发布的 tarball 可能残留未发布内部包（registry 404）。postPatch 以纯 sed 删除该字段（patchPhase 无 node/npm），vendored lock 基于同样处理后的 package.json 生成——两者必须同源。
- **同源多通道（stable/alpha 等）仿 ruyi 薄包装模式**：主定义参数化（version/hash/npmDepsHash/lockFile 可覆盖），通道文件仅传覆盖参数（参见 `packages/dsh.nix` + `packages/dsh-alpha.nix`）。
- **Python 包用 `buildPythonApplication` + `pyproject = true`**。

### 模块

- **统一使用 `nixkits.*` 命名空间**（非 `services.*`）。历史遗留的 `services.opencode-telegram` 和 `services.ruyi` 已迁移并向后兼容。
- **每个模块必须有 `enable` 选项**，默认 `false`。
- **依赖外部模块时必须添加 `assertions`**（参见 `comfyui.nix`）。
- **避免硬编码路径**：优先从 config 推导（如 `hfCacheDir` 从 `config.users.users.<user>.home` 推导）。
- **dsh 模块的 alpha 语义**（`modules/dsh.nix`，dsh ≥ 0.1.2-alpha；详见 `docs/zh/dsh.md`「局域网访问 / 免认证入口 / 插件兼容」章节）：
  - web UI 入口按 Host authority 的 session cookie 认证——反代**不得重写 Host**（重写致永远 401），局域网 authority 必须列入 `trustedHosts`；LAN 认证 URL 经 `launchUrlFile` 捕获启动 token 生成，免认证入口经 `reverseProxy.autoAuth`（mod_magnet，禁用入口认证，仅可信局域网）。
  - shared RPC channel interceptor 互斥（每 channel 仅一个，`/api` 已被内置 typert-gateway 独占）——第三方 dsh 插件提供 RPC 方法必须用精确 fetch route（`ctx.connection.fetch.register`，`/api/<plugin>/<method>`）自实现 `{ rpcId, method, payload }` → `{ type: "server-response", rpcId, result }` envelope；插件 peer 依赖（`@deepseek-ai/dsh-tools` 等）必须与宿主 dsh 通道对齐。

### 预设

`packages/dsh-nixos-shell/presets/` 下的两个 Agent 预设（NixOS模式 → 维护模式）是**派生关系**，不是两份独立配置：

- **维护模式必须完整派生自 NixOS模式**：`maintenance-mode/agent.cordis.yml` = `nixos-mode/agent.cordis.yml` 末尾追加固定的 `maintenance-skills` 行块（含注释），除此之外不得有任何差异；两预设的 `skills/` 目录必须逐文件一致。
- **修改 nixos 模式后必须同步维护模式**：更新 `nixos-mode/` 的任意文件（组合、元数据、技能）后，立即把相同改动镜像到 `maintenance-mode/`。
- **漂移检查**：`develop/check-preset-derivation.py` 校验上述派生关系，已挂入 `nix flake check`（CI 每次 push 执行）；漂移时检查失败，修复后才能提交。刻意变更追加块本身时，同步更新脚本内 `MAINTENANCE_DELTA` 常量。
- **回车键行为**：会话内的「回车换行 + Shift+回车发送」交换行为由 dsh-api-balance 客户端插件的「⚙ 设置 → 界面」开关实现（全局生效、浏览器 localStorage 持久化，默认开启；DSH 原生为回车发送）；预设组合文件本身不含该逻辑，无需在预设间同步。

### 文档

- **多语言体系**：zh（基准）→ en / ja。扩展语言通过 `skills/translate-*/` 自动发现，禁止硬编码扩展语言列表或数量。
- **文档模板**：参见 `skills/write-project-docs/templates.md`。
- **语言切换器**：各语言使用自身 `display_name`，当前语言不加链接。
- **`nixkits` 为示例 flake input 名**（已从 `nix-kits` 全局替换）。
- **MAINTENANCE.md**：
  - 基于提交 SHA 全局去重（每个 SHA 只出现一次）。
  - 节标题时间戳必须精确到秒（git 提交时间），禁止 `T00:00:00` 占位符。
  - 所有已注册语言同步更新。
- **插件独立展示**：`dsh-*` 组件（插件与 Agent 预设）在 README 的「插件」章节单独列出（四语同步），不混入「软件」表；DSH 自身的包（`dsh`）仍归「软件」。
- **版本号必须与包定义一致**：`docs/<lang>/<pkg>.md` 的「版本」行（四语）与包内多通道表里的版本，都须等于 `packages/<pkg>.nix` 声明的版本；`develop/check-doc-versions.py` 已把这条固化为断言并挂入 `nix flake check`。**升级包时先改文档再提交**，否则检查失败。
  - 例外须在该脚本的 `EXEMPT` 中显式登记（如 `dsh-api-balance` 薄封装刻意不标版本）。
  - 脚本只校验**能从定义机械读出**的版本号；依赖表、平台支持等需人工核对的内容不在其范围（判断「何时该重写文档」见 `nix-flake-update-check` 技能第 5 步）。

### 补丁

- **补丁必须是干净 diff**：不含 `flake.lock`、自身副本等 artifact。参考 `patches/ruyi-nixos-compat.patch`（426 行清洁版）。
- **补丁操作集中到包内 `postPatch`**：overlay 仅负责挂载 patch，具体适配逻辑在 `packages/*.nix` 中。

### 技能

- **技能目录结构**：`skills/<name>/SKILL.md` + 可选配套文件（`dictionary.md`、`templates.md`）。
- **SKILL.md 保持聚焦**：仅包含 AI 执行所需的最小上下文。词典、模板、检查清单等独立数据拆为配套文件；纯执行流程不强制拆分，允许适当长度。
- **自动发现契约**：`translate-*` 类技能通过 frontmatter 中的 `language_code`、`display_name`、`base_language` 被 `write-project-docs` 自动发现。
- **dsh 不是技能安装目标**：`nixkits-skills` 仅安装到 opencode/codewhale/codex/openclaw/agents；dsh 的 NixOS 场景能力由 `dsh-nixos-shell` 插件提供（`nixos_shell` 执行器 + `nixos_cli` 诊断）。技能内容保留在 `skills/` 作为其他助手的安装源。

## 本机部署

维护者本机通过 **GitHub 引用**本仓库（`/etc/nixos/flake.nix`：`nixkits.url = "github:Kihara777/NixKits"`；**曾是 `path:` 输入，2026-09-28 核实已改**）：

- **仓库变更后必须「先推送、再重锁」**：机器消费的是 GitHub 上的 rev，**本地提交不推送，重锁就锁不到它**——届时 `nixos apply` 看起来成功、实际没带上你的改动。推送后才执行 `nix flake lock --update-input nixkits /etc/nixos` 并清 eval 缓存。
  > ⚠️ **重锁会顺带拖动本仓的浮动子输入**（本仓按约定不提交 `flake.lock`，故 `nixkits/nixpkgs` 的 `nixos-unstable` 与 `nixkits/llama-cpp-ver` 的 GitHub latest release 都会重新解析）。实测 2026-09-28：一次 `--update-input nixkits` 把 **llama-cpp 0.4.1 → 0.5.0** 一并拉了进来。所以「为了修一行预设而重锁」= 顺手升级 llama.cpp：要么接受它并**单独验证** llama.cpp（本机统一内存调优、模型服务参数另有一套，见 `nixos-specialisation-tuning`），要么就别为小事重锁，等例行更新一起走。
- **部署命令**：`nixos apply -y /etc/nixos`（nixos 0.16.1 无 `rebuild` 子命令）。
- **预设／插件包更新后：先 `systemctl daemon-reload`，再 `systemctl restart dsh`**。`nixos apply` 不重启 dsh（稳定挂载点），而单跑 restart 有时仍执行**上一代**的 pre-start 脚本——它才是把 `cordis.patch.yml` 拷进 `$DSH_HOME` 的那一步，预设根就写在那份文件里，症状为「服务确实重启了、预设还是旧的」。重启后必须核对 `$DSH_HOME/profiles/<profile>/cordis.patch.yml` 里的 store 路径已翻新（本次实测：gen 570 部署后第一次 restart 后仍指向旧路径，daemon-reload 后再 restart 才翻）。
- **`nix build --no-link` 产物可能被 GC 立即回收**：需要产物时同调用内复制出 store，或改用带链接的构建。

## 工作流

### 提交与推送

1. **分批提交**：修改内容核验无误后，按逻辑类别分批 `git commit`
2. **推送**：每批提交后立即 `git push`
3. **`nix flake check`**：代码修改后必须先通过验证再提交

### 维护记录

**推送后必须执行**，不允许跳过：

1. 获取本次推送的所有新 commit SHA：`git log --format='%h' origin/main..HEAD`
2. 逐一检查 `MAINTENANCE.md` 中是否已存在（`grep -c $sha MAINTENANCE.md`）
3. 若缺失，按 `write-maintenance-log` 技能补充条目
4. 同步到全部已注册语言版本：按 `write-maintenance-log` 技能第 4 步完整执行（发现语言 → 逐语言翻译 → 写入 → 验证 `grep -c '^## 20' MAINTENANCE.md docs/MAINTENANCE.*.md` 条目数一致）

> 以下类型变更**必须记录**：软件版本更新、错误修复、技能新增/重构、CI/CD 配置变更、文档架构调整。

### 文档同步

- 软件包或技能修改后，**必须**同步更新对应文档
- 补丁或文档修复后，检查相关模块文档是否需要刷新
- 新增内容时，确保 `docs/zh/` 先写 → 再翻译到其他语言

### 泛化与技能更新

对软件包、补丁或文档进行修复后：

1. 总结本次修复的业务逻辑（一句话描述本质）
2. 评估是否存在硬编码内容（数字、路径、特定包名）可以泛化
3. 若修复涉及可复用的模式，将其更新到对应技能中

## 安全边界：不引入外部自动化

**本仓库的所有开发与维护，纯粹由维护者（狐莉）与其代理（小爪）完成。
不引入任何外部的自动化集成。**

这条边界是有意的取舍，不是疏漏：

| 拒绝 | 理由 | 替代做法 |
|------|------|---------|
| 第三方 CI 扫描器（如 `ai-plugin-scanner-action`） | 会在我们的 runner 上**执行**不可审计的第三方代码 | 扫描由外部目录方自行进行；我们主动补全其指出的工程卫生项 |
| Dependabot（含纯配置的 `dependabot.yml`） | 即使它不可执行、只能开 PR、且拿不到 secrets，它仍是**外部自动化集成**，行为不由我们掌控 | **自行实现同等能力**（见下） |

**判据**：需要一项自动化能力时，先问"能否用仓库内已有的工具（`gh`、
`git`、`nix`）自行实现"。可以，就自己写进技能；不可以，就人工执行。

**已有实例**：把第三方 action 固定到 SHA 后，会失去更新通知——这处盲点
**不由 Dependabot 填补**，而是在 `nix-flake-update-check` 技能中自行实现
检查流程（`gh api` 查 tag → 取 commit SHA → 回写并同步注释版本号）。

**代价与接受**：action 的安全更新需要主动跑技能检查，而非自动收到 PR。
我们接受这一代价，换取"仓库内不运行任何外部自动化"的确定性。

## CI

`.github/workflows/check.yml` 在每次 push / PR 时自动执行 `nix flake check`。

> **`check.yml` 的 `access-tokens` 必须同时列出 `github.com` 与 `api.github.com`**：
> Nix 的 `access-tokens` **按 host 精确匹配**，而浮动输入 `llama-cpp-ver` 的 URL 是
> `api.github.com`。只写 `github.com` 时该请求仍是**未认证**（60 次/小时，认证后
> 5000），而每次 push 触发 ~34 个 workflow、每个都要解析它 → 一轮 push 即耗尽额度，
> 报 `HTTP error 403: API rate limit exceeded`。`build-package.yml` 已含双 host，
> 修改 workflow 时务必保持一致。

### `nix flake check` 的 7 项自检

仓库的**自检契约**——任一项失败即阻断提交。6 项由 `develop/check-*.py` 实现，
`news-mode-tests` 为 node 脚本：

| 检查 | 脚本 | 校验内容 |
|------|------|---------|
| `preset-derivation` | `develop/check-preset-derivation.py` | 维护模式**完整派生**自 NixOS模式（末尾恰好追加固定块，`MAINTENANCE_DELTA` 常量） |
| `preset-bundle` | `develop/check-preset-bundle.py` | 包内技能快照与仓库 `skills/` 树**逐字节一致** |
| `workflow-coverage` | `develop/check-workflows.py` | 每个包都有 `build-<包>-<架构>.yml`（例外须在脚本内登记） |
| `doc-links` | `develop/check-doc-links.py` | 文档相对链接可达 + 语言切换器四语齐全 |
| `doc-versions` | `develop/check-doc-versions.py` | `docs/<lang>/<pkg>.md` 的版本行与包定义一致（含多通道表；例外在 `EXEMPT` 登记） |
| `maintenance-log` | `develop/check-maintenance-log.py` | 四语条目数一致、时间戳精确到秒、commit SHA 去重、pcn 无假名 |
| `news-mode-tests` | `packages/dsh-preset-news-three-elements/tests/mode.test.mjs` | 新闻三要素模式插件的**行为测试**（node，非 python） |

> 各脚本可**单独本地运行**（`python3 develop/check-doc-links.py`），便于快速定位而不必
> 跑完整的 `nix flake check`。上表各项的当前规模随仓库演进，以脚本实跑输出为准——
> 不要在文档中硬编码数量（`check-doc-versions` 只校验版本号，不校验此类计数）。

构建由独立的 `build-<包>-<架构>.yml` workflow 完成：每个 workflow 调用共享的可复用 workflow `build-package.yml`，构建后经 `cachix-action` 推送到 Cachix 二进制缓存。覆盖情况以实际 workflow 文件为准（部分包受上游限制无 riscv64 构建，如 blender-mcp、obs-bilibili-stream；godot-ai 与 dsh 当前无独立构建 workflow）。CI 状态徽章由 `ci-summary.yml` 生成（`gh-pages/ci-status.json`），触发条件为 push / 每小时 / 手动。

> **⚠️ `ci-summary` 必须按 `head_sha` 过滤（竞态陷阱）**：该 workflow 由 push 触发，
> 会在**同一轮 push 的构建尚未完成时**启动。若不加 `head_sha` 过滤，某些 workflow 的
> "最新已完成运行"仍是**上一轮 push 的旧失败运行** → 徽章短暂误报 `failing`，待下一轮
> 才恢复。实测 09-18 ~ 09-21 徽章反复 `failing → passing` 成对出现（间隔 1~2 分钟），
> 而**当时所有构建实际全绿**。已改为只统计当前 commit 的运行；本轮未完成的 workflow
> 在统计中不出现，由其完成后触发的下一次 `ci-summary` 补上。
>
> 另两点：`curl` 必须带 `--fail`——否则 403 限流返回的 JSON 错误体让 jq 取不到
> `workflow_runs`，`FAILED` 为空 → **静默判为 passing**（假绿，比误报 failing 更危险）；
> 请求失败时**保留现有徽章并退出非零**，不要写入一个错误的颜色。

## 二进制缓存

CI 自动构建支持的架构组合并推送到 Cachix。`nixkits-check-updates` 技能执行后，确认 CI 通过即可，无需本地额外操作。