需求文档编制(wwlia-doc-gen)
一套可跨项目复用的「科技项目 / 系统类文档」编制标准与落地方法。沉淀自多个真实项目的踩坑。
1. 适用场景
- 从合同 / 立项材料 / 方案里抽出"系统需求",产出 PRD 或 SRS(系统需求报告)。
- 给已有需求/设计文档做清洗:去 AI 味、标题去英文括号、署名改真实人员。
- 系统已开发完,需据实际代码反查、把遗漏的基础功能需求补进需求报告。
- 把最终 Markdown 需求文档生成 Word(.docx) 交付。
2. 工作流总览
抽需求(PRD/SRS) → 清洗(去合同/去AI味/中文标题/真实署名) → [可选]据系统代码补全需求 → 生成 Word(.docx) → 独立核验 → 交付
团队协作(如已启用 software-company 专家):
1. 主理人亲自 TeamCreate(命名 software-<项目简称>),不得代写成员产出。
2. 分派 software-product-manager(name 与 subagent_type 同传该值)产出/修订文档。
3. 回收产出 → 独立核验文件是否真落盘 → present_files 交付。
> ⚠️ agent 不可用兜底:若分派 PM 时框架直接报 Task agent software-product-manager is not available(0 秒失败,属环境/注册问题而非内容问题),主理人应直接执行文档修订并独立核验,不卡流程。Word 生成本就是主理人职责,不受影响。
3. 写作规范(关键,跨项目通用)
- 纯系统需求(去合同/科研味):当上游是"科技研发项目合同 / 立项书",且系统只是其中一项交付物时,只论系统本身(功能/性能/接口/部署);不提"合同/专利/论文/技术报告/成果形式/研究内容/预期目标"等科研产出字样。可留部署上下文(如"拟在 XX 现场示范"),但不要把专利/论文当系统需求写。
- 去 AI 味:删除自说自话的元注释——
>引述式说明(如"优先级说明:""每条功能需求含:""本报告正文完…""文档版本:v1.0""编制角色:产品经理(许清楚)""文档性质:…");- 正文不留"本报告/本文档/本章/本节"等自我指涉,改直接陈述("本报告聚焦…" → "本系统需求聚焦…")。
- 标题纯中文:中文标题后不加英文括号。例:
待确认问题(Open Questions)→待确认问题;系统需求报告(SRS)/功能需求(FR)/非功能需求(NFR)/接口与集成需求(IR)去掉括号英文。 - 保留:必要的技术专有词(RFID、EPC Gen2、MySQL 等)与需求编号(FR-01、NFR-P1、IR-01、P0/P1/P2)——它们是标识符,不是"标题后加英文"。
- 署名用真实人员(如
<真实人员名>,项目里曾用「常亮」),不用 AI 角色名(许清楚/高见远/寇豆码/严过关)。标题下用两行普通文本:版本:v1.x/编制:<真实人员名>(不要 blockquote、不要"产品经理/agent"字眼)。 - 结构化:表格 / 编号 / 分级标题,避免纯叙述。需求分级 P0 必备核心 / P1 重要增强 / P2 延伸可选。
- Word 落地排版(关键,已按通用业务需求报告样式校准):
- 标题居中(仅主标题):仅文档主标题(Markdown
#映射的 Heading 1)居中对齐;一级章节(##)与以下小节(### / ####)一律左对齐。 - 全文黑色:所有文字、标题、表头均为黑色(
000000),不使用任何彩色(不含彩色标题字、彩色表头底纹、彩色边框)。表格用黑色细边框,表头行仅加粗不染色。 - 全文无斜体:正文、表头、表格内容、图注一律不得打斜体(
run.italic一律不设为True;不解析 Markdown*text*斜体语法)。Markdown**加粗**仅转加粗,不产生斜体。 - 已取消"待确认问题"章节:标准 SRS 结构不再含 Q-xx 待确认问题(见第 4 节)。如业务确需遗留疑问,仅在对话中口头提示,不写入交付文档。
4. 从合同/材料抽系统需求(PRD / SRS)
- 通读上游材料,识别系统相关条款:可落地为功能/性能/接口/部署的部分、预期目标、技术经济指标。
- 产出结构(SRS 推荐):
- 文档说明与范围(纯系统视角,不含"合同"二字)
- 系统目标(G-xx)
- 用户角色(系统使用者视角)
- 功能需求(FR-xx,分层:数字身份/作业感知、边缘协同/出入控制、仓储基础功能、平台与验证)
- 非功能需求(NFR:性能硬指标如识别率≥90%、响应≤10s、权限验证≥99% + 可靠性/安全/可扩展/可监测)
- 接口与集成需求(IR-xx)
- (不再单独设"待确认问题"章节;遗留疑问在对话中口头提示,不入交付文档) - 需求编号建议:功能
FR-01起、非功能NFR-P1起、接口IR-01起,便于追溯。
5. 基于已开发系统补全需求
当系统已开发完、需补全(尤其仓储/业务基础功能常被首版遗漏):
- 用 Explore 子代理(read-only,thorough)通读系统代码,按模块产出结构化清单:
- 后端:domain/entity、mapper/SQL 表、controller 的 REST 端点(URL+方法+作用)
- 前端:Vue 页面(views)与对应功能
- 单据状态机(草稿→待执行→已完成/已取消等)
- 菜单/权限层级 - 把代码里实际实现但需求报告缺失的功能,补成 FR 表格插入对应小节;原小节顺延编号(如原 4.3→4.4),版本号升 v1.x 并加修订说明行。
- 通用"基础功能"补全清单(以 WMS 为例,其它系统类比):
- 物理空间(仓库/库区/库位三级 + 状态)
- 主数据(物资/物料、批次、序列件+标签绑定、往来单位)
- 作业闭环(入库、出库、移位/调拨、盘点、实时库存、统计报表)
- 单据工作流(状态流转,是否含审核节点按实际)
6. 生成 Word 文档(python-docx · 受管 venv)
最终交付常需 .docx。用 WorkBuddy 受管 Python 环境,避免污染用户环境。
6.1 准备环境(Windows 注意 Scripts/ 而非 bin/):
PY="C:/Users/20500/.workbuddy/binaries/python/versions/3.13.12/python.exe"
VENV="C:/Users/20500/.workbuddy/binaries/python/envs/default"
"$PY" -m venv "$VENV"
"$VENV/Scripts/pip.exe" install python-docx
"$VENV/Scripts/python.exe" -c "import docx; print(docx.__version__)"
6.2 转换脚本要点(写一个临时 md2docx.py 运行后删除):
- 逐行解析 Markdown:
- #→Heading 1(文档主标题,居中,22pt),##→Heading 2(一级章节,左对齐,16pt),###→Heading 3(小节,左对齐,14pt),####→Heading 4(左对齐,12pt)
- 以 | 开头的行 → 表格;首行表头加粗;跳过 |----| 分隔行;连续 | 行合并为同一表
- **加粗** → 用 run.bold=True 拆分处理
- --- 单独一行 → 跳过分隔线(或插分页符,按偏好)
- > 开头 → 去 > 作普通段落
- -/* 开头 → 项目符号列表
- 其余 → 普通段落
- 中文字体:document.styles['Normal'].font.name='Microsoft YaHei',并用 run._element.rPr.rFonts.set(qn('w:eastAsia'),'Microsoft YaHei') 让中文生效。
- 排版(居中 + 全黑,强制):
- 所有 run 设 font.color.rgb = RGBColor(0x00,0x00,0x00),标题/表头也不许用彩色;run.italic 一律不设为 True(全文无斜体)。
- 标题段落 paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER(仅 #/## 层级);###/#### 与正文保持左对齐。
- 表格样式用 Table Grid(黑色细边框);表头行仅 run.bold=True,不设底纹填充(避免彩色/灰底)。
- 正文 12pt,标题按层级 22/16/14/12pt,均黑色。
- 输出:Document(p).save(<同名>.docx),落盘到与 .md 同目录。
- 跑完用脚本自检:表格数、各表行数、标题层级、关键指标是否加粗、是否含彩色(grep 颜色值 1F3B57 等应为 0)、是否含斜体(document.xml 中 w:i 应无出现)、是否残留"待确认问题"章节应为否,确认无遗漏再交付。
6.3 推荐转换模板(居中 + 全黑,直接可用):
# -*- coding: utf-8 -*-
import re
from docx import Document
from docx.shared import Pt, RGBColor
from docx.oxml.ns import qn
from docx.enum.text import WD_ALIGN_PARAGRAPH
SRC = "需求报告.md"
OUT = "需求报告.docx"
BLACK = RGBColor(0x00, 0x00, 0x00)
CN = "Microsoft YaHei"
doc = Document()
st = doc.styles["Normal"]
st.font.name = CN; st.font.size = Pt(12)
st.element.rPr.rFonts.set(qn("w:eastAsia"), CN)
# 标题层级:字号 + 是否居中
LEVEL = {1: (22, True), 2: (16, False), 3: (14, False), 4: (12, False)}
for hid in ["Heading 1", "Heading 2", "Heading 3", "Heading 4"]:
try:
s = doc.styles[hid]
s.font.name = CN; s.font.color.rgb = BLACK; s.font.bold = True
s.element.rPr.rFonts.set(qn("w:eastAsia"), CN)
except KeyError:
pass
def set_cn(run, bold=False, size=None):
run.font.name = CN
run._element.rPr.rFonts.set(qn("w:eastAsia"), CN)
run.font.color.rgb = BLACK
if bold: run.bold = True
if size: run.font.size = Pt(size)
def add_runs(para, text, center=False, size=None):
if center: para.alignment = WD_ALIGN_PARAGRAPH.CENTER
for part in re.split(r"(\*\*.+?\*\*)", text):
if not part: continue
if part.startswith("**") and part.endswith("**"):
r = para.add_run(part[2:-2]); set_cn(r, bold=True, size=size)
else:
r = para.add_run(part); set_cn(r, size=size)
lines = open(SRC, encoding="utf-8").read().split("\n")
i, n = 0, len(lines)
while i < n:
raw = lines[i].rstrip("\n")
if raw.strip() == "": i += 1; continue
if raw.strip() == "---": i += 1; continue
if raw.strip().startswith("|") and raw.strip().endswith("|"):
rows = []
while i < n and lines[i].strip().startswith("|"):
cells = [c.strip() for c in lines[i].strip().strip("|").split("|")]
if not all(re.fullmatch(r":?-{2,}:?", c) for c in cells if c):
rows.append(cells)
i += 1
if rows:
nc = max(len(r) for r in rows)
for r in rows: r += [""] * (nc - len(r))
t = doc.add_table(rows=0, cols=nc); t.style = "Table Grid"
for ri, row in enumerate(rows):
for c in t.add_row().cells:
p = c.paragraphs[0]
if ri == 0:
r = p.add_run(row[ci]); set_cn(r, bold=True)
else:
add_runs(p, row[ci])
doc.add_paragraph()
continue
m = re.match(r"^(#{1,4})\s+(.*)$", raw)
if m:
lvl = len(m.group(1)); sz, center = LEVEL[lvl]
h = doc.add_heading(level=lvl); add_runs(h, m.group(2).strip(), center=center, size=sz)
i += 1; continue
if raw.lstrip().startswith(">"):
add_runs(doc.add_paragraph(), raw.lstrip()[1:].strip()); i += 1; continue
if re.match(r"^\d+\.\s+", raw):
add_runs(doc.add_paragraph(style="List Number"), raw.strip()); i += 1; continue
if re.match(r"^[-*]\s+", raw):
add_runs(doc.add_paragraph(style="List Bullet"), raw.strip()[2:].strip()); i += 1; continue
add_runs(doc.add_paragraph(), raw.strip()); i += 1
doc.save(OUT)
print("SAVED:", OUT)
> 若用户偏好可双击运行的程序而非脚本,可改用 PyInstaller 打包;常规交付用脚本即可。
7. 避坑(最重要,已复现多次)
> 派 software-product-manager 写/改文档时,首轮极高概率只回"completed"通知、不实际调用 Write 落盘(某会话复现 ≥4 次:去合同 / 去 AI 味 / 去标题英文 三轮均首轮"completed 不写",且曾出现"写了但漏改 2 处"的不完整写入)。
- 必须独立核验:文档任务完成后,用
Grep/Read查目标文件是否真改。AI 味/格式漏改检索词例:许清楚|编制角色|本报告|(SRS)|(FR)|(NFR)|(IR)|1F3B57|待确认问题|FR-14|v1.1。其中1F3B57/待确认问题应为 0 命中(彩色与待确认问题章节已去除);Word 另需查document.xml中<w:i/>或w:i应为 0(全文无斜体)。不要轻信"completed"通知。 - 未改/漏改就
SendMessage(type 必须传"message")二次催促,明确要求"必须调用 Write 工具覆盖写入原路径",PM 收到催促后通常会真正落盘(已验证有效,最多 2~3 轮收敛)。 - 主理人不得代写成员专业产出(铁律);但 agent 不可用时应直接执行并核验(见第 2 节兜底)。
- Word 生成后务必自检结构(表格数/行数/标题),脚本静默成功也可能漏解析某段。
8. 交付
present_files交付 Markdown 与/或 .docx;对话内给:TL;DR / 交付概览(含独立核验结论)/ 文件清单 / 用户下一步建议。- 落盘路径惯例:
<项目目录>/docs/<系统名>-系统需求报告.md(项目专属时)或用户指定路径;Word 同目录同名 .docx。 - 默认不自动落盘"交付报告"文件;除非用户明确要求。