Skip to content
Back to skills

1545 Requirements Dc2d4f30

ASecurity

**Status: APPROVED** **Created**: 2026-02-05 **Issues**: #132, #136, #138 (SEQUENCED) **Scope**: Refactor 3 god classes (~4,500 lines) into maintainable modules ---

  • 9 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 11, 2026
testingpythongodockertestingrefactoring

Security analysis

A100/100

Scanned October 11, 2026

npx -y skills add tools-only/X-Skills --skill 1545-requirements_dc2d4f30 --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of 1545 Requirements Dc2d4f30?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for 1545 Requirements Dc2d4f30
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/tools-only-1545-requirements-dc2d4f30/badge)](https://www.skillsdirectory.com/skills/tools-only-1545-requirements-dc2d4f30)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

SKILL.md
# Requirements: core-refactoring

**Status: APPROVED**
**Created**: 2026-02-05
**Issues**: #132, #136, #138 (SEQUENCED)
**Scope**: Refactor 3 god classes (~4,500 lines) into maintainable modules

---

## Executive Summary

Refactor `launcher.py` (2,010 lines), `orchestrator.py` (1,344 lines), and `worker_protocol.py` (1,143 lines) to eliminate sync/async duplication, split god classes, and decouple shared mutable state. Delivered as 3 sequenced PRs.

---

## Sequence

| Order | Issue | Title | Priority |
|-------|-------|-------|----------|
| 1 | #136 | Sync/async deduplication | MEDIUM |
| 2 | #132 | God class splitting | HIGH |
| 3 | #138 | State decoupling via WorkerRegistry | MEDIUM |

**Rationale**: Dedup first reduces code volume by ~445 lines, making splits cleaner. State decoupling is easiest after classes are properly sized.

---

## PR 1: Sync/Async Deduplication (#136)

### Strategy: Hybrid

- **Most methods**: Async-first implementation, sync wrappers via `asyncio.run()`
- **Poll/heartbeat loops**: Shared logic extraction with `sleep_fn` callable injection (sync fast-path required)

### Targets (6 duplication pairs, ~445 lines)

| Component | Sync | Async | Strategy |
|-----------|------|-------|----------|
| `spawn_with_retry` | launcher.py:354-420 | launcher.py:422-485 | async-first + wrapper |
| `spawn` (subprocess) | SubprocessLauncher | SubprocessLauncher | async-first + wrapper |
| `spawn` (container) | ContainerLauncher | ContainerLauncher | async-first + wrapper |
| `_start_container` | launcher.py:1235-1357 | launcher.py:1833-1930 | async-first + wrapper |
| `_main_loop` | orchestrator.py | orchestrator.py | callable injection (has polling) |
| `_poll_workers` | orchestrator.py | orchestrator.py | callable injection (has polling) |
| `claim_next_task` | level_coordinator.py | level_coordinator.py | async-first + wrapper |

### Acceptance Criteria

- [ ] No duplicate sync/async method pairs remain
- [ ] Single source of truth for retry logic
- [ ] Single source of truth for Docker command building
- [ ] Poll/heartbeat loops have zero `asyncio.run()` overhead in sync mode
- [ ] All existing tests pass
- [ ] No new dependencies added

---

## PR 2: God Class Splitting (#132)

### Strategy: Split into subpackages and flat modules

#### launcher.py (2,010 lines) → launchers/ subpackage

```
zerg/
├── launcher_types.py          # LauncherConfig, SpawnResult, WorkerHandle dataclasses
├── env_validator.py           # Environment variable validation logic
└── launchers/
    ├── __init__.py            # Factory function, public re-exports
    ├── base.py                # WorkerLauncher ABC + retry logic
    ├── subprocess_launcher.py # SubprocessLauncher
    └── container_launcher.py  # ContainerLauncher
```

#### orchestrator.py (1,344 lines) → slimmed orchestrator + extracted components

- Extract `_create_launcher` → `launcher_configurator.py` (already exists, verify coverage)
- Slim `__init__` using Builder pattern
- Remove thin wrappers (lines 297-526) — call extracted components directly
- **Target**: orchestrator.py ≤ 500 lines (coordination logic only)

#### worker_protocol.py (1,143 lines) → flat modules

```
zerg/
├── protocol_types.py     # Message types, enums, dataclasses
├── protocol_handler.py   # Protocol parsing, message routing
└── protocol_state.py     # State machine, transitions
```

### Compatibility

- **Clean break**: No re-exports from old import paths
- Update all imports project-wide in the same PR
- Update `__init__.py` exports

### File Size Target

- **Soft cap**: 500 lines
- **Hard cap**: 600 lines (only if natural class boundary requires it)

### Acceptance Criteria

- [ ] No file exceeds 600 lines (target 500)
- [ ] Each class has single responsibility
- [ ] All imports updated — no broken references
- [ ] Unit tests can mock individual components
- [ ] `python -m zerg.validate_commands` passes

---

## PR 3: State Decoupling via WorkerRegistry (#138)

### Strategy: WorkerRegistry abstraction

```python
class WorkerRegistry:
    """Single source of truth for worker state."""

    def __init__(self):
        self._workers: dict[int, WorkerHandle] = {}
        self._lock = threading.RLock()

    def register(self, worker_id: int, handle: WorkerHandle) -> None: ...
    def get(self, worker_id: int) -> WorkerHandle | None: ...
    def update_status(self, worker_id: int, status: WorkerStatus) -> None: ...
    def all(self) -> list[WorkerHandle]: ...
    def active(self) -> list[WorkerHandle]: ...
    def by_level(self, level: int) -> list[WorkerHandle]: ...
```

### Migration Scope: All shared-dict consumers

| Component | Current | After |
|-----------|---------|-------|
| `orchestrator.py` | `self._workers` dict | `self._registry: WorkerRegistry` |
| `worker_manager.py` | `workers=self._workers` | `registry=self._registry` |
| `level_coordinator.py` | `workers=self._workers` | `registry=self._registry` |
| `state_sync_service.py` | `workers=self._workers` | `registry=self._registry` |
| `task_retry_manager.py` | `workers=self._workers` | `registry=self._registry` |

### New File

- `zerg/worker_registry.py` — WorkerRegistry class (~100-150 lines)

### Acceptance Criteria

- [ ] Worker state owned by single component (WorkerRegistry)
- [ ] All 5 consumers migrated from raw dict to registry
- [ ] Clear mutation paths with thread-safe access (RLock)
- [ ] Components testable with mock registry
- [ ] No direct `self._workers` dict access outside WorkerRegistry

---

## Cross-Cutting Requirements

### Testing

- Each PR includes updated/new unit tests for refactored modules
- Integration tests verify end-to-end worker lifecycle still works
- No test may be skipped or deleted to pass CI

### Quality Gates

- `python -m zerg.validate_commands` must pass (drift detection)
- `ruff check` / `ruff format` pass
- Type checking passes (if configured)
- All existing CI checks green

### Risk Mitigations

- Each PR is independently mergeable and testable
- PR 2 (splits) builds on PR 1 (dedup) — must not start until PR 1 is merged
- PR 3 (registry) builds on PR 2 (splits) — must not start until PR 2 is merged
- Feature branch per PR: `refactor/sync-async-dedup`, `refactor/god-class-split`, `refactor/worker-registry`

---

## Open Questions

None — all resolved during Socratic discovery.

---

## Decision Log

| Decision | Choice | Rationale |
|----------|--------|-----------|
| Sequence | #136 → #132 → #138 | Dedup reduces volume before splits |
| Async pattern | Hybrid (async-first + callable injection for polling) | Sync fast-path needed for poll/heartbeat |
| Protocol structure | Flat modules (not subpackage) | Matches project style |
| File size limit | 500 soft / 600 hard | Allows natural class boundaries |
| PR strategy | 1 PR per issue (3 total) | Atomic logical changes |
| Registry scope | All 5 shared-dict consumers | Full decoupling |
| Backward compat | Clean break, no re-exports | Simpler codebase |
| DI approach | Simple WorkerRegistry | No new dependencies |

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.