@gao_sean/jt-push-main

push main check

G@Gao_Sean·latest·↓ 2·updated 2026-06-27·MIT
reviewgit

jt-push-main

git push origin main 之前的前置门禁. 全部通过才允许 push origin main.

声明: "正在使用 jt-push-main skill 执行 push origin main 前置检查."

适用范围

只管 push origin main. 用户 push 其它分支时本 skill 不介入 (直接正常 push). 全局规则里 "push" 默认指当前分支 — 所以只有当前在 main 且要 push 到 origin 时, 本门禁才生效.

设计理念: main 只应该装 "已完成并已归档" 的 OpenSpec change. 进行中的 change 工作应在 worktree (分支) 里做, 完成 + 归档后再合回 main. 本门禁就是把这个理念落到 push 这一步.

流程

依次执行三个 gate, 任一硬失败则报告并退出, 不要 push.

Gate 1: 场景确认 (是否 push origin main)

current_branch=$(git branch --show-current)
if [ "$current_branch" != "main" ]; then
  echo "INFO: 当前分支是 '$current_branch', 不是 main."
  echo "jt-push-main 只管 push origin main, 其它分支不介入 — 请直接 git push."
  exit 0   # 退出门禁, 让用户正常 push 自己的分支
fi
git fetch origin --quiet

不在 main → 本 skill 不适用, 退出 (exit 0, 非失败), 用户照常 push 该分支.

Gate 2: OpenSpec change 门禁 (聚焦本次 push 引入的内容)

只检查 本次 push 会带上去的 commit (origin/main..HEAD) 所触及的 active change. 对已经存在于 origin/main 的历史遗留 active change (本次未触及) 只 warn 不拦 —— 拦了也没用, 已是既成事实, 否则每次 push 都会被 main 上长期存在的 proposal 反复拦截.

change 的"是否 active(未归档)"和"完成度"直接问 openspec list --json (官方权威源, 只列 active change, 输出无 ANSI), 不解析 tasks.md 勾选 —— 摆脱对其 markdown 格式的依赖. 阶段按 completedTasks / totalTasks 判: 相等且 >0 = 完成未归档; 0<完成<总数 = 进行中; 完成=0 = proposal.

for cmd in openspec jq; do
  command -v "$cmd" >/dev/null 2>&1 || { echo "ERROR: 未找到 $cmd, 无法做 change 门禁 (需在 openspec 项目根目录, 且装有 openspec/jq)."; exit 1; }
done

blocked=0
has_remote_main=0
git rev-parse --verify origin/main >/dev/null 2>&1 && has_remote_main=1

# 本次 push 触及的 change 名字 (git diff 输出无色; --no-color 双保险防 color.ui=always 注入)
touched=""
if [ "$has_remote_main" -eq 1 ]; then
  touched=$(git diff --no-color --name-only origin/main..HEAD -- openspec/changes/ 2>/dev/null \
    | sed -n 's#^openspec/changes/\([^/]*\)/.*#\1#p' | grep -v '^archive$' | sort -u)
fi

# active(未归档) change + 完成度: 直接问 openspec CLI — 官方权威源, 输出无 ANSI, 不依赖
# tasks.md 的 markdown 勾选格式; 且只列 active change (自动排除 archive/, 本次 push 把某
# change 归档后它从 list 消失 -> 天然不拦, 无需 [ -d ] 特判).
# 注意: 用进程替换 < <(...) 而非管道 | while, 否则 blocked=1 落在子 shell 会丢失.
while IFS=$'\t' read -r name comp total; do
  [ -n "$name" ] || continue

  # 是否本次 push 引入? 远端无 main (首次 push) 时全部算引入; 否则只 git diff 命中的算.
  if [ "$has_remote_main" -eq 1 ]; then
    printf '%s\n' "$touched" | grep -Fqx "$name" || {
      echo "WARN  [$name] main 上历史遗留的 active change (非本次 push 引入) -> 不阻止, 建议另行归档清理"
      continue
    }
  fi

  if [ "$total" -gt 0 ] && [ "$comp" -eq "$total" ]; then
    echo "BLOCK [$name] 已完成但未归档 -> 先归档: openspec archive $name -y"
  elif [ "$comp" -gt 0 ]; then
    echo "BLOCK [$name] 进行中 (已完成 $comp / 共 $total) -> 先完成并归档, 或把改动挪到 worktree 再 push"
  else
    echo "BLOCK [$name] 仍在 proposal/未开始 ($comp / $total) -> 先完成实现并归档, 或把改动挪到 worktree 再 push"
  fi
  blocked=1
done < <(openspec list --json | jq -r '.changes[] | [.name, .completedTasks, .totalTasks] | @tsv')

if [ "$blocked" -eq 1 ]; then
  echo "RESULT: openspec-gate FAILED — 不允许 push origin main"
  exit 1
fi
echo "RESULT: openspec-gate PASSED"

命中 BLOCK 时的处理 (任一命中即不许 push):

  • 已完成但未归档: 引导用户 openspec archive <name> -y 归档后重新 push.
  • 进行中 / proposal: 引导用户先完成实现并归档.
  • 如果用户确实就是想 push (不想先处理 change): 引导用 jt-worktree skill 把这些改动挪到一个 worktree (分支) 里去 push, 不要 push 到 main.

Gate 3: Lint 门禁 (本项目 lint = bun run check, 底层 ultracite)

对当前工作树做硬检查. 红了先 bun run fix; 修不了就停下报告, 不要硬 push.

ultracite 已忽略 .gitignore 里的 .worktrees/, 常驻 worktree 不影响全仓 lint (实测 170 files / 0 errors), 所以不做 "看到 worktree 就降级" 的预判 —— 那会让 lint gate 在有常驻 worktree 时永远不真跑. 直接真跑:

if [ -f package.json ] && grep -q '"check"' package.json; then
  echo "运行 lint: bun run check ..."
  if ! bun run check; then
    echo "lint 红了, 尝试自动修复: bun run fix ..."
    bun run fix || true
    echo "重新检查: bun run check ..."
    if ! bun run check; then
      echo "ERROR: lint 仍未通过, 自动修复无法解决. 停下报告, 不要 push."
      exit 1
    fi
    echo "RESULT: lint-gate PASSED (经 bun run fix 修复)"
    echo "NOTE: bun run fix 可能改动了文件, 见下方处理"
  else
    echo "RESULT: lint-gate PASSED"
  fi
fi

如果 bun run fix 改动了文件 (git status --porcelain 非空且来自 fix): 不要带着未提交的 fix 直接 push —— 那样推上去的代码仍是红的. 停下, 让用户 review 这些 fix 并 commit 后, 重新走一遍本门禁 再 push. 不要自动 commit (避免意外提交).

Gate 4: 执行 push

三个 gate 全部通过 (无 BLOCK / 无未提交的 lint fix), 才允许:

git push origin main

报告

全部通过并 push 成功:

## push origin main 成功

- openspec-gate: PASSED (本次引入的 change 均已归档 / 无引入)
- lint-gate: PASSED
- 已 push: origin main

被门禁拦截:

## push origin main 被拦截

- 失败 gate: <openspec-gate / lint-gate>
- 命中项:
  - <BLOCK / ERROR 行>
- 建议:
  - <归档 / 完成实现 / 挪到 worktree / 修 lint 后重试>

红线

永远不要:

  • 在 Gate 2 命中 BLOCK (本次 push 引入了未归档的 active change) 时 push origin main
  • 在 bun run check (含 bun run fix 后复检) 仍红时 push
  • 带着 bun run fix 产生的未提交改动直接 push (会把红的代码推上去)
  • 因 main 上历史遗留的 active change 而硬拦 push (那些只 warn)
  • 介入 push origin main 以外的任何分支

Featured in 0 collections

See how others combine this skill with their stack

Not yet featured in any collection.

No reviews yet

Use it and be the first to leave a review

Version history

v0.0.0-202606271035latest
2026-06-27