projectpulse-mcp 1.2.1 → 1.4.0
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.
- package/README.md +111 -64
- package/dist/analytics/dora.d.ts +3 -0
- package/dist/analytics/dora.d.ts.map +1 -0
- package/dist/analytics/dora.js +77 -0
- package/dist/analytics/dora.js.map +1 -0
- package/dist/analytics/dora.test.d.ts +2 -0
- package/dist/analytics/dora.test.d.ts.map +1 -0
- package/dist/analytics/dora.test.js +164 -0
- package/dist/analytics/dora.test.js.map +1 -0
- package/dist/analytics/types.d.ts +50 -0
- package/dist/analytics/types.d.ts.map +1 -0
- package/dist/analytics/types.js +2 -0
- package/dist/analytics/types.js.map +1 -0
- package/dist/github/client.d.ts +4 -0
- package/dist/github/client.d.ts.map +1 -1
- package/dist/github/client.js +50 -0
- package/dist/github/client.js.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/tools/compare-repos.d.ts +21 -0
- package/dist/tools/compare-repos.d.ts.map +1 -0
- package/dist/tools/compare-repos.js +81 -0
- package/dist/tools/compare-repos.js.map +1 -0
- package/dist/tools/compare-repos.test.d.ts +2 -0
- package/dist/tools/compare-repos.test.d.ts.map +1 -0
- package/dist/tools/compare-repos.test.js +138 -0
- package/dist/tools/compare-repos.test.js.map +1 -0
- package/dist/tools/get-dora-metrics.d.ts +13 -0
- package/dist/tools/get-dora-metrics.d.ts.map +1 -0
- package/dist/tools/get-dora-metrics.js +29 -0
- package/dist/tools/get-dora-metrics.js.map +1 -0
- package/dist/tools/get-dora-metrics.test.d.ts +2 -0
- package/dist/tools/get-dora-metrics.test.d.ts.map +1 -0
- package/dist/tools/get-dora-metrics.test.js +71 -0
- package/dist/tools/get-dora-metrics.test.js.map +1 -0
- package/dist/types/health.d.ts +11 -1
- package/dist/types/health.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,105 +1,152 @@
|
|
|
1
|
-
|
|
1
|
+
<div id="top" align="center">
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<img src="https://raw.githubusercontent.com/alexbypa/github-projectpulse-mcp/main/docs/images/code-scanning-example.png" width="80%" alt="ProjectPulse MCP Logo" style="border-radius: 12px; box-shadow: 0 8px 24px rgba(106, 13, 173, 0.2); margin-bottom: 20px;"/>
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
# ⚡ ProjectPulse MCP
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
**Monitor, score, and compare GitHub repository health directly from your AI agent conversations.**
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
-
|
|
12
|
-
|
|
13
|
-
-
|
|
14
|
-
- **Ping**: Simple connectivity test tool.
|
|
9
|
+
[](https://www.npmjs.com/package/projectpulse-mcp)
|
|
10
|
+
[](https://opensource.org/licenses/MIT)
|
|
11
|
+
[](https://www.typescriptlang.org/)
|
|
12
|
+
[](https://modelcontextprotocol.io)
|
|
13
|
+
[](https://vitest.dev/)
|
|
15
14
|
|
|
16
|
-
|
|
15
|
+
<p align="center">
|
|
16
|
+
<a href="#-overview">Overview</a> •
|
|
17
|
+
<a href="#-features">Features</a> •
|
|
18
|
+
<a href="#-tools-reference">Tools</a> •
|
|
19
|
+
<a href="#-quick-start">Quick Start</a> •
|
|
20
|
+
<a href="#-configuration">Configuration</a> •
|
|
21
|
+
<a href="#-license">License</a>
|
|
22
|
+
</p>
|
|
17
23
|
|
|
18
|
-
|
|
24
|
+
---
|
|
19
25
|
|
|
20
|
-
|
|
26
|
+
</div>
|
|
21
27
|
|
|
22
|
-
|
|
28
|
+
## 🌟 Overview
|
|
23
29
|
|
|
24
|
-
|
|
30
|
+
**ProjectPulse MCP** is a comprehensive [Model Context Protocol (MCP)](https://modelcontextprotocol.io) server engineered for AI assistants (Claude Desktop, Cursor, Windsurf, Claude Code CLI). It equips LLMs with real-time capabilities to evaluate codebase maintainability, monitor CI/CD reliability, audit security vulnerabilities, track DORA engineering metrics, and rank multiple repositories side-by-side.
|
|
25
31
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## 🚀 Features
|
|
35
|
+
|
|
36
|
+
### 🔍 1. Multi-Repository Benchmark & Comparison
|
|
37
|
+
* **`compare_repos`**: Compares 2 to 5 repositories simultaneously and returns a ranked JSON score.
|
|
38
|
+
* **Competition Ranking**: Employs standard competition ranking (1224 scheme) with full tie-breaking.
|
|
39
|
+
* **Fault Tolerance**: Isolates repository errors (e.g. 404s) so remaining repositories are scored seamlessly.
|
|
40
|
+
|
|
41
|
+
### 📊 2. Algorithmic Health Scoring (Grade A–F)
|
|
42
|
+
* **`get_health_score`**: Weighted composite metric across 5 dimensions:
|
|
43
|
+
- **CI/CD Reliability (25%)**: Evaluates pass rate across recent workflow runs.
|
|
44
|
+
- **Security & Dependabot (25%)**: Audits open critical, high, medium, and low security advisories.
|
|
45
|
+
- **Codebase Freshness (20%)**: Tracks commit recency and push activity.
|
|
46
|
+
- **Community & Activity (15%)**: Measures open issue resolution and fork engagement.
|
|
47
|
+
- **Maintenance Standards (15%)**: Assesses licensing, descriptions, and active status.
|
|
48
|
+
|
|
49
|
+
### ⏱️ 3. Proxy DORA Metrics
|
|
50
|
+
* **`get_dora_metrics`**: Computes core DevOps Research & Assessment metrics over a configurable window (7–90 days):
|
|
51
|
+
- **Deployment Frequency**: Weekly release throughput.
|
|
52
|
+
- **Lead Time for Changes**: Median PR duration from opening to merge.
|
|
53
|
+
- **Change Failure Rate**: Proportion of failed workflow executions.
|
|
54
|
+
- **Mean Time to Recovery (MTTR)**: Median duration between failure and subsequent branch recovery.
|
|
55
|
+
|
|
56
|
+
### 🛡️ 4. Security & Code Quality Audits
|
|
57
|
+
* **`analyze_dependencies`**: Detailed Dependabot alert breakdowns with severity filtering.
|
|
58
|
+
* **`analyze_code_scanning`**: CodeQL static analysis and security scanning summaries.
|
|
59
|
+
* **`check_ci_status`**: Live inspection of recent GitHub Actions pipeline conclusions.
|
|
60
|
+
* **`get_repo_health`**: Raw telemetry (stars, forks, languages, licenses, default branches).
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## 🛠️ Tools Reference
|
|
65
|
+
|
|
66
|
+
| Tool Name | Description | Input Schema |
|
|
67
|
+
| :--- | :--- | :--- |
|
|
68
|
+
| **`compare_repos`** | Compares and ranks 2 to 5 GitHub repositories by health score using standard competition ranking. | `repos: Array<{ owner: string, repo: string }>` *(min: 2, max: 5)* |
|
|
69
|
+
| **`get_health_score`** | Calculates a 0–100 weighted health score with letter grade (A–F), category breakdown, and suggestions. | `owner: string`, `repo: string` |
|
|
70
|
+
| **`get_dora_metrics`** | Calculates DORA proxy metrics (Deployment Frequency, Lead Time, Change Failure Rate, MTTR). | `owner: string`, `repo: string`, `days?: number` *(7–90, default: 30)* |
|
|
71
|
+
| **`get_repo_health`** | Retrieves core repository metadata (stars, open issues, language, license, activity). | `owner: string`, `repo: string` |
|
|
72
|
+
| **`analyze_dependencies`** | Lists Dependabot security advisories grouped by severity level. | `owner: string`, `repo: string`, `severity?: "critical" \| "high" \| "medium" \| "low"` |
|
|
73
|
+
| **`analyze_code_scanning`** | Fetches CodeQL and static analysis scanning alerts. | `owner: string`, `repo: string` |
|
|
74
|
+
| **`check_ci_status`** | Fetches recent GitHub Actions workflow runs and conclusions. | `owner: string`, `repo: string`, `limit?: number` *(default: 10)* |
|
|
75
|
+
| **`ping`** | Simple connectivity and latency validation check. | `message: string` |
|
|
29
76
|
|
|
30
|
-
|
|
77
|
+
---
|
|
31
78
|
|
|
32
|
-
|
|
79
|
+
## 📦 Quick Start
|
|
33
80
|
|
|
34
|
-
|
|
81
|
+
### Option A: Claude Desktop Setup
|
|
82
|
+
|
|
83
|
+
Add the server to your `claude_desktop_config.json`:
|
|
84
|
+
* **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
|
|
85
|
+
* **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
35
86
|
|
|
36
87
|
```json
|
|
37
88
|
{
|
|
38
89
|
"mcpServers": {
|
|
39
90
|
"projectpulse": {
|
|
40
91
|
"command": "npx",
|
|
41
|
-
"args": [
|
|
42
|
-
"-y",
|
|
43
|
-
"projectpulse-mcp"
|
|
44
|
-
],
|
|
92
|
+
"args": ["-y", "projectpulse-mcp"],
|
|
45
93
|
"env": {
|
|
46
|
-
"GITHUB_TOKEN": "
|
|
94
|
+
"GITHUB_TOKEN": "ghp_your_github_personal_access_token"
|
|
47
95
|
}
|
|
48
96
|
}
|
|
49
97
|
}
|
|
50
98
|
}
|
|
51
99
|
```
|
|
52
100
|
|
|
53
|
-
###
|
|
54
|
-
|
|
55
|
-
To use this MCP server in other clients that support `stdio` transport (like Cursor, Windsurf, or custom applications), configure a new MCP server with the following settings:
|
|
56
|
-
|
|
57
|
-
- **Type/Transport**: `stdio`
|
|
58
|
-
- **Command**: `npx`
|
|
59
|
-
- **Arguments**: `-y projectpulse-mcp`
|
|
60
|
-
- **Environment Variables**: Add `GITHUB_TOKEN` with your GitHub Personal Access Token.
|
|
101
|
+
### Option B: Claude Code CLI
|
|
61
102
|
|
|
62
|
-
|
|
103
|
+
```bash
|
|
104
|
+
claude mcp add projectpulse -- npx projectpulse-mcp
|
|
105
|
+
```
|
|
63
106
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
| `analyze_dependencies` | Get Dependabot alerts for a GitHub repository. | `owner` (string), `repo` (string), `severity` (optional enum: critical, high, medium, low) |
|
|
69
|
-
| `check_ci_status` | Get recent CI/CD workflow runs for a GitHub repository. | `owner` (string), `repo` (string), `limit` (optional number, default: 10) |
|
|
70
|
-
| `analyze_code_scanning` | Get Code Scanning alerts for a GitHub repository with CodeQL. | `owner` (string), `repo` (string) |
|
|
71
|
-
| `ping` | Simple connectivity check that responds with a pong. | `message` (string) |
|
|
107
|
+
### Option C: Cursor & Other MCP Clients (stdio transport)
|
|
108
|
+
* **Command:** `npx`
|
|
109
|
+
* **Args:** `-y projectpulse-mcp`
|
|
110
|
+
* **Environment Variable:** `GITHUB_TOKEN=your_token_here`
|
|
72
111
|
|
|
73
|
-
|
|
112
|
+
---
|
|
74
113
|
|
|
75
|
-
|
|
114
|
+
## 🔑 Configuration & GitHub Tokens
|
|
76
115
|
|
|
77
|
-
To
|
|
116
|
+
To prevent GitHub API rate-limiting and allow reading security advisories (Dependabot/CodeQL), generate a GitHub Personal Access Token (PAT):
|
|
78
117
|
|
|
79
|
-
**
|
|
80
|
-
|
|
81
|
-
2. Click **Generate new token**.
|
|
82
|
-
3. Under **Repository access**, select the repositories you want to analyze.
|
|
83
|
-
4. Under **Repository permissions**, grant **Read-only** access to:
|
|
118
|
+
1. Navigate to **GitHub** > **Settings** > **Developer settings** > **Personal access tokens** > **Fine-grained tokens**.
|
|
119
|
+
2. Grant **Read-only** permissions to:
|
|
84
120
|
- `Code scanning alerts`
|
|
85
121
|
- `Dependabot alerts`
|
|
86
|
-
- `
|
|
122
|
+
- `Actions`
|
|
123
|
+
- `Metadata` (default)
|
|
124
|
+
3. Set your token as the `GITHUB_TOKEN` environment variable in your `.env` file or MCP client configuration.
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## 🧪 Development & Testing
|
|
87
129
|
|
|
88
|
-
|
|
89
|
-
|
|
130
|
+
```bash
|
|
131
|
+
# Install dependencies
|
|
132
|
+
npm install
|
|
90
133
|
|
|
91
|
-
|
|
134
|
+
# Run comprehensive test suite (58 unit tests)
|
|
135
|
+
npm test
|
|
92
136
|
|
|
93
|
-
|
|
94
|
-
|
|
137
|
+
# Type-check TypeScript codebase
|
|
138
|
+
npm run lint
|
|
95
139
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
```env
|
|
99
|
-
GITHUB_TOKEN=ghp_your_token_here
|
|
140
|
+
# Build production bundle
|
|
141
|
+
npm run build
|
|
100
142
|
```
|
|
101
|
-
The MCP server automatically loads environment variables from `.env` files on startup.
|
|
102
143
|
|
|
103
|
-
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## 📄 License
|
|
147
|
+
|
|
148
|
+
Distributed under the **MIT License**. See [`LICENSE`](./LICENSE) for more information.
|
|
104
149
|
|
|
105
|
-
|
|
150
|
+
<div align="center">
|
|
151
|
+
<sub>Built with ❤️ by <a href="https://github.com/alexbypa">Alessandro Chiodo</a></sub>
|
|
152
|
+
</div>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dora.d.ts","sourceRoot":"","sources":["../../src/analytics/dora.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,WAAW,EAAe,MAAM,YAAY,CAAC;AAkBjE,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,SAAS,GAAG,WAAW,CAiElE"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
function getMedian(values) {
|
|
2
|
+
if (values.length === 0)
|
|
3
|
+
return null;
|
|
4
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
5
|
+
const mid = Math.floor(sorted.length / 2);
|
|
6
|
+
if (sorted.length % 2 === 0) {
|
|
7
|
+
return (sorted[mid - 1] + sorted[mid]) / 2;
|
|
8
|
+
}
|
|
9
|
+
return sorted[mid];
|
|
10
|
+
}
|
|
11
|
+
function diffHours(start, end) {
|
|
12
|
+
const s = new Date(start).getTime();
|
|
13
|
+
const e = new Date(end).getTime();
|
|
14
|
+
return (e - s) / (1000 * 3600);
|
|
15
|
+
}
|
|
16
|
+
export function calculateDoraMetrics(input) {
|
|
17
|
+
// 1. deployment_frequency
|
|
18
|
+
const releases_count = input.releases.length;
|
|
19
|
+
const per_week = releases_count > 0 ? releases_count / (input.period_days / 7) : null;
|
|
20
|
+
// 2. lead_time
|
|
21
|
+
const prHours = input.mergedPulls.map(pr => diffHours(pr.created_at, pr.merged_at));
|
|
22
|
+
const lead_median = getMedian(prHours);
|
|
23
|
+
// 3. change_failure_rate
|
|
24
|
+
const total_runs = input.workflowRuns.length;
|
|
25
|
+
const failed_runs = input.workflowRuns.filter(r => r.conclusion === 'failure').length;
|
|
26
|
+
const rate_percent = total_runs > 0 ? (failed_runs / total_runs) * 100 : null;
|
|
27
|
+
// 4. mttr
|
|
28
|
+
const branchRuns = new Map();
|
|
29
|
+
for (const run of input.workflowRuns) {
|
|
30
|
+
if (!branchRuns.has(run.head_branch)) {
|
|
31
|
+
branchRuns.set(run.head_branch, []);
|
|
32
|
+
}
|
|
33
|
+
branchRuns.get(run.head_branch).push(run);
|
|
34
|
+
}
|
|
35
|
+
const recoveryTimes = [];
|
|
36
|
+
for (const runs of branchRuns.values()) {
|
|
37
|
+
runs.sort((a, b) => new Date(a.created_at).getTime() - new Date(b.created_at).getTime());
|
|
38
|
+
let currentFailure = null;
|
|
39
|
+
for (const run of runs) {
|
|
40
|
+
if (run.conclusion === 'failure' && !currentFailure) {
|
|
41
|
+
currentFailure = run.created_at;
|
|
42
|
+
}
|
|
43
|
+
else if (run.conclusion === 'success' && currentFailure) {
|
|
44
|
+
recoveryTimes.push(diffHours(currentFailure, run.created_at));
|
|
45
|
+
currentFailure = null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// 5. metadata
|
|
50
|
+
const measured_to = new Date();
|
|
51
|
+
const measured_from = new Date(measured_to.getTime() - input.period_days * 24 * 3600 * 1000);
|
|
52
|
+
return {
|
|
53
|
+
owner: input.owner,
|
|
54
|
+
repo: input.repo,
|
|
55
|
+
period_days: input.period_days,
|
|
56
|
+
measured_from: measured_from.toISOString(),
|
|
57
|
+
measured_to: measured_to.toISOString(),
|
|
58
|
+
deployment_frequency: {
|
|
59
|
+
releases_count,
|
|
60
|
+
per_week
|
|
61
|
+
},
|
|
62
|
+
lead_time: {
|
|
63
|
+
median_hours: lead_median,
|
|
64
|
+
pr_count: input.mergedPulls.length
|
|
65
|
+
},
|
|
66
|
+
change_failure_rate: {
|
|
67
|
+
total_runs,
|
|
68
|
+
failed_runs,
|
|
69
|
+
rate_percent
|
|
70
|
+
},
|
|
71
|
+
mttr: {
|
|
72
|
+
median_hours: getMedian(recoveryTimes),
|
|
73
|
+
incidents_count: recoveryTimes.length
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=dora.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dora.js","sourceRoot":"","sources":["../../src/analytics/dora.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dora.test.d.ts","sourceRoot":"","sources":["../../src/analytics/dora.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { calculateDoraMetrics } from './dora.js';
|
|
3
|
+
// Helper: crea DoraInput con default vuoti, override parziale
|
|
4
|
+
function makeInput(overrides = {}) {
|
|
5
|
+
return {
|
|
6
|
+
owner: 'testowner',
|
|
7
|
+
repo: 'testrepo',
|
|
8
|
+
period_days: 28,
|
|
9
|
+
releases: [],
|
|
10
|
+
mergedPulls: [],
|
|
11
|
+
workflowRuns: [],
|
|
12
|
+
...overrides
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
describe('calculateDoraMetrics', () => {
|
|
16
|
+
describe('1. deployment_frequency', () => {
|
|
17
|
+
it('4 release in 28 giorni -> releases_count: 4, per_week: 1', () => {
|
|
18
|
+
const input = makeInput({
|
|
19
|
+
releases: [
|
|
20
|
+
{ id: 1, tag_name: 'v1.0', published_at: '2026-08-01T00:00:00Z' },
|
|
21
|
+
{ id: 2, tag_name: 'v1.1', published_at: '2026-08-08T00:00:00Z' },
|
|
22
|
+
{ id: 3, tag_name: 'v1.2', published_at: '2026-08-15T00:00:00Z' },
|
|
23
|
+
{ id: 4, tag_name: 'v1.3', published_at: '2026-08-22T00:00:00Z' },
|
|
24
|
+
],
|
|
25
|
+
period_days: 28
|
|
26
|
+
});
|
|
27
|
+
const result = calculateDoraMetrics(input);
|
|
28
|
+
expect(result.deployment_frequency.releases_count).toBe(4);
|
|
29
|
+
expect(result.deployment_frequency.per_week).toBe(1);
|
|
30
|
+
});
|
|
31
|
+
it('0 release -> releases_count: 0, per_week: null', () => {
|
|
32
|
+
const input = makeInput({ releases: [] });
|
|
33
|
+
const result = calculateDoraMetrics(input);
|
|
34
|
+
expect(result.deployment_frequency.releases_count).toBe(0);
|
|
35
|
+
expect(result.deployment_frequency.per_week).toBeNull();
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
describe('2. lead_time', () => {
|
|
39
|
+
it('3 PR con merge time 24h, 48h, 72h -> median_hours: 48, pr_count: 3', () => {
|
|
40
|
+
const input = makeInput({
|
|
41
|
+
mergedPulls: [
|
|
42
|
+
{ number: 1, created_at: '2026-08-01T00:00:00Z', merged_at: '2026-08-02T00:00:00Z' }, // 24h
|
|
43
|
+
{ number: 2, created_at: '2026-08-02T00:00:00Z', merged_at: '2026-08-04T00:00:00Z' }, // 48h
|
|
44
|
+
{ number: 3, created_at: '2026-08-03T00:00:00Z', merged_at: '2026-08-06T00:00:00Z' }, // 72h
|
|
45
|
+
]
|
|
46
|
+
});
|
|
47
|
+
const result = calculateDoraMetrics(input);
|
|
48
|
+
expect(result.lead_time.pr_count).toBe(3);
|
|
49
|
+
expect(result.lead_time.median_hours).toBe(48);
|
|
50
|
+
});
|
|
51
|
+
it('0 PR -> median_hours: null, pr_count: 0', () => {
|
|
52
|
+
const input = makeInput({ mergedPulls: [] });
|
|
53
|
+
const result = calculateDoraMetrics(input);
|
|
54
|
+
expect(result.lead_time.pr_count).toBe(0);
|
|
55
|
+
expect(result.lead_time.median_hours).toBeNull();
|
|
56
|
+
});
|
|
57
|
+
it('2 PR (pari) con 24h e 48h -> median_hours: 36 (media dei due centrali)', () => {
|
|
58
|
+
const input = makeInput({
|
|
59
|
+
mergedPulls: [
|
|
60
|
+
{ number: 1, created_at: '2026-08-01T00:00:00Z', merged_at: '2026-08-02T00:00:00Z' }, // 24h
|
|
61
|
+
{ number: 2, created_at: '2026-08-02T00:00:00Z', merged_at: '2026-08-04T00:00:00Z' }, // 48h
|
|
62
|
+
]
|
|
63
|
+
});
|
|
64
|
+
const result = calculateDoraMetrics(input);
|
|
65
|
+
expect(result.lead_time.pr_count).toBe(2);
|
|
66
|
+
expect(result.lead_time.median_hours).toBe(36);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
describe('3. change_failure_rate', () => {
|
|
70
|
+
it('10 run, 3 failure -> total_runs: 10, failed_runs: 3, rate_percent: 30', () => {
|
|
71
|
+
const runs = Array.from({ length: 10 }).map((_, i) => ({
|
|
72
|
+
id: i,
|
|
73
|
+
name: 'CI',
|
|
74
|
+
head_branch: 'main',
|
|
75
|
+
conclusion: i < 3 ? 'failure' : 'success',
|
|
76
|
+
created_at: '2026-08-01T00:00:00Z'
|
|
77
|
+
}));
|
|
78
|
+
const input = makeInput({ workflowRuns: runs });
|
|
79
|
+
const result = calculateDoraMetrics(input);
|
|
80
|
+
expect(result.change_failure_rate.total_runs).toBe(10);
|
|
81
|
+
expect(result.change_failure_rate.failed_runs).toBe(3);
|
|
82
|
+
expect(result.change_failure_rate.rate_percent).toBe(30);
|
|
83
|
+
});
|
|
84
|
+
it('0 run -> rate_percent: null', () => {
|
|
85
|
+
const input = makeInput({ workflowRuns: [] });
|
|
86
|
+
const result = calculateDoraMetrics(input);
|
|
87
|
+
expect(result.change_failure_rate.total_runs).toBe(0);
|
|
88
|
+
expect(result.change_failure_rate.rate_percent).toBeNull();
|
|
89
|
+
});
|
|
90
|
+
it('Tutti success -> rate_percent: 0', () => {
|
|
91
|
+
const input = makeInput({
|
|
92
|
+
workflowRuns: [
|
|
93
|
+
{ id: 1, name: 'CI', head_branch: 'main', conclusion: 'success', created_at: '2026-08-01T00:00:00Z' }
|
|
94
|
+
]
|
|
95
|
+
});
|
|
96
|
+
const result = calculateDoraMetrics(input);
|
|
97
|
+
expect(result.change_failure_rate.failed_runs).toBe(0);
|
|
98
|
+
expect(result.change_failure_rate.rate_percent).toBe(0);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
describe('4. mttr', () => {
|
|
102
|
+
it('2 cicli failure->success (4h e 8h) -> median_hours: 6, incidents_count: 2', () => {
|
|
103
|
+
const input = makeInput({
|
|
104
|
+
workflowRuns: [
|
|
105
|
+
// Cycle 1: 4h recovery
|
|
106
|
+
{ id: 1, name: 'CI', head_branch: 'main', conclusion: 'failure', created_at: '2026-08-01T10:00:00Z' },
|
|
107
|
+
{ id: 2, name: 'CI', head_branch: 'main', conclusion: 'success', created_at: '2026-08-01T14:00:00Z' },
|
|
108
|
+
// Cycle 2: 8h recovery
|
|
109
|
+
{ id: 3, name: 'CI', head_branch: 'main', conclusion: 'failure', created_at: '2026-08-02T10:00:00Z' },
|
|
110
|
+
{ id: 4, name: 'CI', head_branch: 'main', conclusion: 'success', created_at: '2026-08-02T18:00:00Z' },
|
|
111
|
+
]
|
|
112
|
+
});
|
|
113
|
+
const result = calculateDoraMetrics(input);
|
|
114
|
+
expect(result.mttr.incidents_count).toBe(2);
|
|
115
|
+
expect(result.mttr.median_hours).toBe(6);
|
|
116
|
+
});
|
|
117
|
+
it('Solo success -> median_hours: null, incidents_count: 0', () => {
|
|
118
|
+
const input = makeInput({
|
|
119
|
+
workflowRuns: [
|
|
120
|
+
{ id: 1, name: 'CI', head_branch: 'main', conclusion: 'success', created_at: '2026-08-01T10:00:00Z' }
|
|
121
|
+
]
|
|
122
|
+
});
|
|
123
|
+
const result = calculateDoraMetrics(input);
|
|
124
|
+
expect(result.mttr.incidents_count).toBe(0);
|
|
125
|
+
expect(result.mttr.median_hours).toBeNull();
|
|
126
|
+
});
|
|
127
|
+
it('Multi-branch: main ha failure->success (4h), dev ha failure senza recovery -> incidents_count: 1 (solo main conta)', () => {
|
|
128
|
+
const input = makeInput({
|
|
129
|
+
workflowRuns: [
|
|
130
|
+
// main: 4h recovery
|
|
131
|
+
{ id: 1, name: 'CI', head_branch: 'main', conclusion: 'failure', created_at: '2026-08-01T10:00:00Z' },
|
|
132
|
+
{ id: 2, name: 'CI', head_branch: 'main', conclusion: 'success', created_at: '2026-08-01T14:00:00Z' },
|
|
133
|
+
// dev: failure without success
|
|
134
|
+
{ id: 3, name: 'CI', head_branch: 'dev', conclusion: 'failure', created_at: '2026-08-02T10:00:00Z' },
|
|
135
|
+
]
|
|
136
|
+
});
|
|
137
|
+
const result = calculateDoraMetrics(input);
|
|
138
|
+
expect(result.mttr.incidents_count).toBe(1);
|
|
139
|
+
expect(result.mttr.median_hours).toBe(4);
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
describe('5. metadata', () => {
|
|
143
|
+
it('Verifica owner, repo, period_days, measured_from, measured_to presenti', () => {
|
|
144
|
+
const input = makeInput({ owner: 'alice', repo: 'wonderland', period_days: 14 });
|
|
145
|
+
const result = calculateDoraMetrics(input);
|
|
146
|
+
expect(result.owner).toBe('alice');
|
|
147
|
+
expect(result.repo).toBe('wonderland');
|
|
148
|
+
expect(result.period_days).toBe(14);
|
|
149
|
+
expect(result.measured_to).toBeDefined();
|
|
150
|
+
expect(result.measured_from).toBeDefined();
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
describe('6. all null scenario', () => {
|
|
154
|
+
it('Input completamente vuoto -> tutti i nullable sono null, nessun crash', () => {
|
|
155
|
+
const input = makeInput();
|
|
156
|
+
const result = calculateDoraMetrics(input);
|
|
157
|
+
expect(result.deployment_frequency.per_week).toBeNull();
|
|
158
|
+
expect(result.lead_time.median_hours).toBeNull();
|
|
159
|
+
expect(result.change_failure_rate.rate_percent).toBeNull();
|
|
160
|
+
expect(result.mttr.median_hours).toBeNull();
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
//# sourceMappingURL=dora.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dora.test.js","sourceRoot":"","sources":["../../src/analytics/dora.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AAGjD,8DAA8D;AAC9D,SAAS,SAAS,CAAC,YAAgC,EAAE;IACjD,OAAO;QACH,KAAK,EAAE,WAAW;QAClB,IAAI,EAAE,UAAU;QAChB,WAAW,EAAE,EAAE;QACf,QAAQ,EAAE,EAAE;QACZ,WAAW,EAAE,EAAE;QACf,YAAY,EAAE,EAAE;QAChB,GAAG,SAAS;KACf,CAAC;AACN,CAAC;AAED,QAAQ,CAAC,sBAAsB,EAAE,GAAG,EAAE;IAElC,QAAQ,CAAC,yBAAyB,EAAE,GAAG,EAAE;QACrC,EAAE,CAAC,0DAA0D,EAAE,GAAG,EAAE;YAChE,MAAM,KAAK,GAAG,SAAS,CAAC;gBACpB,QAAQ,EAAE;oBACN,EAAE,EAAE,EAAE,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,sBAAsB,EAAE;oBACjE,EAAE,EAAE,EAAE,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,sBAAsB,EAAE;oBACjE,EAAE,EAAE,EAAE,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,sBAAsB,EAAE;oBACjE,EAAE,EAAE,EAAE,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,sBAAsB,EAAE;iBACpE;gBACD,WAAW,EAAE,EAAE;aAClB,CAAC,CAAC;YACH,MAAM,MAAM,GAAG,oBAAoB,CAAC,KAAK,CAAC,CAAC;YAC3C,MAAM,CAAC,MAAM,CAAC,oBAAoB,CAAC,cAAc,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAC3D,MAAM,CAAC,MAAM,CAAC,oBAAoB,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACzD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,gDAAgD,EAAE,GAAG,EAAE;YACtD,MAAM,KAAK,GAAG,SAAS,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC,CAAC;YAC1C,MAAM,MAAM,GAAG,oBAAoB,CAAC,KAAK,CAAC,CAAC;YAC3C,MAAM,CAAC,MAAM,CAAC,oBAAoB,CAAC,cAAc,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAC3D,MAAM,CAAC,MAAM,CAAC,oBAAoB,CAAC,QAAQ,CAAC,CAAC,QAAQ,EAAE,CAAC;QAC5D,CAAC,CAAC,CAAC;IACP,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,cAAc,EAAE,GAAG,EAAE;QAC1B,EAAE,CAAC,oEAAoE,EAAE,GAAG,EAAE;YAC1E,MAAM,KAAK,GAAG,SAAS,CAAC;gBACpB,WAAW,EAAE;oBACT,EAAE,MAAM,EAAE,CAAC,EAAE,UAAU,EAAE,sBAAsB,EAAE,SAAS,EAAE,sBAAsB,EAAE,EAAE,MAAM;oBAC5F,EAAE,MAAM,EAAE,CAAC,EAAE,UAAU,EAAE,sBAAsB,EAAE,SAAS,EAAE,sBAAsB,EAAE,EAAE,MAAM;oBAC5F,EAAE,MAAM,EAAE,CAAC,EAAE,UAAU,EAAE,sBAAsB,EAAE,SAAS,EAAE,sBAAsB,EAAE,EAAE,MAAM;iBAC/F;aACJ,CAAC,CAAC;YACH,MAAM,MAAM,GAAG,oBAAoB,CAAC,KAAK,CAAC,CAAC;YAC3C,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAC1C,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACnD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,yCAAyC,EAAE,GAAG,EAAE;YAC/C,MAAM,KAAK,GAAG,SAAS,CAAC,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC,CAAC;YAC7C,MAAM,MAAM,GAAG,oBAAoB,CAAC,KAAK,CAAC,CAAC;YAC3C,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAC1C,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC,QAAQ,EAAE,CAAC;QACrD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,wEAAwE,EAAE,GAAG,EAAE;YAC9E,MAAM,KAAK,GAAG,SAAS,CAAC;gBACpB,WAAW,EAAE;oBACT,EAAE,MAAM,EAAE,CAAC,EAAE,UAAU,EAAE,sBAAsB,EAAE,SAAS,EAAE,sBAAsB,EAAE,EAAE,MAAM;oBAC5F,EAAE,MAAM,EAAE,CAAC,EAAE,UAAU,EAAE,sBAAsB,EAAE,SAAS,EAAE,sBAAsB,EAAE,EAAE,MAAM;iBAC/F;aACJ,CAAC,CAAC;YACH,MAAM,MAAM,GAAG,oBAAoB,CAAC,KAAK,CAAC,CAAC;YAC3C,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAC1C,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACnD,CAAC,CAAC,CAAC;IACP,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,wBAAwB,EAAE,GAAG,EAAE;QACpC,EAAE,CAAC,uEAAuE,EAAE,GAAG,EAAE;YAC7E,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;gBACnD,EAAE,EAAE,CAAC;gBACL,IAAI,EAAE,IAAI;gBACV,WAAW,EAAE,MAAM;gBACnB,UAAU,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS;gBACzC,UAAU,EAAE,sBAAsB;aACrC,CAAC,CAAC,CAAC;YACJ,MAAM,KAAK,GAAG,SAAS,CAAC,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC;YAChD,MAAM,MAAM,GAAG,oBAAoB,CAAC,KAAK,CAAC,CAAC;YAC3C,MAAM,CAAC,MAAM,CAAC,mBAAmB,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACvD,MAAM,CAAC,MAAM,CAAC,mBAAmB,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YACvD,MAAM,CAAC,MAAM,CAAC,mBAAmB,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC7D,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,6BAA6B,EAAE,GAAG,EAAE;YACnC,MAAM,KAAK,GAAG,SAAS,CAAC,EAAE,YAAY,EAAE,EAAE,EAAE,CAAC,CAAC;YAC9C,MAAM,MAAM,GAAG,oBAAoB,CAAC,KAAK,CAAC,CAAC;YAC3C,MAAM,CAAC,MAAM,CAAC,mBAAmB,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YACtD,MAAM,CAAC,MAAM,CAAC,mBAAmB,CAAC,YAAY,CAAC,CAAC,QAAQ,EAAE,CAAC;QAC/D,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,kCAAkC,EAAE,GAAG,EAAE;YACxC,MAAM,KAAK,GAAG,SAAS,CAAC;gBACpB,YAAY,EAAE;oBACV,EAAE,EAAE,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,sBAAsB,EAAE;iBACxG;aACJ,CAAC,CAAC;YACH,MAAM,MAAM,GAAG,oBAAoB,CAAC,KAAK,CAAC,CAAC;YAC3C,MAAM,CAAC,MAAM,CAAC,mBAAmB,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YACvD,MAAM,CAAC,MAAM,CAAC,mBAAmB,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC5D,CAAC,CAAC,CAAC;IACP,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,SAAS,EAAE,GAAG,EAAE;QACrB,EAAE,CAAC,2EAA2E,EAAE,GAAG,EAAE;YACjF,MAAM,KAAK,GAAG,SAAS,CAAC;gBACpB,YAAY,EAAE;oBACV,uBAAuB;oBACvB,EAAE,EAAE,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,sBAAsB,EAAE;oBACrG,EAAE,EAAE,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,sBAAsB,EAAE;oBACrG,uBAAuB;oBACvB,EAAE,EAAE,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,sBAAsB,EAAE;oBACrG,EAAE,EAAE,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,sBAAsB,EAAE;iBACxG;aACJ,CAAC,CAAC;YACH,MAAM,MAAM,GAAG,oBAAoB,CAAC,KAAK,CAAC,CAAC;YAC3C,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAC5C,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC7C,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,wDAAwD,EAAE,GAAG,EAAE;YAC9D,MAAM,KAAK,GAAG,SAAS,CAAC;gBACpB,YAAY,EAAE;oBACV,EAAE,EAAE,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,sBAAsB,EAAE;iBACxG;aACJ,CAAC,CAAC;YACH,MAAM,MAAM,GAAG,oBAAoB,CAAC,KAAK,CAAC,CAAC;YAC3C,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAC5C,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,QAAQ,EAAE,CAAC;QAChD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,oHAAoH,EAAE,GAAG,EAAE;YAC1H,MAAM,KAAK,GAAG,SAAS,CAAC;gBACpB,YAAY,EAAE;oBACV,oBAAoB;oBACpB,EAAE,EAAE,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,sBAAsB,EAAE;oBACrG,EAAE,EAAE,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,sBAAsB,EAAE;oBACrG,+BAA+B;oBAC/B,EAAE,EAAE,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,sBAAsB,EAAE;iBACvG;aACJ,CAAC,CAAC;YACH,MAAM,MAAM,GAAG,oBAAoB,CAAC,KAAK,CAAC,CAAC;YAC3C,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAC5C,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC7C,CAAC,CAAC,CAAC;IACP,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,aAAa,EAAE,GAAG,EAAE;QACzB,EAAE,CAAC,wEAAwE,EAAE,GAAG,EAAE;YAC9E,MAAM,KAAK,GAAG,SAAS,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC,CAAC;YACjF,MAAM,MAAM,GAAG,oBAAoB,CAAC,KAAK,CAAC,CAAC;YAC3C,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACnC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;YACvC,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACpC,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,WAAW,EAAE,CAAC;YACzC,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,WAAW,EAAE,CAAC;QAC/C,CAAC,CAAC,CAAC;IACP,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,sBAAsB,EAAE,GAAG,EAAE;QAClC,EAAE,CAAC,uEAAuE,EAAE,GAAG,EAAE;YAC7E,MAAM,KAAK,GAAG,SAAS,EAAE,CAAC;YAC1B,MAAM,MAAM,GAAG,oBAAoB,CAAC,KAAK,CAAC,CAAC;YAE3C,MAAM,CAAC,MAAM,CAAC,oBAAoB,CAAC,QAAQ,CAAC,CAAC,QAAQ,EAAE,CAAC;YACxD,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC,QAAQ,EAAE,CAAC;YACjD,MAAM,CAAC,MAAM,CAAC,mBAAmB,CAAC,YAAY,CAAC,CAAC,QAAQ,EAAE,CAAC;YAC3D,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,QAAQ,EAAE,CAAC;QAChD,CAAC,CAAC,CAAC;IACP,CAAC,CAAC,CAAC;AAEP,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export type Release = {
|
|
2
|
+
id: number;
|
|
3
|
+
tag_name: string;
|
|
4
|
+
published_at: string;
|
|
5
|
+
};
|
|
6
|
+
export type PullRequest = {
|
|
7
|
+
number: number;
|
|
8
|
+
created_at: string;
|
|
9
|
+
merged_at: string | null;
|
|
10
|
+
};
|
|
11
|
+
export type WorkflowRun = {
|
|
12
|
+
id: number;
|
|
13
|
+
name: string;
|
|
14
|
+
head_branch: string;
|
|
15
|
+
conclusion: string | null;
|
|
16
|
+
created_at: string;
|
|
17
|
+
};
|
|
18
|
+
export type DoraInput = {
|
|
19
|
+
owner: string;
|
|
20
|
+
repo: string;
|
|
21
|
+
period_days: number;
|
|
22
|
+
releases: Release[];
|
|
23
|
+
mergedPulls: PullRequest[];
|
|
24
|
+
workflowRuns: WorkflowRun[];
|
|
25
|
+
};
|
|
26
|
+
export type DoraMetrics = {
|
|
27
|
+
owner: string;
|
|
28
|
+
repo: string;
|
|
29
|
+
period_days: number;
|
|
30
|
+
measured_from: string;
|
|
31
|
+
measured_to: string;
|
|
32
|
+
deployment_frequency: {
|
|
33
|
+
releases_count: number;
|
|
34
|
+
per_week: number | null;
|
|
35
|
+
};
|
|
36
|
+
lead_time: {
|
|
37
|
+
median_hours: number | null;
|
|
38
|
+
pr_count: number;
|
|
39
|
+
};
|
|
40
|
+
change_failure_rate: {
|
|
41
|
+
total_runs: number;
|
|
42
|
+
failed_runs: number;
|
|
43
|
+
rate_percent: number | null;
|
|
44
|
+
};
|
|
45
|
+
mttr: {
|
|
46
|
+
median_hours: number | null;
|
|
47
|
+
incidents_count: number;
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/analytics/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,OAAO,GAAG;IAClB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,WAAW,EAAE,WAAW,EAAE,CAAC;IAC3B,YAAY,EAAE,WAAW,EAAE,CAAC;CAC/B,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,oBAAoB,EAAE;QAClB,cAAc,EAAE,MAAM,CAAC;QACvB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;KAC3B,CAAC;IACF,SAAS,EAAE;QACP,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;QAC5B,QAAQ,EAAE,MAAM,CAAC;KACpB,CAAC;IACF,mBAAmB,EAAE;QACjB,UAAU,EAAE,MAAM,CAAC;QACnB,WAAW,EAAE,MAAM,CAAC;QACpB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;KAC/B,CAAC;IACF,IAAI,EAAE;QACF,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;QAC5B,eAAe,EAAE,MAAM,CAAC;KAC3B,CAAC;CACL,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/analytics/types.ts"],"names":[],"mappings":""}
|
package/dist/github/client.d.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
1
|
import { Octokit } from "@octokit/rest";
|
|
2
|
+
import type { Release, PullRequest, WorkflowRun } from "../analytics/types.js";
|
|
2
3
|
export declare function getOctokit(): Octokit;
|
|
4
|
+
export declare function listReleases(owner: string, repo: string, since: Date): Promise<Release[]>;
|
|
5
|
+
export declare function listMergedPulls(owner: string, repo: string, since: Date): Promise<PullRequest[]>;
|
|
6
|
+
export declare function listWorkflowRuns(owner: string, repo: string, since: Date): Promise<WorkflowRun[]>;
|
|
3
7
|
//# sourceMappingURL=client.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/github/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/github/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAI/E,wBAAgB,UAAU,IAAI,OAAO,CAMpC;AAED,wBAAsB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAc/F;AAED,wBAAsB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAiBtG;AAED,wBAAsB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAgBvG"}
|
package/dist/github/client.js
CHANGED
|
@@ -7,4 +7,54 @@ export function getOctokit() {
|
|
|
7
7
|
}
|
|
8
8
|
return octokitInstance;
|
|
9
9
|
}
|
|
10
|
+
export async function listReleases(owner, repo, since) {
|
|
11
|
+
const octokit = getOctokit();
|
|
12
|
+
const { data } = await octokit.repos.listReleases({
|
|
13
|
+
owner,
|
|
14
|
+
repo,
|
|
15
|
+
per_page: 100
|
|
16
|
+
});
|
|
17
|
+
return data
|
|
18
|
+
.filter(r => r.published_at !== null && new Date(r.published_at) >= since)
|
|
19
|
+
.map((r) => ({
|
|
20
|
+
id: r.id,
|
|
21
|
+
tag_name: r.tag_name,
|
|
22
|
+
published_at: r.published_at,
|
|
23
|
+
}));
|
|
24
|
+
}
|
|
25
|
+
export async function listMergedPulls(owner, repo, since) {
|
|
26
|
+
const octokit = getOctokit();
|
|
27
|
+
const { data } = await octokit.pulls.list({
|
|
28
|
+
owner,
|
|
29
|
+
repo,
|
|
30
|
+
state: "closed",
|
|
31
|
+
sort: "updated",
|
|
32
|
+
direction: "desc",
|
|
33
|
+
per_page: 100
|
|
34
|
+
});
|
|
35
|
+
return data
|
|
36
|
+
.filter(p => p.merged_at !== null && new Date(p.merged_at) >= since)
|
|
37
|
+
.map((p) => ({
|
|
38
|
+
number: p.number,
|
|
39
|
+
created_at: p.created_at,
|
|
40
|
+
merged_at: p.merged_at,
|
|
41
|
+
}));
|
|
42
|
+
}
|
|
43
|
+
export async function listWorkflowRuns(owner, repo, since) {
|
|
44
|
+
const octokit = getOctokit();
|
|
45
|
+
const { data } = await octokit.actions.listWorkflowRunsForRepo({
|
|
46
|
+
owner,
|
|
47
|
+
repo,
|
|
48
|
+
per_page: 100
|
|
49
|
+
});
|
|
50
|
+
return data.workflow_runs
|
|
51
|
+
.filter(r => new Date(r.created_at) >= since)
|
|
52
|
+
.map((r) => ({
|
|
53
|
+
id: r.id,
|
|
54
|
+
name: r.name ?? "unknown",
|
|
55
|
+
head_branch: r.head_branch ?? "unknown",
|
|
56
|
+
conclusion: r.conclusion,
|
|
57
|
+
created_at: r.created_at,
|
|
58
|
+
}));
|
|
59
|
+
}
|
|
10
60
|
//# sourceMappingURL=client.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/github/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/github/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAGxC,IAAI,eAAe,GAAmB,IAAI,CAAC;AAE3C,MAAM,UAAU,UAAU;IACtB,IAAI,CAAC,eAAe,EAAE,CAAC;QACnB,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC;QACvC,eAAe,GAAG,IAAI,OAAO,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACnD,CAAC;IACD,OAAO,eAAe,CAAC;AAC3B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,KAAa,EAAE,IAAY,EAAE,KAAW;IACvE,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC;IAC7B,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC;QAC9C,KAAK;QACL,IAAI;QACJ,QAAQ,EAAE,GAAG;KAChB,CAAC,CAAC;IACH,OAAO,IAAI;SACN,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,YAAY,KAAK,IAAI,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,YAAY,CAAC,IAAI,KAAK,CAAC;SACzE,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACb,EAAE,EAAE,CAAC,CAAC,EAAE;QACR,QAAQ,EAAE,CAAC,CAAC,QAAQ;QACpB,YAAY,EAAE,CAAC,CAAC,YAAa;KAChC,CAAC,CAAC,CAAC;AACR,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,KAAa,EAAE,IAAY,EAAE,KAAW;IAC1E,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC;IAC7B,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC;QACtC,KAAK;QACL,IAAI;QACJ,KAAK,EAAE,QAAQ;QACf,IAAI,EAAE,SAAS;QACf,SAAS,EAAE,MAAM;QACjB,QAAQ,EAAE,GAAG;KAChB,CAAC,CAAC;IACH,OAAO,IAAI;SACN,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,KAAK,IAAI,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,KAAK,CAAC;SACnE,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACb,MAAM,EAAE,CAAC,CAAC,MAAM;QAChB,UAAU,EAAE,CAAC,CAAC,UAAU;QACxB,SAAS,EAAE,CAAC,CAAC,SAAS;KACzB,CAAC,CAAC,CAAC;AACR,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,KAAa,EAAE,IAAY,EAAE,KAAW;IAC3E,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC;IAC7B,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,uBAAuB,CAAC;QAC3D,KAAK;QACL,IAAI;QACJ,QAAQ,EAAE,GAAG;KAChB,CAAC,CAAC;IACH,OAAO,IAAI,CAAC,aAAa;SACpB,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,KAAK,CAAC;SAC5C,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACb,EAAE,EAAE,CAAC,CAAC,EAAE;QACR,IAAI,EAAE,CAAC,CAAC,IAAI,IAAI,SAAS;QACzB,WAAW,EAAE,CAAC,CAAC,WAAW,IAAI,SAAS;QACvC,UAAU,EAAE,CAAC,CAAC,UAAU;QACxB,UAAU,EAAE,CAAC,CAAC,UAAU;KAC3B,CAAC,CAAC,CAAC;AACR,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -6,12 +6,16 @@ import { registerGetRepoHealth } from "./tools/get-repo-health.js";
|
|
|
6
6
|
import { registerAnalyzeDependencies } from "./tools/analyze-dependencies.js";
|
|
7
7
|
import { registerAnalyzeCodeScanning } from "./tools/analyze-code-scanning.js";
|
|
8
8
|
import { registerGetHealthScore } from "./tools/get-health-score.js";
|
|
9
|
+
import { registerGetDoraMetrics } from "./tools/get-dora-metrics.js";
|
|
10
|
+
import { registerCompareRepos } from "./tools/compare-repos.js";
|
|
9
11
|
const server = new McpServer({ name: "projectpulse-mcp", version: "0.1.0" });
|
|
10
12
|
registerCheckCiStatus(server);
|
|
11
13
|
registerGetRepoHealth(server);
|
|
12
14
|
registerAnalyzeDependencies(server);
|
|
13
15
|
registerAnalyzeCodeScanning(server);
|
|
14
16
|
registerGetHealthScore(server);
|
|
17
|
+
registerGetDoraMetrics(server);
|
|
18
|
+
registerCompareRepos(server);
|
|
15
19
|
server.registerTool("ping", {
|
|
16
20
|
description: "This is a simple ping, response with pong",
|
|
17
21
|
inputSchema: z.object({
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAA;AAChF,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AACnE,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AACnE,OAAO,EAAE,2BAA2B,EAAE,MAAM,iCAAiC,CAAC;AAC9E,OAAO,EAAE,2BAA2B,EAAE,MAAM,kCAAkC,CAAC;AAC/E,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAA;AAChF,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AACnE,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AACnE,OAAO,EAAE,2BAA2B,EAAE,MAAM,iCAAiC,CAAC;AAC9E,OAAO,EAAE,2BAA2B,EAAE,MAAM,kCAAkC,CAAC;AAC/E,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AACrE,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AACrE,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAChE,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,EAAE,IAAI,EAAE,kBAAkB,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;AAE7E,qBAAqB,CAAC,MAAM,CAAC,CAAC;AAC9B,qBAAqB,CAAC,MAAM,CAAC,CAAC;AAC9B,2BAA2B,CAAC,MAAM,CAAC,CAAC;AACpC,2BAA2B,CAAC,MAAM,CAAC,CAAC;AACpC,sBAAsB,CAAC,MAAM,CAAC,CAAC;AAC/B,sBAAsB,CAAC,MAAM,CAAC,CAAC;AAC/B,oBAAoB,CAAC,MAAM,CAAC,CAAC;AAE7B,MAAM,CAAC,YAAY,CACf,MAAM,EAAE;IACR,WAAW,EAAE,2CAA2C;IACxD,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC;QAClB,OAAO,EAAE,CAAC;aACL,MAAM,EAAE;aACR,QAAQ,CAAC,2CAA2C,CAAC;KAC7D,CAAC;CACL,EACG,KAAK,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE;IAClB,OAAO;QACH,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,OAAO,EAAE,EAAE,CAAC;KACvD,CAAC;AACN,CAAC,CACJ,CAAA;AAED,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAA;AAC5C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
export declare const inputSchema: z.ZodObject<{
|
|
4
|
+
repos: z.ZodArray<z.ZodObject<{
|
|
5
|
+
owner: z.ZodString;
|
|
6
|
+
repo: z.ZodString;
|
|
7
|
+
}, z.core.$strip>>;
|
|
8
|
+
}, z.core.$strip>;
|
|
9
|
+
export declare function executeCompareRepos({ repos }: {
|
|
10
|
+
repos: {
|
|
11
|
+
owner: string;
|
|
12
|
+
repo: string;
|
|
13
|
+
}[];
|
|
14
|
+
}): Promise<{
|
|
15
|
+
content: {
|
|
16
|
+
type: "text";
|
|
17
|
+
text: string;
|
|
18
|
+
}[];
|
|
19
|
+
}>;
|
|
20
|
+
export declare function registerCompareRepos(server: McpServer): void;
|
|
21
|
+
//# sourceMappingURL=compare-repos.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compare-repos.d.ts","sourceRoot":"","sources":["../../src/tools/compare-repos.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAIpE,eAAO,MAAM,WAAW;;;;;iBAWtB,CAAC;AAEH,wBAAsB,mBAAmB,CAAC,EAAE,KAAK,EAAE,EAAE;IAAE,KAAK,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;KAAE,EAAE,CAAA;CAAE;;;;;GAoEjG;AAED,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAS5D"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { executeGetHealthScore } from "./get-health-score.js";
|
|
3
|
+
export const inputSchema = z.object({
|
|
4
|
+
repos: z.array(z.object({
|
|
5
|
+
owner: z.string().describe("GitHub repository owner"),
|
|
6
|
+
repo: z.string().describe("GitHub repository name")
|
|
7
|
+
})).min(2, {
|
|
8
|
+
message: "At least 2 repositories are required"
|
|
9
|
+
}).max(5, {
|
|
10
|
+
message: "At most 5 repositories are allowed"
|
|
11
|
+
})
|
|
12
|
+
});
|
|
13
|
+
export async function executeCompareRepos({ repos }) {
|
|
14
|
+
const reposResults = await Promise.all(repos.map(async (repo) => {
|
|
15
|
+
try {
|
|
16
|
+
const result = await executeGetHealthScore(repo);
|
|
17
|
+
const report = JSON.parse(result.content[0].text);
|
|
18
|
+
return {
|
|
19
|
+
owner: repo.owner,
|
|
20
|
+
repo: repo.repo,
|
|
21
|
+
health: report,
|
|
22
|
+
error: null,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
return {
|
|
27
|
+
owner: repo.owner,
|
|
28
|
+
repo: repo.repo,
|
|
29
|
+
health: null,
|
|
30
|
+
error: error.message || String(error),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
}));
|
|
34
|
+
const successes = reposResults
|
|
35
|
+
.filter((r) => r.health !== null)
|
|
36
|
+
.sort((a, b) => b.health.score - a.health.score);
|
|
37
|
+
const failures = reposResults
|
|
38
|
+
.filter(r => r.health === null)
|
|
39
|
+
.map(r => ({
|
|
40
|
+
owner: r.owner,
|
|
41
|
+
repo: r.repo,
|
|
42
|
+
rank: 0,
|
|
43
|
+
health: null,
|
|
44
|
+
error: r.error
|
|
45
|
+
}));
|
|
46
|
+
const rankedSuccesses = [];
|
|
47
|
+
let currentRank = 1;
|
|
48
|
+
for (let i = 0; i < successes.length; i++) {
|
|
49
|
+
const current = successes[i];
|
|
50
|
+
if (i > 0) {
|
|
51
|
+
const prev = successes[i - 1];
|
|
52
|
+
if (current.health.score < prev.health.score) {
|
|
53
|
+
currentRank = i + 1;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
rankedSuccesses.push({
|
|
57
|
+
owner: current.owner,
|
|
58
|
+
repo: current.repo,
|
|
59
|
+
rank: currentRank,
|
|
60
|
+
health: current.health,
|
|
61
|
+
error: null
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
const comparedRepos = [...rankedSuccesses, ...failures];
|
|
65
|
+
const comparison = {
|
|
66
|
+
compared_at: new Date().toISOString(),
|
|
67
|
+
repos: comparedRepos
|
|
68
|
+
};
|
|
69
|
+
return {
|
|
70
|
+
content: [
|
|
71
|
+
{ type: "text", text: JSON.stringify(comparison, null, 2) }
|
|
72
|
+
]
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
export function registerCompareRepos(server) {
|
|
76
|
+
server.registerTool("compare_repos", {
|
|
77
|
+
description: "Compare health scores of multiple GitHub repositories, returning ranked JSON with category breakdown and ranking for each repository",
|
|
78
|
+
inputSchema,
|
|
79
|
+
}, executeCompareRepos);
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=compare-repos.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compare-repos.js","sourceRoot":"","sources":["../../src/tools/compare-repos.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAG9D,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,CAAC,MAAM,CAAC;IAChC,KAAK,EAAE,CAAC,CAAC,KAAK,CACV,CAAC,CAAC,MAAM,CAAC;QACL,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,yBAAyB,CAAC;QACrD,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,wBAAwB,CAAC;KACtD,CAAC,CACL,CAAC,GAAG,CAAC,CAAC,EAAE;QACL,OAAO,EAAE,sCAAsC;KAClD,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE;QACN,OAAO,EAAE,oCAAoC;KAChD,CAAC;CACL,CAAC,CAAC;AAEH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,EAAE,KAAK,EAAiD;IAC9F,MAAM,YAAY,GAAG,MAAM,OAAO,CAAC,GAAG,CAClC,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,EAA+F,EAAE;QAClH,IAAI,CAAC;YACD,MAAM,MAAM,GAAG,MAAM,qBAAqB,CAAC,IAAI,CAAC,CAAC;YACjD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAiB,CAAC;YAClE,OAAO;gBACH,KAAK,EAAE,IAAI,CAAC,KAAK;gBACjB,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,MAAM,EAAE,MAAM;gBACd,KAAK,EAAE,IAAI;aACd,CAAC;QACN,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACb,OAAO;gBACH,KAAK,EAAE,IAAI,CAAC,KAAK;gBACjB,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,MAAM,EAAE,IAAI;gBACZ,KAAK,EAAG,KAAe,CAAC,OAAO,IAAI,MAAM,CAAC,KAAK,CAAC;aACnD,CAAC;QACN,CAAC;IACL,CAAC,CAAC,CACL,CAAC;IAEF,MAAM,SAAS,GAAG,YAAY;SACzB,MAAM,CAAC,CAAC,CAAC,EAA4C,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,IAAI,CAAC;SAC1E,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,GAAG,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAErD,MAAM,QAAQ,GAAG,YAAY;SACxB,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,IAAI,CAAC;SAC9B,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACP,KAAK,EAAE,CAAC,CAAC,KAAK;QACd,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,IAAI,EAAE,CAAC;QACP,MAAM,EAAE,IAAI;QACZ,KAAK,EAAE,CAAC,CAAC,KAAK;KACjB,CAAC,CAAC,CAAC;IAER,MAAM,eAAe,GAA4G,EAAE,CAAC;IACpI,IAAI,WAAW,GAAG,CAAC,CAAC;IACpB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACxC,MAAM,OAAO,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC;QAC7B,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;YACR,MAAM,IAAI,GAAG,SAAS,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YAC9B,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;gBAC3C,WAAW,GAAG,CAAC,GAAG,CAAC,CAAC;YACxB,CAAC;QACL,CAAC;QACD,eAAe,CAAC,IAAI,CAAC;YACjB,KAAK,EAAE,OAAO,CAAC,KAAK;YACpB,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,IAAI,EAAE,WAAW;YACjB,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,KAAK,EAAE,IAAI;SACd,CAAC,CAAC;IACP,CAAC;IAED,MAAM,aAAa,GAAG,CAAC,GAAG,eAAe,EAAE,GAAG,QAAQ,CAAC,CAAC;IAExD,MAAM,UAAU,GAAqB;QACjC,WAAW,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACrC,KAAK,EAAE,aAAa;KACvB,CAAC;IAEF,OAAO;QACH,OAAO,EAAE;YACL,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE;SACvE;KACJ,CAAC;AACN,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,MAAiB;IAClD,MAAM,CAAC,YAAY,CACf,eAAe,EACf;QACI,WAAW,EAAE,sIAAsI;QACnJ,WAAW;KACd,EACD,mBAAmB,CACtB,CAAC;AACN,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compare-repos.test.d.ts","sourceRoot":"","sources":["../../src/tools/compare-repos.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import { executeCompareRepos, inputSchema } from './compare-repos.js';
|
|
3
|
+
import { executeGetHealthScore } from './get-health-score.js';
|
|
4
|
+
vi.mock('./get-health-score.js');
|
|
5
|
+
describe('executeCompareRepos', () => {
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
vi.resetAllMocks();
|
|
8
|
+
});
|
|
9
|
+
it('should retrieve scores, rank repositories correctly including ties, and handle failures gracefully', async () => {
|
|
10
|
+
// Mock executeGetHealthScore behavior:
|
|
11
|
+
// repo-a: 85 score
|
|
12
|
+
// repo-b: 85 score (tie with repo-a)
|
|
13
|
+
// repo-c: 70 score (should be rank 3 in standard competition ranking)
|
|
14
|
+
// repo-fail: fails
|
|
15
|
+
vi.mocked(executeGetHealthScore).mockImplementation(async (repo) => {
|
|
16
|
+
if (repo.repo === 'repo-a') {
|
|
17
|
+
return {
|
|
18
|
+
content: [{
|
|
19
|
+
type: 'text',
|
|
20
|
+
text: JSON.stringify({
|
|
21
|
+
repo: 'test/repo-a',
|
|
22
|
+
score: 85,
|
|
23
|
+
grade: 'B',
|
|
24
|
+
breakdown: {},
|
|
25
|
+
suggestions: [],
|
|
26
|
+
checkedAt: '2023-10-01T00:00:00Z'
|
|
27
|
+
})
|
|
28
|
+
}]
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
if (repo.repo === 'repo-b') {
|
|
32
|
+
return {
|
|
33
|
+
content: [{
|
|
34
|
+
type: 'text',
|
|
35
|
+
text: JSON.stringify({
|
|
36
|
+
repo: 'test/repo-b',
|
|
37
|
+
score: 85,
|
|
38
|
+
grade: 'B',
|
|
39
|
+
breakdown: {},
|
|
40
|
+
suggestions: [],
|
|
41
|
+
checkedAt: '2023-10-01T00:00:00Z'
|
|
42
|
+
})
|
|
43
|
+
}]
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
if (repo.repo === 'repo-c') {
|
|
47
|
+
return {
|
|
48
|
+
content: [{
|
|
49
|
+
type: 'text',
|
|
50
|
+
text: JSON.stringify({
|
|
51
|
+
repo: 'test/repo-c',
|
|
52
|
+
score: 70,
|
|
53
|
+
grade: 'C',
|
|
54
|
+
breakdown: {},
|
|
55
|
+
suggestions: [],
|
|
56
|
+
checkedAt: '2023-10-01T00:00:00Z'
|
|
57
|
+
})
|
|
58
|
+
}]
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
throw new Error('API Rate Limit or Repository not found');
|
|
62
|
+
});
|
|
63
|
+
const repos = [
|
|
64
|
+
{ owner: 'test', repo: 'repo-a' },
|
|
65
|
+
{ owner: 'test', repo: 'repo-fail' },
|
|
66
|
+
{ owner: 'test', repo: 'repo-b' },
|
|
67
|
+
{ owner: 'test', repo: 'repo-c' }
|
|
68
|
+
];
|
|
69
|
+
const result = await executeCompareRepos({ repos });
|
|
70
|
+
expect(result.content).toHaveLength(1);
|
|
71
|
+
expect(result.content[0].type).toBe('text');
|
|
72
|
+
const comparison = JSON.parse(result.content[0].text);
|
|
73
|
+
expect(comparison.compared_at).toBeDefined();
|
|
74
|
+
expect(comparison.repos).toHaveLength(4);
|
|
75
|
+
// Verify sorting order: success ordered by score desc, then failures
|
|
76
|
+
expect(comparison.repos[0].repo).toMatch(/repo-a|repo-b/);
|
|
77
|
+
expect(comparison.repos[1].repo).toMatch(/repo-a|repo-b/);
|
|
78
|
+
expect(comparison.repos[2].repo).toBe('repo-c');
|
|
79
|
+
expect(comparison.repos[3].repo).toBe('repo-fail');
|
|
80
|
+
const repoA = comparison.repos.find((r) => r.repo === 'repo-a');
|
|
81
|
+
const repoB = comparison.repos.find((r) => r.repo === 'repo-b');
|
|
82
|
+
const repoC = comparison.repos.find((r) => r.repo === 'repo-c');
|
|
83
|
+
const repoFail = comparison.repos.find((r) => r.repo === 'repo-fail');
|
|
84
|
+
expect(repoA.rank).toBe(1);
|
|
85
|
+
expect(repoA.health.score).toBe(85);
|
|
86
|
+
expect(repoA.error).toBeNull();
|
|
87
|
+
expect(repoB.rank).toBe(1);
|
|
88
|
+
expect(repoB.health.score).toBe(85);
|
|
89
|
+
expect(repoB.error).toBeNull();
|
|
90
|
+
// repo-c should be rank 3 (since 2 repos share rank 1)
|
|
91
|
+
expect(repoC.rank).toBe(3);
|
|
92
|
+
expect(repoC.health.score).toBe(70);
|
|
93
|
+
expect(repoC.error).toBeNull();
|
|
94
|
+
// repo-fail should be rank 0 with error details
|
|
95
|
+
expect(repoFail.rank).toBe(0);
|
|
96
|
+
expect(repoFail.health).toBeNull();
|
|
97
|
+
expect(repoFail.error).toContain('API Rate Limit');
|
|
98
|
+
});
|
|
99
|
+
describe('inputSchema validation', () => {
|
|
100
|
+
it('should validate correctly with 2 to 5 repos', () => {
|
|
101
|
+
const valid = inputSchema.safeParse({
|
|
102
|
+
repos: [
|
|
103
|
+
{ owner: 'owner1', repo: 'repo1' },
|
|
104
|
+
{ owner: 'owner2', repo: 'repo2' }
|
|
105
|
+
]
|
|
106
|
+
});
|
|
107
|
+
expect(valid.success).toBe(true);
|
|
108
|
+
});
|
|
109
|
+
it('should fail validation with less than 2 repos', () => {
|
|
110
|
+
const invalid = inputSchema.safeParse({
|
|
111
|
+
repos: [
|
|
112
|
+
{ owner: 'owner1', repo: 'repo1' }
|
|
113
|
+
]
|
|
114
|
+
});
|
|
115
|
+
expect(invalid.success).toBe(false);
|
|
116
|
+
if (!invalid.success) {
|
|
117
|
+
expect(invalid.error.issues[0].message).toBe('At least 2 repositories are required');
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
it('should fail validation with more than 5 repos', () => {
|
|
121
|
+
const invalid = inputSchema.safeParse({
|
|
122
|
+
repos: [
|
|
123
|
+
{ owner: 'owner1', repo: 'repo1' },
|
|
124
|
+
{ owner: 'owner2', repo: 'repo2' },
|
|
125
|
+
{ owner: 'owner3', repo: 'repo3' },
|
|
126
|
+
{ owner: 'owner4', repo: 'repo4' },
|
|
127
|
+
{ owner: 'owner5', repo: 'repo5' },
|
|
128
|
+
{ owner: 'owner6', repo: 'repo6' }
|
|
129
|
+
]
|
|
130
|
+
});
|
|
131
|
+
expect(invalid.success).toBe(false);
|
|
132
|
+
if (!invalid.success) {
|
|
133
|
+
expect(invalid.error.issues[0].message).toBe('At most 5 repositories are allowed');
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
//# sourceMappingURL=compare-repos.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compare-repos.test.js","sourceRoot":"","sources":["../../src/tools/compare-repos.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
export declare function executeGetDoraMetrics({ owner, repo, days }: {
|
|
3
|
+
owner: string;
|
|
4
|
+
repo: string;
|
|
5
|
+
days: number;
|
|
6
|
+
}): Promise<{
|
|
7
|
+
content: {
|
|
8
|
+
type: "text";
|
|
9
|
+
text: string;
|
|
10
|
+
}[];
|
|
11
|
+
}>;
|
|
12
|
+
export declare function registerGetDoraMetrics(server: McpServer): void;
|
|
13
|
+
//# sourceMappingURL=get-dora-metrics.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-dora-metrics.d.ts","sourceRoot":"","sources":["../../src/tools/get-dora-metrics.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAQpE,wBAAsB,qBAAqB,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE;;;;;GAe/G;AAED,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAS9D"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { listMergedPulls, listReleases, listWorkflowRuns } from "../github/client.js";
|
|
3
|
+
import { calculateDoraMetrics } from "../analytics/dora.js";
|
|
4
|
+
const inputSchema = z.object({
|
|
5
|
+
owner: z.string().describe("GitHub repository owner"),
|
|
6
|
+
repo: z.string().describe("GitHub repository name"),
|
|
7
|
+
days: z.number().min(7).max(90).default(30).describe("Number of past days to analyze (default: 30)")
|
|
8
|
+
});
|
|
9
|
+
export async function executeGetDoraMetrics({ owner, repo, days }) {
|
|
10
|
+
const since = new Date(Date.now() - days * 24 * 60 * 60 * 1000);
|
|
11
|
+
const [releases, runs, prs] = await Promise.all([
|
|
12
|
+
listReleases(owner, repo, since),
|
|
13
|
+
listWorkflowRuns(owner, repo, since),
|
|
14
|
+
listMergedPulls(owner, repo, since)
|
|
15
|
+
]);
|
|
16
|
+
const metrics = calculateDoraMetrics({ owner, repo, period_days: days, releases, workflowRuns: runs, mergedPulls: prs });
|
|
17
|
+
return {
|
|
18
|
+
content: [
|
|
19
|
+
{ type: "text", text: JSON.stringify(metrics, null, 2) }
|
|
20
|
+
]
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
export function registerGetDoraMetrics(server) {
|
|
24
|
+
server.registerTool("get_dora_metrics", {
|
|
25
|
+
description: "Calculate DORA metrics (deployment frequency, lead time, change failure rate, and MTTR) for a GitHub repository",
|
|
26
|
+
inputSchema,
|
|
27
|
+
}, executeGetDoraMetrics);
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=get-dora-metrics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-dora-metrics.js","sourceRoot":"","sources":["../../src/tools/get-dora-metrics.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACtF,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAG5D,MAAM,WAAW,GAAG,CAAC,CAAC,MAAM,CAAC;IACzB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,yBAAyB,CAAC;IACrD,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,wBAAwB,CAAC;IACnD,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,8CAA8C,CAAC;CACvG,CAAC,CAAC;AAEH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAiD;IAC5G,MAAM,KAAK,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;IAChE,MAAM,CAAC,QAAQ,EAAE,IAAI,EAAE,GAAG,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;QAC5C,YAAY,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,CAAC;QAChC,gBAAgB,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,CAAC;QACpC,eAAe,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,CAAC;KACtC,CAAC,CAAA;IAEF,MAAM,OAAO,GAAG,oBAAoB,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,QAAQ,EAAE,YAAY,EAAE,IAAI,EAAE,WAAW,EAAE,GAAG,EAAE,CAAC,CAAA;IAExH,OAAO;QACH,OAAO,EAAE;YACL,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE;SACpE;KACJ,CAAA;AACL,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,MAAiB;IACpD,MAAM,CAAC,YAAY,CACf,kBAAkB,EAClB;QACI,WAAW,EAAE,iHAAiH;QAC9H,WAAW;KACd,EACD,qBAAqB,CACxB,CAAC;AACN,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-dora-metrics.test.d.ts","sourceRoot":"","sources":["../../src/tools/get-dora-metrics.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import { listMergedPulls, listReleases, listWorkflowRuns } from '../github/client.js';
|
|
3
|
+
import { executeGetDoraMetrics } from './get-dora-metrics.js';
|
|
4
|
+
vi.mock('../github/client.js');
|
|
5
|
+
describe('executeGetDoraMetrics', () => {
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
vi.resetAllMocks();
|
|
8
|
+
});
|
|
9
|
+
it('should fetch data and return metrics as JSON text', async () => {
|
|
10
|
+
// mock return values for the client functions
|
|
11
|
+
vi.mocked(listReleases).mockResolvedValue([
|
|
12
|
+
{ id: 1, tag_name: 'v1.0', published_at: '2026-08-01T00:00:00Z' }
|
|
13
|
+
]);
|
|
14
|
+
vi.mocked(listWorkflowRuns).mockResolvedValue([
|
|
15
|
+
{ id: 1, name: 'CI', head_branch: 'main', conclusion: 'success', created_at: '2026-08-01T00:00:00Z' }
|
|
16
|
+
]);
|
|
17
|
+
vi.mocked(listMergedPulls).mockResolvedValue([
|
|
18
|
+
{ number: 1, created_at: '2026-08-01T00:00:00Z', merged_at: '2026-08-02T00:00:00Z' }
|
|
19
|
+
]);
|
|
20
|
+
const result = await executeGetDoraMetrics({ owner: 'testowner', repo: 'testrepo', days: 14 });
|
|
21
|
+
expect(listReleases).toHaveBeenCalledWith('testowner', 'testrepo', expect.any(Date));
|
|
22
|
+
expect(listWorkflowRuns).toHaveBeenCalledWith('testowner', 'testrepo', expect.any(Date));
|
|
23
|
+
expect(listMergedPulls).toHaveBeenCalledWith('testowner', 'testrepo', expect.any(Date));
|
|
24
|
+
expect(result.content).toHaveLength(1);
|
|
25
|
+
expect(result.content[0].type).toBe('text');
|
|
26
|
+
const parsedText = JSON.parse(result.content[0].text);
|
|
27
|
+
expect(parsedText.owner).toBe('testowner');
|
|
28
|
+
expect(parsedText.repo).toBe('testrepo');
|
|
29
|
+
expect(parsedText.period_days).toBe(14);
|
|
30
|
+
// Assert some metric outputs based on the mocked input
|
|
31
|
+
expect(parsedText.deployment_frequency.releases_count).toBe(1);
|
|
32
|
+
expect(parsedText.lead_time.pr_count).toBe(1);
|
|
33
|
+
expect(parsedText.change_failure_rate.total_runs).toBe(1);
|
|
34
|
+
expect(parsedText.change_failure_rate.failed_runs).toBe(0);
|
|
35
|
+
});
|
|
36
|
+
it('should handle empty data correctly (zero releases, PRs, runs)', async () => {
|
|
37
|
+
vi.mocked(listReleases).mockResolvedValue([]);
|
|
38
|
+
vi.mocked(listWorkflowRuns).mockResolvedValue([]);
|
|
39
|
+
vi.mocked(listMergedPulls).mockResolvedValue([]);
|
|
40
|
+
const result = await executeGetDoraMetrics({ owner: 'testowner', repo: 'testrepo', days: 30 });
|
|
41
|
+
const parsedText = JSON.parse(result.content[0].text);
|
|
42
|
+
expect(parsedText.deployment_frequency.releases_count).toBe(0);
|
|
43
|
+
expect(parsedText.deployment_frequency.per_week).toBeNull();
|
|
44
|
+
expect(parsedText.lead_time.pr_count).toBe(0);
|
|
45
|
+
expect(parsedText.lead_time.median_hours).toBeNull();
|
|
46
|
+
expect(parsedText.change_failure_rate.total_runs).toBe(0);
|
|
47
|
+
expect(parsedText.change_failure_rate.rate_percent).toBeNull();
|
|
48
|
+
expect(parsedText.mttr.incidents_count).toBe(0);
|
|
49
|
+
expect(parsedText.mttr.median_hours).toBeNull();
|
|
50
|
+
});
|
|
51
|
+
it('should pass the correct since Date to client functions', async () => {
|
|
52
|
+
vi.mocked(listReleases).mockResolvedValue([]);
|
|
53
|
+
vi.mocked(listWorkflowRuns).mockResolvedValue([]);
|
|
54
|
+
vi.mocked(listMergedPulls).mockResolvedValue([]);
|
|
55
|
+
const days = 14;
|
|
56
|
+
await executeGetDoraMetrics({ owner: 'testowner', repo: 'testrepo', days });
|
|
57
|
+
const expectedSince = new Date(Date.now() - days * 24 * 60 * 60 * 1000);
|
|
58
|
+
const callArg = vi.mocked(listReleases).mock.calls[0][2];
|
|
59
|
+
const diffMs = Math.abs(callArg.getTime() - expectedSince.getTime());
|
|
60
|
+
expect(diffMs).toBeLessThan(1000); // Tolleranza di 1 secondo per l'esecuzione
|
|
61
|
+
});
|
|
62
|
+
it('should fail if any client call fails', async () => {
|
|
63
|
+
vi.mocked(listReleases).mockRejectedValue(new Error('API Error'));
|
|
64
|
+
vi.mocked(listWorkflowRuns).mockResolvedValue([]);
|
|
65
|
+
vi.mocked(listMergedPulls).mockResolvedValue([]);
|
|
66
|
+
await expect(executeGetDoraMetrics({ owner: 'testowner', repo: 'testrepo', days: 30 }))
|
|
67
|
+
.rejects
|
|
68
|
+
.toThrow('API Error');
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
//# sourceMappingURL=get-dora-metrics.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-dora-metrics.test.js","sourceRoot":"","sources":["../../src/tools/get-dora-metrics.test.ts"],"names":[],"mappings":"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"}
|
package/dist/types/health.d.ts
CHANGED
|
@@ -12,5 +12,15 @@ type HealthReport = {
|
|
|
12
12
|
suggestions: string[];
|
|
13
13
|
checkedAt: string;
|
|
14
14
|
};
|
|
15
|
-
|
|
15
|
+
type ComparisonResult = {
|
|
16
|
+
compared_at: string;
|
|
17
|
+
repos: Array<{
|
|
18
|
+
owner: string;
|
|
19
|
+
repo: string;
|
|
20
|
+
rank: number;
|
|
21
|
+
health: HealthReport | null;
|
|
22
|
+
error: string | null;
|
|
23
|
+
}>;
|
|
24
|
+
};
|
|
25
|
+
export { Grade, CategoryScore, HealthReport, ComparisonResult };
|
|
16
26
|
//# sourceMappingURL=health.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"health.d.ts","sourceRoot":"","sources":["../../src/types/health.ts"],"names":[],"mappings":"AACA,KAAK,KAAK,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;AAGzC,KAAK,aAAa,GAAG;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB,CAAA;AAGD,KAAK,YAAY,GAAG;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,KAAK,CAAC;IACb,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IACzC,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAA;AAED,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,CAAC"}
|
|
1
|
+
{"version":3,"file":"health.d.ts","sourceRoot":"","sources":["../../src/types/health.ts"],"names":[],"mappings":"AACA,KAAK,KAAK,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;AAGzC,KAAK,aAAa,GAAG;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB,CAAA;AAGD,KAAK,YAAY,GAAG;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,KAAK,CAAC;IACb,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IACzC,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAA;AAED,KAAK,gBAAgB,GAAG;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,KAAK,CAAC;QACX,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,YAAY,GAAG,IAAI,CAAC;QAC5B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;KACtB,CAAC,CAAC;CAEJ,CAAA;AAED,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,gBAAgB,EAAE,CAAC"}
|