tixio 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
tixio-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,167 @@
1
+ Metadata-Version: 2.4
2
+ Name: tixio
3
+ Version: 0.1.0
4
+ Summary: Confidence-gated, model-assisted issue triage for engineering teams
5
+ Project-URL: Homepage, https://github.com/Bojan-Ivanovski/tixio
6
+ Project-URL: Repository, https://github.com/Bojan-Ivanovski/tixio
7
+ Project-URL: Issues, https://github.com/Bojan-Ivanovski/tixio/issues
8
+ Keywords: github,issue-triage,classification,automation,decider
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Environment :: Console
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Topic :: Software Development :: Bug Tracking
15
+ Requires-Python: >=3.11
16
+ Description-Content-Type: text/markdown
17
+ Requires-Dist: decider-ai==1.5.0
18
+ Requires-Dist: typer
19
+ Requires-Dist: requests
20
+ Provides-Extra: dev
21
+ Requires-Dist: tox; extra == "dev"
22
+ Requires-Dist: black; extra == "dev"
23
+ Requires-Dist: isort; extra == "dev"
24
+ Requires-Dist: pyright; extra == "dev"
25
+
26
+ <div align="center">
27
+
28
+ # Tixio
29
+
30
+ **Confidence-gated, model-assisted issue triage for engineering teams.**
31
+
32
+ [![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-3776AB?logo=python&logoColor=white)](https://www.python.org/)
33
+ [![PyPI](https://img.shields.io/pypi/v/tixio?logo=pypi&logoColor=white)](https://pypi.org/project/tixio/)
34
+ [![Mapika Decider](https://img.shields.io/badge/powered%20by-Mapika%20Decider-6f42c1)](https://huggingface.co/Mapika)
35
+ [![GitHub](https://img.shields.io/badge/platform-GitHub-181717?logo=github)](https://github.com/Bojan-Ivanovski/tixio)
36
+
37
+ </div>
38
+
39
+ Tixio turns incoming issues into structured, reviewable triage decisions. It
40
+ reads the issue and recent discussion, classifies it with Mapika Decider, and
41
+ proposes consistent labels and ownership without silently changing your
42
+ repository.
43
+
44
+ Dry-run is the default. Changes are only eligible for automatic application
45
+ when their probability crosses the configured confidence threshold, and
46
+ security-sensitive issues suppress normal automated routing.
47
+
48
+ ## Why Tixio?
49
+
50
+ - **Safe by default** — preview every decision before applying it.
51
+ - **Confidence gated** — uncertain classifications stay queued for human review.
52
+ - **Shared taxonomy** — keep types, components, priorities, statuses, and sizes
53
+ consistent across repositories.
54
+ - **Multi-component aware** — one issue can belong to several engineering areas.
55
+ - **Provider-oriented design** — the triage engine depends on a common client
56
+ interface, with GitHub as the first implementation.
57
+ - **Local model workflow** — classification is powered by Mapika Decider rather
58
+ than a hosted text-generation API.
59
+
60
+ ## What it classifies
61
+
62
+ | Descriptor | Behavior |
63
+ | --- | --- |
64
+ | Type | Selects the primary nature of the issue. |
65
+ | Component | Selects one or more affected project areas. |
66
+ | Priority | Estimates impact and urgency. |
67
+ | Status | Proposes the appropriate delivery state. |
68
+ | Size | Estimates relative scope and decomposition level. |
69
+ | Signals | Detects security, privacy, compliance, missing information, and reproduction needs. |
70
+ | Assignee | Proposes an initial owner when confidence is sufficient. |
71
+
72
+ ## Installation
73
+
74
+ Tixio requires Python 3.11 or newer. Install the published package with:
75
+
76
+ ```bash
77
+ pip install tixio
78
+ ```
79
+
80
+ Or install the latest source version:
81
+
82
+ ```bash
83
+ git clone https://github.com/Bojan-Ivanovski/tixio.git
84
+ cd tixio
85
+ python -m venv .venv
86
+ source .venv/bin/activate
87
+ pip install -e .
88
+ ```
89
+
90
+ Set the target repository and a GitHub token with issue and label permissions:
91
+
92
+ ```bash
93
+ export GITHUB_TOKEN=github_pat_...
94
+ export GITHUB_REPOSITORY=owner/repository
95
+ ```
96
+
97
+ The first triage run may download and initialize the configured Decider model.
98
+
99
+ ## Quick start
100
+
101
+ ```bash
102
+ tixio triage 123 # preview decisions
103
+ tixio triage 123 --apply # apply high-confidence changes
104
+ tixio triage 123 --json # machine-readable output
105
+ tixio triage 123 --model Mapika/decider-4b
106
+ ```
107
+
108
+ ## Shared descriptors
109
+
110
+ The canonical taxonomy lives in `tixio/data/descriptors.py`. Types and
111
+ components can be synchronized with GitHub labels; priority, status, and size
112
+ remain static shared definitions.
113
+
114
+ ```bash
115
+ tixio descriptors view
116
+ tixio descriptors view --diff
117
+ tixio descriptors apply
118
+ tixio descriptors apply --force
119
+ ```
120
+
121
+ `descriptors apply` adds missing labels and updates mismatched descriptions or
122
+ colors. `--force` deletes and recreates mutable type and component labels, so
123
+ review the diff before using it on an established repository.
124
+
125
+ ## Safety model
126
+
127
+ Tixio separates a model recommendation from permission to mutate a repository:
128
+
129
+ 1. The issue, bounded body text, and recent comments become model context.
130
+ 2. Decider evaluates closed descriptor choices and independent risk signals.
131
+ 3. Tixio parses probabilities and applies confidence thresholds.
132
+ 4. The CLI prints the proposed label and assignment changes.
133
+ 5. GitHub is only modified when `--apply` is explicitly provided.
134
+
135
+ High-confidence security-sensitive results suppress normal label and assignee
136
+ updates so maintainers can route them through a private security process.
137
+
138
+ ## Architecture
139
+
140
+ ```text
141
+ tixio/
142
+ ├── cli/ Typer commands and presentation
143
+ ├── clients/ Provider-neutral interfaces
144
+ │ └── github/ GitHub transport, tickets, and descriptors
145
+ ├── data/ Canonical descriptor registry
146
+ └── decider/ Context, model adapter, policy, and result models
147
+ ```
148
+
149
+ The core `TriageService` accepts the shared `Client` abstraction. Additional
150
+ issue trackers can implement the same ticket, descriptor, and transport
151
+ interfaces without changing the classifier.
152
+
153
+ ## Development
154
+
155
+ ```bash
156
+ pip install -e '.[dev]'
157
+ tox -e lint
158
+ ```
159
+
160
+ The lint environment runs Pyright, isort, and Black checks.
161
+
162
+ ## Project status
163
+
164
+ Tixio is early-stage software. Run it in dry-run mode first, verify your label
165
+ taxonomy, and review model recommendations before enabling repository writes.
166
+
167
+ Contributions, issue reports, and provider integrations are welcome.
tixio-0.1.0/README.md ADDED
@@ -0,0 +1,142 @@
1
+ <div align="center">
2
+
3
+ # Tixio
4
+
5
+ **Confidence-gated, model-assisted issue triage for engineering teams.**
6
+
7
+ [![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-3776AB?logo=python&logoColor=white)](https://www.python.org/)
8
+ [![PyPI](https://img.shields.io/pypi/v/tixio?logo=pypi&logoColor=white)](https://pypi.org/project/tixio/)
9
+ [![Mapika Decider](https://img.shields.io/badge/powered%20by-Mapika%20Decider-6f42c1)](https://huggingface.co/Mapika)
10
+ [![GitHub](https://img.shields.io/badge/platform-GitHub-181717?logo=github)](https://github.com/Bojan-Ivanovski/tixio)
11
+
12
+ </div>
13
+
14
+ Tixio turns incoming issues into structured, reviewable triage decisions. It
15
+ reads the issue and recent discussion, classifies it with Mapika Decider, and
16
+ proposes consistent labels and ownership without silently changing your
17
+ repository.
18
+
19
+ Dry-run is the default. Changes are only eligible for automatic application
20
+ when their probability crosses the configured confidence threshold, and
21
+ security-sensitive issues suppress normal automated routing.
22
+
23
+ ## Why Tixio?
24
+
25
+ - **Safe by default** — preview every decision before applying it.
26
+ - **Confidence gated** — uncertain classifications stay queued for human review.
27
+ - **Shared taxonomy** — keep types, components, priorities, statuses, and sizes
28
+ consistent across repositories.
29
+ - **Multi-component aware** — one issue can belong to several engineering areas.
30
+ - **Provider-oriented design** — the triage engine depends on a common client
31
+ interface, with GitHub as the first implementation.
32
+ - **Local model workflow** — classification is powered by Mapika Decider rather
33
+ than a hosted text-generation API.
34
+
35
+ ## What it classifies
36
+
37
+ | Descriptor | Behavior |
38
+ | --- | --- |
39
+ | Type | Selects the primary nature of the issue. |
40
+ | Component | Selects one or more affected project areas. |
41
+ | Priority | Estimates impact and urgency. |
42
+ | Status | Proposes the appropriate delivery state. |
43
+ | Size | Estimates relative scope and decomposition level. |
44
+ | Signals | Detects security, privacy, compliance, missing information, and reproduction needs. |
45
+ | Assignee | Proposes an initial owner when confidence is sufficient. |
46
+
47
+ ## Installation
48
+
49
+ Tixio requires Python 3.11 or newer. Install the published package with:
50
+
51
+ ```bash
52
+ pip install tixio
53
+ ```
54
+
55
+ Or install the latest source version:
56
+
57
+ ```bash
58
+ git clone https://github.com/Bojan-Ivanovski/tixio.git
59
+ cd tixio
60
+ python -m venv .venv
61
+ source .venv/bin/activate
62
+ pip install -e .
63
+ ```
64
+
65
+ Set the target repository and a GitHub token with issue and label permissions:
66
+
67
+ ```bash
68
+ export GITHUB_TOKEN=github_pat_...
69
+ export GITHUB_REPOSITORY=owner/repository
70
+ ```
71
+
72
+ The first triage run may download and initialize the configured Decider model.
73
+
74
+ ## Quick start
75
+
76
+ ```bash
77
+ tixio triage 123 # preview decisions
78
+ tixio triage 123 --apply # apply high-confidence changes
79
+ tixio triage 123 --json # machine-readable output
80
+ tixio triage 123 --model Mapika/decider-4b
81
+ ```
82
+
83
+ ## Shared descriptors
84
+
85
+ The canonical taxonomy lives in `tixio/data/descriptors.py`. Types and
86
+ components can be synchronized with GitHub labels; priority, status, and size
87
+ remain static shared definitions.
88
+
89
+ ```bash
90
+ tixio descriptors view
91
+ tixio descriptors view --diff
92
+ tixio descriptors apply
93
+ tixio descriptors apply --force
94
+ ```
95
+
96
+ `descriptors apply` adds missing labels and updates mismatched descriptions or
97
+ colors. `--force` deletes and recreates mutable type and component labels, so
98
+ review the diff before using it on an established repository.
99
+
100
+ ## Safety model
101
+
102
+ Tixio separates a model recommendation from permission to mutate a repository:
103
+
104
+ 1. The issue, bounded body text, and recent comments become model context.
105
+ 2. Decider evaluates closed descriptor choices and independent risk signals.
106
+ 3. Tixio parses probabilities and applies confidence thresholds.
107
+ 4. The CLI prints the proposed label and assignment changes.
108
+ 5. GitHub is only modified when `--apply` is explicitly provided.
109
+
110
+ High-confidence security-sensitive results suppress normal label and assignee
111
+ updates so maintainers can route them through a private security process.
112
+
113
+ ## Architecture
114
+
115
+ ```text
116
+ tixio/
117
+ ├── cli/ Typer commands and presentation
118
+ ├── clients/ Provider-neutral interfaces
119
+ │ └── github/ GitHub transport, tickets, and descriptors
120
+ ├── data/ Canonical descriptor registry
121
+ └── decider/ Context, model adapter, policy, and result models
122
+ ```
123
+
124
+ The core `TriageService` accepts the shared `Client` abstraction. Additional
125
+ issue trackers can implement the same ticket, descriptor, and transport
126
+ interfaces without changing the classifier.
127
+
128
+ ## Development
129
+
130
+ ```bash
131
+ pip install -e '.[dev]'
132
+ tox -e lint
133
+ ```
134
+
135
+ The lint environment runs Pyright, isort, and Black checks.
136
+
137
+ ## Project status
138
+
139
+ Tixio is early-stage software. Run it in dry-run mode first, verify your label
140
+ taxonomy, and review model recommendations before enabling repository writes.
141
+
142
+ Contributions, issue reports, and provider integrations are welcome.
@@ -0,0 +1,51 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "tixio"
7
+ version = "0.1.0"
8
+ description = "Confidence-gated, model-assisted issue triage for engineering teams"
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ dependencies = ["decider-ai==1.5.0", "typer", "requests"]
12
+ keywords = ["github", "issue-triage", "classification", "automation", "decider"]
13
+ classifiers = [
14
+ "Development Status :: 3 - Alpha",
15
+ "Environment :: Console",
16
+ "Intended Audience :: Developers",
17
+ "Programming Language :: Python :: 3",
18
+ "Programming Language :: Python :: 3.11",
19
+ "Topic :: Software Development :: Bug Tracking",
20
+ ]
21
+
22
+ [project.urls]
23
+ Homepage = "https://github.com/Bojan-Ivanovski/tixio"
24
+ Repository = "https://github.com/Bojan-Ivanovski/tixio"
25
+ Issues = "https://github.com/Bojan-Ivanovski/tixio/issues"
26
+
27
+ [project.scripts]
28
+ tixio = "tixio.cli:main"
29
+
30
+ [project.optional-dependencies]
31
+ dev = ["tox", "black", "isort", "pyright"]
32
+
33
+ [tool.setuptools.packages.find]
34
+ where = ["."]
35
+ include = ["tixio*"]
36
+
37
+ [tool.setuptools.package-data]
38
+ tbt_engine = ["py.typed"]
39
+
40
+ [tool.black]
41
+ line-length = 100
42
+ target-version = ["py311"]
43
+
44
+ [tool.isort]
45
+ profile = "black"
46
+ line_length = 100
47
+
48
+ [tool.pyright]
49
+ include = ["tixio"]
50
+ typeCheckingMode = "strict"
51
+ reportMissingTypeStubs = false
tixio-0.1.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
File without changes
@@ -0,0 +1,5 @@
1
+ """Tixio command-line interface."""
2
+
3
+ from .app import app, main
4
+
5
+ __all__ = ["app", "main"]
@@ -0,0 +1,14 @@
1
+ """Root Typer application."""
2
+
3
+ import typer
4
+
5
+ from .descriptors import app as descriptors_app
6
+ from .triage import triage
7
+
8
+ app = typer.Typer(name="tixio", help="Ticket and issue classification tools")
9
+ app.add_typer(descriptors_app, name="descriptors")
10
+ app.command("triage")(triage)
11
+
12
+
13
+ def main() -> None:
14
+ app()
@@ -0,0 +1,18 @@
1
+ """Shared CLI dependencies."""
2
+
3
+ import os
4
+
5
+ import typer
6
+
7
+ from ..clients.client import Client
8
+ from ..clients.github import GithubClient
9
+
10
+
11
+ def build_client(token: str | None, repo: str | None) -> Client:
12
+ resolved_token = token or os.getenv("GITHUB_TOKEN")
13
+ resolved_repo = repo or os.getenv("GITHUB_REPOSITORY")
14
+ if not resolved_token or not resolved_repo:
15
+ raise typer.BadParameter(
16
+ "Provide --token and --repo, or set GITHUB_TOKEN and GITHUB_REPOSITORY."
17
+ )
18
+ return GithubClient(resolved_token, resolved_repo)
@@ -0,0 +1,95 @@
1
+ """Descriptor CLI commands."""
2
+
3
+ from copy import deepcopy
4
+ from typing import Any
5
+
6
+ import typer
7
+
8
+ from ..clients.client import Client
9
+ from ..data.descriptors import TICKET_DESCRIPTORS
10
+ from .common import build_client
11
+
12
+ app = typer.Typer(name="descriptors", help="Manage shared descriptors")
13
+
14
+
15
+ def static_descriptors() -> dict[str, Any]:
16
+ return {
17
+ "types": deepcopy(TICKET_DESCRIPTORS["type"]),
18
+ "components": deepcopy(TICKET_DESCRIPTORS["component"]),
19
+ "priority": deepcopy(TICKET_DESCRIPTORS["priority"]),
20
+ "status": deepcopy(TICKET_DESCRIPTORS["status"]),
21
+ "size": deepcopy(TICKET_DESCRIPTORS["size"]),
22
+ }
23
+
24
+
25
+ def client_descriptors(client: Client) -> dict[str, Any]:
26
+ return {
27
+ "types": client.descriptors.get_types(),
28
+ "components": client.descriptors.get_components(),
29
+ "priority": client.descriptors.get_priority(),
30
+ "status": client.descriptors.get_status(),
31
+ "size": client.descriptors.get_size(),
32
+ }
33
+
34
+
35
+ @app.command("view")
36
+ def view(
37
+ diff: bool = typer.Option(False, "--diff"),
38
+ token: str | None = typer.Option(None, "--token"),
39
+ repo: str | None = typer.Option(None, "--repo"),
40
+ ) -> None:
41
+ remote = client_descriptors(build_client(token, repo))
42
+ for group, descriptor in remote.items():
43
+ typer.echo(f"\n{group}")
44
+ for name, value in descriptor.get("labels", {}).items():
45
+ if isinstance(value, dict):
46
+ typer.echo(
47
+ f" {name}: {value.get('description', '')} ({value.get('color', '')})"
48
+ )
49
+ else:
50
+ typer.echo(f" {name}: {value}")
51
+
52
+ if diff:
53
+ expected = static_descriptors()
54
+ typer.echo("\nDiff")
55
+ for group in expected:
56
+ expected_names = set(expected[group].get("labels", {}))
57
+ remote_names = set(remote[group].get("labels", {}))
58
+ for name in sorted(expected_names - remote_names):
59
+ typer.echo(f" missing {name}")
60
+ for name in sorted(remote_names - expected_names):
61
+ typer.echo(f" extra {name}")
62
+
63
+
64
+ @app.command("apply")
65
+ def apply(
66
+ force: bool = typer.Option(False, "--force"),
67
+ token: str | None = typer.Option(None, "--token"),
68
+ repo: str | None = typer.Option(None, "--repo"),
69
+ ) -> None:
70
+ client = build_client(token, repo)
71
+ remote = client_descriptors(client)
72
+ expected = static_descriptors()
73
+
74
+ for group in ("types", "components"):
75
+ delete = client.descriptors.delete_types if group == "types" else client.descriptors.delete_components
76
+ create = client.descriptors.create_types if group == "types" else client.descriptors.create_components
77
+ update = client.descriptors.update_types if group == "types" else client.descriptors.update_components
78
+ current: dict[str, Any] = remote[group].get("labels", {})
79
+ if force:
80
+ for name in current:
81
+ delete(name)
82
+ current = {}
83
+
84
+ color = str(expected[group].get("color", "#6f42c1"))
85
+ for name, description in expected[group].get("labels", {}).items():
86
+ existing = current.get(name)
87
+ if existing is None:
88
+ create(name, description, color=color)
89
+ typer.echo(f"added {name}")
90
+ continue
91
+ remote_description = existing.get("description", "")
92
+ remote_color = existing.get("color", "")
93
+ if remote_description != description or remote_color.lower() != color.lower():
94
+ update(name, description=description, color=color)
95
+ typer.echo(f"updated {name}")
@@ -0,0 +1,36 @@
1
+ """Ticket triage CLI command."""
2
+
3
+ import json
4
+ from dataclasses import asdict
5
+
6
+ import typer
7
+
8
+ from ..decider.service import DEFAULT_MODEL, TriageService
9
+ from .common import build_client
10
+
11
+
12
+ def triage(
13
+ issue_number: int,
14
+ apply: bool = typer.Option(False, "--apply", help="Apply high-confidence changes."),
15
+ model: str = typer.Option(DEFAULT_MODEL, "--model"),
16
+ json_output: bool = typer.Option(False, "--json"),
17
+ token: str | None = typer.Option(None, "--token"),
18
+ repo: str | None = typer.Option(None, "--repo"),
19
+ ) -> None:
20
+ result = TriageService(build_client(token, repo), model).triage(issue_number, apply=apply)
21
+ if json_output:
22
+ typer.echo(json.dumps(asdict(result), indent=2))
23
+ return
24
+
25
+ typer.echo(f"Issue: {result.ticket_title}")
26
+ for decision in result.decisions:
27
+ action = "AUTO" if decision.auto_apply else "REVIEW"
28
+ typer.echo(
29
+ f"{decision.field:20} {str(decision.value):24} "
30
+ f"p={decision.probability:.3f} {action}"
31
+ )
32
+ typer.echo(f"old labels: {result.previous_labels}")
33
+ typer.echo(f"new labels: {result.proposed_labels}")
34
+ for warning in result.warnings:
35
+ typer.echo(f"WARNING: {warning}", err=True)
36
+ typer.echo("Applied changes." if result.applied else "DRY RUN: nothing was changed.")
@@ -0,0 +1 @@
1
+ """External service clients used by Tixio."""
@@ -0,0 +1,25 @@
1
+ """Common client composition."""
2
+
3
+ from abc import ABC
4
+ from typing import Any
5
+
6
+ from .tickets import Tickets
7
+ from .transport import Transport
8
+ from .descriptors import Descriptors
9
+
10
+ class Client(ABC):
11
+ """Common client holding ticket operations and transport."""
12
+
13
+ def __init__(
14
+ self,
15
+ tickets: Tickets,
16
+ transport: Transport,
17
+ descriptors: Descriptors,
18
+ ):
19
+ self.tickets = tickets
20
+ self.transport = transport
21
+ self.descriptors = descriptors
22
+
23
+ def assignees(self) -> list[dict[str, Any]]:
24
+ """Return assignable users when the provider supports ownership."""
25
+ return []
@@ -0,0 +1,52 @@
1
+ """Common descriptor interface."""
2
+
3
+ from abc import ABC, abstractmethod
4
+ from typing import Any
5
+
6
+
7
+ class Descriptors(ABC):
8
+ """Interface for descriptor CRUD operations."""
9
+
10
+ @abstractmethod
11
+ def get_types(self) -> dict[str, Any]: raise NotImplementedError
12
+ @abstractmethod
13
+ def get_components(self) -> dict[str, Any]: raise NotImplementedError
14
+ @abstractmethod
15
+ def get_priority(self) -> dict[str, Any]: raise NotImplementedError
16
+ @abstractmethod
17
+ def get_status(self) -> dict[str, Any]: raise NotImplementedError
18
+ @abstractmethod
19
+ def get_size(self) -> dict[str, Any]: raise NotImplementedError
20
+
21
+ @abstractmethod
22
+ def create_types(self, name: str, description: str, *, color: str) -> dict[str, Any]: raise NotImplementedError
23
+ @abstractmethod
24
+ def create_components(self, name: str, description: str, *, color: str) -> dict[str, Any]: raise NotImplementedError
25
+ @abstractmethod
26
+ def create_priority(self, name: str, description: str, *, color: str) -> dict[str, Any]: raise NotImplementedError
27
+ @abstractmethod
28
+ def create_status(self, name: str, description: str, *, color: str) -> dict[str, Any]: raise NotImplementedError
29
+ @abstractmethod
30
+ def create_size(self, name: str, description: str, *, color: str) -> dict[str, Any]: raise NotImplementedError
31
+
32
+ @abstractmethod
33
+ def update_types(self, name: str, *, description: str | None = None, color: str | None = None) -> dict[str, Any]: raise NotImplementedError
34
+ @abstractmethod
35
+ def update_components(self, name: str, *, description: str | None = None, color: str | None = None) -> dict[str, Any]: raise NotImplementedError
36
+ @abstractmethod
37
+ def update_priority(self, name: str, *, description: str | None = None, color: str | None = None) -> dict[str, Any]: raise NotImplementedError
38
+ @abstractmethod
39
+ def update_status(self, name: str, *, description: str | None = None, color: str | None = None) -> dict[str, Any]: raise NotImplementedError
40
+ @abstractmethod
41
+ def update_size(self, name: str, *, description: str | None = None, color: str | None = None) -> dict[str, Any]: raise NotImplementedError
42
+
43
+ @abstractmethod
44
+ def delete_types(self, name: str) -> None: raise NotImplementedError
45
+ @abstractmethod
46
+ def delete_components(self, name: str) -> None: raise NotImplementedError
47
+ @abstractmethod
48
+ def delete_priority(self, name: str) -> None: raise NotImplementedError
49
+ @abstractmethod
50
+ def delete_status(self, name: str) -> None: raise NotImplementedError
51
+ @abstractmethod
52
+ def delete_size(self, name: str) -> None: raise NotImplementedError
@@ -0,0 +1,13 @@
1
+ """Common client errors."""
2
+
3
+
4
+ class ClientError(RuntimeError):
5
+ """Base error for client failures."""
6
+
7
+
8
+ class ClientConfigurationError(ClientError):
9
+ """Raised when client configuration is invalid."""
10
+
11
+
12
+ class ClientRequestError(ClientError):
13
+ """Raised when a remote service rejects or cannot complete a request."""
@@ -0,0 +1,37 @@
1
+ """Composable GitHub client for issue triage."""
2
+
3
+ from typing import Any
4
+
5
+ from ..client import Client
6
+ from .descriptors import GithubDescriptors
7
+ from .tickets import GithubIssues
8
+ from .transport import GithubTransport
9
+
10
+ class GithubClient(Client):
11
+ """Facade over focused GitHub API clients."""
12
+
13
+ def __init__(
14
+ self,
15
+ token: str,
16
+ repository: str,
17
+ *,
18
+ timeout: float = 30.0,
19
+ descriptors: GithubDescriptors | None = None,
20
+ ):
21
+ transport = GithubTransport(token, repository, timeout=timeout)
22
+ super().__init__(
23
+ GithubIssues(transport),
24
+ transport,
25
+ descriptors or GithubDescriptors(transport),
26
+ )
27
+
28
+ def assignees(self) -> list[dict[str, Any]]:
29
+ return self.transport.get("/assignees", params={"per_page": 100})
30
+
31
+ __all__ = [
32
+ "GithubClient",
33
+ "Client",
34
+ "GithubIssues",
35
+ "GithubDescriptors",
36
+ "GithubTransport",
37
+ ]