From 8406e11346e198d61239a87c235273cfac67c07a Mon Sep 17 00:00:00 2001 From: "Ragdoll-Opus-4.7" Date: Sun, 28 Jun 2026 22:54:29 +0800 Subject: [PATCH 1/9] chore(governance): add PR fork base rails (LL-080) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 2026-06-26 PR #1030 误提到 zts212653/clowder-ai (fork parent) — gh CLI defaults to parent for forks when --repo is omitted. Closed 10 min later and rebuilt as PR #30 on clowder-labs. co-creator "又" = same pattern recurred (prior thread_mpz55rv7dahgk4ce discussed fork workflow but no hard rule landed). This commit installs four layered rails so the trap can't recur on fresh clone / new worktree / re-auth / hand-typed command paths. What changes: - cat-cafe-skills/merge-gate/SKILL.md: SOP command now explicit `--repo clowder-labs/clowder-ai --base main` + fork-trap comment - .envrc: GH_REPO backstop for hand-typed gh commands - .claude/hooks/user-level/session-start-recall.sh: conditional fork warning + GH_REPO status (only fires inside clowder-ai clones — does not pollute other projects) - docs/public-lessons.md: LL-080 records the 3-layer root cause, 4-layer rails, and 1 known TD (git-worktree-publisher.ts has same trap, deferred) Already done outside this PR: - `git remote rename upstream zts-mirror` (executed locally — kills the cognitive trap; 7 tracking branches + 14 worktrees auto-migrated) Out of scope: - CONTRIBUTING.md L54 `--repo zts212653/clowder-ai` is correct (external contributor SOP for the public fork) — not changed - sync-upstream-main.yml already uses `--repo "$GITHUB_REPOSITORY"` — not changed Co-Authored-By: Claude Opus 4.7 --- .../hooks/user-level/session-start-recall.sh | 19 ++++++++++++++ .envrc | 20 ++++++++++++++ cat-cafe-skills/merge-gate/SKILL.md | 9 ++++++- docs/public-lessons.md | 26 +++++++++++++++++++ 4 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 .envrc diff --git a/.claude/hooks/user-level/session-start-recall.sh b/.claude/hooks/user-level/session-start-recall.sh index 319db602e5..a6f46feb38 100755 --- a/.claude/hooks/user-level/session-start-recall.sh +++ b/.claude/hooks/user-level/session-start-recall.sh @@ -89,6 +89,25 @@ ${ROOT_CLUTTER} " fi +# 7. Fork base trap 检测(LL-080) +# 只在 clowder-labs/clowder-ai 这种 GitHub fork 仓里显示,跨项目时静默 +ORIGIN_URL=$(git config --get remote.origin.url 2>/dev/null) +if [[ "$ORIGIN_URL" == *"clowder-labs/clowder-ai"* ]]; then + # 检测 GH_REPO 兜底是否生效 + if [ "$GH_REPO" = "clowder-labs/clowder-ai" ]; then + GH_REPO_STATUS="GH_REPO 兜底已生效" + elif [ -z "$GH_REPO" ]; then + GH_REPO_STATUS="GH_REPO 未设——建议 \`source .envrc\` 或装 direnv(参考 .envrc)" + else + GH_REPO_STATUS="⚠️ GH_REPO=$GH_REPO(不是 clowder-labs/clowder-ai,可能误导 gh CLI)" + fi + WARNINGS="${WARNINGS} +⚠️ 本仓在 GitHub 是 zts212653/clowder-ai 的 fork(LL-080) +→ \`gh pr create\` 必须显式 \`--repo clowder-labs/clowder-ai --base main\`,否则默认提到 parent (zts212653) +→ ${GH_REPO_STATUS} +" +fi + # 输出提醒(只在有警告时才输出) if [ -n "$WARNINGS" ]; then echo "🐾 开工自检:${WARNINGS}" diff --git a/.envrc b/.envrc new file mode 100644 index 0000000000..7316edbc40 --- /dev/null +++ b/.envrc @@ -0,0 +1,20 @@ +# ============================================================ +# Cat Cafe / Clowder AI — Shell environment(手敲命令时兜底) +# ============================================================ +# +# 这个文件的作用:补 §SOP 之外的"手敲命令"场景。 +# 详见 LL-080(PR Fork Base Trap)+ cat-cafe-skills/merge-gate/SKILL.md。 +# +# 用法(按你的环境二选一): +# 1. direnv(推荐):`brew install direnv` + 在 shell rc 加 `eval "$(direnv hook zsh)"` +# + 在本仓首次运行 `direnv allow` —— 进入本仓时自动 export +# 2. 手动:在 cat agent 启动前跑 `source .envrc`(不持久但单次 shell 生效) +# +# 注:cat-cafe-skills 里的 SOP 命令已经显式 `--repo clowder-labs/clowder-ai`, +# 不依赖 GH_REPO;这里是给"绕开 SOP、直接敲 gh 命令"的猫兜底用。 +# ============================================================ + +# Fork base trap 兜底:本仓在 GitHub 是 zts212653/clowder-ai 的 fork, +# 不显式 --repo / GH_REPO 时,gh CLI 会默认把 PR / issue 提到 parent。 +# 历史事故:PR #1030 (2026-06-26) 误提到 zts,10 分钟后 close + 重建 PR #30。 +export GH_REPO="clowder-labs/clowder-ai" diff --git a/cat-cafe-skills/merge-gate/SKILL.md b/cat-cafe-skills/merge-gate/SKILL.md index aa1cdc5d68..de01a91621 100644 --- a/cat-cafe-skills/merge-gate/SKILL.md +++ b/cat-cafe-skills/merge-gate/SKILL.md @@ -198,7 +198,14 @@ fi git push origin {branch} # 2. 开 PR(读 refs/pr-template.md 获取 body 模板,用 HEREDOC 填写) -gh pr create --title "feat(xxx): ..." --body "$(cat <<'EOF' +# +# ⚠️ Fork base trap(LL-080):本仓 clowder-labs/clowder-ai 在 GitHub 后台是 +# zts212653/clowder-ai 的 fork。`gh pr create` 不带 --repo 时,gh CLI 在 +# fresh clone / 未写 `gh-resolved` 的 worktree 上会**默认把 PR 提到 parent +# (zts212653)**——必须显式 --repo clowder-labs/clowder-ai --base main。 +# 历史事故:PR #1030 (2026-06-26) 误提到 zts,10 分钟后 close + 重建 PR #30。 +gh pr create --repo clowder-labs/clowder-ai --base main \ + --title "feat(xxx): ..." --body "$(cat <<'EOF' ... 按 refs/pr-template.md 模板填写 ... EOF )" diff --git a/docs/public-lessons.md b/docs/public-lessons.md index c43dc08642..09467c8403 100644 --- a/docs/public-lessons.md +++ b/docs/public-lessons.md @@ -1768,3 +1768,29 @@ created: 2026-02-26 - 原理:Spec 是 "deployable state declaration", 不是 "future intent declaration"。预先 batch 写 future PR 的 normative 字段, 等于 declare 不存在的 dist——sanity check 会真炸。边界混淆的代价是 implementer 在 PR 内反复踩 self-inflict 雷。同源于 R1 .mjs-only over-correction (把 scope decision 提前 batch 拍板) 也同源于 LL-087 plan-time invariant 思路 (declaration 也是 invariant 的一种, 必须 trace 真实 state)。 - 关联:F243 Phase B (active) | LL-087 plan-time invariant table 同源 (declaration = invariant 的一种) | feedback_xiaci_yiding_self_diagnosis (糖衣话术"未来一次写完"=包装当下偷懒) | feedback_grep_consumers_before_contract_change (改 contract 前 grep 全消费方; 这里 dual——declare contract 前看每个 PR 的 actual deployable state) + +--- + +### LL-080: PR fork base trap — `gh pr create` 在 fork 仓默认提到 parent +- 状态:validated +- 更新时间:2026-06-28 + +- 坑:本仓 `clowder-labs/clowder-ai` 在 GitHub 后台是 `zts212653/clowder-ai` 的 fork。`gh pr create` 不带 `--repo` 时,gh CLI **默认把 PR 提到 parent (zts212653)**——这是 gh CLI 公开行为,不是 bug。2026-06-26 实证:PR #1030 误提到 zts,10 分钟后 close + 重建 PR #30 到 clowder-labs。co-creator:"又"——同型陷阱以前讨论过 fork workflow (thread_mpz55rv7dahgk4ce) 但没沉淀成硬规则。 +- 根因:三层叠加。(1) **机制层**:GitHub fork relationship 是 gh CLI 后台事实,本地配置不能改变。(2) **认知层**:本地 remote 名字曾叫 `upstream`,看名字像"正确目标"——但 zts212653 是 fork 父,不是上游。(3) **SOP 层**:`cat-cafe-skills/merge-gate/SKILL.md` 旧版命令裸 `gh pr create` 没 `--repo`,照抄即踩。 +- 触发条件:(a) Fresh clone / 新机器 / CI sandbox / teleport 后 `gh-resolved` git config 未写入;(b) cat 照旧 SOP 抄裸命令;(c) cat 看到 remote `upstream` 误判语义;(d) `gh auth` 重登录 / gh CLI 升级清掉 `gh-resolved` 让旧 clone 退化成 fresh clone 行为。 +- 修复:PR #1030 close + 重建 PR #30;本地 `git remote rename upstream zts-mirror`(rename 自动迁移 7 个 tracking 分支 + 14 个 worktree 同步);SOP 显式 `--repo`;写 `.envrc` 兜底 `GH_REPO`;SessionStart hook 加 fork warning。 +- 防护:四层硬约束 + 一层认知。 + 1. **L1 SOP(核心)**:`cat-cafe-skills/merge-gate/SKILL.md` 命令显式 `gh pr create --repo clowder-labs/clowder-ai --base main` + 旁注 fork trap 出处。猫照 SOP 抄 = 自动安全 + 2. **L2 命名(认知)**:本地 remote `upstream` → `zts-mirror`(rename 消除名字诱导;`pushurl=DISABLED` + `pushdefault=origin` 保留) + 3. **L3 环境(兜底)**:`.envrc` 设 `export GH_REPO=clowder-labs/clowder-ai`,cat 手敲命令绕开 SOP 时兜底(家里未默认装 direnv,但文件作为 source-of-truth + 单次手动 `source .envrc` 可用) + 4. **L4 自检(提醒)**:`.claude/hooks/user-level/session-start-recall.sh` 检测当前仓 origin URL 含 `clowder-labs/clowder-ai` 时,开工自检显示 fork warning + GH_REPO 状态 + 5. **(认知)**:明白 GitHub fork relationship 是不可改的后台事实,本地任何配置都只能在 invocation 上强制覆盖默认值——不能"修复"fork 状态本身 +- 来源锚点: + - `cat-cafe-skills/merge-gate/SKILL.md#L128`(修复后 SOP) + - PR #30(重建到 clowder-labs)/ PR #1030 (zts212653,已 CLOSED) — 2026-06-26 事故 + - thread_mpz55rv7dahgk4ce(2026 早期讨论 fork workflow 但未沉淀硬规则) + - `.envrc` / `.claude/hooks/user-level/session-start-recall.sh`(防护实现) +- 原理:fork relationship 是 GitHub 后台的**固定结构事实**——`fork=true, parent=zts212653/clowder-ai` 写在 GitHub API。本地任何 git config (`gh-resolved`、`pushdefault`、`pushurl=DISABLED`) 都是 **per-clone 的覆盖层**,覆盖层会因为 fresh clone / re-auth 失效。**只有"每次 invocation 显式 `--repo`"才是不依赖任何 per-clone state 的硬约束**。命名陷阱 (`upstream`) 是认知层的"错觉地基"——rename 消除地基,而不是治理症状。 +- 已知 TD(后续治理):`packages/api/src/infrastructure/harness-eval/publish-verdict/git-worktree-publisher.ts:138` F192 verdict publisher 同型——`gh pr create` 不带 `--repo`,fresh deploy 上踩坑。窄场景(API server fresh deploy 时),独立 issue 跟进。 + +- 关联:cat-cafe-skills/merge-gate/SKILL.md / .envrc / .claude/hooks/user-level/session-start-recall.sh / PR #30 / PR #1030 / thread_mpz55rv7dahgk4ce From 9edc193db2c6a197de1e4edb2b031a2ee91f0e87 Mon Sep 17 00:00:00 2001 From: "Ragdoll-Opus-4.7" Date: Sun, 28 Jun 2026 23:05:32 +0800 Subject: [PATCH 2/9] =?UTF-8?q?fix(#37):=20address=20=E7=A0=9A=E7=A0=9A=20?= =?UTF-8?q?P2/P3=20+=20cloud=20P1=20=E2=80=94=20exact=20match=20+=20${GH?= =?UTF-8?q?=5FREPO}=20+=20full-SOP=20--repo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 砚砚 P2: hook substring match误伤 clowder-labs/clowder-ai-docs 前缀仓 → normalize origin URL to owner/repo (sed strip protocol/host + optional .git), then exact `==` compare. Verified: docs prefix repo no longer false-positives. 砚砚 P3: $GH_REPO 紧贴全角中文括号被 bash locale 解析坏 (值显示为 ��) → ${GH_REPO} 显式包裹 + 改用半角括号包装错误值说明。Verified: GH_REPO=zts212653/clowder-ai 完整显示。 Cloud P1 (codex-connector inline at SKILL.md:135): pin --repo on ALL gh PR/api commands → SOP 顶部加 "Fork base trap 总规则" 块覆盖整节命令;逐行加 --repo clowder-labs/clowder-ai 到 gh pr view/comment/merge/checks/edit/create + gh api repos/ (13 处);硬编码而非 REPO 变量,让每段命令 self-contained,cat 抄哪段都安全。 LL-080 updated: L1 防护栏明确覆盖"所有 PR-number 命令",不只 create。 Verified 3 scenarios: - A: clowder-labs/clowder-ai → fork warning shown ✅ - B: clowder-labs/clowder-ai-docs → no false positive ✅ - C: GH_REPO=zts212653/clowder-ai → value preserved ✅ Co-Authored-By: Claude Opus 4.7 --- .../hooks/user-level/session-start-recall.sh | 14 +++--- cat-cafe-skills/merge-gate/SKILL.md | 43 ++++++++++--------- docs/public-lessons.md | 2 +- 3 files changed, 33 insertions(+), 26 deletions(-) diff --git a/.claude/hooks/user-level/session-start-recall.sh b/.claude/hooks/user-level/session-start-recall.sh index a6f46feb38..998d1fac65 100755 --- a/.claude/hooks/user-level/session-start-recall.sh +++ b/.claude/hooks/user-level/session-start-recall.sh @@ -90,16 +90,20 @@ ${ROOT_CLUTTER} fi # 7. Fork base trap 检测(LL-080) -# 只在 clowder-labs/clowder-ai 这种 GitHub fork 仓里显示,跨项目时静默 +# user-level hook:跨项目共享,必须**精确**匹配仓 slug,不能 substring。 +# 砚砚 P2 (PR #37): substring `*clowder-labs/clowder-ai*` 会误伤 `clowder-labs/clowder-ai-docs` 等前缀仓。 +# 修法:normalize origin URL → owner/repo 形式(支持 https/ssh + 可选 .git 后缀),再 == 精确比较。 ORIGIN_URL=$(git config --get remote.origin.url 2>/dev/null) -if [[ "$ORIGIN_URL" == *"clowder-labs/clowder-ai"* ]]; then +REPO_SLUG=$(echo "$ORIGIN_URL" | sed -E 's#^.+[/:]([^/:]+/[^/:]+)$#\1#; s#\.git$##') +if [ "$REPO_SLUG" = "clowder-labs/clowder-ai" ]; then # 检测 GH_REPO 兜底是否生效 - if [ "$GH_REPO" = "clowder-labs/clowder-ai" ]; then + # 砚砚 P3 (PR #37): `$GH_REPO` 紧贴全角中文括号在 bash locale 下解析坏,错误值被吞成 ��。用 ${GH_REPO} 显式包裹。 + if [ "${GH_REPO}" = "clowder-labs/clowder-ai" ]; then GH_REPO_STATUS="GH_REPO 兜底已生效" - elif [ -z "$GH_REPO" ]; then + elif [ -z "${GH_REPO}" ]; then GH_REPO_STATUS="GH_REPO 未设——建议 \`source .envrc\` 或装 direnv(参考 .envrc)" else - GH_REPO_STATUS="⚠️ GH_REPO=$GH_REPO(不是 clowder-labs/clowder-ai,可能误导 gh CLI)" + GH_REPO_STATUS="⚠️ GH_REPO=${GH_REPO} (不是 clowder-labs/clowder-ai,可能误导 gh CLI)" fi WARNINGS="${WARNINGS} ⚠️ 本仓在 GitHub 是 zts212653/clowder-ai 的 fork(LL-080) diff --git a/cat-cafe-skills/merge-gate/SKILL.md b/cat-cafe-skills/merge-gate/SKILL.md index de01a91621..ef97db545c 100644 --- a/cat-cafe-skills/merge-gate/SKILL.md +++ b/cat-cafe-skills/merge-gate/SKILL.md @@ -64,7 +64,7 @@ triggers: 进入 Step 7 之前,author 必须核对: ```bash -CURRENT_HEAD="$(gh pr view {PR_NUMBER} --json headRefOid --jq '.headRefOid')" +CURRENT_HEAD="$(gh pr view {PR_NUMBER} --repo clowder-labs/clowder-ai --json headRefOid --jq '.headRefOid')" echo "$CURRENT_HEAD" ``` @@ -193,17 +193,20 @@ fi ### 合入方式(唯一正确做法) +> **⚠️ Fork base trap 总规则(LL-080,覆盖本节所有命令)**: +> 本仓 `clowder-labs/clowder-ai` 在 GitHub 后台是 `zts212653/clowder-ai` 的 fork。 +> **所有** `gh pr *` 和 `gh api repos/...` 命令在 fresh clone / 未写 `gh-resolved` 的 +> worktree 上若不显式带 `--repo clowder-labs/clowder-ai`,gh CLI 会默认解析到 parent +> (zts212653) ——PR 创建、`gh pr view {N}`、`gh pr comment {N}`、`gh pr merge {N}` +> 全部会找错仓。**本节所有命令必须保持 `--repo clowder-labs/clowder-ai`**,并且新加命令时同步带上。 +> 历史事故:PR #1030 (2026-06-26) 误提到 zts,10 分钟后 close + 重建 PR #30。 +> 砚砚 cloud P1 PR #37:仅 pin `gh pr create` 不够,后续 PR-number 命令同样需要。 + ```bash # 1. Push feature branch git push origin {branch} # 2. 开 PR(读 refs/pr-template.md 获取 body 模板,用 HEREDOC 填写) -# -# ⚠️ Fork base trap(LL-080):本仓 clowder-labs/clowder-ai 在 GitHub 后台是 -# zts212653/clowder-ai 的 fork。`gh pr create` 不带 --repo 时,gh CLI 在 -# fresh clone / 未写 `gh-resolved` 的 worktree 上会**默认把 PR 提到 parent -# (zts212653)**——必须显式 --repo clowder-labs/clowder-ai --base main。 -# 历史事故:PR #1030 (2026-06-26) 误提到 zts,10 分钟后 close + 重建 PR #30。 gh pr create --repo clowder-labs/clowder-ai --base main \ --title "feat(xxx): ..." --body "$(cat <<'EOF' ... 按 refs/pr-template.md 模板填写 ... @@ -226,7 +229,7 @@ EOF # ⚠️ 时机契约(fingerprint 去重边界):在 CI **还没绿之前**翻成 merge 才保证被唤醒。 # CiCdRouter 按 headSha:bucket 去重——若同一 head 的 CI-pass 已在 review intent 下投递过, # 之后再翻 merge **不会补发唤醒**。所以:若翻 intent 时 CI **已经绿了**,别等回调,直接 -# `gh pr checks {PR}` 自查、继续 merge 流程(无新 head 时 CI 不会重跑、不会再有 pass 事件)。 +# `gh pr checks {PR} --repo clowder-labs/clowder-ai` 自查、继续 merge 流程(无新 head 时 CI 不会重跑、不会再有 pass 事件)。 # # 收到冲突通知时(F140 Phase B): # - 暂停当前工作,处理冲突优先(冲突是 merge blocker) @@ -235,7 +238,7 @@ EOF # - 复杂冲突 → 通知operator,等指示后再继续 # 4. PR body 防呆检查(禁止任何 @句柄出现在 body) -PR_BODY="$(gh pr view {PR_NUMBER} --json body --jq '.body')" || \ +PR_BODY="$(gh pr view {PR_NUMBER} --repo clowder-labs/clowder-ai --json body --jq '.body')" || \ { echo "❌ 无法读取 PR body,停止流程"; exit 1; } printf '%s\n' "$PR_BODY" | rg -q '@[A-Za-z0-9_-]+ review' && \ { echo "❌ 不合规:remote review 触发句柄只能写在 comment,不能写在 body"; exit 1; } @@ -248,19 +251,19 @@ printf '%s\n' "$PR_BODY" | rg -q '@(codex|chatgpt-codex-connector|gpt52|opus|son # 详见 refs/pr-template.md「云端 Review 触发 Comment 模板」 # 5.1 去重防呆 -LAST_TRIGGER=”$(gh pr view {PR_NUMBER} --json comments | jq -r ' +LAST_TRIGGER=”$(gh pr view {PR_NUMBER} --repo clowder-labs/clowder-ai --json comments | jq -r ' [.comments[] | select(.body | test(“^@codex\\s+review\\s*$”; “m”))] | last | .url // empty ')” # 有已触发 → 检查是否需要重发(新 commit / create-environment 回复 / 无 👀) -gh pr comment {PR_NUMBER} --body '@codex review' +gh pr comment {PR_NUMBER} --repo clowder-labs/clowder-ai --body '@codex review' # 6. 等remote review(事件驱动,不轮询) # # 6.1 👀 接单检测(触发后 5 分钟查一次) -TRIGGER_COMMENT_ID=”$(gh api repos/{OWNER}/{REPO}/issues/{PR_NUMBER}/comments \ +TRIGGER_COMMENT_ID=”$(gh api repos/clowder-labs/clowder-ai/issues/{PR_NUMBER}/comments \ --jq '[.[] | select(.body | test(“^@codex\\s+review”; “m”))] | last | .id')” -EYES=”$(gh api repos/{OWNER}/{REPO}/issues/comments/${TRIGGER_COMMENT_ID}/reactions \ +EYES=”$(gh api repos/clowder-labs/clowder-ai/issues/comments/${TRIGGER_COMMENT_ID}/reactions \ --jq '[.[] | select(.content == “eyes”)] | length')” # - EYES > 0 → 云端已接单 → 停止监控,PR tracking 会自动通知结果。 # ⚠️ KD-27:此时必须释放 hold_ball,禁止续约轮询。PR tracking 回调是唯一通知渠道。 @@ -326,11 +329,11 @@ fi IS_HOTFIX="$(echo "$HOTFIX_JSON" | jq -r '.hotfix // false')" LABEL_ERROR="$(echo "$HOTFIX_JSON" | jq -r '.labelError // empty')" if [ -n "$LABEL_ERROR" ]; then - echo "⚠️ Hotfix label 添加失败: $LABEL_ERROR — 请手动: gh pr edit {PR_NUMBER} --add-label hotfix" + echo "⚠️ Hotfix label 添加失败: $LABEL_ERROR — 请手动: gh pr edit {PR_NUMBER} --repo clowder-labs/clowder-ai --add-label hotfix" fi if [ "$IS_HOTFIX" = "true" ]; then - PR_AUTHOR="$(gh pr view {PR_NUMBER} --json author --jq '.author.login')" - REVIEWERS="$(gh pr view {PR_NUMBER} --json reviews --jq '[.reviews[] | select(.state == "APPROVED") | .author.login] | unique | join(",")')" + PR_AUTHOR="$(gh pr view {PR_NUMBER} --repo clowder-labs/clowder-ai --json author --jq '.author.login')" + REVIEWERS="$(gh pr view {PR_NUMBER} --repo clowder-labs/clowder-ai --json reviews --jq '[.reviews[] | select(.state == "APPROVED") | .author.login] | unique | join(",")')" if [ -z "$REVIEWERS" ] || echo "$REVIEWERS" | grep -q "^${PR_AUTHOR}$"; then echo "❌ Hotfix PR 必须有跨猫 review 放行(禁止 self-merge)" echo " Author: $PR_AUTHOR | Approved by: ${REVIEWERS:-none}" @@ -357,7 +360,7 @@ node scripts/check-feature-truth.mjs # → 详见下方「Feature Doc Truth 核对(Step 7.5)」§ 7.5a(含人工核对项) # 7. Squash merge(GitHub 处理,禁止本地 squash!) -gh pr merge {PR_NUMBER} --squash --delete-branch +gh pr merge {PR_NUMBER} --repo clowder-labs/clowder-ai --squash --delete-branch # 7.5b Post-merge: 记录已合入状态(每次 merge 必做!)🔴 # Phase ✅ / AC 打勾 / Timeline 记 merged / Status 推进 → commit → 复跑 check-feature-truth @@ -467,7 +470,7 @@ ReviewRouter 现在会在投递通知时**主动拉取** review body + inline co merge 前仍需执行以下检查作为兜底: ```bash -gh api --paginate repos/{OWNER}/{REPO}/pulls/{PR_NUMBER}/comments \ +gh api --paginate repos/clowder-labs/clowder-ai/pulls/{PR_NUMBER}/comments \ --jq '.[] | select(.body | test("\\bP[012]\\b"; "i")) | {body: .body[:200], path: .path}' ``` @@ -590,7 +593,7 @@ cd "$MAIN_WT" && git pull origin main # 取回刚 squash 的 commit,doc-sync **动作**:**只发 `@codex review` 一行**重新触发(同 SHA 不需要新 commit)。 ```bash -gh pr comment {PR_NUMBER} --body '@codex review' +gh pr comment {PR_NUMBER} --repo clowder-labs/clowder-ai --body '@codex review' ``` > 教训演进:2026-04-18 曾以为是"后台 bug / 没接单",2026-04-20 PR #1300 确认根因是**详细格式触发 code-write 解析**。极简格式是唯一可靠触发方式(PR #1258 + PR #1300 两次实战验证)。 @@ -626,7 +629,7 @@ gh pr comment {PR_NUMBER} --body '@codex review' **⚠️ 共享 API 池陷阱(F238 教训)**:同一 provider 的不同 model(Codex/GPT-5.4/GPT-5.5)共享 API 额度。降级必须跨 provider family(OpenAI → Anthropic),不能在同 provider 内换个体。 -操作:`gh pr comment {PR} --body "..."` 用标准触发模板 @ 降级 reviewer(句柄查 `cat-config.json`)。 +操作:`gh pr comment {PR} --repo clowder-labs/clowder-ai --body "..."` 用标准触发模板 @ 降级 reviewer(句柄查 `cat-config.json`)。 ## 和其他 skill 的区别 diff --git a/docs/public-lessons.md b/docs/public-lessons.md index 09467c8403..caf964f871 100644 --- a/docs/public-lessons.md +++ b/docs/public-lessons.md @@ -1780,7 +1780,7 @@ created: 2026-02-26 - 触发条件:(a) Fresh clone / 新机器 / CI sandbox / teleport 后 `gh-resolved` git config 未写入;(b) cat 照旧 SOP 抄裸命令;(c) cat 看到 remote `upstream` 误判语义;(d) `gh auth` 重登录 / gh CLI 升级清掉 `gh-resolved` 让旧 clone 退化成 fresh clone 行为。 - 修复:PR #1030 close + 重建 PR #30;本地 `git remote rename upstream zts-mirror`(rename 自动迁移 7 个 tracking 分支 + 14 个 worktree 同步);SOP 显式 `--repo`;写 `.envrc` 兜底 `GH_REPO`;SessionStart hook 加 fork warning。 - 防护:四层硬约束 + 一层认知。 - 1. **L1 SOP(核心)**:`cat-cafe-skills/merge-gate/SKILL.md` 命令显式 `gh pr create --repo clowder-labs/clowder-ai --base main` + 旁注 fork trap 出处。猫照 SOP 抄 = 自动安全 + 1. **L1 SOP(核心)**:`cat-cafe-skills/merge-gate/SKILL.md` **全部** `gh pr *` / `gh api repos/...` 命令显式 `--repo clowder-labs/clowder-ai`——不只 `gh pr create`,也包括后续 `gh pr view`/`comment`/`merge`/`checks`/`edit`/`gh api repos/.../comments` 等所有用 PR number 寻址的命令(砚砚 cloud P1 PR #37:仅 pin create 不够,fresh clone 上后续命令仍会按 PR number 解析到 fork parent)。SOP 顶部加"Fork base trap 总规则"块作为人类可读约束。猫照 SOP 抄 = 自动安全 2. **L2 命名(认知)**:本地 remote `upstream` → `zts-mirror`(rename 消除名字诱导;`pushurl=DISABLED` + `pushdefault=origin` 保留) 3. **L3 环境(兜底)**:`.envrc` 设 `export GH_REPO=clowder-labs/clowder-ai`,cat 手敲命令绕开 SOP 时兜底(家里未默认装 direnv,但文件作为 source-of-truth + 单次手动 `source .envrc` 可用) 4. **L4 自检(提醒)**:`.claude/hooks/user-level/session-start-recall.sh` 检测当前仓 origin URL 含 `clowder-labs/clowder-ai` 时,开工自检显示 fork warning + GH_REPO 状态 From 7e25f8e5180242069d68728a66cd7229d758b264 Mon Sep 17 00:00:00 2001 From: "Ragdoll-Opus-4.7" Date: Sun, 28 Jun 2026 23:08:29 +0800 Subject: [PATCH 3/9] fix(#37): drop hardcoded cat name in SKILL.md per check-skills-manifest lint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit check-skills-manifest CI flagged "砚砚" hardcoded name at SKILL.md:131 (SKILL files are cross-cat shared SOPs — must use role descriptions, not individual cat handles). Replaced "砚砚 cloud P1 PR #37" → "PR #37 cloud review P1" — preserves the attribution to the source PR finding without naming a specific cat. Co-Authored-By: Claude Opus 4.7 --- cat-cafe-skills/merge-gate/SKILL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cat-cafe-skills/merge-gate/SKILL.md b/cat-cafe-skills/merge-gate/SKILL.md index ef97db545c..ebaedc8a47 100644 --- a/cat-cafe-skills/merge-gate/SKILL.md +++ b/cat-cafe-skills/merge-gate/SKILL.md @@ -200,7 +200,7 @@ fi > (zts212653) ——PR 创建、`gh pr view {N}`、`gh pr comment {N}`、`gh pr merge {N}` > 全部会找错仓。**本节所有命令必须保持 `--repo clowder-labs/clowder-ai`**,并且新加命令时同步带上。 > 历史事故:PR #1030 (2026-06-26) 误提到 zts,10 分钟后 close + 重建 PR #30。 -> 砚砚 cloud P1 PR #37:仅 pin `gh pr create` 不够,后续 PR-number 命令同样需要。 +> PR #37 cloud review P1:仅 pin `gh pr create` 不够,后续 PR-number 命令同样需要。 ```bash # 1. Push feature branch From e666221fb5ea2ab2e112845d38e80a42065fdaaf Mon Sep 17 00:00:00 2001 From: "Ragdoll-Opus-4.7" Date: Sun, 28 Jun 2026 23:16:58 +0800 Subject: [PATCH 4/9] fix(#37): pin --repo on Quick Reference + Common Mistakes shortcuts (cloud R2 P2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cloud R2 finding at SKILL.md:432: Quick Reference cloud-pass shortcut "gh pr checks {PR}" was left without --repo while the top guard block claims all PR-number commands are pinned. Fresh-clone users copying that shortcut would still query the fork parent. Fixes: - Line 432 (Quick Reference): "gh pr checks {PR} --repo clowder-labs/clowder-ai" - Line 447/448 (Common Mistakes): pin --repo on actionable commands; add "都按 Fork base trap 总规则带 --repo" attribution note on definitional references Rationale for going beyond cloud's specific call-out: Common Mistakes table also contains actionable shortcuts (gh pr merge --squash / gh pr close). Pin consistently to avoid R3+ rediscovery of the same pattern in the same file. SKILL.md now has zero bare gh pr/gh api repos commands. Only line 345 (a descriptive sentence "gh pr view 的 --json reviews ...") and line 14 (trigger keyword) remain unmodified — neither is a shortcut. Co-Authored-By: Claude Opus 4.7 --- cat-cafe-skills/merge-gate/SKILL.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cat-cafe-skills/merge-gate/SKILL.md b/cat-cafe-skills/merge-gate/SKILL.md index ebaedc8a47..83c635e69e 100644 --- a/cat-cafe-skills/merge-gate/SKILL.md +++ b/cat-cafe-skills/merge-gate/SKILL.md @@ -540,7 +540,7 @@ cd "$MAIN_WT" && git pull origin main # 取回刚 squash 的 commit,doc-sync | Reviewer 放行? | 搜索明确信号词 | | P1/P2 清零? | 检查 review 记录 | | BACKLOG 更新? | `grep '\[x\]' docs/ROADMAP.md` | -| 云端通过? | `gh pr checks {PR}` | +| 云端通过? | `gh pr checks {PR} --repo clowder-labs/clowder-ai` | | Evidence validation 通过?(Step 6.9) | E1-E5 五项全绿(head 一致 + review 不 stale + provenance 闭合 + verdict passed + gate passed) | | Feature doc 说真话?(pre-merge) | doc 标 ✅/打勾 AC 有代码支撑 + `node scripts/check-feature-truth.mjs` 绿 | | 已合入状态记录?(post-merge) | feature doc Phase ✅ + AC 打勾 + Timeline 有 merged 记录 + Status 推进 | @@ -557,8 +557,8 @@ cd "$MAIN_WT" && git pull origin main # 取回刚 squash 的 commit,doc-sync | 修了 P1 不 re-trigger review | 修完 push 后**必须重新触发**remote review | | cloud P1/P2 修完后又 @ 本地旧 reviewer 续签 | `headChangeCause=cloud-finding` → re-trigger cloud review + 等 PR tracking;本地 peer 不是 Stage ④ 常驻 gate | | `pnpm gate` rebase / fixup 后沿用旧 review 直接 merge | 先对齐 `headRefOid`;**只要 HEAD 变了,先按 Review Provenance Matrix 判定 nextGateOwner** | -| 本地 `git rebase -i` 手动 squash | 用 `gh pr merge --squash`(GitHub 处理) | -| 本地 merge 后 `gh pr close` | `gh pr close` = 放弃,`gh pr merge` = 合入 | +| 本地 `git rebase -i` 手动 squash | 用 `gh pr merge --repo clowder-labs/clowder-ai --squash`(GitHub 处理) | +| 本地 merge 后 `gh pr close --repo clowder-labs/clowder-ai` | `gh pr close` = 放弃,`gh pr merge` = 合入(都按 Fork base trap 总规则带 `--repo`) | | 不等remote review 直接合入 | 必须等 0 P1/P2 | | 把截图/录屏/.pen 直接 commit 到仓库根目录 | Step 0.5 Root Artifact Guard 先拦截;先归档再开 PR | | 跳过 evidence validation 直接 merge | Step 6.9 五项 E1-E5 全过才能进 Step 7;不组装 evidence = 不知道 review 是否 stale | From 1e1a2409663805ef678aec6b50ac931e915f76f1 Mon Sep 17 00:00:00 2001 From: "Ragdoll-Opus-4.7" Date: Sun, 28 Jun 2026 23:23:42 +0800 Subject: [PATCH 5/9] =?UTF-8?q?fix(#37):=20rename=20.envrc=20=E2=86=92=20.?= =?UTF-8?q?envrc.example=20per=20Config=20Immutability=20(cloud=20R3=20P1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cloud R3 P1 finding: committing an active `.envrc` violates AGENTS.md / CLAUDE.md Config Immutability — direnv auto-sources it after `direnv allow`, so a team-level commit silently retargets every shell environment that allows it. That's "runtime config injected via repo", which is exactly the immutability rule's prohibition. Fix: - `git mv .envrc .envrc.example` — only a template ships - `.gitignore`: add `.envrc` so cats can't accidentally commit a personal copy - `.envrc.example` header: document `cp .envrc.example .envrc && direnv allow` (or `source .envrc.example` for one-shot use) as the explicit opt-in path - SessionStart hook GH_REPO_STATUS: point at the new opt-in command - LL-080: update防护栏 L3 description + 来源锚点 + 关联 This preserves the four-layer functional coverage (cat manually opts in for hand-typed commands) while honoring the team's runtime-config-immutability contract. SOP-driven cats are unaffected — they never depended on .envrc because every gh command is now explicit `--repo clowder-labs/clowder-ai`. Verified: - `.envrc.example` still source-able → GH_REPO=clowder-labs/clowder-ai ✅ - SessionStart hook shows updated opt-in command ✅ - Hook syntax pass ✅ - `.envrc` ignored on subsequent local creation ✅ Co-Authored-By: Claude Opus 4.7 --- .../hooks/user-level/session-start-recall.sh | 2 +- .envrc | 20 ------------- .envrc.example | 30 +++++++++++++++++++ .gitignore | 1 + docs/public-lessons.md | 8 ++--- 5 files changed, 36 insertions(+), 25 deletions(-) delete mode 100644 .envrc create mode 100644 .envrc.example diff --git a/.claude/hooks/user-level/session-start-recall.sh b/.claude/hooks/user-level/session-start-recall.sh index 998d1fac65..1b79706137 100755 --- a/.claude/hooks/user-level/session-start-recall.sh +++ b/.claude/hooks/user-level/session-start-recall.sh @@ -101,7 +101,7 @@ if [ "$REPO_SLUG" = "clowder-labs/clowder-ai" ]; then if [ "${GH_REPO}" = "clowder-labs/clowder-ai" ]; then GH_REPO_STATUS="GH_REPO 兜底已生效" elif [ -z "${GH_REPO}" ]; then - GH_REPO_STATUS="GH_REPO 未设——建议 \`source .envrc\` 或装 direnv(参考 .envrc)" + GH_REPO_STATUS="GH_REPO 未设——\`cp .envrc.example .envrc && direnv allow\` 或 \`source .envrc.example\` 启用" else GH_REPO_STATUS="⚠️ GH_REPO=${GH_REPO} (不是 clowder-labs/clowder-ai,可能误导 gh CLI)" fi diff --git a/.envrc b/.envrc deleted file mode 100644 index 7316edbc40..0000000000 --- a/.envrc +++ /dev/null @@ -1,20 +0,0 @@ -# ============================================================ -# Cat Cafe / Clowder AI — Shell environment(手敲命令时兜底) -# ============================================================ -# -# 这个文件的作用:补 §SOP 之外的"手敲命令"场景。 -# 详见 LL-080(PR Fork Base Trap)+ cat-cafe-skills/merge-gate/SKILL.md。 -# -# 用法(按你的环境二选一): -# 1. direnv(推荐):`brew install direnv` + 在 shell rc 加 `eval "$(direnv hook zsh)"` -# + 在本仓首次运行 `direnv allow` —— 进入本仓时自动 export -# 2. 手动:在 cat agent 启动前跑 `source .envrc`(不持久但单次 shell 生效) -# -# 注:cat-cafe-skills 里的 SOP 命令已经显式 `--repo clowder-labs/clowder-ai`, -# 不依赖 GH_REPO;这里是给"绕开 SOP、直接敲 gh 命令"的猫兜底用。 -# ============================================================ - -# Fork base trap 兜底:本仓在 GitHub 是 zts212653/clowder-ai 的 fork, -# 不显式 --repo / GH_REPO 时,gh CLI 会默认把 PR / issue 提到 parent。 -# 历史事故:PR #1030 (2026-06-26) 误提到 zts,10 分钟后 close + 重建 PR #30。 -export GH_REPO="clowder-labs/clowder-ai" diff --git a/.envrc.example b/.envrc.example new file mode 100644 index 0000000000..7ea1ee483a --- /dev/null +++ b/.envrc.example @@ -0,0 +1,30 @@ +# ============================================================ +# Cat Cafe / Clowder AI — Shell environment 模板(手敲命令时兜底) +# ============================================================ +# +# 这个文件是**模板**(.envrc.example),不直接生效。 +# 用户主动 `cp .envrc.example .envrc` 后才会被 direnv 自动 source。 +# +# 设计选择(AGENTS.md / CLAUDE.md Config Immutability 铁律): +# runtime shell 配置不通过 commit 渗透。team-level 提供模板,individual 自己启用。 +# 这样审查/回滚 GH_REPO 改动归个人 git 操作,不进 PR review 影子层。 +# +# 这个文件的作用:补 §SOP 之外的"手敲命令"场景。 +# 详见 LL-080(PR Fork Base Trap)+ cat-cafe-skills/merge-gate/SKILL.md。 +# +# 启用步骤(按你的环境二选一): +# 1. direnv(推荐): +# brew install direnv +# # shell rc 加:eval "$(direnv hook zsh)" +# cp .envrc.example .envrc +# direnv allow # 进入本仓自动 export +# 2. 手动:每次 cat agent 启动前 `source .envrc.example`(单次 shell 生效) +# +# 注:cat-cafe-skills 里的 SOP 命令已经显式 `--repo clowder-labs/clowder-ai`, +# 不依赖 GH_REPO;这里是给"绕开 SOP、直接敲 gh 命令"的猫兜底用。 +# ============================================================ + +# Fork base trap 兜底:本仓在 GitHub 是 zts212653/clowder-ai 的 fork, +# 不显式 --repo / GH_REPO 时,gh CLI 会默认把 PR / issue 提到 parent。 +# 历史事故:PR #1030 (2026-06-26) 误提到 zts,10 分钟后 close + 重建 PR #30。 +export GH_REPO="clowder-labs/clowder-ai" diff --git a/.gitignore b/.gitignore index 0c023a9e1f..8ea0accbf0 100644 --- a/.gitignore +++ b/.gitignore @@ -35,6 +35,7 @@ packages/web/public/vendor/ .env .env.local .env.*.local +.envrc # LL-080 Config Immutability: only .envrc.example is committed # Local overlay files (#603 .local convention, F225 prompt templates) *.local.md diff --git a/docs/public-lessons.md b/docs/public-lessons.md index caf964f871..9f70583492 100644 --- a/docs/public-lessons.md +++ b/docs/public-lessons.md @@ -1778,19 +1778,19 @@ created: 2026-02-26 - 坑:本仓 `clowder-labs/clowder-ai` 在 GitHub 后台是 `zts212653/clowder-ai` 的 fork。`gh pr create` 不带 `--repo` 时,gh CLI **默认把 PR 提到 parent (zts212653)**——这是 gh CLI 公开行为,不是 bug。2026-06-26 实证:PR #1030 误提到 zts,10 分钟后 close + 重建 PR #30 到 clowder-labs。co-creator:"又"——同型陷阱以前讨论过 fork workflow (thread_mpz55rv7dahgk4ce) 但没沉淀成硬规则。 - 根因:三层叠加。(1) **机制层**:GitHub fork relationship 是 gh CLI 后台事实,本地配置不能改变。(2) **认知层**:本地 remote 名字曾叫 `upstream`,看名字像"正确目标"——但 zts212653 是 fork 父,不是上游。(3) **SOP 层**:`cat-cafe-skills/merge-gate/SKILL.md` 旧版命令裸 `gh pr create` 没 `--repo`,照抄即踩。 - 触发条件:(a) Fresh clone / 新机器 / CI sandbox / teleport 后 `gh-resolved` git config 未写入;(b) cat 照旧 SOP 抄裸命令;(c) cat 看到 remote `upstream` 误判语义;(d) `gh auth` 重登录 / gh CLI 升级清掉 `gh-resolved` 让旧 clone 退化成 fresh clone 行为。 -- 修复:PR #1030 close + 重建 PR #30;本地 `git remote rename upstream zts-mirror`(rename 自动迁移 7 个 tracking 分支 + 14 个 worktree 同步);SOP 显式 `--repo`;写 `.envrc` 兜底 `GH_REPO`;SessionStart hook 加 fork warning。 +- 修复:PR #1030 close + 重建 PR #30;本地 `git remote rename upstream zts-mirror`(rename 自动迁移 7 个 tracking 分支 + 14 个 worktree 同步);SOP 显式 `--repo`;提供 `.envrc.example` 模板(用户主动 cp 启用,避免 runtime config 通过 commit 渗透——cloud R3 P1);SessionStart hook 加 fork warning。 - 防护:四层硬约束 + 一层认知。 1. **L1 SOP(核心)**:`cat-cafe-skills/merge-gate/SKILL.md` **全部** `gh pr *` / `gh api repos/...` 命令显式 `--repo clowder-labs/clowder-ai`——不只 `gh pr create`,也包括后续 `gh pr view`/`comment`/`merge`/`checks`/`edit`/`gh api repos/.../comments` 等所有用 PR number 寻址的命令(砚砚 cloud P1 PR #37:仅 pin create 不够,fresh clone 上后续命令仍会按 PR number 解析到 fork parent)。SOP 顶部加"Fork base trap 总规则"块作为人类可读约束。猫照 SOP 抄 = 自动安全 2. **L2 命名(认知)**:本地 remote `upstream` → `zts-mirror`(rename 消除名字诱导;`pushurl=DISABLED` + `pushdefault=origin` 保留) - 3. **L3 环境(兜底)**:`.envrc` 设 `export GH_REPO=clowder-labs/clowder-ai`,cat 手敲命令绕开 SOP 时兜底(家里未默认装 direnv,但文件作为 source-of-truth + 单次手动 `source .envrc` 可用) + 3. **L3 环境(兜底)**:仓内只 commit `.envrc.example` 模板(含 `export GH_REPO=clowder-labs/clowder-ai`),用户主动 `cp .envrc.example .envrc && direnv allow` 或 `source .envrc.example` 启用。`.envrc` 加入 `.gitignore` —— **运行时 shell 配置不通过 commit 渗透**(AGENTS.md / CLAUDE.md Config Immutability 铁律 + cloud R3 P1)。L3 是给"绕开 SOP、直接手敲 gh 命令"的猫兜底用 4. **L4 自检(提醒)**:`.claude/hooks/user-level/session-start-recall.sh` 检测当前仓 origin URL 含 `clowder-labs/clowder-ai` 时,开工自检显示 fork warning + GH_REPO 状态 5. **(认知)**:明白 GitHub fork relationship 是不可改的后台事实,本地任何配置都只能在 invocation 上强制覆盖默认值——不能"修复"fork 状态本身 - 来源锚点: - `cat-cafe-skills/merge-gate/SKILL.md#L128`(修复后 SOP) - PR #30(重建到 clowder-labs)/ PR #1030 (zts212653,已 CLOSED) — 2026-06-26 事故 - thread_mpz55rv7dahgk4ce(2026 早期讨论 fork workflow 但未沉淀硬规则) - - `.envrc` / `.claude/hooks/user-level/session-start-recall.sh`(防护实现) + - `.envrc.example` / `.gitignore` / `.claude/hooks/user-level/session-start-recall.sh`(防护实现) - 原理:fork relationship 是 GitHub 后台的**固定结构事实**——`fork=true, parent=zts212653/clowder-ai` 写在 GitHub API。本地任何 git config (`gh-resolved`、`pushdefault`、`pushurl=DISABLED`) 都是 **per-clone 的覆盖层**,覆盖层会因为 fresh clone / re-auth 失效。**只有"每次 invocation 显式 `--repo`"才是不依赖任何 per-clone state 的硬约束**。命名陷阱 (`upstream`) 是认知层的"错觉地基"——rename 消除地基,而不是治理症状。 - 已知 TD(后续治理):`packages/api/src/infrastructure/harness-eval/publish-verdict/git-worktree-publisher.ts:138` F192 verdict publisher 同型——`gh pr create` 不带 `--repo`,fresh deploy 上踩坑。窄场景(API server fresh deploy 时),独立 issue 跟进。 -- 关联:cat-cafe-skills/merge-gate/SKILL.md / .envrc / .claude/hooks/user-level/session-start-recall.sh / PR #30 / PR #1030 / thread_mpz55rv7dahgk4ce +- 关联:cat-cafe-skills/merge-gate/SKILL.md / .envrc.example / .gitignore / .claude/hooks/user-level/session-start-recall.sh / PR #30 / PR #1030 / PR #37 / thread_mpz55rv7dahgk4ce From 9729bd0221e99a92f9e3986bdddc6ed50346eade Mon Sep 17 00:00:00 2001 From: "Ragdoll-Opus-4.7" Date: Sun, 28 Jun 2026 23:33:42 +0800 Subject: [PATCH 6/9] fix(#37): document GH_REPO leak risk on manual source (cloud R4 P2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cloud R4 finding: `source .envrc.example` persistently exports GH_REPO to the whole shell session. After cd to another repo, hand-typed `gh pr/issue` commands stay forced to clowder-labs/clowder-ai — the same class of cross-repo misrouting this rail is meant to prevent (GH_REPO reverse pollution vs fork base default routing). Reframe `.envrc.example` "enable steps" as 3-tier (priority top-down): 1. direnv (strong recommend — auto export/unset on enter/leave) 2. per-command `GH_REPO=clowder-labs/clowder-ai gh ...` (no direnv path) 3. source + `unset GH_REPO` cleanup (short-task only, side-effect noted) Sync wording in: - .envrc.example: enable-steps block + R4 P2 warning callout - session-start-recall.sh:104: drop `source` from unset-status hint; promote per-command alternative for no-direnv path - public-lessons.md LL-080 L3: 3-tier enable guidance + R4 P2 rationale Verified: - `bash -n` hook: pass - Hook dry-run with GH_REPO unset: new per-command hint shown - Hook dry-run with GH_REPO=clowder-labs/clowder-ai: "兜底已生效" unchanged - Hook dry-run from /tmp: silent (cross-project isolation preserved) - `source .envrc.example && unset GH_REPO`: GH_REPO cleared correctly Provenance: - currentHead: post-this-commit - cloudReviewSha: 773b4795 (R4 P2) - headChangeCause: cloud-finding → re-trigger cloud + local-peer delta - nextGateOwner: cloud R5 + local-peer Maine Coon final cross-family Co-Authored-By: Claude Opus 4.7 --- .../hooks/user-level/session-start-recall.sh | 2 +- .envrc.example | 21 +++++++++++++++---- docs/public-lessons.md | 2 +- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/.claude/hooks/user-level/session-start-recall.sh b/.claude/hooks/user-level/session-start-recall.sh index 1b79706137..f25c2ea58e 100755 --- a/.claude/hooks/user-level/session-start-recall.sh +++ b/.claude/hooks/user-level/session-start-recall.sh @@ -101,7 +101,7 @@ if [ "$REPO_SLUG" = "clowder-labs/clowder-ai" ]; then if [ "${GH_REPO}" = "clowder-labs/clowder-ai" ]; then GH_REPO_STATUS="GH_REPO 兜底已生效" elif [ -z "${GH_REPO}" ]; then - GH_REPO_STATUS="GH_REPO 未设——\`cp .envrc.example .envrc && direnv allow\` 或 \`source .envrc.example\` 启用" + GH_REPO_STATUS="GH_REPO 未设——\`cp .envrc.example .envrc && direnv allow\`(推荐)或 per-command \`GH_REPO=clowder-labs/clowder-ai gh ...\`(无 direnv 时不污染 session)" else GH_REPO_STATUS="⚠️ GH_REPO=${GH_REPO} (不是 clowder-labs/clowder-ai,可能误导 gh CLI)" fi diff --git a/.envrc.example b/.envrc.example index 7ea1ee483a..b6f301ca97 100644 --- a/.envrc.example +++ b/.envrc.example @@ -12,13 +12,26 @@ # 这个文件的作用:补 §SOP 之外的"手敲命令"场景。 # 详见 LL-080(PR Fork Base Trap)+ cat-cafe-skills/merge-gate/SKILL.md。 # -# 启用步骤(按你的环境二选一): -# 1. direnv(推荐): +# 启用步骤(按你的环境三选一,优先级从上到下): +# 1. direnv(**强推荐** — 进出 repo 自动 export/unset,无 cross-repo 污染): # brew install direnv # # shell rc 加:eval "$(direnv hook zsh)" # cp .envrc.example .envrc -# direnv allow # 进入本仓自动 export -# 2. 手动:每次 cat agent 启动前 `source .envrc.example`(单次 shell 生效) +# direnv allow # 进入本仓自动 export,离开自动 unset +# +# 2. Per-command 前置(无 direnv 时**推荐替代**,不污染 session): +# GH_REPO=clowder-labs/clowder-ai gh pr view {N} +# GH_REPO=clowder-labs/clowder-ai gh issue create ... +# +# 3. 手动 source(**有副作用**,仅限短任务且离开本仓前必须清理): +# ⚠️ PR #37 cloud R4 P2:`source .envrc.example` 会把 GH_REPO **持久 export** +# 到整个 shell session。之后 cd 到其他 repo,`gh pr/issue` 仍被强制路由到 +# clowder-labs/clowder-ai——这正是这条 rail 防的**同类错路由**(GH_REPO +# 反向污染 vs fork base 默认路由,同一类错仓 mutation)。 +# 用法(自带清理): +# source .envrc.example +# gh pr ... # 你的工作 +# unset GH_REPO # ❗ 离开本 repo / 换任务前必清理 # # 注:cat-cafe-skills 里的 SOP 命令已经显式 `--repo clowder-labs/clowder-ai`, # 不依赖 GH_REPO;这里是给"绕开 SOP、直接敲 gh 命令"的猫兜底用。 diff --git a/docs/public-lessons.md b/docs/public-lessons.md index 9f70583492..ac7dd3d233 100644 --- a/docs/public-lessons.md +++ b/docs/public-lessons.md @@ -1782,7 +1782,7 @@ created: 2026-02-26 - 防护:四层硬约束 + 一层认知。 1. **L1 SOP(核心)**:`cat-cafe-skills/merge-gate/SKILL.md` **全部** `gh pr *` / `gh api repos/...` 命令显式 `--repo clowder-labs/clowder-ai`——不只 `gh pr create`,也包括后续 `gh pr view`/`comment`/`merge`/`checks`/`edit`/`gh api repos/.../comments` 等所有用 PR number 寻址的命令(砚砚 cloud P1 PR #37:仅 pin create 不够,fresh clone 上后续命令仍会按 PR number 解析到 fork parent)。SOP 顶部加"Fork base trap 总规则"块作为人类可读约束。猫照 SOP 抄 = 自动安全 2. **L2 命名(认知)**:本地 remote `upstream` → `zts-mirror`(rename 消除名字诱导;`pushurl=DISABLED` + `pushdefault=origin` 保留) - 3. **L3 环境(兜底)**:仓内只 commit `.envrc.example` 模板(含 `export GH_REPO=clowder-labs/clowder-ai`),用户主动 `cp .envrc.example .envrc && direnv allow` 或 `source .envrc.example` 启用。`.envrc` 加入 `.gitignore` —— **运行时 shell 配置不通过 commit 渗透**(AGENTS.md / CLAUDE.md Config Immutability 铁律 + cloud R3 P1)。L3 是给"绕开 SOP、直接手敲 gh 命令"的猫兜底用 + 3. **L3 环境(兜底)**:仓内只 commit `.envrc.example` 模板(含 `export GH_REPO=clowder-labs/clowder-ai`),用户三选一启用 — (a) `cp .envrc.example .envrc && direnv allow`(**强推荐**,进出 repo 自动 export/unset,无 cross-repo 污染);(b) per-command `GH_REPO=clowder-labs/clowder-ai gh ...`(无 direnv 时推荐,单次生效不污染 session);(c) `source .envrc.example`(仅限短任务,必须配套 `unset GH_REPO` 清理 — cloud R4 P2:source 会把 GH_REPO 持久 export 到整个 shell session,cd 到其他 repo 仍强制路由到 clowder-labs/clowder-ai,是这条 rail 防的同类错路由)。`.envrc` 加入 `.gitignore` —— **运行时 shell 配置不通过 commit 渗透**(AGENTS.md / CLAUDE.md Config Immutability 铁律 + cloud R3 P1)。L3 是给"绕开 SOP、直接手敲 gh 命令"的猫兜底用 4. **L4 自检(提醒)**:`.claude/hooks/user-level/session-start-recall.sh` 检测当前仓 origin URL 含 `clowder-labs/clowder-ai` 时,开工自检显示 fork warning + GH_REPO 状态 5. **(认知)**:明白 GitHub fork relationship 是不可改的后台事实,本地任何配置都只能在 invocation 上强制覆盖默认值——不能"修复"fork 状态本身 - 来源锚点: From 341dd7f40e02c7c7e48e0fcd0f321f561d2b199c Mon Sep 17 00:00:00 2001 From: "Ragdoll-Opus-4.7" Date: Sun, 28 Jun 2026 23:40:51 +0800 Subject: [PATCH 7/9] fix(#37): split .gitignore inline comment to enable .envrc rule (cloud R5 P2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cloud R5 finding: `.envrc # LL-080 Config Immutability: only .envrc.example is committed` was treated by git as a literal pattern containing spaces and `#` — gitignore does not parse trailing inline comments. Result: `git check-ignore -v .envrc` exit 1 (no match). The R3 Config Immutability rail was nominally in place but factually inactive — `cp .envrc.example .envrc` followed by `git add` would happily stage the runtime config. Fix: move the explanation to its own `#` comment lines above the pattern, keep `.envrc` on its own line. Verified: - Before: `git check-ignore -v .envrc` → exit 1 (not ignored) - After: `git check-ignore -v .envrc` → `.gitignore:30:.envrc .envrc` exit 0 (matched by line 30) This is R3-fix-introduced secondary bug surfaced by R5 — not a new finding class. R3 cloud reviewer did not catch it; R5 did. Healthy iteration, not recursive spinning. Provenance: - currentHead: post-this-commit - cloudReviewSha: 43f7e5d7 (R5 P2) - headChangeCause: cloud-finding → re-trigger cloud verification + local-peer continuity - nextGateOwner: cloud R6 verify + local-peer (Maine Coon) final cross-family Co-Authored-By: Claude Opus 4.7 --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 8ea0accbf0..38d8d6606d 100644 --- a/.gitignore +++ b/.gitignore @@ -35,7 +35,9 @@ packages/web/public/vendor/ .env .env.local .env.*.local -.envrc # LL-080 Config Immutability: only .envrc.example is committed +# LL-080 Config Immutability: only .envrc.example is committed +# cloud R5 P2: gitignore 不支持行尾内联注释——必须独占一行才会被解析为 pattern +.envrc # Local overlay files (#603 .local convention, F225 prompt templates) *.local.md From 929fd13bb3b7f8fe5a86011b25c8bce87dc801e0 Mon Sep 17 00:00:00 2001 From: "Ragdoll-Opus-4.6" Date: Wed, 8 Jul 2026 10:55:46 +0800 Subject: [PATCH 8/9] =?UTF-8?q?chore(#37):=20renumber=20LL-080=20=E2=86=92?= =?UTF-8?q?=20LL-090=20to=20resolve=20collision=20with=20main?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit During rebase, main acquired its own LL-080 (Same-account self-APPROVE limitation). PR #37's LL-080 (PR fork base trap) was originally numbered when the slot was free. Now that both exist, renumber PR's entry to LL-090 (main's highest is LL-089) across all 4 files: - docs/public-lessons.md: header LL-080 → LL-090 - cat-cafe-skills/merge-gate/SKILL.md: Fork base trap 总规则 ref - .envrc.example: LL-080 comment ref - .claude/hooks/user-level/session-start-recall.sh: 2 refs (comment + warning) Co-Authored-By: Claude Opus 4.6 --- .claude/hooks/user-level/session-start-recall.sh | 4 ++-- .envrc.example | 2 +- cat-cafe-skills/merge-gate/SKILL.md | 2 +- docs/public-lessons.md | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.claude/hooks/user-level/session-start-recall.sh b/.claude/hooks/user-level/session-start-recall.sh index f25c2ea58e..9127875553 100755 --- a/.claude/hooks/user-level/session-start-recall.sh +++ b/.claude/hooks/user-level/session-start-recall.sh @@ -89,7 +89,7 @@ ${ROOT_CLUTTER} " fi -# 7. Fork base trap 检测(LL-080) +# 7. Fork base trap 检测(LL-090) # user-level hook:跨项目共享,必须**精确**匹配仓 slug,不能 substring。 # 砚砚 P2 (PR #37): substring `*clowder-labs/clowder-ai*` 会误伤 `clowder-labs/clowder-ai-docs` 等前缀仓。 # 修法:normalize origin URL → owner/repo 形式(支持 https/ssh + 可选 .git 后缀),再 == 精确比较。 @@ -106,7 +106,7 @@ if [ "$REPO_SLUG" = "clowder-labs/clowder-ai" ]; then GH_REPO_STATUS="⚠️ GH_REPO=${GH_REPO} (不是 clowder-labs/clowder-ai,可能误导 gh CLI)" fi WARNINGS="${WARNINGS} -⚠️ 本仓在 GitHub 是 zts212653/clowder-ai 的 fork(LL-080) +⚠️ 本仓在 GitHub 是 zts212653/clowder-ai 的 fork(LL-090) → \`gh pr create\` 必须显式 \`--repo clowder-labs/clowder-ai --base main\`,否则默认提到 parent (zts212653) → ${GH_REPO_STATUS} " diff --git a/.envrc.example b/.envrc.example index b6f301ca97..8113f75856 100644 --- a/.envrc.example +++ b/.envrc.example @@ -10,7 +10,7 @@ # 这样审查/回滚 GH_REPO 改动归个人 git 操作,不进 PR review 影子层。 # # 这个文件的作用:补 §SOP 之外的"手敲命令"场景。 -# 详见 LL-080(PR Fork Base Trap)+ cat-cafe-skills/merge-gate/SKILL.md。 +# 详见 LL-090(PR Fork Base Trap)+ cat-cafe-skills/merge-gate/SKILL.md。 # # 启用步骤(按你的环境三选一,优先级从上到下): # 1. direnv(**强推荐** — 进出 repo 自动 export/unset,无 cross-repo 污染): diff --git a/cat-cafe-skills/merge-gate/SKILL.md b/cat-cafe-skills/merge-gate/SKILL.md index 83c635e69e..47c33b8294 100644 --- a/cat-cafe-skills/merge-gate/SKILL.md +++ b/cat-cafe-skills/merge-gate/SKILL.md @@ -193,7 +193,7 @@ fi ### 合入方式(唯一正确做法) -> **⚠️ Fork base trap 总规则(LL-080,覆盖本节所有命令)**: +> **⚠️ Fork base trap 总规则(LL-090,覆盖本节所有命令)**: > 本仓 `clowder-labs/clowder-ai` 在 GitHub 后台是 `zts212653/clowder-ai` 的 fork。 > **所有** `gh pr *` 和 `gh api repos/...` 命令在 fresh clone / 未写 `gh-resolved` 的 > worktree 上若不显式带 `--repo clowder-labs/clowder-ai`,gh CLI 会默认解析到 parent diff --git a/docs/public-lessons.md b/docs/public-lessons.md index ac7dd3d233..e7b8d97ba7 100644 --- a/docs/public-lessons.md +++ b/docs/public-lessons.md @@ -1771,7 +1771,7 @@ created: 2026-02-26 --- -### LL-080: PR fork base trap — `gh pr create` 在 fork 仓默认提到 parent +### LL-090: PR fork base trap — `gh pr create` 在 fork 仓默认提到 parent - 状态:validated - 更新时间:2026-06-28 From 195cc48c298e15dd1b9ced45a3155609a5153155 Mon Sep 17 00:00:00 2001 From: "Ragdoll-Opus-4.6" Date: Wed, 8 Jul 2026 11:03:57 +0800 Subject: [PATCH 9/9] fix(#37): pin --repo on E1 validation table + post-merge note (cloud R6 P2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit R6 found 2 remaining unpinned gh commands after rebase to 929fd13b: - E1 Evidence Validation table: `gh pr view` without `--repo` - Post-merge worktree warning: `gh pr merge` without `--repo` Both are copyable action commands that can misroute to parent fork in fresh-clone/no-gh-resolved scenarios. Descriptive text mentions (lines 15, 200, 435) intentionally left unpinned per prior decision. [宪宪/claude-opus-4-6🐾] Co-Authored-By: Claude Opus 4.6 --- cat-cafe-skills/merge-gate/SKILL.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cat-cafe-skills/merge-gate/SKILL.md b/cat-cafe-skills/merge-gate/SKILL.md index 47c33b8294..3bf27e652a 100644 --- a/cat-cafe-skills/merge-gate/SKILL.md +++ b/cat-cafe-skills/merge-gate/SKILL.md @@ -114,7 +114,7 @@ merge-gate 执行时,在 Step 7(squash merge)**之前**,猫必须**组 | # | 检查项 | 验证方式 | 失败动作 | |---|--------|----------|----------| -| E1 | `head` === PR current HEAD | `git rev-parse HEAD` vs `gh pr view {PR_NUMBER} --json headRefOid --jq '.headRefOid'` | BLOCKED — HEAD 不一致,可能有 unpushed commit | +| E1 | `head` === PR current HEAD | `git rev-parse HEAD` vs `gh pr view {PR_NUMBER} --repo clowder-labs/clowder-ai --json headRefOid --jq '.headRefOid'` | BLOCKED — HEAD 不一致,可能有 unpushed commit | | E2 | `stale` === false | 按 `headChangeCause` 判定的活跃 review 源覆盖当前 `head`(见上方 `stale` 字段定义的完整映射表)。`nextGateOwner=author` 时(merge-ready 态),沿用最后一次 `headChangeCause` 确定的活跃源;`nextGateOwner=ci/guardian` 时,review 覆盖规则不变(CI/guardian 是额外 gate,不改变 review 覆盖链) | BLOCKED — 活跃 review 源的 SHA 过期,需要 re-review | | E3 | reviewer provenance 闭合 | 至少一个 review 源(local 或 cloud)非空且覆盖 `head`(exempt PR 无 cloud 时只看 local) | BLOCKED — 缺 review provenance | | E4 | `verdict` !== "blocked" | review 结果为 APPROVE(非 BLOCK / CHANGES_REQUESTED) | BLOCKED — reviewer 未放行 | @@ -504,7 +504,7 @@ merge 是把状态写进 main 的不可逆点(其他 session 立即读到) #### 7.5b — Post-merge:记录已合入状态(在 Step 7 merge 之后) -⚠️ **切到持有 main 的 worktree 再 commit**:`gh pr merge --squash --delete-branch` 之后你仍在 feature worktree 上。直接 commit 会落到**已合并/已删的 feature branch**(或留脏工作树让 Step 8 fail-closed abort)。而且 worktree 开发场景下 `main` 由主仓 worktree 持有——**在 feature worktree `git checkout main` 会被 git 拒绝**(ref 已被另一 worktree 占用)。所以切到持有 main 的 worktree(而非 checkout): +⚠️ **切到持有 main 的 worktree 再 commit**:`gh pr merge --repo clowder-labs/clowder-ai --squash --delete-branch` 之后你仍在 feature worktree 上。直接 commit 会落到**已合并/已删的 feature branch**(或留脏工作树让 Step 8 fail-closed abort)。而且 worktree 开发场景下 `main` 由主仓 worktree 持有——**在 feature worktree `git checkout main` 会被 git 拒绝**(ref 已被另一 worktree 占用)。所以切到持有 main 的 worktree(而非 checkout): ```bash # 找到持有 main 的 worktree(通常是主仓 cat-cafe/),cd 过去做 doc-sync: