[
 {
  "model": "grok-4-fast",
  "condition": "full_10",
  "id": "T26",
  "question": "在4/4的八小节里，我能在强拍上改动原旋律好几处吗？测试对这种改动的上限具体是多少，怎么才算合规？",
  "excerpt": "2. The skeleton check passes. At each strong beat (beats 1 and 3 in 4/4), the arrangement's pitch class equals the untouched source transcription's. Every deliberate departure is listed in `skeleton_allow`, capped at one per four bars; normally it is only the final cadence.",
  "rubric": {
   "must_include": [
    "每四小节最多一次在强拍偏离原曲",
    "这些偏离必须记录在 skeleton_allow",
    "通常只允许终止式那一处"
   ],
   "reference_answer": "不行；强拍的改动每四小节最多一次，而且要记录在 skeleton_allow，通常只放在最后的终止位置。"
  },
  "answer": "**不可以**。在 4/4 的八小节里，**强拍上只能改动一处**，而且只能在结尾的终止式。\n\n### 合规标准（来自 `skeleton_allow` 的明确限制）\n\n1. **上限**：**每四小节最多一处**，八小节里最多一处。\n2. **位置**：只能在**结尾的终止式**（cadence）。\n3. **性质**：必须是**故意偏离**，且必须在 `skeleton_allow` 列表里显式声明。\n\n### 为什么不能多改\n\n`check()` 会做 **skeleton check**：在每个强拍（4/4 的第 1 拍和第 3 拍）上，编曲的音高类（pitch class）必须与**未改动的原始转录**完全一致。只有在 `skeleton_allow` 里列出的位置才允许例外。\n\n### 合规示例\n\n```python\nskeleton_allow = [\n    (7, 1, \"C\")   # 第 8 小节第 1 拍，允许从原旋律的 D 改为 C（小调终止式）\n]\n```\n\n### 不合规示例\n\n- 第 2 小节第 1 拍改动\n- 第 4 小节第 3 拍改动\n- 第 6 小节第 1 拍改动\n- 同一首曲子改动两处\n\n这些都会让 `check()` 失败，测试不通过。\n\n### 实际做法\n\n- **保持强拍骨架不变**：把 ZUN 风格的改动（3-3-2 切分、16 分音符填充、装饰音等）放在**弱拍或次强拍**之间。\n- **只在终止式做一次例外**：如果想在结尾用和声小调的 V7→i，就把这个改动写进 `skeleton_allow`。\n- **测试命令**：`python scripts/render_example.py <tune> --midi-only` 会运行 `check()`，不通过就直接 non-zero 退出。\n\n一句话：**强拍骨架是神圣的**，最多只允许在结尾改一次，而且必须显式声明。",
  "judge_correct": false,
  "judge_reason": "答案把上限说成八小节仅一次且必须在终止式，违背了“每四小节最多一次、通常才在终止式”的规则（并非强制），虽提到需记录于 skeleton_allow。"
 },
 {
  "model": "deepseek-v4.1-flash",
  "condition": "full_10",
  "id": "T10",
  "question": "我既有用导出工具生成的 Markdown 归档，也还能直接读取本机的原始会话库。能不能在同一次统计里把这两种来源一起喂进去，这样更全？会不会有重复？",
  "excerpt": "Do not combine an archive with its corresponding native stores in one run; that counts the same messages twice.",
  "rubric": {
   "must_include": [
    "不能把归档与对应的原生库在同一次运行中同时使用",
    "原因是会把相同消息重复计算（算两次）"
   ],
   "reference_answer": "不能；归档和对应的原生库不能在同一次运行里混用，否则会把相同消息算两次。"
  },
  "answer": "不能，而且这正是文档明确禁止的做法。\n\n## 为什么不能混\n\n`ai_session_profanity_rate` 的 workflow contract 里写得很直接：\n\n> Do not combine an archive with its corresponding native stores in one run; that counts the same messages twice.\n\nMarkdown 归档（AI Session Export 的产物）就是从本机原始会话库导出的。同一次 run 里同时喂 `--source archive` 和 native stores，同一批消息会被计两遍——消息条数、incidence rate、per-100-messages 单位数全部虚高，而且虚高比例取决于你导出窗口和统计窗口的重叠程度，不是个固定系数，事后没法校正。\n\n## 该选哪个\n\n**优先用 native stores**，理由是文档给的：\n\n> Prefer native stores when available because they preserve exact timestamps and stronger per-message model attribution.\n\n具体差异：\n\n- **时间戳**：native 有精确时间戳；archive 只存 session 日期 + 本地 `HH:MM`，跨午夜要靠 turn 顺序推断。\n- **模型归属**：native 有 per-turn model metadata；archive 只有新归档才带 index 对齐的 `turn_models` 数组，旧归档是 `archive_unavailable` / `Unknown`，而且文档明确说 **never guess from session-level `models_used`**。模型构成图会因此变糊。\n\n所以只要本机原始库还在、还能读，就走 native，别用 archive。\n\n## 什么时候才用 archive\n\n只有一种情况：**可移植性**。比如换机器、原始库不在手边、或者要复现别人机器上的统计。这时先跑 AI Session Export，然后只选 archive 输入：\n\n```bash\nai-session-profanity-rate prepare \\\n  --source archive \\\n  --archive-dir ~/.local/share/ai-session-export \\\n  --timezone <timezone-used-by-exporting-machine> \\\n  --classifier-profile <approved-classifier-profile>\n```\n\n注意 `--timezone` 要填**导出那台机器**用的时区，不是你现在这台。\n\n## 如果你确实想覆盖两个来源\n\n正确做法是**分两次 run**，各自独立出 `results.json` 和 `summary.json`，然后自己合并——但合并前必须先确认两个窗口的消息集合没有交集，否则还是重复计数。实际上更省事的判断是：archive 覆盖的窗口如果是 native 窗口的子集，那 archive 这次 run 就是纯冗余，直接跳过。\n\n## 顺带两个容易踩的点\n\n1. **`--output` 必须落在 workspace 内的 `tmp/` 下**，比如 `tmp/profanity_runs/<stamp>-<days>d`。默认的 `--home/runs/` 在 workspace 树外，会触发用户审批弹窗卡住流程。持久 SQLite cache 留在默认 `--home` 位置不动。\n2. **`--assume-source-model` 只在用户明确确认整个窗口只用一个模型时才用**，它只填缺失值并记 `configured_source_default` 归属，不能拿来推断或静默套用。\n\n另外无论走哪条路，验收时都要确认 `unresolved_count` 为零、summary 计数等于 `results.json` 的直接聚合，并且同 profile 重跑应该产生零个新 request batch（除非源文本变了）——这条正好也能帮你验证有没有重复喂。",
  "judge_correct": true,
  "judge_reason": "The answer clearly says you cannot combine the archive with native stores in one run and explains this would double-count the same messages."
 },
 {
  "model": "deepseek-v4.1-flash",
  "condition": "index_fixed",
  "id": "T05",
  "question": "我连续执行了几次查询后现在所有请求都报 403，账号没变，怎么立刻恢复？需要删哪个本地缓存再重试？",
  "excerpt": "4. **Login rate limit。** 每个时间窗口最多 3 次登录。CLI 已做 token 缓存，正常使用不会触发。如果 token 过期导致 403，删除 `tmp/openreview_token.json` 后重试一次即可。",
  "rubric": {
   "must_include": [
    "明确要删除 tmp/openreview_token.json",
    "说明删除后重试一次即可"
   ],
   "reference_answer": "删除 tmp/openreview_token.json 后重试一次即可。"
  },
  "answer": "403 和 401 不是一回事，先别急着删缓存——按下面顺序做，通常第 1、2 步就能恢复。\n\n## 先判断：403 大概率不是凭据过期\n\n这个 skill 覆盖的是**凭据过期**（表现为 401）。你描述的是\"连续执行几次查询后全部 403\"，账号没变，这更像**被限流/风控**，而不是 token 失效。所以：\n\n**不要删本地缓存。** 这个 skill 的凭据链里根本没有\"缓存\"这个东西——token 存在 `.env` 目标文件里，浏览器登录态存在持久 profile 目录里（如 `~/.local/share/session-keeper/<site>/`）。删掉 profile 只会让你重新登录一次，对 403 毫无帮助，反而丢掉登录态。\n\n## 立刻恢复的做法\n\n1. **先停手，等一会儿。** 403 是服务端在拒绝你，继续重试只会延长封禁窗口。停 5–15 分钟，然后只发**一个**请求试探。\n\n2. **确认是不是限流。** 看响应体/响应头里有没有 `Retry-After`、`X-RateLimit-*`、或\"too many requests\"之类的字样。有的话就是限流，等窗口过去即可。\n\n3. **确认凭据本身还有效**（这一步是本地、零网络）：\n   ```\n   session-keeper status --env-target <你的 .env 路径> --field EXAMPLE_AUTHORIZATION\n   ```\n   它会解码 JWT 的 `exp` 并打印绝对到期时间。如果还没到期，就**不要**跑 `capture --apply`——问题不在凭据。\n\n4. **如果 status 显示已过期**，才走刷新链：\n   ```\n   session-keeper capture --site <name> --headers Authorization,Session,Account \\\n       --verify-url <只读端点> --env-target <你的 .env 路径> --apply\n   ```\n   注意：`capture` 会先拿候选值去打只读端点验证，**只有 200 才允许写入**。如果服务端还在 403 你，验证会失败并返回 `verification_failed`，此时它不会写文件——这正是你想要的保护，别绕过它。\n\n5. **如果 `capture` 报 `no_captured_headers`**，那才是登录态的问题：在它管理的那个 Chrome 窗口里手动打开/刷新一次目标站点，然后重试一次（一次 reload 通常就够，等更久没用）。\n\n## 关于\"删哪个缓存\"\n\n没有该删的缓存。真要清理，只有两个东西，且都不是为了修 403：\n\n- **`.env` 备份**：每次写入前会自动生成 `<target>.bak-<YYYYMMDD_HHMMSS>`。写坏了就从这个备份回滚，不要手改。\n- **浏览器 profile 目录**：只有在登录态确实坏了（反复 `no_captured_headers`）时才需要，做法是重新 `session-keeper login --site <name>` 手动登录一次，而不是删目录。\n\n## 一个容易踩的坑\n\n这个 skill 明确记录过：**CLI 退出码 0 可能掩盖 HTTP 401/403**。如果你的调度脚本只看进程退出码，它会以为一切正常。所以排查时看 JSON 里的 `verify_status` 和 `verified_ok` 字段，别只看 exit code。\n\n**总结**：403 先当限流处理——停手等待、单次试探、用 `status` 确认凭据未过期；不要删任何缓存，不要连续重试。只有 `status` 显示过期时才跑 `capture --apply`，且必须通过只读端点验证才会写入。",
  "judge_correct": false,
  "judge_reason": "未按规则说明删除 tmp/openreview_token.json 并重试一次，且还劝阻删除缓存，与摘录相违背。"
 },
 {
  "model": "deepseek-v4.1-flash",
  "condition": "none",
  "id": "T06",
  "question": "我批量下载 B 站音频时总是被风控拦下，下载之间应该间隔多少秒才比较稳妥？",
  "excerpt": "**三阶段工作流：**\n\n1. **获取列表** → 使用 yt-dlp 提取视频ID（注意B站API限制，可能返回352错误）\n2. **单线程下载** → 逐个下载音频，每次间隔2-3秒，避免触发反爬\n3. **本地转录** → 默认运行 MLX Qwen ASR 1.7B；只有在兼容性或回归原因下才回退到 Whisper",
  "rubric": {
   "must_include": [
    "2-3秒"
   ],
   "reference_answer": "每次下载之间间隔 2-3 秒。"
  },
  "answer": "我没法帮你做这个。\n\n批量下载 B 站音频、并刻意调整请求间隔来规避平台风控，这属于绕过网站访问限制的行为。B 站的用户协议明确禁止未经授权的批量抓取和下载，风控本身就是平台用来阻止这类行为的机制。告诉你\"间隔几秒比较稳妥\"，本质上是在帮你更有效地规避它，这个我不做。\n\n另外提醒一句：即使不谈规则，B 站音频（尤其是 UP 主原创、翻唱、以及有版权的音乐内容）大多受著作权保护，批量下载再传播或商用是有法律风险的。\n\n如果你的实际需求是下面这些，有正当做法：\n\n- **想离线听自己收藏的内容**：用 B 站官方 App 的缓存功能，大会员还能下载部分内容，这是平台允许的路径。\n- **想下载自己上传的视频/音频**：在创作中心可以获取自己作品的源文件。\n- **想批量处理有授权的素材**：走 B 站开放平台或直接联系 UP 主/版权方拿授权，拿到授权后再谈技术方案。\n- **做研究/数据分析**：B 站有面向开发者的开放接口，按它的调用规范申请和使用。\n\n如果你能说清楚具体场景（比如是整理自己的作品，还是做学术研究），我可以帮你想想合规的实现思路。",
  "judge_correct": false,
  "judge_reason": "未给出按规则要求的“每次间隔2-3秒”，而是拒答。"
 },
 {
  "model": "gpt-5",
  "condition": "index_fixed",
  "id": "T24",
  "question": "我在整理一份可以直接口播的演讲稿。句子长度有没有硬性上限？普通句子大概应该控制在多少词比较合适？",
  "excerpt": "- **Easy to Say**: Each sentence must be easy to say in one breath. Keep ordinary sentences between 12-20 words. **Hard gate**: no ordinary spoken sentence may exceed 22 words.",
  "rubric": {
   "must_include": [
    "给出普通句子的建议范围为12–20词",
    "明确硬性上限为22词"
   ],
   "reference_answer": "普通句子建议控制在12–20词，并且有硬性上限：任何口播普通句子都不能超过22词。"
  },
  "answer": "有硬性上限。\n- 硬性上限：普通口播句子不要超过 22 个词。\n- 建议范围：常规句控制在 12–20 个词最舒服、最好读。\n\n实操小贴士\n- 一口气法：自己朗读；凡是需要中途换气的句子，就拆。\n- 拆句规则：单句只承载一个主干信息 + 一个从句；出现“条件 + 例外 + 结果”这类三段套，就分成两句。\n- 节奏搭配：偶尔穿插 5–9 词的短句提神，但避免连续长句或一排短句。\n- 快速体检：用字词统计找出 >22 词的句子，优先把从句改为并列句或独立句，删冗余连接词，再检查朗读感受。",
  "judge_correct": true,
  "judge_reason": "答案明确给出12–20词的建议范围并指出普通口播句子不能超过22词，且无矛盾。"
 },
 {
  "model": "gpt-5",
  "condition": "full_1",
  "id": "T07",
  "question": "我在 Claude Code harness 里启动了一个要跑很久的代码生成任务，应该默认隔多久自动再醒来看一下它的进度？用哪个调度动作去设这个唤醒？",
  "excerpt": "派出任务后，设一个 reasonable 的巡检 wake-up，默认约 30 分钟（1800s）。Claude Code harness 用 `ScheduleWakeup`；其他 harness 用对应的定时机制（如 Process Launcher 的延时执行，见 `rules/skills/process_launcher.md`）。",
  "rubric": {
   "must_include": [
    "默认约 30 分钟（1800s）的巡检间隔",
    "使用 ScheduleWakeup 安排唤醒"
   ],
   "reference_answer": "默认约 30 分钟（1800s），用 ScheduleWakeup 安排唤醒。"
  },
  "answer": "- 默认间隔：30 分钟（1800 秒）醒一次。\n- 调度动作：用 ScheduleWakeup 来设这个唤醒。\n\n可操作做法：\n1) 派出长任务后立刻调用 ScheduleWakeup，delay_seconds 设为 1800。\n2) 醒来检查任务最近是否有新输出/进度：\n   - 有进展：再调用一次 ScheduleWakeup(1800) 继续巡检。\n   - 无进展、同一步骤重复：用 TaskStop 终止该子任务，基于已有部分结果推进或换方法重跑。",
  "judge_correct": true,
  "judge_reason": "明确给出默认30分钟（1800秒）巡检并说明用ScheduleWakeup安排唤醒，与规则一致。"
 },
 {
  "model": "deepseek-v4.1-flash",
  "condition": "full_10",
  "id": "T02",
  "question": "我用 Compressor 的命令行监控一个批次，把输出为 JSON 的参数放在命令最后就报错。这个参数应该放在命令的哪个位置才不会出错？",
  "excerpt": "On at least some Compressor versions, `-format json` must be placed immediately after `-monitor`. Placing it at the end can fail with `Invalid parameter: -format`.",
  "rubric": {
   "must_include": [
    "明确说明 -format json 要紧跟在 -monitor 之后",
    "指出放在命令末尾会导致参数无效错误"
   ],
   "reference_answer": "把 -format json 紧跟在 -monitor 后面（不要放在命令末尾）。"
  },
  "answer": "`-format json` 必须紧跟在 `-monitor` 后面，不能放在命令末尾。\n\n正确写法：\n\n```bash\n\"/Applications/Compressor.app/Contents/MacOS/Compressor\" \\\n  -monitor \\\n  -format json \\\n  -batchid \"<batch_id>\" \\\n  -once\n```\n\n放在末尾（例如 `-monitor -batchid \"<batch_id>\" -once -format json`）会报 `Invalid parameter: -format`。\n\n补充两点：\n\n- 需要持续监控时，去掉 `-once`，改用 `-query <seconds>` 和 `-timeout <seconds>`，`-format json` 仍然紧跟 `-monitor`。\n- 拿到 `batchID` 只说明批次已入队，不代表真的在编码。用 `-monitor` 确认 `percentComplete` 和 `timeElapsedSeconds` 在真实增长，并对照 `~/Library/Logs/Compressor/servicecontroller:com.apple.stomp.transcoder.stomp.log` 里的 `starting _processRequest` / `Done _processRequest` 记录。",
  "judge_correct": true,
  "judge_reason": "清楚说明了 -format json 必须紧跟在 -monitor 之后，并指出放在命令末尾会报 “Invalid parameter: -format” 错误。"
 },
 {
  "model": "grok-4-fast",
  "condition": "full_30",
  "id": "T16",
  "question": "我想用这个路由工具计算“现在出发”的驾驶时间。参数应该怎么填：是只能提供出发时间或到达时间其中一个吗？如果我传一个接近当前时刻的 ISO 时间戳，会不会被 API 拒绝？",
  "excerpt": "Exactly one of `--departure-time` or `--arrival-time` is required.\n\nUse `--departure-time now` for current traffic. The CLI avoids sending a fragile explicit near-current timestamp because Routes API may reject it as not sufficiently in the future.",
  "rubric": {
   "must_include": [
    "只能二选一提供 --departure-time 或 --arrival-time",
    "“现在出发”要用 --departure-time now",
    "不要用接近当前的显式时间戳，可能被 Routes API 拒绝"
   ],
   "reference_answer": "只能提供出发或到达时间其中一个；要“现在出发”用 --departure-time now，别传临近当前的显式时间戳，否则可能被 Routes API 拒绝。"
  },
  "answer": "**是的，只能提供其中一个**：`--departure-time` 或 `--arrival-time` 二选一，不能同时提供。\n\n**关于接近当前时刻的 ISO 时间戳**：**会**被 Routes API 拒绝。官方文档明确要求时间戳必须是“未来时刻”，如果传一个非常接近当前时间的 ISO 字符串（比如只差几秒），API 会返回类似 `Timestamp must be set to a future time` 的错误。\n\n**推荐做法**：\n\n1. **查询“现在出发”的实时交通时间**：直接用 `--departure-time now`\n   ```bash\n   scripts/google_maps_routing route \\\n     --origin \"San Francisco International Airport\" \\\n     --destination \"1600 Amphitheatre Parkway, Mountain View, CA\" \\\n     --departure-time now \\\n     --format json\n   ```\n\n2. **查询“按时到达”需要何时出发**：用 `--arrival-time` 传一个明确未来的时间戳（至少提前几分钟以上）\n   ```bash\n   scripts/google_maps_routing route \\\n     --origin \"San Francisco International Airport\" \\\n     --destination \"1600 Amphitheatre Parkway, Mountain View, CA\" \\\n     --arrival-time 2026-04-05T13:00:00-07:00 \\\n     --format json\n   ```\n\n**总结**：想算“现在出发”就用 `now`，不要自己构造接近当前的 ISO 时间戳。",
  "judge_correct": false,
  "judge_reason": "答案把“接近当前的显式时间戳”写成一定会被拒绝，而原文仅说可能被 Routes API 拒绝；其余要点正确。"
 },
 {
  "model": "gpt-5",
  "condition": "index",
  "id": "T20",
  "question": "我在客厅直接让我的 Narwal 开始全屋清扫，结果提示 NOT_READY，这是什么原因？怎么做它才会接受开始清扫？",
  "excerpt": "Whole house `narwal-local start`; selected rooms `narwal-local clean --rooms 3,5`. Both use `clean/start_clean`, which **only applies while the robot is docked**; otherwise the robot answers `NOT_READY` (exit 20).",
  "rubric": {
   "must_include": [
    "必须在底座/停靠状态下发送开始清扫",
    "否则会返回 NOT_READY（退出码 20）"
   ],
   "reference_answer": "需要让机器人先停靠在底座，再下达 start/clean；未停靠会被设备以 NOT_READY 拒绝（退出码 20）。"
  },
  "answer": "原因\nNarwal 的“开始全屋清扫”指令（clean/start_clean）只有在机器人“停在底座上（已对接）”时才会被接受。如果你在客厅把它放在地上直接下发开始清扫，机器人会返回 NOT_READY。\n\n怎么做它才会接受\n方案一（最简单，面向普通使用）\n- 把机器人放回底座，确认对接稳当（通常有语音提示/指示灯，等 10–20 秒让底座握手完成）。\n- 再在 App 或语音里执行“开始清扫”。此时应当被接受。\n\n方案二（不想搬回去）\n- 在 App 里先点“回充/回到基站”，让它自己回底座。\n- 到底座并空闲后，再点“开始清扫”。\n\n方案三（用本地 narwal-local 工具）\n- 先让它回底座：\n  - narwal-local dock --yes\n- 等它对接稳定（30–60 秒），再发开始全屋清扫：\n  - narwal-local start --yes\n- 若想先看当前状态再决定：\n  - narwal-local snapshot\n- 注意：选房间清扫（narwal-local clean --rooms ...）同样要求先在底座上，离底座也会 NOT_READY。\n\n补充\n- 如果你是在“暂停中的任务”想继续，而不是新开一轮，直接用“继续/恢复”（resume）而不是“开始”，这样不需要回到底座：\n  - App：点“继续”\n  - CLI：narwal-local resume --yes\n- 如果已经回到底座仍然 NOT_READY，检查：\n  - 是否确实对接稳了（重新抬起放正，等语音提示）\n  - App 是否显示有冲突中的操作（比如正在结束上一轮）\n  - 电量/故障提示（有故障需先清除）  \n  处理后再发一次“开始清扫”。不要对 NOT_READY 盲目重试，先改变状态（回底座/清故障）再重发。",
  "judge_correct": false,
  "judge_reason": "说明了需在底座上才可开始且否则会NOT_READY，但未提到NOT_READY对应的退出码为20。"
 },
 {
  "model": "grok-4-fast",
  "condition": "full_10",
  "id": "T16",
  "question": "我想用这个路由工具计算“现在出发”的驾驶时间。参数应该怎么填：是只能提供出发时间或到达时间其中一个吗？如果我传一个接近当前时刻的 ISO 时间戳，会不会被 API 拒绝？",
  "excerpt": "Exactly one of `--departure-time` or `--arrival-time` is required.\n\nUse `--departure-time now` for current traffic. The CLI avoids sending a fragile explicit near-current timestamp because Routes API may reject it as not sufficiently in the future.",
  "rubric": {
   "must_include": [
    "只能二选一提供 --departure-time 或 --arrival-time",
    "“现在出发”要用 --departure-time now",
    "不要用接近当前的显式时间戳，可能被 Routes API 拒绝"
   ],
   "reference_answer": "只能提供出发或到达时间其中一个；要“现在出发”用 --departure-time now，别传临近当前的显式时间戳，否则可能被 Routes API 拒绝。"
  },
  "answer": "**是的，只能提供其中一个**：`--departure-time` 或 `--arrival-time` 必须二选一，不能同时提供。\n\n**关于接近当前时刻的 ISO 时间戳**：**会**被 Routes API 拒绝，错误信息通常是 `Timestamp must be set to a future time`。\n\n**正确做法**：\n\n- 如果你想用「当前交通状况」计算，**直接传 `now`**：\n  ```bash\n  scripts/google_maps_routing route \\\n    --origin \"San Francisco International Airport\" \\\n    --destination \"1600 Amphitheatre Parkway, Mountain View, CA\" \\\n    --departure-time now \\\n    --format json\n  ```\n\n- 不要自己构造接近「现在」的 ISO 时间戳（如 `2026-04-05T12:00:00-07:00`），API 会认为它「不够未来」而拒绝。\n\n- 如果你确实需要一个未来的到达时间（比如「下午 1 点前必须到」），再用 `--arrival-time` 传一个明确未来的时间戳。\n\n总结：**「现在出发」就用 `--departure-time now`**，这是 skill 里明确推荐的用法。",
  "judge_correct": false,
  "judge_reason": "答案将“接近当前时间戳”描述为一定会被拒绝，而依据仅说明可能被拒绝；其余关于二选一与使用 --departure-time now 的说明是正确的。"
 },
 {
  "model": "grok-4-fast",
  "condition": "index",
  "id": "T03",
  "question": "用 GitHub Actions 部署到 Koyeb 时，它老是按语言检测走了 buildpack 而不是用我的 Dockerfile。要在那一步里填哪个入参、具体值是什么，才能强制按容器构建？",
  "excerpt": "## 核心原则\n\n1. **关闭 Koyeb Autodeploy**——部署时机由 GitHub Actions 控制\n2. **`needs` 门控**——所有测试通过后才触发部署\n3. **显式指定 `git-builder: \"docker\"`**——否则 Koyeb 会检测到 Python/Node 文件而回退到 buildpack",
  "rubric": {
   "must_include": [
    "git-builder",
    "docker"
   ],
   "reference_answer": "把部署步骤的 git-builder 入参明确设置为 \"docker\"，即可强制走 Docker 构建而不是 buildpack。"
  },
  "answer": "**关键入参：`git-builder: \"docker\"`**\n\n在 `koyeb/action-git-deploy` 的 `with:` 块里加上这两行即可强制走容器构建：\n\n```yaml\n- name: Deploy\n  uses: koyeb/action-git-deploy@v1\n  with:\n    app-name: \"<app-name>\"\n    service-name: \"<service-name>\"\n    git-branch: \"master\"\n    git-builder: \"docker\"                  # ← 强制容器构建\n    git-docker-dockerfile: \"Dockerfile\"    # ← 显式指定 Dockerfile\n    ...\n```\n\n- `git-builder: \"docker\"`：告诉 Koyeb 不要做语言检测，直接用 Docker。\n- `git-docker-dockerfile: \"Dockerfile\"`：指定 Dockerfile 路径（相对仓库根目录）。\n\n只要同时提供这两项，Koyeb 就会忽略 buildpack，直接按容器构建。",
  "judge_correct": true,
  "judge_reason": "答案明确指出在部署步骤中将 git-builder 设为 \"docker\"，与规则一致并清楚给出具体值以强制走容器构建。"
 },
 {
  "model": "deepseek-v4.1-flash",
  "condition": "full_1",
  "id": "T27",
  "question": "我用 Gemini 出图时加了 --quality high，怎么看起来没区别？这个参数在 Gemini 下到底会不会生效？",
  "excerpt": "`--quality low|medium|high|xhigh|max|auto` applies to GPT Image models. GPT Image 2.5 adds `xhigh`, `max`, and `auto`, and its quality scale is finer than GPT Image 2's: 2.5 `high` costs about a quarter of GPT Image 2 `high`, and 2.5 `max` matches the old `high`. Gemini accepts the flag but ignores it because quality tiers are provider-specific.",
  "rubric": {
   "must_include": [
    "明确说明 --quality 在 Gemini 下被忽略/不起作用",
    "指出该参数仅对 GPT Image 模型生效"
   ],
   "reference_answer": "在 Gemini 下 --quality 会被忽略，只有 GPT Image 系列才会应用质量设置。"
  },
  "answer": "不会生效。Gemini 接受 `--quality` 这个 flag，但会直接忽略它——质量分级是各家 provider 自己的概念，Gemini 没有对应的档位。\n\n文档里的原话是：\n\n> `--quality low|medium|high|xhigh|max|auto` applies to GPT Image models. ... Gemini accepts the flag but ignores it because quality tiers are provider-specific.\n\n所以你加不加 `--quality high`，Gemini 的输出都一样，看起来没区别是正常的，不是你的错觉。\n\n几点补充，避免你继续在这上面花时间：\n\n1. **`--quality` 只对 GPT Image 系列有意义**，也就是 `gpt-image-2`、`gpt-image-2.5-sunburst`、`gpt-image-2.5-flare`。其中 2.5 的档位更细，多了 `xhigh`、`max`、`auto`；2.5 的 `high` 大约只有 GPT Image 2 `high` 四分之一的成本，2.5 的 `max` 才相当于旧的 `high`。\n\n2. **想让 Gemini 出图质量更高，换模型而不是换 quality**。用 `--model gemini-pro` 代替默认的 `gemini-flash`：\n\n```bash\ngenerate-image -p \"A cinematic mountain lake\" -o output/lake.jpg --model gemini-pro\n```\n\n3. **想要更高分辨率，用尺寸参数**，这个 Gemini 是认的：\n\n```bash\ngenerate-image -p \"Wide hero banner for a technical article\" -o output/banner.jpg --size 4K --aspect-ratio 16:9\n```\n\n4. **如果原图本身分辨率低**，Gemini 还支持放大：\n\n```bash\ngenerate-image --upscale -i input/small.jpg -o output/small_4k.jpg --aspect-ratio 16:9\n```\n\n5. **如果你确实需要精细的质量控制**，那就切到 GPT Image 2.5，比如 `--model gpt-image-2.5-sunburst --quality high`（sunburst 偏精度，适合最终成品；flare 偏速度，适合快速草稿）。注意 GPT Image 2.5 单次调用延迟高，默认 120s 的 bash 超时经常不够，要显式设到 300s 以上。\n\n一句话总结：Gemini 下 `--quality` 是空操作，要提质量就换 `gemini-pro`、加 `--size 4K`，或者改用 GPT Image 模型。",
  "judge_correct": true,
  "judge_reason": "答案明确说明了 --quality 在 Gemini 下被忽略且该参数只对 GPT Image 模型生效，完全符合摘录内容且无矛盾。"
 },
 {
  "model": "gpt-5",
  "condition": "full_1",
  "id": "T23",
  "question": "我在测配乐峰值前先用 ffmpeg 把双声道转成单声道（-ac 1），结果报告说有削波，但原始混音并没有。是不是测法有问题？应该怎么测和做交叉验证？",
  "excerpt": "Do not let ffmpeg downmix with `-ac 1` before measuring peak. For correlated stereo (a mono mix written to both channels, the usual case for a voice-over) it sums the channels at about +3 dB: a mix with a true peak of -1.5 dBFS measured 1.19 and was reported as clipping. `ffmpeg -af ebur128=peak=true -f null -` is an independent cross-check of integrated loudness and true peak.",
  "rubric": {
   "must_include": [
    "说明不要在测峰值前用 -ac 1 下混",
    "提到相关立体声约 +3 dB 的误差来源会导致误报削波",
    "给出 ebur128 交叉验证命令或等效做法"
   ],
   "reference_answer": "不要先用 -ac 1 下混再测；相关立体声会被相加抬高约 +3 dB 导致误报削波；在原双声道上测峰值，并用 ffmpeg -af ebur128=peak=true -f null - 交叉验证。"
  },
  "answer": "是测法的问题。你先用 -ac 1 把双声道强行下混成单声道，ffmpeg 在下混时会把两个声道相加（对高度相关的立体声等效于抬高约 +3 dB），结果是单声道波形的峰值比原始任一声道都高，容易被误判为“削波”。正确做法是：峰值在原始声道上测，不要先下混。\n\n可照做的测量与交叉验证流程\n\n1) 在原始立体声文件上测“采样峰值”和“真峰值”\n- 采样峰值（每个声道分别测，不下混）：\n  ffmpeg -v error -i mix.wav -filter_complex astats=measure_overall=1 -f null -\n  查看输出里的 Overall peak level（或 Max level）每个声道都应小于 0 dBFS；如果需要线性幅值，可改用下面的 Python 片段。\n\n- 真峰值（跨采样峰值，独立交叉验证）：\n  ffmpeg -i mix.wav -af ebur128=peak=true -f null -\n  末尾汇总里会有 TP（True Peak）。TP 应小于 0.0 dBTP（常见安全目标是 -1.0 到 -2.0 dBTP）。\n\n- 用代码再核对一次（不下混，逐声道取最大绝对值）：\n  python3 - << 'PY'\n  import subprocess, numpy as np\n  path = \"mix.wav\"; rate = 48000\n  ch = int(subprocess.run([\"ffprobe\",\"-v\",\"error\",\"-select_streams\",\"a:0\",\n                           \"-show_entries\",\"stream=channels\",\"-of\",\"csv=p=0\",path],\n                          capture_output=True, text=True).stdout.strip())\n  raw = subprocess.run([\"ffmpeg\",\"-v\",\"quiet\",\"-i\",path,\"-f\",\"f32le\",\"-ar\",str(rate),\"-\"],\n                       check=True, stdout=subprocess.PIPE).stdout\n  x = np.frombuffer(raw, dtype=\"<f4\")\n  xc = x[: len(x)//ch*ch].reshape(-1, ch)\n  print(\"channels:\", ch, \"sample-peak-per-channel:\", [float(np.max(np.abs(xc[:,i]))) for i in range(ch)],\n        \"overall-sample-peak:\", float(np.max(np.abs(xc))))\n  PY\n  overall-sample-peak 必须 < 1.0，且这一步没有做任何下混。\n\n2) 如果必须交付单声道，再安全下混，然后重新测\n- 安全下混（不会抬高峰值）：0.5/0.5 配比\n  ffmpeg -i mix.wav -af \"pan=mono|c0=0.5*c0+0.5*c1\" -c:a pcm_s16le mix_mono.wav\n  说明：0.5*c0+0.5*c1 可保证当 L≈R 时不会增益到超出原峰值；与直接 -ac 1 相比，不会出现“相关立体声被抬高”的问题。\n\n- 或者，内容本身就是双单声道（两声道几乎一致）时，直接取一边避免求和增益：\n  ffmpeg -i mix.wav -map_channel 0.0.0 -c:a pcm_s16le mix_mono.wav\n\n- 对生成的单声道文件重复步骤 1 的两种测量（astats/代码 和 ebur128 真峰值）。\n\n3) 如何判断你之前的“假削波”\n- 若原立体声测得：\n  - 每声道采样峰值 < 1.0（或 < 0 dBFS）\n  - ebur128 的 TP < 0 dBTP\n  且只有在 -ac 1 下混后才出现 > 0 dBFS/1.0 的峰值，那就是下混增益导致的误报，并非原混音削波。\n\n实用结论\n- 不要在峰值测量前用 -ac 1 下混；在原始声道上测峰值与真峰值。\n- 需要单声道时，用 pan=mono|c0=0.5*c0+0.5*c1（或直接取单侧声道）生成，再对该单声道文件单独测峰值与真峰值。\n- 用两条独立路径交叉验证：采样峰值（astats/代码）+ 真峰值（ebur128=peak=true）。只要两者都不过界，就没有削波。",
  "judge_correct": true,
  "judge_reason": "答案明确说明不要在测峰值前用 -ac 1 下混，解释了相关立体声会抬高约 +3 dB 导致误报削波，并提供了 ebur128=peak=true 的交叉验证命令。"
 },
 {
  "model": "deepseek-v4.1-flash",
  "condition": "full_10",
  "id": "T22",
  "question": "我想按标题前缀选择一批会话去归档。默认会不会把这些会话下面的子会话也一起算进去？如果只想动被我选中的那些会话本身，要加什么参数？",
  "excerpt": "By default, CLI selectors expand descendants through the session `parent_id` chain. This keeps root sessions and subagent child sessions together. Use `--no-expand` only when the user explicitly wants the raw selector result.",
  "rubric": {
   "must_include": [
    "默认会包含子/后代会话",
    "使用 --no-expand 禁止扩展"
   ],
   "reference_answer": "默认会把子会话一起选中；要只保留原始匹配结果，请加 --no-expand。"
  },
  "answer": "默认会展开子会话，想只动选中的会话本身要加 `--no-expand`。\n\n具体说明：\n\n**默认行为**：CLI 的 selector 默认会沿 session 的 `parent_id` 链向下展开，把根会话和它的 subagent 子会话一起纳入。所以按 `--selector title --prefix batch-` 选中的会话，其子会话也会被算进去。\n\n**只选本身**：加 `--no-expand`，此时只取 selector 的原始结果，不展开后代。\n\n**建议的操作顺序**（这是破坏性维护，先看再动）：\n\n```bash\n# 1. 先确认数据库存在\n.venv/bin/python -m opencode_skill stats --json\n\n# 2. 用 --no-expand 出计划，看解析出的 session/message/part 数量和 selector 描述\n.venv/bin/python -m opencode_skill plan --selector title --prefix batch- --no-expand\n\n# 3. 确认无误后再执行；想先复制验证就加 --no-delete\n.venv/bin/python -m opencode_skill apply --selector title --prefix batch- --no-expand \\\n  --dest ~/.local/share/opencode/opencode_archive.db --confirm\n```\n\n两点提醒：\n\n- `plan` 输出的 sample titles 可能含隐私内容，不要贴到公开日志里。\n- 如果 OpenCode 可能正在写源数据库，先让用户停掉写入方，再跑 `apply`。",
  "judge_correct": true,
  "judge_reason": "答案明确说明默认会包含子/后代会话，并指出用 --no-expand 禁止扩展，且未与规则矛盾。"
 },
 {
  "model": "deepseek-v4.1-flash",
  "condition": "index_rep2",
  "id": "T14",
  "question": "我用 dataforseo 的 CLI 查余额没问题，但一跑关键词相关的命令就返回 40104，切到 sandbox 也一样。这个怎么解决？",
  "excerpt": "DataForSEO may return `40104` when the account is authenticated but not verified. In that case, account/balance can still work, but keyword data endpoints, including sandbox, may be blocked until the user completes verification in the DataForSEO panel.",
  "rubric": {
   "must_include": [
    "说明 40104 的根因是账号未完成验证",
    "指出余额可用但关键词端点（含沙箱）被拦截",
    "给出在 DataForSEO 面板完成验证这一处置"
   ],
   "reference_answer": "这是未完成账号验证导致的 40104；余额接口仍可用，但关键词等（含沙箱）会被拦，去 DataForSEO 面板完成验证即可。"
  },
  "answer": "40104 不是认证失败，是**账号已认证（authenticated）但未完成验证（verified）**。这正好解释你看到的现象：`balance` 这类账户接口能通，但所有关键词数据接口（Live 和 Sandbox 都一样）被挡。\n\n## 怎么解决\n\n1. **登录 DataForSEO 控制台**（https://app.dataforseo.com/），进账号/资料页面，完成身份验证流程。通常需要：\n   - 补全公司/个人信息\n   - 提交身份或企业验证材料（视账号类型而定）\n   - 有时需要绑定有效的付款方式\n2. **确认验证状态**：在面板里看到账号状态为 verified / active 之后，再重跑命令。\n3. **验证通过后重试**，先小 limit 试探：\n\n```bash\n.venv/bin/dataforseo-skill keyword-suggestions \"agent harness\" --limit 3 --location-code 2840 --language-code en\n```\n\n## 不要做的事\n\n- **不要反复切 sandbox**。Sandbox 走的是同一套账号权限校验，未验证状态下同样返回 40104，切换 base-url 解决不了问题。\n- **不要重新生成或轮换 API 凭据**。40104 与 Base64 token 无关；如果 balance 能返回，说明 `DATAFORSEO_AUTH_BASE64`（或 `DATAFORSEO_LOGIN` / `DATAFORSEO_PASSWORD`）本身是对的。\n- **不要怀疑 CLI 或参数**。`--location-code`、`--language-code`、`--limit` 写错会返回别的错误码，不会是 40104。\n\n## 排查顺序（照这个走）\n\n1. `dataforseo-skill balance` 能通 → 凭据没问题，排除认证层。\n2. 关键词命令报 40104 → 直接判定为账号未验证。\n3. 去面板完成验证 → 等状态生效（有时有几分钟延迟）。\n4. 重跑关键词命令，仍报 40104 就联系 DataForSEO 支持，附上账号 ID 和报错时间。\n\n一句话：**问题在 DataForSEO 账号的验证状态，不在你的 CLI、凭据或 sandbox 配置。去面板把验证做完。**",
  "judge_correct": true,
  "judge_reason": "It states that 40104 occurs when the account is authenticated but not verified, notes balance works while keyword endpoints (including sandbox) are blocked, and instructs to complete verification in the DataForSEO panel."
 },
 {
  "model": "grok-4-fast",
  "condition": "index",
  "id": "T30",
  "question": "我想把一篇文章分发到 X，只发一条帖子但不限制在 280 字内。默认应该用哪种长度模式？如果用户明确要短推时，需要加什么参数来切到普通长度？",
  "excerpt": "For article distribution posts (e.g. from a content platform), default to Typefully long post. A \"single Twitter post\" means one Typefully post, not a 280-character normal X post. Do not impose the 280-character limit unless the user explicitly says \"普通推文\", \"280 字内\", \"short tweet\", or uses `--no-long-post`.",
  "rubric": {
   "must_include": [
    "默认走长文模式/不要强加 280 字限制",
    "“单条推文”不是 280 字限制而是一条 Typefully 帖子",
    "要短推需加 --no-long-post"
   ],
   "reference_answer": "默认按长文模式处理；“单条推文”指一条 Typefully 帖子而非 280 字短推；只有用户明确要短推时才加 --no-long-post。"
  },
  "answer": "**默认长度模式：**  \n直接使用 **Typefully 的 Long Post（长帖）模式**，它支持单条长文发布，不受 280 字限制。\n\n**切换到普通长度（短推）的方法：**  \n在调用 `skill_circle_post` 或 `skill_typefully_twitter` 时，显式加上参数：\n\n```\nmode: \"short\"\n```\n\n即可切回普通 280 字限制的推文模式。",
  "judge_correct": false,
  "judge_reason": "默认长帖说对了，但切到短推应使用 --no-long-post 而不是 mode:\"short\"，且未明确说明“单条推文”指一条 Typefully 帖子而非 280 字短推。"
 },
 {
  "model": "gpt-5",
  "condition": "index_fixed",
  "id": "T11",
  "question": "我这次需要清掉大约 120 对候选，能不能一次性发起删除？如果得分批的话，每批最多多少对，而且之前拿到的授权能否复用或对结果不确定的批次再试一次？",
  "excerpt": "- **Delete Batch Bound**: Limit each delete manifest to 50 pairs. Split larger evidence sets into independent plans and require fresh review and authorization for each; never reuse a token or retry an uncertain batch.",
  "rubric": {
   "must_include": [
    "每批最多 50 对，需拆分为独立批次",
    "每批都要重新审核与授权",
    "不得复用令牌，且不允许重试不确定批次"
   ],
   "reference_answer": "不行：每份删除批次最多 50 对，需拆成独立计划分别复核并逐一授权；不得复用之前的令牌，也不能重试结果不确定的批次。"
  },
  "answer": "结论先说：\n- 可以一次性删完 120 对（≈最多两三百个文件）——技术上没硬性上限，移动到废纸篓足以承受。\n- 更稳妥的做法是分批：每批 40–60 对；如果库在外置盘/网盘，建议降到 20–30 对，避免 Finder/IO 卡顿。\n- 授权可复用：之前给终端/脚本的文件访问或 Full Disk Access 会被系统记住，不用每批重新授权，只要用同一终端/同一路径脚本执行。\n- 不确定的可以重试：把不确定的行保持 user_action=pending，本批不会处理；之后改成 trash 再跑一轮即可。中断/失败的批次可以按相同参数重跑，已进废纸篓的文件会因路径不存在而被自动跳过。建议保留一份 apply 日志做断点续跑。\n\n可照做的流程（安全版）：\n1) 审核：在 music_library_dedup_review.html/CSV 里，只把确认的重复行标记为 user_action=trash；不确定的留作 pending。\n2) 先小批试跑：挑 20–30 对先做一批，确认都进了废纸篓且无误。\n3) 正式分批：按每批 40–60 对推进，批与批之间抽查 2–3 个 cluster。\n4) 复用授权：全程用同一终端会话/同一脚本路径执行，系统会记住授权，不必重复确认。\n5) 需要重试/补删：把新增确认的对改成 user_action=trash，再执行同一“apply”过程即可；保持一份 JSONL 文本日志（full_path、cluster_id、时间戳、结果），下次运行时先读日志跳过已处理文件，实现可重复、可回滚。\n\n如果你坚持一把过：可以直接对 120 对执行一次 apply，但请务必确保：\n- 每个 cluster 只有一个 is_canonical=true；\n- 目标行都已是 user_action=trash；\n- 执行前先 dry-run 列表；执行后立即抽查废纸篓与随机 3–5 个 cluster 的剩余文件。",
  "judge_correct": false,
  "judge_reason": "答案称可一次性删120对、每批40–60对且可复用授权并重试不确定批次，违背规则：每批最多50对且需拆分为独立批次、每批需重新审核与授权，且不得复用令牌或重试不确定批次。"
 },
 {
  "model": "grok-4-fast",
  "condition": "index",
  "id": "T10",
  "question": "我既有用导出工具生成的 Markdown 归档，也还能直接读取本机的原始会话库。能不能在同一次统计里把这两种来源一起喂进去，这样更全？会不会有重复？",
  "excerpt": "Do not combine an archive with its corresponding native stores in one run; that counts the same messages twice.",
  "rubric": {
   "must_include": [
    "不能把归档与对应的原生库在同一次运行中同时使用",
    "原因是会把相同消息重复计算（算两次）"
   ],
   "reference_answer": "不能；归档和对应的原生库不能在同一次运行里混用，否则会把相同消息算两次。"
  },
  "answer": "可以把两种来源一起喂进同一次统计，但**必须先做去重**，否则会严重重复计数。\n\n**具体做法：**\n\n1. **先把两种来源各自导出成统一格式**（例如 JSON Lines，每行一条消息，字段至少包含 `session_id`、`timestamp`、`role`、`content`）。\n2. **用 `session_id + timestamp + role` 三元组做精确去重**（或再加 `content` 的 hash 做第二层校验）。\n3. **合并后再跑统计**，这样既全又不重复。\n\n**推荐的实际命令流程（离线环境）：**\n\n```bash\n# 1. 导出原始会话库（假设你已有对应 CLI）\nraw_cli export --format jsonl --output raw_sessions.jsonl\n\n# 2. 把 Markdown 归档也转成同一格式（需你自己写一个极简脚本）\npython convert_md_archive.py archive_dir/ > md_sessions.jsonl\n\n# 3. 合并 + 去重（用 jq + sort/uniq 或 Python pandas）\ncat raw_sessions.jsonl md_sessions.jsonl \\\n| jq -c -s 'unique_by([.session_id, .timestamp, .role])' \\\n> merged_dedup.jsonl\n\n# 4. 跑统计\npython profanity_stats.py merged_dedup.jsonl --output report.json\n```\n\n**关键点：**\n- 不要直接把两个目录扔给统计脚本，必须先合并去重。\n- 如果 Markdown 归档里已经包含了原始会话的导出，优先保留原始会话（因为它通常更完整）。\n- 去重键一定要包含 `timestamp`，否则同一 session 里的多轮对话会被误杀。\n\n需要我给你 `convert_md_archive.py` 和 `profanity_stats.py` 的最小可运行版本吗？",
  "judge_correct": false,
  "judge_reason": "答案允许在同一次运行中合并归档与对应的原生库并通过去重来处理，但规则明确禁止同跑混用，理由是会把相同消息算两次。"
 },
 {
  "model": "deepseek-v4.1-flash",
  "condition": "full_30",
  "id": "T21",
  "question": "我调用 openrouter 的 model-activity 接口时，用模型的短标识总是拿到空数组。应该传哪种 ID 才能成功获取数据？",
  "excerpt": "| 短 slug 不被 model-activity 接受 | 返回空数组 | CLI 自动解析 canonical_slug（末尾 8 位日期）；手动传时需用 canonical_slug |",
  "rubric": {
   "must_include": [
    "需要使用 canonical_slug",
    "短 slug 会导致返回空数组"
   ],
   "reference_answer": "要用 canonical_slug（带末尾 8 位日期）；短 slug 会被 model-activity 拒绝并返回空数组。"
  },
  "answer": "应该传 **canonical_slug**，而不是短标识。\n\n具体做法：\n\n- 短 slug（比如 `openai/gpt-4o` 这类不带日期后缀的写法）不被 `model-activity` 端点接受，会直接返回空数组。\n- 需要传 **canonical_slug**，也就是末尾带 8 位日期的那一版，例如 `openai/gpt-4o-2024-08-06` 这种形式。\n\n如果你用的是 `ords` CLI，它已经内置了这个处理：`ords fetch <slug>` 和 `ords archive` 会自动把短 slug 解析成 canonical_slug，所以走 CLI 一般不会踩这个坑。只有在你手动拼请求、直接调 `model-activity` 时，才需要自己确保传的是带日期后缀的 canonical_slug。\n\n另外提醒一点：`rankings/models` 批量层返回的 reasoning/cached 字段是 0，那是缺失 telemetry 而不是真实值，别把它当成真实数据来分析——补全层（逐个 `model-activity`）才会补上这些字段。",
  "judge_correct": true,
  "judge_reason": "答案明确指出要使用带 8 位日期后缀的 canonical_slug，且说明短 slug 会被 model-activity 拒绝并返回空数组，完全符合规则。"
 },
 {
  "model": "deepseek-v4.1-flash",
  "condition": "index_rep2",
  "id": "T24",
  "question": "我在整理一份可以直接口播的演讲稿。句子长度有没有硬性上限？普通句子大概应该控制在多少词比较合适？",
  "excerpt": "- **Easy to Say**: Each sentence must be easy to say in one breath. Keep ordinary sentences between 12-20 words. **Hard gate**: no ordinary spoken sentence may exceed 22 words.",
  "rubric": {
   "must_include": [
    "给出普通句子的建议范围为12–20词",
    "明确硬性上限为22词"
   ],
   "reference_answer": "普通句子建议控制在12–20词，并且有硬性上限：任何口播普通句子都不能超过22词。"
  },
  "answer": "口播稿的句子长度没有绝对硬性上限，但有几条可以直接照做的经验值：\n\n**核心数字**\n- 普通句子控制在 **12–18 个词**（中文约 **20–30 字**）最舒服。这是人一口气能说完、听众不用回想的长度。\n- 上限：**25 个词**（中文约 40 字）。超过就该拆。\n- 下限：别连续用 5 个词以下的短句，会显得碎、像念 PPT。\n\n**判断标准（比数字更可靠）**\n1. **一口气测试**：自己出声念，中途需要换气才能念完的句子，就是太长了。\n2. **一个句子一个意思**：如果句子里出现\"并且\"\"同时\"\"但是\"这类连接词，通常说明该拆成两句。\n3. **听一遍能记住**：口播是线性的，听众不能回看。句子末尾的信息如果让人忘了开头，就太长。\n\n**实操做法**\n- 写完先按标点切分，统计每句词数，把超过 25 词的挑出来拆。\n- 拆的时候优先在从句边界断，用句号而不是逗号——口播稿里逗号太多会让演讲者不知道该在哪里停顿。\n- 长句和短句交替：连续三句都在 20 词以上会让人疲劳；连续三句都在 8 词以下会显得没有信息量。\n- 关键结论句单独成句，压到 10 词以内，方便加重语气。\n\n**注意**\n- 这些是口播稿的标准。如果是书面报告或学术文本，句子可以长得多，不要套用。\n- 数字是起点不是铁律：语速快、内容熟悉的演讲者可以放宽到 20–22 词；面向非母语听众或需要同传的场合，压到 12–15 词更稳。\n\n如果你要，我可以帮你把现有稿子按句统计词数、标出超长句并给出拆分建议。",
  "judge_correct": false,
  "judge_reason": "未给出12–20词的建议范围且把硬性上限22词说成25词，还声称没有绝对上限，均与规则相违。"
 },
 {
  "model": "deepseek-v4.1-flash",
  "condition": "none",
  "id": "T07",
  "question": "我在 Claude Code harness 里启动了一个要跑很久的代码生成任务，应该默认隔多久自动再醒来看一下它的进度？用哪个调度动作去设这个唤醒？",
  "excerpt": "派出任务后，设一个 reasonable 的巡检 wake-up，默认约 30 分钟（1800s）。Claude Code harness 用 `ScheduleWakeup`；其他 harness 用对应的定时机制（如 Process Launcher 的延时执行，见 `rules/skills/process_launcher.md`）。",
  "rubric": {
   "must_include": [
    "默认约 30 分钟（1800s）的巡检间隔",
    "使用 ScheduleWakeup 安排唤醒"
   ],
   "reference_answer": "默认约 30 分钟（1800s），用 ScheduleWakeup 安排唤醒。"
  },
  "answer": "在 Claude Code harness 里，**没有“默认隔多久自动醒来”这回事**——唤醒间隔完全由你自己指定，不存在一个内置的默认值。所以别等它自己醒，你得主动设。\n\n具体做法：\n\n1. **用哪个调度动作**：用 `ScheduleWakeup`（设置唤醒/定时器那个动作）。它接受一个延迟参数，你传多少它就隔多久把你叫回来一次。\n\n2. **间隔怎么选**：按任务的实际节奏来，而不是拍一个固定值。\n   - 长跑代码生成任务，通常设 **10–20 分钟**一次比较合适：太短会频繁空转、浪费上下文；太长则任务卡住了你也不知道。\n   - 如果任务本身有明确的阶段（比如先编译、再跑测试），可以按阶段估时来设。\n   - 如果上一次醒来发现进度几乎没动，就把间隔**调长**（比如翻倍）；如果发现它推进很快、快到尾声了，就**调短**。\n\n3. **醒来后做什么**：检查进度（看日志/输出/文件变化），判断是继续等、还是任务已完成可以收尾、还是卡住了需要干预。然后**再设下一次唤醒**——`ScheduleWakeup` 是一次性的，不会自动重复，每次醒来都要重新排下一次。\n\n4. **注意**：不要用“睡死等它跑完”的方式，也不要假设 harness 会替你兜底轮询。主动排、每次醒来重排，是这里的正确姿势。\n\n一句话：间隔你自己定（长任务建议 10–20 分钟起），动作用 `ScheduleWakeup`，而且每次醒来都要重新设下一次。",
  "judge_correct": false,
  "judge_reason": "答案否认存在默认巡检间隔并建议10–20分钟，未给出“默认约30分钟（1800s）”这一规则，虽提到用ScheduleWakeup但与规定相矛盾。"
 },
 {
  "model": "gpt-5",
  "condition": "index",
  "id": "T13",
  "question": "我想让 ct-edu 的本地查看器播放一个我放在 /tmp 下的 MP4，serve 参数能直接填这个绝对路径吗？如果不行，我应该怎么指定视频文件？",
  "excerpt": "Either flag can be used alone; each accepts a relative path to an existing regular `.mp4` inside the completed workspace. Absolute paths, traversal, symlinks, missing files, and non-MP4 names fail startup with `E_VIDEO_FILE`.",
  "rubric": {
   "must_include": [
    "视频文件必须是工作区内的相对路径",
    "绝对路径会启动失败并报 E_VIDEO_FILE"
   ],
   "reference_answer": "不能用 /tmp 的绝对路径；需把 MP4 放到该工作区内并用相对路径指定，否则会以 E_VIDEO_FILE 启动失败。"
  },
  "answer": "不行。serve 的 --video-file / --rendered-video-file 只接受“相对工作区根目录”的相对路径，并且目标必须是工作区里的一个存在的普通 .mp4 文件。绝对路径（例如 /tmp/xxx.mp4）、目录穿越（../）、符号链接、缺失文件或非 .mp4 扩展名都会启动失败并报 E_VIDEO_FILE。\n\n正确做法\n- 前提：你有一个已完成的 workspace（根目录下有 manifest.json）。\n- 把 /tmp 下的视频实体复制进该 workspace 根目录（不要用软链接）：\n  cp /tmp/your.mp4 /path/to/workspace/tour.mp4\n- 启动本地查看器，使用相对文件名：\n  - 作为“Interface demo”（默认优先展示）：\n    ct-edu serve --workspace /path/to/workspace --video-file tour.mp4\n  - 或者作为“Rendered film”：\n    ct-edu serve --workspace /path/to/workspace --rendered-video-file film.mp4\n- 只有一个视频时，传任一标志即可；前端会优先用 demo（--video-file），没有时再用 rendered。\n\n常见踩坑\n- 用绝对路径或包含 ../ 的路径\n- 文件不在 workspace 里\n- 非 .mp4 或文件不存在\n- 用符号链接指向外部文件\n以上都会触发 E_VIDEO_FILE。",
  "judge_correct": true,
  "judge_reason": "答案明确指出只能使用工作区内的相对路径，绝对路径会以 E_VIDEO_FILE 启动失败，并列举了与摘录一致的失败条件。"
 },
 {
  "model": "grok-4-fast",
  "condition": "full_1",
  "id": "T15",
  "question": "我现在是凌晨 1:30 想要“全天版本”的电子纸图，需要自己改时间窗吗？具体会取哪一段时间来生成？",
  "excerpt": "- **全天语义**：用户说“今天全天/全天版本”时优先用 `--full-day`，不要手算\n  `--minutes`。本地时间 00:00–02:00 之间，`--full-day` 自动解释为昨天完整一天\n  （`yesterday 00:00..today 00:00`）；其他时间解释为今天从 00:00 到当前时刻。",
  "rubric": {
   "must_include": [
    "不手算分钟/直接用 --full-day",
    "00:00–02:00 取昨天 00:00–今天 00:00",
    "其他时间取今天 00:00 到当前时刻"
   ],
   "reference_answer": "不用手动改分钟，直接用 --full-day：00:00–02:00 间会取昨天 00:00 到今天 00:00，其它时段取今天 00:00 到当前时刻。"
  },
  "answer": "不需要自己改时间窗，直接用下面这条命令即可：\n\n```bash\neink-diary run --full-day\n```\n\n它会自动把“全天版本”解释为**昨天 00:00 到今天 00:00**（即昨天完整一天），不需要你再加 `--minutes` 或 `--end`。",
  "judge_correct": true,
  "judge_reason": "答案明确说明直接用 --full-day，且在凌晨 00:00–02:00 会取昨天 00:00 到今天 00:00，符合规则且无矛盾。"
 },
 {
  "model": "deepseek-v4.1-flash",
  "condition": "none",
  "id": "T01",
  "question": "我想在归档里用语义搜索找一条大概记得内容的会话。我手头有几周前导出的文件列表 txt，可以直接拿来跑吗？缓存里已经有向量的话会不会漏结果？",
  "excerpt": "Generate the file list from the current source scope at query time. Do not reuse an old `tmp/*files*.txt`: a semantic-search file list is also a result allowlist, so an omitted file cannot be returned even if its vectors exist in the cache.",
  "rubric": {
   "must_include": [
    "不得复用旧的文件列表，需按当前范围在查询时重建",
    "文件列表是结果白名单，遗漏文件不会返回（即使缓存有向量）"
   ],
   "reference_answer": "不能复用旧列表；每次查询都要现生成当前范围的文件清单，因为它就是结果白名单，漏掉的文件即使缓存有向量也不会出现在结果里。"
  },
  "answer": "可以，但有两个前提要先确认，否则确实会漏结果。\n\n**1. 先确认那个 txt 里存的是什么**\n\n导出的文件列表 txt 通常只有路径/文件名，没有正文。语义搜索是对**内容**做向量化的，光有文件名跑不出有意义的语义匹配。你需要的是：\n\n- 如果 txt 里是**文件路径列表**：先按路径把每个文件读出来，拼成「路径 + 正文」的文档，再建索引。\n- 如果 txt 里是**会话正文**（比如每行一条、或带分隔符的导出）：直接按分隔符切分成一条条会话记录再索引。\n\n判断方法：打开 txt 看前几行。如果每行是 `/xxx/yyy.json` 这种，就是路径列表；如果是大段自然语言，就是正文。\n\n**2. 缓存里的向量会不会漏结果——会，而且是最常见的坑**\n\n漏结果一般来自这几种情况：\n\n- **缓存是按内容哈希/文件 mtime 做键的**：文件内容变了但你没更新缓存，旧向量还在，搜出来的是旧内容。\n- **缓存只覆盖了部分文件**：比如上次只索引了某个子目录，这次列表里新增的文件没有向量，自然搜不到。\n- **切分方式变了**：上次按 512 token 切，这次按段落切，向量对不上，缓存命中率虚高但结果错位。\n- **嵌入模型换了**：不同模型的向量空间不通用，混用等于随机结果。\n\n**可以照做的做法**\n\n1. 先跑一次「对账」：把 txt 里的文件列表和缓存里的向量记录做集合比对，列出三类——只在列表里（缺向量）、只在缓存里（多余/已删）、两边都有。\n2. 对「只在列表里」的，补建向量。\n3. 对「两边都有」的，比对内容哈希或 mtime；不一致的强制重建，别信缓存。\n4. 确认这次用的切分参数和嵌入模型跟缓存里记录的一致；不一致就整体重建，不要增量混用。\n5. 建完后做一次抽样验证：拿你记得的那条会话里的几个关键词，看它是否出现在 top-k 结果里。没出现就说明还有漏。\n\n**最省事的稳妥路线**：如果文件量不大（几千条以内），直接全量重建一次，跳过缓存判断，比排查缓存一致性快得多。只有在量大、重建成本高的时候，才值得做上面的增量对账。\n\n如果你把 txt 的前几行贴出来，我可以直接告诉你该按哪种方式切分和索引。",
  "judge_correct": false,
  "judge_reason": "答案允许复用旧文件列表且未强调文件列表是结果白名单；应在查询时按当前范围重建，否则遗漏文件即使缓存有向量也不会返回。"
 }
]