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 +167 -0
- tixio-0.1.0/README.md +142 -0
- tixio-0.1.0/pyproject.toml +51 -0
- tixio-0.1.0/setup.cfg +4 -0
- tixio-0.1.0/tixio/__init__.py +0 -0
- tixio-0.1.0/tixio/cli/__init__.py +5 -0
- tixio-0.1.0/tixio/cli/app.py +14 -0
- tixio-0.1.0/tixio/cli/common.py +18 -0
- tixio-0.1.0/tixio/cli/descriptors.py +95 -0
- tixio-0.1.0/tixio/cli/triage.py +36 -0
- tixio-0.1.0/tixio/clients/__init__.py +1 -0
- tixio-0.1.0/tixio/clients/client.py +25 -0
- tixio-0.1.0/tixio/clients/descriptors.py +52 -0
- tixio-0.1.0/tixio/clients/errors.py +13 -0
- tixio-0.1.0/tixio/clients/github/__init__.py +37 -0
- tixio-0.1.0/tixio/clients/github/descriptors.py +138 -0
- tixio-0.1.0/tixio/clients/github/tickets.py +64 -0
- tixio-0.1.0/tixio/clients/github/transport.py +69 -0
- tixio-0.1.0/tixio/clients/tickets.py +42 -0
- tixio-0.1.0/tixio/clients/transport.py +28 -0
- tixio-0.1.0/tixio/data/descriptors.py +92 -0
- tixio-0.1.0/tixio/decider/__init__.py +6 -0
- tixio-0.1.0/tixio/decider/context.py +28 -0
- tixio-0.1.0/tixio/decider/model.py +92 -0
- tixio-0.1.0/tixio/decider/models.py +24 -0
- tixio-0.1.0/tixio/decider/service.py +143 -0
- tixio-0.1.0/tixio/main.py +9 -0
- tixio-0.1.0/tixio.egg-info/PKG-INFO +167 -0
- tixio-0.1.0/tixio.egg-info/SOURCES.txt +31 -0
- tixio-0.1.0/tixio.egg-info/dependency_links.txt +1 -0
- tixio-0.1.0/tixio.egg-info/entry_points.txt +2 -0
- tixio-0.1.0/tixio.egg-info/requires.txt +9 -0
- tixio-0.1.0/tixio.egg-info/top_level.txt +1 -0
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
|
+
[](https://www.python.org/)
|
|
33
|
+
[](https://pypi.org/project/tixio/)
|
|
34
|
+
[](https://huggingface.co/Mapika)
|
|
35
|
+
[](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
|
+
[](https://www.python.org/)
|
|
8
|
+
[](https://pypi.org/project/tixio/)
|
|
9
|
+
[](https://huggingface.co/Mapika)
|
|
10
|
+
[](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
|
File without changes
|
|
@@ -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
|
+
]
|