gongwen-skill 2.7.0 → 2.8.0

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.
@@ -103,6 +103,7 @@ metadata:
103
103
  ```
104
104
  - **PyPI 为权威判定渠道**(pip 包核心分发,pip install -U 即从 PyPI 拉取);PyPI 不可达时回退 GitHub tag(备用渠道,git 用户/CI 触发源)
105
105
  - GitCode/AtomGit 与 GitHub 同源 tag,仅在 GitHub 不可达时作国内拉取镜像提示(不参与版本判定)
106
+ - GitHub 不可达且触发 DNS 诊断时(系统解析为保留/Fake-IP 段),`check-update --json` 会输出 `dns_diagnosis` 字段(含 `hosts_suggestions`),如实向用户转述排查建议即可
106
107
  - 若全部渠道均不可用(无 git/无网络),**必须明确告知用户"版本自检因无法访问远程而跳过"**,不得静默假设本地即最新
107
108
  3.5. **本地 git tag 对比**(P6,Agent 执行版本确认时补充):
108
109
  - 优先对 skill 安装目录执行 git tag 对比:
@@ -925,15 +926,16 @@ python -m gongwen fix-common 文件.docx -o 成品.docx
925
926
  | `table-signs` | 从名单批量生成会议桌签 | `python -m gongwen table-signs 名单.txt -o 桌签.docx` |
926
927
  | `review` | 生成公文审稿流转单(五/三角色) | `python -m gongwen review report -o 审稿单.docx` |
927
928
  | `handoff` | 跨会话交接(长任务收尾必写) | `python -m gongwen handoff --write` |
928
- | `check-update` | 版本自检(PyPI pip 包权威 + GitHub 备用,自动检测安装形态) | `python -m gongwen check-update` |
929
- | `font` | 公文标准字体管理(安装/检查/列出) | `python -m gongwen font install` |
929
+ | `check-update` | 版本自检(PyPI pip 包权威 + GitHub 备用,自动检测安装形态;GitHub 不可达时 DNS 诊断) | `python -m gongwen check-update` |
930
+ | `font` | 公文标准字体管理(安装/检查/列出;下载失败时自动安全 DNS 直连兜底) | `python -m gongwen font install` |
930
931
 
931
932
  ### 🩺 诊断与修复
932
933
 
933
934
  | 命令 | 用途 | 最小用法 |
934
935
  |------|------|---------|
935
- | `doctor` | 全面诊断:检查 Python 版本/依赖/版本一致性/字体/DSH 文件/代码风格等 | `python -m gongwen doctor` |
936
+ | `doctor` | 全面诊断:检查 Python 版本/依赖/版本一致性/字体/DSH 文件/代码风格/网络 DNS 等 | `python -m gongwen doctor` |
936
937
  | `doctor --json` | JSON 结构化输出(便于 Agent 解析) | `python -m gongwen doctor --json` |
938
+ | `doctor --offline` | 跳过网络/DNS 诊断(离线模式) | `python -m gongwen doctor --offline` |
937
939
  | `repair` | 修复常见问题:安装缺失依赖/字体/同步 SKILL.md 副本 | `python -m gongwen repair` |
938
940
 
939
941
  ### ⚙️ 规则管理
@@ -103,6 +103,7 @@ metadata:
103
103
  ```
104
104
  - **PyPI 为权威判定渠道**(pip 包核心分发,pip install -U 即从 PyPI 拉取);PyPI 不可达时回退 GitHub tag(备用渠道,git 用户/CI 触发源)
105
105
  - GitCode/AtomGit 与 GitHub 同源 tag,仅在 GitHub 不可达时作国内拉取镜像提示(不参与版本判定)
106
+ - GitHub 不可达且触发 DNS 诊断时(系统解析为保留/Fake-IP 段),`check-update --json` 会输出 `dns_diagnosis` 字段(含 `hosts_suggestions`),如实向用户转述排查建议即可
106
107
  - 若全部渠道均不可用(无 git/无网络),**必须明确告知用户"版本自检因无法访问远程而跳过"**,不得静默假设本地即最新
107
108
  3.5. **本地 git tag 对比**(P6,Agent 执行版本确认时补充):
108
109
  - 优先对 skill 安装目录执行 git tag 对比:
@@ -925,15 +926,16 @@ python -m gongwen fix-common 文件.docx -o 成品.docx
925
926
  | `table-signs` | 从名单批量生成会议桌签 | `python -m gongwen table-signs 名单.txt -o 桌签.docx` |
926
927
  | `review` | 生成公文审稿流转单(五/三角色) | `python -m gongwen review report -o 审稿单.docx` |
927
928
  | `handoff` | 跨会话交接(长任务收尾必写) | `python -m gongwen handoff --write` |
928
- | `check-update` | 版本自检(PyPI pip 包权威 + GitHub 备用,自动检测安装形态) | `python -m gongwen check-update` |
929
- | `font` | 公文标准字体管理(安装/检查/列出) | `python -m gongwen font install` |
929
+ | `check-update` | 版本自检(PyPI pip 包权威 + GitHub 备用,自动检测安装形态;GitHub 不可达时 DNS 诊断) | `python -m gongwen check-update` |
930
+ | `font` | 公文标准字体管理(安装/检查/列出;下载失败时自动安全 DNS 直连兜底) | `python -m gongwen font install` |
930
931
 
931
932
  ### 🩺 诊断与修复
932
933
 
933
934
  | 命令 | 用途 | 最小用法 |
934
935
  |------|------|---------|
935
- | `doctor` | 全面诊断:检查 Python 版本/依赖/版本一致性/字体/DSH 文件/代码风格等 | `python -m gongwen doctor` |
936
+ | `doctor` | 全面诊断:检查 Python 版本/依赖/版本一致性/字体/DSH 文件/代码风格/网络 DNS 等 | `python -m gongwen doctor` |
936
937
  | `doctor --json` | JSON 结构化输出(便于 Agent 解析) | `python -m gongwen doctor --json` |
938
+ | `doctor --offline` | 跳过网络/DNS 诊断(离线模式) | `python -m gongwen doctor --offline` |
937
939
  | `repair` | 修复常见问题:安装缺失依赖/字体/同步 SKILL.md 副本 | `python -m gongwen repair` |
938
940
 
939
941
  ### ⚙️ 规则管理
package/CHANGELOG.md CHANGED
@@ -4,6 +4,19 @@
4
4
  Licensed under the MIT License. See the LICENSE file for details.
5
5
  -->
6
6
 
7
+ ## v2.8.0 (2026-09-03)
8
+
9
+ ### Added
10
+ - **DNS 污染诊断(安全 DNS / DoH)**:`check-update` 在 GitHub 渠道不可达时自动触发 DNS 污染诊断,对比系统解析与安全 DNS(DoH)真实 IP,输出污染判定、对比表与可直接粘贴的 hosts 条目建议
11
+ - **`doctor` 新增「网络/DNS 诊断」检查项**:默认联网检测 GitHub/PyPI 关键域名是否疑似 DNS 污染(系统解析落在 198.18.0.0/15 等保留/Fake-IP 段时判定疑似污染),`--offline` 参数可完全跳过网络查询
12
+ - **新增 `gongwen/cli/netcheck.py` 模块**:DoH 解析(内置阿里 `dns.alidns.com` / 腾讯 `doh.pub` / `1.12.12.12` / Google 备用,多端点自动降级)+ 系统解析对比 + 污染判定 + hosts 建议生成;环境变量 `GONGWEN_DOH` 可覆盖为自定义 DoH 端点
13
+ - **`check-update --json` 输出新增 `dns_diagnosis` 字段**:含 `polluted` / `detail` / `hosts_suggestions` 等,便于 Agent 解析
14
+ - **自动直连兜底(DNS 污染时零操作下载)**:`font install` 下载字体、`check-update`/`helpers` 查询 PyPI 常规请求失败时,自动用 DoH 真实 IP + TLS SNI 直连重试(`netcheck.download_with_doh_fallback` / `_DoHHTTPSConnection`),证书校验仍针对真实域名,安全不降级
15
+
16
+ ### Notes
17
+ - 诊断只做建议,不修改 hosts、不改变版本判定逻辑;自动直连仅作为下载/查询失败的兜底路径(YAGNI)
18
+ - DoH 查询经第三方公共 DNS 服务(阿里/腾讯),仅诊断/兜底失败时发起少量查询;可设置 `GONGWEN_DOH` 指向私有端点
19
+
7
20
  ## v2.7.0 (2026-09-03)
8
21
 
9
22
  ### Added
package/README.md CHANGED
@@ -45,8 +45,8 @@ Licensed under the MIT License. See the LICENSE file for details.
45
45
  | 🔍 审稿生成 | `review` | 按五角色审稿机制生成审稿意见 |
46
46
  | 🧩 完整审校 | `full-review` | 修订+批注联合命令(句子级差异修订 + 分类批注) |
47
47
  | 🎨 样式学习 | `style-learn` / `style-list` | 上传标准文档学习 Run/段落/页面三级样式(字体/字号/字间距/行距/缩进/页边距),生成命名模板持久化,后续用 `optimize -t 模板名` 套用 |
48
- | 🔄 版本自检 | `check-update` | 版本自检(PyPI pip 包权威判定 + GitHub 备用,GitCode/AtomGit 作国内镜像提示) |
49
- | 🩺 自我诊断 | `doctor` / `repair` | 全面诊断 22 项(Python/依赖/版本一致性/字体/DSH 文件/DSH 技能 frontmatter/代码风格),自动修复常见问题 |
48
+ | 🔄 版本自检 | `check-update` | 版本自检(PyPI pip 包权威判定 + GitHub 备用,GitCode/AtomGit 作国内镜像提示;GitHub 不可达时自动做 DNS 污染诊断 + hosts 建议) |
49
+ | 🩺 自我诊断 | `doctor` / `repair` | 全面诊断 23 项(Python/依赖/版本一致性/字体/DSH 文件/DSH 技能 frontmatter/代码风格/网络 DNS),自动修复常见问题;`--offline` 跳过网络检查 |
50
50
  | 🕵️ 文档审计 | `audit` | 检查删除线/加粗/AI 声明等痕迹 |
51
51
  | 🤝 会话交接 | `handoff` | 跨会话上下文传递(`--list` / `--latest` / Agent 长任务收尾必写) |
52
52
  | ⚙️ 规则管理 | `rule-export/import/list` | YAML 规则三层定制(官方/单位/用户) |
@@ -336,7 +336,7 @@ python -m gongwen rule-list notice
336
336
 
337
337
  Agent 加载 skill 后**必须执行版本追新自检**,确保使用最新版本:
338
338
 
339
- 1. **远程自检**(首选):`python -m gongwen check-update`——以 **PyPI(pip 包发布源)为权威判定渠道**并发查询比对本地(pip install -U 即从 PyPI 拉取);PyPI 不可达时回退 GitHub tag(备用渠道)。全部渠道不可达时明确告知"版本自检跳过"。GitHub 为海外渠道(国内常超时)采用短超时快速降级;GitHub 不可达时自动提示国内代码镜像(GitCode/AtomGit,与 GitHub 同源 tag)与 GitHub520 hosts 加速方案
339
+ 1. **远程自检**(首选):`python -m gongwen check-update`——以 **PyPI(pip 包发布源)为权威判定渠道**并发查询比对本地(pip install -U 即从 PyPI 拉取);PyPI 不可达时回退 GitHub tag(备用渠道)。全部渠道不可达时明确告知"版本自检跳过"。GitHub 为海外渠道(国内常超时)采用短超时快速降级;GitHub 不可达时自动提示国内代码镜像(GitCode/AtomGit,与 GitHub 同源 tag)、GitHub520 hosts 加速方案,并自动做 **DNS 污染诊断**(对比系统解析与安全 DNS/DoH 真实 IP,输出可直接粘贴的 hosts 条目建议)
340
340
  2. **本地 git tag 对比**(补充):对 skill 安装目录执行 `git -C "<skill安装目录>" describe --tags --abbrev=0`;若安装目录不在 git 管理下,应告知用户"无法执行版本对比,建议手动检查 GitHub 更新"
341
341
  3. **落后则警告**:发现本地版本落后于最新版本时,**必须在执行前警告用户**并提示更新——`check-update` 会按安装形态自动给出精准更新命令(pip 包安装:`pip install --upgrade gongwen-skill`;git/skill 目录安装:`cd <gongwen-skill目录> && git pull && git fetch --tags`),不得静默使用旧版本
342
342
 
@@ -356,7 +356,7 @@ DSH 采用 **Cordis 模块化微内核架构**:技能体系基于本地文件
356
356
  git clone https://github.com/linhut/gongwen-skill.git
357
357
  cd gongwen-skill
358
358
  pip install -r requirements.txt # 或 pip install gongwen-skill(已上 PyPI)
359
- python -m gongwen --version # 检验:gongwen-skill v2.7.0
359
+ python -m gongwen --version # 检验:gongwen-skill v2.8.0
360
360
  ```
361
361
 
362
362
  ### 方式一:作为 DSH Skill 注册(基于本地文件系统)
@@ -412,7 +412,7 @@ pnpm add -w gongwen-skill
412
412
  "dependencies": {
413
413
  "@deepseek-ai/dsh-base": "...",
414
414
  "@deepseek-ai/dsh-web-app": "...",
415
- "gongwen-skill": "^2.7.0"
415
+ "gongwen-skill": "^2.8.0"
416
416
  },
417
417
  "dsh": {
418
418
  "profile": {
@@ -593,7 +593,7 @@ pip install -r requirements.txt
593
593
  用户:帮我优化这份会议通知的第二章节措辞
594
594
 
595
595
  Agent:📋 合规自检报告
596
- Skill 版本: v2.7.0(版本自检已确认最新)
596
+ Skill 版本: v2.8.0(版本自检已确认最新)
597
597
  路径判定: B(内容优化)
598
598
  依据: 用户指定了已有文档,且要求"优化措辞"
599
599
  命令调用: 1. python -m gongwen optimize-content 会议通知.docx --changes changes.json --apply --paragraphs "5-8"
@@ -629,6 +629,23 @@ Skill 定位为**工具层**,默认不依赖 LLM(确定性工作全自包含
629
629
 
630
630
  ---
631
631
 
632
+ ## 🌐 GitHub 不可达排查(安全 DNS / DoH)
633
+
634
+ 国内网络访问 GitHub 常遇「无法访问 / 超时」问题,常见原因之一是 **DNS 污染**——系统 DNS 返回的不是真实 IP,而是保留/Fake-IP 段(如 198.18.0.0/15、0.0.0.0),连接自然失败或超时。
635
+
636
+ **快速诊断**:运行 python -m gongwen doctor(含网络/DNS 检查项),或 python -m gongwen check-update(GitHub 渠道不可达时自动诊断)。检测到疑似污染时,会输出系统解析 vs 安全 DNS 真实 IP 对比,以及可直接粘贴的 hosts 条目建议。
637
+
638
+ **原理**:安全 DNS(DoH,DNS over HTTPS)通过加密 HTTP 查询 DNS,避免中间设备篡改解析结果,可拿到域名的真实 IP。本工具内置阿里(dns.alidns.com)、腾讯(doh.pub / 1.12.12.12)等国内公共 DoH 端点,多端点自动降级;可通过环境变量 GONGWEN_DOH 覆盖为自定义端点(如自建的 DoH 服务)。
639
+
640
+ **自动兜底(v2.8.0)**:`font install` 下载字体、`check-update` 查 PyPI 时若常规请求失败(疑似 DNS 污染),自动用 DoH 真实 IP + TLS SNI 直连重试——TLS 证书仍按真实域名校验,安全不降级,用户零操作。
641
+
642
+ **处置建议**(按推荐度):
643
+ 1. 若使用了代理工具(Clash/V2Ray 等)且系统解析命中 198.18.x Fake-IP,优先检查其 DNS 模式的 fake-ip-filter 是否漏掉 GitHub 域名(比改 hosts 更治本)
644
+ 2. 将诊断输出的 hosts 条目写入 C:\Windows\System32\drivers\etc\hosts(需管理员权限),git / 浏览器即可直连真实 IP
645
+ 3. 或使用国内镜像仓库克隆/更新(见下方「镜像仓库」)
646
+
647
+ > 诊断 + 自动兜底:本工具不写入 hosts、不修改系统配置;但 `font install` 下载字体、`check-update` 查询 PyPI 遇到 DNS 污染导致的失败时,会**自动用安全 DNS(DoH)真实 IP + TLS SNI 直连重试**(零操作,证书校验不降级)。DoH 查询经第三方公共 DNS 服务,仅在诊断/兜底失败时发起少量查询,隐私敏感者可设置 GONGWEN_DOH 指向自有端点。
648
+
632
649
  ## 📄 许可证与出处
633
650
 
634
651
  MIT License · **(c) 2026 Jose AI** · https://www.linhut.cn
package/SKILL.md CHANGED
@@ -103,6 +103,7 @@ metadata:
103
103
  ```
104
104
  - **PyPI 为权威判定渠道**(pip 包核心分发,pip install -U 即从 PyPI 拉取);PyPI 不可达时回退 GitHub tag(备用渠道,git 用户/CI 触发源)
105
105
  - GitCode/AtomGit 与 GitHub 同源 tag,仅在 GitHub 不可达时作国内拉取镜像提示(不参与版本判定)
106
+ - GitHub 不可达且触发 DNS 诊断时(系统解析为保留/Fake-IP 段),`check-update --json` 会输出 `dns_diagnosis` 字段(含 `hosts_suggestions`),如实向用户转述排查建议即可
106
107
  - 若全部渠道均不可用(无 git/无网络),**必须明确告知用户"版本自检因无法访问远程而跳过"**,不得静默假设本地即最新
107
108
  3.5. **本地 git tag 对比**(P6,Agent 执行版本确认时补充):
108
109
  - 优先对 skill 安装目录执行 git tag 对比:
@@ -925,15 +926,16 @@ python -m gongwen fix-common 文件.docx -o 成品.docx
925
926
  | `table-signs` | 从名单批量生成会议桌签 | `python -m gongwen table-signs 名单.txt -o 桌签.docx` |
926
927
  | `review` | 生成公文审稿流转单(五/三角色) | `python -m gongwen review report -o 审稿单.docx` |
927
928
  | `handoff` | 跨会话交接(长任务收尾必写) | `python -m gongwen handoff --write` |
928
- | `check-update` | 版本自检(PyPI pip 包权威 + GitHub 备用,自动检测安装形态) | `python -m gongwen check-update` |
929
- | `font` | 公文标准字体管理(安装/检查/列出) | `python -m gongwen font install` |
929
+ | `check-update` | 版本自检(PyPI pip 包权威 + GitHub 备用,自动检测安装形态;GitHub 不可达时 DNS 诊断) | `python -m gongwen check-update` |
930
+ | `font` | 公文标准字体管理(安装/检查/列出;下载失败时自动安全 DNS 直连兜底) | `python -m gongwen font install` |
930
931
 
931
932
  ### 🩺 诊断与修复
932
933
 
933
934
  | 命令 | 用途 | 最小用法 |
934
935
  |------|------|---------|
935
- | `doctor` | 全面诊断:检查 Python 版本/依赖/版本一致性/字体/DSH 文件/代码风格等 | `python -m gongwen doctor` |
936
+ | `doctor` | 全面诊断:检查 Python 版本/依赖/版本一致性/字体/DSH 文件/代码风格/网络 DNS 等 | `python -m gongwen doctor` |
936
937
  | `doctor --json` | JSON 结构化输出(便于 Agent 解析) | `python -m gongwen doctor --json` |
938
+ | `doctor --offline` | 跳过网络/DNS 诊断(离线模式) | `python -m gongwen doctor --offline` |
937
939
  | `repair` | 修复常见问题:安装缺失依赖/字体/同步 SKILL.md 副本 | `python -m gongwen repair` |
938
940
 
939
941
  ### ⚙️ 规则管理
package/dsh/index.js CHANGED
@@ -1,4 +1,4 @@
1
- // 公文全流程处理工具 - DSH plugin bridge (gongwen-skill, v2.7.0+)
1
+ // 公文全流程处理工具 - DSH plugin bridge (gongwen-skill, v2.8.0+)
2
2
  // (c) 2026 Jose AI (https://www.linhut.cn)
3
3
  // https://github.com/linhut/gongwen-skill
4
4
  // Licensed under the MIT License. See the LICENSE file for details.
@@ -7,7 +7,7 @@
7
7
  #
8
8
  # 公文全流程处理工具 - gongwen-skill Python package
9
9
 
10
- __version__ = "2.7.0"
10
+ __version__ = "2.8.0"
11
11
 
12
12
  # Re-export everything from the legacy module for backward compatibility
13
13
  # This allows: from gongwen import main, cmd_check, etc.
@@ -52,7 +52,7 @@ from engine.core.document.font_utils import (
52
52
  PAGE_NUMBER_FONT,
53
53
  PAGE_NUMBER_SIZE_PT,
54
54
  )
55
- __version__ = "2.7.0"
55
+ __version__ = "2.8.0"
56
56
  # 版本号应与 gongwen/__init__.py 保持一致,每次发版同步更新
57
57
  """
58
58
  中文公文全流程处理工具 —— 基于 GB/T 9704《党政机关公文格式》国家标准。
@@ -1120,9 +1120,11 @@ def main():
1120
1120
  p.set_defaults(func=cmd_font)
1121
1121
 
1122
1122
  # ---- 自我诊断与修复 ----
1123
- p = sub.add_parser("doctor", help="全面诊断:检查 Python 版本/依赖/版本一致性/字体/DSH 文件/代码风格等")
1123
+ p = sub.add_parser("doctor", help="全面诊断:检查 Python 版本/依赖/版本一致性/字体/DSH 文件/代码风格/网络 DNS 等")
1124
1124
  p.add_argument("--json", action="store_true",
1125
1125
  help="输出 JSON 格式结果(便于 Agent 解析)")
1126
+ p.add_argument("--offline", action="store_true",
1127
+ help="跳过网络/DNS 诊断(离线模式)")
1126
1128
  p.set_defaults(func=cmd_doctor)
1127
1129
 
1128
1130
  p = sub.add_parser("repair", help="修复常见问题:安装缺失依赖/字体/同步 SKILL.md 副本")
@@ -548,7 +548,57 @@ def _check_npm_package() -> dict:
548
548
  }
549
549
 
550
550
 
551
- def _run_all_checks() -> dict:
551
+ def _check_network_dns(offline: bool = False) -> dict:
552
+ """网络与 DNS 诊断:检测 GitHub/PyPI 域名是否疑似 DNS 污染。
553
+
554
+ 原理:系统 DNS 常被污染(返回 198.18.0.0/15 等保留段 Fake-IP),
555
+ 通过安全 DNS(DoH)查询真实 IP 对比,判定是否疑似污染,
556
+ 并给出 hosts 建议(v2.8.0 规格)。
557
+ --offline 时跳过网络查询(WARN)。
558
+ """
559
+ if offline:
560
+ return {
561
+ "name": "网络/DNS 诊断",
562
+ "ok": None,
563
+ "detail": "已跳过(--offline 离线模式)",
564
+ "hint": None,
565
+ }
566
+ try:
567
+ from gongwen.cli import netcheck
568
+ entries = netcheck.check_hosts()
569
+ s = netcheck.summarize(entries)
570
+ if s.get("polluted"):
571
+ return {
572
+ "name": "网络/DNS 诊断",
573
+ "ok": False,
574
+ "detail": s.get("detail", ""),
575
+ "hint": ("疑似 DNS 污染(系统解析为保留/Fake-IP 段)。"
576
+ "可参考安全 DNS(DoH)方案,或使用国内镜像;"
577
+ "hosts 建议: " + s.get("hosts_suggestions", "")),
578
+ }
579
+ if s.get("checked") and s.get("ok_count") == s.get("checked"):
580
+ return {
581
+ "name": "网络/DNS 诊断",
582
+ "ok": True,
583
+ "detail": "GitHub/PyPI 域名解析正常(与安全 DNS 一致)",
584
+ "hint": None,
585
+ }
586
+ return {
587
+ "name": "网络/DNS 诊断",
588
+ "ok": None,
589
+ "detail": s.get("detail", "") or "部分域名解析存在差异",
590
+ "hint": "部分域名解析与安全 DNS 不一致,建议进一步排查",
591
+ }
592
+ except Exception as e:
593
+ return {
594
+ "name": "网络/DNS 诊断",
595
+ "ok": None,
596
+ "detail": "诊断失败: " + str(e)[:100],
597
+ "hint": None,
598
+ }
599
+
600
+
601
+ def _run_all_checks(offline: bool = False) -> dict:
552
602
  """运行所有检查,返回结构化报告。"""
553
603
  t0 = time.time()
554
604
 
@@ -584,6 +634,7 @@ def _run_all_checks() -> dict:
584
634
  add(_check_git_status())
585
635
  add(_check_pycodestyle())
586
636
  add(_check_npm_package())
637
+ add(_check_network_dns(offline))
587
638
 
588
639
  report["elapsed_seconds"] = round(time.time() - t0, 1)
589
640
  return report
@@ -596,7 +647,8 @@ def cmd_doctor(args):
596
647
  但实际文件残缺。本命令逐一检查每个组件,不依赖"看起来正常"的状态。
597
648
  """
598
649
  use_json = getattr(args, "json", False)
599
- report = _run_all_checks()
650
+ offline = getattr(args, "offline", False)
651
+ report = _run_all_checks(offline=offline)
600
652
 
601
653
  if use_json:
602
654
  print(json.dumps(report, ensure_ascii=False, indent=2))
@@ -35,7 +35,11 @@ def _get_fonts_dir() -> Path:
35
35
 
36
36
 
37
37
  def _download_font(ttf_file: str, dest: Path) -> bool:
38
- """从 GitHub 下载字体文件到 dest,返回是否成功。"""
38
+ """从 GitHub 下载字体文件到 dest,返回是否成功。
39
+
40
+ 常规下载失败(如 DNS 污染)时自动用安全 DNS(DoH)真实 IP + TLS SNI
41
+ 直连兜底,用户零操作(证书校验不降级,仍针对真实域名)。
42
+ """
39
43
  import urllib.request
40
44
  import urllib.error
41
45
  from urllib.parse import quote
@@ -46,17 +50,24 @@ def _download_font(ttf_file: str, dest: Path) -> bool:
46
50
  req = urllib.request.Request(url, headers={"User-Agent": "gongwen-skill"})
47
51
  with urllib.request.urlopen(req, timeout=60) as resp:
48
52
  data = resp.read()
49
- # SEC-V168-03 修复:完整性校验——字体文件应 >1MB
50
- if len(data) < 1_000_000:
51
- print(f" ❌ 下载文件过小 ({len(data)} bytes),可能不完整")
52
- return False
53
- dest.parent.mkdir(parents=True, exist_ok=True)
54
- dest.write_bytes(data)
55
- print(f" 📦 已下载: {dest} ({len(data) / 1024:.0f}KB)")
56
- return True
57
53
  except Exception as e:
58
- print(f" ❌ 下载失败: {e}")
54
+ # DNS 污染兜底:常规下载失败时改用安全 DNS 真实 IP 直连
55
+ try:
56
+ print(f" ⚠️ 常规下载失败: {str(e)[:120]}")
57
+ print(" 🔄 尝试安全 DNS(DoH)真实 IP 直连下载 ...")
58
+ from gongwen.cli import netcheck as gwnet
59
+ data = gwnet.download_with_doh_fallback(url, timeout=60)
60
+ except Exception as e2:
61
+ print(f" ❌ 下载失败: {str(e2)[:120]}")
62
+ return False
63
+ # SEC-V168-03 修复:完整性校验——字体文件应 >1MB
64
+ if len(data) < 1_000_000:
65
+ print(f" ❌ 下载文件过小 ({len(data)} bytes),可能不完整")
59
66
  return False
67
+ dest.parent.mkdir(parents=True, exist_ok=True)
68
+ dest.write_bytes(data)
69
+ print(f" 📦 已下载: {dest} ({len(data) / 1024:.0f}KB)")
70
+ return True
60
71
 
61
72
 
62
73
  def _ensure_font_file(ttf_file: str, fonts_dir: Path) -> Path | None:
@@ -188,18 +188,31 @@ def verify_output_fresh(output_path: Path, start_time: float, label: str = "输
188
188
  return False
189
189
 
190
190
 
191
+ def _parse_pypi_version(data: dict) -> str:
192
+ """从 PyPI JSON 响应提取版本号(自动补 v 前缀)。"""
193
+ version = data.get("info", {}).get("version", "")
194
+ if version and not version.startswith("v"):
195
+ version = "v" + version
196
+ return version
197
+
198
+
191
199
  def latest_version_from_pypi(timeout: int = 10) -> tuple[bool, str]:
192
- """从 PyPI JSON API 查询最新发布版本。"""
200
+ """从 PyPI JSON API 查询最新发布版本。
201
+
202
+ DNS 污染兜底:常规请求失败时自动用安全 DNS(DoH)真实 IP + TLS SNI
203
+ 直连重试(PyPI 域名也可能被污染),用户零操作。
204
+ """
193
205
  import urllib.request
194
206
  try:
195
207
  req = urllib.request.Request(PYPI_API, headers={"Accept": "application/json"})
196
208
  with urllib.request.urlopen(req, timeout=timeout) as resp:
197
209
  data = json.loads(resp.read().decode("utf-8"))
198
- version = data.get("info", {}).get("version", "")
199
- if version:
200
- if not version.startswith("v"):
201
- version = f"v{version}"
202
- return True, version
203
- return False, "PyPI API 返回无版本号"
210
+ return True, _parse_pypi_version(data)
204
211
  except Exception as e:
205
- return False, str(e)[:120]
212
+ try:
213
+ from gongwen.cli import netcheck as gwnet
214
+ raw = gwnet.download_with_doh_fallback(PYPI_API, timeout=timeout)
215
+ data = json.loads(raw.decode("utf-8"))
216
+ return True, _parse_pypi_version(data)
217
+ except Exception as e2:
218
+ return False, str(e2 or e)[:120]
@@ -0,0 +1,362 @@
1
+ #!/usr/bin/env python3
2
+
3
+ # -*- coding: utf-8 -*-
4
+ #
5
+ # (c) 2026 Jose AI (https://www.linhut.cn)
6
+ # https://github.com/linhut/gongwen-skill
7
+ # Licensed under the MIT License. See the LICENSE file for details.
8
+ #
9
+ """
10
+ gongwen.cli.netcheck -- DNS 污染诊断 + 自动直连兜底(安全 DNS / DoH)。
11
+
12
+ 背景:系统 DNS 常被污染(返回 198.18.0.0/15 等保留段 Fake-IP),
13
+ 导致 GitHub 等域名无法访问/超时。本模块通过 DoH(DNS over HTTPS)
14
+ 查询真实 IP,与系统解析对比,判定是否疑似 DNS 污染,
15
+ 并输出可直接粘贴的 hosts 条目建议。
16
+
17
+ 诊断(v2.8.0 规格 docs/superpowers/specs/2026-09-03-dns-pollution-diagnosis-design.md):
18
+ - 不修改 hosts;诊断结果仅供参考
19
+ - 内置国内公共 DoH(阿里/腾讯),支持环境变量 GONGWEN_DOH 自定义
20
+ - 所有网络操作超时短、可完全离线(--offline 时调用方不调用本模块)
21
+
22
+ 自动直连兜底(download_with_doh_fallback):
23
+ - 常规下载失败(疑似 DNS 污染)时,自动用 DoH 真实 IP + TLS SNI 直连
24
+ - 证书校验保持针对真实域名(server_hostname),安全不降级,用户零操作
25
+ """
26
+ from __future__ import annotations
27
+
28
+ import http.client
29
+ import ipaddress
30
+ import json
31
+ import logging
32
+ import os
33
+ import socket
34
+ import urllib.parse
35
+ import urllib.request
36
+
37
+ _logger = logging.getLogger(__name__)
38
+
39
+ # 超时(秒):DoH 查询每个端点,短超时快速降级
40
+ _DOH_TIMEOUT = 5
41
+
42
+ # 默认 DoH 端点:优先国内公共 DoH(实测 2026-09-03 可用,Cloudflare 不可达)。
43
+ # 环境变量 GONGWEN_DOH 可覆盖/追加自定义端点(如用户自建 oaifree 服务)。
44
+ # 格式:application/dns-json GET 风格,追加 ?name=X&type=A 查询
45
+ _DOH_ENDPOINTS = [
46
+ "https://dns.alidns.com/resolve", # 阿里公共 DNS
47
+ "https://doh.pub/resolve", # 腾讯 DNSPod
48
+ "https://1.12.12.12/resolve", # 腾讯 DNSPod(IP 直连)
49
+ "https://dns.google/resolve", # Google(备用,实测可用)
50
+ ]
51
+
52
+
53
+ def _doh_endpoints() -> list:
54
+ """返回 DoH 端点列表:环境变量 GONGWEN_DOH 置顶,其余跟随默认列表。"""
55
+ custom = os.environ.get("GONGWEN_DOH", "").strip()
56
+ if not custom:
57
+ return list(_DOH_ENDPOINTS)
58
+ # 支持逗号分隔多个自定义端点(如 GONGWEN_DOH=a.com/x,b.com/y)
59
+ custom_list = [u.strip() for u in custom.split(",") if u.strip()]
60
+ return custom_list + [u for u in _DOH_ENDPOINTS if u not in custom_list]
61
+
62
+
63
+ def _is_ipv4(s: str) -> bool:
64
+ """判断字符串是否为合法 IPv4 地址。"""
65
+ try:
66
+ ipaddress.IPv4Address(s)
67
+ return True
68
+ except (ipaddress.AddressValueError, TypeError, ValueError):
69
+ return False
70
+
71
+
72
+ def resolve_via_doh(host: str) -> list:
73
+ """通过 DoH 查询域名的真实 IPv4 地址(多端点降级)。
74
+
75
+ Args:
76
+ host: 域名,如 "github.com"。
77
+
78
+ Returns:
79
+ IPv4 地址列表;全部端点失败时返回空列表。
80
+ """
81
+ for base in _doh_endpoints():
82
+ try:
83
+ query = urllib.parse.urlencode({"name": host, "type": "A"})
84
+ url = base + "?" + query
85
+ req = urllib.request.Request(url, headers={
86
+ "Accept": "application/dns-json",
87
+ "User-Agent": "gongwen-skill/2.8",
88
+ })
89
+ with urllib.request.urlopen(req, timeout=_DOH_TIMEOUT) as resp:
90
+ data = json.loads(resp.read().decode("utf-8", errors="replace"))
91
+ ips = [
92
+ a.get("data", "") for a in data.get("Answer", [])
93
+ if a.get("type") == 1 and _is_ipv4(a.get("data", ""))
94
+ ]
95
+ if ips:
96
+ return list(dict.fromkeys(ips)) # 去重保序
97
+ except Exception as e:
98
+ _logger.debug("DoH 端点 %s 查询 %s 失败: %s", base, host, e)
99
+ continue
100
+ return []
101
+
102
+
103
+ def system_resolve(host: str) -> list:
104
+ """系统 DNS 解析域名,返回 IPv4 地址列表。失败返回空列表。"""
105
+ try:
106
+ infos = socket.getaddrinfo(host, None, proto=socket.IPPROTO_TCP)
107
+ ips = [info[4][0] for info in infos if ":" not in info[4][0]]
108
+ return list(dict.fromkeys(ips))
109
+ except Exception as e:
110
+ _logger.debug("系统解析 %s 失败: %s", host, e)
111
+ return []
112
+
113
+
114
+ def _is_reserved_or_fake_ip(ip: str) -> bool:
115
+ """判断 IP 是否落在保留/异常网段(DNS 污染 / Fake-IP 典型特征)。
116
+
117
+ 覆盖:198.18.0.0/15(Fake-IP 基准段)、0.0.0.0/8、127.0.0.0/8、
118
+ 10.0.0.0/8、172.16.0.0/12、192.168.0.0/16。
119
+ """
120
+ try:
121
+ addr = ipaddress.IPv4Address(ip)
122
+ except (ipaddress.AddressValueError, ValueError):
123
+ return False
124
+ reserved = [
125
+ ipaddress.ip_network("198.18.0.0/15"),
126
+ ipaddress.ip_network("0.0.0.0/8"),
127
+ ipaddress.ip_network("127.0.0.0/8"),
128
+ ipaddress.ip_network("10.0.0.0/8"),
129
+ ipaddress.ip_network("172.16.0.0/12"),
130
+ ipaddress.ip_network("192.168.0.0/16"),
131
+ ]
132
+ return any(addr in net for net in reserved)
133
+
134
+
135
+ def _join_ips(ips) -> str:
136
+ """IP 列表转中文顿号分隔字符串。"""
137
+ return "、".join(ips) if ips else "(无)"
138
+
139
+
140
+ def detect_pollution(host: str) -> dict:
141
+ """对单个域名做 DNS 污染诊断。
142
+
143
+ Returns:
144
+ dict: {
145
+ "host": 域名,
146
+ "ok": True=正常 / False=疑似污染 / None=无法判定或差异提示,
147
+ "system_ips": 系统解析 IP 列表,
148
+ "doh_ips": DoH 真实 IP 列表,
149
+ "detail": 诊断说明,
150
+ }
151
+ """
152
+ sys_ips = system_resolve(host)
153
+ doh_ips = resolve_via_doh(host)
154
+ sys_str = _join_ips(sys_ips)
155
+ doh_str = _join_ips(doh_ips)
156
+
157
+ # DoH 全部端点不可达 → 无法判定(不误报)
158
+ if not doh_ips:
159
+ return {
160
+ "host": host, "ok": None,
161
+ "system_ips": sys_ips, "doh_ips": [],
162
+ "detail": "安全 DNS(DoH)查询失败,无法判定(可能离线或 DoH 服务不可达)",
163
+ }
164
+
165
+ # 系统解析失败 + DoH 成功 → 疑似污染
166
+ if not sys_ips:
167
+ return {
168
+ "host": host, "ok": False,
169
+ "system_ips": [], "doh_ips": doh_ips,
170
+ "detail": "系统 DNS 解析失败,安全 DNS 真实 IP 为 " + doh_str,
171
+ }
172
+
173
+ overlap = set(sys_ips) & set(doh_ips)
174
+ if overlap:
175
+ return {
176
+ "host": host, "ok": True,
177
+ "system_ips": sys_ips, "doh_ips": doh_ips,
178
+ "detail": "系统 DNS 与安全 DNS 解析一致",
179
+ }
180
+
181
+ # 无交集:若系统 IP 全部落在保留段 → 疑似污染;否则为多区域差异提示
182
+ if all(_is_reserved_or_fake_ip(ip) for ip in sys_ips):
183
+ return {
184
+ "host": host, "ok": False,
185
+ "system_ips": sys_ips, "doh_ips": doh_ips,
186
+ "detail": ("系统 DNS 解析为 " + sys_str + "(保留/Fake-IP 段),"
187
+ "安全 DNS 真实 IP 为 " + doh_str + " —— 疑似 DNS 污染"),
188
+ }
189
+ return {
190
+ "host": host, "ok": None,
191
+ "system_ips": sys_ips, "doh_ips": doh_ips,
192
+ "detail": ("系统 DNS 解析为 " + sys_str + ",安全 DNS 为 " + doh_str
193
+ + "(无保留段特征,可能为 CDN 多区域差异)"),
194
+ }
195
+
196
+
197
+ def check_hosts(hosts: list = None) -> list:
198
+ """批量诊断一组域名(默认 GitHub 关键域名 + PyPI)。
199
+
200
+ Args:
201
+ hosts: 域名列表;None 时使用默认检查清单。
202
+
203
+ Returns:
204
+ 每个域名的 detect_pollution 结果列表。
205
+ """
206
+ default_hosts = [
207
+ "github.com",
208
+ "api.github.com",
209
+ "raw.githubusercontent.com",
210
+ "codeload.github.com",
211
+ "objects.githubusercontent.com",
212
+ "pypi.org",
213
+ ]
214
+ targets = hosts if hosts else default_hosts
215
+ return [detect_pollution(h) for h in targets]
216
+
217
+
218
+ def format_hosts(entries: list) -> str:
219
+ """根据诊断结果生成可粘贴的 hosts 条目文本。
220
+
221
+ Args:
222
+ entries: check_hosts / detect_pollution 的结果列表。
223
+
224
+ Returns:
225
+ hosts 条目文本(每行 "IP 域名"),仅包含有真实 IP 的域名。
226
+ """
227
+ lines_out = []
228
+ for e in entries:
229
+ ips = e.get("doh_ips") or []
230
+ host = e.get("host") or ""
231
+ if ips:
232
+ lines_out.append(ips[0] + " " + host)
233
+ if not lines_out:
234
+ return "(无可用的 hosts 建议:安全 DNS 未取得真实 IP)"
235
+ return chr(10).join(lines_out)
236
+
237
+
238
+ def summarize(entries: list) -> dict:
239
+ """汇总批量诊断结果(供 check-update --json / doctor 使用)。
240
+
241
+ Returns:
242
+ dict: {
243
+ "polluted": 是否存在疑似污染域名,
244
+ "checked": 检查域名数,
245
+ "ok_count": 正常数,
246
+ "warn_count": 差异提示数,
247
+ "fail_count": 疑似污染数,
248
+ "detail": 汇总说明,
249
+ "hosts_suggestions": hosts 条目文本,
250
+ }
251
+ """
252
+ ok_count = sum(1 for e in entries if e.get("ok") is True)
253
+ fail_count = sum(1 for e in entries if e.get("ok") is False)
254
+ warn_count = sum(1 for e in entries if e.get("ok") is None)
255
+ polluted = fail_count > 0
256
+ detail_parts = []
257
+ for e in entries:
258
+ if e.get("ok") is False:
259
+ detail_parts.append(e.get("host", "") + ": " + str(e.get("detail", "")))
260
+ detail = ";".join(detail_parts) if detail_parts else "未检测到 DNS 污染"
261
+ return {
262
+ "polluted": polluted,
263
+ "checked": len(entries),
264
+ "ok_count": ok_count,
265
+ "warn_count": warn_count,
266
+ "fail_count": fail_count,
267
+ "detail": detail,
268
+ "hosts_suggestions": format_hosts(entries),
269
+ }
270
+
271
+
272
+ class _DoHHTTPSConnection(http.client.HTTPSConnection):
273
+ """用 DoH 真实 IP 直连的 HTTPS 连接(TLS SNI 保持真实域名)。
274
+
275
+ 证书校验仍针对真实域名(server_hostname=host),安全不降级:
276
+ 连接的目标 IP 来自 DoH 真实解析,但 TLS 握手/证书校验完全等价于
277
+ 常规 HTTPS 请求,只是绕过了被污染的系统 DNS。
278
+ """
279
+
280
+ def __init__(self, host, ip, timeout=None):
281
+ super().__init__(host, timeout=timeout)
282
+ self._doh_ip = ip
283
+
284
+ def connect(self):
285
+ """连接 DoH 解析出的真实 IP,TLS 握手仍使用真实域名(SNI)。"""
286
+ sock = socket.create_connection((self._doh_ip, self.port), self.timeout)
287
+ self.sock = self._context.wrap_socket(sock, server_hostname=self.host)
288
+
289
+
290
+ def _http_get_via_ip(host, ip, path, timeout, headers):
291
+ """通过指定 IP 发起 HTTPS GET(SNI/证书校验针对 host),返回 (status, data, location)。"""
292
+ conn = _DoHHTTPSConnection(host, ip, timeout=timeout)
293
+ try:
294
+ conn.request("GET", path, headers=headers)
295
+ resp = conn.getresponse()
296
+ return resp.status, resp.read(), resp.getheader("Location")
297
+ finally:
298
+ conn.close()
299
+
300
+
301
+ def download_with_doh_fallback(url, timeout=30, headers=None, max_redirects=5):
302
+ """下载 URL 内容;常规下载失败时自动用 DoH 真实 IP + TLS SNI 直连兜底。
303
+
304
+ Args:
305
+ url: http(s) 下载地址。
306
+ timeout: 单次请求超时(秒),默认 30。
307
+ headers: 附加请求头(dict);缺省用 gongwen-skill UA。
308
+ max_redirects: 最大跟随重定向次数(兜底路径手动处理 3xx)。
309
+
310
+ Returns:
311
+ bytes: 响应体。
312
+
313
+ Raises:
314
+ Exception: 常规与 DoH 兜底均失败时抛出(由调用方决定提示)。
315
+
316
+ Note:
317
+ - 常规 urllib 请求自动跟随重定向;兜底路径手动跟随并重新解析新域名。
318
+ - 仅 https 参与 DoH 兜底(http 无 TLS,无 SNI 语义)。
319
+ - 证书校验不降级:wrap_socket 时 server_hostname=host,等价常规 HTTPS。
320
+ """
321
+ if headers is None:
322
+ headers = {"User-Agent": "gongwen-skill"}
323
+ current = url
324
+ for _ in range(max_redirects + 1):
325
+ parsed = urllib.parse.urlsplit(current)
326
+ host = parsed.hostname or ""
327
+ is_https = parsed.scheme == "https"
328
+ try:
329
+ req = urllib.request.Request(current, headers=headers)
330
+ with urllib.request.urlopen(req, timeout=timeout) as resp:
331
+ return resp.read()
332
+ except Exception as normal_err:
333
+ if not is_https:
334
+ raise
335
+ ips = resolve_via_doh(host)
336
+ last_err = normal_err
337
+ redirected = False
338
+ for ip in ips:
339
+ try:
340
+ status, data, location = _http_get_via_ip(
341
+ host, ip, parsed.path or "/", timeout, headers,
342
+ )
343
+ except Exception as ip_err:
344
+ last_err = ip_err
345
+ continue
346
+ if status in (301, 302, 303, 307, 308) and location:
347
+ current = urllib.parse.urljoin(current, location)
348
+ redirected = True
349
+ break
350
+ if 200 <= status < 300:
351
+ return data
352
+ if status >= 400:
353
+ # 4xx/5xx 一般为真实错误,但不排除中间设备干扰(如 400),
354
+ # 已尝试下一 IP;全部失败后抛出最后错误。
355
+ last_err = OSError("HTTP " + str(status) + " via DoH IP " + ip)
356
+ continue
357
+ last_err = OSError("HTTP " + str(status) + " via DoH IP " + ip)
358
+ continue
359
+ if redirected:
360
+ continue
361
+ raise last_err
362
+ raise OSError("下载重定向次数超限: " + url)
@@ -160,12 +160,32 @@ def cmd_check_update(args):
160
160
  if not use_json:
161
161
  print(f" ⚠️ {name:<8} 不可达: {val}")
162
162
 
163
- # GitHub 海外渠道不可达时,给出国内加速指引(EasyTier 式加速代理 + GitHub520 hosts)
164
- if not use_json and results.get("GitHub", "") == "" and ok_count > 0:
165
- print(" 💡 国内访问 GitHub 常超时,可用国内代码镜像:")
166
- for _name, _url in _GIT_MIRRORS.items():
167
- print(f" - {_name}: {_url}")
168
- print(" 或参考 GitHub520 hosts 加速方案:https://github.com/521xueweihan/GitHub520")
163
+ # GitHub 海外渠道不可达时,给出国内加速指引 + DNS 污染诊断(安全 DNS / DoH)
164
+ dns_diag = None
165
+ github_unreachable = results.get("GitHub", "") == "" and ok_count > 0
166
+ if github_unreachable:
167
+ from gongwen.cli import netcheck as _netcheck
168
+ _dns_entries = _netcheck.check_hosts(
169
+ ["github.com", "api.github.com", "raw.githubusercontent.com"]
170
+ )
171
+ dns_diag = _netcheck.summarize(_dns_entries)
172
+ if not use_json:
173
+ print(" 💡 国内访问 GitHub 常超时,可用国内代码镜像:")
174
+ for _name, _url in _GIT_MIRRORS.items():
175
+ print(f" - {_name}: {_url}")
176
+ print(" 或参考 GitHub520 hosts 加速方案:https://github.com/521xueweihan/GitHub520")
177
+ if dns_diag.get("polluted"):
178
+ print(" 🔬 DNS 诊断:检测到疑似 DNS 污染(系统解析为保留/Fake-IP 段)")
179
+ for _e in _dns_entries:
180
+ _mark = ("疑似污染" if _e.get("ok") is False
181
+ else "正常" if _e.get("ok") is True else "差异提示")
182
+ _sys = "、".join(_e.get("system_ips") or []) or "解析失败"
183
+ _doh = "、".join(_e.get("doh_ips") or []) or "(无)"
184
+ _host = _e.get("host", "")
185
+ print(f" - {_host}: 系统 {_sys} → 安全DNS {_doh}({_mark})")
186
+ print(" 📝 hosts 建议(管理员权限写入 hosts 文件):")
187
+ for _line in dns_diag.get("hosts_suggestions", "").splitlines():
188
+ print(f" {_line}")
169
189
 
170
190
  if not use_json:
171
191
  print(f"{'─' * 50}")
@@ -217,6 +237,7 @@ def cmd_check_update(args):
217
237
  "install_type": _detect_install_type(),
218
238
  "reachable_channels": ok_count,
219
239
  "elapsed_seconds": round(time.time() - t0, 1),
240
+ "dns_diagnosis": dns_diag,
220
241
  }
221
242
  print(_json.dumps(output, ensure_ascii=False, indent=2))
222
243
  return 0 if not has_update else 1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gongwen-skill",
3
- "version": "2.7.0",
3
+ "version": "2.8.0",
4
4
  "description": "公文全流程处理工具 - GB/T 9704 格式检查/修复/内容优化/模板生成/版式注入",
5
5
  "type": "module",
6
6
  "main": "dsh/index.js",
@@ -201,7 +201,7 @@ python -m gongwen optimize 文件.docx -o 优化版.docx
201
201
  **Agent 响应**:
202
202
  ```
203
203
  📋 合规自检报告
204
- Skill 版本: v2.7.0
204
+ Skill 版本: v2.8.0
205
205
  路径判定: B(内容优化)
206
206
  依据: 用户提供纯文本+优化要求,无已有文档
207
207
  命令调用: 将原文保存为临时文件后执行 optimize-content --apply --paragraphs "1-3"
package/pyproject.toml CHANGED
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "gongwen-skill"
7
- version = "2.7.0"
7
+ version = "2.8.0"
8
8
  description = "公文全流程处理工具 - GB/T 9704 格式检查/修复/内容优化/模板生成/版式注入"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}