ado-git-repo-insights 1.2.1__py3-none-any.whl → 2.7.4__py3-none-any.whl

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.
Files changed (28) hide show
  1. ado_git_repo_insights/__init__.py +3 -3
  2. ado_git_repo_insights/cli.py +703 -354
  3. ado_git_repo_insights/config.py +186 -186
  4. ado_git_repo_insights/extractor/__init__.py +1 -1
  5. ado_git_repo_insights/extractor/ado_client.py +452 -246
  6. ado_git_repo_insights/extractor/pr_extractor.py +239 -239
  7. ado_git_repo_insights/ml/__init__.py +13 -0
  8. ado_git_repo_insights/ml/date_utils.py +70 -0
  9. ado_git_repo_insights/ml/forecaster.py +288 -0
  10. ado_git_repo_insights/ml/insights.py +497 -0
  11. ado_git_repo_insights/persistence/__init__.py +1 -1
  12. ado_git_repo_insights/persistence/database.py +193 -193
  13. ado_git_repo_insights/persistence/models.py +207 -145
  14. ado_git_repo_insights/persistence/repository.py +662 -376
  15. ado_git_repo_insights/transform/__init__.py +1 -1
  16. ado_git_repo_insights/transform/aggregators.py +950 -0
  17. ado_git_repo_insights/transform/csv_generator.py +132 -132
  18. ado_git_repo_insights/utils/__init__.py +1 -1
  19. ado_git_repo_insights/utils/datetime_utils.py +101 -101
  20. ado_git_repo_insights/utils/logging_config.py +172 -172
  21. ado_git_repo_insights/utils/run_summary.py +207 -206
  22. {ado_git_repo_insights-1.2.1.dist-info → ado_git_repo_insights-2.7.4.dist-info}/METADATA +56 -15
  23. ado_git_repo_insights-2.7.4.dist-info/RECORD +27 -0
  24. {ado_git_repo_insights-1.2.1.dist-info → ado_git_repo_insights-2.7.4.dist-info}/licenses/LICENSE +21 -21
  25. ado_git_repo_insights-1.2.1.dist-info/RECORD +0 -22
  26. {ado_git_repo_insights-1.2.1.dist-info → ado_git_repo_insights-2.7.4.dist-info}/WHEEL +0 -0
  27. {ado_git_repo_insights-1.2.1.dist-info → ado_git_repo_insights-2.7.4.dist-info}/entry_points.txt +0 -0
  28. {ado_git_repo_insights-1.2.1.dist-info → ado_git_repo_insights-2.7.4.dist-info}/top_level.txt +0 -0
@@ -1,206 +1,207 @@
1
- """Run summary tracking with enriched error diagnostics.
2
-
3
- Captures comprehensive run telemetry including per-project status and first fatal error.
4
- """
5
- # ruff: noqa: S603, S607
6
-
7
- from __future__ import annotations
8
-
9
- import json
10
- import os
11
- import re
12
- import subprocess
13
- from dataclasses import dataclass, field
14
- from datetime import date
15
- from pathlib import Path
16
- from typing import Any, Literal
17
-
18
-
19
- def normalize_error_message(error: str, max_length: int = 500) -> str:
20
- """Normalize and bound error messages to prevent secret leakage.
21
-
22
- Args:
23
- error: Raw error message.
24
- max_length: Maximum length for bounded message.
25
-
26
- Returns:
27
- Normalized error message.
28
- """
29
- # Strip URLs with query strings (can contain secrets)
30
- error = re.sub(r"https?://[^\s]+\?[^\s]+", "[URL_WITH_PARAMS]", error)
31
-
32
- # Strip full URLs (can contain hostnames/paths)
33
- error = re.sub(r"https?://[^\s]+", "[URL]", error)
34
-
35
- # Truncate to max length
36
- if len(error) > max_length:
37
- error = error[:max_length] + "...[truncated]"
38
-
39
- return error
40
-
41
-
42
- @dataclass
43
- class RunCounts:
44
- """Counts of extracted/generated items."""
45
-
46
- prs_fetched: int = 0
47
- prs_updated: int = 0
48
- rows_per_csv: dict[str, int] = field(default_factory=dict)
49
-
50
-
51
- @dataclass
52
- class RunTimings:
53
- """Timing information for run phases."""
54
-
55
- total_seconds: float = 0.0
56
- extract_seconds: float = 0.0
57
- persist_seconds: float = 0.0
58
- export_seconds: float = 0.0
59
-
60
-
61
- @dataclass
62
- class RunSummary:
63
- """Comprehensive run summary with forensic diagnostics."""
64
-
65
- tool_version: str
66
- git_sha: str | None
67
- organization: str
68
- projects: list[str]
69
- date_range_start: str # ISO format date
70
- date_range_end: str # ISO format date
71
- counts: RunCounts
72
- timings: RunTimings
73
- warnings: list[str]
74
- final_status: Literal["success", "failed"]
75
- per_project_status: dict[str, str] = field(default_factory=dict)
76
- first_fatal_error: str | None = None
77
-
78
- def __post_init__(self) -> None:
79
- """Normalize error message on initialization."""
80
- if self.first_fatal_error:
81
- self.first_fatal_error = normalize_error_message(self.first_fatal_error)
82
-
83
- def to_dict(self) -> dict[str, Any]:
84
- """Convert to dictionary for JSON serialization."""
85
- return {
86
- "tool_version": self.tool_version,
87
- "git_sha": self.git_sha,
88
- "organization": self.organization,
89
- "projects": self.projects,
90
- "date_range": {
91
- "start": self.date_range_start,
92
- "end": self.date_range_end,
93
- },
94
- "counts": {
95
- "prs_fetched": self.counts.prs_fetched,
96
- "prs_updated": self.counts.prs_updated,
97
- "rows_per_csv": self.counts.rows_per_csv,
98
- },
99
- "timings": {
100
- "total_seconds": self.timings.total_seconds,
101
- "extract_seconds": self.timings.extract_seconds,
102
- "persist_seconds": self.timings.persist_seconds,
103
- "export_seconds": self.timings.export_seconds,
104
- },
105
- "warnings": self.warnings,
106
- "final_status": self.final_status,
107
- "per_project_status": self.per_project_status,
108
- "first_fatal_error": self.first_fatal_error,
109
- }
110
-
111
- def write(self, path: Path) -> None:
112
- """Write summary to JSON file.
113
-
114
- Args:
115
- path: Path to write summary file.
116
- """
117
- path.parent.mkdir(parents=True, exist_ok=True)
118
- with path.open("w", encoding="utf-8") as f:
119
- json.dump(self.to_dict(), f, indent=2)
120
-
121
- def print_final_line(self) -> None:
122
- """Print one-liner summary to stdout."""
123
- status_symbol = "✓" if self.final_status == "success" else "✗"
124
- print(
125
- f"{status_symbol} {self.final_status.upper()}: "
126
- f"{self.counts.prs_fetched} PRs extracted, "
127
- f"{len(self.counts.rows_per_csv)} CSVs written "
128
- f"({self.timings.total_seconds:.1f}s)"
129
- )
130
-
131
- def emit_ado_commands(self) -> None:
132
- """Emit Azure Pipelines logging commands."""
133
- # Only emit if running in Azure Pipelines
134
- if os.environ.get("TF_BUILD") != "true":
135
- return
136
-
137
- if self.final_status == "failed":
138
- if self.first_fatal_error:
139
- print(f"##vso[task.logissue type=error]{self.first_fatal_error}")
140
- print("##vso[task.complete result=Failed]")
141
- elif self.warnings:
142
- for warning in self.warnings:
143
- print(f"##vso[task.logissue type=warning]{warning}")
144
-
145
-
146
- def get_tool_version() -> str:
147
- """Get tool version from VERSION file."""
148
- version_file = Path(__file__).parent.parent.parent.parent / "VERSION"
149
- if version_file.exists():
150
- return version_file.read_text().strip()
151
- return "unknown"
152
-
153
-
154
- def get_git_sha() -> str | None:
155
- """Get Git SHA from VERSION file or git command.
156
-
157
- Returns:
158
- Git SHA or None if unavailable.
159
- """
160
- # Try VERSION file first
161
- version_file = Path(__file__).parent.parent.parent.parent / "VERSION"
162
- if version_file.exists():
163
- version = version_file.read_text().strip()
164
- if "+" in version: # Version format like "1.0.7+8d88fb4"
165
- return version.split("+")[1]
166
-
167
- # Fallback to git command
168
- try:
169
- result = subprocess.run( # noqa: S603, S607
170
- ["git", "rev-parse", "--short", "HEAD"],
171
- capture_output=True,
172
- text=True,
173
- check=True,
174
- timeout=5,
175
- )
176
- return result.stdout.strip()
177
- except Exception:
178
- return None
179
-
180
-
181
- def create_minimal_summary(
182
- error_message: str,
183
- artifacts_dir: Path = Path("run_artifacts"),
184
- ) -> RunSummary:
185
- """Create a partial summary for early failures.
186
-
187
- Args:
188
- error_message: Error message describing the failure.
189
- artifacts_dir: Directory for artifacts.
190
-
191
- Returns:
192
- Minimal RunSummary with failure status.
193
- """
194
- return RunSummary(
195
- tool_version=get_tool_version(),
196
- git_sha=get_git_sha(),
197
- organization="unknown",
198
- projects=[],
199
- date_range_start=str(date.today()),
200
- date_range_end=str(date.today()),
201
- counts=RunCounts(),
202
- timings=RunTimings(),
203
- warnings=[],
204
- final_status="failed",
205
- first_fatal_error=normalize_error_message(error_message),
206
- )
1
+ """Run summary tracking with enriched error diagnostics.
2
+
3
+ Captures comprehensive run telemetry including per-project status and first fatal error.
4
+ """
5
+ # ruff: noqa: S603, S607
6
+
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ import os
11
+ import re
12
+ import subprocess
13
+ from dataclasses import dataclass, field
14
+ from datetime import date
15
+ from pathlib import Path
16
+ from typing import Any, Literal
17
+
18
+
19
+ def normalize_error_message(error: str, max_length: int = 500) -> str:
20
+ """Normalize and bound error messages to prevent secret leakage.
21
+
22
+ Args:
23
+ error: Raw error message.
24
+ max_length: Maximum length for bounded message.
25
+
26
+ Returns:
27
+ Normalized error message.
28
+ """
29
+ # Strip URLs with query strings (can contain secrets)
30
+ error = re.sub(r"https?://[^\s]+\?[^\s]+", "[URL_WITH_PARAMS]", error)
31
+
32
+ # Strip full URLs (can contain hostnames/paths)
33
+ error = re.sub(r"https?://[^\s]+", "[URL]", error)
34
+
35
+ # Truncate to max length
36
+ if len(error) > max_length:
37
+ error = error[:max_length] + "...[truncated]"
38
+
39
+ return error
40
+
41
+
42
+ @dataclass
43
+ class RunCounts:
44
+ """Counts of extracted/generated items."""
45
+
46
+ prs_fetched: int = 0
47
+ prs_updated: int = 0
48
+ rows_per_csv: dict[str, int] = field(default_factory=dict)
49
+
50
+
51
+ @dataclass
52
+ class RunTimings:
53
+ """Timing information for run phases."""
54
+
55
+ total_seconds: float = 0.0
56
+ extract_seconds: float = 0.0
57
+ persist_seconds: float = 0.0
58
+ export_seconds: float = 0.0
59
+
60
+
61
+ @dataclass
62
+ class RunSummary:
63
+ """Comprehensive run summary with forensic diagnostics."""
64
+
65
+ tool_version: str
66
+ git_sha: str | None
67
+ organization: str
68
+ projects: list[str]
69
+ date_range_start: str # ISO format date
70
+ date_range_end: str # ISO format date
71
+ counts: RunCounts
72
+ timings: RunTimings
73
+ warnings: list[str]
74
+ final_status: Literal["success", "failed"]
75
+ per_project_status: dict[str, str] = field(default_factory=dict)
76
+ first_fatal_error: str | None = None
77
+
78
+ def __post_init__(self) -> None:
79
+ """Normalize error message on initialization."""
80
+ if self.first_fatal_error:
81
+ self.first_fatal_error = normalize_error_message(self.first_fatal_error)
82
+
83
+ def to_dict(self) -> dict[str, Any]:
84
+ """Convert to dictionary for JSON serialization."""
85
+ return {
86
+ "tool_version": self.tool_version,
87
+ "git_sha": self.git_sha,
88
+ "organization": self.organization,
89
+ "projects": self.projects,
90
+ "date_range": {
91
+ "start": self.date_range_start,
92
+ "end": self.date_range_end,
93
+ },
94
+ "counts": {
95
+ "prs_fetched": self.counts.prs_fetched,
96
+ "prs_updated": self.counts.prs_updated,
97
+ "rows_per_csv": self.counts.rows_per_csv,
98
+ },
99
+ "timings": {
100
+ "total_seconds": self.timings.total_seconds,
101
+ "extract_seconds": self.timings.extract_seconds,
102
+ "persist_seconds": self.timings.persist_seconds,
103
+ "export_seconds": self.timings.export_seconds,
104
+ },
105
+ "warnings": self.warnings,
106
+ "final_status": self.final_status,
107
+ "per_project_status": self.per_project_status,
108
+ "first_fatal_error": self.first_fatal_error,
109
+ }
110
+
111
+ def write(self, path: Path) -> None:
112
+ """Write summary to JSON file.
113
+
114
+ Args:
115
+ path: Path to write summary file.
116
+ """
117
+ path.parent.mkdir(parents=True, exist_ok=True)
118
+ with path.open("w", encoding="utf-8") as f:
119
+ json.dump(self.to_dict(), f, indent=2)
120
+
121
+ def print_final_line(self) -> None:
122
+ """Print one-liner summary to stdout."""
123
+ # Use ASCII symbols for Windows cp1252 compatibility
124
+ status_symbol = "[OK]" if self.final_status == "success" else "[FAIL]"
125
+ print(
126
+ f"{status_symbol} {self.final_status.upper()}: "
127
+ f"{self.counts.prs_fetched} PRs extracted, "
128
+ f"{len(self.counts.rows_per_csv)} CSVs written "
129
+ f"({self.timings.total_seconds:.1f}s)"
130
+ )
131
+
132
+ def emit_ado_commands(self) -> None:
133
+ """Emit Azure Pipelines logging commands."""
134
+ # Only emit if running in Azure Pipelines
135
+ if os.environ.get("TF_BUILD") != "true":
136
+ return
137
+
138
+ if self.final_status == "failed":
139
+ if self.first_fatal_error:
140
+ print(f"##vso[task.logissue type=error]{self.first_fatal_error}")
141
+ print("##vso[task.complete result=Failed]")
142
+ elif self.warnings:
143
+ for warning in self.warnings:
144
+ print(f"##vso[task.logissue type=warning]{warning}")
145
+
146
+
147
+ def get_tool_version() -> str:
148
+ """Get tool version from VERSION file."""
149
+ version_file = Path(__file__).parent.parent.parent.parent / "VERSION"
150
+ if version_file.exists():
151
+ return version_file.read_text().strip()
152
+ return "unknown"
153
+
154
+
155
+ def get_git_sha() -> str | None:
156
+ """Get Git SHA from VERSION file or git command.
157
+
158
+ Returns:
159
+ Git SHA or None if unavailable.
160
+ """
161
+ # Try VERSION file first
162
+ version_file = Path(__file__).parent.parent.parent.parent / "VERSION"
163
+ if version_file.exists():
164
+ version = version_file.read_text().strip()
165
+ if "+" in version: # Version format like "1.0.7+8d88fb4"
166
+ return version.split("+")[1]
167
+
168
+ # Fallback to git command
169
+ try:
170
+ result = subprocess.run( # noqa: S603, S607
171
+ ["git", "rev-parse", "--short", "HEAD"],
172
+ capture_output=True,
173
+ text=True,
174
+ check=True,
175
+ timeout=5,
176
+ )
177
+ return result.stdout.strip()
178
+ except Exception:
179
+ return None
180
+
181
+
182
+ def create_minimal_summary(
183
+ error_message: str,
184
+ artifacts_dir: Path = Path("run_artifacts"),
185
+ ) -> RunSummary:
186
+ """Create a partial summary for early failures.
187
+
188
+ Args:
189
+ error_message: Error message describing the failure.
190
+ artifacts_dir: Directory for artifacts.
191
+
192
+ Returns:
193
+ Minimal RunSummary with failure status.
194
+ """
195
+ return RunSummary(
196
+ tool_version=get_tool_version(),
197
+ git_sha=get_git_sha(),
198
+ organization="unknown",
199
+ projects=[],
200
+ date_range_start=str(date.today()),
201
+ date_range_end=str(date.today()),
202
+ counts=RunCounts(),
203
+ timings=RunTimings(),
204
+ warnings=[],
205
+ final_status="failed",
206
+ first_fatal_error=normalize_error_message(error_message),
207
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ado-git-repo-insights
3
- Version: 1.2.1
3
+ Version: 2.7.4
4
4
  Summary: Extract Azure DevOps Pull Request metrics to SQLite and generate PowerBI-compatible CSVs.
5
5
  Author-email: "Odd Essentials, LLC" <admin@oddessentials.com>
6
6
  License: MIT
@@ -21,12 +21,15 @@ Requires-Dist: azure-storage-blob>=12.0.0
21
21
  Provides-Extra: dev
22
22
  Requires-Dist: pytest>=7.0; extra == "dev"
23
23
  Requires-Dist: pytest-cov>=4.0; extra == "dev"
24
- Requires-Dist: ruff>=0.1.0; extra == "dev"
24
+ Requires-Dist: ruff==0.14.11; extra == "dev"
25
25
  Requires-Dist: mypy>=1.0; extra == "dev"
26
26
  Requires-Dist: pre-commit>=3.0; extra == "dev"
27
27
  Requires-Dist: types-requests>=2.28.0; extra == "dev"
28
28
  Requires-Dist: types-PyYAML>=6.0; extra == "dev"
29
29
  Requires-Dist: pandas-stubs>=2.0.0; extra == "dev"
30
+ Provides-Extra: ml
31
+ Requires-Dist: prophet>=1.1.0; extra == "ml"
32
+ Requires-Dist: openai>=1.0.0; extra == "ml"
30
33
  Dynamic: license-file
31
34
 
32
35
  # ado-git-repo-insights
@@ -110,7 +113,7 @@ Best for teams that prefer the ADO pipeline editor UI or want a self-contained t
110
113
 
111
114
  ```yaml
112
115
  steps:
113
- - task: ExtractPullRequests@1
116
+ - task: ExtractPullRequests@2
114
117
  inputs:
115
118
  organization: 'MyOrg'
116
119
  projects: 'Project1,Project2'
@@ -123,6 +126,16 @@ steps:
123
126
  1. Download the `.vsix` from [GitHub Releases](https://github.com/oddessentials/ado-git-repo-insights/releases)
124
127
  2. Install in your ADO organization: Organization Settings → Extensions → Browse local extensions
125
128
 
129
+ ### PR Insights Dashboard
130
+
131
+ Once the extension is installed and a pipeline runs successfully with the `aggregates` artifact published, the **PR Insights** hub appears in the project navigation menu. The dashboard auto-discovers pipelines that publish aggregates.
132
+
133
+ **Configuration precedence:**
134
+ 1. `?dataset=<url>` — Direct URL (dev/testing only)
135
+ 2. `?pipelineId=<id>` — Query parameter override
136
+ 3. Extension settings — User-scoped saved preference (Project Settings → PR Insights Settings)
137
+ 4. Auto-discovery — Find pipelines with 'aggregates' artifact
138
+
126
139
  ## Configuration
127
140
 
128
141
  Create a `config.yaml` file:
@@ -156,9 +169,16 @@ ado-insights extract --config config.yaml --pat $ADO_PAT
156
169
 
157
170
  ## Azure DevOps Pipeline Integration
158
171
 
159
- See [sample-pipeline.yml](sample-pipeline.yml) for a complete example.
172
+ Use [pr-insights-pipeline.yml](pr-insights-pipeline.yml) for a production-ready template that includes:
173
+ - Daily incremental extraction
174
+ - Sunday backfill for data convergence
175
+ - Dashboard-compatible `aggregates` artifact
176
+
177
+ See [sample-pipeline.yml](sample-pipeline.yml) for additional reference.
160
178
 
161
- ### Scheduled Daily Extraction
179
+ ### Daily Schedule with Sunday Backfill
180
+
181
+ The production template uses a single daily schedule that detects Sundays for backfill:
162
182
 
163
183
  ```yaml
164
184
  schedules:
@@ -169,16 +189,7 @@ schedules:
169
189
  always: true
170
190
  ```
171
191
 
172
- ### Weekly Backfill
173
-
174
- ```yaml
175
- schedules:
176
- - cron: "0 6 * * 0" # Weekly on Sunday
177
- displayName: "Weekly Backfill"
178
- branches:
179
- include: [main]
180
- always: true
181
- ```
192
+ On Sundays, the pipeline automatically performs a 60-day backfill for data convergence.
182
193
 
183
194
  ## CSV Output Contract
184
195
 
@@ -193,6 +204,20 @@ The following CSVs are generated with **exact schema and column order** for Powe
193
204
  | `users.csv` | `user_id`, `display_name`, `email` |
194
205
  | `reviewers.csv` | `pull_request_uid`, `user_id`, `vote`, `repository_id` |
195
206
 
207
+ ## Security & Permissions
208
+
209
+ ### PR Insights Dashboard (Phase 3)
210
+
211
+ The PR Insights dashboard reads data from pipeline-produced artifacts. **Users must have Build Read permission** on the analytics pipeline to view dashboard data.
212
+
213
+ | Requirement | Details |
214
+ |-------------|---------|
215
+ | **Permission scope** | Build → Read on the pipeline that produces artifacts |
216
+ | **No special redaction** | Data is not filtered per-user; access is all-or-nothing |
217
+ | **Artifact retention** | Operators must configure retention for their desired analytics window |
218
+
219
+ If a user lacks permissions, the dashboard displays: *"No access to analytics pipeline artifacts. Ask an admin for Build Read on pipeline X."*
220
+
196
221
  ## Governance
197
222
 
198
223
  This project is governed by authoritative documents in `agents/`:
@@ -220,6 +245,22 @@ mypy src/
220
245
  pytest
221
246
  ```
222
247
 
248
+ ## Contributing
249
+
250
+ ### Line Endings (Windows Developers)
251
+
252
+ This repo uses LF line endings for cross-platform compatibility. The `.gitattributes` file handles this automatically, but for best results:
253
+
254
+ ```bash
255
+ # Recommended: Let .gitattributes be the source of truth
256
+ git config core.autocrlf false
257
+
258
+ # Alternative: Convert on commit (but not checkout)
259
+ git config core.autocrlf input
260
+ ```
261
+
262
+ If you see "CRLF will be replaced by LF" warnings, that's expected behavior.
263
+
223
264
  ## License
224
265
 
225
266
  MIT
@@ -0,0 +1,27 @@
1
+ ado_git_repo_insights/__init__.py,sha256=730M8o7DUvF6VGejYhP6U9G5kF-Vk05sHmuIbsz8szk,136
2
+ ado_git_repo_insights/cli.py,sha256=RQl9Le8zQsPj1-iZAcqF1bi6v91ducJt1tzJ4BmNrLY,23676
3
+ ado_git_repo_insights/config.py,sha256=0oeB7IQ6srCCCY9qFU0EEj_F1svGI9q-j0xIHcLmKpA,6185
4
+ ado_git_repo_insights/extractor/__init__.py,sha256=H3y-GwGtlsAoZMt0brBL1WqbP9cjY6Kn9TnSwFaLy2U,58
5
+ ado_git_repo_insights/extractor/ado_client.py,sha256=sDNCH0okayjHDdeTrjUNA48OI5trgD3XY_DsAAgu-ck,14471
6
+ ado_git_repo_insights/extractor/pr_extractor.py,sha256=1SSRtjQGp_FkX_ONrJaKJWRMvy67PPgPKH7h2tyPeC4,7562
7
+ ado_git_repo_insights/ml/__init__.py,sha256=zPtv9Ca0QruQ4w6N_p9mTRYD95Lz6Vr9HxnWyqXNacw,472
8
+ ado_git_repo_insights/ml/date_utils.py,sha256=FlHqDPU9D5KxJMnkBPrqU5Dx9IPvPm2-1Xg_DB4w8bk,1981
9
+ ado_git_repo_insights/ml/forecaster.py,sha256=Z217j6BgBUl4_VOrEuHDm2FOg_u3Jzg3nX41nQffGkM,8895
10
+ ado_git_repo_insights/ml/insights.py,sha256=ecqKy_VApEROa88AAu4S6I0Xh2ryk4nfgvemifpaMxY,17530
11
+ ado_git_repo_insights/persistence/__init__.py,sha256=jpVCJOia8ZuZmFA_9kpTz_5u8Gwl0SXJRXS-E81w79w,56
12
+ ado_git_repo_insights/persistence/database.py,sha256=dUp1buCyrUrxN3QP8rmM8LgX9nTsontIDqhH6RqXviw,5930
13
+ ado_git_repo_insights/persistence/models.py,sha256=klQhCUcxjKKxe-_qsCyRfcncafL8utRUEXCiwSnwqeA,7766
14
+ ado_git_repo_insights/persistence/repository.py,sha256=iJ4f2u3GgFB_meKOAB4ElkVAez6BsRwzAbDbnksiDDY,20896
15
+ ado_git_repo_insights/transform/__init__.py,sha256=lDjE3oMQQFBgpNSw-iC7Lyi1tcjvNgGTWrVxOx7stAQ,43
16
+ ado_git_repo_insights/transform/aggregators.py,sha256=rjnfWD79imVKtbmBjkfCsQ9l_d1-bFq68KAsxgpjPhA,34793
17
+ ado_git_repo_insights/transform/csv_generator.py,sha256=zfhS93EXQtAm9gEAZcTMjDHj31bjBqO3cqEVv9TYGNI,3957
18
+ ado_git_repo_insights/utils/__init__.py,sha256=NEwaQ6FcwYVzrvJ8es3X-vaOc7a5MCD_3rIYDAwn-z4,52
19
+ ado_git_repo_insights/utils/datetime_utils.py,sha256=L556SFQjaUBaPmNlnU6atIZIINI5SuCOZiheGoDSv7c,2936
20
+ ado_git_repo_insights/utils/logging_config.py,sha256=o2RG8fhX6SeM4qSleGSZDBKJmUhwd-3kvCBCljje5qA,5521
21
+ ado_git_repo_insights/utils/run_summary.py,sha256=bcDo-7w1kplxjhG8Agoqb_FysAL3QeYQKLMSI87Ulrw,6492
22
+ ado_git_repo_insights-2.7.4.dist-info/licenses/LICENSE,sha256=cjgHmK9h1hMSh7DdPI3FNFU132QQAv7OOgLX7xCcX44,1069
23
+ ado_git_repo_insights-2.7.4.dist-info/METADATA,sha256=dP0BUHRkNnP_QOA_0adXefdsXJ1xqf8lQ4WrVz9fAhE,8430
24
+ ado_git_repo_insights-2.7.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
25
+ ado_git_repo_insights-2.7.4.dist-info/entry_points.txt,sha256=xG4WnncN88TiGSTdm-aQc_coA9oo0ygX8XxaPSM0wGk,64
26
+ ado_git_repo_insights-2.7.4.dist-info/top_level.txt,sha256=g27zDliEsKELgsQvn9TPdqbGAj-cX2CQ8nCaqyx4PbM,22
27
+ ado_git_repo_insights-2.7.4.dist-info/RECORD,,
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 Contributors
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -1,22 +0,0 @@
1
- ado_git_repo_insights/__init__.py,sha256=GdPeAjnS_8m6gBWAFFGd-roF1ol9Qov9CtUOjaPJ3bY,139
2
- ado_git_repo_insights/cli.py,sha256=Ioxi3Kp3KwJ0dWoHNiMmXPQtRXH1XdJAqk1MjOJi4-E,11685
3
- ado_git_repo_insights/config.py,sha256=RoRULJ2CvHhYc9NGBhc6awKheTZrapU8-VADvYeLgmw,6371
4
- ado_git_repo_insights/extractor/__init__.py,sha256=c1iaZJEmUWA4NhCG9WN1WDR417loNJ562wbUphfvI68,59
5
- ado_git_repo_insights/extractor/ado_client.py,sha256=72tKnI0DrfHRANPjltKBCP06r6xV5thjxOxn76Pf4jk,8005
6
- ado_git_repo_insights/extractor/pr_extractor.py,sha256=HN9XJJ5B5x_MuGcUUX-4mSrId6uAy07X7kL5c-0hFmE,7801
7
- ado_git_repo_insights/persistence/__init__.py,sha256=Gk7TqvLoqx1-vwTCcPrXgzh4pMWKyZxA-NTEOr1VDB8,57
8
- ado_git_repo_insights/persistence/database.py,sha256=mShHYtIiWNKqpK2LBKERbt1ZVPKQYNVTPMqdMY7ev7s,6123
9
- ado_git_repo_insights/persistence/models.py,sha256=-Ioa4qkTbhEbKfLIiVkz1xB3QBkZrux2S0U4rzBNuDI,5323
10
- ado_git_repo_insights/persistence/repository.py,sha256=A_JpOsuyzCzzfAklcwq13j9abkMVSoW1WYl8iTBHFTQ,12077
11
- ado_git_repo_insights/transform/__init__.py,sha256=jBn6xWAeoNWwMzGaialhQAv9bhrVKc6fPP3uI5sPPmQ,44
12
- ado_git_repo_insights/transform/csv_generator.py,sha256=yquM3nbYJt9Yxs0pSevvGPcHkGHnJjkxy2ZqONpWNuE,4089
13
- ado_git_repo_insights/utils/__init__.py,sha256=brlDxZd5bEDX3QguzN1Q-FEol9NvlT0hbxHVEh8QIeA,53
14
- ado_git_repo_insights/utils/datetime_utils.py,sha256=I51MLZxpG8F18JnrI4mHQYGkXgIfKsGyPCCSVrZs7xk,3037
15
- ado_git_repo_insights/utils/logging_config.py,sha256=hzhYL0NDsfgpEIlyZei0HjJU2etlV3SrhqtqJSqP5MA,5693
16
- ado_git_repo_insights/utils/run_summary.py,sha256=VPcg4M-DWKdJDtDcbbrsq9Pe1Hu3vAo9HKHbDpLgwns,6633
17
- ado_git_repo_insights-1.2.1.dist-info/licenses/LICENSE,sha256=1L7ghNYfOgApbwgAkiJ4AQ6HHt0VpIGXXELsaKYSfIo,1090
18
- ado_git_repo_insights-1.2.1.dist-info/METADATA,sha256=pTX0MtoXRNcp70S1JycdlPXoW7XjwiTchOlwfb5hCBc,6370
19
- ado_git_repo_insights-1.2.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
20
- ado_git_repo_insights-1.2.1.dist-info/entry_points.txt,sha256=xG4WnncN88TiGSTdm-aQc_coA9oo0ygX8XxaPSM0wGk,64
21
- ado_git_repo_insights-1.2.1.dist-info/top_level.txt,sha256=g27zDliEsKELgsQvn9TPdqbGAj-cX2CQ8nCaqyx4PbM,22
22
- ado_git_repo_insights-1.2.1.dist-info/RECORD,,