Files
mcn-short-video/.workbuddy/memory/2026-10-08.md
T
maogeigei 3a940270b5 docs: 会话任务链路待优化项立档(跨会话可见)
新增 mcn-workshop/docs/会话任务链路-待优化项.md:4 项待优化(任务模型浮动/面板跳转入口/任务权限/旧会话归位)+红线+已修历史。
同步:项目 MEMORY.md 权威源表加该文档指针,工作台红线补 workspace_scope 一条。
理由:只写进当天日志的规矩换会话读不到,必须落到工作台文档与长期记忆。
2026-10-08 18:00:20 +08:00

279 lines
39 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 2026-10-08
## 会话技能加载 + 项目记忆去重
- 按用户指令加载 `session-mechanism`:四类必读已读(`00-动手前必过.md` / `rules.md` / `manifest.md` / `01-文档索引.md`)+ SKILL.md 第一屏(触发句、第 0 步加载门槛、十条禁令、自决策白名单、现状地图)。
- 内化判据:**边界内自决策**(技术选型/实现路径/命名/调参/部署/排查/版本/兼容降级/文档技术内容);**只提报三类**(功能语义分叉/红线门禁/超边界:花钱·对外承诺·要凭据)。
- 项目 `MEMORY.md` 去重重组为 v4(17,841 → 17,315 B):新增 §0 两条指针(工作台 AI 通路 `docs/AI链路-本地CLI接入.md`、技能侧 WeKnora 调用);§2.1 补 09-29 AI 通路四条硬红线+两段流程模型来源;§4.2/§4.3 降级为「存根+硬红线」;清历史流水。
### 未决
- ⚠️ `MEMORY.md` 体积仍会被注入截断(阈值约 10–12 KB)。解法二选一:① 维持单文件(细节自动可见,但注入截断)② 拆专题文件(不截断,但细节失去自动注入)。**无客观优劣 ⇒ 待用户定**。
## 工作台「更新榜单数据」按钮排查(功能正常,是可见性问题)
- 现象:用户点击后「没有会话执行」。
- 取证(四处读数):宿主库 `automations` 有该 once 任务(ACTIVE、`next_run_at` 已过期、`model=hy4-preview`、`skills=["mcn-data-insight"]`);`automation_runs.status=IN_PROGRESS` + `automation_runtime_state.running=1`(⚠️ `running_conversation_id=null` 是常态,不代表没起会话);`sessions` 已建后台会话 `8a085794…`(bg=1,10:07:23);会话 jsonl 283 KB 且 mtime 持续更新(10:14),工具调用 30 Bash/14 Read/4 Write ⇒ **任务真的在跑**。`/api/dsh/ranking-update` 返回 `files=104`、`lastUpdateAt` 10:13:51 ⇒ 数据已在落盘。
- 真因两条(都与抓取逻辑无关):① 会话 `cwd = …\mcn-work-shop` ⇒ 落在侧边栏 **mcn-work-shop 空间分组**,不在用户当前空间,需切组才可见(这是设计,非 bug);② 工作台服务作为会话后台任务**被回收** ⇒ 前端轮询 `/api/run/status` 持续抛错。
- 修复:`public/data-pages.js` `updateRankingData()` 轮询加失败计数 —— 连续 3 次查询失败即提示「工作台服务已断开,任务仍在后台执行」并结束轮询(原逻辑静默 `continue` 到 15 分钟超时,用户零感知)。
- ⚠️ **排查方法论沉淀**:判「后台任务有没有在执行」的正确取證链 = `automations` → `automation_runs` → `automation_runtime_state` → `sessions` → 会话 jsonl 的 mtime/大小。⛔ 别只看 `running_conversation_id`(常为 null,会误判成没执行)。
## 工作台目录统一改名 `mcn-work-shop` → `mcn-workshop`
- 动因(用户):「分组已改为 mcn-workshop,需要全部替换;执行会话任务时需要按分组名称显示在会话列表」。
🔑 **机制**:WorkBuddy 侧边栏空间分组名 = 会话 `cwd` 的 **basename** ⇒ **只改字符串无效,必须真改名目录**。
- 执行五步:① 停服务释放句柄(否则 rename 必 `WinError 5`)→ ② `mv mcn-work-shop mcn-workshop`(成功)→ ③ 临时脚本全仓替换 **`mcn-work-shop`→`mcn-workshop`:37 文件 / 121 处**;⛔ 按**沿革句规矩跳过** `.workbuddy/memory/YYYY-MM-DD.md`(16 个历史日志保留当时目录名史实)→ ④ 修 2 处被替换改坏语义的注释(`server.js` L1265 / L1269,原「旧 mcn-workshop/新建 mcn-workshop」被换成同词,已补回三代路径)→ ⑤ 重启验证:`app.js`/`data-pages.js` 200,`data-pages.js` 含 `pollFail` 修复。
- ⚠️ **事实澄清(易踩)**:`D:\AI技能\mcn-workshop` **不是工作台**,是 09-01 路径拼接 bug 留下的**垃圾目录**(3 个 0 字节文件 + `outputs`)。工作台本体一直在仓库内 `…\V1.0\mcn-workshop`。sessions 表取证:该垃圾目录下**零会话**;未删除会话中仅 1 条 mcn-work 相关,cwd 仍是旧 `…\mcn-work-shop`。
- 净效果:新建后台会话 cwd = `…\V1.0\mcn-workshop` ⇒ 侧边栏分组显示 **`mcn-workshop`** ✅
- 残留:`project/…/subskills/mcn-data-insight/scripts/__pycache__/*.pyc`(二进制缓存含旧串,会自行重生成,无需处理)。
### 追加澄清(用户真实意图,⛔ 别再理解成"目录改名")
- 用户要的不是改目录名,而是:**工作台提交 WorkBuddy 定时任务后,产生的 AI 会话应归属哪个「空间分组」**。
- 机制:分组名 = 会话 `cwd` 的**目录名**;工作台侧由 `server.js` 的 `SESSION_CWD`(当前 = 工作台自身目录)决定 ⇒ **改目录名只是让分组名变成 `mcn-workshop` 的手段**,不是目的本身。
- ⚠️ **存量不掉头**:10:07 那条榜单更新任务 cwd 仍是旧 `…\mcn-work-shop` ⇒ 显示在**旧**分组;改名后**新建**的任务才落到 `mcn-workshop`。清理用的 `SESSION_CWD_PATTERN='%mcn-work%'` 覆盖新旧三代,不受影响。
- 待用户定(功能语义分叉):① 维持工作台专属分组 ② 归入用户当前主项目分组 ③ 自定义独立分组(需目录真实存在且在 workspaces 登记,08-27 即此法)。可选项:把 `SESSION_CWD` 提升为 `config.json` 可配字段,换分组只改配置不动代码。
## 后台任务进度可见(用户:「我需要看到 才知道执行是否正常」)
- 真需求:不是分组放哪,而是**在工作台内直接看到执行状态**,判断在跑还是卡住、做了什么。
- 后端 `server.js` `/api/run/status` 增强:automation → 关联它拉起的后台会话(`sessions.is_background_automation=1` 且创建时间落在任务创建 −15s~+300s)→ 读该会话 jsonl **尾部 256 KB** → 新增字段
`elapsedMs` / `sessionId` / `sessionTitle` / `lastAction`(最近一段文本,截断 160 字)/ `toolCalls`(按工具名计数)/ `updatedAgoMs` / `stalled`(>90 s 无新输出)/ `logBytes`。
- ⭐ 会话日志路径换算(可复用):cwd → projects 目录名 = **盘符小写 + 分隔符全转 `-`**(`escapeCwdForProjects()`);基目录 `<workbuddy>/projects/<转义名>/<sessionId>.jsonl`。
- ⚠️ `stalled` 只在 `state=running` 时有意义(已完成任务日志当然不再写入,会恒 true)。
- 前端新增 `window.TaskProgress`(实现在 `public/app.js`,样式 `style.css` 的 `.tp-*`):右下角常驻面板 = 状态/已运行时长/工具调用/最近动作/停滞警告。**四处轮询点全接入**:`app.js executeTask`、`data-pages.js` 的榜单更新/批量更新账号/视频解析。终态保留 8 秒自动收起,运行超时则保留供继续观察。
- 验证:三文件 `node --check` 全过;服务起来后 `app.js` 含 6 处、`data-pages.js` 含 9 处 `TaskProgress`,`style.css` 含 `.tp-panel`;状态接口返回完整进度字段。
### ✅ 分组归属定案:就用 `mcn-workshop` 文件夹对应的分组(已实测)
- 用户拍板:任务会话归到 **`mcn-workshop` 文件夹对应的那个分组**。
- 实测(提交轻量自检任务取证):新会话 `cwd = …\V1.0\mcn-workshop` ✅;对照 10:07 那条旧榜单任务仍是 `…\mcn-work-shop`(**两者在侧边栏分属不同分组**,属正常存量,不用管)。
- 结论:**不需要再改代码** —— `SESSION_CWD = ROOT`,目录改名后自动生效。自检任务已删排期。
- ⚠️ 认知要点:侧边栏分组按 **完整 cwd** 聚合,不是按文件夹名合并 ⇒ 同名不同路径会是两个分组。
### ⚠️ 更正(12:2x):分组靠 cwd **命中已打开的工作区**,光改目录名不够
- 上一条只对了一半:目录改名后会话 cwd 是**仓库内** `…\V1.0\mcn-workshop`,**不是用户的工作区** ⇒ 仍显示在「**未分组任务**」区域(用户实测反馈)。
- 用户纠正:`D:\AI技能\mcn-workshop` **才是那个工作区**。我此前仅凭目录里只有 3 个 0 字节垃圾文件就判它"是垃圾目录"——**没查登记表就下结论**,违反「判状态先问程序自己」。
- 🔑 **真机制**:侧边栏分组 = 会话 cwd **命中已打开过的工作区**才显示为命名分组;没命中 ⇒ 落「未分组任务」。
- 取证:`workspaces` 表仅 7 行且全是 `C:\Users\maidou\WorkBuddy\<时间戳>`,**不含**任何 `D:\AI技能\*` ⇒ 分组依据不是这张表,而是按会话 cwd 对已打开工作区的聚合。
- 修法(配置化,⛔ 不写死):`load-config.js` 增 `sessionCwd`(默认 `''` = 用 ROOT)→ `server.js` `SESSION_CWD = String(CFG.sessionCwd||'').trim() || ROOT` → `config.json` 设 `"sessionCwd": "D:\\AI技能\\mcn-workshop"`。**换分组只改配置,不动代码。**
- 实测:新会话 cwd = `D:/AI技能/mcn-workshop` ✅;任务在该工作区执行正常(done,结果"成功",73 秒)。对照 12:23 那条仍在仓库内路径 ⇒ 两组并存,旧的会随过期清理消失。
- ✅ 13:06 **二次模拟请求复验一致**(用户指示:不测真实功能,只发模拟请求看分组):写入 `cwds=["D:\\AI技能\\mcn-workshop"]`,会话 cwd 同样命中 ⇒ 配置化改法稳定。两次自检排期均已删,会话留在目标工作区供用户肉眼确认。
## 提交(13:09)
- commit **`f77735b`**:目录改名 + 121 处替换 + 任务进度面板 + 会话归属配置化 + 前端轮询缺陷修复。提交前已查:仓库内**无 API key**(`grep -rl "ck_fzpjin5c0iyo"` 空)、新目录**未被 gitignore 误伤**(`git check-ignore` 无输出)。工作区干净(0 未提交)。
- ⚠️ **push 失败(非权限问题)**:`ssh work.alotbuy.com:222` 连接超时 —— 公司内网 git 服务器,当前网络不通。走 10800 代理也失败:Git Bash **没有 `connect` 工具**(`ProxyCommand` 用不了)。
- 待办:回到公司网络/VPN 后跑 `git push origin main`(本地提交已就位,领先 origin 1 个)。
### 🔴 新远程地址实测:能连通,但**两条历史无共同祖先**(已停手,未推送)
- 用户给新地址 `[email protected]:admin/mcn-short-video.git`(旧 `work.alotbuy.com:222` 超时)。实测 **能连通**:`ls-remote` 成功,远程 main = `e738dd1`(2026-10-07 10:48)。
- 🔴 **`git merge-base main FETCH_HEAD` 为空** ⇒ 无共同祖先;两边各 **253** 个提交,`rev-list --left-right --count` = **253 / 253**。
- 本地最早 2026-08-06「Full upload: MCNVideo AI project」,远程内容同源但 **hash 全不同** ⇒ 历史被重写过,是两条独立线。
- 远程有本地没有的提交(如 10-07 那条 gitignore 补充),说明远程不等于"落后于本地"。
- ⛔ **不能直接 push**:普通 push 必被拒(非 fast-forward);**强推会抹掉远程 253 个提交,不可逆**。
- 待用户定:① 推到远程**新分支**(安全,推荐)② 合并两条历史(`--allow-unrelated-histories`,冲突量极大)③ 强推覆盖(⛔ 需明确授权)④ 不推,等原地址恢复。
- ⚠️ 排查教训:换远程地址前必须先看 `merge-base`,⛔ 不能因为"地址能连通"就推。
## 合并两条历史并完成同步(15:4x)
- 合并 `admin/main`(两条独立历史、无共同祖先):冲突 **20 文件 47 块**。
- **40 块自动**:规则①一边为空 → 取非空(纯新增,取并集);规则②ours 含 `mcn-workshop` 且 theirs 含 `mcn-work-shop` → 取 ours(目录改名,本地最新)。
- **7 块人工**(全在 `MEMORY.md`):本地 v4 精简版覆盖远程 v3,远程的详细条目在 RUNBOOK/技能文档均有指针 ⇒ 取本地。
- ⚠️ 脚本坑:冲突标记正则**必须写 `\r?\n`**(工作区是 CRLF),第一版漏了导致 0 匹配。
- ⭐⭐ **合并的隐藏副作用(换线合并必查)**:远程那条线的工作台目录仍是旧名 ⇒ 合并后**旧目录 `mcn-work-shop` 被整个带回**,两套代码并存。
- 取证三步:① `diff -rq` 显示旧目录**零独有文件**;② `server.js` 报 2672 行差异,实为**行尾差异**(旧 CRLF / 新 LF),`diff --strip-trailing-cr` 后仅 **206 行**;③ 其中旧目录独有的 40 行**全是本地已升级掉的旧代码**(旧版凭据解析 / 旧状态接口 / 写死的会话目录)⇒ 远程无本地缺失的实质改动。
- 结论:安全删除,已 `git rm -r -f`(合并暂存态需 `-f`)。
- 推送:`git push admin main` = **快进** `e738dd1..67317c4`(合并后远程变成本地祖先,**不是强推**)。本地与 admin 已完全一致(`rev-list --left-right --count` = `0 / 0`)。
- ⚠️ 旧 origin(`work.alotbuy.com:222`)仍 **ahead 256**,网络不通未推。
## 旧远程清理(16:1x,用户:「旧的远程没用了」)
- 处理三步:① `git remote set-url origin` 指向 `[email protected]:admin/mcn-short-video.git`——**保留默认远程名 origin**,脚本与命令习惯不用改;② 删除冗余别名 `admin`(与 origin 同址);③ 删除随旧远程留下的本地分支 `admin-main`(= e738dd1,是 main 的祖先,零独有内容)。
- 验证:只剩 origin 一个远程;本地与 origin 完全一致(`0 / 0`)。
- 📌 **远程口径(本项目现行)**:`[email protected]:admin/mcn-short-video.git`(旧 `work.alotbuy.com:222` 已废弃)。
### 🔴🔴 再修正:后台任务会话**不进「空间」分组**(这才是真机制,前两轮都猜错了)
- 现象:cwd 已改成 `D:\AI技能\mcn-workshop`,会话仍落在「**未分组任务**」。
- 取证(sessions 表):`group_id` / `group_title` **全为 null**;`project_id` 也全 null ⇒ 分组不靠这三个字段。
- ⭐ 真因:**侧边栏是「空间区」与「任务区」两套**——
- **手动会话**(`is_background_automation` 为 0/null)→ 按 cwd 聚合成**空间分组**(如 mcn-short-video、vibe-video-analysis);
- **后台任务会话**(`is_background_automation=1`)→ 一律进**任务区**,与 cwd 无关。
- 工作台 `/api/run` 提交的任务会话**全是 bg=1** ⇒ **改 cwd 永远改不了它进哪个区**。
- 想让「mcn-workshop 空间」出现在侧边栏:**在 `D:\AI技能\mcn-workshop` 手动开一个会话**(手动会话按 cwd 聚合才会生成该空间);但后台任务仍会在任务区,属产品设计。
- ✅ 正解(已落地):看后台任务执行状态用**工作台右下角进度面板**,不必依赖侧边栏分区。
- ⚠️ `app.asar` 里 grep「未分组」= **0 次** ⇒ 该文案在别处生成(未继续逆向)。
### ✅✅ 真根因并修复:`automations.workspace_scope` 漏填(不是 cwd、不是工作区登记表)
- 逆向线索:`is_playground` 赋值处 = `typeof e.space?.autoGenerated=='boolean' ? +!!e.space.autoGenerated : null` ⇒ 「空间是自动生成的」才落未分组。
- 交叉表取证(决定性):
- 走**正规 automation 工具**创建的任务:**`workspace_scope='workspace'`(36 条)** ⇒ 会话 `is_playground=0` ⇒ 正常进工作区分组;
- **工作台直接写库**的任务(`createOnceAutomation` + `/api/run` 两处 INSERT):**`workspace_scope` 为 null(当天 5 条)** ⇒ 会话 `is_playground=1` ⇒ 落「未分组任务」。
- ❌ 排除的假因:改 cwd(无效)、把目录登记进 `workspaces` 表(无效,该表不是决定因素;登记行已保留,无害)。
- 🔧 修复:`server.js` 两处 `INSERT INTO automations` 增加 `workspace_scope` 列并填 `'workspace'`(`createOnceAutomation` 约 L407、`/api/run` 约 L879)。
- 实测:新任务 `scope=workspace`、新会话 **`is_playground=0`** ✅(对照组仍是 1)。
- 📌 **红线**:工作台凡直接写宿主 `automations` 表,必须带 `workspace_scope='workspace'`,否则会话一律落「未分组任务」。
## 会话任务链路审计(用户:「看看哪些需要调整」)
**已修**:4 个轮询点口径统一(此前只有榜单更新一处有容错)。
- 轮询点 = `app.js executeTask`(通用 AI 任务)/`data-pages` 的榜单更新、批量更新账号、视频解析。
- 原先 3 处是 `catch(e){continue}`,服务被回收时静默空转 15~35 分钟才超时 ⇒ 用户只看到按钮转圈。现均为「连续 3 次查询失败 → 提示服务断开 → 结束轮询」。
**已确认健康(无需改)**:
- 写宿主 `automations` 表**仅 2 处**(`createOnceAutomation` 约 L409、`/api/run` 约 L880),均已带 `workspace_scope`。
- `submit-review-tasks.cjs`、`check-run-status.cjs` 走 HTTP 接口/只读,不直接写库 ⇒ 自动被修复覆盖。
- 技能挂载(`skills_json`)、浏览器锁约束、会话清理(`SESSION_CWD_PATTERN='%mcn-work%'` 仍覆盖新 cwd)均正常。
**待用户定(各有权衡)**:
1、**任务模型浮动**:`model_id` 取 `sessions` 最近活跃**手动**会话的 model,与 `config.ai.cli.model`(deepseek-v4.1-flash)**无关** ⇒ 同一任务换次数可能换模型,产出不稳定。建议加 `taskModel` 配置(留空=跟随现状,填了=固定)。
2、**进度面板缺「打开会话」入口**:能看状态但跳不进会话。
3、**任务权限恒为 `fullAccess`**(可写文件/执行命令):是否收紧取决于任务类型。
4、**修复前创建的旧会话仍 playground=1**:改库可归位,但需重启 WorkBuddy 才生效。
## 素材库补采:批 65 收尾 + 批 66/67 完成(30 卡)
- 承 09-29 进度:批 65(细节专业 15 卡)已 apply 完成,细节专业 40→55。
- **批 66(细节专业 15 卡)**:取材 31748 姜乘澜底妆 / 17511 笑笑易 / 19014 俊希做菜 / 24821 烟道逃生 / 25090 姜乘澜画眼线。✅ **本批 5 条原生即教程/实操题材**,与细节专业天然契合(不像批 65 从非专业题材硬提)。apply:接受 15/退回 0,问法改写 15 张平均 +32 字。细节专业 55→70。
- **批 67(自嘲反差 15 卡)**:取材 26484 路之坑爹感恩宴 / 31816 直男五合一洗护 / 31970 李普不离谱 / 21500 桃气小周买饭 / 26411 老弟受难日记。含 1 张**元层面自黑**卡(31970 全家崩溃时推销泡面)。apply:接受 15/退回 0,问法改写 15 张平均 +12 字。自嘲反差 56→71。
- 两批均走完整链路:`写 md → _tmp_bNN_spec.py 生成 spec → meta.json → build-pending → cat PROMPT_polish_v3.md + build-pending 组装 prompt → 派 doubao-seed-2-1-pro → _wNN.py 校验落盘 → apply-pending`。
### 本轮新踩坑
- ⚠️ **MCP `myai-mcp-production` 登录态会过期**:批 66 拉第 4 条时报「认证失败」。解法:先 `auth_status` 确认 → `feishu_login` 重授权(拉起浏览器)→ 重试即通。**连续拉多条详情时中途要留意**。
- ⚠️ **spec 脚本里别夹英文单词**:批 66 的 neg 句误写「等摊主 processing」,批量产出会污染语料。写完 spec 脚本前先自查。
### 当前缺口(修正后口径,2026-10-08 10:50)
| 标签 | 现有 | 缺口 | 候选 |
|:--|--:|--:|--:|
| 食材极致 | 8 | 92 | ⛔ 0 |
| 预见式服务 | 21 | 79 | ⛔ 2 |
| 感官沉浸 | 59 | 41 | 14 |
| 细节专业 | 70 | 30 | 22 |
| 自嘲反差 | 71 | 29 | 34 |
| 视觉冲击 | 76 | 24 | 10 |
达标:品质对比 110 / 氛围沉浸 106 / 反差 217 / 反常识 128 / 价值观冲击 108。
- ⛔ **卡点未解**:`食材极致`(缺口 92)与 `预见式服务`(缺口 79)候选池近乎空,占剩余缺口一半以上。需换关键词或换捞法才能推进。
- 暂存批累计 24 批(43–52、54–67,缺 53)。
## 补采续跑:批 68(感官沉浸 15 卡)/批 69(细节专业 9 卡)
**批 68 · 感官沉浸(33289 子杭自驾318 ×6 / 31773 萝卜乔乔英国留学回国 ×6 / 30090 姐弟双向送礼 ×3)**
- apply 结果:**接受 15/退回 0,问法改写 15 张,平均 +19 字**。感官沉浸 59 → 74。
- ⭐ **两条来源 0 卡,已在 md 写明判据**:`28012`(电焊工试戏)男主嘴配的「滋滋」是**谐音梗的音效**而非真实声音质感,事件主角是"身在曹营心在焊" → 归 `反常识`/`自嘲反差`;`28415`(男朋友的算计)踩碎眼镜/摔筷子/拍桌的动静是**发疯甩锅的附产品** → 归 `反差`/`价值观冲击`。
- 感官通道分布齐全(冷/缺氧失眠/冰雹砸车/撕羊腿/热水澡/久坐腰酸 · 热瓶/苦到脸垮/中药味/红米肠/赶机喘/长途疲惫 · 中暑虚脱/猛灌水/红糖蛋滋啦)。
**批 69 · 细节专业(28328 海鲜蒸汽 ×4 / 37316 巧克力棒手作 ×5)= 9 卡**
- apply 结果:**接受 9/退回 0,问法改写 9 张,平均 +20 字**。细节专业 70 → 79。
- ⭐ **本批 3 条来源 0 卡,坚持不凑数**(沿用批 56 口径):
- `20461`(机票骗局)—— 骗子的"专业"是**话术与骗术设计**(伪装客服、真退票取信、连环索要卡号),不落在"这一下换普通人来做就做不到位"的操作细节上 → `反差`/`反常识`。**这是细节专业最容易误判的一类:话术专业 ≠ 操作专业。**
- `24820`(烟道脱困)—— 主角是**作死翻车与体力消耗**,片尾"专业人士、专业场地、专业操作"是**反讽** → `自嘲反差`/`难度极限`;且同账号同题材 `24821` 已在批 66 取过两卡,本条脱困手法同构,不重复入库。
- `22163`(轮椅游渔岛)—— 倒拖轮椅过软沙滩属**体力付出** → `难度极限`;其余为情侣互坑 → `反差`/`自嘲反差`。
- ⭐ **取批命中率下降的信号**:细节专业候选池看着有 22 条,但主队列取 5 条只有 2 条可用(命中率 40%)。后续该标签每批卡数会低于 15,属正常,不要为了凑 15 张硬塞。
**新增踩坑与复用做法**
- ✅ **`_wNN.py` 用 sed 复用**(`sed -e 's/_raw68/_raw69/g' -e 's/pending_0068/pending_0069/g' -e 's/^N = 15/N = 9/' _w68.py > _w69.py`)—— 卡数变化时记得同步改 `N`。本轮连续 3 批(67/68/69)一次通过,`bad: []`。
- ✅ **超长详情自动落盘**:`33289` 返回 88,150 字符超限 → 工具落盘到 `D:\.workbuddy\projects\d-AI技能-mcn-short-video\<sessionId>\tool-results\*.txt`,用 Read 直接读(该文件只有 117 行,一次读完)。
- ⚠️ 派模型改用「让 Agent 自己 Read prompt 文件 → 自己 Write 落盘 `_rawNN.txt`」——比让模型把 JSON 回吐到对话再手工落盘更省上下文,连续 3 批稳定。
**当前缺口(批 69 后)**:自嘲反差 29(候选 34)|感官沉浸 26(候选 14)|视觉冲击 24(候选 10)|细节专业 21(候选 22)。食材极致 92/预见式服务 79 仍挂起(用户 2026-10-08 决策:先不处理)。
## 批 70(自嘲反差 15 卡)· 本轮第 5 批 → 复核点
**结果**:接受 15/退回 0,问法改写 15 张,平均 **+27 字**(前几批 +19/+20,本批问法增厚更充分)。自嘲反差 71→**86**(缺口 14)。
**来源**:35376 唐轩乌龙×6 | 35301 丁浩普信男×6 | 28146 妈妈硬核推理×3。**5 条视频里 2 条 0 卡**(33593、21923)。
**⭐ 自嘲反差口径新增形态「姿态过载」**(本批确立):
- 定义:主角自己把一件小事升格到远超其体量的规格,且全程一本正经(妈妈把女儿回家晚做成刑侦审讯:北风4级/100米/11分37秒 vs 6点12分39秒)。
- 判据仍落回一句话:**笑点落在主角自己身上**。妈妈是主角、推理的荒谬感由她自己一本正经地制造 → 收。
- ⛔ 反向凡尔赛揭晓(语数英 100/98/100 + 兑现平板)→ 笑点在预期被颠覆 → `反常识`;儿子 O 型嘴石化 → 笑点落在旁观者受击 → `反差`(世界参差)。均不收。
**两条 0 卡判据(写入 meta)**:
- **33593(张开父子的下场)**:家族护短复仇爽剧,爽点落在反派父子被制裁上,主角阵营全程降维打击,无一处笑点落在自己身上 → `反差`/`价值观冲击`。其「悲痛段落硬塞洗面奶口播」虽属元层面自黑,但批 67 已收同构卡(C1 全家崩溃时推销泡面)→ 不重复入库。
- **21923(消防设施瘫痪的代价)**:真实事件改编的沉重社会悲剧(烟花引火→消防栓被锁→接头拧不上→母亲丧生→儿子一夜白头),全片无笑点 → `价值观冲击`。
- ⭐ **通用判据沉淀**:**沉重社会悲剧类一律不进自嘲反差**;**元层面自黑要查是否已收同构卡**(批 67 已占一类)。
**其他弃卡判据**:35376「你那算盘珠子都崩我脸上了」是吐槽他人 → `反差`;「我没吃过我妈包的包子」与「我妈不会做饭」是同一场乌龙的两端 → 分作「立 flag」与「揭盅」两卡,不合并(同批 67 B2/B3/B4 细分做法)。35301 防晒植入段属商业植入;丽丽怒骂并入 B4 不单列。
**本轮 5 批(66–70)合计 69 卡**:66=15 | 67=15(自嘲反差)| 68=15(感官沉浸)| 69=9(细节专业,不凑数)| 70=15(自嘲反差)。
**当前缺口(批 70 后)**:感官沉浸 **26**(候选 14)→ 视觉冲击 24(10)→ 细节专业 21(22)→ 自嘲反差 14(34)。食材极致 92/预见式服务 79 挂起。
**体检**:`5_tag_coverage.py` 卡片总数 1017(源文件 69 个),12 维无自造标签;⛔ 零覆盖仅 `难度极限`(用户决策不处理),⚠️ 偏少仅 `食材极致`(8,挂起)。
**复核抽查**:批 70 卡 6(哭着嚼坚果上供)润色生效——台词前置 + 破折号拍点(「我以后再也不养小仓鼠了,我对不起你」——唐轩把相框平放在桌上…),锁定 4 字段零改动,无编造。spec 回写 sim=3/neg=2、tag 单一、src 3 个,全部符合。
**⭐ 入库状态核查(用户问「素材都同步到 wekonra 了吗」→ 答案:没有,两层原因)**
- **流程层**:按暂存模式,补采只落 `batches/pending_import/`,统一入库必须跑 `13_import_pending.py --run`。截至批 70,**27 批 397 卡全部堆在暂存区,一条都没进库**(`13_import_pending.py --help` 盘点:27 批/397 卡/异常 0)。`batches/*.md` 41 批(早期跑过 `2_import_batch.py` 的)是唯一进过库的部分。
- **环境层**:`WeKnora-app` 容器 **Exited (127)**,最后一条日志停在 **2026-09-29 23:53**(health 200)→ 31607 端口不通,连"库里现有多少条"都查不了。同项目其余容器(frontend 24719 / docreader / postgres / redis)都在跑。**整个 10-08 的补采从未连过库。**
- **部署信息**:镜像 `wechatopenai/weknora-app:latest`,restart `unless-stopped`,compose 工作目录标签 `/home/maidou/weknora`(WSL 路径,本机 `docker ps -a` 可见)。拉起命令 `docker start WeKnora-app`;若仍 127 需查 compose 的 command/entrypoint。
- **回炉链路同样未完成**:「删旧条目 + 重导 + 检索终验」都还没做;回炉批 31–33 也未跑完。
- ⚠️ 排查口诀补充:**问「同步了吗」时先查两处**——① `13_import_pending.py --help` 看暂存盘点 ② `docker ps -a` 看 WeKnora-app 是否 Up。两者都要看,只查一处会误判。
**⭐ 规则核对:入库前必须补全 question(用户 2026-10-08 追问「是否遵循」→ 核查 27 批结论)**
- **规则依据(能查到的最强出处)**:RUNBOOK §步骤 6「写 spec.json(检索问法)」=流水线必经步骤;**真正的动机在 RUNBOOK 红线**:「追加相似问不重建索引(`POST /faq/entries/{id}/similar-questions` 返回 200 但向量分数按位不变)→ **要改问法只能删 + 重导**」。所以「入库前补全」不是形式要求,而是**事后补的代价=整批删+重导**。
- ⚠️ **未找到该规则的原始对话出处**:`conversation_search` 两次 0 命中,本地 memory / RUNBOOK 也没有「入库前要补全 question」这句原话。**已按「规范 + 红线」等价认定,未擅自当作既有明文规则引用。**
- **核查结论(27 批全量)**:全部有 spec.json、`std` 全部非空 → **形式上 100% 遵循**。但有 **2 批偏离现行规范**:
| 批 | 偏离 | 范围 |
|:--|:--|:--|
| **43** | `neg=3` 条(现行应为 2),且内容是**关键词短语**(「白酒带货 / 餐厅推荐 / 商务礼仪培训」)不是问句 | 15/15 卡 |
| **48** | `sim=2` 条(现行应为 3) | 15/15 卡 |
其余 25 批:sim=3(批 47 起)或 sim=6(批 44/45/46,合旧规范)/neg=2,全部合规。
- ⚠️ **文档与执行脱节(待用户裁定)**:RUNBOOK §步骤 6 明文写 **sim「口语问法 4–6 条」**,但**批 47 起实际统一为 3 条**(`_wNN.py` 校验也写死 `!= 3`)。要么改文档、要么把 sim 补回 ≥4 条。
- ⭐ **核查脚本口诀**:`glob('batches/pending_import/batch_*.spec.json')` 逐批 `Counter(len(x['sim']))` + `Counter(len(x['neg']))` 打分布表,一眼看出条数偏离;**别用 md 的 ```text 计数 //2 算卡数**(会算成一半,以 spec 长度为准)。
**⭐ Docker WeKnora 启动失败排查(2026-10-08 · 已修复)**
- **根因(不是应用崩溃,是容器 init 前的 bind mount 失败)**:`docker inspect WeKnora-app` 的 `.State.Error` 给出原文——
`OCI runtime create failed: runc create failed: ... error mounting "/run/desktop/mnt/host/wsl/docker-desktop-bind-mounts/Ubuntu-24.04/4f4eee8c…" to rootfs at "/app/config/config.yaml": not a directory: Are you trying to mount a directory onto a file (or vice-versa)?`
- **链路**:compose 写 `./config/config.yaml:/app/config/config.yaml`(文件→文件,写法正确,源文件 `/home/maidou/weknora/config/config.yaml` 确实存在且是 6545 B 的文件)→ 但 **Docker Desktop 的 bind-mount 代理层**(WSL 9P 中转,路径被哈希成 `4f4eee8c…`)把源**识别成了目录** → 挂载失败 → 容器连 init 都没进 → **Exit 127**(entrypoint `./scripts/docker-entrypoint.sh` 根本没执行)。
- **时间线**:StartedAt 09-28 01:45,FinishedAt **10-08 02:51** —— 正常跑了 10 天,今天凌晨有一次重启尝试(Docker Desktop/WSL 层变动)时代理层状态不对而失败。restart `unless-stopped` 不会救「启动失败」,所以一直停在 Exited。
- ✅ **修复:`docker start WeKnora-app` 一次即恢复**(`Up (healthy)`,31607 通)→ 属**代理层临时状态问题**,重试可解。复发时的升级路径:重启 Docker Desktop → `docker compose up -d` 重建容器(会换新的代理哈希)。治本方向:单文件 bind mount 跨 WSL 本就脆弱,可改 `docker cp` 或 volume。
- ⭐ **排查口诀:容器 Exited(127) 先看 `docker inspect --format '{{.State.Error}}'`**,不要只看 `docker logs`(日志只到上次正常运行,看不出启动失败原因)。本次日志最后一条停在 09-29 23:53 health 200,完全看不出问题,是 inspect 的 State.Error 直接给出根因。
**库内实测(服务恢复后)**
- FAQ 总数 **631**(分页抖动,去重拉取 624)。**问法 100% 齐全**:无 std 0 / 无 sim 0 / 无 neg 0。
- sim 条数分布 `{5:312, 6:222, 3:46, 2:30, 4:14}`、neg `{2:542, 3:82}` —— 历史口径混着(旧 5/6 条、现 3 条、少量 2 条)。
- 库内 tag_name 分布:反差 172/反常识 117/价值观冲击 93/视觉冲击 67/自嘲反差 44/细节专业 32/感官沉浸 26/氛围沉浸 24/品质对比 19/预见式服务 19/食材极致 9/难度极限 2 → **与本地统计一致,即已入库 = 早期 41 批;暂存 27 批 397 卡确实未进。**
- ⚠️ **接口结构坑**:FAQ 列表返回是 **`data.data`**(`{data:{total,page,page_size,data:[...]}}`)两层嵌套,只取一层会得到空列表;条目标签字段是 **`tag_name`**(不是 `tag`/`tags`);`page_size=total` 一次拉全会返回空 → 必须分页 100 逐页 + 按 id 去重。
**⭐⭐ 27 批 397 卡统一入库完成(2026-10-08 · `13_import_pending.py --run`)**
- **结果**:成功 27 批 [43–70 除 53] / 失败 0。**库内 631 → 1028,增量 397 == 暂存卡数**,完全对上(判据用库内增量,不用 SUMMARY 的 success 数)。问法完整性:无 std 0/无 sim 0/无 neg 0。
- 导入后 tag:反差 224 / 反常识 132 / 品质对比 109 / 价值观冲击 108 / 氛围沉浸 105 / 自嘲反差 85 / 细节专业 79 / 视觉冲击 76 / 感官沉浸 70 / 预见式服务 22 / 食材极致 9 / 难度极限 2。
- ⚠️ 注意:**库内 tag 数与本地 `5_tag_coverage.py` 统计不同**(例:感官沉浸本地 74 vs 库内 70)—— 库内按 spec 的单值 `tag`(每卡主标签),本地按卡片事件标签字段(含辅标签计数)。**两者口径不同,不要互相校验。**
**🔴 新踩坑(重要,会静默导致整批导入失败):spec.json 的 `card` 字段必须是「标题行」,不是卡片全文**
- **现象**:首次 `--run` 成功 13 批 [43–47,50–58],失败 14 批 [48,49,59–70],报错 `✗ spec 与 md 对不上:[整张卡片全文]`。
- **根因**:`2_import_batch.py` L40–48 的比对是
`cards={}` → `t = b.splitlines()[0].replace("### ","").strip()` → `cards[t] = b.strip()` → `miss = [s["card"] for s in spec if s["card"] not in cards]`
⚠️ **`cards` 是 dict,`in` 判的是 key(=标题行),不是 value** → 所以 **spec 的 `card` 必须等于「去 `### ` 前缀后的标题行」**,放全文必然 miss。
- **我犯的错**:`_tmp_bNN_spec.py` 用 `CARD_RE` 提整张卡塞进 `card`(且带 `### `)→ 批 66–70(及此前 59–65、48、49)全部中招。对比成功的批 58:`card` = `"20829-1|凌晨三点半看完球…"`(仅标题)。
- **修复**:对 14 批执行 `sp['card'] = blocks[i].splitlines()[0].replace('### ','').strip()` 后重跑 → 27/27 成功。
- ⭐ **更新铁律表述**(旧的「card 禁带 `### ` 前缀」不够精确,易误解成"全文去前缀"):**`card` = 标题行本身(去 `### `、strip),不含正文字段。**
- ⭐ **排查口诀**:报「spec 与 md 对不上」时,**不要读那一大坨报错文本**(它把整卡打印出来,看不出差异),直接复现 L40–48 的两行逻辑比对 `spec[0]['card']` 与 `cards` 的 key。
- ⚠️ **apply-pending 不会修 card**:`10_polish_batch.py` L412–413 只回写 `sp["std"]/["sim"]/["neg"]`,**不动 `card`**(标题润色前后不变,所以不修是对的)。**card 的正确性全在生成 spec 那一刻**,写错只能事后修。
**导入后的遗留项**
- 批 48 的 `sim=2`(少 1 条)**已随导入进库** → 按红线「改问法只能删+重导」,若要补须删该批 15 条重导。**未处理,待用户定夺。**
- 批 43 的 `neg=3`(且是关键词短语)—— **neg 不入索引**(RUNBOOK §步骤6),对召回无影响,可不处理。
- 回炉链路仍未完成:删旧条目(按 `polish_plan.json` 冻结的 526 id)+ 重导 + 检索终验。
**⭐ 方向性判断:「现在要不要测精准率/召回率」(2026-10-08 用户问,结论待执行)**
- **结论:分拆看,不要笼统测。**
| 指标 | 该不该测 | 理由 |
|:--|:--|:--|
| **召回率(反查法 R@1/R@3)** | ⚠️ **只做回归验证**,不做优化基线 | 有 ground truth(卡自身)、能隔离「问法」变量、成本 2 分钟。但历史基线已是 R@1 34/35,问法改写实测差异在噪声内 → **测了也难再提升**,价值只剩「验证 397 卡导入后检索没坏」。 |
| **精准率** | ❌ **暂不必测** | 需人工标注「top-N 每条是否相关」,1028 条库成本极高;且分数间隔极窄(top1~top10 仅差 0.047)→ 机器判不了优劣,只能人工。已知不是瓶颈。 |
| **覆盖率**(真实需求节点 → 能否找到素材) | ✅ **该测,且是当前唯一真瓶颈** | 3/5 真实节点零命中 = 素材覆盖不足。刚导入 397 卡(631→1028),覆盖率已变,**正是重新测的时机**。 |
- ⭐⭐ **根本前提(决定一切检索指标的传导性)**:**素材召回端到端触发率 ≈ 0**,且用户已裁定这是「流程不主动召回、知识库按需被取」的**设计意图**,`hybrid_search` 0 次 ≠ bug。
→ **推论:检索指标再好,也传导不到最终产出**(优化一个几乎不被调用的函数)。所以「测检索指标」的价值**只在回归验证**,不在效果优化。
- ⭐ **刚导入 397 卡后必做的不是指标测试,而是「入库正确性验证」**:确认新卡真能被检索到(不是精准率/召回率,是「导入成功了吗」)。
**⭐ 入库验证 + R@N 基线(2026-10-08 · 反查法,81 条样本)**
- **结论:R@1 = 81/81 = 100%**,27 批全部命中 → **397 卡导入成功,检索链路正常**。明细 `recall_cache/verify_import_1028.json`,脚本 `_tmp_verify_import.py`(可复用,支持 `--dry`)。
- 抽样:27 个暂存批 × 每批首/中/尾 = 81 条,覆盖 9 个标签。查询源 = **卡片正文「极致内容」**(不在索引里,索引只有 std/sim;neg 不入索引)→ 与 std 不同源,比 18b 的「从 standard_question 提取事件」更远一步。
- ⚠️ **这个 100% 不能当「召回质量好」的证据**:std 本就是由卡片正文改写而来,正文查自己属**同源查询,存在天花板效应**。**它只证明「入库成功 + 检索没坏」,不证明「问法有效」。** 与历史基线(18b 反查法 R@1 34/35 ≈ 97%)量级一致,属正常。真要判召回质量仍需异源测试(真实编导需求节点)。
**🔴 新踩坑(极具迷惑性,差点得出「召回率 0%」的错误结论)**
- **检索接口路径与参数名**:正确是 **`POST /knowledge-bases/{KB}/faq/search`**,body 用 **`query_text`**(不是 `query`),返回 **`r.get("data") or r.get("results")`(直接是列表,不是嵌套 `data.data`)**。
- 我写成了 `/faq/search` + `query` → **HTTP 404**。
- ⚠️ **坑的杀伤力**:404 被 `try/except` 静默吞掉 → 每条都记成「未命中」→ 输出 **R@1 = 0/81 = 0%**,看起来像「导入失败/检索全崩」。
- ⭐ **识别口诀**:**跑完先看耗时**。81 条检索正常约 14s;若「0 命中且耗时 1s」→ 一定是异常被吞了,不是真实结果。**批量检索脚本必须让异常显式暴露(打印 err),不要静默记为未命中。**
**新增踩坑**
- ⚠️ `_tmp_bNN_spec.py` 里卡片名提取必须 **`.replace("###","")`**:`head.split("|")[0].strip()` 会带 `### ` 前缀 → `assert name in Q` 报 `AssertionError: ### 吃包子吃出丧母感`。批 70 踩到一次,加 replace 后通过。
- ✅ prompt 组装字节数核对:模板 7,683 + 载荷 34,647 = 42,330(`wc -c` 可验),比批 68 记录的 15,772 大是因当时统计口径不同,**以 `wc -c` 为准**。