ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

gsd-core 路线图硬换行字段解析修复:4731 多行提取器与 Requirements Coverage Gate 的完整剖析

gsd-core 路线图硬换行字段解析修复:4731 多行提取器与 Requirements Coverage Gate 的完整剖析 【免费下载链接】gsd-coreGit. Ship. Done - Core项目地址https://gitcode.com/gh_mirrors/ge/gsd-core点击查看免费下载导读本文深入剖析 gsd-coreGit. Ship. Done - Core中一项针对路线图Roadmap文档解析的修复当 roadmapper 将过长的 Goal/Requirements 字段在约 85 字符处软换行soft-wrap后旧的单行正则会把每个被换行的字段截断在第一行导致需求 ID 静默丢失、Goal 被腰斩、Phase Complete 留下错误 Pending 状态。文章以 .changeset/merry-lynx-climb.md 变更集为骨架结合 src/roadmap-parser.cts、src/roadmap.cts、src/phase.cts、src/init.cts 的源码实现与 tests/init.test.cjs、tests/phase.test.cjs、tests/roadmap-parser.test.cjs 的回归测试完整还原问题成因、共享多行提取器的设计、五处调用点的接入方式以及随之而来的边界加固读完可掌握 gsd-core 路线图字段解析的底层原理与同类问题的排查方法。一、变更集背景一次影响五条路径的解析缺陷1.1 变更集原文解读.changeset/merry-lynx-climb.md 是本次修复的官方变更记录type:FixedPR: 4826关联 issue #4731Roadmap queries read past hard-wrapped Goal/Requirements fields— the roadmapper soft-wraps long fields at ~85 chars, but five single-line field regexes truncated every wrapped Goal/Requirements at the first line: plan-phasesphase_req_idssilently dropped continuation-line REQ IDs (escaping the Requirements Coverage Gate), get-phase/analyze cut the goal mid-sentence, and phase complete left later REQs Pending with empty warnings. A shared multiline extractor now reads wrapped fields to the next field label. (#4731)关键信息可以拆成四点缺陷来源roadmapper 生成路线图时会把超过约 85 字符的长字段软换行——即不改变 Markdown 语义、仅按字符宽度插入换行。五条受影响路径plan-phase的phase_req_ids、get-phase/analyze的 Goal 提取、phase complete的需求引用扫描。三类后果REQ ID 静默丢失并逃过 Requirements Coverage Gate、Goal 在句中截断、Phase Complete 把后续 REQ 留在 Pending 且警告为空。修复方案引入共享的多行提取器让字段读取一直持续到下一个字段标签。1.2 为什么“软换行”会引爆解析在 gsd-core 的规划工作区中ROADMAP.md 的每个 Phase 段落以粗体标签组织字段典型形态如下节选自 tests/init.test.cjs 的 #4731 fixture### Phase 1: Demo **Goal:** Deliver a small demo feature that exercises the planning pipeline end to end with a goal sentence long enough to wrap onto a second line **Requirements**: REQ-01, REQ-02, REQ-03, REQ-04, REQ-05, REQ-06, REQ-07, REQ-08, REQ-09, REQ-10, REQ-11 **Plans**: 1 plans这里的 Goal 与 Requirements 都被 roadmapper 硬换行hard-wrap。传统做法是逐行用**Field:**\s*([^\n])之类的正则捕获首行内容结果Goal 只得到第一行Deliver a small demo feature that exercises the planning pipeline end to end with a句子被拦腰截断Requirements 只得到REQ-01 ... REQ-09,结尾多一个逗号REQ-10, REQ-11两个 ID 静默丢失。二、共享多行提取器extractPhaseFieldMultiline的实现剖析2.1 函数签名与整体策略本次修复的核心是 src/roadmap-parser.cts 中新增并导出的extractPhaseFieldMultiline(section, label)function extractPhaseFieldMultiline(section: string, label: string): string | null { // #2769 label shapes: **X:**, **X**:, and the spaced **X** :. // #4837: anchored to line start (^[ \t]*, m flag) ... const labelRe new RegExp( ^[ \\t]*\\*\\* label (?::\\*\\*|\\*\\*\\s*:?)\\s*([^\\n]), im, ); const match section.match(labelRe); if (!match) return null; // ...捕获首行后逐行向后扫描续行直到遇到结构性边界 }函数只接收两个参数section当前 Phase 段落文本与label如Goal、Requirements返回拼接后的完整字段或null标签不存在时。整个逻辑分两步第一步定位标签行。正则锚定到行首^[ \t]*配合m标志兼容 #2769 记录的三种标签形态**X:**、**X**:、**X** :。第二步续行扫描。取标签行匹配结束位置之后的文本逐行累积到contLines直到命中任一停止条件最后将[firstLine, ...contLines]用单个空格拼接并 trim。2.2 六类停止条件什么是“字段边界”续行循环逐个执行以下检查对应源码L2408-L2422停止条件正则作用空行!raw.trim()字段以空行自然结束列表项^\s*[-*]\s防止- Deferred to Phase N: OTHER-ID这类子弹把别的阶段 REQ-ID 折进本字段粗体标签行^\s*\*\*[A-Za-z][A-Za-z ]*:?(\*\*)?:?\s下一个字段标签大小写不敏感标题^\s*#{1,4}\sMarkdown 标题即结构边界表格行^\s*\|防止字段后的表格行内容混入代码围栏^\s*(?:{3,}|~{3,})围栏开闭都终止扫描此外还有两处前置防护源码L2394-L2400围栏同线检测**Requirements:** \js这种把围栏开启符写在标签同一行的情况旧实现在标签行之后才检查围栏会先漏进一行围栏内容新实现先在首行做^(?:{3,}|~{3,})检查一旦命中直接返回null。首行空行跳过split(\n)后第一个元素是首行自身换行的残余需显式跳过避免把标签行的换行误判为“空行”。2.3 关于“约 85 字符”的边界说明变更集提到 roadmapper 软换行阈值约为 85 字符。这一数值属于路线图生成器roadmapper的格式化行为本文讨论的extractPhaseFieldMultiline是读取端的修复不依赖具体阈值——无论换行发生在 80、85 还是 100 字符多行提取器都按“读到下一个字段标签为止”的策略工作因此对生成端阈值不敏感。三、五处调用点修复如何落地到各命令extractPhaseFieldMultiline在仓库中共有五处消费逐一对应变更集描述的五条路径。3.1init plan-phasephase_req_ids的完整需求 ID最严重的静默缺陷src/init.cts 与L1200-L1205两处对应不同命令形态const reqExtracted roadmapParser.extractPhaseFieldMultiline(phaseSection, Requirements); // ... const phase_req_ids reqExtracted reqExtracted ! TBD ? reqExtracted : null;这是变更集点名的核心漏洞REQ ID 静默丢失并逃过 Requirements Coverage Gate。规划流程中存在 Requirements Coverage Gate 环节它依赖phase_req_ids判断阶段是否完整覆盖了需求一旦续行 ID 被丢弃门禁认为“没有未覆盖需求”从而放行一个实际上未映射完整需求的阶段属于典型的静默失败silent failure。回归测试见 tests/init.test.cjs构造Requirements字段横跨三行、共 11 个 REQ-ID 的 fixture断言init plan-phase 1 --pick phase_req_ids输出必须包含REQ-01到REQ-11全部 ID并注明“continuation-line REQ-10/REQ-11 must not be silently dropped”。3.2query roadmap.get-phase与analyze完整 Goalsrc/roadmap.cts 与L515两处getPhase与analyze两个查询入口// #4731: multiline-aware — hard-wrapped Goals read past the line break. const goal roadmapParserModule.extractPhaseFieldMultiline(section, Goal);修复前get-phase/analyze会把长 Goal 截断在中句修复后返回完整句子。对应断言见 tests/init.test.cjsquery roadmap.get-phase 1 --pick goal必须返回拼合后的完整 Goal 文本。3.3phase complete需求引用扫描只读本阶段 IDsrc/phase.ctsconst reqLine sectionText ? roadmapParserMod.extractPhaseFieldMultiline(sectionText, Requirements) : null;这里是变更集描述的第三类后果phase complete 把后续 REQ 留在 Pending 且警告为空。修复后Phase Complete 的 citation-scan 拿到的是换行拼合后的完整引用列表能正确 tick 本阶段需求。3.4init其余入口src/init.cts 还包含一处Goal提取与 3.1/3.2 共享同一实现保证init各子命令行为一致。3.5 调用点小结调用文件行号标签消费命令src/init.ctsL1008 / L1200Requirementsinit plan-phasephase_req_idssrc/init.ctsL2940Goalinit相关阶段初始化src/roadmap.ctsL239 / L515Goalquery roadmap.get-phase、analyzesrc/phase.ctsL4024Requirementsphase complete需求引用扫描四、修复引发的边界加固#4837 结构性边界第一版多行提取器#4731上线后隔离评审发现四个未设防的结构性边界由 #4837 跟进补齐最终实现在 src/roadmap-parser.cts 的最终形态中。回归测试见 tests/roadmap-parser.test.cjs列表项折入真实破坏向量- Deferred to Phase 2: REQ-99这类子弹行会把别的阶段 REQ-ID 折进本字段的引用扫描最终导致 Phase Complete 误 tick 他阶段需求。现在^\s*[-*]\s立即终止扫描。小写首字母标签**depends on**: Phase 0与**Requirements**:同样是字段边界使用[A-Za-z]字符类而非仅大写。未锚定标签匹配被内联**mention**遮蔽某字段正文里提到**Requirements**字样时旧的正则可能错误命中^[ \t]*锚定保证只匹配行首的真正标签声明。围栏代码块标签行后紧跟或~~~围栏时立即终止围栏内容绝不被折入。这些测试的注释明确写道#4826#4731 后续为extractPhaseFieldMultiline加入续行扫描但留下四个未防护的结构边界缺陷 1列表项正是 #4837 报告的真实损坏向量端到端回归见 tests/phase.test.cjs。五、端到端回归Phase Complete 不越界 tick 表格行tests/phase.test.cjs 的#4731 review follow-up测试组验证了最微妙的一个场景换行 Requirements 字段后紧跟一张表格时引用扫描只认换行内的 ID绝不读取表格行。fixture 构造如下L12629-L12664REQUIREMENTS.md 中REQ-11/12/13属于 Phase 1 且 PendingREQ-99属于 Phase 2 且 PendingROADMAP.md 的 Phase 01 段落里Requirements 字段被硬换行为三行REQ-11,/REQ-12,/REQ-13随后紧跟一张包含REQ-99的 Deliverable 表格。修复前inline lookahead 只停在下一个**Bold**标签或段末会把表格里REQ-99这样的 ID 形单元格也吞进引用扫描导致 phase complete tick 掉 Phase 2 的复选框、翻转其 Traceability 行。修复后共享提取器在表格行^\s*\|处停止测试断言Phase 1 的 REQ-11/12/13 全部被正确引用并 tickrequirements_updated true表格中的 REQ-99 保持 Pending不产生“幽灵”ghost警告。六、对开发者的启示同类换行解析问题的排查清单从本次修复可以提炼一组可复用的排查方法适用于任何对“人工可读文档”做结构化解析的工具链先确认换行是否“软”的软换行不改变语义但破坏单行正则。凡是([^\n])形态的捕获都要问一句“字段会不会被 wrap”。ID 类字段的静默丢失最危险phase_req_ids丢 ID 之所以严重是因为下游 Requirements Coverage Gate 无法感知“丢失”与“没有”的区别。对这类字段应设置“必须读到字段终止符”的强约束而非“读到行尾”。字段终止条件要与 Markdown 语法一致空行、标题、表格行、列表项、代码围栏、下一个粗体标签——这六类边界是 #4731/#4837 用真实缺陷换来的清单可平移复用到其他文档解析器。结构化文档的解析结果必须配回归测试仓库对 #4731 建立了三层测试——单元级tests/roadmap-parser.test.cjs 的边界矩阵、命令级tests/init.test.cjs 的 CLI 断言、端到端级tests/phase.test.cjs 的表格越界场景每一层对应一类失败模式。七、总结本次修复以 .changeset/merry-lynx-climb.md 为变更记录把散落在plan-phase、get-phase、analyze、phase complete等五条路径上的单行字段正则收敛为 src/roadmap-parser.cts 中单一共享的多行提取器extractPhaseFieldMultiline在“读到下一个字段标签”的主策略之上用六类结构性停止条件堵住了列表项折入、表格越界、围栏泄漏等后续发现的边界漏洞。它不仅修复了 REQ ID 静默丢失逃过 Requirements Coverage Gate、Goal 截断、Pending 状态错误三类具体缺陷更确立了一个可复用的范式对软换行友好的文档解析必须基于“字段边界”而非“行边界”设计正则。赞分享【免费下载链接】gsd-coreGit. Ship. Done - Core项目地址https://gitcode.com/gh_mirrors/ge/gsd-core点击查看免费下载相关推荐gsd-core 变更日志解析修复与范围提取parseChangelog 多行条目捕获与 extract 子命令实战gsd core 变更日志解析修复与范围提取parseChangelog 多行条目捕获与 extract 子命令实战 导读 本文聚焦 gsd core 仓库中gsd-core 多运行时兼容修复/gsd:init 按运行时解析 agents 目录的深度解析gsd core 多运行时兼容修复/gsd:init 按运行时解析 agents 目录的深度解析 导读 本文围绕 gsd coreGit. Ship. Dogsd-core 修复实践codebase-drift-gate 在 shim-only 安装下通过 gsd_run 正确解析 gsd-toolsgsd core 修复实践codebase drift gate 在 shim only 安装下通过 gsd_run 正确解析 gsd tools 导读 本文上一篇IsaacLab 容器化部署两分钟跑通第一个仿真任务下一篇Classy变量与导入功能打造可复用的iOS样式库创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表