Files
workbuddy_skills/session-mechanism/references/dsh-decision-method/素材库-A-AI推理.md
T
admin 64dd82073b session-mechanism: 修复钩子静默失效 + 3 处判据缺陷;禁「变相征询」
1) stop-dialog-guard: session_budget() 早退路径返回 2 值、末尾返回 3 值,调用方按 3 值解包
   ⇒ transcript > 64 MiB 时每轮 ValueError。因 fail-open(异常仍 exit 0),
   宿主零报错、install.py --verify 只判 rc=0 ⇒ 假绿;实测 86 条 EXCEPTION,
   死掉的是整条(水位/收口、接续机制起点、预算告警、门禁自检、路径自检)。

2) session-rules-check 三处判据:
   · hook_reg  按旧文件名找 ⇒ 合并成 prompt-guards.py 后每轮假红 ⇒ 改为一组可接受名
   · snap_sync 拿 mtime 当内容判据 ⇒ 连续 4 天假红 ⇒ 改为复用抽取器本体比对内容
               (变异对照:截断快照能报 fail,非恒绿)
   · mem_ptr   只查全局技能根 ⇒ 工作区自带技能被判悬空 ⇒ 改查「全局 ∪ 工作区」

3) pitfalls 新增 P0-95(改判据必须重跑变异对照;fail-open + 只看 rc=0 = 假绿温床)

4) 回复排版核心块新增「变相征询同样禁止」(先只报不动/等你发话/我倾向X你看呢
   这类不带选项的待定清单,一律按待拍板项写:问题+说明+各候选优缺点+倾向)
2026-10-06 22:27:03 +08:00

19 KiB
Raw Blame History

素材库 · AI 的有效决策(A1–A22)

归属:技能 dsh-decision-method 的素材库(按需读,不是每次都要读)。 主文件 / 索引 / 判定核心 = ../SKILL.md(§4 判定核心 · §5 流程 · §7 语言表 · 附 自检)。 用法:只在「要判某条是否属于既有口径」或「要引用用户原话」时读本文件;别整段抄进答复。 维护:条目只增不改(编号进位到末尾);用户原话逐字引用;每条必须带「实例出处 + 判据」。


2. 决策素材库 · AI 的有效决策(A1–A16)

这些是被实践证明有效的推理方式,不是结论。新任务遇到同类岔路时直接套用。

A1|取证优先于推断:源码级 / 命令级,禁止只靠文档

  • 实例:--dump-config 被证伪(它不反映 bundle/profile patch 层,连已生效的行都不显示)→ 判定口径改为「加载标记 + 浏览器实测」;「手放 node_modules ≠ 安装」(lockfile 才是账本);「空闲回收」从未生效(全历史 idle-reap 仅 1 次)
  • 做法 → 结论前先问"这条我能用什么命令证明",写成命令 + 期望输出;文档只当线索

A2|方案对比表:≥2 选项 + 影响/风险 + 建议,必须含"不做"

  • 实例:档案 20 方案 A(加固,推荐)/ B(启 enablePatch,不推荐);档案 45 方案 A(会话迁移,不做)/ B(提示,采用);档案 16 §四 folder_plugins(查明后建议废弃)
  • 做法 → 表格四列:方案 / 内容 / 判定 / 理由;推荐项置首并标注 "(Recommended)";给不出"不做"这条路说明分析还没做完

A3|决策点显式列出,等用户拍板(不替用户决定)

  • 实例:交接单 8 段里第 4 段就是「决策点」——已定的写"已定(谁定的/依据)",未定的写"开跑前问用户"
  • 做法 → 一单最多留 3–5 个决策点,每个给推荐项 + 代价;未定的决策点不允许执行会话自行拍板

A4|能用 A/B 实测就 A/B,别写"应该会更快"

  • 实例:NODE_COMPILE_CACHE 冷启动 5.0s → 4.0s(实测);反向教训也记:--max-old-space-size 不降稳态内存(优化前后 cgroup 都 ~98 MiB)→ 它买的是可诊断性,不是省内存
  • 做法 → 性能/资源类结论必须给前后两个数;说不出第二个数就明说"未实测"

A5|把"不可验证"改造成"可验证"

  • 实例:给插件加加载标记 [workspace-scoped-picker] loaded root=… → 把升级回归(C4)从"看 UI"变成"看日志";GET /api/capabilities 人机同源
  • 做法 → 一个改动如果只能靠"看起来生效了",就顺手加一行可 grep 的日志/标记/端点 —— 这是最便宜的可验证性投资

A6|选「最小代价的合法路径」,不选「最彻底的」

  • 实例:档案 45 —— 根治方案是改写存量会话的种子事件(属 R5「扩大」+ 多帧 zstd append-only 日志重写,风险收益不成比例)→ 改选一句提示文案;档案 42 —— 想禁 python3.6 的"遮蔽"会让实例起不来 → 改选文档引导(成本 0、风险 0)
  • 判据 → 问三句:① 有没有更小的改动达到同样目的?② 这个改动扩大了吗?③ 失败时的后果对称吗?
  • ⚠️ 边界(U27):A6 只约束路径(实现取最小代价),不约束目标 —— 不许把"选最小代价"读成"降低目标 / 延期 / 静默兜底"。目标不打折,路径取最小代价。

A7|能一行代码解决,就不要改平台配置

  • 实例:实例内 Python 抓 HTTPS 报 CA 错 → 正解是脚本里 SSL_CERT_FILE=/etc/pki/tls/certs/ca-bundle.crt,不走 R5 注入 env(因为注入 env = 扩大,而一行代码就够了)
  • 判据 → 平台级改动(env / 挂载 / nft / 配额)是最后手段,不是第一手段

A8|遇到"要删/要迁移"先判代价对称性;不对称 → 保留

  • 实例:folder_plugins / workspaces 表加废弃注释保留(跨 10 文件 + 含 k8s/PG 未验证路径 → 删除风险不对称);4 份 cold 档案不迁 archive(收益小);技能包裁剪则相反 —— 真 0 引用的才剔
  • 判据 → 删除的收益 < 潜在破坏时,选择"标注废弃 + 禁止再加功能",而不是删

A9|明确「不做」并写明「打开条件」

  • 实例:白名单源码安装 不做(与"平台不执行第三方构建脚本"红线冲突)→ 同时留「打开条件清单」(沙箱构建 / --ignore-scripts / 仅 admin / 审计 / 全量扫描);熔断实测、管理类插件化也都明确关闭
  • 做法 → 「关闭」必须写两句:为什么不做 + 什么条件下可以重开;只写"不做"会在下次被重新提起

A10|失败面要留证据,不要吞错误

  • 实例:插件探活失败回传 dsh 真实错误(duplicate loader entry id / ERR_MODULE_NOT_FOUND),不要泛化成"插件不兼容";坏包隔离并改名标注 -BAD-missing-index.tgz
  • 判据 → 报错信息是 admin 判断根因的唯一线索,泛化等于毁掉线索

A11|不确定的能力显式抛 unsupported,不假装支持

  • 实例:K8sUserFs.readFile 显式抛 unsupported(sidecar 尚无端点)→ 「属未验证路径,不假装支持」
  • 判据 → 本地验证过的能力才写"支持";没验证的路径要么标注"未验证",要么直接拒绝

A12|只读核对也要留痕(不改也要出结论)

  • 实例:所有"核查 / 取证 / 评估"类任务都产出档案(编号 + 状态 + 触发 + 结论表),即使一行代码都没改
  • 判据 → 「核查完成」本身就是一个交付物;不落档的结论等于没发生(下次会重复发现)

A13|自查要分「当时成文的口径」与「新立的口径」

  • 实例:越界自查结论 = 按当时的 R1-R6 未违反;按当天新立的 R7/R8 则5 次实质越界 —— 两种口径都给出,不粉饰
  • 判据 → 复盘/自查时说明用的是哪一版标准,否则结论不可信

A14|删除 / 移除 / 下线前,先查清楚再动手(用户 2026-09-12 明确)

  • 实例(我的失手):我把服务器上两个文件报成"多余、待用户定是否删除",却没先看它们是什么 —— 一查才发现三件事: ① 本机 scripts/ 里都有(我只 ls 了根目录就说"本机没有") ② 服务器那两份早已被并行会话清掉(我把一个已消失的问题抛给了用户) ③ 顺着 grep -rn 找到档案 73,一句话就看清用途 —— lock-guard-hook.py 是 PreToolUse 强制钩子
  • 删除前三问(没答完就不动):
    1. 它是什么 —— 读内容 / 读关联档案的 TL;DR,不要凭文件名猜。
    2. 被谁引用 —— grep -rn <名> --include="*.md" --include="*.ts" --include="*.cjs";全库 find,不要只看当前目录。
    3. 删了影响谁 —— 是否有别的会话 / 服务 / cron 在用;是不是唯一副本。
  • 判据 → 未查明就不动;查明后确认是"放错位置的多余副本"(正本在别处且 md5 一致)才可直接清理。
  • 与 A8 配合:删除是风险不对称动作(删错 = 丢失,留着 = 只占空间)→ 默认保留,除非已查清。

A15|写状态必须带「三态 + 级别」,禁止用「支持 / 可用」描述未验证项

  • 实例:开源文档把未验证的 Kubernetes 模式写成方案 B → 用户纠正:「模式 B · Kubernetes 去掉这个,根本没验证,应该是待开发验证」。
  • 三态词表(强制):
    写法 什么时候能用 必须附
    ✅ 已验证 有可复跑的命令 / 日志 / 截图,且取数时间明确 命令 + 期望输出(§4.3 L2–L5)
    ⚠️ 待开发验证 代码 / 文档存在,但没跑过端到端 「未验证」三个字必须出现在结论句里
    ❌ 已知不支持 实测证伪 失败证据 + 复现条件
    ⛔ 禁用词:「支持 / 可用 / 已实现 / 已落地」不得用于 ⚠️ 段;上一轮(档案 76 §10.7)就是用文件名代替读包,把「没这能力」说成了结论。
  • 落地:开源 README / 档案状态 / 能力清单 / 交付回执 四处统一用这套词。

A16|回滚 / 改名 / 迁移类动作:先列「伴随物清单」,只改主体必留隐患

  • 实例(同日两起):① 回滚只回滚了部署、没回滚源码 → 下次重建又把补丁带回来(0.2.19 误带堆限 ⇒ 0.2.20 才真修);② 改 SQLite 库名漏了 -wal / -shm → 358 KB WAL 未被 replay、丢了 2 条会话记录(归位后 4→6 恢复)。
  • 伴随物清单(动手前逐项打勾):
    动作 主体 必须一起处理的伴随物
    回滚 制品 / 部署 源码 + 构建缓存 + lockfile + 实例内已加载的 bundle
    改名 / 迁移 主文件 旁路文件(SQLite -wal -shm)· 目录 · 符号链接 · 所有引用点(grep -rn)· 运行中的进程 / 服务
    批量替换 命中文件 自引用 / 自赋值(replace_all 会命中刚插入的定义行)· 行尾风格 · 语法校验(用 build 当校验器)
  • 判据 → 凡「一个名字 / 一份数据 / 一段制品」被改或退回,先写出它的伴随物清单再动手;改完必须读回复核(不靠「应该没问题」)。

A17|判「这是限制」之前先分层取证:配置项 / 已抽象层 / 真硬编码

  • 实例(用户当场纠正我):我把「DB 是单文件 SQLite」列为集群化的限制 → 用户口径「可以改为连接数据库集群,数据库不是限制」;实测 DSHS_DB_URL 非空即切 PG(db/index.ts:19),db/adapter.ts 头注释已声明「routes depend only on this abstraction」⇒ 早有抽象,是配置项不是天花板。
  • 判据 → 任何「做不到 / 是限制 / 是天花板」的结论,先把它归到三类之一并给出代码行号或命令:① 配置项(改 env/参数即可)② 已抽象层(换实现后端)③ 真硬编码(必须改码)。
  • 反面同时成立:别把「能配置」当成「已经能用」 —— 同一次实测才发现 dsh_instances 归属表只有 k8s 路径在写(LocalSpawner 根本不收 db)⇒「换库单独做 = 零收益」。

A18|静默失败会伪造结论:工具静默 + 降级静默,两头都要防

  • 实例(同日两起,都差点骗了我):① grep -v node_modules 把要查的行本身也滤掉 ⇒ 得出"源仓 0 处"的假结论(改用 ripgrep 才看到真相);rg 在本机 PATH 不存在 + 2>/dev/null ⇒ 静默返回空,看起来像"扫干净了"。② listCatalogProviders() 的 readdir 抛错被 catch 吞掉返回 [],注释还把降级写成特性 ⇒ "没生效"和"没做"不可区分,缺陷潜伏三轮。
  • 判据 → 断言"扫干净了 / 没有 / 全绿"之前,先自证工具真的跑了:跑一次已知命中的探测、看退出码、别用 2>/dev/null 掩盖失败;排依赖目录用 --exclude-dir/.gitignore,不要用行过滤。
    • ⚠️ 姊妹坑:把失败误读成成功(2026-09-15 实证)—— 用管道截尾看命令输出时(| grep / | tail),失败提示里也含成功关键词 ⇒ 假命中 ⇒ "以为抢到锁了"却在无锁状态改库。 ✅ 判据:先看退出码(不要接管道),再复读关键状态文件/OWNER 并断言;"输出了像成功的话"不等于操作成功。

降级必须留痕:静默 catch 至少 console.warn 一次,或把「不可读 / 目录为空」透出到状态端点(本次已落地:/api/me/model-providers 的 catalog{dir,readable,count})。

A19|验收判据必须包含「第二环境」——同环境反复通过会掩盖跨环境从未验证

  • 实例:写死 /usr/local/lib/node_modules 在本机(npm 默认 prefix)恰好是对的 ⇒ 端到端验收"38 家全绿"只证明了"在这一台机器上对";换 /usr/lib 布局(install.sh 部署的机器)则厂家目录读空、兼容性预检整体失效、目录选择器 import 即抛 —— 且全都静默。同一个未验证假设被复制了三轮(picker → plugin-compat → model-catalog)。
  • 判据 → ① 凡「读别人安装位置 / 版本 / 布局」的代码,路径必须走解析层,且至少一条单测用假根(已落地 scripts/verify-dsh-install.mjs,15 项断言);② 验收判据显式包含"非本机布局"(一句 env 注入即可);③ 前端验收别用 fixture 桩掩盖后端不可读(本次就是断言全绿而端点其实是空的)。
  • 🔑 信号识别:注释里出现「本部署事实 / 目前是 / 暂时」⇒ 当场转成待验证项(那是作者自己知道这是假设的痕迹)。

A20|存量烂账(巨量重复 / 历史遗留):选「只增不改 + 库外视图」,不就地重写

  • 实例:档案 82 = 2024 行 / 89.1 KB,切 224 块后 重复标题 22 个、冗余块 190 个(≈85%);但「八、口径提醒」有两个内容不同的变体 ⇒ 不是纯复制,盲目去重会丢信息。
  • 做法(零改写):原文一字未改 → 文末追加「修正(日期)」小节(实测数据 + 视图路径 + 阅读建议)→ 生成去重视图(保留每组信息最全的一份,其余留占位注释)只写库外 .workbuddy/cache/dedupe-view/ → manifest 加 dedupeView 字段、检索命中时提示"优先读视图"。89.1 KB → 59.0 KB,零风险。
  • 判据 → ① 先做**变体检测(同名 ≠ 同内容)**再决定能不能去重;② 写入权留在原文(历史冻结),派生物放库外;③ 根治(拆分)与止血(视图)分开立项,别用一次大改解决两件事。

A21|任何自动判据都要报「分布」;失效就改,别让它假装有区分度

  • 实例:tier 判据原为"被引用次数 ≥8" ⇒ hot 44/89 ≈ 一半(等于没筛);改为「谁在引」四档(hot = 被 L1/L2 现行层引 ≥3 次)⇒ hot 7/89(8%) ✓。⚠️ 我第一版把 L4 台账类也算现行层 ⇒ hot 反升到 60,更糟(台账会顺带列出几乎所有档案号,索引式提及 ≠ 要读)。
  • 判据 → ① 判据上线时必须报分布(各档占比),一眼看出有没有区分度;② 区分"顺带列举"与"真的依赖"(索引/台账/清单类文件的提及不算引用);③ 判据本身要定期体检(老判据会随规模失效)。

A22|替换 / 退役类动作的顺序:先补位,再退役

  • 实例:univer 承担 ① AI 生成 docx/xlsx ② Office 导入导出 ③ .univer 协作预览(独有)⇒ 要弃用必须先把 ① 换成 dsh-office 并验证,再停 univer;「顺序不能倒」。同理 @softspark/dsh-file-preview 的退役必须等平台升级(阶段 4)之后 —— 现在退 = 生产立刻失去预览。
  • 判据 → 动「下线 / 替换 / 摘除」之前先写三行:它现在承担什么(逐项列)→ 每一项换成什么 + 验证过没有 → 换完之前不许停。
  • 与 A16(伴随物清单) 互补:A16 管"要一起改什么",本条管"先做哪一步"。

A23|流程类失效多半不是「忘了」,而是「触发词没命中」

  • 实例(2026-09-14 事故复盘,档案 95):改平台代码 / 铺插件 / 重启 dshs 时,AI 没把自己这次动作分类成"落地一次改造" ⇒ 六阶段流程整条不存在(阶段 0 前置检查、阶段 2 方案确认、阶段 5 归档全缺)。自审原话:「本技能就在本机,我一开始没加载;直到用户追问才加载」。
  • 根因是结构性的:流程只写在按需加载的技能里,而常驻层 CODEBUDDY.md §2 自己就写着「技能的加载由模型判断相关性,不能保证」 ⇒ 没加载 = 没有流程。
  • 判据 → ① 凡「动作前必须生效」的规则,必须写进常驻层(项目根 CODEBUDDY.md / MEMORY.md),技能只放"需要时去拿的方法论";② 常驻层的触发条件要用「动作词」("要改平台代码 / 要铺插件 / 要重启服务"),不要依赖模型自我分类("我要做一次改造"这种判断本身就会失效);③ 自审时区分**"当时成文口径"与"新立口径"(A13)—— 本次 95 的自审引的是过期 R8**(要求"取得确认"),而 R8 已于 09-13 由用户改为"开发环境服务器不必等确认,只需动手前一句话说明"⇒ 两栏都要给,否则违规被高估。

A24|拦截面必须覆盖「真实行为面」——装在工具上的闸门,拦不住正文里的行为

  • 实例(2026-09-15 实测):昨天给「提问闸门」装了 PreToolUse + matcher ^AskUserQuestion$ 的 hook,想治"AI 老让用户确认简单问题"。今天核账:本工作区日志 tool=AskUserQuestion 调用数 = 09-12: 43 / 09-13: 3 / 09-14: 0 / 09-15: 0(UserQuestion/elicitation 的命中全是 --tools 参数与 host capability 字符串,不是调用)⇒ 真实提报用户几乎全在正文里,hook 从装好那天起就 0 命中。
  • 判据 → 设计任何"拦截 / 校验 / 门禁"之前,先用日志或计数证明行为发生在哪一面: ① 统计该面的真实发生率(不是"应该有");② 若闸门装面上限远低于行为面,闸门等于装饰(还制造"已经治好了"的假安全感); ③ 正文类行为(无法被工具 hook 拦)只能靠常驻层的可执行自检动作(详见 CODEBUDDY.md §1 "回话前自检")或 Stop hook 扫最后一条回复。
  • 可复跑的核账命令:grep -c "tool=AskUserQuestion" <工作区宿主日志>(宿主日志在 ~/.workbuddy/logs/<日期>/<工作区名>__*.log,记录 [ToolManager] execute | tool=X)。

A25|本机改完 ≠ 交付 —— 先画出「改动层 → 生效链路」再宣布完成

  • 实例(同类 3 次,2026-09-13/14/15):① 只做到本地打包、没部署 ⇒ 用户「点开看还是和之前一样」;② 平台登录页/运行时只改本机、从没部署到服务器 ⇒ 用户看的是旧页面(原话:「那你看的当然还是旧页面」)。
  • 判据 → 宣布完成前逐项答三句:① 改的是哪一层?② 这一层的生效链路是什么?③ 最后一步走了吗、在「用户可见面」验了吗? 链路清单(缺一步都不算交付)见 dsh-change-workflow **阶段 5 §0「交付门禁」**表格:静态页 = scp(含 CDN 缓存坑)|平台 TS = build + restart|插件 = tgz → 候选池 → 实例启用 → 重启实例|文档库/技能 = scp + 对账|配置 = 改 + reload/restart。
  • 四条"自我安慰",一条都不算交付:本机改完了 · build 通过了 · 本地打包完成 · 已 commit 了。
  • 与 A19(验收要含第二环境)互补:A19 管"在哪个环境验",本条管"链路走没走完";与 U20 / X9 互补:部署本身不必问用户,但必须做。