【免费下载链接】gsd-coreGit. Ship. Done - Core项目地址https://gitcode.com/gh_mirrors/ge/gsd-core点击查看免费下载本文聚焦 GSD Core 的/gsd:undo命令在--phase与--plan两种模式下提交选择逻辑的一次关键修复issue #4465随 PR #4472 以 changeset 记录旧实现通过仓库级git log --all的 commit-subject 正则 grep 选择待回滚提交存在跨里程碑污染与不可达分支误选等缺陷新实现改为以目标阶段自身目录作为锚点PHASE_START^..HEAD窗口并在无法解析锚点时 fail closed。读完本文你将掌握该提交选择窗口的完整推导过程、三条拒绝路径归档解析、异仓库锚点、路径复用的判定依据以及底层select-revert-commits子命令的结构化解析原理可直接据此评估与复现该修复的行为边界。一、修复背景旧选择的四个缺陷类本次 changeset.changeset/undo-bounded-commit-selection.md记录的是一类Fixed变更/gsd:undo --phase与--plan不再从不达分支选择提交也不再锚定到早期里程碑使用过的目录上。其直接缺陷源于 gsd-core/workflows/undo.md 中两条手写的git log --oneline | grep -E ...管道。在 src/undo-commit-selection.cts 的模块文档中这一旧实现被归纳为四类已文档化的 bugissue #4661正则注入旧 grep 将未经校验的用户输入 id 直接插值进 ERE.与等元字符会变成通配/量词畸形或形状不同的 id 会静默匹配到错误阶段。无锚定grep 未锚定一个只是提及某作用域的提交如docs(99-01): explain feat(03-01): commit convention会被错误地当作该作用域的声明选中。两种模式语法不一致phase 模式的\):后缀与 plan 模式缺失后缀在同一个 breaking-change 提交feat(03-01)!: breaking change上产生分歧。明显修复是个陷阱对git log --oneline记录起点做锚定会静默丢弃fixup! .../Revert ...包装提交更糟的静默部分回滚且在color.uialways下返回零匹配ANSI 码先于 sha。此外还有最危险的缺陷——选择没有里程碑边界与可达性边界git log --oneline --no-merges --all的--all会遍历所有引用因此一条HEAD不可达的提交也可能被选中并喂给git revert --no-commit从而暂存对前一里程碑文件的删除。测试文件 tests/undo-commit-selection-4465.test.cjs 的负向对照组精确复现了这一行为修复前选择行在multiMilestoneFixture上会把废弃分支的feat(03-02): abandoned experiment与已归档里程碑的feat(03-01): implement auth endpoint一并选中。二、修复核心以阶段目录为锚的提交窗口新实现将 issue #3995 中已在 gsd-core/workflows/code-review.md 使用的PHASE_START锚点移植到--phase与--plan两种模式并彻底移除--all改为 fail closed。锚点推导逻辑两种模式共享见 gsd-core/workflows/undo.md 的gather_commits步骤PHASE_START$(git -C ${PROJECT_ROOT:-.} log --format%H --diff-filterA -- ${PHASE_DIR} 2/dev/null | tail -1) # 只有 HEAD 自身历史内的提交才能作为锚点 if [ -n $PHASE_START ] ! git merge-base --is-ancestor $PHASE_START HEAD 2/dev/null; then PHASE_START; fi UNDO_RANGE if [ -n $PHASE_START ]; then if git rev-parse ${PHASE_START}^ /dev/null 21; then UNDO_RANGE${PHASE_START}^..HEAD else # PHASE_START 是根提交无父提交可排除${PHASE_START}..HEAD 会丢弃 PHASE_START 本身 UNDO_RANGEHEAD fi fi几个关键设计点窗口上界为 HEAD选择区间是${PHASE_START}^..HEAD杜绝了旧实现的--all无界搜索——不可达分支的提交根本不在窗口内。根提交特判${PHASE_START}..HEAD会排除PHASE_START本身当锚点是根提交时其没有父提交可排除因此必须退化为UNDO_RANGEHEAD否则首个阶段提交会被静默丢弃导致合法的撤销被拒绝。项目根来源统一PHASE_DIR由find-phase返回是相对项目根PROJECT ROOT的路径而 path-scoped 的 git 调用以 git 的 cwd 读取路径。测试专门覆盖了从sub/dir子目录调用的情况测试用例from a subdirectory确保所有PHASE_DIR作用域内的 git 调用都以git -C ${PROJECT_ROOT:-.}从项目根执行。测试文件还验证了窗口边界的两侧行为limit-1用例在PHASE_DIR创建之前提交的同作用域提交feat(03-01): stray pre-phase commit被排除在窗口之外而PHASE_START本身docs(03-01): add phase plan被包含。single-milestone用例单一里程碑场景下阶段内全部提交含docs(03): phase summary、fix(03-02)、feat(03-02)、feat(03-01)被选中后续阶段04-search的提交不被选中。三、三条拒绝路径归档、异仓库与路径复用锚点本身无法区分两类前一里程碑路由因此工作流引入了三条显式拒绝路径全部 fail closed。3.1 归档解析拒绝PHASE_DIR_ARCHIVEDfind-phase的歧义检查不会跨搜索目录它先搜索活跃的phases/目录再按版本升序搜索每个milestones/vX.Y-phases/目录matches.length 1检查位于cmdFindPhasesrc/phase.cts的每次searchDir循环内部。因此一个不再活跃的阶段号会静默解析到拥有该阶段的旧归档里程碑。锚定到那里有两个方向的错误最老的添加该路径的提交是归档移动提交该阶段真正的开发工作早于窗口被排除在外而窗口从归档点向前延伸会匹配到后续里程碑的同号阶段。测试在双归档里程碑 fixture 上复现未加守卫时--phase 03选中了 v2.0 的feat(03-01): add search index却排除了 v1.0 自己的feat(03-01): implement auth endpoint。守卫如下PHASE_DIR_ARCHIVED case ${PHASE_DIR} in */milestones/v[0-9]*-phases/*|milestones/v[0-9]*-phases/*|*/milestones/ws-*/phases/*|milestones/ws-*/phases/*) PHASE_DIR_ARCHIVED${PHASE_DIR}; PHASE_DIR ;; esac注意模式必须匹配归档布局而非裸milestones标记一个名为milestones的 workstream 或项目会被裸*/milestones/*误判为归档而拒绝活跃阶段测试用例a workstream named milestones is still live验证了这一点。这里覆盖了listArchiveVersionDirssrc/phase-locator.cts枚举的两种布局——扁平vX.Y-phases/phase与workstream complete写入的ws-name-date/phases/phase。3.2 异仓库锚点拒绝PHASE_DIR_FOREIGN在sub_repos项目中.planning/位于父仓库而代码在子仓库从子目录调用时PROJECT_ROOT是父目录锚点读到的提交是子仓库从未见过的。守卫比较公共 git 目录对象数据库的物理路径而非 per-worktree 的 git 目录PHASE_DIR_FOREIGN if [ -n ${PHASE_DIR} ] [ -n ${PROJECT_ROOT} ]; then _gd_here$(_d$(git rev-parse --git-common-dir 2/dev/null) [ -n $_d ] cd $_d pwd -P) || _gd_here _gd_root$(cd ${PROJECT_ROOT} 2/dev/null _d$(git rev-parse --git-common-dir 2/dev/null) [ -n $_d ] cd $_d pwd -P) || _gd_root if [ -z $_gd_here ] || [ $_gd_here ! $_gd_root ]; then PHASE_DIR_FOREIGN${PROJECT_ROOT}; PHASE_DIR fi fi使用--git-common-dir而非--absolute-git-dir是刻意的gsd-tools会把无自身.planning/的 linked worktree 映射到主 worktreeresolveMainWorktreeCwd那是同一个仓库、持有 linked worktree 拥有的全部提交若按 per-worktree 比较会误拒合法的撤销。测试覆盖了 linked worktree 在阶段之前/之后分支两种情形。防御纵深即使移除此守卫锚点 fence 自身的merge-base --is-ancestor检查仍会把不在本仓库历史内的PHASE_START置空窗口不会扩大到 HEAD测试用例defense in depth。3.3 路径复用拒绝PHASE_DIR_REUSED一个活跃目录的名字若与已归档里程碑下的目录同名后续里程碑复用编号与 slug重建了完全相同的字面路径--diff-filterA不跟随重命名最老的添加提交属于前一个占用者。归档守卫看不到它——find-phase返回的是活跃目录。解决方式不是布局 glob而是询问 git 历史本身该路径是否曾在HEAD历史中变空又回来PHASE_DIR_REUSED; PHASE_DIR_LIVE if [ -n ${PHASE_DIR} ]; then for _c in $(git -C ${PROJECT_ROOT:-.} log -m --no-renames --diff-filterD --format%H -- ${PHASE_DIR} 2/dev/null); do if [ -z $(git -C ${PROJECT_ROOT:-.} ls-tree -d $_c -- ${PHASE_DIR} 2/dev/null) ]; then PHASE_DIR_LIVE${PHASE_DIR}; PHASE_DIR_REUSED$_c; PHASE_DIR; break fi done fi--no-renames确保移出被解读为该路径上的删除-m合并清空该路径也能被看到ls-tree -d在该提交上检查区分目录消失与普通删除的计划文件。ls-tree -d的语义很重要——阶段内普通删除一个PLAN.md不会触发拒绝测试用例a dropped plan file验证整个阶段仍可选中而目录级清空会。反向控制测试false-positive control确保归档下同名文件、畸形里程碑目录都不会误拒从未被清空的活跃路径。四、选择是结构化解析而非子串 grep上述窗口确定后COMMITS的筛选不再走grep -E而是gsd_run query select-revert-commits子命令src/undo-commit-selection.cts 的selectCommitsByDeclaredScopeCOMMITS$(gsd_run query select-revert-commits --phase ${TARGET_PHASE} --range ${UNDO_RANGE} --raw || true)其核心原理同一份锚定文法复用 scripts/release-notes/conventional-title.cjs 中 changelog/PR-title 门禁用的HEADER_RE /^([a-z])(\([^)]*\))?(!)?:/i通过require()加载该文件位于scripts/在src的rootDir之外且无.d.ts故不能用类型化的import require。绝不 fork 第二份文法副本。精确字符串相等解析每个候选提交主题的(scope)组后与目标 id 做精确字符串比较。任何时刻都不从用户输入构建正则validatePhaseNumber之后即如此见 src/security.cts因此畸形 id 无法造成旧 grep 那样的通配式过匹配。单层解包unwrapSubject剥掉一层fixup!/squash!/Revert ...包装与 git 自身--fixup/--squash/git revert产生的包装形状一致。双层包装Revert fixup! feat(03-01): work解包后仍不可解析、不选中——从不错选这是文档化的已知限制而非缺陷。模式差异phase 模式额外接受targetId-rest前缀作用域阶段内 plan 作用域提交仍选中对应旧 grep 的-NN容忍plan 模式仅精确相等这使feat(03-01)!: breaking change在两种模式下选择一致消除了旧 grep 对的分歧。零填充未填充的3会补零为03再比较对应旧 grep 的0*容忍letter-first 的自定义 id如PROJ-42从不补零避免剥掉有意义的合法前缀。plan id 则按两个 phase 数字形状的分段分别补零3-1→03-01——这是对旧 plan grep 的刻意放宽。该模块刻意不触碰文件系统与子进程它只操作调用方已获取的{sha, subject}记录如git log --format%H%x00%s因此可脱离 git 与文件系统进行单元测试见 tests/undo-commit-selection.test.cjs。五、超过 50 个提交报告截断绝不静默截断两种模式都要求选择超过 50 个提交时显示计数并停止而不是用head -50静默封顶——部分阶段回滚留下的树状态比全部回滚或都不回滚更糟Phase ${TARGET_PHASE} selects ${N} commits (50). Refusing to revert a partial phase. Use /gsd:undo --plan NN-MM per plan, or /gsd:undo --last N.测试L同时锁定拒绝文案与可执行代码中不得出现| head -50两层约束。六、依赖检查的工作流感知dependency_check步骤从工作流解析的规划根读取而非硬编码.planning/PLANNING_DIR$(gsd_run query planning inspect --pick generated_from.planning_root --raw 2/dev/null) [ -n $PLANNING_DIR ] || PLANNING_DIR.planning在活跃 workstream 下该根是.planning/workstreams/ws/而非根项目的.planning/测试断言了这一点因此活跃 workstream 的 ROADMAP 与阶段目录被正确咨询。测试还验证了无.planning时的字面回退路径。七、已知残留与安全方向保证文档明确列出了修复后仍存在的边界全部为安全方向拒绝/少选而非误选/多选修订范围是血缘而非时序PHASE_START^..HEAD排除可达于PHASE_START^的一切长寿命旁支若在阶段之前创建、携带匹配作用域并在PHASE_START之后合入则可达于HEAD却非PHASE_START^的祖先仍可被选中。这严格窄于被替换的无界搜索但非零。重命名的阶段目录少选锚点是--diff-filterA于当前路径且不跟随重命名目录移动后最老的添加是移动提交真实开发提交落在窗口外——撤销过少或拒绝从不过多。合并提交引入的目录解析不到锚点默认git log不遍历合并 diff目录在合并决议中首次出现时PHASE_START为空、两种模式 fail closed。安全方向拒绝而非误选。碰撞检查只读历史单个同时移走并重建的提交无空窗可寻会漏旁支清空而合并保留的路径会被过拒拒绝过多只费一次--last N拒绝过少则回滚他人工作——因此检查以空窗本身为键。并发 workstream 无判别器提交主题契约type({phase}-{plan})无 workstream 标记并发同号阶段主题不可区分引用 #3995Message subjects demonstrably do not carry enough information to identify a phaseconfirm_revert是最终兜底。八、执行与验证git revert --no-commit 永不 reset修复保持了原有的安全执行属性git revert --no-commit逐提交执行逆时间序脏树守卫先行拒绝未提交更改冲突时先git revert --abort再git reset HEAD与git restore .清理最后以单个revert(...)提交收尾。成功标准gsd-core/workflows/undo.md 的success_criteria明确git reset --hard在任何情况下都绝不被使用。对该修复的行为验证集中在 tests/undo-commit-selection-4465.test.cjs该测试分两层——形状断言对部署文本中的 bash fence 做子串断言锁定--all缺席、PHASE_START锚定拼写、UNDO_RANGE边界等与行为执行将undo.md的真实 bash fence 在真实 git fixture 上通过bash -c运行使用真实gsd-tools.cjs与 #2308、#2352 的先例一致。子串匹配无法区分活调用与死调用一次真实运行可以。结语本次修复把/gsd:undo --phase/--plan的提交选择从无界 grep重构为锚定窗口 结构化解析 三层拒绝的模型窗口由阶段自身目录的首次添加提交锚定并以上界 HEAD 收束归档解析、异仓库与路径复用三条路由全部 fail closedselect-revert-commits以精确字符串比较取代正则插值。整体上它严格收窄了旧行为——从不会比无界搜索选得更多——而残留边界全部落在安全方向拒绝或少选从不错选、从不跨里程碑。如需回滚一个已归档或已复用路径的阶段/gsd:undo --last N的显式选择是文档指定的替代路由。赞分享【免费下载链接】gsd-coreGit. Ship. Done - Core项目地址https://gitcode.com/gh_mirrors/ge/gsd-core点击查看免费下载相关推荐jQuery Gridly API 参考从基础配置到高级回调函数全解析jQuery Gridly API 参考从基础配置到高级回调函数全解析 想要为你的网站添加优雅的网格拖放和调整大小功能吗jQuery Gridly 是一个强前端UI库/组件gsd-core UI Safety Gate 误报修复从子串匹配到词边界锚定正则的工程实践gsd core UI Safety Gate 误报修复从子串匹配到词边界锚定正则的工程实践 导读 gsd core Git. Ship. Done — CGSD 多 Agent 并行化策略跨边界并行、边界内串行以及里程碑级并行编排的工程实践GSD 多 Agent 并行化策略跨边界并行、边界内串行以及里程碑级并行编排的工程实践 导读 并行化是让编码 Agent 长时自主工作的关键加速手段但错误人工智能AI Agent代码智能体Agent 编排CLIAI 应用上一篇在Windows 10上运行Android子系统WSA-Windows-10安装指南下一篇Notepad Markdown 实时预览插件MarkdownViewer 新手完整指南5 分钟装好即用创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考