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 .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ data/
logs/
*.log

# Documentation (not needed in container, except README.md for setup.py)
# Documentation (not needed in container)
docs/

# Tests (not needed in production)
Expand Down
7 changes: 1 addition & 6 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,11 @@ HOTKEY_NAME=default
# Weights & Biases API key for logging
# Signup https://wandb.ai/site for a key
WANDB_API_KEY=
# for issue bounties api calls
# GitHub PAT for validator API calls
GITTENSOR_VALIDATOR_PAT=
# Optional custom name for wandb logging
WANDB_VALIDATOR_NAME=vali

# ******* MINER VARIABLES *******
# GitHub Personal Access Token
# https://github.com/settings/personal-access-tokens
GITTENSOR_MINER_PAT=

# validator database settings (for gittensor validator/dashboard)
STORE_DB_RESULTS=false
# DB_HOST=
Expand Down
26 changes: 13 additions & 13 deletions .github/PULL_REQUEST_TEMPLATE/weight_adjustment.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,28 @@

### Changes Summary

| Metric | Gold | Silver | Bronze | Total |
| -------------------- | ---- | ------ | ------ | ----- |
| Repositories Added | 0 | 0 | 0 | 0 |
| Repositories Removed | 0 | 0 | 0 | 0 |
| Weights Modified | 0 | 0 | 0 | 0 |
| Net Weight Change | 0 | 0 | 0 | 0 |
| Metric | Total |
| -------------------- | ----- |
| Repositories Added | 0 |
| Repositories Removed | 0 |
| Weights Modified | 0 |
| Net Weight Change | 0 |

### Added Repositories

<!-- Delete this section if none -->

| Repository | Tier | Branch | Weight |
| ---------- | ------ | ------ | ------ |
| owner/repo | silver | main | 20.00 |
| Repository | Branch | Weight |
| ---------- | ------ | ------ |
| owner/repo | main | 20.00 |

### Removed Repositories

<!-- Delete this section if none -->

| Repository | Tier | Reason |
| ---------- | ------ | ------ |
| owner/repo | silver | — |
| Repository | Reason |
| ---------- | ------ |
| owner/repo | — |

### Justification

Expand All @@ -49,4 +49,4 @@ Example:

- [ ] Changes summary table is filled in accurately
- [ ] Net weight changes are justified in the Justification section
- [ ] Added repositories have correct tier, branch, and initial weight
- [ ] Added repositories have correct branch and initial weight
14 changes: 6 additions & 8 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,14 @@ jobs:
- name: Checkout code
uses: actions/checkout@v4

- name: Install uv
uses: astral-sh/setup-uv@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: 'pip'
run: uv python install 3.12

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
run: uv sync --extra dev

- name: Run tests
run: pytest tests/ -v
run: uv run pytest tests/ -v
23 changes: 23 additions & 0 deletions .github/workflows/typecheck.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: Type Check

on:
pull_request:
branches: [main, test]

jobs:
pyright:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Install uv
uses: astral-sh/setup-uv@v4

- name: Set up Python
run: uv python install 3.12

- name: Install dependencies
run: uv sync --extra dev

- name: Run pyright
run: uv run pyright
10 changes: 5 additions & 5 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,8 @@ wandb

*.log

# Merge predictions local DB
merge-prediction-data/
gt-merge-preds.db
gt-merge-preds.db-wal
gt-merge-preds.db-shm
# Validator data directory (contains sensitive miner PATs)
data/

CLAUDE.md
.claude/
Expand All @@ -30,3 +27,6 @@ CLAUDE.md
target/
**/*.rs.bk
*.lock
!uv.lock

**/.venv/
16 changes: 7 additions & 9 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,13 @@ RUN pip install --break-system-packages uv

WORKDIR /app

# Copy dependency files
COPY pyproject.toml uv.lock ./
# Copy dependency files first (for Docker layer caching)
COPY pyproject.toml uv.lock README.md ./

# Create venv and sync dependencies
ENV VENV_DIR=/opt/venv
ENV VIRTUAL_ENV=$VENV_DIR
ENV PATH="$VENV_DIR/bin:$PATH"
RUN uv venv --python python3 $VENV_DIR && uv sync
# Install dependencies only (no project install yet — source code not copied)
ENV PATH="/app/.venv/bin:$PATH"
RUN uv sync --no-install-project

# Copy application code and install
# Copy application code and install the project
COPY . .
RUN uv pip install -e .
RUN uv sync
20 changes: 11 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,20 +34,22 @@ The result: a sustainable incentive layer that channels resources toward buildin

## Miners

**Recommended: Deploy with Docker**

> [!NOTE]
> The port in .env must be accessible by the public.
No miner neuron required — just register your GitHub PAT with validators using the CLI.

```bash
# Quick start
# Install
git clone https://github.com/entrius/gittensor.git
cd gittensor
cp .env.example .env
# Edit .env with proper values
nano .env
uv sync

# Set your GitHub PAT
export GITTENSOR_MINER_PAT=ghp_your_token_here

# Broadcast PAT to validators
gitt miner post --wallet <name> --hotkey <hotkey>

docker-compose -f docker-compose.miner.yml up -d
# Check which validators have your PAT stored
gitt miner check --wallet <name> --hotkey <hotkey>
```

See full guide **[here](https://docs.gittensor.io/miner.html)**
Expand Down
15 changes: 0 additions & 15 deletions docker-compose.miner.yml

This file was deleted.

2 changes: 1 addition & 1 deletion docker-compose.vali.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ services:
volumes:
# 'ro' = readonly
- ${WALLET_PATH}:/root/.bittensor/wallets:ro
- ./merge-prediction-data:/app/data
- ./data:/app/data
# optional: uncomment this if you are running validator database
# networks:
# - gittensor_network
Expand Down
66 changes: 37 additions & 29 deletions gittensor/classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@

from gittensor.constants import MIN_TOKEN_SCORE_FOR_BASE_SCORE
from gittensor.utils.utils import parse_repo_name
from gittensor.validator.oss_contributions.tier_config import Tier, TierConfig, TierStats

GITHUB_DOMAIN = 'https://github.com/'

Expand Down Expand Up @@ -131,6 +130,18 @@ class Issue:
state: Optional[str] = None # "OPEN" or "CLOSED"
author_association: Optional[str] = None # e.g., "OWNER", "MEMBER", "COLLABORATOR", "CONTRIBUTOR", "NONE"

# Issue discovery fields
author_github_id: Optional[str] = None # Issue author's GitHub user ID (for miner matching)
is_transferred: bool = False
updated_at: Optional[datetime] = None
discovery_base_score: float = 0.0
discovery_earned_score: float = 0.0
discovery_review_quality_multiplier: float = 1.0
discovery_repo_weight_multiplier: float = 1.0
discovery_time_decay_multiplier: float = 1.0
discovery_credibility_multiplier: float = 1.0
discovery_open_issue_spam_multiplier: float = 1.0


@dataclass
class PullRequest:
Expand All @@ -143,15 +154,14 @@ class PullRequest:
repository_full_name: str
uid: int
hotkey: str
github_id: str
github_id: Optional[str]
title: str
author_login: str
merged_at: Optional[datetime] # None for OPEN PRs
created_at: datetime

# PR state based fields
pr_state: PRState
repository_tier_configuration: Optional[TierConfig] = None # assigned when scoring PR

# Score fields
repo_weight_multiplier: float = 1.0
Expand All @@ -164,8 +174,6 @@ class PullRequest:
credibility_multiplier: float = 1.0
review_quality_multiplier: float = 1.0 # Penalty for CHANGES_REQUESTED reviews from maintainers
changes_requested_count: int = 0 # Number of maintainer CHANGES_REQUESTED reviews
raw_credibility: float = 1.0 # Before applying ^k scalar
credibility_scalar: int = 1 # The k value from tier config
earned_score: float = 0.0
collateral_score: float = 0.0 # For OPEN PRs: potential_score * collateral_percent

Expand Down Expand Up @@ -196,14 +204,9 @@ def set_file_changes(self, file_changes: List[FileChange]) -> None:
def is_pioneer_eligible(self) -> bool:
"""Check if this PR qualifies for pioneer consideration.

A PR is eligible if it is merged, has a tier configuration,
and meets the minimum token score quality gate.
A PR is eligible if it is merged and meets the minimum token score quality gate.
"""
return (
self.repository_tier_configuration is not None
and self.merged_at is not None
and self.token_score >= MIN_TOKEN_SCORE_FOR_BASE_SCORE
)
return self.merged_at is not None and self.token_score >= MIN_TOKEN_SCORE_FOR_BASE_SCORE

def calculate_final_earned_score(self) -> float:
"""Combine base score with all multipliers. Pioneer dividend is added separately after."""
Expand All @@ -218,13 +221,7 @@ def calculate_final_earned_score(self) -> float:

self.earned_score = self.base_score * prod(multipliers.values())

# Log all multipliers (credibility shows ^k format)
def _format_multiplier(k: str, v: float) -> str:
if k == 'cred':
return f'cred={self.raw_credibility:.2f}^{self.credibility_scalar}'
return f'{k}={v:.2f}'

mult_str = ' × '.join(_format_multiplier(k, v) for k, v in multipliers.items())
mult_str = ' × '.join(f'{k}={v:.2f}' for k, v in multipliers.items())
bt.logging.info(
f'├─ {self.pr_state.value} PR #{self.number} ({self.repository_full_name}) → {self.earned_score:.2f}'
)
Expand All @@ -233,7 +230,7 @@ def _format_multiplier(k: str, v: float) -> str:
return self.earned_score

@classmethod
def from_graphql_response(cls, pr_data: dict, uid: int, hotkey: str, github_id: str) -> 'PullRequest':
def from_graphql_response(cls, pr_data: dict, uid: int, hotkey: str, github_id: Optional[str]) -> 'PullRequest':
"""Create PullRequest from GraphQL API response for any PR state."""
from gittensor.validator.utils.datetime_utils import parse_github_timestamp_to_cst

Expand All @@ -247,6 +244,8 @@ def from_graphql_response(cls, pr_data: dict, uid: int, hotkey: str, github_id:
for issue in raw_issues:
if is_merged and not (issue.get('closedAt') and issue.get('state') == 'CLOSED'):
continue
issue_author = issue.get('author') or {}
author_db_id = issue_author.get('databaseId')
issues.append(
Issue(
number=issue['number'],
Expand All @@ -255,16 +254,17 @@ def from_graphql_response(cls, pr_data: dict, uid: int, hotkey: str, github_id:
title=issue['title'],
created_at=parse_github_timestamp_to_cst(issue['createdAt']) if issue.get('createdAt') else None,
closed_at=parse_github_timestamp_to_cst(issue['closedAt']) if issue.get('closedAt') else None,
author_login=issue.get('author', {}).get('login') if issue.get('author') else None,
author_login=issue_author.get('login'),
state=issue.get('state'),
author_association=issue.get('authorAssociation'),
author_github_id=str(author_db_id) if author_db_id else None,
updated_at=parse_github_timestamp_to_cst(issue['updatedAt']) if issue.get('updatedAt') else None,
)
)

description: str = pr_data.get('bodyText', '')
last_edited_at = (
parse_github_timestamp_to_cst(pr_data.get('lastEditedAt')) if pr_data.get('lastEditedAt') else None
)
raw_edited_at = pr_data.get('lastEditedAt')
last_edited_at = parse_github_timestamp_to_cst(raw_edited_at) if isinstance(raw_edited_at, str) else None
merged_at = parse_github_timestamp_to_cst(pr_data['mergedAt']) if is_merged else None

return cls(
Expand Down Expand Up @@ -301,7 +301,6 @@ class MinerEvaluation:
total_collateral_score: float = 0.0 # Collateral from open PRs
total_nodes_scored: int = 0 # Total AST nodes scored across all PRs
unique_repos_count: int = 0
qualified_unique_repos_count: int = 0 # Repos meeting min token score threshold

# Overall token scoring breakdown (aggregated across all PRs)
total_token_score: float = 0.0
Expand All @@ -316,10 +315,19 @@ class MinerEvaluation:
closed_pull_requests: List[PullRequest] = field(default_factory=list)
unique_repos_contributed_to: Set[str] = field(default_factory=set)

# Tier level details (None = no tier unlocked yet)
current_tier: Optional[Tier] = None
credibility_by_tier: Dict[Tier, float] = field(default_factory=dict)
stats_by_tier: Dict[Tier, TierStats] = field(default_factory=lambda: {tier: TierStats() for tier in Tier})
# Eligibility and credibility
is_eligible: bool = False
credibility: float = 0.0

# Issue discovery scoring
issue_discovery_score: float = 0.0
issue_token_score: float = 0.0 # sum of solving PR token_scores for scored issues
issue_credibility: float = 0.0
is_issue_eligible: bool = False
total_solved_issues: int = 0
total_valid_solved_issues: int = 0 # solved issues where solving PR has token_score >= 5
total_closed_issues: int = 0
total_open_issues: int = 0

@property
def total_prs(self) -> int:
Expand Down
Loading
Loading