# Wiki 详设页面 AI 操作手册（读取 / 编辑 / 更新）

> **归属**：`xiaoma-generate-detail-design` skill（本文在 `reference/operation-manual.md`）
> **目标系统**：公司 Confluence Wiki（`http://wiki.corp.yljr.com`，Atlassian Confluence Server/DC）
> **运行环境**：**仅 Windows**，脚本全部为 `.ps1`，用系统自带 PowerShell 5.1 运行，零额外依赖（无需 python/curl/Git Bash）。
> **用途**：根据已生成的 `epic.md`、架构设计文档、需求文档，结合用户提供的详设页面地址和账号密码，自动帮用户生成/更新详细设计。
> **适用对象**：AI Agent（Claude Code / Cursor）。写操作优先走 HTTP。
> **配套脚本**：`../scripts/*.ps1`（preflight/login/read/update/verify + common.ps1），均已参数化、实测跑通，见 `portability-audit.md`。

---

## 0. 能力总览与路径选择

| 路径 | 说明 | 适用场景 |
|---|---|---|
| **C：命令行 HTTP（默认，首选）** | `../scripts/*.ps1` 调 REST，用系统自带 PowerShell 5.1，**零额外依赖（无需 python/curl/Git Bash）** | **默认路径**。无需任何 MCP，最稳 |
| **A：HTTP REST（浏览器内）** | 已登录页面里 `browser_evaluate` fetch（自动带 Cookie） | 会话已加载 Playwright MCP 且需要浏览器上下文时 |
| **B：UI 操作（Playwright）** | 点「编辑」进富文本编辑器 → 注入内容 → 点「更新」 | REST 被禁（403/404）、或必须走草稿流程时兜底 |

### 路径 C 脚本命令（均在项目根目录执行，env 传参）

调用统一为 `powershell.exe -NoProfile -ExecutionPolicy Bypass -File <脚本>`：

| 步骤 | 脚本 |
|---|---|
| 自检 | `wiki-preflight.ps1` |
| 登录 | `wiki-login.ps1` |
| 读取+备份 | `wiki-read.ps1` |
| 更新 | `wiki-update.ps1 [内容文件]` |
| 校验 | `wiki-verify.ps1 <标志\|@文件>` |

每个脚本**自行登录**（无跨进程会话状态、不依赖 cookie 文件，天然免疫会话过期）。
⚠️ PS 5.1 对无 charset 的 JSON 响应会误解码 UTF-8，脚本已用 `Invoke-WebRequest + 手动 UTF-8 解码` 修复；**不要**改用 `Invoke-RestMethod` 读正文。

**路径选择规则**：

```
preflight 自检通过 → C（脚本 REST 读写，默认）
      │
   REST 写返回 403/404（被禁/无权限）
      ↓
B（UI 编辑器全流程）──仍失败──→ 截图留证，停下报告用户
      │
（会话有 Playwright 且偏好浏览器）──→ A（浏览器内 fetch，等价 C）
```

> 脚本仅依赖 Windows 自带的 PowerShell 5.1（无需 python/curl/Git Bash）。换机器先跑 `wiki-preflight.ps1`。

---

## 1. 第 0 步：开工门禁 —— 先确认账户、密码、要修改的网页（确认前不发起任何网络操作，preflight 也不做）

**硬门禁**：6 项逐项问到、拼卡片回读，拿到用户明确「确认」后才允许登录/读取/编辑/更新。
其中 **①账户、②密码、③目标页面** 是三项绝对必需项，缺任何一项都不许开工。
任何一项不许自行推断、从历史会话猜、或沿用上次会话的值。

| # | 信息 | 怎么取 / 校验 | 缺失或不确定时 |
|---|---|---|---|
| 1 | **账户** `WIKI_USER` | 用户提供的 Wiki 登录账号；登录后用 `session_user` 回读核对 | 停下来问，不猜、不沿用上次 |
| 2 | **密码** `WIKI_PASS` | 用户提供的明文密码；**仅内存，不落盘、不回显**（卡片写「已提供」） | 停下来问 |
| 3 | **目标页面 URL** `PAGE_ID` | 用户提供的 `viewpage.action?pageId=…`；解析 pageId，回读确认「要改的就是这个页面吗」。**只操作此页面** | 停下来问；解析不出 pageId 让用户重给 |
| 4 | **源文档清单** | `epic.md`、架构设计、需求文档本地路径，必须真实存在可读 | 缺哪个问哪个；无源文档的纯测试要明说「最小改动」 |
| 5 | **更新方式** | 默认「**合并修改**」（保留原骨架只改相关章节）；「整页覆盖」须用户明说 | 默认合并修改，也要列出让用户否决 |
| 6 | **编辑入口** | 用户是否直接给编辑页 URL（`resumedraft.action?draftId=...`） | 没给则自行进入，不追问 |

### 确认卡片（回读格式）

```
写入 Wiki 详设前请确认以下信息：

  1. 账户        : zhangsan01
  2. 密码        : 已提供（仅内存使用，不落盘/不回显）
  3. 目标页面    : http://wiki.corp.yljr.com/pages/viewpage.action?pageId=123456789
                   （pageId = 123456789，只操作此页面）
  4. 源文档      : epic.md ✓ / 架构设计.md ✓ / 需求文档.md ✓（路径：……）
  5. 更新方式    : 合并修改（保留原页面骨架）   ← 默认值，可改为「整页覆盖」
  6. 编辑入口    : 未提供，将自行进入编辑页

以上确认无误请回复「确认」，我再开始登录并读取页面。
```

### 门禁规则

- 每项显示**实际值**，不许写「同上」「已确认」糊弄；账户/密码/目标页面三项尤其逐字回读。
- 默认值（更新方式）必须显式列出，让用户有机会否决。
- 用户只改一部分（如「页面换成 xxx」「账户换一个」）→ 改完重新回读整张卡片，再等确认。
- ⛔ 用户没回「确认」前，一个网络动作都不做——不 preflight、不登录、不读、不写。
- 确认后若登录返回的 `session_user` 与账户不符，或 pageId 对应页面与用户描述不符 → 立即停下再核对，不硬闯。

---

## 2. 登录

### 2.1 路径 C：脚本登录（默认）

```bash
WIKI_USER='zhangsan01' WIKI_PASS='***' powershell.exe -NoProfile -ExecutionPolicy Bypass -File .claude\skills\xiaoma-generate-detail-design\scripts\wiki-login.ps1
```

- 看到 `login_ok=1 session_user=<账户>` 即成功（会话只存在于脚本进程内存，**不落 cookie 文件**——每个脚本自行登录，见 §0 会话模型）。
- 退出码：`3`=登录失败（仍停在登录页：账密错/锁定/验证码），`4`=会话用户与 `WIKI_USER` 不符。
- **失败即停问用户，不反复试密码。**

### 2.2 路径 B：UI 登录（Playwright，仅当走 UI 全流程）

1. `browser_navigate` → `http://wiki.corp.yljr.com/login.action`
2. `browser_snapshot` 定位元素，候选选择器：

| 元素 | 候选选择器（按顺序试） |
|---|---|
| 用户名框 | `#os_username` → `input[name="os_username"]` → 快照里「用户名/账号」textbox |
| 密码框 | `#os_password` → `input[name="os_password"]` → 快照里「密码」textbox |
| 登录按钮 | `#loginButton` → 文本「登 录/登录」的 button |

3. `browser_type` 填账户/密码 → `browser_click` 登录 → `browser_wait_for` URL 不含 `login.action`（15s）。
4. `browser_snapshot` 见当前用户即成功；出现「用户名或密码不正确」「登录失败」→ 停止报告。

### 2.3 登录异常处理

| 现象 | 处理 |
|---|---|
| 出现验证码 | 请用户人工过验证码；或换路径 |
| 跳 CAS/SSO | 顺着跳转身后的表单填；不认识就问用户 |
| 账号锁定/禁用 | 停止，报告用户 |
| 登录成功但目标页无权限 | 停止，请用户确认账号有该页面**编辑权限** |

> ⚠️ **会话校验接口**：用 `/rest/api/user/current`。本机 Confluence 上不带参数的 `/rest/api/user` 会返回 `400`（"Only one query param of key or username is required"）。

---

## 3. 读取页面内容

### 3.1 解析 pageId

从用户 URL query 取 `pageId`。REST、备份文件名、后续操作都用它。

### 3.2 路径 C：脚本读取 + 强制备份（默认，可编辑格式）

```bash
PAGE_ID='123456789' powershell.exe -NoProfile -ExecutionPolicy Bypass -File .claude\skills\xiaoma-generate-detail-design\scripts\wiki-read.ps1
```

产出（脚本自动完成）：
- `backup/page-{pageId}-{时间戳}.xhtml` —— **原文 storage 备份，回滚依据。无备份禁止更新。**
- `.tmp/wiki-page-state.json` —— `title/version/bodyLen/backup`。**页面 title 从这里读**（控制台中文可能乱码）。
- `.tmp/wiki-headings.txt` —— h1~h6 章节树（UTF-8），供合并前分析骨架。

退出码：`5`=未登录/会话过期/页面不可读 → 先跑 `wiki-login.ps1` 再重试（401→重登，403→无权限，404→pageId 错）。

底层等价 REST（路径 A 浏览器内或手工调用）：
`GET /rest/api/content/{id}?expand=body.storage,version,space`，取 `body.storage.value`。storage 是**可编辑格式**（XHTML）。

### 3.3 路径 B：UI 读取（渲染后的可读内容，仅辅助理解）

`browser_navigate` viewpage → `browser_snapshot`（长页面先 `window.scrollTo` 再分段）→ `browser_evaluate` `document.querySelector('#main-content')?.innerText`。
⚠️ 这是渲染结果，只用于理解；真正回写依据必须是 §3.2 的 storage 原文。

### 3.4 已有草稿（用户给的编辑入口是 resumedraft.action）

- 草稿内容 = 编辑页加载出来的内容；进编辑器后可先取出草稿正文。
- 尽力而为接口：`GET /rest/tinymce/1/drafts/{draftId}`。
- **草稿 ≠ 已发布内容**：REST 发布的是页面正文；发布成功后旧草稿仍在，用户下次点「编辑」会被提示恢复旧草稿（见 §5.1 收尾）。

---

## 4. 生成详设内容（源文档 → 页面内容）

### 4.1 读取源文档

按门禁清单用 Read 读 `epic.md`、架构设计、需求文档全文，提取：背景目标、范围、功能点、接口清单、表结构、流程时序、配置变更、非功能要求。

### 4.2 章节骨架与来源映射（推荐模板）

| # | 章节 | 来源 | 要点 |
|---|---|---|---|
| 1 | 需求概述 | 需求文档 + epic.md | 背景、目标、范围、明确不做的事 |
| 2 | 名词解释 | 需求文档 | 术语表 |
| 3 | 总体方案 | 架构设计文档 | 架构图、模块划分、技术选型、上下游依赖 |
| 4 | 详细设计 | 架构 + 需求（有代码仓库时结合代码） | 4.1 接口设计（路径/方法/出入参/错误码）；4.2 数据库设计（DDL、索引）；4.3 核心流程时序；4.4 配置变更（nacos/MQ/定时任务/防火墙） |
| 5 | 非功能设计 | 架构设计文档 | 性能、容量、幂等、事务、日志、监控告警、安全 |
| 6 | 影响面与兼容性 | 综合 | 上下游影响、灰度、存量数据兼容 |
| 7 | 测试与验收 | 需求文档 | 测试要点、验收标准 |
| 8 | 上线与回滚方案 | 综合 | 上线步骤、回滚、应急预案 |

> 源文档没有的章节**不许编造**：留空并标「待补充（源文档未涉及）」，在变更摘要里说明。

### 4.3 合并策略（默认「合并修改」）

1. 用 `.tmp/wiki-headings.txt` / 解析 storage 原文拿 `h1/h2` 骨架；
2. 与原骨架**同名/同主题**章节 → 原位替换；
3. **新增**章节 → 按原页面排序追加（默认正文末尾、附录之前）；
4. 本次**不涉及**章节 → 原样保留 storage 片段，一字不动；
5. 「整页覆盖」仅用户明确要求时使用，且覆盖前再次声明。

### 4.4 Markdown → Confluence storage 转换表

| Markdown | storage XHTML |
|---|---|
| `# 标题`~`######` | `<h1>`~`<h6>` |
| 段落 | `<p>文本</p>` |
| `**加粗**` / `*斜体*` | `<strong>` / `<em>` |
| `` `行内代码` `` | `<code>…</code>` |
| `- 列表` / `1. 列表` | `<ul><li>…</li></ul>` / `<ol><li>…</li></ol>` |
| `[文字](url)` | `<a href="url">文字</a>` |
| 表格 | `<table><tbody><tr><th>列</th></tr><tr><td>值</td></tr></tbody></table>`（storage 里 `th` 也在 `tbody` 内） |
| `---` | `<hr />` |
| `> 引用` | `<blockquote><p>…</p></blockquote>` |
| 代码块 | code 宏（见下） |
| 提示框 | info/note/warning/tip 宏（见下） |

**代码块宏**：

```xml
<ac:structured-macro ac:name="code">
  <ac:parameter ac:name="language">java</ac:parameter>
  <ac:plain-text-body><![CDATA[
public void demo() { }
]]></ac:plain-text-body>
</ac:structured-macro>
```

**提示框宏**（name 可换 `info`/`note`/`warning`/`tip`）：

```xml
<ac:structured-macro ac:name="info">
  <ac:parameter ac:name="title">说明</ac:parameter>
  <ac:rich-text-body><p>提示内容</p></ac:rich-text-body>
</ac:structured-macro>
```

**图片**（引用附件，先上传，见附录 C.3）：

```xml
<ac:image ac:width="600"><ri:attachment ri:filename="arch.png" /></ac:image>
```

### 4.5 生成物落盘 + 变更摘要确认（第二道门禁）

1. 新 storage 全文写 `output/page-{pageId}-new.xhtml`；
2. 生成**变更摘要**：改了/新增哪些章节、每章大致内容、总字符数变化、是否含代码块/表格/图片；
3. 摘要回读用户，**再次等「确认」后才执行 §5 更新**。未确认不得 PUT / 点「更新」。

---

## 5. 更新页面内容

### 5.1 路径 C：脚本 PUT 发布（默认）

```bash
# 中文版本备注写 UTF-8 文件经 VERSION_MSG_FILE 传（经 Git Bash 传中文 env 可能乱码）
PAGE_ID='123456789' VERSION_MSG_FILE='.tmp\version-msg.txt' powershell.exe -NoProfile -ExecutionPolicy Bypass -File .claude\skills\xiaoma-generate-detail-design\scripts\wiki-update.ps1
```

- 默认内容文件 `output\page-{PAGE_ID}-new.xhtml`；**回滚**时传备份文件路径作第一参数。
- 提交前**实时读最新版本号**，与 read 记录的基线不一致 → `version_drift` 退出 `4`（防覆盖他人改动）；确认后 `FORCE=1` 重试。
- `DRY_RUN=1`：只组装报文到 `.tmp\wiki-payload.json` 不提交（更新前预检）。
- PUT 成功见 `put_status=200 update_ok=1`。

结果处理：

| 状态码/退出码 | 含义 | 处理 |
|---|---|---|
| 200 / `update_ok=1` | 成功 | 进 §6 校验 |
| 退出 4 `version_drift` | 有人并发改 | 重跑 `wiki-read.ps1` 重新合并再试（≤2 次） |
| 401 | 会话过期 | 重跑 `wiki-login.ps1` 后重试 |
| 403 | 无编辑权限/REST 写被禁 | 报告用户；仅 REST 被禁 → 走路径 B |
| 404 | pageId 错/页面删 | 停止，核对 URL |
| 409 | 版本冲突 | 重读最新 version 与正文重并再 PUT（≤2 次） |
| 413 | 内容过大 | 精简或拆分 |

**收尾（提醒用户，默认不自动执行）**：REST 直接发布后旧草稿可能仍挂着，用户下次点「编辑」会被提示恢复旧草稿。清理接口 `DELETE /rest/tinymce/1/content/{pageId}/draft`，**须征得用户同意**；不执行就在报告里提醒。

### 5.2 路径 B：UI 编辑器更新（兜底）

**进入编辑器**（按序试）：用户给的 `resumedraft.action?...` 直接导航；否则 viewpage 上点「编辑」（`#editPageLink` → 文本「编辑」）；仍找不到 → 直接 `browser_navigate` `editpage.action?pageId={pageId}`（有草稿会自动转 resumedraft，正常）。

**等编辑器就绪**：

```js
(async () => {
  for (let i = 0; i < 20; i++) {
    if (document.querySelector('iframe#wysiwyg') || document.querySelector('[contenteditable="true"]')) return 'ready';
    await new Promise(r => setTimeout(r, 500));
  }
  return 'timeout';
})()
```

**注入新内容**（三级兜底）：

```js
(() => {
  const html = window.__draft;
  if (window.tinymce && tinymce.activeEditor) { tinymce.activeEditor.setContent(html); tinymce.activeEditor.fire('change'); return 'tinymce'; }
  const f = document.querySelector('iframe#wysiwyg');
  if (f && f.contentDocument) { const d=f.contentDocument, b=d.getElementById('tinymce')||d.body; b.innerHTML=html; d.dispatchEvent(new Event('input',{bubbles:true})); return 'iframe'; }
  const ce = document.querySelector('[contenteditable="true"]');
  if (ce) { ce.innerHTML=html; ce.dispatchEvent(new Event('input',{bubbles:true})); return 'contenteditable'; }
  return 'not-found';
})()
```

**点「更新」**：`browser_snapshot` 找 `#rte-button-publish` / 文本「更新/保存」→ `browser_click`；置灰 = 未标脏 → 补 `tinymce.activeEditor.fire('change')`；中途弹窗用 `browser_handle_dialog`。成功标志：`browser_wait_for` URL 回到 `viewpage.action?pageId={pageId}`。

### 5.3 路径 A：浏览器内 fetch（会话有 Playwright 时等价 C）

```js
(async () => {
  const pageId = '123456789';
  const meta = await (await fetch('/rest/api/content/' + pageId + '?expand=version,title', { credentials: 'include' })).json();
  const payload = { id: pageId, type: 'page', status: 'current', title: meta.title,
    version: { number: meta.version.number + 1, message: 'AI 生成详设' },
    body: { storage: { value: window.__draft, representation: 'storage' } } };
  const r = await fetch('/rest/api/content/' + pageId, { method: 'PUT', credentials: 'include',
    headers: { 'Content-Type': 'application/json; charset=utf-8' }, body: JSON.stringify(payload) });
  return { status: r.status, preview: (await r.text()).slice(0, 500) };
})()
```

---

## 6. 更新后校验（必做）

```bash
PAGE_ID='123456789' powershell.exe -NoProfile -ExecutionPolicy Bypass -File .claude\skills\xiaoma-generate-detail-design\scripts\wiki-verify.ps1 '@.tmp\marker.txt'
```

- 标志文本先写进文件再用 `@文件` 传（避免命令行引号/编码问题）。
- 见 `version_now=<旧+1> marker_found=true`。退出 `7`=标志未找到。
- 再加页面校验：`browser_navigate` viewpage → `browser_snapshot` 确认渲染 → `browser_take_screenshot` 留证（可选）。
- **交付报告**：旧→新版本号、字符数变化、章节变更清单、备份路径、是否残留旧草稿。

---

## 7. 回滚

- **首选**：把备份作为内容再 PUT（版本 +1，`VERSION_MSG='回滚至 vX'`）：
  `wiki-update.ps1 backup\page-{pageId}-{时间戳}.xhtml`。
- **备选**：指导用户 UI：页面「…」→ 页面信息/历史版本 → 选旧版本恢复。
- 回滚也是写操作：**执行前向用户回读确认**。

---

## 8. 故障排查速查表

| # | 现象 | 可能原因 | 处理 |
|---|---|---|---|
| 1 | 登录提交后仍停在登录页 | 账密错/锁定 | 停止问用户，不反复试密码 |
| 2 | 登录页有验证码 | 风控 | 请用户人工过；或换路径 |
| 3 | 跳 CAS/SSO | 统一认证 | 顺着跳转填；不认识就问 |
| 4 | REST 返回 401 | 会话过期 | 重跑 `wiki-login.ps1` |
| 5 | REST 返回 403 | 无权限/REST 被禁 | 请用户确认编辑权限；纯 REST 被禁转路径 B |
| 6 | REST 返回 404 | pageId 错/页面删 | 停止核对 URL |
| 7 | PUT 409 / `version_drift` | 并发编辑 | 重读最新版本合并重试（≤2 次） |
| 8 | 编辑页白屏/iframe 不出现 | 编辑器加载慢 | 加长 `browser_wait_for`；仍不行转 REST |
| 9 | 「更新」按钮置灰 | 脏状态未触发 | 补 `fire('change')` |
| 10 | 「草稿被他人锁定」 | 他人正在编辑 | 请用户联系释放；删草稿须同意 |
| 11 | 保存跳到「版本比较/冲突」 | 发布冲突 | 按 409 重取重并 |
| 12 | 内容过大 413 | 超限 | 减块大小/精简；仍不行问用户 |
| 13 | 更新成功但内容缺失/错乱 | XHTML 有问题 | **立即用备份回滚（§7）**，修正后重来 |
| 14 | `/rest/api/user` 返回 400 | 该接口要 query 参数 | 用 `/rest/api/user/current` |
| 15 | 控制台中文乱码 | 终端 GBK 编码 | 以 UTF-8 文件为准（state/backup/headings），非数据损坏 |
| 16 | Claude Code Bash 工具内联命令单引号冲突 | 命令被 eval 包裹后单引号串解析错乱 | 把命令写进脚本文件再执行，不写含单引号的长内联命令 |
| 17 | PowerShell 读到的正文比实际长（中文变乱码） | PS 5.1 对无 charset 的 `application/json` 响应按单字节误解码 UTF-8 | 必须用脚本内置的 `Invoke-WebRequest + 手动 UTF-8 解码`（common.ps1 `Read-WikiJson`），**禁止**改用 `Invoke-RestMethod` 读正文 |
| 18 | .ps1 里的中文字面量变乱码 | PS 5.1 读无 BOM .ps1 按 ANSI | .ps1 源码保持纯 ASCII，中文一律走 UTF-8 文件或 env |
| 19 | Git Bash 传中文 env 给 powershell 后乱码 | 代码页转换 | 中文内容写 UTF-8 文件，用 `VERSION_MSG_FILE` / `@文件` 传 |
| 20 | 快照里找不到「编辑」按钮 | 无权限或在「…」菜单 | 直接访问 `editpage.action?pageId=`；403 停下问权限 |

---

## 9. 安全与纪律

1. **只动用户点名的那一个页面**。不浏览/修改/创建/删除其它页面。
2. **密码只存在于内存**：不写入文件/日志/commit/确认卡片（卡片写「已提供」）。
3. **更新前必备份**（§3.2），无备份不更新。
4. **两次确认门禁**：开工 6 项（§1）+ 更新前变更摘要（§4.5）。用户没说「确认」，一个写动作都不做。
5. 保持页面标题不变（除非用户明确要求改）。
6. 409/401 自动重试 ≤ 2 次；403/404 立即停止报告。
7. 删除草稿、覆盖整页、回滚，均须用户单独点头。
8. 源文档没有的信息不编造，标「待补充」并在摘要说明。
9. 全流程结束后，把备份路径、截图、新版本号一并报告。

---

## 附录 A：Confluence REST API 速查

| 用途 | 请求 |
|---|---|
| 当前用户（验证会话） | `GET /rest/api/user/current`（不带参数的 `/rest/api/user` 部分版本返回 400） |
| 读页面 | `GET /rest/api/content/{id}?expand=body.storage,version,space` |
| 只读渲染版（不可回写） | `GET /rest/api/content/{id}?expand=body.view` |
| 更新页面 | `PUT /rest/api/content/{id}`（`version.number`=现值+1） |
| 新建页面 | `POST /rest/api/content`（需 `space.key`/`title`/`type:"page"`） |
| 按标题查 | `GET /rest/api/content?spaceKey={key}&title={title}&expand=version` |
| 版本历史 | `GET /rest/api/content/{id}/version` |
| 删旧草稿（须用户同意） | `DELETE /rest/tinymce/1/content/{pageId}/draft` |
| 上传附件/图片 | `POST /rest/api/content/{id}/child/attachment`，须带 `X-Atlassian-Token: no-check` |

## 附录 B：Playwright MCP 工具速查（路径 A/B 用）

`browser_navigate`/`browser_snapshot`（点击前先快照拿 ref）/`browser_click`/`browser_type`/`browser_fill_form`/`browser_press_key`/`browser_evaluate`（REST 读写、注入、轮询）/`browser_wait_for`/`browser_take_screenshot`/`browser_handle_dialog`/`browser_tab_list`/`browser_tab_select`/`browser_network_requests`/`browser_console_messages`/`browser_navigate_back`/`browser_close`。

## 附录 C：常用片段

### C.1 会话自检（浏览器内）

```js
(async () => {
  const r = await fetch('/rest/api/user/current', { credentials: 'include' });
  if (!r.ok) return { loggedIn: false, status: r.status };
  const j = await r.json();
  return { loggedIn: true, user: j.username };
})()
```

### C.2 读取章节骨架

```js
(() => {
  const t = document.createElement('div');
  t.innerHTML = window.__page.body;
  return Array.from(t.querySelectorAll('h1,h2')).map(h => h.tagName + ': ' + h.textContent.trim());
})()
```

### C.3 上传图片附件（PowerShell 7+）

```powershell
Invoke-RestMethod -Method POST -Uri 'http://wiki.corp.yljr.com/rest/api/content/123456789/child/attachment' `
  -WebSession $wiki -Headers @{ 'X-Atlassian-Token' = 'no-check' } `
  -Form @{ file = Get-Item .\arch.png; comment = '架构图' }
```

## 附录 D：完整走查示例（真实场景，已实测）

1. 用户提供：账户 `zhangsan01`、密码（内存）、页面 `viewpage.action?pageId=123456789`、源文档路径；
2. §1 门禁确认 → 用户回「确认」；
3. `wiki-login.ps1` 登录成功（session_user=zhangsan01）；
4. `wiki-read.ps1` 读 `123456789`：title=「示例项目_详细设计说明书_后端」/version=25/body=40192，备份 `backup/page-123456789-20260813-110257.xhtml`；
5. §4 生成新 storage → `output/page-123456789-new.xhtml` → 变更摘要回读 → 用户回「确认」；
6. `wiki-update.ps1` PUT（version 25→26）→ `update_ok=1`；
7. `wiki-verify.ps1` 回读：`version_now=26 marker_found=true`；
8. 交付报告：新版本号、章节变更、备份路径、旧草稿（draftId=123456790）是否清理建议。

---

*手册版本：v4.0（2026-08-13，精简为仅 Windows：路径 C 全部 `.ps1` + PowerShell 5.1 零依赖，已实测）。Wiki 升级 Confluence 大版本后，若选择器/接口变化，优先按 §8 定位，再更新本手册与 `portability-audit.md`。*
