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你看呢
这类不带选项的待定清单,一律按待拍板项写:问题+说明+各候选优缺点+倾向)
This commit is contained in:
1 parent
19101acd65
commit
64dd82073b
21 files changed
+8444
-4523
No files matched your search
@@ -0,0 +1,165 @@
|
||||
# 素材库 · 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** 互补:部署本身**不必问用户**,但**必须做**。
|
||||
Reference in new issue
Block a user