# 可移植性审计报告（xiaoma-generate-detail-design skill）

> 审计日期：2026-08-13 ｜ 审计方式：真实环境全链路实测（Windows 10，目标 wiki.corp.yljr.com，详设页面实测 v25→v26）+ 脚本参数化重构后复测。
> 范围声明：**本 skill 仅面向 Windows**（用户确认不需要 macOS/Linux）。脚本全部为 `.ps1`，用系统自带 PowerShell 5.1 运行。

## 结论

skill 在 Windows 上**零额外依赖、开箱即用**：
- 全部脚本为 `.ps1`，只依赖 **Windows 系统自带的 PowerShell 5.1**（原生 JSON + HTTP）。
- **不需要 python、不需要 curl、不需要 Git Bash、不需要 Playwright MCP**。
- 同一内网 Confluence 下任何 Windows 机器直接可用；换公司/换实例按文末「迁移清单」改环境变量即可。

> **python 依赖的来历与消除**：v1 脚本（bash+curl）用 python 做 JSON 解析/构造，没装 python 的机器跑不了。v2 改用 PowerShell 实现后彻底去掉 python；随后按「仅 Windows」要求删除了全部 `.sh` 实现，只保留 `.ps1`。

## 1. 依赖项审计

| 依赖 | 必需 | 探测方式 | 缺失时的兜底 |
|---|---|---|---|
| **PowerShell 5.1+** | 是（系统自带） | `wiki-preflight.ps1` 打印 `powershell=<版本>` | Windows 10/11、Server 均自带，无缺失场景 |
| Playwright MCP | **否** | 会话内是否有 `browser_*` 工具 | 脚本路径 C 覆盖全流程（实测在无 MCP 环境跑通） |
| 网络 | 是 | `wiki-preflight.ps1`（login_page=200 / rest_unauth=401 或 200） | 确认内网可达；检查代理环境变量 |
| Confluence 版本 | ≥5.5 | REST `/rest/api/content` 存在性 | 低于 5.5 无 REST，只能走 UI 路径 B |

## 2. 硬编码清理对照（重构前 → 重构后）

| 项 | 重构前（.tmp 一次性脚本） | 重构后（skill scripts/*.ps1） |
|---|---|---|
| python 路径 | 本机 Python312 安装路径写死 | **已整体移除 python 依赖** |
| 项目目录 | `cd /d/project/jiaofu` 写死 | 以 CWD 为项目根，脚本自身位置用 `$PSScriptRoot` 解析 |
| 账户 | 个人账户写死 | `WIKI_USER` 环境变量 |
| 密码 | 从需求文档硬解析 | `WIKI_PASS` 环境变量（仅内存，不落盘） |
| pageId | 页面 ID 写死 | `PAGE_ID` 环境变量 |
| wiki 域名 | `wiki.corp.yljr.com` 写死 | `WIKI_BASE` 环境变量（默认本域名，在 `common.ps1`） |
| 版本备注 | 写死文案 | `VERSION_MSG` / `VERSION_MSG_FILE` 环境变量 |
| 内容文件 | 固定 output 路径 | `wiki-update.ps1` 第一参数（默认 output，传备份文件即回滚） |

## 3. 已知坑清单（全部实测踩过并已修复）

| # | 坑 | 现象 | 规避 |
|---|---|---|---|
| 1 | `/rest/api/user` 不带参数返回 400 | "Only one query param of key or username is required" | 会话校验统一用 `/rest/api/user/current`（脚本已内置） |
| 2 | Git Bash / 控制台 GBK 编码 | 中文输出全部乱码（但数据完好） | 脚本输出全部为 ASCII `key=value`；中文内容只写 UTF-8 文件（state/backup/headings）；**判断数据以文件为准，别看控制台** |
| 3 | Confluence 保存时规范化实体 | `→` 变 `&rarr;`，回读长度比本地多几个字符 | 属正常（±少量字符），校验用标志文本而非精确长度 |
| 4 | PUT 版本号必须 = 当前+1 | 版本不对返回 409 | `wiki-update.ps1` 提交前实时读最新版本；与读取基线不一致 → `version_drift` 退出 4，防覆盖他人并发修改（`FORCE=1` 显式覆盖） |
| 5 | REST 直更不清草稿 | 页面残留旧草稿，下次 UI 编辑被提示恢复 | 交付报告提醒用户；清理须用户同意（`DELETE /rest/tinymce/1/content/{id}/draft`） |
| 6 | **PS 5.1 误读无 charset 的 JSON 响应** | `Invoke-RestMethod` 读到 body_len=48749（正确是 40327），中文全变乱码——Confluence 返回 `Content-Type: application/json` 不带 charset，PS 按单字节误解码 UTF-8 | **所有 REST 读取走 `Invoke-WebRequest` + `RawContentStream` 手动 UTF-8 解码**（`common.ps1` 的 `Read-WikiJson`）。若误用旧方式 PUT 会把乱码写进页面，属致命坑 |
| 7 | .ps1 里的中文字面量变乱码 | PS 5.1 读无 BOM 的 .ps1 按 ANSI | 全部 .ps1 源码保持纯 ASCII；中文一律走 UTF-8 文件（`Read-Utf8`）或 env |
| 8 | Git Bash export 的中文 env 传给 powershell 后乱码 | POSIX→UTF-16 环境块按代码页转换 | 中文版本备注/标志文本写 UTF-8 文件，用 `VERSION_MSG_FILE` / `wiki-verify.ps1 '@文件'` 传 |
| 9 | PS 5.1 `ConvertTo-Json` 把 `<` `>` `&` 转成 Unicode 转义序列（反斜杠 u + 4 位十六进制形式） | 看起来像"被转义破坏" | 合法 JSON，Confluence 解析时还原为原字符，**无损**；已用回环测试验证 `body_roundtrip_equal=true`（payload 体积略增无碍） |
| 10 | ps1 跨进程无法共享 WebSession | 每个 powershell.exe 是独立进程 | 设计上**每个脚本自行登录**（凭据走 env），不依赖 cookie 文件，天然免疫会话过期/丢失 |
| 11 | Claude Code Bash 工具内联命令单引号冲突 | 命令被 eval 包裹后单引号串解析错乱、变量丢失 | **脚本化**：把命令写进文件再执行，不在 Bash 工具里写含单引号的长内联命令 |

## 4. 平台矩阵

| 平台 | 状态 | 说明 |
|---|---|---|
| Windows 10/11 + PowerShell 5.1（.ps1） | ✅ **实测全链路通过，零额外依赖** | 无需 python/curl/Git Bash；登录/读取/更新报文(DRY_RUN+回环校验)/校验 |
| Windows Server + PowerShell 5.1 | ✅ 理论通过 | 同套脚本，无桌面系统专属代码；注意 `Invoke-WebRequest -UseBasicParsing` 已加 |
| 其它 Windows（PS 7 / pwsh） | ✅ 理论通过 | 脚本按 5.1 兼容写法，PS 7 向下兼容 |
| macOS / Linux | — | **不在支持范围**（用户明确只用 Windows） |

## 5. 迁移清单（换公司 / 换 Confluence 实例）

1. `WIKI_BASE` 指向新域名（或改 `common.ps1` 里 `Get-WikiBase` 的默认值）。
2. 确认登录表单是 Confluence 标准 `dologin.action` + `os_username`/`os_password`（Atlassian 标准，一般不变）；若被 SSO/CAS 改造 → `operation-manual.md` §2.3。
3. `powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts\wiki-preflight.ps1` 验证：`login_page=200`、`rest_unauth=401 或 200`（匿名可读时是 200，非异常）。
4. REST 需要 Confluence ≥ 5.5；`/rest/api/content` 返回 404 → 见手册 §8。
5. 页面 URL 形态若不同（如带 context path `/wiki/...`），`WIKI_BASE` 带上 context path 即可（脚本全部基于 `$base` 拼接）。
6. 安全纪律不变：只动点名页面、备份先行、两次确认、密码不落盘。

## 6. 可复现验证命令（只读，不写页面）

```bash
# 从 Claude Code（Git Bash）调用，env 传参：
export WIKI_USER='<账户>' WIKI_PASS='***' PAGE_ID='<pageId>'
powershell.exe -NoProfile -ExecutionPolicy Bypass -File .claude\skills\xiaoma-generate-detail-design\scripts\wiki-preflight.ps1
powershell.exe -NoProfile -ExecutionPolicy Bypass -File .claude\skills\xiaoma-generate-detail-design\scripts\wiki-login.ps1
powershell.exe -NoProfile -ExecutionPolicy Bypass -File .claude\skills\xiaoma-generate-detail-design\scripts\wiki-read.ps1
# 更新报文预检（不提交）：
DRY_RUN=1 powershell.exe -NoProfile -ExecutionPolicy Bypass -File .claude\skills\xiaoma-generate-detail-design\scripts\wiki-update.ps1
```

## 7. 验证记录

| 日期 | 内容 | 结果 |
|---|---|---|
| 2026-08-13 | v1（bash+curl+python）一次性脚本实测：登录→读取→生成→PUT→回读（版本 v25→v26） | ✅（历史） |
| 2026-08-13 | v1 参数化重构 + 复测（preflight/login/read 只读链路） | ✅（历史） |
| 2026-08-13 | **去 python 化：新增 .ps1 实现（common/preflight/login/read/update/verify）** | ✅ preflight/login/read 全过；body_len=40327 与 v1 一致 |
| 2026-08-13 | 发现并修复 PS 5.1 无 charset 误解码坑（48749→40327） | ✅ `Invoke-WebRequest`+手动 UTF-8 解码，中文解码抽样核验无误 |
| 2026-08-13 | `wiki-update.ps1` DRY_RUN 报文组装 + JSON 回环校验 | ✅ `body_roundtrip_equal=true`，无写入 |
| 2026-08-13 | `wiki-verify.ps1 '@marker'` 只读校验线上页 | ✅ `marker_found=true` |
| 2026-08-13 | **按「仅 Windows」要求删除全部 .sh 实现，只保留 .ps1** | ✅ scripts/ 仅存 6 个 .ps1 |
