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.
Files changed (38) hide show
  1. package/README.md +111 -64
  2. package/dist/analytics/dora.d.ts +3 -0
  3. package/dist/analytics/dora.d.ts.map +1 -0
  4. package/dist/analytics/dora.js +77 -0
  5. package/dist/analytics/dora.js.map +1 -0
  6. package/dist/analytics/dora.test.d.ts +2 -0
  7. package/dist/analytics/dora.test.d.ts.map +1 -0
  8. package/dist/analytics/dora.test.js +164 -0
  9. package/dist/analytics/dora.test.js.map +1 -0
  10. package/dist/analytics/types.d.ts +50 -0
  11. package/dist/analytics/types.d.ts.map +1 -0
  12. package/dist/analytics/types.js +2 -0
  13. package/dist/analytics/types.js.map +1 -0
  14. package/dist/github/client.d.ts +4 -0
  15. package/dist/github/client.d.ts.map +1 -1
  16. package/dist/github/client.js +50 -0
  17. package/dist/github/client.js.map +1 -1
  18. package/dist/index.js +4 -0
  19. package/dist/index.js.map +1 -1
  20. package/dist/tools/compare-repos.d.ts +21 -0
  21. package/dist/tools/compare-repos.d.ts.map +1 -0
  22. package/dist/tools/compare-repos.js +81 -0
  23. package/dist/tools/compare-repos.js.map +1 -0
  24. package/dist/tools/compare-repos.test.d.ts +2 -0
  25. package/dist/tools/compare-repos.test.d.ts.map +1 -0
  26. package/dist/tools/compare-repos.test.js +138 -0
  27. package/dist/tools/compare-repos.test.js.map +1 -0
  28. package/dist/tools/get-dora-metrics.d.ts +13 -0
  29. package/dist/tools/get-dora-metrics.d.ts.map +1 -0
  30. package/dist/tools/get-dora-metrics.js +29 -0
  31. package/dist/tools/get-dora-metrics.js.map +1 -0
  32. package/dist/tools/get-dora-metrics.test.d.ts +2 -0
  33. package/dist/tools/get-dora-metrics.test.d.ts.map +1 -0
  34. package/dist/tools/get-dora-metrics.test.js +71 -0
  35. package/dist/tools/get-dora-metrics.test.js.map +1 -0
  36. package/dist/types/health.d.ts +11 -1
  37. package/dist/types/health.d.ts.map +1 -1
  38. package/package.json +1 -1
package/README.md CHANGED
@@ -1,105 +1,152 @@
1
- # projectpulse-mcp
1
+ <div id="top" align="center">
2
2
 
3
- GitHub repository health monitoring via MCP.
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
- This Model Context Protocol (MCP) server allows AI assistants to analyze the health, security, and maintenance status of GitHub repositories directly from your conversations.
5
+ # ⚡ ProjectPulse MCP
6
6
 
7
- ## Features
7
+ **Monitor, score, and compare GitHub repository health directly from your AI agent conversations.**
8
8
 
9
- - **Repository Health Score**: Calculates a comprehensive 0-100 score based on CI status, freshness, security, and community metrics.
10
- - **Basic Repository Info**: Fetches general metadata like stars, forks, language, and license.
11
- - **Dependency Analysis**: Lists Dependabot alerts with severity filtering.
12
- - **CI/CD Status**: Checks recent GitHub Actions workflow runs and their conclusions.
13
- - **Code Scanning**: Retrieves CodeQL and other code scanning alerts.
14
- - **Ping**: Simple connectivity test tool.
9
+ [![npm version](https://img.shields.io/npm/v/projectpulse-mcp.svg?style=for-the-badge&color=6A0DAD&logo=npm&logoColor=white)](https://www.npmjs.com/package/projectpulse-mcp)
10
+ [![License: MIT](https://img.shields.io/badge/License-MIT-6A0DAD.svg?style=for-the-badge&logo=opensourceinitiative&logoColor=white)](https://opensource.org/licenses/MIT)
11
+ [![TypeScript](https://img.shields.io/badge/TypeScript-007ACC.svg?style=for-the-badge&logo=typescript&logoColor=white)](https://www.typescriptlang.org/)
12
+ [![Model Context Protocol](https://img.shields.io/badge/MCP-Protocol-6A0DAD.svg?style=for-the-badge&logo=anthropic&logoColor=white)](https://modelcontextprotocol.io)
13
+ [![Vitest](https://img.shields.io/badge/Tests-58%20Passed-2EA44F.svg?style=for-the-badge&logo=vitest&logoColor=white)](https://vitest.dev/)
15
14
 
16
- ### Example Output with tool Code Scanning
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
- ![Code Scanning Example](https://raw.githubusercontent.com/alexbypa/github-projectpulse-mcp/main/docs/images/code-scanning-example.png)
24
+ ---
19
25
 
20
- ## Quick Start
26
+ </div>
21
27
 
22
- ### Claude Code (CLI)
28
+ ## 🌟 Overview
23
29
 
24
- To use this MCP server with Claude Code, run the following command in your terminal:
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
- ```bash
27
- claude mcp add projectpulse -- npx projectpulse-mcp
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
- *Note: You must have a `.env` file with your `GITHUB_TOKEN` in the directory where you run Claude Code.*
77
+ ---
31
78
 
32
- ### Claude Desktop Configuration
79
+ ## 📦 Quick Start
33
80
 
34
- To use this with Claude Desktop, add the following configuration to your `claude_desktop_config.json` file (usually located at `%APPDATA%\Claude\claude_desktop_config.json` on Windows or `~/Library/Application Support/Claude/claude_desktop_config.json` on macOS):
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": "your-github-personal-access-token"
94
+ "GITHUB_TOKEN": "ghp_your_github_personal_access_token"
47
95
  }
48
96
  }
49
97
  }
50
98
  }
51
99
  ```
52
100
 
53
- ### Other MCP Clients (Cursor, Windsurf, etc.)
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
- ## Tools
103
+ ```bash
104
+ claude mcp add projectpulse -- npx projectpulse-mcp
105
+ ```
63
106
 
64
- | Tool Name | Description | Input Schema |
65
- | --- | --- | --- |
66
- | `get_health_score` | Calculate a 0-100 health score for a GitHub repository with grade (A-F), category breakdown, and improvement suggestions. | `owner` (string), `repo` (string) |
67
- | `get_repo_health` | Get health information for a GitHub repository (stars, issues, language, license, etc.). | `owner` (string), `repo` (string) |
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
- ## Configuration
112
+ ---
74
113
 
75
- ### GITHUB_TOKEN setup
114
+ ## 🔑 Configuration & GitHub Tokens
76
115
 
77
- To use the GitHub API without severe rate limits and to access protected data (like security alerts), you must provide a GitHub Personal Access Token (PAT).
116
+ To prevent GitHub API rate-limiting and allow reading security advisories (Dependabot/CodeQL), generate a GitHub Personal Access Token (PAT):
78
117
 
79
- **Option A: Fine-grained Personal Access Token (Recommended)**
80
- 1. Go to **Settings** > **Developer settings** > **Personal access tokens** > **Fine-grained tokens**.
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
- - `Metadata` (mandatory default)
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
- **Option B: Classic Token**
89
- Generate a classic token with at least the `repo` and `security_events` scopes.
130
+ ```bash
131
+ # Install dependencies
132
+ npm install
90
133
 
91
- #### Providing the token to the MCP server
134
+ # Run comprehensive test suite (58 unit tests)
135
+ npm test
92
136
 
93
- **For Claude Desktop:**
94
- Set the `GITHUB_TOKEN` environment variable in your `claude_desktop_config.json` as shown in the Quick Start section.
137
+ # Type-check TypeScript codebase
138
+ npm run lint
95
139
 
96
- **For Claude Code (CLI) or Local Testing:**
97
- Create a `.env` file in the root of this project (or your working directory) with the following content:
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
- ## License
144
+ ---
145
+
146
+ ## 📄 License
147
+
148
+ Distributed under the **MIT License**. See [`LICENSE`](./LICENSE) for more information.
104
149
 
105
- MIT License. See the `LICENSE` file for details.
150
+ <div align="center">
151
+ <sub>Built with ❤️ by <a href="https://github.com/alexbypa">Alessandro Chiodo</a></sub>
152
+ </div>
@@ -0,0 +1,3 @@
1
+ import { DoraInput, DoraMetrics } from './types.js';
2
+ export declare function calculateDoraMetrics(input: DoraInput): DoraMetrics;
3
+ //# sourceMappingURL=dora.d.ts.map
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=dora.test.d.ts.map
@@ -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":"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"}
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/analytics/types.ts"],"names":[],"mappings":""}
@@ -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;AAIxC,wBAAgB,UAAU,IAAI,OAAO,CAMpC"}
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"}
@@ -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;AAExC,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"}
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;AAErE,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;AAE/B,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"}
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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=compare-repos.test.d.ts.map
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=get-dora-metrics.test.d.ts.map
@@ -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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"}
@@ -12,5 +12,15 @@ type HealthReport = {
12
12
  suggestions: string[];
13
13
  checkedAt: string;
14
14
  };
15
- export { Grade, CategoryScore, HealthReport };
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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "projectpulse-mcp",
3
3
  "description": "MCP Server for GitHub repository health monitoring",
4
- "version": "1.2.1",
4
+ "version": "1.4.0",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
7
7
  "test": "vitest run",