Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ arbiter serve [--port 8080] # API + dashboard

```bash
pip install ".[test]"
PYTHONPATH=src python -m pytest tests/ -v # 78 tests, <7 seconds
PYTHONPATH=src python -m pytest tests/ -v
```

## Quality Gate
Expand Down
2 changes: 1 addition & 1 deletion src/arbiter/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""Arbiter — Agent-aware code quality system."""

__version__ = "0.1.0"
__version__ = "0.6.0"
2 changes: 1 addition & 1 deletion src/arbiter/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1427,7 +1427,7 @@ def _repo_dict(name: str, score: RepoScore, loc: int, density: float) -> dict:
else:
col_w = max(len(name_a), len(name_b), 15)
print("\nArbiter Compare")
print("\u2550" * 50)
print("=" * 50)
print(f"{'':24s}{name_a:<{col_w}s} {name_b}")

def _score_str(s: RepoScore) -> str:
Expand Down
3 changes: 2 additions & 1 deletion src/arbiter/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from typing import Any
from urllib.parse import parse_qs, urlparse

from arbiter import __version__
from arbiter.store import Store

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -128,7 +129,7 @@ def _handle_commit_detail(self, params: dict, commit_hash: str) -> None:
self._send_json({"error": "Commit not found"}, status=404)

def _handle_health(self, params: dict) -> None:
self._send_json({"status": "ok", "version": "0.2.0"})
self._send_json({"status": "ok", "version": __version__})

def _serve_dashboard(self) -> None:
index = _DASHBOARD_DIR / "index.html"
Expand Down
47 changes: 42 additions & 5 deletions src/arbiter/git_historian.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,21 @@ class CommitInfo:
# Git log format: hash|author_name|author_email|timestamp|subject
_LOG_FORMAT = "%H|%an|%ae|%aI|%s"
_LOG_SEP = "|"
_FALLBACK_EXCLUDED_DIRS = {
".git",
".hg",
".svn",
"__pycache__",
".mypy_cache",
".pytest_cache",
".ruff_cache",
".tox",
".venv",
"venv",
"build",
"dist",
"node_modules",
}


def walk_commits(
Expand Down Expand Up @@ -178,15 +193,37 @@ def get_diff_files(repo_path: str | Path, base_branch: str = "main") -> list[str
def get_python_files(repo_path: str | Path) -> list[Path]:
"""List all tracked Python files in the repo."""
repo = Path(repo_path)
result = subprocess.run(
["git", "-C", str(repo), "ls-files", "*.py"],
capture_output=True, text=True, timeout=10,
)
try:
result = subprocess.run(
["git", "-C", str(repo), "ls-files", "*.py"],
capture_output=True, text=True, timeout=10,
)
except (FileNotFoundError, OSError, subprocess.SubprocessError):
return _fallback_python_files(repo)
if result.returncode != 0:
return []
return _fallback_python_files(repo)
return [repo / f for f in result.stdout.strip().split("\n") if f]


def _fallback_python_files(repo: Path) -> list[Path]:
"""Find Python files without git, used when git is unavailable."""
if not repo.exists():
return []
if repo.is_file():
return [repo] if repo.suffix == ".py" else []

files: list[Path] = []
for path in repo.rglob("*.py"):
try:
rel_parts = path.relative_to(repo).parts
except ValueError:
continue
if any(part in _FALLBACK_EXCLUDED_DIRS for part in rel_parts[:-1]):
continue
files.append(path)
return sorted(files)


def count_loc(repo_path: str | Path) -> int:
"""Count total lines of Python code in the repo."""
total = 0
Expand Down
2 changes: 2 additions & 0 deletions tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from pathlib import Path

import pytest
from arbiter import __version__
from arbiter.api import ArbiterHandler
from arbiter.scoring import RepoScore
from arbiter.store import Store
Expand Down Expand Up @@ -63,6 +64,7 @@ def test_health(self, server_url):
status, data = _get(server_url, "/api/health")
assert status == 200
assert data["status"] == "ok"
assert data["version"] == __version__

def test_score(self, server_url):
status, data = _get(server_url, "/api/score")
Expand Down
27 changes: 27 additions & 0 deletions tests/test_release_metadata.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"""Release metadata checks for the public package surface."""

from __future__ import annotations

from pathlib import Path
import tomllib

import arbiter


ROOT = Path(__file__).resolve().parents[1]


def test_runtime_version_matches_pyproject() -> None:
metadata = tomllib.loads((ROOT / "pyproject.toml").read_text(encoding="utf-8"))

assert arbiter.__version__ == metadata["project"]["version"]


def test_readme_uses_distribution_name() -> None:
readme = (ROOT / "README.md").read_text(encoding="utf-8")

assert "pypi/v/arbiter-score" in readme
assert "https://pypi.org/project/arbiter-score/" in readme
assert "pip install arbiter-score" in readme
assert "pip install arbiter " not in readme
assert "pip install \"arbiter[analyzers]\"" not in readme
Loading