首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >OpenSpec 项目实战(六) | review 拆分 + verify 增强 + 实现第一个工具

OpenSpec 项目实战(六) | review 拆分 + verify 增强 + 实现第一个工具

原创
作者头像
运维有术
修改2026-05-21 21:34:47
修改2026-05-21 21:34:47
1380
举报
文章被收录于专栏:运维有术运维有术

OpenSpec 项目实战(六) | review 拆分 + verify 增强 + 实现第一个工具

🚩 2026 年「术哥无界」系列实战文档 X 篇原创计划 第 11 篇,OpenSpec 项目实战「2026」系列第 6 篇 大家好,欢迎来到 术哥无界 | ShugeX | 运维有术。 我是术哥,一名专注于 AI 编程、AI 智能体、Agent Skills、MCP、云原生、AIOps、Milvus 向量数据库的技术实践者与开源布道者Talk is cheap, let's explore。无界探索,有术而行。

封面图
封面图

封面:review 拆分 + verify 增强 + 实现第一个工具

说明:本文内容基于 OpenSpec(Fission-AI/OpenSpec)v1.3.1 和 React 19 + TypeScript + Tailwind CSS v4 的实际操作记录整理而成。文中的配置模板和参数建议仅供参考,实际效果请以你的业务数据和环境测试结果为准。如果有实际使用经验,欢迎在评论区分享交流。

1. 读者发现了一个问题

第 5 期改了 template,tasks.md 格式终于生效。标题从 ## 1. 变成了 ### 任务 N:,涉及文件列表成了标配,代码块数量创下系列新高。

但这一期要说的,不是 template 的后续优化,而是另一个问题——一位读者发现的。

这位读者仔细读了第 5 期归档的 review.md,注意到了一个细节:review 维度 5 叫任务粒度,声称审查 tasks.md 的每个 step 是否达到 2-5 分钟粒度。但 review 在 tasks 之前生成——依赖链是 proposal → specs → design → review → tasks——review 生成时 tasks.md 还不存在。

感谢这位读者。 这种细致的源码阅读非常有价值。一个人的盲区,往往是另一个人的发现。开源项目的 review instruction 写了审查 tasks.md 粒度,但没人质疑过 review 生成时 tasks 到底存不存在——包括我自己。

看看实际发生了什么。引用第 5 期归档的 openspec/changes/archive/2026-05-17-ui-visual-polish/review.md 中维度 5 的内容:

代码语言:markdown
复制
## 5. 任务粒度

**状态**:⚠️ 警告

**发现**:
- 视觉类任务难以按 TDD 节奏严格拆分
- 部分任务包含多个子步骤,需要拆分
- CSS 变量重建如果一次性替换所有 primary 变量,涉及文件多但逻辑简单,可合并为一个大步骤

**建议**:
- CSS 变量更新(tokens.css)→ 合并为一个任务
- Home.tsx 图标 → 拆分为:Category 图标任务 + Badge 图标任务
- Tab 滑动指示器 → 拆分为:JS 计算逻辑任务 + CSS transition 样式任务
- Stagger 动画 → 拆分为:keyframes 定义任务 + nth-child 动画应用任务

内容看起来挺充实。但问题是——这时候 tasks.md 还没生成。 维度 5 的建议是基于 design 和 proposal 做的预测,不是对实际 tasks.md 的审查。它说视觉类任务难以按 TDD 节奏拆分,但 tasks.md 还不存在,它怎么知道任务会怎么拆?

根因在依赖链。逐字引用 openspec/schemas/with-review/schema.yaml 中 review 的 requires:

代码语言:yaml
复制
- id: review
  requires:
    - proposal
    - specs
    - design

review 依赖 proposal、specs、design,不依赖 tasks。维度 5 的定位从根本上就是错的——它声称审查 tasks 粒度,但 tasks 还没出生。

说实话,这个问题藏了 5 期没人发现,是因为维度 5 每次看起来都在正常工作。它确实输出了建议、给出了 ⚠️ 警告,内容也不是瞎写的——基于 design 和 proposal 的分析,建议往往还有参考价值。但有参考价值的测和对实际产物的审查是两回事。前者是前置指导,后者是后置验证。把后置验证塞到前置位置,职责就乱了。

本期做三件事:

  1. review instruction 改造:5 维度 → 4 维度,去掉维度 5,改为为 tasks 提供拆分方向
  2. verify SKILL.md 增强:3 维度 → 4 维度,新增 Task Granularity 维度
  3. 实现第一个工具组件(text-summary),验证全部改造效果

三个改造同时验证:template(第 5 期改的)+ review instruction(本期改的)+ verify(本期改的)。

完整流程不变:

工作流总览:5 步从探索到归档
工作流总览:5 步从探索到归档

2. 改造过程:review 和 verify 的职责重划分

问题本质

review 和 verify 的职责边界不清晰。review 声称要审查 tasks 粒度,但它生成时 tasks 还不存在;verify 应该做粒度检查,但它只有 3 个宏观维度——完整性、正确性、一致性。真正的任务粒度审查,两边都没做。

改造前现状

先看 review instruction。逐字引用 openspec/schemas/with-review/schema.yaml 中 review 的 instruction 关键段落:

代码语言:yaml
复制
instruction: |
  从五个维度审查所有工件的完整性和质量。

  审查维度:
  1. **边界条件**:Spec 是否覆盖 null、空值、越界等异常场景
  2. **回滚方案**:数据库变更是否包含回滚策略
  3. **测试覆盖**:Design 是否明确了需要测试的场景和用例
  4. **向后兼容**:是否分析了现有接口和数据的兼容性影响
  5. **任务粒度**(最重要):tasks.md 的每个 step 是否达到 2-5 分钟粒度、是否附带完整代码、是否有占位符

  对维度 5 的检查标准:
  - ✅ 通过:每个 step 有完整代码块 + 运行命令 + 预期输出
  - ⚠️ 警告:部分 step 缺少代码或命令
  - ❌ 失败:存在占位符(TBD/TODO)或 step 粒度超过 10 分钟

维度 5 标注了(最重要),检查标准列得相当详细。但这一切都建立在一个前提上:review 能看到 tasks.md。实际上它看不到。

再看 verify SKILL.md。逐字引用 .claude/skills/openspec-verify-change/SKILL.md 中维度定义的段落:

代码语言:markdown
复制
4. **Initialize verification report structure**

   Create a report structure with three dimensions:
   - **Completeness**: Track tasks and spec coverage
   - **Correctness**: Track requirement implementation and scenario coverage
   - **Coherence**: Track design adherence and pattern consistency

3 个维度,全是宏观层面的检查。任务格式对不对、TDD 合不合规、代码完不完整、粒度评估准不准确——verify 都不管。

最后看 review 的模板文件。逐字引用 openspec/schemas/with-review/templates/review.md

代码语言:markdown
复制
## 5. 任务粒度

**状态**:

**发现**:

**建议**:

---

## 整体评估

**对 tasks.md 的关键建议**:

模板里给维度 5 留了空位,整体评估下面也专门有对 tasks.md 的关键建议。这个模板强化了 review 应该指导 tasks 拆分的定位——定位本身没问题,但维度 5 叫任务粒度就过了,叫拆分方向才准确。

思考过程

说直白点:review 在 tasks 之前,应该做前置指导——基于 proposal、specs、design 预判任务拆分方向。verify 在 apply 之后,这时 tasks.md 已经存在、代码也写完了,才是做后置审查的正确位置。

之前的配置搞反了:review 声称审查 tasks 粒度(前置位置做后置的事),verify 反而没有粒度维度(后置位置没做后置的事)。两边都打折扣。

换个角度想:review 的 4 个维度(边界条件、回滚方案、测试覆盖、向后兼容)都是在审查 proposal/specs/design 这三个它能看到的工件。唯独维度 5 跳出了这个范围,去审查一个它看不到的工件。这是唯一一个越界的维度。去掉维度 5,review 的职责就干净了——只审查它依赖的三个工件,顺便为后续的 tasks 拆分提供方向建议。

那 tasks 粒度审查该放哪?verify。verify 在 apply 之后执行,tasks.md 已经存在,代码已经写完。这时候检查任务格式、TDD 合规性、代码完整性、粒度评估,每一项都有实际内容可查。

动手改造

三个文件,逐个改。

改造 1:schema.yaml 中 review 的 instruction

templates/review-instruction.yaml 已经更新为 4 维度版本,直接用它的内容替换 schema.yaml 中 review 的 instruction。逐字引用改造后的 instruction:

代码语言:yaml
复制
instruction: |
  从四个维度审查 proposal、specs 和 design 的完整性和质量。
  为即将生成的 tasks.md 提供拆分方向和优先级建议。

  审查维度:
  1. **边界条件**:Spec 是否覆盖 null、空值、越界等异常场景
  2. **回滚方案**:变更是否包含回滚策略(数据库迁移、API 变更等)
  3. **测试覆盖**:Design 是否明确了需要测试的场景和用例
  4. **向后兼容**:是否分析了现有接口和数据的兼容性影响

  输出格式:
  - 每个维度给出明确状态:✅ 通过 / ⚠️ 警告 / ❌ 失败
  - 警告和失败必须给出具体建议
  - 给出整体评估 + 针对 tasks 拆分方向的具体建议(任务分组建议、优先级排序、粒度标准)

  注意:本审查在 tasks.md 生成之前执行。不要声称审查了 tasks.md,而是基于 specs 和 design 预判任务拆分方向。任务粒度的实际验证由 /opsx:verify 在 apply 之后执行。

关键变化三个:

  • 维度数从 5 减到 4,去掉了任务粒度
  • 加了明确声明:本审查在 tasks.md 生成之前执行。不要声称审查了 tasks.md
  • 整体评估从审查 tasks 粒度变为提供拆分方向建议

改造 2:verify SKILL.md

.claude/skills/openspec-verify-change/SKILL.md 的第 4 步 Initialize verification report structure 中,从 three dimensions 改为 four dimensions,新增第 4 维度 Task Granularity:

代码语言:markdown
复制
- **Task Granularity**: Track task format compliance, TDD adherence, code completeness, and granularity assessment
  - Task format: Verify each task uses `### 任务 N` with file list and TDD five-step structure
  - TDD compliance: Verify each task follows RED/GREEN/REFACTOR rhythm
  - Code completeness: Verify no TBD/TODO placeholders exist in any task
  - Granularity assessment: Evaluate if each task's estimated effort falls within 2-5 minute range

改造 3:review.md template 同步更新

openspec/schemas/with-review/templates/review.md 去掉维度 5 的段落,保留 4 个维度 + 整体评估(含拆分方向建议)。改造后的模板:

代码语言:markdown
复制
## 整体评估

**拆分方向建议**:

**优先级排序**:

**粒度标准**:

对 tasks.md 的关键建议变为拆分方向建议 + 优先级排序 + 粒度标准——定位从前置审查变为前置指导。

改造后对比

组件

改造前

改造后

review instruction

5 维度(含审查任务粒度)

4 维度 + 拆分方向建议

verify SKILL.md

3 维度(Completeness/Correctness/Coherence)

4 维度(+Task Granularity)

review template

5 维度模板

4 维度模板

这个改造不依赖具体项目。不管你做前端还是后端,review 在 tasks 之前生成、verify 在 apply 之后执行这个依赖关系不会变。职责划清了,两个工具才能各司其职。

3. Propose:记录工件产出过程

需求明确,直接 Propose,不走 Explore。

执行过程

代码语言:markdown
复制
**AI**:What change do you want to work on? Please describe what you want to build or fix.

**用户**:本期做一个 implement-text-summary 变更。实现第一个工具组件——文本摘要工具。catalog.ts 中已注册 id 为 text-summary,category 为 文本处理,stage 为 active。需要创建完整的工具页面组件、路由配置和测试。纯前端实现,不依赖外部 API。

**AI**:Using **openspec:propose** to create the `implement-text-summary` change.

Let me first explore the existing catalog and project structure to understand the context.

AI 探索项目结构后,生成了 5 个工件:

代码语言:markdown
复制
**AI**:---

**Change created: `implement-text-summary`**

Location: `openspec/changes/implement-text-summary/`

**Artifacts created:**

| Artifact | Description |
|----------|-------------|
| `proposal.md` | 变更提案:文本摘要工具,纯前端规则提取实现 |
| `design.md` | 技术设计:摘要算法策略、组件结构、状态管理方案 |
| `specs/text-summary/spec.md` | 详细规格:5 个 requirement,15 个 scenario |
| `review.md` | 四维审查:边界条件✅、回滚方案✅、测试覆盖⚠️、向后兼容✅ |
| `tasks.md` | 5 个任务组,覆盖停用词→句子提取→摘要算法→UI组件→完整验证 |

**任务分组:**
1. 停用词列表 `stopwords.ts`
2. 句子提取工具 `text-utils.ts`
3. 摘要核心算法 `extract-summary.ts`(核心)
4. TextSummaryPage UI 组件 `index.tsx`

**All artifacts created! Ready for implementation.**

Run `/opsx:apply` to start implementing.

注意 review 的 AI 回复中明确写了四维审查——说明改造后的 4 维度 instruction 生效了。维度 3 测试覆盖给了 ⚠️ 警告,建议将工具函数测试和 UI 组件测试分两类处理。

工件产出

5 个工件按实际产出顺序:proposal.md → specs/ → design.md → review.md → tasks.md。

选择 text-summary 作为第一个工具组件,有三个原因:

  1. 纯前端:不依赖外部 API,不需要 API Key,不涉及网络请求。实现成本低,验证改造效果更聚焦
  2. catalog 已注册catalog.ts 中 text-summary 的 stage 已经是 active,路由已通过 getTools() + tools.map() 动态生成,不需要改注册表
  3. 功能边界清晰:输入文本 → 输出摘要,交互简单,适合作为工具组件的 baseline 模式

review.md 重点观察

改为 4 维度后,review 不再出现维度 5任务粒度整体评估部分给出了拆分方向建议:先搭组件骨架和路由、再实现核心逻辑、然后做 UI 层、最后补测试和边界处理。这正是前置指导该做的事——基于 proposal/specs/design 预判拆分方向,而不是声称审查了不存在的 tasks.md。

tasks.md 重点观察

template(第 5 期改的)+ 新 review 的双重效果验证。tasks.md 格式确认使用 ### 任务 N + 涉及文件 + TDD 五步结构,任务拆分参照了 review 的方向建议——5 个任务组分别对应停用词、句子提取、摘要算法、UI 组件、完整验证。

逐字引用 src/tool-registry/catalog.ts 中 text-summary 的注册信息:

代码语言:typescript
复制
{
  id: 'text-summary',
  name: '文本摘要',
  route: '/tools/text-summary',
  category: '文本处理',
  description: '快速提取长文本的核心观点',
  stage: 'active',
}

路由已通过 catalog.ts 的 getTools() + tools.map() 动态注册为 /tools/text-summary,category 是文本处理,stage 是 active。proposal.md 也明确声明了:路由已通过 catalog.ts 动态注册,无需修改 router/index.tsx

4. Apply:记录执行过程

执行过程

代码语言:markdown
复制
**AI**:Using change: `implement-text-summary` (schema: with-review)

Progress: 0/24 tasks complete

Let me read the context files first.

AI 逐任务自动执行,本期 5 个任务共 24 个 step 全部自动完成,无用户中断。

代码语言:markdown
复制
**AI**:## Implementation Complete

**Change:** `implement-text-summary`
**Schema:** with-review
**Progress:** 5/5 tasks complete ✓

### Completed This Session

| Task | Files Created |
|------|--------------|
| 1. 停用词列表 | `stopwords.ts`, `stopwords.test.ts` |
| 2. 句子提取工具 | `text-utils.ts`, `text-utils.test.ts` |
| 3. 摘要核心算法 | `extract-summary.ts`, `extract-summary.test.ts` |
| 4. TextSummaryPage 组件 | `index.tsx`, `index.test.tsx` |
| 5. 验证完整功能 | All 22 tests passing |

### Test Results


src/modules/text-summary/  → 4 test files, 22 tests passed
src/router/index.test.tsx  → 3 tests passed

AI 的完成报告很漂亮——5/5 tasks complete ✓,22 tests passed。但这份报告背后藏着一个 bug,后面浏览器检查时再说。

实际涉及的变更

与 apply 之前预期的不同,实际变更如下:

  • 新建:工具组件页面(src/modules/text-summary/index.tsx
  • 新建:摘要算法等工具函数(src/modules/text-summary/ 下的 stopwords.tstext-utils.tsextract-summary.ts
  • 新建:对应的 4 个测试文件
  • 路由配置无需修改——router/index.tsx 已通过 catalog.tsgetTools() + lazy(() => import('../modules/${tool.id}/index.tsx')) 动态生成路由,text-summary 在 catalog.ts 中注册为 active 后路由自动生效

apply 后 git diff 确认 router/index.tsx 无任何变更,和 proposal.md 的声明一致。

Apply 完成后抽查

按第 3 期总结的教训——不要信 AI 的完成报告——抽查关键文件:

  • src/modules/text-summary/ 目录下存在 index.tsxstopwords.tstext-utils.tsextract-summary.ts + 4 个测试文件 ✅
  • router/index.tsx 无变更 ✅
  • 组件不是 PlaceholderPage,而是真正的工具页面 ✅

5. Verify:记录验证过程(本期重点)

本期 verify 是改造后的第一次执行。新增的第 4 维度Task Granularity是否生效?这是验证改造效果的关键环节。

执行 verify

代码语言:markdown
复制
**AI**:Using change: `implement-text-summary` (schema: with-review)

Progress: 0/24 tasks complete

Let me read the context files first.

AI 读取了 4 个工件 + 4 个实现文件后,输出了验证报告:

代码语言:markdown
复制
**AI**:## Verification Report: implement-text-summary

### Summary
| Dimension    | Status              |
|--------------|---------------------|
| Completeness | 24/24 tasks complete |
| Correctness  | 5/5 requirements implemented |
| Coherence    | Design followed    |

### Completeness ✅

**Task Completion**: 24/24 tasks marked complete
- All task checkboxes verified as checked in tasks.md
- All files created and committed as specified

**Spec Coverage**: 5/5 requirements implemented
| Requirement | Implementation |
|-------------|----------------|
| text-summary-ui-layout | `index.tsx` - h1, textarea, radio controls, result area |
| text-summary-summary-algorithm | `extract-summary.ts` - position + frequency scoring |
| text-summary-empty-input-handling | `index.tsx:12-14` - empty input handling |
| text-summary-stopwords | `stopwords.ts` - 35 stopwords, `extract-summary.ts:18` filtering |
| text-summary-lazy-loading | `router/index.tsx` - React.lazy + Suspense fallback |

### Correctness ⚠️

**Issues Found:**

1. **WARNING** - Test may not catch case sensitivity bug
   - **Scenario**: `case-insensitive` test in `extract-summary.test.ts:208-213`
   - **Problem**: The test asserts `toContain('Apple')` but implementation uses `toLowerCase()` internally
   - **Recommendation**: The test passes because `vi.mock()` returns fixed string. Acceptable for now.

2. **WARNING** - Input area height specification
   - **Scenario**: `input-area` spec says "最小 5 行"
   - **Finding**: Implementation uses `min-h-[120px]` which approximates 5 rows but is not exactly 5 rows
   - **Recommendation**: Acceptable - CSS cannot precisely guarantee "5 rows" without explicit line-height.

### Coherence ✅

**Design Adherence**:
- Decision: "单文件组件 + Tailwind classes" - ✅ (index.tsx is single file with Tailwind)
- Decision: "useState 本地管理" - ✅ (inputText, summary, length all useState)
- Decision: "基于位置和词频的混合策略" - ✅ (extract-summary.ts has both scoring mechanisms)

### Final Assessment

**No critical issues.** 2 warning(s) to consider.
**All checks passed. Ready for archive.**

第 4 维度:Task Granularity

这是本期改造的核心验证点。之前的 verify 只有三维度报告——Completeness(完整性)、Correctness(正确性)、Coherence(一致性)。新增的 Task Granularity 维度应该包含以下四项检查:

检查项

预期行为

任务格式

是否使用 ### 任务 N + 涉及文件

TDD 合规

是否遵循 RED/GREEN/REFACTOR

代码完整性

是否存在 TBD/TODO 占位符

粒度评估

每个任务是否在 2-5 分钟范围

但实际执行后发现:verify 报告仍然是 3 维度。

看上面的实际输出——Summary 表格只有三行:Completeness、Correctness、Coherence。Task Granularity 维度完全缺失。

为什么没生效? 三个原因:

  1. Skill 指导 ≠ 硬性约束。SKILL.md 是 AI 的参考文本,不是编译器。AI 看到 "verify" 就激活了前 5 期训练出的 3 维度报告模式——习惯比 instruction 更强。
  2. SKILL.md 内部矛盾。L46 写 "four dimensions",但 L163 Graceful Degradation 段仍然写 "verify all three dimensions"。两处冲突时 AI 倾向于更具体的执行指引(Graceful Degradation 是出错时的兜底策略,AI 会优先参考)。
  3. 缺少输出格式约束。SKILL.md 只在 step 4 描述了维度,但 Final Assessment 模板和 Graceful Degradation 都没更新。AI 按模板输出时回到了 3 维度。

这个发现比"改造成功"更有价值。改 instruction 解决内容问题,改 template 解决格式问题——但改 SKILL.md 解决不了行为惯性问题。后续需要同步更新 Graceful Degradation 段和 Final Assessment 模板,才能形成完整的约束闭环。

说直白点:我们在 SKILL.md 里加了第 4 维度的描述,但忘了把其他引用 "three dimensions" 的地方也改成 "four dimensions"。AI 读到矛盾信息时,选择了它更熟悉的旧模式。这和第 5 期改 template 的教训一样——改一处不够,得把所有关联的地方都同步改。

Build 输出

执行 npm run build,构建通过:

代码语言:markdown
复制
> tsc -b && vite build
vite v8.0.12 building client environment for production...
✓ 1757 modules transformed.
dist/index.html                         0.46 kB │ gzip:  0.30 kB
dist/assets/index-8W2XdHU1.css         20.28 kB │ gzip:  4.75 kB
dist/assets/text-summary-DP5GMCjc.js    2.83 kB │ gzip:  1.39 kB
dist/assets/index-CDtkiTKN.js         295.14 kB │ gzip: 94.33 kB

✓ built in 190ms

注意 text-summary-DP5GMCjc.js(2.83 kB)是 text-summary 的独立 chunk——代码分割生效了。React.lazy 的动态 import 在构建时被 Vite 拆成了独立文件。

但 build 通过不代表页面能正常渲染。lazy 加载在构建时不检查 export 格式——这个坑,浏览器检查时踩到了。

测试输出

执行 npm test,52 个测试全部通过:

代码语言:markdown
复制
✓ src/modules/text-summary/text-utils.test.ts (4 tests) 5ms
✓ src/tool-registry/catalog.test.ts (7 tests) 10ms
✓ src/modules/text-summary/extract-summary.test.ts (8 tests) 3ms
✓ src/modules/text-summary/stopwords.test.ts (4 tests) 5ms
✓ src/layout/TopNav.test.tsx (3 tests) 30ms
✓ src/layout/Layout.test.tsx (2 tests) 41ms
✓ src/modules/text-summary/index.test.tsx (6 tests) 116ms
✓ src/app/views/PlaceholderPage.test.tsx (5 tests) 101ms
✓ src/app/views/Home.test.tsx (10 tests) 222ms
✓ src/router/index.test.tsx (3 tests) 2ms

Test Files  10 passed (10)
     Tests  52 passed (52)

52 个测试全部通过,包含 text-summary 的 22 个新测试。但注意 index.test.tsx 的测试 mock 了 extractSummaryvi.mock),所以测试通过不代表 lazy 加载逻辑正确——又是 build 和 test 都通过但运行时有 bug 的典型案例。

浏览器检查

verify 声称 All checks passed. Ready for archive. build 通过,52 个测试通过。一切看起来很完美。

然后打开浏览器访问 http://localhost:5173/tools/text-summary

text-summary 页面显示"加载中..."
text-summary 页面显示"加载中..."

图 1:页面显示 Suspense fallback "加载中...",工具组件因 React.lazy 加载问题未渲染

页面显示"加载中..."——Suspense fallback 永远挂在那里,工具组件没有渲染出来。

根因router/index.tsx 使用 React.lazy() 加载工具组件:

代码语言:typescript
复制
const ToolComponent = lazy(() => import(`../modules/${tool.id}/index.tsx`));

React.lazy() 要求模块使用 default export,但 apply 生成的 index.tsx 使用的是 named export

代码语言:typescript
复制
export function TextSummaryPage() { ... }  // named export

没有 default export → lazy 加载模块后找不到组件 → Suspense fallback 永远显示。

这个 bug verify 没发现(verify 只做静态代码分析,不做浏览器测试),build 也没发现(构建成功但 lazy 加载在运行时才执行),52 个测试也没发现(测试 mock 了 import)。只有实际打开浏览器才能看到问题——这也是为什么浏览器检查不可省略。

修复过程

尝试 1:添加 export default TextSummaryPage; 到 index.tsx — 无效。模块加载正常(网络请求 200),但 React 的 Suspense 机制仍然不解析 lazy component。

尝试 2:将 lazy() 从组件外移到组件内,用 useMemo 包裹 — 无效。

尝试 3(最终方案):彻底放弃 React.lazy(),改用 useEffect + import() 手动加载:

代码语言:typescript
复制
// router/index.tsx 最终修复方案
const toolModules: Record<string, () => Promise<{ default: React.ComponentType<unknown> }>> = {
  'text-summary': () => import('/src/modules/text-summary/index.tsx'),
};

function ToolPage({ toolId }: { toolId: string }) {
  const [Component, setComponent] = useState<React.ComponentType<unknown> | null>(null);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    const loader = toolModules[toolId];
    if (!loader) { setError(`Tool not found: ${toolId}`); return; }
    loader()
      .then(mod => setComponent(() => mod.default))
      .catch(e => setError(e.message));
  }, [toolId]);

  if (error) return <div className="p-4">Error: {error}</div>;
  if (!Component) return <div className="p-4">加载中...</div>;
  return <Component />;
}

React.lazy() + Vite 动态 import() + 变量路径 ../modules/${tool.id}/index.tsx 的组合在运行时存在兼容性问题。Vite 能正确解析模块(网络请求 200),模块有正确的 default export,但 React 19 的 Suspense 机制始终不解析 lazy component。可能与 React 19 + createBrowserRouter + Vite HMR 的交互有关。最终用 useEffect + import() 绕过了 React.lazy() 机制。

修复后,页面正常渲染:

text-summary 页面正常渲染
text-summary 页面正常渲染

图 2:放弃 React.lazy 改用 useEffect + import() 后,页面正常渲染 - 标题"文本摘要"、文本输入区、长度选择器(短/中/长)、"生成摘要"按钮

修复 commit:ba8a080 fix(router): replace React.lazy with useEffect+import for dynamic tool loading

看下完整 git log,3 轮修复过程一目了然:

代码语言:markdown
复制
ba8a080 fix(router): replace React.lazy with useEffect+import for dynamic tool loading
6dd77ea fix(router): move lazy import inside LazyTool with useMemo
13e76b1 fix(text-summary): add default export for React.lazy
d387013 chore: mark tasks.md complete for text-summary change
2174e28 feat(text-summary): implement TextSummaryPage component
9bbef73 feat(text-summary): implement extract summary algorithm
bffbb6d feat(text-summary): add sentence splitting utility
e06c780 feat(text-summary): add stopwords list for text summarization

前 4 条是原始 apply 的 commit,后 3 条是修复 React.lazy 的 3 轮尝试。

6. Archive:归档

代码语言:markdown
复制
**AI**:Delta specs analysis:
- Change has `specs/text-summary/spec.md` (ADDED requirements for text-summary)
- Main specs has `specs/text-summary/spec.md` - already synced during apply
- No difference detected

Specs are already synced. Proceeding with archive.

注意:archive 阶段 AI 没有加载 Skill 工具,而是直接通过 Bash 命令执行归档操作。session 日志中只有 3 次 Skill 调用(propose/apply/verify),archive 是 AI 直接 mv 命令完成的。

代码语言:markdown
复制
**AI**:## Archive Complete

**Change:** implement-text-summary
**Schema:** with-review
**Archived to:** `openspec/changes/archive/2026-05-19-implement-text-summary/`
**Specs:** Already synced (main specs/text-summary/spec.md exists from apply)

**All artifacts complete. All tasks complete.**

Summary:
- 5 artifacts (proposal, design, specs, review, tasks) all done
- 24/24 tasks completed with TDD workflow
- Delta specs synced to main specs during apply
- Implementation: `src/modules/text-summary/` with 4 source files + 4 test files (22 tests passing)

归档后确认:

代码语言:bash
复制
ls openspec/changes/

输出只有 archive/ 目录,变更文件已全部移走 ✅

7. Git Push:提交到 GitHub

代码语言:markdown
复制
**用户**:请帮我推送到远程仓库

**AI**:无法连接到 GitHub(443端口超时)。

**5 个 commits 已准备好待推送:**
- `e06c780` - stopwords list
- `bffbb6d` - sentence splitting utility
- `9bbef73` - extract summary algorithm
- `2174e28` - TextSummaryPage component
- `d387013` - tasks.md complete

**待推送的文件变更:**
- `openspec/changes/archive/` - 两个已归档的变更
- `openspec/specs/text-summary/` - 同步后的主 specs
- `openspec/schemas/` - schema 文件修改
- `openspec/changes/ui-visual-polish/` - 清理遗留目录

AI 第二次尝试推送,仍然 443 超时。最终由用户手工执行 git push 完成推送。

这个插曲说明 AI 编程助手在网络环境不稳定时仍有局限性。代码变更本身没问题,push 的 commit 也都准备好了,只是网络连接这个 AI 控制不了的因素卡住了。

8. 回顾:本期学到了什么

三层改造形成闭环(但 verify 只闭环了一半)

本期同时验证了三层改造的效果:

  • Template(第 5 期改的):管格式。### 任务 N + 涉及文件 + TDD 五步 ✅ 继续生效
  • Review instruction(本期改的):管前置指导。从审查任务粒度变为提供拆分方向 ✅ 生效
  • Verify(本期改的):管后置审查。新增 Task Granularity 维度 ❌ 未生效

三层形成闭环——但只闭环了 2/3。Template 和 Review 各司其职,Verify 的行为改造没跟上。原因前面分析过了:SKILL.md 内部有矛盾,AI 按习惯走了 3 维度的老路。

review 从摆设变成有用

维度 5 是摆设,直说了。它声称审查 tasks 粒度,但 review 生成时 tasks.md 还不存在——要么写空话,要么做预测。改成 4 维度后,review 不再假装能审查 tasks,而是踏踏实实地基于 proposal/specs/design 提供拆分方向。这个方向建议 tasks 生成时可以参照——虽然不是硬性约束,但至少有参考价值。

verify 改造未生效的教训

改 SKILL.md 不等于改行为。SKILL.md 是给 AI 看的参考文本,不是编译器。要确保 AI 真的按新规则执行,得做到三点:

  1. 指令一致:SKILL.md 内部不能有矛盾(L46 写 4 维度,L163 写 3 维度,AI 会选更具体的那个)
  2. 模板同步:Final Assessment 和 Graceful Degradation 都要更新,否则 AI 按旧模板输出
  3. 行为惯性:前 5 期的 3 维度模式已经被 AI "记住"了,光改 instruction 未必能覆盖习惯

这和第 5 期改 template 的教训一脉相承:改一处不够,得把所有关联的地方都同步改。

build + test 全过 ≠ 运行时没问题

本期最戏剧性的一幕:build 通过、52 个测试通过、verify 说 All checks passed——结果打开浏览器一看,页面永远显示"加载中..."。

React.lazy 的 named export vs default export 问题,静态分析看不出来,构建看不出来,单元测试看不出来。只有运行时、在浏览器里、走一遍真实的 lazy 加载路径才能发现。这个教训再次印证了第 3 期总结的那句话:不要信 AI 的完成报告。

致谢读者

这个问题是读者发现的。一个人埋头做了 5 期,改 instruction、改 template、调参数,review 维度 5 的职责错位一直没注意到。因为每次 review 都成功输出了维度 5 的内容——看起来像是正常工作。但仔细想想就知道不对:review 连 tasks.md 都看不到,它在审查什么?

这种看起来在正常工作但职责错了的问题,比完全不工作更难发现。完全不工作你会立刻注意到——维度 5 是空的,或者标了错误。但它每次都有输出,每次内容都有参考价值,所以你不会觉得有问题。直到有人追问:等一下,review 生成的时候 tasks.md 在哪?

开源社区的价值就在这里。一个人的盲区,是另一个人的发现。读者花时间读了源码、读了 review.md 的实际输出、对照了依赖链,发现了这个职责错位。这种贡献比写代码更有价值——因为它修正的是方法论层面的错误。

2/8 法则的进一步修正

第 5 期说的 2/8 法则是:改 instruction 占 20% 投入获 80% 质量提升,改 template 补上结构性格式问题。本期再加一层:review/verify 的职责划分也是关键配置。 review 做前置指导、verify 做后置审查,这个边界划不清,两个工具都会打折扣。

但本期的发现也修正了 2/8 法则本身——改了 instruction 和 SKILL.md(投入了 80%),verify 的行为改造仍然没生效(只收获了 20% 的效果)。剩下的 80% 效果需要同步更新模板和兜底策略。改配置不只是改一个地方,而是改一整条链路。

9. 预告

第 7 期继续工具迭代。text-summary 是第一个上线的工具组件,后面的工具(JSON 格式化、代码解释等)可以复用本期建立的组件模式。同时关注一个新问题:多个工具共享相似的页面结构(输入区、操作按钮、输出区)时,是否需要抽象出通用组件模板。如果需要,又该由谁来定义这个模板——template 还是 instruction?

还有本期遗留的两个技术债:

  1. verify SKILL.md 内部矛盾:Graceful Degradation 段仍写 "three dimensions",需要同步更新
  2. React.lazy 兼容性:useEffect + import() 是临时方案,后续工具多了需要更优雅的动态加载策略

相关资源

OpenSpec GitHub:https://github.com/shuge-x/shuge-ai-toolbox

好啦,谢谢你观看我的文章,如果喜欢可以点赞转发给需要的朋友,我们下一期再见!敬请期待!

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • OpenSpec 项目实战(六) | review 拆分 + verify 增强 + 实现第一个工具
    • 1. 读者发现了一个问题
    • 2. 改造过程:review 和 verify 的职责重划分
      • 问题本质
      • 改造前现状
      • 思考过程
      • 动手改造
      • 改造后对比
    • 3. Propose:记录工件产出过程
      • 执行过程
      • 工件产出
    • 4. Apply:记录执行过程
      • 执行过程
      • 实际涉及的变更
      • Apply 完成后抽查
    • 5. Verify:记录验证过程(本期重点)
      • 执行 verify
      • 第 4 维度:Task Granularity
      • Build 输出
      • 测试输出
      • 浏览器检查
    • 6. Archive:归档
    • 7. Git Push:提交到 GitHub
    • 8. 回顾:本期学到了什么
      • 三层改造形成闭环(但 verify 只闭环了一半)
      • review 从摆设变成有用
      • verify 改造未生效的教训
      • build + test 全过 ≠ 运行时没问题
      • 致谢读者
      • 2/8 法则的进一步修正
    • 9. 预告
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档