☰
loop-sync CRLF Frontmatter 回归测试:为 Windows 贡献者守护 YAML 解析的正确性
2026/9/24 23:49:06 网站建设 项目流程
  • 人工智能
  • AI Agent
  • Agent 工作流
  • CLI
  • 研发协作
  • AI 技能
  • MCP 服务

【免费下载链接】loop-engineering

Practical patterns, starters & CLI tools for loop engineering with AI coding agents. Design systems that prompt and orchestrate agents (inspired by Addy Osmani and Boris Cherny). Includes loop-audit, loop-init, loop-cost.

项目地址:https://gitcode.com/gh_mirrors/lo/loop-engineering
点击查看免费下载

本文围绕 loop-engineering 仓库中 scripts/issue-bodies/loop-sync-crlf-tests.md 这一任务文档展开,讲解loop-sync工具中extractFrontmatter对 LF / CRLF 换行符的处理逻辑,以及如何为它补齐回归测试,防止 Windows 贡献者的提交在未来的重构中被悄悄破坏。

导读

loop-sync是 loop-engineering 仓库中负责检测并同步 Loop 配置文件(STATE.md、LOOP.md、gate.yaml、docs/safety.md等)漂移的 CLI 工具。社区 PR #476 修复了extractFrontmatter在 Windows 换行符(\r\n,即 CRLF)下的解析问题,但如果缺少回归测试,任何未来的改动都可能重新破坏 Windows 贡献者的体验。本文以 loop-sync-crlf-tests.md 这份任务说明为主体骨架,结合仓库源码与既有测试,完整还原:为什么 CRLF 会破坏 frontmatter 解析、修复后的解析规则是什么、回归测试应该覆盖哪些边界条件,以及如何运行与验证测试。

背景:任务文档的由来与价值

该任务文档是一份典型的"好第一议题"(good first issue)描述,明确给出了目标、改动文件、验收标准与预估时间:

  • Goal:为loop-sync的 CRLF frontmatter 解析添加回归测试;
  • Pain source:社区 PR #476 修复了extractFrontmatter对 Windows 换行符(\r\n)的处理。没有测试,未来的改动可能再次破坏 Windows 贡献者;
  • Files:扩展tools/loop-sync/test/sync.test.mjs,可选在tools/loop-sync/test/fixtures/下添加 fixture 文件;
  • Estimated time:约 30–40 分钟,标签tooling。

它与仓库中的另一份文档 windows-contributor-notes.md 互为呼应——后者建议在 docs/QUICKSTART.md 中补充 Windows/CRLF 注意事项,并明确指向"loop-sync frontmatter LF/CRLF support (after #476)"。这说明 CRLF 支持并非孤立的修复,而是 Windows 贡献者体验整体建设的一部分。

为什么 CRLF 会破坏 frontmatter 解析

Markdown frontmatter 的规范形态是以---作为开闭围栏,中间是key: value形式的键值对:

--- key: value --- body text

在 Linux/macOS 上,行尾是\n(LF);在 Windows 上,文本文件的行尾通常是\r\n(CRLF)。Git 的core.autocrlf配置若未正确设置,可能导致仓库中的STATE.md、LOOP.md或 SKILL.md 以 CRLF 提交。此时 frontmatter 长这样:

---\r\n key: value\r\n ---\r\n body text\r\n

问题在于:如果解析器按\n切分后不做处理,value\r中的\r会被当作值的尾部字符,导致解析出的 key 或 value 带上隐藏的\r后缀。这会让依赖 frontmatter 精确匹配的逻辑(例如scanSkillsDirectory读取 SKILL.md 的version字段、loop-sync对比STATE.md ↔ LOOP.md的结构相似度)产生"看起来一样、实际不相等"的漂移误报,也就是 QUICKSTART 中提示的"loop-syncreports drift unexpectedly on Windows"。

修复后的解析规则(源码级还原)

当前仓库中extractFrontmatter的实现位于 tools/loop-sync/src/sync.ts,编译产物在 tools/loop-sync/dist/sync.js。整体逻辑可以拆成四个关键分支:

1. 开围栏同时接受 LF 与 CRLF

if (content.startsWith('---\n') || content.startsWith('---\r\n')) {

只有文件严格以---\n或---\r\n开头时才进入 frontmatter 解析。这正是验收标准中第三条"---hello(开围栏后没有换行)必须被拒绝/不当作 frontmatter"的判定基础——'---hello'既不以---\n开头,也不以---\r\n开头,直接被拒。

2. 闭合围栏必须前有换行

const endIndex = content.indexOf('\n---', 3);

通过搜索\n---定位闭合围栏,这意味着闭合围栏之前必须存在一个换行符。若文件以---\nkey: value\n---oops\n结尾,indexOf('\n---', 3)无法匹配到合法的\n---序列(---oops前虽有换行,但\n---后紧跟oops而不是行尾/换行),从而被拒绝。既有测试 sync.test.mjs 的 "rejects closing fence without preceding newline" 用例验证的正是这一边界。

3. 闭合后校验:行尾或换行(含\r)

if (endIndex !== -1 && (endIndex + 4 === content.length || content[endIndex + 4] === '\n' || content[endIndex + 4] === '\r')) {

找到\n---后,闭合围栏之后的内容必须是:文件末尾、\n或\r(兼容 CRLF 的\r\n尾部)。这样---\nkey: value\n---oops这类"闭合围栏后跟多余字符"的畸形输入会被拒绝。

4. 键值按\r?\n切分并 trim

for (const line of fmContent.split(/\r?\n/)) { const colonIndex = line.indexOf(':'); if (colonIndex !== -1) { const key = line.slice(0, colonIndex).trim(); const value = line.slice(colonIndex + 1).trim(); frontmatter[key] = value; } }

这是修复的核心:用正则/\r?\n/切分 frontmatter 内容,无论行尾是 LF 还是 CRLF 都能正确分行;随后对 key 与 value 都执行.trim(),把可能残留的\r一并清除。因此 CRLF 输入中的value\r最终被解析为干净的value。这正是任务验收标准中"CRLF frontmatter 解析出的 key 不带尾部\r"的底层保证。

回归测试:任务要求的三个验收点

任务文档给出了三条明确的验收标准,而仓库的既有测试已经把它们全部落地在 tools/loop-sync/test/sync.test.mjs 的describe('extractFrontmatter')块中。

验收点 1:LF frontmatter 仍然正常解析(既有行为不被破坏)

test('parses LF frontmatter', () => { const { frontmatter, body } = extractFrontmatter( '---\nkey: value\n---\nbody text\n', ); assert.deepEqual(frontmatter, { key: 'value' }); assert.equal(body, 'body text\n'); });

回归测试首先锁定既有行为:LF 输入必须解析出{ key: 'value' },且 body 保持'body text\n'原样。这是防回归的基线——修复 CRLF 不能以破坏 LF 为代价。

验收点 2:CRLF frontmatter 解析出不带\r的键值

test('parses CRLF frontmatter without trailing carriage returns', () => { const { frontmatter, body } = extractFrontmatter( '---\r\nkey: value\r\nother: thing\r\n---\r\nbody text\r\n', ); assert.deepEqual(frontmatter, { key: 'value', other: 'thing' }); assert.ok(!Object.values(frontmatter).some((v) => v.endsWith('\r'))); assert.ok(!Object.keys(frontmatter).some((k) => k.endsWith('\r'))); assert.equal(body, 'body text\r\n'); });

这个用例直击任务文档的核心验收标准:CRLF frontmatter(---\r\nkey: value\r\n---\r\nbody形态)必须解析出不带尾部\r的 key 与 value。断言同时覆盖 value 和 key 两个方向,确保.trim()的修复不遗漏任何一侧。body 部分则验证body text\r\n原样保留(body 不做换行符归一化)。

验收点 3:开围栏后无换行的---hello必须被拒绝

test('rejects opening fence without newline (---hello)', () => { const { frontmatter } = extractFrontmatter('---hello\nkey: value\n---\n'); assert.deepEqual(frontmatter, {}); });

输入---hello不满足content.startsWith('---\n')或content.startsWith('---\r\n'),因此extractFrontmatter返回空对象{}——这防止了将非 frontmatter 内容误判为配置元数据。既有测试还额外补了一个对称用例(闭合围栏后跟oops被拒绝),虽然不是任务文档的显式要求,但属于同一"围栏合法性"边界的自然延伸。

extractFrontmatter 在 loop-sync 中的真实调用链

extractFrontmatter并非孤立函数,它是 loop-sync 读取配置元数据的底层基础设施。核心调用点位于 tools/loop-sync/src/sync.ts 的scanSkillsDirectory:

const skillMd = path.join(skillPath, 'SKILL.md'); const content = await readFileContent(skillMd); if (content) { const { frontmatter } = extractFrontmatter(content); skillsVersions.set(entry, frontmatter.version || 'unknown'); }

scanSkillsDirectory会扫描skills/、.grok/skills/、.claude/skills/、.codex/skills/四个目录,对每个技能的SKILL.md调用extractFrontmatter读取version字段。如果 Windows 贡献者在编辑 SKILL.md 时引入了 CRLF,而解析器把version\r当作版本号,loop-sync就会报告版本漂移。换言之,CRLF 回归测试保护的不仅是extractFrontmatter本身,更是整个 loop-sync 的"Skills version updates"检查链路(对应 README 中的检查项 4)。

此外,runSync中STATE.md ↔ LOOP.md的一致性检查(sync.ts)依赖readFileContent读入的原始文本进行结构相似度对比,CRLF 残留同样会造成similarity计算偏差。这正是 docs/QUICKSTART.md 中"如果 loop-sync 在 Windows 上意外报告漂移,先检查STATE.md/LOOP.md的 CRLF"这条排障建议的技术根因。

如何运行与验证测试

任务的验收标准最后一条是cd tools/loop-sync && npm test通过。仓库的 package.json 定义了完整的测试链路:

"scripts": { "build": "tsc", "test": "npm run build && node --test test/*.test.mjs", "prepublishOnly": "npm test" }

npm test会先执行tsc把src/*.ts编译到dist/,再用 Node 内置的node:test运行test/*.test.mjs。注意测试文件顶部import { runSync, formatReport, extractFrontmatter } from '../dist/sync.js'——测试对象是编译产物而非 TypeScript 源码,因此每次改动src/sync.ts后必须先构建,再跑测试。这一点从 tools/loop-sync/README.md 的 Development 段落也能看到同样顺序:

cd tools/loop-sync npm install npm run build npm test

从 scripts/issue-bodies/loop-sync-crlf-tests.md 的定位看,这份任务是面向贡献者的引导式任务(文末"Comment 'I'll take this' to get assigned"是标准的好议题认领流程),预估 30–40 分钟、标签tooling。对完成者而言,最小改动路径有两种:

  1. 扩展现有测试文件:在 sync.test.mjs 的describe('extractFrontmatter')块中追加用例——仓库目前已经包含了任务要求的所有用例,这是推荐做法,改动集中、可读性好;
  2. 添加 fixture 文件:当用例较长或需要模拟真实文件时,可在tools/loop-sync/test/fixtures/下放置带 CRLF 的STATE.md/LOOP.md样本,再在测试中通过readFile读取断言。

更广视角:CRLF 问题是 Windows 贡献者体验的一部分

这次回归测试任务不是孤例。仓库中与之配套的文档工作包括:

  • scripts/issue-bodies/windows-contributor-notes.md:建议在 docs/QUICKSTART.md 增加 Windows/CRLF 注意事项小节;
  • docs/QUICKSTART.md:已落地 "Windows / CRLF notes" 小节,推荐git config core.autocrlf input保持仓库 LF 行尾,并明确说明"loop-syncstrips carriage returns"(即 strip\r的行为正是extractFrontmatter中/\r?\n/切分与.trim()的实现效果);
  • CONTRIBUTING.md:同样涉及 line-ending 相关的贡献规范。

把这些线索串起来可以看到:回归测试(本任务)防止代码回退,文档(windows-contributor-notes)防止用户踩坑,两者共同构成 CRLF 兼容性的完整闭环。

小结

  • extractFrontmatter通过content.startsWith('---\n') || content.startsWith('---\r\n')同时接受 LF 与 CRLF 开围栏,用/\r?\n/切分并trim()清除残留\r,从而让 CRLF frontmatter 解析出干净的键值;
  • 回归测试的三个验收点(LF 基线、CRLF 无\r残留、---hello拒绝)已在 sync.test.mjs 全部落地,并额外覆盖了"闭合围栏后跟多余字符被拒绝"的对称边界;
  • 运行验证方式为cd tools/loop-sync && npm test,测试链路为tsc构建后由node --test执行;
  • CRLF 解析的正确性直接支撑scanSkillsDirectory的版本读取与STATE.md ↔ LOOP.md一致性检查,是 Windows 贡献者体验整体建设(配合 docs/QUICKSTART.md 的排障文档)的基础设施级保障。

相关仓库路径速查:任务文档 scripts/issue-bodies/loop-sync-crlf-tests.md|实现源码 tools/loop-sync/src/sync.ts|测试用例 tools/loop-sync/test/sync.test.mjs|CLI 入口 tools/loop-sync/src/cli.ts|使用文档 tools/loop-sync/README.md|Windows 注意事项 docs/QUICKSTART.md

  • 人工智能
  • AI Agent
  • Agent 工作流
  • CLI
  • 研发协作
  • AI 技能
  • MCP 服务

【免费下载链接】loop-engineering

Practical patterns, starters & CLI tools for loop engineering with AI coding agents. Design systems that prompt and orchestrate agents (inspired by Addy Osmani and Boris Cherny). Includes loop-audit, loop-init, loop-cost.

项目地址:https://gitcode.com/gh_mirrors/lo/loop-engineering
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询