diff --git a/.opencode/agents/ca-issue-worker.md b/.opencode/agents/ca-issue-worker.md index e017773e..06a4b603 100644 --- a/.opencode/agents/ca-issue-worker.md +++ b/.opencode/agents/ca-issue-worker.md @@ -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- +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) diff --git a/features/steps/async_audit_recording_steps.py b/features/steps/async_audit_recording_steps.py index 2465defc..e7372a02 100644 --- a/features/steps/async_audit_recording_steps.py +++ b/features/steps/async_audit_recording_steps.py @@ -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 ────────────────────────────────────────────────