菜鸟AI - 让提示词生成更简单! 全站导航 全站导航
AI工具安装 新手教程 进阶教程 辅助资源 AI提示词 热点资讯 技术资讯 产业资讯 内容生成 模型技术 AI信息库

已有账号?

首页 > 资讯 > 2025年高效前端Harness工程化工作流增强:Claude Code方案深度实战测评与精选推荐
其他资讯 综合资讯

2025年高效前端Harness工程化工作流增强:Claude Code方案深度实战测评与精选推荐

2026-06-08
阅读 0
热度 0
作者 菜鸟AI编辑部
摘要

摘要

前端 Harness 工程化 - Claude Code 工作流增强方案 在 React + TypeScript 项目里用 Claude Code 写代码

前端 Harness 工程化 - Claude Code 工作流增强方案


在 React + TypeScript 项目里用 Claude Code 写代码,效率确实高,但 AI 生成的内容偶尔也会出现一些低级错误——比如突然来个 any 类型、内联样式炸开、甚至不小心执行个 rm -rf 把项目源码删了……这些坑,踩过的都懂。

有没有办法让 AI 在犯错之前就被拦住?答案是肯定的。下面这套方案,就是专门为 Claude Code + React + TypeScript 项目量身打造的工程化增强方案。核心思路很简单:利用 Claude Code 的 Hook 机制,在关键环节嵌入质量检查,从源头约束 AI 的编码行为。说白了,就是给 AI 装上一道「护栏」。

前端 Harness 工程化 - Claude Code 工作流增强方案

核心设计理念(六层防御)

整个方案围绕六层防御展开,每一层都卡在关键位置:

层级机制作用
第一层危险操作拦截PreToolUse 在 Bash 命令执行前介入,直接拦截 rm -rf /、强制推送到 main、DROP DATABASE 等毁灭性操作
第二层书写规范校验PostToolUse 每次写入文件后自动校验,不符合项目规范直接打回
第三层技能强制评估PostToolUse 写完后强制匹配并激活对应 Skill,确保规范不只是嘴上说说,而是真正被执行
第四层上下文智能注入根据操作的文件路径,自动把对应的规范注入到对话上下文中
第五层结束前验证Stop Hook 在每次回答结束时强制进行五项质量门检查
第六层专业化分 Agent针对不同场景(代码审查、性能审计等)使用专门的 Subagent 处理,互不干扰

从零开始配置

接下来一步步拆解,怎么把这套东西搭起来。

前置要求

  • 已安装 Claude Code CLI
  • React + TypeScript 项目
  • 项目已遵从本文所述规范

第一步:创建目录结构

在项目根目录创建 .claude/ 目录,结构如下:

.claude/
├── settings.json          # Claude 设置(Hook 配置)
├── CLAUDE.md              # 项目级规范说明
├── hooks/                 # Hook 脚本
│   ├── inject-context.js  # 上下文注入
│   ├── validate-frontend.js # 前端规范验证
│   ├── block-dangerous-ops.js # 危险操作拦截
│   └── skill-forced-eval.js
├── rules/                 # 路径规则(按路径注入不同规范)
│   ├── component-rules.md
│   ├── api-rules.md
│   ├── style-rules.md
│   └── typescript-rules.md
├── context/
│   └── fe-conventions.md  # 完整前端规范(SessionStart 注入)
├── agents/                # 自定义 Subagent
│   ├── component-analyzer.md
│   ├── api-checker.md
│   ├── code-reviewer.md
│   └── performance-auditor.md
├── commands/              # 自定义 Slash 命令
│   ├── dev.md
│   ├── check.md
│   ├── crud.md
│   └── code-format.md
└── skills/                # 自定义 Skills
    ├── ui-pc/SKILL.md
    ├── store-pc/SKILL.md
    └── ...

如果你面前已经有一个配置好的项目,可以直接把 .claude 目录整个复制到新项目里:

# 从已有项目复制配置
cp -r .claude /path/to/your-project/

第二步:配置 settings.json

这是 Hook 机制的总控配置文件,所有拦截、校验、注入的逻辑都在这里声明:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "node "$CLAUDE_PROJECT_DIR/.claude/hooks/validate-frontend.js"",
            "timeout": 30,
            "statusMessage": "验证前端规范..."
          },
          {
            "type": "command",
            "command": "node "$CLAUDE_PROJECT_DIR/.claude/hooks/skill-forced-eval.js"",
            "timeout": 10,
            "statusMessage": "技能匹配检查..."
          }
        ]
      }
    ],
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "node "$CLAUDE_PROJECT_DIR/.claude/hooks/block-dangerous-ops.js"",
            "timeout": 10
          }
        ]
      }
    ],
    "SessionStart": [
      {
        "matcher": "compact",
        "hooks": [
          {
            "type": "command",
            "command": "node "$CLAUDE_PROJECT_DIR/.claude/hooks/inject-context.js"",
            "statusMessage": "重新注入前端规范..."
          }
        ]
      }
    ],
    "Stop": [
      {
        "hooks": [
          {
            "type": "prompt",
            "prompt": "结束前请验证:(1) 所有请求的功能已实现并测试。(2) 新代码中无 TODO/FIXME 注释遗留。(3) CLAUDE.md 的当前迭代状态已更新。(4) 生产代码中无 console.log 或 any 类型。(5) pnpm run lint 通过。如有未完成项,告知用户但不要重新开始工作。",
            "model": "claude-haiku-4-5-20251001"
          }
        ]
      }
    ]
  }
}

第三步:复制 Hook 脚本

配置文件写好之后,接下来把真正干活的脚本放进去。每个脚本的职责都很明确,逐一来看。

hooks/block-dangerous-ops.js - 危险操作拦截
async function main() {
  if (process.stdin.isTTY) {
    process.exit(0);
  }  const chunks = [];
  for await (const chunk of process.stdin) {
    chunks.push(chunk);
  }  let input;
  try {
    input = JSON.parse(chunks.join(''));
  } catch {
    process.exit(0);
  }  const cmd = (input?.tool_input?.command || '').trim();  const blocked = [];  // Rule 1: Block rm -rf on non-node_modules/dist paths
  if (/rms+(-w*rw*fw*|--recursives+--force)s+/.test(cmd)) {
    if (!/node_modules|dist|.tmp/.test(cmd)) {
      blocked.push('Dangerous rm -rf detected (only allowed for node_modules/dist)');
    }
  }  // Rule 2: Block git force push to main/master
  if (/gits+push.*--force/.test(cmd)) {
    if (/main|master/.test(cmd)) {
      blocked.push('Force push to main/master is blocked');
    } else if (!/feature|fix|bugfix|develop|release/.test(cmd)) {
      blocked.push('Force push detected -- verify target branch is not main/master');
    }
  }  // Rule 3: Block dropping databases
  if (/bdrops+(database|schema)b/i.test(cmd)) {
    blocked.push('DROP DATABASE/SCHEMA is blocked');
  }  if (blocked.length > 0) {
    blocked.forEach(msg => console.error(`BLOCKED: ${msg}`));
    process.exit(2);
  }  process.exit(0);
}main().catch(() => process.exit(0));

拦截策略:只允许对 node_modulesdist.tmp 执行 rm -rf,禁止直接删除项目源代码——这条规则能救你很多次。


hooks/validate-frontend.js - 前端规范自动验证
import { readFileSync } from 'node:fs';
import { extname, basename, sep } from 'node:path';const SEP = sep === '\' ? '\\' : '/';function isComponentFile(filePath) {
  return new RegExp(`src${SEP}components${SEP}|src${SEP}pages${SEP}`).test(filePath)
    || /src[\/]components[\/]/.test(filePath)
    || /src[\/]pages[\/]/.test(filePath);
}function isApiOrStoreFile(filePath) {
  return /src[\/]api[\/]/.test(filePath) || /src[\/]stores[\/]/.test(filePath);
}function isLegacyCss(filePath) {
  return /(?:App|index).css$/.test(filePath);
}async function main() {
  if (process.stdin.isTTY) {
    process.exit(0);
  }  const chunks = [];
  for await (const chunk of process.stdin) {
    chunks.push(chunk);
  }  let input;
  try {
    input = JSON.parse(chunks.join(''));
  } catch {
    process.exit(0);
  }  const filePath = input?.tool_input?.file_path || '';
  const ext = extname(filePath);  if (!['.ts', '.tsx', '.less', '.css'].includes(ext)) {
    process.exit(0);
  }  let content;
  try {
    content = readFileSync(filePath, 'utf8');
  } catch {
    process.exit(0);
  }  const errors = [];
  const warnings = [];  // TypeScript: always run base checks
  if (['.ts', '.tsx'].includes(ext)) {
    validateTypeScript(content, filePath, errors, warnings);    if (isComponentFile(filePath)) {
      validateComponent(content, filePath, errors, warnings);
    }    if (isApiOrStoreFile(filePath)) {
      validateApiLayer(content, filePath, errors, warnings);
    }
  }  // Styles: run expanded checks
  if (['.less', '.css'].includes(ext)) {
    validateStyles(content, filePath, errors, warnings);
  }  if (errors.length > 0) {
    console.error(`=== Frontend validation FAILED: ${basename(filePath)} ===`);
    errors.forEach(e => console.error(`  CRITICAL: ${e}`));
    warnings.forEach(w => console.error(`  WARNING: ${w}`));
    process.exit(2);
  }  if (warnings.length > 0) {
    console.error(`Warnings in ${basename(filePath)}:`);
    warnings.forEach(w => console.error(`  ${w}`));
  }  console.log(`Frontend validation passed: ${basename(filePath)}`);
  process.exit(0);
}// --- Spec 1: ESLint-level TypeScript checks ---
function validateTypeScript(content, filePath, errors, warnings) {
  const lines = content.split('n');  lines.forEach((line, i) => {
    const lineNum = i + 1;
    const trimmed = line.trim();    if (trimmed.startsWith('//') || trimmed.startsWith('*') || trimmed.startsWith('/*')) {
      return;
    }    if (/: anyb/.test(trimmed) || /as any/.test(trimmed) || //.test(trimmed)) {
      errors.push(`Line ${lineNum}: Forbidden 'any' type usage`);
    }    if (/console.log/.test(trimmed) && !/.(test|spec)./.test(filePath)) {
      warnings.push(`Line ${lineNum}: console.log found (remove before production)`);
    }    if (/bvars+w/.test(trimmed)) {
      errors.push(`Line ${lineNum}: Use const/let instead of var`);
    }    if (/(@ts-ignore|@ts-nocheck)/.test(trimmed)) {
      errors.push(`Line ${lineNum}: Forbidden @ts-ignore or @ts-nocheck`);
    }
  });
}// --- Spec 2: Component Development checks ---
function validateComponent(content, filePath, errors, warnings) {
  const fileName = basename(filePath);  // PascalCase file name check
  if (!/^[A-Z][A-Za-z0-9]*.tsx$/.test(fileName)) {
    errors.push(`Component file must be PascalCase.tsx (got: ${fileName})`);
  }  const lines = content.split('n');
  lines.forEach((line, i) => {
    const trimmed = line.trim();
    if (trimmed.startsWith('//') || trimmed.startsWith('*')) return;    if (/style={{/.test(line)) {
      errors.push(`Line ${i + 1}: Forbidden inline style (style={{}})`);
    }    // No key={index}
    if (/key={(?:i|idx|index)}/.test(line)) {
      errors.push(`Line ${i + 1}: Forbidden key={index} in list rendering`);
    }
  });  // Props interface must end with "Props"
  const propsMatch = content.match(/interfaces+(w+)s*{/g);
  if (propsMatch) {
    propsMatch.forEach(m => {
      const name = m.match(/interfaces+(w+)/)[1];
      if (name && !name.endsWith('Props')) {
        errors.push(`Props interface must end with "Props" (got: ${name})`);
      }
    });
  }
}// --- Spec 4: API/Data Layer checks ---
function validateApiLayer(content, filePath, errors, warnings) {
  const lines = content.split('n');  lines.forEach((line, i) => {
    const trimmed = line.trim();
    if (trimmed.startsWith('//') || trimmed.startsWith('*') || trimmed.startsWith('/*')) return;    // API endpoint must use /api prefix
    if ((/(?:fetch|axios|request)s*(s*['"`]/.test(trimmed) || /['"`]s*+s*['"`]/.test(trimmed))) {
      const urlMatch = trimmed.match(/['"`](/[^'"`]*?)['"`]/);
      if (urlMatch && !urlMatch[1].startsWith('/api')) {
        errors.push(`Line ${i + 1}: API endpoint must start with /api (got: ${urlMatch[1]})`);
      }
    }
  });  // Store file naming: must be useXxxStore.ts
  if (/stores[\/]/.test(filePath)) {
    const fileName = basename(filePath);
    if (!/^use[A-Z]w*Store.ts$/.test(fileName)) {
      errors.push(`Store file must match useXxxStore.ts pattern (got: ${fileName})`);
    }    // Must use zustand create()
    if (!/froms+['"]zustand['"]/.test(content)) {
      errors.push('Store file must import from "zustand"');
    }
  }
}// --- Spec 3: Design System/Style checks ---
function validateStyles(content, filePath, errors, warnings) {
  if (isLegacyCss(filePath)) {
    const lines = content.split('n');
    lines.forEach((line, i) => {
      if (/!important/.test(line) && !line.trim().startsWith('/*') && !line.trim().startsWith('*')) {
        warnings.push(`Line ${i + 1}: !important found (should use specificity or justify with comment)`);
      }
    });
    return;
  }  const lines = content.split('n');
  lines.forEach((line, i) => {
    const trimmed = line.trim();    if (trimmed.startsWith('//') || trimmed.startsWith('/*') || trimmed.startsWith('*')) return;    if (/!important/.test(line)) {
      warnings.push(`Line ${i + 1}: !important found (should use specificity or justify with comment)`);
    }    // No hardcoded hex colors (outside variable definitions)
    const hexMatch = line.match(/#[0-9a-fA-F]{3,8}b/);
    if (hexMatch && !/^s*@/.test(trimmed) && !/^s*--/.test(trimmed)) {
      errors.push(`Line ${i + 1}: Hardcoded hex color "${hexMatch[0]}" — use a Less variable instead`);
    }    // No z-index literals (should use variable)
    const zIndexMatch = line.match(/z-indexs*:s*(d+)/);
    if (zIndexMatch && !/@/.test(trimmed) && !/vars*(/.test(trimmed)) {
      warnings.push(`Line ${i + 1}: z-index literal "${zIndexMatch[1]}" — use a Less variable instead`);
    }    // Magic number pixel values (skip 0px, 1px, 100%)
    const pxMatches = line.match(/b(d+)pxb/g);
    if (pxMatches && !/^s*@/.test(trimmed) && !/^s*--/.test(trimmed)) {
      const problematic = pxMatches.filter(m => parseInt(m) > 1);
      if (problematic.length > 0 && !/calc(/.test(trimmed)) {
        warnings.push(`Line ${i + 1}: Magic pixel values ${problematic.join(', ')} — use Less variables from variables.less`);
      }
    }
  });
}main().catch(() => process.exit(0));

验证维度

类别检查项级别
TypeScriptany 类型Error
TypeScriptvar 声明Error
TypeScript@ts-ignoreError
TypeScriptconsole.log(非测试)Warning
组件文件名 PascalCaseError
组件内联样式 style={{}}Error
组件key={index}Error
组件Props 接口以 Props 结尾Error
API接口以 /api 开头Error
Store文件名 useXxxStore.tsError
Store从 zustand 导入Error
样式硬编码十六进制颜色Error
样式魔法像素值Warning
样式字面量 z-indexWarning
样式!important 无注释Warning

hooks/inject-context.js - 上下文智能注入
import { readFileSync, existsSync } from 'node:fs';
import { join } from 'node:path';async function main() {
  const projectDir = process.env.CLAUDE_PROJECT_DIR;
  if (!projectDir) {
    process.exit(0);
  }  const conventionFile = join(projectDir, '.claude', 'context', 'fe-conventions.md');  if (existsSync(conventionFile)) {
    const content = readFileSync(conventionFile, 'utf8');
    console.log(content);
  }  process.exit(0);
}main().catch(() => process.exit(0));

这个脚本的作用很直接:在 Session 启动(compact 后)自动把完整的前端规范注入到对话上下文中,确保 AI 从一开始就知道要遵守什么规矩。


第四步:配置路径规则注入

rules/ 目录下,每个规则文件以 --- frontmatter 开头,声明匹配的路径。这样一来,Claude 编辑不同路径的文件时,会自动加载对应的规范。

rules/component-rules.md
---
paths:
  - "src/components/**"
  - "src/pages/**"
---# 组件开发规则(操作组件/页面文件时自动加载)## 文件结构
- 每个 .tsx 文件一个组件
- 同目录同名 .less 文件(如 Button.tsx + Button.less)
- Props 接口定义在同文件中(XxxProps 后缀)
- 类型导入使用 `import type` 语法## 命名规范
- 组件文件:PascalCase.tsx
- 组件函数:PascalCase,与文件名一致
- Props 接口:组件名Props(如 ButtonProps)
- 事件处理:onXxx(如 onClick、onSubmit)
- 组件内自定义 hook:use组件名Action## 导出模式
```typescript
export function Button({ children, variant }: ButtonProps) {
  // 实现
}
export default Button;

禁止模式

  • style={{}}(使用 className + .less)
  • key={index} 在 .map() 中(使用稳定 ID)
  • 直接 fetch() 调用(使用 api/ 层)
  • useEffect 缺少依赖项
  • 超过 2 层的 props 透传(使用 zustand 或 Context)

#### `rules/api-rules.md````markdown
---
paths:
  - "src/api/**"
  - "src/stores/**"
---# 接口与状态管理规则(操作 api/store 文件时自动加载)## 接口层
- 基础请求封装:src/api/request.ts
- 接口模块:src/api/modules/{资源名}.ts
- 所有端点:/api/{资源名} 模式
- 错误处理:const [err, data] = await apiCall()
- 响应类型:定义在 src/types/ 或同目录## 请求封装模式
```typescript
export async function request(url: string, options?: RequestInit): Promise<[Error | null, T | null]> {
  // 实现
}

zustand Store 模式

import { create } from 'zustand';
import type { UserState, UserActions } from './types';export const useUserStore = create<UserState & UserActions>((set, get) => ({
  user: null,
  setUser: (user) => set({ user }),
}));

禁止事项

  • 组件中直接 fetch()
  • 用 React state 存储 API 响应(使用 zustand)
  • API 类型定义中使用 any
  • 接口调用缺少错误处理
  • zustand 中存储不可序列化数据(函数、DOM 节点)

#### `rules/style-rules.md````markdown
---
paths:
  - "src/**/*.less"
  - "src/styles/**"
---# 样式开发规则(操作 Less/样式文件时自动加载)## 变量体系
- 所有值来自 variables.less:@spacing-xs/sm/md/lg/xl,@font-size-xs/sm/md/lg/xl
- 颜色:@color-primary、@color-text、@color-bg、@color-border 
- z-index:@z-dropdown、@z-sticky、@z-modal、@z-popover、@z-tooltip
- 断点:@screen-sm/md/lg/xl## 命名规范(BEM)
- .block__element--modifier
- .card__title--large
- .na v__item--active
- 类名禁止 camelCase
- 类名禁止单字母## 禁止事项
- 魔法数字:裸像素值(5px、16px)未使用变量
- 硬编码颜色:#ff0000、rgba(0,0,0,0.1) 未使用变量
- 无注释说明的 !important
- 嵌套选择器超过 3 
- 组件样式中使用 ID 选择器(#id)
rules/typescript-rules.md
---
paths:
  - "src/**/*.ts"
  - "src/**/*.tsx"
---# TypeScript 规则(操作 TypeScript 文件时自动加载)## 严格合规
- 类型导入使用 import type(verbatimModuleSyntax)
- 禁止 TypeScript enum(erasableSyntaxOnly)—— 使用 `as const` 对象
- 禁止 namespace
- 禁止未使用的局部变量或参数(noUnusedLocals、noUnusedParameters)
- 禁止 var 声明(使用 const/let)## 类型模式
```typescript
// 替代 enum:
export const Status = { ACTIVE: 'active', INACTIVE: 'inactive' } as const;
export type Status = (typeof Status)[keyof typeof Status];// 替代 any:
// 使用 unknown + 类型守卫,或具体的联合类型

禁止事项

  • any 类型(确实未知时用 unknown)
  • @ts-ignore / @ts-nocheck(修复类型错误而非忽略)
  • 无守卫的非空断言 !
  • 无理由的类型断言 as X(使用类型守卫)
  • 非测试文件中的 console.log

Claude Code 会**根据当前编辑的文件路径**自动匹配并加载对应的规则注入到对话上下文,提示 Claude 遵守规范。---### 第五步:配置 Subagents(专业分 Agent).claude/agents/ 目录下创建以下 Subagent 定义文件,每个负责一个特定领域:#### `agents/component-analyzer.md````markdown
# React Component Dependency Analysis AgentAnalyzes component imports, detects circular dependencies, validates component structure and naming conventions. Use when creating new components, refactoring imports, or debugging component architecture issues. Runs in isolated context to prevent dependency graph from polluting main conversation.## Tools Read, Grep, Glob
agents/api-checker.md
# API & State Validation AgentChecks API endpoint conventions, error handling patterns, zustand store structure, and type definitions. Use when creating API modules, zustand stores, or auditing the data layer. Runs in isolated context.## Tools: Read, Grep, Glob
agents/code-reviewer.md
# Frontend Code Review AgentValidates React/TypeScript code against project conventions including component naming, Props interface, API patterns, zustand store structure, and style rules.## Tools: Read, Grep, Glob
agents/performance-auditor.md
# React Performance Audit AgentAnalyzes component re-render risks, bundle size concerns, lazy loading opportunities, and rendering optimization. Use when optimizing performance, reviewing component patterns, or before production deployment. Runs in isolated context.## Tools: Read, Grep, Glob, Bash

调用方式也很直观:

/Agent api-checker "Check the new API module I just wrote"


验证安装完成

所有配置完成后,重启 Claude Code,执行 /check 命令。如果看到类似这样的输出:

Frontend validation passed: xxx.tsx

说明配置已经生效了。

顺便测试一下拦截功能——尝试执行 rm -rf src,应该会被稳稳拦住:

BLOCKED: Dangerous rm -rf detected

完整项目规范(内置)

这套方案里内置了一整套项目规范,覆盖了文件结构、TypeScript 规则、组件规范、样式规范和接口规范。直接开箱可用。

文件结构约定

src/
  components/    # 通用复用组件(PascalCase 目录)
  pages/         # 路由级页面组件(PascalCase 目录)
  stores/        # zustand 状态(useXxxStore.ts)
  api/           # 接口层(request.ts + modules/)
  styles/        # 全局 Less(variables.less、global.less)
  hooks/         # 自定义 React hooks(useXxx.ts)
  utils/         # 纯工具函数
  types/         # 共享 TypeScript 类型(*.d.ts)
  assets/        # 静态资源

TypeScript 严格规则

  • verbatimModuleSyntax:类型导入必须用 import type
  • erasableSyntaxOnly:禁止 enum、禁止 namespace → 使用 as const
  • noUnusedLocals / noUnusedParameters:零容忍未使用变量
  • 禁止:varany@ts-ignore

组件规范

  • 每个文件一个组件
  • 文件名:PascalCase.tsx
  • 导出:命名导出 + 默认导出
  • Props:interface XxxProps(必须以 Props 后缀)
  • 样式:同目录同名 .less 文件
  • 禁止:内联样式 style={{}}.map() 中使用 index 作为 key

样式规范(Less)

  • 变量定义在 src/styles/variables.less
  • 禁止魔法数字(使用 @spacing-sm@font-size-md 等)
  • BEM 命名:.block__element--modifier
  • 禁止无注释说明的 !important
  • 颜色值必须通过变量引用

接口规范

  • 所有接口统一 /api 前缀
  • 错误处理:const [err, data] = await apiCall()(Go 风格)
  • 响应类型定义在 types/ 目录
  • 组件中禁止直接 fetch(),必须通过 api/ 层调用

六层防御体系工作流

最后再整体梳理一下这六层防御在真实工作流中是怎么配合的。

1. 操作前 - PreToolUse

执行 Bash 命令前先做危险操作检查,遇到 rm -rf /、强制推 main、删库等操作直接拦截——从源头掐灭事故。

2. 写入后 - PostToolUse:规范校验

每次 Write / Edit 之后自动触发规范校验,不通过就直接中止,要求 Claude 修复才能继续。

3. 写入后 - PostToolUse:技能强制评估

规范校验通过之后还不够,还要强制 Claude 评估当前任务匹配哪个内置 Skill,必须激活对应的 Skill 才能继续。这一步确保规范真的被执行,而不是嘴上说「我遵守了」。

4. 编辑时 - 路径规则注入

Claude 根据编辑的文件路径,自动把对应领域的规则注入到对话上下文,提示 AI 遵守。

5. 回答结束 - Stop Hook

每次回答结束时强制进行五项质量门检查:

  1. 所有请求的功能已实现并测试
  2. 新代码中无 TODO/FIXME 注释遗留
  3. CLAUDE.md 的当前迭代状态已更新
  4. 生产代码中无 console.log 或 any 类型
  5. pnpm run lint 通过

有未完成项必须告知用户,不能悄悄带过。

6. 专业任务 - 专用 Subagent

复杂任务交给专门的 Subagent 在隔离上下文中处理,不会污染主对话。


最佳实践

对 AI 友好的规范

规则要清晰、可检查,大部分能自动化验证。避免那种「代码整洁」「代码优雅」之类的模糊要求——AI 理解不了,人也理解不了。每条规则都应该明确写在 markdown 中,AI 可以直接阅读和遵守。

错误即拦截

PostToolUse 验证不通过就直接进程非零退出,Claude 能看到错误并自我修复。整个过程不需要人工介入,AI 能自己发现问题并修正,这是这套方案最大的价值所在。

渐进式落地

不需要一次性把存量代码全部改造。从新建文件开始遵守规范,存量代码改到哪规范到哪,逐步覆盖就好。


常见问题

Q: Hook 脚本需要执行权限吗?
A: 不需要。因为用 node 执行,只要文件可读即可。

Q: 会减慢 Claude 响应速度吗?
A: 验证脚本是纯 Node.js 运行,一般几十毫秒,对响应速度的影响基本可以忽略。

Q: 如何禁用某个检查?
A: 在 .claude/hooks/validate-frontend.js 中注释掉对应的检查行即可。


效果对比

问题无 Harness有 Harness
违规命名需要人工提醒自动拦截
any/console.log 残留合并代码才发现写入时就发现
内联样式人工 code review 发现自动报错
忘记激活 Skill 规范AI 经常忘记强制评估,必须激活
危险操作手滑就出事直接拦截
忘记更新文档AI 容易忘Stop Hook 强制检查

来源:互联网

免责声明

本网站新闻资讯均来自公开渠道,力求准确但不保证绝对无误,内容观点仅代表作者本人,与本站无关。若涉及侵权,请联系我们处理。本站保留对声明的修改权,最终解释权归本站所有。

同类文章推荐

相关文章推荐

更多