dsh-fact-check 1.5.1 → 1.5.3
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/ADAPTING.md +8 -3
- package/CHANGELOG.md +26 -0
- package/CONTRIBUTING.md +3 -2
- package/CONTRIBUTING.zh-CN.md +3 -2
- package/FOR-AI.md +6 -6
- package/INSTRUCTIONS.md +22 -3
- package/README.md +12 -4
- package/README.zh-CN.md +12 -4
- package/SKILL.md +23 -4
- package/package.json +1 -1
- package/tools/config.mjs +83 -0
- package/tools/doctor.mjs +11 -8
- package/tools/engine-routes.json +1 -1
- package/tools/jev-verdict.mjs +3 -2
- package/tools/local.example.json +1 -1
- package/tools/route.mjs +11 -2
package/ADAPTING.md
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
# 换环境适配清单(ADAPTING)
|
|
2
2
|
|
|
3
|
-
本技能**不绑定任何主机、产品、路径或密钥**:正文只讲能力与规则,两个工具是零依赖的 Node 脚本,所有环境相关的值都在 `
|
|
3
|
+
本技能**不绑定任何主机、产品、路径或密钥**:正文只讲能力与规则,两个工具是零依赖的 Node 脚本,所有环境相关的值都在 `local.json`(放在**包外**的用户配置目录,见下)。本文说明落到你的环境需要做哪几件事。
|
|
4
4
|
|
|
5
5
|
> 逐步执行请用 **`FOR-AI.md`**(它是操作手册);本文是配套的背景与决策依据(为什么这样改、没有决策模型怎么办、判据校准纪律)。
|
|
6
6
|
|
|
7
|
+
> **配置文件位置**:默认 `%LOCALAPPDATA%\dsh-fact-check\local.json`(Windows)或
|
|
8
|
+
> `~/.config/dsh-fact-check/local.json`;`JEV_LOCAL_CONFIG` 可指定任意路径;旧的包内
|
|
9
|
+
> `tools/local.json` 仍会被读,但只作为兜底。放在包外是为了 `dsh plugin update` / `npm i`
|
|
10
|
+
> 换掉整个包目录时,不会把本机设置一起清掉。`secrets.json` 用同一套目录。
|
|
11
|
+
|
|
7
12
|
---
|
|
8
13
|
|
|
9
14
|
## 1. 五分钟接上
|
|
@@ -17,8 +22,8 @@ node tools/doctor.mjs --net # 建议也跑一次:会真跑
|
|
|
17
22
|
export TYPESAFE_API_KEY=… # 或 doctor --write 生成 local.json 后填 keyFile
|
|
18
23
|
|
|
19
24
|
# 3) 本机值(可选):自托管搜索、代理、可选工具路径;还有能力绑定 / 输出约定 / 运行时基线
|
|
20
|
-
node tools/doctor.mjs --write #
|
|
21
|
-
# 或:
|
|
25
|
+
node tools/doctor.mjs --write # 生成配置草稿(写到包外用户目录),再把 null 的项补上
|
|
26
|
+
# 或: 把 tools/local.example.json 复制成配置文件(路径看 doctor 输出里的「本机配置」一行)
|
|
22
27
|
|
|
23
28
|
# 4) 验收(离线,不花钱)
|
|
24
29
|
node tools/jev-verdict.mjs selftest # 期望 25/25
|
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,32 @@
|
|
|
3
3
|
面向使用者的**版本间净变化**(修复 / 改进 / 新增)。只写最终结果,不写过程。
|
|
4
4
|
判据、阈值、触发词的任何改动都必须同时更新 `MEASUREMENTS.md` 的数字与 `cases/` 的回归结果。
|
|
5
5
|
|
|
6
|
+
## 1.5.3 — 2026-11-09
|
|
7
|
+
|
|
8
|
+
**新增**
|
|
9
|
+
|
|
10
|
+
- **Step 5「交付前回检(措辞与断言)」**:报告最容易在措辞层被放大——一旦被断章引用,错的不是结论,是读者以为的事实。工作流程末尾新增一步,交付前逐条过 8 项:① 单位没被偷换(条数 ≠ 人数、账号 ≠ 人、场次 ≠ 观众、人次 ≠ 人,凡给"多少人 / 多少参与方"先数**去重后**的主体标识);② 量词配得上对象(人用「位 / 个 / 名」,不用「条 / 只 / 头」);③ 覆盖面写清分母与口径;④ 百分比写清基准;⑤ 比较类数字标混杂因素;⑥ 未核实处显式标注;⑦ 一句话结论 / 标题 / 摘要单独再查一遍;⑧ 修辞不得改变事实。判据:任一条过不了就改数字或加限定词——不许把不确定写成确定。
|
|
11
|
+
|
|
12
|
+
**其它**
|
|
13
|
+
|
|
14
|
+
- `FOR-AI.md` 两处对 `SKILL.md` 步骤范围的引用同步到 **Step 0–5**:装载自检句(原「按 Step 0–4 走」)与「第 5 步 · 使用规范」里的流程链(补上「交付前回检措辞与断言(Step 5)」)。`FOR-AI.md` 自身第 0–4 步的任务书编号不受影响。
|
|
15
|
+
- `SKILL.md` frontmatter 与 `package.json` 版本号同步到 **1.5.3**;`INSTRUCTIONS.md` 由 `tools/make-instructions.mjs` 重新生成(正文未变,产物与改前逐字一致)。
|
|
16
|
+
- **判据层未动**:`tools/jev-verdict.mjs`、`engine-routes.json`、`tools/route.mjs` 的触发词与域路由表、`lib/`、`cases/` 本轮无改动,因此 `MEASUREMENTS.md` 的数字与回归结果不适用。
|
|
17
|
+
- 验收:`smoke-plugin 27/27`、`route selftest 44/44`、`jev-verdict selftest 25/25`、`regress 30/30` 全部通过。
|
|
18
|
+
|
|
19
|
+
## 1.5.2 — 2026-11-05
|
|
20
|
+
|
|
21
|
+
**修复**
|
|
22
|
+
|
|
23
|
+
- **包更新会把本机配置一起冲掉**:`local.json` / `secrets.json` 原先只从包内 `tools/` 读,而 `dsh plugin update`(以及任何 `npm i`)是**整包替换**,装在包里的本机设置必然消失;又因为 package.json 的 `files` 不含 `local.json`,用户只能在装完后手工往 `node_modules/…/tools/` 里放,每次更新重来一遍。现改为**包外配置目录**:Windows `%LOCALAPPDATA%\dsh-fact-check\`,其它平台 `$XDG_CONFIG_HOME/dsh-fact-check/` 或 `~/.config/dsh-fact-check/`;`JEV_LOCAL_CONFIG` 可显式指定文件。解析顺序 `JEV_LOCAL_CONFIG` → 用户配置目录 → 包内旧位置(兜底,不破坏已有安装)。
|
|
24
|
+
- `tools/doctor.mjs --write` 改为写入包外用户目录(已存在则不覆盖),并在输出里加一行「本机配置:…」显示**实际生效**的文件路径;`route.mjs` / `jev-verdict.mjs` / `doctor.mjs` 的读取全部走同一套解析(新增 `tools/config.mjs`,仍零依赖)。密钥文件同目录解析(`secrets.json`)。
|
|
25
|
+
|
|
26
|
+
**其它**
|
|
27
|
+
|
|
28
|
+
- 文档同步:`ADAPTING.md`(新增「配置文件位置」说明)、`FOR-AI.md`、`README.md`、`README.zh-CN.md`、`tools/local.example.json` 的路径性描述改为包外位置;判据、阈值与流程正文未动。
|
|
29
|
+
- 验收:`route selftest 44/44`、`jev-verdict selftest 25/25`、`smoke-plugin 27/27` 全部通过;解析优先级实测三条:`JEV_LOCAL_CONFIG` 指定路径生效、用户目录生效、两者皆无时 `overrides` 为空且不报错。
|
|
30
|
+
- 升级提示:老安装升级到 1.5.2 后,把原来的 `tools/local.json` 复制到上面那个包外路径即可(包内旧位置仍会被读到,但下次整包替换后就没了)。
|
|
31
|
+
|
|
6
32
|
## 1.5.1 — 2026-10-28
|
|
7
33
|
|
|
8
34
|
**修复**
|
package/CONTRIBUTING.md
CHANGED
|
@@ -38,8 +38,9 @@ All of these run offline and need no API key.
|
|
|
38
38
|
|
|
39
39
|
## Machine-specific values never enter the repository
|
|
40
40
|
|
|
41
|
-
Endpoints, keys, host paths and "which channel actually works here" belong in `
|
|
42
|
-
|
|
41
|
+
Endpoints, keys, host paths and "which channel actually works here" belong in `local.json` **outside the package**
|
|
42
|
+
(default `%LOCALAPPDATA%\dsh-fact-check\local.json` on Windows, `~/.config/dsh-fact-check/local.json` elsewhere,
|
|
43
|
+
or whatever `JEV_LOCAL_CONFIG` points at); `tools/local.example.json` is the template. `tools/*.mjs` carry assertions that fail if a host
|
|
43
44
|
path is hard-coded, and `SKILL.md` may not name a host, an internal address, a path or a key.
|
|
44
45
|
|
|
45
46
|
## Versioning
|
package/CONTRIBUTING.zh-CN.md
CHANGED
|
@@ -36,8 +36,9 @@ node tools/make-instructions.mjs # 只有改了 SKILL.md 才
|
|
|
36
36
|
|
|
37
37
|
## 主机相关的值永不入库
|
|
38
38
|
|
|
39
|
-
端点、密钥、主机路径,以及「这台机器上哪个渠道真的能用」,一律放 `
|
|
40
|
-
|
|
39
|
+
端点、密钥、主机路径,以及「这台机器上哪个渠道真的能用」,一律放 **包外** 的 `local.json`
|
|
40
|
+
(Windows 默认 `%LOCALAPPDATA%\dsh-fact-check\local.json`,其它平台 `~/.config/dsh-fact-check/local.json`,
|
|
41
|
+
或用 `JEV_LOCAL_CONFIG` 指定),模板是 `tools/local.example.json`。`tools/*.mjs` 里有断言:一旦硬编码主机路径就失败;
|
|
41
42
|
`SKILL.md` 里也不得出现主机名、内网地址、路径或密钥。
|
|
42
43
|
|
|
43
44
|
## 版本纪律
|
package/FOR-AI.md
CHANGED
|
@@ -46,10 +46,10 @@ node tools/doctor.mjs --net # 联网版:额外自测抓取链与判据端
|
|
|
46
46
|
2. 或者生成配置草稿再补空值:
|
|
47
47
|
|
|
48
48
|
```bash
|
|
49
|
-
node tools/doctor.mjs --write #
|
|
49
|
+
node tools/doctor.mjs --write # 生成配置文件(默认写到包外用户目录;已存在则不会覆盖)
|
|
50
50
|
```
|
|
51
51
|
|
|
52
|
-
3. 若环境里有自托管搜索 / 代理 /
|
|
52
|
+
3. 若环境里有自托管搜索 / 代理 / 工具,把它们填进配置文件的 `overrides`:
|
|
53
53
|
|
|
54
54
|
```json
|
|
55
55
|
{ "overrides": { "SEARXNG_URL": "…", "HTTP_PROXY_URL": "…", "VIDEO_FETCH_CMD": "…", "EXIFTOOL_CMD": "…" } }
|
|
@@ -63,7 +63,7 @@ node tools/doctor.mjs --write # 生成 tools/local.json(已存在则不会
|
|
|
63
63
|
- `runtimeNotes`:运行时基线,自由文本。最值得记的是"这台机器上哪些渠道真的能用"——例如元搜索实例里哪个引擎被 CAPTCHA 拦、哪些站点必须走代理。这类信息直接决定核查质量,但不适合写进正文。
|
|
64
64
|
|
|
65
65
|
**硬性禁止**(违反即失职):
|
|
66
|
-
- 不要把密钥**写进** `SKILL.md`、任何 `.md`、任何数据表、或提交到版本库;`local.json`
|
|
66
|
+
- 不要把密钥**写进** `SKILL.md`、任何 `.md`、任何数据表、或提交到版本库;`local.json` 属于本机配置,放**包外**用户配置目录(见 `ADAPTING.md`)。
|
|
67
67
|
- 不要把密钥**打印**到对话、日志或终端输出里;只需要说明"密钥来源是 X"。
|
|
68
68
|
- 不要修改 `cases/` 里语料的 `label`(那是校准真值)。
|
|
69
69
|
|
|
@@ -102,13 +102,13 @@ node tools/jev-verdict.mjs regress --file cases/injection-cases-neutral.json --c
|
|
|
102
102
|
|
|
103
103
|
**同时必须做的一件事**:把 `SKILL.md` 开头那张「能力 → 常见实现」表里的**右列换成你环境里真实可调用的工具名**。左列(能力名)是规范,右列是本地绑定——**只改右列**,正文一个字都不要动。
|
|
104
104
|
|
|
105
|
-
装载完用一句话自检:"面对'核实 X 是否属实',我会按 SKILL.md 的 Step 0–
|
|
105
|
+
装载完用一句话自检:"面对'核实 X 是否属实',我会按 SKILL.md 的 Step 0–5 走。"
|
|
106
106
|
|
|
107
107
|
---
|
|
108
108
|
|
|
109
109
|
## 第 5 步 · 使用规范(每次核查都遵守)
|
|
110
110
|
|
|
111
|
-
**流程**:拆事实点(Step 0)→ 并行搜索、中英+质疑词(Step 1)→ 抓一手原文(Step 2)→ 交叉比对与来源分级(Step 3)→ 出报告(Step 4)。细节在 `SKILL.md`,别跳过。
|
|
111
|
+
**流程**:拆事实点(Step 0)→ 并行搜索、中英+质疑词(Step 1)→ 抓一手原文(Step 2)→ 交叉比对与来源分级(Step 3)→ 出报告(Step 4)→ 交付前回检措辞与断言(Step 5)。细节在 `SKILL.md`,别跳过。
|
|
112
112
|
|
|
113
113
|
**两个工具怎么用**:
|
|
114
114
|
|
|
@@ -189,5 +189,5 @@ node tools/jev-verdict.mjs batch --file my-cases.json --question support --conc
|
|
|
189
189
|
- **把"伪多源"当交叉验证**:多家转载同一篇首发只算 **1** 个独立来源;要追到最上游。
|
|
190
190
|
- **去痕语料的数字更低**:包里是去痕版,`support` 期望约 37/40(92.5%);原始版是 38–39/40。这不是判据退化,而是"具体出处本身是实据"——见 `MEASUREMENTS.md` §1.1。第二遍去痕(私有工具名/宿主产品名)则**没有**造成可测量的变化。
|
|
191
191
|
- **阈值附近会漂**:同一批样本两轮结果可能差 1 例,差异都在 p≈0.5 的样本上。这正是"只能降档""unsure 交人"的理由。
|
|
192
|
-
- **不要在技能正文里写本机事实**(内网地址、绝对路径、密钥、账号):本机值只进 `local.json
|
|
192
|
+
- **不要在技能正文里写本机事实**(内网地址、绝对路径、密钥、账号):本机值只进 `local.json`(包外用户配置目录,见 `ADAPTING.md`),实测数字只进 `MEASUREMENTS.md`。两个工具的离线自检里有断言在守这条,别删。
|
|
193
193
|
- **工具一律 fail-open**:找不到密钥/超时/HTTP 报错都返回 `unknown` 并回退人工规则,核查流程不应中断。
|
package/INSTRUCTIONS.md
CHANGED
|
@@ -23,12 +23,12 @@
|
|
|
23
23
|
4. **平台专用工具**——视频 / 社交类平台的专用 CLI(字幕、弹幕、评论等);
|
|
24
24
|
5. **免登录直取接口**——平台公开的免登录端点;拿不到就如实标注"未能获取原文",不要伪造。
|
|
25
25
|
|
|
26
|
-
哪条链在本机可用、哪个站已经挂了,属于**运行时基线**:写进 `
|
|
26
|
+
哪条链在本机可用、哪个站已经挂了,属于**运行时基线**:写进 `local.json`(包外用户配置目录,见 `ADAPTING.md`)的 `runtimeNotes`(带日期),**不要写进本文**——正文没有时效标注机制,写在这里半年后就是过期信息,而且换台机器就得改正文。
|
|
27
27
|
|
|
28
28
|
**首选路径失败必须自动降级到次选**,而不是直接判定"无法核查"。只有**五条路径全部失败**才判"无法核查",且必须在报告的「本次未能核实到的」里写明试过哪几条、各自报什么错。
|
|
29
29
|
**搜索结果的 `content` / `snippet` 字段只是摘要,不能当正文计入证据。**
|
|
30
30
|
|
|
31
|
-
**本技能不绑定任何具体产品、主机或工具链。** 正文只使用"能力名";把能力落到你的环境里见 `ADAPTING.md`;所有"在某台机器上测出来"的数字都在 `MEASUREMENTS.md`,它们**是证据,不是配置**。本机的能力绑定与输出约定可写进 `
|
|
31
|
+
**本技能不绑定任何具体产品、主机或工具链。** 正文只使用"能力名";把能力落到你的环境里见 `ADAPTING.md`;所有"在某台机器上测出来"的数字都在 `MEASUREMENTS.md`,它们**是证据,不是配置**。本机的能力绑定与输出约定可写进 `local.json`(包外,可选字段,见 `tools/local.example.json`),正文只保留默认值。
|
|
32
32
|
|
|
33
33
|
自检:`node tools/doctor.mjs` 会探测当前环境有哪些能力、缺什么、会降级成什么样;加 `--net` 会额外自测判据端点与**两条可自测的抓取链**(宿主内置抓取与搜索服务的抽取字段它探不到,需自己各跑一次真实抓取确认)。
|
|
34
34
|
|
|
@@ -173,6 +173,25 @@
|
|
|
173
173
|
|
|
174
174
|
---
|
|
175
175
|
|
|
176
|
+
### Step 5 交付前回检(措辞与断言)
|
|
177
|
+
|
|
178
|
+
**写完报告不等于可以交。** 这一步查的不是"证据够不够"(那是 Step 3),而是**写出来的每个字是否还等于证据**。报告最容易在措辞层被放大——一旦被断章引用,错的不是结论,是读者以为的事实。
|
|
179
|
+
|
|
180
|
+
逐条过:
|
|
181
|
+
|
|
182
|
+
1. **单位没被偷换**:条数 ≠ 人数、账号 ≠ 人、场次 ≠ 观众、人次 ≠ 人。凡给出"多少人 / 多少参与方",先数**去重后**的主体标识。
|
|
183
|
+
2. **量词配得上对象**:人用「位 / 个 / 名」,不用「条 / 只 / 头」。
|
|
184
|
+
3. **覆盖面写清分母与口径**:「X% 没有某待遇」要指明哪一类人、什么统计范围。「没有带薪年假」不等于「一天带薪假都没有」(可能仍有带薪节假日)。
|
|
185
|
+
4. **百分比写清基准**:「增长 / 超出 X%」必须说明相对哪个基数;换个基数,结论可能差一倍。
|
|
186
|
+
5. **比较类数字标混杂因素**:自动标注、篇幅差异、样本选择造成的偏差写在括号里,否则加「约」。
|
|
187
|
+
6. **未核实处显式标注**:写进「本次未能核实到的」,或就地加「(未验证)」;不用「据说 / 普遍认为」糊过去。
|
|
188
|
+
7. **一句话结论、标题、摘要单独再查一遍**:它们被引用的概率最高、被读的字最少,也最容易被写成"更响亮但不准确"的地方。改完回头与 Step 3 的来源档位对一次。
|
|
189
|
+
8. **修辞不得改变事实**:为了让判断更锋利而改动词汇,即使每个字都能查证,也已经是错的。
|
|
190
|
+
|
|
191
|
+
> **判据**:任一条过不了,就改数字或加限定词——不许把不确定写成确定。
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
176
195
|
## 利益相关与污染面(标注制、不降权、双轨呈现)
|
|
177
196
|
|
|
178
197
|
**本节只标注,不降权。** 一个来源是否存在利益关联、是否处在大规模投毒污染的信息链上,**本技能不做判定**;它只把能拿出来看的东西标出来,并给出两套固定口径下的结论,由读报告的人自己裁定。
|
|
@@ -342,7 +361,7 @@
|
|
|
342
361
|
- 目录结构:**`FOR-AI.md`(交给 AI 的操作说明:逐步配置与使用)** + `SKILL.md`(本文件,流程与规则)+ `ADAPTING.md`(换环境要改什么)+ `MEASUREMENTS.md`(实测数字与复跑)+ `CHANGELOG.md`(版本间净变化)+ `lib/index.js`(DSH 插件入口:把本文件挂到 `ctx.skills`)+ `cordis.patch.yml`(让本包能被 `dsh plugin add` 安装)+ `tools/`(两个可选工具、本机配置模板与数据表)+ `cases/`(回归语料)+ `README.md` / `README.zh-CN.md`(给人类的说明)+ `submission/`(投稿到插件市场的条目文件,不属于插件包本身)。
|
|
343
362
|
- **两种分发形态同一份正文**:① `dsh plugin add github:7starsseeker/dsh-fact-check`(DSH 插件市场的形态,`lib/index.js` 现读本文件,所以改本文件不必改代码);② 把整个目录交给任何 AI 工具(`FOR-AI.md` 那套)。**不要为其中一种抄一份正文出来**——两份必然漂移,`tools/smoke-plugin.mjs` 里有断言在守"候选描述 = 本文件 frontmatter 的 description"这条单一真相源。
|
|
344
363
|
- **改判据 = 重新校准事件**:`tools/jev-verdict.mjs` 里的 `QUESTIONS` 是冻结字面量,任何 instruction/criteria/阈值改动都必须重跑 `cases/` 下的回归,并把新数字写回 `MEASUREMENTS.md`。**`tools/route.mjs` 的触发词表与域路由表同理**:改完要重跑 `node tools/route.mjs regress --file cases/route-cases-neutral.json` 与 `node tools/route.mjs selftest`。
|
|
345
|
-
- **环境相关的值只放 `
|
|
364
|
+
- **环境相关的值只放 `local.json`**(包外用户配置目录;模板 `tools/local.example.json`):密钥、端点、state 目录、数据表里的 `${占位符}`,以及可选的能力绑定 `capabilities`、输出约定 `outputConvention`、运行时基线 `runtimeNotes`。代码与数据表**不写死任何主机路径**,`selftest` 里有断言在守这条。
|
|
346
365
|
- **不要在本技能正文里写入任何主机、内网地址、路径或密钥**。本机事实要么进 `local.json`,要么作为"在某环境测得"的证据进 `MEASUREMENTS.md`。
|
|
347
366
|
- **版本纪律**:凡是改动面向使用者的行为(流程、报告模板、判据、触发词),就升 frontmatter 的 `version` 并在 `CHANGELOG.md` 记一条净变化;只改措辞或文档不算。
|
|
348
367
|
- **「利益相关与污染面」是标注制,不是降权制**:不要把任何"利益相关 / 疑似投毒 ⇒ 降档"的规则接进定档。至少会同时坏三件事(非单调、奖励信息垄断、全局降档),三条理由是那一节的正文,改之前先重读。
|
package/README.md
CHANGED
|
@@ -68,7 +68,7 @@ are kept in their own sections — *unverified* and *undecidable* never sit in t
|
|
|
68
68
|
The skill body is not bound to any host, product or tool chain: it names *capabilities*, and
|
|
69
69
|
[`ADAPTING.md`](./ADAPTING.md) maps them onto your environment.
|
|
70
70
|
|
|
71
|
-
**Verified hosts: DSH 0.1.7-rc.2, 0.2.0-rc.2, 0.2.1-rc.2
|
|
71
|
+
**Verified hosts: DSH 0.1.7-rc.2, 0.2.0-rc.2, 0.2.1-rc.2, 0.2.1-rc.3, 0.2.1-rc.5 and 0.2.2-rc.1.** DSH 0.1.7-rc.2 was checked end to end on 2026-09-25 (Node 24.21.0, WSL/Linux): the host discovers the skill
|
|
72
72
|
and loads its body; the plugin form mounts on the host's own skill registry with `source: bundled`, its
|
|
73
73
|
description read from `SKILL.md` and `get()` returning that body verbatim; `npm run selftest` passes
|
|
74
74
|
(plugin mount 27/27, `jev-verdict` 25/25, `route` 37/37); `route.mjs regress` is 26/26 offline; and the
|
|
@@ -85,6 +85,12 @@ forbidden.
|
|
|
85
85
|
|
|
86
86
|
**DSH 0.2.1-rc.3 was checked on 2026-10-24** (Node 24.22.1, Windows). Nothing in the registry contract moved: `@deepseek-ai/dsh-skill` 0.2.1-rc.3 still exports `BUNDLED_SKILL_RANK` (600), `SkillRegistry`, `isSkillName`, `isModelInvocable` and `isUserInvocable`, still hands `registerProvider()` the same `control` (`{ signal, invalidate }`), and `invalidateCache()` still advances the cache revision and broadcasts `skills/change`. The plugin was mounted on that registry — taken from the shipped `app.asar`, wired with the host's own cordis 4.0.5 — and passed **28/28** checks: `inject: ['skills']`; the candidate carrying `source: bundled`, `rank` 600 equal to the host's own exported constant, the absolute `SKILL.md` as `path`, the package root as `resourceBase`, and the frontmatter description verbatim; `get()` returning the body (13,533 characters, frontmatter stripped, `path` retained); `control.invalidate()` advancing the registry revision 1 → 2 and emitting exactly one `skills/change`; and disposal removing the provider and aborting `control.signal`. Two details worth writing down because they look like failures and are not: the host's `toSummary()` deliberately drops `rank` from catalog summaries (it is a same-name arbitration input, not a display field), and registration only becomes observable once `ctx.plugin()`'s thenable resolves — inspected before that await, even a successful mount shows no `control`. In this same rc3 session the **directory** form is the one that runs (the host's own skill directory), and the session's skill catalog lists `fact-check` with this repository's `SKILL.md` description.
|
|
87
87
|
|
|
88
|
+
**DSH 0.2.1-rc.5 was checked on 2026-10-31** (Node 26.13.0, Windows; the host version is the one the shipped `app.asar`'s `@deepseek-ai/dsh-desktop` declares, and `dsh --version` reports the same `0.2.1-rc.5`). The registry contract did not move again: `@deepseek-ai/dsh-skill` 0.2.1-rc.5 still exports `BUNDLED_SKILL_RANK` (600) together with the same `SkillRegistry` / `isSkillName` / `isModelInvocable` / `isUserInvocable` surface, still hands `registerProvider()` the same `control` (`{ signal, invalidate }`), and `validateCandidate`, `toSummary` and `renderSkillContent` behave as in 0.2.1-rc.3 — checked line by line against the file taken from the shipped `app.asar`, wired with the host's own cordis 4.0.5. The plugin mounted on that registry and passed **24/24** checks: `inject: ['skills']`; exactly one candidate for `fact-check`, carrying `source: bundled`, `rank` 600 equal to the host's exported constant, the absolute `SKILL.md` as `path` and the package root as `resourceBase`; `get()` returning the body (14,462 characters, frontmatter stripped); that body rendering through `renderSkillContent` with the package root as the resource base; `control.invalidate()` making an edited description visible to the catalogue (edited and reverted inside the probe — `git status --porcelain` empty afterwards); and disposal removing the provider again. The bundled suite re-ran green on Node 26.13.0: `smoke-plugin` 27/27, `jev-verdict selftest` 25/25, `route selftest` 44/44, `route.mjs regress` 30/30 offline, `doctor` exit 0.
|
|
89
|
+
|
|
90
|
+
One host behaviour added in this release decides whether *any* plugin loads, so it is recorded here. `@deepseek-ai/dsh-app-boot` gained a plugin↔runtime compatibility gate: a plugin that declares `engines.dsh` must satisfy it; a plugin that declares no `engines.dsh` but *does* declare `peerDependencies` must satisfy every `@deepseek-ai/dsh` / `@deepseek-ai/dsh-*` peer; **a plugin with neither passes unconditionally**. Incompatible profile bundles are skipped without printing anything, and incompatible `cordis.patch.yml` rows are set `disabled`. This package declares only `engines.node` and no `peerDependencies`, so the gate cannot deny it on any runtime — a second reason, after the plugin-market one above, to keep the manifest free of host versions.
|
|
91
|
+
|
|
92
|
+
**DSH 0.2.2-rc.1 was checked on 2026-11-06** (Node 26.13.0, Windows; the shipped `app.asar`'s `@deepseek-ai/dsh-desktop` and `@deepseek-ai/dsh-desktop-runtime` both declare `0.2.2-rc.1`, and `dsh --version` reports the same). This round is **behavioural evidence rather than a line-by-line diff**: the host was updated in place, so the 0.2.1-rc.5 counterpart of each package no longer exists on disk to diff against. What the shipped build still declares was checked directly: `@deepseek-ai/dsh-skill`'s `lib/index.js` still has `const BUNDLED_SKILL_RANK = 600;` (line 23) and still hands `registerProvider()` the same `control` (`{ signal, invalidate }`), whose `invalidate()` consults `active.layer.providers.get(active.name)?.provider === provider` before invalidating the catalogue; the export list at line 566 is unchanged. On the running host the plugin is installed as `link:T:/fact-check-skill`; the plugin market reports its activation as `live` ("已热加载(bundle patch)") with an empty `diagnostics.findings`, and the session's own skill catalogue lists `fact-check` with this repository's `SKILL.md` description — the only possible source, since neither `$DSH_HOME/skills` nor the profile's `skills/` directory contains a `fact-check` copy. The bundled suite re-ran green on this host: `smoke-plugin` 27/27, `jev-verdict selftest` 25/25, `route selftest` 44/44. Not covered this round: the edit→`control.invalidate()` watcher (it requires touching `SKILL.md` in the live repository), and the registry's `validateCandidate` / `toSummary` internals, which were not re-read against a previous build.
|
|
93
|
+
|
|
88
94
|
## Install
|
|
89
95
|
|
|
90
96
|
### As a DSH plugin
|
|
@@ -148,8 +154,8 @@ Three judgements are inherently about facts rather than about prose, and the ski
|
|
|
148
154
|
Two things about it are deliberate:
|
|
149
155
|
|
|
150
156
|
- **It is optional.** With no key configured, the same three judgements are made by the deterministic
|
|
151
|
-
rules written in `SKILL.md`; nothing else changes. A key can come from
|
|
152
|
-
`TYPESAFE_API_KEY` environment variable, or from `tools/jev-verdict.mjs --key-file`.
|
|
157
|
+
rules written in `SKILL.md`; nothing else changes. A key can come from the local config
|
|
158
|
+
(`local.json`), from the `TYPESAFE_API_KEY` environment variable, or from `tools/jev-verdict.mjs --key-file`.
|
|
153
159
|
- **A model verdict can only lower a grade, never raise it.** The conclusion tier is decided by the
|
|
154
160
|
deterministic rule *number of independent sources → tier*; a model saying "the evidence supports this"
|
|
155
161
|
promotes nothing. Only "insufficient evidence" or "contradicts the evidence" triggers an action.
|
|
@@ -199,7 +205,9 @@ generated `INSTRUCTIONS.md` still matches `SKILL.md`.
|
|
|
199
205
|
## Configuration
|
|
200
206
|
|
|
201
207
|
Machine-specific values — endpoints, keys, which channels actually work on your machine — live in
|
|
202
|
-
`
|
|
208
|
+
`local.json` **outside the package** (default `%LOCALAPPDATA%\dsh-fact-check\local.json` on Windows,
|
|
209
|
+
`~/.config/dsh-fact-check/local.json` elsewhere; override with `JEV_LOCAL_CONFIG`), so a package update
|
|
210
|
+
cannot wipe them. The template ships as [`tools/local.example.json`](./tools/local.example.json).
|
|
203
211
|
No host path, endpoint or key is hard-coded in the code or the data tables, and `tools/*.mjs` carry
|
|
204
212
|
assertions that fail if one ever appears.
|
|
205
213
|
|
package/README.zh-CN.md
CHANGED
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
技能正文不绑定任何产品、主机或工具链:它只写**能力名**,由 [`ADAPTING.md`](./ADAPTING.md) 把能力
|
|
63
63
|
落到你的环境里。
|
|
64
64
|
|
|
65
|
-
**已验证宿主:DSH 0.1.7-rc.2、0.2.0-rc.2、0.2.1-rc.2 与 0.2.
|
|
65
|
+
**已验证宿主:DSH 0.1.7-rc.2、0.2.0-rc.2、0.2.1-rc.2、0.2.1-rc.3、0.2.1-rc.5 与 0.2.2-rc.1。** 0.1.7-rc.2 于 2026-09-25 完整跑过一遍(Node 24.21.0,WSL/Linux):宿主能发现本技能并读入正文;
|
|
66
66
|
插件形态能挂到宿主自己的技能注册表上,`source: bundled`,描述取自 `SKILL.md`,`get()` 交出的正文
|
|
67
67
|
与 `SKILL.md` 逐字一致;`npm run selftest` 全过(插件挂载 27/27、`jev-verdict` 25/25、`route` 37/37);
|
|
68
68
|
`route.mjs regress` 离线 26/26;判据语料复现 [`MEASUREMENTS.md`](./MEASUREMENTS.md) §6 的预期 ——
|
|
@@ -77,6 +77,12 @@
|
|
|
77
77
|
|
|
78
78
|
**DSH 0.2.1-rc.3 于 2026-10-24 完整跑过一遍**(Node 24.22.1,Windows)。注册表契约没有位移:`@deepseek-ai/dsh-skill` 0.2.1-rc.3 仍导出 `BUNDLED_SKILL_RANK`(600)、`SkillRegistry`、`isSkillName`、`isModelInvocable` 与 `isUserInvocable`,`registerProvider()` 交出的 `control`(`{ signal, invalidate }`)不变,`invalidateCache()` 仍推进缓存 revision 并广播 `skills/change`。插件挂在这套注册表上(取自随宿主发布的 `app.asar`,配宿主自己的 cordis 4.0.5)通过了 **28/28** 项检查:`inject: ['skills']`;候选带 `source: bundled`、`rank` 600 等于宿主导出的常量、绝对 `SKILL.md` 作为 `path`、包根目录作为 `resourceBase`、描述与 frontmatter 逐字一致;`get()` 交出正文(13,533 字符,frontmatter 已剥离,`path` 保留);`control.invalidate()` 让注册表 revision 从 1 到 2 并恰好广播一次 `skills/change`;拆卸后 provider 消失且 `control.signal` 已 abort。两处看着像失败、其实不是的细节也一并记下:宿主的 `toSummary()` 有意不在目录摘要里带 `rank`(它是同名仲裁的输入,不是展示字段);注册只在 `ctx.plugin()` 的 thenable 落定后可观测 —— 在 await 之前去看,即便挂载成功也看不到 `control`。同一次 rc3 会话里真正运行的是**目录形态**(宿主自己的技能目录),会话的技能目录用本仓 `SKILL.md` 的描述列出了 `fact-check`。
|
|
79
79
|
|
|
80
|
+
**DSH 0.2.1-rc.5 于 2026-10-31 完整跑过一遍**(Node 26.13.0,Windows;宿主版本以随宿主发布的 `app.asar` 里 `@deepseek-ai/dsh-desktop` 声明的为准,`dsh --version` 报的也是同一个 `0.2.1-rc.5`)。注册表契约再次没有位移:`@deepseek-ai/dsh-skill` 0.2.1-rc.5 仍导出 `BUNDLED_SKILL_RANK`(600)与同一套 `SkillRegistry` / `isSkillName` / `isModelInvocable` / `isUserInvocable`,`registerProvider()` 交出的 `control`(`{ signal, invalidate }`)不变,`validateCandidate`、`toSummary`、`renderSkillContent` 的行为与 0.2.1-rc.3 一致 —— 均逐行对着从 `app.asar` 取出的那份文件、配宿主自己的 cordis 4.0.5 核过。插件挂在这套注册表上通过了 **24/24** 项检查:`inject: ['skills']`;`fact-check` 恰好一条候选,带 `source: bundled`、`rank` 600 等于宿主导出的常量、绝对 `SKILL.md` 作为 `path`、包根目录作为 `resourceBase`;`get()` 交出正文(14,462 字符,frontmatter 已剥离);该正文经 `renderSkillContent` 可正常渲染且资源基准为包根;`control.invalidate()` 让改动过的描述在目录里可见(探针内改完即还原,事后 `git status --porcelain` 为空);拆卸后 provider 再次消失。仓库自带套件在 Node 26.13.0 上复跑全绿:`smoke-plugin` 27/27、`jev-verdict selftest` 25/25、`route selftest` 44/44、`route.mjs regress` 离线 30/30、`doctor` exit 0。
|
|
81
|
+
|
|
82
|
+
这一版新增的一条宿主行为决定**任何**插件能不能加载,所以记在这里。`@deepseek-ai/dsh-app-boot` 加了插件↔运行时兼容门禁:声明了 `engines.dsh` 的插件必须满足它;没有 `engines.dsh` 但**有** `peerDependencies` 的插件,其中凡是 `@deepseek-ai/dsh` / `@deepseek-ai/dsh-*` 的 peer 都必须满足;**两者都没有的插件无条件通过**。不兼容的 profile bundle 会被静默跳过、不打印任何东西,不兼容的 `cordis.patch.yml` 行会被置 `disabled`。本包只声明 `engines.node`、没有 `peerDependencies`,所以门禁在任何运行时上都无法拒载它 —— 这是除上面那条插件市场理由之外,又一条把 manifest 保持为「不声明宿主版本」的理由。
|
|
83
|
+
|
|
84
|
+
**DSH 0.2.2-rc.1 于 2026-11-06 完整跑过一遍**(Node 26.13.0,Windows;宿主版本以随宿主发布的 `app.asar` 里 `@deepseek-ai/dsh-desktop` / `@deepseek-ai/dsh-desktop-runtime` 声明的为准,`dsh --version` 报的也是同一个 `0.2.2-rc.1`)。这一轮以**运行时证据为主,而非逐行 diff**:宿主是原地升级,磁盘上已没有 0.2.1-rc.5 的对应文件可作对照。仍直接核对了随宿主发布的构建里留下的契约:`@deepseek-ai/dsh-skill` 的 `lib/index.js` 第 23 行仍是 `const BUNDLED_SKILL_RANK = 600;`;`registerProvider()` 交出的 `control` 仍是 `{ signal, invalidate }`,`invalidate()` 仍先确认 `active.layer.providers.get(active.name)?.provider === provider` 再刷新目录;第 566 行的导出清单不变。运行中的宿主上,插件以 `link:T:/fact-check-skill` 挂载;插件市场把它的激活状态报成 `live`(「已热加载(bundle patch)」),`diagnostics.findings` 为空;会话自己的技能目录列出了 `fact-check`、描述与本仓 `SKILL.md` 一致 —— 而且 `$DSH_HOME/skills` 与 profile 的 `skills/` 下都没有 fact-check 副本,所以它只能来自插件。仓库自带套件在该宿主上复跑全绿:`smoke-plugin` 27/27、`jev-verdict selftest` 25/25、`route selftest` 44/44。这一轮**没有**覆盖:改 `SKILL.md` → `control.invalidate()` 的热刷新(需要在真实仓库里改文件才能验),以及 `validateCandidate` / `toSummary` 的内部行为(没有旧构建可逐行对照)。
|
|
85
|
+
|
|
80
86
|
## 安装
|
|
81
87
|
|
|
82
88
|
### 作为 DSH 插件
|
|
@@ -138,7 +144,7 @@ dsh plugin add ./dsh-fact-check
|
|
|
138
144
|
两件事是刻意的:
|
|
139
145
|
|
|
140
146
|
- **它是可选的。** 没配密钥时,这三件事由 `SKILL.md` 里写死的确定性规则执行,其余流程完全不变。
|
|
141
|
-
|
|
147
|
+
密钥可以来自本机配置 `local.json`、环境变量 `TYPESAFE_API_KEY`,或 `tools/jev-verdict.mjs --key-file`。
|
|
142
148
|
- **模型的判断只能降档,不能升档。** 结论档位由确定性规则「独立来源数 → 档位」裁定;模型说「支持」
|
|
143
149
|
不会让任何东西升级。只有「证据不足」或「与证据矛盾」才触发动作。
|
|
144
150
|
|
|
@@ -183,8 +189,10 @@ CI 在 Linux 与 Windows 上、Node 18 与 22 下跑的就是上面这几条,
|
|
|
183
189
|
|
|
184
190
|
## 配置
|
|
185
191
|
|
|
186
|
-
主机相关的值——端点、密钥、你那台机器上哪些渠道真的能用——一律放 `
|
|
187
|
-
|
|
192
|
+
主机相关的值——端点、密钥、你那台机器上哪些渠道真的能用——一律放 `local.json`,它在**包外**
|
|
193
|
+
(默认 `%LOCALAPPDATA%\dsh-fact-check\local.json`,其它平台 `~/.config/dsh-fact-check/local.json`,
|
|
194
|
+
可用 `JEV_LOCAL_CONFIG` 覆盖),这样包更新不会把它清掉。模板只分发
|
|
195
|
+
[`tools/local.example.json`](./tools/local.example.json)。代码与数据表里**不写死**任何主机路径、
|
|
188
196
|
端点或密钥,`tools/*.mjs` 里有断言专门守这条。
|
|
189
197
|
|
|
190
198
|
## 参与贡献
|
package/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: fact-check
|
|
3
|
-
version: 1.5.
|
|
3
|
+
version: 1.5.3
|
|
4
4
|
description: 事实核查与资料搜索。当用户要求核实事实/传闻/新闻/数据/情况的真伪、查证信息、确认现状、补充资料时使用——即使用户只说了"查一下""确认一下""核实""这是不是真的""帮我看看",也应触发。核心行为:不依赖自身知识库,所有关键信息一律通过互联网搜索获取,搜索途径与信息来源尽量多样以便交叉核实,并同时覆盖国内与国外信息生态。执行模式按工作量自动选择:轻量核查由主对话直连完成,渠道多或需抓取大量长文时自动分派并行子代理。
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -24,12 +24,12 @@ description: 事实核查与资料搜索。当用户要求核实事实/传闻/
|
|
|
24
24
|
4. **平台专用工具**——视频 / 社交类平台的专用 CLI(字幕、弹幕、评论等);
|
|
25
25
|
5. **免登录直取接口**——平台公开的免登录端点;拿不到就如实标注"未能获取原文",不要伪造。
|
|
26
26
|
|
|
27
|
-
哪条链在本机可用、哪个站已经挂了,属于**运行时基线**:写进 `
|
|
27
|
+
哪条链在本机可用、哪个站已经挂了,属于**运行时基线**:写进 `local.json`(包外用户配置目录,见 `ADAPTING.md`)的 `runtimeNotes`(带日期),**不要写进本文**——正文没有时效标注机制,写在这里半年后就是过期信息,而且换台机器就得改正文。
|
|
28
28
|
|
|
29
29
|
**首选路径失败必须自动降级到次选**,而不是直接判定"无法核查"。只有**五条路径全部失败**才判"无法核查",且必须在报告的「本次未能核实到的」里写明试过哪几条、各自报什么错。
|
|
30
30
|
**搜索结果的 `content` / `snippet` 字段只是摘要,不能当正文计入证据。**
|
|
31
31
|
|
|
32
|
-
**本技能不绑定任何具体产品、主机或工具链。** 正文只使用"能力名";把能力落到你的环境里见 `ADAPTING.md`;所有"在某台机器上测出来"的数字都在 `MEASUREMENTS.md`,它们**是证据,不是配置**。本机的能力绑定与输出约定可写进 `
|
|
32
|
+
**本技能不绑定任何具体产品、主机或工具链。** 正文只使用"能力名";把能力落到你的环境里见 `ADAPTING.md`;所有"在某台机器上测出来"的数字都在 `MEASUREMENTS.md`,它们**是证据,不是配置**。本机的能力绑定与输出约定可写进 `local.json`(包外,可选字段,见 `tools/local.example.json`),正文只保留默认值。
|
|
33
33
|
|
|
34
34
|
自检:`node tools/doctor.mjs` 会探测当前环境有哪些能力、缺什么、会降级成什么样;加 `--net` 会额外自测判据端点与**两条可自测的抓取链**(宿主内置抓取与搜索服务的抽取字段它探不到,需自己各跑一次真实抓取确认)。
|
|
35
35
|
|
|
@@ -174,6 +174,25 @@ description: 事实核查与资料搜索。当用户要求核实事实/传闻/
|
|
|
174
174
|
|
|
175
175
|
---
|
|
176
176
|
|
|
177
|
+
### Step 5 交付前回检(措辞与断言)
|
|
178
|
+
|
|
179
|
+
**写完报告不等于可以交。** 这一步查的不是"证据够不够"(那是 Step 3),而是**写出来的每个字是否还等于证据**。报告最容易在措辞层被放大——一旦被断章引用,错的不是结论,是读者以为的事实。
|
|
180
|
+
|
|
181
|
+
逐条过:
|
|
182
|
+
|
|
183
|
+
1. **单位没被偷换**:条数 ≠ 人数、账号 ≠ 人、场次 ≠ 观众、人次 ≠ 人。凡给出"多少人 / 多少参与方",先数**去重后**的主体标识。
|
|
184
|
+
2. **量词配得上对象**:人用「位 / 个 / 名」,不用「条 / 只 / 头」。
|
|
185
|
+
3. **覆盖面写清分母与口径**:「X% 没有某待遇」要指明哪一类人、什么统计范围。「没有带薪年假」不等于「一天带薪假都没有」(可能仍有带薪节假日)。
|
|
186
|
+
4. **百分比写清基准**:「增长 / 超出 X%」必须说明相对哪个基数;换个基数,结论可能差一倍。
|
|
187
|
+
5. **比较类数字标混杂因素**:自动标注、篇幅差异、样本选择造成的偏差写在括号里,否则加「约」。
|
|
188
|
+
6. **未核实处显式标注**:写进「本次未能核实到的」,或就地加「(未验证)」;不用「据说 / 普遍认为」糊过去。
|
|
189
|
+
7. **一句话结论、标题、摘要单独再查一遍**:它们被引用的概率最高、被读的字最少,也最容易被写成"更响亮但不准确"的地方。改完回头与 Step 3 的来源档位对一次。
|
|
190
|
+
8. **修辞不得改变事实**:为了让判断更锋利而改动词汇,即使每个字都能查证,也已经是错的。
|
|
191
|
+
|
|
192
|
+
> **判据**:任一条过不了,就改数字或加限定词——不许把不确定写成确定。
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
177
196
|
## 利益相关与污染面(标注制、不降权、双轨呈现)
|
|
178
197
|
|
|
179
198
|
**本节只标注,不降权。** 一个来源是否存在利益关联、是否处在大规模投毒污染的信息链上,**本技能不做判定**;它只把能拿出来看的东西标出来,并给出两套固定口径下的结论,由读报告的人自己裁定。
|
|
@@ -343,7 +362,7 @@ description: 事实核查与资料搜索。当用户要求核实事实/传闻/
|
|
|
343
362
|
- 目录结构:**`FOR-AI.md`(交给 AI 的操作说明:逐步配置与使用)** + `SKILL.md`(本文件,流程与规则)+ `ADAPTING.md`(换环境要改什么)+ `MEASUREMENTS.md`(实测数字与复跑)+ `CHANGELOG.md`(版本间净变化)+ `lib/index.js`(DSH 插件入口:把本文件挂到 `ctx.skills`)+ `cordis.patch.yml`(让本包能被 `dsh plugin add` 安装)+ `tools/`(两个可选工具、本机配置模板与数据表)+ `cases/`(回归语料)+ `README.md` / `README.zh-CN.md`(给人类的说明)+ `submission/`(投稿到插件市场的条目文件,不属于插件包本身)。
|
|
344
363
|
- **两种分发形态同一份正文**:① `dsh plugin add github:7starsseeker/dsh-fact-check`(DSH 插件市场的形态,`lib/index.js` 现读本文件,所以改本文件不必改代码);② 把整个目录交给任何 AI 工具(`FOR-AI.md` 那套)。**不要为其中一种抄一份正文出来**——两份必然漂移,`tools/smoke-plugin.mjs` 里有断言在守"候选描述 = 本文件 frontmatter 的 description"这条单一真相源。
|
|
345
364
|
- **改判据 = 重新校准事件**:`tools/jev-verdict.mjs` 里的 `QUESTIONS` 是冻结字面量,任何 instruction/criteria/阈值改动都必须重跑 `cases/` 下的回归,并把新数字写回 `MEASUREMENTS.md`。**`tools/route.mjs` 的触发词表与域路由表同理**:改完要重跑 `node tools/route.mjs regress --file cases/route-cases-neutral.json` 与 `node tools/route.mjs selftest`。
|
|
346
|
-
- **环境相关的值只放 `
|
|
365
|
+
- **环境相关的值只放 `local.json`**(包外用户配置目录;模板 `tools/local.example.json`):密钥、端点、state 目录、数据表里的 `${占位符}`,以及可选的能力绑定 `capabilities`、输出约定 `outputConvention`、运行时基线 `runtimeNotes`。代码与数据表**不写死任何主机路径**,`selftest` 里有断言在守这条。
|
|
347
366
|
- **不要在本技能正文里写入任何主机、内网地址、路径或密钥**。本机事实要么进 `local.json`,要么作为"在某环境测得"的证据进 `MEASUREMENTS.md`。
|
|
348
367
|
- **版本纪律**:凡是改动面向使用者的行为(流程、报告模板、判据、触发词),就升 frontmatter 的 `version` 并在 `CHANGELOG.md` 记一条净变化;只改措辞或文档不算。
|
|
349
368
|
- **「利益相关与污染面」是标注制,不是降权制**:不要把任何"利益相关 / 疑似投毒 ⇒ 降档"的规则接进定档。至少会同时坏三件事(非单调、奖励信息垄断、全局降档),三条理由是那一节的正文,改之前先重读。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-fact-check",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.3",
|
|
4
4
|
"description": "Fact-checking skill for DeepSeek Harness: verifies claims against the open web instead of model memory, cross-checks independent sources across domestic and international search ecosystems, and can route three factual judgements (does the evidence support the claim / is this source primary / does this page carry usable text) to a TypeSafe Jev (System One) decision model when a key is configured, falling back to the skill's deterministic rules when it is not.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"deepseek-harness",
|
package/tools/config.mjs
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* config — 本机配置(local.json / secrets.json)的**包外**解析。
|
|
4
|
+
*
|
|
5
|
+
* 为什么需要这一层:`dsh plugin update` / `npm i` 换掉的是整个包目录。
|
|
6
|
+
* 配置如果写在 `tools/local.json`,每次更新都会被清掉;这个文件只决定
|
|
7
|
+
* “去哪儿找配置”,不改变配置本身的格式。
|
|
8
|
+
*
|
|
9
|
+
* 解析顺序(先命中先用):
|
|
10
|
+
* 1. 环境变量 `JEV_LOCAL_CONFIG`(显式指定文件,便于测试与多机切换)
|
|
11
|
+
* 2. 平台配置目录:
|
|
12
|
+
* Windows %LOCALAPPDATA%\dsh-fact-check\local.json
|
|
13
|
+
* 其它 $XDG_CONFIG_HOME/dsh-fact-check/local.json 或 ~/.config/dsh-fact-check/local.json
|
|
14
|
+
* 3. 旧的包内路径 `tools/local.json`(向后兼容,不推荐继续用)
|
|
15
|
+
*
|
|
16
|
+
* `secrets.json` 用同一套目录,只是文件名不同。
|
|
17
|
+
* 零依赖;解析失败一律当作“没有配置”,绝不阻断核查流程。
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
|
|
21
|
+
import { homedir } from 'node:os'
|
|
22
|
+
import { join } from 'node:path'
|
|
23
|
+
|
|
24
|
+
export const APP_DIR = 'dsh-fact-check'
|
|
25
|
+
|
|
26
|
+
/** 平台配置目录——包目录之外,更新不会动它。 */
|
|
27
|
+
export function configDir() {
|
|
28
|
+
if (process.platform === 'win32' && process.env.LOCALAPPDATA) {
|
|
29
|
+
return join(process.env.LOCALAPPDATA, APP_DIR)
|
|
30
|
+
}
|
|
31
|
+
if (process.env.XDG_CONFIG_HOME) return join(process.env.XDG_CONFIG_HOME, APP_DIR)
|
|
32
|
+
return join(homedir(), '.config', APP_DIR)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** 用户配置文件的**目标**路径(可能还不存在)。 */
|
|
36
|
+
export function userConfigPath(name = 'local.json') {
|
|
37
|
+
return join(configDir(), name)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 按优先级列出候选路径。`pkgDir` 是 tools/ 目录(旧的包内位置)。
|
|
42
|
+
* 存在的排前面,但顺序严格按优先级——不存在的也在列表里,供 --write 判断。
|
|
43
|
+
*/
|
|
44
|
+
export function configCandidates(pkgDir, name = 'local.json') {
|
|
45
|
+
return [process.env.JEV_LOCAL_CONFIG, userConfigPath(name), join(pkgDir, name)].filter(Boolean)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** 当前实际生效的配置文件;一个都不存在时返回 null。 */
|
|
49
|
+
export function activeConfigPath(pkgDir, name = 'local.json') {
|
|
50
|
+
return configCandidates(pkgDir, name).find((f) => existsSync(f)) || null
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** 读 JSON;任何异常都返回 null(坏配置不能把核查流程带崩)。 */
|
|
54
|
+
export function readJsonSafe(file) {
|
|
55
|
+
try {
|
|
56
|
+
return JSON.parse(readFileSync(file, 'utf8'))
|
|
57
|
+
} catch {
|
|
58
|
+
return null
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 读本机配置。
|
|
64
|
+
* pkgDir tools/ 目录(`import.meta.url` 所在目录)
|
|
65
|
+
* name 文件名,默认 local.json
|
|
66
|
+
*/
|
|
67
|
+
export function readLocalConfig(pkgDir, name = 'local.json') {
|
|
68
|
+
const file = activeConfigPath(pkgDir, name)
|
|
69
|
+
return file ? readJsonSafe(file) || {} : {}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* 写配置草稿:默认写到包外用户目录,已存在则不覆盖。
|
|
74
|
+
* 返回 { path, written }。
|
|
75
|
+
*/
|
|
76
|
+
export function writeLocalConfig(pkgDir, data, name = 'local.json') {
|
|
77
|
+
const existing = activeConfigPath(pkgDir, name)
|
|
78
|
+
if (existing) return { path: existing, written: false }
|
|
79
|
+
const target = userConfigPath(name)
|
|
80
|
+
mkdirSync(configDir(), { recursive: true })
|
|
81
|
+
writeFileSync(target, JSON.stringify(data, null, 2) + '\n', 'utf8')
|
|
82
|
+
return { path: target, written: true }
|
|
83
|
+
}
|
package/tools/doctor.mjs
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*
|
|
9
9
|
* node tools/doctor.mjs # offline checks only
|
|
10
10
|
* node tools/doctor.mjs --net # also probe the endpoint, the local search service, and the fetch chain
|
|
11
|
-
* node tools/doctor.mjs --write # write
|
|
11
|
+
* node tools/doctor.mjs --write # write the local config from detected values (won't overwrite)
|
|
12
12
|
* node tools/doctor.mjs --json # machine-readable
|
|
13
13
|
*
|
|
14
14
|
* It never prints a secret — only where a key was found.
|
|
@@ -18,6 +18,7 @@ import { existsSync, readFileSync, writeFileSync, mkdirSync, rmSync } from 'node
|
|
|
18
18
|
import { dirname, join, isAbsolute, resolve } from 'node:path'
|
|
19
19
|
import { fileURLToPath } from 'node:url'
|
|
20
20
|
import { homedir, tmpdir } from 'node:os'
|
|
21
|
+
import { activeConfigPath, readLocalConfig, userConfigPath, writeLocalConfig } from './config.mjs'
|
|
21
22
|
import { spawnSync } from 'node:child_process'
|
|
22
23
|
|
|
23
24
|
const HERE = dirname(fileURLToPath(import.meta.url))
|
|
@@ -36,7 +37,7 @@ function readJson(file) {
|
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
function localConfig() {
|
|
39
|
-
return
|
|
40
|
+
return readLocalConfig(HERE)
|
|
40
41
|
}
|
|
41
42
|
|
|
42
43
|
/* ---------------- runtime ---------------- */
|
|
@@ -44,6 +45,7 @@ function localConfig() {
|
|
|
44
45
|
const major = Number(process.versions.node.split('.')[0])
|
|
45
46
|
report.info.node = process.versions.node
|
|
46
47
|
report.info.platform = `${process.platform} ${process.arch}`
|
|
48
|
+
report.info.configFile = activeConfigPath(HERE) || `${userConfigPath('local.json')}(尚未生成)`
|
|
47
49
|
if (major >= 18) report.ok.push(`Node ${process.versions.node}(≥18,内置 fetch / AbortSignal.timeout 可用)`)
|
|
48
50
|
else report.fail.push(`Node ${process.versions.node} 过旧:需要 ≥18(内置 fetch)。判据层不可用,流程仍可按 SKILL.md 人工执行`)
|
|
49
51
|
|
|
@@ -255,9 +257,9 @@ if (has('--net')) {
|
|
|
255
257
|
/* ---------------- --write ---------------- */
|
|
256
258
|
|
|
257
259
|
if (has('--write')) {
|
|
258
|
-
const
|
|
259
|
-
if (
|
|
260
|
-
report.warn.push(`local.json 已存在,未覆盖:${
|
|
260
|
+
const existing = activeConfigPath(HERE)
|
|
261
|
+
if (existing) {
|
|
262
|
+
report.warn.push(`local.json 已存在,未覆盖:${existing}`)
|
|
261
263
|
} else {
|
|
262
264
|
const generated = {
|
|
263
265
|
_about: '由 doctor.mjs 生成;可按需修改。请勿提交或分享。',
|
|
@@ -272,8 +274,8 @@ if (has('--write')) {
|
|
|
272
274
|
EXIFTOOL_CMD: process.env.JEV_EXIFTOOL_CMD || ov.EXIFTOOL_CMD || report.info.optionalTools.exiftool || null,
|
|
273
275
|
},
|
|
274
276
|
}
|
|
275
|
-
|
|
276
|
-
report.ok.push(`已生成 ${
|
|
277
|
+
const { path } = writeLocalConfig(HERE, generated)
|
|
278
|
+
report.ok.push(`已生成 ${path} —— 请把 null 的项按你的环境补上`)
|
|
277
279
|
}
|
|
278
280
|
}
|
|
279
281
|
|
|
@@ -287,6 +289,7 @@ if (has('--json')) {
|
|
|
287
289
|
line('# fact-check skill · 环境自检')
|
|
288
290
|
line('')
|
|
289
291
|
line(`运行环境:Node ${report.info.node} · ${report.info.platform}`)
|
|
292
|
+
line(`本机配置:${report.info.configFile}`)
|
|
290
293
|
line(`判据端点:${report.info.endpoint} · 模型:${report.info.model}`)
|
|
291
294
|
line(`日志目录:${report.info.stateDir}${writable ? '(可写)' : '(不可写,会静默跳过)'}`)
|
|
292
295
|
line('')
|
|
@@ -311,7 +314,7 @@ if (has('--json')) {
|
|
|
311
314
|
'正文抽取字段探不到,请各自用一次真实抓取确认——这三条是"任一可用即可"的能力族。',
|
|
312
315
|
)
|
|
313
316
|
}
|
|
314
|
-
line(
|
|
317
|
+
line(`下一步:node tools/doctor.mjs --write 生成配置草稿(写到 ${userConfigPath('local.json')},包更新不会清掉);或手动创建同名文件。`)
|
|
315
318
|
line('')
|
|
316
319
|
}
|
|
317
320
|
|
package/tools/engine-routes.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"_about": "渠道路由表:确定性映射,模型不参与。**这是模板,不是配置**:表里所有 ${...} 都是变量,取值由
|
|
2
|
+
"_about": "渠道路由表:确定性映射,模型不参与。**这是模板,不是配置**:表里所有 ${...} 都是变量,取值由 `local.json`(包外用户配置目录)的 overrides(或 JEV_* 环境变量)提供;未填也能跑,计划会原样显示占位符并把它列进 localPlaceholdersUnset。",
|
|
3
3
|
"_template_note": "本表只描述公开可用的渠道与接口,不包含任何主机地址。下面的 capabilities 段是「这类渠道能干什么」,具体地址由你填。",
|
|
4
4
|
"capabilities": {
|
|
5
5
|
"searxng": "${SEARXNG_URL} —— 自托管元搜索:GET /search?q=…&format=json;引擎数量看你自己实例的 GET /config;若走 HTTP 代理,本地地址必须绕过代理;冷启首查可能超时,建议放宽到 20~30s",
|
package/tools/jev-verdict.mjs
CHANGED
|
@@ -31,6 +31,7 @@ import { readFileSync, writeFileSync, appendFileSync, existsSync, mkdirSync, rea
|
|
|
31
31
|
import { dirname, join, isAbsolute, resolve } from 'node:path'
|
|
32
32
|
import { fileURLToPath } from 'node:url'
|
|
33
33
|
import { homedir } from 'node:os'
|
|
34
|
+
import { readLocalConfig, userConfigPath } from './config.mjs'
|
|
34
35
|
|
|
35
36
|
const HERE = dirname(fileURLToPath(import.meta.url))
|
|
36
37
|
const ENDPOINT = 'https://api.typesafe.ai/v1/systemone'
|
|
@@ -48,8 +49,7 @@ const PRICE_PER_MTOK = 0.042
|
|
|
48
49
|
*/
|
|
49
50
|
export function localConfig() {
|
|
50
51
|
try {
|
|
51
|
-
|
|
52
|
-
if (existsSync(f)) return JSON.parse(readFileSync(f, 'utf8'))
|
|
52
|
+
return readLocalConfig(HERE)
|
|
53
53
|
} catch {
|
|
54
54
|
/* a broken local.json must never break a verdict */
|
|
55
55
|
}
|
|
@@ -273,6 +273,7 @@ export function resolveKey({ explicit } = {}) {
|
|
|
273
273
|
? process.env.JEV_KEY_FILES.split(process.platform === 'win32' ? ';' : ':')
|
|
274
274
|
: []),
|
|
275
275
|
join(HERE, 'secrets.json'),
|
|
276
|
+
userConfigPath('secrets.json'),
|
|
276
277
|
].filter(Boolean)
|
|
277
278
|
for (const f of candidates) {
|
|
278
279
|
try {
|
package/tools/local.example.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"_about": "复制成 local.json 并填入你自己的值(或先跑 `node tools/doctor.mjs --write`
|
|
2
|
+
"_about": "复制成 local.json 并填入你自己的值(或先跑 `node tools/doctor.mjs --write` 生成草稿)。推荐放在包外的用户配置目录:Windows `%LOCALAPPDATA%\\dsh-fact-check\\local.json`,其它平台 `~/.config/dsh-fact-check/local.json`;也可用 JEV_LOCAL_CONFIG 指定路径。local.json 属于本机配置:不要提交、不要随包分享、不要写进 SKILL.md。",
|
|
3
3
|
"_note": "全部字段都可以省略:省了就按内置默认值跑,占位符未填时 route.mjs 会把它们列进 localPlaceholdersUnset,jev-verdict.mjs 会在找不到密钥时明确报错并回退到人工规则。环境变量优先级高于本文件(JEV_ 前缀或原名,例如 JEV_SEARXNG_URL / SEARXNG_URL)。",
|
|
4
4
|
"keyFile": "/path/to/your/secrets.json",
|
|
5
5
|
"keyFiles": [],
|
package/tools/route.mjs
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
import { readFileSync, writeFileSync, existsSync } from 'node:fs'
|
|
17
17
|
import { dirname, join, isAbsolute, resolve } from 'node:path'
|
|
18
18
|
import { fileURLToPath } from 'node:url'
|
|
19
|
+
import { activeConfigPath, readLocalConfig } from './config.mjs'
|
|
19
20
|
|
|
20
21
|
const HERE = dirname(fileURLToPath(import.meta.url))
|
|
21
22
|
const matrix = JSON.parse(readFileSync(join(HERE, 'source-matrix.json'), 'utf8'))
|
|
@@ -28,17 +29,25 @@ const CASES_DIR = resolve(HERE, '..', 'cases')
|
|
|
28
29
|
* never in the tables or the code. Anything environment-specific in the tables
|
|
29
30
|
* is written as ${PLACEHOLDER} and resolved here from:
|
|
30
31
|
* env JEV_<NAME> → env <NAME> → local.json `overrides.<NAME>` → left as-is.
|
|
32
|
+
*
|
|
33
|
+
* The file itself lives OUTSIDE the package (see `config.mjs`), so a package
|
|
34
|
+
* update cannot delete the machine's settings; `tools/local.json` is still read
|
|
35
|
+
* as a fallback for older installs.
|
|
31
36
|
*/
|
|
32
37
|
export function localOverrides() {
|
|
33
38
|
try {
|
|
34
|
-
|
|
35
|
-
if (existsSync(f)) return JSON.parse(readFileSync(f, 'utf8')).overrides || {}
|
|
39
|
+
return readLocalConfig(HERE).overrides || {}
|
|
36
40
|
} catch {
|
|
37
41
|
/* a broken local.json must not break planning */
|
|
38
42
|
}
|
|
39
43
|
return {}
|
|
40
44
|
}
|
|
41
45
|
|
|
46
|
+
/** Which local.json is actually in effect — surfaced by the doctor/plan output. */
|
|
47
|
+
export function localConfigPath() {
|
|
48
|
+
return activeConfigPath(HERE)
|
|
49
|
+
}
|
|
50
|
+
|
|
42
51
|
export function subst(text) {
|
|
43
52
|
const ov = localOverrides()
|
|
44
53
|
return String(text).replace(/\$\{([A-Z0-9_]+)\}/g, (m, name) =>
|