这是「GSD 全景代码解析」专题的第 52 篇。
一、漂移检测概述
在大型 AI Agent 项目中,配置、代码与文档的三角漂移是常见问题:
graph TD
A[配置漂移] --> B[代码与配置不一致]
A --> C[文档与配置不一致]
D[Schema 漂移] --> E[版本不兼容]
D --> F[字段缺失/冗余]
G[文档漂移] --> H[API 文档过时]
G --> I[使用说明失效]GSD 通过 drift-detection 和 schema-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-project 到 plan-phase 的项目初始化与规划全流程。敬请期待。