Skip to content

Latest commit

 

History

History
486 lines (360 loc) · 23 KB

File metadata and controls

486 lines (360 loc) · 23 KB

Usage Guide

Branch Setup (one-time)

CGW uses a two-branch model. Create development before starting work:

git checkout -b development
git push -u origin development

Note: git push -u origin development above is a one-time bootstrap exception — CGW isn't configured yet at this point so the wrapper scripts aren't available. All subsequent pushes should use ./scripts/git/push_validated.sh.

Keep main as the GitHub default branch. Charlie reads its config from the default branch.


Commit Message Format

Conventional commit format (enforced by commit_enhanced.sh):

feat: add user authentication
fix: resolve memory leak in pipeline
docs: update API reference
chore: bump dependencies
test: add unit tests for parser
refactor: extract validation logic
style: fix formatting
perf: optimize image resizing

Add project-specific prefixes via CGW_EXTRA_PREFIXES="cuda|tensorrt" in .cgw.conf. Branches that target another project with its own message style (e.g. an upstream PR branch) can skip this check entirely via CGW_FREEFORM_MESSAGE_BRANCHES="up/*". The source, target, and any protected branch are never exempted this way, even by a glob as broad as "*".

Subject Line Length Rules (50/72 Rule)

commit_enhanced.sh enforces Pro Git commit guidelines on the subject line (the text following the prefix):

  • Soft length (50 characters): Subjects exceeding CGW_COMMIT_SUBJECT_SOFT_LEN=50 display an advisory tip.
  • Hard length (72 characters): Subjects exceeding CGW_COMMIT_SUBJECT_HARD_LEN=72 block the commit (prompting confirmation in interactive mode or aborting non-interactively) when CGW_ENFORCE_SUBJECT_LENGTH=1. Set CGW_ENFORCE_SUBJECT_LENGTH=0 to make this advisory-only.

Common Operations

Commit with lint validation

# Auto-detect .venv
./scripts/git/commit_enhanced.sh "feat: add feature"

# Skip .venv (use system lint tool)
./scripts/git/commit_enhanced.sh --no-venv "feat: add feature"

# Non-interactive (CI/CD, Claude Code, Antigravity)
./scripts/git/commit_enhanced.sh --non-interactive "feat: add feature"

# Stage only specific paths, then commit
./scripts/git/commit_enhanced.sh --only src/foo.py --only src/bar.py "fix: narrow fix"

# Commit pre-staged files only (skip auto-staging)
./scripts/git/commit_enhanced.sh --staged-only "fix: pre-staged only"

# Force-stage all tracked changes (override pre-staged respect)
./scripts/git/commit_enhanced.sh --all "chore: bulk update"

# Skip lint checks (all)
./scripts/git/commit_enhanced.sh --skip-lint "feat: add feature"

# Skip markdown lint only
./scripts/git/commit_enhanced.sh --skip-md-lint "docs: update readme"

# Force interactive mode even without a TTY
./scripts/git/commit_enhanced.sh --interactive "feat: review before commit"

Staging behaviour (non-interactive):

  • Pre-staged files exist + unstaged changes exist → commits pre-staged only (warns about skipped files). Use --all to override.
  • Nothing pre-staged → auto-stages all tracked changes (legacy behaviour).
  • --only <path> → resets index, stages listed paths only.

Staged-Blob Congruence Guard:

commit_enhanced.sh validates code and markdown on the working tree, but git commit records index blobs. If a staged file's blob diverges from the disk content that was validated (e.g. from an un-staged format auto-fix or partial git add -p), the congruence guard fails closed (CGW_ALLOW_STAGED_DIVERGENCE=0) rather than committing unvalidated code. Set CGW_ALLOW_STAGED_DIVERGENCE=1 only if you explicitly intend to record divergent staged blobs. See ADR-0001.

Merge to target branch (direct)

./scripts/git/merge_with_validation.sh --dry-run          # preview
./scripts/git/merge_with_validation.sh --non-interactive  # execute

Merge / cherry-pick / PR with custom branch pair

All four promotion scripts accept --source/--target to override the configured branch pair for a single invocation without modifying .cgw.conf:

# Merge hotfix → release branch (not the usual development → main)
./scripts/git/merge_with_validation.sh --source feature/hotfix --target release/1.2 --dry-run
./scripts/git/merge_with_validation.sh --source feature/hotfix --target release/1.2 --non-interactive

# Cherry-pick to a release branch
./scripts/git/cherry_pick_commits.sh --source feature/hotfix --target release/1.2 --commit abc1234

# Docs-only merge to a custom target
./scripts/git/merge_docs.sh --source feature/hotfix --target release/1.2 --non-interactive

# Open PR for a non-default pair
./scripts/git/create_pr.sh --source feature/hotfix --target release/1.2 --dry-run

The overrides are ephemeral — they do not mutate CGW_SOURCE_BRANCH / CGW_TARGET_BRANCH in config.

Create PR (triggers Charlie CI + GitHub Actions)

./scripts/git/create_pr.sh --dry-run          # preview title + commits
./scripts/git/create_pr.sh                    # interactive — confirm title
./scripts/git/create_pr.sh --non-interactive  # accept auto-generated title
./scripts/git/create_pr.sh --draft            # open as draft (skip auto-review)

Requires: gh CLI installed and authenticated (gh auth login). Set CGW_MERGE_MODE="pr" in .cgw.conf to use PRs by default.

Merge PR

./scripts/git/merge_pr.sh 42                    # merge commit (keeps the feature revertable as a unit)
./scripts/git/merge_pr.sh 42 --dry-run          # validate and preview only
./scripts/git/merge_pr.sh 42 --retarget 43      # then retarget stacked PR #43 onto 42's base
./scripts/git/merge_pr.sh 42 --squash --allow-non-merge   # flattens the PR; explicit ack required

--retarget is for stacked PRs in repos that don't auto-delete merged branches: it is validated before the merge (PR 43's base must be PR 42's head) and run right after it. The head branch is never deleted unless you pass --delete-branch. Undo a merge with ./scripts/git/rollback_merge.sh --revert --target <merge-sha> (the SHA is printed on success).

Push

./scripts/git/push_validated.sh               # with lint check
./scripts/git/push_validated.sh --skip-lint   # skip all lint
./scripts/git/push_validated.sh --skip-md-lint  # skip markdown lint only
./scripts/git/push_validated.sh --skip-typecheck  # skip typecheck only (blocking otherwise)
./scripts/git/push_validated.sh --dry-run     # preview
./scripts/git/push_validated.sh --no-venv     # use system lint tool (no .venv)
./scripts/git/push_validated.sh --branch hotfix/1.2  # push a different branch
./scripts/git/push_validated.sh --pushed-only # gate the committed branch, not the working tree
./scripts/git/push_validated.sh --worktree    # gate the working tree (default)

Pushed-only gate. By default the pre-push lint/typecheck runs against your working tree, so uncommitted edits can make a push pass or fail. With --pushed-only (or CGW_PUSH_LINT_SCOPE=pushed) the gate extracts the committed branch into a throwaway directory: typecheck covers the whole snapshot, lint/format/markdown only the files the push publishes. It calls check_lint.sh --ref <rev> [--base <rev> | --unpushed], which you can also run directly. check_lint.sh exits 0 (ok), 1 (lint/markdown), 2 (typecheck; never overridable) or 3 (the snapshot could not be set up, so no checks ran; fatal). Known gaps are listed in KNOWN_ISSUES.

Post-Push CI Verification Gate:

When operating through an AI agent harness (Claude Code or Antigravity), every push performed triggers the CI verification gate (CGW_CI_VERIFY=1). The agent automatically subscribes to and monitors triggered GitHub Actions or Charlie CI runs until they finish, automatically diagnosing, fixing, and retrying failures up to CGW_CI_MAX_FIX_ROUNDS=3. See CI Setup.

Sync with remote

./scripts/git/sync_branches.sh              # sync current branch
./scripts/git/sync_branches.sh --all        # sync both source and target branches
./scripts/git/sync_branches.sh --branch main  # sync a specific branch
./scripts/git/sync_branches.sh --dry-run    # preview (fetch only, no merge)
./scripts/git/sync_branches.sh --prune      # also remove stale remote-tracking refs

sync_branches.sh protects any skip-worktree local file (see CGW_LOCAL_FILES) that has genuinely diverged from HEAD on disk: it surfaces the divergence instead of a false "clean", backs up and resets the file to HEAD so pull --rebase can proceed, then restores the local bytes, re-applies the bit, and prints the upstream diff so new shared content can be reconciled by hand. No-op when nothing has diverged.

Branches listed in CGW_PROTECTED_BRANCHES (default: the target branch) sync with git pull --ff-only; if one has diverged from its remote the script refuses rather than rebasing it (see docs/adr/0004). All other branches use rebase.

Rollback a merge

./scripts/git/rollback_merge.sh                           # interactive (hard reset)
./scripts/git/rollback_merge.sh --revert                  # safe revert (preserves history, no force-push)
./scripts/git/rollback_merge.sh --non-interactive         # auto-pick latest pre-merge tag, only if it equals HEAD^1; else refuses
./scripts/git/rollback_merge.sh --target pre-merge-20260101_120000-12345

Cherry-pick

./scripts/git/cherry_pick_commits.sh                   # interactive
./scripts/git/cherry_pick_commits.sh --commit abc1234  # non-interactive
# Partial pick: only files matching the pathspecs (repeatable --only)
./scripts/git/cherry_pick_commits.sh --commit abc1234 --only src/a.py --only docs/

Stash work in progress

./scripts/git/stash_work.sh push "wip: half-done refactor"
./scripts/git/stash_work.sh list
./scripts/git/stash_work.sh pop
./scripts/git/stash_work.sh apply stash@{1}   # apply without removing

Create a release

./scripts/git/create_release.sh v1.2.3            # annotated tag only
./scripts/git/create_release.sh v1.2.3 --push     # tag + push (triggers release.yml)
./scripts/git/create_release.sh v1.2.3 --sign --push  # GPG/SSH-signed tag + push
./scripts/git/create_release.sh v1.2.3 --dry-run  # preview
./scripts/git/create_release.sh archive/pre-rewrite --allow-non-semver  # non-semver archive tag (release.yml fires only on v*)

Enable signing globally in .cgw.conf: CGW_SIGN_TAGS=1. Requires a GPG or SSH signing key configured in git (gpg.signingKey / gpg.format=ssh). Verify a tag with: git tag -v v1.2.3.

Configure .gitattributes (Python, TouchDesigner, GLSL)

./scripts/git/setup_attributes.sh --dry-run   # preview
./scripts/git/setup_attributes.sh             # write .gitattributes

Clean build artifacts

./scripts/git/clean_build.sh                  # dry-run (safe preview)
./scripts/git/clean_build.sh --execute        # actually delete
./scripts/git/clean_build.sh --td --execute   # TouchDesigner artifacts (plus common junk and any auto-detected categories)

Repository health check

./scripts/git/repo_health.sh                  # integrity, size, large files
./scripts/git/repo_health.sh --gc             # also run garbage collection
./scripts/git/repo_health.sh --large 5        # report files >5MB

Undo last commit / unstage / amend

./scripts/git/undo_last.sh commit                            # undo last commit, keep changes staged
./scripts/git/undo_last.sh unstage src/file.py               # remove file from staging area
./scripts/git/undo_last.sh discard src/file.py               # discard working-tree changes (irreversible)
./scripts/git/undo_last.sh amend-message "fix: correct msg"  # rewrite last commit message

undo_last.sh commit creates a pre-undo-commit-<timestamp>-<pid> backup tag first; unstage, discard and amend-message do not.

Branch cleanup

./scripts/git/branch_cleanup.sh                              # dry-run preview (safe default)
./scripts/git/branch_cleanup.sh --execute                    # delete merged local branches
./scripts/git/branch_cleanup.sh --execute --remote           # also prune stale remote-tracking refs
./scripts/git/branch_cleanup.sh --tags --execute             # also remove old backup tags
./scripts/git/branch_cleanup.sh --tags --older-than 30 --execute   # only backup tags older than 30 days

Safe rebase

./scripts/git/rebase_safe.sh --onto main              # rebase current branch onto main
./scripts/git/rebase_safe.sh --squash-last 3          # interactive squash of last 3 commits
./scripts/git/rebase_safe.sh --squash-last 3 --autosquash  # auto-apply fixup!/squash! prefixes
./scripts/git/rebase_safe.sh --abort                  # abort in-progress rebase
./scripts/git/rebase_safe.sh --continue               # continue after resolving conflicts

Creates a backup tag (pre-rebase-<timestamp>-<pid>) before rebasing. The installed pre-rebase hook also enforces this at the git level — if commits are already pushed, the rebase is refused unless CGW_ALLOW_REBASE_PUBLISHED=1 is set (for controlled force-push workflows).

Bisect a bug

# Automated: find first-bad commit using a test script
./scripts/git/bisect_helper.sh --good v1.0.0 --run "bash tests/smoke_test.sh"

# Manual: guided interactive bisect
./scripts/git/bisect_helper.sh --good v1.0.0
# → git bisect good / git bisect bad after each checkout

./scripts/git/bisect_helper.sh --abort   # stop in-progress bisect session

Generate changelog

./scripts/git/changelog_generate.sh                          # since latest semver tag → stdout
./scripts/git/changelog_generate.sh --from v1.0.0            # since specific tag
./scripts/git/changelog_generate.sh --from v1.0.0 --output CHANGELOG.md
./scripts/git/changelog_generate.sh --from v1.0.0 --format text  # plain text

# Cutting a release before the tag exists: --version supplies the heading
# (git describe can't find the tag yet, so it would otherwise fall back to a hash).
./scripts/git/changelog_generate.sh --from v1.0.0 --version v1.1.0 --output CHANGELOG.md

# --prepend stacks the new section above CHANGELOG.md's existing content instead of
# overwriting it, building a cumulative history. Refuses if that heading is already present.
./scripts/git/changelog_generate.sh --from v1.0.0 --version v1.1.0 \
  --output CHANGELOG.md --prepend

Recover lost commits

Git's reflog records every time a ref moves; git fsck surfaces unreachable objects when the reflog is gone. recover.sh wraps both:

./scripts/git/recover.sh reflog                              # show HEAD reflog with restore hints
./scripts/git/recover.sh reflog --limit 50 --ref main       # different ref
./scripts/git/recover.sh show HEAD@{3}                      # inspect a specific entry or SHA
./scripts/git/recover.sh dangling                           # git fsck --full: find unreachable commits
./scripts/git/recover.sh restore abc1234 --branch recovered/lost-work  # restore as a new branch

restore creates a pre-recover-<timestamp>-<pid> backup tag before touching any ref. All other subcommands are read-only.

Manage linked worktrees

Linked worktrees let you check out multiple branches simultaneously in separate directories — useful for hot-fixes without stashing:

./scripts/git/worktree_manage.sh list                            # show all worktrees
./scripts/git/worktree_manage.sh add ../hotfix hotfix/urgent     # add linked worktree (creates branch)
./scripts/git/worktree_manage.sh add ../review existing-branch   # check out existing branch
./scripts/git/worktree_manage.sh add ../part2 feat/part-2 feat/part-1  # new branch starting at <base>
./scripts/git/worktree_manage.sh link                            # link CGW tooling into the current worktree
./scripts/git/worktree_manage.sh remove --execute ../hotfix      # remove worktree link
./scripts/git/worktree_manage.sh prune                           # dry-run: show stale admin files
./scripts/git/worktree_manage.sh prune --execute                 # remove stale admin files

remove and prune default to dry-run; pass --execute to apply.

scripts/git/ and .githooks/ are commonly gitignored, so git worktree add — which only checks out tracked content — leaves a linked worktree without CGW's scripts or hooks. Any CGW-installed hook now detects this and fails closed (it will not silently skip its check), printing a CGW not found error instead of running. Fix it with:

./scripts/git/worktree_manage.sh link ../hotfix    # from the main worktree, by path
# From inside the linked worktree itself, ./scripts/git/ isn't there to run --
# invoke the main worktree's copy instead (git worktree list's first entry is
# always the main worktree):
"$(git worktree list --porcelain | head -1 | cut -d' ' -f2-)/scripts/git/worktree_manage.sh" link

This writes a real scripts/git/ directory in the linked worktree containing one small shim per public script. Each shim finds the main worktree at run time and execs its copy, so both worktrees share one set of tooling while the current directory (and so the project root, config and logs) stays the worktree's. The libraries and .githooks are not copied — hooks and install_hooks.sh fall back to the main worktree. It's idempotent (re-running refreshes the shims) and refuses to overwrite a real directory that isn't its own shim directory. worktree_manage.sh add runs link automatically for newly created worktrees.

Earlier CGW versions linked scripts/git and .githooks with a symlink / NTFS junction instead. link replaces those on sight. Such a link is dangerous on Windows: a raw git worktree remove follows it and deletes the main worktree's gitignored tooling. Shims have no such link, but still remove worktrees with worktree_manage.sh remove --execute — it also clears any legacy link first, and the agent guardrail blocks raw git worktree remove. To recover an emptied main checkout, re-run cgw-install.cmd / cgw-batch-install.cmd from the CGW source repo (they leave .cgw.conf alone).

Diff your branch against the default branch

branch_diff.sh auto-detects the repository's default branch (main, master, or a custom name) via ${CGW_REMOTE}/HEAD, falling back to CGW_TARGET_BRANCH when that ref is unset:

./scripts/git/branch_diff.sh                 # full patch vs default branch
./scripts/git/branch_diff.sh --files         # changed file names only
./scripts/git/branch_diff.sh --stat --no-ws  # diffstat, ignoring whitespace
./scripts/git/branch_diff.sh --base release/1.2  # compare against an explicit ref

Read-only — safe to run with a dirty working tree. Uses triple-dot diff (<base>...HEAD), so only changes made on the current branch are shown.

Review a PR locally

./scripts/git/pr_checkout.sh 42                            # check out PR #42
./scripts/git/pr_checkout.sh --pr 42 --branch review/pr-42  # into a named local branch
./scripts/git/pr_checkout.sh 42 --dry-run                  # preview the gh command only

Wraps gh pr checkout; requires gh auth login. Refuses to switch branches over uncommitted tracked changes unless --force is passed.

Generate or update a Markdown Table of Contents

./scripts/git/md_toc.sh docs/usage.md              # print TOC to stdout
./scripts/git/md_toc.sh docs/usage.md --insert      # insert/update the <!--ts-->...<!--te--> block in place
./scripts/git/md_toc.sh docs/usage.md --check       # CI: exit 1 if the TOC is stale
./scripts/git/md_toc.sh --all                       # update every tracked *.md file with a <!--ts--> marker

Computes GitHub-compatible anchor slugs offline (no network access or auth token needed).


Fix lint issues (code + Markdown)

./scripts/git/fix_lint.sh                    # auto-fix code lint + markdown, full repo
./scripts/git/fix_lint.sh --modified-only     # only files changed vs HEAD
./scripts/git/fix_lint.sh --md-only           # markdown auto-fix only, skip code lint
./scripts/git/fix_lint.sh --skip-md-lint      # code lint auto-fix only, skip markdown

Markdown auto-fix uses CGW_MARKDOWNLINT_CMD (auto-detected at runtime — see Configuration) with CGW_MARKDOWNLINT_FIX_ARGS (default --fix). Only auto-fixable rules are corrected in place; manual-only violations (e.g. MD013, MD041) still require a hand fix and are reported by check_lint.sh.

check_lint.sh accepts the matching --md-only flag to verify markdown alone (./scripts/git/check_lint.sh --md-only) — fix_lint.sh --md-only forwards it automatically to its own final verification step.

check_lint.sh and push_validated.sh also accept --skip-typecheck to bypass the whole-project typecheck (CGW_TYPECHECK_CMD) for a single run — e.g. ./scripts/git/check_lint.sh --skip-typecheck. Unlike lint/format/markdown, a failing typecheck blocks both scripts by default (advisory only in the pre-commit hook); see Configuration.

Gitignored .md files (local CLAUDE.md, session logs, ...) are skipped automatically via .markdownlint-cli2.jsonc — see Configuration.


Environment Variables

All scripts support CGW_* environment variables to override config at runtime:

CGW_NON_INTERACTIVE=1 ./scripts/git/commit_enhanced.sh "feat: message"
CGW_SOURCE_BRANCH=dev ./scripts/git/merge_with_validation.sh --dry-run
CGW_LINT_CMD="" ./scripts/git/check_lint.sh   # skip lint for this run
CGW_NO_VENV=1 ./scripts/git/commit_enhanced.sh "feat: message"  # system lint
CGW_SKIP_LINT=1 ./scripts/git/commit_enhanced.sh "feat: message"  # skip all lint
CGW_SKIP_MD_LINT=1 ./scripts/git/commit_enhanced.sh "docs: update"  # skip md lint only
CGW_SKIP_TYPECHECK=1 ./scripts/git/push_validated.sh  # skip the blocking typecheck for this push
CGW_ALL=1 ./scripts/git/commit_enhanced.sh "chore: bulk stage"  # force-stage all

Legacy CLAUDE_GIT_* variables are still supported:

  • CLAUDE_GIT_NON_INTERACTIVE=1 → CGW_NON_INTERACTIVE=1
  • CLAUDE_GIT_NO_VENV=1 → CGW_NO_VENV=1
  • CLAUDE_GIT_STAGED_ONLY=1 → CGW_STAGED_ONLY=1

Updating local-only file protection

CGW_LOCAL_FILES (and CGW_LOCAL_FILES_EXEMPT) is read from .cgw.conf by the git hooks at run time, so editing the config takes effect immediately:

# Add a new local-only entry — no re-render needed
sed -i 's/^CGW_LOCAL_FILES=.*/CGW_LOCAL_FILES="CLAUDE.md MEMORY.md notes.md .claude\/ logs\/"/' .cgw.conf
git add notes.md && git commit -m "test"   # → blocked by pre-commit on next try

Match contract:

  • A bare name (CLAUDE.md) matches the path exactly — CLAUDE.md.bak does NOT match.
  • A trailing-slash entry (logs/) matches the directory itself or anything inside it.
  • CGW_LOCAL_FILES_EXEMPT accepts exact paths that override a match.
  • No globs, no substring matches.

Migration note: if you upgraded from a CGW version older than the single-source-of-truth matcher, your .git/hooks/pre-commit and .git/hooks/pre-push may still contain compiled-in patterns from the old configure.sh. Re-run ./scripts/git/install_hooks.sh once to refresh them — afterwards .cgw.conf edits will be picked up at run time.