4.3 KiB
Hardening CLI And Sync Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Make Gitea mirror fail reliably on API/git/config/path errors and expose a proper package CLI entrypoint.
Architecture: Keep the existing module layout, but move executable orchestration into an importable src.cli module. Preserve gitea-mirror.py as a compatibility wrapper while making process_repo return success/failure for aggregate exit handling.
Tech Stack: Python 3.12, requests, pydantic v2, pytest, ruff, uv.
Task 1: API Errors Are Fatal
Files:
-
Modify:
src/gitea_api.py -
Test:
tests/test_gitea_api.py -
Step 1: Write failing tests
def test_get_repositories_raises_on_http_error(monkeypatch):
api = GiteaApi(endpoint="https://example.com", api_token="token")
session = FakeSession([FakeResponse(status_code=500, data=[])])
monkeypatch.setattr(requests, "session", lambda: session)
with pytest.raises(GiteaApiError):
api.get_repositories()
- Step 2: Run test to verify it fails
Run: uv run pytest tests/test_gitea_api.py -v
Expected: FAIL because GiteaApiError does not exist or is not raised.
- Step 3: Implement minimal code
Add GiteaApiError, request timeout, network exception handling, JSON/schema exception handling, and raise instead of returning [].
- Step 4: Run test to verify it passes
Run: uv run pytest tests/test_gitea_api.py -v
Expected: PASS.
Task 2: Git Failures Affect Exit Code
Files:
-
Create:
src/cli.py -
Modify:
gitea-mirror.py -
Test:
tests/test_mirror_process.py,tests/test_cli.py -
Step 1: Write failing tests
def test_process_repo_returns_false_when_clone_fails(monkeypatch, tmp_path):
monkeypatch.setattr(cli, "git_clone", lambda **kwargs: False)
assert not cli.process_repo(config=make_config(tmp_path, FetchConfig(ssh_key="/tmp/key")), repo=make_repo())
- Step 2: Run test to verify it fails
Run: uv run pytest tests/test_mirror_process.py tests/test_cli.py -v
Expected: FAIL because process_repo currently returns None and no importable CLI module exists.
- Step 3: Implement minimal code
Move orchestration to src.cli, return booleans from process_repo, aggregate failures in run, and make main return non-zero for failures.
- Step 4: Run test to verify it passes
Run: uv run pytest tests/test_mirror_process.py tests/test_cli.py -v
Expected: PASS.
Task 3: Validate Repository Paths And Config
Files:
-
Modify:
src/models.py -
Modify:
src/repository_name.py -
Test:
tests/test_config.py,tests/test_repository_name.py -
Step 1: Write failing tests
def test_output_path_rejects_parent_traversal(tmp_path):
repo = make_repo()
with pytest.raises(ValueError):
get_repository_path(str(tmp_path), "../{name}", repo)
- Step 2: Run test to verify it fails
Run: uv run pytest tests/test_config.py tests/test_repository_name.py -v
Expected: FAIL because validation is missing.
- Step 3: Implement minimal code
Add repository format validation in Config, log level validation in LoggingConfig, and a safe output path helper that rejects absolute paths and paths escaping out_dir.
- Step 4: Run test to verify it passes
Run: uv run pytest tests/test_config.py tests/test_repository_name.py -v
Expected: PASS.
Task 4: Package CLI Entrypoint
Files:
-
Modify:
pyproject.toml -
Modify:
gitea-mirror.py -
Test:
tests/test_cli.py -
Step 1: Write failing test
def test_project_defines_console_script():
data = tomllib.loads(Path("pyproject.toml").read_text())
assert data["project"]["scripts"]["gitea-mirror"] == "src.cli:main"
- Step 2: Run test to verify it fails
Run: uv run pytest tests/test_cli.py -v
Expected: FAIL because [project.scripts] is absent.
- Step 3: Implement minimal code
Add [project.scripts] gitea-mirror = "src.cli:main" and make gitea-mirror.py delegate to src.cli.main.
- Step 4: Run full verification
Run: make check
Expected: PASS.