proposal: 阶段二两段式 + render_docx 透传 fenced 代码块

- SKILL.md 阶段二改两段式: 先列 3-6 条要点 → 用户确认 → 再起草 → 用户确认。关键章节 (立项依据/研究方案/技术路线/考核指标) 一段一卡。一次性出全文容易把错方向推到底,要点阶段拦得早
- render_docx.py 支持 ```...``` 围栏: 中文新宋体 + 西文 Consolas + 行距 1.0 + 不缩进 + xml:space=preserve。原先 ASCII 流程图被当散文段落合并,框完全错位
- PROGRESS.md backlog 加 mermaid 预渲染 (mmdc → PNG → add_picture),等 ASCII 透传不够用再做

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
caoqianming 2026-05-08 08:58:18 +08:00
parent f4432cca20
commit 56e414e046
3 changed files with 60 additions and 7 deletions

View File

@ -86,3 +86,4 @@ Python 合计 ~1669 行
3. **小修打磨**(~半小时)—— `Session.save()` 改原子写(tmp + rename),防 surrogate 等异常 truncate 3. **小修打磨**(~半小时)—— `Session.save()` 改原子写(tmp + rename),防 surrogate 等异常 truncate
4. **Phase 7 Docker 沙盒**(~1 天)—— 替换 subprocess,run_python 安全升级 4. **Phase 7 Docker 沙盒**(~1 天)—— 替换 subprocess,run_python 安全升级
5. **Phase 7 更多 skill / 模型档案**(持续) 5. **Phase 7 更多 skill / 模型档案**(持续)
6. **Proposal mermaid 流程图预渲染**(~半天,看到第二张图再做)—— 现状是 ASCII 框图走 fenced code 透传 (新宋体 + Consolas + xml:space=preserve),中文与 box drawing 字符宽度对不齐时还是有错位。增强方案: ` ```mermaid ` 块在 `render_docx.py` 里调 `mmdc` (mermaid-cli) → PNG → `add_picture` 嵌入。依赖 Node.js + `npm i -g @mermaid-js/mermaid-cli`,首次配置略麻烦,所以等 ASCII 透传明显不够用再做

View File

@ -47,15 +47,23 @@ markitdown https://example.com/x -o <task_dir>/source/policy.md
## 阶段二: 逐章起草 ## 阶段二: 逐章起草
每章流程: 每章两段式:**先列要点 → 用户确认 → 再起草 → 用户确认**。不要直接出正文。
1. 读 `<task_dir>/spec_lock.md``<skill_dir>/references/fund_types.md` 拿本章字数预算与必填要素
2. 复制 `<skill_dir>/templates/<fund_type>.md` 对应小节到 `<task_dir>/sections/NN_xxx.md`,填空
3. 报告: 章节名 / 实际字数 / 字数预算 / 与指南对齐情况
4. ⛔ **BLOCKING:停下来等用户明确反馈** ("OK"、"下一章"、"继续") 后才进下一章。"看起来不错"、沉默、追问都不算确认 —— 主动问"这一章可以了吗?要改哪里?"
**为什么逐章 + 强等?** 申报书 1.5-3 万字,模型连续生成时容易自我加速、把错的方向推到底。每章一卡可以在第 2 章被用户拦下。 **A. 起草前列要点** (改要点比改正文便宜):
1. 读 `<task_dir>/spec_lock.md``<skill_dir>/references/fund_types.md`,拿本章字数预算与必填要素
2. 列出 3-6 条要点骨架: 本章打算覆盖的论点 / 数据 / 表格,每条贴上对齐的指南要素与预估字数
3. ⛔ **BLOCKING:用户确认要点 (改 / 加 / 删) 后才动正文**
**例外**: 用户**主动且明确**说"别问,直接全做"或"一气呵成" —— 才能一次跑完,跑完必须 `quality_check.py`。"逐章太慢"之类的抱怨**不算**例外指令,继续问。 **B. 正文起草**:
4. 复制 `<skill_dir>/templates/<fund_type>.md` 对应小节到 `<task_dir>/sections/NN_xxx.md`,按要点填
5. **关键章节一段一卡** —— 立项依据 / 研究方案 / 技术路线 / 考核指标矩阵: 写一段 → 报字数 → 等用户确认 → 写下一段
普通章节一节一卡: 整节写完再报
6. 报告: 章节名 / 实际字数 / 字数预算 / 与指南对齐情况
7. ⛔ **BLOCKING:停下来等用户明确反馈** ("OK"、"下一章"、"继续") 后才进下一章。"看起来不错"、沉默、追问都不算确认 —— 主动问"这一章/这一段可以了吗?要改哪里?"
**为什么两段式 + 强等?** 申报书 1.5-3 万字,模型连续生成容易自我加速、把错方向推到底。要点阶段拦得早,关键章节段段卡可以在第 2 段被用户拦下。
**例外**: 用户**主动且明确**说"别问,直接全做"或"一气呵成" —— 才能一次跑完,跑完必须 `quality_check.py`。"逐章太慢"/"段段太碎"之类的抱怨**不算**例外指令,继续问。
## 阶段三: 验收 + 渲染 ## 阶段三: 验收 + 渲染
@ -106,6 +114,8 @@ python <skill_dir>/scripts/render_docx.py <task_dir>/sections/ --fund-type key
- 未 spec_lock 就开始硬编正文 - 未 spec_lock 就开始硬编正文
- 一次性出全文 (中途改向就全推翻) - 一次性出全文 (中途改向就全推翻)
- 跳过"列要点"直接写正文 —— 即使只有一章也要先列要点过一遍
- 关键章节 (立项依据 / 研究方案 / 技术路线 / 考核指标) 整章一次出 —— 必须段段卡
- **基于"通用模板"自行套基金类型** —— 重大专项任务书与国自然申请书结构完全不同,**先查 `fund_types.md`** - **基于"通用模板"自行套基金类型** —— 重大专项任务书与国自然申请书结构完全不同,**先查 `fund_types.md`**
- **自己造数据/指标/单位/经费** —— 不知道就 `<TODO 待用户提供>`,不要硬编 - **自己造数据/指标/单位/经费** —— 不知道就 `<TODO 待用户提供>`,不要硬编
- 引文写"[Smith et al., 2023]" 但其实没这篇文献 - 引文写"[Smith et al., 2023]" 但其实没这篇文献

View File

@ -224,6 +224,7 @@ _HEADING_RE = re.compile(r"^(#{1,6})\s+(.+)$")
_TABLE_LINE_RE = re.compile(r"^\s*\|.*\|\s*$") _TABLE_LINE_RE = re.compile(r"^\s*\|.*\|\s*$")
_BLOCKQUOTE_RE = re.compile(r"^\s*>\s?") _BLOCKQUOTE_RE = re.compile(r"^\s*>\s?")
_HR_RE = re.compile(r"^\s*-{3,}\s*$|^\s*={3,}\s*$|^\s*_{3,}\s*$") _HR_RE = re.compile(r"^\s*-{3,}\s*$|^\s*={3,}\s*$|^\s*_{3,}\s*$")
_FENCE_RE = re.compile(r"^\s*(`{3,}|~{3,})\s*(\S*)\s*$")
# 列表项 (各自独立成段, 不跟相邻行合并, 不缩进首行) # 列表项 (各自独立成段, 不跟相邻行合并, 不缩进首行)
_LIST_PATTERNS = [ _LIST_PATTERNS = [
@ -259,6 +260,29 @@ def is_hr(line: str) -> bool:
return bool(_HR_RE.match(line)) return bool(_HR_RE.match(line))
# ───────────────────────── 代码块 / ASCII 图 ─────────────────────────
def add_code_block(doc: Document, lines: list[str], lang: str = "") -> None:
"""fenced ``` 块 / ASCII 流程图: 等宽字体 + 行距 1.0 + 不缩进 + 不解析内联 + 保留空格。
中文用"新宋体"(NSimSun, Windows 自带等宽宋体), 西文用 Consolas, 这样
` ` 这类 box drawing 字符与中文字符的视觉宽度更接近, ASCII 流程图不至于错位
"""
for ln in lines:
p = doc.add_paragraph()
pf = p.paragraph_format
pf.first_line_indent = None
pf.line_spacing = 1.0
pf.space_before = Pt(0)
pf.space_after = Pt(0)
run = p.add_run(ln if ln else " ") # 空行也占一行高
run.font.size = Pt(10.5) # 五号
_set_run_fonts(run, cn_font="新宋体", en_font="Consolas")
# docx 默认会压缩连续空格 -> 显式 xml:space=preserve, 否则 ASCII 对齐会被破坏
for t in run._element.iter(qn("w:t")):
t.set(qn("xml:space"), "preserve")
# ───────────────────────── 表格 ───────────────────────── # ───────────────────────── 表格 ─────────────────────────
def _split_md_row(line: str) -> list[str]: def _split_md_row(line: str) -> list[str]:
@ -322,6 +346,24 @@ def render_md_block(doc: Document, md_text: str) -> None:
i += 1 i += 1
continue continue
# fenced 代码块 / ASCII 流程图 (```...``` 或 ~~~...~~~)
m_fence = _FENCE_RE.match(line)
if m_fence:
fence = m_fence.group(1)
lang = m_fence.group(2) or ""
code: list[str] = []
i += 1
while i < n:
m_close = _FENCE_RE.match(lines[i])
# 闭围栏: 同种符号 (` vs ~) 且长度 ≥ 开围栏
if m_close and m_close.group(1)[0] == fence[0] and len(m_close.group(1)) >= len(fence):
i += 1
break
code.append(lines[i]) # 不 rstrip, 保留原始空格
i += 1
add_code_block(doc, code, lang)
continue
# 表格 (连续若干行 | ... | 视为一张表) # 表格 (连续若干行 | ... | 视为一张表)
if is_table_line(line): if is_table_line(line):
block: list[str] = [] block: list[str] = []