@kira905/dsh-diagnostics-extra 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.en.md ADDED
@@ -0,0 +1,269 @@
1
+ # dsh-diagnostics-extra · Diagnostics & reconciliation utilities for DSH
2
+
3
+ > A small set of **read-only diagnostic tools** that actually get run when DeepSeek Harness (DSH)
4
+ > misbehaves: **image-attachment reconciliation / recovery** (why a session synced from another
5
+ > machine fails with TRANSPORT on the next turn), **one-command "what is running where"**
6
+ > (host / dsh & guard start times / connections on port 3080 / guard log tail), and an
7
+ > **"owner action required" scanner** that merges pending decisions from board cards,
8
+ > handoff documents and peer-machine run sheets.
9
+ >
10
+ > All tools run on **Node.js ≥ 20** with **zero third-party runtime dependencies** (`node:` builtins only). Windows-first.
11
+
12
+ ---
13
+
14
+ ## ⚠️ Read this first (more important than any usage example)
15
+
16
+ 1. **One of the four scripts silently ignores unknown arguments and then executes the real action.**
17
+ `owner-todo-scan.mjs` decides switches by "is the flag present"; it has **no help flag**.
18
+ Typing an unrecognized argument will not stop it, and with `--write` it **rewrites
19
+ `docs\your-todo-<device>.md`**. ⇒ Read that script's header comment (first 25 lines) or copy
20
+ the exact flags from the per-tool table below.
21
+ 2. **The other three are safe**: `attachments-audit.mjs`, `attachments-fetch.mjs` and `dsh-why.mjs`
22
+ implement `-h` / help output and **reject unknown arguments by printing usage instead of acting**.
23
+ Still, this document gives no help examples inside code blocks — the four tools differ enough
24
+ that copying the per-tool table is safest.
25
+ 3. **Only two write paths exist**: `attachments-fetch.mjs` materializes missing objects
26
+ (supports `--dry-run` preview, `--report` and `--rollback`) and `owner-todo-scan.mjs --write`
27
+ rewrites the pending list. Everything else is read-only.
28
+ 4. These tools make **no network requests** and **never read credential files**. They only read
29
+ your local DSH data root and docs tree.
30
+
31
+ ---
32
+
33
+ ## 1. Requirements & install
34
+
35
+ ```powershell
36
+ # Node 20+; no npm install needed (zero deps)
37
+ node --version
38
+
39
+ # Run from the repository root
40
+ node attachments-audit.mjs --json
41
+ node dsh-why.mjs --log-lines 20
42
+
43
+ # Or install globally and use the bin names
44
+ npm i -g @kira905/dsh-diagnostics-extra
45
+ dsh-attachments-audit --json
46
+ ```
47
+
48
+ ### Environment variables (all optional)
49
+
50
+ | Variable | Meaning | Default |
51
+ |---|---|---|
52
+ | `DSH_ROOT` | DSH workspace root (the tree holding `docs\`, `tools\`, `_archive\`) | `~/DSH` |
53
+ | `DSH_HOME` / `BUTLER_DSH_HOME` | DSH home (`sessions`, `attachments`, `task-board`) | `~/.dsh` |
54
+ | `DSH_WORKSPACE` | Overrides workspace-root resolution | derived from `DSH_HOME` / `DSH_ROOT` |
55
+ | `DSH_DOCS_DIR` | Docs directory scanned by `owner-todo-scan` | `<workspace>/docs` |
56
+ | `DSH_TOOLS_DIR` | DSH tools root (parent of the optional kernel) | `<workspace>/tools` |
57
+ | `DSH_BUTLER_LIB` | Optional kernel directory (`upgrade-lock.mjs` / `lane-gate.mjs` / `residue-reaper.mjs`) | `<DSH_TOOLS_DIR>/dsh-butler/lib` |
58
+ | `DSH_DEVICE_LABEL` | Device label (list filename / health-check short code) | host name |
59
+ | `DSH_PEER_LABEL` | What "the peer machine" is called in your setup (peer-run-sheet judge) | `对端机` |
60
+ | `DSH_ATTACH_RELAY` | Attachment relay directory (highest priority for `attachments-fetch`) | see §3.2 |
61
+
62
+ > These tools grew inside a private working tree. The published version replaces every
63
+ > machine-absolute path with `DSH_ROOT` / `os.homedir()` / environment variables + relative paths,
64
+ > and replaces host names and user names with generic placeholders.
65
+ > Set `DSH_ROOT` on your machine first, otherwise `docs\` is looked up under `~/DSH`.
66
+
67
+ ---
68
+
69
+ ## 2. Repository layout (every file)
70
+
71
+ ```
72
+ dsh-diagnostics-extra/
73
+ ├── attachments-audit.mjs # referenced image attachments vs local objects: read-only reconciliation
74
+ ├── attachments-fetch.mjs # fetch missing objects by sha256 / export / rollback (writes; dry-run available)
75
+ ├── dsh-why.mjs # read-only health check: host / dsh & guard start times / port 3080 / log tail
76
+ ├── owner-todo-scan.mjs # scans pending owner actions (--write rewrites the list)
77
+ ├── lib/
78
+ │ ├── dsh-home.mjs # DSH home / workspace / report directory resolution
79
+ │ ├── attach-refs.mjs # attachment references in session logs + object paths (sha256)
80
+ │ ├── session-file-name.mjs # session log filename judge (all generations; avoids exact-name blindness)
81
+ │ ├── powershell-exe.mjs # PowerShell interpreter selection (pwsh 7 first, 5.1 fallback)
82
+ │ ├── redact.mjs # output redaction (key-name or value-shape; display only)
83
+ │ └── generated-docs.mjs # generated-document markers and family matching (pure constants + functions)
84
+ ├── README.md
85
+ ├── README.en.md
86
+ ├── CHANGELOG.md
87
+ ├── LICENSE
88
+ ├── package.json
89
+ ├── .gitignore
90
+ └── .gitattributes
91
+ ```
92
+
93
+ > The closure also contains `lib/session-file-name.mjs`: `attach-refs.mjs` takes its session-log
94
+ > filename judge from it. Without it, reconciliation goes blind when the host package renames the
95
+ > log (`session.vN.jsonl.zstd`).
96
+
97
+ ---
98
+
99
+ ## 3. Per-tool reference
100
+
101
+ ### 3.1 `attachments-audit.mjs` — attachment reconciliation (read-only)
102
+
103
+ **What it answers**: a session with images, synced from another machine, fails on the next turn with
104
+ `DeepSeek API stream from … failed` / `code=TRANSPORT`. It looks like a network fault, but the image
105
+ objects (`<DSH_HOME>/attachments/v1/objects/<first2>/<sha256>`) are simply absent locally: every request
106
+ scans all historical messages for images, the missing object throws a plain Error, and the fallback
107
+ classifies it as TRANSPORT. This tool turns "which objects are missing / which sessions are affected"
108
+ into observable facts.
109
+
110
+ | Flag | Meaning |
111
+ |---|---|
112
+ | `--home <dir>` | Override DSH home (`BUTLER_DSH_HOME` → `DSH_HOME` → `~/.dsh`) |
113
+ | `--session <id\|prefix>` | Audit a single session |
114
+ | `--json` | Machine-readable output |
115
+ | `--quiet` | Exit code only (for boot / handoff hooks) |
116
+ | `--no-cache` | Full rescan, ignore cache |
117
+ | `--cache <file>` | Override cache file (default `<home>/butler-attach-scan.json`) |
118
+ | `--top <n>` | Max sessions listed in the summary (default 20) |
119
+ | `--sessions-only` | Sessions only, no per-object expansion |
120
+ | `--verify-objects` | Recompute sha256 for hits (slow; catches "present but wrong content") |
121
+
122
+ **Exit codes**: `0` no missing objects · `3` missing objects · `2` usage error · `4` environment
123
+ unavailable (cannot read sessions/attachments root). `4` is fail-closed: **unreadable ≠ nothing missing**.
124
+
125
+ **Read-only**: writes nothing except its own cache; the cache key includes relative path + size +
126
+ mtimeMs (mtime alone is fooled by copies).
127
+
128
+ ### 3.2 `attachments-fetch.mjs` — fetch / export / rollback (writes)
129
+
130
+ **What it does**: materializes missing image objects by sha256 into
131
+ `<DSH_HOME>/attachments/v1/objects/<2>/<sha>`, using the exact layout DSH expects.
132
+ Source priority: `--from` → `--relay` / `DSH_ATTACH_RELAY` / common relay dirs → local derived
133
+ sources (`<home>/dsh-imagegen/{images,gallery/images}`, `<workspace>/generated`, matched by content sha).
134
+
135
+ | Flag | Meaning |
136
+ |---|---|
137
+ | `--hash <sha256>` | Fetch given objects (repeatable; comma-separated accepted) |
138
+ | `--session <id\|prefix>` | Fetch every missing object referenced by that session |
139
+ | `--all-missing` | Reconcile and fetch everything missing |
140
+ | `--probe` | List available sources and coverage (**pure read**) |
141
+ | `--export <dir>` | Export locally referenced attachments into a relay bundle |
142
+ | `--rollback <report.json>` | Delete files newly written by the run described in the report |
143
+ | `--from <path>` | Source root (repeatable; attachments root / v1 / objects / any dir, auto-detected) |
144
+ | `--relay <dir>` | Relay directory (repeatable) |
145
+ | `--local-source <dir>` | Extra local derived source (repeatable) |
146
+ | `--no-local-sources` | Skip local derived sources |
147
+ | `--home <dir>` | Override DSH home |
148
+ | `--verify` / `--no-verify` | Re-verify sha256 after placement (default on) |
149
+ | `--dry-run` | Print the plan, write nothing |
150
+ | `--force` | Overwrite when the target exists with different content (default: report conflict, no overwrite) |
151
+ | `--json` / `--report <file>` / `--quiet` | Output shape / write a report / exit code only |
152
+
153
+ **Exit codes**: `0` all placed · `3` some sources not found · `4` failures (write/verify) · `2` usage error.
154
+
155
+ **Write discipline**: write a temp file in the same directory → verify sha256 → atomic `rename` →
156
+ re-read and verify again. Existing object with matching sha ⇒ skip (idempotent). Content mismatch ⇒
157
+ conflict, **no overwrite** unless `--force`.
158
+
159
+ > **First run**: `--probe --dry-run`. It only lists sources and coverage; nothing is written.
160
+
161
+ ### 3.3 `dsh-why.mjs` — read-only health check
162
+
163
+ **Answers in one command**: (1) which host is running (2) when dsh and the guard started
164
+ (**dsh main process = owner of the LISTEN socket on 3080**, never command-line matching)
165
+ (3) connections on port 3080 (4) guard state and log tail.
166
+
167
+ | Flag | Meaning |
168
+ |---|---|
169
+ | `--json` | Machine-readable (stdout only; logs never pollute it) |
170
+ | `--log-lines <n>` | Log tail lines (default 12; `0` disables) |
171
+ | `--procs-file <p>` | Offline process-table JSON instead of live CIM (self-test / negative fixtures / cross-host reproduction; output is labelled) |
172
+
173
+ **Read-only**: no hooks, no forced pipeline, **writes no files**.
174
+
175
+ **Output redaction**: this tool prints process command lines / environment / log tails (command
176
+ lines often contain keys/tokens) ⇒ output is redacted by key name or value shape
177
+ (`lib/redact.mjs`; display only, judging logic unchanged).
178
+
179
+ **Device short code**: host name is slugified by default (`DSH_DEVICE_LABEL` overrides); no machine
180
+ name is hard-coded.
181
+
182
+ ### 3.4 `owner-todo-scan.mjs` — "owner action required" list (`--write` rewrites the file)
183
+
184
+ **Why**: a session title's "done" only means "this turn finished", not "the work is closed".
185
+ What truly waits for you lives in four places: board-card descriptions ("pending decision / waiting
186
+ for owner / waiting for you"), `docs\交接-*.md` handoffs, peer-machine run sheets, and the live DSH
187
+ upgrade lock (interrupted / undeterminable / phase=failed).
188
+
189
+ | Flag | Meaning |
190
+ |---|---|
191
+ | (none) | In-flight cards + handoff documents + peer run sheets |
192
+ | `--all` | Also scan done cards' historical "pending decision" sections (noisy; last resort) |
193
+ | `--write` | Write `docs\your-todo-<device>.md` (whole-file rewrite) |
194
+ | `--json` | Machine-readable (fullest detail: `cards[].hits/grades`, `handoffs[].verdict`) |
195
+
196
+ ⚠️ **This tool has no help command; unknown arguments are silently ignored and the real action runs**
197
+ (with `--write`, that means a real file write).
198
+
199
+ **Grade split**: every item is graded `🔧self` / `🔴red-line` / `❔unknown` by autonomy keywords.
200
+ Self-grade items are **kept out of the Markdown body** (a count only), while `--json` keeps full detail.
201
+
202
+ **Output**: `your-todo-<device>.md` is stamped with a generated-document marker (`todo` family); the
203
+ insertion point comes from `lib/generated-docs.mjs` (`insertAtFor(firstH1Index(...))`) and the file is
204
+ rewritten as a whole.
205
+
206
+ ### 3.5 Optional kernel dependency of `owner-todo-scan.mjs` (not a missing piece)
207
+
208
+ The judging implementations behind three sections — upgrade lock, guard-lane suppression, residue
209
+ reaping — are **not shipped in this repository**. They live in the DSH private tools tree
210
+ (`tools/dsh-butler/lib/`):
211
+
212
+ | Module | Purpose | When absent here |
213
+ |---|---|---|
214
+ | `upgrade-lock.mjs` | single source of truth for the upgrade lock `status()` | prints one line "upgrade lock: not probeable (unavailable ≠ no lock)" and continues |
215
+ | `lane-gate.mjs` | guard-lane suppression judge + wording | silently skipped (not an owner action; avoids noise) |
216
+ | `residue-reaper.mjs` | residue-reaping record judge + wording | silently skipped (same) |
217
+
218
+ **Why split this way**: this repository does not keep a second copy of those judges — duplicating
219
+ them is where calibration drift starts. The published version only changes the **static import into a
220
+ dynamic one**: with the kernel present, behaviour is byte-for-byte the same as the private tree;
221
+ without it, the fallbacks above apply. **No judging rule (which states need a human) was changed.**
222
+
223
+ To enable the real judges: point `DSH_TOOLS_DIR` at your tools tree (default `<workspace>/tools`), or
224
+ `DSH_BUTLER_LIB` directly at `dsh-butler/lib`.
225
+
226
+ ---
227
+
228
+ ## 4. Recommended sequence for the attachment pair
229
+
230
+ ```powershell
231
+ # 1) Reconcile first (read-only): how many objects are missing and which sessions are affected
232
+ node attachments-audit.mjs --json > audit.json
233
+
234
+ # 2) Inspect sources (read-only): what sources exist and coverage
235
+ node attachments-fetch.mjs --probe
236
+
237
+ # 3) Preview (writes nothing): what it plans to place
238
+ node attachments-fetch.mjs --all-missing --dry-run --report plan.json
239
+
240
+ # 4) Fetch for real (writes; double verification on by default)
241
+ node attachments-fetch.mjs --all-missing --report fetch.json
242
+
243
+ # 5) Roll back from the report (deletes only files newly written by that run)
244
+ node attachments-fetch.mjs --rollback fetch.json
245
+ ```
246
+
247
+ ---
248
+
249
+ ## 5. Security & privacy
250
+
251
+ - **No network**: none of the four tools makes HTTP requests.
252
+ - **No credentials**: they never touch `credentials.yaml`, tokens, or secret environment variables.
253
+ - **Local paths only**: `DSH_HOME`, `DSH_ROOT`, `docs` — writes are limited to §3.2 / §3.4.
254
+ - **Redaction**: secret-shaped strings in `dsh-why` output are redacted via `lib/redact.mjs`.
255
+ - What they scan is **your own** session logs and board ledger. Do not commit that data to a public repo.
256
+
257
+ ---
258
+
259
+ ## 6. License
260
+
261
+ Code is released under **AGPL-3.0 + commercial licensing** (dual licensing); see [LICENSE](LICENSE).
262
+ In short: **use/modify under AGPL-3.0 and keep derivatives open, or obtain a commercial license.**
263
+ (AGPL itself does not forbid commercial use; "+ commercial license" is dual licensing, not a non-commercial clause.)
264
+
265
+ ---
266
+
267
+ ## 7. Changelog
268
+
269
+ See [CHANGELOG.md](CHANGELOG.md). First public version: `v0.1.0` (2026-10-19).
package/README.md CHANGED
@@ -1,3 +1,277 @@
1
- # Temporary Holding Version
1
+ # dsh-diagnostics-extra · DSH 诊断与对账小工具
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ > **本仓是命令行工具,不挂宿主界面,不需要 `dsh.bundle`、不需要写 `cordis.patch.yml`。** 装它 = `npm i -g @kira905/dsh-diagnostics-extra`(或 `npx -p @kira905/dsh-diagnostics-extra dsh-why`)。装进 profile 也对,但那条 `declares no dsh.bundle` 警告是**正常现象**,不是故障。
4
+
5
+ ## 装完自检(三条只读验法)
6
+
7
+ `<profile>` 换成你实际用的 profile(默认 `web`),`<数据根>` 换成你的 DSH 数据根(默认 `~/.dsh`)。
8
+
9
+ ```powershell
10
+ # ① 组合树里有没有它(只读、约 0.6 秒、不启服务)
11
+ dsh --profile <profile> --dump-config | Select-String "dsh-diagnostics-extra"
12
+ # 期望:bundle 层出现一行 "# == <登记名>"、insert 层出现 "- id: <entry>"(问的都是「它挂上没」);
13
+ # 本仓包名 @kira905/dsh-diagnostics-extra —— 带 scope 装的写带 scope 名、老工装 / 手写的可能是裸名,两种都搜一遍
14
+ # ⚠️ 若该包会 insert 一行,还要数一遍"同一个 id 出现了几次",必须 == 1
15
+
16
+ # ② 组合列表里有没有它(只读)
17
+ Get-Content <数据根>/profiles/<profile>/package.json | Select-String "@kira905/dsh-diagnostics-extra"
18
+ # 期望:dependencies 与 dsh.profile.bundles 两处都有(CLI 类只需要 dependencies;bundles 里也可能写裸名 dsh-diagnostics-extra)
19
+
20
+ # ③ 命令在不在 —— 【只查存在,不执行】
21
+ Test-Path <数据根>/profiles/<profile>/node_modules/.bin/dsh-why.CMD # 装进 profile 的
22
+ Test-Path "$env:APPDATA\npm\dsh-why.CMD" # npm i -g 装的
23
+ # 期望:True。两条路径按你实际用的装法挑一条
24
+ ```
25
+
26
+ > 探针只做存在性检查(只看命令文件在不在,不执行任何命令)。
27
+ > 要不要用 `--help` 验,见本仓当前说明与该命令的参数表。
28
+
29
+ ---
30
+
31
+ > DeepSeek Harness(DSH)生态里**出问题时真正会去跑**的几件只读诊断工具:
32
+ > **图片附件对账 / 取回**(从别的机器同步来的含图会话为什么一续聊就报 TRANSPORT)、
33
+ > **一条命令答清「现在哪台机在跑、dsh 与守护鱼何时起、3080 端口连了谁」**、
34
+ > **把散落在看板卡 / 交接文档 / 对端执行单里的「等你操作」合成一张清单**。
35
+ >
36
+ > 全部 **Node.js ≥ 20**、**零第三方运行时依赖**(只用 `node:` 内置模块)。Windows 优先。
37
+
38
+ ---
39
+
40
+ ## ⚠️ 先读这段(比任何用法都重要)
41
+
42
+ 1. **`owner-todo-scan.mjs` 自 2026-10-30 起已支持 `--help`(打印用法、退出码 0)与未知参数拒收
43
+ (退出码 2,绝不继续执行)** —— 敲错参数会被拦下,不会带着 `--write` 去重写清单。
44
+ 用法文本里写明了它唯一的写动作:`--write` 会**整体重写 `docs\待你操作-<设备>.md`**。
45
+ ⇒ 拿不准就先 `--help`,或先读脚本头部注释。
46
+ 2. **另外三件**:`attachments-audit.mjs` / `attachments-fetch.mjs` / `dsh-why.mjs`
47
+ 也都实现了 `-h` 与帮助输出,**未知参数会报错并停在用法提示**(不会执行真动作)。
48
+ 本文件仍不在代码块里给帮助示例 —— 四件的参数写法差异大,照抄逐件表最稳。
49
+ 3. **写动作只有两处**:`attachments-fetch.mjs` 的取回落盘(可 `--dry-run` 预演、`--report` 留报告、`--rollback` 回退)
50
+ 与 `owner-todo-scan.mjs --write` 的清单刷新(整体重写)。其余全部只读。
51
+ 4. 本仓工具**不做任何网络请求、不读凭据文件**。它们只读本机 DSH 数据根与 docs 树。
52
+
53
+ ---
54
+
55
+ ## 1. 环境与安装
56
+
57
+ ```powershell
58
+ # Node 20+;无需 npm install(零依赖)
59
+ node --version
60
+
61
+ # 直接用(在仓根)
62
+ node attachments-audit.mjs --json
63
+ node dsh-why.mjs --log-lines 20
64
+
65
+ # 或者装到全局用 bin 名
66
+ npm i -g @kira905/dsh-diagnostics-extra
67
+ dsh-attachments-audit --json
68
+ ```
69
+
70
+ ### 环境变量(都可选)
71
+
72
+ | 变量 | 含义 | 缺省 |
73
+ |---|---|---|
74
+ | `DSH_ROOT` | DSH 工作区根(放 `docs\`、`tools\`、`_archive\` 的那棵树) | `~/DSH` |
75
+ | `DSH_HOME` / `BUTLER_DSH_HOME` | DSH home(`sessions`、`attachments`、`task-board` 所在处) | `~/.dsh` |
76
+ | `DSH_WORKSPACE` | 覆盖工作区根解析(优先于 `DSH_HOME` 推导) | 由 `DSH_HOME` / `DSH_ROOT` 推导 |
77
+ | `DSH_DOCS_DIR` | `owner-todo-scan` 扫的文档目录 | `<workspace>/docs` |
78
+ | `DSH_TOOLS_DIR` | DSH 工具树根(可选内核的父目录) | `<workspace>/tools` |
79
+ | `DSH_BUTLER_LIB` | 可选内核目录(`upgrade-lock.mjs` / `lane-gate.mjs` / `residue-reaper.mjs`) | `<DSH_TOOLS_DIR>/dsh-butler/lib` |
80
+ | `DSH_DEVICE_LABEL` | 设备展示名(清单文件名 / 体检短码;不设则用主机名) | 主机名 |
81
+ | `DSH_PEER_LABEL` | 「对端机」在本线的叫法(对端执行单判据用) | `对端机` |
82
+ | `DSH_ATTACH_RELAY` | 附件中转目录(一个,`attachments-fetch` 优先取) | 见该件说明 |
83
+
84
+ > 这些工具原本长在一棵私有工作树里。**发布版把所有本机绝对路径改成了
85
+ > `DSH_ROOT` / `os.homedir()` / 环境变量 + 相对路径**;机器名与用户名换成了通用占位。
86
+ > 第一次在自己机器上用,先设 `DSH_ROOT`(否则默认按 `~/DSH` 找 `docs\`)。
87
+
88
+ ---
89
+
90
+ ## 2. 仓库结构(全量文件)
91
+
92
+ ```
93
+ dsh-diagnostics-extra/
94
+ ├── attachments-audit.mjs # 会话引用的图片附件 vs 本机实体:只读对账
95
+ ├── attachments-fetch.mjs # 按 sha256 取回缺失附件 / 导出 / 回滚(会写盘,可 dry-run)
96
+ ├── dsh-why.mjs # 只读现状体检:机器 / dsh 与守护鱼何时起 / 3080 连接 / 日志尾
97
+ ├── owner-todo-scan.mjs # 扫「等你操作 / 待拍板」悬项(--write 会重写清单)
98
+ ├── lib/
99
+ │ ├── dsh-home.mjs # DSH home / workspace / 报告目录解析
100
+ │ ├── attach-refs.mjs # 会话日志里的附件引用解析 + 对象路径(sha256 口径)
101
+ │ ├── session-file-name.mjs # 会话日志文件名判据(认全世代,防按精确文件名失明)
102
+ │ ├── powershell-exe.mjs # PowerShell 解释器选择(pwsh 7 优先,5.1 兜底)
103
+ │ ├── redact.mjs # 输出脱敏(键名 or 值形态双判据;只影响显示)
104
+ │ └── generated-docs.mjs # 「自动生成档」判级标记与家族匹配(纯常量 + 纯函数)
105
+ ├── README.md
106
+ ├── README.en.md
107
+ ├── CHANGELOG.md
108
+ ├── LICENSE
109
+ ├── package.json
110
+ ├── .gitignore
111
+ └── .gitattributes
112
+ ```
113
+
114
+ > 闭包里还有 `lib/session-file-name.mjs`:`attach-refs.mjs` 的会话文件名判据来自它,
115
+ > 不带上它会在主包换代(`session.vN.jsonl.zstd`)时**整条失明**。
116
+
117
+ ---
118
+
119
+ ## 3. 逐件说明
120
+
121
+ ### 3.1 `attachments-audit.mjs` —— 图片附件对账(只读)
122
+
123
+ **它回答什么**:从另一台机同步来的「含图会话」在本机续聊必挂,报
124
+ `DeepSeek API stream from … failed` / `code=TRANSPORT`。看着像网络,其实是历史图片实体
125
+ (`<DSH_HOME>/attachments/v1/objects/<sha前2位>/<sha256>`)不在本机 —— 每轮请求前会扫**全部历史消息**里的图,
126
+ 读不到实体就抛普通 Error,被兜底 catch 归成 TRANSPORT。
127
+ 本工具把「缺哪些对象 / 哪些会话受影响」变成看得见的事实。
128
+
129
+ | 参数 | 说明 |
130
+ |---|---|
131
+ | `--home <dir>` | 覆盖 DSH home(默认按 `BUTLER_DSH_HOME` → `DSH_HOME` → `~/.dsh`) |
132
+ | `--session <id\|前缀>` | 只看一个会话 |
133
+ | `--json` | 机器可读输出 |
134
+ | `--quiet` | 只出退出码(给开机 / 交接挂点用) |
135
+ | `--no-cache` | 忽略缓存全量重扫 |
136
+ | `--cache <file>` | 覆盖缓存文件(默认 `<home>/butler-attach-scan.json`) |
137
+ | `--top <n>` | 摘要里最多列几个会话(默认 20) |
138
+ | `--sessions-only` | 只列会话,不逐对象展开 |
139
+ | `--verify-objects` | 对命中的对象再算一遍 sha256(慢,但能抓「实体在、内容错」) |
140
+
141
+ **退出码**:`0` 无缺失 · `3` 有缺失 · `2` 用法错 · `4` 环境不可用(读不到会话/附件根)。
142
+ ⚠️ `4` 是 fail-closed:**读不到 ≠ 没缺失**。
143
+
144
+ **只读声明**:除自身缓存(`butler-attach-scan.json`)外不写任何文件;缓存键 = 相对路径 + size + mtimeMs
145
+ (mtime 会被复制/搬动骗过,所以同时含 size)。
146
+
147
+ ### 3.2 `attachments-fetch.mjs` —— 缺失附件取回 / 导出 / 回滚(会写盘)
148
+
149
+ **它做什么**:按 sha256 把缺失的图片对象补回 `<DSH_HOME>/attachments/v1/objects/<2>/<sha>`,
150
+ 布局与 DSH 完全一致。取回源按优先级:`--from` → `--relay` / `DSH_ATTACH_RELAY` / 常见中转目录 →
151
+ 本机派生源(`<home>/dsh-imagegen/{images,gallery/images}`、`<workspace>/generated`,按内容 sha 命中)。
152
+
153
+ | 参数 | 说明 |
154
+ |---|---|
155
+ | `--hash <sha256>` | 取回指定对象(可重复,也支持逗号分隔) |
156
+ | `--session <id\|前缀>` | 取回该会话引用的全部缺失对象 |
157
+ | `--all-missing` | 全量对账后补所有缺失 |
158
+ | `--probe` | 只列可用取回源与覆盖率(**纯只读**) |
159
+ | `--export <dir>` | 反向:把本机引用到的附件导出成中转包 |
160
+ | `--rollback <报告.json>` | 按报告删除本次新写文件 |
161
+ | `--from <path>` | 取回源(可重复;对端 attachments 根 / v1 / objects / 任意目录,自动识别层级) |
162
+ | `--relay <dir>` | 中转目录(可重复) |
163
+ | `--local-source <dir>` | 追加本机派生源(可重复) |
164
+ | `--no-local-sources` | 不扫本机派生源 |
165
+ | `--home <dir>` | 覆盖 DSH home |
166
+ | `--verify` / `--no-verify` | 落位后二次校验 sha256(默认开) |
167
+ | `--dry-run` | 只打印计划,不写盘 |
168
+ | `--force` | 目标已存在但内容不符时覆盖(默认报 conflict、不覆盖) |
169
+ | `--json` / `--report <file>` / `--quiet` | 输出形态 / 落报告 / 只出退出码 |
170
+
171
+ **退出码**:`0` 全部就位 · `3` 有未找到源的缺失 · `4` 有失败(写盘/校验) · `2` 用法错。
172
+
173
+ **落盘铁律**:写同目录临时文件 → 校验 sha256 → `rename` 原子落位 → 重读二次校验;
174
+ 已存在且 sha 一致 ⇒ skip(幂等);内容不符 ⇒ conflict,**不覆盖**(除非 `--force`)。
175
+
176
+ > **第一次用先跑 `--probe --dry-run`**:它只列源与覆盖率,不写盘。
177
+
178
+ ### 3.3 `dsh-why.mjs` —— 只读现状体检
179
+
180
+ **一条命令答清**:① 现在跑在哪台机 ② dsh 与守护鱼何时起(**dsh 主进程 = 3080 LISTEN 的 owner**,不靠命令行匹配)
181
+ ③ 3080 端口连接情况 ④ 守护鱼状态与日志尾。
182
+
183
+ | 参数 | 说明 |
184
+ |---|---|
185
+ | `--json` | 机器可读(只落 stdout;日志不污染) |
186
+ | `--log-lines <n>` | 日志尾行数(默认 12;`0` = 不打印) |
187
+ | `--procs-file <p>` | 用离线进程表 JSON 代替实时 CIM(自测 / 负样例 / 异机复现;输出会标注来源) |
188
+
189
+ **只读声明**:不挂钩子、不进任何强制流程、**不写任何文件**。
190
+
191
+ **输出脱敏**:本件会打印进程命令行 / 环境 / 日志尾(命令行里常带 key/token)⇒
192
+ 输出前按「键名 or 值形态」双判据脱敏(`lib/redact.mjs`;只影响显示,不改判定)。
193
+
194
+ **设备短码**:默认把主机名 slug 化(`DSH_DEVICE_LABEL` 可覆盖),不锁任何具体机器。
195
+
196
+ ### 3.4 `owner-todo-scan.mjs` —— 「等你操作」清单(`--write` 会重写清单)
197
+
198
+ **为什么需要**:会话标题的【完成】只表示「这一轮说完了」,不等于活闭环。真正等你的事散落在四处:
199
+ 看板卡 description 里的「待拍板 / 待主人 / 等你」段 · `docs\交接-*.md` 里的悬项 ·
200
+ 给对端机执行的单子 · DSH 现场升级锁(中断 / 不可判定 / phase=failed)。
201
+
202
+ | 参数 | 说明 |
203
+ |---|---|
204
+ | (无参数) | 在途卡 + 交接文档 + 对端执行单 |
205
+ | `--all` | 追加 done 卡里的「待拍板」历史段(噪声多,兜底用) |
206
+ | `--write` | 落 `docs\待你操作-<设备>.md`(**整体重写刷新**) |
207
+ | `--json` | 机器可读(明细最全:`cards[].hits/grades`、`handoffs[].verdict`) |
208
+
209
+ ⚠️ **本件没有帮助命令,未知参数会被静默忽略并直接执行**(`--write` 时就是真写盘)。
210
+
211
+ **档位分拣**:每条按「自主授权」关键词分 `🔧甲档(应自办)` / `🔴红线(待主人)` / `❔未定档`;
212
+ 甲档条目**不进 Markdown 正文**(只在段头留计数),明细仍完整保留在 `--json`。
213
+
214
+ **输出**:`待你操作-<设备>.md` 带档级标记(`todo` 族),插入点由 `lib/generated-docs.mjs`
215
+ 的 `insertAtFor(firstH1Index(...))` 决定,**整体重写**。
216
+
217
+ ### 3.5 `owner-todo-scan.mjs` 的可选内核依赖(重要,别当成缺件)
218
+
219
+ 「升级锁 / 守护鱼闸抑制 / 残留收敛」三节的**判定实现不在本仓**,它们长在 DSH 私有工具树
220
+ (`tools/dsh-butler/lib/`)里:
221
+
222
+ | 模块 | 用途 | 本仓缺席时 |
223
+ |---|---|---|
224
+ | `upgrade-lock.mjs` | 升级锁 `status()` 的**唯一真源** | 打印一行「升级锁:不可探测(判据不可用 ≠ 没有锁)」并继续 |
225
+ | `lane-gate.mjs` | 守护鱼闸抑制判据 + 文案 | 静默跳过(该类不是「有你的事」,不刷噪音) |
226
+ | `residue-reaper.mjs` | 残留收敛记录判据 + 文案 | 静默跳过(同上) |
227
+
228
+ **为什么这样拆**:本仓不做「第二份判定实现」——复制判据就是口径漂移的起点。
229
+ 发布版只把**静态 import 改成动态加载**,内核在时行为与私有树**逐字同源**,内核缺席时按上表降级;
230
+ **判定口径(哪些态要人处置)没有改一个字**。
231
+
232
+ 想启用真判据:`DSH_TOOLS_DIR` 指向工具树(默认 `<workspace>/tools`),或 `DSH_BUTLER_LIB` 直接指到 `dsh-butler/lib`。
233
+
234
+ ---
235
+
236
+ ## 4. 两件附件工具的配合(推荐顺序)
237
+
238
+ ```powershell
239
+ # 1) 先对账(只读):缺多少、影响哪些会话
240
+ node attachments-audit.mjs --json > audit.json
241
+
242
+ # 2) 再看源(只读):有哪些取回源、覆盖率多少
243
+ node attachments-fetch.mjs --probe
244
+
245
+ # 3) 预演(不写盘):看它准备写哪些对象
246
+ node attachments-fetch.mjs --all-missing --dry-run --report plan.json
247
+
248
+ # 4) 真取回(写盘;默认二次校验)
249
+ node attachments-fetch.mjs --all-missing --report fetch.json
250
+
251
+ # 5) 出问题就按报告回退(只删本次新写文件)
252
+ node attachments-fetch.mjs --rollback fetch.json
253
+ ```
254
+
255
+ ---
256
+
257
+ ## 5. 安全与隐私
258
+
259
+ - **不联网**:四件都不做 HTTP 请求。
260
+ - **不读凭据**:不碰 `credentials.yaml` / token / 环境变量里的密钥。
261
+ - **只在你指定/推导出的本机路径上读写**:`DSH_HOME`、`DSH_ROOT`、`docs`;写动作见 §3.2 / §3.4。
262
+ - **输出里的敏感字串**(key/token 形状)在 `dsh-why` 侧按 `lib/redact.mjs` 脱敏。
263
+ - 扫描的是**你自己的**会话日志与看板账本;别把这些数据提交进公开仓。
264
+
265
+ ---
266
+
267
+ ## 6. 许可
268
+
269
+ 本仓代码以 **AGPL-3.0 + 商业授权**(双许可)发布,详见 [LICENSE](LICENSE)。
270
+ 简言之:**要么按 AGPL-3.0 使用/修改并让衍生作品同样开源,要么取得商业授权**。
271
+ (AGPL 本身不禁止商用;「+ 商业授权」是双许可,不是禁商用条款。)
272
+
273
+ ---
274
+
275
+ ## 7. 变更记录
276
+
277
+ 见 [CHANGELOG.md](CHANGELOG.md)。首个公开版本 `v0.1.0`(2026-10-19)。