gongwen-skill 2.6.1 → 2.7.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.
- package/.dsh/skills/gongwen-skill/SKILL.md +39 -8
- package/.dsh/skills/gongwen-skill.md +39 -8
- package/CHANGELOG.md +9 -0
- package/README.md +22 -3
- package/SKILL.md +39 -8
- package/dsh/index.js +1 -1
- package/engine/core/document/_generator_helpers.py +3 -2
- package/engine/core/document/markdown_converter.py +4 -2
- package/engine/core/document/ooxml_workflow.py +2 -2
- package/engine/core/rules/checker.py +1 -1
- package/engine/inject.py +10 -6
- package/gongwen/__init__.py +1 -1
- package/gongwen/_legacy.py +19 -3
- package/gongwen/cli/wizard_cmds.py +420 -0
- package/gongwen/md2docx_render.py +3 -2
- package/package.json +1 -1
- package/prompts/usage-prompts.md +1 -1
- package/pyproject.toml +1 -1
|
@@ -258,21 +258,23 @@ python -m gongwen handoff --latest --summary # 最新交接文档(Markdown 摘
|
|
|
258
258
|
|
|
259
259
|
## 用户交互指引(Agent 必须遵守)
|
|
260
260
|
|
|
261
|
+
> **终端用户也可直接运行 `python -m gongwen wizard`**:交互式向导以菜单方式完成同样的 A/B/C/D 路径选择与逐步确认(详见下文「向导式交互」小节)。Agent 读到本条时,若用户表示「不熟悉命令/不想记参数」,应主动引导其使用向导,而非逐字念命令行参数。
|
|
262
|
+
|
|
261
263
|
### 第一步:确认路径(必须,严禁跳过)
|
|
262
264
|
|
|
263
|
-
|
|
265
|
+
用通俗语言问用户走哪条路,**不允许跳过**。向导模式与对话模式共用同一套路径:
|
|
264
266
|
|
|
265
267
|
> 这个工具支持四种处理方式:
|
|
266
|
-
> -
|
|
267
|
-
> -
|
|
268
|
-
> -
|
|
269
|
-
> -
|
|
268
|
+
> - **A. 格式优化**:不改文字,只修排版(字体/字号/页边距/行距/缩进),按国标标准化
|
|
269
|
+
> - **B. 内容优化**:润色文字表达,生成带标记的对比文档——保持原文档排版样式,修改处红色高亮或删除线、每段附修改说明
|
|
270
|
+
> - **C. 生成模板**:直接生成一份空白公文模板,按国标设好版式
|
|
271
|
+
> - **D. 一键格式修复**(fix-common):快速规范化常见格式问题(段落类型/编号拆分/首句加粗),输出不含 AI 声明段的干净文档
|
|
270
272
|
|
|
271
273
|
若用户说「帮我优化一下」,必须追问是改格式还是改内容。
|
|
272
274
|
|
|
273
275
|
### 第二步:告知输出物
|
|
274
276
|
|
|
275
|
-
|
|
277
|
+
执行前明确说会生成什么文件、包含什么标记。向导模式会在执行前展示将运行的完整命令。
|
|
276
278
|
|
|
277
279
|
### 第三步:执行后验证
|
|
278
280
|
|
|
@@ -280,6 +282,29 @@ python -m gongwen handoff --latest --summary # 最新交接文档(Markdown 摘
|
|
|
280
282
|
|
|
281
283
|
---
|
|
282
284
|
|
|
285
|
+
### 向导式交互(`wizard` 命令)
|
|
286
|
+
|
|
287
|
+
当用户**不熟悉命令行**、希望**逐步确认后再执行**,或 Agent 需要**把交互决策委托给工具**时,使用向导:
|
|
288
|
+
|
|
289
|
+
```
|
|
290
|
+
python -m gongwen wizard # 终端交互:菜单选 A/B/C/D → 逐项填参 → 预览确认 → 执行
|
|
291
|
+
python -m gongwen wizard --answers 答案.json # Agent 非交互:跳过提问直接执行
|
|
292
|
+
python -m gongwen wizard --answers 答案.json --dry-run # 只打印将执行的命令,不真正执行
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
- **路径菜单**:A 格式优化(`optimize`)|B 内容优化(`optimize-content`)|C 生成模板(`template`)|D 一键格式修复(`fix-common`)
|
|
296
|
+
- **交互流程**:选择路径 → 收集参数(公文类型支持 序号 / id / 中文名,如 `1`、`notice`、`通知`)→ A/B/D 先预览再 y/n 确认 → 执行;C 无修改风险直接执行
|
|
297
|
+
- **`--answers` JSON 扁平结构**(Agent 场景,顶层带 `path`):
|
|
298
|
+
```json
|
|
299
|
+
{"path": "A", "input": "原文.docx", "doc_type": "notice", "output": "成品.docx", "apply": true}
|
|
300
|
+
```
|
|
301
|
+
- 字段缺失:非交互模式直接报错并列出缺失项(不静默、不进交互菜单)
|
|
302
|
+
- 不写 `apply` 时 A/B/D **仅预览不执行**(安全默认);`apply: true` 跳过确认直接执行
|
|
303
|
+
- 非交互执行机制:`subprocess` 调用 `sys.executable -m gongwen <子命令>`,输出流式透传,Agent 可直接解析子命令原始输出
|
|
304
|
+
- **`--dry-run`**:只打印将执行的命令;A/B 会同时打印「预览」与「执行」两条命令,便于 Agent 预演与调试
|
|
305
|
+
|
|
306
|
+
---
|
|
307
|
+
|
|
283
308
|
## 执行标准总则(所有路径共享)
|
|
284
309
|
|
|
285
310
|
### Agent 行为准则
|
|
@@ -295,7 +320,7 @@ python -m gongwen handoff --latest --summary # 最新交接文档(Markdown 摘
|
|
|
295
320
|
| "按上级文件要求调整" | 用户有政策依据但未提供 | 追问具体文件名或文号,或用 web 搜索相关政策 |
|
|
296
321
|
| "弄漂亮一点" | 希望格式规范 | 解释 GB/T 9704 标准是唯一合规格式 |
|
|
297
322
|
|
|
298
|
-
**原则**:不确定时 **先确认路径**(A/B/C
|
|
323
|
+
**原则**:不确定时 **先确认路径**(A/B/C/D,向导模式同),再用 **具体示例引导用户选择**,不猜测。
|
|
299
324
|
|
|
300
325
|
#### 2. 充分利用 skill 知识库
|
|
301
326
|
|
|
@@ -846,10 +871,16 @@ python -m gongwen fix-common 文件.docx -o 成品.docx
|
|
|
846
871
|
|
|
847
872
|
---
|
|
848
873
|
|
|
849
|
-
## 附录:全部命令速查(
|
|
874
|
+
## 附录:全部命令速查(25 个,按用途分组)
|
|
850
875
|
|
|
851
876
|
> Agent 遇到用户需求时,先在此表定位对应命令;命令用法不明确时运行 `python -m gongwen <命令> --help` 查看完整参数。
|
|
852
877
|
|
|
878
|
+
### 🧭 向导式交互
|
|
879
|
+
|
|
880
|
+
| 命令 | 用途 | 最小用法 |
|
|
881
|
+
|------|------|---------|
|
|
882
|
+
| `wizard` | 交互式路径引导(A/B/C/D)+ 一键执行;Agent 用 `--answers` 非交互 / `--dry-run` 只打印命令 | `python -m gongwen wizard` |
|
|
883
|
+
|
|
853
884
|
### 🏗️ 生成与模板
|
|
854
885
|
|
|
855
886
|
| 命令 | 用途 | 最小用法 |
|
|
@@ -258,21 +258,23 @@ python -m gongwen handoff --latest --summary # 最新交接文档(Markdown 摘
|
|
|
258
258
|
|
|
259
259
|
## 用户交互指引(Agent 必须遵守)
|
|
260
260
|
|
|
261
|
+
> **终端用户也可直接运行 `python -m gongwen wizard`**:交互式向导以菜单方式完成同样的 A/B/C/D 路径选择与逐步确认(详见下文「向导式交互」小节)。Agent 读到本条时,若用户表示「不熟悉命令/不想记参数」,应主动引导其使用向导,而非逐字念命令行参数。
|
|
262
|
+
|
|
261
263
|
### 第一步:确认路径(必须,严禁跳过)
|
|
262
264
|
|
|
263
|
-
|
|
265
|
+
用通俗语言问用户走哪条路,**不允许跳过**。向导模式与对话模式共用同一套路径:
|
|
264
266
|
|
|
265
267
|
> 这个工具支持四种处理方式:
|
|
266
|
-
> -
|
|
267
|
-
> -
|
|
268
|
-
> -
|
|
269
|
-
> -
|
|
268
|
+
> - **A. 格式优化**:不改文字,只修排版(字体/字号/页边距/行距/缩进),按国标标准化
|
|
269
|
+
> - **B. 内容优化**:润色文字表达,生成带标记的对比文档——保持原文档排版样式,修改处红色高亮或删除线、每段附修改说明
|
|
270
|
+
> - **C. 生成模板**:直接生成一份空白公文模板,按国标设好版式
|
|
271
|
+
> - **D. 一键格式修复**(fix-common):快速规范化常见格式问题(段落类型/编号拆分/首句加粗),输出不含 AI 声明段的干净文档
|
|
270
272
|
|
|
271
273
|
若用户说「帮我优化一下」,必须追问是改格式还是改内容。
|
|
272
274
|
|
|
273
275
|
### 第二步:告知输出物
|
|
274
276
|
|
|
275
|
-
|
|
277
|
+
执行前明确说会生成什么文件、包含什么标记。向导模式会在执行前展示将运行的完整命令。
|
|
276
278
|
|
|
277
279
|
### 第三步:执行后验证
|
|
278
280
|
|
|
@@ -280,6 +282,29 @@ python -m gongwen handoff --latest --summary # 最新交接文档(Markdown 摘
|
|
|
280
282
|
|
|
281
283
|
---
|
|
282
284
|
|
|
285
|
+
### 向导式交互(`wizard` 命令)
|
|
286
|
+
|
|
287
|
+
当用户**不熟悉命令行**、希望**逐步确认后再执行**,或 Agent 需要**把交互决策委托给工具**时,使用向导:
|
|
288
|
+
|
|
289
|
+
```
|
|
290
|
+
python -m gongwen wizard # 终端交互:菜单选 A/B/C/D → 逐项填参 → 预览确认 → 执行
|
|
291
|
+
python -m gongwen wizard --answers 答案.json # Agent 非交互:跳过提问直接执行
|
|
292
|
+
python -m gongwen wizard --answers 答案.json --dry-run # 只打印将执行的命令,不真正执行
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
- **路径菜单**:A 格式优化(`optimize`)|B 内容优化(`optimize-content`)|C 生成模板(`template`)|D 一键格式修复(`fix-common`)
|
|
296
|
+
- **交互流程**:选择路径 → 收集参数(公文类型支持 序号 / id / 中文名,如 `1`、`notice`、`通知`)→ A/B/D 先预览再 y/n 确认 → 执行;C 无修改风险直接执行
|
|
297
|
+
- **`--answers` JSON 扁平结构**(Agent 场景,顶层带 `path`):
|
|
298
|
+
```json
|
|
299
|
+
{"path": "A", "input": "原文.docx", "doc_type": "notice", "output": "成品.docx", "apply": true}
|
|
300
|
+
```
|
|
301
|
+
- 字段缺失:非交互模式直接报错并列出缺失项(不静默、不进交互菜单)
|
|
302
|
+
- 不写 `apply` 时 A/B/D **仅预览不执行**(安全默认);`apply: true` 跳过确认直接执行
|
|
303
|
+
- 非交互执行机制:`subprocess` 调用 `sys.executable -m gongwen <子命令>`,输出流式透传,Agent 可直接解析子命令原始输出
|
|
304
|
+
- **`--dry-run`**:只打印将执行的命令;A/B 会同时打印「预览」与「执行」两条命令,便于 Agent 预演与调试
|
|
305
|
+
|
|
306
|
+
---
|
|
307
|
+
|
|
283
308
|
## 执行标准总则(所有路径共享)
|
|
284
309
|
|
|
285
310
|
### Agent 行为准则
|
|
@@ -295,7 +320,7 @@ python -m gongwen handoff --latest --summary # 最新交接文档(Markdown 摘
|
|
|
295
320
|
| "按上级文件要求调整" | 用户有政策依据但未提供 | 追问具体文件名或文号,或用 web 搜索相关政策 |
|
|
296
321
|
| "弄漂亮一点" | 希望格式规范 | 解释 GB/T 9704 标准是唯一合规格式 |
|
|
297
322
|
|
|
298
|
-
**原则**:不确定时 **先确认路径**(A/B/C
|
|
323
|
+
**原则**:不确定时 **先确认路径**(A/B/C/D,向导模式同),再用 **具体示例引导用户选择**,不猜测。
|
|
299
324
|
|
|
300
325
|
#### 2. 充分利用 skill 知识库
|
|
301
326
|
|
|
@@ -846,10 +871,16 @@ python -m gongwen fix-common 文件.docx -o 成品.docx
|
|
|
846
871
|
|
|
847
872
|
---
|
|
848
873
|
|
|
849
|
-
## 附录:全部命令速查(
|
|
874
|
+
## 附录:全部命令速查(25 个,按用途分组)
|
|
850
875
|
|
|
851
876
|
> Agent 遇到用户需求时,先在此表定位对应命令;命令用法不明确时运行 `python -m gongwen <命令> --help` 查看完整参数。
|
|
852
877
|
|
|
878
|
+
### 🧭 向导式交互
|
|
879
|
+
|
|
880
|
+
| 命令 | 用途 | 最小用法 |
|
|
881
|
+
|------|------|---------|
|
|
882
|
+
| `wizard` | 交互式路径引导(A/B/C/D)+ 一键执行;Agent 用 `--answers` 非交互 / `--dry-run` 只打印命令 | `python -m gongwen wizard` |
|
|
883
|
+
|
|
853
884
|
### 🏗️ 生成与模板
|
|
854
885
|
|
|
855
886
|
| 命令 | 用途 | 最小用法 |
|
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,15 @@
|
|
|
4
4
|
Licensed under the MIT License. See the LICENSE file for details.
|
|
5
5
|
-->
|
|
6
6
|
|
|
7
|
+
## v2.7.0 (2026-09-03)
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- **新增 `wizard` 向导式交互命令**:`python -m gongwen wizard` 以 A/B/C/D 路径菜单引导用户(A 格式优化 `optimize` / B 内容优化 `optimize-content` / C 生成模板 `template` / D 一键格式修复 `fix-common`),交互收集参数后一键执行
|
|
11
|
+
- **双模式交互**:终端 input() 交互(公文类型支持序号 / id / 中文名智能匹配)+ `--answers` 扁平 JSON 非交互(Agent 场景,顶层带 `path`,如 `{"path":"A","input":"a.docx","apply":true}`)
|
|
12
|
+
- **安全默认**:A/B/D 修改类路径先预览再 y/n 确认;非交互模式不写 `apply` 时仅预览不执行;`--dry-run` 只打印将执行的命令(A/B 同时打印预览+执行两条)
|
|
13
|
+
- **执行机制**:`subprocess` 调用 `sys.executable -m gongwen <子命令>`,输出流式透传,Agent 可直接解析子命令原始输出
|
|
14
|
+
- **文档同步**:SKILL.md「用户交互指引」升级为向导式三步流程并新增「向导式交互」小节(命令速查 24→25 个);README 能力表与命令行速查补充 wizard 用法
|
|
15
|
+
|
|
7
16
|
## v2.6.1 (2026-09-02)
|
|
8
17
|
|
|
9
18
|
### Changed
|
package/README.md
CHANGED
|
@@ -50,6 +50,7 @@ Licensed under the MIT License. See the LICENSE file for details.
|
|
|
50
50
|
| 🕵️ 文档审计 | `audit` | 检查删除线/加粗/AI 声明等痕迹 |
|
|
51
51
|
| 🤝 会话交接 | `handoff` | 跨会话上下文传递(`--list` / `--latest` / Agent 长任务收尾必写) |
|
|
52
52
|
| ⚙️ 规则管理 | `rule-export/import/list` | YAML 规则三层定制(官方/单位/用户) |
|
|
53
|
+
| 🧭 向导式交互 | `wizard` | 交互式路径引导(A/B/C/D)+ 一键执行;Agent 用 `--answers` 非交互 / `--dry-run` 只打印命令 |
|
|
53
54
|
|
|
54
55
|
## 使用示例
|
|
55
56
|
|
|
@@ -259,6 +260,24 @@ python -m gongwen optimize-content 新闻稿.docx --changes changes.json \
|
|
|
259
260
|
--show-confirmed 已确认实体也生成批注
|
|
260
261
|
```
|
|
261
262
|
|
|
263
|
+
### 🧭 向导式交互(wizard)
|
|
264
|
+
|
|
265
|
+
交互式引导选择处理路径并一键执行,适合不熟悉命令行的用户;Agent 可走非交互模式:
|
|
266
|
+
|
|
267
|
+
```bash
|
|
268
|
+
python -m gongwen wizard # 终端交互:菜单选 A/B/C/D → 逐项填参 → 预览确认 → 执行
|
|
269
|
+
python -m gongwen wizard --answers 答案.json # Agent 非交互:跳过提问直接执行
|
|
270
|
+
python -m gongwen wizard --answers 答案.json --dry-run # 只打印将执行的命令
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
`--answers` 扁平 JSON(顶层带 `path`):
|
|
274
|
+
|
|
275
|
+
```json
|
|
276
|
+
{"path": "A", "input": "原文.docx", "doc_type": "notice", "output": "成品.docx", "apply": true}
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
路径:A 格式优化(`optimize`)|B 内容优化(`optimize-content`)|C 生成模板(`template`)|D 一键格式修复(`fix-common`)。A/B/D 默认先预览再 y/n 确认;不写 `apply` 时非交互模式仅预览不执行(安全默认)。
|
|
280
|
+
|
|
262
281
|
## 📐 GB/T 9704 标准格式
|
|
263
282
|
|
|
264
283
|
| 元素 | 字体 | 字号 | 对齐 |
|
|
@@ -337,7 +356,7 @@ DSH 采用 **Cordis 模块化微内核架构**:技能体系基于本地文件
|
|
|
337
356
|
git clone https://github.com/linhut/gongwen-skill.git
|
|
338
357
|
cd gongwen-skill
|
|
339
358
|
pip install -r requirements.txt # 或 pip install gongwen-skill(已上 PyPI)
|
|
340
|
-
python -m gongwen --version # 检验:gongwen-skill v2.
|
|
359
|
+
python -m gongwen --version # 检验:gongwen-skill v2.7.0
|
|
341
360
|
```
|
|
342
361
|
|
|
343
362
|
### 方式一:作为 DSH Skill 注册(基于本地文件系统)
|
|
@@ -393,7 +412,7 @@ pnpm add -w gongwen-skill
|
|
|
393
412
|
"dependencies": {
|
|
394
413
|
"@deepseek-ai/dsh-base": "...",
|
|
395
414
|
"@deepseek-ai/dsh-web-app": "...",
|
|
396
|
-
"gongwen-skill": "^2.
|
|
415
|
+
"gongwen-skill": "^2.7.0"
|
|
397
416
|
},
|
|
398
417
|
"dsh": {
|
|
399
418
|
"profile": {
|
|
@@ -574,7 +593,7 @@ pip install -r requirements.txt
|
|
|
574
593
|
用户:帮我优化这份会议通知的第二章节措辞
|
|
575
594
|
|
|
576
595
|
Agent:📋 合规自检报告
|
|
577
|
-
Skill 版本: v2.
|
|
596
|
+
Skill 版本: v2.7.0(版本自检已确认最新)
|
|
578
597
|
路径判定: B(内容优化)
|
|
579
598
|
依据: 用户指定了已有文档,且要求"优化措辞"
|
|
580
599
|
命令调用: 1. python -m gongwen optimize-content 会议通知.docx --changes changes.json --apply --paragraphs "5-8"
|
package/SKILL.md
CHANGED
|
@@ -258,21 +258,23 @@ python -m gongwen handoff --latest --summary # 最新交接文档(Markdown 摘
|
|
|
258
258
|
|
|
259
259
|
## 用户交互指引(Agent 必须遵守)
|
|
260
260
|
|
|
261
|
+
> **终端用户也可直接运行 `python -m gongwen wizard`**:交互式向导以菜单方式完成同样的 A/B/C/D 路径选择与逐步确认(详见下文「向导式交互」小节)。Agent 读到本条时,若用户表示「不熟悉命令/不想记参数」,应主动引导其使用向导,而非逐字念命令行参数。
|
|
262
|
+
|
|
261
263
|
### 第一步:确认路径(必须,严禁跳过)
|
|
262
264
|
|
|
263
|
-
|
|
265
|
+
用通俗语言问用户走哪条路,**不允许跳过**。向导模式与对话模式共用同一套路径:
|
|
264
266
|
|
|
265
267
|
> 这个工具支持四种处理方式:
|
|
266
|
-
> -
|
|
267
|
-
> -
|
|
268
|
-
> -
|
|
269
|
-
> -
|
|
268
|
+
> - **A. 格式优化**:不改文字,只修排版(字体/字号/页边距/行距/缩进),按国标标准化
|
|
269
|
+
> - **B. 内容优化**:润色文字表达,生成带标记的对比文档——保持原文档排版样式,修改处红色高亮或删除线、每段附修改说明
|
|
270
|
+
> - **C. 生成模板**:直接生成一份空白公文模板,按国标设好版式
|
|
271
|
+
> - **D. 一键格式修复**(fix-common):快速规范化常见格式问题(段落类型/编号拆分/首句加粗),输出不含 AI 声明段的干净文档
|
|
270
272
|
|
|
271
273
|
若用户说「帮我优化一下」,必须追问是改格式还是改内容。
|
|
272
274
|
|
|
273
275
|
### 第二步:告知输出物
|
|
274
276
|
|
|
275
|
-
|
|
277
|
+
执行前明确说会生成什么文件、包含什么标记。向导模式会在执行前展示将运行的完整命令。
|
|
276
278
|
|
|
277
279
|
### 第三步:执行后验证
|
|
278
280
|
|
|
@@ -280,6 +282,29 @@ python -m gongwen handoff --latest --summary # 最新交接文档(Markdown 摘
|
|
|
280
282
|
|
|
281
283
|
---
|
|
282
284
|
|
|
285
|
+
### 向导式交互(`wizard` 命令)
|
|
286
|
+
|
|
287
|
+
当用户**不熟悉命令行**、希望**逐步确认后再执行**,或 Agent 需要**把交互决策委托给工具**时,使用向导:
|
|
288
|
+
|
|
289
|
+
```
|
|
290
|
+
python -m gongwen wizard # 终端交互:菜单选 A/B/C/D → 逐项填参 → 预览确认 → 执行
|
|
291
|
+
python -m gongwen wizard --answers 答案.json # Agent 非交互:跳过提问直接执行
|
|
292
|
+
python -m gongwen wizard --answers 答案.json --dry-run # 只打印将执行的命令,不真正执行
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
- **路径菜单**:A 格式优化(`optimize`)|B 内容优化(`optimize-content`)|C 生成模板(`template`)|D 一键格式修复(`fix-common`)
|
|
296
|
+
- **交互流程**:选择路径 → 收集参数(公文类型支持 序号 / id / 中文名,如 `1`、`notice`、`通知`)→ A/B/D 先预览再 y/n 确认 → 执行;C 无修改风险直接执行
|
|
297
|
+
- **`--answers` JSON 扁平结构**(Agent 场景,顶层带 `path`):
|
|
298
|
+
```json
|
|
299
|
+
{"path": "A", "input": "原文.docx", "doc_type": "notice", "output": "成品.docx", "apply": true}
|
|
300
|
+
```
|
|
301
|
+
- 字段缺失:非交互模式直接报错并列出缺失项(不静默、不进交互菜单)
|
|
302
|
+
- 不写 `apply` 时 A/B/D **仅预览不执行**(安全默认);`apply: true` 跳过确认直接执行
|
|
303
|
+
- 非交互执行机制:`subprocess` 调用 `sys.executable -m gongwen <子命令>`,输出流式透传,Agent 可直接解析子命令原始输出
|
|
304
|
+
- **`--dry-run`**:只打印将执行的命令;A/B 会同时打印「预览」与「执行」两条命令,便于 Agent 预演与调试
|
|
305
|
+
|
|
306
|
+
---
|
|
307
|
+
|
|
283
308
|
## 执行标准总则(所有路径共享)
|
|
284
309
|
|
|
285
310
|
### Agent 行为准则
|
|
@@ -295,7 +320,7 @@ python -m gongwen handoff --latest --summary # 最新交接文档(Markdown 摘
|
|
|
295
320
|
| "按上级文件要求调整" | 用户有政策依据但未提供 | 追问具体文件名或文号,或用 web 搜索相关政策 |
|
|
296
321
|
| "弄漂亮一点" | 希望格式规范 | 解释 GB/T 9704 标准是唯一合规格式 |
|
|
297
322
|
|
|
298
|
-
**原则**:不确定时 **先确认路径**(A/B/C
|
|
323
|
+
**原则**:不确定时 **先确认路径**(A/B/C/D,向导模式同),再用 **具体示例引导用户选择**,不猜测。
|
|
299
324
|
|
|
300
325
|
#### 2. 充分利用 skill 知识库
|
|
301
326
|
|
|
@@ -846,10 +871,16 @@ python -m gongwen fix-common 文件.docx -o 成品.docx
|
|
|
846
871
|
|
|
847
872
|
---
|
|
848
873
|
|
|
849
|
-
## 附录:全部命令速查(
|
|
874
|
+
## 附录:全部命令速查(25 个,按用途分组)
|
|
850
875
|
|
|
851
876
|
> Agent 遇到用户需求时,先在此表定位对应命令;命令用法不明确时运行 `python -m gongwen <命令> --help` 查看完整参数。
|
|
852
877
|
|
|
878
|
+
### 🧭 向导式交互
|
|
879
|
+
|
|
880
|
+
| 命令 | 用途 | 最小用法 |
|
|
881
|
+
|------|------|---------|
|
|
882
|
+
| `wizard` | 交互式路径引导(A/B/C/D)+ 一键执行;Agent 用 `--answers` 非交互 / `--dry-run` 只打印命令 | `python -m gongwen wizard` |
|
|
883
|
+
|
|
853
884
|
### 🏗️ 生成与模板
|
|
854
885
|
|
|
855
886
|
| 命令 | 用途 | 最小用法 |
|
package/dsh/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// 公文全流程处理工具 - DSH plugin bridge (gongwen-skill, v2.
|
|
1
|
+
// 公文全流程处理工具 - DSH plugin bridge (gongwen-skill, v2.7.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.
|
|
@@ -529,8 +529,9 @@ def _apply_run_format(run, run_model: Run):
|
|
|
529
529
|
run.font.italic = fmt.italic
|
|
530
530
|
if fmt.underline is not None:
|
|
531
531
|
run.font.underline = fmt.underline
|
|
532
|
-
if fmt.strikethrough is
|
|
533
|
-
|
|
532
|
+
if fmt.strikethrough is not None:
|
|
533
|
+
# True 设置删除线 / False 显式清除已有删除线(避免 strikethrough=False 时旧删除线残留)
|
|
534
|
+
run.font.strike = fmt.strikethrough
|
|
534
535
|
|
|
535
536
|
# === 颜色 ===
|
|
536
537
|
if fmt.color:
|
|
@@ -30,8 +30,8 @@ _MD_UL_RE = re.compile(r'^[-*+]\s+')
|
|
|
30
30
|
# markdown 有序列表前缀:1. 2. 3. 或 1、2、3、
|
|
31
31
|
_MD_OL_RE = re.compile(r'^\d+[.、]\s*')
|
|
32
32
|
|
|
33
|
-
# markdown
|
|
34
|
-
_MD_TABLE_RE = re.compile(r'
|
|
33
|
+
# markdown 表格行(支持单列:| a |;贪婪匹配到行尾,多列 | a | b | 亦能识别)
|
|
34
|
+
_MD_TABLE_RE = re.compile(r'^\|.+\|$')
|
|
35
35
|
|
|
36
36
|
# markdown 表格分隔行:|----|----|
|
|
37
37
|
_MD_TABLE_SEP_RE = re.compile(r'^\|[\s\-:|]+\|$')
|
|
@@ -263,6 +263,8 @@ def convert_markdown(model: DocumentModel) -> int:
|
|
|
263
263
|
ol_match = _MD_OL_RE.match(text)
|
|
264
264
|
if ol_match and not para.is_heading:
|
|
265
265
|
is_list = True
|
|
266
|
+
text = _MD_OL_RE.sub('', text)
|
|
267
|
+
list_indent_pt = 32 # 有序列表同样 2 字符缩进(与无序列表一致)
|
|
266
268
|
|
|
267
269
|
# --- 应用格式修改到 run ---
|
|
268
270
|
|
|
@@ -79,7 +79,7 @@ class OOXMLWorkflow:
|
|
|
79
79
|
return self
|
|
80
80
|
|
|
81
81
|
def __exit__(self, exc_type, exc_val, exc_tb) -> None:
|
|
82
|
-
#
|
|
83
|
-
if self.unpack_dir is not None
|
|
82
|
+
# 修复:无论正常结束或异常均清理临时目录,避免每次异常残留 gongwen_ooxml_* 目录泄漏磁盘
|
|
83
|
+
if self.unpack_dir is not None:
|
|
84
84
|
shutil.rmtree(self.unpack_dir, ignore_errors=True)
|
|
85
85
|
self.unpack_dir = None
|
|
@@ -893,7 +893,7 @@ def _check_header_field(model, rule_id: str, severity: str, name: str,
|
|
|
893
893
|
suggested_fix="仅保留一个主送机关",
|
|
894
894
|
reason="请示一般只写一个主送机关(发现多个主送机关段)",
|
|
895
895
|
))
|
|
896
|
-
elif re.search(r'[、,,
|
|
896
|
+
elif re.search(r'[、,,]{2,}', first.strip().rstrip('::')) and len(first.strip().rstrip('::')) > 5:
|
|
897
897
|
issues.append(CheckIssue(
|
|
898
898
|
rule_id=rule_id, check_type="content", severity=severity,
|
|
899
899
|
name=name, location=f"paragraph:{recips[0].index}",
|
package/engine/inject.py
CHANGED
|
@@ -379,7 +379,7 @@ def inject_footer(output_path: str, footer_config: dict) -> None:
|
|
|
379
379
|
# 页码注入:Word PAGE 域动态页码
|
|
380
380
|
# ---------------------------------------------------------------------------
|
|
381
381
|
|
|
382
|
-
def _add_page_run(para_elem, text: str, font_name: str, size_pt:
|
|
382
|
+
def _add_page_run(para_elem, text: str, font_name: str, size_pt: float) -> None:
|
|
383
383
|
"""添加一个普通文本 run 到段落元素。"""
|
|
384
384
|
from docx.oxml import OxmlElement
|
|
385
385
|
from docx.oxml.ns import qn
|
|
@@ -402,7 +402,7 @@ def _add_page_run(para_elem, text: str, font_name: str, size_pt: int) -> None:
|
|
|
402
402
|
para_elem.append(r)
|
|
403
403
|
|
|
404
404
|
|
|
405
|
-
def _build_page_number_xml(fmt: str, font_name: str, size_pt:
|
|
405
|
+
def _build_page_number_xml(fmt: str, font_name: str, size_pt: float) -> list:
|
|
406
406
|
"""解析页码格式字符串,返回 (类型, 内容, 字体, 字号) 元素列表。"""
|
|
407
407
|
import re
|
|
408
408
|
elements = []
|
|
@@ -621,6 +621,10 @@ def inject_page_number(output_path: str, page_number_config: dict) -> None:
|
|
|
621
621
|
from docx.oxml import OxmlElement
|
|
622
622
|
from docx.oxml.ns import qn
|
|
623
623
|
from docx.shared import Pt
|
|
624
|
+
from engine.core.document.font_utils import (
|
|
625
|
+
PAGE_NUMBER_FONT,
|
|
626
|
+
PAGE_NUMBER_SIZE_PT,
|
|
627
|
+
)
|
|
624
628
|
|
|
625
629
|
enabled = page_number_config.get('enabled')
|
|
626
630
|
if enabled is None:
|
|
@@ -629,8 +633,8 @@ def inject_page_number(output_path: str, page_number_config: dict) -> None:
|
|
|
629
633
|
return
|
|
630
634
|
|
|
631
635
|
fmt = page_number_config.get('format', '- {PAGE} -')
|
|
632
|
-
font_name = page_number_config.get('font',
|
|
633
|
-
size_pt = page_number_config.get('size',
|
|
636
|
+
font_name = page_number_config.get('font', PAGE_NUMBER_FONT)
|
|
637
|
+
size_pt = page_number_config.get('size', PAGE_NUMBER_SIZE_PT)
|
|
634
638
|
align = page_number_config.get('alignment') or page_number_config.get('position', 'center')
|
|
635
639
|
if align == 'right-left':
|
|
636
640
|
align = 'right'
|
|
@@ -671,9 +675,9 @@ def inject_page_number(output_path: str, page_number_config: dict) -> None:
|
|
|
671
675
|
if is_odd_even:
|
|
672
676
|
ind = OxmlElement('w:ind')
|
|
673
677
|
if align == 'right':
|
|
674
|
-
ind.set(qn('w:right'), str(int(
|
|
678
|
+
ind.set(qn('w:right'), str(int(PAGE_NUMBER_SIZE_PT * 20)))
|
|
675
679
|
else:
|
|
676
|
-
ind.set(qn('w:left'), str(int(
|
|
680
|
+
ind.set(qn('w:left'), str(int(PAGE_NUMBER_SIZE_PT * 20)))
|
|
677
681
|
pPr.append(ind)
|
|
678
682
|
try:
|
|
679
683
|
bm = section.bottom_margin
|
package/gongwen/__init__.py
CHANGED
package/gongwen/_legacy.py
CHANGED
|
@@ -39,13 +39,20 @@ from gongwen.cli.doctor_cmds import (
|
|
|
39
39
|
cmd_doctor,
|
|
40
40
|
cmd_repair,
|
|
41
41
|
)
|
|
42
|
+
from gongwen.cli.wizard_cmds import (
|
|
43
|
+
cmd_wizard,
|
|
44
|
+
)
|
|
42
45
|
from gongwen.cli.helpers import (
|
|
43
46
|
detect_doc_type as _detect_doc_type,
|
|
44
47
|
build_output_name as _build_output_name,
|
|
45
48
|
parse_config_overrides as _parse_config_overrides,
|
|
46
49
|
load_rules_with_overrides as _load_rules_with_overrides,
|
|
47
50
|
)
|
|
48
|
-
|
|
51
|
+
from engine.core.document.font_utils import (
|
|
52
|
+
PAGE_NUMBER_FONT,
|
|
53
|
+
PAGE_NUMBER_SIZE_PT,
|
|
54
|
+
)
|
|
55
|
+
__version__ = "2.7.0"
|
|
49
56
|
# 版本号应与 gongwen/__init__.py 保持一致,每次发版同步更新
|
|
50
57
|
"""
|
|
51
58
|
中文公文全流程处理工具 —— 基于 GB/T 9704《党政机关公文格式》国家标准。
|
|
@@ -68,6 +75,7 @@ __version__ = "2.6.1"
|
|
|
68
75
|
rule-export <type> 导出某类型的合并规则为 YAML 用于二次定制
|
|
69
76
|
rule-list 列出三层规则(official / custom / user)
|
|
70
77
|
rule-import <key> -f <file> 导入/保存自定义规则 YAML
|
|
78
|
+
wizard [--answers json] 向导式交互:A/B/C/D 路径引导 + 一键执行(--dry-run 只打印命令)
|
|
71
79
|
font [list|check|install] 公文标准字体管理(方正小标宋简体/仿宋_GB2312/楷体_GB2312)
|
|
72
80
|
|
|
73
81
|
示例:
|
|
@@ -945,8 +953,8 @@ def main():
|
|
|
945
953
|
p = sub.add_parser("pagenum", help="注入页码:Word PAGE 域动态页码")
|
|
946
954
|
p.add_argument("input", help="输入 .docx 路径")
|
|
947
955
|
p.add_argument("-o", "--output", help="输出 .docx 路径(默认原地修改)")
|
|
948
|
-
p.add_argument("--font", default=
|
|
949
|
-
p.add_argument("--size", type=int, default=
|
|
956
|
+
p.add_argument("--font", default=PAGE_NUMBER_FONT, help="页码字体(默认 宋体)")
|
|
957
|
+
p.add_argument("--size", type=int, default=PAGE_NUMBER_SIZE_PT, help="页码字号(默认 14)")
|
|
950
958
|
p.add_argument("--alignment", default="right",
|
|
951
959
|
choices=["center", "left", "right"],
|
|
952
960
|
help="对齐(默认 right 单右双左奇偶排版,适配双面打印;center 居中;left 左对齐)")
|
|
@@ -1096,6 +1104,14 @@ def main():
|
|
|
1096
1104
|
p.add_argument("--title", default="", help="待审文稿标题")
|
|
1097
1105
|
p.set_defaults(func=cmd_review)
|
|
1098
1106
|
|
|
1107
|
+
# ---- 向导式交互 ----
|
|
1108
|
+
p = sub.add_parser("wizard", help="向导式交互:A/B/C/D 路径引导 + 一键执行(--answers 非交互 / --dry-run 只打印)")
|
|
1109
|
+
p.add_argument("--answers", default="",
|
|
1110
|
+
help="答案 JSON 文件路径(Agent 非交互模式):"
|
|
1111
|
+
'{"path":"A","input":"a.docx","apply":true}')
|
|
1112
|
+
p.add_argument("--dry-run", action="store_true", help="只打印将执行的命令,不真正执行")
|
|
1113
|
+
p.set_defaults(func=cmd_wizard)
|
|
1114
|
+
|
|
1099
1115
|
# ---- 字体管理 ----
|
|
1100
1116
|
p = sub.add_parser("font", help="公文标准字体管理:安装/检查/列出内置字体")
|
|
1101
1117
|
p.add_argument("action", nargs="?", default="list",
|
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
#
|
|
4
|
+
# (c) 2026 Jose AI (https://www.linhut.cn)
|
|
5
|
+
# https://github.com/linhut/gongwen-skill
|
|
6
|
+
# Licensed under the MIT License. See the LICENSE file for details.
|
|
7
|
+
#
|
|
8
|
+
# gongwen wizard —— 向导式交互命令
|
|
9
|
+
# A/B/C/D 路径引导 + 一键执行;终端交互 + --answers JSON 非交互双模式。
|
|
10
|
+
"""向导式交互:以 A/B/C/D 路径菜单引导用户,交互收集参数后直接执行对应命令。
|
|
11
|
+
|
|
12
|
+
使用方式:
|
|
13
|
+
python -m gongwen wizard # 终端交互模式
|
|
14
|
+
python -m gongwen wizard --answers a.json # Agent 非交互模式
|
|
15
|
+
python -m gongwen wizard --answers a.json --dry-run # 只打印将执行的命令
|
|
16
|
+
|
|
17
|
+
--answers JSON 扁平结构(顶层带 path):
|
|
18
|
+
{"path": "A", "input": "a.docx", "doc_type": "notice",
|
|
19
|
+
"output": "b.docx", "apply": true}
|
|
20
|
+
"""
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import argparse
|
|
24
|
+
import json
|
|
25
|
+
import shlex
|
|
26
|
+
import subprocess
|
|
27
|
+
import sys
|
|
28
|
+
from pathlib import Path
|
|
29
|
+
|
|
30
|
+
# 路径定义:(键, 标题, 一句话说明, 子命令)
|
|
31
|
+
PATH_DEFS = [
|
|
32
|
+
("A", "格式优化", "不改文字只修排版,按国标(GB/T 9704)标准化", "optimize"),
|
|
33
|
+
("B", "内容优化", "润色文字表达,红色标注+删除线对比版", "optimize-content"),
|
|
34
|
+
("C", "生成模板", "按类型生成一份 GB/T 9704 空白模板", "template"),
|
|
35
|
+
("D", "一键格式修复", "段落类型/编号拆分/首句加粗等常见问题快速修复", "fix-common"),
|
|
36
|
+
]
|
|
37
|
+
PATH_KEYS = [p[0] for p in PATH_DEFS]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# ---------------------------------------------------------------------------
|
|
41
|
+
# 交互 helper
|
|
42
|
+
# ---------------------------------------------------------------------------
|
|
43
|
+
|
|
44
|
+
def _ask(question: str, default: str | None = None) -> str:
|
|
45
|
+
"""交互提问:显示默认值,返回去除首尾空白的输入。
|
|
46
|
+
|
|
47
|
+
输入为空时返回默认值;default 为 None 且输入为空时返回空串,
|
|
48
|
+
由调用方决定是否必填重问。
|
|
49
|
+
"""
|
|
50
|
+
if default:
|
|
51
|
+
prompt = f"{question} [{default}]: "
|
|
52
|
+
else:
|
|
53
|
+
prompt = f"{question}: "
|
|
54
|
+
try:
|
|
55
|
+
raw = input(prompt).strip()
|
|
56
|
+
except EOFError:
|
|
57
|
+
return default or ""
|
|
58
|
+
except KeyboardInterrupt:
|
|
59
|
+
print("\n已取消向导。", file=sys.stderr)
|
|
60
|
+
raise SystemExit(130)
|
|
61
|
+
return raw or (default or "")
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _ask_yes_no(question: str, default: bool = True) -> bool:
|
|
65
|
+
"""y/n 确认。Enter 使用默认值。"""
|
|
66
|
+
hint = "Y/n" if default else "y/N"
|
|
67
|
+
while True:
|
|
68
|
+
raw = input(f"{question} ({hint}): ").strip().lower()
|
|
69
|
+
if not raw:
|
|
70
|
+
return default
|
|
71
|
+
if raw in ("y", "yes", "是"):
|
|
72
|
+
return True
|
|
73
|
+
if raw in ("n", "no", "否"):
|
|
74
|
+
return False
|
|
75
|
+
print("请输入 y 或 n。", file=sys.stderr)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _ask_path() -> str:
|
|
79
|
+
"""展示 A/B/C/D 菜单,返回路径键。"""
|
|
80
|
+
print("\n请选择要执行的操作:\n")
|
|
81
|
+
for key, title, desc, _sub in PATH_DEFS:
|
|
82
|
+
print(f" {key}. {title} —— {desc}")
|
|
83
|
+
print()
|
|
84
|
+
while True:
|
|
85
|
+
try:
|
|
86
|
+
raw = input("请输入路径(A/B/C/D,Enter 退出): ").strip().upper()
|
|
87
|
+
except EOFError:
|
|
88
|
+
print("已退出向导。")
|
|
89
|
+
raise SystemExit(0)
|
|
90
|
+
if not raw:
|
|
91
|
+
print("已退出向导。")
|
|
92
|
+
raise SystemExit(0)
|
|
93
|
+
if raw in PATH_KEYS:
|
|
94
|
+
return raw
|
|
95
|
+
print("无效路径,请输入 A/B/C/D。", file=sys.stderr)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
# ---------------------------------------------------------------------------
|
|
99
|
+
# 类型匹配
|
|
100
|
+
# ---------------------------------------------------------------------------
|
|
101
|
+
|
|
102
|
+
def _load_available_types() -> list[str]:
|
|
103
|
+
"""加载 rules/official 下的公文类型 id 列表(排序)。"""
|
|
104
|
+
from engine.core.rules.loader import list_available_types
|
|
105
|
+
return list_available_types()
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _resolve_doc_type(raw: str, types: list[str]) -> str:
|
|
109
|
+
"""类型智能匹配:序号 / id / 中文名 / 子串。找不到抛 ValueError。"""
|
|
110
|
+
raw = raw.strip()
|
|
111
|
+
if not raw:
|
|
112
|
+
raise ValueError("公文类型不能为空")
|
|
113
|
+
if raw.isdigit():
|
|
114
|
+
idx = int(raw)
|
|
115
|
+
if 1 <= idx <= len(types):
|
|
116
|
+
return types[idx - 1]
|
|
117
|
+
raise ValueError(f"序号超出范围(1-{len(types)}):{raw}")
|
|
118
|
+
|
|
119
|
+
if raw in types:
|
|
120
|
+
return raw
|
|
121
|
+
|
|
122
|
+
# 中文名匹配(复用 helpers.TYPE_KEYWORDS 关键词表)
|
|
123
|
+
try:
|
|
124
|
+
from gongwen.cli.helpers import TYPE_KEYWORDS
|
|
125
|
+
for kw, tid in TYPE_KEYWORDS.items():
|
|
126
|
+
if raw == kw or raw in kw or kw in raw:
|
|
127
|
+
return tid
|
|
128
|
+
except ImportError:
|
|
129
|
+
pass
|
|
130
|
+
|
|
131
|
+
# 子串匹配 id(如 "not" → notice)
|
|
132
|
+
hits = [t for t in types if raw.lower() in t]
|
|
133
|
+
if len(hits) == 1:
|
|
134
|
+
return hits[0]
|
|
135
|
+
|
|
136
|
+
raise ValueError(
|
|
137
|
+
f"无法识别公文类型:{raw}。可用:{', '.join(types)},"
|
|
138
|
+
"或输入序号/中文名(如 通知)。"
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _prompt_doc_type(default: str | None = None) -> str:
|
|
143
|
+
"""交互收集公文类型:展示序号列表,支持序号/id/中文名。"""
|
|
144
|
+
types = _load_available_types()
|
|
145
|
+
print("\n可用公文类型:")
|
|
146
|
+
for i, t in enumerate(types, 1):
|
|
147
|
+
print(f" {i:2d}. {t}")
|
|
148
|
+
print()
|
|
149
|
+
while True:
|
|
150
|
+
raw = _ask("请输入公文类型(序号 / id / 中文名)", default or "")
|
|
151
|
+
if not raw:
|
|
152
|
+
return types[0]
|
|
153
|
+
try:
|
|
154
|
+
return _resolve_doc_type(raw, types)
|
|
155
|
+
except ValueError as e:
|
|
156
|
+
print(f" ⚠ {e}", file=sys.stderr)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
# ---------------------------------------------------------------------------
|
|
160
|
+
# 参数收集
|
|
161
|
+
# ---------------------------------------------------------------------------
|
|
162
|
+
|
|
163
|
+
def _get(answers: dict, key: str, default: str | None = None) -> str | None:
|
|
164
|
+
"""从 answers 取参数;缺失返回 default。"""
|
|
165
|
+
val = answers.get(key)
|
|
166
|
+
if val is None:
|
|
167
|
+
return default
|
|
168
|
+
s = str(val).strip()
|
|
169
|
+
return s or default
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def _require(answers: dict, key: str, label: str, interactive: bool,
|
|
173
|
+
validator=None) -> str:
|
|
174
|
+
"""必填参数:answers 优先;缺失时交互补充;非交互报错。"""
|
|
175
|
+
val = _get(answers, key)
|
|
176
|
+
if val:
|
|
177
|
+
if validator and not validator(val):
|
|
178
|
+
raise SystemExit(f"参数验证失败: {label}={val!r}")
|
|
179
|
+
return val
|
|
180
|
+
if not interactive:
|
|
181
|
+
raise SystemExit(f"缺少必填参数 {key}({label})—— 请在 --answers JSON 中提供")
|
|
182
|
+
while True:
|
|
183
|
+
raw = _ask(f"请输入{label}")
|
|
184
|
+
if not raw:
|
|
185
|
+
print(f" ⚠ {label}不能为空", file=sys.stderr)
|
|
186
|
+
continue
|
|
187
|
+
if validator and not validator(raw):
|
|
188
|
+
print(f" ⚠ {label}验证失败,请重新输入", file=sys.stderr)
|
|
189
|
+
continue
|
|
190
|
+
return raw
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def _file_exists(p: str) -> bool:
|
|
194
|
+
return Path(p).expanduser().is_file()
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def _collect_params(path_key: str, answers: dict, interactive: bool) -> dict:
|
|
198
|
+
"""按路径收集参数并返回。answers 优先,缺失时交互补充。"""
|
|
199
|
+
params: dict = {}
|
|
200
|
+
|
|
201
|
+
if path_key == "A":
|
|
202
|
+
params["input"] = _require(
|
|
203
|
+
answers, "input", "输入 .docx 路径", interactive,
|
|
204
|
+
validator=lambda p: _file_exists(p) or print(f" ⚠ 文件不存在: {p}", file=sys.stderr) or False)
|
|
205
|
+
params["doc_type"] = _get(answers, "doc_type") or ""
|
|
206
|
+
params["output"] = _get(answers, "output") or ""
|
|
207
|
+
elif path_key == "B":
|
|
208
|
+
params["input"] = _require(
|
|
209
|
+
answers, "input", "输入 .docx 路径", interactive,
|
|
210
|
+
validator=lambda p: _file_exists(p) or print(f" ⚠ 文件不存在: {p}", file=sys.stderr) or False)
|
|
211
|
+
changes = _get(answers, "changes")
|
|
212
|
+
if interactive and not changes:
|
|
213
|
+
changes = _ask("变更 JSON 路径(可回车留空,留空需 --auto-generate 生成建议)")
|
|
214
|
+
params["changes"] = changes or ""
|
|
215
|
+
params["output"] = _get(answers, "output") or ""
|
|
216
|
+
elif path_key == "C":
|
|
217
|
+
raw_type = _get(answers, "doc_type")
|
|
218
|
+
if interactive and not raw_type:
|
|
219
|
+
params["doc_type"] = _prompt_doc_type()
|
|
220
|
+
elif raw_type:
|
|
221
|
+
params["doc_type"] = _resolve_doc_type(raw_type, _load_available_types())
|
|
222
|
+
else:
|
|
223
|
+
raise SystemExit("缺少必填参数 doc_type(公文类型)—— 请在 --answers JSON 中提供")
|
|
224
|
+
params["output"] = _get(answers, "output") or ""
|
|
225
|
+
elif path_key == "D":
|
|
226
|
+
params["input"] = _require(
|
|
227
|
+
answers, "input", "输入 .docx 路径", interactive,
|
|
228
|
+
validator=lambda p: _file_exists(p) or print(f" ⚠ 文件不存在: {p}", file=sys.stderr) or False)
|
|
229
|
+
params["output"] = _get(answers, "output") or ""
|
|
230
|
+
else:
|
|
231
|
+
raise SystemExit(f"未知路径: {path_key}")
|
|
232
|
+
|
|
233
|
+
return params
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
# ---------------------------------------------------------------------------
|
|
237
|
+
# 命令构造与执行
|
|
238
|
+
# ---------------------------------------------------------------------------
|
|
239
|
+
|
|
240
|
+
def _build_cmd(path_key: str, params: dict, apply: bool) -> list[str]:
|
|
241
|
+
"""拼出 [sys.executable, -m, gongwen, 子命令, ...] argv。"""
|
|
242
|
+
sub_cmd = dict((p[0], p[3]) for p in PATH_DEFS)[path_key]
|
|
243
|
+
argv = [sys.executable, "-m", "gongwen", sub_cmd]
|
|
244
|
+
|
|
245
|
+
if path_key == "A":
|
|
246
|
+
argv.append(params["input"])
|
|
247
|
+
if params.get("doc_type"):
|
|
248
|
+
argv += ["-t", params["doc_type"]]
|
|
249
|
+
if params.get("output"):
|
|
250
|
+
argv += ["-o", params["output"]]
|
|
251
|
+
if apply:
|
|
252
|
+
argv.append("--apply")
|
|
253
|
+
elif path_key == "B":
|
|
254
|
+
argv.append(params["input"])
|
|
255
|
+
if params.get("changes"):
|
|
256
|
+
argv += ["--changes", params["changes"]]
|
|
257
|
+
elif apply:
|
|
258
|
+
argv.append("--auto-generate")
|
|
259
|
+
if params.get("output"):
|
|
260
|
+
argv += ["-o", params["output"]]
|
|
261
|
+
if apply:
|
|
262
|
+
argv.append("--apply")
|
|
263
|
+
elif path_key == "C":
|
|
264
|
+
argv.append(params["doc_type"])
|
|
265
|
+
if params.get("output"):
|
|
266
|
+
argv += ["-o", params["output"]]
|
|
267
|
+
elif path_key == "D":
|
|
268
|
+
argv.append(params["input"])
|
|
269
|
+
if params.get("output"):
|
|
270
|
+
argv += ["-o", params["output"]]
|
|
271
|
+
|
|
272
|
+
return argv
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def _print_cmd(argv: list[str]) -> str:
|
|
276
|
+
"""把 argv 拼成可读命令行字符串(Windows 兼容/引号)。"""
|
|
277
|
+
return " ".join(shlex.quote(a) for a in argv)
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def _run(argv: list[str], dry_run: bool) -> int:
|
|
281
|
+
"""subprocess 执行(输出流式透传);dry-run 只打印。"""
|
|
282
|
+
cmd_str = _print_cmd(argv)
|
|
283
|
+
if dry_run:
|
|
284
|
+
print(f"[dry-run] {cmd_str}")
|
|
285
|
+
return 0
|
|
286
|
+
print(f"▶ {cmd_str}", file=sys.stderr)
|
|
287
|
+
return subprocess.call(argv)
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def _confirm_and_run(argv: list[str], dry_run: bool, interactive: bool,
|
|
291
|
+
apply: bool) -> int:
|
|
292
|
+
"""A/B/D 修改类路径执行。
|
|
293
|
+
|
|
294
|
+
apply=True(用户选择直接执行)→ 跳过预览,直接执行(argv 已含 --apply;
|
|
295
|
+
D 无 --apply 概念,原样执行)。
|
|
296
|
+
apply=False → 先预览(optimize/optimize-content 无 --apply 即预览模式;
|
|
297
|
+
fix-common 显示将要执行的命令),再 y/n 确认,确认后 A/B 自动补
|
|
298
|
+
--apply 真正执行,拒绝则取消。
|
|
299
|
+
非交互模式 apply=False:仅预览不执行(安全默认),提示加 apply:true。
|
|
300
|
+
"""
|
|
301
|
+
sub_cmd = _argv_sub(argv)
|
|
302
|
+
has_apply = "--apply" in argv
|
|
303
|
+
|
|
304
|
+
if dry_run:
|
|
305
|
+
if path_key_is_b(sub_cmd):
|
|
306
|
+
preview_argv = [a for a in argv if a != "--apply"]
|
|
307
|
+
print(f"[dry-run] 预览: {_print_cmd(preview_argv)}")
|
|
308
|
+
if apply:
|
|
309
|
+
print(f"[dry-run] 执行: {_print_cmd(argv)}")
|
|
310
|
+
else:
|
|
311
|
+
print(f"[dry-run] 执行: {_print_cmd(argv)}")
|
|
312
|
+
return 0
|
|
313
|
+
|
|
314
|
+
if apply:
|
|
315
|
+
# 直接执行(跳过预览确认)
|
|
316
|
+
return _run(argv, dry_run=False)
|
|
317
|
+
|
|
318
|
+
# 先预览
|
|
319
|
+
if path_key_is_b(sub_cmd):
|
|
320
|
+
rc = _run(argv, dry_run=False) # argv 无 --apply 即预览模式
|
|
321
|
+
if rc != 0:
|
|
322
|
+
return rc
|
|
323
|
+
else:
|
|
324
|
+
print(f"将要执行: {_print_cmd(argv)}")
|
|
325
|
+
|
|
326
|
+
if not interactive:
|
|
327
|
+
# 非交互缺 apply:安全默认只预览,不执行
|
|
328
|
+
print("\n(非交互模式未提供 apply:true,仅预览未执行。"
|
|
329
|
+
"如需执行请加 apply:true 重新运行)")
|
|
330
|
+
return 0
|
|
331
|
+
|
|
332
|
+
if not _ask_yes_no("确认执行?(将修改/生成文件)", default=False):
|
|
333
|
+
print("已取消。")
|
|
334
|
+
return 0
|
|
335
|
+
# 确认后:A/B 补 --apply 真正执行(D 无 apply 概念,原样执行)
|
|
336
|
+
exec_argv = list(argv)
|
|
337
|
+
if path_key_is_b(sub_cmd) and not has_apply:
|
|
338
|
+
exec_argv.append("--apply")
|
|
339
|
+
return _run(exec_argv, dry_run=False)
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def _argv_sub(argv: list[str]) -> str:
|
|
343
|
+
"""从 argv 提取子命令名([python, -m, gongwen, sub, ...])。"""
|
|
344
|
+
return argv[3] if len(argv) > 3 else ""
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def path_key_is_b(sub_cmd: str) -> bool:
|
|
348
|
+
"""子命令是否需要先跑预览模式(optimize / optimize-content)。"""
|
|
349
|
+
return sub_cmd in ("optimize", "optimize-content")
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def _non_interactive_plan(path_key: str, params: dict, apply: bool,
|
|
353
|
+
dry_run: bool) -> int:
|
|
354
|
+
"""非交互(--answers)执行路径;dry-run 只打印。"""
|
|
355
|
+
argv = _build_cmd(path_key, params, apply)
|
|
356
|
+
if path_key == "C":
|
|
357
|
+
return _run(argv, dry_run)
|
|
358
|
+
return _confirm_and_run(argv, dry_run, interactive=False, apply=apply)
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
# ---------------------------------------------------------------------------
|
|
362
|
+
# 入口
|
|
363
|
+
# ---------------------------------------------------------------------------
|
|
364
|
+
|
|
365
|
+
def cmd_wizard(args: argparse.Namespace) -> int:
|
|
366
|
+
"""wizard 命令入口。"""
|
|
367
|
+
answers: dict = {}
|
|
368
|
+
# --answers 提供即视为非交互(Agent 场景),严格校验字段,不做 isatty 猜测
|
|
369
|
+
interactive = True
|
|
370
|
+
|
|
371
|
+
if getattr(args, "answers", None):
|
|
372
|
+
p = Path(args.answers).expanduser()
|
|
373
|
+
if not p.is_file():
|
|
374
|
+
raise SystemExit(f"--answers 文件不存在: {p}")
|
|
375
|
+
try:
|
|
376
|
+
data = json.loads(p.read_text(encoding="utf-8"))
|
|
377
|
+
except json.JSONDecodeError as e:
|
|
378
|
+
raise SystemExit(f"--answers JSON 解析失败: {e}")
|
|
379
|
+
if not isinstance(data, dict):
|
|
380
|
+
raise SystemExit("--answers 必须是一个 JSON 对象")
|
|
381
|
+
answers = data
|
|
382
|
+
interactive = False
|
|
383
|
+
|
|
384
|
+
dry_run = bool(getattr(args, "dry_run", False))
|
|
385
|
+
|
|
386
|
+
path_key = answers.get("path")
|
|
387
|
+
if not path_key:
|
|
388
|
+
if not interactive:
|
|
389
|
+
raise SystemExit("缺少必填参数 path(A/B/C/D)—— 请在 --answers JSON 中提供")
|
|
390
|
+
path_key = _ask_path()
|
|
391
|
+
path_key = str(path_key).strip().upper()
|
|
392
|
+
if path_key not in PATH_KEYS:
|
|
393
|
+
raise SystemExit(f"无效 path: {path_key}(可选 A/B/C/D)")
|
|
394
|
+
|
|
395
|
+
apply = bool(answers.get("apply", False))
|
|
396
|
+
|
|
397
|
+
try:
|
|
398
|
+
params = _collect_params(path_key, answers, interactive)
|
|
399
|
+
except ValueError as e:
|
|
400
|
+
raise SystemExit(str(e))
|
|
401
|
+
|
|
402
|
+
if not interactive:
|
|
403
|
+
return _non_interactive_plan(path_key, params, apply, dry_run)
|
|
404
|
+
if dry_run:
|
|
405
|
+
# 交互 + dry-run:打印将执行的命令
|
|
406
|
+
argv = _build_cmd(path_key, params, False)
|
|
407
|
+
print(f"[dry-run] {_print_cmd(argv)}")
|
|
408
|
+
return 0
|
|
409
|
+
return _interactive_flow_selected(path_key, params)
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
def _interactive_flow_selected(path_key: str, params: dict) -> int:
|
|
413
|
+
"""交互模式已有 path/params 时的执行分支。"""
|
|
414
|
+
if path_key == "C":
|
|
415
|
+
# C 生成模板无修改风险,直接执行,不问确认
|
|
416
|
+
argv = _build_cmd(path_key, params, False)
|
|
417
|
+
return _run(argv, dry_run=False)
|
|
418
|
+
apply = _ask_yes_no("是否直接执行(跳过预览确认)?", default=False)
|
|
419
|
+
argv = _build_cmd(path_key, params, apply)
|
|
420
|
+
return _confirm_and_run(argv, dry_run=False, interactive=True, apply=apply)
|
|
@@ -397,8 +397,9 @@ def _add_paragraph(doc: Document, para, rules: dict):
|
|
|
397
397
|
run.font.bold = True
|
|
398
398
|
if fmt.italic is not None:
|
|
399
399
|
run.font.italic = fmt.italic
|
|
400
|
-
if fmt.strikethrough is
|
|
401
|
-
|
|
400
|
+
if fmt.strikethrough is not None:
|
|
401
|
+
# True 设置删除线 / False 显式清除已有删除线(避免 strikethrough=False 时旧删除线残留)
|
|
402
|
+
run.font.strike = fmt.strikethrough
|
|
402
403
|
if fmt.color:
|
|
403
404
|
try:
|
|
404
405
|
rgb = str(fmt.color).lstrip("#")
|
package/package.json
CHANGED
package/prompts/usage-prompts.md
CHANGED