# 08 · AI 执行清单

> 用法：**开工前读第一段、实现中对照第二段、交付前逐条跑完第三段**；每一条都可勾选、可验证。
> 引用分册：技术栈与结构 `01-技术栈与项目结构.md`｜样式与构建 `02-样式与构建.md`｜布局与模板骨架 `03-布局与模板骨架.md`｜能力模块 `04-能力模块清单.md`｜代码片段 `05-代码片段库.md`｜SEO `06-SEO与Yoast集成.md`｜性能安全部署 `07-性能安全与部署.md`。
> 骨架来源：本技能 `templates/`（`package.json`、`assets/css/src/main.css`、`functions.php`、`header.php`、`footer.php`、`index.php`、`archive.php`、`search.php`、`404.php`、`page.php`、`inc/config.php`、`inc/seo.php`、`inc/comments.php`、`single.php`、`comments.php`、`template-parts/post-list.php`、`assets/css/theme.css`、`部署文档模板.md`、`打包命令.md`）。

---

## 一、动手前（开工前必须确认 / 读取）

- [ ] **确认技术栈与本规范一致**：经典 PHP 主题（非 FSE、非区块主题）+ Tailwind CSS 4 + daisyUI 5，样式由 `npm run build:css` 预编译成静态 `assets/css/tailwind.css`，前台不依赖 Node、不依赖 WindPress。
- [ ] **若项目要求 FSE / 区块主题（`theme.json`、`templates/*.html`）：先停下与用户确认偏离范围**，不擅自更换范式。
- [ ] **读取项目现有主题目录与 `package.json`**：确认 `build:css` / `watch:css` 脚本存在，并核对构建入口的 `@source` 扫描范围覆盖本项目要改的目录（根 `*.php`、`template-parts/*.php`、`inc/*.php`、`assets/js/*.js`）；新增目录必须补 `@source`。
- [ ] **确认站点是否启用 Yoast（或其它 SEO 插件）**：明确 title / description / canonical / robots / OG / Twitter / JSON-LD / sitemap 由插件负责，**主题不得重复输出**；换插件时先查其钩子名与 meta key，不照抄 `wpseo_*`（见 `06-SEO与Yoast集成.md`）。
- [ ] **确认站点是否在 CDN / 反向代理之后**：决定是否配置真实访客 IP 的来源请求头（Cloudflare = `HTTP_CF_CONNECTING_IP`，通用反代 = `HTTP_X_FORWARDED_FOR`），写入 `XY_MBX_PROXY_IP_HEADER`；不配的后果是全站共用一个限流桶。
- [ ] **确认要实现的「能力模块」**：对照 `04-能力模块清单.md` 按项目类型（博客 / 音乐站 / 视频站 / 工具站）逐项勾选，**只实现被勾选项**。
- [ ] **确认页面清单与每页 h1 落点**：对照 `03-布局与模板骨架.md` 的 h1 表格逐页确认，明确「每页有且只有一个 `h1`」、列表卡片标题用 `h2`。
- [ ] **列出本项目改动会碰到的既有文件清单**（含新建文件），确认清单之外的文件一律不动。
- [ ] **不确定就提问**：技术栈、能力模块、SEO 归属、CDN 场景任一项无法确认时，先问用户，不擅自扩大范围。

---

## 二、实现中（按顺序执行）

### 骨架起手
- [ ] 从 `templates/` 按「落地位置」复制所需骨架：`package.json`、`assets/css/src/main.css`、`functions.php`、`header.php`、`footer.php`、`index.php`、`archive.php`、`search.php`、`404.php`、`page.php`、`inc/config.php`、`inc/seo.php`、`inc/comments.php`、`single.php`、`comments.php`、`template-parts/post-list.php`、`assets/css/theme.css`、`部署文档模板.md`、`打包命令.md`。
- [ ] 全局替换 `mytheme` 前缀（`mytheme` / `MYTHEME_` / `mytheme-`）为项目主题短名；函数名、常量名、资源 handle、`wp_localize_script` 对象名一并改并保持统一（命名约定见 `01-技术栈与项目结构.md`）。
- [ ] `style.css` 只放主题头、不写真实样式；`Text Domain` 与主题目录名一致。

### 样式链路
- [ ] 在主题目录执行 `npm install`（首次一次）→ `npm run build:css`，确认生成 `assets/css/tailwind.css`。
- [ ] **任何新增 / 修改的 Tailwind / daisyUI 类名，改完立刻 `npm run build:css`**——不构建就等于样式不生效。重灾区：`single.php` 布局类、`inc/comments.php` 表单类、新增侧栏模块的类、`assets/js` 里的状态类。
- [ ] 类名必须是字面量：不写 `'badge-' + type` 这类动态拼接，或把拼接结果以字面量列入 JS / 模板。
- [ ] 自定义 CSS 只在工具类与组件类无法表达时才写，且只写进 `assets/css/theme.css`；不在模板里写 `<style>` 内联块。

### 结构纪律
- [ ] `add_theme_support` 四组一次声明齐：`title-tag`、`automatic-feed-links`、`post-thumbnails`、`html5`（`comment-list` / `comment-form` / `search-form` / `gallery` / `caption` / `style` / `script`）。
- [ ] 资源入队前先 `file_exists()` 判断，**文件不存在不入队**（避免 404）；版本号一律 `(string) filemtime( $绝对路径 )`。
- [ ] `inc/` 下新增文件都在 `functions.php` 里 `require_once` 加载，顺序固定为 config → class → 数据层 → rest-api → seo → comments。
- [ ] 只向前端暴露必要配置：`wp_localize_script` 只注入 `restUrl` 这类非敏感值。

### 布局纪律
- [ ] 文章详情页布局类名照抄 `03-布局与模板骨架.md`，一个字不改：容器 `container mx-auto max-w-6xl px-4 py-8`；两栏 `flex flex-col gap-6 lg:grid lg:grid-cols-[minmax(0,1fr)_20rem] lg:gap-8`；左栏正文 `min-w-0 lg:col-start-1 lg:row-start-1`；左栏评论区 `lg:col-start-1 lg:row-start-2`；侧栏 `lg:col-start-2 lg:row-span-2 lg:row-start-1` + 内部 `space-y-4 lg:sticky lg:top-6 lg:max-h-[calc(100vh-3rem)] lg:overflow-y-auto`。
- [ ] 侧栏新增模块只往内层 sticky 容器追加一行 `get_template_part()`，不改任何类名；模块自身不再加 `mt-*`（间距由容器 `space-y-4` 统一负责）。
- [ ] 窄屏顺序由 DOM 决定（正文 → 侧栏 → 评论），不为改窄屏顺序去动 DOM 顺序或加 `order-*`。

### 代码纪律
- [ ] 动态输出一律转义：文本 `esc_html()`、属性 `esc_attr()`、URL `esc_url()`；只有 WordPress 自身负责转义的函数（`the_title()` / `the_content()` / `comment_text()` / `the_permalink()`）可直接输出。
- [ ] 可复用结构用 `get_template_part()` 引入，不复制粘贴；同一件事在库里只保留一份实现（片段见 `05-代码片段库.md`）。
- [ ] 函数 / 常量 / 模板变量统一前缀避免撞名；每个 PHP 文件开头保留 `if ( ! defined( 'ABSPATH' ) ) { exit; }`。
- [ ] 空态与降级：数据为空、计数为 0、上游失败时整块不输出或给中文空态，不留半个空卡片。

### 安全纪律
- [ ] 密钥只留服务端（`inc/config.php` 或 `wp-config.php`），每个常量用 `if ( ! defined( 'X' ) )` 保护；`app_secret` 绝不出现在 JS / HTML / `wp_localize_script` / REST 响应里。
- [ ] 对外参数先 `trim( (string) ... )` + 白名单校验，**再限流、最后请求上游**（顺序不可调换）；长度校验用 `mb_strlen()`。
- [ ] 第三方 HTML 走「服务端 kses 白名单过滤 + 前端 `sandbox=""` iframe」双重隔离，两级都不能省。
- [ ] 限流用固定窗口计数（窗口自本窗口第一次请求起算、后续请求只累加不续期）+ 真实访客 IP，键名 `xy_mbx_rate_<bucket>_<md5(ip)>`。
- [ ] 长轮询类接口必须配 `flock` 去重锁（维度 = 邮箱地址），并在上游返回后无论成败立即释放。

### SEO 纪律
- [ ] 不重复输出插件已负责的标签（`description` / `og:*` / `twitter:*` / `canonical` / `ld+json` / `<title>`）；需要改值只用过滤器（`wpseo_canonical` / `wpseo_opengraph_url`）。
- [ ] `meta keywords` 只在有值时输出（`is_search()` / `is_404()` 不输出）；**它对 Google 已无效**，每页只给 1–3 个精准词，绝不堆砌。
- [ ] 主题侧 SEO 只做 5 件事：keywords、SEO 关键词字段、图片 alt 三级回退、静态首页 canonical / og:url 修正、h1 唯一 + 面包屑与相关文章内链 + RSS 标签。
- [ ] 面包屑、相关文章、页脚友链等内链一律**服务端渲染**，不改前端异步（异步内容蜘蛛抓不到）。

---

## 三、完成前（自检，逐条做完才算交付）

- [ ] **PHP 语法检查**（主题目录下执行，全部输出 `No syntax errors detected`）：
  ```powershell
  Get-ChildItem -Recurse -Filter *.php | ForEach-Object { & 'D:\BtSoft\php\82\php.exe' -l $_.FullName }
  ```
- [ ] **样式产物体积**：`[math]::Round((Get-Item 'assets/css/tailwind.css').Length/1KB,1)` → 期望 **66–71 KB**；< 30KB 说明 `@source` 漏扫或 daisyUI 插件未生效，> 500KB 说明 `source(none)` 被删、扫进了 `node_modules`。
- [ ] **本项目新增的关键类名已进产物**（压缩产物只有几行，用字面子串判断，不按行数判断）：
  ```powershell
  $t = Get-Content -Raw 'assets/css/tailwind.css'
  foreach ($c in @('lg\:sticky','lg\:row-span-2','lg\:grid-cols-','lg\:max-h','rounded-box','breadcrumbs')) { "$c => " + $t.Contains($c) }
  ```
  把本项目新增的类名替换进上面的数组，**全部为 `True` 才通过**。
- [ ] **浏览器几何自检**（在 1440 / 1024 / 375 三档视口各执行一次同一段脚本）：
  ```js
  (() => {
    const wrap = document.querySelector('main div[class*="lg:grid-cols-"]');
    const aside = document.querySelector('aside[aria-label="侧栏"]');
    const sticky = aside && aside.firstElementChild;
    const before = sticky ? Math.round(sticky.getBoundingClientRect().top) : null;
    window.scrollTo(0, 1200);
    const after = sticky ? Math.round(sticky.getBoundingClientRect().top) : null;
    window.scrollTo(0, 0);
    return {
      width: innerWidth,
      columns: wrap && getComputedStyle(wrap).gridTemplateColumns,
      order: wrap && [...wrap.children].map(el => el.tagName),
      position: sticky && getComputedStyle(sticky).position,
      topAtPageTop: before, topAfterScroll: after,
      overflow: [document.documentElement.scrollWidth, document.documentElement.clientWidth],
      h1Count: document.querySelectorAll('h1').length
    };
  })()
  ```
  判定：1440 视口 `columns` = `768px 320px`；`position` = `sticky`，页顶 `topAtPageTop ≈ 128`、`scrollTo(0,1200)` 后 `topAfterScroll ≈ 24`；375 视口 `order` 为 `DIV → ASIDE → DIV`（正文 → 侧栏 → 评论）且 `columns` 为空（单栏）；三档 `scrollWidth == clientWidth`。
- [ ] **console 无报错、资源无 404**：控制台无 error 级消息；Network 里 `assets/css/tailwind.css` 返回 200。
- [ ] **每页仅一个 h1**：`document.querySelectorAll('h1').length === 1`（首页 / 列表 / 归档 / 搜索 / 文章 / 页面 / 404 逐页跑一遍）。
- [ ] **面包屑层级正确**：文章页为「首页 › 文章（未设置文章页时省略）› 首个分类（无分类时省略）› 当前标题」；`nav` 带 `aria-label="面包屑"`；末项无链接且带 `aria-current="page"`。
- [ ] **列表页分页与空态正常**：翻页 URL 生效且当前页高亮；文章不足一页时不出现分页；搜索无结果的提示语带搜索词，其它列表页显示「暂无内容。」。
- [ ] **SEO 各只有一份**（用随机参数绕过 CDN 缓存后核对）：`canonical` / `og:url` / `ld+json` / `meta name="keywords"` 每页各 1 条；搜索页与 404 的 `robots` 含 `noindex`。
  ```powershell
  $html = (Invoke-WebRequest "https://你的域名/?nocache=$([guid]::NewGuid().ToString('N'))" -UseBasicParsing).Content
  foreach ($p in @('rel="canonical"','property="og:url"','application/ld\+json','name="keywords"')) { "{0} => {1}" -f $p, ([regex]::Matches($html,$p)).Count }
  ```
- [ ] **密钥未下发前端**：页面源码与所有 REST 响应中搜索 `sk_` / `app_secret` / `APP_SECRET` 均无命中（允许命中范围只有 `inc/config.php` / `wp-config.php` 与文档）。
- [ ] **越权 / 非法参数有明确错误返回**：缺 `address`、非法邮箱、`since` 非整数、超长 `client_id` 分别返回 `20001` / `20002` 与中文 `msg`；超限返回 `20040`。
- [ ] **部署自检**：按 `打包命令.md` 打包（排除 `node_modules` 与工作物料），校验 zip 顶层为**单一主题目录**且含 `style.css` 与 `assets/css/tailwind.css`；再按 `部署文档模板.md` 的上线清单逐条核对。
- [ ] **改动范围自检**：`git status` 与 `git diff --stat` 中出现的文件都在第一步列出的清单内，无无关文件被改动。

---

## 四、禁止事项

1. **不要用自定义 CSS 重复实现 daisyUI 已提供的组件外观**（按钮 / 卡片 / 徽标 / 输入框等）——优先用 `btn` / `card` / `badge` / `input` 等类名，否则样式分两处维护、主题升级必冲突。
2. **不要重复输出 Yoast 已负责的 SEO 标签**（description / og / twitter / canonical / ld+json / title）——同页两套声明会互相冲突，搜索引擎往往选错那套；要改值只用过滤器。
3. **不要把 API 密钥 / 凭证下发到前端**（JS、HTML、`wp_localize_script`、REST 响应）——前端只拿 `restUrl`，否则等于把接口送人。
4. **不要手改上游返回的 HTML 后直接 `echo`**——必须「服务端 kses 白名单 + 前端 `sandbox=""` iframe」双重隔离，否则第三方脚本会在本站域下执行。
5. **不要在模板里直接写业务请求逻辑**（取数、签名、缓存、限流）——这类逻辑放 `inc/` 分层文件，模板只做输出。
6. **不要改动与本任务无关的既有代码，也不要擅自回滚他人改动**——只碰第一步列出的文件，最小化改动。
7. **不要用未编译的类名交付**（改了 Tailwind / daisyUI 类名但没跑 `npm run build:css`）——样式不会生效，且上线后极难排查。
8. **不要把关键词堆砌当作 SEO 手段**——`meta keywords` 对 Google 已无效，堆砌只有作弊风险、收益为零。

---

## 五、一句话总结

1. **改类名就必须 `npm run build:css`**：样式是预编译静态产物，没构建就等于没写。
2. **布局与标签各只有一份**：文章详情页类名照抄、每页只一个 `h1`、SEO 标签同一类只有一个来源。
3. **密钥不出服务端、第三方 HTML 必隔离**：交付前用 `php -l` + 浏览器几何值 + 源码搜索密钥三道自检收口。
