Skip to content
dsh.fish
Bundle

dsh-attention-health

DeepSeek Harness 插件:内容退化检测(思考打转/复读)+ 继续 vs 交接成本账 + 零模型交接文档(host + web UI 双面)

Source
donghangxunlang-cmd
License
MIT
Updated
Updated yesterday

Readme

# dsh-attention-health

**会话越跑越傻、越跑越贵的时候,你需要一个"该不该交接"的判断和一个可信的交接文档。**

这个插件读**真实会话日志**(不联网、零 token)做三件事:

1. **内容退化检测** —— 思考打转、输出复读、守卫命中;
2. **"继续 vs 交接"的账** —— 用官方价格与真实缓存命中率,算清换会话划不划算;
3. **机械生成交接文档** —— **绝不用模型写**:该交接的时刻,正是模型不可靠的时刻。

**当前状态:三个阶段均已落地并实测生效**(离线工具 → host 插件 projection → Web UI 提示条)。

---

## 安装

标准 DSH 插件包(host + 浏览器双面,一个包):

```bash
dsh plugin --profile web add -w dsh-attention-health
```

- **`-w` 不能省**:profile 自身是 pnpm workspace 根,否则会报 `ERR_PNPM_ADDING_TO_ROOT`;
- 包管理器请用**与 profile 一致的 pnpm 主版本**(profile 的 `node_modules/.modules.yaml` 记着
  创建它的版本;版本不一致会报 `ERR_PNPM_VIRTUAL_STORE_DIR_MAX_LENGTH_DIFF`);
- 从源码/GitHub 安装:`dsh plugin --profile web add -w github:<你的用户名>/dsh-attention-health`;
- 卸载:`dsh plugin --profile web remove -w dsh-attention-health`。

装完**必须重启 DSH**(插件行不会热加载;浏览器半只需刷新页面)。
本机实测:`dsh-node.err.log` 出现 `[attention-health] projection "attentionHealth" registered`,
且页面启动图里出现 `<包名>/client.js`。

## 关于本项目

由 AI 主导开发——需求、验收标准与真实场景测试由作者提出,代码、测试与文档由 AI 在长程会话中编写,
并经过多轮独立审查与真实数据校准(**670 项自动化测试**;阈值来自 2,244 个真实思考块与 79 个会话实测)。
它不是随手生成的玩具,也不是专业团队打磨的产品。

**维护状态**:个人项目,维护能力有限;欢迎 issue 与 PR,但**不承诺 SLA**。

---

## 这是什么

三个能力:

1. **实时思考守卫**(2026-09-16 新增)—— 在生成过程中逐帧检测思考的**复读 / 空转**,
   命中就**立刻中止本轮并提示原因**。这是本项目唯一会**主动干预**的能力。
2. **会话健康自检** —— 从会话日志机械推断注意力退化风险
3. **交接文档生成** —— 从会话日志机械提炼出可直接交给新会话的结构化 Markdown

除守卫外,其余能力都**不调用任何模型**;守卫本身也只做字符串判据,不额外发起模型请求。

核心设计立场:**要交接的时刻,正是模型已经不可靠的时刻**。所以交接文档绝不能靠 LLM 写——让退化的模型总结它退化的原因,产出必然是垃圾。会话日志是 append-only 的事件溯源记录,数据本身完好,与模型状态无关。

### 与生态插件的分工(2026-09-16 定位收缩)

DSH 生态里已有更成熟的**上下文洞察**工具,本插件刻意**不重复**它们:

| 你想要的 | 用哪个 |
| --- | --- |
| 上下文占用 / 构成 / 趋势、逐请求浏览、成本估算(元) | **`dsh-context`** —— 它读官方 token-meter 投影,比本插件自算的更准更细 |
| 一键导出交接文档(LLM 总结式,交互更直接) | `dsh-handoff-button` 等 |
| 按**规模 / 成本 / 缓存**判断"要不要新开" | `dsh-context-compass` 等(已覆盖,UI 更完整) |
| 按**内容退化信号**(思考打转 / 输出复读 / 守卫命中)判断"要不要交接" + 零模型交接文档 | **本插件**(生态里没有替代) |

所以它只做生态**不做**的三件事:

1. **退化 / 异常检测** —— 思考打转、输出复读、符号堆砌(真实事故:单思考块 53.5 万字符、
   同行重复 69,667 次 ≈ 一次 ¥1.02)。
2. **由检测得出的行动建议** —— 继续 / 交接。生态里的会话健康工具(如 `dsh-context-compass`)
   主要看**规模 / 成本**;本插件看的是**内容退化**(思考打转 / 输出复读 / 守卫命中),
   并据此**主动**给出"该交接了"的结论。
3. **零模型交接材料** —— 交接时刻正是模型不可靠的时刻,所以这份材料**绝不用 LLM 写**。
   这与"LLM 总结式"竞品是**不同取舍**,不是单纯的优劣。

成本明细只在**结论是交接**时作为依据显示,其余情况交给 `dsh-context`。

---

## 用法

```bash
# 健康自检:当前工作目录的最新会话
node handoff.mjs --health

# 健康自检:指定会话(支持 ID 前缀)
node handoff.mjs d346d3e8 --health

# 生成交接文档到文件
node handoff.mjs -o HANDOFF.md

# 指定会话 + 限制规模
node handoff.mjs 6938bea6 --max-turns 30 --max-requests 20 -o HANDOFF.md

# 列出会话
node handoff.mjs --list

# 生成 + 打印解析统计
node handoff.mjs --stats -o HANDOFF.md

# 汇总历史交接记录(阈值回溯素材,零 token;见下文「校准留痕」)
node handoff.mjs --calibration
```

`--health` 输出示例(2026-09-15 实测,对象是**本机真实发生过的退化事故会话**):

```
会话        :session-SAMPLE-e79e963a
健康评分    :50 / 100 — 上下文偏大
上下文规模  :353,386 token
占声明窗口  :35.3%(窗口 1,000,000)
有效占用    :58.9%(有效窗口 600,000,系数 0.6)
思考异常    :1 次(打转 1 / 其他 0),最长同一行重复 69,667 次
发现:
  - 已过有效窗口一半(评分 −25)
  - 思考过程出现打转:1 次(最长同一行重复 69,667 次)(评分 −25)
```

> 上面是 2026-09-16 边界设定后的真实输出。同一条会话在旧系数(0.5)下是 **30 分 / 上下文过大**,
> 现在 50 分 / 上下文偏大 —— 因为有效窗口从 500K 变成 600K,**分母变大、判定变宽**。
> 这是有意的:Chroma 的 Context Rot 研究给的是「声明窗口的 50%~60%」(见下文第 3 节),
> 0.5 取的是区间下端、0.6 取上端,同时让"何时劝交接"不再早到离谱(见「双口径」一节)。

> 那次事故:模型在**思考过程**里把「好。」重复了 69,667 次,`reasoningTokens` 打满 256,000
> 才停 —— 而同一轮的最终回复完全正常,所以只看输出的旧判据一个字都没报。
> 现在思考退化单列一行,并且参与评分(见下文「退化检测」)。

> 等级文案(正常 / 留意上下文 / 上下文偏大 / 上下文过大)**只描述上下文规模**,
> 不含行动建议;要不要交接,看交接文档里的「维度 A 质量(异常信号)」与命中率成本。
> **评分为启发式指标**(阈值内部校准、未做回溯验证):看同一会话的趋势可以,不代表精确测量。

---

## 生成的交接文档包含什么

| 章节 | 内容 | 数据来源 |
|---|---|---|
| 0. 为什么要交接 | 健康评分、上下文规模、风险信号 | `usage` + 事件分析 |
| 1. 会话标识 | ID、标题、cwd、preset、模型、窗口、时长 | header + `request/context` |
| 2. 当前目标 | goal 的 objective / phase / 轮次 | `goal/change` |
| 3. 用户请求主线 | **全部人类输入**(含问答回答) | `user/message` + `ask_user_question` 结果 |
| 4. 各轮进度概要 | 每轮的请求、结果、工具数、错误数、结束原因 | `turn/*` + `assistant/message` |
| 5. 涉及的文件 | 路径 + 操作类型与次数 | `tool/call` 的 arguments |
| 6. 待办事项 | checkbox 列表 | `todo/write` |
| 7. 遇到的错误 | 工具失败 + 轮次异常结束 | `tool/result.isError` + `turn/end.reason` |
| 8. 工作量统计 | 轮次、步骤、工具分布、token | 全量事件 |

---

## 会话内一键交接(UI 按钮,2026-09-15 新增)

会话里健康提示条**展开后**有一个「复制交接内容」按钮,点一下:

1. 浏览器请求 host 半注册的本地路由 `GET /attention-health/handoff?sessionId=…`;
2. host 半用**纯规则**折叠该会话的事件流(活会话读内存、冷会话读压缩日志),
   产出精简交接 Markdown —— **不调用任何模型、零 token**;
3. 内容**直接进剪贴板**,界面提示复制了多少字符 / 多少行,粘贴到新会话即可。

实测:当前会话 **3ms** 出结果、4516 字符;最大会话(9745 事件)也是 4ms 级别。

### 它是怎么实现的(接口都查证过)

| 环节 | 用的接口 | 说明 |
|---|---|---|
| 生成内容 | host 半 `webServer.register({kind:'prefix', path:'/attention-health', handler})` | 原生 `(req,res)` 通道。官方 `@Remote`/`ctx.remote` 需要 typert 生成链,本机无源码 checkout 无法构建,故不用 |
| 读会话事件 | 活会话 `ctx.sessions.get(id).snapshotEvents()`;冷会话 `ctx.sessionQuery.readSession(id)` | 活会话优先,拿到的是内存中最新内容 |
| 复制剪贴板 | `navigator.clipboard.writeText`,兜底 `document.execCommand('copy')` | 两条都失败时**明确报错**,不假装成功 |

⚠️ **注册路由必须用 `ctx.inject(['webServer'], cb)`,不能写成 `ctx.get('webServer')`**:
profile 的加载条目是**并行**初始化的,插件常常在 webserver 之前跑完 `apply`,
那一刻 `ctx.get` 只能拿到 `undefined`,路由就悄悄丢了(浏览器点按钮报 HTTP 404)。
这个坑真实踩过,`test/plugin-test.mjs` 里有一项专门守着它(mock 的 `get('webServer')`
故意返回 undefined,只有走 inject 才能注册成功)。

### 为什么不用"自动新开会话并预填"

曾经实现过:客户端 `uiWorkspace.startSession()` 新开会话 + 新会话 slot props 的
`inputActions.setDraft(text)` 写入草稿。这两个接口本身确实存在(都是官方 contract),
但实测客户端 `ctx.get('uiWorkspace')` 在 apply 阶段**同样拿不到服务**(与 host 侧同一个
时序问题),功能不可用。按用户决定改为剪贴板方案:不依赖任何服务时序、一步到位、零副作用。

> 失败时**不会静默降级**:按钮旁会显示具体原因(后端错误 / 剪贴板被拒等)。

### 交接内容结构(2026-09-15 提炼质量增强)

文档头之后(**「给接手方」操作指引 → 零模型声明 → 隐私提示 → 生成时间 / 数据来源**):

| 小节 | 内容要点 |
|---|---|
| 为什么交接(健康度) | 评分、上下文规模、有效占用,**并附评分机制说明**(分数只由单会话上下文规模驱动、与时间无关;质量维度**需伴随退化信号**才给交接结论:≥70% 有效 + 退化 → 建议交接、≥90% 有效 + 退化 → 立即新开会话;只有规模大、无退化则交给压缩/成本维度) |
| 会话 | ID、**最新标题**(含曾用标题)、cwd、preset、模型、时间跨度 |
| **最新目标** | 优先最新 `goal/change`;无 goal 时取**最近一条**用户请求;首尾差异大时并列「最初目标 / 最新目标」并提示以最新为准 |
| 用户请求主线 | 超长正文(>320 字)自动压缩成摘要,原文进附录 B |
| **关键决策与未决问题** | 从助手文本与问答回答里抽句,去重、按时间排序、标注来源轮次与角色。**两级词表**:主表是 25 个取舍词(根因 / 结论 / 决定 / 改为 / 放弃 / 必须 / 风险…);当整场会话主表命中不足 3 条时,自动用**通用结论词**(发现 / 推荐 / 因此 / 最优 / 汇总 / 合计)兜底补抽 —— 咨询、报表、生活对话里的数字结论(如"散件合计约 11900 元")靠这一级补回。文档里会写明本会话是否启用了兜底 |
| 各轮进度 | 最近若干轮的请求 / 结果 / 工具 / 错误 / 结束原因 |
| 涉及的文件 | 文件工具清单(精确)+「可能涉及(来自命令)」(从 `pwsh`/`bash` 文本启发式挑出的写文件动作) |
| 待办 | 最后一次 `todo/write` |
| 遇到的错误 | **按错误码聚合**:× N、出现轮次,以及"第 X 轮之后未再出现"或"最近一轮仍出现(未解决)" |
| 下一步 | 规则推导(未闭合轮次、未解决错误、进行中待办…) |
| 统计 | 总量 + **各轮上下文增长表**(增量 / 输出 token / 工具结果字符 / 主要来源),并列出**增长最快的轮次**及成因判定(大工具输出 vs 模型长输出) |
| 附录 A | **最近 1~2 轮完整原文**(不截断,总长上限 20,000 字符,超出在尾部截断并标注);**自动跳过"继续 / 好了"这类指令型轮次**,并在标题注明跳过了哪几轮 |
| 附录 B | 超长用户请求原文(**上限**:完整档 12,000 字符;精简档只展开第 1 条 ≈900 字符,超出截断并标注) |

> 全部为本地规则:零模型调用、零 token、不联网。

### 压缩成本 / 交接时机(双口径,2026-09-16 边界设定)

两个维度**各用一个坐标系**,运行时并列展示、各自标注口径,互不换算:

| 维度 | 口径 | 阈值 |
|---|---|---|
| **A 质量** | **有效窗口**(0.6×声明) | 规模**且**退化才动手:≥90% 有效 **且**检出退化信号(重复调用 / 输出退化 / **思考打转** / 思考异常)→ 立即新开;≥70% 有效 **且**检出退化信号 → 建议交接;只有规模大、无退化 → **本维度不给结论**(交给 B) |
| **B 压缩** | **声明窗口**(与官方线同坐标) | <50% 正常;50~65% 偏大;65~80% 较大;≥80%(含 80% 线前方 5 个百分点的预警带)→ **接近官方自动压缩线**。档位只描述规模,**不含任何行动号召**(见下) |

覆盖规则:**退化信号优先于压缩**;压缩后仍 >60% 声明、或已发生 ≥2 次**模型摘要** → 建议交接。
⚠️ 这两条覆盖规则都还要过一道**成本闸门**:按真实价格继续若并不比新开贵,就不改判(只在依据里保留"反复摘要有损"这个事实)。

**行动建议只有四档**(AG 节 2026-09-22 之后;**压缩不是建议** —— 它已退出行动域,
因为手动压缩的一次性成本在官方线到来前摊不回来,而到线官方会自己处理):

| 档 | 含义 | 界面 |
|---|---|---|
| 可以继续 | 没有触发任何判据 | 绿 `#22c55e` ● |
| 每轮成本偏高 | 省额为正但不值一提(< 门槛的 60%)—— 只是**状态**,不是建议 | 浅蓝 `#38bdf8` · |
| 接近交接阈值 | 钱上快划算了;**或**钱已划算但会话还早(轮数 < 15)—— 不催促 | 浅黄 `#facc15` ○ |
| 建议机械交接 | 钱上划算**且**会话已成熟;或有退化 / 守卫信号 | 黄 `#eab308` ● 加粗 |

> **为什么有"会话还早"这道软门槛**(2026-09-22 用户实测:「对话才 6 轮插件就建议机械交接了」):
> 一个 7 轮的会话在成本上确实"该换"(每轮十几次请求 × 二十几万存量,到官方线前省 ¥2.5~3),
> 但任务刚展开就换会话会把任务切成碎片,而"任务连续性 / 重建理解"这些代价**不在成本模型里**。
> 所以轮数低于 `matureSessionTurns`(15)时,**成本通道最高只到"接近交接阈值"**;
> 质量通道(退化 / 守卫)**不受此限** —— 那是止损,不是打扰。
> 标定与逐会话对照见 `work/ag-gradient-scan.mjs`(82 个真实会话:4 个从"建议交接"降为"接近阈值")。

> **为什么质量维度要加「且退化」这个前提**(2026-09-16 用扫描矩阵实测出来的):
> 旧规则里"有效 ≥90% → 立即新开"**只看规模**,而 90% 有效 = 声明 45% —— 早于官方线(80% 声明)。
> 结果是声明 45%~100% 全区间都被 `drivenBy=quality` 的 handoff 顶替,
> **那条「现在手动 `/compact`,否则官方即将自动压缩」的建议一次都没出现过**(官方线分支永远不可达)。
> 而"上下文大"本身不是故障信号,真正不可救的是退化(压缩只腾空间、不修退化);
> 反过来没有退化,也就没必要为了规模换会话。
>
> **为什么压缩档位改锚声明窗口**:锚在有效窗口(0.5×声明)时,声明 40% 起就全部落进 urgent,
> 紧急档失去区分度;更要命的是它必须与质量维度共用坐标,质量维度一旦只看规模就会遮蔽整条压缩曲线。

**推迟压缩**(2026-09-16 用户反馈后新增):档位只说明"风险在升高",**该不该现在动手**还要看
「回本能否在官方自动压缩线到来之前兑现」—— 因为官方压缩到线时**先做零 token 裁剪**
(prune;**仍会丢弃工具结果中段**),只有不够才调模型做有损摘要;而手动 `/compact` 是立刻调模型、立刻付全价。
于是满足任一条件就不催压缩,改为「可以继续(暂不必压缩)」:

- 距官方线还有 ≥30 轮(现在压没意义);
- **回本轮数 > 距官方线轮数**(收益兑现不了);
- 估不出轮数时,占声明窗口 <60%(保守代理)。

> 官方线分支**排在推迟规则之前**:到了官方线就不再谈"值不值"—— 那是控制权问题。
> 手动压缩与自动压缩都要调模型、都有损,区别只在谁定时机、以及之前能否先用零 token 裁剪争取空间。
> (代理线定为 60 而不是 50:50 正好等于档位起点,会让这条代理判据**永远不可达**,本轮踩过。)

同时修正两句不实文案:不再出现"有效窗口 ≥80% 但离官方线还远,却说否则官方即将自动压缩";
推荐档不再说"**先**压缩**再**继续"(档位本意是"阶段结束点执行")。

- 同时展示:声明窗口、有效窗口、当前上下文、**距官方压缩线余量**(含"还能撑几轮")、缓存命中/未命中
- 成本模型:**两套账并列** ——
  - **token 当量**(相对量,便于横向比较):A 继续 = 上下文×(命中率×`cacheFactor`(0.02)+未命中率×1) 每轮;
    B 先压缩 = **被压缩历史×缓存系数 + 摘要输出×全价 + 缓存重建(1.5 轮)**,之后 `压缩后上下文×同系数` 每轮;
    C 机械交接 = **交接文档实测长度**(两遍渲染回填,不是固定值);
  - **真实价格(元)**(绝对量,用于决策):DeepSeek flash 空闲价 **命中 0.02 / 未命中 1 / 输出 4**(元每百万 token)。
    新开会话**分段计费**:首轮 = 跨会话公共前缀(系统提示 + 工具 schema,实测首轮命中率中位数 **76%**)
    加权 + **交接文档按未命中价**;**第 2 轮起**首轮输入已落盘为缓存前缀单元 → 按命中价。
    判定 =「交接回本轮数 + 1 轮余量 < 预计剩余轮数」。
    (2026-09-18 修正:旧口径"新开会话一律按未命中价、`新开总额 × 1.5 < 继续总额`"
    对任何窗口长度**都无解** —— 实测 76 个会话里旧判定命中 0 个,成本维度等于被钉死。)
- 边界:**只提示、不自动压缩、不改官方 compaction 配置**;压缩是**续命**(有损),机械交接是**无损事实底座**

---

## 架构

```
handoff.mjs            CLI 入口(--list / --health / -o / --stats)
                       ⚠️ 渲染**直接复用** lib/handoff.js 的 buildHandoff
lib/                   ← 2026-09-18 标准包化:仓库根 == npm 包根,全部源码都在这里
  zstd-frames.mjs      Zstandard 多帧扫描与解压
  session-log.mjs      会话定位、代际选择、事件解析
  extract.mjs          【已弃用】早期的第二套折叠/渲染实现,仅供测试交叉验证
  detect.mjs           【离线侧】扫日志后**交给插件的同一套判据与评分**(不再自持实现)

  —— 下面是运行时(host + UI)的两半,也是产物质量的**唯一来源** ——
  index.js             host 半:attentionHealth projection + 交接生成路由
  handoff.js           barrel:只转发下面 6 个模块的导出(CLI 与插件都 import 这个路径)
  handoff-core.js      常量 / 评分 / token 口径(不依赖任何兄弟模块)
  handoff-text.js      文本工具(截断 / 清洗 / 切句 / 决策句抽取)
  handoff-judge.js     退化判据(全项目唯一实现)
  handoff-fold.js      事件流折叠(冷会话日志 → 结构化事实)
  handoff-plan.js      计划推导(评分 → 继续 / 压缩 / 交接)
  handoff-render.js    Markdown 渲染(交接文档)
  history.js           交接历史留痕(`--calibration` 的素材;纯本地、零 token)
  guard.js             实时思考守卫(复读 / 空转即中止本轮)
  guard-log.js         守卫现场落档(尾部 500 字,可切隐私模式)
  prices.js            价格数据与峰谷档位(可被 prices.json 覆盖)
  jsonl.js             JSONL 追加 + 512 KB 轮转
  client.js            UI 半:健康提示条 + 复制交接内容(浏览器 bundle)
cordis.patch.yml       本包作为 bundle 的挂载行(一行同时装入 host 与 UI 两面)
```

> **包形态(2026-09-18 标准包化)**:`package.json` 声明 `main: lib/index.js`、
> `exports["./client"]: ./lib/client.js`、`dsh.bundle.patch: ./cordis.patch.yml`、
> `dsh.client.platform: web` —— 与生态里同为双面的 `dsh-context` 完全同形,
> 所以别人可以 `dsh plugin --profile web add dsh-attention-health` 直接装。
> 浏览器半的**模块 id 必须等于包名**(`dsh-client-modules` 用解析出的包名当模块身份)。
> `files` 白名单只放 `lib/` + `handoff.mjs` + `cordis.patch.yml` + `README.md` +
> `SECURITY.md` + `LICENSE`,开发档案(`DEPLOYMENT-NOTES` / `HANDOFF.md` / `work/` /
> `test/` / `*.ps1`)**不进包**。
>
> **能力声明与 CLI 入口(2026-09-22 生态审计后补)**:`dsh.seams: ["fs", "web"]` 显式声明
> 本插件用到的能力(本地 JSONL 读写 → `fs`;浏览器半同源取数 → `web`);
> `bin: { "attention-health-handoff": "./handoff.mjs" }` 让离线 CLI 成为**可声明的入口**。
> 两者正是 [`dsh-vet`](https://github.com/rogerdigital/dsh-vet) 这类审计工具判断
> "声明与实际是否一致"的依据 —— 不声明时它只能按 `perm.network-client` 之类的
> per-capability 规则报警,声明后降为清单项。

> **安全审计(2026-09-22)**:按生态标准 `dsh-vet` 对**实际发布的 npm 包**扫描,
> 评级 **A**(0 critical / 0 high / **0 medium**)。剩下两条 info 是**预期内**的:
> `perm.unreachable-files` 只点名 `lib/detect.mjs` 与 `lib/extract.mjs` —— 它们是
> **测试与离线对齐专用**(见上表与下面的"漂移防护"),不进插件运行时图,
> 因此从入口做静态可达性分析时确实"不可达";`perm.network-client` 声明 `web` seam 后
> 降为 info("已声明的能力,仅作清单")。复现方式:`npm pack` → 解包 → `npx dsh-vet .`
> (**要扫 npm 包,不要扫仓库根**:仓库里的 `test/` `tools/` 会引入与包无关的告警)。
> 数据去向、能力边界与漏洞报告渠道见 `SECURITY.md`。

> **漂移防护(2026-09-15 全量复核 N-1 / N-5)**:CLI 与插件曾各持一套渲染实现,
> 第二批修复的 12 项只落到了插件里,于是同一会话会产出**两种质量**的文档
> (表格错位、附录 B 无上限、"最新目标"可能仍是"继续")。
> 现在 `handoff.mjs` 直接 `import { buildHandoff } from './lib/handoff.js'` ——
> **一处修改、两处生效**,漂移在结构上不可能再发生;`lib/extract.mjs` 的渲染层已标记弃用。
> 判据与评分同理:唯一实现是 `handoff.js` 的 `replyDegradeReasons()` / `scanReasoning()` /
> `scoreContextHealth()`,`lib/detect.mjs` 只负责扫日志、然后调用它们
> (`plugin-test` 会逐会话比对热/冷两条路径的 token 数与评分)。
>
> **2026-09-18 D3**:`handoff.js` 已按「判据 / 折叠 / 计划 / 渲染」拆成 6 个模块,
> 对外仍是同一个 barrel(导出 **17 个名字,一个不多一个不少**),
> 所以上面这些"唯一实现"的说法全部不变 —— 变的只是各自有了独立的文件边界,
> 改判据时不必再在 3,656 行里找依赖。

---

## 关键技术陷阱(都已实测确认)

### 1. 会话日志是多帧 zstd,不能用 Node 内置 API 直接解

- ❌ `zlib.zstdDecompressSync(buffer)` **只解第一帧**(435KB 文件只解出 207 字节)
- ❌ `zlib.createZstdDecompress()` 流式报 `ZSTD_error_prefix_unknown`
- ✅ 必须自己走帧边界再逐帧解压 —— `scanZstdFrames()` 移植自 DSH 自带的
  `@deepseek-ai/dsh-session-persistence-jsonl`(MIT),保证与官方实现一致

**实测**:71/71 个会话解析成功,3 秒完成,0 帧失败。

### 2. `usage.inputTokens` 是**未命中缓存**的输入,不是上下文规模

这是最容易踩的坑。实测某个长会话:

```
inputTokens: 142       ← 只有 142!
cacheReadTokens: 258944
totalTokens: 259196    ← 真实上下文规模
```

**用 `inputTokens` 会把上下文低估三个数量级。** 必须用 `totalTokens`
(= inputTokens + cacheReadTokens + outputTokens,已实测验证)。

### 3. 模型声明的窗口 ≠ 可靠工作区间

用户环境里 `contextWindow` 被配成 **1,000,000**。但 Chroma 的 Context Rot 研究显示,
宣称百万窗口的模型在**声明窗口的 50%~60%** 处就已明显退化。

因此评分基于「**有效窗口** = 声明窗口 × 0.6」,而占声明窗口的百分比仍然如实呈现 ——
**事实与判断分开**,避免误导。系数的**唯一来源**是 `lib/handoff.js` 的
`COMPACT_DEFAULTS.effectiveWindowRatio`(`lib/detect.mjs` 那份与它保持同值)。

### 4. 用户对 `ask_user_question` 的回答**不走** `user/message`

它作为**工具结果**返回:`{"answers":[{id, selected, custom}]}`。
只统计 `user/message` 会漏掉这些交互(实测某会话 9 次人类输入中有 3 次是这样来的)。

### 5. `user/message` 的 `source.kind` 必须过滤

实测分布:`user` × 6、`agent-message` × 3、`subagent-settled` × 3、
`agent-instructions` × 1、`plugin` × 1、`goal` × 1。只有 `kind === 'user'` 是人类输入。

### 6. 磁盘上存在多个代际,必须只读最高版本

`session.jsonl.zstd`(v0) / `session.v2.jsonl.zstd` / `session.v3.jsonl.zstd`。
v0 的词汇表不同(`assistant/chunk`、`reasoning-chunks` 等),且用 `seq0`/`time0` 字段名。
实测分布:v0 × 38、v2 × 3、v3 × 30。

### 7. `todo` 投影会在每轮开始被清空

DSH 的 `dsh-tool-todo` 在 `turn/start` 时把投影重置为 `null`。所以必须**自己折叠日志**,
取最后一个 `turn/start` 之后的最后一条 `todo/write`。

---

## 已知限制(诚实声明)

- **文件修改清单不完整**:只覆盖经 DSH 文件工具(`str_replace_editor` / `write` / `edit`)
  完成的修改。**通过 `pwsh` / `bash` 直接写文件的操作不会出现在日志中**。
- **"关键决策与未决问题"是启发式抽取**(已实现,不是"没做")。日志里没有"决策"事件,
  只能按关键词从助手文本与问答回答里抽句。词表刻意偏"取舍 / 结论"语境(宁可少抽也不要噪音),
  所以**非工程领域可能漏抽**;漏掉的内容由「用户请求主线」与附录原文兜底。
- **压缩过的会话评分有上限**:经历 1 次模型摘要 → 最高 79 分、2 次 → 65、≥3 次 → 50。
  压缩是"续命"(有损),不该把分数送回满分(那会把"续命"记成"治愈")。
- **重复工具调用检测过于保守**:只做精确匹配(同工具 + 同参数)。
  实测 71 个会话中该信号**从未触发**(因为 DSH 自带的 `repeat-tool-reminder`
  会在 3 次时提醒模型,模型通常会改)。近似重复(改了个路径)检测不到。
- **退化检测是启发式的**,分两类信号(判据的实测依据见下一节):
  - **输出退化**:代码块围栏不闭合、同行复读、段落逐字重复、符号堆砌(表格框线字符已排除,
    且**先剥掉代码、要求命中串以非 ASCII 符号为主** —— 见下文二次复核)。
    原「中英严重混杂」判据**已删除** —— 全量 25 次命中、25/25 全是误报。
  - **思考退化**:行重复率 ≥0.7 且行数 ≥30(打转)、单块 ≥10 万字符(异常长)、
    **`usage.reasoningTokens` ≥ 16,000(真实计量,与字符数无关)**。
  - 立场不变:**宁可漏报也不误报**(误报会让用户忽略提示,比不提示更糟)。
- **评分是启发式指标**:阈值与扣分力度是内部校准出来的,**没有做回溯验证**,
  用途是**比较同一会话的变化趋势**,不是精确测量 —— 交接文档与界面都写明了这一点
  (阈值校准留痕见下一节)。
- **`contextWindow` 依赖日志里的 `request/context` 事件**。若会话从未记录该事件,
  无法计算占用百分比。
- **验证范围是单机、单 DSH 版本**(2026-09-20 补):全部阈值与判据是在**一台机器**、
  `dsh 0.1.5-rc.2` + `cordis 4.0.2` 上、用 79 个真实会话标定与回归的。换机器 / 换模型 /
  官方改契约后,结论可能不再成立 —— 遇到不符请带着会话日志提 issue。
- **成本口径是估算,不是账单**:单价来自官方公开价目表(可被 `prices.json` 覆盖),
  「每轮」按实测平均请求数换算;官方调价或改缓存规则后需要更新价表。

---

## 隐私与威胁模型(2026-09-18 补)

插件**全程本地**:零模型调用、零 token、不联网、不启动子进程。但它确实会**写盘**,
位置与上限如下(全部在 `$DSH_HOME\attention-health\`,可用 `DSH_HOME` 环境变量整体改道):

| 文件 | 内容 | 上限 |
| --- | --- | --- |
| `handoff-history.jsonl` | 每次交接的一行统计(评分、建议、成本口径,**不含对话原文**) | 512 KB 后轮转为 `.1.jsonl`,共两份 |
| `guard-trips.jsonl` | 守卫命中的种类、理由、`detail`,以及**当时的思考原文尾部** | 同上 |
| `prices.json` | 可选的价格覆盖表(没有就用插件内置价) | 手工维护,无轮转 |

**守卫现场(唯一会落"原始文本"的地方)**:

- 默认保留思考原文**尾部 500 字** —— 这是为了"误杀要能事后复核":
  守卫会**中断正在进行的生成**,抓错了必须能回看当时到底贴了什么(`dsh-node.err.log`
  每次重启覆盖,靠不住)。实际至今真实命中 0 次、该文件为空;
- 想完全不落原文:设 `DSH_ATTENTION_HEALTH_GUARD_TAIL=0`,改为**只存哈希 + 总长度 +
  首尾各 40 字特征**(足够人工认出是哪一次,但不含完整正文);
- 想更短:设成任意非负整数(如 `120`)即保留尾部 120 字;非法值回落到 500。

**清理入口**:直接删掉 `$DSH_HOME\attention-health\` 目录即可(没有索引、没有外部副本,
删了就是删了);插件下次用到时会自己重建。交接历史丢了只影响阈值回溯分析,不影响功能。

**威胁模型**:插件的 HTTP 路由 `GET /attention-health/handoff` 是**本机通道**,把守条件为
四层同时成立 —— Host 是 loopback、`sec-fetch-site` 非 cross-site、Origin(若带)与 Host 同源、
以及**连接来源 `remoteAddress` 是 loopback**。

- **不校验 token**:官方 `isTrustedApiRequest` 同样不校验(token 校验在 `browser-auth` 层,
  值存在模块私有的 `PROCESS_LAUNCH_TOKENS` WeakMap 里,**插件拿不到**),所以这里不发明新机制;
  `remoteAddress` 是**唯一不可伪造**的一层(Host 头可以伪造),当前只绑 `127.0.0.1` 时它无副作用,
  将来若开放局域网,它能挡住"外部伪造 Host"这一类;
- 由此得出的边界:**能访问这条路由的本机进程,本来就能直接读 `~/.dsh/sessions`**
  (交接文档的全部素材都来自那里),所以这条路不额外扩大暴露面。

---

---

## 退化检测与校准留痕(2026-09-15 复核 N-5 / N-6)

### 判据按实测数据重做

旧判据在**全量 73 会话 / 4,185 条回复 / 3,491 个思考块**上的实测表现:

| 旧判据 | 真实触发 | 处置 |
|---|---|---|
| 中英严重混杂(全篇中文占比 5%~25%) | **25 次,25/25 全是误报** | **删除** |
| 长段落逐字重复(>60 字) | 1 次 | 阈值降到 25 字(中文一句话常 25~50 字) |
| 围栏不闭合 / 同行复读 / 符号堆砌 | **0 次** | 被全局 `length < 80` 门槛挡住 → 门槛删除 |

误报的代价是具体的:规则 5 每条扣 6 分、最多扣 30 分,还会让提示条显示"输出异常 N 条" ——
而**误报会让用户忽略提示,比不提示更糟**(这是插件自己写下的立场)。

另外「符号堆砌」原来会把 Unicode 边框表格(`╔══╦══╗`)判成退化,现在排除了表格框线字符;
命中原因现在会带进提示条与文档(如"输出异常 2 条(符号堆砌 ×2)"),用户能自己判断提示可不可信。

### 二次复核:符号堆砌在思考里 100% 是误报(2026-09-15 深夜)

N-5 排除了框线字符,但漏掉了更大的误报源:**代码本身**。全量实测
(70 会话 / 3,638 个思考块 + 2,271 个回复块):

| 判据 | 思考侧命中 | 回复侧命中 | 人工判定 |
|---|---|---|---|
| 围栏不闭合 | 0 | 0 | 从未触发(安全网)|
| 同行复读 | 0 | 0 | 从未触发(安全网)|
| 段落复读 | 0 | 1 | 真实重复(同一段代码块逐字出现两次)|
| **符号堆砌** | **21** | **1** | **22/22 全部落在代码里** |
| 思考打转 | 1 | — | 真事故 |
| 思考异常长 | 1 | — | 真事故 |

命中的串清一色是**模型正在思考正则和代码**:

```
/^[{}()[\];,.<>|=+\-*/\\_#`~^&%$@!?:"']+$/
`^\*\*(.+)\*\*$`
"((?:[^"\\]|\\.)*)"
('|---|---|---|---|');
```

也就是说,旧判据等于**因为模型思考正则表达式而扣它 12 分**(`reasoningFlags` 每条 −4、上限 −12)。
修复两条:先 `stripCodeBlocks()` 剥掉围栏代码块与行内代码;再要求命中串**以非 ASCII 符号为主**
(真实乱码 / 卡图必然是非 ASCII,而长串 ASCII 标点实测 100% 是正则或代码)。

复验:思考侧 21 → **2**、回复侧 1 → **0**。残留 2 条的形态是 `◆◇■□▲△▼▽…`,
来自一个"正在写符号堆砌测试样本"的会话 —— 文本上无法与真乱码区分,属可接受的止损。
受此修复影响,3 个历史会话曾被错误多扣 12 分、另外 3 个多扣 4 分。

### 最大盲区:思考过程

旧版只读 `type === 'text'`,思考块**一个字都没检查**。而实测思考量是最终回复的 7.9 倍
(3,491 块 / 592 万字符 vs 2,284 块 / 75 万字符),用户描述的退化现象恰恰是
"对话长了以后思考过程夹杂一堆无用的东西和符号"。真实事故:

```
会话      :session-SAMPLE-bfd2b142(本机)
单个思考块:535,355 字符 / 116,152 行 / 只有 56 个唯一行(重复率 0.9995)
最高频行  :「好。」×69,667、「执行。」×23,214、「(执行)」×23,205
usage     :reasoningTokens = 256,000(输出预算被打满)
旧判据    :**正常**(漏检)          新判据:思考打转 ×1 → 评分 30/100
```

新增两条思考判据(阈值由该事故 + 全量 3,491 个思考块**零误报**共同定出):

| 判据 | 条件 | 阈值敏感性(实测) |
|---|---|---|
| `thinkLoop` 思考打转 | 行数 ≥30 且行重复率 ≥0.7 | 0.6/0.7/0.8/0.9 都只命中那 1 个事故块;0.5 会多带 2 个正常代码块 |
| `thinkHuge` 思考异常长 | 单块 ≥10 万字符 | 实测最长正常块 44,249,事故块 535,355 |
| `thinkBudget` 思考预算打满 | `usage.reasoningTokens` ≥16,000(**真实计量**) | 3,534 条带该字段的消息 p50=269 / p90=1,648 / p99=4,237,事故那条 256,000 → 语料上零误报 |

> `thinkBudget` 与 `thinkHuge` 不是重复:后者只看**单个块**的字符数,
> 遇到"多个中等块加起来烧掉预算"就会漏(每块都不到 10 万字符);
> `thinkBudget` 按**消息级真实 token** 判定,且**不依赖思考正文是否落盘**(该字段 81% 的消息都有)。

思考退化参与评分:打转 **−25/次(上限 −50)**、其他异常 −4/次(上限 −12)——
比输出退化(−6/条,上限 −30)重,因为一次打转就是几万 token 的输出预算被烧掉。

### 校准留痕(N-6)

复核指出:所有阈值(30/50/65/70/80/90、扣分力度、0.6 系数)都是**内部校准**出来的,
校准目标是"分数与等级自洽",**没有做过预测准确性验证**。最低成本的改进是留痕:

- 每次生成交接文档(UI 按钮 / CLI)都会往
  `$DSH_HOME\attention-health\handoff-history.jsonl` **追加一行**:
  时间 / 会话 / 评分 / 等级 / 有效占用 / 压缩次数 / 退化信号 / 文档字符数;
- `node handoff.mjs --calibration` 汇总这些记录(评分分布、有效占用中位数、最近 10 次);
- 零 token、纯本地、只追加,**写失败绝不影响交接生成**。

攒够样本后就能回答复核提的那个问题 —— "评分 55 分时交接,新会话实际能撑多久" ——
用真实数据反推阈值。

---

## 现状与后续

### ✅ 阶段 1:离线工具(已完成)

- zstd 多帧解析、会话定位、代际选择
- 交接文档机械提炼(8 个章节)
- 健康度检测(上下文占用 / 重复调用 / 输出退化)
- CLI 三模式(`--list` / `--health` / 生成文档)
- **验证**:71/71 会话解析成功,3 秒,0 错误

### ✅ 阶段 2:host 插件(已部署并生效)

实现:`lib/index.js`。**2026-09-20 门 3 起,线上形态是标准插件包**:
`$DSH_HOME\profiles\web\node_modules\dsh-attention-health`(由 `dsh plugin add` 装入,
层栈记在 profile 的 `dsh.profile.bundles`)。旧形态(`profiles\web\attention-health\` +
`profiles\node_modules\attention-health-ui\` 两个手拷目录 + patch 里两条 insert 行)
**已退役但保留在磁盘上**,作为回退线(快照见 `<TOOLS>\DSH\留档\attention-health-发布前快照-20260920\`)。

> ⚠️ 开发时的关键差别:**改 `lib/` 不会自动同步到线上**。改完要
> `.\install-package.ps1`(打包 + 安装)再重启;自测用 `.\test-all.ps1`(自动指向线上那份)。
> 这两个脚本随仓库公开;`deploy.ps1` / `restart-attention-health.ps1` 是作者本机的开发工具
> (内含本机 DSH 安装布局),**不随仓库公开**。

- 函数形态插件:`export { apply, inject, name }`,`inject: ['sessionProjections']`
- 注册 `attentionHealth` projection(`ctx.sessionProjections.register(def)`),**客户端零代码**自动同步
- `apply(state, event)` 纯同步折叠,**无关事件返回同一引用**(严格遵守投影契约)
- 检测数据全部来自已入日志的事件,**零 token**
- **算法与 `lib/detect.mjs` 刻意保持一致**,用测试保证不漂移

**验证**(五套自测合计 **486 项**全绿,2026-09-16):

| 测试 | 规模 | 覆盖 |
| --- | --- | --- |
| `test/plugin-test.mjs` | **84 项** | 加载 / 注册 / schema 契约(**含 wire 键集合逐键比对**);折叠契约(无关事件返回同一引用、null 安全);6 个真实会话与离线版 **token 数 / 评分 / 等级 / 退化计数逐一致**;边界(缺 usage 不崩、正确使用 totalTokens、100% 有效占用判为 critical);**畸形输入加固回归(null 事件、contextWindow 0/负/NaN、usage NaN/Infinity 均不得产出非法 state)**;**推迟压缩规则**;有损压缩的评分封顶;**热/冷两条路径的退化判据逐项一致(含思考打转)** |
| `test/ui-build-test.mjs` | **100 项** | 客户端包契约、各 health 取值的渲染、**"整条提示条只允许一个行动建议"**、展开区排版(每项只出现一次、不出现未渲染的 Markdown)、**思考异常行与命中原因**、**"为什么这么建议"与三方案成本对照**、**缺数据时不许编造结论**、**host 未重启(缺新字段)时的向后兼容**、**结论说"别压"时不得出现省钱暗示**、**回本偏慢的语境**、启发式标注 |
| `test/handoff-test.mjs` | **278 项** | 提炼正确性、渲染完整性、边界健壮性、性能、两个输出档位、压缩不该"治愈"评分、**退化判据的正/反例(含真实事故形态、旧误报样本、代码里的正则不算退化)**、思考预算真实计量判据、**上下文规模单一实现(缺 `totalTokens` 时三项相加、忽略 NaN/Infinity)**、**缺数据时"继续"与"无法判断"分开 + 界面依据 `FINAL_NOTES`(无 Markdown、不复述结论)**、**推迟压缩(回本 vs 官方线)**、**压缩次数覆盖规则的成本闸门(摘要 ≥2 但钱不支持新开时不得再劝交接;缓存真崩时仍须劝)**、**边界可达性守卫(网格扫描:四个档位 / 官方线分支 / 结构不变量 / 常量间结构关系)**、**文档侧不得给出"每轮省"暗示**、分级词表、校准留痕、**输出体积(附录上限不变式 + 确定性 fixture)** |
| `test/guard-test.mjs` | **13 项** | 实时思考守卫(**正反例并重**):正常推理 / 100 行各不相同 / 短行反复 / 同关键词多句 —— 均**不得**触发;同行连续 8 次 / 行内片段复读 / 窗口空转 / 真实事故形态 —— **必须**抓住;`enabled=false` 与畸形输入安全;性能(20000 delta ≈ 80 万字符 < 2 秒) |
| `test/crosscheck-test.mjs` | **11 项** | **多方面求证**:74 个会话在**热路径投影 / 冷路径日志折叠 / 离线 CLI** 三条实现上逐项一致;全量不变量(score 区间、level 与阈值一致、计数非负、封顶生效、schema 与 view 可序列化);**27 例畸形输入不得抛异常**;性能(无 O(n²) 迹象、最大真实会话折叠 < 2 秒)。**加载已部署副本**,所以顺带验证"源码改了是否真的部署了"|

测试过程中抓到并修复了两个真实 bug:

1. `wire.view` 直接返回 state 会被 `viewSchema` 的 `.strict()` 拒绝
   (`ZodError: unrecognized_keys ["lastCallKey","callStreak"]`)—— 若直接挂载,DSH 每次发布都会报错
2. 评分与等级阈值不匹配:100% 有效占用只判到 `warn`。已校准扣分力度
   (≥90% → −75 → critical),并同步修正 `lib/detect.mjs`

✅ **已加载生效**:新增/更新插件行**不会热加载,必须重启 DSH**(HANDOFF.md 第 2.1 节实测)。
本机已验证:`dsh-node.err.log` 出现 `[attention-health] projection "attentionHealth" registered`。
重启与自动验证用 `restart-attention-health.ps1`(14 项检查,结果写
`<TOOLS>\DSH\临时\restart-result.txt`)。
日志按 `[RUN <id>] BEGIN` / `[RUN <id>] END` **配对**读:**只有 BEGIN 没有 END = 这次没跑完**,
紧挨着的最后一条日志就是中断位置(不再需要猜"是卡死了还是没输出")。
`-VerifyOnly` 可只验证当前状态、**不重启**(同步执行,不会掐断会话);
完整说明见 `DEPLOYMENT-NOTES-20260915.md` 第 21 节。

### ✅ 阶段 3:Web UI 提示(已实现并注册进页面)

采用了原「出路 1」:**手写浏览器半产物**(`lib\client.js`),不依赖 tsdown 构建链。
本机无源码 checkout,但宿主加载客户端 bundle 的契约很简单(`window.__ModuleLoader__.load`),
且所需依赖(`react`、`@deepseek-ai/dsh-client-ui-primitives` 等)全在页面基座 seed 内,
所以手写可行 —— 已实测进页面插件批次并正常渲染。

- 位置:`conversation.composer.dock`(composer 卡下方常驻条目),`id: attention-health`,
  与既有 `ui-chat` 的 StatsPills(`id: stats`)并存互不覆盖
- 渲染条件:**`shouldNotify`**(`level !== 'ok'` **或**最终建议不是"继续")。
  早期只看 `level`,于是"score 92/ok 但建议交接"时整条提示条**连同复制按钮一起消失**
- 健康时常驻一个**极轻入口**(一行淡字「复制交接内容」),不再是"正常时完全不占空间" ——
  用户可能在任何时候想主动交接,而不是只在被提示时
- 形态:彩色圆点 + **唯一行动建议**(双维度最终结论)+ 评分(有损压缩过则附「有损压缩 N 次」,
  检出思考退化时附橙色「**思考异常 N 次**」);
  点击展开:上下文规模 / 维度A 质量(有效窗口)/ 维度B 上下文占用(声明窗口分档)/
  **为什么这么建议** / **三方案每轮成本** / 评分依据 / 启发式说明;
  退化原因带次数(如"输出异常 2 条(符号堆砌 ×2)"、"思考异常 1 次(思考打转)")
- **为什么这么建议**(2026-09-16 新增):显示 host 给出的**一句话决策依据**
  (如"已发生 3 次有损摘要,反复压缩收益递减""退化信号优先于压缩")——
  它解释的是决策逻辑,不复述占用率,避免同一信息渲染两遍
- **三方案每轮成本**(2026-09-16 新增):`继续 X / 先压缩 Y(有损) / 机械交接 Z(无损)`,
  附「压缩每轮省 S,约 N 轮回本」与**缓存命中率**(成本折扣的依据)。
  三个数都是**相对 token 当量**,不是钱 —— 让"权衡"本身可见,而不是替用户下结论
- **缺数据时如实说"无法判断"**(2026-09-16 边界修复):没有窗口或没有用量时,
  规模显示"未知"、两个维度显示"无法判断(缺窗口或用量数据)"、成本显示"未知"。
  旧版会渲染成 "0 token · 有效占用 未知" 并给出 "可继续 / 继续" 这种**编造出来的结论**
- 展开区底部两个按钮:**复制交接内容**(精简档,默认)与**复制完整版**
- 等级文案**只描述规模**(留意上下文 / 上下文偏大 / 上下文过大),不含任何行动建议;
  行动建议的唯一来源是 host 的双维度结论(`compactAdviceText`)
- 安装方式(2026-09-20 起):与 host 半**同一个包** ——
  `$DSH_HOME\profiles\web\node_modules\dsh-attention-health\lib\client.js`,
  由包内 `cordis.patch.yml` 的**一行** `insert` 同时挂载(`dsh.client.platform: web` +
  `exports["./client"]` 让外壳自己把它变成浏览器 bundle;模块 id 必须等于**包名**)。
  旧形态(单独的 `attention-health-ui` 包 + patch 里第二行)已退役,见「阶段 2」的说明。

> 另注:DSH **自带**的 `ContextMeter` 已经在 composer 旁显示占用率,但它不在任何 slot 中、
> 也未从包导出,外部插件无法替换它,且它只有中性色、没有健康语义 —— 这正是本插件提示条的价值空间。


---

## 相关文档

- [`docs/architecture.md`](docs/architecture.md) —— 架构:两个半身、三条实现路径、判定链的单一来源
- [`docs/development.md`](docs/development.md) —— 开发、测试与发布(含无真实语料环境怎么跑自测、换机清单)
- [`docs/privacy.md`](docs/privacy.md) —— 数据去向、隐私开关、发布侧脱敏流水线与四条防线
- [`SECURITY.md`](SECURITY.md) —— 能力面、数据去向声明与漏洞报告渠道
- [`AGENTS.md`](AGENTS.md) —— 项目常驻约定(每一条都是踩过的坑)
- `DEPLOYMENT-NOTES-20260915.md` —— (开发档案,不随包分发)**部署与排障**:
  一键部署/重启脚本用法(第 8 节)、两个历史故障的根因与修复、四个真实坑的记录与修法(第 9 节)
- `HANDOFF.md` —— (开发档案)完整的框架调研结论(需求、环境、技术结论、坑与红线、待验证清单)

Install

dsh plugin --profile web add github:donghangxunlang-cmd/dsh-attention-health

Profile: web

  • This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.
Source