projectpulse-mcp 1.6.14 → 1.8.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 +45 -0
- package/dist/analytics/best-practices.d.ts +34 -0
- package/dist/analytics/best-practices.d.ts.map +1 -0
- package/dist/analytics/best-practices.js +49 -0
- package/dist/analytics/best-practices.js.map +1 -0
- package/dist/github/client.d.ts +24 -0
- package/dist/github/client.d.ts.map +1 -1
- package/dist/github/client.js +43 -0
- package/dist/github/client.js.map +1 -1
- package/dist/index.js +104 -10
- package/dist/index.js.map +1 -1
- package/dist/tools/check-best-practices.d.ts +19 -0
- package/dist/tools/check-best-practices.d.ts.map +1 -0
- package/dist/tools/check-best-practices.js +104 -0
- package/dist/tools/check-best-practices.js.map +1 -0
- package/dist/types/best-practices.d.ts +21 -0
- package/dist/types/best-practices.d.ts.map +1 -0
- package/dist/types/best-practices.js +2 -0
- package/dist/types/best-practices.js.map +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
<a href="https://github.com/alexbypa/github-projectpulse-mcp/stargazers"><img src="https://img.shields.io/github/stars/alexbypa/github-projectpulse-mcp" alt="stars" /></a>
|
|
12
12
|
<a href="https://www.npmjs.com/package/projectpulse-mcp"><img src="https://img.shields.io/npm/dt/projectpulse-mcp" alt="downloads" /></a>
|
|
13
13
|
<a href="https://scorecard.dev/viewer/?uri=github.com/alexbypa/github-projectpulse-mcp"><img src="https://img.shields.io/ossf-scorecard/github.com/alexbypa/github-projectpulse-mcp?label=openssf+scorecard" alt="OpenSSF Scorecard" /></a>
|
|
14
|
+
<a href="https://m8ven.ai/badge/mcp/alexbypa/github-projectpulse-mcp"><img src="https://m8ven.ai/badge/mcp/alexbypa/github-projectpulse-mcp" alt="M8ven MCP" /></a>
|
|
14
15
|
</p>
|
|
15
16
|
|
|
16
17
|
---
|
|
@@ -26,6 +27,7 @@ This [Model Context Protocol](https://modelcontextprotocol.io/) (MCP) server giv
|
|
|
26
27
|
- 📦 **Dependency Analysis** — Dependabot alerts with severity filtering
|
|
27
28
|
- ⚙️ **CI/CD Status** — recent GitHub Actions workflow runs and conclusions
|
|
28
29
|
- 📋 **Repository Info** — stars, forks, language, license, and general metadata
|
|
30
|
+
- 📝 **Best Practices** — 12-point checklist for open-source repo quality (README, LICENSE, SECURITY.md, templates, branch protection, etc.) with A-F grade
|
|
29
31
|
|
|
30
32
|
## 📸 Examples
|
|
31
33
|
|
|
@@ -67,6 +69,12 @@ This [Model Context Protocol](https://modelcontextprotocol.io/) (MCP) server giv
|
|
|
67
69
|

|
|
68
70
|
</details>
|
|
69
71
|
|
|
72
|
+
<details>
|
|
73
|
+
<summary><strong>check_best_practices</strong> — Open-source repo quality checklist with A-F grade</summary>
|
|
74
|
+
|
|
75
|
+

|
|
76
|
+
</details>
|
|
77
|
+
|
|
70
78
|
## 💡 Use Cases
|
|
71
79
|
|
|
72
80
|
> Copy-paste these prompts into Claude, Cursor, Windsurf, or any MCP-compatible assistant.
|
|
@@ -214,6 +222,12 @@ Lists **CodeQL and other code scanning alerts**: rule ID, severity, vulnerabilit
|
|
|
214
222
|
**Inputs:** `owner`, `repo`, `trigger_scan` (optional, default `false`), `poll_timeout_seconds` (optional, default 300), `poll_interval_seconds` (optional, default 15)
|
|
215
223
|
**Try asking:** *"Are there any code scanning vulnerabilities in my-org/my-api?"*
|
|
216
224
|
|
|
225
|
+
### `check_best_practices`
|
|
226
|
+
Evaluates whether a repository follows **open-source community best practices**. Runs 12 weighted checks: README, LICENSE, SECURITY.md, CONTRIBUTING.md, CODE_OF_CONDUCT, issue/PR templates, branch protection, Dependabot config, CI/CD workflows, description, and topics. Returns a 0-100 weighted score with A-F grade, per-check pass/fail/unknown status, and actionable suggestions for every missing item. Branch protection check requires admin token access — returns "unknown" (not penalized) if unavailable.
|
|
227
|
+
|
|
228
|
+
**Inputs:** `owner`, `repo`
|
|
229
|
+
**Try asking:** *"Does my-org/my-repo follow open-source best practices?"*
|
|
230
|
+
|
|
217
231
|
### `ping`
|
|
218
232
|
Simple connectivity check. Returns "pong" with your message. Use to verify the MCP server is running.
|
|
219
233
|
|
|
@@ -221,6 +235,37 @@ Simple connectivity check. Returns "pong" with your message. Use to verify the M
|
|
|
221
235
|
|
|
222
236
|
## 🆕 What's New
|
|
223
237
|
|
|
238
|
+
### HTTP Transport (v1.7.0)
|
|
239
|
+
|
|
240
|
+
ProjectPulse now supports **Streamable HTTP transport** in addition to stdio. This enables running as a standalone HTTP server — ideal for Docker containers, remote deployments, and cross-runtime integrations (e.g., .NET clients consuming Node.js MCP tools over the network).
|
|
241
|
+
|
|
242
|
+
**Default behavior is unchanged** — `npx projectpulse-mcp` still uses stdio. To activate HTTP mode:
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
MCP_TRANSPORT=http MCP_PORT=3000 node dist/index.js
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
| Env Variable | Default | Description |
|
|
249
|
+
| --- | --- | --- |
|
|
250
|
+
| `MCP_TRANSPORT` | `stdio` | Transport mode: `stdio` or `http` |
|
|
251
|
+
| `MCP_PORT` | `3000` | HTTP server port (only used when `MCP_TRANSPORT=http`) |
|
|
252
|
+
|
|
253
|
+
**Docker example:**
|
|
254
|
+
|
|
255
|
+
```yaml
|
|
256
|
+
projectpulse:
|
|
257
|
+
image: node:22-slim
|
|
258
|
+
command: >-
|
|
259
|
+
sh -c "npm install -g projectpulse-mcp &&
|
|
260
|
+
node /usr/local/lib/node_modules/projectpulse-mcp/dist/index.js"
|
|
261
|
+
environment:
|
|
262
|
+
- MCP_TRANSPORT=http
|
|
263
|
+
- MCP_PORT=3000
|
|
264
|
+
- GITHUB_TOKEN=${GITHUB_TOKEN}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
Health check endpoint available at `GET /` (returns JSON with server name and version). MCP protocol endpoint at `POST /mcp`.
|
|
268
|
+
|
|
224
269
|
### OpenSSF Scorecard Integration
|
|
225
270
|
|
|
226
271
|
Security score now blends **Dependabot alerts** (60%) with **OpenSSF Scorecard** checks (40%) for a more complete picture. 12 security-relevant checks are evaluated — repos without a scorecard gracefully fall back to Dependabot-only scoring.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { BestPracticesReport } from '../types/best-practices.js';
|
|
2
|
+
type BestPracticesInput = {
|
|
3
|
+
owner: string;
|
|
4
|
+
repo: string;
|
|
5
|
+
repoData: {
|
|
6
|
+
description: string | null;
|
|
7
|
+
license: {
|
|
8
|
+
spdx_id: string;
|
|
9
|
+
} | null;
|
|
10
|
+
topics: string[];
|
|
11
|
+
default_branch: string;
|
|
12
|
+
};
|
|
13
|
+
communityProfile: {
|
|
14
|
+
files: {
|
|
15
|
+
code_of_conduct: unknown | null;
|
|
16
|
+
contributing: unknown | null;
|
|
17
|
+
security: unknown | null;
|
|
18
|
+
pull_request_template: unknown | null;
|
|
19
|
+
issue_template: unknown | null;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
readme: {
|
|
23
|
+
found: boolean;
|
|
24
|
+
size: number;
|
|
25
|
+
};
|
|
26
|
+
dependabotConfigured: boolean;
|
|
27
|
+
hasWorkflows: boolean;
|
|
28
|
+
branchProtection: {
|
|
29
|
+
protected: boolean | null;
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
export declare function analyzeBestPractices(input: BestPracticesInput): BestPracticesReport;
|
|
33
|
+
export type { BestPracticesInput };
|
|
34
|
+
//# sourceMappingURL=best-practices.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"best-practices.d.ts","sourceRoot":"","sources":["../../src/analytics/best-practices.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAqB,mBAAmB,EAAe,MAAM,4BAA4B,CAAC;AAItG,KAAK,kBAAkB,GAAG;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE;QACR,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;QAC3B,OAAO,EAAE;YAAE,OAAO,EAAE,MAAM,CAAA;SAAE,GAAG,IAAI,CAAC;QACpC,MAAM,EAAE,MAAM,EAAE,CAAC;QACjB,cAAc,EAAE,MAAM,CAAC;KACxB,CAAC;IACF,gBAAgB,EAAE;QAChB,KAAK,EAAE;YACL,eAAe,EAAE,OAAO,GAAG,IAAI,CAAC;YAChC,YAAY,EAAE,OAAO,GAAG,IAAI,CAAC;YAC7B,QAAQ,EAAE,OAAO,GAAG,IAAI,CAAC;YACzB,qBAAqB,EAAE,OAAO,GAAG,IAAI,CAAC;YACtC,cAAc,EAAE,OAAO,GAAG,IAAI,CAAC;SAChC,CAAC;KACH,CAAC;IACF,MAAM,EAAE;QAAE,KAAK,EAAE,OAAO,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACzC,oBAAoB,EAAE,OAAO,CAAC;IAC9B,YAAY,EAAE,OAAO,CAAC;IACtB,gBAAgB,EAAE;QAAE,SAAS,EAAE,OAAO,GAAG,IAAI,CAAA;KAAE,CAAC;CACjD,CAAC;AAMF,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,kBAAkB,GAAG,mBAAmB,CAuEnF;AAED,YAAY,EAAE,kBAAkB,EAAE,CAAC"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { scoreToGrade } from '../scoring/health-score.js';
|
|
2
|
+
function check(name, status, weight, detail) {
|
|
3
|
+
return { name, status, weight, detail };
|
|
4
|
+
}
|
|
5
|
+
export function analyzeBestPractices(input) {
|
|
6
|
+
const { repoData, communityProfile, readme, dependabotConfigured, hasWorkflows, branchProtection } = input;
|
|
7
|
+
const files = communityProfile.files;
|
|
8
|
+
const checks = [
|
|
9
|
+
// Alto (weight 3)
|
|
10
|
+
check('README', readme.found && readme.size > 0 ? 'pass' : 'fail', 3, readme.found ? (readme.size > 0 ? 'README present and not empty' : 'README exists but is empty') : 'No README found'),
|
|
11
|
+
check('LICENSE', repoData.license != null ? 'pass' : 'fail', 3, repoData.license ? `License: ${repoData.license.spdx_id}` : 'No license — code is not legally reusable'),
|
|
12
|
+
check('Security Policy', files.security != null ? 'pass' : 'fail', 3, files.security ? 'SECURITY.md or security policy present' : 'No security policy — vulnerability reporters have no contact channel'),
|
|
13
|
+
check('Branch Protection', branchProtection.protected == null ? 'unknown' : (branchProtection.protected ? 'pass' : 'fail'), 3, branchProtection.protected == null ? 'Cannot verify — requires admin access'
|
|
14
|
+
: (branchProtection.protected ? 'Default branch is protected' : 'Default branch has no protection — anyone can push directly')),
|
|
15
|
+
check('Dependabot', dependabotConfigured ? 'pass' : 'fail', 3, dependabotConfigured ? '.github/dependabot.yml configured' : 'No Dependabot config — dependency vulnerabilities not auto-detected'),
|
|
16
|
+
check('CI/CD', hasWorkflows ? 'pass' : 'fail', 3, hasWorkflows ? 'GitHub Actions workflows configured' : 'No CI/CD workflows found'),
|
|
17
|
+
// Medio (weight 2)
|
|
18
|
+
check('CONTRIBUTING', files.contributing != null ? 'pass' : 'fail', 2, files.contributing ? 'CONTRIBUTING.md present' : 'No contributing guide — new contributors lack onboarding'),
|
|
19
|
+
check('Issue Templates', files.issue_template != null ? 'pass' : 'fail', 2, files.issue_template ? 'Issue templates configured' : 'No issue templates — bug reports will lack structure'),
|
|
20
|
+
check('PR Template', files.pull_request_template != null ? 'pass' : 'fail', 2, files.pull_request_template ? 'Pull request template configured' : 'No PR template'),
|
|
21
|
+
check('Description', (repoData.description ?? '').length > 0 ? 'pass' : 'fail', 2, repoData.description ? 'Repository description set' : 'No description — repo is invisible in search'),
|
|
22
|
+
// Basso (weight 1)
|
|
23
|
+
check('Code of Conduct', files.code_of_conduct != null ? 'pass' : 'fail', 1, files.code_of_conduct ? 'Code of conduct present' : 'No code of conduct'),
|
|
24
|
+
check('Topics', repoData.topics.length > 0 ? 'pass' : 'fail', 1, repoData.topics.length > 0 ? `${repoData.topics.length} topics configured` : 'No topics — reduces discoverability'),
|
|
25
|
+
];
|
|
26
|
+
// Score pesato: solo pass e fail contano, unknown esclusi
|
|
27
|
+
const scorable = checks.filter(c => c.status !== 'unknown');
|
|
28
|
+
const totalWeight = scorable.reduce((sum, c) => sum + c.weight, 0);
|
|
29
|
+
const passedWeight = scorable.filter(c => c.status === 'pass').reduce((sum, c) => sum + c.weight, 0);
|
|
30
|
+
const score = totalWeight > 0 ? Math.round((passedWeight / totalWeight) * 100) : 0;
|
|
31
|
+
// Suggestions per i fail, ordinati per weight decrescente
|
|
32
|
+
const suggestions = checks
|
|
33
|
+
.filter(c => c.status === 'fail')
|
|
34
|
+
.sort((a, b) => b.weight - a.weight)
|
|
35
|
+
.map(c => `${c.name}: ${c.detail}`);
|
|
36
|
+
return {
|
|
37
|
+
repo: `${input.owner}/${input.repo}`,
|
|
38
|
+
score,
|
|
39
|
+
grade: scoreToGrade(score),
|
|
40
|
+
totalChecks: checks.length,
|
|
41
|
+
passed: checks.filter(c => c.status === 'pass').length,
|
|
42
|
+
failed: checks.filter(c => c.status === 'fail').length,
|
|
43
|
+
unknown: checks.filter(c => c.status === 'unknown').length,
|
|
44
|
+
checks,
|
|
45
|
+
suggestions,
|
|
46
|
+
checkedAt: new Date().toISOString(),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=best-practices.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"best-practices.js","sourceRoot":"","sources":["../../src/analytics/best-practices.ts"],"names":[],"mappings":"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"}
|
package/dist/github/client.d.ts
CHANGED
|
@@ -4,4 +4,28 @@ export declare function getOctokit(): Octokit;
|
|
|
4
4
|
export declare function listReleases(owner: string, repo: string, since: Date): Promise<Release[]>;
|
|
5
5
|
export declare function listMergedPulls(owner: string, repo: string, since: Date): Promise<PullRequest[]>;
|
|
6
6
|
export declare function listWorkflowRuns(owner: string, repo: string, since: Date): Promise<WorkflowRun[]>;
|
|
7
|
+
export declare function getCommunityProfile(owner: string, repo: string): Promise<{
|
|
8
|
+
health_percentage: number;
|
|
9
|
+
description: string | null;
|
|
10
|
+
documentation: string | null;
|
|
11
|
+
files: {
|
|
12
|
+
code_of_conduct: import("@octokit/openapi-types").components["schemas"]["nullable-code-of-conduct-simple"];
|
|
13
|
+
code_of_conduct_file: import("@octokit/openapi-types").components["schemas"]["nullable-community-health-file"];
|
|
14
|
+
license: import("@octokit/openapi-types").components["schemas"]["nullable-license-simple"];
|
|
15
|
+
contributing: import("@octokit/openapi-types").components["schemas"]["nullable-community-health-file"];
|
|
16
|
+
readme: import("@octokit/openapi-types").components["schemas"]["nullable-community-health-file"];
|
|
17
|
+
issue_template: import("@octokit/openapi-types").components["schemas"]["nullable-community-health-file"];
|
|
18
|
+
pull_request_template: import("@octokit/openapi-types").components["schemas"]["nullable-community-health-file"];
|
|
19
|
+
};
|
|
20
|
+
updated_at: string | null;
|
|
21
|
+
content_reports_enabled?: boolean;
|
|
22
|
+
}>;
|
|
23
|
+
export declare function getReadme(owner: string, repo: string): Promise<{
|
|
24
|
+
found: boolean;
|
|
25
|
+
size: number;
|
|
26
|
+
}>;
|
|
27
|
+
export declare function getFileExists(owner: string, repo: string, path: string): Promise<boolean>;
|
|
28
|
+
export declare function getBranchProtection(owner: string, repo: string, branch: string): Promise<{
|
|
29
|
+
protected: boolean | null;
|
|
30
|
+
}>;
|
|
7
31
|
//# 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;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"}
|
|
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;AAID,wBAAsB,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM;;;;;yBAwCk6pnC,sDAAsB;8BAAkE,sDAAsB;iBAAoD,sDAAsB;sBAAkD,sDAAsB;gBAAmD,sDAAsB;wBAA2D,sDAAsB;+BAAkE,sDAAsB;;;;GAjCx9qnC;AAED,wBAAsB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAQtG;AAED,wBAAsB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAQ/F;AAED,wBAAsB,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,SAAS,EAAE,OAAO,GAAG,IAAI,CAAA;CAAE,CAAC,CAW7H"}
|
package/dist/github/client.js
CHANGED
|
@@ -57,4 +57,47 @@ export async function listWorkflowRuns(owner, repo, since) {
|
|
|
57
57
|
created_at: r.created_at,
|
|
58
58
|
}));
|
|
59
59
|
}
|
|
60
|
+
// --- Best Practices ---
|
|
61
|
+
export async function getCommunityProfile(owner, repo) {
|
|
62
|
+
const octokit = getOctokit();
|
|
63
|
+
const { data } = await octokit.request('GET /repos/{owner}/{repo}/community/profile', {
|
|
64
|
+
owner,
|
|
65
|
+
repo,
|
|
66
|
+
});
|
|
67
|
+
return data;
|
|
68
|
+
}
|
|
69
|
+
export async function getReadme(owner, repo) {
|
|
70
|
+
const octokit = getOctokit();
|
|
71
|
+
try {
|
|
72
|
+
const { data } = await octokit.repos.getReadme({ owner, repo });
|
|
73
|
+
return { found: true, size: data.size };
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return { found: false, size: 0 };
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
export async function getFileExists(owner, repo, path) {
|
|
80
|
+
const octokit = getOctokit();
|
|
81
|
+
try {
|
|
82
|
+
await octokit.repos.getContent({ owner, repo, path });
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
export async function getBranchProtection(owner, repo, branch) {
|
|
90
|
+
const octokit = getOctokit();
|
|
91
|
+
try {
|
|
92
|
+
await octokit.repos.getBranchProtection({ owner, repo, branch });
|
|
93
|
+
return { protected: true };
|
|
94
|
+
}
|
|
95
|
+
catch (err) {
|
|
96
|
+
const status = err.status;
|
|
97
|
+
if (status === 404)
|
|
98
|
+
return { protected: false };
|
|
99
|
+
// 403 = no admin access, can't check
|
|
100
|
+
return { protected: null };
|
|
101
|
+
}
|
|
102
|
+
}
|
|
60
103
|
//# 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;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"}
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/github/client.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
4
|
+
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
|
|
5
|
+
import { createServer } from "node:http";
|
|
6
|
+
import { randomUUID } from "node:crypto";
|
|
3
7
|
import { registerCheckCiStatus } from "./tools/check-ci-status.js";
|
|
4
8
|
import { registerGetRepoHealth } from "./tools/get-repo-health.js";
|
|
5
9
|
import { registerAnalyzeDependencies } from "./tools/analyze-dependencies.js";
|
|
@@ -7,19 +11,109 @@ import { registerAnalyzeCodeScanning } from "./tools/analyze-code-scanning.js";
|
|
|
7
11
|
import { registerGetHealthScore } from "./tools/get-health-score.js";
|
|
8
12
|
import { registerGetDoraMetrics } from "./tools/get-dora-metrics.js";
|
|
9
13
|
import { registerCompareRepos } from "./tools/compare-repos.js";
|
|
14
|
+
import { registerCheckBestPractices } from "./tools/check-best-practices.js";
|
|
10
15
|
import { readFileSync } from "fs";
|
|
11
16
|
import { join, dirname } from "path";
|
|
12
17
|
import { fileURLToPath } from "url";
|
|
13
18
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
14
19
|
const pkg = JSON.parse(readFileSync(join(__dirname, "../package.json"), "utf-8"));
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
function createMcpServer() {
|
|
21
|
+
const server = new McpServer({ name: "projectpulse-mcp", version: pkg.version });
|
|
22
|
+
registerCheckCiStatus(server);
|
|
23
|
+
registerGetRepoHealth(server);
|
|
24
|
+
registerAnalyzeDependencies(server);
|
|
25
|
+
registerAnalyzeCodeScanning(server);
|
|
26
|
+
registerGetHealthScore(server);
|
|
27
|
+
registerGetDoraMetrics(server);
|
|
28
|
+
registerCompareRepos(server);
|
|
29
|
+
registerCheckBestPractices(server);
|
|
30
|
+
return server;
|
|
31
|
+
}
|
|
32
|
+
// --- Transport selection ---
|
|
33
|
+
// Default: stdio (backward compatible, per uso locale e npx)
|
|
34
|
+
// MCP_TRANSPORT=http: Streamable HTTP server (per Docker e remote clients)
|
|
35
|
+
const transportMode = (process.env.MCP_TRANSPORT ?? "stdio").toLowerCase();
|
|
36
|
+
if (transportMode === "http") {
|
|
37
|
+
const port = parseInt(process.env.MCP_PORT ?? "3000", 10);
|
|
38
|
+
const transports = new Map();
|
|
39
|
+
const httpServer = createServer(async (req, res) => {
|
|
40
|
+
const url = new URL(req.url ?? "/", `http://localhost:${port}`);
|
|
41
|
+
// Only handle /mcp path — MCP protocol endpoint
|
|
42
|
+
if (url.pathname !== "/mcp") {
|
|
43
|
+
// Health check on root
|
|
44
|
+
if (url.pathname === "/" && req.method === "GET") {
|
|
45
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
46
|
+
res.end(JSON.stringify({ status: "ok", server: "projectpulse-mcp", version: pkg.version }));
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
res.writeHead(404);
|
|
50
|
+
res.end("Not Found");
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
// Parse JSON body for POST requests
|
|
54
|
+
if (req.method === "POST") {
|
|
55
|
+
const chunks = [];
|
|
56
|
+
for await (const chunk of req)
|
|
57
|
+
chunks.push(chunk);
|
|
58
|
+
const body = JSON.parse(Buffer.concat(chunks).toString());
|
|
59
|
+
req.body = body;
|
|
60
|
+
const sessionId = req.headers["mcp-session-id"];
|
|
61
|
+
if (sessionId && transports.has(sessionId)) {
|
|
62
|
+
// Existing session — reuse transport
|
|
63
|
+
await transports.get(sessionId).handleRequest(req, res, body);
|
|
64
|
+
}
|
|
65
|
+
else if (!sessionId && isInitializeRequest(body)) {
|
|
66
|
+
// New session — create transport + server
|
|
67
|
+
const transport = new StreamableHTTPServerTransport({
|
|
68
|
+
sessionIdGenerator: () => randomUUID(),
|
|
69
|
+
onsessioninitialized: (sid) => {
|
|
70
|
+
transports.set(sid, transport);
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
transport.onclose = () => {
|
|
74
|
+
const sid = transport.sessionId;
|
|
75
|
+
if (sid)
|
|
76
|
+
transports.delete(sid);
|
|
77
|
+
};
|
|
78
|
+
const server = createMcpServer();
|
|
79
|
+
await server.connect(transport);
|
|
80
|
+
await transport.handleRequest(req, res, body);
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
84
|
+
res.end(JSON.stringify({ jsonrpc: "2.0", error: { code: -32000, message: "Bad Request: No valid session ID" }, id: null }));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
else if (req.method === "GET" || req.method === "DELETE") {
|
|
88
|
+
const sessionId = req.headers["mcp-session-id"];
|
|
89
|
+
if (!sessionId || !transports.has(sessionId)) {
|
|
90
|
+
res.writeHead(400);
|
|
91
|
+
res.end("Invalid or missing session ID");
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
await transports.get(sessionId).handleRequest(req, res);
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
res.writeHead(405);
|
|
98
|
+
res.end("Method Not Allowed");
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
httpServer.listen(port, "0.0.0.0", () => {
|
|
102
|
+
console.log(`ProjectPulse MCP server (HTTP) listening on http://0.0.0.0:${port}/mcp`);
|
|
103
|
+
});
|
|
104
|
+
process.on("SIGINT", async () => {
|
|
105
|
+
for (const [, transport] of transports) {
|
|
106
|
+
await transport.close();
|
|
107
|
+
}
|
|
108
|
+
transports.clear();
|
|
109
|
+
httpServer.close();
|
|
110
|
+
process.exit(0);
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
// Default: stdio transport (backward compatible)
|
|
115
|
+
const server = createMcpServer();
|
|
116
|
+
const transport = new StdioServerTransport();
|
|
117
|
+
await server.connect(transport);
|
|
118
|
+
}
|
|
25
119
|
//# sourceMappingURL=index.js.map
|
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,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
export declare function executeCheckBestPractices({ owner, repo }: {
|
|
3
|
+
owner: string;
|
|
4
|
+
repo: string;
|
|
5
|
+
}): Promise<{
|
|
6
|
+
content: {
|
|
7
|
+
type: "text";
|
|
8
|
+
text: string;
|
|
9
|
+
}[];
|
|
10
|
+
structuredContent: import("../types/best-practices.js").BestPracticesReport;
|
|
11
|
+
} | {
|
|
12
|
+
content: {
|
|
13
|
+
type: "text";
|
|
14
|
+
text: string;
|
|
15
|
+
}[];
|
|
16
|
+
structuredContent?: undefined;
|
|
17
|
+
}>;
|
|
18
|
+
export declare function registerCheckBestPractices(server: McpServer): void;
|
|
19
|
+
//# sourceMappingURL=check-best-practices.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"check-best-practices.d.ts","sourceRoot":"","sources":["../../src/tools/check-best-practices.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AA2BpE,wBAAsB,yBAAyB,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE;;;;;;;;;;;;GA+D/F;AAED,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAsBlE"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { getOctokit, getCommunityProfile, getReadme, getFileExists, getBranchProtection } from "../github/client.js";
|
|
3
|
+
import { analyzeBestPractices } from "../analytics/best-practices.js";
|
|
4
|
+
const inputSchema = z.object({
|
|
5
|
+
owner: z.string().describe("GitHub repository owner (e.g., 'facebook')"),
|
|
6
|
+
repo: z.string().describe("GitHub repository name (e.g., 'react')")
|
|
7
|
+
});
|
|
8
|
+
const outputSchema = z.object({
|
|
9
|
+
repo: z.string(),
|
|
10
|
+
score: z.number(),
|
|
11
|
+
grade: z.string(),
|
|
12
|
+
totalChecks: z.number(),
|
|
13
|
+
passed: z.number(),
|
|
14
|
+
failed: z.number(),
|
|
15
|
+
unknown: z.number(),
|
|
16
|
+
checks: z.array(z.object({
|
|
17
|
+
name: z.string(),
|
|
18
|
+
status: z.enum(['pass', 'fail', 'unknown']),
|
|
19
|
+
weight: z.number(),
|
|
20
|
+
detail: z.string()
|
|
21
|
+
})),
|
|
22
|
+
suggestions: z.array(z.string()),
|
|
23
|
+
checkedAt: z.string()
|
|
24
|
+
});
|
|
25
|
+
export async function executeCheckBestPractices({ owner, repo }) {
|
|
26
|
+
try {
|
|
27
|
+
const octokit = getOctokit();
|
|
28
|
+
// Fetch tutto in parallelo — 5 call, ~1 secondo totale
|
|
29
|
+
const [repoResponse, communityProfile, readme, dependabotConfigured, workflowsResponse] = await Promise.all([
|
|
30
|
+
octokit.repos.get({ owner, repo }),
|
|
31
|
+
getCommunityProfile(owner, repo),
|
|
32
|
+
getReadme(owner, repo),
|
|
33
|
+
getFileExists(owner, repo, '.github/dependabot.yml'),
|
|
34
|
+
octokit.actions.listWorkflowRunsForRepo({ owner, repo, per_page: 1 })
|
|
35
|
+
]);
|
|
36
|
+
const repoData = repoResponse.data;
|
|
37
|
+
// Branch protection separata — può fallire con 403
|
|
38
|
+
const branchProtection = await getBranchProtection(owner, repo, repoData.default_branch);
|
|
39
|
+
const result = analyzeBestPractices({
|
|
40
|
+
owner,
|
|
41
|
+
repo,
|
|
42
|
+
repoData: {
|
|
43
|
+
description: repoData.description,
|
|
44
|
+
license: repoData.license ? { spdx_id: repoData.license.spdx_id ?? 'Unknown' } : null,
|
|
45
|
+
topics: repoData.topics ?? [],
|
|
46
|
+
default_branch: repoData.default_branch,
|
|
47
|
+
},
|
|
48
|
+
communityProfile: {
|
|
49
|
+
files: {
|
|
50
|
+
code_of_conduct: communityProfile.files.code_of_conduct,
|
|
51
|
+
contributing: communityProfile.files.contributing,
|
|
52
|
+
security: communityProfile.files.security ?? communityProfile.files.security_advisories,
|
|
53
|
+
pull_request_template: communityProfile.files.pull_request_template,
|
|
54
|
+
issue_template: communityProfile.files.issue_template,
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
readme,
|
|
58
|
+
dependabotConfigured,
|
|
59
|
+
hasWorkflows: workflowsResponse.data.total_count > 0,
|
|
60
|
+
branchProtection,
|
|
61
|
+
});
|
|
62
|
+
return {
|
|
63
|
+
content: [{
|
|
64
|
+
type: "text",
|
|
65
|
+
text: JSON.stringify(result, null, 2),
|
|
66
|
+
}],
|
|
67
|
+
structuredContent: result
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
if (error.status === 404) {
|
|
72
|
+
return {
|
|
73
|
+
content: [{
|
|
74
|
+
type: "text",
|
|
75
|
+
text: JSON.stringify({
|
|
76
|
+
error: "Repository not found",
|
|
77
|
+
details: error.message
|
|
78
|
+
}, null, 2),
|
|
79
|
+
}]
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
throw error;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
export function registerCheckBestPractices(server) {
|
|
86
|
+
server.registerTool("check_best_practices", {
|
|
87
|
+
description: `Checks whether a GitHub repository follows open-source community best practices.
|
|
88
|
+
- Evaluates 12 checks: README, LICENSE, SECURITY.md, CONTRIBUTING.md, CODE_OF_CONDUCT, issue/PR templates, branch protection, Dependabot, CI/CD, description, and topics.
|
|
89
|
+
- Returns a weighted 0-100 score with A-F grade, per-check pass/fail/unknown status, and actionable suggestions for missing items.
|
|
90
|
+
- Side effects: None. Read-only operation.
|
|
91
|
+
- Rate limits: ~6 GitHub API calls per invocation.
|
|
92
|
+
- Branch protection check requires admin access — returns "unknown" (not penalized) if token lacks permission.
|
|
93
|
+
- Use this tool to audit repo quality and discoverability. For a computed health grade based on CI/security/activity, use 'get_health_score' instead.`,
|
|
94
|
+
inputSchema,
|
|
95
|
+
outputSchema,
|
|
96
|
+
annotations: {
|
|
97
|
+
readOnlyHint: true,
|
|
98
|
+
destructiveHint: false,
|
|
99
|
+
idempotentHint: true,
|
|
100
|
+
openWorldHint: true
|
|
101
|
+
}
|
|
102
|
+
}, executeCheckBestPractices);
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=check-best-practices.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"check-best-practices.js","sourceRoot":"","sources":["../../src/tools/check-best-practices.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
type CheckStatus = 'pass' | 'fail' | 'unknown';
|
|
2
|
+
type BestPracticeCheck = {
|
|
3
|
+
name: string;
|
|
4
|
+
status: CheckStatus;
|
|
5
|
+
weight: number;
|
|
6
|
+
detail: string;
|
|
7
|
+
};
|
|
8
|
+
type BestPracticesReport = {
|
|
9
|
+
repo: string;
|
|
10
|
+
score: number;
|
|
11
|
+
grade: string;
|
|
12
|
+
totalChecks: number;
|
|
13
|
+
passed: number;
|
|
14
|
+
failed: number;
|
|
15
|
+
unknown: number;
|
|
16
|
+
checks: BestPracticeCheck[];
|
|
17
|
+
suggestions: string[];
|
|
18
|
+
checkedAt: string;
|
|
19
|
+
};
|
|
20
|
+
export type { CheckStatus, BestPracticeCheck, BestPracticesReport };
|
|
21
|
+
//# sourceMappingURL=best-practices.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"best-practices.d.ts","sourceRoot":"","sources":["../../src/types/best-practices.ts"],"names":[],"mappings":"AACA,KAAK,WAAW,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;AAG/C,KAAK,iBAAiB,GAAG;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,WAAW,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB,CAAA;AAGD,KAAK,mBAAmB,GAAG;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,iBAAiB,EAAE,CAAC;IAC5B,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAA;AAED,YAAY,EAAE,WAAW,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"best-practices.js","sourceRoot":"","sources":["../../src/types/best-practices.ts"],"names":[],"mappings":""}
|
package/package.json
CHANGED