
【免费下载链接】gsd-coreGit. Ship. Done - Core项目地址https://gitcode.com/gh_mirrors/ge/gsd-core点击查看免费下载本篇技术指南以 gsd-core 的 changeset 记录PR #4879关联 issue #4784为核心线索完整剖析 post-merge gate 在真实 Xcode 工程布局下的检测与命令构造逻辑为什么缺少-project会导致任意下一级目录中的工程以 exit 66 失败、为什么硬编码模拟器名称是机器状态而非项目状态、以及修复后如何通过simctl动态解析目标并用可复现的测试锁定行为。读完本文你将掌握该 gate 的完整命令解析链、配置入口与源码级验证方法。1. 背景post-merge gate 为何需要 Xcode 检测在 gsd-core 的 execute-phase 工作流中post_merge_gate后合并门禁是所有工作树worktree合并之后、进入下一波wave之前的一道构建与测试关卡。它的职责在 post-merge-gate.md 开头有明确定义Runs after all worktrees in a wave are merged (parallel mode), or after the last plan completes (serial mode). Catches cross-plan integration failures that individual worktree self-checks cannot detect.也就是说单个执行代理executor在隔离工作树中各自通过 Self-Check 并不足够——合并后共享文件类型定义、导出、导入路径、链接符号的冲突只有在合并完成后整体构建时才会暴露。正是这一点催生了 build gateStep A与 test gateStep B两道闸门。而该 gate 的第一项能力就是没有显式配置时自动探测构建/测试命令。探测顺序为项目配置workflow.build_command/workflow.test_command Xcode 工程 Makefile 语言嗅探。Xcode 分支正是本次 changeset.changeset/gallant-quails-click.md修复的核心区域。2. 缺陷回顾两个真实工程布局下的失败模式原始 changeset 记录将问题归纳为两个相互独立的缺陷二者都在真实工程目录结构下被触发2.1 缺陷一解析出了.xcodeproj路径命令却丢了-projectgate 在探测阶段会用find定位工程文件XCODEPROJ$(find . -maxdepth 2 -name *.xcodeproj -not -path */node_modules/* 2/dev/null | head -1)但修复前构造出的xcodebuild build命令并不携带-project $XCODEPROJ。xcodebuild不会递归搜索子目录因此任何位于仓库下一级目录中的工程典型布局如App/App.xcodeproj都会在启动后立刻以exit 66失败——即xcodebuild找不到工程文件。测试文件 test-gate-watch-mode.test.cjs 在#4784: Xcode gate construction一节中明确记录了这一根因// The gate resolves XCODEPROJ (find . -maxdepth 2) and even uses it for // xcodebuild -list -json -project — but the build/test commands used to // drop it, so any project one directory down (App/App.xcodeproj) failed // with exit 66: xcodebuild does not search subdirectories.2.2 缺陷二硬编码模拟器名称是把机器状态当成了项目状态修复前的 gate 把iPhone 16这样的具体模拟器名称写死在-destination nameiPhone 16里。changeset 的措辞非常精准——这是一个machine property机器属性而不是项目属性the destination resolves from the machines available simulators测试注释给出了实际场景报告者的机器上没有任何匹配硬编码名称的设备模拟器从未被创建过于是xcodebuild的 destination 解析必然失败。测试断言明确要求硬编码模拟器名称必须消失assert.ok( !gate.includes(nameiPhone 16), the hardcoded simulator name must be gone, );3. 修复方案命令携带-project 动态解析模拟器 UDID修复后的 gate 在 post-merge-gate.md 中的完整构造逻辑如下Step A 构建门禁XCODEPROJ$(find . -maxdepth 2 -name *.xcodeproj -not -path */node_modules/* 2/dev/null | head -1) if [ -n $XCODEPROJ ]; then # 1) 从 xcodebuild -list -json 中取第一个 scheme XCODE_SCHEME$(xcodebuild -list -json -project $XCODEPROJ 2/dev/null | python3 -c import sys,json; djson.load(sys.stdin); print(d.get(project,{}).get(schemes,[None])[0] or ) 2/dev/null || true) # 2) 从 simctl 可用设备列表中提取第一个 iOS 模拟器 UDID XCODE_SIM$(xcrun simctl list devices available 2/dev/null | sed -n s/.*(\([A-F0-9-]\{8,\}\)).*/\1/p | head -1) if [ -z $XCODE_SIM ]; then echo ⚠ No available iOS Simulator found — skipping the Xcode build gate. Set workflow.build_command to run it anyway. BUILD_CMD XCODEPROJ else XCODE_DESTid$XCODE_SIM # 单引号转义命令会经 bash -c 重新解析仓库/方案名中的撇号会提前终止引号 XCODEPROJ$(printf %s $XCODEPROJ | sed s//\\\\/g) XCODE_SCHEME$(printf %s $XCODE_SCHEME | sed s//\\\\/g) fi if [ -n $XCODEPROJ ] [ -n $XCODE_SCHEME ]; then BUILD_CMDxcodebuild build -project $XCODEPROJ -scheme $XCODE_SCHEME -destination ${XCODE_DEST} elif [ -n $XCODEPROJ ]; then BUILD_CMDxcodebuild build -project $XCODEPROJ -destination ${XCODE_DEST} fi fi3.1 为什么-project必须显式携带xcodebuild在未指定-project时只会查找当前目录下的.xcodeproj。gate 的find -maxdepth 2允许工程存在于仓库下一级目录如App/但命令本身若不带路径参数xcodebuild 便无从定位。修复后四条命令构造分支build 带/不带 scheme、test 带/不带 scheme全部携带-project $XCODEPROJ测试对此做了穷举断言const constructed gate.match(/xcodebuild (?:build|test)[^\n]*/g) || []; assert.ok(constructed.length 4, ...); for (const cmd of constructed) { assert.ok(cmd.includes(-project $XCODEPROJ), ...); }3.2 模拟器 UDID 提取管线的真实格式考量sed提取管线是本修复中最微妙的一行xcrun simctl list devices available 2/dev/null | sed -n s/.*(\([A-F0-9-]\{8,\}\)).*/\1/p | head -1测试文件明确记录了此处的对抗性审查发现Finding 1第一版实现把 sed 锚定在行尾$但真实simctl输出行以(Shutdown)/(Booted)状态后缀结尾常带尾随空格锚定形式匹配不到任何东西导致 gate 在每台机器上都跳过。修复后的正则取最后一个不带$锚定的、≥8 位十六进制/连字符的括号组——因为状态词永远不属于十六进制字符类所以 UDID 始终是最后一个匹配组即使设备名称自身带括号也不受影响。测试用一条真实格式的 fixture 行验证了该管线的行为const realFormatLine iPhone 17 Pro (94B8198C-8CF1-4860-994A-5669D3388BE8) (Shutdown) ; const udid execFileSync(sh, [-c, printf %s\\n $1 | ${pipeMatch[1]} | head -1, ...]).trim(); assert.match(udid, /^[A-F0-9-]{8,}$/, ...);destination 采用id$XCODE_SIM形式而非name...因为 UDID 对 build 与 test 均有效且与设备名称无关——同一台机器上模拟器叫什么名字不再影响门禁结果。3.3 无模拟器时的响亮跳过当机器上没有任何可用模拟器如纯 CI 容器或从未初始化模拟器的开发机时继续执行xcodebuild是注定失败的命令。修复后的行为是跳过并大声提示且跳过信息分别点名对应的配置项指引用户显式覆盖Step Abuild⚠ No available iOS Simulator found — skipping the Xcode build gate. Set workflow.build_command to run it anyway.Step Btest⚠ No available iOS Simulator found — skipping the Xcode test gate. Set workflow.test_command to run it anyway.同时跳过分支必须把BUILD_CMD/TEST_CMD显式赋值为空字符串——测试专门为set -u健壮性加了断言若 agent shell 启用了未绑定变量报错未赋值的命令变量会导致整个脚本中止assert.ok(/(?:BUILD|TEST)_CMD/.test(arm), each skip arm must assign its command variable (a set -u agent shell must not abort on an unbound variable));4. 超时信息点名 sysdiagnose 收集器一个反直觉的 exit 124本次 changeset 的第三个改动落在 test gate 的超时提示文案上。修复前的超时信息笼统地提示runner 未退出而真实场景远比 watch 模式更隐蔽A real iOS suite measured 854s wall clock, ~600s of it simctl diagnose collecting a sysdiagnose AFTER the suite passed — the default timeout returns 124 on a passing suite.也就是说测试套件本身通过了但xcodebuild默认会在.xcresult中收集一份 sysdiagnose 诊断包通过后仍需约 10 分钟把总耗时拉爆默认超时于是通过中的套件被误报为 exit 124 超时。修复后的超时文案在 post-merge-gate.md 中明确给出两条出路echo ⚠ POST-MERGE TEST GATE TIMED OUT after ${TEST_GATE_TIMEOUT}s — the runner did not exit, likely stuck in watch/dev mode (e.g. vitest without run). Verify tests with a one-shot command (e.g. vitest run) or raise workflow.test_gate_timeout. For Xcode suites: xcodebuild collects a sysdiagnose into the .xcresult by default (~10 minutes on a passing suite) — add -collect-test-diagnostics never to the test command or raise workflow.test_gate_timeout.对应的测试断言锁定必须提及-collect-test-diagnostics neverassert.ok( gate.includes(-collect-test-diagnostics never), the gate must mention -collect-test-diagnostics never for Xcode suites, );4.1 超时语义非阻塞但不沉默test gate 的三分叉处理在 post-merge-gate.md 中定义TEST_EXIT判定处理0通过✓ Post-merge test gate passed — no cross-plan conflicts继续追踪更新124超时打印明确原因watch 模式或 sysdiagnose非阻塞长套件可调大workflow.test_gate_timeout但绝不静默忽略非 0失败递增WAVE_FAILURE_COUNT跨波累计呈现Fix now / Continue选项需要注意Step A 的 5 分钟 build 超时是硬编码的run-with-timeout 300而 Step B 的 test 超时来自配置workflow.test_gate_timeout默认600秒。5. 配置入口三个可覆盖项Xcode 自动探测只是默认路径。要完全掌控 gate 行为可在workflow配置节覆盖三个键详见 CONFIGURATION.md 第 543、560、561 行配置键类型默认值语义workflow.build_commandstring无Step A 的显式构建命令未设置时自动探测Xcode.xcodeproj→xcodebuild build、Makefile的build:→make build、Justfile →just build、Cargo.toml→cargo build、go.mod→go build ./...、Python →python -m py_compile、package.json的build脚本 →npm run buildworkflow.test_commandstring无Step B 的显式测试命令未设置时自动探测Xcode →xcodebuild test、Makefile的test:→make test、Justfile →just test、package.json→npm test、Cargo.toml→cargo test、go.mod→go test ./...、Python →python -m pytestworkflow.test_gate_timeoutnumber600test gate 的墙钟超时秒watch 模式 runnervitest/jest永不退出时在此预算后中止而非无限挂起一个关键实现细节gate 读取这两个命令键时必须携带--raw。这是另一个缺陷类#2350的教训——config-get KEY --default 不加--raw时未设置键会打印 JSON 编码的空字符串两字节的[ -z $CMD ]守卫会看到非空字符串从而跳过整个自动探测级联并把字面量当作命令执行报出 exit 127 的假失败。测试#2350: every gate resolves build/test commands with --raw对三个 gate 文件post-merge、regression-run、audit-fix做了全面扫描确保没有一行config-get遗漏--raw。6. 与 watch 模式防护的联动normalize-test-command虽然本次 changeset 聚焦 Xcode但 test gate 的另一半防护——watch 模式 runner——与 Xcode 分支同处一个 gate 文件且共享同一个兜底超时。门禁把解析出的测试命令先送入共享规范化助手 normalize-test-command.cts单一事实来源regression gate、post-merge gate、audit-fix gate 三条路径共用TEST_CMD$(gsd_run query normalize-test-command $TEST_CMD --cwd . 2/dev/null || echo $TEST_CMD) TEST_GATE_TIMEOUT$(gsd_run query config-get workflow.test_gate_timeout --raw 2/dev/null || echo 600) gsd_run run-with-timeout $TEST_GATE_TIMEOUT -- bash -c $TEST_CMD 21规范化策略是保守的一次性one-shot改写三原则绝不重复加 flag——已是单次执行的命令原样返回识别标记包括vitest run、--run、--no-watch、--watchAllfalse、--ci、CI前缀只碰确认是 watch runner 的命令——vitest作为独立 token避免误伤vitest.config.js、显式--watch/--watchAll的jest无法分类的原样返回——兜底由墙钟timeout保证例如项目test脚本里写死的--watch连CI1都覆盖不了。对包管理器脚本调用npm test/pnpm run test等它会解析目标目录的package.json中scripts.test若解析出 vitest默认 watch或显式 watch 的 jest则前缀CItrue强制进入非交互模式。从源码结构看该助手还内置了安全边界输入长度上限 4096 字符、线性时间扫描、package.json仅以普通文件身份读取保证规范化本身不可能在对抗性输入上挂起或超线性耗时。7. changeset 机制本身一次修复如何进入 CHANGELOG值得顺带说明的是本文所依据的文档.changeset/gallant-quails-click.md本身就是 gsd-core 变更管理机制的一部分。.changeset/目录存放每 PR 一条的 CHANGELOG 片段格式约定见 .changeset/README.mdnode scripts/changeset/new.cjs \ --type Fixed \ --pr 1234 \ --body fix the thing — explain the user-visible change in one sentence每条片段由形容词-名词-名词三段随机词命名如gallant-quails-click并发 PR 不会碰撞frontmatter 声明type遵循 Keep a ChangelogAdded/Changed/Deprecated/Removed/Fixed/Security与pr编号发布时由 release 工作流的finalize任务自动执行node scripts/changeset/cli.cjs render --version vX.Y.Z --date YYYY-MM-DD按type分组合入顶层CHANGELOG.md替换## [Unreleased]并删除已消费片段已发布版本≤ 1.3.1的片段归档在.changeset/archived/仅供溯源所有 changeset 工具非递归枚举不会重复渲染。这种每 PR 独占一行的设计动机很实际两个 PR 同时编辑CHANGELOG.md的### Fixed块必然合并冲突而各自新增一个独立片段文件则永不冲突。8. 验证体系测试如何锁定这次修复本次改动不是修完即走而是由 test-gate-watch-mode.test.cjs 中#4784: Xcode gate construction一节的五条断言完整锁定形成回归防线四条命令构造全部携带-project $XCODEPROJ——防止任何一条构造分支回退到丢路径的旧行为destination 从simctl list devices available派生且断言硬编码nameiPhone 16已从 gate 文本中消失两条跳过分支build/test分别点名workflow.build_command/workflow.test_command——任何删掉跳过提示的编辑都会变红UDID 提取管线对真实格式 fixture 行的行为测试——用 iPhone 17 Pro (94B8198C-8CF1-4860-994A-5669D3388BE8) (Shutdown) 实测 sed 管线断言输出符合^[A-F0-9-]{8,}$超时文案必须提及-collect-test-diagnostics never——防止 sysdiagnose 教训被从提示中删掉。同时#1857一节的既有测试继续保证 gate 调用normalize-test-command、读取workflow.test_gate_timeout、以run-with-timeout $TEST_GATE_TIMEOUT包裹执行、并以 exit 124 watch/dev 提示呈现超时——三条 gate 路径post-merge、regression-run、audit-fix无一例外。9. 实践建议结合本次修复与相关缺陷的教训在真实工程中使用 post-merge gate 的 Xcode 分支时可遵循以下 checklist工程不在仓库根目录时无需任何配置find -maxdepth 2 显式-project即可覆盖App/App.xcodeproj这类布局模拟器未创建/CI 环境无模拟器gate 会响亮跳过而非假失败若仍需构建显式设置workflow.build_command如xcodebuild build -project App/App.xcodeproj -scheme App -destination platformiOS Simulator,name你的设备绕过自动跳过iOS 套件频繁 exit 124 且日志显示套件本身通过优先在workflow.test_command中加入-collect-test-diagnostics never关闭 sysdiagnose 收集其次才是调大workflow.test_gate_timeout任何自定义测试命令保持一次性one-shot形态vitest run/jest --ci并确认config-get调用携带--raw避免空默认值被当成可执行命令的假失败。从源码结构看这条 gate 是 gsd-core 中自动化门禁必须可配置、可跳过、可诊断、可回归设计哲学的一个缩影每一个失败模式exit 66、硬编码设备名、sysdiagnose 超时、watch 挂起、空字符串命令都对应一段显式的处理分支与一条对应的测试断言确保修复不会在后续迭代中悄悄回退。赞分享【免费下载链接】gsd-coreGit. Ship. Done - Core项目地址https://gitcode.com/gh_mirrors/ge/gsd-core点击查看免费下载相关推荐gsd-core 修复 W006/W007 漂移validate.ts 与 verify.cjs 的生成器模式gen-validate.mjs深度解析gsd core 修复 W006/W007 漂移validate.ts 与 verify.cjs 的生成器模式gen validate.mjs深度解析 导gsd-core 修复实践codebase-drift-gate 在 shim-only 安装下通过 gsd_run 正确解析 gsd-toolsgsd core 修复实践codebase drift gate 在 shim only 安装下通过 gsd_run 正确解析 gsd tools 导读 本文GSD Core 修复 Codex SessionStart Hook 裸 node 命令从 exit 127 到绝对路径 runnerGSD Core 修复 Codex SessionStart Hook 裸 node 命令从 exit 127 到绝对路径 runner 导读 本篇文章聚焦上一篇Cangaroo开源CAN总线分析软件完整使用教程下一篇Android ProGuard Snippets一站式解决Android应用混淆配置难题创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考