chore(agents): add pre-PR rebase step to issue worker to prevent stale conflicts #1407
@@ -273,6 +273,33 @@ for wave_number, wave_subtasks in enumerate(waves):
|
||||
|
||||
## Phase 3: Commit and Push
|
||||
|
||||
### Step 3.0: Rebase onto Latest Master
|
||||
|
||||
**Before committing, rebase the branch onto the latest master.** With many
|
||||
workers running in parallel, master moves fast. A branch that was created
|
||||
from master 10 minutes ago may already be behind several merged PRs. If you
|
||||
skip this step, the resulting PR will likely have merge conflicts by the time
|
||||
the reviewer gets to it — wasting the entire review cycle.
|
||||
|
||||
```bash
|
||||
cd /tmp/cleveragents-<branch-name>
|
||||
git stash # Stash any uncommitted changes
|
||||
git fetch origin # Get latest master
|
||||
git rebase origin/master # Rebase onto latest master
|
||||
git stash pop # Re-apply uncommitted changes (if any)
|
||||
```
|
||||
|
||||
**If the rebase produces conflicts:**
|
||||
1. Attempt to resolve them automatically by examining both sides and
|
||||
choosing the implementation that preserves your changes while
|
||||
incorporating upstream updates.
|
||||
2. If auto-resolution fails, abort the rebase (`git rebase --abort`,
|
||||
`git stash pop`) and proceed without rebasing. A PR with conflicts
|
||||
is better than no PR — the reviewer can request a rebase later.
|
||||
3. Log whether the rebase succeeded or was skipped in the return value.
|
||||
|
||||
### Step 3.1: Commit and Push
|
||||
|
||||
1. Invoke `ca-commit-message-formatter` with:
|
||||
- The issue metadata (specifically the Commit Message field and the
|
||||
issue number)
|
||||
@@ -390,3 +417,5 @@ Report back to the orchestrator with:
|
||||
- Aggregate: total attempts across all subtasks, total escalations
|
||||
- **Resume status**: whether this was a fresh run or a resume, and if
|
||||
resumed, what phase it resumed from
|
||||
- **Rebase status**: whether the pre-PR rebase succeeded, was skipped
|
||||
(no conflicts), or failed (conflicts, proceeded without rebase)
|
||||
|
||||
@@ -50,6 +50,7 @@ def _make_async_service(context: Context) -> AuditService:
|
||||
engine = create_engine(db_url, connect_args={"check_same_thread": False})
|
||||
Base.metadata.create_all(engine, tables=[])
|
||||
from cleveragents.infrastructure.database.models import AuditLogModel
|
||||
|
||||
Base.metadata.create_all(engine, tables=[AuditLogModel.__table__])
|
||||
engine.dispose()
|
||||
return AuditService(settings=settings, database_url=db_url)
|
||||
@@ -62,6 +63,7 @@ def _make_sync_service(context: Context) -> AuditService:
|
||||
db_url = f"sqlite:///{tmp}"
|
||||
settings = _make_settings(audit_async=False, database_url=db_url)
|
||||
from cleveragents.infrastructure.database.models import AuditLogModel
|
||||
|
||||
engine = create_engine(db_url)
|
||||
Base.metadata.create_all(engine, tables=[AuditLogModel.__table__])
|
||||
engine.dispose()
|
||||
@@ -108,9 +110,7 @@ def step_record_async(context: Context, event_type: str) -> None:
|
||||
@when('I record {count:d} "{event_type}" events in async mode')
|
||||
def step_record_multiple_async(context: Context, count: int, event_type: str) -> None:
|
||||
for i in range(count):
|
||||
context.async_service.record(
|
||||
event_type=event_type, plan_id=f"plan-async-{i}"
|
||||
)
|
||||
context.async_service.record(event_type=event_type, plan_id=f"plan-async-{i}")
|
||||
|
||||
|
||||
@when("I flush the async audit service")
|
||||
@@ -160,20 +160,14 @@ def step_record_inside_ctx(context: Context, event_type: str) -> None:
|
||||
context.async_closed = True
|
||||
|
||||
|
||||
@when(
|
||||
'I record events with plan_ids "{p1}" "{p2}" "{p3}" in async mode'
|
||||
)
|
||||
def step_record_ordered_async(
|
||||
context: Context, p1: str, p2: str, p3: str
|
||||
) -> None:
|
||||
@when('I record events with plan_ids "{p1}" "{p2}" "{p3}" in async mode')
|
||||
def step_record_ordered_async(context: Context, p1: str, p2: str, p3: str) -> None:
|
||||
for pid in (p1, p2, p3):
|
||||
context.async_service.record(event_type="plan_applied", plan_id=pid)
|
||||
context.ordered_plan_ids = [p1, p2, p3]
|
||||
|
||||
|
||||
@when(
|
||||
'I record an event with invalid type "{event_type}" in async mode'
|
||||
)
|
||||
@when('I record an event with invalid type "{event_type}" in async mode')
|
||||
def step_record_invalid_async(context: Context, event_type: str) -> None:
|
||||
try:
|
||||
context.async_service.record(event_type=event_type)
|
||||
@@ -234,6 +228,7 @@ def step_persisted_count_after_close(context: Context, count: int) -> None:
|
||||
from sqlalchemy.orm import sessionmaker as _sm
|
||||
|
||||
from cleveragents.infrastructure.database.models import AuditLogModel as _M
|
||||
|
||||
engine = _ce(f"sqlite:///{db_path}")
|
||||
session = _sm(bind=engine)()
|
||||
actual = session.query(_M).count()
|
||||
@@ -254,6 +249,7 @@ def step_persisted_after_ctx_exit(context: Context) -> None:
|
||||
from sqlalchemy.orm import sessionmaker as _sm
|
||||
|
||||
from cleveragents.infrastructure.database.models import AuditLogModel as _M
|
||||
|
||||
engine = _ce(f"sqlite:///{db_path}")
|
||||
session = _sm(bind=engine)()
|
||||
actual = session.query(_M).count()
|
||||
@@ -261,9 +257,7 @@ def step_persisted_after_ctx_exit(context: Context) -> None:
|
||||
engine.dispose()
|
||||
else:
|
||||
actual = context.async_service.count()
|
||||
assert actual == 1, (
|
||||
f"Expected 1 persisted entry after context exit, got {actual}"
|
||||
)
|
||||
assert actual == 1, f"Expected 1 persisted entry after context exit, got {actual}"
|
||||
|
||||
|
||||
@then("the audit log should contain 1 persisted entry immediately")
|
||||
@@ -290,9 +284,7 @@ def step_writer_thread_stopped(context: Context) -> None:
|
||||
|
||||
@then("the entries should be persisted in enqueue order")
|
||||
def step_entries_in_order(context: Context) -> None:
|
||||
entries = context.async_service.list_entries(
|
||||
event_type="plan_applied", limit=100
|
||||
)
|
||||
entries = context.async_service.list_entries(event_type="plan_applied", limit=100)
|
||||
# list_entries returns newest-first; reverse to get enqueue order.
|
||||
persisted_ids = [e.plan_id for e in reversed(entries)]
|
||||
assert persisted_ids == context.ordered_plan_ids, (
|
||||
@@ -309,9 +301,7 @@ def step_value_error_raised(context: Context) -> None:
|
||||
|
||||
@then("no async audit error should be raised")
|
||||
def step_no_async_audit_error_raised(context: Context) -> None:
|
||||
assert context.async_error is None, (
|
||||
f"Expected no error, got {context.async_error}"
|
||||
)
|
||||
assert context.async_error is None, f"Expected no error, got {context.async_error}"
|
||||
|
||||
|
||||
# ── Settings steps ────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user