# 诚实的局限与发布注意 本文件写给**评估这个插件能不能用、以及准备公开它的人**:不粉饰的已知弱点、标定口径的出处,以及发布前必须知道的留痕情况。 使用见 [../README.md](../README.md);术语见 [glossary.md](glossary.md);契约见 [recall-contract.md](recall-contract.md)。 ## 1. 九条诚实的局限 1. **标定常数是单一来源、单一声明的产物**。`SCALE_A` / `SCALE_B` / `WEAK_THRESHOLD` / `STRONG_THRESHOLD` 是在**一份语料、一种文风**上标出来的。**语料明显增长或文风变化后必须重标**。`scripts/calibrate.mjs` **默认只打印建议、绝不自动改写**任何东西;唯一的落地路径是**显式** `--write`(写前备份 + 打印 diff + 锚点缺失即失败关闭)—— 也就是说,重标仍是**人工动作**,不重标就会静默地失准。 2. **图奖励接近二值**。因为用了 `max` 聚合 + 硬上限,`graph` 列的取值实际上几乎只有「0」和「一个接近上限的值」两种。所以**不要用 `graph` 的大小去比较两条记忆**;衡量图传播是否起作用应该看 **`reachable`**(有多少条是靠图到达的),那才是它价值的指标。 3. **注入边界对 `{{…}}` 做了净化,但写入协议可以被绕过**。插件侧的注入文本会过 `sanitizeForPrompt()`;但**手工改 NDJSON 文件**就绕过了 `memory_remember` 的写入协议 —— 库是纯文本、人类可读的,这是它的优点,也是它的**绕过面**。绕过协议写入的坏数据不会被插件拦住。 4. **库里存在少量近似重复**。在导入 `gotchas.md` 语料时,产生了若干「**手写版 + 导入原文版**」并存的记录 —— 同一件事被记了两遍,措辞不同。`memory_prune` 的合并规则是「同 kind 同标题」,**标题不同就合不掉**,所以这类重复会留在库里。 5. **`cov` 是诊断列,不是质量保证**。标签覆盖率只反映「查询词与标签的重合度」,高 `cov` 不等于高相关;它**不门控**任何结果(这是刻意的:上游回退过「低覆盖整批否决」)。 6. **标签空间靠人工稳定复用**。锚点质量依赖「全库稳定复用同一批标签」这个纪律;标签随手造(错别字、临时记号)会让锚点退化。锚点资格过滤只掐掉「覆盖全库的公共标签」这一种噪音,**掐不掉 df=1 的一次性标签**(那需要 idf 式排序,本增量刻意没做,只做了有失败证据的那一半)。 7. **内容量闸门只封顶「贴线」的单内容词元查询(本轮加了例外)**。`match` 由 `rel` + 阈值 + **内容量闸门**共同决定(见 [recall-contract.md](recall-contract.md) §5):查询里「在库内出现过的去重词元数」`qTok < CONTENT_TOKEN_MIN (2)` **且** `rel < GATE_MARGIN (2.0) × STRONG_THRESHOLD` 时,`strong` 降为 `weak`。这是为修「单字/单词查询的 `rel` 虚高」付的代价 —— 单内容词元查询里**只比阈值高一点点**的(噪声 `ok`/`做`/`b` 实测 1.13~1.23 倍)从此最多 `weak`。但**单个高专有词元**(`rel` 远超 2×strong,实测 `adb` 4.35 倍)**不再被封顶**,仍判 `strong` —— 上一版纯按 `qTok` 封顶会把这类真话题一起误杀。方向是**保守**的(宁可不吹 `strong`,也不误杀真话题;代价是 `ci` 这类 1.77 倍的查询仍被封顶),且**不否决**任何行。想完全关掉闸门把配置设 `score.contentTokenMin = 1`,想让例外更宽设 `score.gateMargin`(`0` = 永不封顶)。 8. **`node --test` 不可用**,测试必须逐文件直跑(见 [development.md](development.md))。这意味着 CI 不能照抄常规 Node 测试命令。 9. **`scope` 是调用方声明的字符串,不是引擎鉴定的工作区身份**。`memory_recall` 传了 `scope` 才过滤(只返回该值或 `global`),**默认不过滤**;字符串比较**大小写敏感**(与标签的小写归一不同),且「哪个项目算哪个 scope」由调用方约定(建议 `project:<名>`)。插件**故意不**用会话 cwd 自动推断 scope:那会改变默认行为,且「cwd → 名字」只是自造约定(`docs/recall-contract.md` §8 有源码依据)。旧记录没有 `scope` 字段 ⇒ 一律视为 `global`,所以升级不会让老记忆消失。 ## 2. 标定出处(语料变了必须重标) 四个打分常数出自离线标定,**样本来源是本机真实记忆库**。以下是**第二次落地**时的本机快照(**2026-10-09 实测**,会随使用变化): | 项 | 值 | | --- | --- | | 库内有效记录 | **204 条** | | 正样本 | **408 个**(每条记录的标题 + 前 3 个标签各一次查询,对自身打分) | | 负样本 | **240 个**(12 个固定头部 + 228 个跨域词表组合;确定性,两次运行逐字节一致) | **第二次标定:建议值与落地值已一致**(这一次差异被采纳了,不再是「该做但没做」): | 常数 | 落地值(`src/pure.ts`) | 204 条库上的建议值 | 与旧值的差 | 口径 | | --- | --- | --- | --- | --- | | `SCALE_A` | **0.0199** | 0.0199 | +0.0012 | p50(负样本) | | `SCALE_B` | **0.3666** | 0.3666 | **+0.0246** | p95(正样本) | | `WEAK_THRESHOLD` | **0.0382** | 0.0382 | +0.0019 | p95(负样本) | | `STRONG_THRESHOLD` | **0.1507** | 0.1507 | +0.0031 | p10(正样本) | **显著性判断**(阈值:差异 ≥ 0.005 视为显著):四个量里**只有 `SCALE_B` 单独显著**(+0.0246);其余三个都在 0.005 以内。之所以仍按**整组**落地,是因为它们出自**同一次确定性运行** —— 跨运行混搭常数比整组落地更难解释。四个常数各配了一条**测试样本 rel 落在新旧值之间**的边界失败证据(改回旧值即红),见 `test/scoring.test.mjs`。 **落地路径**:`scripts/calibrate.mjs` **默认仍然只打印、绝不自动改写**;唯一的落地路径是**显式** `--write`(写前备份成 `.bak-<时间戳>`、打印逐行 diff,锚点缺失即失败关闭)。这条「默认不变」的契约由 `test/calibrate.test.mjs` 钉住(含「指定了 `--target` 但不带 `--write` 也一个字节都不写」)。 另有两点口径说明: - **分诊阈值与图系数不由 `calibrate.mjs` 标定**:`noveltyThreshold`(0.5)、`activationThreshold`(0.05)是**门控行为量**不是分数标度;图传播的 `lambda` / `outBudget` / `hubEta` / `decay` 等图系数同理。`GRAPH_BONUS_CAP = 0.018` 仍标注为「**待真实语料标定**」。 - **取证日志里的数字是历史快照,不是当前值**。例如 `redproof/i2.2-calibrate-real-library.txt` 记录的是当天的 **194 条**库与 **388 个**正样本,`redproof/i2.3-calibrate-real-library.txt` 记录的是 **195 条**库与 **390 个**正样本;第一次标定的建议值(0.0188 / 0.3482 / 0.0364 / 0.1478)当时未被采纳。日志是「当时跑出什么」的凭证,**不应**被当成现值来引用;现值请以 `memory_recall` 的 `total` 字段或重新跑 `scripts/calibrate.mjs` 为准。 ## 3. 发布注意:路径已规范化 **本仓的路径已规范化,不再包含机器相关的本机绝对路径。** 规范化只把**机器相关前缀**逐字换掉,命令与输出内容一字未动(取证仍然是取证)。 | 原字样(机器相关前缀) | 规范化写法 | | --- | --- | | 应用私有 home | `$DSH_HOME` | | 安装前缀 | `$PREFIX` | | 应用私有数据根 | `$APP_DATA` | | 本仓所在绝对路径 | `` | | 工作区绝对路径 | `` | | 外部存储挂载点 | `` | - `redproof/run-all.sh` 的 shebang 也从写死的绝对 bash 路径改成 `#!/usr/bin/env bash`。 - 代码里原本写死本机默认 home 的那一处(`src/store.ts` 的 `DEFAULT_HOME`)改成了**可移植**推导:`$HOME/.dsh`(`$HOME` 缺失时退回 `os.homedir()`)。所以换机器会自动跟着 `$HOME` 走,本机行为不变。 - 引擎安装位置(`test/inject.test.mjs` 与 `scripts/i4a-measure.mjs` 里的 `ENGINE_BASE`)改成从 `$PREFIX` 推;`$PREFIX` 缺失或引擎不在那里时,走它们本来就有的「如实降级」分支。 - 为什么保留 `redproof/`:这些日志记录的是「在哪条命令、哪个库状态上跑出这个结果」,是失败证据**可复现性**的一部分。规范化只替掉机器相关前缀,取证内容保持原样。 - **没有发现任何密钥 / 令牌 / 凭据**。`token` 字样的命中全部是标定用的 `nonexistent-token` 负样本串。**也没有**设备型号或系统版本号。 ### 入库面文件里含本机绝对路径的数量 **口径**:把每个文件按下述三个**规范化前的前缀**取**并集**,命中即计数: - 应用私有目录的**两种等价别名**(规范化后写作 `$APP_DATA`):本机是 `/data/user/0` 与 `/data/data` 两条写法; - **外部存储挂载点**(规范化后写作 ``)。 实际统计按各自**带尾斜杠**的串匹配。本文这里把前两条写成不带尾斜杠的前缀、第三条直接用占位符,正是为了不让本文档自己命中(早先版本逐字写下完整前缀,于是本文件永远把自己算成 1 个)。统计范围为**入库面文件**:**已跟踪 ∪ 未跟踪但不被忽略**(`git ls-files --cached --others --exclude-standard`);被忽略的 `.tmp-test/`、`node_modules/`、`*.log` 不进来。 **为什么统计范围不是「只扫已跟踪」(时序陷阱,曾连续复发三次)**:`git ls-files` 只列**已跟踪**文件。失败证据刚生成、还没 `git add` 时它跑守卫是**绿的**;`git add` 之后同一条内容才变红 —— 而**失败证据天生会抄 `AssertionError` 的堆栈**,Node 的堆栈里必然带本仓绝对路径(`at TestContext. (file:///test/x.test.mjs:NNN:CC)`)。于是「生成 → 先跑绿 → 后 `git add` → 推上去才红」这个顺序被连漏三轮;这次就是 `redproof/i12-scope-convention-disable.txt` 第 49 行的堆栈。现在口径改成扫「已跟踪 + 未跟踪但不被忽略」,新文件**当场**就变红,不必等 `git add`。失败证据见 `redproof/i12b-guard-scope-redproof.mjs`(A 未跟踪文件变红、B 已跟踪文件仍变红,两条都可重放且跑完自动还原)。 | 时点 | 三模式并集 | 单模式粗筛(只 grep `/data`) | 只含 `` 前缀、不含 `/data` 的文件 | | --- | --- | --- | --- | | **规范化前**(2026-10-09,含 `lib/` 产物与本轮 `redproof/i7*` 证据) | **60 个文件** | 12 个 | 48 个 | | **规范化后**(同一天,同一命令重测) | **0 个文件** | 0 个 | 0 个 | | **证据入库后回涨**(`redproof/i8-*`,硬编码本机 home 且未脱敏) | **3 个文件** | 3 个 | 0 个 | | **修复后重测**(`redproof/i8-*` 的脚本改成 `$HOME/.dsh` + 逐行脱敏) | **0 个文件** | 0 个 | 0 个 | | **回涨(已 push 后才暴露)**:`redproof/i12-scope-convention-disable.txt` 第 49 行抄了 `AssertionError` 的堆栈,堆栈含本仓绝对路径;**守卫当时只扫已跟踪集**,所以「生成时跑绿、`git add` 后变红」 | **1 个文件** | 1 个 | 0 个 | | **清洗 + 守卫改口径后重测**(扫「已跟踪 + 未跟踪但不被忽略」) | **0 个文件** | 0 个 | 0 个 | 后两行是这次的实例记录:**取证脚本自己也会把机器前缀带进仓库**(脚本里硬编码默认 home、输出里原样打印路径)。修法是两条一起上 —— 默认路径改成可移植推导(`$HOME/.dsh`),输出逐行走 `SANITIZE_RULES` 脱敏;`test/release.test.mjs` 的第 ③ 条断言就是钉这个的(它在这两轮里确实让回涨变红过)。 规范化前的并按目录分布:`redproof/` 53 个、`docs/` 2 个、`lib/` 2 个(`lib/store.js` 与 `lib/types/store.d.ts`,都是 `src/store.ts` 那条默认 home 的产物)、`scripts/i4a-measure.mjs`、`src/store.ts`、`test/inject.test.mjs` 各 1 个。 **必须取三模式并集**:单模式粗筛会严重偏低 —— 规范化前只 grep `/data` 得到 12,而并集是 60(48 个文件只含外部存储前缀)。这正是早先把 57 误报成 12 的原因。三模式都命中过的只有本文件一个(它要写下这三个前缀本身)。 ## 4. 许可文本 本插件采用 **CC BY-NC-SA 4.0**(Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International),与上游 VCPToolBox 保持同源许可。 - 完整条款见 [LICENSE](../LICENSE)(Creative Commons 标准法律文本,未作任何增删改写);来源与署名见 [NOTICE](../NOTICE)。 - **如实说明**:Creative Commons 官方**不建议将 CC 协议用于软件** —— CC 协议不含专利授权条款,且「非商业性使用」在软件场景下含义不够确定。本项目刻意选择 CC BY-NC-SA 4.0 的理由是**与上游同源**,而不是因为它是软件许可的常规选择。