首页
/ oh-my-pi 的 AI Changelog 撰写规范:changelog-system.md 系统提示词拆解与代码流水线实现

oh-my-pi 的 AI Changelog 撰写规范:changelog-system.md 系统提示词拆解与代码流水线实现

2026-09-09 19:44:10作者:咎岭娴Homer

导读

本文以 packages/coding-agent/src/commit/prompts/changelog-system.md 为主线,完整拆解 oh-my-pi(仓库代码仓库中的 AI Coding Agent 项目)如何用一条系统提示词驱动 LLM 从 git diff 自动产出符合 Keep a Changelog 规范的变更日志条目。读完本文,你将掌握该提示词的全部约束设计(分类体系、条目格式、排除规则、JSON 输出契约),并通过源码链路了解它在 omp commit 中是如何被渲染、调用、解析、去重并写回 CHANGELOG.md 的。

一、文档定位:一条被工程化消费的"提示词即契约"

changelog-system.md 并不是给人看的说明文档,而是一条被源码以文本形式导入的系统提示词。在 generate.ts 中可以看到它被直接加载:

import changelogSystemPrompt from "../../commit/prompts/changelog-system.md" with { type: "text" };
import changelogUserPrompt from "../../commit/prompts/changelog-user.md" with { type: "text" };

随后在 generateChangelogEntries 中,系统提示词被渲染并作为 systemPrompt 发送给模型(generate.ts):

const response = await retryTransientCompletion(() =>
	completeSimple(
		model,
		{
			systemPrompt: [prompt.render(changelogSystemPrompt)],
			messages: [{ role: "user", content: userContent, timestamp: Date.now() }],
			tools: [changelogTool],
		},
		{ apiKey, sessionId, maxTokens: 1200, reasoning: toReasoningEffort(thinkingLevel) },
	),
);

也就是说,这条提示词是"AI 变更日志写手(expert changelog writer)"的角色设定与行为契约,配套的用户侧模板 changelog-user.md 负责注入 diff 上下文。两者配合构成了完整的"系统约束 + 任务输入"结构。

二、核心指令(instructions):只写用户可见的变更

提示词开头定义了三条硬性指令:

  1. Identify only user-visible changes
  2. Categorize each change (use categories below)
  3. Omit categories with no entries

拆解如下:

  • 只识别用户可见的变更:这是整个提示词的第一原则。内部重构、代码风格调整、纯测试改动、文档微调都被视为"对用户无感",不应当出现在 changelog 中;
  • 逐条归类:每条变更必须落入下方七类分类之一,不允许出现游离于分类体系之外的描述;
  • 省略空分类:没有任何条目的分类不要输出,保证结果 JSON 的最小化。

这一"用户可见性"原则在源码侧还有一处呼应:types.ts 中定义了 ConventionalDetail 结构,携带 userVisible: boolean 字段(types.ts),说明"是否用户可见"是整个 commit 分析流水线的通用判定标准,而不仅存在于提示词里。

三、七类分类体系(categories)与源码契约

提示词定义的分类与含义如下:

分类 含义 典型场景
Added 新增功能、公共 API、面向用户的能力 新命令、新参数、新工具
Changed 修改了既有行为 默认值调整、流程变化
Deprecated 计划移除的功能 标记废弃但暂未删除
Removed 删除的功能或 API 下线旧接口
Fixed 可观察影响的缺陷修复 bug 修复
Security 漏洞修复 安全问题
Breaking Changes API 不兼容的变更(谨慎使用) 移除认证流程等

值得注意的是:提示词正文将 Breaking Changes 列在最后,但仓库内部对其顺序另有规定。在 types.ts 中,CHANGELOG_CATEGORIES 定义了规范的渲染顺序

export const CHANGELOG_CATEGORIES: ChangelogCategory[] = [
	"Breaking Changes",
	"Added",
	"Changed",
	"Deprecated",
	"Removed",
	"Fixed",
	"Security",
];

这是 Keep a Changelog 惯例的分类展示顺序(Breaking Changes 置顶),由 renderUnreleasedSectionschangelog/index.ts 中按此顺序渲染 ### 分类 小节。

同时,在模型工具层,generate.ts 用 arktype 定义了与提示词一一对应的结构化 schema(generate.ts):

const changelogEntriesSchema = type({
	"Breaking Changes?": "string[]",
	"Added?": "string[]",
	"Changed?": "string[]",
	"Deprecated?": "string[]",
	"Removed?": "string[]",
	"Fixed?": "string[]",
	"Security?": "string[]",
});

export const changelogTool = {
	name: "create_changelog_entries",
	description: "Generate changelog entries grouped by Keep a Changelog categories.",
	parameters: type({ entries: changelogEntriesSchema }),
};

每个分类都是可选的字符串数组,模型可以调用 create_changelog_entries 工具返回结构化结果,而不是裸文本——这保证了后续解析的可靠性。

四、条目撰写格式(entry-format):四条铁律与正反例

提示词对单条条目给出四条格式要求:

  • 以过去式动词开头AddedFixedImplementedUpdated
  • 描述用户可见的影响,而非实现细节:写"用户能感知到什么变化",不写"内部怎么改的";
  • 点名具体功能、选项或行为:条目必须可定位到具体对象;
  • 保持 1~2 行,不加句尾句号:简洁且语法统一。

好的例子(Good)

- Added --dry-run flag to preview changes without applying them
- Fixed memory leak when processing large files
- Changed default timeout from 30s to 60s for slow connections

坏例子(Bad)及原因

- **cli**: Added dry-run flag → redundant scope prefix
- Added new feature. → vague, trailing period
- Refactored parser internals → not user-visible

提示词明确给出了三类"坏"的原因:

  1. 多余的作用域前缀**cli**:):changelog 条目不应重复 scope 前缀,保持干净的自然语言;
  2. 含糊 + 句号Added new feature.):没有点名具体功能,且末尾句号违反格式要求;
  3. 非用户可见Refactored parser internals):纯内部重构被明确排除。

Breaking Changes 的专门示例

- Removed legacy auth flow; users must re-authenticate with OAuth tokens

破坏性变更需要写出对用户的后续影响(必须重新认证),这是该分类特有的写作要求。

值得补充的是,"去掉句尾句号"不仅是提示词要求,源码在解析阶段也做了兜底。dedupeEntries 会对每条记录执行 value.trim().replace(/\.$/, "")generate.ts),normalizeEntries 同样执行 .trim().replace(/\.$/, "")changelog/index.ts)——即使模型偶发输出句号,写入文件前也会被规范化。

五、排除规则(exclude):什么不该写

提示词明确规定以下内容不得进入 changelog:

Internal refactoring, code style changes, test-only modifications, minor doc updates.

内部重构、代码风格变更、纯测试修改、次要文档更新四类一律排除。这与第一部分的"只写用户可见变更"相互呼应:被排除的四类恰好都是用户无感的变化。这也解释了为什么坏例中的 Refactored parser internals 会被标记为 Bad。

六、输出格式(output-format):严格的 JSON 契约

提示词对输出格式提出两条强制要求:

Return ONLY valid JSON; no markdown fences or explanation.

即:

  • 只返回合法 JSON,禁止出现 markdown 代码围栏(```)或任何解释文字;
  • 有条目时返回 {"entries": {"Added": ["entry 1"], "Fixed": ["entry 2"]}}
  • 无可写条目时返回 {"entries": {}}

源码如何消化这个契约

parseChangelogResponsegenerate.ts)实现了双通道解析:

function parseChangelogResponse(message: AssistantMessage): ChangelogGenerationResult {
	const toolCall = extractToolCall(message, "create_changelog_entries");
	if (toolCall) {
		const parsed = validateToolCall([changelogTool], toolCall) as typeof changelogTool.parameters.infer;
		return { entries: parsed.entries ?? {} };
	}
	const text = extractTextContent(message);
	const parsed = parseJsonPayload(text) as ChangelogGenerationResult;
	return { entries: parsed.entries ?? {} };
}
  • 优先通道:若模型调用了 create_changelog_entries 工具,则用 validateToolCall 按 arktype schema 严格校验;
  • 兜底通道:若模型只返回文本,则用 parseJsonPayload 从纯文本中提取 JSON。

两个通道都兼容 {"entries": {}} 的空结果——当 Object.keys(generated.entries).length === 0 时,runChangelogFlow 会跳过该文件的写入(changelog/index.ts)。

去重细节

dedupeEntries 按"小写化后的文本"去重,并且再次去掉句尾句号、剔除空字符串(generate.ts)。这意味着即使模型在同一分类下重复输出相似条目,最终也只保留一条。

七、从提示词到完整流水线:omp commit 中的调用链路

changelog-system.md 不是孤立存在的,它被嵌入在 oh-my-pi commit 流水线的完整链路中:

cli.ts --no-changelog 开关
  → pipeline.ts 调用 runChangelogFlow
    → detect.ts   找出每个变更文件"最近的 CHANGELOG.md"边界
    → index.ts    取 cached diff + numstat 统计,截断超大 diff
    → parse.ts    解析现有 [Unreleased] 小节的条目
    → generate.ts 渲染 changelog-system.md + changelog-user.md,调用模型
    → index.ts    合并、去重、按 CHANGELOG_CATEGORIES 顺序渲染并写回

各环节的关键实现证据:

1. 边界探测(detect.ts)

detect.ts 对每个暂存文件从所在目录逐级向上查找最近的 CHANGELOG.md(不区分大小写匹配 changelog.md,且跳过 changelog 文件自身),将多个变更文件按所属 changelog 文件分组。这支持了 monorepo 场景下"一个包一份 changelog"的诉求。

2. diff 与统计信息(index.ts)

runChangelogFlowchangelog/index.ts)会:

  • repo.diffText({ cached: true, files: boundary.files }) 取暂存区 diff;
  • repo.numstat 渲染类似 git diff --stat 的摘要(renderStat);
  • truncateDiff 将超长 diff 截断到默认 120_000 字符(DEFAULT_MAX_DIFF_CHARS),超出部分标注 […N ch elided…]

截断后的 diff 与 stat 都会注入到 changelog-user.md 模板的 <diff-summary><diff> 块中,作为用户消息发送给模型。

3. 已有条目防重复(changelog-user.md)

changelog-user.md 是配套的用户模板:

<context>
Changelog: {{ changelog_path }}
{{#if is_package_changelog}}Scope: Package-level changelog. Omit package name prefix from entries.{{/if}}
</context>
{{#if existing_entries}}
<existing-entries>
Already documented—skip these:
{{ existing_entries }}
</existing-entries>
{{/if}}

其中两个细节值得注意:

  • existing_entries:已解析出的 [Unreleased] 现有条目会以"Already documented—skip these"的形式提供给模型,避免重复记录;
  • is_package_changelog:当目标 changelog 不是仓库根 CHANGELOG.md 时,要求条目省略包名前缀。该判断来自 changelog/index.tspath.resolve(boundary.changelogPath) !== path.resolve(cwd, "CHANGELOG.md")

4. 写入与合并(index.ts)

applyChangelogEntrieschangelog/index.ts)在解析出的 [Unreleased] 行区间内重写小节:现有条目与新条目按分类合并(mergeEntries 同样做小写去重),并支持 deletions 定向删除。非 dryRun 模式下写入文件后还会 repo.stageFiles 把 changelog 重新加入暂存区。

八、双保险:Agentic 模式下 propose_changelog 工具的强校验

除了提示词约束,oh-my-pi 的 agentic commit 流程还提供了一个独立工具 propose_changelogpropose-changelog.ts),对模型输出做二次强校验

  • 分类白名单校验:allowedCategories 只接受 CHANGELOG_CATEGORIES 七类,未知分类直接报错 Unknown changelog category for {path}
  • 条目规范化:value.trim().replace(/\.$/, "") + 空值过滤 + Set 去重,与提示词格式要求完全对齐;
  • 目标文件校验:模型只能对预期内的 changelog 文件提条目,越界(Changelog not expected)、重复(Duplicate changelog entry)、遗漏(Missing changelog entries)都会被判定为校验失败;
  • 校验失败时返回 "Changelog validation failed." 并附带 errors 列表,供 Agent 自我修正。

这体现了该项目的设计哲学:提示词负责引导,schema 与校验代码负责兜底,两者共同保障 changelog 数据的结构化与正确性。

九、运行与调试实操

1. 触发时机

changelog 更新内嵌于 omp commit 流水线,由 pipeline.ts 在生成 commit message 后执行;agentic 模式则由 agentic/index.ts 在流程中调用。CLI 层提供开关(cli.ts):

--no-changelog   Skip changelog updates

默认 noChangelog: false,即默认会更新 changelog;显式传入 --no-changelog 可跳过。

2. 调试提示词效果

如果你希望调整模型产出风格,可以修改 changelog-system.md 中的 entry-format 示例或 categories 描述,改动会被源码以文本导入直接生效(无需重新编译模板)。但请保持以下契约不变,否则会破坏解析:

  • 输出必须是合法 JSON({"entries": {...}}{"entries": {}});
  • 分类名必须与 types.tsCHANGELOG_CATEGORIES 的七类完全一致;
  • 单条条目不应以句号结尾(解析层会兜底清除,但保持规范更利于模型稳定)。

3. 常见观察点

  • 若 changelog 未更新,先检查 [Unreleased] 小节是否存在(parse.ts## [Unreleased] 正则不匹配会抛错并跳过);
  • 若 diff 过大,模型看到的可能是截断内容(默认 120K 字符),复杂变更建议分批暂存提交;
  • 若条目重复,检查 dedupeEntriesmergeEntries 的小写化去重逻辑是否命中预期。

十、总结

changelog-system.md 虽只有短短 50 行,却浓缩了一套完整的 AI 变更日志工程规范:用户可见性过滤 → 七类分类 → 格式约束 → 排除规则 → 严格 JSON 契约。在 oh-my-pi 中,它并非孤立的提示词,而是与 types.ts 的分类常量、generate.ts 的 arktype schema 与去重逻辑、detect.ts/parse.ts/index.ts 的边界探测与写入流水线、以及 propose_changelog 工具的强校验共同构成闭环。理解这条提示词,就等于理解了如何用"提示词 + 结构化工具 + 代码校验"三件套,把 LLM 的模糊语言输出驯化为机器可验证、可落盘的高质量 changelog 数据。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.76 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
860
1.35 K
docsdocs
暂无描述
Markdown
899
5.83 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
925
1.85 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.84 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
533
601
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.03 K
525
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.37 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
395