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
7 changes: 7 additions & 0 deletions .github/workflows/_claude-code.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ on:
required: false
default: true
type: boolean
use_sticky_comment:
description: 'Use just one comment to deliver PR comments (only applies for pull_request event workflows)'
required: false
default: false
type: boolean
secrets:
ANTHROPIC_API_KEY:
required: true
Expand Down Expand Up @@ -85,13 +90,15 @@ jobs:
--model claude-sonnet-4-5-20250929
--allowed-tools "${{ inputs.allowed_tools }}"
--system-prompt "Read the CLAUDE.md file in the root folder of this repository and explicitely acknowledge you will apply **all** guidance therein and in **all** linked documents."

- name: Run Claude Code (Automation Mode)
if: inputs.mode == 'automation'
uses: anthropics/claude-code-action@v1.0.22
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
track_progress: ${{ inputs.track_progress }}
use_sticky_comment: ${{ inputs.use_sticky_comment }}
additional_permissions: |
actions: read
allowed_bots: "dependabot[bot],renovate[bot]"
Expand Down
10 changes: 5 additions & 5 deletions .github/workflows/_package-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,15 @@ jobs:
experimental: [false]
include:
- runner: ubuntu-24.04-arm
experimental: true
experimental: false
- runner: macos-latest
experimental: true
experimental: false
- runner: macos-13
experimental: true
experimental: false
- runner: windows-latest
experimental: true
experimental: false
- runner: windows-11-arm
experimental: true
experimental: false

permissions:
attestations: write
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/claude-code-automation-pr-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,206 +11,211 @@

jobs:
claude-review:
if: |
contains(github.event.pull_request.labels.*.name, 'claude') ||
github.event.action == 'ready_for_review'
uses: ./.github/workflows/_claude-code.yml
with:
mode: 'automation'
allowed_tools: 'mcp__github_inline_comment__create_inline_comment,Read,Write,Edit,MultiEdit,Glob,Grep,LS,WebFetch,WebSearch,Bash(git:*),Bash(bun:*),Bash(npm:*),Bash(npx:*),Bash(gh:*),Bash(uv:*),Bash(make:*)'
track_progress: ${{ github.event_name == 'pull_request' && contains(fromJSON('["opened", "synchronize", "ready_for_review", "reopened"]'), github.event.action) }}
use_sticky_comment: true
prompt: |
# PR REVIEW FOR AIGNOSTICS PYTHON SDK

**REPO**: ${{ github.repository }}
**PR**: #${{ github.event.pull_request.number }}
**BRANCH**: ${{ github.head_ref }}

## Context

This is a **medical device software SDK** for computational pathology. You are reviewing code that:
- Processes whole slide images (WSI) - gigapixel medical images
- Integrates with FDA/MDR regulated AI/ML applications
- Handles sensitive medical data (HIPAA compliance required)
- Follows a **modulith architecture** with strict module boundaries

## Documentation References

**IMPORTANT**: This repository has comprehensive documentation you MUST reference:
- **CLAUDE.md** (root) - Architecture, testing workflow, development standards
- **.github/CLAUDE.md** - Complete CI/CD guide (19 workflows, test strategy)
- **src/aignostics/*/CLAUDE.md** - Module-specific implementation guides

Read these files and apply their guidance throughout your review.

## CRITICAL CHECKS (BLOCKING - Must Pass)

### 1. Test Markers (CRITICAL!)

**EVERY test MUST have at least one marker: `unit`, `integration`, or `e2e`**

Tests without these markers will **NOT run in CI** because the pipeline explicitly filters by markers.

```bash
# Find unmarked tests (should return 0 tests):
uv run pytest -m "not unit and not integration and not e2e" --collect-only

# If unmarked tests found, check the specific files changed in this PR:
uv run pytest tests/aignostics/<module>/<file>_test.py --collect-only
```

**Action if violations found**:
- List all test files missing markers with file paths and line numbers
- Provide exact decorator to add (e.g., `@pytest.mark.unit`)
- Reference `.github/CLAUDE.md` test categorization guide

### 2. Test Coverage (85% Minimum)

If coverage drops below 85%, identify which new code lacks tests.

### 3. Code Quality (Must Pass)

```bash
# Check linting:
make lint
# Runs: ruff format, ruff check, pyright (basic), mypy (strict)
```

All 4 checks must pass. If failures occur, provide specific fixes.

### 4. Conventional Commits

All commits must follow: `type(scope): description`

Valid types: `feat`, `fix`, `docs`, `refactor`, `test`, `chore`, `ci`, `perf`, `build`

```bash
# Check commit messages:
git log --oneline origin/main..HEAD
```

## Repository-Specific Review Areas

### 5. Architecture Compliance

**Modulith Principles**:
- Each module is self-contained (Service + optional CLI + optional GUI)
- **CRITICAL**: CLI and GUI must NEVER depend on each other
- Both depend on Service layer only
- No circular dependencies between modules

**Service Pattern**:
- All services inherit from `BaseService` (from `utils`)
- Must implement `health()` → `Health` and `info()` → `dict`
- Use dependency injection via `locate_implementations(BaseService)`
- No decorator-based DI

**Check**:
```bash
# Find all service implementations:
grep -r "class.*Service.*BaseService" src/aignostics/

# Check for circular imports:
# Look for cross-module imports that shouldn't exist
```

### 6. Testing Strategy

**Test Categories** (see `.github/CLAUDE.md` for full details):
- `unit`: Isolated, no external deps, <10s timeout, sequential (XDIST_WORKER_FACTOR=0.0)
- `integration`: Mocked external services, <5min, limited parallel (0.2)
- `e2e`: Real API calls, staging environment, full parallel (1.0)
- `long_running`: E2E tests ≥5min, aggressive parallel (2.0), opt-out with label
- `very_long_running`: E2E tests ≥60min, only run when explicitly enabled

**For new tests, verify**:
- Correct marker applied based on test characteristics
- Timeout set if >10s (use `@pytest.mark.timeout(60)`)
- E2E tests only run against staging (never production in PR CI)

### 7. Medical Device & Security

**Check for**:
- Secrets/tokens in code or commits (use .env, never hardcode)
- Sensitive data masking in logs (use `mask_secrets=True`)
- Medical data handling (DICOM compliance, proper anonymization)
- OAuth token management (refresh before 5min expiry)

**WSI Processing**:
- Large images processed in tiles (never load full image in memory)
- Proper cleanup of temp files
- Progress tracking for long operations

### 8. Breaking Changes

**Check if PR introduces**:
- API client method signature changes
- CLI command changes (breaking user scripts)
- Environment variable changes
- Configuration file format changes
- OpenAPI model changes (from codegen)

If breaking changes detected, verify:
- Proper deprecation warnings added
- Migration guide in PR description
- Version bump appropriate (major vs minor)

### 9. CI/CD Impact

**If workflow files changed** (`.github/workflows/*.yml`):
- Verify syntax is correct
- Check reusable workflow inputs/secrets match
- Validate test marker filtering logic
- Ensure BetterStack heartbeats still work

**If new tests added**:
- Are they categorized correctly for CI execution?
- Do they need to be scheduled tests?
- Is XDIST_WORKER_FACTOR appropriate?

### 10. Documentation Updates

**If new features added**:
- Is CLAUDE.md updated? (root or module-specific)
- Are docstrings added (Google style)?
- Is README updated if user-facing change?
- Are type hints complete?

## Code Quality Standards

- **Type Checking**: Dual checking (MyPy strict + PyRight basic) - both must pass
- **Line Length**: 120 chars max
- **Imports**: stdlib → 3rd-party → local, use relative imports within modules
- **Docstrings**: Google style for all public APIs
- **Error Handling**: Custom exceptions from `system/_exceptions.py`
- **Logging**: Structured logging via `get_logger(__name__)`

## Review Output Format

For each issue found, provide:

1. **Location**: `file_path:line_number` (e.g., `src/aignostics/platform/_client.py:123`)
2. **Issue**: Clear description with reference to CLAUDE.md section
3. **Reproduce**: Exact command to reproduce (e.g., `uv run pytest tests/...`)
4. **Fix**: Concrete code example or command
5. **Verify**: Command to verify fix works (e.g., `make lint && make test`)

**Use inline comments** for specific code issues.
**Use PR-level comment** for:
- Summary of findings (blocking vs suggestions)
- Overall architecture feedback
- Praise for excellent work
- Missing CLAUDE.md updates needed

## Final Steps

After completing your review:

1. **Summarize findings** in PR comment using `gh pr comment`
2. **Categorize issues**: Blocking (must fix) vs Suggestions (nice to have)
3. **Provide commands** to fix all blocking issues in one go if possible
4. **Reference documentation**: Link to relevant CLAUDE.md sections

Use `gh pr comment` with your Bash tool to leave your comprehensive review as a comment on the PR.

---

**Remember**: This is medical device software. Insist on highest standards. Be thorough, actionable, and kind.
secrets:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {}
2 changes: 1 addition & 1 deletion CLI_REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ $ aignostics [OPTIONS] COMMAND [ARGS]...
* `--show-completion`: Show completion for the current shell, to copy it or customize the installation.
* `--help`: Show this message and exit.

🔬 Aignostics Python SDK v0.2.226 - built with love in Berlin 🐻
🔬 Aignostics Python SDK v0.2.226 - built with love in Berlin 🐻 // Python v3.14.1

**Commands**:

Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ dependencies = [
# From Template
"fastapi[all,standard]>=0.123.10",
"humanize>=4.14.0,<5",
"nicegui[native]>=3.1.0,<3.2.0", # Regression in 3.2.0
"nicegui[native]>=3.3.1,<4",
"packaging>=25.0,<26",
"platformdirs>=4.5.1,<5",
"psutil>=7.1.3,<8",
Expand Down Expand Up @@ -153,7 +153,7 @@ dev = [
"pip-licenses @ git+https://github.com/neXenio/pip-licenses.git@master", # https://github.com/raimon49/pip-licenses/pull/224
"pre-commit>=4.5.0,<5",
"pyright>=1.1.406,<1.1.407", # Regression in 1.1.407, see https://github.com/microsoft/pyright/issues/11060
"pytest>=9.0.1,<10",
"pytest>=9.0.2,<10",
"pytest-asyncio>=1.3.0,<2",
"pytest-cov>=7.0.0,<8",
"pytest-docker>=3.2.5,<4",
Expand Down
22 changes: 11 additions & 11 deletions src/aignostics/application/_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -746,7 +746,7 @@ def run_submit( # noqa: PLR0913, PLR0917
)
except ValueError as e:
logger.warning(
"Bad input to create run for application '%s' (version: '%s'): %s", application_id, application_version, e
"Bad input to create run for application '{}' (version: '{}'): {}", application_id, application_version, e
)
console.print(
f"[warning]Warning:[/warning] Bad input to create run for application "
Expand All @@ -755,7 +755,7 @@ def run_submit( # noqa: PLR0913, PLR0917
sys.exit(2)
except NotFoundException as e:
logger.warning(
"Could not find application version '%s' (version: '%s'): %s", application_id, application_version, e
"Could not find application version '{}' (version: '{}'): {}", application_id, application_version, e
)
console.print(
f"[warning]Warning:[/warning] Could not find application '{application_id} "
Expand All @@ -773,10 +773,10 @@ def run_submit( # noqa: PLR0913, PLR0917
try:
metadata_dict = read_metadata_csv_to_dict(metadata_csv_file=metadata_csv_file)
if not metadata_dict:
console.print("Could mot read metadata file '%s'", metadata_csv_file)
console.print(f"Could not read metadata file '{metadata_csv_file}'")
sys.exit(2)
logger.trace(
"Submitting run for application '%s' (version: '%s') with metadata: %s",
"Submitting run for application '{}' (version: '{}') with metadata: {}",
application_id,
app_version.version_number,
metadata_dict,
Expand Down Expand Up @@ -809,7 +809,7 @@ def run_submit( # noqa: PLR0913, PLR0917
return application_run.run_id
except ValueError as e:
logger.warning(
"Bad input to create run for application '%s' (version: %s): %s",
"Bad input to create run for application '{}' (version: {}): {}",
application_id,
app_version.version_number,
e,
Expand All @@ -821,7 +821,7 @@ def run_submit( # noqa: PLR0913, PLR0917
sys.exit(2)
except Exception as e:
logger.exception(
"Failed to create run for application '%s' (version: %s)", application_id, app_version.version_number
"Failed to create run for application '{}' (version: {})", application_id, app_version.version_number
)
console.print(
f"[error]Error:[/error] Failed to create run for application "
Expand Down Expand Up @@ -1059,7 +1059,7 @@ def run_cancel_by_filter( # noqa: C901, PLR0912, PLR0915
sys.exit(1)

logger.trace(
"Canceling runs with filters: tags=%s, application_id=%s, application_version=%s, limit=%s, dry_run=%s",
"Canceling runs with filters: tags={}, application_id={}, application_version={}, limit={}, dry_run={}",
tags,
application_id,
application_version,
Expand Down Expand Up @@ -1207,7 +1207,7 @@ def run_update_item_metadata(
sys.exit(2)
except ValueError as e:
logger.warning(
"Run ID '%s' or item external ID '%s' invalid or metadata invalid: %s",
"Run ID '{}' or item external ID '{}' invalid or metadata invalid: {}",
run_id,
external_id,
e,
Expand All @@ -1219,7 +1219,7 @@ def run_update_item_metadata(
sys.exit(2)
except Exception as e:
logger.exception(
"Failed to update custom metadata for item '%s' in run with ID '%s'",
"Failed to update custom metadata for item '{}' in run with ID '{}'",
external_id,
run_id,
)
Expand Down Expand Up @@ -1278,8 +1278,8 @@ def result_download( # noqa: C901, PLR0913, PLR0915, PLR0917
) -> None:
"""Download results of a run."""
logger.trace(
"Downloading results for run with ID '%s' to '%s' with options: "
"create_subdirectory_for_run=%s, create_subdirectory_per_item=%s, wait_for_completion=%s, qupath_project=%r",
"Downloading results for run with ID '{}' to '{}' with options: "
"create_subdirectory_for_run={}, create_subdirectory_per_item={}, wait_for_completion={}, qupath_project={}",
run_id,
destination_directory,
create_subdirectory_for_run,
Expand Down
Loading
Loading