molecule-core/docs/engineering/pr-hygiene.md
claude-ceo-assistant 3501e6bfd7
Some checks failed
CodeQL / Analyze (${{ matrix.language }}) (go) (pull_request) Successful in 13s
CodeQL / Analyze (${{ matrix.language }}) (python) (pull_request) Successful in 11s
CodeQL / Analyze (${{ matrix.language }}) (javascript-typescript) (pull_request) Successful in 12s
Check merge_group trigger on required workflows / Required workflows have merge_group trigger (pull_request) Successful in 15s
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 27s
CI / Detect changes (pull_request) Successful in 20s
Retarget main PRs to staging / Retarget to staging (pull_request) Has been skipped
Lint curl status-code capture / Scan workflows for curl status-capture pollution (pull_request) Successful in 15s
E2E API Smoke Test / detect-changes (pull_request) Successful in 51s
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 51s
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 39s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 51s
Harness Replays / detect-changes (pull_request) Successful in 53s
Runtime PR-Built Compatibility / detect-changes (pull_request) Successful in 48s
Ops Scripts Tests / Ops scripts (unittest) (pull_request) Successful in 1m7s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 31s
Harness Replays / Harness Replays (pull_request) Failing after 1m18s
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 2m19s
Runtime PR-Built Compatibility / PR-built wheel + import smoke (pull_request) Successful in 3m14s
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Successful in 6m1s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Successful in 6m47s
CI / Python Lint & Test (pull_request) Successful in 8m16s
CI / Canvas (Next.js) (pull_request) Failing after 9m36s
CI / Canvas Deploy Reminder (pull_request) Has been skipped
CI / Platform (Go) (pull_request) Successful in 12m18s
fix(post-suspension): vanity import paths go.moleculesai.app/core/{platform,tests/harness/cp-stub} (closes molecule-ai/internal#71 phase 2)
Migrates the two Go modules under molecule-core off the dead
github.com/Molecule-AI/molecule-monorepo/... identity onto the vanity
host go.moleculesai.app. Also fixes the historical naming
inconsistency where the Gitea repo is molecule-core but the Go module
path said molecule-monorepo.

Module changes:
- workspace-server/go.mod:
    github.com/Molecule-AI/molecule-monorepo/platform
    -> go.moleculesai.app/core/platform
- tests/harness/cp-stub/go.mod:
    github.com/Molecule-AI/molecule-monorepo/tests/harness/cp-stub
    -> go.moleculesai.app/core/tests/harness/cp-stub

Surfaces touched
- 174 *.go files (374 import lines) — every import under
  workspace-server/ + tests/harness/cp-stub/
- 2 Dockerfiles (workspace-server/Dockerfile + Dockerfile.tenant) —
  -ldflags strings updated in lockstep with the module rename so
  buildinfo.GitSHA injection still resolves correctly
- README + docs + scripts + comment URLs to git.moleculesai.app form
- NEW workspace-server/internal/lint/import_path_lint_test.go —
  structural lint gate rejecting future github.com/Molecule-AI/ or
  Molecule-AI/molecule-monorepo references. Identical template to the
  other migration PRs (plugin-gh-identity#3, molecule-cli#2,
  molecule-controlplane#32).

Cross-repo dep allowlist (documented in lint gate)
workspace-server requires molecule-ai-plugin-gh-identity, whose own
vanity migration is PR molecule-ai-plugin-gh-identity#3. Until that PR
merges + a tag is cut at go.moleculesai.app/plugin/gh-identity, the
two locations referencing the legacy github.com path
(workspace-server/go.mod require, cmd/server/main.go import) remain
allowlisted. Follow-up PR drops the allowlist + updates both refs in
one shot once gh-identity is fully migrated.

Test plan
- go build ./... clean for both modules
- go test ./... green except two pre-existing failures
  (TestStartSweeper_RecordsMetricsOnSuccess flaky-on-suite,
  TestLocalResolver_BubblesUpCopyFailure relies on read-only fs perms
  but runs as root on operator host) — both reproduce identically on
  baseline main pre-migration; NOT regressions of this PR
- Mutation-tested: lint gate fails on canaries in .go + .md;
  allowlist correctly suppresses cross-repo dep references in go.mod
  while still flagging unrelated additions

Open dependency
- go.moleculesai.app responder must be deployed before fresh-clone
  external builds resolve the vanity path. Existing CI / Docker builds
  ride pinned go.sum + self-referential module path + responder is
  not on critical path for those.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 22:37:42 +00:00

5.6 KiB

Pull Request Hygiene

Status: Guide. Violations are a review-time flag, not a CI gate. Audience: Humans and agents opening PRs in this repo. Cross-refs: testing-strategy.md, backends.md

Why this exists

On 2026-04-23 a backlog audit found 23 open PRs on molecule-core, of which 8 had accumulated 70-380 files of bloat (+2000/-8000 lines) from stale branch drift. The underlying fix in each was 1-5 files; the rest was merge artifact. Half the PRs were closed that day because they weren't reviewable and the real fix had to be re-extracted onto a clean branch.

This document captures the patterns that avoid that outcome.

The rules

1. Small PRs, single concern

Change size Reviewability
≤100 lines Good. One sitting.
100-300 lines ⚠️ Acceptable if genuinely one logical change.
300-1000 lines 🔴 Too large. Split.
1000+ lines 🚫 Unreviewable — split before opening.

Exception: complete file deletions and automated refactors where the reviewer only needs to verify intent.

2. Branch hygiene — rebase, don't merge-in

When your branch falls behind the base:

Do:

git fetch origin staging
git rebase origin/staging
# resolve conflicts
git push --force-with-lease

Don't:

git fetch origin staging
git merge origin/staging  # creates merge commit + pulls ALL of base's files into your diff

A merge commit from origin/staging brings every base-branch commit into your PR's diff. That's where the 235-file bloat comes from. Once you have it, you can't get rid of it without resetting the branch.

3. If your branch has already drifted — cherry-pick onto fresh base

# Identify your real commits
git log origin/staging..HEAD

# Create a fresh branch off current base
git checkout -b your-branch-clean origin/staging

# Cherry-pick only the commits you actually authored
git cherry-pick abc1234 def5678

# Push and open a new PR; close the old one as "superseded by #N"
git push -u origin your-branch-clean

Don't try to rebase a drifted branch interactively to remove the base-branch commits. It fights you every merge.

4. Target staging unless you're doing a staging→main promote

Per branching policy (feedback memory rule): every change lands on staging first. Once validated there, a periodic chore: sync staging → main PR promotes the bundle.

Exception: hotfixes that also land on main directly with CEO approval.

5. Describe the why, not the what

A good PR title:

  • fix(provision): write org_instances row BEFORE readiness check to unblock boot-event auth

A bad PR title:

  • Update orgs.go
  • Fix bug
  • Phase 1

The body should explain:

  • What's broken / missing (or what's the opportunity)
  • Why this fix — especially if there are alternatives you considered
  • What's tested — which scenarios the test plan covers
  • What's deferred — if there are follow-ups, file issues and link them

Anti-pattern: ## Summary\n- Fix bug. That's not a summary; that's a stub.

6. Close the loop on review comments

  • Comments labeled Nit: / Optional: / FYI can be left for follow-up — but leave a reply acknowledging.
  • Critical/required comments need a fix or a justified reply before merge.
  • Don't resolve threads without replying — silent resolves read as dismissal.

7. CI must be green (or the failure must be acknowledged)

  • Never push --no-verify unless explicitly requested.
  • If a pre-existing failure is blocking merge, document it inline and file a tracking issue — don't silently let it erode the "all green" norm.

Patterns for specific situations

Re-targeting an old branch

When a PR was opened weeks ago against main but policy now says staging:

git fetch origin staging
git rebase --onto origin/staging old-base HEAD
git push --force-with-lease
# Edit the PR's base branch in GitHub UI

Splitting a large PR

If your PR is already open and the reviewer asks for a split:

  1. Identify the cleanest split boundary — usually along file groups or dependency layers.
  2. Create two new branches off current staging.
  3. Cherry-pick the commits for each concern into its branch.
  4. Open two new PRs, close the original as "superseded by #A and #B".

Marketing / docs-heavy PRs

Marketing content has been moved to an internal repo per commit 93324e7. If your PR modifies files under docs/marketing/campaigns/, docs/marketing/plans/, or docs/marketing/briefs/ (with non-public-facing strategy content):

  1. Check if the file still exists on origin/staging.
  2. If deleted, open the PR in the internal marketing repo instead.
  3. Public-facing marketing (blog posts, SEO pages under docs/blog/) stays in this repo.

Signs your PR has a hygiene problem

  • 70+ files changed when your commit message mentions 2-3 files
  • +2000/-3500 lines but the actual fix is ~100 lines
  • State: DIRTY in GitHub for >1 day
  • Filenames in the diff you don't recognize (someone else's changes in your PR)
  • Merge commits in your branch's log named Merge remote-tracking branch 'origin/staging' into ...

If you see any of these, don't try to "clean it up in place" — cherry-pick onto a fresh branch (rule 3 above).