From 2798a32780298e312bc00ab4320a26f853451c98 Mon Sep 17 00:00:00 2001 From: liboze2026 Date: Fri, 15 May 2026 15:44:13 +0800 Subject: [PATCH 1/4] =?UTF-8?q?fix(test):=20codex-hooks-config=20=E5=9C=A8?= =?UTF-8?q?=20.codex/=20=E4=B8=8D=E5=AD=98=E5=9C=A8=E6=97=B6=E8=87=AA?= =?UTF-8?q?=E9=80=82=E5=BA=94=E8=B7=B3=E8=BF=87=20(issue=20#3=20Gap=202)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR #4 dogfood 后跑 Ubuntu CI 的 pnpm test 报 7 个 fail,全在 packages/cli/src/__tests__/codex-hooks-config.test.ts 一个文件里, 错误一致: Error: ENOENT: no such file or directory, open '/home/runner/work/Matrix/Matrix/.codex/hooks.json' 根因:这套测试来自 TeamBrain(issue #290 Codex CLI hook adapter), Matrix baseline(2026-05-14 history-stripped fork)没把 .codex/ 目录 带过来。target 文件不存在 → ENOENT。 按内存 [Matrix is independent]:Matrix 自走、不反向 merge TeamBrain, 不该把 .codex/ 拉回来。改用 vitest describe.skipIf: const codexHooksExist = existsSync(hooksJsonPath); describe.skipIf(!codexHooksExist)('.codex/hooks.json ...', () => { ... }) 行为: - Matrix(.codex/ 不存在) → 整个 describe block 跳过,7 tests skipped - 上游 TeamBrain(.codex/ 存在) → 测试照常激活 - 以后 Matrix 决定接 Codex hooks → 自动重新生效,无须改测试 本地验证(`vitest run` 单文件): Test Files: 1 skipped Tests: 7 skipped (7) 至此 issue #3 Gap 2 收尾。理论上下一次 pr-review.yml 跑应该: - repro-pass: pass(豁免,PR #4 修) - tests-and-merge-clean: pass(0 failed) → verdict: pass → can-auto-merge: 全部门控过 → 自动 squash merge 这将是仓库第一次真全自动合 PR。 Co-Authored-By: Claude Opus 4.7 (1M context) --- packages/cli/src/__tests__/codex-hooks-config.test.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/cli/src/__tests__/codex-hooks-config.test.ts b/packages/cli/src/__tests__/codex-hooks-config.test.ts index 6fbb525..aa71b70 100644 --- a/packages/cli/src/__tests__/codex-hooks-config.test.ts +++ b/packages/cli/src/__tests__/codex-hooks-config.test.ts @@ -38,6 +38,15 @@ function findRepoRoot(): string { const repoRoot = findRepoRoot(); const hooksJsonPath = path.join(repoRoot, ".codex", "hooks.json"); +// Matrix baseline 是 history-stripped fork(2026-05-14),没把 TeamBrain 的 +// .codex/ 目录(Codex CLI hook adapter,issue #290 范围)带过来。本测试套件 +// 来自上游,但 target 文件不存在,在 Matrix 上跑会 7 个全挂 ENOENT。 +// +// 用 describe.skipIf 自适应:.codex/hooks.json 存在 → 测试激活(TeamBrain 路径); +// 不存在 → 整个 describe block 跳过(Matrix 路径)。无须 Matrix 维护这套 fixture, +// 也无须删测试丢上游 contract 文档。issue #3 Gap 2 收尾。 +const codexHooksExist = existsSync(hooksJsonPath); + interface CodexHookEntry { matcher?: string; hooks: Array<{ type: string; command: string; timeout?: number }>; @@ -50,7 +59,7 @@ function loadConfig(): CodexHooksConfig { return JSON.parse(readFileSync(hooksJsonPath, "utf8")) as CodexHooksConfig; } -describe(".codex/hooks.json (issue #290)", () => { +describe.skipIf(!codexHooksExist)(".codex/hooks.json (issue #290)", () => { it("registers SessionStart, PreToolUse, and Stop events", () => { const cfg = loadConfig(); expect(Object.keys(cfg.hooks).sort()).toEqual([ From 1aa98e3793a1a3c357fbc5a2ec96b5bda343fd5a Mon Sep 17 00:00:00 2001 From: liboze2026 Date: Fri, 15 May 2026 16:11:17 +0800 Subject: [PATCH 2/4] =?UTF-8?q?fix(gitignore):=20ignore=20.claude/hooks/ch?= =?UTF-8?q?eck-teamagent.sh=20=E2=80=94=E2=80=94=20teamagent=20init=20?= =?UTF-8?q?=E6=B5=81=E7=A8=8B=E4=BA=A7=E7=89=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR #7(Gap 2)dogfood 第一次跑暴露第二个 sub-gap: codex-hooks-config 的 skipIf 修复让 pnpm test 真过(3526 / 56 skip / 0 fail),但 tests job 仍 failure。根因是后一个 step「Working tree must be clean」检测到 untracked 文件: ##[error]tests 跑完后工作树不干净: ?? .claude/hooks/check-teamagent.sh `teamagent init` 是 pnpm test 链路里跑的(测试产物之一)。它生成: - .teamagent/required.json(已被 .teamagent/ ignore) - .claude/hooks/check-teamagent.sh(漏了) 两个是同一 "Required 模式产物" pipeline 的输出,语义一致 → 加进 .gitignore。 至此 PR #7 应能让 tests-and-merge-clean 真翻绿,verdict=pass, 触发首次完全自动 squash-merge。 Co-Authored-By: Claude Opus 4.7 (1M context) --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index acd15b9..50f907e 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,9 @@ coverage/ *.tsbuildinfo packages/*/.teamagent/ .teamagent/ +# Required-mode hook generated by `teamagent init`(与 .teamagent/required.json 同流程, +# 同样应忽略;之前漏了让 CI 的「Working tree must be clean」检查炸。issue #3 Gap 2 dogfood 揭示。) +.claude/hooks/check-teamagent.sh .worktrees/ .claude/worktrees/ .codex/worktrees/ From 49ba4a0bdfc1dbf32f3720ae82eb30b76f4ad890 Mon Sep 17 00:00:00 2001 From: liboze2026 Date: Fri, 15 May 2026 16:22:54 +0800 Subject: [PATCH 3/4] =?UTF-8?q?fix(review):=20post-pr-comment=20=E5=88=97?= =?UTF-8?q?=E8=AF=84=E8=AE=BA=E4=B9=9F=E8=B5=B0=20REST=20API=20=E2=80=94?= =?UTF-8?q?=E2=80=94=20=E4=BF=AE=20GraphQL/REST=20id=20=E6=B7=B7=E7=94=A8?= =?UTF-8?q?=20404?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR #7 v2 dogfood 第三个 sub-gap:tests + tree-clean 都修过后,verdict job 在 'Post comment + set status' step 仍崩: Error: update comment 失败: gh: Not Found (HTTP 404) 根因(本 session PR #2 引入的 post-pr-comment.ts bug): - line 71: `gh pr view --json comments` 返回 GraphQL node ID(IC_kwDOI...) - line 81: `gh api repos/.../issues/comments/{id}` PATCH 要 REST integer id - 不匹配 → PATCH 404 为什么 PR #2 / #4 没炸:每个 PR 只跑过一次 verdict → 没现成评论可更新 → 只走 CREATE 路径(line 87 `gh pr comment`)→ 没暴露。PR #7 是本仓库 第一个 multi-commit PR:v1 (2798a32) 创了评论 → v2 (1aa98e3) 想更新 → 404。 修复: - 列评论改用 `gh api repos/.../issues/{pr}/comments` —— 返回 REST id, 跟 PATCH 端点格式一致 - CREATE 也改走 REST POST(对称;新评论的 id 直接是 REST 制式,下次能 正确 UPDATE) 这是 dogfood 的核心价值 —— 第一次真有 multi-commit PR 跑流水线就揪出了 首次 ship 时遗漏的 code path bug。 Co-Authored-By: Claude Opus 4.7 (1M context) --- scripts/review/post-pr-comment.ts | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/scripts/review/post-pr-comment.ts b/scripts/review/post-pr-comment.ts index fc62e07..fc1429a 100644 --- a/scripts/review/post-pr-comment.ts +++ b/scripts/review/post-pr-comment.ts @@ -67,16 +67,19 @@ function parseArgv(argv: string[]): CliOpts { } function postOrUpdateComment(pr: number, body: string): void { - // 找现有的 marker 评论 - const list = spawnSync("gh", ["pr", "view", String(pr), "--json", "comments"], { encoding: "utf-8" }); - if (list.status !== 0) throw new Error(`gh pr view 失败: ${list.stderr}`); - const parsed = JSON.parse(list.stdout) as { comments?: Array<{ id?: string; body?: string }> }; - const comments = parsed.comments ?? []; + const repo = process.env["GITHUB_REPOSITORY"]; + if (!repo) throw new Error("GITHUB_REPOSITORY env 未设"); + // 一定要走 REST API 列评论 —— `gh pr view --json comments` 返回的是 GraphQL + // node id (IC_kwDOI...),不能喂给 `gh api /repos/.../issues/comments/{id}` + // 的 PATCH(那个要 REST integer id),否则 404。本 session PR #7 v2 翻车 + // 揭示的 bug;PR #2/#4 每个 PR 只跑过一次 → 只走 CREATE 路径 → 没暴露。 + const list = spawnSync("gh", [ + "api", `repos/${repo}/issues/${pr}/comments?per_page=100`, + ], { encoding: "utf-8" }); + if (list.status !== 0) throw new Error(`list comments 失败: ${list.stderr}`); + const comments = JSON.parse(list.stdout) as Array<{ id?: number; body?: string }>; const existing = comments.find((c) => (c.body ?? "").startsWith(COMMENT_MARKER)); - if (existing && existing.id) { - // gh 不直接支持 update comment,用 API - const repo = process.env["GITHUB_REPOSITORY"]; - if (!repo) throw new Error("GITHUB_REPOSITORY env 未设"); + if (existing && existing.id !== undefined) { const r = spawnSync("gh", [ "api", "--method", "PATCH", `repos/${repo}/issues/comments/${existing.id}`, @@ -84,8 +87,13 @@ function postOrUpdateComment(pr: number, body: string): void { ], { encoding: "utf-8" }); if (r.status !== 0) throw new Error(`update comment 失败: ${r.stderr}`); } else { - const r = spawnSync("gh", ["pr", "comment", String(pr), "--body", body], { encoding: "utf-8" }); - if (r.status !== 0) throw new Error(`gh pr comment 失败: ${r.stderr}`); + // 也走 REST API 保持对称(REST ids → 下次能正确 UPDATE)。 + const r = spawnSync("gh", [ + "api", "--method", "POST", + `repos/${repo}/issues/${pr}/comments`, + "-f", `body=${body}`, + ], { encoding: "utf-8" }); + if (r.status !== 0) throw new Error(`create comment 失败: ${r.stderr}`); } } From 8d292a746d816db3d2de847ba258d8f07c5e3322 Mon Sep 17 00:00:00 2001 From: liboze2026 Date: Fri, 15 May 2026 16:50:27 +0800 Subject: [PATCH 4/4] =?UTF-8?q?fix(automerge):=20=E5=8A=A0=20pnpm/action-s?= =?UTF-8?q?etup=20=E5=9C=A8=20setup-node=20=E4=B9=8B=E5=89=8D=20=E2=80=94?= =?UTF-8?q?=E2=80=94=20=E4=BF=AE=204th=20sub-gap?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR #7 v3 dogfood 全部 review 翻绿后,auto-merge.yml workflow 真触发 (workflow_run on PR Review success),但本身崩在 setup-node 那步: ##[error]Unable to locate executable file: pnpm. 根因:actions/setup-node@v5 默认 package-manager-cache=true,看到 pnpm-lock.yaml 就要找 pnpm 来计算 cache 目录;auto-merge.yml 没在 setup-node 之前先 pnpm/action-setup → setup-node 找不到 pnpm 报错。 setup-repo composite action 顺序是对的(pnpm 先,node 后);auto-merge.yml 没复用 composite,自己 inline 两行,顺序错了。 修复:加 pnpm/action-setup@v5 在 setup-node@v5 之前,并显式 cache: pnpm。 至此 PR #7 应能真触发 auto-merge.yml + 真合 main。这条路径要等本 commit 进 main 之后下一个 PR 才能真测。本 PR(#7)自己的 auto-merge 还会用 main 上现有的(broken)auto-merge.yml,所以 PR #7 仍要 human-merge 一次。 链式 dogfood 揪 bug 数累计:4 个。所有 4 个都是本 session 之前 ship 时未被多 commit / 第一个真 SHA 路径覆盖到的盲区。 Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/auto-merge.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/auto-merge.yml b/.github/workflows/auto-merge.yml index d7cf5d8..9f6470b 100644 --- a/.github/workflows/auto-merge.yml +++ b/.github/workflows/auto-merge.yml @@ -28,9 +28,15 @@ jobs: with: ref: ${{ github.event.workflow_run.head_sha }} fetch-depth: 0 + # setup-node@v5 默认 package-manager-cache=true,看到 pnpm-lock.yaml 就找 + # pnpm 来确定 cache 目录;所以 pnpm/action-setup 必须先于 setup-node。 + # PR #7 dogfood 揭示的第 4 个 bug(无 pnpm → setup-node 报 'Unable to + # locate executable file: pnpm')。 + - uses: pnpm/action-setup@v5 - uses: actions/setup-node@v5 with: node-version: '22' + cache: pnpm - name: Resolve PR number from workflow_run id: pr