游乐游手机版
首页/AI教程/文章详情

AI规范驱动编程的Harness工程实战项目

时间:2026-07-16 21:45
针对AI编码缺乏工程边界的问题,提出一套Harness机制:通过文件规则匹配、SkillGate强制加载规范、编辑次数限制、审计日志与快照回滚,在AI修改代码前实施前置检查,将规范从建议转为执行条件,从而降低CodeReview负担并提升代码质量。

1. 引言:纯对话式编程终将失控,AI 编码需要工程化的约束

AI 编程最令人困扰的并非它无法生成代码,而是它缺乏稳定可靠的工程边界。今天它还记得用 intl.get() 处理国际化,明天可能就直接嵌入中文硬编码;上一轮还能规规矩矩地拆分组件,下一轮就把页面、状态、事件和样式全部塞进一个 index.tsx 里。更糟糕的是,你让它修复一个 bug,它顺手就把周围代码的风格一并改了。

AI规范驱动编程-harness工程项目实战

单独来看,每个问题都不算致命,但累积起来会持续消耗你在 Code Review 上的精力。原本 Review 应该聚焦在业务逻辑、交互边界和异常处理上,结果却变成了替 AI 擦屁股:检查有没有 any 类型逃逸、有没有硬编码的中文、列表的 key 是否用了 index、有没有乱改配置文件。

在我们的项目中,恰恰遇到了同样的困境:仅仅停留在对话编程层面,缺乏一个强制性的机制来约束 AI 行为。

Prompt 可以提醒 AI,但提醒不等于约束。README 里写得再清楚,AI 也不一定会每次去读。真正需要的是一套工程化的 Harness:在 AI 编辑代码的关键动作之前,插入一道检查关卡,把“建议”变成“机制”。

这套体系的核心可以浓缩为一句话:

2. 原理速览:有 AI 规范驱动与无约束的差异

2.1 没有 Harness:AI 直接冲进代码库

没有 Harness 时,AI 的工作流短得可怜:用户提出需求,AI 直接上手编辑文件,然后进入 Review 环节。

sequenceDiagramparticipant User as 开发者participant AI as AI 编码助手participant Repo as 代码仓库participant Review as Code ReviewUser->>AI: 实现一个 TODOLIST 页面AI->>Repo: 直接编辑 index.tsxAI->>Repo: 顺手补样式和状态逻辑AI->>Review: 提交结果Review-->>User: 发现 any、硬编码中文、key=index、组件过重User->>AI: 继续返工

这个流程的问题不在于 AI 不够聪明,而是中间缺少任何强制性的检查点。AI 是否读取了规范、是否遵守了 i18n 规则、有没有限制修改范围,完全依赖模型当时的上下文和运气。

2.2 有 Harness:关键动作前先过闸

AgentFlow Web 的 Harness 基于 Claude Code Hooks 实现。核心是两个 Hook:

  • PreToolUse:在 AI 调用 Edit / Write 前执行
  • PostToolUse:在 AI 调用 Skill 后执行

sequenceDiagramparticipant User as 开发者participant AI as AI 编码助手participant Hook as PreToolUse Hookparticipant Skill as Skill 规范participant State as Session Stateparticipant Repo as 代码仓库participant Snapshot as Snapshot / AuditUser->>AI: 实现一个 TODOLIST 页面AI->>Hook: 请求编辑 .tsx 文件Hook->>Hook: 匹配文件规则Hook->>State: 检查 coding-standards 是否已加载State-->>Hook: 未加载Hook-->>AI: 阻断,提示先加载 coding-standardsAI->>Skill: 加载 coding-standardsAI->>Hook: 再次请求编辑Hook->>Snapshot: 保存原文件快照 + 写审计日志Hook-->>AI: 放行AI->>Repo: 编辑文件

这个机制的关键点在于:AI 不是被禁止写代码,而是必须先带着项目规范写代码。

3. AI 规范驱动工程的设计与实现

我们的 Harness 不是单一脚本,而是一组互相配合的机制。

当前目录结构如下:

.claude/├── README.md# Harness 工程说明文档├── settings.json# Claude Code Hook 注册与权限配置├── hooks/│ ├── pre-edit-check.js# Edit/Write 前置检查:规则匹配、Skill Gate、快照、执行边界│ ├── post-skill-mark.js # Skill 调用后置处理:标记已加载、重置计数、i18n 提醒│ └── lib/│ ├── match-rules.js # 文件路径到 Skill 的映射规则│ ├── session-state.js # 会话状态、编辑计数、TTL 清理、审计日志│ └── snapshot.js# 文件修改前快照、列表、恢复、清理├── session-state/│ ├── .gitignore # 忽略运行期状态文件│ └── audit.log# 追加写入的审计日志├── snapshots/│ └── .gitignore # 忽略运行期快照文件└── skills/├── coding-standards/# 编码规范:React、状态、API、TypeScript、Monorepo├── i18n-coding/ # 国际化规范:intl.get、locale 文件、common key 复用├── ui-design/ # UI 规范:design token、页面骨架、组件模式├── code-review/ # 代码审查编排:按文件类型分发 checklist

3.1 先定义 AI 能在哪些地方动手

不是所有文件都会被拦截。测试文件、类型声明、第三方依赖,甚至是 Harness 自身的文件,都不应该触发规范检查。所以第一层是动作空间注册表:只对关键业务文件启用强约束。

// .claude/hooks/lib/match-rules.jsconst RULES = [{name: 'React 组件',// 组件文件最容易出现 props、memo、i18n、样式等规范问题pattern: /.(tsx|jsx)$/i,skill: 'coding-standards',},{name: 'Zustand store',// store 影响跨页面状态,必须遵守状态拆分和订阅规范pattern: /[/]src[/]store[/].*.ts$/i,skill: 'coding-standards',},{name: 'API 层',// API 函数需要统一命名、泛型和错误处理方式pattern: /[/]src[/]api[/].*.ts$/i,skill: 'coding-standards',},{name: '自定义 Hook',// Hook 需要遵守依赖数组、命名、请求封装等规则pattern: /[/](src[/]hooks|src[/]pages[/][^/]+[/]hooks)[/].*.ts$/i,skill: 'coding-standards',},];

这里的设计边界很明确:src/utils/ 没有被纳入规则。工具函数通常是纯逻辑,不涉及 React 生命周期、组件设计和 i18n。Harness 不应该为了“看起来严格”而随意扩大拦截范围,否则会让 AI 和开发者都感到被打扰。

3.2 没有加载规范,不允许编辑

第二层是 Skill Gate。一旦命中文件规则,Hook 就会检查当前 session 是否已经加载过 coding-standards。如果没有,直接阻断。

// .claude/hooks/pre-edit-check.jsif (!isSkillLoaded(sessionId, rule.skill)) {audit({session: sessionId,tool: toolName,file: filePath,rule: rule.name,skill: rule.skill,action: 'blocked',});// exit 2 是 Claude Code Hook 的阻断信号// stderr 会反馈给 AI,指导它下一步该加载哪个 Skillprocess.stderr.write(`请先执行: Skill(skill="${rule.skill}")`);process.exit(2);}

这个设计的核心意图很明确:把“请遵守规范”从提示词变成执行前置条件。它的适用边界也很清晰:只拦截 Edit 和 Write,不拦截 Read、Grep 或 Glob。AI 可以自由阅读代码和规范,但真正动手前,必须过闸。

3.3 防止 AI 陷入循环修改

AI 有一种典型的失控模式:在同一个文件上改来改去,越改越偏。为此,我们加入了执行边界。

指标当前值行为
单 session 编辑次数15 次/openspec 工程40次超过后阻断
同文件编辑次数5 次警告,不阻断
session TTL7 天自动清理过期状态

// .claude/hooks/lib/session-state.jsconst DEFAULT_EDIT_LIMIT = 15;

function incrementEditCount(sessionId) { const state = loadState(sessionId); // 每次 Edit / Write 放行前计数 +1 state.editCount = (state.editCount || 0) + 1; sa veState(sessionId, state); return state.editCount; }

为什么是 15 次?这是一个经验阈值,不是数学真理。判断依据是:一个明确的小任务如果需要超过 15 次编辑,通常意味着需求理解、类型设计或者上下文已经出现了偏差。这时候,与其让 AI 继续自动修改,不如停下来重新确认方向。

3.4 让 AI 行为可追踪

每次放行、阻断、加载 Skill,都会追加一条审计日志。

{ "ts": "2026-06-12T10:30:00.000Z", "tool": "Edit", "file": "src/pages/todo/index.tsx", "action": "pass-skill-loaded" }

审计日志的边界也需要说清楚:它不替代 Git 历史,也不记录文件内容,只记录操作的元信息。它能很好地回答这些问题:AI 是什么时候开始改这个文件的?有没有被阻断过?是不是在未加载规范时就尝试编辑了?

3.5 给 AI 修改加上回滚保险

最后一层是快照。每次 Edit 或 Write 放行前,Harness 会把原文件内容保存到 .claude/snapshots/{session_id}/ 下。

// .claude/hooks/lib/snapshot.js function sa veSnapshot(sessionId, filePath) { if (!filePath || !fs.existsSync(filePath)) { // 新建文件没有原始内容,不需要快照 return false; } const content = fs.readFileSync(filePath, 'utf8'); const snapshot = { ts: new Date().toISOString(), file: filePath, content, }; fs.writeFileSync(snapshotPath, JSON.stringify(snapshot, null, 2)); return true; }

它的适用边界是“局部误改恢复”,不是替代 Git。如果 AI 把某个文件改坏了,快照能快速找回编辑前的状态;如果是跨分支、跨提交的大规模回滚,依然应该交给 Git。

4. 实战迁移:用 Harness 实现一个 TODOLIST 页面

为了验证这套 Harness 的实际效果,我们做了一次小实验:让 AI 实现同一个 TODOLIST 页面,一组模拟没有 Harness 的输出,另一组按 Harness 规范输出。实验产物保存在:

docs/harness-todolist-experiment/ ├── no-harness/ └── with-harness/

任务要求完全一致:需要实现任务列表、新增、完成切换、删除、空状态、用户可见文本、样式和类型。

4.1 无 Harness:代码能跑,但 Review 压力转嫁给人

没有 Harness 的版本,就是典型的“一把梭”实现:全部逻辑都塞进 index.tsx 里,状态、渲染、事件全在同一个组件中。

// docs/harness-todolist-experiment/no-harness/index.tsx export default function TodoPage() { const [text, setText] = useState(''); // 问题 1:any[] 逃逸,后续 item 字段完全失去类型保护 const [todos, setTodos] = useState([]); return (

{/* 问题 2:用户可见中文直接硬编码 */}

任务列表

setText(e.target.value)} /> {todos.map((item, index) => ( // 问题 3:列表 key 使用 index,删除/排序后可能导致渲染错位

{item.text}

))}

); }

这类代码的问题在于:它看起来完成了需求,但把所有的规范成本,都一股脑甩给了 Review。

4.2 有 Harness:先加载规范,再写代码

有 Harness 的时候,AI 第一次尝试写 .tsx 文件就会被拦截:

[harness] 该文件命中规则「React 组件」 要求:动手前必须先调用 Skill 工具加载 `coding-standards`。 如涉及国际化文案:Skill(skill="i18n-coding")

随后 AI 会加载:

  • coding-standards:组件设计、类型、状态、React 模式
  • i18n-coding:用户可见文本国际化

最终输出会自然地拆分成多个文件:

with-harness/ ├── types.ts ├── TodoItem.tsx ├── TodoList.tsx ├── index.tsx └── index.less

核心代码如下:

// docs/harness-todolist-experiment/with-harness/types.ts export interface TodoItem { // 使用稳定 id,避免列表 key 使用 index id: string; title: string; completed: boolean; }

// docs/harness-todolist-experiment/with-harness/TodoList.tsx export interface TodoListProps { // 只传子组件需要的数据,不传整个页面状态 items: TodoItemType[]; onToggle: (id: string) => void; onDelete: (id: string) => void; } export default function TodoList({ items, onToggle, onDelete }: TodoListProps) { if (items.length === 0) { // 用户可见文案统一走 intl.get return ; } return (

{items.map(item => ( ))}

); }

// docs/harness-todolist-experiment/with-harness/index.tsx

export default function TodoPage() { const [inputValue, setInputValue] = useState(''); const [items, setItems] = useState([]);

const handleAdd = () => { const title = inputValue.trim(); if (!title) return; // 使用函数式 setState,避免依赖旧闭包状态 setItems(prevItems => [ ...prevItems, { id: `${Date.now()}`, title, completed: false, }, ]); setInputValue(''); };

return (

{intl.get('todo.page.title')}

setInputValue(event.target.value)} />

); }

样式也从硬编码色值切换为 token:

.page { /* 使用 design token,避免散落的 #fff / #eee / #999 */ padding: var(--spacing-6); background: var(--color-bg-container); } .completedTitle { flex: 1; color: var(--color-text-tertiary); text-decoration: line-through; }

5. AI 规范驱动带来的实际提升效果

这次实验的统计脚本对两组代码做了静态扫描,统计项包括文件数、tsx 组件数、代码行数、anykey={index}intl.get、硬编码中文、硬编码色值和 Review 问题数。

5.1 实验结果对比

指标无 Harness有 Harness变化
文件数25组件和类型拆分更清晰
TSX 组件数13从单文件页面变成页面 + 列表 + 项
有效代码行数60124代码更多,但职责边界更明确
any 次数10类型逃逸消除
key={index} 次数10列表渲染风险消除
intl.get 次数05用户可见文案全部国际化
硬编码中文数量50i18n 漏项消除
硬编码色值数量30样式 token 化
Review 问题数60基础规范问题前置消化

无 Harness 版本被统计出的 6 个 Review 问题是:

  1. 存在 any 类型逃逸
  2. 列表 key 使用 index
  3. 存在硬编码中文文案
  4. 存在硬编码色值
  5. 未使用 intl.get
  6. 组件拆分不足

5.2 如何理解这些数据

有 Harness 版本的代码行数更多,这完全在预期之中。工程规范的目标不是让代码更短,而是让代码更容易维护。types.tsTodoList.tsxTodoItem.tsx 的拆分增加了文件数量,却实实在在地降低了页面组件的认知负担。

真正值得关注的是,Review 问题从 6 个直接降到了 0 个。这里的 0 不是说代码完美无缺了,而是说那些基础的规范类问题,被前置消化在了编码阶段。Review 工作得以回归到它更有价值的轨道上:交互是否符合产品预期?空输入是否需要提示?是否需要持久化?是否要接 API?

6. 团队落地的三个阶段

第一阶段:先把规范写清楚。 只做 CLAUDE.md 和 Skill,不做阻断。目标是让 AI 有规则可依,让团队先统一语言。

第二阶段:关键文件启用阻断。 只拦截 .tsxsrc/apisrc/storesrc/hooks 这类高风险文件。不要一开始就试图全仓库管控。

第三阶段:接入审计和回滚。 等 AI 参与的任务变多之后,再启用 audit log、snapshot 和 execution boundary。到那时候,这些机制的价值会体现得非常明显。

7. 总结:Prompt 是建议,Harness 是机制

AI 编码的核心矛盾,不在于模型能不能写代码,而在于团队能不能承接它带来的不确定性。

Prompt 解决的是“告诉 AI 怎么做”;Skill 解决的是“把规范模块化注入上下文”;Hook 解决的是“在关键动作前强制检查”;Snapshot 和 Audit 解决的是“出问题后可追踪、可恢复”。

来源:https://juejin.cn/post/7652277909156970496
上一篇组数据证实:提示词越礼貌AI越敷衍 下一篇OpenAI Codex Sites:开发网站变成一句话的事
本站内容用于信息整理与展示,如有侵权或内容问题请及时联系处理。

相关推荐

补充同频道和同主题内容,方便继续浏览更多相关内容。

同类最新

继续查看同栏目最近更新的文章。

更多
你的下一代Claw不止OpenClaw一种选择
AI教程 · 2026-07-16

你的下一代Claw不止OpenClaw一种选择

某品牌推动人工智能全民普及,但其实现机制实则无聊,核心是厂商售卖代币的营销。为此打造企业级人工智能代理平台,提供管理后台、统计分析等能力,让人工智能真正落地企业场景,实现可运维与可量化。

淘宝商品详情API实时更新SKU价格与库存的方法
AI教程 · 2026-07-16

淘宝商品详情API实时更新SKU价格与库存的方法

淘宝商品详情API的实时性优化需从分层缓存隔离、分级异步轮询、精准字段筛选、SKU增量更新、超时重试与兜底、大促专项优化及消息回调机制入手,平衡接口稳定性、成本与时效性,实现企业级秒级数据监控。

年AI绘画新手超详细入门教程
AI教程 · 2026-07-16

年AI绘画新手超详细入门教程

在使用AI绘画工具时,许多人都常常困惑于一个问题:AI绘画关键词到底该如何撰写,才能生成真正高质量、富有质感的画面?答案其实并不复杂,关键就在于“合适”二字。 选对关键词,是生成好作品的第一步。如果关键词过于简单,比如只写“猫”“风景”,生成的画面往往平淡无奇,缺乏惊喜;但如果一头扎进复杂词汇堆里,

Agentic AI重启入口之争新趋势
AI教程 · 2026-07-16

Agentic AI重启入口之争新趋势

大模型突破推动AgenticAI进入超级创新周期,AI助手成为新一代用户入口。大模型创业企业、互联网平台、硬件厂商等各方围绕模型能力、Agent生态、流量和软硬件整合展开激烈竞争,争夺未来话语权。

OpenAI神秘草莓模型两周内上线,数学推理暴涨月费200美元已有用户付费
AI教程 · 2026-07-16

OpenAI神秘草莓模型两周内上线,数学推理暴涨月费200美元已有用户付费

OpenAI计划两周内发布推理模型“草莓”,需思考十至二十秒后再响应,虽然更智能但更慢也更贵。其月费或为200美元,擅长数学、编码及复杂任务。该模型基于自我博弈强化学习范式,旨在为下一代模型“猎户座”生成高质量数据。