diff --git a/.claude/hooks/user-level/session-start-recall.sh b/.claude/hooks/user-level/session-start-recall.sh index 319db602e5..9127875553 100755 --- a/.claude/hooks/user-level/session-start-recall.sh +++ b/.claude/hooks/user-level/session-start-recall.sh @@ -89,6 +89,29 @@ ${ROOT_CLUTTER} " fi +# 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 后缀),再 == 精确比较。 +ORIGIN_URL=$(git config --get remote.origin.url 2>/dev/null) +REPO_SLUG=$(echo "$ORIGIN_URL" | sed -E 's#^.+[/:]([^/:]+/[^/:]+)$#\1#; s#\.git$##') +if [ "$REPO_SLUG" = "clowder-labs/clowder-ai" ]; then + # 检测 GH_REPO 兜底是否生效 + # 砚砚 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 + 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 + WARNINGS="${WARNINGS} +⚠️ 本仓在 GitHub 是 zts212653/clowder-ai 的 fork(LL-090) +→ \`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.example b/.envrc.example new file mode 100644 index 0000000000..8113f75856 --- /dev/null +++ b/.envrc.example @@ -0,0 +1,43 @@ +# ============================================================ +# 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-090(PR Fork Base Trap)+ cat-cafe-skills/merge-gate/SKILL.md。 +# +# 启用步骤(按你的环境三选一,优先级从上到下): +# 1. direnv(**强推荐** — 进出 repo 自动 export/unset,无 cross-repo 污染): +# brew install direnv +# # shell rc 加:eval "$(direnv hook zsh)" +# cp .envrc.example .envrc +# 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 命令"的猫兜底用。 +# ============================================================ + +# 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..38d8d6606d 100644 --- a/.gitignore +++ b/.gitignore @@ -35,6 +35,9 @@ packages/web/public/vendor/ .env .env.local .env.*.local +# 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 diff --git a/cat-cafe-skills/merge-gate/SKILL.md b/cat-cafe-skills/merge-gate/SKILL.md index aa1cdc5d68..3bf27e652a 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" ``` @@ -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 未放行 | @@ -193,12 +193,22 @@ fi ### 合入方式(唯一正确做法) +> **⚠️ 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 +> (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。 +> PR #37 cloud review P1:仅 pin `gh pr create` 不够,后续 PR-number 命令同样需要。 + ```bash # 1. Push feature branch git push origin {branch} # 2. 开 PR(读 refs/pr-template.md 获取 body 模板,用 HEREDOC 填写) -gh pr create --title "feat(xxx): ..." --body "$(cat <<'EOF' +gh pr create --repo clowder-labs/clowder-ai --base main \ + --title "feat(xxx): ..." --body "$(cat <<'EOF' ... 按 refs/pr-template.md 模板填写 ... EOF )" @@ -219,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) @@ -228,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; } @@ -241,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 回调是唯一通知渠道。 @@ -319,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}" @@ -350,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 @@ -460,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}' ``` @@ -494,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: @@ -530,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 推进 | @@ -547,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 | @@ -583,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 两次实战验证)。 @@ -619,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 c43dc08642..e7b8d97ba7 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-090: 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.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 环境(兜底)**:仓内只 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 状态本身 +- 来源锚点: + - `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.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.example / .gitignore / .claude/hooks/user-level/session-start-recall.sh / PR #30 / PR #1030 / PR #37 / thread_mpz55rv7dahgk4ce