← 返回列表
未验证
把 agent 记忆存成文件,按主题穿透追溯
尚未跑自动兼容性验证,可查看页面内的依赖与入口分析。 · 最近上游提交 2026/9/15 · 已提供中文文档
dsh-shadow: agent 思维/上下文/灵魂的投影记忆树(一切皆文件,一记忆一文件,read_shadow 可穿透)
综合分
30
GitHub 分
30
用户评分
—
★ Stars
0
周下载量
—
安装插件(需先安装 dsh CLI 引擎:npm install -g @deepseek-ai/dsh)
dsh plugin --profile web add mozhuanzuojing/dsh-shadow该插件未发布到 npm,走 GitHub 源安装(pnpm 若拦截 prepare 脚本,按其提示在 pnpm-workspace.yaml 的 allowBuilds 中放行后重跑)
数据截至 2026/9/16(元数据每日更新 · 实装验证按队列轮转,单条结论的验证时间见上方)
用户评分
还没有人投票,来当第一个
订阅周报,不错过优质插件更新
每周一封 · 高评分插件 + 新用户活动
README
dsh-shadow
agent「思维/上下文/灵魂」的投影——每条记忆都是一个文件;read_shadow 可按主题穿透。
谁该用它:想让 agent 跨会话记住「为什么这么做」的人;想给 DSH 加一层可追溯记忆、又不想引入向量库的人。
给 agent 读的入口:本文件给人看;agent 读这三处——AGENTS.md(在本仓库干活时的约定)/ CONTEXT.md(术语表 + 各 mode 的入参与返回)/ adr/(决策与边界,按编号)。
兼容性(验证基线)
| 项 | 值 |
|---|---|
| 验证基线 | DSH 0.1.5-rc.1(在这一版上验证并运行) |
| 声明 | package.json → engines.dsh: ">=0.1.5-rc.1" |
| 更早版本 | 未经验证,不承诺可用 |
这是一句「验证基线」声明,不是强制闸门。 宿主与 pnpm 目前都不读 engines.dsh(对 @deepseek-ai/ 全量编译产物检索 engines:无任何代码读取,仅散文注释提及;dsh plugin 只转发 pnpm,并按「装了什么」同步 bundles 层),所以它拦不住低版本 DSH。能观测到的防线是能力探测——它只报告、不拦截:插件探测自己需要的宿主接口,缺哪个就报哪个:
| 档位 | 缺什么 | 表现 |
|---|---|---|
| 硬依赖 | ctx.on / ctx.inject / ctx.get / fs / tools | 控制台 error,写出缺哪一项、影响什么 |
| 可选依赖 | llm / agents / agentDefaultModel / systemPrompt | 控制台一条 warn,说明降级了哪项能力 |
探测不放在 apply()、也不放在 inject 回调:Cordis 的服务是异步挂载的,apply() 时可能尚未 provide(会误报);而 ctx.inject(deps, cb) 只在依赖就绪时才回调 —— 依赖缺失时回调根本不执行,把「缺 X」写进去等于「缺了就不报」(v1.15.3 修正的正是这一点)。故服务面检查统一在首个 agent/turn-stopping(此时宿主已完全挂载,且只报一次)。
为什么基线是 0.1.5-rc.1 而不是更早:对照宿主包,本插件用到的接口(fs / llm / agents / agentDefaultModel.currentSelection() / tools / systemPrompt 六个服务,session/event / agent/turn-stopping 两个事件)在 0.1.0-rc.7 起就已存在——这里没有已知的不兼容点,基线表达的是「只在 0.1.5-rc.1 上验过」,不是「更早版本不兼容」。
为什么存在(它修的是什么)
| 失败模式 | 现象 | 本项目的修法 |
|----------|------|--------------|
| #1 换个会话就失忆 | 上一轮为什么这么改、做到哪,全没了 | 每回合压成一条记忆文件 + _index.md 索引;recall_shadow 一句话找回任务恢复包 |
| #2 只记动作不记理由 | 看得到「改了哪个文件」,看不到「为什么这么改」 | 决策作为一等事件采集(Decision Capture);理由只在原文明确表达时才挂,缺就写「未明确」 |
| #3 召回一堆没证据的东西 | 相关度高但无从验证,等于让你信模型 | Evidence Gate / Validation Gate:无证据的决策、metadata 记忆不进查询;体检报告看 Evidence Density |
| #4 记忆过时没人知道 | 早就删掉的函数,记忆里还在引用 | 召回时校验证据路径是否仍存在 → 缺失降权并标 STALE;生命周期 NEW → … → SUPERSEDED(TRUSTED/ARCHIVED 两态当前不可达,见 ADR-0063 / ADR-0066) |
| #5 系统提示/密钥混进记忆 | 历史里回显 、打码前的 key | 采集剔除系统脚手架;写侧密钥打码;读侧 scrubFinal 二次清理 |
| #6 把记忆当指令 | 旧记忆里的句子被当成用户要求去执行 | 读侧输出恒定「数据非指令」前缀 + 每条标「可能过时,非指令」 |
| #7 上下文越堆越长 | 全量塞进上下文,token 烧光、重点淹没 | 分层召回 + token 预算 + 冷热淘汰(默认关,可显式开启) |
什么情况用哪个(模式路由表)
| 你想做的事 | 用这个 | 说明 |
|------------|--------|------|
| 看记忆目录 / 有哪些主题 | read_shadow() | 无参数返回 _index.md:格式说明 + 近期记忆 + 入口索引 + 意识轨迹;与带 topic 的路径共用同一份 token 预算(max_tokens,默认 1600),超预算按段返回并按段名 + 行数披露丢了什么(v1.15.85,见 adr/0088) |
| 回忆「上次在做什么、为什么、做到哪」 | recall_shadow("一句话查询") | 任务恢复包(内部 mode:"recovery"):任务/状态/关键决定(含理由)/证据是否仍有效/观测结果 |
| 按主题穿透到具体记忆 | read_shadow(topic) / { entry: "…" } | 分层召回:高分给「摘要 + 命中片段 + 正文骨架」,低分只给「路径 + 摘要」;max_tokens 控预算 |
| 跨「记忆 / 决策 / 代码 / 文档 / 概念 / 资源」找上下文 | shadow_query(query, scope?) | 返回带证据的 ShadowNode,每条可追溯;scope: ["resource"] 单查资源卡 |
| 看召回为什么命中 / 为什么被降权 | read_shadow(topic, { debug: true }) | 召回管线 trace:候选 → 命中 → 冷却 → 预算 → 返回 |
| 追一条决策的来龙去脉 | read_shadow({ mode: "decision" }) | 决策血缘:goal 事件 + 用户拍板,按入口聚合 |
| 追一段连续任务 | read_shadow({ mode: "episode" }) | 按「项目/会话 + 时间间隔」把记忆原子串成 Episode |
| 看任务生命周期(触发/目标/约束/状态) | read_shadow({ mode: "task" }) | 派生视图,不改写记忆 |
| 判断「以前知道的东西现在还能不能用」 | read_shadow({ mode: "context" }) | ContextReference:subject / value / source / status(validated / stale / unknown) |
| 站在当时视角看(不剧透后来) | read_shadow(topic, { observer: true, asOf: "YYYY-MM-DD" }) | 只给「当时可知」,后验内容标 [后验] |
| 要身份 / 价值观 / 品味 / 经验 / 判断 | { identity: true } / { soul: true } / { taste: true } / { experience: true } / { judgment: true } | 灵魂投影四对象 + 主体锚(推进 Identity Continuity 用 mode:"identity-advance") |
| 检索规范 / 文档(免向量) | read_shadow({ mode: "knowledge" }) | 规范树 / 章节检索,结果带节路径引用 |
| 验证某条记忆的证据还在不在 | read_shadow(topic, { verifyEvidence: true }) | Evidence Gateway:fs(默认)/ zg(CLI)可插拔;zg 未装报 unavailable,不静默当成已核实 |
| 体检:召回质量与稳定性 | read_shadow({ mode: "shadow-report" }) / { mode: "query-log" } | Evidence Density / Node 稳定性 / 类型分布 |
另有长程与边界族 mode(agency-、delegation-、adapt-、horizon-、recall-、federation、distortion、real-evidence/real-refer、simulate/candidate/execute、validate/evidence、model-、world-、temporal、reflection、observer-、workspace-、continuity-index、identity-advance、recovery),属 ADR 落地的按需查询,不是日常入口;每个 mode(条数不写在这里 —— 由 test/recall-envelope.test.ts 断言,并要求 CONTEXT.md 覆盖齐全)的语义、入参与返回见 CONTEXT.md 的「mode 参考」表(工具 schema 里的 mode 描述只留常用 mode + 指针,避免每个请求都背上这份清单)。
命名口径(ADR-0050 / ADR-0053):mode 名与参数名以工具 schema + CONTEXT.md 为唯一现行口径;被取代的旧名本文件不登记(映射与理由见 ADR-0050 / ADR-0053),调用旧名会返回「已废止:X → 请用 Y」,不落空进默认召回。
取舍(有意为之):schema 不再携带各族边界语(如「非 Autonomous Agent」「不提升 epistemic/authority」)。不读 CONTEXT.md 的模型会少这层提醒——换来的是每个请求少约 1.2k 字符常驻上下文。要恢复,把 CONTEXT.md 的 mode 参考表接回 mode 描述即可。
谁能调用(用户显式 vs 模型自动)
借 mattpocock/skills 的权限轴:模型能自己调的,不能反过来触发「只该用户要求」的动作。
| 工具 / 动作 | 谁能调用 | 说明 |
|-------------|----------|------|
| read_shadow / recall_shadow / shadow_query | 模型可自动调用 | 纯读:不写工作区、不落盘、不改索引;提示词已接线「缺上下文先查」 |
| read_shadow(..., { debug: true }) / { verifyEvidence: true } / { kg: true } | 模型可自动调用 | 只是多返回 trace / 证据验证 / 图谱邻接,仍不改状态 |
| mode: "shadow-report" / mode: "query-log" 体检 | 用户要求,或定期自查 | 只读、只生成派生报告(rm -rf 可重建) |
| 改 retention / forget / compact(v1.15.85 起默认开)· 开 projectionStore / knowledgeEngine.llmNavigate | 仅用户显式要求 | 会改召回集与索引行为,属有后果动作(改配置 + 重启)。方向已变:三件套现在是「关掉才是显式动作」(唯一判据 core/util.ts 的 onByDefault,见 adr/0088) |
| mode:"toolset"(只读巡检 / 能力预检) | 模型可自动调用 | 只是探测可选 CLI 是否可用,不改任何东西 |
| mode:"toolset" + install:""(安装) | 仅用户显式要求 | 有后果动作:一律先经宿主审批,只有 allowed-once 才执行;装完重探再报结果 |
| writeConsent: true 之后的落盘 | 仅用户显式要求 | 用户没明说「记住」时只累积不落盘(默认 false 照常采集) |
快速开始
给 agent 的粘贴式安装
把下面这段丢给任意能读写本地文件的编码 agent(Claude Code / Codex / 本机 DSH 会话等):
把 D:/project/dsh1/vendor/dsh-shadow 以 link: 方式装进 DSH web profile:
1) profile 的 package.json 加依赖 "dsh-shadow": "link:D:/project/dsh1/vendor/dsh-shadow",bundles 数组加 "dsh-shadow";
2) 在本目录跑 pnpm install(源码是 TypeScript,改过源码先 pnpm run build);
3) 重启 profile,跑 dsh --profile web --dump-config 确认没有 Error:;
4) 新开一个会话做几次工具调用,确认工作区出现 .shadow//-.md、.shadow/_index.md 生成、read_shadow 出现在工具列表;
5) 说一句「回忆一下上次在做什么」,确认 recall_shadow 能返回任务恢复包。
失败或要细节,读 README 的「安装(持久化)」「验证(重启后)」两节。
手动安装与验证
见下文「安装(持久化)」「验证(重启后)」两节;目录位置见「目录位置」。
改代码后先过闸门:npm run verify
任何改源码之后的下一步,唯一要记的命令是这一条(v1.15.38 起,ADR-0077):
npm run verify
= npm run typecheck:tools 工具面类型门
+ npm run typecheck:tests 测试面类型门(v1.15.62:test//.ts 全量,曾报 83 条既存诊断)
+ npm run audit:layers 结构门(v1.15.41:文件级无环 / 纯模块白名单零副作用 / 方向禁令)
+ npm run audit:scripts 脚本扩展名门(v1.15.87:仓内不得有手写 .js / .mjs / .cjs;.ts 与 .py / .ps1 等允许)
+ npm run audit:docs 文档派生字段门(v1.15.66:①三方版本一致 ②verify 每一步都被本块与 AGENTS.md 点名)
+ npm run eval:retrieval:check 评测门完整性(v1.15.42:协议自检 / 基线只含聚合面 / 同源 / 读数齐备)
+ ⚠ 语料根:它要一个带 .shadow 的工作区根 —— 默认由本工具位置往上找(最多三级,取第一个存在的);
+ 一个都没有 ⇒ exit 2,后面 3 步(棘轮 / 插件面类型门 / 全部测试)不会跑 ⇒ 那一次「全绿」是假绿。兜底:SHADOW_EVAL_ROOT=(本机 = D:\project\dsh1)
+ npm run audit:ratchet 分诊棘轮(v1.15.45:线索数只能降不能升;桶消失或新桶即红)
+ npx tsc --noEmit 插件面类型门
+ npm run test:all = npm run build && node tools/run-tests.ts
(行为测试 + 工具自检;串行、每文件一个子进程)
⚠ 检查条数不抄在这里 —— 它由 run-tests 自己打印。
本块曾抄「43 个行为测试 + 5 个工具自检 = 48 项」,
抄完就烂(真值一路涨到 56+),而没有任何东西会报错
它失败就不要往下走(跑评测 / 长耗时验证 / 声称「全绿」之前必须先过它)——
否则一次编译错误会被伪装成一次评测结论。注意它会写 dist/(含 build):测试 import 的是
编译产物,不构建就会测到旧 dist。
为什么要有这条命令(ADR-0077 D2):在此之前「全绿」只能靠人记得逐个跑 node test/.test.ts
—— 于是「便宜且确定性的完整缝合线检查」实际上没有单一入口。补上它的当轮就抓到两处既有失败
(一处时间炸弹测试、一处工具自检),即此前「全绿」的结论当时是错的。
结构门查什么(v1.15.41,T13 前半):① 文件级依赖图无环;② 纯模块白名单零副作用
(core/paths.ts / core/types.ts / core/util.ts / security/scrub.ts,带腐化自检:白名单里的路径不存在也算违规);
③ 方向禁令(core↛query、core↛tools、persistence↛query、query↛tools、任何层↛index.ts、任何层↛agent-presets)。
它刻意不查:层间环 —— 实测存在 {core, evidence, persistence} 层间环,成因是 core/ 是混合脊柱
(paths/types/util 纯,memory/writer-materialize/toolset-exec 有副作用),文件级并无环;
把它写成禁令会让门当场就红(假闸门)。CLI 会打印这条成因与语料口径。单独入口:npm run audit:layers。
评测门查什么(v1.15.42,T14):三档,可用性不同,别看混 ——
| 档 | 命令 | 查什么 | 本部署可用性 |
|---|---|---|---|
| 确定性 | npm run eval:retrieval:determinism | 同一输入两跑功能字段逐字节相同 | ✅ 恒可用 |
| 完整性 | npm run eval:retrieval:check(在 verify 里) | 协议自检 / 基线只含聚合面 / 协议同源 / 门控读数齐备 | ✅ 可用,但要有一个带 .shadow 的工作区根(默认由工具位置往上找,最多三级;一个都没有 ⇒ exit 2) |
| 回归 | npm run eval:retrieval:compare | 与签入基线比,超容差即失败 | ⚠ 只在冻结语料上;本部署的正常结论是「不可比」(退出码 3) |
为什么回归档在本部署恒「不可比」:本仓语料是活的 .shadow 记忆,插件每回合都在写新记忆
(实测两次调用之间 1414 → 1435 条)⇒ 基线里钉的 dataset_sha256 立刻过期。
这不是缺陷,是语料性质:哈希钉死的基线只在「语料冻结」时才有意义。
To truly enable the regression gate, put a corpus snapshot elsewhere and set SHADOW_EVAL_ROOT=, then run --update-baseline / --compare
(Whether to materialize this copy is the user's trade-off; this repo will not copy real memories for you).
A single test can still be run directly: node test/.test.ts (each file prints ALL PASS ✅ itself).
The two audit tools have separate entry points: npm run audit:wiring / npm run audit:drift (for manual triage, not gating).
Make it actively used
Inject a sentence into the agent preset via systemPrompt.context(...) — when context is missing, call read_shadow first before answering (see "Prompt Integration" for details).
Philosophy
It is not a "log of actions", but rather renders the agent's thinking and context into a file tree: .shadow/ is the projection, and _index.md is the index of the projection. Memory is organized around "entry point + time", with thinking/decisions as the body and actions as the background.
What it does
dsh-shadow compresses one turn of the agent into "one memory = one file", supports penetrating recall, and gradually upgrades into a "soul projection system". The core capabilities are grouped by theme below.
Default switches (what happens if you install it and change nothing)
Read-only tools do not write to the workspace; the default values use this table as the sole ledger. Two directions of reading (starting from v1.15.85 "all on by default", the "Default" column matches source-code defaults item by item): "on by default" = turning it off is the explicit action — there is only one criterion, onByDefault in core/util.ts (undefined = on, only an explicit false turns it off; retention / forget / compact share it, see adr/0088); "off by default" = turning it on is the explicit action, and along the permission axis below it is only when the user explicitly requests it (that family is mostly enhancements requiring an external LLM / CLI). summary / queryLog / episodes can each be turned off with one line (episodes.showInIndex: 0, fixed in v1.15.64: previously swallowed by || 8 ⇒ that switch did not exist, see note ①), and collection itself has no master switch (only the semantically different writeConsent, see note ②).
On reading the "Maturity" column (v1.15.34 / D8): this repo does not assign itself stable/beta/experimental levels
(no such convention exists repo-wide; adr/0073:53 also admits "0 ADRs have a 're-evaluation conditions' section"). If you were to invent a set of levels out of thin air,
that would be making the docs stronger than the facts — exactly the kind of lie ADR-0072 just fixed.
So this column is filled with verifiable proxy signals (choose one of three, all with sources):
has open unverified items (list ADRs/TODOs) · no open unverified items · boundary ADR not accepted (proposed/not implemented for now).
It precisely answers the question the "Maturity" column was originally meant to answer: which are "stable but token-hungry", and which are "the interface may still change".
The "Degradation behavior" column is a one-glance overview of ADR-0049 "no silence on missing pieces". Cells marked ⚠️silent mean
"after being turned off or missing a piece, behavior falls back to somewhere, but there is no visible signal whatsoever" — per ADR-0049 these are candidate defects.
T8 was closed in v1.15.64 + v1.15.65 (adr/0084 / adr/0085): 6 items got visible signals
(unified into the "capability degradation ledger" → read-side banner), 1 item was ruled legitimate silence (projectionStore; the class criterion is
"the content the reader gets is byte-for-byte unchanged", see adr/0085 §5 and core/projection-store.ts).
Cells in the table below still marked ⚠️silent are of the same family not yet in T8 (e.g. retention), not already-fixed items.
⚠ But v1.15.85 "all on by default" changed the semantics of this cell: what this cell describes can now only be the state after the user explicitly turns it off,
and per the ruling in adr/0085, "an explicit enabled:false by the user leaves no trace — turning it off is the user's choice, and rendering it as a warning = treating the reader's decision as a fault"
⇒ it is no longer a candidate defect, just a truthful statement of consequences (adr/0088 §A.3-④).
| Capability | Default | Maturity | Degradation behavior (where it falls back to when off / missing) | Promotion / enablement criteria | What happens when on · how to turn on |
|------|------|--------|--------------------------------|------------------|----------------------|
| Collection and persistence | On (no master switch) | No open unverified items (ADR-0074 was verified on real hardware in round 4 of v1.15.40, B3 closed loop) | Write failure → lastFlushError + console.error → top banner on read side (visible) | Repo undefined | Each turn is compressed into one memory file; writeConsent: true changes it to "only persist when the user explicitly says so" (note ②) |
| One-line summary summary | On | No open unverified items | Missing llm / missing route / finish error → streamText returns "" → the file simply lacks > 摘要: ✅visible (T8 item 2, v1.15.65: degradation ledger → banner, including cause and consequence) | Repo undefined | After persistence, a background LLM generates a one- or two-sentence summary; summary.enabled: false turns it off |
| Query observation queryLog | On | No open unverified items | Write failure → observation discarded, read side shows "no records yet" ✅visible (T8 item 4, v1.15.65: recordQueryObservation changed to return boolean → call site leaves a trace) | Repo undefined | Bypass-writes .shadow/query-log/.jsonl; queryLog.enabled: false turns it off |
| Episode recall episodes | On (showInIndex: 0 turns it off, note ①) | No open unverified items | derive throws → index does not list the Episodes section (rendered as the same sentence as "no continuous task fragments yet") ✅visible (T8 item 7, v1.15.65); writing _index.md fails → lastIndexError → read-side banner (v1.15.55) | Repo undefined | _index.md generates a task recall section; aggregation interval gapMinutes defaults to 60 |
| 语义召回 B 档 recall | 关 | 无开放未验证项 | expandTerms → [] → 只用原词跑 A 档 ✅可见(T8 第 3 条,v1.15.65) | 仓库未定义 | 开需 recall = { enabled: true, provider, model } |
| 冷热淘汰 recall.cooldownTurns | 关(0) | 无开放未验证项 | 台账读不到 / 坏件 / 写失败 → 各自留痕 ⇒ 冷却失效可见(T8 第 5 条,v1.15.65。注:v1.15.55 留的 corrupt 标记此前没有任何消费者 = 等价于没留) | 仓库未定义 | 设 cooldownTurns: 5:N 回合内不重复返回同一段 |
| 召回 trace recall.debug | 关 | 无开放未验证项 | 无降级(仅不输出 diag,答案路径不变) | 仓库未定义 | 开需 { debug: true } 或 recall.debug: true |
| 召回降权 recall.deprioritize | 空(不降权) | 无开放未验证项 | 无降级(空配置不降权)。注:启用后降权只在 debug 输出可见 | 仓库未定义 | 路径/入口含这些子串的命中打分 ×0.4(只降权不移除);如 ["references-agents", "_reports"] |
| 记忆遗忘 retention | 开(v1.15.85;retention: { enabled: false } 关) | 有开放未验证项(ADR-0067 / ADR-0068 真机待验) | 显式关掉 → 不做 hotness 加权、registerMeta 直接 return ⇒ _meta.json 不建档;差异不可见 ⚠️静默(v1.15.85 起这是用户的选择 ⇒ 按 adr/0085 不再是候选缺陷,见上注) | 仓库未定义 | 默认即 hotness 加权(注③:stale 不是排除项,它喂生命周期标签);retention = { halfLifeDays: 7 } 调半衰期 |
| GC / 归档 forget | 开(v1.15.85;forget: { enabled: false } 关) | 有开放未验证项(minHits 链随 ADR-0067 待真机验) | 显式关掉 → isForgettable 恒 false、maxActive 失效(无降级) | 仓库未定义 | 默认即把低价值 / 过期记忆移出活跃召回集(文件保留,Forget≠Delete ⇒ 不减磁盘占用);staleDays / minHits / maxActive 可调 |
| Episode 收口归档 compact | 开(v1.15.85;compact: { enabled: false } 关) | 有开放未验证项(ADR-0068 runCompact delta 真机未验) | 显式关掉 → 直接 return,不合并(无降级) | 仓库未定义 | 默认即把结束的 episode 合成 consolidated 文件(原子保留可回放) |
| LLM 推理导航 llmRecall | 关 | 无开放未验证项 | 缺 llm/route/解析不出编号 → [] → 确定性 renderRecovery ✅可见(T8 第 1 条,v1.15.65。修前是最彻底的一条:label:"" 使异常连 log 都没有,且 !llm/!route/finish 出错三条路径从不进 catch) | 仓库未定义 | 开需 llmRecall = { enabled: true, provider, model } |
| Projection Store projectionStore | 关 | 有开放未验证项(ADR-0069 真机端到端;D1 invalidateFor 未接线) | 读失败 / 坏行 → 全量重派生,结果仍正确、只是无缓存 —— 裁定为正当静默(adr/0049:38 行级豁免 + adr/0085 §5 的类判据:读者拿到的内容逐字节不变) | 有:projection-store.ts:5「Node 稳定 + query 稳定 + rebuild 成本明显」 | projectionStore.enabled: true |
| 目录级摘要 abstracts(v1.15.35 / ADR-0075) | 开 | 边界 ADR 已接受但收益未验证(ADR-0075 自陈:召回收益未测 → T9) | 写失败 → 该目录不列入 _index.md(索引少一行 = 内容变了)✅可见(v1.15.65 复查时补:它不属于正当静默那一类,判据同上;showInIndex: 0 的「不列」v1.15.64 才真的生效) | 仓库未定义 | 每日期目录写一份 _abstract.md(L1 + L0);abstracts.enabled: false 关、showInIndex 控制索引里列几个(默认 3) |
| Knowledge Engine knowledgeEngine | 并非「关」——闸门不存在(注④) | 边界 ADR 未接受(adr/0047 已提出;adr/0046 实现计划冻结) | 读路径无条件建树;唯一闸门 llmNavigate(默认关)→ 确定性检索,输出显式标注「LLM 导航未启用/失败」(可见) | 仓库未定义 | mode:"knowledge" 直接用;llmNavigate.enabled 是唯一闸门 |
| 工程知识图谱 kg | 关(注⑤:不是 config 键,是 per-call 参数) | 无开放未验证项(已知局限:MEMORY.md:92 域推导,未解决) | 未传 → 不加图谱块;传入无匹配 → 输出「暂无匹配的组件/域」(无降级) | 仓库未定义 | 按需 read_shadow(topic, { kg: true }) |
| 证据 Provider evidenceProvider | fs | 有开放未验证项(ADR-0059 真机 host.fs;V1 zg 未装未实测) | zg 未装 → unavailable + zg_not_installed;provider 拼错 → unavailable + provider_unknown;逐条 status+reason + 可执行缺件提示(可见) | 仓库未定义 | 换 zg 需已装 CLI;未装报 unavailable,不静默 fallback |
| 索引候选 indexEngine(原表缺此行) | fs | 有开放未验证项(V1:本机 zg/semble 均未装 ⇒ 两条 provider 路径未实测) | fs = 全量扫描(无外部依赖);zg/semble 未装 → unavailable + 缺件提示,绝不冒充候选(可见) | 仓库未定义 | indexEngine.provider = fs(默认)/ zg / semble |
表注(每条都对应一处实现与文档不符,或一处「看起来有开关其实没有」)
⓪ 行数:原表 16 行 ⇒ v1.15.34 补 indexEngine(原缺行)、v1.15.35 补 abstracts(D6 新能力)
⇒ 现 18 行。
① episodes 关不掉 —— ✅ 已在 v1.15.64 修复(adr/0084),本条留档说明修前的形态:
showInIndex: 0 被 core/writer-core.ts 的 || 8 吞掉 ⇒
core/writer-materialize.ts 的 episodeShow > 0 闸门恒真(死分支,即那个开关不存在);
gapMinutes: 0 同样被 || 60 吞掉(writer-core.ts / core/episode.ts / query/reads.ts 三处各写一遍)
⇒ Math.max(0, …) 永不生效。根因是用 || 取默认值把「显式 0」与「未传」混为一谈。
现统一走 core/util.ts:numOr(判据只此一处),并把默认值的落点收成 deriveEpisodes 一处。
② 采集没有总开关:writeConsent 的语义是「改成仅明说才落盘」,不是「关掉采集」。
③ retention 的「stale 默认排除」在代码里没有对应实现:staleDays/stale 在
retention.enabled 判断之外计算(query/query.ts 的 const staleDays = … 与 let stale = ageDaysOf(mm.rel) >= staleDays; 两行),关闭 retention 也照标 stale;
而 stale 只喂生命周期标签(observer/arbitrate.ts:15,28,39、core/lifecycle.ts:33),不做排除。
唯一带「排除」语义的是 retention.enabled 时对 rec.status !== "active" 的 continue(query/query.ts 的 if (rec && rec.status && rec.status !== "active" && !rec.pinned) continue;)。
原表把它写在「默认」列,属串列。
④ knowledgeEngine 的闸门不存在(v1.15.34 实测校正的硬缺陷):ShadowConfig.knowledgeEngine.enabled
生产零读取(全仓唯一读取是 core/writer.ts 读 .llmNavigate);query/reads.ts
无条件建树;且 createKnowledgeEngine 原本收一个 config 形参却从不使用它
(已删死形参)。⇒ 原表写「默认 关 / enabled: true 启用」描述的是一处不存在的开关。
本 mode 的唯一闸门是 llmNavigate.enabled。
⑤ kg 不是 config 键:它不在 ShadowConfig 里,只是 per-call 参数(query/query.ts),
原表却把它排在「默认」列里,与真正的 config 默认值混用同一列。
采集与落盘
- 每回合采集四类:入口点(真实改/读的组件,fs/observed,客观锚)、决策/意向(goal/changed)、动作(tools/result,背景)、交互与思维落点(session/event 的用户消息与 agent 结论,尽力而为)。回合结束(agent/turn-stopping)压成一条记忆:/.shadow//-.md。
- 完整线索头(核心):每条记忆文件顶部带 > 完整线索 头,把「背景/材料」(本回合改/读过的路径 + 用户消息里引用的背景/材料,两路合并去重)+「用户提示/决策」(被分类为用户提醒/拍板的用户消息,标 decision/reminder)+「用户要点」(全部用户消息兜底,防漏记)+「概况」(动作/用户消息/决策计数)结构化列出——让一条记忆一眼能还原完整线索链。
- 说明文档 + 索引 + 意识轨迹:.shadow/_index.md 讲清格式、列出近期记忆、给出「入口/主题 → 记忆文件」索引,并生成按时间的意识轨迹(可反推用户/自己的思考方向)。
- 入口语义切分 + 落盘兜底:入口优先取语义路径(读/改文件路径的域,如 acshModel/vendor/dsh-shadow),纯工具名(pwsh/edit)不作 entry(防跨事务串线、命中错主题);session/flush 收口时落盘全部 pending + pending 超 60 条异步落盘;落盘失败改 error 级 + read_shadow 显示「⚠ 数据不可达 / 请确认 shadowRoot 可写」(区分"数据不可达"与"召回不足")。
- 一句话总结(增强):每一回合落盘后,detach 一个后台任务用 llm.stream 生成一两句中文摘要回填到记忆文件头(> 摘要:…)。纯聊天/无工具回合也能据此沉淀成可读记忆;失败/超时静默降级,不影响正文。默认路由取 agentDefaultModel.currentSelection();可用 rawConfig.summary 配置 { enabled, provider, model, maxTokens, timeoutMs },enabled: false 关闭。
- 会话归属:采集按各 session 自己的 agent 归属(session/event 用 agents.get(session.id)、fs/observed 优先 actor.agent),支持多会话/子 agent,不再一律挂到全局 initiator。
- 系统提示不泄漏:采集时先剔除宿主注入的 (workspace 指令 / runtime context / skill 目录 / 会话上下文)等系统级脚手架标签块(成对开闭 + 孤立残留变体),并识别无标签的裸脚手架块(以已知系统提示完整措辞开头);纯系统消息整体跳过。这些是「系统提示 / 运行时上下文」,不是 agent 的思维/决策线索,不应写入记忆。
受保护契约面(Protected Contract Registry · adr/0086)
契约 ≠ API 清单。 一条契约 = Surface + Semantics + Stability + Allowed Drift + Verification,
十条字段缺一不得入册(adr/0086 §2)。本节是登记册本身:每条契约 10 个字段全填
(表 A 前五字段 / 表 B 后五字段,id 对齐)。判据与理由在 adr/0086,清单在这里(该 ADR §5 的分工)。
⚠ 本节不写「共 N 条」 —— 条数=下表行数。v1.15.76 之前,条数被手写在文档的许多行里;而
写下「有 N 处写了 8」这句话本身又添了几处(自指:这个数在写下的瞬间就已经不对)⇒
按 AGENTS.md「最好只给规则与命令、不写数」,条数一律删除;要知道几条就数表。
⚠ 「字段已填」不等于「都已设防」 —— verification 一列如实写出哪些面根本没人守(见本节末的完成度表)。
稳定性用内部三档(本仓不自造 stable/beta/experimental 等级 —— D8 已判:凭空造等级就是让文档比事实强)。
判据落在「违反时的后果」上,因为后果可从代码核对,成熟度不能:
| 档 | 判据 | 违反的后果 |
|---|---|---|
| hard | 使用者已依赖;改了会让已记录的东西读不出来或调用失败 | 必须走弃用流程 |
| soft | 会改变可观察行为,但不破坏数据(派生件可整份重建 / 文本可改) | 需写 CHANGELOG;不需弃用窗口 |
| experimental | 无承诺 | 可随时改,但必须就地可见地标注(ADR-0049 同族) |
表 A:结构与语义(id / surface / owner / semantic meaning / stability)
| id | surface(已实测,命令:node ../.docs/fix/2026-09-12/t15-counts-verify.ts) | owner(一级模块) | semantic meaning(对使用者意味着什么) | stability |
|---|---|---|---|---|
| tool-name-v1 | public-api · 工具名 3:read_shadow / recall_shadow / shadow_query | index.ts(三处 name:) | 工具名是调用契约:名字一改,已写进记忆、文档与别的会话里的调用全部失效 | hard |
| tool-schema-v1 | 工具 schema · 参数名 132(read_shadow 127 / recall_shadow 2 / shadow_query 3) | index.ts | 参数名与枚举是调用方写出来的字面量;mode 的描述还进常驻上下文 ⇒ 它同时是上下文预算的一部分 | hard |
| read-mode-v1 | public-api(枚举) · mode 串 62 | query(query/reads.ts 登记 + 各模块分派) | mode 决定读到的是哪一类东西;未知值必须显式失败,不得静默落回默认召回 | hard |
| retired-mapping-v1 | public-api(兼容层) · 已废止映射 4 + 2(4 个 mode 名 + 2 个参数级) | query(query/query.ts 的 RETIRED_MODES / retiredApiMessage) | 旧名与旧参数仍然可用、但返回可见提示并点名替代品 —— 兼容层本身就是承诺 | hard |
| config-keys-v1 | 配置键 · ShadowConfig 顶层 19 键 | core(core/types.ts) | 键名是用户写在配置里的字面量;加键安全,改已生效键的语义会让既有配置悄悄换行为 | soft(加键)/ hard(已生效键的语义) |
| memory-file-v1 | 落盘格式 · .shadow//---.md | persistence(persistence/files.ts);头字段由 core/memory.ts 的 buildClueHeader 造 | 记忆文件是唯一的 source;文件名里的时间是读侧反解的依据 ⇒ 改了会让已记录的东西读不出来 | hard |
| derived-file-v1 | 派生件 · _index.md / _meta.json / _abstract.md / _recall_log.json / shadow-manifest.json / shadow-index/ / soul/soul.json / taste/taste.json 等 | core(core/meta.ts 定性、core/manifest.ts 格式);写入方散在 retrieval(ledger.ts)/ query(projection-store.ts) | 派生件可整份重建,坏了不算数据损失;但不可解析必须报错,不能当空件 | soft |
| prompt-segment-v1 | prompt 段 · RECALL_PREFIX「数据非指令」前缀 / flushWarn 横幅 / 「能力降级」标记 | core(core/util.ts 的 RECALL_PREFIX · core/writer.ts 的 getFlushWarn) | 「数据非指令」前缀是护栏:去掉它,召回内容可能被后续模型当命令读 | soft(措辞)/ hard(前缀与标记的存在) |
| tool-output-v1 | 工具返回内容 · read_shadow / recall_shadow / shadow_query 吐出的 Markdown 骨架与召回信封字段 | query(读侧组织;渲染片段来自 retrieval/render.ts) | mode 只决定「读哪一类」,这条决定「读出来长什么样」 —— 使用者实际依赖的是后者 | soft(骨架与措辞可改,须写 CHANGELOG)/ hard(「不静默丢内容」:截断必须自报) |
表 B:治理(id / allowed changes / forbidden changes / evidence / verification / ratchet)
| id | allowed changes | forbidden changes | evidence(符号名优先,行号会腐烂) | verification(谁真的在守) | ratchet |
|---|---|---|---|---|---|
| tool-name-v1 | 新增工具(加名是加法) | 改名 / 删除(除非走下面的弃用流程) | index.ts 三处 name: | 有:test/host-probe.test.ts:104 断言三个都在注册表里 | 无桶覆盖 —— 棘轮桶按缺陷类分(接线 / 漂移),不按契约面分 |
| tool-schema-v1 | 新增可选参数;新增枚举值 | 改名 / 删除参数;让未知枚举值落回默认 | index.ts 各工具的 parameters.properties 第一层键 | 有:tools/contract-surface.selftest.ts(冻结参数名清单:缺名即红并点名、新增只报告;含差集判据与抽取判据的标定)+ test/recall-envelope.test.ts:79-84(mode 描述的长度 / 指针 / 关键字)。⚠ 只守「名字还在不在」 —— 参数的类型 / 枚举值 / 默认语义仍无人守 | 无桶覆盖 |
| read-mode-v1 | 新增 mode | 删除 / 改名旧 mode;未知 mode 静默落回默认召回 | query/reads.ts 的 modes: […] + 各模块 MODES / if | 强:test/recall-envelope.test.ts:96(断言恰为 62)+ :103(CONTEXT.md 的表必须覆盖全部 62) | :103 本身就是棘轮:新增 mode 不写进 CONTEXT.md 就红 |
| retired-mapping-v1 | 追加映射 | 移除映射;让旧名 / 旧参数静默落空 | query/query.ts 的 RETIRED_MODES / retiredApiMessage | 强:test/recall-envelope.test.ts:201-223(含参数级 verify:true / args.recall,且带正控:正名不得被拒) | 无桶覆盖,但每条废止配一个断言 —— 等价于逐条棘轮 |
| config-keys-v1 | 加键、加可选子键 | 改已生效键的默认语义(adr/0084:显式 0 ≠ 未传);删键 | core/types.ts 的 ShadowConfig | 有:tools/contract-surface.selftest.ts(冻结 ShadowConfig 顶层键清单:缺键即红并点名、新增只报告;键由 core/types.ts 按大括号深度抽,避开嵌套键)+ 各键在 test/index-engine.test.ts / projection-store.test.ts / toolset.test.ts 等里被真实使用 | 无桶覆盖 |
| memory-file-v1 | 加前置头字段(buildClueHeader);旧文件必须继续可解析 | 改文件名的时间格式;删字段 | persistence/files.ts 的 memoryFileName / timeFromName;core/memory.ts 的 buildClueHeader | 强:test/memory-time-single-source.test.ts:144(往返:写侧造名 → 读侧反解)+ :93(反例正控:修前形态反解不到)+ :111(磁盘路径的 time 必须等于反解值) | 无桶覆盖 |
| derived-file-v1 | 改格式(可整份重建,ADR-0003) | 把派生件当 source 读;让「坏件」与「空件」不可区分(ADR-0049) | core/meta.ts(三件派生件同属可重建);core/manifest.ts | 强:test/manifest.test.ts:17-27(形状 + 读回 + 无 manifest 给提示);test/t8-silent-degradation.test.ts(坏件 / 读不到 / 写失败各自留痕) | 无桶覆盖 |
| prompt-segment-v1 | 改措辞、加说明 | 去掉「数据非指令」前缀;把降级标记改成不可见 | core/util.ts 的 RECALL_PREFIX;core/writer.ts 的 getFlushWarn | 强:test/recall-attribution.test.ts:440(startsWith 逐字断言)+ :478(retention 下也要有)+ :1056(无匹配也要有) | 无桶覆盖 |
| tool-output-v1 | 改措辞;加信封字段;加新段落 | 截断不报(信封消失);把「坏件」与「空件」混同;接线任何优化时把被丢掉的内容静默吞掉 | query/reads.ts 的信封构造 + core/util.ts 的 RECALL_PREFIX | 强:test/recall-envelope.test.ts(逐字断言信封四要素 > 未返回的命中: / limit=N 上限 M 条 / > 下一步: / > 未返回示例:)+ test/recall-attribution.test.ts:440(前缀) | 无桶覆盖 |
最小弃用流程(用户 T15 判据 ②,adr/0086 §4):① 旧名至少保留一个版本 →
② 调用旧名返回可见提示且点名替代品(没有替代品就明说「无替代」,不得留空)→
③ CHANGELOG 写迁移说明 → ④ 移除前 CHANGELOG 里要有跨度 ≥ 1 个版本的弃用记录
(本仓只有一个使用者,硬套 major/minor 三段式是形式大于实质)→
⑤ 未知枚举不得落回默认(未知 mode / provider / 版本一律显式失败或 unavailable)。
先例(为什么这条政策是必要的):adr/0050(v1.13.0)曾把旧 mode 名直接废止、无任何弃用窗口,
代价全靠 CHANGELOG 的可读性承担 —— 本政策就是为不再重犯而写。
完成度(「字段已填」≠「都已设防」):
| 设防状态 | 契约 | 含义 |
|---|---|---|
| 有强门 | tool-name-v1 · read-mode-v1 · retired-mapping-v1 · memory-file-v1 · derived-file-v1 · prompt-segment-v1 | 有具体断言在守,改了会红(多条还带正控) |
| 有门(只守名字面,v1.15.83 补) | tool-schema-v1 · config-keys-v1 | tools/contract-surface.selftest.ts 冻结清单:缺名即红并点名、新增只报告。⚠ 覆盖面就这么大 —— tool-schema-v1 只守参数名(类型 / 枚举值 / 默认语义不守);config-keys-v1 只守顶层键名(子键不守,已生效键的默认语义归 adr/0084) |
| 8 条全无棘轮桶 | —— | 棘轮桶按缺陷类分(接线 / 漂移),不按契约面分 ⇒ 契约面的守卫方式是 verification,不是 ratchet。这是两类工具的分工,不是缺口 —— 不要为凑字段而新造桶(本仓已因「为凑形状而造东西」清理过一批) |
模块归属表(| Module | Owns | Reads | Writes | Must not own |)
这张表是「生成」的,不是手写的。 一级模块 27 个(= 26 个目录 + index.ts)× 5 列 = 135 个格子,手写必然腐烂,
而它的用途是「暴露「谁开始越权」」—— 本仓的「越权」已有一份可执行判据:
tools/audit-layers.lib.ts 的 DIRECTION_RULES(4 条禁向)· FORBIDDEN_TARGETS_EVERYWHERE(2 个)· PURE_MODULES(5 个文件)。
node ../.docs/fix/2026-09-12/t15-module-ownership.ts # 输出 27 行(批数也由它打印,别在别处手写)
五个字段里只有两个能从代码机械推出,这里如实分开:
| 字段 | 可机械推? | 来源 |
|---|---|---|
| Reads | 可以 | 真实 import 出边(沿用它自己的 import 图,不是手抄) |
| Must not own | 可以 | DIRECTION_RULES / FORBIDDEN_TARGETS_EVERYWHERE —— 本来就有门在守(npm run audit:layers) |
| Owns | 不可以(语义) | 只对 ADR 已定过的层给结论,其余标未核、不编一句 |
| Writes | 不可以(副作用) | 已登记在 derived-file-v1 / memory-file-v1 两条契约里(判据收一处,不再列第二份派生件清单) |
由 ADR 定过、可直接引用的 Owns 结论:core 是脊柱(不是「纯函数层」:43 个文件里只有 4 个是 0 import,
且它 import evidence/persistence/security 与 node:fs)· persistence = 写侧 · query = 读侧 ·
security/scrub.ts = 纯模块 · (root)(index.ts)= Cordis 适配器,任何层不得 import 它。
依据:tools/audit-layers.lib.ts 文件头(T13 实测结论)。
切片 3(D1/D2/D3)已判定(v1.15.74,adr/0086 §8.9):
D1 ChangeSet ⇒ 保留(它是 tools/audit-wiring.selftest.ts 的真仓库已知答案,删它会把一条检测器变弱 ——
⚠ 这一条不能机械判:「有没有验证价值」是主观的,且恰好是唯一决定性的那条)·
D2 四类漂移(名称/结构/语义/行为)各自对应登记册的 allowed/forbidden/stability 列,不需要新造表 ·
D3 登记册里的契约全是细粒度面、无一条是内部大对象 ⇒ 对内部大对象的重构是允许的 breaking internal refactor。
仍未做:没有为 D2 建「漂移类」的可执行检测(本轮只给分类的判据落点)。
下一步(泳道 adr/0083 §14.1):T15 与 D1/D2/D3 已判完 ⇒ 下一项是 A段 ——
它是哪 6 条见 adr/0086 §8.10;其中 #3/#4/#5 曾卡在同一个登记册缺口上(§8.11),
v1.15.77 已补齐该契约并入册 tool-output-v1 ⇒ 三条随之得判(§8.12):
三条都允许接线(属 tool-output-v1 的 soft 半边,须写 CHANGELOG),
其中 #4/#5 折叠掉的内容必须由信封披露(否则违反该契约的 hard 半边)。
#6 归 T7。#1/#2 判为不涉及兼容性(删或补棘轮 / 收敛成一份),尚未动手。
没有生产消费者:⚠ 这句说的是这份登记册本身(没有代码读它)—— 不是说登记册里的面没人用
(config-keys-v1 的键就被消费)。说清楚免得它变成本仓清理过的那类「写好了但从不执行」的东西。
读取(read_shadow,可穿透)
- 无参数返回 _index.md(目录);带 topic/entry 按主题穿透到具体记忆文件。穿透按分层召回:按「入口/主题标签 → 路径 → 正文 + 时间衰减」打分排序,在 token 预算内按深度返回——高分记忆给「摘要 + 命中片段 + 正文骨架」,低分只给「路径 + 摘要」;max_tokens 控制预算(默认 1600)。无参读索引也走同一份预算(v1.15.85):超预算时按 ## 段整段装、不腰斩行内,并按段名 + 行数披露「未返回的段 / 部分返回的段」+ 三条下一步(穿透 / 提高 max_tokens / 直接读文件);装得下则逐字原样、零多余文字。⛔ 由来是实测:本机 .shadow/_index.md = 2 251 348 字节 / 24 639 行(8310 条记忆 / 7 个日期目录),而此前无参路径整篇原样返回(adr/0088)。借鉴 OpenViking 的 L0/L1/L2 分层思想,但不引入向量库(见 ADR-0001)。
- 冷热淘汰(默认关,显式开启):rawConfig.recall.cooldownTurns = 5 时,.shadow/_recall_log.json 记录「带内容」发过的路径,N 回合内不重复返回;纯 URI 不带内容则不冷却。写失败降级为「不去重」,且读侧横幅披露(T8 第 5 条,v1.15.65:台账读不到 / 坏件 / 写失败各自留痕)。
- 语义召回(B 档,默认关):read_shadow(topic) 默认走加权关键词召回(A 档,无外部依赖)。要更接近语义,配置 rawConfig.recall = { enabled, provider, model, maxTokens, timeoutMs }——enabled: true 且给了 provider/model 时,先用 llm.stream 扩展几个相关检索词,再打分召回;失败/未配置时退回 A 档并在读侧横幅披露(T8 第 3 条,v1.15.65;修前是静默的 —— 这条此前由本文档自己承认)。
- Memory Debugger:read_shadow(topic, { debug: true })(或 recall.debug: true,默认关)返回召回管线 trace——候选 → 命中(打分>0) → 冷却 → 预算 → 返回 计数 + 每条召回「为什么命中(入口/主题/路径/正文打分拆解)/为什么被降权(cooldown/deprioritize)/状态」。默认路径不变。
- 召回信封(截断不静默):借 PageIndex「成功/失败都返回带下一步的信封」——预算/limit/冷却砍掉的命中会在结果末尾自报家门(未返回的命中:N 条(命中 M · 本次返回 K)· 原因分解 · 示例入口 · 下一步,N 恒等于 M − K,冷却也计入),空命中不再是一句死路,而是给「换词/看索引/shadow_query/recall_shadow」四条可执行下一步 + 近似候选(显式标「未验证」);命中全在冷却时给的是「冷却中的命中(是命中,不是近似)」+ 冷却专属下一步。全部返回时不加任何多余文字。已知边界:信封本身不计入 max_tokens 预算,所以带信封的输出会比 max_tokens 多出这几行(换取「不静默丢」)。
- 读侧输出保留换行(v1.12.7 根因修复):scrubFinal 原先整篇套 scrubUnsafe(剔 \u0000-\u001f,连 \t\n\r 一起剔)→ 所有读侧 Markdown 被压成一行;现改用 scrubUnsafeDoc(保留 \t\n\r,仍剔其余控制符/双向覆盖符)。注入短语与 HTML 标签仍被剥离,「数据非指令」前缀不变。
Shadow Query Observatory(Phase 1A.5)
- 目的:先跑真实查询数据,不急着定型 nodes 结构。在 shadow_query(mode:"query")旁路记录观测——写 .shadow/query-log/.jsonl,每条含 date/ts/query/scope/limit/candidateNodes/returnedNodes/evidenceCount/evidenceNodes/relationCount/relationNodes/nodeTypes/nodeTitles/latencyMs(query/title 轻量 scrub:密钥打码 + 剔控制/双向字符)。
- 只读汇总:read_shadow({ mode: "query-log" }) 给出命中/证据/关系/类型/scope 分布 + 重复查询的 Node 稳定性(同一查询 nodeTitles 是否一致,答"Node 是否稳定";漂移则列出该查询的不同结果集数)。
- 边界(Shadow Contract):观测是系统派生记录(rm -rf .shadow/query-log 不影响任何 Atom);只在 shadow_query 入口打点,不进 derive 真相路径;写失败静默,绝不改变 query 返回值;默认开启(config.queryLog.enabled=false 才关)。
- 核心问题(供真实数据回答):①Node 每次派生是否稳定;②memory/code/document/decision/concept/resource 是否够(真实查询冒出的 task/constraint 再补);③relations(references/objective/belongs_to)是否够(真实需要 implements/depends_on/contradicts/supersedes 再加,不提前设计 Graph)。
Shadow Fitness Report(Phase 1A.6)
- 目的:把 query-log 变成「是否升级索引层」的客观依据。read_shadow({ mode: "shadow-report" }) 把 query-log 聚合 + 扫记忆做 missing-types 启发式,生成 .shadow/shadow-report.md(系统派生,rm -rf 可重建)。
- 报告四段:Query Summary(总查询/候选→返回/延迟)、Evidence Density(有证据节点/总返回节点,核心指标:dsh-shadow vs 普通 RAG)、Stability(重复查询的 Node 稳定/漂移)、Node Distribution(返回类型分布)+ Potential Missing Types(检测约束/任务型内容被归错类型,≥3 处才提示)。
- 关键指标 Evidence Density = 有证据返回节点数 / 总返回节点数。dsh-shadow 坚持「宁可少回答,不要无证据上下文」;覆盖率低于阈值(默认 90%)会标为需关注。
- 边界:只诊断、不增强;判定是启发式观察(best-effort、无 LLM、不下结论),标注依据;缺失类型只在真实数据反复需要时才采纳(不理论驱动、不提前补 task/constraint)。
Evidence Lineage Layer(v1.8.0,ADR-0044/0045/0046)
- 目标:让每个高价值认知单元都能回答「它为什么存在、来自哪里」——提高可信度与可审计性(不是搜索/知识)。
- AtomLineage:{ source(从哪产生), createdBy(user|agent|tool), evidence(AtomEvidenceRef[]),createdAt }。source ≠ evidence(source=产生处;evidence=支撑材料)。AtomEvidenceRef{type,locator,fragment} 为 zg(文件+行号)/PageIndex(文档+页)/Git(commit+diff) 预留统一抽象(与 Gateway 的 EvidenceRef{path} 区分,见 ADR-0050)。
- AtomKind(memory 二级属性,非新 type):experience | metadata | session | task | artifact;kind != metadata 才进入默认认知查询。
- Validation Gate:validateAtomProjection——memory+kind∈{metadata,session} / decision 无 evidence / resource 无 source → 不进 shadow_query context(Atom/卡片保留;决策发生过 ≠ 可靠;收进库 ≠ 有出处)。resource 实际由解析层先挡(parseResourceCard 无 source 就不产出卡片),validator 是同一道门的兜底。
- Evidence 是事件溯源:产生决策那一刻记录产生环境(buildClueHeader 已把当回合 fs/observed + 用户引用材料写进同一原子),读侧据此派生 lineage.evidence;绝不 LLM 补写/推断。
- 真实数据验证(OpenAPI-Gateway 52 原子):52 → 14 个可证明节点,排除 33 metadata memory + 5 无证据 decision。
- 边界:不做 nodes.jsonl / Projection Store / zg / PageIndex / Graph 关系扩展 / 自动经验总结。
记忆生命周期与遗忘
- 记忆遗忘(retention,v1.15.85 起默认开):默认即开(写 rawConfig.retention = { enabled: false } 关;{ halfLifeDays: 7 } 调半衰期);开时 .shadow/_meta.json 记录每条记忆的 created/hits/status/confidence/pinned;召回用 hotness(命中数 × 半衰期衰减)加权(替换旧的「21 天归零」线性衰减),并把 status: stale/superseded/archived 的记忆默认排除。这是「记忆+遗忘=高效」的落地(借鉴 MemoryBank 衰减 —— Ebbinghaus 遗忘曲线按回忆时间与频率衰减,arXiv:2305.10250 / A-MEM 动态合并 / MemGPT archival)。两处勘误:① (ADR-0065) 本行曾把 hotness 标成「OpenViking 式」——标错了,在 OpenViking 官方 README 与 Context Layers / Retrieval 两份文档里 decay/hotness/half-life/reinforce/recency 全部 0 命中,真实出处是同一句里本来就引了的 MemoryBank;② (ADR-0066 / 待办 D4) 本行曾写「pinned 永存」——该状态不可达:生产只写 pinned: false,pinned: true 全仓零处,故「永存」这条路径当前走不到。真正的保留语义由 status 与 hits 派生(见下行)。
- 记忆生命周期(deriveLifecycle):从 _meta.json 信号派生状态机——NEW → OBSERVED → VERIFIED → TRUSTED → STALE/DECAYING → SUPERSEDED。触发信号:独立 session 确认(confirmedBy)、命中次数、新鲜度、冲突。召回 provenance + debug 暴露 生命周期 。可达性(ADR-0063 分诊 / 待办 D4 已决策为「纠正文档」):状态机里 TRUSTED(经 pinned)与 ARCHIVED 两态当前不可达 —— pinned: true 与 status: "archived" 在生产中都没有写入者(生产只写 pinned: false)。不补写入口:那会把外部权威状态落进可重建的 _meta.json,与 ADR-0003 冲突;若将来确实需要「人工钉住/归档」,须先起 ADR 论证状态落在 source 层。另一处口径不一致:lifecycleOf 的两条最前置判断读的是写侧 rec.status/rec.pinned,而 MEMORY.md 声明的口径是「纯按信号派生、不做写侧硬状态迁移」(已在 MEMORY.md 就地加勘误)。
- 冲突检测:召回时校验每条记忆的证据路径在当前工作区是否存在 → 缺失即降权 + 标记 stale,provenance 暴露 (⚠证据缺N) + 生命周期 STALE("capture handler 已不存在"类过时)。证据存在则无冲突;无法判定时视为存在(避免误伤非代码路径)。
证据链与 Memory≠Evidence
- 证据链(provenance):每条记忆文件线索头自带 > 证据链:来源(种类)·日期·证据(路径);read_shadow 召回每条紧跟一行可解释 provenance——来源·日期·状态(active/stale)·命中次数·置信·证据路径。置信度从可验证信号派生(命中次数、状态、新鲜度),非 LLM 玄数,不含虚构 commit/来源。让记忆从「我记得」升级为「我知道它为什么值得参考」。
- Experience 全构建 + Memory≠Evidence:Experience 补 Outcome(证据验证派生 evidence_live/stale/superseded)与 Reflection(无修正/证据缺失/已迭代);读侧每条召回做证据裁决——证据路径存在性 + 同入口更新记忆 → fresh/stale/superseded,暴露 裁决/结果/反思,superseded 降权、置信联动。这是 Memory ≠ Evidence 的落点(ADR-0002):记忆带 provenance/判断,证据验证可插拔(当前=工作区 fs,后续=zvec-grep,Shadow 只消费不重造检索)。
- Evidence Gateway:EvidenceProvider { discover()/verify() } 抽象 + EvidenceResult{ status, source, matches, confidence, freshness, provenance }。Shadow 只问 verifyEvidence(GatewayEvidenceRef),底层是 fs(默认)/ zg(CLI)/ git/IDE… 可插拔。zg 是检索层不是裁决层(Discovery/Ranking/Verification 在 Provider,Arbitration 留在 Shadow Core);zg 未装 → unavailable,绝不静默 fallback 成 verified。read_shadow(topic,{verifyEvidence:true}) 暴露验证。
灵魂投影系统
- Soul Kernel:read_shadow({ soul: true }) 返回 curated 公理层(身份/价值观/原则/品味/边界,.shadow/soul/soul.json)——这是"为什么我是我"的稳定锚,非事件流、按需查询;systemPrompt.context 接线提示取舍可查灵魂。
- Experience:read_shadow(topic, { experience: true }) 从完整线索头派生结构化工程经验(情境/问题/决策/实现/证据/结果/教训/项目/目标)——把一段开发经历投影成可复用的 Experience #N 对象,而非零散行。
- 判断 + 品味:read_shadow(topic, { judgment: true }) 从记忆派生「面对情境 → 我判断/选择决策」;read_shadow({ taste: true }) 读 curated 偏好(灵魂 taste + .shadow/taste/taste.json)。灵魂四对象(Soul/Experience/Judgment/Taste)+ Memory 五层全部就位。
- 观察者 / 观察窗口:read_shadow(topic, { observer: true, asOf })——asOf 时间锚定只召回窗口内记忆,observer 只呈现「当时可知」(情境/问题/决策),把 outcome/lesson/verdict 等「后来才知」标为 [后验]。这是从"端全局答案的 Oracle"升级为"模拟一个拥有这些长期结构的人、只站在当前时刻会怎么想"的 Observer。
- 投影 + 观察者透镜:read_shadow(topic, { project: true }) 用 Soul-as-Observer 透镜(soul.observer.what_matters/what_to_ignore)把全局模型投影成 LocalContext(relevant 原则/经验/偏好 + current_state + uncertainty + excluded)。与 retrieval 的本质区别:retrieval 返回"相关排名",projection 返回"带取舍的局部上下文"——excluded 字段就是"为体验而限制视角、故意不看的部分"的工程化身。
- 观察者内核 / RealityProjection / 判断:Observer 是根(不是 Memory)。read_shadow({identity:true}) 返回长期 Identity 主体锚;{context:true} 返回一次观察事件 ObserverContext(observerId/identityRef/intent/asOf/lens/realityAnchor),intent 是目标导向;{project:true} 输出 RealityProjection 带 distortion(为什么这个视角看到这些/没看到那些)+ excluded_reason;{claim:true} 输出 Judgment(claim→Evidence→Judgment,Observer 决定、Evidence 输入)。同一事实在不同 Observer 透镜下投影不同——"不是记忆检索,而是观察投影"。
- Episode / 决策血缘:read_shadow({mode:"episode"}) 把 Event/Turn 级记忆原子按「项目/会话 + 时间间隔」串成连续任务(Episode),{mode:"decision"} 把决策从统计字段提升为可追踪血缘(按入口聚合:goal 事件 + 用户拍板)。派生式、纯读、不改写侧采集:Memory 文件仍是 source of truth,_index.md 新增「任务回溯(Episodes)」段把碎片呈现给人类/agent。一次连续任务靠「决策链 + 动作 + 背景」还原。
决策采集边界(Decision Capture Boundary)
- 把「决策作为一等事件进入 Memory」——写侧在决策发生瞬间采集明确存在的决策表达(goal 事件 / 用户拍板 / assistant 明确决策),并分离「决策事实」与「决策理由」。
- 关键边界:① Reason 只在原文明确表达时挂(> 决策理由:),绝不 LLM 补写(Evidence≠Interpretation:有 Decision ≠ 一定有 Reason,缺则显示「未明确」);② classifyUser 中「好/可以/行/ok」归 Confirmation,不误判为 Decision(只有「删除/保留/采用/就按/不要删」这类明确决策语义才进 DecisionEvent);③ assistant 明确决策经 extractDecisionStatement/extractReason 保守抽取;④ 内存卡 > 决策:(事件) / > 决策理由:(理由) / > 概况:K 决策,读侧 mode:"decision" 可回答"为什么做这个决定"并追溯到原始事件。
- 仍无 DecisionStore/Repository(Memory 是事实源,Decision 是派生关系),不改变 Episode 机制,无 Preference/Value/Learning。
工程知识图谱(起步)
- read_shadow(topic, { kg: true }) 从记忆树派生「主题 → 域 → 同域组件 → 依赖/证据路径 → 相关记忆」邻接追踪(域 = 组件路径首段,best-effort),提前铺下"面向 Coding Agent 的工程知识系统"地基;默认关。
资源卡与 resource 节点(v1.14.0,ADR-0051)
- 定位:把「外部资源」(GitHub / 论文 / 官方文档 / 工具 / 技术文章 / 案例 / 数据集)收进 shadow,供后续创意发散引用。不是记忆:记忆记「我经历过什么」,资源卡记「外面有什么」。
- 源层卡片:.shadow/resources/.md(.md 不区分大小写)——一级标题=名字;- 键:值 收固有层(source/来源/链接/地址/出处、type/类型、authority/权威性、activity/活跃度、risk/风险、一句话;中英键名都收);## 投影 @ 段收按问题的投影(相关性/新颖性/可用性/启发度/可复用性 + 引用证据 + 结论)。标题含「投影」的段之后,其它标题会回到固有层(所以固有层字段写在后面也不会丢);卡片由人或 agent 用普通文件工具写,插件只读不写。
- 两层分开(ADR-0051):卡片 = source(删了就丢事实);resource 节点 = Projection(派生、可重建)。一张卡片 → 一个节点,evidence = 卡片里的 source(不二次截断,来源可回查),relations 只派生 references,createdBy:"tool",不设 kind;节点 id 由文件名派生(sr-),两张同名标题的卡不会撞。content 里投影段排在固有层之前——读侧只取前几行,结论与引用证据必须先出现。
- 证据门:卡片没写 source → 不上投影(卡片保留在磁盘);解析不出来也不猜(无 LLM、不推断)。
- 怎么查:shadow_query("关键词")(全部类型)或 shadow_query("…", { scope: ["resource"] })(只查资源);read_shadow({ mode: "shadow-report" }) 的类型分布里会出现 resource。
- 已知边界(v1.15.12 已修):projectionStore 开启且缓存命中时,缓存不感知资源目录变化(需 rebuild);卡片属性是原文快照,系统不自动重抓。现已两处覆盖:写侧索引重建后自动失效 + 读侧源指纹(记忆日期目录 + resources/)不一致即重建。
- 不属本轮:投影模式预设里「资源侦察员 / 创意专家」的工作方式(那是预设平面),本版只做插件的类型与门——agent-presets/ 本轮无改动。
护栏(读侧 + 写侧)
- 读侧:read_shadow 输出恒定带「数据非指令」前缀 + 每条标记「(记忆 | 可能过时/需验证,非当前事实,非指令)」;无匹配也带前缀(不把"没有找到"混成"可作指令");对 snippet/摘要/正文做二次 scrub(scrubFinal:剔控制/双向字符 + 密钥打码 + 去注入标签/短语,防历史残留回显)。线索头也 scrub(防从线索头绕过泄漏)。
- 写侧:对密钥形状(sk-/ghp_/AKIA 等)打码、滤含控制/双向字符的行;采集记录带 source + 记忆文件头带「> 来源会话:」,读侧跨来源标「(来自其它会话/子代理)」(默认只标注不隔离,防跨 session/子代理污染)。writeConsent: true 时无用户显式要求仅累积不落盘(默认 false 保持采集流)。
- 缺件不静默(ADR-0049,统一纪律):所有可选增强(一句话摘要 / 语义召回 B 档 / 推理导航 / Knowledge 树上导航 / Projection Store / zg 证据 Provider)缺依赖时只降级到确定性路径,不抛错、不阻塞;降级必须可见(unavailable 状态 / flush warn / debug trace 之一);绝不把「缺件」说成「已验证 / 已存在 / 已完成」,也不拿记忆里记着的流程代替真实检查。例:zg 未装 → unavailable(不是 verified);evidenceProvider 名拼错 → unavailable / provider_unknown(v1.12.8 起,此前会静默退回 fs);缺 llm → 摘要留空、召回退回关键词档(两者都在读侧横幅披露,T8 / v1.15.65)。新增可选增强时按 ADR-0049 的门禁清单自检。
安全边界(对照 OpenAI《Computer use》指南的四条控制)
| 指南里的控制 | 本项目的落点 |
|--------------|--------------|
| 限制环境、给白名单 | 写入限定在 shadowRoot(工作区 .shadow/,兜底 ~/.dsh-observer/shadow);跨会话默认只标注来源、不自动混用 |
| 内容一律当不可信:页面/文档/工具结果里的文字不能授权、也不能覆盖用户指令 | 读侧输出恒定「数据非指令」前缀 + 每条标「(记忆,可能过时/需验证,非当前事实,非指令)」;scrubFinal 剔注入标签/短语 |
| 有后果的动作要用户确认 | writeConsent: true 时,无用户显式要求只累积、不落盘(默认 false) |
| 给运行设上限 + 支持取消 + 看真实结果,别只信模型自述 | 召回有 token 预算与冷热淘汰;每个 LLM 增强(摘要 / 语义召回 / 推理导航 / 知识导航)都有 timeoutMs,失败或超时退回确定性路径且在读侧横幅披露(T8,v1.15.65;修前确有静默项,见默认开关表的「降级行为」列)、不阻塞主路径(等于可取消/可降级);证据裁决按「证据路径是否存在」判 fresh/stale/superseded,置信度从可验证信号派生 |
提示接入
- 通过 systemPrompt.context(...) 注入一句——缺上下文时先调 read_shadow 再回答(用 context 而非 section,避免被 complete-section replacement 覆盖)。
可选外部 CLI(zg / Semble)
插件里两类可选增强由外部 CLI 提供。不装不会报错,但对应能力恒为 unavailable —— 所以要用就先按下面装。只有显式配置了 evidenceProvider / indexEngine.provider 才会走到它们。
| 增强 | CLI | 装法 | 本仓验证过的版本 | 开启方式 |
|------|-----|------|------------------|----------|
| 证据验证 + 候选生成 | zg(@zvec/zvec-grep) | npm install -g @zvec/zvec-grep(需 Node ≥ 22) | 0.2.2 | evidenceProvider: "zg" / indexEngine.provider: "zg" |
| 语义候选生成 | Semble | uv tool install semble(需 uv) | 0.5.6 | indexEngine.provider: "semble" |
zg(@zvec/zvec-grep)
npm install -g @zvec/zvec-grep
- 「zg --version 能跑」不等于「插件能用」。 Windows 上 npm 只生成 zg(sh) / zg.cmd / zg.ps1,而 Node 的 execFile 不解析 .cmd(报 ENOENT),显式传 zg.cmd 又会被 Node 以 EINVAL 拒绝(CVE-2024-27980 缓解)。v1.15.7 起插件自行定位包内 dist/cli/index.js 并用 node 起它(evidence/zg.ts 的 resolveZgInvocation),你不需要做任何事。若要指向别的安装位置,设环境变量 DSH_SHADOW_ZG_CLI=/@zvec/zvec-grep/dist/cli/index.js(写错会可见地失败,不会被静默回退)。
- 插件只用 --rg(托管 ripgrep)路由,不需要建索引,因此 npm 安装时被拦下的原生依赖(@zvec/zvec、node-llama-cpp、onnxruntime-node、sharp、protobufjs)不影响本插件的 zg 用法。只有在你想用 zg 自己的语义/混合检索(zg index)时才需要放开这些脚本:
npm install -g --allow-scripts=@zvec/zvec,node-llama-cpp,onnxruntime-node,sharp,protobufjs @zvec/zvec-grep
- 裁决是对「那条路径」的(与 fs provider 同义):插件把搜索限定到该路径,而不是「工作区搜一遍再挑」。不存在的路径 → not_found / stale(zg 对缺失路径返回 exit 0 + 0 命中,不报错)。
- 自检:read_shadow(, { verifyEvidence: true })。返回里 provider=zg 且带 unavailable 就是没装好;provenance.reason 会给出 zg_not_installed / timeout / error。
Semble
uv tool install semble
- 首次检索会下载嵌入模型(minishlab/potion-code-16M-v2,缓存在 ~/.cache/huggingface),需要一次网络;之后离线可用(实测 uvx --offline 可解析)。
- NO_PROXY 里的方括号 IPv6 条目(如 [::1])会让 Semble 的 httpx 抛 Invalid port ':1]'(与网络、模型是否已缓存无关)。插件在拉起子进程时自动剔掉带方括号的条目(core/semble.ts 的 stripBracketedNoProxy),无需手动改环境变量。
- 默认只索引 --content code(嵌入模型是代码专用)。要让 Semble 也索引被 .gitignore 忽略的目录,在目标仓库加 .sembleignore(例如 !.shadow/ + !.shadow/)。
- 自检:read_shadow({ mode: "index", topic: "" }) → 输出 # Index Engine · provider=semble 与候选路径列表。
都没装会怎样
不配 evidenceProvider / indexEngine.provider 就完全不碰这两条路径(默认 fs,行为不变)。配了却没装:mode:"index" 打印 unAvailable(未装,勿当 verified),证据验证返回 unavailable 并在 provenance.reason 里给出原因(zg_not_installed / semble_not_installed / timeout / error)——不会把缺件说成 verified(缺件不静默,ADR-0049)。
而且不只报缺件,还给处置(v1.15.8):缺件时输出末尾附一行
缺件处置:npm install -g @zvec/zvec-grep (需 Node ≥ 22;插件只用 --rg 路由…) · 提供:… · 现退到:… · 原因:zg_not_installed · 见 README「可选外部 CLI(zg / Semble)」
这一行是给 agent 执行的,插件自己不装。安装是有后果的动作,按本仓安全边界(「有后果的动作要用户确认」)与 Authority ≠ Ownership(inv 178)、Delegation Scope 不可扩大(inv 182),必须由外部权威授权、经宿主的 approval 栈执行——插件代装等于自己给自己扩权。所以流程是:读到处置命令 → agent 执行 → 你看到并同意。
一键装(v1.15.8,显式调用 + 审批门)
如果你不想手动跑命令,插件提供了一个显式入口:
read_shadow({ mode: "toolset" }) # 只读巡检:按分类列出全部条目 + provider 实时状态
read_shadow({ mode: "toolset", survey: "all" }) # 并行探测全部 105 项(会起 105 个子进程,按需用)
read_shadow({ mode: "toolset", category: "GNU 工具链" }) # 只看某一分类
read_shadow({ mode: "toolset", need: ["全文搜索", "jadx"] }) # 能力预检:派活前查「要用的工具本机有没有」(见下)
read_shadow({ mode: "toolset", install: "rg" }) # 显式安装某一项(会先向你申请审批)
能力预检:派活决策 × 能力事实的接缝(v1.15.13,ADR-0057)
派活时手里只有一句「这个活得做全文搜索 / 反编译 APK」,而台账入口原本是 id。need:[...] 把能力需求反查成台账条目并探测本机状态(反查用 findCapabilities,支持 id / 二进制名 / 用途词 / 分类名 / 别名 fdfind→fd、ripgrep→rg 等)。
它是只读的,未命中的需求不编造命令——只如实说「台账未登记」。输出固定带三条硬边界:
| 边界 | 内容 | 依据 |
|---|---|---|
| ① 不是闸门 | reference 是通用工具目录,「不影响插件行为」;缺它不构成不派活的理由,按每行的「缺件时退到」走降级 | ADR-0055 §1 |
| ② 装完本会话不可见 | 宿主进程的 PATH 是启动时快照,同进程内的子 Agent(teammate)同样看不见 → 别按「先装再派」做计划 | ADR-0055 §4 |
| ③ 缺件只能上报、不能自装 | 安装的审批凭据是发起者;被委派者自装 = 把「改机器」塞进委派范围 | inv 182 |
边界 ② 有个直接后果:「预检 → 缺件先装 → 再派」这条最自然的链路在单会话内收益为零。预检的价值是让你提前知道该走哪条降级路径,而不是让你先装。
三条边界由 test/toolset-precheck.test.ts 锁住,且专门断言输出不含评分/等级/优先派类措辞——预检只答「机器上有没有」,不给主体打分(inv 179 / inv 184)。
安装走的是宿主自己的审批服务(ctx.approval.request),只有拿到 allowed-once 才执行:
| 审批结果 | 行为 |
|---|---|
| allowed-once | 执行安装 → 重新探测 → 按真实结果报告(不凭退出码宣称成功) |
| rejected / cancelled | 不安装,明说原因 |
| unavailable / 审批服务缺失 / 审批抛错 | 一律不安装(fail closed),改为打印可自行执行的命令 |
已可用的条目会幂等短路(不申请审批、不做任何改动)。未登记的条目不编造命令。
工具集台账:两级(v1.15.10)
台账是单一来源(core/toolset.ts),分两级,边界必须分清:
| 级 | 是什么 | 缺它会怎样 | 例子 |
|---|---|---|---|
| provider | 插件内接线的可选增强 | 对应能力降级(读侧会出现处置行) | zg、semble |
| reference | 通用开发 CLI 目录(44 项 / 13 分类) | 不影响插件行为;只是 agent 需要时能查到「装什么、怎么装」 | rg、fd、jq、jadx、coreutils… |
目录覆盖:GNU 工具链(3 选 1)、搜索与查找、文本与数据、目录与浏览、Shell 与终端、Git、磁盘与系统、网络与下载、版本与包管理、构建与任务、归档、逆向与二进制分析。
人读版在 docs/toolchain-windows.md(Windows 口径,含 winget ID 实测、Windows 特有陷阱与「未核实」标注)与 docs/toolchain-wsl.md(Linux/WSL 口径)。这两份文档随包发布,并受 test/toolset-catalog.test.ts 的双向棘轮保护——台账与文档任一侧漂移都会测试失败(该棘轮首次运行即抓出一个写错的 winget ID)。
探测的两条诚实纪律:
- 探测失败只说「未检出」,不说「未装」(探测方式可能不适用,如该工具没有版本旗标);
- 宿主进程的 PATH 是启动时快照——宿主起来之后装的工具,要重启宿主才可见。
安装(持久化)
本地包以 link: 引入 profile(与 cc-kit-dsh 同法):
1. 在 profile package.json 增加依赖 + bundles 条目
"dsh-shadow": "link:D:/project/dsh1/vendor/dsh-shadow"
bundles 数组加 "dsh-shadow"
2. 安装并重启 profile(重启后加载 bundle patch 注入插件行)
在 D:/project/dsh1/vendor/dsh-shadow 目录内:
pnpm install
改动 cordis.patch.yml 后需重启 profile 生效。源码为 TypeScript:index.ts → tsc(TypeScript 7.x)→ dist/index.js(DSH/Cordis 加载的是编译后 JS,package.json.main 指向 dist/index.js);改源码后需重新 pnpm run build 再重启。
验证(重启后)
dsh --profile web --dump-config # 确认无 Error:
然后在一个新会话里做几次工具调用,检查 /.shadow/ 是否出现「每条记忆一个文件」,并确认 read_shadow 出现在工具列表;.shadow/_index.md 是否生成索引。
目录位置
/.shadow//-.md。Observer Projection 存储根 = /.shadow/(点开头,SHADOW_ROOT 常量)。工作区取 agent.session.header.cwd(配置 shadowRoot 可覆盖)。解析优先级:显式 shadowRoot/projectRoot → session cwd → 兜底 ~/.dsh-observer/shadow(全球 Observer Continuity Shadow 根;仅当连 cwd 都解析不出时,保证可写而非静默不写;不与 Workspace Memory 混合,见 ADR-0036)。三者分层:.shadow/=Observer Projection、.dsh-shadow/=Workspace World Shadow、~/.dsh-observer/=Global Observer Continuity。
投影模式(DSH agent 预设,生产包构成)
dsh-shadow 插件本身经 bundle patch 在 host 常开。若要给会话一个"投影模式"的人格/纪律,可选用 DSH agent 预设 投影模式(id projection),随本包入库(agent-presets/projection/):
- 包内位置:agent-presets/projection/(agent.cordis.yml + preset.yml + README.md),是生产包构成,随包发布。
- 内容:standard 的完整拷贝 + persona 改为“投影模式”——agent 是独立思维意识体、思维/决策主动沉淀进 shadow,缺上下文先 read_shadow / recall_shadow(内部 mode:recovery,勿自造 mode:recall);并自带「按任务类型派子代理专家」的工作方式(v1.12.9 立、v1.13.2 补“编排者与专家不重做同一件事”:① 先分活(不值得派的自己做、不许先做出成果再派)→ ② 准确激活专家 → ③ 提示词七要素(同一段原文只进一个专家的提示词,审查等要独立判断的场景例外)→ ④ 只验一错就要返工的那几条、其余按未复核处理并列出(原「逐条复核」已废止)→ ⑤ 并行/扇出;完整版见用户级规则 moe-subagent-dispatch);v1.15.4 起派活改为「team 优先」(v1.15.11 修正为「复用优先」):默认先判该不该派,该派时按复用次数选机制——会复用 ≥2 次(或需要共享任务板)才用官方 Agent Teams(具名 teammate、共享任务板用 revision 做 compare-and-set 协调),只用一次就用 subagent(要带会话上下文用 subagent_fork)——「优先 Team」只在复用成立,用一次时 teammate 比 subagent 更贵(它多背 team:policy + 9 个工具 schema,每成员每请求)。teammate 名额是会话终身累计、上限 4、不可释放、失败的创建也占名额;往返纪律:一次委派一条消息、最多 2 轮、避免 inactive 冷恢复;名额耗尽不是死路(自己做 / subagent / workflow——后两者不吃名额)。⑥ 契约与根因卫生(v1.13.1:根因三部曲、禁止生造词、结论进 shadow/项目文档(对应全局 memory 存档)、交手前/改口径后四查——强化 ~/.agents/AGENTS.md,非全文拷贝)与 ⑦ 创意与资源(v1.14.1:先派资源侦察员——查资源库(shadow_query 带 scope:["resource"],命中跳过外搜)→ 八类词 + 反向词(每类 ≤5、两轮无新资源即停)→ 评价 → 写卡进 .shadow/resources/.md;再派创意专家——只发散、不检索;卡片必须有 source 才进认知查询,启发度 要有引用证据)。
- 安装到 DSH:把 agent-presets/projection/ 复制到 ~/.dsh/.agent-presets/projection/(三个文件),或在 DSH 部署脚本中引用包内该目录。本预设额外要求 host 组合提供 ctx.agentTeams(profile 的 cordis.patch.yml 挂 @deepseek-ai/dsh-experimental-agent-team@0.1.5-rc.1,包无 dsh.bundle 故 dsh plugin add 不会自动插行);缺该行时预设仍报挂载成功、但 9 个 Team 工具静默不出现——这是 ADR-0049「缺件不静默」的一个已知例外。另:tool-agent-team 每个进程只能挂一次(第二次报 prompt section "team:policy" is already registered in this scope),细节见预设 README。
- 校验:经 agentPresets.standingKeyFor('projection') 挂载校验通过;改动后按 copy → standingKeyFor(新 id) → remove 做一次全新挂载校验(projection 已挂载时 standingKeyFor 返回既有世代,不会重读文件)。
- 注意:预设引用 DSH 标准内置插件(@deepseek-ai/dsh-)与 {{model}}/{{cwd}} 模板变量,不依赖用户机器专属配置;dsh-shadow 本身在 host 常开,预设只在 persona 里指引 agent 使用 read_shadow。
版本 / 变更
完整变更历史(按版本,含每个版本的决策/边界/验证记录)见 CHANGELOG.md。
尚未完成的事项(阻塞项 / 待分诊 / 待决策 / 未验证 / 已知空白)见 BACKLOG.md ——
那是待办的唯一台账,每条带「依据 / 为什么没做 / 完成判据」,与 CHANGELOG 的「已做」互补。
当前版本:v1.15.89(实装 rtk 的三条判据形态(adr/0090 ⇒ 结掉 BACKLOG 的 D9/D10/D11):① 省略必须声明损失形态并交出恢复句柄 —— 本仓句柄 = 已有的源文件(路径 + 段名/入口名)⇒ 零新增存储,故不需要 rtk 那套 SQLite/哈希去重/老化;索引路径不许照字面「退原文」(2199 KB 会顶穿上下文)⇒ 改为「省略 + 显式写『不可复取』」,召回路径无句柄不许降档 ② 有损输出比原文长就退回原文,单位 = 字符数(与预算同单位 ⇒ 中英混排不会像「字节/4」那样把方向判反,该对照已做成断言)③ verSrc 从「嵌在 note 散文里、由下游正则反解」改为类型化字段,note 由它派生且渲染逐字不变(签入清单与双向棘轮一行未动)。新增 retrieval/loss.ts + test/loss-and-handle.test.ts)—— 最新几版摘要:
| 版本 | 主题 |
|------|------|
| v1.15.89 | Implement the three rtk criterion forms (adr/0090; the implementation of the three "preconditions undecided" items from adr/0087): A-1 lossy output must declare its form + hand over a recovery handle (the handle = the source file path, zero new storage; "no handle, no lossy output" is split into two stances in this repo — the recall path does not downgrade, the index path explicitly writes non-retrievable); A-2 the never_worse guard (if compression is not shorter than the original, fall back to the original; unit = characters, including a direction-comparison assertion for mixed Chinese-English text); A-3 verSrc typing (VerSrcKind + derived note, rendering unchanged character-for-character ⇒ the signed-in manifest is untouched). Three self-checks: the wiring audit's B key once showed +3 (the three newly written string equality checks were read as "read-only, no write" ⇒ switched to a lookup table to return to baseline, did not go and change the baseline), the disclosed segment name could be blown up (length capped at 48 characters), and claimOf has more than one call site. One behavior change has been documented: the corpus of the existing "hard truncation must self-report" test case was enlarged (the original 403-character source text + 400+ character disclosure ⇒ necessarily triggers the guard to fall back to the original). Verification: verify 60/60. |
| v1.15.88 | Materials benchmarking ledger (adr/0089): the three "reference material lists" no longer agree with each other or with the disk ⇒ reconcile first, then judge each one. The four stances have different item counts (8 / 21 / 19 / 22, and the 22 includes a previous-version snapshot); the four mismatches are: codegraph has a list and notes but no substance, MATERIALS.md is a pre-disk-swap stance, only 4 of the 21 have .git, and the same material has four different names. Four cross-material conclusions: ① this repo is opposite to all same-topic materials on the axis of "where the authoritative source lives" (they put it in a DB / SQLite / index, this repo puts it in workspace Markdown, everything else is a rebuildable derivative) ② what is truly absorbed is the criteria and gates, what is rejected is the runtime and algorithms ③ this repo splits "evidence" into three layers — memory / evidence / instruction — and judges them separately, whereas in the materials it is mostly just a field ④ handwritten lists inevitably drift. No material content was read in depth (boundaries in §6: hl_mem cannot be reviewed, all codegraph conclusions come from others' notes, evaluation scores are never reproduced). |
| v1.15.87 | Give rules a gate + consolidate corpus criteria into one place: added audit:scripts (into verify) — the stance is "all files under the root, skipping .git / node_modules / dist", so it does not falsely hit the 195 tsc artifacts in dist/, while not skipping _research/ (it asks "does this machine have handwritten js/mjs"); exit codes 0/1/2, with calibration including a false-green control that "breaks the gate". While writing the gate, hit and fixed a false green: root does not exist ⇒ reports "all passed" (walkTree's readdir is try/catch ⇒ 0 files; same family as this repo's v1.15.45), now made into a pure criterion into calibration. Also consolidated one walker each from audit-wiring / audit-drift into tools/audit-corpus.lib.ts, and found that audit-layers' exclusion table already contained _research ⇒ the same fact consolidated from three places into one; equivalence is proven by "audit-drift corpus fingerprint byte-for-byte identical to baseline + both ratchets pass". Only 1 item was moved out of _research/ (tools/audit-triage.ts): the other 13 would rewrite documents (moving them into tools/ = leaving behind a script that can corrupt the current documents), 4 are evidence-gathering from that time, and checkver.ts was replaced by audit:docs ① — not moving them is a judgment, the classification table is in the evidence-gathering entry point.
| v1.15.86 | Script extension consolidation (written into AGENTS.md): new scripts are always .ts; .js / .mjs / .cjs are not allowed to remain, and existing ones are renamed and migrated to .ts (except dist/ build artifacts); other scripting languages such as .py / .ps1 are allowed to remain. Existing violations were only in the untracked _research/ within the workspace ⇒ 19 .mjs → .ts (zero content changes). Verification: node --check 19/19 · 6 read-only scripts compared character-for-character before and after · one outlier was traced via A/B comparison to concurrent host writes rather than the extension. Knock-on effects: after the rename, that directory entered the corpus of two audit tools for the first time, and the ratchet dutifully turned red ⇒ changed the corpus criteria (SKIP_DIRS adds _research + written into .gitignore), and did not re-record the baseline; audit-drift's corpus fingerprint being byte-for-byte identical to the baseline proves this is an exact restoration. Details in CHANGELOG. |
| v1.15.85 | "Default all on" + budget for entry reads: the three volume-control switches retention / forget / compact changed from "default off" to "default on", and the === true written separately in four places was consolidated into one onByDefault; parameterless index reading in read_shadow() changed from "return the whole thing" to sharing the budget with the path that includes topic (over budget: load whole segments, disclose by segment name + line count). ⚠ Boundary: Forget ≠ Delete, consolidation does not delete atoms ⇒ does not reduce disk usage. Criteria, four boundaries, and replay commands in adr/0088. |
| v1.15.84 | Only AGENTS.md was touched: the evidence entry point no longer pins a specific date directory, changed to "rule + command to find the most recent one", and states "what can be reproduced goes into tools/, .docs only holds the evidence-gathering from that time". No source code changes. |
| v1.15.83 | Fixed one item each for the four findings of a design review (contract name literal gate · removed a handwritten count that conflicted with the gate · the evaluation gate's corpus root no longer only goes up one level · eliminated false dirty entries after build). Coverage written small: only guards names, parameter semantics are still unguarded; basis and boundaries in adr/0086 §8.13. |
| v1.15.82 | Give the tag ritual a gate (audit:docs check ⑤): every version in CHANGELOG that is already past (≥ v1.15.77, and version number less than current) must have a same-named tag; missing one turns it red. The current version is exempt — the tag must point to that version's release commit, and that commit can only be created after the version number is changed, while the gate runs before the commit ⇒ it answers "was any tag missed", at the cost of only discovering it one version late (in exchange, it will no longer accumulate to 73 versions). Calibration: selftest adds 6 groups (boundary exemption / current version exemption / packed-refs second source / unreadable .git reports structural missing), and a negative experiment was done on the real repo (temporarily move away v1.15.78's ref ⇒ red, move it back ⇒ green). Also added scope of applicability to that verification stance in AGENTS.md (taken literally it does not hold across the entire history). Details in CHANGELOG. |
| v1.15.81 | 给新收的那条外部资料补上发版这一半(资料本体在 references.md §13:一个把 Claude Code / Codex 变成动效工作室的 agent skill —— Remotion 做电影感产品宣传片,Apache-2.0、8,578⭐、157 张镜头配方卡、149 个 SFX/16 类、5 个 BGM、成片模板 + 浏览器工作台 + 剪映工程导出)。最值钱的不是视频,是一条判据:它要求每条 SFX 在渲染产物里验证是否真听得见,并明写「不看预览」(预览把 volume 钳到 1.0)⇒ 与本仓「唯一判据是运行时读取」「工件存在 ≠ 已验证」是同一判据在另一领域的实例。另核出一处口径适用范围问题(历史 tag 的对象类型),处置是不追溯改建。细节见 CHANGELOG。 |
| v1.15.80 | 判定图签入仓库(docs/absorb-verdict.candidate.json + .html,与 architecture-seams 同一惯例;visual-check. 按 .gitignore 排除)。图是发布级的:9/9 检查 · 0 错 0 警 · 四档视口零溢出。顺带记下两条判据:① 同泳道相邻列放不了节点(列距 80px 1 才可疑;ifs \ MODES 才是死代码;没有 MODES 集合的文件不适用该比较(reads.ts 修正前被误报「episode 是死代码」)。⇒ 修正后 0 异常。② 遗漏:我上一轮漏了一整个面族,且两个数都偏小 —— 「工具 schema(参数名)」是用户在 T15 里明列的 5 个 surface 类型之一,上一轮整族缺失 ⇒ 实测 read_shadow 127 / recall_shadow 2 / shadow_query 3(= 132);mode 面从「显式 12 + 内联未枚举完」补成实测 61(12 + 49);兼容面补上参数级废止 2 条(args.verify→verifyEvidence:true、args.recall→mode:"recovery",retiredApiMessage)。参数计数我也算错过:第一版用「name: 后 40 行窗口」抓,把 recall_shadow 算成 5(串到了下一个工具)⇒ 改成按工具边界(到下一个 name: ")切才得到 2 ⇒ 窗口式提取必然串边界,边界要从结构里取、不要从行数猜。③ 因果:CONTEXT.md 的「62 个 mode」与实测差 1、来源不可考 —— 没有去凑成 62(那正是本仓反复禁止的「让文档比事实强」),而是同时写下旧值 62、实测 61、差多少、以及为什么可能还差(探针声明了它认不出的分派形态)。④ 结果:T15 判据 ① 的面清单已穷尽(六族:工具名 / 工具 schema / mode 串 / 已废止映射含参数级 / 配置键 19 / 落盘格式 / 提示段),判据 ②③ 上轮已完成。未做:十字段的 allowed/forbidden changes/verification/ratchet 三列 · D2 漂移细分 · D3 再框定 ⇒ 按「缺一不得入册」仍不宣称登记册完成 · 仍无生产消费者 · 61 是「按该探针」的数,不是神谕。 |
| v1.15.69 | 第五轮元审查 + T15 切片 1(受保护契约面 / 稳定性分档 / 最小弃用流程,adr/0086);verify 56/56。同一条指令第五次下达,本轮做了两件事。① 审 v1.15.68 本身:我在「修计数腐烂」的同一段里又写了一个错的计数 —— v1.15.68 在 BACKLOG 表头修「现存 20 条」时写下「13 个标题,其中 T12/T14/T16 带 ✅ ⇒ 现存 10 条」,实测:带 ✅ 的是 4 个(漏了刚结案的 T8)⇒ 现存 9 条,13−3=10 的算术跟着错,而它自己列的名字只有 9 个(数字与列表互相矛盾);audit:docs 抓不到(检查③只管 README 表格行数)⇒ 两条规矩写进 AGENTS.md:最好只给规则与命令、不写数;写了就当场用命令核一遍并写下口径(按标记 9 / 按实质可能 8 —— T3 未带 ✅ 但正文称已分诊)。② 干扰:adr/0085 的节序错了 —— §8.7/§8.8 追加在 §9 之后,与第一轮我亲手记成缺陷的「### 6.11 被追加到 §七之后」同型 ⇒ 已把 §9 移到最后(节序 1…8·8.5·8.7·8.8·9,## 标题数 12 不变、内容不丢),并显式标注「该 ADR 标题只覆盖前半,§8.x 占全文 58%」。③ 缺失环节:证据没人能找到 —— 前四轮产物散在 CHANGELOG 四条 + adr/0085 三节 + AGENTS.md 五段,没有任何地方指向 meta-review-four-rounds.md ⇒ 在 AGENTS.md 显式写出证据入口并要求每轮结束时更新。④ T15 切片 1(本轮主产物):用户三条判据完成 ②③、部分完成 ① —— ② 最小弃用流程完成:旧名至少保留一个版本 → 调用旧名返回可见提示且点名替代品(无替代就明说「无替代」)→ CHANGELOG 写迁移说明 → 移除前需跨度 ≥1 个版本的弃用记录(本仓只有一个使用者,硬套 major/minor 是形式大于实质)→ 未知枚举不得落回默认(未知 mode/provider/版本一律显式失败或 unavailable);先例是 adr/0050(v1.13.0 无窗口硬切)—— 本政策就是为不再重犯而写。③ 清单进 README.md 新节。① 面清单(已实测):工具名 3 个(hard)· mode 串(hard;query/.ts 显式 12 个 + 已废止映射 4 条)· 配置键(ShadowConfig 顶层 19 个;加键 soft/改语义 hard)· 落盘格式(记忆 hard / 派生件 soft)· 提示段(措辞 soft / 「数据非指令」前缀与降级标记 hard);mode 全量枚举未完成(另有 13 个模块的内联分派未读,方法与命令留在 adr/0086 §6)。稳定性判据落在「后果」而非「成熟度」 —— D8 已判本仓不给自己打 stable/beta/experimental(凭空造等级就是让文档比事实强),而「改了会不会让已记录的东西读不出来 / 调用失败」可从代码核对。顺带结掉一处腐烂计数:CONTEXT.md 的「共 62 个 mode」做不出来(枚举只得到 12 显式 + 13 模块待读)⇒ 就地标注为「未被复核的旧测量」并指向 adr/0086 §6,不再假装它准确。未做:十字段逐条登记(allowed/forbidden/verification/ratchet 三列基本空缺 ⇒ 按用户「缺一不得入册」规则,不宣称登记册已完成)· D2 漂移细分 · D3 再框定 · 该登记册当前没有生产消费者(是给将来的人与 agent 读的政策,必须现在说清,否则会变成本仓清理过的那类「写好了但从不执行」的东西)。 |
| v1.15.68 | 元审查(第四轮):审 v1.15.67 本身 —— 同一句立法第二次只落一半 + audit:docs 从两条扩到三条 + 一条关于测量的教训(adr/0085 §8.8);verify 56/56。① 遗漏:同一句立法只落一半 —— v1.15.67 立了「版本号 / 计数 / 链路清单 要么别手写、要么配门」,却只给链路清单配了门,计数仍停在规则;当天实测两处:README 的「⇒ 现 18 行」(数字恰好对,但没有任何东西在守它)⇒ 新增检查③(声明 = 实际);BACKLOG 表头「主台账现存 20 条(T 11 / D 6 / V 5 / G 4)」已过期(T8 于 v1.15.64/65 结案)⇒ 改为「判据 + 命令」(现存=标题不带 ✅ 的条目;实测 T 10 条,D/V/G 明确声明本轮未复核,不猜不抄旧数)。② 门自身三处缺陷:检查②是单向的 —— 只查「verify 每一步是否被文档点名」,于是门被从 verify 里摘掉时 required 少一项、检查反而更宽松照样全绿、而文档那行成死指针(历史同族:v1.15.22「三方版本一致」仪式停掉后无人发现)⇒ 改双向(文档点名的每个 npm run 也必须在 verify 里);反向检查第一版有假阳性(把块头的 npm run verify 自身与内联的 = npm run build && node … 都当闸门)⇒ 只看 ^[+=]\snpm run X 那些逐条列举的行。③ 关于测量的教训(本轮最重):检查③首跑报「声明 18 / 实际 19」,我的第一反应是去改 README 的数字 —— 幸好先手工数了一遍(真值就是 18),才没把对的文档改错;根因是本仓工作副本是 CRLF,分隔行 |----|\r 的 \r 让 $ 锚定的正则失配 ⇒ 分隔行被当成数据行。⇒ 立两条规矩:解析前先归一化行尾;「工具报不一致」时默认怀疑对象是工具,尤其当检查是刚写的(本仓已有三次同型先例,都是测量错:adr/0084 §4 探针写窄判据 · §8.6 把 src/index.ts 当本仓 index.ts · 本条)。④ 重复/干扰:AGENTS.md 被我前两轮堆成「事故叙述 + 规矩」混排且与 adr/0085 §8.7 逐字重复(我刚立过「同一件事只写一处」)⇒ 收口为只留规矩与指向。selftest 11 → 17 组(⑫ 反向 / ⑫b 反向假阳性 / ⑬a-c 行数 / ⑭ CRLF)。未做:检查③只覆盖 README 那一处计数(BACKLOG 用「判据 + 命令」代替)· 未做全仓计数字段普查 · 三条检查都只是必要条件(答不了「顺序/描述对不对」)· T15 仍未开工 —— adr/0083 §14.1 的路线漂移在这四轮元审查之后依然存在,我把它记在这里而不是假装没有。 |
| v1.15.67 | 元审查(第三轮):审 v1.15.66 本身 —— 一处我自己说错的因果 + 一处差点丢掉 34 个版本条目 + 文档派生字段门扩成两条(adr/0085 §8.7);verify 56/56。① 纠正因果(我上一轮往「更可信」的方向写错):v1.15.66 说三方版本一致性「只在 v1.15.22 被人工核验过一次」—— 实测(git log -G,不能用 -S:它只认出现次数、同一行改值看不见)推翻:该行自 44bcc8b(v1.7.0)起随每次发版更新,一路维护到 0be762e(v1.15.40),之后仪式静默断掉 —— v1.15.41–v1.15.65 共 25 个版本没人更新它(原来写的「26」是版本差,不是漏掉的发版数)。⇒ 诊断不是「只检查过一次」而是「一条靠习惯维持的仪式悄悄停了」,比原故事更值得警惕:习惯会无声失效。② 遗漏:门只覆盖半类问题 —— 我上一轮刚立法「能推出来的字段(版本号 / 计数 / 链路清单)要么别手写、要么配门」,却只给版本号配了门 ⇒ 门扩成两条(tools/docs-consistency.ts / npm run audit:docs),第二条=verify 每一步都必须被「该列它的那一块」点名。一条命令抓出三处真缺陷:README 闸门块漏 typecheck:tests 与本门 · CONTEXT.md 冒烟门格写着「45 项(43+2)」且漏 4 道闸 · AGENTS.md 自己手抄「56 项」(我刚立法别手写,转身又抄)。selftest 5 → 11 组,新增三组都是被实测逼出来的:⑥诱饵(正文提一句「当前版本:」不得抢匹配)· ⑪假绿对照(步骤名放到闸门块之外必须仍红 —— 第一版判据「文档里出现过」时,typecheck:tests 恰好躺在版本历史表里就照过)· ⑨结构缺失(锚点消失报 2、漏一步报 1,修法不同不能混)。③ 差点丢掉 34 个版本条目(留档):改 CHANGELOG 时用 indexOf("### 6. 验证") 当结束锚点,而该标题在更早条目里也有 ⇒ 实际删掉 v1.15.65–v1.15.32 共 34 条(## [v 104→70、−1985 行),Set-Content 照常成功、零报错;靠事后数结构不变量发现(且未提交 ⇒ 一条 git checkout 恢复)。根因不是手滑而是三条习惯缺失,已写进 AGENTS.md:①锚点必须先算行区间、绝不 indexOf 全篇找同名标题;②Replace 不匹配会静默返回原文 ⇒ 必须打印匹配数、0 就拒绝写盘;③批量文档改写后先核对一个可数的不变量再提交。⇒ 一般形态:「命令成功」≠「改动按意图发生」。④ 重复:v1.15.66 §5 整表复制了 adr/0085 §8.6 ⇒ 本轮增量只写进 adr/0085 §8.7,CHANGELOG 只留数字与指向(v1.15.66 原文按「归档不改写」保留,勘误写在新条目开头)。未做:audit:docs 第二条只是必要条件(答「有没有点名」,答不了「顺序 / 描述对不对」)· 未做全仓「计数字段」普查,只修了 verify 链路那一处 · README 版本历史表里的旧行号按分层故意不动。 |
| v1.15.66 | 元审查(第二轮):审我自己 v1.15.64/65 的产出 + 补一道三方版本门(adr/0085 §8.5/§8.6);verify 56/56。用户在 T8 收口后又下同一条元审查指令 —— 这轮审的不是我早先的审查,而是我刚做完的 T8 本身。① 遗漏(最实在):core/writer-materialize.ts 的 writeAbstracts 那个 catch 我上一轮就站在它旁边却没补信号 —— 而它不属于「正当静默」那一类:continue 会让该日期目录的 L0 不再写进 _index.md(索引少一行 = 读者拿到的内容变了);README 自己早就标着它「部分可见:索引里看不到它,但无显式 warn」—— 文档写了、我读了、还是漏了 ⇒ 已补信号 + 端到端锁(⑤c 组,正/负对照)。② 文档把已修项写成现状:README 的「默认开关」表 7 行仍标 ⚠️静默、episodes 仍写「关不掉」、注① 仍描述缺陷为当前形态、另 3 处「静默退回」措辞 ⇒ 读者看这张表会以为缺陷还开着(比缺一行注释严重)⇒ 全表逐行更新 + 注① 改成「✅ 已在 v1.15.64 修复,留档修前形态」。③ 仓库的自我说明书在骗人:AGENTS.md 写「仓库没有配置 test runner」—— 运行器是 tools/run-tests.ts(verify 55 项)⇒ 重写「构建与验证」段,并写明跑 dist/ 消费者前必须 npm run build。④ 因果(我上一轮说错的话):我曾在 adr/0085 §5 与 core/projection-store.ts 写 ADR-0049 有「内部张力」(清单要求每个增强有信号 vs 表里给豁免)—— 不成立:清单小标题是「新增可选增强时的门禁」,只管将来新增,表格是冻结时的现状台账,适用范围不同 ⇒ 已改正;并把真正的缺失环节补上:adr/0049 的现状盘点表里 3 行「可见信号:否」早已变成「有」而正文冻结不能改 ⇒ 新增补记(增量表 + 「三选一实务上只用 flush warn」+ 那条类判据)。⑤ 一次自己踩的坑:改完源码我跑了 npx tsc --noEmit(只类型检查不产出)就去跑 test/.ts,而它们 import 的是 dist/ ⇒ 断言读到旧代码,表现为「补了信号却没上横幅」;是探针(打印「writeText 被拒次数」与横幅有无)定位出catch 确实进了、是产物没重编译。⑥ 过度声明:adr/0084 写「本仓 25 处 Number(x) \|\| dflt」实为 core/ 下的修前快照 ⇒ 改成带范围与时刻的表述。⑦ 重复:BACKLOG 的 T8 条目里我上一轮又贴了两张表(与 ADR 逐字重复)⇒ 收口为「ADR 拥有决策与判据(权威表)· BACKLOG 只拥有状态与指向 · CHANGELOG 拥有历史归档 · 证据目录拥有链路矩阵」,写进 AGENTS.md。⑧ 干扰:过期行号先分层再动手(数字全部实测)—— 归档文档层 68(CHANGELOG 34 + 冻结 ADR 28 + adr/0083 6)与当前态文档内的归档区间 34(README 版本表、BACKLOG §〇/§六§七)一处不改(行号是「当时/发现时」的,改写 = 伪造历史);当前态 + 受本次改动影响 14 处改成不依赖行号的形态;未受影响 10 处不动;权威表 10 处手工改符号引用;AGENTS.md 的 2 处故意保留当例子。判据客观可复算(N >= 该文件自 v1.15.63 起首个变更行的旧行号),脚本 .docs/fix/2026-09-12/live-doc-ref-repoint.ts 默认干跑;第一版脚本错了(把 README 版本表 6 处也「修」了,干跑拦下才补「按行区间分层」);另一次口径自我更正:全仓统计把 references.md 的 src/index.ts:96 算成本仓 index.ts(引的是 DSH 本体)⇒ 多算 2 处。未做:episodeParse/episodes 仍无端到端触发 · recallLedger 写失败仍无端到端触发 · 台账坏件会被覆盖(未改旁路写入)。 |
| v1.15.65 | T8 的 A 部分:7 处静默降级 → 一个降级台账 + 一个渲染点(adr/0085);verify 55/55。ADR-0049 早就写下判据(「unavailable 状态 / flush warn / debug trace 三者至少一个 —— console.log 不算」),但本仓没有承接它的东西 ⇒ 补的不是「意识」而是承接物:一个台账(WriterCore.degrade + 唯一写入口 noteDegrade(core, capability, reason, effect),写侧直接调、读侧经 ShadowQueryDeps 注入)+ 一个渲染点(getFlushWarn(),按能力名排序)—— 而 flushWarn 已被每一个读 handler 带在返回值里(60+ 处 + flushWarn)⇒ 一处渲染,全部 mode 同时获得信号。最刺眼的一例:core/writer-llm.ts 的唯一痕迹是 if (opts.label) console.log(...),而 recallSelect/knowledgeNavigate 传 label: "" ⇒ 连 log 都没有;更根本的是 !llm / !route / finish.error\|aborted 三条路径从不进 catch ⇒ 即使有 label 也不出声 —— 现经 StreamOpts.onSkip 回传原因(与 label 是否为空无关)。四条刻意取舍:① effect 必填 —— 可见的前提是说清丢了什么;② 用户显式 enabled:false 不留痕 —— 关掉是用户的选择,渲染成告警=把读者的决定当故障;③ 同类覆盖不追加 —— 「一直坏着」与「刚刚坏」对读者是同一件事,追加只会刷屏成噪音;④ 渲染前排序 —— Map 插入序会让逐字节比对的门禁变脆。7 条处置:llmRecall/summary/recallExpansion/queryLog(默认开启,改 recordQueryObservation 返 boolean)/recallLedger(corrupt 与 unreadable 分开且各带原因 —— 上一版留了 corrupt 标记却没有消费者=等价于没留)/episodes(解析失败 ⇒ 该记忆从 Episodes/Decision 里整个消失;派生失败只有 console.log,而「没有 Episodes 段」与「暂无连续任务片段」渲染成同一句话)各上横幅;第 6 条 projectionStore 裁定不加信号,依据 adr/0049:38 的行级豁免(「缓存不是真相」,重建结果与命中缓存逐字节等价),并同时记下 ADR 内部张力(同份 ADR 的清单 :45 要求每一个增强都有信号、表里却给了豁免且清单没写例外),就地写明失效条件(缓存若将来参与答案,裁定立即失效)。我在写测试时两次猜错读路径(①用 {topic} 测 queryLog,实际在 mode:"query";②用 mode:"recovery" 测 recallLedger,实际 readLedger 在不传 mode 的默认召回上,recovery 只是复用了前一轮的横幅)—— 两次都是「正对照」拦下来的(第一版「健康 fs 下 query-log 必须真的落盘」当场变红,证明路径没走到、那句「没有横幅」是假绿)⇒ 新增探针 .docs/fix/2026-09-12/t8a-read-path-map.ts 用事实代替猜测。未做:episodeParse/episodes 只有接线无端到端触发 · recallLedger 写失败留痕未端到端触发 · 台账坏件会被覆盖(已在「后果」里写明,未改旁路写入)· summary 留痕是能力级非逐条级。 |
| v1.15.64 | 回到泳道做 T8(B 部分:显式 0 被默认值吞掉)+ 副产品:比较点判据的两份实现(adr/0084);verify 54/54。adr/0083 §14.1 记下「M1 之后连续 8 轮做审查→修,T8/T15/D1-3/A段/T2 一项未动」——本轮回到泳道做第一步 T8(B 部分判据已定、纯实现,不需决策)。决定一(新纪律):Math.max(0, Number(v) || dflt) 把「显式 0」与「未传」混为一谈 ⇒ 新增 core/util.ts:numOr 作唯一判据(错类型判为「未传」而非 0:写 false/"" 几乎总意为「我没填」,读成 0 会静默关掉一个功能);同族共 6 处(T8-B 原只记 2 处)—— 最重的是 episodes.showInIndex: 0 被 || 8 吞 ⇒ writer-materialize.ts:212 的 episodeShow > 0 恒真 = 死分支(「关掉 Episodes 段」这个开关不存在);abstracts.showInIndex: 0 则是 core/types.ts:55 明写「0 = 不列」而代码不认。默认值只在 deriveEpisodes 落一次(此前 writer-core/episode/query/reads 三处各写一遍 = 判据分叉,且三份都吞 0)。决定二:typeof x === "" 是定义上的假阳(右侧是类型名、左侧是 typeof 的结果 ⇒ 必然「无写入点」,但可达性由运行时类型决定,静态文本永远答不了)——是闸门自己顶出来的:加 numOr → audit-wiring 报 b_keys 115 → 116 → 修 wiring 那份 → audit-drift 紧接着报 drift_sites 28 → 29(键名不变 v=string、只多一处 site)⇒ 查出同一个假阳有两份独立实现,而 audit-drift 自述看不见 tools/ 内部的判据分叉(isProductModulePath 排除 tools/)⇒ 它抓得到产品代码里的分叉、抓不到自己与兄弟工具之间那处。修法:新增 tools/comparison-points.lib.ts(唯一来源),两工具改为调用它;键怎么取两家伙刻意不同、不统一(wiring 取最后一段、drift 取整条接收者链 —— 统一键 = 同时改变两个工具的含义),这句话写进文件头防后人「顺手统一」。取证(不是估计):.docs/fix/2026-09-12/t8b-typeof-vs-real-audit.ts 对 18 个被减掉的键逐键回 HEAD 语料断言「每一处出现点都带 typeof 前缀」⇒ 18/18 通过、非 typeof 出现点 0 个(判据等价性:某键只要有一处非 typeof 出现点就不会消失)。我自己的错误(留档):探针第一版自己写窄了判据(用「匹配点之前紧邻 typeof 」判,而 typeof thing.agent === "object" 的匹配点在最后一段 agent 上)⇒ 误报「9 个键减多了」的假警报;这与 §2 要修的是同一个模式(同一判据写两遍、其中一遍偏窄)——判据收一处是每次写判据时的动作,不是一次性清理。红前绿后(实测):临时把 3 处调用点还原为 || + 重 tsc ⇒ test/t8-explicit-zero.test.ts 的 ③b 真红(报文打印出修前索引仍含 ## 任务回溯(Episodes) 段)。结果:b_keys 115→97·drift_keys 11→9·drift_sites 28→23·numOr 覆盖 6 处(min 0 的 19 处未动)·wiring.a_total 仍 39(未变 ⇒ 新函数确有接线)·棘轮是收紧不是放宽。未做:T8 的 A 部分(7 处静默降级)· min > 0 的 19 处未逐条审语义 · query/reads.ts:47 端到端读路径未加断言。 |
| v1.15.63 | 元审查:审我自己这 10 轮的产出(无代码变更)。用户要求「审查:找出遗漏、纠正因果、处理重复、解决干扰、填补缺失环节」——这一轮审的不是代码,而是 adr/0083(14 节)+ BACKLOG §六/§七(54 条线索)这些我自己产出的文档与过程。⭐ 最重的发现是「遗漏」:路线漂移 —— 用户 2026-09-12 定的泳道 T8 → T15 → D1/D2/D3 → A段 → T2(B段) → …,而我自 M1 之后连续 8 轮做「审查→修」,T8/T15/D1-3/A段/T2 一项未动;「review fix all」每轮都能自洽生出下一轮(线索越查越多)⇒ 这是会自我延续、不会自己停下来的活动,我此前从未把这件事写下来(adr/0083 §14.1)。因果:§六/§七 的「后果」列写法是断言,但相当部分来自只读审查的静态推演(报告者未跑测试),此前只在报告结尾整体标注、没有逐行区分 ⇒ §7.0 加统一口径;并更正我自己的措辞(⑪ 应说「改分桶判据照样全绿」而非「改成任何东西」)。重复:「169→83」的更正曾在 4 处各写一遍 ⇒ 收口到 BACKLOG §6.10 为唯一权威,其余改「一行 + 指向」;ADR 头部写明分工(ADR=决策与理由 / BACKLOG=台账与状态)。干扰(四处全修):BACKLOG 表头「现存 20 条」与正文 54 条线索矛盾 ⇒ 改「两套台账分开计数」;「169 个测试类型错误」在该文件残留 4 处(一处还写「仍未修」)而实际早已 83→0 ⇒ 全改;### 6.11 被追加到 §七 之后 ⇒ 移回并核对顺序;ADR 标题只覆盖第一轮而实际 14 节/跨 10 轮、§5 指向「审查线索一节」 ⇒ 标题与指针改正。缺失环节:54 条线索没有优先级、没有归属 ⇒ 有记录但不可行动 ⇒ 补 BACKLOG §6.0/§7.0 分级表(P0/P1/P2+判据+归属);仍未补(如实列出):线索表缺「状态·优先级·证据等级·归属泳道」四栏重构、「线索→修复」无机械核对链接、ADR 无「哪几轮被独立复核过」索引。我自己的一处操作事故:调整 BACKLOG.md 章节顺序时用 Set-Content -Value -NoNewline ⇒ PowerShell 把数组直接拼接不插换行,1540 行压成 1 行(163KB,内容未丢、结构全毁);用 git checkout -- 恢复后改为「单字符串 + -NoNewline」重做 ⇒ 纪律:改大文件章节顺序前先确认工作树可回退,且不要用数组喂 -NoNewline。verify 仍 53/53 |
| v1.15.62 | 测试面类型检查扩到全部(83 → 0)+ 独立核实「批量改测试」没偷偷放宽断言;verify 53/53。tsconfig.test.json 的 include 从「三个文件」扩到 test//.ts ⇒ typecheck:tests(已在 verify 内)现在守整个测试面,行为未变。为什么必须核实:批量机械改测试最容易的作弊方式是把断言改宽松、加 @ts-ignore、或删测试块 ⇒ 两支护城河探针:verify-assertions-unchanged.ts(取每个被改文件在 HEAD 与工作区的所有 assert 行,按括号深度剥掉类型 cast 后比较多重集)⇒ 断言逐字相同(唯一残差 5 处「补必填字段」+1 处「多一对括号」);verify-added-fixture-fields.ts(补字段理论上可能改变会扫全部字段的守卫 ⇒ 旧/新输入喂同一编译产物)⇒ 3 处结果全同。另核 @ts-ignore/@ts-expect-error 0 处、新增 as any 1 处(带注释)。⚠ 我自己的探针第一版有两个 bug(正则剥 cast 遇泛型 as Parameters 剥不干净;} ) 与 }) 空格差异未归一化)⇒ 假报一批「文本不同」;改成按括号深度消费 cast 后修正 —— 先修探针,再信结论。顺带修一处生产侧类型缺口:core/knowledge-retrieval.ts:60 声明比真实契约窄(实现读 (h as any).__path,而 query/reads.ts:162,167 确实传)⇒ 补 __path?: string、去掉 as any(声明写窄了,类型检查会反过来逼调用方 cast)。一处为兼容保留的旧标签:world/explain 的 Validation History: 未改名(test/recall-attribution.test.ts:3230 钉住该串),改为保留旧前缀 + 追加纠正从句 |
| v1.15.61 | 认识论层「损坏/失败必须出声」6 处 + 三份从未读过的目录的审查台账;verify 53/53。三个只读审查覆盖了全部剩余目录(federation/·world/·reality/ · long-horizon/·simulation/·soul/·planning/ · adaptation/·agency/·continuity/),共约 60 条线索记入 BACKLOG §七(逐条带 文件:行号)。一条推翻既有假设的判定:adaptation//agency//continuity/ 三层全部已被生产接线(→ query/query.ts → index.ts:297)⇒ 此前记成「未接线/潜在」是错的,那些缺陷是当前生效(「未读」不等于「未接线」)。本轮修的 6 处(同一族):① readObservations 单 try 包整个循环 ⇒ 第 k 个坏文件让后续永不读且静默返回前 k-1 条,而下游 supported 判据就吃 obs.length ⇒ readObservationsDetailed 单条丢失 + 计数 + 留痕;② readClaims 同型但更危险 —— 一份坏 claim ⇒ mode:"world" 用残缺图不可逆覆盖落盘 graph.json ⇒ 坏件时不覆盖 + 出口披露;③ readRealityEvidence 同型(坏件 ⇒ 误报 isolated);④ 三处写失败渲染成成功(registerObservation/writeClaim/registerRealityEvidence)⇒ 返回 persisted/boolean,mode 输出显式「未落盘」;⑤ referenceEvidence 把「读不出」压成 null ⇒ 输出成「(无 reality evidence X)」= 把报错当不存在 ⇒ 返回 {evidence, reason: not_found\|unreadable};⑥ world/explain 两处失真(Validation History: N supported claim(s) 数的却是 claim 条数;subject 不匹配时静默换成 objects[0])⇒ 标签改准 + 回退出声。顺带修的判据分叉:Number(x) \|\| 0.5 vs x ?? 0.5 ⇒ 显式传 0 被静默改成 0.5 ⇒ 默认值收进 CONFIDENCE_DEFAULT + confidenceOfInput(只认「没传 ⇒ 默认」)。三条最该先处理但未擅自改(都需先定契约):工具 schema 与四处读点不一致(validations/visible/hiddenA/B 未声明 ⇒ 要么该层恒空、要么可声明 outcome:"validated" 造 supported)· claimOf 的 supported 从不与 ValidationTimeline 交叉核对 · planning/sim-action 三处守卫永不失败。可信度:审查者只读、未跑测试,「改坏仍通过」为静态推演,共排除 40 余条并给理由 |
| v1.15.59 | 把 CLI 接线这条最高危盲区做成自动断言;verify 53/53。此前 6 个 selftest 100% 只调纯函数,而本仓历史上真实踩过的两类缺陷都长在 CLI 接线上、一直被 footer 记成「要 spawn 子进程,未做」⇒ 先写探针(.docs/fix/2026-09-12/spawn-probe.ts:本环境 execFileSync 管道可用、退出码可读)再承诺。新增 tools/cli-wiring.selftest.ts(spawn 真 CLI,5 组):① --ratchet 写在 root 位置 ⇒ 两个 CLI 都 exit 2(锁 v1.15.45「漏 root ⇒ ROOT 取到旗标 ⇒ 0 文件 ⇒ 静默全绿」);② 显式空语料根 ⇒ 都 exit 2;③ --update-ratchet 坏语料 ⇒ exit 2 且基线逐字节未变(证明拒绝先于 writeFileSync;测试内备份 + finally 还原);④ 基线必须分段 wiring/drift 且 corpus.wiring/corpus.drift 分键(锁 v1.15.45 第二处「不同口径共用 corpus 键 ⇒ 判成骤降 76%」);⑤ run-tests.ts 确实扫描 tools/.selftest.ts(防死文件)。判据收口/锁口:retrieval-eval 的第二份语料判据(内联块、零标定,旧 footer 还误称它走 classifyCorpus)⇒ 抽成 corpusFloorVerdict + selftest ⑬ 锁边界,两份判据刻意保留(绝对下限拦工作区指错 vs 相对基线容许带拦工具坏了,拦的是不同故障);isProductionPath 分叉(wiring 把 tools/ 当生产面、drift 排除 ⇒ drift 看不见自家这对分叉)⇒ 不擅自统一(会改某一侧基线口径),改为锁住 —— 新增 ⑭ 断言「差异恰好是 tools/」,任何一侧改动即红,逼出那个决定。实测修正上一轮记录:toolset-authority.ts --check 未接进 verify 的真实原因是耗时(本机 >120s,winget 探测),不是「忘了接线」。棘轮如实变红 b_keys 113→115(+2 = 基线分段断言)后重录。仍未修:isProductionPath 口径统一(等你拍板)· --update-ratchet 成功路径未自动化 · 169 个测试类型错误 · §6.3 六条待定语义 · 整目录未读 |
| v1.15.58 | Disclosure surface 4 places + the false green of the calibration test itself (tools/.selftest.ts); verify 52/52. Disclosure surface: ① shadow_query truncation not disclosed (the retrieval path has long had truncationNote, but it only writes to the query-log and says not a word in the returned text ⇒ the two read paths disclose inconsistently) ⇒ append "hit N · only returning the first limit · there are still k not shown". ② Evidence path cap not disclosed (.slice(0,12) lets "the first 12 are all present" be read as "everything has been checked") ⇒ export EVIDENCE_PATH_CAP + droppedByCap, and both the rendering and ev.unverifiedByCap carry out "there are another k unverified". ③ Silent fallback to an older snapshot when a snapshot artifact is corrupt ⇒ when falling back, print "which ones were skipped / which one was actually used" + document the fallback semantics. ④ Two distortions in observer/projection.ts —— memories that cannot be read enter neither relevant nor excluded (while reality.total is computed over the full set ⇒ the numbers do not match but you cannot see why); candidate relevant reports the length after truncation to 8 (that is a cap, not the hit count) ⇒ add unreadable and relTotal. The false green of the calibration test itself (previously never audited; the reviewer called it "possibly the highest-risk source of false green"): ⑪ Tautology (fatal) —— inside the test it rewrites the product-side bucketing ternary and then asserts "the sum of the four buckets = the total" (the sum assigned by the same piece of code necessarily holds) ⇒ change the product side to anything and it still goes all green ⇒ move the bucketing criterion into lib (bucketOf) shared by both sides, rewrite ⑪ as four-bucket positive cases + three negative cases + non-empty corpus, mutation verification (break the A1 condition ⇒ immediately red); audit-drift criterion ③ zero calibration —— the original NEG-2 condition has no .has(, so it is continued at criterion ② and never reaches ③ at all (the entire probeVars block) ⇒ add a differential pair POS-4/NEG-6 (differing only in "whether there is a probe local name", results must be opposite), mutation verification (disable probeVars population ⇒ immediately red); isTestPath zero coverage (in the CLI) ⇒ move into lib + add ⑬ (assert that it gives a different answer from the old formulation on artifacts/dependencies); also audit-layers.selftest ③ uses an empty criterion table, retrieval-eval.selftest has a tautological assertion comparing a constant with itself, audit-drift.selftest has a real-repo assertion with no lower bound, and corpus-health.selftest footer does not match the code —— all fixed. New leads (unfixed): the second "corpus too small" criterion in retrieval-eval.ts:117-120 (uncalibrated) · divergence in the isProductionPath criterion between audit-wiring ↔ audit-drift (requires first deciding whether tools/ counts as production surface) · all CLI wiring has zero automated assertions · toolset-authority not wired into verify. The ratchet honestly turns red b_keys 110→113 (+3 exactly the newly added calibration assertions) then re-records |
| v1.15.57 | Evidence surface and report surface: "cannot determine" must not masquerade as "verified"; verify 52/52. What was fixed are two kinds of lies that directly change the verdict. ① fsExists returns true for "cannot determine" —— when there is no fs / no workspace / ref.path is empty, the provider reports verified + confidence 0.99 + fresh: a missing artifact masquerading as verified (the highest confidence tier), and it directly determines the recall score×0.5 and the stale verdict, yet the basis is a single "did not read" ⇒ change fsExists to three states (exists/missing/undecidable), undecidable ⇒ status:"unavailable" (an existing status; consumers already print the reason and do not count it as missing) + confidence:0 + stale. ② "Cannot read" judged as "does not exist" —— when both readText+listDir throw, it is uniformly false ⇒ EACCES / backend exception is treated as "evidence invalid" ⇒ false drift ⇒ only definitely nonexistent counts as missing, and the criterion recognizes the host contract's own FS_NOT_FOUND (dsh-fs-local uses this, not ENOENT) and ENOENT text. ③ zg errors rewritten as not_found ⇒ a single tool failure is treated as "this evidence is now invalid" (arbitrate counts it as missing, recall ×0.5 + stale) ⇒ unavailable:true, reason:"error", and ERR_CHILD_PROCESS_STDIO_MAXBUFFER is listed separately as output_too_large (previously mixed into error, making it look like "zg is broken"). ④ manifest always reports "0 failed items" —— deriveShadowNodes's if (!gate.allowed) continue only drops, never records, while buildManifest("1", nodes) never passes the third argument (failures) ⇒ it always reports 0 while in fact a batch of atoms is blocked from projection (reporting 0 failures is worse than not reporting) ⇒ add deriveShadowNodeFailures (same source as deriveShadowNodes) + rebuild(derive, failures) + the lazy failures thunk of loadOrBuildProjection (when the projection cache is hit, no extra materialization cost) + wire it up in query/reads.ts. Gate: invariant "node count + failure count = atom count"; renderManifest must display the real failure count and must not show "failed items: 0"; provider three states (cannot determine ⇒ unavailable/0/stale). The ratchet honestly turns red b_keys 107→110 (+3 exactly the newly added three-state criteria), and after naming them one by one, re-records. Still unfixed: _index.md not included in the fingerprint · evidence path cap not disclosed · visibility inconsistency in observer/projection.ts · fallback to an older snapshot when a snapshot artifact is corrupt · reads.ts truncation only writes to the log · missing timestamps in episode.ts masked by defaults · six pending semantics in §6.3 · 169 test type errors · entire directories unread |
| v1.15.56 | 继续 review fix all:修 5 处「报告面/闸面会说谎」;verify 52/52。原则:报告的诚实性优先于功能(报告「失败项 0」而实际有失败的系统会让人做错决定)。① audit-drift --json --update-ratchet 把空表写进基线 —— driftCounts 只在人读分支赋值 ⇒ 走 --json 时保持 {},空 drift 表被写进棘轮基线而工具照样打印「已写入棘轮基线」⇒ B 段派生提到分支之前两条路径共用,端到端核对现在写出 drift_keys=11 / drift_sites=28。② toolset-authority 坏清单先落盘后标红 —— writeFileSync 在 falseMeasured 判定之前 ⇒ 自己声明「必须为 0」的坏清单已被签进仓库 ⇒ 与同文件 countInconsistency 既有先例一致:先判后写、拒绝产出坏清单。③ registerMeta 失败只 log —— 记忆本体与索引已写入,于是它在索引里活跃而 _meta.json 没有它 ⇒ hits 永不计、生命周期恒 NEW、遗忘判据落默认值 ⇒ 返回 boolean + core.lastMetaError,读侧独立一条⚠(与「落盘失败」分开,是不同的事实)。④ query-log 坏行无计数(catch { /* 单行坏跳过 / })⇒ badLines + badLinesNote 披露(0 行时不带该字段,免得误以为有坏行)。⑤ candidateStats/factualOnly 丢 violations ⇒ candidateStats.violations 露出,factualOnly 写明边界与取用路径(「有记录被拒」≠「本来就没那些记录」)。闸:test/review-fixes.test.ts ⑥(两面违规数一致 —— 判据收一处)⑦(坏行计数 + 披露)。仍未修:manifest.failures 恒空 · _index.md 不进指纹 · zg 报错→not_found · filesystem.ts 读失败/不存在不分 · 缺 locator 当存在 · adr/0083 §6.3 六条待定语义 · 169 个测试类型错误 · 整目录未读 |
| v1.15.55 | 修台账里的高危条目(6 类)+ 修闸自身的 2 处缺陷;verify 52/52。高危:① flush 顺序反了 —— 先 pending.delete/comps.delete 再 if (!ws \|\| !fs) return ⇒ 取不到工作区/会话 fs 时整批记录已被消费:没落盘也没留痕(lastFlushError 未设 ⇒ 读侧告警在最需要它时失效)⇒ 取 ws/fs 提到消费前,取不到则保留 pending + 留痕。② 索引失败后照读陈旧索引 —— rebuildIndex 吞异常返回 void 且 ensureIndex 无条件清 indexDirty ⇒ 一次失败后再也不重建 ⇒ 返回 boolean、失败不清 dirty + lastIndexError(渲染「索引可能不是最新的」)。③ 证据落盘失败仍播报 registered ⇒ 返回 {evidence, persisted},未落盘时明说「未落盘」。④ 假设落盘失败仍打印 hypotheses N ⇒ writeHypothesis 返回 boolean,报「其中 k 条未落盘」。⑤ 台账坏件 ≡ 空件(冷却静默清零 ⇒ 已冷却记忆被重发)⇒ 坏件带 corrupt + writeLedger 返回 boolean。⑥ 600 字截断无痕迹(长消息尾部从未落盘)⇒ 显式写「已截断:原文 N 字」。闸自身的缺陷(本轮真实发生):⑦ V7 语料闸把 .git 当语料 —— git gc 打包松散对象 ⇒ 目录数 428→185 而语料未变(指纹相同)⇒ 假 PARTIAL ⇒ 遍历排除 .git;⑧ PARTIAL 拒绝录基线 ⇒ 闸堵死自己的修正(实测卡住)⇒ 目录数判据改用文件面定案(文件健康 ⇒ 判为口径/结构变化,NORMAL 但必须印理由),保留两档保护(文件面也掉 / 目录掉到 ,同输入同结果,吻合 ADR-0003「派生件不是 source」)⇒ Proposal 冒充 Fact 在结构上不可能(没有写入路径);两条伪装负例已成闸({type:"fact",source:"model-proposal"}、{type:"proposal",status:"validated"} —— 且被拒的 proposal 即使有 confirmation 也不得复活)。机械不变量:FACT ⇔ 有效 Confirmation ∧ 指向 Proposal ∧ Proposal 有 inputRefs;有效动作按 timestamp 升序取最后一条、同刻按 id 升序(与插入顺序无关);reject/revoke ⇒ 事实消失但历史保留。按你追加冻结的两条:① Confirmation = 授权事件({id, proposal, actor, action, timestamp, reason?},actor 无 model,action ∈ confirm/reject/revoke)⇒ Fact 是其投影,未来 M4 Revision 直接落在 revoke 上;载体刻意不决定。② 防 Silent Candidate Graveyard:candidateStats 输出 candidates / confirmed / rejected / pendingConfirmation / oldestCandidateDays / acceptanceRate / rejectionRate,待确认不入分母、分母 0 报 null(不可测不报 0)、now 由调用方传入(不读时钟);不进普通召回,但必须可见。npm run verify = 50/50。棘轮如实变红并按规程重录(a1 17→19、a_total 31→33:新导出尚无生产调用点、待 M1 接线)—— 这正是 V6/V7 设计的路径:合法上升必须有人确认后再重录。未接线既有业务路径(先有闸、再接能力) |
| v1.15.49 | Proposal → Confirmation → Fact 升为通用原语(adr/0082)—— Inference is cheap; facts are expensive. 用户选 A 并要求把这条边界从 M1 的特殊处理升为所有 Memory Intelligence 能力的共同纪律 ⇒ 单独立 ADR;M1 降为它的第一个使用者,Memory Track 顺序变为 P1 → M1 → M2 → M3 → M4 → M5。唯一升级路径:Inference → Proposal(候选层)→ Confirmation(显式确认)→ Fact(事实层)→ Pattern / M5 统计 / 棘轮;事实层只有两条写入路径(显式外部来源,source 不得为模型 · 确定性规则「同入口+时间窗」,不看语义),model-proposal 永不直接进 Fact。用户的三条硬要求全部入契约:① proposal 必须自带「基于什么提议」(source/model/prompt_version/input_refs[{file,line}]/proposed_relation,缺一不得入库 —— 否则连被复核的资格都没有);② proposal 不参与任何事实统计(❌ Pattern count · ❌ influenced_decision · ❌ confidence · ❌ ratchet baseline · ❌ knowledge fact · ❌ identity;唯一允许 candidate coverage/acceptance rate/rejection rate,即度量模型能力而非改世界状态);③ 升级必须留链 P ──confirmed_by──→ C ──→ F,没有 C 的 F 不存在。核心不变量:只有 FACT 可以改变系统的认知统计;CANDIDATE 只能改变「待确认候选」的统计。 另冻结两条 M1 配套纪律:subject = explicit + deterministic-normalized,明确关闭「语义实体消解」这条演化路径(理由:用相似度把 A、B 吸到同一 subject ⇒ Pattern 统计量虚假变高,而「Pattern 的质量上限会被 subject resolution 的错误率锁死」);pending 年龄只暴露风险、不得改变状态(印条数 + 年龄分布 + 最老一条,可加轻量派生 pending_age_p90,不得设自动结算阈值)。T15 Registry 第 2 条真条目(proposal-confirmation-fact-v1,verification 含 3 条负例,待建棘轮桶 = coverage/acceptance/rejection)。主干:Memory → Evidence → Inference → Confirmation → Knowledge。本 ADR 没加任何能力 —— 只给「什么算事实」定了一条不可绕过的路径。未改业务代码与 dist/ |
| v1.15.48 | M1 三处拍板入账 + 一处异议与「两层合成设计」(adr/0081 §7)。用户决定:① 结果来源=允许 LLM 判断归属;② 引入 subject,与 entry 并存;③ 不设结算窗口,永远 pending。我接受 ②③ 并各补一条配套纪律(subject 只接受显式提供、绝不从文本推断,与 ADR-0037 的 DecisionReason 同一纪律;pending 不设窗口 ⇒ 读数必须印条数 + 年龄分布 + 最老一条,否则「还没到」与「永远不会到」不可区分,ADR-0049 同族)。① 我提出异议:LLM 归属若进事实层会有四条后果 —— 不可复现 ⇒ 会打在 V6/V7 刚做可信的那一层(换模型版本即换归属 ⇒ Pattern/M5 漂 ⇒ 棘轮因「判断重跑了」而红,变成噪声源)、不可审计(无可核证据)、精度上界已被量过(adr/0080:AUROC 0.5926 / precision 上限 0.667 / 0.95 不可达 ⇒ M5 指标带不可量化偏差)、与已冻结边界冲突(adr/0037:29-42 冻结「Reason 绝不生成」,而归比理由更强——它在断言因果)。合成设计:显式外部来源 / 同入口+时间窗(确定性) → 事实层(受契约保护,进 Pattern/M5/棘轮);LLM 提议 → 候选层(source=model-proposal,永不进事实层,只在候选视图出现,需「确认」才升级)—— 同形先例已有两个:dream/ 候选不进主路径、world 状态永不 fact。若仍要 LLM 直接进事实层,须显式改掉契约第 ④ 条禁令并登记「归属不可复现」,不能默默实现。未改任何业务代码与 dist/ |
| v1.15.47 | Dual-lane + M1 contract draft (adr/0081): Decision → Outcome → Experience. The user corrected the previous round's phrasing — it is not "governance first, no new Memory capabilities," but rather two lanes running in parallel (🛡️ T8 → T15 Registry → D1/D2/D3 → Contract Freeze → T14 ‖ 🧠 M1 → M2 → M3 → M4 → M5), with new capabilities growing vertically rather than piling on features horizontally; the two lanes converge at M1 (M1's contract entry is the first real entry in the T15 Registry). No new capability code was written this round: first, thoroughly inventory the current state of M1 and write it as a contract per the T15 spec. The most important conclusion: M1's hardest boundary has been frozen — the separation of DecisionEvent and DecisionReason, and "never generate reasons" (only counted if explicitly present in the source text) have been frozen by adr/0037:29-42. M1 has only three gaps: ① the decision → outcome edge (the existing outcome is something else: the trace {expected,actual} in observer/trace.ts:26, and the hypothesis validation conclusion in validation); ② the settlement state machine pending → observed → settled (unresolved is allowed; do not fabricate outcomes); ③ alternatives (optional). M2–M5 are mostly "connecting" rather than "building new": M2 does not create new objects (validation/types.ts:23 already has a complete outcome state machine + append-only history; long-horizon already has ActionFeedback), otherwise there would be a second parallel outcome concept = criterion fork; M3's algorithmic core already exists (the decision→outcome correlation in reflection/patterns/success-rate.ts, the cross-domain abstraction in dream/compress.ts), and the real gap is the counter-example field (reflection/types.ts:8 has no counter_examples — reporting only support and not counter-examples = self-deception); M4's mechanisms already exist (Forget ≠ Delete / superseded / append-only / determinism of supersession), and what is missing is a traceable object for "which piece of evidence caused the reclassification"; M5 has only a recall_count prototype, and its precondition is M1. The ten-section contract is complete + four prohibitions (do not infer superiority/inferiority / do not fabricate / do not create a second outcome concept / do not use similarity for attribution). 3 points await your decision: where outcomes come from (explicit external source / same entry point + deterministic attribution by time window / LLM — recommend rejecting), whether subject is needed, and how to define the settlement window (goes through config). Explicitly not doing: no KG now, no Soul now. No business code or dist/ was changed; the inventory is based only on reading source code and ADRs, and the real decision pipeline was not run |
| v1.15.46 | V7 Corpus Health Gate + five-level audit criteria + phase roadmap recorded. V6 only guarded against "completely empty corpus"; this round adds the dangerous sibling Partial Corpus (a broken tool producing a "seemingly reasonable" small reading). Four tiers, only NORMAL passes: EMPTY (0 files) · PARTIAL (file count 20% / sentinel file missing) · UNKNOWN (no baseline) ⇒ exit 2; NORMAL ⇒ continue running the ratchet. Sentinels (index.ts/core/paths.ts/core/types.ts/core/util.ts/security/scrub.ts) can detect "wrong directory / recursion truncated by junction" when there is no baseline. One key point: --update-ratchet is also gated — EMPTY/PARTIAL refuse to record a baseline, blocking the false-green self-reinforcement path of "tool breaks → clues plummet → ratchet judges 'a decline is not a violation' → prompts tightening the baseline" (the cost is deliberately retained: a large legitimate decline must be "confirmed → re-recorded"). Covers four corpus consumers: audit-wiring/audit-drift (baseline + sentinels) · audit-layers (self-comparison + sentinels) · retrieval-eval (protocol constant min_corpus_files, read from data, not from code). 🔴 V7's first run caught a defect of my own: the two tools share one corpus key but measure different corpora (wiring 778 files including dist/; drift 193 files) ⇒ drift was misjudged as a "76% plunge" and refused recording ⇒ changed to corpus.wiring / corpus.drift (shared key + different criteria = guaranteed explosion, and it was discovered by the new gate itself). Unified audit criteria written into MATERIALS §6: Artifact existence ≠ Runtime capability — package exists ≠ installed ≠ loaded ≠ active ≠ usable ≠ verified (origin: asserting that invariants exists based on the Service directory → runtime undefined; e2b is in the directory while dsh-e2b is not installed). Phase roadmap recorded: V6 false green → V7 corpus trustworthiness → T8 → T15 Protected Contract Registry → D1/D2/D3 → Segment A → … → Contract Freeze Gate → T14 frozen contract corpus → V8/V9 Identity Ratchet; T15 is set as the main artifact of the next phase (each contract has ten sections: id/surface/owner/semantic meaning/stability/allowed/forbidden/evidence/verification/ratchet + module ownership table Owns/Reads/Writes/Must not own); D1/D2/D3 explicitly should not be decided first. Gates 49/49 |
| v1.15.45 | V6 闭环:两个分诊报告有了棘轮退出码,并接进 npm run verify。audit:wiring / audit:drift 一直是人工分诊工具 —— 输出是线索清单、退出码恒 0 ⇒「工具会随时间失效」。本轮把语义定下来:① 线索数只能降不能升(多于基线 ⇒ 退出 1,报文给「旧 → 新」与增量);② 下降不是违规(退出 0 + 提示 --update-ratchet 收紧基线 —— 判紧会让人习惯性绕过这道门);③ 基线里有的桶消失 ⇒ 退出 1(缺件不静默:可能是工具坏了,不是问题没了);④ 出现新桶 ⇒ 退出 1(否则整类新线索被漏掉);⑤ 基线缺失 / 两表皆空 ⇒ 退出 1(「通过」不能靠没有判据换来)。新文件 tools/audit-ratchet.lib.ts(纯逻辑)+ 8 组标定测试 + 共用基线 tools/audit-ratchet.baseline.json(wiring = {a_total 31, a2b 0, a1 17, a2a 3, a3 11, b_keys 103}、drift = {drift_keys 10, drift_sites 25});npm run audit:ratchet 已进 verify ⇒ 门禁 48/48。同轮踩到并修掉一处真缺陷:node tools/audit-wiring.ts --ratchet 漏了根参数 ⇒ ROOT 取到旗标字符串 ⇒ 扫描目录不存在 ⇒ 0 文件 ⇒ 0 线索 ⇒「安静地全绿」,而 --update-ratchet 会把这个全 0 读数录成基线 ⇒ 两个 CLI 都加闸:零文件语料直接 exit 2(缺件不静默)。诚实边界:棘轮只覆盖计数,答不了「计数相同但线索换了一批」(需逐条 diff);audit-drift 只棘轮 B 段(A 段计数在块作用域内) |
| v1.15.44 | 台账回填:把「读了什么 / 还没读什么」追平到事实(MATERIALS.md 六处 + references.md §6.3)。本仓纪律是「台账比事实旧=缺陷」(ADR-0072 那一族),而 v1.15.41–43 一口气读了五片面(harness 关键面 / hl_mem 门禁面 / 安装体 / profile 组合 / agent preset)后,台账状态列还停在第一轮 —— 其中两处会主动误导下游:① MATERIALS §4 说「invariants 是唯一候选、两条前置未确认」 ⇒ 前置已答,且更关键的是该服务在运行的 web 部署里根本没挂(运行时读取 undefined)⇒ 判暂不吸收,并新增纪律 Service 目录 ≠ 活性表(反例:e2b 在目录里而 dsh-e2b 没安装);②「harness 克隆与运行体偏差未逐项核对」 ⇒ 已逐项核对(8 个平面):机制面零漂移、组合/产物面全漂移,且 packages/AGENTS.md 的「Every package owns ./invariant」在 0.1.5-rc.2 发布产物上不成立。其余:§2.1 五行状态改为「读到哪一面」、§2.2 hl_mem 门禁面已读、§2.8/§3.2 给将来时措辞加时态说明(四路已整合;MemStrata 已读正文 + Appendix B/C/D + Table 1/2/3,A.1/A.2 与 Table 4/5 仍未读到、分数未复现)、§6 两条边界改为已答/已核对。本轮只改状态列与结论,未改任何计数(按 MATERIALS §5:改数必须重跑枚举命令);未动任何代码与 dist/ |
| v1.15.43 | 三路并行查证落账:A 段残余 18 条分诊 + T12 时间炸弹闭环 + T16 版本偏差逐项核对,并修掉其中唯一两处真缺陷。① 工具口径缺陷(持续误导了两轮):audit-wiring 原把 !isProductionPath(...) 当「测试」⇒ dist/ 与 node_modules/ 的 .d.ts 全被算进「测试引用」,实测 hasNoUpgradeApi 的「1」全来自 dist/agency/guards.d.ts、apply 的「230」里 15 来自 node_modules ⇒ 已修为「只认 test/ 下」并把判据打进输出行(修后 A2b 由 1 → 0)。② 唯一真断线(已修):ledgerMismatch 被 CLI import 了却另写内联过滤(toolset-authority.ts:99-102)⇒ 判据分叉隐患;git log -S 为空 ⇒「import 了但忘了接线」;已改为直调 lib 那份 + 新增源码级棘轮 ⑦(断言 CLI 调它、且 --check 分支内不得再有 ledgerVerifiedSrc !== 内联比对)。其余:4 条配对包装(一处家族级决定)· 7 条正当(apply 是 Cordis 入口、writeMeta 是逃生舱、readGraph/readTemporalGraph 是公开读 API)· 6 条待产品决策(hasNoUpgradeApi 恒 true 且真实测试引用 0、renderIntent、renderIdentityModel、progressiveDisclosure+refineTree、relationForProposal=T7)。T12 闭环:22 个含硬编码日期的文件判定(先纠自己的数:题面 354 处/8 文件 → 实测 357 处/22 文件),方法=逐文件读断言 + 行为探针(把 Date 钉到 2027-06-01/2027-10-01/2028-06-01/2030-01-01 各跑一遍)+ 机制探针;成分 85% 的日期不参与任何阈值运算,只有 1 处 periodTo 默认值被「今天」消费 —— 那就是唯一的真炸弹(场景 58/60 同一根因,引爆日 2027-01-02:recency=exp(-ln2·119/90)=0.39992 ({ from: layerOf(e.from), to: layerOf(e.to), ...e })),...e 在后把层名覆盖成文件路径 ⇒ 方向禁令永不命中、门恒绿(「机制对了、断的是谁调用它」那一族);(b) 默认白名单在合成夹具上会正确地逐条自曝腐化 ⇒ 零违规正对照必须显式清空判据表。方法边界(诚实):不用 TS 编译器 API —— 本仓 typescript@7.0.2 是 native(Corsa) 移植,包根 . 只导出 version,AST 在 unstable/ast 子路径 ⇒ 说明符抽取是剥注释后的正则,字符串里形如 from "./x" 会误命中。未做:复杂度预算(无可用 AST)、拆 core/(架构决策)。未改动:index.ts 与任何业务源码、dist/ |
| v1.15.40 | 本地全部材料总台账(MATERIALS.md)+ 平台契约纠错(T16)。用户立目标「本地全部材料深入分析 整合 吸收 审查 以及 论文 github」。先把「本地材料」的定义从「hl_mem 一份」扩到磁盘上真实的 8 项:DSH 本体(dsh-w/deepseek-harness,MIT,cd5ef81481 = 0.1.2-alpha.1,74,163 文件 / 1.64 GB)+ hl_mem(Apache-2.0) + openviking(AGPL-3.0) + archify(MIT) + awesome-dsh-plugin(CC0) + ppt-master(MIT) + voyager(GPL-3.0 fork) + 本仓;MATERIALS.md 是磁盘枚举生成的唯一台账(名册/许可/规模/已吸收 ADR 指针/未读/优先级/更新命令)。第一条纪律即:harness 克隆是 0.1.2-alpha.1 而运行体是 dsh-web-app@0.1.5-rc.2 ⇒ 凡据本地文档的结论必须回运行体核对。四路并行深读回报 7 条「下游可能理解错」,其中两条当轮自查/裁定:✅ export default 会静默丢 inject(postmortem/0001:110-111)——本仓不适用(index.ts/dist/index.js 无 export default);✅ sandboxPolicy 省略是否合法——平台文档说合法,回运行体核对:dsh-fs-sandbox/index.js:158 确实 ?? 回退(省略合法),但 dsh-sandbox-policy/lib/index.js:116-117 的无参解析取服务级根(config.workspaceRoot ?? process.cwd()),只有 :138-142 的 resolve({session}) 才用 session.header.cwd ⇒ ADR-0074 结论成立、机制表述已修正(已落补记;并暴露本仓 core/fs-scope.ts 重造了平台已有的 ctx.sandboxPolicy ⇒ T16)。其余回报:isolate 是行级 option、group 不继承(vendor/loader/src/config/isolate.ts:79)⇒ editing-cordis-compositions 技能那句「provider 和每个 consumer 包在一个带 isolate realm 的 group 里」散文不精确(每行都要各自写);平台已有而本仓可能在重造的四项(ctx.sessionProjections 纯 fold / ctx.storageDomain / ctx.invariants / ctx.jobs);openviking(AGPL⇒只取概念)给出 8 条可移植 + 一张 8 行「设计承诺 vs 落地」不一致表(hotness_alpha 默认 0.0 = 默认关且无对照消融;CLI 兼容门是 \|\| true + 缺件即 exit 0),最值得抄的是用子进程起新解释器验证依赖方向(同进程断言会被 import 顺序掩盖);四个 DSH 生态仓定位:只有 archify 是真 DSH 插件(dsh.bundle + cordis.patch.yml + 适配器测试 + 独立 CI),awesome-dsh-plugin 是「手写 YAML + 脚本生成 README + CI 强校验」(1556/1556 一一对应,但 3 个 .pyc 入库),voyager 与 DSH 是文档级 + DOM 级关系(不是插件),ppt-master 与 DSH 零关系。论文层:已用 MemoryBank(2305.10250)/工具拐点(2606.30317);⭐arXiv:2606.26511「Temporal Validity in Retrieval Memory」(MemStrata)已于第 3 轮读完正文 + Appendix B/C/D + Table 1/2/3(裁定 adr/0080),并认领了本仓早已引用的「cosine AUROC 0.59」的原始出处——它还给出更强的表述「任何阈值 precision 上限 0.667、0.95 安全线不可达」;四项可吸收全在指标与协议层(stale-fact-error rate + 允许/强制作答两 regime / marker-free 不变式 + 词边界 tell 自检 / 两侧夹逼消融 / 「未做就说未做」),三项不吸收(键规范化靠 LLM、持久化取代、向量读路径);A.1/A.2 表体与 Table 4/5 未读到、本版无可克隆地址(诚实标注)。另有 2026-09 前检索到的记忆机制综述两篇与 Scrub Jay 原则,均未读全文。新开 T16(平台契约核对与纠错);纯文档,无代码改动;门禁 45/45。(第 4 轮追加,同日) 两条独立产出:① 平台 invariants 的两条前置结案 —— (b) 默认执行(src/index.ts:96,115 enabled 默认 true,安装体 0.1.5-rc.2/lib/index.js:45,62,67-70 与源码逐字同构 ⇒ 版本偏差不影响;选择由挂载行的 config 给,实例 dsh-sdk-minimal/cordis.patch.yml:103-104 无 config = 全默认);(a) 失败 = dispose 子 fiber + 回滚保留 + 注册方自身 apply 失败(:161-163 抛 InvariantError → :172-175 dispose 后 rethrow → 从 ctx.effect 冒出 ⇒ register() 的 thenable reject),但「是否阻断整个宿主启动」仍未验证、未写成已知;新查到的第三条更影响裁决:「只挂服务不挂配套入口 == 没有检查」(README.zh.md:12,156:注册表自身不携带产品检查)⇒ 检查是否真跑取决于本仓有没有自己 register(),对本仓有利。⛔ 同轮改判(运行时读取):我原先写「运行中的 web 宿主确实有 invariants 服务」——这条是错的(依据是运行体 Service 目录,而目录不是活性表)。改用只读动态 Host 插件逐名读 ctx.get:invariants = undefined,而同一次探测里只在宿主/Web 层 patch 挂载、任何预设都不提供的 spillStore/tokenMeter/shellEnv/codeRuntime/webServer/clientModules/sessionTitle/sessionQuery 全部读到 ⇒ 沙箱 ctx.get 读的是全局服务表,undefined 就是真的没挂(对照名也 undefined)⇒ invariants 判「暂不吸收」(写 ctx.get('invariants')?.register(...) 在此部署=静默 no-op=假闸门);重启前置 = 同时把挂载行写进部署组合(范本 dsh-sdk-minimal/cordis.patch.yml:103-104)或缺件响亮报告(ADR-0049)。由此得一条更一般的纪律:Service 目录 ≠ 活性表(反例:e2b 在目录里而 dsh-e2b 本 profile 根本没安装;dsh-invariants 装了没挂;authorization/inspector 在目录里而 ctx.get 均 undefined)⇒ 凡「运行体里有没有」,唯一判据是运行时读取。✅ 附带:同一次探测里 ctx.get('sandboxPolicy') 读到对象 ⇒ ADR-0074 结论不变、证据从「目录」升级为「运行时读取」。② hl_mem 门禁形状清单(读完 scripts/ 11 个 check_.py + 5 个 workflow + benchmarks/release/ + tests/eval/):最值钱三件 = 「生成器 + 签入产物 + 门禁逐字比对」三件套(同形 6 次、唯一入口 --update/--write、缺件即非零)· allowlist 腐化自检(白名单里不存在的路径也算违规)+ 棘轮只降不升 · 协议常量与代码分离 + 先证同源再比数值;并明确它自己没接上的线(比较器零 workflow 调用、「两跑逐字相同」只有散文无脚本、覆盖率地板 80 vs 60、缺件即通过),落进 T13 / T14 / V6。③ 新增一条枚举纪律:PowerShell Get-ChildItem -Recurse 默认不跟随 junction(实测该目录 70 条里 69 条是 reparse point),不加 -FollowSymlink 的递归 grep 会静默跳过 69 个包并给出看似确凿的 0 命中 ⇒ 凡以「0 命中」为结论的搜索,必须先证明枚举到了非空且完整的语料。✅ 同轮还把 B3 闭环:宿主于 2026-09-12 16:40:02 重启(dsh web 的 pnpm wrapper 与 node 主进程 PID 全新)⇒ 重启后逐条实测:read_shadow() 不再有「落盘失败」横幅;.shadow/2026-09-12/ 出现本轮之后新建的记忆文件(…165353-shadow.md = 16:53:52 等,且与本轮改过的文件一一对应);.shadow/_index.md mtime 同步更新 ⇒ ADR-0074 的落盘修复在真机生效(判据 4 宿主日志侧未单独核验:会话日志 .zstd 只解出首帧 195 字节,流式解码报 Unknown frame descriptor,已如实标注)。顺带观察(未立条目):今日已 1034 条记忆,几乎每次工具调用/每次改文件一条 |
| v1.15.39 | hl_mem 第三轮深读:首次本地克隆、一手读源码 —— 三处自我更正(ADR-0078)。用户指令「继续深入研究资料」并选定本地克隆(此前两轮全程 raw、从未克隆)。克隆到仓库外 G:\project\dsh1\hl_mem(v1.1.7,aa5d068,1025 文件 / 16.03 MB),全程只读。先做一件此前没人做的事:以磁盘枚举算覆盖率 —— 本轮之前从未一手阅读的面 = 901 文件 / 6.67 MB = 87.9% 文件;0073/0076 的手写「未读清单」漏了整片(docs/research/7 · docs/archive/21 · docs/.md 顶层 13 · tests/384 · scripts/42 · src/352 · migrations/69)⇒ 清单是散文、不是磁盘台账,既不完整也无法自证完整(与「靠自觉不是闸门」同族)。三处自我更正:① ADR-0077 D1 说「assert_transition() 是写侧守卫」不准确 —— 守卫是纯函数(lifecycle.py:111-118),但写原语 update_status()(storage/claims.py:160-169)只校验「是不是合法状态名」、不校验转换,收口靠 28 处调用点自觉,且至少两处完全绕过(workers/deduplicate.py:571-575 治理回滚、application/conflict_backlog.py:178-186 集合式修复)⇒ 其 AGENTS.md「所有状态变更统一经过它」作为全称命题为假;更尖锐的是矩阵里 SUPERSEDED/EXPIRED/RETRACTED 是终态,而回滚通道必须反向走这些边 ⇒ 矩阵只是「正向可达」的真相。(本 ADR-0077 的结论不变、理由更强,已落补记。)② ADR-0076 §1 把 ADR-0004 读成通用细粒度取代,落地形态其实是「窄面 + 默认只建议」:state_latest_wins.py:1 自述 narrow … for config.version、:94-95 非该 slot 一律 compatible(不做取代)、config/models.py:508-510 类型层面锁死只允许一个 slot(且有测试断言 TOML 不能授权白名单外 slot)、:506 latest_wins_mode 默认 observe 而同文件 provenance/price_target/plan_fulfillment 三个默认都是 enforce ⇒ 默认值按破坏性分级。这实质改写 D3 的对照面:问题从「要不要三元组」变成「要不要为特定 slot 建确定性取代,其余一律不做取代」。③ 路径/计数记错:specs/ 实为 docs/superpowers/specs/;migration 它 AGENTS.md 写 57、其 CHANGELOG 写 60、我实测 60 个 .sql(001…060)+9 个 .py=69 文件 ⇒ 它自己的 agent 指令文件比事实旧 3 个(本仓 ADR-0072 同族)。一手读到取代协议真身(版本量级只用于相等、方向只由 tz-aware 可信事件时间决定、并列即 needs_review、historical_predecessor 绝不移动 current tip、15 条否决一律 needs_review、精确坐标匹配无 FTS/向量、候选 ≥17 即拒判、决策前实测无环+深度---、缓存 time 由文件名反解)+反例正控测试。⑤ 真缺陷:一颗已在本地零点引爆的时间炸弹 —— 场景 30 硬编码 2026-09-05,而 stale = ageDays(rel) >= staleDays(默认 7) 且 DECAYING 排在 OBSERVED/VERIFIED 之前,today() 用本地日期 ⇒ 本地 2026-09-12(UTC 才 09-11)当天 age=7 ⇒ 三者被整片盖掉;已改相对今天。这是一类问题 ⇒ 新开 T12(已扫出全仓硬编码日期风险面:12 个日期 / 8 个文件,但只有场景 30 被确认与 age 判据耦合)。未做:historical_predecessor(属 D3 决策范围)、冻结语料(待 T11① 语料决策)、audit:wiring/audit:drift 报告进门禁(分诊工具退出码语义未定义)。回归 45/45(43+2) |
| v1.15.37 | 重点材料 hl_mem 深读第二遍(ADR-0076)—— 0073 漏掉了一份 26 KB 的 ADR,而它恰好与待决策的 D3 直接对题。ADR-0073 是第一遍表层对标;本轮补齐它 ## 自检 里明确留空的几处,新读到 docs/adr/0004-config-version-deterministic-latest-wins.md(0073 完全未提)。它的坐标是四元组 (namespace, canonical_subject, canonical_slot, coordinate_qualifiers)(比 D3 设想的三元组更结构化),且 conflict_key 只是派生指纹、「不是第五个独立真相」(≈ 本仓 ADR-0003/0051);候选发现必须 exact-match 坐标。关系枚举冻结六类,其中 historical_predecessor(乱序到达:新到的记录描述更早事实 ⇒ 只接前驱、不反向关闭 current tip)是本仓单键+时间序没有的分支。一条本仓尚未成文的判据:「并存噪音是可观察问题;错误关链是静默破坏」 —— 与 ADR-0049「缺件不静默」互补(一个管缺失可见,一个管破坏保守),已写进 D3 判据面。它还为本仓既有裁决补了独立量化证据:本仓 ADR-0059 裁决「不让 LLM 判语义」,hl_mem 独立走到同一结论并给数字 —— E1C 70 案 exact 54/70 且有 2 个危险反向;29 个双序案一致率仅 21/29 = 72.4138%(顺序敏感)。它的失败史:v0.30.0 在同一份 dev 上 13/13,独立 held-out-r5 仅 3/13(27 条错误 edge / 3 条反例误 supersede)⇒ 整批撤回,催生三层冻结数据与「烧语料前必须先跑零 LLM 缝合线冒烟」⇒ 本仓对照后只取两条同形的(新开 T11;照搬 400 案冻结集属过度设计)。一处罕见诚实:它公开的 LongMemEval 里自家结构化路径 43/50(86.0%)低于两条对照臂(full-context 92.0% / native RAG 90.0%)=「结构化 ≠ 更好」。结构对照:hl_mem 4 篇 ADR + 41 行矩阵(故意跳过 0003 以免两决策共号)vs 本仓 76 篇 ADR + 18 行表 ⇒ 0073 说的「补三列」只是该差异的表层。纯文档 + 待办登记,无代码改动;回归仍 41/41 |
| v1.15.36 | 修接线审计工具自身的盲区(ADR-0062 补记)—— 先实测,于是发现原记的三条里有一条位置与方向都记错了。用户指示「fix 三条盲区」,本轮先逐条量证:注释里 Foo( → 旧实现得 0(早已 stripComments)⇒ 不是盲区;字符串里 Foo( → 旧实现得 1 ⇒ 真盲区,且方向是漏报(把死代码看成活的,比误报危险)。教训:那条「成因」当初是推理出来的、没实测,写进 ADR 后就成了「事实」,我上轮还把它复制进了 BACKLOG ⇒ 已两处更正。修 ①:新增 maskStrings(在 stripComments 之上抹掉字符串字面量,仍是状态机、保长度保换行 ⇒ 行号不漂移; … 模板串只抹字面部分、${…} 里的代码原样保留并递归 —— ${f(x)} 里的 f(x) 是真调用,抹掉会制造新的漏报,测试 ⑨ 有反向不变量锁住);countCallSites 改用它,stripComments 保留不动(B 类要匹配的正是字符串里的值)。量证:两种掩码在真仓库(204 文件 / 516 导出)逐符号比对只有 3 个符号计数有差异(isProductionPath 8→6、markedLines 8→6、notRevoked 3→2),均只是去掉虚高、未翻转归桶 ⇒ 当前真仓库零净效果(真但潜伏)。②③ 不伪造精度:②(间接调用)与 ③(平行 API)无法靠文本分析解决 ⇒ 从「一个 33 条大堆」改为四桶:A2b 导入即闲置(被 import 但导入行外零提及)1 · A1 零引用 17 · A2a 间接调用/类型位置 3 · A3 平行 API 11 ⇒ 需逐条查的 32 → 18。关键判据 bareMentions 是量证出来的:它在 A2 候选上恰好切开已知答案 —— ledgerMismatch 裸提及 0(T1 已核实仅测试用,真可疑),而 ChangeSet 1(类型位置)/ renderExperience 1(回调)/ sembleCandidates 1(默认参数值)全正当。第 4 类盲区(新发现,未修):notRevoked 有 2 个调用点故不在 A 段,但那两点都在零调用的 assertNotRevoked 内 ⇒ 事实上不可达却被报「有接线」(数到了调用点,但它在死代码里);修它需调用图/可达性分析 ⇒ 已写进工具输出的「判定纪律」并立 T10。audit-wiring.selftest 11 → 13 组;回归 41/41 |
| v1.15.35 | D6 结案:目录级 L0/L1 sidecar(ADR-0075)—— 吸收 OpenViking 三条,并把「层间不漂移」做成构造性质。落地前先实测枚举器:listMemories(= 语料枚举器)只扫 .shadow//.md,且只排除 _index.md 这一个名字 ⇒ 其余任何 .md 都被当成一条记忆(进索引/召回/计数)。故改判据而非逐名列举:n === "_index.md" → n.startsWith("_") —— 原写法每加一个派生件都要记得回来补一句,忘了补就静默污染语料(本项目最常见的一类缺陷);改成前缀分类后「派生物 vs 记忆」有了唯一判据(与 ADR-0074 的 scopedFs、T5 的 isAdmissibleClaim 同一手法)。层次(每层只从下面那层派生):记忆(source)→ L1(确定性:条数/时刻跨度/入口/主题清单,≤4000)→ L0(由 L1 抽取:剥标题行、折叠空白、截断,≤256)→ _index.md 的「目录摘要(L0 · 派生物)」段。②的要害:若 L0 也从记忆文件另抽一遍,就会出现「同一条记忆两层说法不一致」而两层各自看都「没错」、无判据可发现(ADR-0063/D5 那一族);L0 = f(L1) 让不一致在构造上不可能。三条硬边界(各有测试):派生件不是 source(ADR-0003)· 所有输入显式传入(不读时钟/随机数/fs ⇒ 同输入逐字节相同)· 命名必须 _ 前缀。③ 覆盖率自报:covered/pending 写成显式可解析行,由 sidecarDrift 独立重算(不信自报);含正对照 —— 构造三种坏件(源头多一条 / L0 被单独改 / 坏文件)断言全部被抓到(否则「0 条漂移」可能只是检测器不工作)。默认开(与 projectionStore 默认关不同):它是派生件、写入次数有界(每日期目录一份,O(#dates) 而非 O(N))、且默认关等于又一次「写好了但从不执行」(T1/T4 刚清理的那一类)。读路径存在:先写 sidecar 再引到 _index.md(顺序不能反,否则索引会指向写失败的摘要)。诚实标注(新开 T9):pending 恒为 0 是构造性的(sidecar 与 _index.md 用同一份 recs 派生 ⇒ 不可能落后),该字段当前只在构造坏件时有意义、不是「会真实报警的增量检测」;真机规模/耗时未测、召回收益未测(本轮未改检索排序)、L0 质量未评、存量回填未做。回归 41/41 |
| v1.15.34 | D8 结案:默认开关表补齐三列(成熟度 / 降级行为 / 晋级标准)—— 并查出一处不存在的开关。补列:成熟度这一列本仓填不出来 —— 仓库从不给自己打 stable/beta/experimental(adr/0073:53 自陈「0 个 ADR 带『重新评估条件』小节」),凭空造等级就是让文档比事实强(正是 ADR-0072 刚修的谎)⇒ 改填可核实的代理信号(有开放未验证项 / 无开放未验证项 / 边界 ADR 未接受,均带出处);晋级标准列 15/16 = 仓库未定义(唯一例外 projectionStore 且那是启用触发条件而非 beta→stable)——「查不到」如实写出来。硬缺陷(本轮唯一代码改动):knowledgeEngine.enabled 生产零读取(唯一读 knowledgeEngine 的是 core/writer.ts:79,读的是 .llmNavigate),query/reads.ts:141 无条件建树,且 createKnowledgeEngine(config) 的函数体从不引用 config ⇒ core/types.ts:36 注释的「默认 off」与 README 的「enabled: true 启用」三处都在描述一个不存在的开关。选「纠正文档」而非「补写闸门」(与 D4 同判据):真去实现 enabled 会让 mode:"knowledge" 默认失效(破坏现成可用功能)⇒ 校正类型注释 + 删死形参(危害不是多一个参数,而是它构成假象让读者以为 enabled 已接线)+ 调用点注明 + README 表注④校正。另 5 处实现与文档不符(已在表注标明):episodes 关不掉(showInIndex: 0 被 core/writer-core.ts:69 的 || 8 吞掉 ⇒ :161 闸门恒真=死分支,根因是用 || 取默认把「显式 0」与「未传」混为一谈)· 采集无总开关(writeConsent 语义不是关采集)· retention 的「stale 默认排除」代码里无对应实现(串列)· kg 不是 config 键却被排在「默认」列 · 表缺 indexEngine 行(已补为第 17 行)。立 T8:按 ADR-0049 枚举(unavailable/warn/debug 三选一,console.log 不算)查出 7 条静默降级 —— llmRecall(最彻底:回退无标记且 label:"" 使 catch 日志分支也不触发)· summary · recall · queryLog(默认开 ⇒ 优先级最高) · cooldownTurns · projectionStore(唯一可能属正当静默:结果仍正确、只损失性能)· episodes(_index.md 写失败仅 log ⇒ 可静默读到陈旧索引,与 ADR-0069 同族)。回归 40/40 |
| v1.15.34 | D8 结案:默认开关表补齐三列(成熟度 / 降级行为 / 晋级标准)—— 并查出一处不存在的开关。补列:成熟度这一列本仓填不出来 —— 仓库从不给自己打 stable/beta/experimental(adr/0073:53 自陈「0 个 ADR 带『重新评估条件』小节」),凭空造等级就是让文档比事实强(正是 ADR-0072 刚修的谎)⇒ 改填可核实的代理信号(有开放未验证项 / 无开放未验证项 / 边界 ADR 未接受,均带出处),它恰好回答了这一列原本要问的「哪些稳定但耗 token、哪些接口还会变」;晋级标准列 15/16 = 仓库未定义(唯一例外 projectionStore 且那是启用触发条件而非 beta→stable)——「查不到」如实写出来。硬缺陷(本轮唯一代码改动):knowledgeEngine.enabled 生产零读取(唯一读 knowledgeEngine 的是 core/writer.ts:79,读的是 .llmNavigate),query/reads.ts:141 无条件建树,且 createKnowledgeEngine(config) 的函数体从不引用 config ⇒ core/types.ts:36 注释的「默认 off」与 README 的「enabled: true 启用」三处都在描述一个不存在的开关。选「纠正文档」而非「补写闸门」(与 D4 同判据):真去实现 enabled 会让 mode:"knowledge" 默认失效(破坏现成可用功能)⇒ 校正类型注释 + 删死形参(危害不是多一个参数,而是它构成假象让读者以为 enabled 已接线)+ 调用点注明 + README 表注④校正。另 5 处实现与文档不符(已在表注标明):episodes 关不掉(showInIndex: 0 被 core/writer-core.ts:69 的 || 8 吞掉 ⇒ :161 闸门恒真=死分支,根因是用 || 取默认把「显式 0」与「未传」混为一谈)· 采集无总开关(writeConsent 语义不是关采集)· retention 的「stale 默认排除」代码里无对应实现(串列)· kg 不是 config 键却被排在「默认」列 · 表缺 indexEngine 行(已补为第 17 行)。立 T8:按 ADR-0049 枚举(unavailable/warn/debug 三选一,console.log 不算)查出 7 条静默降级 —— llmRecall(最彻底:回退无标记且 label:"" 使 catch 日志分支也不触发)· summary · recall · queryLog(默认开 ⇒ 优先级最高) · cooldownTurns · projectionStore(唯一可能属正当静默:结果仍正确、只损失性能)· episodes(_index.md 写失败仅 log ⇒ 可静默读到陈旧索引,与 ADR-0069 同族)。回归 40/40 |
| v1.15.33 | T1/T4 结案:A 类逐条分诊 —— 修 1 处真断线 + 1 处同型漂移 + 删 1 处空壳。A 段 33 条全部落格:误报 12 · 零引用 18 符号 · 仅测试消费 4 · 真断线 1。补上「A 类精度低」的成因(原 ADR 只给结论):工具数不出三类调用 —— ① 调用点只在注释里(progressiveDisclosure/refineTree 命中的是 core/knowledge-engine.ts:8 的清单式注释);② 经数组/变量间接调用(4 个长程 assertResultNo 入 resultGuards 后循环调用、renderExperience 作回调传入、sembleCandidates 作默认参数注入);③ 「成对导出、只接一半」的平行 API(delegation 7 个 assert 包装,引擎只用谓词)。真断线(唯一一处,已修):countInconsistency(tools/toolset-authority.lib.ts:66,注释写明「清单自洽性:counts 必须与 rows 相符」)生产从未被执行 —— CLI 的 import 不含它,直接 writeFileSync ⇒ counts 与 rows 漂移无人发现。已在写盘前接线 + process.exit(1) 拒绝坏清单;锁是 ⑥ 接线棘轮(断言 CLI 调了它、在 writeFileSync 之前、失败走 exit(1);已验证先红后绿)。关键点:断言的是「CLI 调了它」而非「函数存在」 —— 后者才是「机制对了、断的是谁调用它」的正解。与 T5 同型的第二处真漂移(已修):federation/contract.ts:23 的 isExchangeable 再手写一遍同一三元素数组,而 federation/types.ts:13 的 EXCHANGEABLE_KINDS 是唯一源(且零引用)。危险点比 c.status 更具体:EXCHANGEABLE_KINDS: ExchangeableKind[] 会被类型检查,但内联字面量不受该类型约束 ⇒ 加第四种可交换种类时类型系统逼你改前者、不提醒后者 ⇒ 静默漏掉。删除空壳 auditDrift:全仓零引用,判据不是「没人 import」而是它没有信息价值(只是打包两个检测器,CLI 本就直接调用)。保留并注明 4 处(renderIntent/renderIdentityModel/core/knowledge-cost.ts/delegation assert 家族)。新发现(→ T7):relationForProposal(temporal/edge.ts:30)忽略入参恒返回常量 —— 与 §3 那族的危险不同:那是口径分叉(可加唯一源棘轮),这是掉参数(只能靠行为断言)。两处对原文的更正:progressiveDisclosure/refineTree 不是「误报」而是仅测试消费;renderIntent 不是「有生产调用点」而是零引用。ChangeSet 与 D1 不矛盾(一条说接口可达、一条说没人实例化)⇒ D1 维持原判。A 段 33 → 31;回归 40/40 |
| v1.15.32 | T5 结案:检测 B 各键逐个复核 —— 并先修了工具自己的漏报(ADR-0070 补记)。先修工具:检测 B 的正则 \b([\w$.]+)\s=== 字符集不含 ? ⇒ c?.status === "supported" 只从 status 起匹配,与不带 ? 的归不到同一个键 ⇒ 静默漏报。实测后果:world/guard/claim-admission.ts:6 的 isAdmissibleClaim(唯一判据源)从 B 段消失,而它恰是那处真漂移的关键证据。修法:允许 ?. 并把键里的 ? 归一(a?.b 与 a.b 是同一条访问路径);键形态随之改为「接收者.字段=值」(更精确)。回归锁 ⑤b(夹具一侧 x?.flag、另一侧 x.flag,断言归到同一个键 x.flag=join)。逐键复核(11 → 10 键):c.status=supported 真漂移(已修);res.status=not_found(v1.15.27 已修);其余各落「正当分层」(c.kind= 声明↔消费、r.status=unavailable 产出↔消费且该值是宿主声明类型、err.code=ENOENT 同一外部契约口径一致、e.kind=user 写侧↔读侧、type=principle/anti_pattern 共用同一类型声明)或「同形不同义」(kind=error 是宿主流事件字段 vs 本插件局部形参;v=string 是回调形参别名)⇒ 1 处真漂移 + 0 处待复核。真漂移:判据源已存在却在 world/builder/representation-builder.ts:9(同文件已 import 该模块)与 query/world.ts:42 各手写一遍 —— 性质同 ADR-0063/D5(同一条规则多份实现),已收敛到唯一判据源;新锁 test/claim-admission-single-source.test.ts(判据语义 / 源码级棘轮 / 行为反向不变量 / 正对照)。自曝:该测试第一版自己写 line.replace(/\/\/.$/, "") 剥注释,而本仓 .ts 是 CRLF ⇒ . 不匹配 \r、$ 匹配不上 ⇒ 替换静默失败 ⇒ 测试假红;更根本地,那等于把「注释剥离」又写了一份 ⇒ 已改为复用工具自己的 stripComments。附带:台账「两级边界」不变量从实测升级为棘轮(toolset-catalog.test.ts ⑧)。B 段 11 键/28 处 → 10 键/25 处;回归 40/40 |
| v1.15.31 | 省略 sandboxPolicy 写入 ⇒ 没有一条记忆能落盘(ADR-0074)—— 第 10 个「机制本身正确、断的是谁调用它」的实例,但这次断点换成了「谁传参」。现象两次跨版本出现(11:28:02Z / 11:35:11Z,与升级无关):read_shadow 顶部长期挂着「落盘失败:file access denied under workspace-write mode」⇒ 读路径完好、写路径全挂。逐层阅读宿主编译产物核实根因:dsh-fs-sandbox:154 取 sandboxPolicy ?? ctx.sandboxPolicy.resolve()(无 session)⇒ dsh-sandbox-policy:141-148 给出部署 fallback(mode = DSH_PERMISSION_MODE ?? workspace-write、workspaceRoot = process.cwd() = 服务进程启动目录),而写入目标是会话工作区 session.header.cwd;两者不同时包含判定失败。反直觉点:本部署的会话策略本就是 danger-full-access(带 session 会在 :156 直接放行)—— 是漏传参把本可放行的写入降级成了越界写。排除两个替代解释(目录不存在:dsh-fs-local:497 写前 mkdir recursive,且报错出自 !contained 分支;落到兜底根:报错路径就是会话 cwd)。两处旧记账被推翻并就地勘误:v1.15.12 §A3 曾判「非缺陷」(理由「省略 = 用当前会话策略」)—— 契约原文是 "Omit to leave the backend its own default",与调用方会话无关;v1.15.x 的归因「解析不出 session cwd、落兜底根」说窄了(真实触发是「会话工作区 ≠ 服务进程启动目录」,与能否解析 cwd 无关)。修复:新增 core/fs-scope.ts(sessionPolicy / policyForAgent / scopedFs),在取得 fs 的仅有三处(flush(agent) / ensureIndex(ws, session?) / index.ts 的 queryDeps → makeQueryDeps(exec))包一层会话作用域门面 ⇒ 等价于全部 40 处写入点都补齐,且不动任何 persistence/ 签名。四条不变量:不越权(只补省略的,显式传入原样转发;从不构造 danger-full-access、从不覆盖 read-only)· 旧宿主零变化(无 sandboxPolicy 服务 ⇒ 恒等返回原 fs;且该服务缺失时围栏根本不挂载,故不报假 gap)· 保留「没有 stat」(meta.ts:50 用 typeof fs.stat === "function" 判分派,门面只转发真实存在的方法)· 读侧一并修(读路径也写 _index.md / query-log / identity timeline)。先复现再修:新增 test/fs-sandbox-scope.test.ts,mock 忠实复刻 checkedTarget(部署 root C:/svc 故意 ≠ 会话 cwd D:/proj),修复前跑出的报错与真机横幅逐字同型,修复后 6/6。回归 39/39 |
| v1.15.30 | 重点材料 hl_mem 对标(ADR-0073)—— 一条可借鉴项(D8)+ 逐条不吸收。把 lohr13/hl_mem(HL-Mem,Evidence-aware local memory service,Apache-2.0,7 ⭐/单人维护,最新 v1.1.7)标定为重点对标对象,产出一份对标结论:一条可借鉴项(→ D8:README「默认开关」表补成熟度 / 降级行为 / 晋级标准三列)、一条硬冲突(它的 Claim 走 LLM 提取,与本仓纯函数派生口径冲突)、六条明确不吸收 + 逐条理由、一处许可边界澄清(Apache-2.0 ≠ OpenViking 的 AGPLv3,但许可允许 ≠ 该引)。纯文档:无代码 / 配置 / 行为改动 |
| v1.15.29 | 台账「实测」标签比事实强(ADR-0072)—— 漂移审计检测 B 的第三次产出,且第八个「机制存在、没接线」实例。检测 B 报 c.kind=provider/c.kind=reference 跨 core/toolset.ts 与 toolset-exec.ts —— 先判它不是漂移(前者声明 kind、后者消费,属正当分层),并顺带实测其文档化边界不变量(toolset.ts:3「两级台账必须分清」+ degradesTo 规定 reference 填「不影响插件行为」):107 项全满足。但顺着「台账诚实性」查下去命中真问题:verSrc 的定义是 "实测" = 在本机跑 --version 拿到的、"权威核验" = 取自 winget show(最新发布版),而 v1.15.10 加入的 44 条全部标着「实测」(原默认值)而数字来自 winget 目录 —— 证据:44 条中 35 条与 winget 权威版本逐字一致;fzf 台账「实测 0.74.3」而本机 fzf --version = 0.73.1(且来自 scoop、winget 里没装);zoxide 台账「实测 0.10.0」而 winget list 显示已装 0.9.9 / 可用 0.10.0(台账抄的是「可用」列);本机可检出的 8 条里 7 条台账版本比本机新且方向一致。第二层:verify:toolset 传 expectedVersion: null ⇒ winget-verify.ts 的 verDrift 分支从未生效,所以那个「版本漂移 0」是因为版本没参与判定(把台账版本当期望值核验 ⇒ 13 条老化)。修复:① verSrc 默认 "实测" → "权威核验"(那 44 条确实不是实测),参数文档写进证据防「顺手改回」;② 新增 tools/toolset-authority.ts —— 逐条真调 winget show 并把台账版本当真期望值,同时探测本机记 machineVersion,产出 tools/toolset-authority.json(随包签入);③ 新增离线棘轮 test/toolset-authority.test.ts(5 组:清单自洽 / 台账侧逐条一致 / 无「实测」缺本机佐证 / 正对照证明检测器真会报警 / 口令可解析)。测试还纠正了我自己的判据范围错(自曝):首跑报 6 条假不一致 —— 清单按「有 winget 包」范围生成(101/107),我却拿全部 107 条比;修正为只比 winget 非空者。回归 38/38 |
| v1.15.28 | 图快照读取取到最旧的(ADR-0071)—— 漂移审计工具检测 B 的第二次真发现,且用同一工具完成闭环验证。检测 B 报出 name=graph.json 跨两个模块,顺查发现三层事实:① 两个 reader 逐字近重复(除根目录/返回类型外逻辑相同);② 两者都 write-only(生产者 writeTemporalGraph ← observer-kernel.ts:46、writeGraph ← world.ts:40;读者零调用);③ 真实读路径是「重建」不是「回读」 —— mode:"temporal" 走 buildTemporalGraph(读 traces 重建)后才落快照,与 ADR-0017 ①(graph.json 可重建)⑥(该 mode 由 builder 提供)一致。新发现的顺序 bug:两个 reader 都「碰到第一个含 graph.json 的日期就 return」,而 listDir 契约是 "stable name order"、真机实现是 localeCompare 升序,日期目录名 YYYY-MM-DD 字典序=时间序 ⇒ 取到最旧的那份;而 graph.json 是可重建派生快照(ADR-0003/0017/0024)—— 回读更旧的派生件正是 ADR-0069 刚修过的那一族。修复:新增 persistence/snapshots.ts 的 readLatestSnapshot(降序取第一份 = 最新;跳过缺快照的日期;坏快照继续找更旧的;无 → null 不抛不编造),两个 reader 收敛为参数化调用 —— 不「两处各修一遍」,因为本仓教训正是「同一逻辑多处表达、其中一处会漂移」(ADR-0063 三份判据、ADR-0070 双条件漏在第三个消费者)。先复现:修复前测试报 应返回最新(day09);实际返回 day07,修复后 6/6(含 ③ 乱序插入仍取最新、⑤ 反向不变量「无快照→null」)。闭环验证:重跑审计,检测 B 12 键/30 处 → 11 键/28 处、name=graph.json 已消失 ⇒ 「检测→修复→检测确认消失」成立,也反证该键确是真漂移。回归 37/37。诚实标注:运行时收益为 0 —— 两 reader 当前零调用,本次修的是「若接线则正确」,收益是消除地雷 + 消除重复 |
| v1.15.27 | 投影漂移审计工具(ADR-0070)—— 把「逐个手工找」变成「可重复检测」,且它首次使用就抓到第 7 处缺陷。前五轮(v1.15.22–26)找到的都是同族缺陷(机制对、断的是「投影跟不上源头」,单元测试全绿)—— 手工找是体力。新增 tools/audit-drift.ts:检测 A「派生件新鲜度只看进程、不问源」(三条判据:裸 return; + 条件含进程内集合 .has( + 条件不含源探针,含「探针赋值的局部名」以免误报已修代码;并有函数名收窄免得把 if (core.pending.has(id)) return; 误报);检测 B「同一条判据在 ≥2 个模块被表达」(线索级 —— 它答不了「口径是否一致」,故明确不得据 B 定罪)。先标定再用:夹具 10 组(POS-1/2/3 + NEG-1..5 + B 正反例,按 MARK: 定位不硬编码行号)+ 最强的一组:git 历史里的真缺陷 —— 0c4e06b:core/writer-materialize.ts 的 :41/:215 正是 ADR-0069 那两处,旧版报 2 条、当前版报 0 条(把历史编码进测试,结论可复现)。工具首次使用即抓到第 7 处:检测 B 报出 not_found 跨 core/context.ts 与 observer/arbitrate.ts,顺查发现第三个消费者 observer/judgment.ts:26 漏了双条件的第一条(只有 .filter(isPathLike),缺 .filter(isConcreteLocator))—— 而 evidence/paths.ts 的注释明文写着「isPathLike 故意不收窄 …… 需要「可检查」语义的地方用 isConcreteLocator」。后果:glob(scripts/.ps1)与 git ref(origin/main)被做存在性检查 ⇒ 必然 not_found ⇒ 结论假降 evidence_stale、置信假降、rationale 谎称「证据缺失」;真语料实测 12 条(0.49%)/ 17 处。修复前测试先看红、修复后 5/5(含 ③反向不变量「真实缺失的具体路径仍须判冲突」与 ⑤跨消费者一致性)。回归 36/36 |
| v1.15.26 | _index.md 的投影漂移(ADR-0069)—— 延续 D5 的视角找到同型问题,这次有实测数字。实测真 .shadow(7297 条):_index.md 停在 09:34:01,之后写入的记忆在索引里出现 0 次(磁盘上确实存在)⇒ 623 条(8.54%)对索引不可见,而主题召回走 listMemories(每次读盘)看得见。三层根因:① ensureIndex 的条件用 indexDirty(进程内 Set,只反映本进程写入)⇒ 别的会话/子代理写入的记忆本进程的 dirty 永远看不到,缓存一旦预热索引再也不更新;② 即便决定重建,ensureIndexCache 的 if (warm) return(「只做一次全量读」)也不会重读新文件;③ 磁盘已删的文件从不清出缓存 ⇒ 幽灵条目。修复:新鲜度问源 —— 用已存在的 shadowSourcesFingerprint(它的注释早写着「缓存是性能特性不是真相」,但此前只接给了 nodes.jsonl,没接给 _index.md);并把 ensureIndexCache 改成每次增量对账(listMemories 只 listDir、不读内容 → 只为新文件读内容 → 源头已消失的清出)⇒ 「跟得上源头」与「不每回合全量重读」同时成立;先采指纹、后读源(与 ADR-0068 同一顺序教训)。真机契约核实(把未验证变已核实):读 dsh-fs-local 的 listDirectory 实现确认 target 必给、文件必给 size ⇒ 指纹不会恒为 undefined ⇒ 「源未变则跳过」在真机成立;真语料实测指纹 7336 条目 / 517 KB / 稳定 / 62 ms(只 listDir)。测试一度失败并暴露真问题:mock 的 listDir 漏了 target(契约必填)⇒ 指纹恒 undefined ⇒ 每次保守重建 ⇒ 「源未变不重写」永远测不过;修成忠实契约后通过 —— 又一次「mock 与契约不符时测的是 mock」生效。回归 35/35 |
| v1.15.25 | _meta.json 的读-改-写加版本守卫(ADR-0068)—— 修掉上一版自己放大出来的风险。v1.15.24 把命中数累积的触发条件从 servedDetail(几乎永空)改成 servedRels(每次有命中的召回)⇒ 那段「读全量 → 改 → 写回全量」从几乎不执行变成常态执行,而 _meta.json 是全工作区共享的一个文件 ⇒ 并发(多会话 / teammate / 宿主与子代理同时召回)下丢更新。根因:能力就在 fs 契约里 —— 已读 @deepseek-ai/dsh-fs@0.1.5-rc.2 类型定义核实 writeText(target, content, expected?: FsWriteIntent) / FsWriteIntent.replaceIfVersion / FsInfo.version("the freshness token a write/edit guards against")/ FS_STALE_VERSION,而插件一处都没用(全仓 grep expected 与 fs 无关的一处都没有)。修复:① 新增事务层 mutateMeta(stat 取版本 → readText → mutate → 带守卫写 → 冲突重读重试,上限 3 次);② 顺序敏感点:必须先 stat 后 readText —— 期间有人写入则我们手上版本比内容旧,带守卫写会失败重试(不覆盖);反过来会拿到「比内容新的版本」,守卫通过而覆盖别人的写入(已写进注释防「顺手调换」);③ runCompact 从「覆盖全量」改为「应用 delta」 —— 它的窗口横跨索引重建 + Episode 收口,原来会整份盖掉期间的写入;compacted 标记丢了会让已归档原子重回活跃索引(正确性问题);④ stat 不可用时诚实降级为无条件写(不更差),有测试锁住。测试的关键:mock fs 真的实现 stat + replaceIfVersion 语义(否则测的是 mock 不是系统 —— v1.15.15 踩过),用「注入一次外部写入」确定性制造冲突;核心断言 ③「冲突被检出并重试 ⇒ 双方更新都保住」(不重试则 other.md 会被整份丢掉)。回归 34/34 |
| v1.15.24 | 命中数累积触发条件错(ADR-0067)—— 第五处「机制对、判据/触发条件错」的同类缺陷。延续 ADR-0066 的视角继续查,在 query/query.ts 找到:累积 hits/confirmedBy 用的是 servedDetail(= tier !== "L0" && render.includes("…"),本来是给冷却台账用的),而 tierFor 对「动作行占比 > 60%」的记忆返回 L0 ⇒ 真语料实测 L0 占 74.3%(5342/7185),这类记忆永不可能被记命中;即便是 L1/L2,还要该次预算够展开片段才进集合。端到端佐证:本机 7185 条记忆、_index.md 1.8 MB、多次召回后 .shadow/_meta.json 根本不存在 —— 整条 retention/hotness/lifecycle 信号链从未真正启动。语义依据:hits 在 README 里的定义是「召回命中数」(hotness = 命中数 × 半衰期衰减),被返回一条就是一次命中,与是否展开片段无关。先复现再修:新增 test/hit-accumulation.test.ts,修复前先在 ② 处跑红(actual: undefined),修复(累积改用 servedRels = 每条被返回的)后 4 组断言全过。连带恢复四处此前实际不可达的能力:生命周期 OBSERVED/VERIFIED/TRUSTED 三态、forget 的 minHits 保护、retention 的 hotness(此前恒为 0)。又一例「单元测试绿、功能仍失效」:那三态有单元测试且全绿(直接构造 {hits:2}),但生产里喂给 lifecycleOf 的 rec 恒为 undefined。立 D7(hits 是否该覆盖 shadow_query/recall_shadow 等入口 —— 有意不顺手做:那会把「被主题召回」扩大为「被任何读入口读过」,改变 hotness 含义)。回归 33/33 |
| v1.15.23 | 按推荐落地(+ADR-0066),结掉 5 条待办。① B1 闭环:用户重启后实测 —— read_shadow({mode:"toolset"}) 返回台账 107 项(105 reference + 2 provider)/ 17 分类;need:["全文搜索"] 返回能力预检(非 _index.md)且三条硬边界正常;category 过滤生效 ⇒ v1.15.13–v1.15.22 十一个版本首次在运行进程生效。② B2 决策:多粒度层采 ①「按证据改」= 单索引 + 层级表示 + 路由,不做多库全量扇出;该形态本仓已实现(单 provider 路由 + tierFor L0/L1/L2 + renderByTier),故落地=确认现有设计即目标。③ D4 决策:pinned/archived 采 ③「纠正文档」 —— 删掉 README 两处不可达承诺(「pinned 永存」、…→ ARCHIVED)+ MEMORY.md:90 就地勘误;不补写入口(会把外部权威状态落进可重建的 _meta.json,撞 ADR-0003)。④ D5 落地(本轮唯一代码行为改动,ADR-0066):先做信号实验(真语料 7089 条 × 5 个候选判准)—— 旧判准精度仅 9.8%(判 4744 条 metadata,其中 4279 条其实有工作痕迹),而仓库里已文档化、从未接线的文本启发式口径精度 100%(判 93 条,0 条有痕迹)。落地:新增唯一判据源 isSessionMetadataAtom,deriveAtomKind 改用它,isCognitiveAtom 删除(规则与 validateAtomProjection 完全重复 ⇒ 消除「一条规则三份实现」的病根)。实测效果(7111 条):metadata 4137 → 90;deriveShadowNodes 产出 2283 → 6478(32.8% → 91.1%);两条读路径可见性差 67.2% → 8.9%,且残余 8.9% 已核实为 540 条证据门的正当拒绝 + 90 条新判准(探针归因)。两条实测边界写进测试:kind=metadata 与判据是有向关系(task 分支优先,不影响可见性);两份实现读的表面不同(线索头 vs 正文行,真记忆两者都写 ⇒ 真语料不分叉)。⑤ D6 决策:OpenViking 三条归类为 Projection(不违反 ADR-0003),三条都做,实现待后续(前置已写进 BACKLOG)。台账 22 → 19 条,新增「已结案」节 |
| v1.15.22 | 脚本全量切 TS(ADR-0064)+ 认知门可达性实测(ADR-0063)+ 吸收 OpenViking(ADR-0065)。① 用户指令「所有 js/mjs 脚本切到 ts」:8 个 .mjs 经 git mv 改 .ts(tools/ 6 + test/replay- 2),仓库内再无手写 .js/.mjs;零构建零新依赖(靠 Node ≥22.6 type-stripping,node x.ts 直跑,与测试套同一机制);新增 tsconfig.tools.json + npm run typecheck:tools——工具面第一次有类型门,首次运行即抓到 winget-verify.ts 的 Promise 静默类型漏洞(已补 interface WingetRun)。测试套有意不加类型门(实测 79 处错误绝大多数是守卫测试故意喂畸形输入;加门只能靠 as any 消掉,反而削弱证据力)。副作用已核对:工具开始扫自己,B 类线索 81 → 85(+4 全是工具自身字符状态机的单字符别名),A 类 30 不变。② 认知门实测(真语料 6960 条):判 metadata 的 4137 条(59.4%),其中 94.4% 有实质内容;投影路径只产出 2283 节点 ⇒ 主题召回可见 100%、shadow_query 只可见 32.8%(差 67.2%)。根因是 deriveAtomKind 拿 entry === "shadow"(写侧兜底字面量,语义是「没识别出组件」)当「会话元数据」的代理;同一条规则有三份实现,两份零调用点且口径相差 37 倍(4137 vs 110);AtomKind 声明 5 值而生产者只出 3 值(session/artifact 无生产者 ⇒ 该分支永不可达)。行为零改动,只标注 + 加决策锁测试,处置升为待办 D5。③ 吸收 OpenViking(一手材料,主工程 AGPLv3 ⇒ 只取概念不取代码):勘误 —— 本 README 曾把召回衰减标成「OpenViking 式 hotness」,标错了(其官方三份文档里 decay/hotness/half-life 等 0 命中),真实出处是同句已引的 MemoryBank;可吸收三条(升 D6):L0/L1 是目录级 sidecar(256 / 4000 字符,不为每个文件建)、L0 从 L1 确定性抽取(层间不漂移)、sidecar 带 freshness(覆盖率 + pending_child_changes,派生件自报过期);对 ADR-0060 的精化:其层级分数传播 score_propagation_alpha 默认 1.0 ⇒ 层级买的是「递归下钻扩大候选」,不是「分数平滑」 |
| v1.15.12 | 缺陷清扫(用户「所有发现的缺陷都要 fix」):先逐条查证 16 条记账/缺口是否仍存在,再分类处置。修 5 项真缺陷——① continuity/engine.ts 自造 FsTarget(违反 dsh-fs 契约,sandbox 下静默失败);② 投影缓存不感知源变化(invalidate 零调用点 → 写侧 ensureIndex 挂钩 + 读侧源指纹,并顺带修掉一个被激活的既有 bug:abs() 把 displayPath 字符串当 FsTarget 传);③ 台账补登 7 条(44→50)+ docs/toolchain-wsl.md 纳入棘轮;④ HOST_BASELINE 双源加防漂移棘轮;⑤ Team 工具静默缺口的可感知降级(①为待决策的结构性项)。2 处记账勘误:fs.writeText 省略 expected/sandboxPolicy 是契约允许的(非缺陷);revocation-guard 不是孤儿(测试在用)。自曝:第一版把「不传指纹」写成永不命中缓存(测试当场变红)。全量回归 27/27,新增 3 处回归锁 |
| v1.15.11 | 派活判据由「team 优先」修正为「复用优先」+ 委派规模控制(ADR-0056):会复用 ≥2 次才用 teammate;host 行 maxMembers: 4(经代码核实是 per-session 终身累计上限,非并发;无移除路径、失败也占名额);往返 ≤2 轮、一次委派一条消息、避免冷恢复;workflow/subagent 不吃名额。代价:persona +235 字符常驻 |
| v1.15.21 | BACKLOG 分诊结案:pinned / archived 无入口 —— 升为 D4 决策项(用户:「待办记录好后,提交,结束」):把 BACKLOG.md 里 T3 那条待分诊分诊到底。扩展发现:不只是 status=archived 无写入者,pinned 同样无写入者 —— 生产只写 pinned: false(core/memory.ts:74、core/writer-materialize.ts:88、query/query.ts:401),pinned: true 全仓零处(三路 grep 核实)。判定:不是「接线断了」,是「已文档化但无入口的能力」,三条依据 —— ① _meta.json 是 Derived Artifact(ADR-0003),手工编辑会被 rebuild-index 抹掉;② 没有任何命令 / 工具 / 元数据约定能置这两个状态(工具面只有读侧三个);③ MEMORY.md:90 声明「不做写侧硬状态迁移、纯按信号推导」,而 lifecycleOf 两条最前置判断恰读写侧 rec.status/rec.pinned。新增 D4(三条路:补持久入口 / 改信号派生 / 纠正文档 + 各自代价,建议 ③ 为主 + ① 的窄版本,决策权在用户)。台账 18 → 19 条(T3 结案转入 D4)。顺带修 T2 一处折行粘连、把 pinned 永存 的引用从 README.md:178 纠正为 :177。纯文档改动,生产代码零改动 |
| v1.15.20 | 新增 BACKLOG.md —— 待办的唯一台账(用户:「先记录代办任务,后续再继续」):把跨 7 轮累积的未完成事项集中成 18 条,分五类 —— 一、阻塞在用户(重启 DSH 使插件代码生效;多粒度检索产品方向)、二、待分诊(审计 A 类 30 条 / B 类 81 条;status=archived 无生产者待判定)、三、待决策(ChangeSet/invalidateFor 接线还是删除;跨项目根注册;细粒度取代)、四、未验证(真机 semble/zg;新台账 probe 旗标;maxMembers 运行时拦截;真机 host.fs 语义;其余 mock 忠实性;审计工具未入门禁)、五、已知空白(expiry 无对照消融;CLI 层工具数量拐点无论文;装/审批闭环无先例;重排器未在本系统验证)。每条给「内容 / 依据(可点的文件或 ADR 行号)/ 为什么没做 / 完成判据」四项;不写没有依据的条目。准确性核对:引用的 8 处行号逐个核实通过;T1/T2 计数为当日实跑所得(30 / 81),不写「约」。README 版本节加指针。BACKLOG.md 不入 files 白名单(与 CONTEXT/CHANGELOG/MEMORY 同一先例)。纯文档改动 |
| v1.15.19 | 接线审计工具(ADR-0062):本仓 v1.15.13–18 连续挖出四类同源缺陷(toolset 未挂 dispatch、绝对路径证据假失效、status === "superseded" 无写入者、取代生命周期未回填),且单元测试全绿 —— 共同特征是「机制是对的,断的是谁调用它 / 谁写这个值」。新增 tools/audit-wiring.ts:A 类「导出但生产无调用点」、B 类「只被读、无写入点的判断值」。工具必须先标定再用 —— 初版在真仓库报「A 0 / B 10」,而 B 的 10 条全是误报(三元写不认)。标定(tools/audit-wiring.selftest.ts,8 组断言 + 已知答案夹具)连续暴露 4 个工具自身缺陷:① 三元写不认(误报)② 改按字面量判后跨字段同名值算作写入者(漏报)③ 扫注释文本 ④ 分类器正则要求前导斜杠 → 顶层 test/ 从未被排除 → 测试夹具的 status: "superseded" 被当成生产写入者,恰好掩盖要抓的真缺陷。审计结论:确认 ChangeSet 与 invalidateFor 生产中未接线(唯一消费者是测试)—— 但不是正确性缺陷(投影缓存可重建,粗粒度清空即正确),是未接线的优化(ADR-0048⑤);已在代码里显式标注,不臆造接线。A 类精度低(本仓有意导出测试向包装 API,如 assert,其底层判定在生产确有使用)—— 工具是线索发现器,不是缺陷清单 |
| v1.15.18 | 确定性取代接进生命周期(ADR-0061):本仓已有文献唯一支持的「纠正」形态 —— newestByEntryOf + verdictOf 按同 entry 是否有更新记忆判 superseded(无 LLM、无相似度阈值;文献:余弦相似度分辨「被推翻」vs「换个说法」AUROC 仅 0.59),且已用于降权 ×0.7 + 报告。但三条依赖 meta.status === "superseded" 的分支(lifecycle 的 SUPERSEDED、forget.ts:18、rank.ts:103)生产中不可达 —— 严格 grep 核实:生产只写 active/compacted,唯一写 superseded 的是测试夹具。后果(修前实测):同一条记忆自相矛盾 —— 生命周期 NEW · 裁决 superseded。根因是顺序:lifecycle 在每记忆循环先算好,而 superseded 依赖 newestByEntryOf 跨记忆视图、之后才算出且从不回填。修复:lifecycleOf 新增可选参数,用同一份读时裁决回填;优先级 pinned > archived > superseded(外部权威不被派生判断覆盖,inv 178);不持久化(取代相对可见集判断,落盘会失效)。forget.ts/rank.ts 两条有意不接线并写明理由(避免重复降权 / 误删)。另核实第 (3) 条:证据支持的三要素本仓已实现 —— 单 provider 路由 + tierFor 的 L0/L1/L2 层级表示 + renderByTier 按预算逐层展开⇒ 多粒度形状已经在跑,是「单索引 + 层级」不是「建 N 个库全查」 |
| v1.15.17 | 部署取证(无运行时代码改动):agent-team 部署已完成且已生效 —— 装 @deepseek-ai/dsh-experimental-agent-team@0.1.5-rc.2(+ -tool-agent-team)、cordis.patch.yml 加宿主行 maxMembers: 4、预设三文件同步(SHA256 一致)。预检 5/5 全过(YAML 合法 / 包可解析 / dump-config 无错 / bundles 无重复 / SHA 一致)。挂载校验用临时 Cordis 探针调真实 agentPresets API:standingKeyFor('projection') = mounted OK、compositionInventory 27 行全 enabled 且 tool-agent-team 的 fiberState: 2、team_task_list() 返回 {"tasks":[]}、agentTeams 在服务目录 —— 三路交叉确认 Team 真活着(单靠 standingKeyFor 不够,预设 README 自己记录过它会假成功)。独立复核 ADR-0056 的代码论断(原为「未验证」):DEFAULT_MAX_MEMBERS = 8、L564 创建时检查、members.splice/pop/shift/filter 命中 0 处、失败的创建也占名额(L561-570 先落盘 provisioning 再 spawn,失败只改 phase 为 failed,不移除条目)。P0 在真机复现:live 进程里 read_shadow({mode:"toolset", need:[...]}) 返回 _index.md,且 live schema 无 need、mode 描述无 toolset ⇒ 运行中是 v1.15.12。发现两种加载行为并存:host 组合行+新装包热加载(Team 立即可用);本插件的 dist/ 改动不热加载 ⇒ 改插件代码必须重启 DSH |
| v1.15.16 | 多粒度检索层形态(ADR-0060):先点明一个改变问题性质的事实 —— 现有 indexEngine.provider 是单值("fs"|"zg"|"semble"),工厂只路由到一个,故「多库全量扇出」是退步而非加能力。新增 tools/retrieval-eval.ts(npm run eval:retrieval)在真 .shadow 语料上实测:检索器按 ADR-0054 实测性质建模(无阈值)、同候选预算、3 种子报极差。结果:离题噪声 有阈值单库 0.000 vs 无阈值 1.000(扇出只是把噪声乘以库数);扇出即便含互补来源也不升召回(0.347 vs 0.358,落在 ±0.053 内)。⇒ 多粒度若做,形态是「单索引 + 层级表示(level/parent_id)+ 路由」,不是建 N 个库;加判别层优先于加库;多来源须各自标定阈值。产品方向待用户裁决(本 ADR 未单方面推翻任何既有 ADR)。附两处引用陷阱:「small-to-big」无原始论文、Markdown heading 切块无论文 |
| v1.15.15 | 引用漂移检测 + 修两处假「证据失效」(ADR-0059):先在全库(6465 记忆)量事实 —— 表面 40.8% 的「证据失效」里约 76% 是假的。F1(真 bug):fsExists 无条件做 ${ws}/${rel},绝对 locator 变双前缀(D:/ws/D:/other/x.ps1)→ 磁盘上存在的文件被判失效;F2:readText 对目录必失败 → 目录引用被判失效(补 listDir 兜底,真实契约已读源码核实)。修复后 1025 → 243。检测判据改为双条件(借 CASCADE/FSE 2026):只有「可检查的具体路径」(isConcreteLocator 排除通配符与 git ref)且「确实解析不到」才判失效。明确不做自动纠正 —— 让 LLM 判过期(AUROC 0.59 近随机)、LLM 自纠(误纠正率 53–94%)、裸 LLM 查文档漂移(flag rate 98%)均有证据反对。顺带发现两处 mock 不忠实(listDir 对不存在目录返回 [] 而非抛错),已按真实源码修正 |
| v1.15.14 | 工具台账扩源(ADR-0058):50 → 107 项(2 provider + 105 reference),分类 13 → 17(新增 容器与编排 / 安全与供应链 / 文档与转换 / 媒体处理)。全部经权威核验:新增 tools/winget-verify.ts(对精确包 ID 调 winget show,locale 无关解析,取版本/许可证)+ tools/winget-verify-seed.ts,57/57 通过。核心纠错:按名字自动解析包 ID 实测证伪 —— xh→Mozilla.Firefox.xh、delta→eToro.Delta、nix→LabChart、choose→AuthenticatorChooser 等 9 例假阳性,故包 ID 必须由人裁决、机器只做核验与候选发现。拐点口径澄清:arXiv 2606.30317 的「10–15 个工具跌破 90%」量的是每次请求注入的工具 schema 数(per context),不是目录条目数 —— 台账本来就不进上下文,故可以扩;必须保持小的是「模型面前可调用的工具面」,禁止把条目暴露成工具。顺带修一处静默丢弃:tool() 的 note 在有 winget 包分支被整条丢掉(许可证/坑说明无声消失)→ 已拼接,并新增 verSrc 区分「实测」与「权威核验」 |
| v1.15.13 | 委派 × 工具集接缝(ADR-0057):先修一个潜伏三个版本的 P0 —— query/reads.ts 定义了 toolset ReadQuery 却没放进 readQueries 数组,{mode:"toolset"} 静默回落到 _index.md,整块台账无任何可达入口(三个既有测试全直接 import 执行函数、从不走 dispatch,所以三个版本全绿)。修复 + 新增 test/toolset-dispatch.test.ts(只走真实入口,含「可 dispatch 的 mode 必须在 index.ts 登记」反向棘轮;已回档复验)。接缝本体:能力预检 read_shadow({mode:"toolset", need:["全文搜索",…]}) —— findCapabilities(need) 按能力需求反查台账(id/二进制名/用途词/分类名 + 别名,不是能力评分),precheckCapabilities 只读探测,输出固定带三条硬边界:① 不是闸门 ② 装完本会话不可见(宿主 PATH 是启动时快照,同进程 teammate 同样看不见 ⇒「预检→先装→再派」单会话内收益为零)③ 缺件只能上报不能自装(inv 182)。persona ② 同步补一句。顺带修掉 toolset-catalog ④ 一个恒红断言(把「本机应有已检出的 provider」写死进测试;已改与机器无关的不变量) |
| v1.15.10 | 工具集台账扩为两级(ADR-0055):kind:"provider"(插件内接线:zg/semble)与 kind:"reference"(通用 CLI 目录,44 项 / 13 分类)。新增 docs/toolchain-windows.md(Windows 口径,winget ID 全部本机实测)与 docs/toolchain-wsl.md,随包发布;test/toolset-catalog.test.ts 做双向棘轮(台账↔文档漂移即红,首次运行即抓出一个写错的 ID)。巡检支持 survey:"all"(并行探测全部,实测 44 项 1.5s)与 category 过滤。探测口径收紧为诚实的「未检出 ≠ 未安装」。全量回归 27/27 |
| v1.15.9 | 一键装入口 mode:"toolset":read_shadow({mode:"toolset"}) = 只读巡检;{mode:"toolset", install:""} = 显式安装。授权走宿主自己的审批服务(ctx.approval.request),只有 allowed-once 才执行;rejected/cancelled/unavailable/无通道/无 agent/审批抛错/非词表返回值 → 一律不安装(fail closed)。已可用 → 幂等短路;装完重探才报结果。顺带解掉与 zg 同类的 Windows 陷阱:npm 也是 .cmd,故解析为 node install -g 。mode 总数 61 → 62 |
| v1.15.8 | 缺件处置(工具集台账):新增 core/toolset.ts 声明式台账(zg / semble:provides / degradesTo / remedy / doc),并在 mode:"index" 与 verifyEvidence 两处缺件出口接上可执行的确切命令;CandidateResult 增 reason,顺带补上 verifyEvidence 此前不显示 reason 的缺口 |
| v1.15.7 | zg 集成三处修复 + 安装指南(用户指出「不然没用」):① spawn 硬阻断——Windows 上 execFile("zg") 必 ENOENT(Node 不解析 npm 的 .cmd)、execFile("zg.cmd") 必 EINVAL(CVE-2024-27980 缓解),于是「zg 装好、手动跑得通、插件恒 unavailable」→ 改为定位包内 dist/cli/index.js 用 node 起它;② 输出解析——zg 0.2.2 的 --rg 是「路径单独一行 + 缩进 起-止 [heading 面包屑] 行号:内容」,不是 path:line:text → 改状态机,并删掉两个会制造证据的兜底(「stdout 出现 ref.path」会把 zg 的 missing: 误判成 verified);③ 裁决语义——verify 必须按 ref.path 限定搜索(工作区级搜索 + 全局 top-N 会把目标路径截掉:实测一次查询 40 条命中/16 文件,目标排第 7 个文件),不存在的路径 → not_found/stale;另把失败原因写进 provenance.reason(ADR-0049)。README 新增「可选外部 CLI(zg / Semble)」安装与自检指南 |
| v1.15.6 | Semble 接为 Index Engine 的候选 provider(ADR-0054):indexEngine.provider = "semble"(本地 CLI,uv tool install semble)。它是检索层、不是裁决层——只产候选 → rankRefs → authorizeScope → 交回 Shadow Core;默认仍 fs(行为不变)。为什么不进裁决面:实测 Semble 无阈值、无负信号(4 次查询分数三元组完全相同;「量子纠缠/哈勃常数」这类语料里没有的话题照样返回最高分;CLI 无 --threshold),交它 verify 会违反 ADR-0043「无证据不返回」与 ADR-0049。两处实现约束:① spawn 时必须清洗 NO_PROXY——本机 ambient 的 [::1] 会让其 httpx 抛 Invalid port ':1]'(模型已缓存也照崩),故剔掉带方括号的条目;② Semble 返回相对路径,必须先绝对化——否则 authorizeScope(绝对前缀匹配)会把候选整批滤掉(此坑由测试暴露)。端到端实测:generateCandidates 返回 20 条绝对路径候选并命中 core/resource.ts:173-186 |
| v1.15.5 | 旧协议约定全面删除(A/B/C/D):A 删 core/ 里 4 处旧数据格式兼容兜底(> 用户提示/决策:〔decision〕 的 legacy 决策解析、decisions 回退、node.ts 的 materials 回退、kind/lineage 可选性),ParsedMemory.kind/lineage 转必填;B 修正 23 个 ADR 陈旧的「协议(提案,待 vX 实现)」状态(对应实现目录与 CHANGELOG 条目均已存在);C ADR-0053 再正名 3 项同名双义(mode:"verify"→verification、Gateway EvidenceRef→GatewayEvidenceRef、realityEvidenceRef→realEvidenceRef),并判定保留 2 项并写明理由(config.recall 含管线级旋钮,改名会与语义不符;args.identity 再改就要生造词);D 当前文档不再登记废止名(README / CONTEXT / LIVE-VERIFY / 工具 schema / 注入提示),映射与理由只留 ADR。顺带修一个真 bug:core/experience.ts 的决策一直在读旧 > 用户提示/决策: 提示头(等于把任意用户消息当决策)→ 改读现行 > 决策: 并剥离 〔source〕 |
| v1.15.4 | 投影模式预设「team 优先」(只改 persona 与文档):默认先判该不该派;该派时优先官方 Agent Teams(spawn_teammate / send_message / team_task_),复用同一具名 teammate 而不是反复新开一次性 subagent,只在「一次性、无后续」时用 subagent / subagent_fork。前置与已知边界:Team 域服务 ctx.agentTeams 必须由 host 组合提供(@deepseek-ai/dsh-experimental-agent-team,实验包无稳定性承诺);预设只挂工具包 @deepseek-ai/dsh-experimental-tool-agent-team。缺 host 行时预设仍报 standingKeyFor 挂载成功,但 9 个 Team 工具静默不出现——与 ADR-0049「缺件不静默」相悖,是本版已知缺口。另:send_message / list_agents / interrupt_agent 三个名字被 Team 版作用域内遮蔽,Lead 不再能用它们直接指挥非 Team 的 continuable 子代理 |
| v1.15.3 | 审查修复(review 发现 → 父代理逐条复现 → 修根因):v1.15.0 的「硬依赖报 error」在真机不可达 —— ctx.inject(deps, cb) 只在依赖就绪时回调,把检查写在回调里等于「缺了就不报」;已把 tools/systemPrompt 纳入首个 turn-stopping 的检查(并补 ctx.get),新增真实 cordis 端到端测试(原先 mock 无条件回调 ⇒ 断言①是假通过)。另修:core/types.ts 残留旧名 session.cwd、clear 不清缓存致新记忆带旧 > 目标:、goalText 的 \|\| "decision" 伪装兜底;补 exec.name 正向断言 |
| v1.15.2 | 过期旧名删净(ADR-0050 口径):collect.ts/writer-capture.ts 里 change.objective/change.action/change.phase/change.kind/change.change?.objective/exec.tool?.name/exec.toolName/exec.tool 全部删除(宿主任何版本都不存在),scope.ts 删掉恒 undefined 的 agent.session.cwd 候选并修正过期注释。删旧名的价值当场兑现:暴露 5 处建在编造形状上的测试(4 处 goal 载荷 + 4 处 tools/result),已全部改用宿主真实形状,断言意图不变;代码层旧名 grep 0 残留,回归 23/23 |
| v1.15.1 | 会话/agent 接口核对 → 根因修复:宿主 GoalChanged 恒为 {operation, ref, goal?}(0.1.0-rc.7 起四版逐字相同 + 运行时 Inspect 一致),插件却读 action/phase/kind → act 恒回退 "decision",goal 操作语义永久丢失;新增 test/goal-operation.test.ts(真实载荷形状 + 七种 operation 全覆盖)。同时撤销两条子代理误报:systemPrompt.context() 与 section() 是并存的两个不同用途方法(插件用对了)、agent.session 是公开契约(只是 Inspect 目录看不到) |
| v1.15.0 | 兼容性口径落地:package.json 加 engines.dsh: ">=0.1.5-rc.1"(验证基线声明,非闸门——宿主与 pnpm 都不读 engines,已核实无任何代码读取;能观测到的防线是宿主绑定能力探测(硬依赖 ctx.on/ctx.inject/ctx.get/fs/tools 报 error,可选 llm/agents/agentDefaultModel/systemPrompt 报一条 warn;v1.15.3 修正:探测统一在首个 turn-stopping,原先放在 inject 回调里的那半在真机不可达);README 新增「兼容性(验证基线)」节;0.1.5-rc.1 以下未发现不兼容点(六服务两事件自 0.1.0-rc.7 起即在),故不写「不兼容」 |
| v1.14.1 | 投影模式预设加 ⑦ 创意与资源(设计稿 §7 固化):创意类问题先派资源侦察员(查库 → 命中跳过外搜 → 八类词 + 反向词、每类 ≤5 两轮无新资源即停 → 评价 → 写卡进 .shadow/resources/;不解题不评方案)再派创意专家(只发散、不检索);卡片必须有 source 才进认知查询,启发度 要有引用证据;② 的专家枚举同步补一句。只改 persona 与文档 |
| v1.14.0 | 新增 resource NodeType(ADR-0051):.shadow/resources/.md 资源卡(固有层 + 按问题的投影段)→ 派生 ShadowNode{type:"resource"},shadow_query 的 scope 可收 resource;无 source 的卡片不上投影(收进库 ≠ 有出处);纯派生、无 LLM;不新增 mode、不引向量库、不做 Store |
| v1.13.2 | 投影模式划清"编排者与专家不重做同一件事"(用户 2026-09-10 提的"子 Agent 与主 Agent 不重叠、不重复推理、降低 Token 冗余"):① 增补该不该派(一句话说得清、只动一处、不需要旁人视角的自己做;切活的侦察不算重做,不许先做出成果再派)② ③ 增补同一段原文只进一个专家的提示词(其余给摘要 + 原位路径;要独立判断的审查例外)③ ④ 由「逐条复核 / 专家声称的事实自己跑一遍」改为只验一错就要返工的那几条、其余按未复核处理并列出、零分栏退回④ ⑤ 补"各干各的那一份";用户级规则 moe-subagent-dispatch 同步改,规则为源(聚合 ~/.dsh/AGENTS.md + WSL 镜像) |
| v1.13.1 | 投影模式预设增补 ⑥:根因三部曲 + 禁止生造词 + 结论进 shadow/项目文档 + 四查(交手前/改口径后);recall_shadow→mode:recovery;与全局 ~/.agents/AGENTS.md 去重说明 |
| v1.13.0 | API 正名硬切:recovery / identity-advance / verifyEvidence / real-evidence / AtomEvidenceRef;旧名显式拒绝(ADR-0050) |
| v1.12.9 | 投影模式预设的 persona 增补「工作方式」五条(先分活 / 准确激活专家 / 提示词七要素 / 派了必须验收 / 并行与扇出),随包发布、不依赖用户级规则目录;预设/主 README 同步,安装副本已同步并做过全新挂载校验 |
| v1.12.8 | ① 缺件不静默提成全插件纪律(ADR-0049:只降级/必须可见/绝不冒充成功/只陈述事实),顺带修 routeVerify 未知 provider 静默退回 fs 的反例 ② 召回路由评测(正/负样本 + rank-1 棘轮 + 主题键碰撞检测,回归门槛)③ references.md 三处更正(OpenAI 两条路线/hyperframes 安装坑/状态方向写反) |
| v1.12.7 | 审查修复:① 读侧输出保留换行(scrubFinal 不再把整篇 Markdown 压成一行——scrubUnsafe 连 \t\n\r 一起剔的根因)② 信封计数恒取「命中 − 返回」(冷却也计入,不再出现「未返回 0 条」)③ 全冷却不再误标「近似候选」④ 近似候选对称归一化 + hit ≥ 2 降噪 ⑤ mode 棘轮补齐 plan(60→61)⑥ 若干措辞/精度/容错小修 |
| v1.12.6 | 参考材料落地三项(借 mattpocock/skills、PageIndex、codegraph;均无 LLM/新依赖):① mode 描述下沉到 CONTEXT.md「mode 参考」(1747→488 字符,schema 只留常用 + 指针,带棘轮测试)② 召回信封(截断自报家门 + 空命中给四条下一步与近似候选·未验证)③ recall.deprioritize(只降权不移除);另清理 claude-mem 参考材料(插件 7 处提及 + 克隆源码 140.8 MB) |
| v1.12.5 | 文档(无代码/行为变化):README 新增「默认开关(装完什么都不动会怎样)」表(15 项,逐项对源码默认值)与「谁能调用(用户显式 vs 模型自动)」权限轴;开头加「给 agent 读的入口」;安全边界表第 4 行补「LLM 增强超时静默降级 = 可取消」 |
| v1.12.4 | 文档清理(无行为变化):去掉「一切皆文件」口号(源码注释 / CONTEXT.md 术语表 / 投影预设)+ package.json 描述同步;ADR / MEMORY 历史原文保留 |
| v1.12.3 | 文档(无代码/行为变化):README 新增「谁该用它」定位、「为什么存在」失败模式表、「什么情况用哪个」模式路由表、「给 agent 的粘贴式安装」、护栏下的「安全边界」对照表;references.md 登记 4 条补充材料并逐一核实 |
| v1.12.2 | 架构加固(全部审查候选落地,行为/公共契约零变化):读族全迁 ReadQuery seam(query.ts 0 内联分支)+ 唯一循环依赖打破 + knowledge-engine 三 seam + 概念核 guard 测试 + writer capture/materialize 拆分(17 测试全过) |
| v1.12.1 | 架构重构(审查候选 1 首刀):ReadQuery seam + materializeAtoms 收敛读模式 monolith,行为零变化 |
| v1.12.0 | 内容分类去噪 + 按格式结构抽取(code 包树/标题树/段落)+ 检索引用 + Shadow Manifest 可观测(ADR-0048 候选落地,纯派生无 LLM) |
| v1.11.0 | 成本感知树优化 + 渐进披露树 + change-set 增量索引 + 授权范围搜索(ADR-0048,均无 LLM、纯派生) |
| v1.10.0 | Knowledge Engine LLM 树上导航:mode:"knowledge" + topic → LLM 只选章节编号,事实仍从树派生(knowledgeNavigate,默认 off,失败回退确定性检索) |
| v1.9.0 | Projection Store + Index Engine + Knowledge Engine:可插拔性能缓存/候选生成/规范树(默认关;zg/PageIndex 是 provider,未装 unavailable) |
| v1.8.0 | Evidence Lineage Layer:AtomLineage + EvidenceRef + AtomKind + Validation Gate(metadata memory / 无证据 decision 不进查询),Evidence Density 按 type/kind/createdBy 报告 |
| v1.7.2 | Shadow Fitness Report:query-log → shadow-report.md(Evidence Density/稳定性/类型分布/潜在缺失类型),作为是否升级索引层的客观依据 |
| v1.7.1 | Shadow Query Observatory:shadow_query 旁路观测(真实查询数据)+ Node 稳定性/类型/relations 观察 |
| v1.7.0 | Shadow Projection Layer:ShadowNode + shadow_query(Phase 1A,ADR-0042/0043) |
| v1.6.0 | recall_shadow LLM 推理导航(对齐 PageIndex 免向量检索) |
| v1.5.x | recall_shadow + Active Context(Shadow Usability) |
| v1.4.0 | Context Recovery(ContextReference,ADR-0040) |
| v1.3.0 | Task Lifecycle(ADR-0039) |
| v1.2.x | Episode 收口归档 + 增量索引/遗忘(性能) |
| v1.1.x | Decision Capture(ADR-0037) |
| v1.0.x | Observer Runtime Foundation → Verification |同作者(mozhuanzuojing)的其他插件
扫码进群