# Overthinkos Overthink Plugins Git Workflow

> git-workflow — branch-per-change, R10-gated auto-landing

- Skill: `tomevault-io/overthinkos-overthink-plugins-git-workflow` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add tomevault-io/overthinkos-overthink-plugins-git-workflow`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tomevault-io/overthinkos-overthink-plugins-git-workflow/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Productivity
- Author: tomevault-io (https://skillmd.com/u/tomevault-io)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/tomevault-io/overthinkos-overthink-plugins-git-workflow

---


# git-workflow — branch-per-change, R10-gated auto-landing

Every change to an charly-project repo follows ONE landing discipline. The **R10
pass is the sole gate**: nothing is committed, pushed, merged, or tagged on
unverified state, and once R10 passes the landing is automatic — no per-change
manual "push" step. This skill is the mechanics; CLAUDE.md "Post-Execution
Policies" carries the mandate, `/charly-internals:cutover-policy` the one-phase rule,
`/charly-build:migrate` the schema-version/tag coupling.

## Non-negotiable invariants

- **NEVER force-push.** No `git push --force`, no `--force-with-lease`, on ANY
  branch (`feat/` included) in ANY repo, ever. The whole flow is designed so a
  force push is never needed: `main` only fast-forwards; tags are add-only;
  `feat/` is pushed once at landing.
- **R10-gated.** Commit/push/merge/tag happen only after R10 PASS. A rule
  violation or R10 FAIL ⇒ none of them happen (fix in the same tree, re-run R10).
- **Zero warnings.** R10 is NOT successful while ANY warning remains — resolver
  newest-wins warnings, build, `charly box validate`, `charly check`, or deploy
  warnings. Every warning is fixed before R10 passes (a version-mismatch warning
  is cleared with `charly box reconcile`; any other warning triggers
  `/charly-internals:root-cause-analyzer` then a real fix). "Warning" is never an
  acceptable end state — it is an R10 failure (strengthens R1).
- **Atomic.** One commit per repo per cutover (the cutover-policy "one phase").
- **Tree-safety before destructive actions (R6).** Always check `git status` +
  `git stash list` before any destructive working-tree action — `git stash`
  discards in-progress work; `rm` on a tracked file is destructive. When the
  sandbox blocks an action, read the reason and find a non-destructive
  alternative — never work around it with a cleverer command.
- **Right worktree — pin ONE absolute path for the whole edit→commit→land
  sequence.** Before branching, staging, or committing, confirm the worktree you
  are driving is the SAME one your edits landed in: `git -C <path> rev-parse
  --show-toplevel` must equal the path you edited, and `git -C <path> status
  --short` must list those edits. Under symlinked or near-twin sibling worktrees —
  a parent dir that is itself a symlink (`~/Atrapub` → `~/Sync/Atrapub`), or
  look-alike names such as `…/overthink` vs `…/av-overthink` — `cd`-ing to the
  wrong sibling makes `git switch -c` + `git commit` run against a CLEAN tree and
  report "nothing to commit", silently landing nothing (or worse, work in the
  wrong repo). Never change the path spelling mid-sequence. An unexpected "nothing
  to commit" / "working tree clean" right after you edited a file is the signature
  of this mistake — STOP and re-verify `--show-toplevel` before retrying (blind
  retry is an R1 violation).
- **Check-coverage.** R10 does not pass unless the change ships the test coverage
  that PROVES its functionality (`check:` checks for new/changed layers & images,
  Go tests for `charly` code) AND the live run exercised it. A change whose new
  functionality has no test that would FAIL without it is not landable.
- **Tags only on `charly.yml` repos.** `plugins` and `pkg/arch` are tag-exempt.

## B1 — the branch-per-change loop (write access)

```bash
# sync-before-start (see B4): branch off up-to-date main
git fetch origin --prune --tags
git switch main && git merge --ff-only origin/main
git switch -c feat/<slug>            # slug = kebab summary of the change

# ... implement the whole cutover; run beds freely throughout to VERIFY
#     (verify before you change — Risk Driven Development: prove high-risk
#     assumptions on a bed first); the COMMIT is gated on the full final-code
#     live test (pasted), which runs at the end ...

# on R10 PASS, automatically and in order:
git add <only the cutover's files>   # never the in-flight state of unrelated work
git commit -m "<conventional commit>  ...  Assisted-by: Claude (<tier>)"
git push origin feat/<slug>
git switch main && git merge --ff-only origin/main   # re-sync; if main advanced, see below
git merge --ff-only feat/<slug>
git tag -a "$(date -u +v%Y.%j.%H%M)" -m "<subject>" HEAD
git push origin main --follow-tags
git branch -d feat/<slug> && git push origin --delete feat/<slug>
```

If `main` advanced between branch-start and landing, the `--ff-only` merge refuses
(that's the safety property — never a force). Rebase `feat/` onto the new `main`,
**re-run R10**, then ff-merge. `feat/` is pushed once at landing, so a
rebase-then-push collision can't normally arise; in the rare case `feat/` was
already pushed and then rebased, update the remote with
`git push --delete origin feat/<slug>` followed by a fresh push — NEVER `--force`.

## B4 — sync to upstream + prune (per repo: main, plugins, box/*)

- **Sync-before-start / before-landing.** `git fetch origin --prune --tags`; ff
  local `main` to `origin/main`. Never force-reset a diverged local `main` — if it
  cannot fast-forward, STOP + run `/charly-internals:root-cause-analyzer`.
- **Switch-to-upstream check.** Before committing, confirm `origin` is the
  canonical upstream and the branch about to merge targets the upstream `main`
  (not a stale fork/branch). On mismatch, STOP and surface it.
- **Prune merged branches.** `feat/` is deleted at landing (B1). Sweep leftovers:
  `git branch --merged main` → delete local; `git fetch --prune` drops
  remote-tracking refs deleted upstream; `git branch -r --merged origin/main` →
  `git push origin --delete` the merged remote `feat/*`. **Only ever delete
  branches confirmed `--merged`**; never `-D` (force-delete) an unmerged/abandoned
  branch without operator confirmation — it may hold unlanded work.
- **Worktree hygiene.** `git worktree list` to inventory; `git worktree prune` to
  clear stale admin entries. Remove an agent `isolation: worktree` after its
  change lands. Before reusing a long-lived worktree, ff its base to `origin/main`.

## B2 — multi-repo / multi-worktree coordination

One logical change spanning several repos uses the **same `feat/<slug>` in each**
(main, `plugins`, `box/<distro>`), so the branches correlate. R10 runs against the
**assembled superproject** (submodule pointers at the `feat/` commits) — the whole
change is verified before anything merges. Then land in **dependency order**:

1. each `box/<distro>` submodule — commit → `--ff-only` merge → tag (it has
   `charly.yml`) → push;
2. `plugins` — commit → `--ff-only` merge → push (**no tag**, no `charly.yml`);
3. the superproject — stage the now-merged submodule pointers → atomic commit →
   `--ff-only` merge → tag `main` → push.

**Submodule-pointer-bump safety (step 3) — bump AFTER the switch, then stage AND
verify.** A `git switch` / `git checkout` re-materializes each submodule at the
gitlink the *target branch* records, silently discarding an **unstaged**
working-tree pointer bump (it happens even with `submodule.recurse` unset — an
unstaged gitlink is not carried across the switch). So bumping the pointer
*before* `git switch -c feat/<slug>` — or merely `git -C <sub> checkout <new>`
without `git add` — drops it from the commit, and a `git add <sub>; git commit`
afterward stages nothing because the working tree was reset to the old pointer.
Always, in order: (a) create/switch to the landing branch FIRST; (b) THEN
`git -C <sub> checkout <new-commit>` + `git add <sub>`; (c) VERIFY it is staged —
`git diff --cached --submodule=short <sub>` must print `<old>...<new>`; (d) after
committing, confirm the commit records it — `git show --stat` lists `<sub>` and
`git ls-tree HEAD <sub>` shows `<new>`. A pointer-bump commit whose `--stat` omits
the submodule is the silent-drop failure. If it was already pushed, land a NEW
pointer-bump commit (NEVER amend/force-push). See `CHANGELOG.md` 2026-06-08 for
the incident this rule prevents.

**Attribution of the pointer-bump commit — derived from what it points at.** When
the bumped submodule commit is itself all-documentation (a skill / `*.md` edit),
the superproject pointer-bump commit IS the Documentation-only change class and
lands at `documentation reviewed`: `pre-commit-gate.sh` recurses into the
submodule's own `old..new` diff to certify it (objects must be present locally; a
bump it cannot certify is rejected). A bump that integrates submodule CODE is a
code class and takes a runtime tier, the docs riding along. So a docs-only skill
cutover lands `plugins` (the `*.md`) at `documentation reviewed`, then the
superproject pointer bump at `documentation reviewed` too — both halves honest, no
runtime tier borrowed.

This mirrors the submodules-first push order. A change developed in a git worktree
keeps its `feat/` branch in the worktree; the ff-merge targets the canonical
repo's `main`; the worktree is removed after. Concurrent worktrees on one repo
each use a distinct `feat/` slug. **For the full multi-worktree end-to-end — which
path lands `main` when it lives in ANOTHER worktree, the doc-tier `git -C`
literal-path rule, and the mandatory post-landing worktree refresh — see B7.**

## B3 — agent teams on ONE shared tree (no worktree)

When an agent team parallelizes work, **the check bed is the unit of isolation,
not a worktree**. Each teammate owns a disjoint `kind: check` bed's SOURCE files;
distinct beds get distinct container/VM/image names; the lead assigns each
disjoint host ports too (the loader does NOT check ports — an overlap fails the
second bed at deploy), and a bed pins an image → layers → files, so bed-ownership
already isolates the source files each teammate edits. **Teammates edit; a
PERSISTENT owner runs every full `charly check run <bed>`** as a `run_in_background`
task — the lead's persistent session, a background agent, or (interactive tmux) a
split-pane teammate; an in-process teammate CANNOT (its bg dies on yield).
Teammates therefore share ONE working tree on ONE `feat/<slug>` branch:

- Teammates edit their bed-scoped files in the shared tree + run short foreground
  checks (`charly check box`) — never the full `charly check run`, and **never commit or
  push**. The lead runs the full beds and owns the single atomic commit, gated on
  the consolidated full final-code bed run (B1).
- Reserve a real `git worktree` (per `isolation: worktree`) only for genuine
  **same-file** concurrency that bed-ownership does not separate — not as the
  default for team parallelism.
- **Schedule longest-pole-first.** `charly check run` has no bed-level concurrency and
  no `charly` cap — the limit is host CPU/RAM/podman. The lead runs ALL full beds as
  concurrent background tasks; order by expected DURATION, not bed count: launch
  the slow VM/desktop beds first and overlap the cheap pod beds, so wall-clock ≈
  the slowest single bed, not the sum.
- **Freeze `charly/*.go` during the bed phase.** `charly`'s stale-binary freshness guard
  gates every heavy verb the instant any `charly/*.go` is newer than `/usr/bin/charly`,
  so a teammate editing Go mid-bed-run aborts every other agent's next
  build/deploy/check. For a SHARED-CORE (Go) cutover the lead lands the core
  first, runs ONE `task build:charly`, then fans out beds with Go frozen; a BED-LOCAL
  (YAML/candy/skills) cutover has no shared binary and needs no such barrier.

## B5 — PR path (no write access) + `gh` auto-approve

Detect permission: `gh repo view --json viewerPermission`
(ADMIN/MAINTAIN/WRITE → Mode 1, else Mode 2).

- **Mode 1 — write access:** the B1 direct path (ff-merge, tag, push, delete).
- **Mode 2 — fork + PR:** on R10 PASS, ensure a fork (`gh repo fork --remote`),
  push `feat/<slug>` to the fork, then
  `gh pr create --base main --head <fork>:feat/<slug>` with a body that pastes the
  R10 evidence and ends with `*Assisted-by: Claude (<tier>)*`. The PR is the
  deliverable; never force-push, never need upstream write.
- **`gh` auto-approve/merge (maintainer-side, with approve rights):** for an open
  PR, **fetch its head, review the diff, and run R10 against it**; ONLY on R10
  PASS (and only if the change ships its check/test coverage) `gh pr review
  --approve` then `gh pr merge --rebase --delete-branch` (rebase keeps `main`
  linear, matching ff-only), then tag the new `main` HEAD. **Never a blind
  approve** — no approval/merge of unreviewed or R10-unverified code, whoever
  opened it. Required status checks / branch protection are respected, never
  bypassed, never force-merged.

## B6 — cross-repo R10 when a change is referenced via `@github`

The resolver (`EnsureRepoDownloaded`) fetches a producer repo from the REMOTE at
the pinned ref, so a producer change on a local `feat/` branch is invisible to a
consumer's R10 — **a local branch is not enough**. Staged landing (no
local-override):

1. Develop producer (A) + consumer (B) on the same `feat/<slug>`.
2. **Land the producer FIRST:** run A's own R10, ff-merge, **tag A `v<CalVer_A>`**,
   push — now an immutable, fetchable remote tag.
3. **Repoint the consumer:** `charly box reconcile` rewrites B's `@github.../A:...`
   pins to `v<CalVer_A>` (see `/charly-build:reconcile`).
4. **Authoritative consumer R10 against the real tag:** B's R10 now fetches A from
   the pushed `v<CalVer_A>` — verified against exactly what ships.
5. **Land the consumer** (ff-merge, tag B, push).
6. **New candy:** a new candy has no standalone R10 — its gate is the consuming
   image's build. A lands a **provisional** `v<CalVer_A>` (layer + `go test` /
   `charly box generate` smoke); step 4 (B's image R10 against that tag) is the real
   gate. On failure, fix A, land a **new** tag (immutable + accumulate — never
   move the old one), re-reconcile, re-run step 4.

Each repo gets ONE R10 against ITS final code — the producer against its own
change, the consumer against the producer's landed tag — and repos land
producer→consumer. Multi-level chains (A→B→C) recurse the same way.

## B7 — Multi-worktree landing + refresh (the canonical end-to-end)

This project is developed across SEVERAL git worktrees sharing one `.git` — and only
ONE worktree can have `main` checked out. Every "push + update all worktrees" follows
this EXACT ordered sequence; deviating is how the ad-hoc disasters happen. It composes
B1 (branch loop), B2 (per-repo order + pointer-bump safety), B4 (sync/prune).

**0. Pre-flight (worktree safety).** `git worktree list` → note which worktree holds
`main`. Pin ONE worktree for the whole edit→commit→land sequence; drive every step with
a **literal absolute path** `git -C /abs/path …`. NEVER a leading `cd`+`\`-continued
chain (it scopes every later command into the submodule) and NEVER a shell variable for
a path — **shell variables do NOT persist between Bash tool calls**, so a `WT=…` set in
an earlier call is EMPTY later and `git -C "$WT/plugins"` silently becomes `git -C
/plugins` (this was a real failure). Verify: `git -C /abs rev-parse --show-toplevel` ==
the path you edited AND `git -C /abs status --short` lists your edits.

**1. Sync-before-start.** `git fetch origin --prune --tags`; ff local `main` to
`origin/main` (B4).

**2. Land in dependency order, same `feat/<slug>` in every repo** (box submodules →
plugins → superproject). Per-repo mechanics = B2 + B1; pointer-bump safety = B2 step 3.
Two proven additions:
  - **plugins docs commit at `documentation reviewed`: `git -C <LITERAL-abs-plugins>
    commit …`.** RDD-proven on the live gate: a literal `-C` scopes `pre-commit-gate.sh`
    to the plugins all-docs index in ONE shot (it passes even while the superproject has
    non-doc code staged, and recurses the submodule's `old..new` diff). Do NOT use a
    `$var` (may be unset → `git diff --cached --raw failed`); do NOT use `cd plugins &&
    git commit` (the gate fires BEFORE the in-command `cd`, so it inspects the
    SUPERPROJECT index and blocks on staged code). The literal `-C` removes the old
    "empty the other index first" dance entirely.
  - **box/<distro> re-stamp** (schema-HEAD bump): edit on the submodule's own `main`;
    **gate = `charly box validate` standalone** (a version-stamp change has no build
    behavior — building proves nothing); commit, annotated tag, push.

**3. Land `main` — it lives in ONE worktree, so NEVER `git switch main` elsewhere (git
fatals "already used by worktree"). Pick ONE path:**
  - **Path A (remote-mediated, from the work worktree):** `git push origin feat/<slug>`
    → `git push origin feat/<slug>:main` (a ff of `origin/main` — the worktree-safe
    `merge --ff-only`, NEVER a force) → tag + push tag → `git -C <main-wt> merge
    --ff-only origin/main` to advance the LOCAL `main` ref (`push :main` doesn't move it).
  - **Path B (drive the main worktree by path):** `git -C <main-wt> merge --ff-only
    feat/<slug>` (guard: `git -C <main-wt> status` clean + `git merge-base --is-ancestor
    <old-main> <feat-HEAD>`) → `git -C <main-wt> push origin main --follow-tags`.
    Advances local `main` automatically; pushes no remote `feat/`.

**4. Tags: annotated only** (`git tag -a v<…> -m "<desc>" HEAD`). `--follow-tags` does
NOT push a LIGHTWEIGHT tag → verify `git cat-file -t <tag>` == `tag` AND `git ls-remote
--tags origin <tag>` is non-empty.

**5. Reconcile (when box submodules were re-stamped).** Bump the superproject GITLINKS
`+1` to the re-stamped box mains (a separate superproject commit; B2 step-3 safety) — do
**NOT** bump the `@github` build pins: they lag deliberately, `charly box reconcile`
reports "already reconciled", and bumping them pulls multi-cutover producer drift (a
separate version-adoption cutover, NOT reconciliation).

**6. Refresh EVERY worktree — PART of landing, NEVER a follow-up (R2).** For each
worktree: the one on `main` → `git -C <wt> merge --ff-only origin/main`; each other →
`git -C <wt> checkout --detach origin/main`; THEN `git -C <wt> submodule update --init
--recursive`. The Skill tool serves skills from the MAIN worktree — a stale main
worktree silently serves STALE SKILLS to sessions, so refreshing it is mandatory. (A
` M <sub>` in a worktree used only for the ff-merge is this drift, not lost work.)

**Landing gotchas (each cost real time):** the **PreToolUse pre-commit-gate fires ONCE
per Bash call, BEFORE the command runs** → a `git reset && git commit` in ONE call fails
(the reset hasn't happened yet); split into separate Bash calls. `task build:charly`
dirties `pkg/arch/PKGBUILD` (makepkg `pkgver()`) → `git -C <pkg/arch> restore PKGBUILD`.
`git merge-base --is-ancestor A B` ERRORS if B's object isn't fetched (common for a
sibling-worktree submodule) → `git fetch` first; cross-check `git ls-tree origin/main
<sub>` before concluding "DIVERGED". A `git grep -- <submodule-path>` from the
superproject is a FALSE ZERO (git grep does not cross a gitlink) → `git -C <sub> grep`
for the R5 sweep.

## CalVer tag computation

`v<YYYY.DDD.HHMM>` from the current UTC push time. Every component is fixed-width
zero-padded (4-digit year, 3-digit day-of-year, 4-digit HHMM) so tags sort
chronologically under a plain alphanumeric sort:

```bash
git tag -a "$(date -u +v%Y.%j.%H%M)" -m "<subject>" HEAD
```

ONE fresh tag per push (a repo accumulates many), immutable (only ever added),
INDEPENDENT of `charly.yml` `version:` (the schema version, bumped only by a
`MigrationStep` raising `LatestSchemaVersion()`). A YAML schema/format change does
BOTH: raise `LatestSchemaVersion()` AND mint the tag. See `/charly-build:migrate`.

## After landing — cleanliness + report

- **Working-tree cleanliness.** After commit + landing, `git status` is clean
  in every repo. Untracked files that aren't part of the cutover (test
  artifacts, build outputs) belong in `.gitignore`; if they aren't, that's its
  own immediate-next cutover, not part of this one.
- **Report format.** The final message states: what was committed (commit
  subject + hash, per repo), the confidence tier with the proof that supports
  it, what was pushed, and the pasted R10 outputs (exploratory +
  fresh-rebuild). The tier must match CLAUDE.md "AI Attribution", keyed to the
  change class (`/charly-check:check` "R10 gate by change class") — a
  Documentation-only change class commit lands at `documentation reviewed`,
  runtime classes at a runtime tier. A worked commit message:

```
Fix: Add fuse-overlayfs for container startup

Tested via overlay session on LOCAL system.

Assisted-by: Claude (fully tested and validated)
```

## If R10 fails

R10 failure is a return-to-implementation signal, not a stopping point:

1. Run `/charly-internals:root-cause-analyzer` BEFORE attempting any fix —
   blind retry is FORBIDDEN.
2. Fix in the SAME working tree — never a follow-up PR.
3. Re-run the FULL R10 from a fresh `charly update`, not just the failing
   piece — a fix that survives only the targeted re-run is a regression in
   waiting.
4. Commit only when R10 passes end-to-end on the FINAL code.

## Cross-References

- CLAUDE.md "Post-Execution Policies" — the mandate this skill operationalizes.
- `/charly-internals:cutover-policy` — one-phase, atomic-commit, R10-at-the-end.
- `/charly-build:migrate` — `version:` ↔ tag coupling, per-push tags, push order.
- `/charly-build:reconcile` — cross-repo `@github` pin alignment used by B6.
- `/charly-check:check` — the check-coverage gate (R10) every change must satisfy.
- `/charly-internals:root-cause-analyzer` — run on any R10 failure before re-trying.

## When to Use This Skill

Invoke before any `git` / `gh` action that commits, branches, pushes, merges,
tags, creates a PR, or approves/merges a PR — and whenever syncing to upstream or
pruning branches/worktrees across the main repo and its submodules.

---
> Source: [overthinkos/overthink-plugins](https://github.com/overthinkos/overthink-plugins) — distributed by [TomeVault](https://tomevault.io).
<!-- tomevault:4.0:skill_md:2026-06-15 -->

