
1. 背景与问题前端工程化发展至今脚手架、构建、测试、CI/CD 已经相当成熟但在「Code Agent 时代」出现了一个新的工程化课题如何把AI 编码工具Claude Code接入既有前端工程让它既能安全地写代码又能自动化完成测试、修复、评审形成可复用的Harness工程化夹具/驱动骨架。传统前端项目里「Harness」通常指测试夹具Test Harness或运行环境封装层用来隔离被测系统并注入依赖。本文把 Harness 的概念向前推进一步为 Claude Code 构建一个可执行、可观测、可约束的工程化运行环境让 AI 在本地仓库中完成「改代码 → 跑测试 → 修问题 → 出报告」的闭环。本文会解决以下问题Claude Code 如何通过CLI 无头模式接入 Node 脚本而非手动对话。如何用自定义 Slash Command沉淀团队工作流。如何用Hooks做质量门禁和危险操作拦截。如何把前端的lint / type-check / test接入反馈闭环。如何搭建一个可复用的 Harness 工程一键驱动整套流程。说明本文以 Claude CodeclaudeCLIAnthropic 官方命令行编码工具为例所有方案同样可抽象到其他支持 CLI / MCP 的 Code Agent。2. 整体架构设计我们把方案拆为四层核心思想是Harness 作为稳定的执行外壳Claude Code 作为会写代码的执行单元失败通过开发者 / CI 触发Harness CLINode 脚本Claude Code Headless自定义 Slash CommandsSubagent 子代理Skills 技能包前端工程产物质量门禁lint / tsc / vitest输出报告 产物Harness CLI负责准备环境、拼装 prompt、调用claude、解析结果、跑质量门禁、决定是否重试。Claude Code Headlessclaude -p无头模式可被 Node/Shell 驱动。自定义命令 / Subagent / Skill把团队规范固化为可复用资产。质量门禁AI 改完代码后Harness 立即执行校验失败则把错误信息回灌给 Claude Code 继续修复。3. 环境准备3.1 前置依赖node-v# 18npm-v# 9claude--version# 需先安装 Claude Code CLI安装 Claude Code如未安装npminstall-ganthropic-ai/claude-code claude# 首次运行会引导登录 Anthropic 账号3.2 项目结构我们以一个 Vue 3 TypeScript Vite 项目为例Harness 单独放在.harness/目录与业务代码解耦frontend-harness-demo/ ├── .claude/ │ ├── settings.json # Claude Code 项目级配置 │ ├── commands/ # 自定义 Slash Commands │ │ ├── fix-lint.md │ │ └── review.md │ └── skills/ # 技能包 │ └── fe-standard/ │ └── SKILL.md ├── .harness/ │ ├── cli.mjs # Harness 主入口 │ ├── prompts.mjs # Prompt 模板 │ ├── gates.mjs # 质量门禁 │ └── report.mjs # 报告生成 ├── src/ │ └── ... # 业务代码 ├── package.json ├── vitest.config.ts └── tsconfig.json4. 实战一Claude Code Headless 最小闭环Claude Code 提供无头模式-p / --print可以在非交互环境下执行一次 prompt 并输出纯文本非常适合被脚本包裹。先写一个最简单的 Harness// .harness/cli.mjsimport{execSync}fromnode:child_process;consttaskprocess.argv.slice(2).join( );if(!task){console.error(Usage: node .harness/cli.mjs 任务描述);process.exit(1);}constcommandclaude -p ${task} --output-format json;try{constrawexecSync(command,{encoding:utf8,maxBuffer:1024*1024*10,});constresultJSON.parse(raw);console.log( Claude Code 输出 );console.log(result.result);}catch(err){console.error(Claude Code 执行失败,err.message);process.exit(1);}执行node.harness/cli.mjs在 src/utils 下新增 formatDate.ts把时间戳格式化为 YYYY-MM-DD HH:mm:ss这样就完成了「脚本驱动 AI 写代码」的第一步。但直接裸调存在几个问题缺少上下文Claude Code 需要明确的仓库背景与约束。缺少验证改完没有自动跑测试。缺少重试一次失败就退出无法形成闭环。下面逐层补齐。5. 实战二Prompt 模板与上下文注入把易变的「任务」和稳定的「工程约束」分离是 Harness 化的关键。我们用一个prompts.mjs负责拼接// .harness/prompts.mjsimportfsfromnode:fs;importpathfromnode:path;constPROJECT_ROOTprocess.cwd();exportfunctionbuildTaskPrompt(task){constmdFilescollectMarkdown(PROJECT_ROOT,[node_modules,.git,dist]);return[你是一名资深前端工程师请在当前仓库中完成以下任务。,,## 工程约束必须遵守,- 使用 TypeScript禁止 any除非有充分理由并加注释。,- 遵循项目已有的代码风格新增文件需要包含清晰的 JSDoc。,- 组件一律用 Vue 3 Composition API script setup langts。,- 不要修改 package.json 中与任务无关的依赖。,- 修改完成后列出所有改动文件及原因。,,## 背景资料节选,...mdFiles.slice(0,8).map((f)###${f.path}\n${f.content}),,## 本次任务,task,].join(\n);}functioncollectMarkdown(root,ignores){constresults[];constwalk(dir){for(constnameoffs.readdirSync(dir)){if(ignores.includes(name))continue;constfullpath.join(dir,name);conststatfs.statSync(full);if(stat.isDirectory()){walk(full);}elseif(name.endsWith(.md)){results.push({path:path.relative(root,full),content:fs.readFileSync(full,utf8).slice(0,1200),});}}};walk(root);returnresults;}然后在主入口接入// .harness/cli.mjs 追加import{buildTaskPrompt}from./prompts.mjs;consttaskprocess.argv.slice(2).join( );constpromptbuildTaskPrompt(task);execSync(claude -p${shellQuote(prompt)}--output-format json,{encoding:utf8,stdio:[ignore,pipe,inherit],});注意直接拼 shell 字符串有注入风险生产环境建议改用临时文件 claude -p $(cat prompt.txt)或通过spawn数组参数传递。import{spawnSync}fromnode:child_process;functionrunClaude(prompt){constresultspawnSync(claude,[-p,prompt,--output-format,json],{encoding:utf8,maxBuffer:1024*1024*20,});if(result.status!0){thrownewError(claude exited with${result.status}:${result.stderr});}returnJSON.parse(result.stdout);}6. 实战三质量门禁与自动修复闭环这是 Harness 的核心价值AI 改完必须经过门禁失败则把错误回灌循环修复。6.1 门禁定义// .harness/gates.mjsimport{spawnSync}fromnode:child_process;constGATES[{name:type-check,command:npm,args:[run,type-check]},{name:lint,command:npm,args:[run,lint]},{name:unit-test,command:npm,args:[run,test:unit,--,--run]},];exportfunctionrunGates(){constpassed[];constfailed[];for(constgateofGATES){constresultspawnSync(gate.command,gate.args,{encoding:utf8,maxBuffer:1024*1024*10,});if(result.status0){passed.push(gate.name);}else{failed.push({name:gate.name,output:result.stdoutresult.stderr});}}return{passed,failed};}6.2 闭环主流程// .harness/cli.mjs 完整闭环import{runGates}from./gates.mjs;import{buildTaskPrompt,buildFixPrompt}from./prompts.mjs;asyncfunctionmain(){consttaskprocess.argv.slice(2).join( );constMAX_ROUNDS3;// 第一轮执行任务letpromptbuildTaskPrompt(task);letresultrunClaude(prompt);console.log([harness] 任务执行完成进入门禁校验);// 循环修复for(letround1;roundMAX_ROUNDS;round){const{passed,failed}runGates();if(failed.length0){console.log([harness] 全部门禁通过 -${passed.join(, )});return;}console.log([harness] 第${round}轮门禁失败${failed.map((f)f.name).join(, )});constfixPromptbuildFixPrompt(failed);resultrunClaude(fixPrompt);}console.error([harness] 达到最大修复轮数仍有门禁未通过);process.exit(1);}main();6.3 修复 Prompt// .harness/prompts.mjs 追加exportfunctionbuildFixPrompt(failed){constdetailsfailed.map((f)###${f.name}失败输出\n\\\\n${f.output.slice(0,4000)}\n\\\).join(\n\n);return[上一轮修改未通过质量门禁请阅读下面的错误输出定位并修复问题。,只修改与失败相关的代码不要重构无关逻辑。,,details,,修复后请确认类型检查通过、lint 无误、单元测试通过。,].join(\n);}package.json 中对应的脚本{scripts:{type-check:vue-tsc --noEmit,lint:eslint . --ext .ts,.vue,test:unit:vitest}}7. 实战四自定义 Slash Commands 沉淀工作流让团队每个人都手写冗长 prompt 不现实Claude Code 支持把常用流程沉淀为.claude/commands/*.md在会话里用/命令名调用。7.1 代码评审命令!-- .claude/commands/review.md -- 请对当前改动git diff 相对 HEAD做严格代码评审重点检查 1. **正确性**逻辑边界、空值、异步竞态、内存泄漏。 2. **类型安全**是否存在 any、类型断言滥用、可空值未处理。 3. **可维护性**命名语义、函数职责单一、重复代码。 4. **前端专项** - 组件副作用是否在 onUnmounted 清理 - 是否存在不必要的响应式依赖 - 样式是否破坏响应式布局 - 可访问性语义标签、键盘导航、焦点管理。 输出格式 - 按严重程度分级 阻塞 / 建议 / 微优化。 - 每条问题给出文件位置、问题描述、修复建议代码片段。 - 最后给出「是否可以合并」的结论。 只评审本次改动不要修改代码。7.2 修复 Lint 命令!-- .claude/commands/fix-lint.md -- 请运行 npm run lint并修复所有可自动修复的问题。 约束 - 优先使用 npx eslint . --fix - 对无法自动修复的问题逐个分析并手工修正 - 不改变业务逻辑不升级依赖 - 修复完成后再次运行 lint 确认 0 错误。7.3 生成测试命令!-- .claude/commands/gen-test.md -- 请为当前未覆盖的核心模块生成 Vitest 单元测试。 要求 - 使用 Vitest vue/test-utils依赖与项目保持一致 - 覆盖正常路径、边界值、异常分支 - 测试命名使用「应该…」可读风格 - 不修改被测源码除非发现明显 bug 需要先报告。 生成后运行 npm run test:unit -- --run 确认通过。这样开发者在 Claude Code 交互会话里输入/review、/fix-lint、/gen-test即可复用团队规范。8. 实战五Hooks 做危险操作拦截与自动校验Claude Code 的 Hooks 允许在 AI 执行特定操作前后插入自定义脚本适合做「安全护栏」。配置位于.claude/settings.json。8.1 项目级配置{permissions:{allow:[Bash(npm run type-check:*),Bash(npm run lint:*),Bash(npm run test:unit:*),Read(~/.harness/**)],deny:[Bash(git push:*),Bash(rm -rf:*),Bash(git reset --hard:*),Edit(.env:*),Edit(.npmrc:*)]},hooks:{PostToolUse:[{matcher:Edit|Write,hooks:[{type:command,command:bash .claude/hooks/auto-format.sh}]}],PreToolUse:[{matcher:Bash,hooks:[{type:command,command:bash .claude/hooks/guard-bash.sh}]}]}}关键点permissions.deny直接拦截git push、rm -rf、git reset --hard等危险命令即使 AI 想执行也会被 CLI 权限层拒绝。PostToolUse在每次写文件后触发自动格式化。PreToolUse在执行任意 Bash 前做二次校验。8.2 自动格式化 Hook#!/usr/bin/env bash# .claude/hooks/auto-format.shset-euopipefail# 读取 stdin 中的 JSON 工具输入INPUT$(cat)# 示例仅对 .vue/.ts 文件触发 prettierFILE$(echo$INPUT|jq-r.tool_input.file_path // empty)if[[-n$FILE$FILE~\.(ts|vue|js)$]];thennpx prettier--write$FILE/dev/null21||truefiecho$INPUT8.3 危险命令守卫 Hook#!/usr/bin/env bash# .claude/hooks/guard-bash.shset-euopipefailINPUT$(cat)COMMAND$(echo$INPUT|jq-r.tool_input.command // )DANGEROUS_PATTERNS(git pushrm -rfgit reset --hardsudo docker rm -f)forpatternin${DANGEROUS_PATTERNS[]};doif[[$COMMAND*$pattern*]];thenecho{hookSpecificOutput:{hookEventName:PreToolUse,permissionDecision:deny,permissionDecisionReason:该命令被 Harness 安全策略拦截请人工确认。}}exit2fidone# 允许执行echo{hookSpecificOutput:{hookEventName:PreToolUse,permissionDecis
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。