## 为什么做这一步 权威文档 74 份此前**只在本机**,评审者 clone 分支后看不到任何设计文档;而仓库里那两份同名目录 是 **2026-09-16 之前的过期副本,连文件名都是旧的**(无体系编号)。本次按「**权威覆盖过期**」入库。 ## 入库内容 | 目录 | 文件数 | 体积 | 说明 | |---|---|---|---| | `客服agent/` | 24 | 0.77 MB | `D2.1`~`D2.6` 对外交付四件套 + 演示脚本/答辩报告 + `_build` 构建工具 | | `开发文档/` | 50 | 2.16 MB | `D1.x` 索引与决策、`D3.x` 方案、`D4.x` 清除与重构留痕、`D5.x` 业务流程、`D6.x` 业务事实基座、`D7.x` 交付物、`D8.x` 规范 | **旧的过期副本整体移除**(`客服Agent执行Todolist.md` → `D2.1-客服Agent执行Todolist.md` 之类 的改名 + 新增 `D2.5`/`D2.6`),入库后目录内容与权威副本**逐文件一致(零差异,已复核)**。 ## 入库前的安全扫描(必须留痕) - 扫描规则:`sk-` 类密钥 / `Bearer` 长串 / `password=`、`api_key=` 赋值 / 会话中出现过的两把明文 key 片段。 - 结论:**真实密钥只出现在 `.env`**(已被 `.gitignore` 命中,未入库);`.env.example` 与 `config/risk.env.example` 只有**空占位**。 - 文档内唯一命中是 `D3.1` 里一处**截断的示例 JWT**(`Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...`), 末尾带省略号,是接口文档的示意值,**不是可用凭据**。
115 lines
5.4 KiB
Python
115 lines
5.4 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""客服 Agent 四份交付文档 · 构建器(JSON 配置驱动)
|
||
|
||
从既有文档抽取「项目 HTML 版式外壳」,再灌入新正文,保证版式与既有体系逐字一致。
|
||
用法:python _docbuilder.py <spec.json>
|
||
"""
|
||
import json
|
||
import re
|
||
import sys
|
||
from pathlib import Path
|
||
|
||
SRC = Path(r"D:\桌面\金融\开发文档\D3.1-客服Agent需求开发文档与设计方案.html")
|
||
OUT_DIR = Path(r"D:\桌面\金融\客服agent")
|
||
|
||
text = SRC.read_text(encoding="utf-8")
|
||
|
||
HEAD_END = '<div id="toc-container">'
|
||
SHELL_HEAD = text[: text.index(HEAD_END) + len(HEAD_END)]
|
||
# ⚠️ 结束锚点必须取「正文第一段注释」的位置:若取 `</div><!-- end doc-container -->`,
|
||
# 那在文件里是**最后一行**,会把原文档整段正文一并带进来(曾因此产出 4920 行的错件)。
|
||
BODY_START = '<!-- ==================== 0 ==================== -->'
|
||
SHELL_MID = text[text.index(' </div>\n</nav>'): text.index(BODY_START)]
|
||
SHELL_TAIL = text[text.index('</div><!-- end doc-container -->'):]
|
||
assert 'doc-header' in SHELL_MID and len(SHELL_MID) < 4000, "SHELL_MID 抽取异常"
|
||
assert SHELL_TAIL.startswith('</div><!-- end doc-container -->')
|
||
|
||
# 备份一份外壳,便于后续单独构建
|
||
Path(r"D:\桌面\金融\_shell_head.html").write_text(SHELL_HEAD, encoding="utf-8")
|
||
Path(r"D:\桌面\金融\_shell_mid.html").write_text(SHELL_MID, encoding="utf-8")
|
||
Path(r"D:\桌面\金融\_shell_tail.html").write_text(SHELL_TAIL, encoding="utf-8")
|
||
|
||
|
||
def build(spec: dict) -> str:
|
||
head = SHELL_HEAD
|
||
head = head.replace(
|
||
"<title>XX科技·智能财富管家系统 — 智能客服 Agent 需求开发文档与设计方案</title>",
|
||
f"<title>{spec['title']}</title>")
|
||
head = head.replace(
|
||
'<div class="sidebar-subtitle">客服 Agent 专项方案 v2.3 · 双角色 · 身份分离</div>',
|
||
f'<div class="sidebar-subtitle">{spec["subtitle"]}</div>')
|
||
|
||
parts = []
|
||
for idx, grp in enumerate(spec["toc"], start=1):
|
||
active = " active" if idx == 1 else ""
|
||
arr = " open" if idx == 1 else ""
|
||
kids = " open" if idx == 1 else ""
|
||
parts.append(f'<div class="toc-group">\n'
|
||
f'<button class="toc-toggle{active}" data-group="{idx}">'
|
||
f'<span class="arr{arr}">▶</span><span>{grp["g"]}</span></button>\n'
|
||
f'<div class="toc-kids{kids}">')
|
||
for href, label in grp["items"]:
|
||
parts.append(f'<a class="toc-item" href="#{href}">{label}</a>')
|
||
parts.append("</div></div>\n")
|
||
toc_html = "\n".join(parts)
|
||
|
||
mid = SHELL_MID
|
||
mid = mid.replace(
|
||
'<div id="topbar-title">智能财富管家系统 — 智能客服 Agent 需求开发文档与设计方案</div>',
|
||
f'<div id="topbar-title">{spec["topbar"]}</div>')
|
||
mid = mid.replace('<span id="topbar-badge">v2.3 · 访客 ≠ 角色 · 重构基线</span>',
|
||
f'<span id="topbar-badge">{spec["badge"]}</span>')
|
||
mid = mid.replace('<div class="doc-eyebrow">专项需求开发文档 · 详细设计方案</div>',
|
||
f'<div class="doc-eyebrow">{spec["eyebrow"]}</div>')
|
||
mid = mid.replace(
|
||
'<h1 class="doc-title">XX科技·智能财富管家系统<br>智能客服 Agent 需求开发文档与设计方案</h1>',
|
||
f'<h1 class="doc-title">{spec["doctitle"]}</h1>')
|
||
meta_html = "".join(
|
||
f' <div class="doc-meta-item">\n'
|
||
f' <span class="doc-meta-label">{k}</span>\n'
|
||
f' <span class="doc-meta-value">{v}</span>\n'
|
||
f' </div>\n' for k, v in spec["meta"])
|
||
mid = re.sub(r' <div class="doc-meta">.*?\n </div>\n',
|
||
f' <div class="doc-meta">\n{meta_html} </div>\n', mid, flags=re.S)
|
||
|
||
tail = SHELL_TAIL
|
||
tail = tail.replace(
|
||
"(<strong>v2.2 双角色 · 重构基线版</strong>)",
|
||
f"(<strong>{spec['closing']}</strong>)")
|
||
|
||
# ⚠️ 版式外壳是从旧版式文档抽取的,会把旧品牌字面一并带进来(曾因此漏改 4 份文档的
|
||
# <title>/<h1>/sidebar 品牌)。这里对「外壳」做一次品牌兜底归一,保证任何一次重新生成
|
||
# 都不会回退到旧口径。只作用于 head/mid/tail;body 内可能有意保留历史字面(如 P-10 行),不动。
|
||
BRAND = (
|
||
("XX科技·智能财富管家系统", "南方基金·智能服务系统"),
|
||
("XX科技 · 智能金融事业部", "南方基金 · 智能服务事业部"),
|
||
("XX科技", "南方基金"),
|
||
("智能财富管家系统", "智能服务系统"),
|
||
("南方财富管理有限公司", "南方基金管理股份有限公司"),
|
||
("南方财富", "南方基金"),
|
||
("400-XXX-XXXX", "400-889-8899"),
|
||
("400-826-9518", "400-889-8899"),
|
||
("nanfangwm.com", "nffund.com"),
|
||
)
|
||
head, mid, tail = (
|
||
__import__("functools").reduce(
|
||
lambda s, kv: s.replace(kv[0], kv[1]), BRAND, part)
|
||
for part in (head, mid, tail)
|
||
)
|
||
|
||
body = Path(spec["body_file"]).read_text(encoding="utf-8")
|
||
return head + "\n" + toc_html + mid + body + tail
|
||
|
||
|
||
def main() -> None:
|
||
spec = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8"))
|
||
OUT_DIR.mkdir(parents=True, exist_ok=True)
|
||
out = OUT_DIR / spec["out_file"]
|
||
html = build(spec)
|
||
out.write_text(html, encoding="utf-8")
|
||
print(f"OK {out} ({html.count(chr(10)) + 1} lines)")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|