主题
实战:构建变更影响雷达
团队很少因为“没有看见变更通知”而出问题,更多时候是看见了,却没把变化连接到代码、配置、文档、客户和负责人。本项目构建一个变更影响雷达:输入旧版与新版材料,输出原子变化、受影响资产、证据、回归检查和待确认问题。
它不是“对两个文档做摘要”。摘要读完仍要人自己找影响;雷达的结果直接进入评审和派单流程。
1. 选择一个窄场景
第一版只做一种变更源和一种资产目录。例如:
text
变更源:产品退款政策 Markdown
资产目录:后端服务、配置项、帮助中心文章、回归测试
使用者:产品运营 + 退款服务负责人
交付:一份待确认影响报告先不要同时支持网页、PDF、代码、合同和邮件。判断 MVP 成功的标准是:过去需要 60 分钟完成的影响梳理,能否在 15 分钟内完成,且没有增加严重漏项。
2. 定义输出协议
ts
type Evidence = {
sourceId: string;
section: string;
quote: string;
};
type AtomicChange = {
id: string;
kind: "added" | "removed" | "modified";
subject: string;
before: string | null;
after: string | null;
condition: string | null;
effectiveAt: string | null;
evidenceBefore: Evidence | null;
evidenceAfter: Evidence | null;
};
type Impact = {
changeId: string;
assetId: string;
impactType: "behavior" | "configuration" | "content" | "test" | "operations";
reason: string;
evidence: string[];
confidence: number;
proposedChecks: string[];
owner: string | null;
};
type ImpactReport = {
reportId: string;
sourceVersions: { before: string; after: string };
changes: AtomicChange[];
impacts: Impact[];
openQuestions: string[];
status: "draft" | "reviewed" | "published";
};reason 不是证据。证据必须指向输入文档或资产目录中的实际字段;confidence 也不能替代人工确认。报告发布前,关键变更至少要有一个明确负责人。
3. 建立资产目录
影响分析的上限取决于你告诉系统“组织里有什么”。第一版可以把目录放在仓库中:
yaml
# .ai/assets.yaml
assets:
- id: service-refund-api
type: service
name: 退款 API
owner: team-billing
paths:
- services/refund/**
keywords:
- 退款窗口
- refund_window_days
- refund eligibility
invariants:
- 退款资格必须由服务端计算
- 历史订单规则必须可追溯
- id: config-refund-window
type: configuration
name: 退款窗口配置
owner: team-billing
paths:
- config/refund-policy.yaml
keywords:
- window_days
- effective_at
- id: article-refund-apply
type: documentation
name: 帮助中心退款申请说明
owner: team-content
paths:
- help/refund-apply.md
keywords:
- 退款
- 申请期限
- id: test-refund-boundary
type: test
name: 退款日期边界回归测试
owner: team-billing
paths:
- tests/refund-boundary.spec.ts
keywords:
- eligibleUntil
- 14 days目录不需要一开始就完整。每次人工发现雷达漏掉一个真实资产,就补入目录并新增回归样本。长期看,这份目录比一条复杂提示词更有价值。
4. 先用程序生成机械差异
模型不应承担逐字符找不同。Markdown 可按标题切成段落,再用普通 diff 找新增、删除和修改候选:
ts
type Section = { heading: string; body: string };
export function splitMarkdown(markdown: string): Section[] {
const lines = markdown.split(/\r?\n/);
const sections: Section[] = [];
let current: Section = { heading: "__preamble__", body: "" };
for (const line of lines) {
const match = line.match(/^#{1,4}\s+(.+)$/);
if (match) {
if (current.body.trim()) sections.push({ ...current, body: current.body.trim() });
current = { heading: match[1].trim(), body: "" };
} else {
current.body += `${line}\n`;
}
}
if (current.body.trim()) sections.push({ ...current, body: current.body.trim() });
return sections;
}用标题精确匹配为主,模糊匹配为辅。无法可靠配对的段落按新增/删除处理,并在报告中标记“段落移动待确认”。不要让模型把纯排版调整解释成业务变化。
5. 把差异拆成原子变化
一段文字可能同时修改资格、窗口和生效日期,必须拆开:
text
输入:旧段落、新段落、机械差异
任务:提取业务含义发生变化的原子主张。
要求:
1. 一个变化只描述一个主体的一条规则。
2. 纯措辞、格式和段落移动不算业务变化。
3. before / after 必须能在对应原文中逐字定位。
4. 条件、例外和生效时间分别输出,不得省略。
5. 无法判断是措辞调整还是规则调整时加入 open_questions。
6. 只输出符合 AtomicChange[] Schema 的 JSON。服务端验证每条引用确实存在于对应版本,并为稳定内容生成 ID:
ts
import { createHash } from "node:crypto";
export function changeId(change: Omit<AtomicChange, "id">): string {
const stable = JSON.stringify({
subject: change.subject,
before: change.before,
after: change.after,
condition: change.condition
});
return `chg_${createHash("sha256").update(stable).digest("hex").slice(0, 12)}`;
}6. 候选召回先于模型判断
对每个原子变化,从资产目录召回候选:
- 用变化中的专有名词、字段名、旧值和新值做精确搜索;
- 搜索资产目录
keywords、invariants和关联路径; - 在允许的代码范围内用
rg搜索; - 可选:对资产说明做向量召回;
- 合并去重后只把候选交给模型判断关联。
bash
rg -n --hidden \
--glob '!node_modules/**' \
--glob '!.git/**' \
--glob '!**/.env*' \
'refund_window_days|14 days|退款窗口' .搜索前必须限制目录和敏感文件。不要把整个代码库、.env、凭证或客户数据拼进模型上下文。
候选结构:
json
{
"asset_id": "service-refund-api",
"catalog_reason": "keyword: refund_window_days",
"snippets": [
{
"path": "services/refund/policy.ts",
"lines": "18-31",
"text": "const eligibleUntil = addDays(paidAt, config.refundWindowDays)"
}
]
}7. 让模型做关联解释,不让它凭空发现资产
text
输入:一个 AtomicChange、候选资产目录项、代码或文档片段。
判断:该资产是否可能因该变化而需要修改、验证或确认?
规则:
1. 只能从候选资产中选择,不能创造不存在的路径。
2. 每个影响必须引用目录字段或候选片段。
3. 区分“需要修改”和“只需回归验证”。
4. 给出最小可执行检查,不生成笼统的“全面测试”。
5. 证据不足时不建立影响,改为 open_question。模型输出后,程序验证 assetId 存在、证据路径属于该资产、验证命令在允许列表内。风险高的变化不因高置信度自动发布。
8. 用规则补上模型容易漏的关系
很多影响可以确定性地产生:
ts
type ImpactRule = {
when: (change: AtomicChange) => boolean;
addAssets: string[];
checks: string[];
};
const rules: ImpactRule[] = [
{
when: (c) => /天|日|窗口/.test(`${c.before ?? ""}${c.after ?? ""}`),
addAssets: ["test-refund-boundary"],
checks: ["验证生效日前后订单", "验证时区边界", "验证历史订单回放"]
},
{
when: (c) => c.effectiveAt !== null,
addAssets: ["config-refund-window"],
checks: ["验证定时生效", "验证配置回滚", "验证多实例一致性"]
}
];模型适合发现难编码的语义关系,规则适合保证关键不变量不被漏掉。两者结果合并,并记录来源是 rule 还是 model。
9. 生成真正能执行的回归清单
“测试退款功能”没有可操作性。每条检查包含前置、动作、预期和责任边界:
yaml
- id: check-refund-before-effective-date
asset: service-refund-api
setup: 创建一笔生效日前一天支付、距今 10 天的订单
action: 调用退款资格接口
expected: 按旧规则返回 eligible=true,并记录 policy_version=v3
execution: manual
owner: team-billing
- id: check-refund-after-effective-date
asset: service-refund-api
setup: 创建一笔生效日后支付、距今 10 天的订单
action: 调用退款资格接口
expected: 按新规则返回 eligible=false,并记录 policy_version=v4
execution: automated-candidate
owner: team-billingAI 可以生成候选,但只有仓库中真实存在的测试命令才可以标记为自动执行。所有 Shell 命令通过允许列表,不把模型文本直接送进终端。
10. 数据库存储报告与人工决定
sql
create table impact_reports (
id text primary key,
source_before_hash text not null,
source_after_hash text not null,
workflow_version text not null,
status text not null check (status in ('draft', 'reviewed', 'published')),
created_at timestamptz not null default now()
);
create table impact_decisions (
report_id text not null references impact_reports(id),
change_id text not null,
asset_id text not null,
decision text not null check (decision in ('accept', 'reject', 'edit')),
reason_code text not null,
decided_by text not null,
decided_at timestamptz not null default now(),
primary key (report_id, change_id, asset_id)
);人工驳回原因至少区分:资产无关、变化识别错误、证据不足、重复影响、已由其他资产覆盖。它们会导向不同的改进方向。
11. API 以资源和状态为中心
text
POST /v1/impact-reports 创建分析任务
GET /v1/impact-reports/{id} 查询状态与结果
POST /v1/impact-reports/{id}/decisions 提交人工决定
POST /v1/impact-reports/{id}/publish 发布已确认报告
GET /v1/impact-reports/{id}/events 查看执行时间线创建请求使用幂等键;分析放入队列;状态返回当前阶段和可恢复错误。发布动作校验所有关键变化已有负责人,并用报告内容哈希绑定审批,避免“审的是 A,发的是 B”。长任务设计见《可恢复的 AI 任务执行器》。
12. 评测集来自历史真实变更
为每个样本保存:旧文档、新文档、人工标注原子变化、应受影响资产、不能受影响资产、必要检查和难点标签。
json
{
"id": "refund-window-14-to-7",
"slices": ["numeric-change", "effective-date", "backward-compatibility"],
"expected_changes": ["refund_window_days:14->7"],
"must_include_assets": ["service-refund-api", "article-refund-apply", "test-refund-boundary"],
"must_not_include_assets": ["service-invoice-export"],
"must_raise_questions": ["legacy_order_policy"]
}核心指标:
- 原子变化精确率与召回率;
- 关键资产召回率;
- 无关资产误报率;
- 证据有效率;
- 人工接受、修改、驳回比例;
- 每份报告节省的人工分钟;
- 严重漏项率。
不要用一个综合分掩盖严重漏项。关键资产召回率必须单独设门禁。
13. 三个迭代,而不是一次做“大而全”
13.1 第一迭代:离线影子报告
手工上传两个 Markdown,只生成报告,不创建工单。用 20 个历史变更校准原子变化和资产召回。
13.2 第二迭代:接入一个真实入口
监听政策仓库合并事件,自动生成草稿;负责人在现有 Pull Request 或工单系统中确认,不新造工作台。
13.3 第三迭代:自动执行低风险检查
只执行仓库配置中明确允许的测试命令,把结果附到报告。修改配置、通知客户、发布帮助页等动作继续走人工审批。
14. 上线前故障演练
至少验证:
- 新旧版本传反时能被发现;
- 只有格式变化时生成空业务变化报告;
- 资产目录缺负责人时阻止发布;
- 文档包含提示词注入文本时不执行其中指令;
- 模型返回不存在的路径时被校验层拒绝;
- 同一 Webhook 重放时不创建重复报告;
- 模型超时后任务可从检查点恢复;
- 人工审批后报告被修改时旧审批失效。
做到这里,产品价值不再是“AI 能读懂文档”,而是把变更从被动通知推进到有证据、有人负责、有检查、有闭环的工程动作。