diff --git a/docs/superpowers/plans/2026-10-20-slice-wxr-adapter-phase2.md b/docs/superpowers/plans/2026-10-20-slice-wxr-adapter-phase2.md index 0a95d76..9631b9d 100644 --- a/docs/superpowers/plans/2026-10-20-slice-wxr-adapter-phase2.md +++ b/docs/superpowers/plans/2026-10-20-slice-wxr-adapter-phase2.md @@ -95,7 +95,7 @@ cms-one/ - [ ] Step 2 实现 `mapItem(item, config): MappedResult`(config: 类型路由表 {post:'wp:post', page:'wp:page', product:'wp:product', attachment:'wp:attachment'} + site locale)——纯函数 - [ ] Step 3 绿 → commit `feat(adapter-wp): WXR→UniversalRow 映射器(status 值域/悬空保留/unsupported 分流)` -### Task 5: HTML 白名单清洗 +### Task 5: HTML 白名单清洗 ✅ 完成(8dd6eaf + 修复 7506fef,双审通过;228 测试;审查修复:srcset 审计三子情形精确化(parse-srcset 直依赖)、false 形态防御、**镜像改为直调 launder.naughtyHref(与库同一函数零漂移)**、等价测试参数化 15 对抗样本) **Files:** Create `src/sanitize/html.ts`(sanitize-html 锁 ^2);Test `test/html-sanitize.test.ts` @@ -111,7 +111,8 @@ cms-one/ **Files:** Create `src/adapter.ts`(parse→map→sanitize 串联为 `AsyncIterable`,RowResult = {kind:'row', row}|{kind:'unsupported',…}|{kind:'error',…});`src/report.ts`(计数汇总);Test `test/adapter.test.ts` -- [ ] Step 1 失败测试:mini.xml 全链 → 3 row(post/product/page)+ 2 attachment row + 0 unsupported + 差异计数(wppop 解析 1 成功);报告结构与 spec §7.7 字段对齐(**首条版本块:Adapter/Mapping/Sanitizer 版本常量**、按类型 Created/Skipped/Failed/Unsupported、清洗统计、行级错误带 rawLocation) +- [ ] Step 1 失败测试:mini.xml 全链 → 3 row(post/product/page)+ 2 attachment row + 0 unsupported + 差异计数(wppop 解析 1 成功);报告结构与 spec §7.7 字段对齐(**首条版本块:Adapter/Mapping/Sanitizer 版本常量——sanitize 模块需导出 SANITIZER_VERSION**、按类型 Created/Skipped/Failed/Unsupported、清洗统计、行级错误带 rawLocation) +- [ ] **Task 5 审查移交的对接清单**:HTML 报告渲染 SanitizeDiff.excerpt 时必须转义 `<>&"`(含恶意标记注入面测试);"内容变更"统计用 `html !== input` 字符串比较(diffs.length===0 ≠ 未变更——空值属性删除/裸 & 转义零 diff);清洗统计按 diff.type 分计数;清洗耗时纳入报告耗时统计(timeBudgetMs 是解析侧预算) - [ ] Step 2 实现(含 validateUniversalRow **加法式**检查:复用 isUniversalRow + 返回 issue[] 的浅扩展,不动 contracts 包) - [ ] Step 3 绿 → commit `feat(adapter-wp): 全链组装与导入前报告`