漂移检测与 Schema 验证:配置一致性、文档同步与版本兼容性

📑 目录

这是「GSD 全景代码解析」专题的第 52 篇。

一、漂移检测概述

在大型 AI Agent 项目中,配置、代码与文档的三角漂移是常见问题:

graph TD
    A[配置漂移] --> B[代码与配置不一致]
    A --> C[文档与配置不一致]
    D[Schema 漂移] --> E[版本不兼容]
    D --> F[字段缺失/冗余]
    G[文档漂移] --> H[API 文档过时]
    G --> I[使用说明失效]

GSD 通过 drift-detectionschema-check 两套机制系统性解决这些问题。

二、配置一致性检测

2.1 检测目标

interface DriftCheck {
  name: string;
  source: string;        // 参考源
  target: string;        // 检测目标
  transform?: (data: any) => any; // 数据转换
}

const DRIFT_CHECKS: DriftCheck[] = [
  {
    name: 'config-vs-code',
    source: 'config/project.yml',
    target: 'src/config/runtime.ts',
    transform: (yaml) => extractKeys(yaml)
  },
  {
    name: 'docs-vs-api',
    source: 'src/api/routes.ts',
    target: 'docs/api-reference.md',
    transform: (code) => extractEndpoints(code)
  },
  {
    name: 'env-vs-readme',
    source: '.env.example',
    target: 'README.md',
    transform: (env) => extractEnvVars(env)
  }
];

2.2 漂移检测引擎

class DriftDetector {
  async detect(checks: DriftCheck[]): Promise<DriftReport> {
    const findings = [];
    
    for (const check of checks) {
      const sourceData = await this.load(check.source);
      const targetData = await this.load(check.target);
      
      const normalizedSource = check.transform 
        ? check.transform(sourceData) 
        : sourceData;
      const normalizedTarget = check.transform 
        ? check.transform(targetData) 
        : targetData;
      
      const diff = this.computeDiff(normalizedSource, normalizedTarget);
      
      if (diff.length > 0) {
        findings.push({
          check: check.name,
          source: check.source,
          target: check.target,
          differences: diff,
          severity: this.assessSeverity(diff)
        });
      }
    }
    
    return { findings, hasDrift: findings.length > 0 };
  }
  
  private computeDiff(source: any, target: any): DiffItem[] {
    const diffs = [];
    const allKeys = new Set([...Object.keys(source), ...Object.keys(target)]);
    
    for (const key of allKeys) {
      if (!(key in source)) {
        diffs.push({ type: 'added', key, value: target[key] });
      } else if (!(key in target)) {
        diffs.push({ type: 'removed', key, value: source[key] });
      } else if (JSON.stringify(source[key]) !== JSON.stringify(target[key])) {
        diffs.push({ 
          type: 'modified', 
          key, 
          expected: source[key], 
          actual: target[key] 
        });
      }
    }
    
    return diffs;
  }
}

2.3 自动修复建议

class DriftFixer {
  generateFixes(report: DriftReport): FixSuggestion[] {
    return report.findings.flatMap(finding => 
      finding.differences.map(diff => {
        switch (diff.type) {
          case 'removed':
            return {
              action: 'add',
              target: finding.target,
              key: diff.key,
              value: diff.value,
              reason: `Missing in ${finding.target}, exists in ${finding.source}`
            };
          case 'added':
            return {
              action: 'review',
              target: finding.target,
              key: diff.key,
              reason: `New in ${finding.target}, not in ${finding.source}`
            };
          case 'modified':
            return {
              action: 'sync',
              target: finding.target,
              key: diff.key,
              expected: diff.expected,
              actual: diff.actual
            };
        }
      })
    );
  }
}

三、Schema 验证

3.1 Schema 定义

// schemas/plan.json
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "required": ["title", "version", "tasks"],
  "properties": {
    "title": { "type": "string", "minLength": 1 },
    "version": { 
      "type": "string", 
      "pattern": "^\\d+\\.\\d+\\.\\d+$" 
    },
    "tasks": {
      "type": "array",
      "items": { "$ref": "#/definitions/task" }
    }
  },
  "definitions": {
    "task": {
      "type": "object",
      "required": ["id", "name", "agent"],
      "properties": {
        "id": { "type": "string", "pattern": "^[a-z0-9-]+$" },
        "name": { "type": "string" },
        "agent": { "type": "string" },
        "dependencies": { 
          "type": "array", 
          "items": { "type": "string" } 
        },
        "prompt": { "type": "string" },
        "outputs": { 
          "type": "array", 
          "items": { "type": "string" } 
        }
      }
    }
  }
}

3.2 Schema 验证器

class SchemaValidator {
  constructor() {
    this.ajv = new Ajv({ allErrors: true, strict: true });
    this.loadSchemas();
  }
  
  validate(data: any, schemaName: string): ValidationResult {
    const validate = this.ajv.getSchema(schemaName);
    if (!validate) {
      throw new Error(`Schema not found: ${schemaName}`);
    }
    
    const valid = validate(data);
    
    return {
      valid,
      errors: valid ? [] : this.formatErrors(validate.errors)
    };
  }
  
  private formatErrors(errors: any[]): ValidationError[] {
    return errors.map(err => ({
      path: err.instancePath,
      message: err.message,
      schemaPath: err.schemaPath,
      params: err.params
    }));
  }
}

3.3 版本兼容性检查

class VersionCompatibility {
  async checkCompatibility(
    currentVersion: string, 
    targetVersion: string
  ): Promise<CompatibilityReport> {
    const currentSchema = await this.loadSchema(currentVersion);
    const targetSchema = await this.loadSchema(targetVersion);
    
    const breaking = [];
    const deprecated = [];
    const added = [];
    
    // 检查移除的字段
    for (const field of currentSchema.required) {
      if (!targetSchema.required.includes(field)) {
        breaking.push({ type: 'removed', field });
      }
    }
    
    // 检查新增的必填字段
    for (const field of targetSchema.required) {
      if (!currentSchema.required.includes(field)) {
        breaking.push({ type: 'added_required', field });
      }
    }
    
    // 检查字段类型变更
    for (const [field, def] of Object.entries(currentSchema.properties)) {
      const targetDef = targetSchema.properties[field];
      if (targetDef && def.type !== targetDef.type) {
        breaking.push({ type: 'type_changed', field, from: def.type, to: targetDef.type });
      }
    }
    
    return {
      compatible: breaking.length === 0,
      breaking,
      deprecated,
      added
    };
  }
}

四、文档同步检查

class DocSyncChecker {
  async checkSync(projectPath: string): Promise<DocSyncReport> {
    const checks = [
      this.checkReadmeTOC(projectPath),
      this.checkApiDocs(projectPath),
      this.checkChangelog(projectPath),
      this.checkGettingStarted(projectPath)
    ];
    
    return { checks: await Promise.all(checks) };
  }
  
  private async checkApiDocs(projectPath: string): Promise<SyncCheck> {
    const routes = await this.extractRoutes(`${projectPath}/src/api`);
    const docs = await this.parseApiDocs(`${projectPath}/docs/api.md`);
    
    const missing = routes.filter(r => !docs.includes(r));
    const outdated = docs.filter(d => !routes.includes(d));
    
    return {
      name: 'api-docs-sync',
      passed: missing.length === 0 && outdated.length === 0,
      missing,
      outdated
    };
  }
}

五、CI 集成

# .github/workflows/drift-check.yml
name: Drift Detection
on:
  push:
    paths:
      - 'config/**'
      - 'src/**'
      - 'docs/**'
  schedule:
    - cron: '0 0 * * *'  # 每日检查

jobs:
  drift:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - run: npm install
      - run: npx gsd-drift-detection
      - run: npx gsd-schema-check
      - run: npx gsd-doc-sync-check
      - if: failure()
        uses: actions/github-script@v6
        with:
          script: |
            github.rest.issues.create({
              owner: context.repo.owner,
              repo: context.repo.repo,
              title: 'Drift detected in latest changes',
              body: 'Please run `npm run drift:fix` locally'
            });

六、一致性检查矩阵

检查项工具频率自动修复
配置-代码一致性drift-detection每次构建部分支持
Schema 有效性schema-check每次提交不支持
版本兼容性version-check版本升级不支持
文档同步doc-sync每日部分支持
环境变量env-check每次提交不支持

下一篇预告: 第 53 篇《实战:从零开始用 GSD 开发项目(上)》

我们将通过一个完整的实战案例,展示从 new-projectplan-phase 的项目初始化与规划全流程。敬请期待。