git-ops · diff
git:20260729.5e912cc to git:20260812.29886c9
33 added, 159 removed. Audit A to A.
---
name: git-ops
description: Commit, push, safely rebase an existing branch, create/read/update/close a pull/merge request (GitHub PR or GitLab MR), cut a feature branch, create/resume a managed worktree, or release. Triggers — gcam / gcamp / gcampr / rebase / 解决冲突 / 提 PR / 提 MR / pull request / merge request / 看 PR / 看 MR / 关 PR / 关 MR / checkout / worktree / 切新分支 / 起新分支 / 发版.
---
- The umbrella for devloop's git + code-review workflow. All git goes through one runner
- (`lib/gitcmd.py`); all code-review hosting through one facade (`lib/forge/`),
- which picks GitHub or GitLab per-repo from the origin remote. You call the scripts below —
- never raw `git commit/push` (the guards intercept those, and the scripts encode the case
- logic + self-narrate a `PLAN:` banner you can trust), and **never hand-roll `curl`/`glab`/`gh`
- against the forge API** — that one facade backs both script surfaces below (gcampr *raises* an
- MR; `pr` *inspects/manages* an existing one) and resolves the token from config, so there's no
- credentials file to hunt for.
-
- Paths use `<PLUGIN_ROOT>` → `${CLAUDE_PLUGIN_ROOT}` on Claude Code; `${PLUGIN_ROOT}` on Codex.
-
- ## Operating principles
-
- The scripts are the executable policy; this skill supplies the decision model. Inspect Git
- state freely, but do not improvise a competing mutation sequence when devloop owns that
- operation.
-
- 1. **Resolve intent before touching HEAD.** `--branch` means new work and cuts from a freshly
- fetched `origin/<target>`; omitting it means continue the current branch. Stacking is never
- inferred from the current checkout — it requires an explicit `--base`.
- 2. **Treat an existing identity as work to preserve.** Reusing a branch or worktree tag means
- resume it as-is, not “make it current.” Checkout must not silently rebase or reset existing
- work. Updating its baseline is a separate, explicit history operation.
- 3. **Revalidate shared state at the mutation boundary.** Injected branch/PR context is useful
- guidance, but the owning script's live fetch/forge/lease check is authoritative. If fresh
- state cannot be obtained, or changed underneath the transaction, stop rather than guess.
- 4. **Scope mutations to the user's change.** Target one repo and an explicit file set when
- untracked or unrelated files exist. Never trade convenience for a repo-wide `git add -A`;
- never let the current cwd silently choose another subproject.
- 5. **Rewrite history only as a resumable transaction.** Capture the old remote SHA before a
- rebase and publish with an exact `force-with-lease`. Never raw force-push, and never guess a
- reset target after the safe abort point has passed.
- 6. **Use hard gates only where no legitimate editing path exists.** Protected and inactive
- branches are denied. In-flight branches remain editable for review fixes, but new work must
- move to a new branch.
- 7. **Keep ownership boundaries explicit.** Commit/push/PR creation, checkout/worktree lifecycle,
- PR management, rebase, and release have separate owning entry points. Merge remains a human
- action. Trust and surface each owner's `PLAN:` or equivalent result.
-
- ## Commit / push / PR
-
- | Intent | Script |
- |--------|--------|
- | commit only | `bash <PLUGIN_ROOT>/scripts/smart_gcam.sh --message "<msg>" [...]` |
- | commit + push | `bash <PLUGIN_ROOT>/scripts/smart_gcamp.sh --message "<msg>" [...]` |
- | commit + push + PR/MR | `bash <PLUGIN_ROOT>/scripts/smart_gcampr.sh --message "<msg>" [...]` |
-
- **Message**: one-line / simple → inline single-quoted (`--message 'fix: …'`). Multi-line, or
- containing quotes / `$` / backticks → use the **Write tool** to fully overwrite canonical
- `<repo>/.devloop/commit_msg` (gitignored one-shot scratch), without reading/editing/patching its
- previous contents, and pass `--message-file <path>` (alias `-F`; `-F -` reads stdin). The script
- removes this canonical file on success and retains it on failure for retry. This avoids shell
- escaping, mirroring `git commit -F` / `gh --body-file`; `--title` defaults to the first line.
-
- Shared flags: `--repo <name|path>` (target repo; no `cd` prefix needed — default is
- cwd's repo, falling back to the workspace's last-active repo), `--branch <name>`
- (required when the context shows **PROTECTED** / **INACTIVE** — the script cuts a
- fresh branch off `origin/<target>`), `--target <branch>`, `--files a,b` (explicit
- staging, auto-rebased onto the repo root; else tracked modifications — never
- `git add -A`), `--title "<PR title>"` (gcampr only). Trust the `PLAN:` banner; on
- `✗`, fix per the message (usually add `--branch`) and retry. The `✗` for an
- **INACTIVE / merged-or-closed** branch is computed from a live, authoritative forge poll and
- quotes the MR's number / state / sha — so it's ground truth even right after you created the MR
- (a colleague can merge it in seconds); add `--branch` and re-run.
-
- ## Checkout / managed worktree
-
- Use ordinary `cd` to select an existing repo or checkout. When another session needs an isolated
- checkout, use the managed helper instead of raw `git worktree add`:
-
- ```
- python3 <PLUGIN_ROOT>/scripts/checkout.py <repo> --worktree <short-unique-tag>
- ```
-
- A new tag creates `worktree-<tag>` from a freshly fetched `origin/<target>` and refuses creation
- if that baseline cannot be refreshed. An existing worktree path or retained `worktree-<tag>`
- branch is resumed without rebase/reset. Reuse the same tag only to continue that work; choose a
- new tag for independent work from the latest target. Baseline updates to retained work are
- explicit Git operations, never a side effect of checkout.
-
- ## Rebase an existing PR/MR branch
-
- Rebase is its own resumable transaction — do **not** route it through gcamp/gcampr and do not
- issue a raw force push. `start` captures the exact current remote source SHA *before* rewriting
- history; `finish` uses that saved SHA in an explicit `--force-with-lease`, so a colleague's
- intervening push fails closed instead of being overwritten.
-
- ```
- bash <PLUGIN_ROOT>/scripts/smart_rebase.sh start --repo <name|path> [--target <branch>]
- # resolve conflicts and git add the resolved files
- bash <PLUGIN_ROOT>/scripts/smart_rebase.sh continue --repo <name|path> # repeat if needed
- # run the relevant tests before publishing
- bash <PLUGIN_ROOT>/scripts/smart_rebase.sh finish --repo <name|path>
- ```
-
- `--target` defaults to the open PR/MR's target, then the repo trunk. `start` and `continue`
- treat conflicts as an expected paused state and print the next action. `finish` publishes the
- already-rewritten commits, so it intentionally takes **no `--message`**. Its atomic push uses
- `--force-with-lease=refs/heads/<branch>:<saved-old-sha>`.
-
- Recovery/inspection: `smart_rebase.sh status --repo …`; while Git is still rebasing,
- `smart_rebase.sh abort --repo …` runs `git rebase --abort` and clears the saved lease. After a
- rebase has completed, abort refuses rather than guessing at a destructive reset target.
-
- ## 后台 code-review(自动,无需你操作)
-
- 启用了 `review` 的仓,commit 后 commit_flow 会**自动 detach 起后台 ocr review**(PLAN 出
- `review: launched in background`)——**你不用起任何东西**。它跑完写 `.devloop/review.json`,
- 结果**下一轮**经注入上下文的 `Review:` 行浮现(`running` / `N finding(s)` / `clean`)。
-
- 看到 `Review: N finding(s)` 时:可读 `.devloop/review.json` 把问题按优先级(High/Medium/Low)
- **简明通报**——这是「递信息」,**不打断 / 不挟持 session 的后续动作**,默认只通报不动手,仅
- 用户明确要才修。review 端到端 advisory(不挡 commit、不夺控制权),从不代替人 merge。完整
- 契约见 [`docs/code-review.md`](../../docs/code-review.md)。
-
- ## Inspect / manage a PR/MR — the `pr` CLI
-
- One provider-neutral, config-driven surface for **inspecting / managing an existing** PR/MR
- (token from env < `~/.devloop/config.json` < nearest `.devloop/config.json`). Raising a new one
- is gcampr's job, above.
+ Use devloop's scripts as the executable policy. Inspect Git state freely, but do not replace an
+ owned workflow with raw `git commit/push`, `git worktree add`, force-push, or hand-written
+ `curl`/`glab`/`gh` forge calls.
- ```
- <PLUGIN_ROOT>/scripts/python <PLUGIN_ROOT>/scripts/pr.py show <number|url> # state/branches/merge-readiness/comments
- <PLUGIN_ROOT>/scripts/python <PLUGIN_ROOT>/scripts/pr.py list [--limit N] [--branch B]
- <PLUGIN_ROOT>/scripts/python <PLUGIN_ROOT>/scripts/pr.py update <number> --title "..." --description "..." --target-branch <b>
- <PLUGIN_ROOT>/scripts/python <PLUGIN_ROOT>/scripts/pr.py close <number> # close without merging
- <PLUGIN_ROOT>/scripts/python <PLUGIN_ROOT>/scripts/pr.py findings <number> [--pending] # findings + 其 ccr:label verdict
- <PLUGIN_ROOT>/scripts/python <PLUGIN_ROOT>/scripts/pr.py reply <number> <comment-id> "<body>" # 回到某 comment 的线程
- ```
+ `<PLUGIN_ROOT>` maps to `${CLAUDE_PLUGIN_ROOT}` on Claude Code and `${PLUGIN_ROOT}` on Codex.
- `findings` / `reply` 是打标闭环的读写两半;判定纪律(必须对照真实代码求证、四档词表)在
- `label-review` skill,别绕开它自己编标准——那会让 ground truth 退化成"模型认同模型"。
+ ## Before mutation
- There is no `pr create`: `pr` only ever operates on an MR that already exists and never touches
- your working tree. Opening a new one is a commit+push transaction under the branch/staging gates
- — that's gcampr, above.
+ Resolve the task's intent and the live branch/PR state before changing HEAD or files. The current
+ checkout is a work site, not automatically the correct baseline for a new task.
- ## 发版 — the `release` CLI
+ | State | Meaning and action |
+ |---|---|
+ | `PROTECTED` | Trunk or release branch. Analyze it only when it is the intended baseline; create a feature branch before changes. |
+ | `HEALTHY` | Active feature branch. Continue only when the task belongs to this branch. |
+ | `IN-FLIGHT` | PR/MR open. Keep it for fixes to that proposal; new work needs a fresh branch. |
+ | `INACTIVE` | PR/MR merged/closed; historical branch. Do not use it as the current-state baseline or edit it. |
- Cut a versioned release over the same forge facade (GitHub Release / GitLab Release). The tag is
- created **server-side** — no `git push --tags`, no working tree, no push guard in the way; and no
- `--target <sha>` to mistype (a mistyped sha shipped a broken release before this existed).
+ Release/version is a branch role, not another lifecycle state. Use one as the baseline only when
+ the task explicitly targets that version.
- ```
- <PLUGIN_ROOT>/scripts/python <PLUGIN_ROOT>/scripts/release.py create <version> [--target <ref>] [--title "..."] [--notes "..." | --notes-file <path>]
- <PLUGIN_ROOT>/scripts/python <PLUGIN_ROOT>/scripts/release.py latest # the current published release
- ```
+ ## Mutation rules
- - `<version>` must be **semver** (`vX.Y.Z`) and strictly greater than the last release — the CLI
- refuses a non-increment before calling the API.
- - `--target` defaults to the repo's **trunk branch name**, so the forge tags that branch's current
- remote tip. Pass a branch/sha/tag only to release off something else.
- - **Notes**: for anything beyond a one-liner, compose them yourself and pass `--notes-file <path>`
- (write it with the Write tool — no shell escaping; `-` reads stdin). With no `--notes`, a plain
- changelog is auto-drafted from PRs/MRs merged since the last release — a fallback, not a substitute
- for hand-written notes. `--title` defaults to the version.
- - Shares `--repo <name|path>` with the scripts above. Since the tag lands on the remote, `git fetch
- --tags` locally afterward if you need it in the working copy.
+ 1. Use `--branch` for new work; it cuts from freshly fetched `origin/<target>`. Omit it only to
+ continue the current branch. Stacking requires explicit `--base`.
+ 2. Preserve existing branches and worktrees as-is. Rebase/reset is a separate explicit operation.
+ 3. Revalidate forge, remote, and lease state at the mutation boundary; injected context is guidance,
+ while the owning script's live check is authoritative.
+ 4. Scope staging to the user's files. Never use repo-wide `git add -A`, and never let an ambiguous
+ workspace cwd silently select another repo.
+ 5. Trust and surface each script's `PLAN:` result. Stop on failure instead of improvising a bypass.
+ Merge remains a human action.
- This is **not** part of gcampr: releasing is a low-frequency, working-tree-free action, so it's a
- peer of `pr` (forge-only), not a step in the commit→push→MR transaction.
+ ## Routes
- ## Branch / PR awareness
+ - Commit, push, or create/reuse a PR/MR → read [[references/commit-flow|commit flow]].
+ - Create or resume an isolated checkout → read [[references/worktree|managed worktrees]].
+ - Rebase an existing PR/MR branch → read [[references/rebase|safe rebase]].
+ - Inspect, update, close, or reply on a PR/MR → read [[references/pr-management|PR/MR management]].
+ - Create or inspect a release → read [[references/release|release flow]].
- The injected `.devloop` context already carries the current branch's state, whether it's
- protected, and a **Recent PRs** digest (the monitor keeps `prs` fresh; GitHub repos show
- `PR #`, GitLab repos `MR !`). Read that before acting instead of re-querying git. The
- branch's own PR is marked `*` in the digest; "INACTIVE" means its PR/MR is merged/closed —
- cut a new branch before more edits.
+ Commit-triggered code review is advisory and automatic. Report a surfaced result without taking
+ over the session; use the `label-review` skill only when findings need verdict labels.