internals/skills/git-workflow/SKILL.md
Use when committing, branching, pushing, merging, tagging, creating PRs, or approving/merging PRs with gh — the feat/-branch, R10-gated, never-force-push landing workflow across the main repo + the plugins submodule + box/<distro> submodules. Covers sync-to-upstream, branch/worktree pruning, the fork+PR path for contributors without write access, and cross-repo @github landing order.
npx skillsauth add overthinkos/overthink-plugins git-workflowInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
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.
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.charly box validate, charly eval, 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).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.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).eval: 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.charly.yml repos. plugins and pkg/arch are tag-exempt.# 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.
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.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.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.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.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:
box/<distro> submodule — commit → --ff-only merge → tag (it has
charly.yml) → push;plugins — commit → --ff-only merge → push (no tag, no charly.yml);--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.
When an agent team parallelizes work, the eval bed is the unit of isolation,
not a worktree. Each teammate owns a disjoint kind: eval 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 eval 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:
charly eval box) — never the full charly eval 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).git worktree (per isolation: worktree) only for genuine
same-file concurrency that bed-ownership does not separate — not as the
default for team parallelism.charly eval 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.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/eval. 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.gh auto-approveDetect permission: gh repo view --json viewerPermission
(ADMIN/MAINTAIN/WRITE → Mode 1, else Mode 2).
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 eval/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.@githubThe 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):
feat/<slug>.v<CalVer_A>,
push — now an immutable, fetchable remote tag.charly box reconcile rewrites B's @github.../A:...
pins to v<CalVer_A> (see /charly-build:reconcile).v<CalVer_A> — verified against exactly what ships.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.
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:
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.
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./charly-eval:eval "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)
R10 failure is a return-to-implementation signal, not a stopping point:
/charly-internals:root-cause-analyzer BEFORE attempting any fix —
blind retry is FORBIDDEN.charly update, not just the failing
piece — a fix that survives only the targeted re-run is a regression in
waiting./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-eval:eval — the eval-coverage gate (R10) every change must satisfy./charly-internals:root-cause-analyzer — run on any R10 failure before re-trying.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.
tools
OpenCharly CLI (charly) binary installed into container/VM images for in-container use. Use when working with charly binary deployment inside containers, native D-Bus support, or the full charly toolchain (charly binary + virtualization + gocryptfs + socat).
development
Operator CachyOS workstation profile — a kind:local template + target:local deploy that installs the full dev stack (30 candies) onto a CachyOS host via ShellExecutor. Lives in the overthinkos/cachyos submodule. MUST be invoked before editing or applying the charly-cachyos workstation profile.
tools
Fedora box with the full charly toolchain using shared candies. Rootless-first — runs as uid=1000 with passwordless sudo (no root, no cap_add: ALL). Same candy list as charly-arch. Includes NVIDIA GPU runtime. MUST be invoked before building, deploying, configuring, or troubleshooting the charly-fedora box.
tools
Arch Linux box with the full charly toolchain. Rootless-first — runs as uid=1000 with passwordless sudo (no root, no cap_add: ALL). Composes /charly-coder:charly-mcp so the box is reachable as an MCP gateway on port 18765. NVIDIA GPU runtime composed in. MUST be invoked before building, deploying, configuring, or troubleshooting the charly-arch box.