第5章 机械化架构约束 — 让 AI Agent 在规则的铁轨上奔跑

核心命题:提示词是"建议",约束是"法律"。当 AI Agent 的输出必须穿越一道由机器验证的规则之墙时,架构退化不再是概率事件,而是物理上不可能发生的事。机械化架构约束(Mechanized Architectural Constraints)是 Harness Engineering 五大核心组件中最具确定性的一环——它将人类的架构智慧编码为可执行的规则,让 Agent 在规则的铁轨上全速奔跑,而永不脱轨。

本章公式Constrained Agent = Agent ∩ Rules(TypeSystem ∩ SchemaValidator ∩ LintEngine ∩ PolicyGuard)

控制论视角:机械化约束本质上是一个"前馈控制器"(Feedforward Controller)——它在 Agent 行动之前就预设了可行空间,将偏差消灭在萌芽状态。这与经典控制论中"反馈控制"(事后修正)形成互补,二者共同构成完整的控制回路。


5.1 引言:架构退化的隐形杀手

5.1.1 一个真实的"温水煮青蛙"案例

2025年6月,一家 SaaS 公司的工程团队经历了一场悄无声息的架构灾难。

这家公司开发一个企业级项目管理平台,团队在年初引入了 AI Agent 辅助开发。最初效果令人惊喜——Agent 的代码生成速度快、质量看起来也不错。团队负责人在季度汇报中骄傲地宣布:“AI 让我们的开发效率提升了 40%。”

但三个月后,问题开始浮现。

一个看似简单的功能需求——“在任务详情页添加一个评论功能”——触发了连锁反应:

  1. Agent 为新功能创建了一个 CommentService,但把它放在了 src/services/ 目录下,而非按架构约定应放在 src/modules/comments/ 目录下
  2. CommentService 直接依赖了 TaskRepository,违反了"模块间通过接口通信"的架构规则
  3. 评论的权限校验逻辑与任务的权限校验逻辑重复实现,但行为略有不同
  4. 新增的 API 路由使用了 REST 风格(/api/comments),而系统其他部分使用 GraphQL
  5. 错误处理采用了直接抛出异常的方式,而系统约定使用 Result<T, E> 模式

这些问题中的每一个单独看都不是致命的。 代码能跑,功能能用,测试能过。但它们合在一起,就像在一座精心设计的哥特式大教堂旁边,突然冒出了一间用预制板搭建的临时工棚。

三个月后,当团队试图做一次大规模的依赖升级时,灾难爆发了:

问题类型 数量 根因
模块间循环依赖 23 处 Agent 不了解分层架构约束
不一致的错误处理 47 处 Agent 有时用 Result,有时用异常,有时用 null
数据模型重复定义 12 处 Agent 不知道已有类型定义在哪里
跨层直接调用 31 处 Agent 为"效率"绕过了 Service 层
不一致的 API 风格 18 处 Agent 每次生成时选择不同风格
安全策略违反 9 处 Agent 在 Controller 层直接操作了数据库

总计 140 处架构违规。 团队花了两周时间才全部修复,而这两周本可以用来开发新功能。

更令人沮丧的是,这些问题本不应该发生。公司的架构文档清楚地写着分层规则、命名约定、错误处理策略。但 AI Agent 不会"阅读"架构文档——它只是根据上下文窗口中的信息来生成代码。

5.1.2 为什么 Prompt 中的架构约束不可靠

很多开发者的第一反应是:“那我可以在 Prompt 里告诉 Agent 这些规则啊。”

是的,你可以。但这种方法有三个致命缺陷:

缺陷一:上下文窗口的稀缺性

一个完整的架构约束集合通常有 50-100 条规则,总计 5000-10000 个 Token。这些 Token 会与代码上下文、需求描述、对话历史竞争有限的窗口空间。当窗口接近饱和时,系统会自动压缩上下文——而架构约束往往是最先被压缩掉的部分。

上下文窗口 200K tokens

压缩时首先被丢弃

代码上下文
~80K tokens
优先级:高

对话历史
~50K tokens
优先级:中

需求描述
~30K tokens
优先级:高

架构约束
~8K tokens
优先级:低 ❌

系统 Prompt
~5K tokens
优先级:固定

约束丢失!

缺陷二:自然语言的模糊性

即使架构约束被完整地包含在 Prompt 中,自然语言的描述方式也会导致歧义:

❌ 自然语言约束(模糊):
"Service 层不应该直接依赖其他模块的 Repository"

AI Agent 的理解可能是:
1. Service 层不能 import 其他模块的 Repository 文件 ✓
2. Service 层不能直接调用 Repository 的方法,但可以通过 DI 注入 ✗
3. 只有同模块的 Service 才能访问自己的 Repository ✗
4. 以上都不对,只是说 Repository 的实例化要由上层负责 ✗✗

同一条规则,四种理解,每种都有道理。自然语言的灵活性在这里成了敌人。

缺陷三:缺乏反馈闭环

即使 Agent 理解了约束,Prompt 中也没有机制告诉它"你刚才违反了这条规则"。Agent 生成代码后就"忘记"了约束的存在。没有检测,没有反馈,没有修正。

Prompt 约束 → Agent 生成代码 → 代码违反约束 → 无反馈 → Agent 下次继续违反
                                    ↑
                              这里缺少一个"检测器"

5.1.3 机械化约束的哲学:让错误"不可能"发生

让我们用一个类比来理解机械化约束的力量。

类比:铁路 vs 公路

公路上的汽车可以自由行驶,但也可以闯红灯、逆行、超速。交通安全依赖于驾驶员的"自觉遵守规则"。即使有交通摄像头(反馈控制),违规行为也是先发生、后被发现。

铁路上的列车则完全不同。铁轨本身就是一种机械化约束——列车物理上不可能偏离轨道。道岔(switch)决定了列车的可行路径。信号系统(signal system)在列车进入区间前就锁定了可行路线。

铁路模式:机械化约束

违反规则

通过所有规则

Agent 生成代码

自动规则引擎

立即反馈 + 修复建议

合并代码

架构完整 ✅

公路模式:Prompt 约束

发现问题

通过

可能遗漏问题

Agent 生成代码

人工审查

要求 Agent 修改

合并代码

架构退化 😱

机械化约束的核心原则

原则 说明 示例
不可绕过性 约束必须在代码路径上强制执行,而非建议 TypeScript 编译器不允许类型错误通过
即时反馈 违规必须在生成后立即被检测到 ESLint 在保存时即时报告问题
机器可验证 规则必须形式化,不留主观判断空间 “函数长度 ≤ 50 行"而非"函数要简短”
可组合性 多条规则可以并行执行,互不冲突 TypeCheck + Lint + Policy 独立运行
渐进式严格 新项目可以从宽松规则开始,逐步收紧 先用 warn 模式收集基线,再切换为 error

在接下来的章节中,我们将深入探讨如何构建这样一个机械化约束系统。它不是替代 Prompt 中的架构指导,而是作为 Prompt 的"执法部门"——Prompt 负责"立法"(描述期望),约束引擎负责"执法"(验证执行)。


5.2 约束的理论基础

5.2.1 约束的分类学:四层防线

在 Harness Engineering 的框架中,机械化约束按照执行时机严格程度分为四个层次,形成纵深防御体系:

第四层:运行时约束

第三层:架构约束

第二层:静态分析约束

第一层:编译时约束

类型错误

风格违规

架构违规

运行时违规

类型系统
TypeScript / Mypy

接口契约
Schema 定义

依赖方向
Module Boundaries

代码风格
ESLint / Pylint

复杂度限制
Cyclomatic Complexity

安全规则
Sensitive Data Detection

分层规则
Layer Dependencies

循环依赖检测
Circular Imports

API 契约
OpenAPI Compliance

输入验证
Runtime Schema

权限检查
RBAC Policy

资源限制
Rate Limiting

AI Agent
代码生成

合规代码 ✅

各层约束的特点对比

层次 执行时机 反馈速度 严格程度 绕过难度 典型工具
编译时 代码编写/保存时 < 1秒 极高(编译失败) 极高 TypeScript, Mypy, Rust
静态分析 保存/提交时 1-5秒 高(CI 门禁) 中(可 eslint-disable) ESLint, Pylint, SonarQube
架构规则 提交/合并时 5-30秒 高(PR 阻断) 低(需要架构评审豁免) ArchUnit, DependencyCruiser
运行时 请求处理时 毫秒级 中(请求拒绝) 极高(无法绕过) Zod, Joi, Casbin

5.2.2 约束的形式化表达

要让约束"机器可验证",首先需要将自然语言描述的规则转化为形式化表达。一个完整的约束定义包含五个要素:

Constraint = {
    id:          唯一标识符(用于追踪和报告)
    scope:       作用域(文件/模块/系统级别)
    predicate:   判定条件(布尔函数,输入为代码 AST 或运行时状态)
    severity:    严重级别(error / warning / info)
    remediation: 修复建议(指导 Agent 如何修正)
}

示例:将自然语言约束形式化

自然语言:“Controller 层不能直接调用 Repository 层,必须通过 Service 层”

形式化表达:

const controllerNoDirectRepoAccess: Constraint = {
  id: "ARCH-001",
  scope: "file",
  predicate: (file) => {
    if (!file.path.includes("/controllers/")) return true; // 只检查 Controller 文件
    const imports = file.getImports();
    return !imports.some(imp => imp.source.includes("/repositories/"));
  },
  severity: "error",
  remediation: "Controller 应通过 Service 层间接访问 Repository。请注入对应的 Service 实例。"
};

自然语言:“所有 API 端点必须有输入验证”

形式化表达:

const apiInputValidation: Constraint = {
  id: "SEC-001",
  scope: "file",
  predicate: (file) => {
    const routes = file.getRouteHandlers();
    return routes.every(route => {
      const params = route.getParameters();
      return params.every(param => param.hasSchemaValidation());
    });
  },
  severity: "error",
  remediation: "所有 API 端点的请求参数必须通过 Zod Schema 或 Joi Schema 进行验证。"
};

5.2.3 约束的完备性与一致性

在设计约束系统时,必须关注两个理论属性:

完备性(Completeness):约束集合是否覆盖了所有需要防护的架构决策?

一致性(Consistency):约束集合中是否存在相互矛盾的规则?

不一致 ❌

规则A:函数≤50行

规则B:禁止拆分函数

(A和B矛盾!)

不完备 ❌

规则A:函数≤50行

(缺少错误处理规则)

(缺少JSDoc规则)

完备但不一致 ✓

规则A:函数≤50行

规则B:必须有错误处理

规则C:必须有JSDoc

在实际工程中,追求"绝对完备"是不现实的(哥德尔不完备定理的启示),但应该追求"足够完备"——覆盖团队最关心的架构决策。

实践建议:从团队过去最常犯的架构违规开始,逐步扩展约束集合。每次发现新的违规模式,就将其编码为一条新规则。


5.3 约束引擎核心实现(TypeScript)

5.3.1 整体架构设计

约束引擎是整个机械化约束系统的核心。它接收 AI Agent 生成的代码作为输入,运行一组可配置的规则,输出验证结果和修复建议。

约束引擎架构

输出层

执行层

规则层

解析层

验证报告
ValidationReport

Agent 生成的代码

代码解析器
AST + 元数据提取

约束引擎
ConstraintEngine

规则注册表
RuleRegistry

并行执行器
Promise.allSettled

类型规则

架构规则

安全规则

质量规则

反馈消息
FeedbackMessage

指标统计
MetricsCollector

5.3.2 核心类型定义

首先定义约束系统的基础类型:

// === constraint-types.ts ===

/**
 * 约束严重级别
 * - error: 阻断性问题,必须修复
 * - warning: 建议性问题,应该修复
 * - info: 信息性提示,可以考虑修复
 */
export type Severity = "error" | "warning" | "info";

/**
 * 约束作用域
 * - file: 单文件级别检查
 * - module: 模块级别检查(跨文件)
 * - project: 项目级别检查(全局架构)
 */
export type Scope = "file" | "module" | "project";

/**
 * 违规位置
 */
export interface ViolationLocation {
  filePath: string;
  line: number;
  column: number;
  endLine?: number;
  endColumn?: number;
}

/**
 * 单条违规记录
 */
export interface Violation {
  ruleId: string;
  severity: Severity;
  message: string;
  location: ViolationLocation;
  remediation: string;
  context?: Record<string, unknown>;
}

/**
 * 代码分析上下文 —— 从 AST 和文件元数据中提取
 */
export interface AnalysisContext {
  /** 当前文件路径 */
  filePath: string;
  /** 文件内容 */
  content: string;
  /** 导入列表 */
  imports: ImportInfo[];
  /** 导出列表 */
  exports: ExportInfo[];
  /** 函数/方法定义列表 */
  functions: FunctionInfo[];
  /** 类定义列表 */
  classes: ClassInfo[];
  /** 类型定义列表 */
  types: TypeInfo[];
  /** 整个项目的文件列表(用于 project 级别检查) */
  allFiles?: string[];
  /** 项目的依赖图(用于循环依赖检测) */
  dependencyGraph?: Map<string, string[]>;
}

export interface ImportInfo {
  source: string;          // import 来源
  specifiers: string[];    // 导入的具体名称
  isTypeOnly: boolean;     // 是否仅导入类型
  line: number;
}

export interface ExportInfo {
  name: string;
  kind: "function" | "class" | "interface" | "type" | "const" | "enum";
  line: number;
}

export interface FunctionInfo {
  name: string;
  line: number;
  endLine: number;
  parameterCount: number;
  returnType: string | null;
  hasJSDoc: boolean;
  body: string;
  complexity: number;        // 圈复杂度
  maxNestingDepth: number;   // 最大嵌套深度
}

export interface ClassInfo {
  name: string;
  line: number;
  endLine: number;
  methods: FunctionInfo[];
  properties: { name: string; type: string | null; visibility: "public" | "private" | "protected" }[];
  implements: string[];
  extends: string | null;
}

export interface TypeInfo {
  name: string;
  kind: "interface" | "type" | "enum";
  line: number;
  hasAnyType: boolean;
}

/**
 * 约束规则接口
 */
export interface ConstraintRule {
  /** 规则唯一 ID */
  id: string;
  /** 规则名称 */
  name: string;
  /** 规则描述 */
  description: string;
  /** 严重级别 */
  severity: Severity;
  /** 作用域 */
  scope: Scope;
  /** 检查函数:返回违规列表(空数组表示通过) */
  check: (context: AnalysisContext) => Violation[];
}

/**
 * 验证报告
 */
export interface ValidationReport {
  /** 检查时间戳 */
  timestamp: string;
  /** 总规则数 */
  totalRules: number;
  /** 通过的规则数 */
  passedRules: number;
  /** 违规总数 */
  totalViolations: number;
  /** 按严重级别统计 */
  violationsBySeverity: { error: number; warning: number; info: number };
  /** 所有违规详情 */
  violations: Violation[];
  /** 检查耗时(毫秒) */
  durationMs: number;
  /** 是否全部通过 */
  passed: boolean;
}

5.3.3 约束引擎实现

// === constraint-engine.ts ===

import { ConstraintRule, AnalysisContext, ValidationReport, Violation, Severity } from "./constraint-types";

/**
 * 约束引擎 —— 管理和执行一组约束规则
 * 
 * 设计原则:
 * 1. 规则独立执行,互不干扰
 * 2. 单条规则失败不影响其他规则
 * 3. 支持并行执行以提高性能
 * 4. 提供详细的验证报告
 */
export class ConstraintEngine {
  private rules: Map<string, ConstraintRule> = new Map();
  private enabledRules: Set<string> = new Set();
  private ruleHistory: Map<string, { pass: number; fail: number }> = new Map();

  /**
   * 注册一条约束规则
   */
  registerRule(rule: ConstraintRule): void {
    if (this.rules.has(rule.id)) {
      throw new Error(`Rule "${rule.id}" is already registered. Rule IDs must be unique.`);
    }
    this.rules.set(rule.id, rule);
    this.enabledRules.add(rule.id);
    this.ruleHistory.set(rule.id, { pass: 0, fail: 0 });
  }

  /**
   * 批量注册规则
   */
  registerRules(rules: ConstraintRule[]): void {
    for (const rule of rules) {
      this.registerRule(rule);
    }
  }

  /**
   * 启用/禁用指定规则
   */
  setRuleEnabled(ruleId: string, enabled: boolean): void {
    if (!this.rules.has(ruleId)) {
      throw new Error(`Rule "${ruleId}" not found.`);
    }
    if (enabled) {
      this.enabledRules.add(ruleId);
    } else {
      this.enabledRules.delete(ruleId);
    }
  }

  /**
   * 获取所有已注册规则
   */
  getRules(): ConstraintRule[] {
    return Array.from(this.rules.values());
  }

  /**
   * 获取已启用的规则
   */
  getEnabledRules(scope?: string): ConstraintRule[] {
    return Array.from(this.rules.values()).filter(
      rule => this.enabledRules.has(rule.id) && (!scope || rule.scope === scope)
    );
  }

  /**
   * 执行所有启用的规则,返回验证报告
   */
  async validate(context: AnalysisContext): Promise<ValidationReport> {
    const startTime = Date.now();
    const enabledRules = this.getEnabledRules();
    const allViolations: Violation[] = [];

    // 按作用域分组执行
    const fileRules = enabledRules.filter(r => r.scope === "file");
    const moduleRules = enabledRules.filter(r => r.scope === "module");
    const projectRules = enabledRules.filter(r => r.scope === "project");

    // 文件级规则:并行执行
    const fileResults = await Promise.allSettled(
      fileRules.map(rule => this.executeRule(rule, context))
    );

    // 模块级规则:并行执行
    const moduleResults = await Promise.allSettled(
      moduleRules.map(rule => this.executeRule(rule, context))
    );

    // 项目级规则:并行执行
    const projectResults = await Promise.allSettled(
      projectRules.map(rule => this.executeRule(rule, context))
    );

    // 收集所有违规
    for (const result of [...fileResults, ...moduleResults, ...projectResults]) {
      if (result.status === "fulfilled") {
        allViolations.push(...result.value);
      } else {
        // 规则执行本身出错,记录但不阻断
        console.error(`Rule execution error: ${result.reason}`);
      }
    }

    // 更新规则历史统计
    const failedRuleIds = new Set(allViolations.map(v => v.ruleId));
    for (const rule of enabledRules) {
      const history = this.ruleHistory.get(rule.id)!;
      if (failedRuleIds.has(rule.id)) {
        history.fail++;
      } else {
        history.pass++;
      }
    }

    // 构建报告
    const violationsBySeverity: Record<Severity, number> = { error: 0, warning: 0, info: 0 };
    for (const v of allViolations) {
      violationsBySeverity[v.severity]++;
    }

    const durationMs = Date.now() - startTime;

    return {
      timestamp: new Date().toISOString(),
      totalRules: enabledRules.length,
      passedRules: enabledRules.length - failedRuleIds.size,
      totalViolations: allViolations.length,
      violationsBySeverity,
      violations: allViolations,
      durationMs,
      passed: violationsBySeverity.error === 0,
    };
  }

  /**
   * 执行单条规则(带超时保护)
   */
  private async executeRule(rule: ConstraintRule, context: AnalysisContext): Promise<Violation[]> {
    const TIMEOUT_MS = 5000; // 单条规则 5 秒超时

    return new Promise<Violation[]>((resolve, reject) => {
      const timer = setTimeout(() => {
        reject(new Error(`Rule "${rule.id}" timed out after ${TIMEOUT_MS}ms`));
      }, TIMEOUT_MS);

      try {
        const violations = rule.check(context);
        clearTimeout(timer);
        resolve(violations);
      } catch (error) {
        clearTimeout(timer);
        reject(error);
      }
    });
  }

  /**
   * 生成人类可读的反馈消息
   */
  formatFeedback(report: ValidationReport): string {
    if (report.passed) {
      return `✅ 全部 ${report.totalRules} 条规则通过(耗时 ${report.durationMs}ms)`;
    }

    const lines: string[] = [];
    lines.push(`❌ 验证失败:${report.totalViolations} 条违规(${report.violationsBySeverity.error} error / ${report.violationsBySeverity.warning} warning / ${report.violationsBySeverity.info} info)`);
    lines.push("");

    // 按严重级别排序:error > warning > info
    const sorted = [...report.violations].sort((a, b) => {
      const order: Record<Severity, number> = { error: 0, warning: 1, info: 2 };
      return order[a.severity] - order[b.severity];
    });

    for (const v of sorted) {
      const icon = v.severity === "error" ? "🔴" : v.severity === "warning" ? "🟡" : "🔵";
      lines.push(`${icon} [${v.ruleId}] ${v.message}`);
      lines.push(`   📍 ${v.location.filePath}:${v.location.line}:${v.location.column}`);
      lines.push(`   💡 ${v.remediation}`);
      lines.push("");
    }

    return lines.join("\n");
  }

  /**
   * 获取规则历史统计(用于分析 Agent 的改进趋势)
   */
  getRuleHistory(): Map<string, { pass: number; fail: number }> {
    return new Map(this.ruleHistory);
  }
}

5.3.4 内置规则集:架构约束

// === rules/architecture-rules.ts ===

import { ConstraintRule, AnalysisContext, Violation } from "../constraint-types";

/**
 * ARCH-001: 分层架构约束
 * Controller → Service → Repository,禁止跨层调用
 */
export const layerDependencyRule: ConstraintRule = {
  id: "ARCH-001",
  name: "分层架构依赖",
  description: "Controller 只能依赖 Service,Service 只能依赖 Repository。禁止跨层或反向依赖。",
  severity: "error",
  scope: "file",
  check: (ctx: AnalysisContext): Violation[] => {
    const violations: Violation[] = [];
    const filePath = ctx.filePath;

    // 定义合法的依赖方向
    const layerMap: Record<string, string[]> = {
      "/controllers/": ["/services/"],          // Controller 只能依赖 Service
      "/services/": ["/repositories/", "/utils/"],  // Service 可以依赖 Repository 和 Utils
      "/repositories/": ["/models/", "/utils/"],    // Repository 可以依赖 Model 和 Utils
      "/models/": [],                              // Model 不依赖任何业务层
    };

    // 确定当前文件所在层
    let currentLayer: string | null = null;
    for (const [layer] of Object.entries(layerMap)) {
      if (filePath.includes(layer)) {
        currentLayer = layer;
        break;
      }
    }

    if (!currentLayer) return violations; // 不在任何业务层中(如 /utils/)

    const allowedLayers = layerMap[currentLayer] || [];

    // 检查每个 import 是否违反层级约束
    for (const imp of ctx.imports) {
      const isFromBusinessLayer = Object.keys(layerMap).some(l => imp.source.includes(l));
      if (!isFromBusinessLayer) continue; // 第三方库或工具类不受此规则约束

      const isAllowed = allowedLayers.some(l => imp.source.includes(l));
      if (!isAllowed) {
        violations.push({
          ruleId: "ARCH-001",
          severity: "error",
          message: `分层架构违规:${currentLayer} 层文件不能导入 ${imp.source}`,
          location: { filePath, line: imp.line, column: 1 },
          remediation: `请通过依赖注入或接口抽象来间接访问。${currentLayer} 层允许依赖的层:${allowedLayers.join(", ") || "无"}`,
        });
      }
    }

    return violations;
  },
};

/**
 * ARCH-002: 循环依赖检测
 */
export const circularDependencyRule: ConstraintRule = {
  id: "ARCH-002",
  name: "循环依赖检测",
  description: "模块之间不允许存在循环依赖",
  severity: "error",
  scope: "project",
  check: (ctx: AnalysisContext): Violation[] => {
    const violations: Violation[] = [];
    const graph = ctx.dependencyGraph;
    if (!graph) return violations;

    // 使用 DFS 检测环
    const visited = new Set<string>();
    const inStack = new Set<string>();
    const cycles: string[][] = [];

    function dfs(node: string, path: string[]): void {
      if (inStack.has(node)) {
        // 找到环
        const cycleStart = path.indexOf(node);
        if (cycleStart >= 0) {
          cycles.push([...path.slice(cycleStart), node]);
        }
        return;
      }
      if (visited.has(node)) return;

      visited.add(node);
      inStack.add(node);
      path.push(node);

      const neighbors = graph.get(node) || [];
      for (const neighbor of neighbors) {
        dfs(neighbor, [...path]);
      }

      inStack.delete(node);
    }

    for (const node of graph.keys()) {
      dfs(node, []);
    }

    for (const cycle of cycles) {
      violations.push({
        ruleId: "ARCH-002",
        severity: "error",
        message: `循环依赖:${cycle.join(" → ")}`,
        location: { filePath: cycle[0], line: 1, column: 1 },
        remediation: "请通过提取公共接口、引入中间层或使用依赖注入来打破循环。",
      });
    }

    return violations;
  },
};

/**
 * ARCH-003: 模块边界约束
 * 模块的私有实现不能从模块外部直接导入
 */
export const moduleBoundaryRule: ConstraintRule = {
  id: "ARCH-003",
  name: "模块边界约束",
  description: "模块的 internal/ 或 private/ 目录下的文件不能从模块外部导入",
  severity: "error",
  scope: "file",
  check: (ctx: AnalysisContext): Violation[] => {
    const violations: Violation[] = [];

    for (const imp of ctx.imports) {
      if (imp.source.includes("/internal/") || imp.source.includes("/private/")) {
        // 提取模块根目录
        const importerModule = extractModuleName(ctx.filePath);
        const importedModule = extractModuleName(imp.source);

        if (importerModule !== importedModule) {
          violations.push({
            ruleId: "ARCH-003",
            severity: "error",
            message: `模块边界违规:不能从模块 "${importerModule}" 导入 "${importedModule}" 的内部实现`,
            location: { filePath: ctx.filePath, line: imp.line, column: 1 },
            remediation: `请通过 "${importedModule}" 的公开 API(index.ts 或 public/ 目录)来访问其功能。`,
          });
        }
      }
    }

    return violations;
  },
};

function extractModuleName(filePath: string): string {
  const match = filePath.match(/\/modules\/([^/]+)/);
  return match ? match[1] : "unknown";
}

5.3.5 内置规则集:代码质量约束

// === rules/quality-rules.ts ===

import { ConstraintRule, AnalysisContext, Violation } from "../constraint-types";

/**
 * QUAL-001: 函数复杂度限制
 */
export const functionComplexityRule: ConstraintRule = {
  id: "QUAL-001",
  name: "函数复杂度限制",
  description: "函数的圈复杂度不得超过 10,最大嵌套深度不得超过 4",
  severity: "warning",
  scope: "file",
  check: (ctx: AnalysisContext): Violation[] => {
    const violations: Violation[] = [];
    const MAX_COMPLEXITY = 10;
    const MAX_NESTING = 4;

    for (const fn of ctx.functions) {
      if (fn.complexity > MAX_COMPLEXITY) {
        violations.push({
          ruleId: "QUAL-001",
          severity: "warning",
          message: `函数 "${fn.name}" 的圈复杂度为 ${fn.complexity},超过阈值 ${MAX_COMPLEXITY}`,
          location: { filePath: ctx.filePath, line: fn.line, column: 1 },
          remediation: "建议将复杂函数拆分为多个职责单一的小函数。考虑使用早返回(early return)模式减少分支嵌套。",
          context: { functionName: fn.name, complexity: fn.complexity },
        });
      }

      if (fn.maxNestingDepth > MAX_NESTING) {
        violations.push({
          ruleId: "QUAL-001",
          severity: "warning",
          message: `函数 "${fn.name}" 的最大嵌套深度为 ${fn.maxNestingDepth},超过阈值 ${MAX_NESTING}`,
          location: { filePath: ctx.filePath, line: fn.line, column: 1 },
          remediation: "使用 early return、guard clause 或提取辅助函数来降低嵌套深度。",
        });
      }
    }

    return violations;
  },
};

/**
 * QUAL-002: 函数长度限制
 */
export const functionLengthRule: ConstraintRule = {
  id: "QUAL-002",
  name: "函数长度限制",
  description: "单个函数的代码行数不得超过 50 行(不含空行和注释)",
  severity: "warning",
  scope: "file",
  check: (ctx: AnalysisContext): Violation[] => {
    const violations: Violation[] = [];
    const MAX_LINES = 50;

    for (const fn of ctx.functions) {
      const length = fn.endLine - fn.line + 1;
      if (length > MAX_LINES) {
        violations.push({
          ruleId: "QUAL-002",
          severity: "warning",
          message: `函数 "${fn.name}" 有 ${length} 行,超过阈值 ${MAX_LINES}`,
          location: { filePath: ctx.filePath, line: fn.line, column: 1, endLine: fn.endLine },
          remediation: "将函数拆分为多个小函数。每个函数应该只做一件事,并把它做好。",
        });
      }
    }

    return violations;
  },
};

/**
 * QUAL-003: JSDoc 文档要求
 */
export const jsdocRequiredRule: ConstraintRule = {
  id: "QUAL-003",
  name: "公开 API 必须有文档",
  description: "所有导出的函数和类必须有 JSDoc 文档",
  severity: "warning",
  scope: "file",
  check: (ctx: AnalysisContext): Violation[] => {
    const violations: Violation[] = [];

    const exportedFunctions = new Set(ctx.exports.filter(e => e.kind === "function").map(e => e.name));
    const exportedClasses = new Set(ctx.exports.filter(e => e.kind === "class").map(e => e.name));

    for (const fn of ctx.functions) {
      if (exportedFunctions.has(fn.name) && !fn.hasJSDoc) {
        violations.push({
          ruleId: "QUAL-003",
          severity: "warning",
          message: `导出的函数 "${fn.name}" 缺少 JSDoc 文档`,
          location: { filePath: ctx.filePath, line: fn.line, column: 1 },
          remediation: "请为导出的函数添加 JSDoc 文档,包括功能描述、参数说明和返回值说明。",
        });
      }
    }

    for (const cls of ctx.classes) {
      if (exportedClasses.has(cls.name)) {
        // 检查类的公开方法是否都有文档
        for (const method of cls.methods) {
          if (method.name !== "constructor" && !method.hasJSDoc) {
            violations.push({
              ruleId: "QUAL-003",
              severity: "warning",
              message: `类 "${cls.name}" 的公开方法 "${method.name}" 缺少 JSDoc 文档`,
              location: { filePath: ctx.filePath, line: method.line, column: 1 },
              remediation: "请为公开方法添加 JSDoc 文档。",
            });
          }
        }
      }
    }

    return violations;
  },
};

/**
 * QUAL-004: 禁止使用 any 类型
 */
export const noAnyTypeRule: ConstraintRule = {
  id: "QUAL-004",
  name: "禁止使用 any 类型",
  description: "禁止在类型定义中使用 any,应使用 unknown 或具体类型",
  severity: "error",
  scope: "file",
  check: (ctx: AnalysisContext): Violation[] => {
    const violations: Violation[] = [];

    for (const type of ctx.types) {
      if (type.hasAnyType) {
        violations.push({
          ruleId: "QUAL-004",
          severity: "error",
          message: `类型 "${type.name}" 包含 any 类型`,
          location: { filePath: ctx.filePath, line: type.line, column: 1 },
          remediation: "将 any 替换为 unknown(需要类型收窄)或具体的类型定义。对于第三方库返回值,使用类型断言 + 运行时验证。",
        });
      }
    }

    // 额外检查:函数参数和返回值中的 any
    for (const fn of ctx.functions) {
      if (fn.returnType === "any") {
        violations.push({
          ruleId: "QUAL-004",
          severity: "error",
          message: `函数 "${fn.name}" 的返回值类型为 any`,
          location: { filePath: ctx.filePath, line: fn.line, column: 1 },
          remediation: "为函数指定明确的返回类型。",
        });
      }
    }

    return violations;
  },
};

5.3.6 内置规则集:安全约束

// === rules/security-rules.ts ===

import { ConstraintRule, AnalysisContext, Violation } from "../constraint-types";

/**
 * SEC-001: 硬编码密钥检测
 */
export const noHardcodedSecretsRule: ConstraintRule = {
  id: "SEC-001",
  name: "禁止硬编码密钥",
  description: "代码中不允许硬编码 API 密钥、密码、Token 等敏感信息",
  severity: "error",
  scope: "file",
  check: (ctx: AnalysisContext): Violation[] => {
    const violations: Violation[] = [];

    // 匹配常见的密钥模式
    const secretPatterns = [
      { pattern: /(?:api[_-]?key|apikey)\s*[:=]\s*['"][A-Za-z0-9_\-]{16,}['"]/gi, name: "API Key" },
      { pattern: /(?:password|passwd|pwd)\s*[:=]\s*['"][^'"]{4,}['"]/gi, name: "密码" },
      { pattern: /(?:secret|token|auth[_-]?token)\s*[:=]\s*['"][^'"]{8,}['"]/gi, name: "密钥/令牌" },
      { pattern: /(?:aws[_-]?access[_-]?key[_-]?id)\s*[:=]\s*['"]AKIA[A-Z0-9]{16}['"]/gi, name: "AWS Access Key" },
      { pattern: /(?:private[_-]?key)\s*[:=]\s*['"]-----BEGIN/gi, name: "私钥" },
      { pattern: /sk-[A-Za-z0-9]{20,}/g, name: "OpenAI Secret Key" },
    ];

    const lines = ctx.content.split("\n");
    for (let i = 0; i < lines.length; i++) {
      const line = lines[i];

      // 跳过注释行和测试文件中的 mock 数据
      if (line.trim().startsWith("//") || line.trim().startsWith("*") || line.trim().startsWith("#")) continue;
      if (ctx.filePath.includes("__tests__") || ctx.filePath.includes(".test.") || ctx.filePath.includes(".spec.")) continue;

      for (const { pattern, name } of secretPatterns) {
        if (pattern.test(line)) {
          violations.push({
            ruleId: "SEC-001",
            severity: "error",
            message: `检测到硬编码的${name}`,
            location: { filePath: ctx.filePath, line: i + 1, column: 1 },
            remediation: `${name}移到环境变量中,使用 process.env.SECRET_NAME 或配置管理工具(如 Vault、AWS Secrets Manager)获取。`,
          });
        }
        pattern.lastIndex = 0; // 重置正则状态
      }
    }

    return violations;
  },
};

/**
 * SEC-002: SQL 注入防护
 */
export const sqlInjectionRule: ConstraintRule = {
  id: "SEC-002",
  name: "SQL 注入防护",
  description: "禁止使用字符串拼接构建 SQL 查询,必须使用参数化查询",
  severity: "error",
  scope: "file",
  check: (ctx: AnalysisContext): Violation[] => {
    const violations: Violation[] = [];

    // 匹配常见的 SQL 拼接模式
    const sqlConcatPatterns = [
      /(?:SELECT|INSERT|UPDATE|DELETE|DROP|ALTER)\s+.*\$\{/gi,       // 模板字符串拼接
      /(?:SELECT|INSERT|UPDATE|DELETE|DROP|ALTER)\s+.*['"]\s*\+/gi,   // 字符串加法拼接
      /(?:query|execute|raw)\s*\(\s*['"](?:SELECT|INSERT|UPDATE|DELETE)\s+.*\+/gi, // 函数调用中的拼接
    ];

    const lines = ctx.content.split("\n");
    for (let i = 0; i < lines.length; i++) {
      const line = lines[i];
      if (line.trim().startsWith("//") || line.trim().startsWith("*")) continue;

      for (const pattern of sqlConcatPatterns) {
        if (pattern.test(line)) {
          violations.push({
            ruleId: "SEC-002",
            severity: "error",
            message: "检测到潜在的 SQL 注入风险:字符串拼接构建 SQL 查询",
            location: { filePath: ctx.filePath, line: i + 1, column: 1 },
            remediation: "使用参数化查询(Prepared Statement)或 ORM 的查询构建器。例如:db.query('SELECT * FROM users WHERE id = $1', [userId])",
          });
        }
        pattern.lastIndex = 0;
      }
    }

    return violations;
  },
};

/**
 * SEC-003: 不安全的反序列化检测
 */
export const unsafeDeserializationRule: ConstraintRule = {
  id: "SEC-003",
  name: "不安全反序列化",
  description: "禁止对用户输入使用 eval()、Function() 或不安全的 JSON.parse",
  severity: "error",
  scope: "file",
  check: (ctx: AnalysisContext): Violation[] => {
    const violations: Violation[] = [];

    const unsafePatterns = [
      { pattern: /\beval\s*\(/g, name: "eval()", suggestion: "使用 JSON.parse 或安全的表达式解析器" },
      { pattern: /\bnew\s+Function\s*\(/g, name: "new Function()", suggestion: "使用白名单方式处理动态逻辑" },
      { pattern: /\bvm\.runInNewContext\s*\(/g, name: "vm.runInNewContext()", suggestion: "使用隔离沙箱或 WASM 执行不可信代码" },
    ];

    const lines = ctx.content.split("\n");
    for (let i = 0; i < lines.length; i++) {
      const line = lines[i];
      if (line.trim().startsWith("//") || line.trim().startsWith("*")) continue;

      for (const { pattern, name, suggestion } of unsafePatterns) {
        if (pattern.test(line)) {
          violations.push({
            ruleId: "SEC-003",
            severity: "error",
            message: `检测到不安全的 ${name} 调用`,
            location: { filePath: ctx.filePath, line: i + 1, column: 1 },
            remediation: suggestion,
          });
        }
        pattern.lastIndex = 0;
      }
    }

    return violations;
  },
};

5.4.2 Python 内置规则集

# === python_rules.py ===

import ast
import re
from constraint_engine import (
    AnalysisContext, ConstraintRule, Scope, Severity, Violation
)


class LayerDependencyRule(ConstraintRule):
    """
    ARCH-P001: Python 项目分层架构约束
    views -> services -> repositories -> models
    """

    def __init__(self):
        super().__init__(
            rule_id="ARCH-P001",
            name="分层架构依赖",
            description="views 只能依赖 services,services 只能依赖 repositories。",
            severity=Severity.ERROR,
            scope=Scope.FILE,
        )
        self._layer_map = {
            "views": ["services", "serializers"],
            "services": ["repositories", "utils", "exceptions"],
            "repositories": ["models", "utils", "exceptions"],
            "models": [],
        }

    def check(self, context: AnalysisContext) -> list[Violation]:
        violations: list[Violation] = []
        current_layer = None
        for layer in self._layer_map:
            if f"/{layer}/" in context.file_path:
                current_layer = layer
                break
        if not current_layer:
            return violations
        allowed_layers = self._layer_map.get(current_layer, [])
        for imp in context.imports:
            source = imp.get("module", "")
            for layer in self._layer_map:
                if f".{layer}." in source or source.endswith(f".{layer}"):
                    if layer not in allowed_layers and layer != current_layer:
                        violations.append(Violation(
                            rule_id=self.id,
                            severity=self.severity,
                            message=f"分层架构违规:{current_layer} 层不能导入 {layer} 层的 {source}",
                            file_path=context.file_path,
                            line=imp.get("line", 1),
                            column=1,
                            remediation=f'请通过依赖注入间接访问。允许依赖:{allowed_layers}',
                        ))
        return violations


class FunctionLengthRule(ConstraintRule):
    """
    QUAL-P001: Python 函数长度限制
    """

    def __init__(self, max_lines: int = 50):
        super().__init__(
            rule_id="QUAL-P001",
            name="函数长度限制",
            description=f"单个函数的代码行数不得超过 {max_lines} 行",
            severity=Severity.WARNING,
            scope=Scope.FILE,
        )
        self.max_lines = max_lines

    def check(self, context: AnalysisContext) -> list[Violation]:
        violations: list[Violation] = []
        if context.ast_tree is None:
            return violations
        for node in ast.walk(context.ast_tree):
            if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
                end_line = getattr(node, "end_lineno", node.lineno + 1)
                length = end_line - node.lineno + 1
                if length > self.max_lines:
                    violations.append(Violation(
                        rule_id=self.id,
                        severity=self.severity,
                        message=f'函数 "{node.name}" 有 {length} 行,超过阈值 {self.max_lines}',
                        file_path=context.file_path,
                        line=node.lineno,
                        column=node.col_offset + 1,
                        remediation="将函数拆分为多个小函数。",
                    ))
        return violations


class NoHardcodedSecretsRule(ConstraintRule):
    """
    SEC-P001: 硬编码密钥检测(Python 版)
    """

    def __init__(self):
        super().__init__(
            rule_id="SEC-P001",
            name="禁止硬编码密钥",
            description="代码中不允许硬编码敏感信息",
            severity=Severity.ERROR,
            scope=Scope.FILE,
        )
        self._patterns = [
            (re.compile(r'(?:api[_-]?key|apikey)\s*=\s*["\'][A-Za-z0-9_\-]{16,}["\']', re.I), "API Key"),
            (re.compile(r'(?:password|passwd|pwd)\s*=\s*["\'][^"\']{4,}["\']', re.I), "密码"),
            (re.compile(r'(?:secret|token|auth[_-]?token)\s*=\s*["\'][^"\']{8,}["\']', re.I), "密钥/令牌"),
        )

    def check(self, context: AnalysisContext) -> list[Violation]:
        violations: list[Violation] = []
        lines = context.content.split("\n")
        for i, line in enumerate(lines, 1):
            stripped = line.strip()
            if stripped.startswith("#"):
                continue
            if "test" in context.file_path:
                continue
            for pattern, name in self._patterns:
                if pattern.search(line):
                    violations.append(Violation(
                        rule_id=self.id,
                        severity=self.severity,
                        message=f"检测到硬编码的{name}",
                        file_path=context.file_path,
                        line=i,
                        column=1,
                        remediation=f'将{name}移到环境变量中,使用 os.environ 或 python-decouple。',
                    ))
        return violations

5.5 约束引擎与 Agent 的集成

5.5.1 Agent 工作流中的约束注入点

约束引擎的价值不在于它能检查代码,而在于它能在正确的时间点将检查结果反馈给 Agent。在 Harness Engineering 的框架中,有三个关键的约束注入点:

代码仓库 约束引擎 AI Agent 用户 代码仓库 约束引擎 AI Agent 用户 注入点1:预生成约束 在 Agent 开始生成前 提供架构规则摘要 注入点2:后生成验证 在 Agent 生成后 立即运行规则检查 注入点3:提交门禁 在 git commit 时 再次验证确保合规 需求描述 适用的约束规则列表 生成代码(受约束引导) 生成的代码 执行所有规则 验证报告 + 修复建议 根据反馈修复违规 合规代码 pre-commit hook 通过/阻断

注入点1:预生成约束(Pre-generation Constraints)

在 Agent 开始生成代码之前,约束引擎将当前上下文相关的规则摘要注入到 Agent 的系统提示中。这不是把所有规则都塞进去,而是根据任务类型智能筛选:

// === constraint-injector.ts ===

import { ConstraintEngine, ConstraintRule } from "./constraint-engine";

/**
 * 约束注入器 - 根据任务上下文筛选相关规则并生成提示摘要
 */
export class ConstraintInjector {
  constructor(private engine: ConstraintEngine) {}

  /**
   * 根据任务类型和文件路径,生成约束提示摘要
   */
  generatePromptHints(taskContext: {
    type: "feature" | "bugfix" | "refactor" | "test";
    targetFile?: string;
    targetModule?: string;
  }): string {
    const rules = this.engine.getEnabledRules();
    const relevantRules = this.filterRelevantRules(rules, taskContext);

    const hints: string[] = [
      "## 架构约束(自动验证,请确保代码符合以下规则)",
      "",
    ];

    for (const rule of relevantRules) {
      hints.push(`- **[${rule.id}]** ${rule.name}${rule.description}`);
    }

    hints.push("");
    hints.push("> 以上规则将在代码提交前自动验证。违规将导致构建失败。");

    return hints.join("\n");
  }

  /**
   * 智能筛选与当前任务相关的规则
   */
  private filterRelevantRules(
    rules: ConstraintRule[],
    ctx: { type: string; targetFile?: string; targetModule?: string }
  ): ConstraintRule[] {
    return rules.filter(rule => {
      // 安全规则始终包含
      if (rule.id.startsWith("SEC")) return true;

      // 根据目标文件路径筛选
      if (ctx.targetFile) {
        if (rule.id.startsWith("ARCH") && ctx.targetFile.includes("/controllers/")) {
          return true;
        }
      }

      // 质量规则对新功能和重构任务适用
      if (rule.id.startsWith("QUAL") && ["feature", "refactor"].includes(ctx.type)) {
        return true;
      }

      return false;
    });
  }
}

注入点2:后生成验证(Post-generation Validation)

这是最关键的注入点。Agent 生成代码后,约束引擎立即运行验证,并将结果作为反馈消息返回给 Agent:

// === agent-harness-middleware.ts ===

import { ConstraintEngine } from "./constraint-engine";
import { TypeScriptAnalyzer } from "./code-analyzer";

/**
 * Agent Harness 中间件
 * 在 Agent 生成代码后自动运行约束验证
 */
export class AgentHarnessMiddleware {
  private analyzer = new TypeScriptAnalyzer();

  constructor(private engine: ConstraintEngine) {}

  /**
   * 处理 Agent 生成的代码
   */
  async processGeneratedCode(filePath: string, content: string): Promise<{
    passed: boolean;
    feedback: string;
    violationCount: number;
  }> {
    const context = this.analyzer.analyze(filePath, content);
    const report = await this.engine.validate(context);
    const feedback = this.engine.formatFeedback(report);

    return {
      passed: report.passed,
      feedback,
      violationCount: report.totalViolations,
    };
  }

  /**
   * 自动修复循环:验证 -> 反馈 -> 修复 -> 再验证
   * 最多迭代 3 次
   */
  async validateAndFix(
    filePath: string,
    content: string,
    fixFunction: (feedback: string, currentCode: string) => Promise<string>
  ): Promise<{ finalContent: string; iterations: number; passed: boolean }> {
    let currentContent = content;
    const MAX_ITERATIONS = 3;

    for (let i = 0; i < MAX_ITERATIONS; i++) {
      const result = await this.processGeneratedCode(filePath, currentContent);

      if (result.passed) {
        return { finalContent: currentContent, iterations: i + 1, passed: true };
      }

      // 将验证反馈传递给 Agent,让它修复
      currentContent = await fixFunction(result.feedback, currentContent);
    }

    const finalResult = await this.processGeneratedCode(filePath, currentContent);
    return {
      finalContent: currentContent,
      iterations: MAX_ITERATIONS,
      passed: finalResult.passed,
    };
  }
}

注入点3:提交门禁(Commit Gate)

作为最后一道防线,通过 Git pre-commit hook 确保任何代码(无论来源是人工还是 AI)都不会绕过约束:

#!/bin/bash
# .husky/pre-commit

echo "Running Harness constraint check..."

STAGED_TS_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep '\.ts$')

if [ -z "$STAGED_TS_FILES" ]; then
  echo "No TypeScript files to check"
  exit 0
fi

npx ts-node scripts/run-constraints.ts $STAGED_TS_FILES
EXIT_CODE=$?

if [ $EXIT_CODE -ne 0 ]; then
  echo ""
  echo "Constraint check failed. Please fix the issues above."
  exit 1
fi

echo "All constraint checks passed"

5.5.2 渐进式约束策略

在实际项目中,不建议一开始就启用所有约束。更好的策略是渐进式严格

阶段 时间 策略 规则集 严格程度
观察期 第 1-2 周 只记录,不阻断 全部规则设为 info 仅统计违规数量
警告期 第 3-4 周 警告但不阻断 关键规则设为 warning 建立基线指标
执行期 第 5 周起 严格阻断 关键规则设为 error CI/CD 门禁
优化期 持续 根据数据调整 动态调整阈值 持续改进

阶段1:观察
全部 info
收集基线

阶段2:警告
关键 warning
建立习惯

阶段3:执行
关键 error
CI 门禁

阶段4:优化
数据驱动
持续改进

关键实践:基线冻结

在从观察期过渡到执行期时,一个有效的策略是基线冻结

  1. 记录当前所有违规(作为基线)
  2. 对基线中的历史违规发放豁免令牌(允许在限定时间内修复)
  3. 新增代码必须完全合规
  4. 豁免令牌到期后,历史违规也变为阻断性错误
// === baseline-manager.ts ===

import * as fs from "fs";
import { Violation } from "./constraint-types";

interface BaselineEntry {
  ruleId: string;
  filePath: string;
  line: number;
  message: string;
  exemptUntil: string;  // ISO date
}

export class BaselineManager {
  private baseline: Map<string, BaselineEntry> = new Map();

  /**
   * 从当前违规列表创建基线
   */
  freezeBaseline(violations: Violation[], exemptionDays: number = 30): void {
    const exemptUntil = new Date(Date.now() + exemptionDays * 86400000).toISOString();

    for (const v of violations) {
      const key = `${v.ruleId}:${v.location.filePath}:${v.location.line}`;
      this.baseline.set(key, {
        ruleId: v.ruleId,
        filePath: v.location.filePath,
        line: v.location.line,
        message: v.message,
        exemptUntil,
      });
    }

    fs.writeFileSync(
      ".harness-baseline.json",
      JSON.stringify(Array.from(this.baseline.entries()), null, 2)
    );
  }

  /**
   * 过滤掉基线中的豁免违规,只返回新增违规
   */
  filterNewViolations(violations: Violation[]): Violation[] {
    const now = new Date().toISOString();
    return violations.filter(v => {
      const key = `${v.ruleId}:${v.location.filePath}:${v.location.line}`;
      const entry = this.baseline.get(key);
      if (!entry) return true;
      return entry.exemptUntil < now;
    });
  }

  loadBaseline(): void {
    if (!fs.existsSync(".harness-baseline.json")) return;
    const data = JSON.parse(fs.readFileSync(".harness-baseline.json", "utf-8"));
    this.baseline = new Map(data);
  }
}

5.5.3 约束指标与可观测性

约束引擎不仅产生验证报告,还应该提供可观测性指标,帮助团队了解 Agent 的架构合规趋势:

// === constraint-metrics.ts ===

export interface ConstraintMetrics {
  /** 各规则的通过率 */
  rulePassRates: Map<string, number>;
  /** 违规趋势(按天统计) */
  violationTrend: { date: string; count: number }[];
  /** 最常违反的规则 Top 5 */
  topViolations: { ruleId: string; count: number; description: string }[];
  /** Agent 的修复成功率 */
  autoFixSuccessRate: number;
  /** 平均违规数/提交 */
  avgViolationsPerCommit: number;
}

export class MetricsCollector {
  private dailyViolations: Map<string, number> = new Map();
  private ruleViolationCounts: Map<string, number> = new Map();
  private totalCommits = 0;
  private totalViolations = 0;
  private fixAttempts = 0;
  private fixSuccesses = 0;

  recordValidation(violations: Violation[]): void {
    const today = new Date().toISOString().slice(0, 10);
    this.dailyViolations.set(today, (this.dailyViolations.get(today) || 0) + violations.length);
    this.totalCommits++;
    this.totalViolations += violations.length;

    for (const v of violations) {
      this.ruleViolationCounts.set(v.ruleId, (this.ruleViolationCounts.get(v.ruleId) || 0) + 1);
    }
  }

  recordFixAttempt(success: boolean): void {
    this.fixAttempts++;
    if (success) this.fixSuccesses++;
  }

  getMetrics(): ConstraintMetrics {
    const trend = Array.from(this.dailyViolations.entries())
      .map(([date, count]) => ({ date, count }))
      .sort((a, b) => a.date.localeCompare(b.date));

    const topViolations = Array.from(this.ruleViolationCounts.entries())
      .sort((a, b) => b[1] - a[1])
      .slice(0, 5)
      .map(([ruleId, count]) => ({ ruleId, count, description: "" }));

    return {
      rulePassRates: new Map(),
      violationTrend: trend,
      topViolations,
      autoFixSuccessRate: this.fixAttempts > 0 ? this.fixSuccesses / this.fixAttempts : 0,
      avgViolationsPerCommit: this.totalCommits > 0 ? this.totalViolations / this.totalCommits : 0,
    };
  }
}

约束指标仪表板

规则通过率
92.3% ↑

本周违规趋势
↓ 15% vs 上周

Top 违规规则
SEC-001 (23次)

自动修复成功率
78.5%

平均违规/提交
1.2 → 0.4

约束引擎


5.6 企业级案例:某金融科技公司的约束驱动开发

5.6.1 背景

2025年下半年,一家金融科技公司(我们称其为 FinSecure)面临一个典型困境。公司在年初全面引入了 AI Agent 辅助开发,团队规模从 20 人扩展到 35 人(其中 15 人是新入职的初级工程师),AI Agent 的代码生成占比达到了 60%。

问题浮现

在引入 AI Agent 的前三个月,一切看起来很美好:

指标 引入前 引入后(3个月) 变化
功能交付速度 8 个/月 14 个/月 +75%
Bug 数量 12 个/月 11 个/月 -8%
代码审查通过率 85% 87% +2%

但六个月后,情况急转直下:

指标 引入后(3个月) 引入后(6个月) 变化
功能交付速度 14 个/月 9 个/月 -36%
Bug 数量 11 个/月 34 个/月 +209%
代码审查通过率 87% 52% -40%
安全漏洞 0 个 7 个 新增
架构违规 ~5 处/月 ~45 处/月 +800%

根本原因:AI Agent 生成的代码在表面上看起来正确(编译通过、测试通过),但大量违反了公司的架构约定和安全规范。 这些"隐性违规"在代码审查阶段难以发现,在运行时才暴露出来。

5.6.2 约束系统的设计与实施

FinSecure 的工程 VP 决定引入机械化约束系统。整个实施分为四个阶段:

第一阶段:规则梳理(1周)

团队花了整整一周时间,从架构文档、代码审查记录、Bug 报告和 Post-Mortem 分析中,梳理出了公司最关心的架构规则:

FinSecure 核心约束规则集

质量约束(6条)

QUAL-001: 复杂度上限

QUAL-002: 函数长度上限

QUAL-003: 文档覆盖率

QUAL-004: any 类型禁止

QUAL-005: 测试覆盖率

QUAL-006: 错误处理完整性

安全约束(9条)

SEC-001: 密钥零硬编码

SEC-002: SQL 参数化

SEC-003: 输入验证

SEC-004: 输出脱敏

SEC-005: 审计日志

SEC-006: 权限检查

SEC-007: 加密存储

SEC-008: HTTPS Only

SEC-009: 依赖漏洞扫描

架构约束(7条)

ARCH-001: 分层依赖方向

ARCH-002: 循环依赖禁止

ARCH-003: 领域边界隔离

ARCH-004: API 版本控制

ARCH-005: 事件驱动通信

ARCH-006: 配置外部化

ARCH-007: 日志规范化

第二阶段:引擎开发与集成(2周)

团队基于本章介绍的约束引擎架构,开发了一个定制化的约束系统。关键定制点:

  1. 金融领域特定规则
// 金融领域特有的约束:所有金额计算必须使用 Decimal
const decimalArithmeticRule: ConstraintRule = {
  id: "FIN-001",
  name: "金额计算必须使用 Decimal",
  description: "所有涉及金额的运算必须使用 decimal.js 或 big.js,禁止使用 JavaScript 原生 number",
  severity: "error",
  scope: "file",
  check: (ctx: AnalysisContext): Violation[] => {
    const violations: Violation[] = [];
    const lines = ctx.content.split("\n");

    for (let i = 0; i < lines.length; i++) {
      const line = lines[i];
      // 检测可能的金额浮点运算
      if (/(?:price|amount|total|balance|fee|cost|revenue)\s*[\+\-\*\/]=?\s*\d/.test(line)) {
        if (!line.includes("Decimal") && !line.includes("Big") && !line.includes("decimal")) {
          violations.push({
            ruleId: "FIN-001",
            severity: "error",
            message: "金额计算疑似使用了 JavaScript 原生 number 类型",
            location: { filePath: ctx.filePath, line: i + 1, column: 1 },
            remediation: "使用 new Decimal(amount).plus(other) 代替 amount + other。浮点精度问题在金融场景中可能导致资金错误。",
          });
        }
      }
    }

    return violations;
  },
};

// 金融领域:所有数据库操作必须有事务
const transactionRequiredRule: ConstraintRule = {
  id: "FIN-002",
  name: "写操作必须在事务中执行",
  description: "所有涉及数据写入的 Service 方法必须在事务上下文中执行",
  severity: "error",
  scope: "file",
  check: (ctx: AnalysisContext): Violation[] => {
    const violations: Violation[] = [];

    for (const cls of ctx.classes) {
      if (!ctx.filePath.includes("/services/")) continue;

      for (const method of cls.methods) {
        // 检查方法是否包含写操作
        const hasWriteOp = /(?:create|update|delete|insert|save|remove)\w*\(/.test(method.body);
        const hasTransaction = /(?:transaction|trx|withTransaction|@Transactional)/.test(method.body);

        if (hasWriteOp && !hasTransaction) {
          violations.push({
            ruleId: "FIN-002",
            severity: "error",
            message: `Service 方法 "${cls.name}.${method.name}" 包含写操作但缺少事务包裹`,
            location: { filePath: ctx.filePath, line: method.line, column: 1 },
            remediation: "将写操作包裹在事务中:await this.db.transaction(async (trx) => { ... })",
          });
        }
      }
    }

    return violations;
  },
};
  1. 合规审计日志规则
// 金融合规:敏感操作必须记录审计日志
const auditLogRule: ConstraintRule = {
  id: "COMP-001",
  name: "敏感操作审计日志",
  description: "涉及用户资金、权限变更、数据导出等操作必须记录审计日志",
  severity: "error",
  scope: "file",
  check: (ctx: AnalysisContext): Violation[] => {
    const violations: Violation[] = [];
    const sensitivePatterns = [
      /(?:transfer|withdraw|deposit|refund|payment)/i,
      /(?:grantRole|revokeRole|changePermission|updateRole)/i,
      /(?:exportData|bulkDownload|dataDump)/i,
    ];

    for (const fn of ctx.functions) {
      const isSensitiveOp = sensitivePatterns.some(p => p.test(fn.name));
      const hasAuditLog = /auditLog|audit\.log|logAudit|recordAudit/.test(fn.body);

      if (isSensitiveOp && !hasAuditLog) {
        violations.push({
          ruleId: "COMP-001",
          severity: "error",
          message: `函数 "${fn.name}" 疑似敏感操作但缺少审计日志记录`,
          location: { filePath: ctx.filePath, line: fn.line, column: 1 },
          remediation: "在操作完成后调用 auditLog.record({ action, userId, resource, details }) 记录审计日志。",
        });
      }
    }

    return violations;
  },
};

第三阶段:渐进式推广(4周)

按照渐进式策略执行:

  • 第 1-2 周(观察期):全部规则设为 info 级别,仅收集数据。结果触目惊心——在 AI 生成的代码中,平均每次提交有 8.3 处违规。
  • 第 3-4 周(警告期):安全规则和架构规则设为 warning。团队开始关注约束反馈。
  • 第 5 周起(执行期):安全规则设为 error,CI 门禁启用。

第四阶段:效果评估(持续)

实施三个月后的数据对比:

指标 实施前 实施后(1个月) 实施后(3个月) 变化
每次提交平均违规数 8.3 4.1 0.8 -90%
安全漏洞 7 个/季 2 个/季 0 个/季 -100%
架构违规 ~45 处/月 ~18 处/月 ~3 处/月 -93%
Agent 代码首次通过率 52% 71% 91% +75%
代码审查耗时 3.2 小时/PR 2.1 小时/PR 1.4 小时/PR -56%
生产 Bug 34 个/月 18 个/月 9 个/月 -74%

实施效果趋势

违规趋势

第0月: 8.3/提交

第1月: 4.1/提交

第2月: 2.0/提交

第3月: 0.8/提交

5.6.3 关键经验教训

经验一:约束不是越多越好

FinSecure 最初设计了 45 条规则,但发现其中 12 条产生了大量误报(false positive)。过多的误报会导致"告警疲劳"——开发者开始忽略所有告警,包括真正重要的。

最佳实践:保持规则数量在 15-25 条之间,确保每条规则的误报率低于 5%。

经验二:修复建议比错误检测更重要

仅仅告诉 Agent “你违反了规则 X” 是不够的。Agent 需要知道如何修复。FinSecure 发现,包含详细修复建议的规则,Agent 的首次修复成功率从 45% 提升到 82%。

经验三:约束规则需要版本化

约束规则本身也需要版本控制。当团队决定修改某条规则的阈值时(比如将函数长度上限从 50 行改为 60 行),应该像代码一样提交 PR、经过审查、记录变更原因。

// 约束规则版本化配置
interface RuleVersion {
  version: string;        // 语义版本号
  effectiveDate: string;  // 生效日期
  changelog: string;      // 变更说明
  author: string;         // 修改人
  approvedBy: string;     // 审批人
}

5.7 最佳实践与反模式

5.7.1 最佳实践

实践一:约束即文档

每条约束规则都应该是架构决策的活文档。当新成员加入团队时,阅读约束规则集就能快速理解项目的架构约定。

约束即文档

约束规则集

新成员阅读
理解架构

AI Agent 参考
生成合规代码

代码审查依据
客观标准

架构演进记录
变更历史

实践二:约束规则的分类管理

rules/
  architecture/        # 架构约束
    layer-dependency.ts
    circular-deps.ts
    module-boundary.ts
  security/            # 安全约束
    no-hardcoded-secrets.ts
    sql-injection.ts
    input-validation.ts
  quality/             # 质量约束
    complexity.ts
    function-length.ts
    documentation.ts
  domain/              # 领域特定约束
    financial-calc.ts
    audit-log.ts
    data-classification.ts

实践三:约束规则测试

约束规则本身也需要测试。每条规则应该有:

// === rules/__tests__/layer-dependency.test.ts ===

import { describe, it, expect } from "vitest";
import { layerDependencyRule } from "../architecture/layer-dependency";

describe("ARCH-001: 分层架构依赖", () => {
  it("应该允许 Controller 导入 Service", () => {
    const ctx = createMockContext({
      filePath: "src/controllers/orderController.ts",
      imports: [{ source: "../services/orderService", specifiers: ["OrderService"], isTypeOnly: false, line: 1 }],
    });
    const violations = layerDependencyRule.check(ctx);
    expect(violations).toHaveLength(0);
  });

  it("应该禁止 Controller 直接导入 Repository", () => {
    const ctx = createMockContext({
      filePath: "src/controllers/orderController.ts",
      imports: [{ source: "../repositories/orderRepository", specifiers: ["OrderRepository"], isTypeOnly: false, line: 1 }],
    });
    const violations = layerDependencyRule.check(ctx);
    expect(violations).toHaveLength(1);
    expect(violations[0].ruleId).toBe("ARCH-001");
  });

  it("应该忽略非业务层的导入(如第三方库)", () => {
    const ctx = createMockContext({
      filePath: "src/controllers/orderController.ts",
      imports: [{ source: "express", specifiers: ["Router"], isTypeOnly: false, line: 1 }],
    });
    const violations = layerDependencyRule.check(ctx);
    expect(violations).toHaveLength(0);
  });

  it("应该允许 Service 导入 Repository", () => {
    const ctx = createMockContext({
      filePath: "src/services/orderService.ts",
      imports: [{ source: "../repositories/orderRepository", specifiers: ["OrderRepository"], isTypeOnly: false, line: 1 }],
    });
    const violations = layerDependencyRule.check(ctx);
    expect(violations).toHaveLength(0);
  });
});

实践四:与 Agent 的反馈闭环

约束引擎的反馈应该被设计成 Agent 能直接理解和行动的格式:

## 约束验证报告

### 结果:❌ 未通过(2 error / 1 warning)

### 必须修复(error)

1. **[SEC-001]** 第 15 行:检测到硬编码的 API Key
   修复方式:将 `const apiKey = "sk-abc..."` 替换为 `const apiKey = process.env.API_KEY`

2. **[ARCH-001]** 第 3 行:Controller 不能直接导入 Repository
   修复方式:删除 `import { OrderRepository }` ,改为注入 `OrderService`

### 建议修复(warning)

3. **[QUAL-003]** 第 22 行:导出的函数缺少 JSDoc 文档
   修复方式:在函数声明前添加 /** @description ... @param ... @returns ... */

5.7.2 常见反模式

反模式一:约束过细

// ❌ 反模式:过度约束
const variableNamingRule: ConstraintRule = {
  id: "STYLE-001",
  name: "变量命名规则",
  description: "所有循环变量必须命名为 i, j, k",  // 太死板了!
  // ...
};

好的约束划定边界,但不扼杀创造力。变量命名应该由 ESLint 的 naming-convention 规则处理(足够灵活),而非硬编码在约束引擎中。

反模式二:约束不一致

规则 A:所有 Service 方法必须有错误处理
规则 B:工具函数不需要错误处理(让调用方处理)
规则 C:Service 中调用的工具函数...(没说清楚)

当规则之间存在灰色地带时,Agent 会在不同规则之间"反复横跳",每次生成可能遵循不同的规则。

反模式三:约束无反馈

// ❌ 反模式:只检查,不告诉怎么修
check: (ctx) => {
  if (/* 违规 */) {
    return [{ message: "不符合架构规范" }];  // 然后呢??
  }
}

反模式四:忽视误报

当一条规则的误报率超过 10% 时,它会迅速失去团队的信任。正确做法是:

  1. 立即将规则降级为 warning 或 info
  2. 分析误报模式
  3. 改进规则的检测逻辑
  4. 在测试中覆盖误报场景
  5. 重新升级为 error

如果误报再现

高误报规则

降级为 warning

分析误报模式

改进检测逻辑

添加测试用例

重新升级为 error

反模式五:约束绕过机制缺失

有些约束在特定场景下确实需要被绕过(比如紧急修复、原型验证)。如果没有正式的绕过机制,开发者会采取更隐蔽的方式(如 // eslint-disable)。

最佳实践:提供正式的绕过流程:

// @harness-exempt ARCH-001 紧急修复 #12345,有效期至 2025-12-31
import { OrderRepository } from "../repositories/orderRepository";

绕过记录会被自动追踪,超过有效期后自动恢复为阻断性错误。


5.8 本章小结

5.8.1 核心要点回顾

本章探讨了机械化架构约束的完整理论与实践。让我们回顾核心要点:

要点 说明
约束优于指令 告诉 Agent "什么不能做"比"怎么做"更有效,因为约束是可验证的、确定的、不可绕过的
四层防线 编译时约束 → 静态分析约束 → 架构约束 → 运行时约束,形成纵深防御
形式化表达 每条约束必须包含 ID、作用域、判定条件、严重级别和修复建议五个要素
三个注入点 预生成约束引导 → 后生成验证反馈 → 提交门禁阻断
渐进式严格 观察期 → 警告期 → 执行期 → 优化期,避免一步到位引起抵触
反馈闭环 约束引擎的检测结果必须以 Agent 可理解的格式反馈,形成自动修复循环
可观测性 约束指标(通过率、违规趋势、修复成功率)是持续改进的基础

5.8.2 约束系统与其他 Harness 组件的关系

Harness 五大组件协作

提供架构知识

验证结果

异常指标

修复请求

验证报告

审查意见

结构化知识系统
(第4章)

机械化架构约束
(本章)

可观测性注入
(第6章)

自修复闭环
(第7章)

Agent 互审机制
(第8章)

协作流程

  1. 知识系统 → 约束引擎:知识系统提供项目的架构文档、编码规范和历史决策记录,约束引擎将这些知识编码为可执行规则
  2. 约束引擎 → 可观测性:约束引擎的验证结果(违规数量、类型、趋势)被发送到可观测性系统,形成架构健康度指标
  3. 可观测性 → 自修复闭环:当可观测性系统检测到架构合规度下降时,触发自修复闭环,自动修复最常见的违规模式
  4. 约束引擎 → Agent 互审:约束引擎的验证报告作为 Agent 互审的客观依据,减少审查中的主观争议

5.8.3 思考题

基础题

  1. 列举你所在项目中 AI Agent 最常犯的 5 个架构违规,并为每个违规设计一条形式化的约束规则。
  2. 解释"渐进式严格"策略的四个阶段,并说明为什么不建议从一开始就启用所有约束。
  3. 比较"预生成约束"和"后生成验证"两种注入方式的优缺点。

进阶题

  1. 设计一套约束规则的测试框架,要求能覆盖正常通过、违规检测、误报排除和边界条件四种场景。
  2. 在你的项目中实现一个"基线冻结"系统,要求支持:创建基线、过期管理、增量违规检测。
  3. 分析约束规则的"完备性"和"一致性"之间的矛盾,给出你在实践中如何平衡这两者的策略。

开放题

  1. 如果约束规则本身也由 AI Agent 来生成和维护,会带来哪些新的挑战和机遇?设计一个"自适应约束系统"的架构草案。
  2. 讨论机械化约束的局限性——有哪些重要的架构决策是难以用机器验证的?对于这些决策,你有什么替代方案?

下一章预告:在第 6 章中,我们将探讨可观测性注入——如何让你的 AI Agent 系统从"黑箱"变为"白箱",通过分布式追踪、实时指标和智能告警,实现对 Agent 行为的全方位洞察。

Logo

中国智能体开发者社区,聚焦智能体与大模型开发,提供前沿资讯、实用工具链、开源项目及行业案例。通过技术沙龙、开发者大赛等活动,促进经验交流与协作,助力开发者快速构建创新智能应用。

更多推荐