geode-gitflow · git:20260825.9516eab · 2026-08-25 · sha256 c35b4f2d6b8e5571

geode-gitflow git:20260825.9516eabA

Immutable. This exact content is served forever at /api/v1/blob/c35b4f2d6b8e5571.

---
name: geode-gitflow
description: GEODE branch strategy and PR rules. feature → develop → main merge flow, Pre-PR Quality Gate (CI guardrails + docs-sync loop), Post-PR CI ratchet (gh pr checks --watch mandatory), Korean PRs, assignee settings. Triggers on "branch", "git", "pr", "merge", "커밋", "풀리퀘스트".
---

# GEODE Git & PR Workflow

## Merge Flow (mandatory)

**feature → develop → main** order. Direct push to main prohibited — must go through PR.

```
feature/xxx ──PR──→ develop ──PR──→ main
```

For releases (version stamp bump + CHANGELOG promote), the release branch lands on **develop first**, then a straight pass-through develop → main PR ships to production. See `## Release Flow` below — this rotation eliminates the post-release backmerge that the older pattern required.

## Full Workflow

> **Principle 1**: Every work unit **starts with worktree open (alloc) and ends with worktree close (free)**.
> No direct `git checkout feature/*` in the main repo. No exceptions.
>
> **Principle 2**: develop merge uses a **queue approach** — one at a time. Rebase next worktree after merge.

```
0.  ★ Frontier Research (for new infrastructure features)
    DISCOVER → COMPARE → DECIDE → DOCUMENT
1.  worktree open + feature branch creation  ← alloc
2.  Code changes (within worktree)
3.  ★ Pre-PR Quality Gate (iterate)
4.  Commit (code + docs together)
5.  PR creation (feature → develop)
6.  ★★ Post-PR CI Ratchet (mandatory)
7.  merge (feature → develop)            ← queue: one at a time
8.  develop → main PR creation (batchable)
9.  ★★ Post-PR CI Ratchet (mandatory)
10. merge (develop → main)
11. ★★★ Docs-Sync Final Verification
12. worktree close + branch deletion          ← free
```

> **Step 0 applicability**: Mandatory for new infrastructure features (Gap, architecture changes).
> Can be skipped for simple bug fixes, documentation updates, or repeating existing patterns.

### Develop Merge Queue (when running parallel worktrees)

When multiple worktrees are open simultaneously, manage develop merges as a sequential queue.

```
Worktree A (fix/xxx)  ──→ PR → CI pass → merge #1 ──┐
                                                      │ develop updated
Worktree B (fix/yyy)  ──→ PR → CI pass ──→ rebase ──→ merge #2 ──┐
                                                                   │
Worktree C (fix/zzz)  ──→ PR → CI pass ──→ rebase ──→ merge #3 ──┘
                                                                   │
                                              develop → main PR (batch)
```

**Queue rules:**
- Only one merge to develop at a time (conflict prevention)
- After merge, next waiting worktree rebases onto develop then pushes
- Re-run CI after merge (code changed due to rebase)
- develop → main can batch multiple features

```bash
# Queue order management — rebase next worktree
cd .claude/worktrees/<next-task-name>
git fetch origin develop
git rebase origin/develop
git push --force-with-lease
# → CI re-triggered → confirm pass → merge
```

### Concurrent-session drift & CI-trigger recovery

Two failure modes surface when **another session merges to develop while your
feature PR is open** (e.g. a Tau2-promotion or scheduled routine running in
parallel). Both are expected under multi-session work — recognise and recover,
don't re-investigate from scratch each time.

**A. PR goes `CONFLICTING` / `DIRTY` — usually a CHANGELOG collision.** The
other merge added its own top entry (often under `## [Unreleased]`) or bumped
the version, so your top-of-CHANGELOG insert conflicts. Recover:

```bash
git fetch origin
git merge origin/develop            # resolve on the feature branch (squash-merge flattens it later)
# CHANGELOG.md is the usual (often only) conflict.
```

- **Fold, don't stack.** Absorb the concurrent `[Unreleased]` / entry INTO your
  release version's own `### Added/Changed/Fixed` — a release captures *all*
  unreleased work since the last version. Never leave a `## [Unreleased]`
  heading on a branch bound for main (CLAUDE.md forbids it).
- **Re-check the version number isn't already taken.** If the other session
  bumped `pyproject`/CHANGELOG to the number you picked, bump past it
  (`grep -m1 '^version' <(git show origin/develop:pyproject.toml)` before
  resolving — see [[feedback_concurrent_session_version_collision]]).
- **Regenerate the derived SoT after editing CHANGELOG.md** — the version
  fan-out is not just the 5 text files: `node site/scripts/sync-stats.mjs`
  (rebuilds `changelog.ts` / `sot.ts` / `llms.txt`) + `uv run python
  scripts/check_llms_version.py --fix`, else the CI version ratchet blocks the
  merge on a stale `changelog.ts`.
- Re-run the gates, `git add -A`, `git commit` (completes the merge), push.

**B. A fresh PR attaches 0 CI checks (webhook miss).** `gh pr checks <N>` prints
"no checks reported" and stays that way. Confirm it's a *miss*, not slowness:

```bash
gh api "repos/<owner>/<repo>/commits/$(git rev-parse HEAD)/check-runs" --jq .total_count   # 0 = nothing attached
```

If `total_count` is 0 minutes after opening (Actions enabled, other PRs' runs
present), re-fire the trigger:

```bash
gh pr close <N> && gh pr reopen <N>   # fires a `reopened` event → CI re-triggers
```

Verify attachment via the `check-runs` API, **not** `gh pr checks` alone (it
errors identically on "not yet run" and "will never run"). Don't leave a monitor
spinning on the repeating "no checks" error — diagnose the trigger first.

---

## Release Flow (rotation — eliminates backmerge)

Pre-2026-05-23 GEODE used the canonical gitflow pattern: release branch off
develop → merge to main → backmerge main → develop. That created a one-way
push of version stamps + CHANGELOG promotes into main, leaving develop's
stamps stale until the backmerge PR landed. Every release cycle paid the
cost of a 6-file backmerge PR, **AND** CHANGELOG conflicts when
develop moved while the release PR was in flight (we hit this 4 times
across PR #1499, #1504, #1506).

The current pattern rotates the order so release stamps land on develop
first:

```
develop ──cut──→ release/vX.Y.Z (stamp bump + CHANGELOG promote)
                       │
                       └─PR─→ develop  (1)  ← release branch absorbs back
                                │
                                └─PR─→ main (2)  ← straight pass-through
```

**Step (1)** — release/* → develop PR. Carries the 5-location stamp bump
(`pyproject.toml`, `CLAUDE.md`, `README.md`, `README.ko.md`, CHANGELOG
header) and the `## [Unreleased]` → `## [X.Y.Z]` promote. Insert a fresh
empty `## [Unreleased]` above the just-promoted section so the next batch
of feature PRs has somewhere to land. Develop is now content-equivalent to
the eventual main state.

**Step (2)** — develop → main PR. No new commits beyond merge — it just
moves main's tip up to develop's. Abbreviated PR body (Summary +
Verification only) is fine here.

**No release-generated backmerge step.** Develop already has every release
commit that main has. After release, the two are content-identical modulo
gitflow merge-commit asymmetry. Main-maintained tracking transactions are a
separate source of drift and use the deliberate pre-sync below.

```bash
# ── Release flow (rotation) ──

# 1. Cut release branch
git fetch origin
git worktree add .claude/worktrees/release-vX.Y.Z -b release/vX.Y.Z origin/develop

# 2. Bump stamps + CHANGELOG promote + add fresh [Unreleased]
# (edit 5 files; see geode-changelog skill for [Unreleased] ratchet rules)

# 3. PR release → develop
gh pr create --base develop --head release/vX.Y.Z \
  --title "release: vX.Y.Z — <summary>" \
  --body "<release notes>"
# → CI ratchet → merge

# 4. PR develop → main (straight pass-through)
gh pr create --base main --head develop \
  --title "release: vX.Y.Z (develop → main)" \
  --body "<abbreviated body>"
# → CI ratchet → merge → Pages workflow fires
```

### Deliberate main-to-develop pre-sync

There is no automatic backmerge workflow. Before every `develop -> main`
promotion, fetch both protected branches and compare their content. If main
has unique tracking work and the sync is conflict-free, open a CI-gated PR
directly from the current `main` head to `develop`. Do not put that clean sync
behind the trusted sync-branch prefix: a merge from current main may simply
fast-forward and therefore has no two-parent head.

When conflict resolution is required, create
`sync/main-into-develop-<task>` from current develop and make an explicit merge
commit from current main. The sync head must have first parent exactly
`refs/remotes/origin/develop` and second parent exactly
`refs/remotes/origin/main`. CI grants main-ledger trust only to that
same-repository graph. Pull-request events do not rerun merely because the
unrelated main branch advances, so fetch and run the same resolver immediately
before merge:

```bash
git fetch origin
uv run python scripts/resolve_architecture_roadmap_trust.py \
  --event-mode pull_request \
  --target-branch develop \
  --head-ref "sync/main-into-develop-<task>" \
  --head-repo mangowhoiscloud/geode \
  --repository mangowhoiscloud/geode \
  --head-sha "$(git rev-parse HEAD)" \
  --require-trust main
```

If the resolver fails, recreate the sync head from the new tips and rerun CI;
never merge the previously green but now-stale PR.

### Docs pipeline compatibility

The rotation pattern preserves every existing docs / release workflow
trigger:

| Workflow | Trigger | Behavior under rotation |
|----------|---------|-------------------------|
| `pages.yml` | `push: main` (paths include `CHANGELOG.md`, `pyproject.toml`, `CLAUDE.md`) | Fires when develop → main pass-through PR merges. Same moment as before. |
| `petri-publish.yml` | `push: main, develop` | Fires on develop merge AND main merge. Unchanged. |
| `release.yml` | `workflow_dispatch` (manual, default `ref: main`) | Manual trigger unchanged. The `version` input matches both `pyproject.toml` and `CHANGELOG.md` `## [X.Y.Z]` header. |
| `site/scripts/sync-stats.mjs` | invoked by `pages.yml` build | Counts CHANGELOG `## [X.Y.Z]` headers excluding `[Unreleased]`. Rotation's fresh-`[Unreleased]` block is correctly skipped. |

No release workflow needs editing for the rotation. Main-maintained tracking
work still follows the explicit pre-sync above.

---

## Step 0: ★ Frontier Research (mandatory pre-implementation research)

> Applicability: Mandatory for new infrastructure features (Gap, architecture changes). Can skip for simple bug fixes.

Investigate implementations in frontier harnesses (Claude Code, Codex CLI, OpenClaw, Aider, autoresearch, etc.),
create a comparison matrix, and document design decisions.

```
DISCOVER (investigate harnesses via parallel Agents)
  → COMPARE (feature × harness matrix)
  → DECIDE (Option A/B/C + selection rationale)
  → DOCUMENT (docs/plans/research-<topic>.md)
```

> Open source (Codex, Aider, autoresearch, OpenClaw) — verify source directly via `gh api`.
> Closed source (Claude Code only) — official docs/secondary sources — state verification limitations.

---

## Step 0: One-time machine setup

`.pre-commit-config.yaml` defines 17 hooks, but they only run once the git hook
is installed — and a fresh clone has no `.git/hooks/pre-commit`. Worktrees share
the main repo's hooks directory, so installing once covers every worktree.

```bash
uv run pre-commit install     # verify: ls .git/hooks/pre-commit
```

Even installed, pre-commit covers 2 of the 17 gates CI enforces; `scripts/preflight.sh`
is what closes the rest. Install both.

## Step 1: Worktree Open (alloc)

**Every work unit** starts by opening a worktree. No exceptions.

```bash
# Fetch the canonical integration head; do not move a checkout held by another
# worktree or reuse a dirty main checkout.
git fetch origin
git worktree add .claude/worktrees/<task-name> \
  -b feature/<branch-name> origin/develop

cd .claude/worktrees/<task-name>

# → Steps 2~11 all performed within this worktree
```

**Worktree rules:**
- `.claude/worktrees/` is in `.gitignore`
- No `git checkout` within worktree (HEAD conflict)
- No `git checkout feature/*` in main repo — access only via worktree
- Leak check: `git worktree list` to find unclosed worktrees

---

## ★ Pre-PR Quality Gate (mandatory loop before commit)

**After code changes, this loop must pass before commit/PR.**

```
Code changes complete
   │
   ▼
┌─────────────────────────────────────────┐
│  Step 1: CI Guardrails (all must pass)  │
│                                         │
│  scripts/preflight.sh                   │ → runs every gate CI enforces
│  scripts/preflight.sh --fast            │ → same minus tests + site build
│                                         │
│  Any failure → fix → re-run Step 1      │
│                                         │
│  ※ Detailed inspection lenses:          │
│    code-review-workflow                  │
│    (structure/deps/security/migration/   │
│     performance)                         │
└────────────────┬────────────────────────┘
                 │ All passed
                 ▼
┌─────────────────────────────────────────┐
│  Step 2: Docs Writing (mandatory on     │
│  code changes)                          │
│                                         │
│  □ Add entry to CHANGELOG.md            │
│    [Unreleased]                         │
│    - Added / Changed / Fixed / Removed  │
│    - Can skip if no code changes        │
│                                         │
│  □ Sync CLAUDE.md metrics (if changed)  │
│    - When Tests, Modules change         │
│                                         │
│  □ Update docs/progress.md today's      │
│    date section                         │
│    - Completion table + remaining table  │
│                                         │
│  Omission found → fix → re-run Step 1   │
└────────────────┬────────────────────────┘
                 │ All complete
                 ▼
┌─────────────────────────────────────────┐
│  Step 3: Commit                         │
│                                         │
│  Include code + docs in a single commit │
│  No separate docs-only commits          │
│  (maintain consistency)                 │
│                                         │
│  git add <code files> CHANGELOG.md ...  │
│  git commit -m "<type>: <description>"  │
└────────────────┬────────────────────────┘
                 │
                 ▼
           Ready to create PR
```

### Quality Gate Anti-patterns

| Anti-pattern | Result | Correct Approach |
|-------------|--------|------------------|
| Creating PR with CI failures | Wastes reviewer time | Pass all locally before PR |
| Code-only commit, docs in separate PR | CHANGELOG missing, version mismatch | Code + docs in same commit |
| Direct push to main | Gitflow violation, history pollution | Must go through PR |
| Skipping docs-sync | README/CHANGELOG fall behind | Step 2 checklist mandatory |
| **Merging without CI confirmation** | **Broken code enters main** | **gh pr checks --watch mandatory** |

---

## ★★ Post-PR CI Ratchet — Mandatory Before Merge (CRITICAL)

> **Karpathy P4**: Ratchet = advance only on verification pass, rollback on failure.
> Merging a PR without CI green is a **ratchet violation**.

### Absolute Rule

**Before running any merge command, you must check CI status with `gh pr checks`.**
Merge prohibited if CI is still running or has failed.

### Merge Ratchet Loop

```
PR creation complete
   │
   ▼
┌──────────────────────────────────────────────────┐
│  Step A: Wait for CI completion + check results   │
│                                                   │
│  gh pr checks <PR#> --watch --repo <owner/repo>   │
│                                                   │
│  → All pass  → Proceed to Step B                  │
│  → Any fail → Proceed to Step C                   │
│  → pending/running → Wait (--watch auto-waits)    │
└────────────────┬──────────────────────────────────┘
                 │
        ┌────────┴────────┐
        ▼                 ▼
┌──────────────┐  ┌──────────────────────────────┐
│  Step B:     │  │  Step C: Failure fix loop      │
│  Run Merge   │  │                                │
│              │  │  1. gh run view --log-failed    │
│  gh pr merge │  │     → Identify failure cause    │
│  <PR#>       │  │  2. Fix locally                 │
│  --merge     │  │  3. Commit + push (same branch) │
│              │  │  4. CI auto re-triggered         │
│              │  │  5. Return to Step A             │
│              │  │                                  │
│              │  │  (Repeat until pass)             │
└──────────────┘  └──────────────────────────────────┘
```

### Merge Command Template (copy and use)

```bash
# ── feature → develop ──

# 1. Create PR
gh pr create --base develop --assignee mangowhoiscloud \
  --title "<type>: <description>" \
  --body "<detailed body template>"

# 2. ★★ CI Ratchet: Wait for checks to pass (MUST — never skip)
gh pr checks <PR#> --watch --repo mangowhoiscloud/geode

# 3. Merge remotely only after all pass. Do not let gh delete the branch here;
#    the guarded free command below owns remote/worktree/local cleanup.
gh api --method PUT repos/mangowhoiscloud/geode/pulls/<PR#>/merge \
  -f merge_method=squash

# ── develop → main ──

# 4. Create PR
gh pr create --base main --head develop --assignee mangowhoiscloud \
  --title "<type>: <description> (develop → main)" \
  --body "<develop → main template>"

# 5. ★★ CI Ratchet: Wait for checks to pass (MUST — never skip)
gh pr checks <PR#> --watch --repo mangowhoiscloud/geode

# 6. Merge only after all pass
gh pr merge <PR#> --merge --repo mangowhoiscloud/geode
```

### CI Failure Fix Loop

```bash
# Check failure logs
gh pr checks <PR#> --repo mangowhoiscloud/geode
gh run view <run_id> --log-failed

# Fix locally → push → CI auto re-runs
# ... fix ...
git add -A && git commit -m "fix: <CI failure cause fix>"
git push

# Check ratchet again
gh pr checks <PR#> --watch --repo mangowhoiscloud/geode
# pass → merge
```

### Common CI Failure Causes and Responses

| Failure | Response |
|---------|----------|
| `ruff` lint error | `uv run ruff check --fix core/ tests/` + `uv run ruff format core/ tests/` |
| `mypy` type error | Fix types, minimize `# type: ignore` |
| `bandit` security warning | Add `# nosec` or to pyproject.toml skips (only when justified) |
| `pytest` failure | Fix test code, add tests for new code |
| `coverage < 75%` | Add tests for modules with insufficient coverage |

---

## PR Writing Rules

| Item | Rule |
|------|------|
| **Language** | **Korean** (both title + body) |
| **Title** | `<type>: <Korean description>` (under 70 chars) |
| **Assignee** | `--assignee mangowhoiscloud` (always) |
| **Base** | feature → `develop`, develop → `main` |

### ★ PR Body Build Rules (CRITICAL — must follow)

> **A weak PR body prevents reviewers from understanding the change intent.**
> A 1-3 line PR body is an **anti-pattern**. Fill all required sections from the template below.

**Before generating PR body, you must:**
1. Check full diff with `git diff develop...HEAD`
2. Classify all changed **files** into core/secondary/docs
3. Write a one-line **why** rationale for each file change
4. Copy test result numbers from **actual execution output** (no XXXX placeholders)
5. Use HEREDOC format (prevents line break/markdown breakage)

### Anti-pattern vs Correct PR Body

| Anti-pattern (prohibited) | Correct Approach |
|---------------------------|------------------|
| `"progress hooks"` (3 words) | Write summary + changes + impact scope + QG in full |
| `"develop → main merge. X changes."` (1 line) | Include PR numbers, CI confirmation results for develop→main too |
| Summary only without listing changed files | Per-file AS-IS → TO-BE + one-line rationale |
| `XXXX passed` (placeholder) | `2168 passed` (actual number) |
| Skipping Quality Gate checklist | All 5 CI tools + 4 docs items checked |

## PR Body Detailed Template (feature → develop)

**All sections are required. If not applicable, state "N/A".**

```markdown
## Summary
<!-- Required. 2-3 lines. "What" + "why" changed. Include background motivation. -->

<Core of the change in 2-3 sentences. What problem existed and how this PR solves it.>

## Changes

### Core Changes (Code)
<!-- Required. List all changed files without omission. -->
- `filepath:line-range`: Change content — AS-IS → TO-BE
  - Rationale: One-line explanation of why this change was made

### Secondary Changes (Code)
<!-- If N/A, state "None" -->
- `filepath`: Rename/format/type fixes etc.

### Documentation/Config Changes
<!-- Required. If code changed, CHANGELOG must be included. -->
- `CHANGELOG.md`: Items added to [Unreleased] > Fixed/Added/Changed
- `CLAUDE.md`: Updated items (if applicable)
- `pyproject.toml`: Dependency/config changes (if applicable)

## Impact Scope
<!-- Required. -->
- **Affected modules**: <specific paths like core/cli, core/ui>
- **Backward compatibility**: Maintained / Broken (if broken, attach migration guide)
- **Test changes**: Added N / Modified N / Deleted N

## Design Decisions
<!-- Required for structural changes. For simple bug fixes, state "Simple fix, no design decisions needed." -->
- Why was approach B chosen over approach A?
- If referencing frontier harness cases: link `docs/plans/research-<topic>.md`
- Alternative comparison: Option A (pros/cons) vs Option B (pros/cons) → selection rationale

## Pre-PR Quality Gate (required — paste actual execution results)

<!-- Why one script instead of a command list: CI enforces 17 gates while this
     checklist listed 5, and 3 of those 5 were documented at a NARROWER scope
     than CI runs them (`mypy core/` vs CI's `mypy core/ evals/ evolve/ scripts/`).
     A branch could pass the checklist verbatim and still fail CI — 5 of 60
     sampled July 2026 failures were exactly that scope gap. Keep preflight.sh
     as the single source; if CI gains a gate, add it there, not here. -->

- [x] `scripts/preflight.sh` — **all gates passed** (paste the final line)
- [x] `pytest -m "not live"` — **N passed** in Xs
- [x] CHANGELOG.md [Unreleased] entry added
- [x] README.md metric consistency verified
- [ ] CLAUDE.md sync (if applicable)
- [x] docs/progress.md today's date section updated

🤖 Generated with [Claude Code](https://claude.com/claude-code)
```

## PR Body Template (develop → main)

```markdown
## Summary
develop → main merge. <1-2 line summary of main changes. What features/fixes are included.>

## Included Changes
<!-- Required. List all feature PRs with numbers and titles. -->
- #number `<type>: <title>` — One-line summary of core change
- #number `<type>: <title>` — One-line summary of core change

## Change Metrics
- **Files**: N files changed
- **Tests**: N passed (compared to previous +N/-N)
- **Modules**: N (specify if changed)

## Testing
- [x] Full CI passed (`gh pr checks --watch` confirmed)
- [x] feature → develop CI pass confirmed

🤖 Generated with [Claude Code](https://claude.com/claude-code)
```

### gh pr create Command — HEREDOC Required

PR body must be passed in **HEREDOC** format. Inline `--body "..."` prohibited.

```bash
# ✅ Correct: HEREDOC
gh pr create --base develop --assignee mangowhoiscloud \
  --title "<type>: <description>" \
  --body "$(cat <<'PRBODY'
## Summary
...fill full template...

🤖 Generated with [Claude Code](https://claude.com/claude-code)
PRBODY
)"

# ❌ Prohibited: Inline (line breaks broken, content truncated)
gh pr create --body "One line summary"
```

---

## ★★★ Docs-Sync Final Verification (after main merge, before cleanup)

> Performed after the work unit is fully merged to main.
> Even if docs were written in Pre-PR Step 2, metrics may change during the merge process, so final verification is needed.

### Verification Checklist

```
main merge complete (step 10)
   │
   ▼
┌──────────────────────────────────────────────────┐
│  □ README.md metric consistency                   │
│    - modules: find core/ -name "*.py" | wc -l     │
│    - tests: uv run pytest --co 2>&1 | wc -l       │
│    - tools count, version                         │
│                                                   │
│  □ CLAUDE.md metric consistency                   │
│    - Verify Tests, Modules using same criteria    │
│                                                   │
│  □ CHANGELOG.md [Unreleased] omission check       │
│    - Are changes merged to main recorded?         │
│                                                   │
│  □ docs/progress.md today's date section exists   │
│    - If missing, add and commit                   │
│                                                   │
│  □ pyproject.toml coverage omit                   │
│    - Check if new module is in omit breaking      │
│      coverage                                     │
│                                                   │
│  → If mismatch found:                             │
│    docs(sync) commit → feature → develop → main   │
│    (apply same gitflow loop)                      │
│  → If no issues: proceed to step 12               │
│    (workspace cleanup)                            │
└──────────────────────────────────────────────────┘
```

### Pre-PR Step 2 vs Docs-Sync Final Verification

| Phase | Timing | Role |
|-------|--------|------|
| Pre-PR Step 2 | Before commit | **Write** docs (CHANGELOG entry, CLAUDE.md metrics) |
| Docs-Sync Final Verification | After main merge | **Verify** docs (README metrics, coverage omit, omission check) |

Docs are written in Pre-PR, and the final verification after main merge catches anything missed — a dual-layer structure.

---

## Branch Structure

```
main ─────────────────────────── production (stable, tagged)
  │
  └── develop ────────────────── integration (CI mandatory)
        │
        ├── feature/<name> ───── Feature development
        ├── hotfix/<name> ────── Emergency fixes (branch from main)
        └── release/v<semver> ── Release preparation
```

## Commit Convention

```
<type>(<scope>): <description>

Types: feat, fix, refactor, test, docs, ci, chore
Scopes: pipeline, scoring, analysis, verification, cli, memory, tools, llm

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
```

## CI Pipeline (GitHub Actions)

```
lint ─────┐
typecheck ─┤
test ──────┼──→ gate (all must pass for merge)
security ──┘
```

```bash
# Local CI guardrails (Pre-PR)
uv run ruff check core/ tests/
uv run ruff format --check core/ tests/
uv run mypy core/
uv run bandit -r core/ -c pyproject.toml
uv run pytest tests/ -m "not live" -q

# GitHub CI ratchet (Post-PR, mandatory before merge)
gh pr checks <PR#> --watch --repo mangowhoiscloud/geode
```

## Step 12: Worktree Close (free)

**After feature merge completion**, worktree must be closed. Run the canonical
guarded cleanup from outside the target worktree:

```bash
cd ~/workspace/geode
uv run python scripts/check_repo_hygiene.py free-merged-worktree \
  --pr <feature-pr> \
  --worktree .claude/worktrees/<task-name>
```

The command performs the leak check and prune. A refusal is a blocking signal;
inspect the reported dirty, owner, ancestry, or tree mismatch instead of using
manual `--force` cleanup.

## Worktree Allocation (Workflow Step 0)

Record on the Progress Board, then allocate the worktree.

```bash
# 1) Record Backlog → In Progress on Progress Board (from main)

# 2) Allocate Worktree
git fetch origin
git worktree add .claude/worktrees/<task-name> \
  -b feature/<branch-name> origin/develop
# Note: the target path IS the worktree checkout root; `.owner` is gitignored
# (see /.owner in .gitignore) so the convention does not pollute feature branches.
echo "session=$(date -Iseconds) task_id=<task-name>" > .claude/worktrees/<task-name>/.owner
```

On completion (after the PR merges) tear down all three stale artifacts —
remote branch, worktree, local branch — per [Post-Merge Cleanup](#post-merge-cleanup-mandatory-after-every-merge)
below. A PR reporting "No commits between" is a content/branch-selection issue,
not permission to push protected branches directly.

## PR Body Template (MANDATORY)

```
## Summary
<1-3 bullet points: what changed and why>

## Why
<Problem statement — what broke, what was missing, what user reported>

## Changes
| File | Change |
|------|--------|
| `path/to/file.py` | description of change |

## GAP Audit (if applicable)
| Item | Status | Notes |
|------|--------|-------|
| ... | Implemented / Dropped / Already exists | ... |

## Verification
- [ ] ruff check clean
- [ ] mypy clean
- [ ] pytest pass (count)
- [ ] E2E unchanged (if applicable)

## Reference
<Source: frontier codebase, PR, issue, serve log, etc.>
```

Minimum required sections: **Summary**, **Why**, **Changes**, **Verification**.
develop → main PRs may use abbreviated form (Summary + Verification only).

| Change | Cascading Updates |
|--------|-------------------|
| New tool | `definitions.json` + handlers + E2E |
| LLM adapter | `core/llm/router/` + `core/llm/providers/` + E2E |

## Post-Merge Cleanup (MANDATORY after every merge)

A squash-merged feature PR leaves three stale artifacts behind: the remote
branch, worktree, and local branch. `git branch -d` cannot recognize a squash
merge and is therefore not the cleanup gate. Run the repository command from
outside the target worktree:

Never run `gh pr merge ... --delete-branch` from a linked worktree. GitHub CLI
may switch that checkout to the PR base branch and fast-forward it as part of
local branch cleanup, violating the one-branch-per-worktree contract. Use the
remote-only API merge above, leave the linked checkout untouched, then free it
from the repository root:

```bash
cd ~/workspace/geode
uv run python scripts/check_repo_hygiene.py free-merged-worktree \
  --pr <feature-pr> \
  --worktree .claude/worktrees/<task-name>
```

The command fails before mutation unless the PR is `MERGED`, its final head
tree equals the merge tree, the checked-out local branch is an ancestor of
that final head, the remote has not advanced, the checkout is clean, and
`.owner.task_id` matches the worktree directory. Only after those proofs does
it remove remote branch → worktree → squash-only local branch and prune. A
refusal must be investigated; never delete another session's owner-protected
worktree manually.

## Rebuild & Restart (Workflow Step 7)

After merging to main, rebuild CLI and serve to update the runtime to the latest code.

```bash
# 1) Stop any running geode serve daemon(s). Use `pgrep -f`, NOT
#    `ps aux | grep "geode serve"` — ps aux truncates the long python path
#    before "geode serve", so the grep silently matches nothing, the kill is a
#    no-op, and stale daemons survive a "rebuild" and then fight over
#    ~/.geode/cli.sock (the multi-serve pathology behind the 2026-06-09
#    model-resolution bug: banner shows one daemon's model, calls route through
#    another). `geode serve stop` / `geode doctor` already use pgrep -f
#    internally (core/cli/cmd_lifecycle.py, core/cli/doctor.py); this manual
#    line should match.
pkill -f "geode serve" || true   # no-op if none running; verify: pgrep -f "geode serve"

# 2) Reinstall CLI as editable + sync dependencies.
#    The [audit] extra (inspect_ai) is REQUIRED — the seed-generation pilot's
#    petri_audit tool and the self-improving loop's audit subprocess both need
#    it. Omitting it makes the pilot fail loudly ("petri_audit aborted —
#    install the [audit] extra") instead of measuring; pre-fix it silently
#    emitted all-zero dim_means (PR-PILOT-PETRI-AUDIT-WIRING, 2026-06-01).
uv tool install -e ".[audit]" --force
uv sync --extra audit

# 3) Verify version + restart serve
geode version          # Confirm version match
geode serve &          # Restart in background
```

## Progress Board (Workflow Step 8)

Update project tracking from main. Backlog → In Progress → Done.