@ossintel/scoring 0.0.0 → 0.1.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/LICENSE +1 -1
- package/README.md +78 -21
- package/dist/index.d.mts +227 -9
- package/dist/index.d.ts +227 -9
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +15 -6
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
|
|
24
24
|
# @ossintel/scoring
|
|
25
25
|
|
|
26
|
-
Deterministic OSS scoring engine.
|
|
26
|
+
Deterministic OSS scoring engine with modular architecture.
|
|
27
27
|
|
|
28
28
|
## Purpose
|
|
29
29
|
|
|
@@ -31,33 +31,54 @@ Converts normalized repository and developer metrics into objective scores.
|
|
|
31
31
|
|
|
32
32
|
## Responsibilities
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
### Repository Scoring
|
|
35
35
|
|
|
36
|
-
- Overall
|
|
37
|
-
- Health Score
|
|
38
|
-
- Impact Score
|
|
39
|
-
- Activity Score
|
|
40
|
-
- Community Score
|
|
41
|
-
- Risk Score
|
|
36
|
+
- Health, Impact, Activity, Community, Risk, and Overall scores
|
|
42
37
|
|
|
43
|
-
|
|
38
|
+
### Identity Scoring (OSSIQ)
|
|
39
|
+
|
|
40
|
+
Four-pillar reputation engine:
|
|
44
41
|
|
|
45
|
-
|
|
42
|
+
- **Maintainer** — Repository health weighted by popularity (GitHub-only)
|
|
43
|
+
- **Contributor** — Quality-weighted external PR scoring
|
|
44
|
+
- **Organization** — Active org leadership evaluation
|
|
45
|
+
- **Influence** — Downstream reach (stars, forks + additive npm/SO bonuses)
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
### Capability Scoring
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
- **Package Publishing** (`calculatePublishingScore`) — npm downloads, active packages, verified publisher (extensible to NuGet, PyPI, crates.io, Go)
|
|
50
|
+
- **Knowledge Sharing** (`calculateKnowledgeScore`) — Stack Overflow reputation, answers, acceptance rate (extensible to Dev.to, Hashnode)
|
|
50
51
|
|
|
51
|
-
|
|
52
|
+
### Supporting Modules
|
|
52
53
|
|
|
53
|
-
|
|
54
|
+
- **Badges** — Achievement detection from cross-platform activity
|
|
55
|
+
- **Skills** — Topic expertise radar aggregating GitHub, npm, and Stack Overflow signals
|
|
56
|
+
- **Evidence & Factors** — Human-readable explanations for each pillar
|
|
54
57
|
|
|
55
|
-
##
|
|
58
|
+
## Philosophy
|
|
56
59
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
60
|
+
- Scores are **deterministic**: same inputs → same outputs
|
|
61
|
+
- Scores are **monotonic**: linking additional providers can only increase reputation
|
|
62
|
+
- GitHub defines the **core OSS reputation** (~80-85%)
|
|
63
|
+
- Additional providers contribute **additive capability bonuses**
|
|
64
|
+
- No AI participates in score calculation
|
|
65
|
+
|
|
66
|
+
## Module Architecture
|
|
67
|
+
|
|
68
|
+
```text
|
|
69
|
+
index.ts
|
|
70
|
+
├── repository-scoring.ts # Repo-level: health, impact, activity, community, risk
|
|
71
|
+
├── identity-scoring.ts # Orchestrator: computes overall OSSIQ score
|
|
72
|
+
├── maintainer-scoring.ts # Pillar: GitHub repo health weighted average
|
|
73
|
+
├── contributor-scoring.ts # Pillar: external PR quality weighting
|
|
74
|
+
├── organization-scoring.ts # Pillar: org leadership evaluation
|
|
75
|
+
├── influence-scoring.ts # Pillar: stars + forks + additive bonuses
|
|
76
|
+
├── publishing-scoring.ts # Capability: Package Publishing (npm)
|
|
77
|
+
├── knowledge-scoring.ts # Capability: Knowledge Sharing (Stack Overflow)
|
|
78
|
+
├── badges.ts # Achievement detection
|
|
79
|
+
├── skills.ts # Topic expertise aggregation
|
|
80
|
+
├── evidence.ts # Evidence & factors generation
|
|
81
|
+
└── topic-mappings.ts # Canonical topic → keyword mappings
|
|
61
82
|
```
|
|
62
83
|
|
|
63
84
|
## Non-goals
|
|
@@ -77,8 +98,44 @@ No external dependencies should affect score calculation.
|
|
|
77
98
|
|
|
78
99
|
## ✨ Why @ossintel/scoring?
|
|
79
100
|
|
|
80
|
-
-
|
|
81
|
-
-
|
|
101
|
+
- **100% Deterministic & Transparent**: Zero network requests or probabilistic AI models; calculations are mathematical and fully reproducible.
|
|
102
|
+
- **Modular Pillar Architecture**: Each scoring dimension (Maintainer, Contributor, Organization, Influence) is a self-contained module, easy to extend or override.
|
|
103
|
+
- **Capability-Specific Scoring**: First-class `calculatePublishingScore` and `calculateKnowledgeScore` exports for direct use by consumers.
|
|
104
|
+
- **Monotonic Guarantees**: Linking npm or Stack Overflow can only increase scores, never reduce them.
|
|
105
|
+
- **Fully Tested**: Every score calculation is validated by comprehensive unit tests to prevent regression.
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## 🌍 Open Source Ecosystem Impact Engine
|
|
110
|
+
|
|
111
|
+
The Ecosystem Impact Engine measures developer impact beyond owned repositories, focusing on contributions to external projects.
|
|
112
|
+
|
|
113
|
+
### Heuristics Classification Pipeline
|
|
114
|
+
|
|
115
|
+
```text
|
|
116
|
+
[User Merged PR]
|
|
117
|
+
│
|
|
118
|
+
├──► Contains "typo", "readme", "docs" in Title/Labels? ─────► [Docs / Typo Fix] (Weight: 0.4x)
|
|
119
|
+
├──► Contains "test", "spec", "qa" in Title/Labels? ────────► [Test Contribution] (Weight: 1.2x)
|
|
120
|
+
├──► Contains "chore", "dependencies" in Title/Labels? ──────► [Chore / Dependency] (Weight: 0.5x)
|
|
121
|
+
└──► Default Code change ────────────────────────────────────► [Core Code Contribution] (Weight: 1.0x)
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Weighting Matrix
|
|
125
|
+
|
|
126
|
+
Merged pull requests are weighted according to target repository popularity and quality multipliers:
|
|
127
|
+
|
|
128
|
+
| Target Repo Stargazers | Tier Category | Base Weight | Multiplier Applied |
|
|
129
|
+
| ---------------------- | ----------------------- | ----------- | ---------------------------------------------------------------- |
|
|
130
|
+
| **≥ 20,000 Stars** | **Tier 1 (Frameworks)** | 5.0 | Quality Multiplier (Docs: 0.4, Test: 1.2, Chore: 0.5, Code: 1.0) |
|
|
131
|
+
| **≥ 2,000 Stars** | **Tier 2 (Medium OSS)** | 3.0 | Quality Multiplier |
|
|
132
|
+
| **< 2,000 Stars** | **Tier 3 (Small OSS)** | 1.0 | Quality Multiplier |
|
|
133
|
+
|
|
134
|
+
The resulting Ecosystem Contribution Score ($S_{\text{eco}}$) is aggregated as:
|
|
135
|
+
|
|
136
|
+
$$
|
|
137
|
+
S_{\text{eco}} = \min\left(100, \sum (\text{BaseWeight} \times \text{QualityMultiplier})\right)
|
|
138
|
+
$$
|
|
82
139
|
|
|
83
140
|
---
|
|
84
141
|
|
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,34 @@
|
|
|
1
|
-
import { NormalizedRepository, NormalizedContributor, NormalizedRelease, NormalizedLanguage } from '@ossintel/github-normalizer';
|
|
1
|
+
import { NormalizedContribution, NormalizedRepository, NormalizedContributor, NormalizedRelease, NormalizedLanguage } from '@ossintel/github-normalizer';
|
|
2
|
+
import { NormalizedNpmUser } from '@ossintel/npm';
|
|
3
|
+
import { NormalizedStackOverflowUser } from '@ossintel/stackoverflow';
|
|
4
|
+
|
|
5
|
+
interface BadgeInputs {
|
|
6
|
+
externalContributions: NormalizedContribution[];
|
|
7
|
+
activeOrgsCount: number;
|
|
8
|
+
totalStarsCount: number;
|
|
9
|
+
totalNpmDownloads: number;
|
|
10
|
+
npmUser?: NormalizedNpmUser | null;
|
|
11
|
+
stackoverflowUser?: NormalizedStackOverflowUser | null;
|
|
12
|
+
}
|
|
13
|
+
/** Compute achievement badges based on cross-platform activity. */
|
|
14
|
+
declare const calculateBadges: (inputs: BadgeInputs) => string[];
|
|
15
|
+
|
|
16
|
+
interface ContributorResult {
|
|
17
|
+
/** Final contributor score (0-100). */
|
|
18
|
+
score: number;
|
|
19
|
+
/** Per-repo breakdown of earned points. */
|
|
20
|
+
breakdown: Array<{
|
|
21
|
+
repo: string;
|
|
22
|
+
points: number;
|
|
23
|
+
}>;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Calculate the contributor pillar score from external (upstream) PRs.
|
|
27
|
+
*
|
|
28
|
+
* Quality-weighted scoring: PR type (code/docs/test/chore) and target
|
|
29
|
+
* repository importance (star count) determine point allocation.
|
|
30
|
+
*/
|
|
31
|
+
declare const calculateContributorScore: (externalContributions: NormalizedContribution[]) => ContributorResult;
|
|
2
32
|
|
|
3
33
|
interface ScoringInputs {
|
|
4
34
|
repository: NormalizedRepository;
|
|
@@ -14,23 +44,211 @@ interface RepositoryScores {
|
|
|
14
44
|
community: number;
|
|
15
45
|
risk: number;
|
|
16
46
|
}
|
|
17
|
-
interface
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
47
|
+
interface TopicExpertise {
|
|
48
|
+
topic: string;
|
|
49
|
+
score: number;
|
|
50
|
+
evidence: {
|
|
51
|
+
githubStars: number;
|
|
52
|
+
githubPrs: number;
|
|
53
|
+
npmDownloads: number;
|
|
54
|
+
npmPackages: number;
|
|
55
|
+
stackoverflowScore: number;
|
|
56
|
+
stackoverflowAnswers: number;
|
|
57
|
+
};
|
|
21
58
|
}
|
|
22
59
|
interface IdentityScoringInputs {
|
|
23
60
|
repositories: NormalizedRepository[];
|
|
24
|
-
|
|
61
|
+
npmUser?: NormalizedNpmUser | null;
|
|
62
|
+
stackoverflowUser?: NormalizedStackOverflowUser | null;
|
|
63
|
+
externalContributions?: NormalizedContribution[];
|
|
64
|
+
organizations?: {
|
|
65
|
+
login?: string;
|
|
66
|
+
publicRepos: number;
|
|
67
|
+
followers?: number;
|
|
68
|
+
stars?: number;
|
|
69
|
+
}[];
|
|
70
|
+
}
|
|
71
|
+
interface PillarEvidence {
|
|
72
|
+
maintainer: string[];
|
|
73
|
+
contributor: string[];
|
|
74
|
+
influence: string[];
|
|
75
|
+
organization: string[];
|
|
76
|
+
}
|
|
77
|
+
interface PillarFactors {
|
|
78
|
+
maintainer: string[];
|
|
79
|
+
contributor: string[];
|
|
80
|
+
influence: string[];
|
|
81
|
+
organization: string[];
|
|
82
|
+
}
|
|
83
|
+
interface IdentityScores {
|
|
84
|
+
overall: number;
|
|
85
|
+
maintainer: number;
|
|
86
|
+
contributor: number;
|
|
87
|
+
organization: number;
|
|
88
|
+
influence: number;
|
|
89
|
+
confidence: "High" | "Medium" | "Low";
|
|
90
|
+
evidence: PillarEvidence;
|
|
91
|
+
factors: PillarFactors;
|
|
92
|
+
badges: string[];
|
|
93
|
+
skills: TopicExpertise[];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
interface EvidenceInputs {
|
|
97
|
+
activeRepos: IdentityScoringInputs["repositories"];
|
|
98
|
+
allRepos: IdentityScoringInputs["repositories"];
|
|
99
|
+
externalContributions: NormalizedContribution[];
|
|
100
|
+
organizations: NonNullable<IdentityScoringInputs["organizations"]>;
|
|
101
|
+
contributorBreakdown: Array<{
|
|
102
|
+
repo: string;
|
|
103
|
+
points: number;
|
|
104
|
+
}>;
|
|
105
|
+
maintainerScore: number;
|
|
106
|
+
sustainedCount: number;
|
|
107
|
+
totalStarsCount: number;
|
|
108
|
+
totalForksCount: number;
|
|
109
|
+
totalNpmDownloads: number;
|
|
110
|
+
soReputation: number;
|
|
111
|
+
npmUser?: NormalizedNpmUser | null;
|
|
112
|
+
stackoverflowUser?: NormalizedStackOverflowUser | null;
|
|
113
|
+
}
|
|
114
|
+
/** Generate factual evidence lists for each identity pillar. */
|
|
115
|
+
declare const generateEvidence: (inputs: EvidenceInputs) => PillarEvidence;
|
|
116
|
+
/** Generate positive/negative factor descriptions for each identity pillar. */
|
|
117
|
+
declare const generateFactors: (inputs: EvidenceInputs) => PillarFactors;
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Calculate the unified OSS identity score.
|
|
121
|
+
*
|
|
122
|
+
* GitHub is the primary identity (~80-85% weight). npm (Package Publishing)
|
|
123
|
+
* and Stack Overflow (Knowledge Sharing) provide additive evidence bonuses
|
|
124
|
+
* that can never reduce the score.
|
|
125
|
+
*
|
|
126
|
+
* @see {@link file://docs/domain-model.md} for scoring philosophy.
|
|
127
|
+
*/
|
|
128
|
+
declare const calculateIdentityScore: (inputs: IdentityScoringInputs) => IdentityScores;
|
|
129
|
+
|
|
130
|
+
interface InfluenceResult {
|
|
131
|
+
/** Final influence score (0-100). */
|
|
132
|
+
score: number;
|
|
133
|
+
/** GitHub-only influence base. */
|
|
134
|
+
githubBase: number;
|
|
135
|
+
/** Additive npm bonus (0-15). */
|
|
136
|
+
npmBonus: number;
|
|
137
|
+
/** Additive Stack Overflow bonus (0-15). */
|
|
138
|
+
soBonus: number;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Calculate the influence pillar score.
|
|
142
|
+
*
|
|
143
|
+
* GitHub stars and forks form the base. npm downloads and
|
|
144
|
+
* Stack Overflow reputation provide bounded additive bonuses.
|
|
145
|
+
*/
|
|
146
|
+
declare const calculateInfluenceScore: (totalStarsCount: number, totalForksCount: number, totalNpmDownloads: number, soReputation: number) => InfluenceResult;
|
|
147
|
+
|
|
148
|
+
interface KnowledgeScore {
|
|
149
|
+
/** Overall knowledge sharing score (0-100). */
|
|
150
|
+
score: number;
|
|
151
|
+
/** Breakdown of sub-scores contributing to the overall. */
|
|
152
|
+
breakdown: {
|
|
153
|
+
/** Score based on reputation volume (0-100). */
|
|
154
|
+
reputation: number;
|
|
155
|
+
/** Score based on answer count (0-100). */
|
|
156
|
+
answers: number;
|
|
157
|
+
/** Score based on acceptance rate (0-100). */
|
|
158
|
+
acceptance: number;
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Calculate a Knowledge Sharing capability score.
|
|
163
|
+
*
|
|
164
|
+
* Currently evaluates Stack Overflow presence only. Designed to be
|
|
165
|
+
* extended with additional platforms (Dev.to, Hashnode, etc.)
|
|
166
|
+
* by adding optional parameters without breaking existing callers.
|
|
167
|
+
*
|
|
168
|
+
* @returns A score (0-100) with breakdown, or null if no data is available.
|
|
169
|
+
*/
|
|
170
|
+
declare const calculateKnowledgeScore: (stackoverflowUser?: NormalizedStackOverflowUser | null) => KnowledgeScore | null;
|
|
171
|
+
|
|
172
|
+
interface MaintainerResult {
|
|
173
|
+
/** Final maintainer score (0-100), including additive npm bonus. */
|
|
174
|
+
score: number;
|
|
175
|
+
/** GitHub-only base score before npm bonus. */
|
|
176
|
+
githubBase: number;
|
|
177
|
+
/** Additive npm bonus (0-10). */
|
|
178
|
+
npmBonus: number;
|
|
179
|
+
/** Number of repos with sustained long-term maintenance. */
|
|
180
|
+
sustainedCount: number;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Calculate the maintainer pillar score.
|
|
184
|
+
*
|
|
185
|
+
* GitHub repo health is the primary signal. npm download volume
|
|
186
|
+
* provides a small additive bonus (capped at 10 points) that can
|
|
187
|
+
* never reduce the score.
|
|
188
|
+
*/
|
|
189
|
+
declare const calculateMaintainerScore: (repositories: NormalizedRepository[], totalNpmDownloads: number) => MaintainerResult;
|
|
190
|
+
|
|
191
|
+
interface OrganizationResult {
|
|
192
|
+
/** Final organization leadership score (0-100). */
|
|
193
|
+
score: number;
|
|
194
|
+
/** Number of active organizations. */
|
|
195
|
+
activeCount: number;
|
|
25
196
|
}
|
|
26
|
-
|
|
197
|
+
/** Calculate the organization leadership pillar score. */
|
|
198
|
+
declare const calculateOrganizationScore: (organizations: NonNullable<IdentityScoringInputs["organizations"]>) => OrganizationResult;
|
|
27
199
|
|
|
200
|
+
interface PublishingScore {
|
|
201
|
+
/** Overall publishing reputation score (0-100). */
|
|
202
|
+
score: number;
|
|
203
|
+
/** Breakdown of sub-scores contributing to the overall. */
|
|
204
|
+
breakdown: {
|
|
205
|
+
/** Score based on total download volume (0-100). */
|
|
206
|
+
downloads: number;
|
|
207
|
+
/** Score based on active package count (0-100). */
|
|
208
|
+
packages: number;
|
|
209
|
+
/** Score based on verified publisher status (0 or 100). */
|
|
210
|
+
verified: number;
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Calculate a Package Publishing capability score.
|
|
215
|
+
*
|
|
216
|
+
* Currently evaluates npm presence only. Designed to be extended
|
|
217
|
+
* with additional registries (NuGet, PyPI, crates.io, Go, etc.)
|
|
218
|
+
* by adding optional parameters without breaking existing callers.
|
|
219
|
+
*
|
|
220
|
+
* @returns A score (0-100) with breakdown, or null if no data is available.
|
|
221
|
+
*/
|
|
222
|
+
declare const calculatePublishingScore: (npmUser?: NormalizedNpmUser | null) => PublishingScore | null;
|
|
223
|
+
|
|
224
|
+
/** Calculate the impact score based on stars, forks, and watchers. */
|
|
28
225
|
declare const calculateImpactScore: (repository: ScoringInputs["repository"]) => number;
|
|
226
|
+
/** Calculate the activity score based on push recency and release frequency. */
|
|
29
227
|
declare const calculateActivityScore: (repository: ScoringInputs["repository"], releases: ScoringInputs["releases"]) => number;
|
|
228
|
+
/** Calculate the community score from contributors, topics, and metadata. */
|
|
30
229
|
declare const calculateCommunityScore: (repository: ScoringInputs["repository"], contributors: ScoringInputs["contributors"]) => number;
|
|
230
|
+
/** Calculate repository health based on issue density, update recency, and fork status. */
|
|
31
231
|
declare const calculateHealthScore: (repository: ScoringInputs["repository"]) => number;
|
|
232
|
+
/** Calculate risk score based on staleness, contributor count, fork status, and issue density. */
|
|
32
233
|
declare const calculateRiskScore: (repository: ScoringInputs["repository"], contributors: ScoringInputs["contributors"]) => number;
|
|
234
|
+
/** Calculate aggregate repository scores across all dimensions. */
|
|
33
235
|
declare const calculateRepositoryScore: (inputs: ScoringInputs) => RepositoryScores;
|
|
34
|
-
declare const calculateIdentityScore: (inputs: IdentityScoringInputs) => IdentityScores;
|
|
35
236
|
|
|
36
|
-
|
|
237
|
+
interface SkillsInputs {
|
|
238
|
+
repositories: IdentityScoringInputs["repositories"];
|
|
239
|
+
externalContributions: NormalizedContribution[];
|
|
240
|
+
npmUser?: NormalizedNpmUser | null;
|
|
241
|
+
stackoverflowUser?: NormalizedStackOverflowUser | null;
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Compute topic expertise scores by aggregating signals across
|
|
245
|
+
* GitHub repos, PRs, npm packages, and Stack Overflow tags.
|
|
246
|
+
*/
|
|
247
|
+
declare const calculateSkills: (inputs: SkillsInputs) => TopicExpertise[];
|
|
248
|
+
|
|
249
|
+
/** Canonical topic-to-keyword mappings for skill classification. */
|
|
250
|
+
declare const TOPIC_MAPPINGS: Record<string, string[]>;
|
|
251
|
+
/** Match a raw topic/language string to a canonical category name. */
|
|
252
|
+
declare const matchTopic: (nameOrTopic: string) => string | null;
|
|
253
|
+
|
|
254
|
+
export { type ContributorResult, type IdentityScores, type IdentityScoringInputs, type InfluenceResult, type KnowledgeScore, type MaintainerResult, type OrganizationResult, type PillarEvidence, type PillarFactors, type PublishingScore, type RepositoryScores, type ScoringInputs, TOPIC_MAPPINGS, type TopicExpertise, calculateActivityScore, calculateBadges, calculateCommunityScore, calculateContributorScore, calculateHealthScore, calculateIdentityScore, calculateImpactScore, calculateInfluenceScore, calculateKnowledgeScore, calculateMaintainerScore, calculateOrganizationScore, calculatePublishingScore, calculateRepositoryScore, calculateRiskScore, calculateSkills, generateEvidence, generateFactors, matchTopic };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,34 @@
|
|
|
1
|
-
import { NormalizedRepository, NormalizedContributor, NormalizedRelease, NormalizedLanguage } from '@ossintel/github-normalizer';
|
|
1
|
+
import { NormalizedContribution, NormalizedRepository, NormalizedContributor, NormalizedRelease, NormalizedLanguage } from '@ossintel/github-normalizer';
|
|
2
|
+
import { NormalizedNpmUser } from '@ossintel/npm';
|
|
3
|
+
import { NormalizedStackOverflowUser } from '@ossintel/stackoverflow';
|
|
4
|
+
|
|
5
|
+
interface BadgeInputs {
|
|
6
|
+
externalContributions: NormalizedContribution[];
|
|
7
|
+
activeOrgsCount: number;
|
|
8
|
+
totalStarsCount: number;
|
|
9
|
+
totalNpmDownloads: number;
|
|
10
|
+
npmUser?: NormalizedNpmUser | null;
|
|
11
|
+
stackoverflowUser?: NormalizedStackOverflowUser | null;
|
|
12
|
+
}
|
|
13
|
+
/** Compute achievement badges based on cross-platform activity. */
|
|
14
|
+
declare const calculateBadges: (inputs: BadgeInputs) => string[];
|
|
15
|
+
|
|
16
|
+
interface ContributorResult {
|
|
17
|
+
/** Final contributor score (0-100). */
|
|
18
|
+
score: number;
|
|
19
|
+
/** Per-repo breakdown of earned points. */
|
|
20
|
+
breakdown: Array<{
|
|
21
|
+
repo: string;
|
|
22
|
+
points: number;
|
|
23
|
+
}>;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Calculate the contributor pillar score from external (upstream) PRs.
|
|
27
|
+
*
|
|
28
|
+
* Quality-weighted scoring: PR type (code/docs/test/chore) and target
|
|
29
|
+
* repository importance (star count) determine point allocation.
|
|
30
|
+
*/
|
|
31
|
+
declare const calculateContributorScore: (externalContributions: NormalizedContribution[]) => ContributorResult;
|
|
2
32
|
|
|
3
33
|
interface ScoringInputs {
|
|
4
34
|
repository: NormalizedRepository;
|
|
@@ -14,23 +44,211 @@ interface RepositoryScores {
|
|
|
14
44
|
community: number;
|
|
15
45
|
risk: number;
|
|
16
46
|
}
|
|
17
|
-
interface
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
47
|
+
interface TopicExpertise {
|
|
48
|
+
topic: string;
|
|
49
|
+
score: number;
|
|
50
|
+
evidence: {
|
|
51
|
+
githubStars: number;
|
|
52
|
+
githubPrs: number;
|
|
53
|
+
npmDownloads: number;
|
|
54
|
+
npmPackages: number;
|
|
55
|
+
stackoverflowScore: number;
|
|
56
|
+
stackoverflowAnswers: number;
|
|
57
|
+
};
|
|
21
58
|
}
|
|
22
59
|
interface IdentityScoringInputs {
|
|
23
60
|
repositories: NormalizedRepository[];
|
|
24
|
-
|
|
61
|
+
npmUser?: NormalizedNpmUser | null;
|
|
62
|
+
stackoverflowUser?: NormalizedStackOverflowUser | null;
|
|
63
|
+
externalContributions?: NormalizedContribution[];
|
|
64
|
+
organizations?: {
|
|
65
|
+
login?: string;
|
|
66
|
+
publicRepos: number;
|
|
67
|
+
followers?: number;
|
|
68
|
+
stars?: number;
|
|
69
|
+
}[];
|
|
70
|
+
}
|
|
71
|
+
interface PillarEvidence {
|
|
72
|
+
maintainer: string[];
|
|
73
|
+
contributor: string[];
|
|
74
|
+
influence: string[];
|
|
75
|
+
organization: string[];
|
|
76
|
+
}
|
|
77
|
+
interface PillarFactors {
|
|
78
|
+
maintainer: string[];
|
|
79
|
+
contributor: string[];
|
|
80
|
+
influence: string[];
|
|
81
|
+
organization: string[];
|
|
82
|
+
}
|
|
83
|
+
interface IdentityScores {
|
|
84
|
+
overall: number;
|
|
85
|
+
maintainer: number;
|
|
86
|
+
contributor: number;
|
|
87
|
+
organization: number;
|
|
88
|
+
influence: number;
|
|
89
|
+
confidence: "High" | "Medium" | "Low";
|
|
90
|
+
evidence: PillarEvidence;
|
|
91
|
+
factors: PillarFactors;
|
|
92
|
+
badges: string[];
|
|
93
|
+
skills: TopicExpertise[];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
interface EvidenceInputs {
|
|
97
|
+
activeRepos: IdentityScoringInputs["repositories"];
|
|
98
|
+
allRepos: IdentityScoringInputs["repositories"];
|
|
99
|
+
externalContributions: NormalizedContribution[];
|
|
100
|
+
organizations: NonNullable<IdentityScoringInputs["organizations"]>;
|
|
101
|
+
contributorBreakdown: Array<{
|
|
102
|
+
repo: string;
|
|
103
|
+
points: number;
|
|
104
|
+
}>;
|
|
105
|
+
maintainerScore: number;
|
|
106
|
+
sustainedCount: number;
|
|
107
|
+
totalStarsCount: number;
|
|
108
|
+
totalForksCount: number;
|
|
109
|
+
totalNpmDownloads: number;
|
|
110
|
+
soReputation: number;
|
|
111
|
+
npmUser?: NormalizedNpmUser | null;
|
|
112
|
+
stackoverflowUser?: NormalizedStackOverflowUser | null;
|
|
113
|
+
}
|
|
114
|
+
/** Generate factual evidence lists for each identity pillar. */
|
|
115
|
+
declare const generateEvidence: (inputs: EvidenceInputs) => PillarEvidence;
|
|
116
|
+
/** Generate positive/negative factor descriptions for each identity pillar. */
|
|
117
|
+
declare const generateFactors: (inputs: EvidenceInputs) => PillarFactors;
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Calculate the unified OSS identity score.
|
|
121
|
+
*
|
|
122
|
+
* GitHub is the primary identity (~80-85% weight). npm (Package Publishing)
|
|
123
|
+
* and Stack Overflow (Knowledge Sharing) provide additive evidence bonuses
|
|
124
|
+
* that can never reduce the score.
|
|
125
|
+
*
|
|
126
|
+
* @see {@link file://docs/domain-model.md} for scoring philosophy.
|
|
127
|
+
*/
|
|
128
|
+
declare const calculateIdentityScore: (inputs: IdentityScoringInputs) => IdentityScores;
|
|
129
|
+
|
|
130
|
+
interface InfluenceResult {
|
|
131
|
+
/** Final influence score (0-100). */
|
|
132
|
+
score: number;
|
|
133
|
+
/** GitHub-only influence base. */
|
|
134
|
+
githubBase: number;
|
|
135
|
+
/** Additive npm bonus (0-15). */
|
|
136
|
+
npmBonus: number;
|
|
137
|
+
/** Additive Stack Overflow bonus (0-15). */
|
|
138
|
+
soBonus: number;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Calculate the influence pillar score.
|
|
142
|
+
*
|
|
143
|
+
* GitHub stars and forks form the base. npm downloads and
|
|
144
|
+
* Stack Overflow reputation provide bounded additive bonuses.
|
|
145
|
+
*/
|
|
146
|
+
declare const calculateInfluenceScore: (totalStarsCount: number, totalForksCount: number, totalNpmDownloads: number, soReputation: number) => InfluenceResult;
|
|
147
|
+
|
|
148
|
+
interface KnowledgeScore {
|
|
149
|
+
/** Overall knowledge sharing score (0-100). */
|
|
150
|
+
score: number;
|
|
151
|
+
/** Breakdown of sub-scores contributing to the overall. */
|
|
152
|
+
breakdown: {
|
|
153
|
+
/** Score based on reputation volume (0-100). */
|
|
154
|
+
reputation: number;
|
|
155
|
+
/** Score based on answer count (0-100). */
|
|
156
|
+
answers: number;
|
|
157
|
+
/** Score based on acceptance rate (0-100). */
|
|
158
|
+
acceptance: number;
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Calculate a Knowledge Sharing capability score.
|
|
163
|
+
*
|
|
164
|
+
* Currently evaluates Stack Overflow presence only. Designed to be
|
|
165
|
+
* extended with additional platforms (Dev.to, Hashnode, etc.)
|
|
166
|
+
* by adding optional parameters without breaking existing callers.
|
|
167
|
+
*
|
|
168
|
+
* @returns A score (0-100) with breakdown, or null if no data is available.
|
|
169
|
+
*/
|
|
170
|
+
declare const calculateKnowledgeScore: (stackoverflowUser?: NormalizedStackOverflowUser | null) => KnowledgeScore | null;
|
|
171
|
+
|
|
172
|
+
interface MaintainerResult {
|
|
173
|
+
/** Final maintainer score (0-100), including additive npm bonus. */
|
|
174
|
+
score: number;
|
|
175
|
+
/** GitHub-only base score before npm bonus. */
|
|
176
|
+
githubBase: number;
|
|
177
|
+
/** Additive npm bonus (0-10). */
|
|
178
|
+
npmBonus: number;
|
|
179
|
+
/** Number of repos with sustained long-term maintenance. */
|
|
180
|
+
sustainedCount: number;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Calculate the maintainer pillar score.
|
|
184
|
+
*
|
|
185
|
+
* GitHub repo health is the primary signal. npm download volume
|
|
186
|
+
* provides a small additive bonus (capped at 10 points) that can
|
|
187
|
+
* never reduce the score.
|
|
188
|
+
*/
|
|
189
|
+
declare const calculateMaintainerScore: (repositories: NormalizedRepository[], totalNpmDownloads: number) => MaintainerResult;
|
|
190
|
+
|
|
191
|
+
interface OrganizationResult {
|
|
192
|
+
/** Final organization leadership score (0-100). */
|
|
193
|
+
score: number;
|
|
194
|
+
/** Number of active organizations. */
|
|
195
|
+
activeCount: number;
|
|
25
196
|
}
|
|
26
|
-
|
|
197
|
+
/** Calculate the organization leadership pillar score. */
|
|
198
|
+
declare const calculateOrganizationScore: (organizations: NonNullable<IdentityScoringInputs["organizations"]>) => OrganizationResult;
|
|
27
199
|
|
|
200
|
+
interface PublishingScore {
|
|
201
|
+
/** Overall publishing reputation score (0-100). */
|
|
202
|
+
score: number;
|
|
203
|
+
/** Breakdown of sub-scores contributing to the overall. */
|
|
204
|
+
breakdown: {
|
|
205
|
+
/** Score based on total download volume (0-100). */
|
|
206
|
+
downloads: number;
|
|
207
|
+
/** Score based on active package count (0-100). */
|
|
208
|
+
packages: number;
|
|
209
|
+
/** Score based on verified publisher status (0 or 100). */
|
|
210
|
+
verified: number;
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Calculate a Package Publishing capability score.
|
|
215
|
+
*
|
|
216
|
+
* Currently evaluates npm presence only. Designed to be extended
|
|
217
|
+
* with additional registries (NuGet, PyPI, crates.io, Go, etc.)
|
|
218
|
+
* by adding optional parameters without breaking existing callers.
|
|
219
|
+
*
|
|
220
|
+
* @returns A score (0-100) with breakdown, or null if no data is available.
|
|
221
|
+
*/
|
|
222
|
+
declare const calculatePublishingScore: (npmUser?: NormalizedNpmUser | null) => PublishingScore | null;
|
|
223
|
+
|
|
224
|
+
/** Calculate the impact score based on stars, forks, and watchers. */
|
|
28
225
|
declare const calculateImpactScore: (repository: ScoringInputs["repository"]) => number;
|
|
226
|
+
/** Calculate the activity score based on push recency and release frequency. */
|
|
29
227
|
declare const calculateActivityScore: (repository: ScoringInputs["repository"], releases: ScoringInputs["releases"]) => number;
|
|
228
|
+
/** Calculate the community score from contributors, topics, and metadata. */
|
|
30
229
|
declare const calculateCommunityScore: (repository: ScoringInputs["repository"], contributors: ScoringInputs["contributors"]) => number;
|
|
230
|
+
/** Calculate repository health based on issue density, update recency, and fork status. */
|
|
31
231
|
declare const calculateHealthScore: (repository: ScoringInputs["repository"]) => number;
|
|
232
|
+
/** Calculate risk score based on staleness, contributor count, fork status, and issue density. */
|
|
32
233
|
declare const calculateRiskScore: (repository: ScoringInputs["repository"], contributors: ScoringInputs["contributors"]) => number;
|
|
234
|
+
/** Calculate aggregate repository scores across all dimensions. */
|
|
33
235
|
declare const calculateRepositoryScore: (inputs: ScoringInputs) => RepositoryScores;
|
|
34
|
-
declare const calculateIdentityScore: (inputs: IdentityScoringInputs) => IdentityScores;
|
|
35
236
|
|
|
36
|
-
|
|
237
|
+
interface SkillsInputs {
|
|
238
|
+
repositories: IdentityScoringInputs["repositories"];
|
|
239
|
+
externalContributions: NormalizedContribution[];
|
|
240
|
+
npmUser?: NormalizedNpmUser | null;
|
|
241
|
+
stackoverflowUser?: NormalizedStackOverflowUser | null;
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Compute topic expertise scores by aggregating signals across
|
|
245
|
+
* GitHub repos, PRs, npm packages, and Stack Overflow tags.
|
|
246
|
+
*/
|
|
247
|
+
declare const calculateSkills: (inputs: SkillsInputs) => TopicExpertise[];
|
|
248
|
+
|
|
249
|
+
/** Canonical topic-to-keyword mappings for skill classification. */
|
|
250
|
+
declare const TOPIC_MAPPINGS: Record<string, string[]>;
|
|
251
|
+
/** Match a raw topic/language string to a canonical category name. */
|
|
252
|
+
declare const matchTopic: (nameOrTopic: string) => string | null;
|
|
253
|
+
|
|
254
|
+
export { type ContributorResult, type IdentityScores, type IdentityScoringInputs, type InfluenceResult, type KnowledgeScore, type MaintainerResult, type OrganizationResult, type PillarEvidence, type PillarFactors, type PublishingScore, type RepositoryScores, type ScoringInputs, TOPIC_MAPPINGS, type TopicExpertise, calculateActivityScore, calculateBadges, calculateCommunityScore, calculateContributorScore, calculateHealthScore, calculateIdentityScore, calculateImpactScore, calculateInfluenceScore, calculateKnowledgeScore, calculateMaintainerScore, calculateOrganizationScore, calculatePublishingScore, calculateRepositoryScore, calculateRiskScore, calculateSkills, generateEvidence, generateFactors, matchTopic };
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var M=Object.defineProperty;var x=Object.getOwnPropertyDescriptor;var A=Object.getOwnPropertyNames;var R=Object.prototype.hasOwnProperty;var F=(t,e)=>{for(var o in e)M(t,o,{get:e[o],enumerable:!0})},z=(t,e,o,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let c of A(e))!R.call(t,c)&&c!==o&&M(t,c,{get:()=>e[c],enumerable:!(i=x(e,c))||i.enumerable});return t};var T=t=>z(M({},"__esModule",{value:!0}),t);var H={};F(H,{calculateActivityScore:()=>C,calculateCommunityScore:()=>w,calculateHealthScore:()=>y,calculateIdentityScore:()=>Y,calculateImpactScore:()=>k,calculateRepositoryScore:()=>v,calculateRiskScore:()=>D});module.exports=T(H);var k=t=>{let e=t.stargazersCount,o=t.forksCount,i=t.watchersCount,c=Math.min(100,Math.log10(e+1)*20),n=Math.min(100,Math.log10(o+1)*25),s=Math.min(100,Math.log10(i+1)*30);return Math.round(c*.5+n*.35+s*.15)},C=(t,e)=>{if(t.isArchived)return 0;let o=new Date,i=new Date(t.pushedAt),c=o.getTime()-i.getTime(),n=Math.max(0,c/(1e3*60*60*24)),s=0;n<=1?s=100:n<=7?s=90:n<=30?s=80:n<=90?s=60:n<=180?s=40:n<=365&&(s=20);let u=50;if(e){let l=new Date;l.setFullYear(o.getFullYear()-1);let p=e.filter(f=>f.publishedAt&&new Date(f.publishedAt)>=l).length;u=Math.min(100,p*20)}return Math.round(s*.6+u*.4)},w=(t,e)=>{let o=e?e.length:0,i=Math.min(100,Math.log10(o+1)*50),c=t.topics.length>0?100:0,n=(t.description?50:0)+(t.homepage?50:0);return Math.round(i*.7+c*.15+n*.15)},y=t=>{if(t.isArchived)return 0;let e=t.stargazersCount+t.forksCount,o=100;if(t.openIssuesCount>0){let l=t.openIssuesCount/(e+10);o=Math.max(0,100-l*500)}let i=new Date(t.updatedAt),c=Date.now()-i.getTime(),n=Math.max(0,c/(1e3*60*60*24)),s=0;n<=30?s=100:n<=90?s=80:n<=180?s=60:n<=365?s=40:s=10;let u=t.isFork?50:100;return Math.round(o*.5+s*.3+u*.2)},D=(t,e)=>{let o=0,i=new Date(t.pushedAt),c=(Date.now()-i.getTime())/(1e3*60*60*24);c>365?o+=30:c>180?o+=20:c>90&&(o+=10);let n=e?e.length:1;n<=1?o+=30:n<=3?o+=20:n<=5&&(o+=10),t.isFork&&(o+=20);let s=t.stargazersCount+t.forksCount;return t.openIssuesCount>50&&t.openIssuesCount>s?o+=20:t.openIssuesCount>20&&(o+=10),Math.min(100,Math.round(o))},v=t=>{let{repository:e,contributors:o,releases:i}=t,c=y(e),n=k(e),s=C(e,i),u=w(e,o),l=D(e,o);return{overall:Math.round(c*.3+n*.25+s*.2+u*.15+(100-l)*.1),health:c,impact:n,activity:s,community:u,risk:l}},Y=t=>{let{repositories:e,npmPackages:o}=t;if(e.length===0)return{overall:0,health:0,impact:0,activity:0,community:0,risk:100};let i=e.reduce((r,a)=>r+a.stargazersCount,0),c=e.reduce((r,a)=>r+a.forksCount,0),n=e.reduce((r,a)=>r+a.watchersCount,0),s=Math.min(100,Math.log10(i+1)*20),u=Math.min(100,Math.log10(c+1)*25),l=Math.min(100,Math.log10(n+1)*30),p=s*.5+u*.35+l*.15;if(o&&o.length>0){let r=o.reduce((h,b)=>h+b.downloads,0),a=Math.min(100,Math.log10(r+1)*15);p=p*.8+a*.2}let f=Math.round(p),I=e.filter(r=>!r.isArchived),m=0,d=0,g=0,S=100;if(I.length>0){let r=I.map(a=>v({repository:a}));m=Math.round(r.reduce((a,h)=>a+h.health,0)/r.length),d=Math.round(r.reduce((a,h)=>a+h.activity,0)/r.length),g=Math.round(r.reduce((a,h)=>a+h.community,0)/r.length),S=Math.round(r.reduce((a,h)=>a+h.risk,0)/r.length)}return{overall:Math.round(m*.3+f*.25+d*.2+g*.15+(100-S)*.1),health:m,impact:f,activity:d,community:g,risk:S}};0&&(module.exports={calculateActivityScore,calculateCommunityScore,calculateHealthScore,calculateIdentityScore,calculateImpactScore,calculateRepositoryScore,calculateRiskScore});
|
|
1
|
+
"use strict";var U=Object.defineProperty;var rt=Object.getOwnPropertyDescriptor;var st=Object.getOwnPropertyNames;var at=Object.prototype.hasOwnProperty;var it=(o,t)=>{for(var e in t)U(o,e,{get:t[e],enumerable:!0})},ct=(o,t,e,s)=>{if(t&&typeof t=="object"||typeof t=="function")for(let a of st(t))!at.call(o,a)&&a!==e&&U(o,a,{get:()=>t[a],enumerable:!(s=rt(t,a))||s.enumerable});return o};var lt=o=>ct(U({},"__esModule",{value:!0}),o);var ut={};it(ut,{TOPIC_MAPPINGS:()=>v,calculateActivityScore:()=>L,calculateBadges:()=>z,calculateCommunityScore:()=>W,calculateContributorScore:()=>I,calculateHealthScore:()=>H,calculateIdentityScore:()=>Y,calculateImpactScore:()=>E,calculateInfluenceScore:()=>D,calculateKnowledgeScore:()=>O,calculateMaintainerScore:()=>$,calculateOrganizationScore:()=>T,calculatePublishingScore:()=>F,calculateRepositoryScore:()=>A,calculateRiskScore:()=>q,calculateSkills:()=>j,generateEvidence:()=>N,generateFactors:()=>P,matchTopic:()=>x});module.exports=lt(ut);var z=o=>{let{externalContributions:t,activeOrgsCount:e,totalStarsCount:s,totalNpmDownloads:a,npmUser:n,stackoverflowUser:i}=o,r=[],c=i?.reputation??0,l=t.length;return t.some(g=>g.targetRepoStars>=2e4)&&r.push("Framework Contributor"),e>=1&&r.push("OSS Founder"),n?.packages&&n.packages.length>=1&&r.push("Package Publisher"),c>=1e4&&r.push("StackOverflow Elite"),i&&i.answerCount>=100&&r.push("Community Helper"),i&&i.acceptanceRate>=80&&i.answerCount>=10&&r.push("High Acceptance"),t.filter(g=>g.type==="test").length>=5&&r.push("Test Champion"),t.some(g=>/\b(security|vuln|cve|fix|patch)\b/i.test(g.title))&&r.push("Security Champion"),l>=15&&r.push("Prodigious Contributor"),s>=1e3&&r.push("1k Stars Earned"),a>=1e6&&r.push("1M npm Downloads"),r};var I=o=>{let t={};for(let n of o)t[n.repoFullName]||(t[n.repoFullName]={repoFullName:n.repoFullName,prs:[],stars:n.targetRepoStars||0}),t[n.repoFullName].prs.push(n);let e=0,s=[];for(let n of Object.keys(t)){let i=t[n],{prs:r,stars:c}=i,l=Math.log10(c+1)/4,u=20+Math.round(Math.min(1,l)*20),m=[...r].sort((w,p)=>new Date(w.createdAt).getTime()-new Date(p.createdAt).getTime()),f=m[0],g=1;f.type==="docs"?g=.4:f.type==="test"?g=1.2:f.type==="chore"&&(g=.5);let b=Math.min(1,l)*g*15;b=b+Math.max(0,l-1)*10;let d=0;for(let w=1;w<m.length;w++){let p=m[w],h=1;p.type==="docs"?h=.4:p.type==="test"?h=1.2:p.type==="chore"&&(h=.5),d+=h*5}d=d*.5;let S=Math.min(u,Math.round(b+d));e+=S,s.push({repo:n,points:S})}return{score:Math.min(100,e),breakdown:s}};var N=o=>{let{activeRepos:t,externalContributions:e,organizations:s,contributorBreakdown:a,maintainerScore:n,sustainedCount:i,totalStarsCount:r,totalForksCount:c,totalNpmDownloads:l,soReputation:u}=o,m=[`${t.length} active repositories`,`${n}% average repository health`];if(i>0&&m.push(`${i} sustained maintenance projects`),t.length>0){let p=[...t].sort((h,y)=>y.stargazersCount-h.stargazersCount)[0];m.push(`Flagship project: ${p.name}`)}let f=e.length,g=[`${f} merged pull requests`];if(a.length>0){let p=[...a].sort((h,y)=>y.points-h.points)[0];g.push(`Top upstream: ${p.repo}`)}if(f>0){let p=e.filter(C=>C.type==="code").length,h=e.filter(C=>C.type==="docs").length,y=e.filter(C=>C.type==="test").length;g.push(`Classified: ${p} code, ${h} docs, ${y} tests`)}let b=[`${r.toLocaleString()} total stargazers`,`${c.toLocaleString()} repository forks`];l>0&&b.push(`${l.toLocaleString()} weekly npm downloads`),u>0&&b.push(`Stack Overflow reputation: ${u.toLocaleString()}`);let S=[`${s.filter(p=>(p.publicRepos||0)>0||(p.followers||0)>0).length} managed organizations`,`${s.reduce((p,h)=>p+(h.publicRepos||0),0)} collective repositories`],w=s.reduce((p,h)=>p+(h.followers||0),0);return w>0&&S.push(`${w.toLocaleString()} total organization followers`),{maintainer:m,contributor:g,influence:b,organization:S}},P=o=>{let{allRepos:t,externalContributions:e,organizations:s,contributorBreakdown:a,maintainerScore:n,sustainedCount:i,totalStarsCount:r,totalNpmDownloads:c,soReputation:l,stackoverflowUser:u}=o,m=e.length,f=e.some(k=>k.targetRepoStars>=2e4),g=[];n>=80&&g.push("Active flagship projects with excellent health"),i>0&&g.push("Sustained long-term repository maintenance"),t.filter(k=>k.isArchived).length>0&&g.push("Some repositories are archived/inactive"),g.length===0&&g.push("Moderate repository activity and health");let d=[];m>0&&d.push(`+ ${m} merged pull requests`),f&&d.push("+ PRs merged into high-importance frameworks");let w=[...a].sort((k,M)=>M.points-k.points)[0];w&&e.filter(M=>M.repoFullName===w.repo).length>2&&d.push(`+ Repeat contributor bonus for ${w.repo}`),m===0?d.push("- No external repository contributions found"):e.some(M=>(Date.now()-new Date(M.createdAt).getTime())/864e5<90)||d.push("- No recent upstream activity (past 90 days)");let p=[];r>=500&&p.push(`+ High community interest (${r} stars)`),c>=1e4&&p.push(`+ Strong download footprint (${c.toLocaleString()} weekly downloads)`),l>=1e3&&p.push(`+ High developer authority (${l.toLocaleString()} Stack Overflow reputation)`),u||p.push("- No Stack Overflow activity linked"),p.length===0&&p.push("Growing library adoption and ecosystem footprint");let h=s.filter(k=>(k.publicRepos||0)>0||(k.followers||0)>0).length,y=s.reduce((k,M)=>k+(M.followers||0),0),C=[];return h>0&&C.push(`+ Active leadership in ${h} organizations`),y>100&&C.push(`+ High organization follower presence (${y})`),h===0&&C.push("- No active organization leadership detected"),{maintainer:g,contributor:d,influence:p,organization:C}};var D=(o,t,e,s)=>{let a=Math.log10(o+1)*20,n=Math.log10(t+1)*20,i=Math.min(100,Math.round(a+n)),r=e>0?Math.min(15,Math.log10(e+1)*2.5):0,c=s>0?Math.min(15,Math.log10(s+1)*2.5):0;return{score:Math.min(100,Math.round(i+r+c)),githubBase:i,npmBonus:r,soBonus:c}};var O=o=>{if(!o)return null;let t=o.reputation??0,e=Math.min(100,Math.log10(t+1)*20),s=Math.min(100,o.answerCount*2),a=o.acceptanceRate||0;return{score:Math.min(100,Math.round(e*.5+s*.3+a*.2)),breakdown:{reputation:Math.round(e),answers:Math.round(s),acceptance:Math.round(a)}}};var E=o=>{let t=o.stargazersCount,e=o.forksCount,s=o.watchersCount,a=Math.min(100,Math.log10(t+1)*20),n=Math.min(100,Math.log10(e+1)*25),i=Math.min(100,Math.log10(s+1)*30);return Math.round(a*.5+n*.35+i*.15)},L=(o,t)=>{if(o.isArchived)return 0;let e=new Date,s=new Date(o.pushedAt),a=e.getTime()-s.getTime(),n=Math.max(0,a/(1e3*60*60*24)),i=0;n<=1?i=100:n<=7?i=90:n<=30?i=80:n<=90?i=60:n<=180?i=40:n<=365&&(i=20);let r=50;if(t){let c=new Date;c.setFullYear(e.getFullYear()-1);let l=t.filter(u=>u.publishedAt&&new Date(u.publishedAt)>=c).length;r=Math.min(100,l*20)}return Math.round(i*.6+r*.4)},W=(o,t)=>{let e=t&&t.length>0?t.length:Math.max(1,Math.min(100,Math.floor(Math.sqrt(o.stargazersCount)))),s=Math.min(100,Math.log10(e+1)*50),a=o.topics.length>0?100:0,n=(o.description?50:0)+(o.homepage?50:0);return Math.round(s*.7+a*.15+n*.15)},H=o=>{if(o.isArchived)return 0;let t=o.stargazersCount+o.forksCount,e=100;if(o.openIssuesCount>0){let c=o.openIssuesCount/(t+10);e=Math.max(0,100-c*500)}let s=new Date(o.updatedAt),a=Date.now()-s.getTime(),n=Math.max(0,a/(1e3*60*60*24)),i=0;n<=30?i=100:n<=90?i=80:n<=180?i=60:n<=365?i=40:i=10;let r=o.isFork?50:100;return Math.round(e*.5+i*.3+r*.2)},q=(o,t)=>{let e=0,s=new Date(o.pushedAt),a=(Date.now()-s.getTime())/(1e3*60*60*24);a>365?e+=30:a>180?e+=20:a>90&&(e+=10);let n=t&&t.length>0?t.length:Math.max(1,Math.min(100,Math.floor(Math.sqrt(o.stargazersCount))));n<=1?e+=30:n<=3?e+=20:n<=5&&(e+=10),o.isFork&&(e+=20);let i=o.stargazersCount+o.forksCount;return o.openIssuesCount>50&&o.openIssuesCount>i?e+=20:o.openIssuesCount>20&&(e+=10),Math.min(100,Math.round(e))},A=o=>{let{repository:t,contributors:e,releases:s}=o,a=H(t),n=E(t),i=L(t,s),r=W(t,e),c=q(t,e);return{overall:Math.round(a*.3+n*.25+i*.2+r*.15+(100-c)*.1),health:a,impact:n,activity:i,community:r,risk:c}};var $=(o,t)=>{let e=o.filter(l=>!l.isArchived),s=0,a=0,n=0;for(let l of e){let m=A({repository:l}).health,f=(Date.now()-new Date(l.pushedAt).getTime())/(1e3*60*60*24),b=(Date.now()-new Date(l.createdAt).getTime())/(1e3*60*60*24)>365&&f<90,d=b?Math.min(100,m+10):m;b&&n++;let S=Math.log10(l.stargazersCount+l.forksCount+1)+1;a+=d*S,s+=S}let i=s>0?Math.round(a/s):0,r=0;return t>0&&(r=Math.min(10,Math.log10(t+1)*1.5+2)),{score:Math.min(100,Math.round(i+r)),githubBase:i,npmBonus:r,sustainedCount:n}};var T=o=>{let t=o.filter(n=>(n.publicRepos||0)>0||(n.followers||0)>0),e=t.length,s=0;for(let n of t){let i=n.stars||0,r=n.followers||0,c=n.publicRepos||0,l=Math.log10(r+c+i+1)*8;s+=l}return{score:Math.min(100,Math.round(e*20+s)),activeCount:e}};var F=o=>{if(!o)return null;let t=o.totalWeeklyDownloads??0,e=Math.min(100,Math.log10(t+1)*15),s=Math.min(100,o.activePackagesCount*10),a=o.isVerifiedPublisher?100:0;return{score:Math.min(100,Math.round(e*.5+s*.3+a*.2)),breakdown:{downloads:Math.round(e),packages:Math.round(s),verified:a}}};var v={React:["react","reactjs","jsx","react-dom","remix","nextjs","next.js"],TypeScript:["typescript","ts"],"Node.js":["nodejs","node","express","koa","npm","yarn","pnpm","nest","nestjs"],JavaScript:["javascript","js","ecmascript"],Python:["python","py","django","flask","fastapi","numpy","pandas"],Rust:["rust","cargo","wasm-bindgen"],"Next.js":["nextjs","next.js"],Vue:["vue","vuejs","nuxt","nuxtjs"],Docker:["docker","kubernetes","k8s","devops","aws","gcp"],Database:["sql","postgres","postgresql","mysql","mongodb","redis","sqlite","prisma"],CSS:["css","sass","scss","tailwind","tailwindcss","postcss"]},x=o=>{let t=o.toLowerCase().replace(/[^a-z0-9]/g,"");for(let[e,s]of Object.entries(v)){if(e.toLowerCase()===t)return e;for(let a of s)if(a.toLowerCase().replace(/[^a-z0-9]/g,"")===t)return e}return null};var j=o=>{let{repositories:t,externalContributions:e,npmUser:s,stackoverflowUser:a}=o,n={};for(let r of Object.keys(v))n[r]={githubStars:0,githubPrs:0,npmDownloads:0,npmPackages:0,stackoverflowScore:0,stackoverflowAnswers:0};for(let r of t){let c=[r.language,...r.topics||[]].filter(Boolean),l=new Set;for(let u of c){let m=x(u);m&&l.add(m)}for(let u of l)n[u].githubStars+=r.stargazersCount}for(let r of e){let c=`${r.repoFullName} ${r.title} ${r.labels.join(" ")}`,l=new Set;for(let[u,m]of Object.entries(v))for(let f of m)new RegExp(`\\b${f}\\b`,"i").test(c)&&l.add(u);for(let u of l)n[u].githubPrs+=1}if(s?.packages)for(let r of s.packages){let c=`${r.name} ${r.description||""} ${r.categories.join(" ")}`,l=new Set;for(let[u,m]of Object.entries(v))for(let f of m)new RegExp(`\\b${f}\\b`,"i").test(c)&&l.add(u);for(let u of l)n[u].npmPackages+=1,n[u].npmDownloads+=r.weeklyDownloads}if(a?.topTags)for(let r of a.topTags){let c=x(r.name);c&&(n[c].stackoverflowScore+=r.score,n[c].stackoverflowAnswers+=r.count)}let i=[];for(let[r,c]of Object.entries(n)){let l=Math.min(40,Math.log10(c.githubStars+1)*10),u=Math.min(25,c.githubPrs*2.5),m=Math.min(20,c.npmPackages*5),f=Math.min(30,Math.log10(c.npmDownloads+1)*5),g=Math.min(35,Math.log10(c.stackoverflowScore+1)*10),b=Math.min(25,c.stackoverflowAnswers*.5),d=l+u+m+f+g+b,S=Math.round(Math.min(100,d));S>0&&i.push({topic:r,score:S,evidence:c})}return i.sort((r,c)=>c.score-r.score),i};var Y=o=>{let{repositories:t=[],npmUser:e=null,stackoverflowUser:s=null,externalContributions:a=[],organizations:n=[]}=o,i=t.reduce((R,B)=>R+B.stargazersCount,0),r=t.reduce((R,B)=>R+B.forksCount,0),c=e?.totalWeeklyDownloads??0,l=s?.reputation??0;if(t.length===0&&a.length===0&&!e&&!s&&n.length===0)return{overall:0,maintainer:0,contributor:0,organization:0,influence:0,confidence:"Low",evidence:{maintainer:[],contributor:[],influence:[],organization:[]},factors:{maintainer:[],contributor:[],influence:[],organization:[]},badges:[],skills:[]};let u=$(t,c),m=I(a),f=T(n),g=D(i,r,c,l),b=t.length,d=a.length,S="Low";b>=10||d>=15||c>=5e3||l>=1e3?S="High":(b>=3||d>=3||l>=100)&&(S="Medium");let w=u.githubBase,p=m.score,h=f.score,y=g.githubBase,C=0;f.activeCount>0?C=Math.round(w*.35+p*.3+h*.15+y*.2):C=Math.round(w*.45+p*.35+y*.2);let k=F(e),M=O(s),K=1+(100-C)/100,_=8,V=8,J=k?k.score/100*_*K:0,Q=M?M.score/100*V*K:0,X=Math.min(100,Math.round(C+J+Q)),Z=t.filter(R=>!R.isArchived),tt=z({externalContributions:a,activeOrgsCount:f.activeCount,totalStarsCount:i,totalNpmDownloads:c,npmUser:e,stackoverflowUser:s}),G={activeRepos:Z,allRepos:t,externalContributions:a,organizations:n,contributorBreakdown:m.breakdown,maintainerScore:u.score,sustainedCount:u.sustainedCount,totalStarsCount:i,totalForksCount:r,totalNpmDownloads:c,soReputation:l,npmUser:e,stackoverflowUser:s},et=N(G),ot=P(G),nt=j({repositories:t,externalContributions:a,npmUser:e,stackoverflowUser:s});return{overall:X,maintainer:u.score,contributor:m.score,organization:h,influence:g.score,confidence:S,evidence:et,factors:ot,badges:tt,skills:nt}};0&&(module.exports={TOPIC_MAPPINGS,calculateActivityScore,calculateBadges,calculateCommunityScore,calculateContributorScore,calculateHealthScore,calculateIdentityScore,calculateImpactScore,calculateInfluenceScore,calculateKnowledgeScore,calculateMaintainerScore,calculateOrganizationScore,calculatePublishingScore,calculateRepositoryScore,calculateRiskScore,calculateSkills,generateEvidence,generateFactors,matchTopic});
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var k=t=>{let o=t.stargazersCount,e=t.forksCount,i=t.watchersCount,a=Math.min(100,Math.log10(o+1)*20),n=Math.min(100,Math.log10(e+1)*25),s=Math.min(100,Math.log10(i+1)*30);return Math.round(a*.5+n*.35+s*.15)},C=(t,o)=>{if(t.isArchived)return 0;let e=new Date,i=new Date(t.pushedAt),a=e.getTime()-i.getTime(),n=Math.max(0,a/(1e3*60*60*24)),s=0;n<=1?s=100:n<=7?s=90:n<=30?s=80:n<=90?s=60:n<=180?s=40:n<=365&&(s=20);let u=50;if(o){let l=new Date;l.setFullYear(e.getFullYear()-1);let p=o.filter(f=>f.publishedAt&&new Date(f.publishedAt)>=l).length;u=Math.min(100,p*20)}return Math.round(s*.6+u*.4)},w=(t,o)=>{let e=o?o.length:0,i=Math.min(100,Math.log10(e+1)*50),a=t.topics.length>0?100:0,n=(t.description?50:0)+(t.homepage?50:0);return Math.round(i*.7+a*.15+n*.15)},y=t=>{if(t.isArchived)return 0;let o=t.stargazersCount+t.forksCount,e=100;if(t.openIssuesCount>0){let l=t.openIssuesCount/(o+10);e=Math.max(0,100-l*500)}let i=new Date(t.updatedAt),a=Date.now()-i.getTime(),n=Math.max(0,a/(1e3*60*60*24)),s=0;n<=30?s=100:n<=90?s=80:n<=180?s=60:n<=365?s=40:s=10;let u=t.isFork?50:100;return Math.round(e*.5+s*.3+u*.2)},D=(t,o)=>{let e=0,i=new Date(t.pushedAt),a=(Date.now()-i.getTime())/(1e3*60*60*24);a>365?e+=30:a>180?e+=20:a>90&&(e+=10);let n=o?o.length:1;n<=1?e+=30:n<=3?e+=20:n<=5&&(e+=10),t.isFork&&(e+=20);let s=t.stargazersCount+t.forksCount;return t.openIssuesCount>50&&t.openIssuesCount>s?e+=20:t.openIssuesCount>20&&(e+=10),Math.min(100,Math.round(e))},v=t=>{let{repository:o,contributors:e,releases:i}=t,a=y(o),n=k(o),s=C(o,i),u=w(o,e),l=D(o,e);return{overall:Math.round(a*.3+n*.25+s*.2+u*.15+(100-l)*.1),health:a,impact:n,activity:s,community:u,risk:l}},x=t=>{let{repositories:o,npmPackages:e}=t;if(o.length===0)return{overall:0,health:0,impact:0,activity:0,community:0,risk:100};let i=o.reduce((c,r)=>c+r.stargazersCount,0),a=o.reduce((c,r)=>c+r.forksCount,0),n=o.reduce((c,r)=>c+r.watchersCount,0),s=Math.min(100,Math.log10(i+1)*20),u=Math.min(100,Math.log10(a+1)*25),l=Math.min(100,Math.log10(n+1)*30),p=s*.5+u*.35+l*.15;if(e&&e.length>0){let c=e.reduce((h,I)=>h+I.downloads,0),r=Math.min(100,Math.log10(c+1)*15);p=p*.8+r*.2}let f=Math.round(p),M=o.filter(c=>!c.isArchived),m=0,d=0,g=0,S=100;if(M.length>0){let c=M.map(r=>v({repository:r}));m=Math.round(c.reduce((r,h)=>r+h.health,0)/c.length),d=Math.round(c.reduce((r,h)=>r+h.activity,0)/c.length),g=Math.round(c.reduce((r,h)=>r+h.community,0)/c.length),S=Math.round(c.reduce((r,h)=>r+h.risk,0)/c.length)}return{overall:Math.round(m*.3+f*.25+d*.2+g*.15+(100-S)*.1),health:m,impact:f,activity:d,community:g,risk:S}};export{C as calculateActivityScore,w as calculateCommunityScore,y as calculateHealthScore,x as calculateIdentityScore,k as calculateImpactScore,v as calculateRepositoryScore,D as calculateRiskScore};
|
|
1
|
+
var I=r=>{let{externalContributions:o,activeOrgsCount:e,totalStarsCount:s,totalNpmDownloads:c,npmUser:t,stackoverflowUser:a}=r,n=[],i=a?.reputation??0,l=o.length;return o.some(g=>g.targetRepoStars>=2e4)&&n.push("Framework Contributor"),e>=1&&n.push("OSS Founder"),t?.packages&&t.packages.length>=1&&n.push("Package Publisher"),i>=1e4&&n.push("StackOverflow Elite"),a&&a.answerCount>=100&&n.push("Community Helper"),a&&a.acceptanceRate>=80&&a.answerCount>=10&&n.push("High Acceptance"),o.filter(g=>g.type==="test").length>=5&&n.push("Test Champion"),o.some(g=>/\b(security|vuln|cve|fix|patch)\b/i.test(g.title))&&n.push("Security Champion"),l>=15&&n.push("Prodigious Contributor"),s>=1e3&&n.push("1k Stars Earned"),c>=1e6&&n.push("1M npm Downloads"),n};var N=r=>{let o={};for(let t of r)o[t.repoFullName]||(o[t.repoFullName]={repoFullName:t.repoFullName,prs:[],stars:t.targetRepoStars||0}),o[t.repoFullName].prs.push(t);let e=0,s=[];for(let t of Object.keys(o)){let a=o[t],{prs:n,stars:i}=a,l=Math.log10(i+1)/4,u=20+Math.round(Math.min(1,l)*20),m=[...n].sort((w,p)=>new Date(w.createdAt).getTime()-new Date(p.createdAt).getTime()),f=m[0],g=1;f.type==="docs"?g=.4:f.type==="test"?g=1.2:f.type==="chore"&&(g=.5);let b=Math.min(1,l)*g*15;b=b+Math.max(0,l-1)*10;let d=0;for(let w=1;w<m.length;w++){let p=m[w],h=1;p.type==="docs"?h=.4:p.type==="test"?h=1.2:p.type==="chore"&&(h=.5),d+=h*5}d=d*.5;let S=Math.min(u,Math.round(b+d));e+=S,s.push({repo:t,points:S})}return{score:Math.min(100,e),breakdown:s}};var P=r=>{let{activeRepos:o,externalContributions:e,organizations:s,contributorBreakdown:c,maintainerScore:t,sustainedCount:a,totalStarsCount:n,totalForksCount:i,totalNpmDownloads:l,soReputation:u}=r,m=[`${o.length} active repositories`,`${t}% average repository health`];if(a>0&&m.push(`${a} sustained maintenance projects`),o.length>0){let p=[...o].sort((h,y)=>y.stargazersCount-h.stargazersCount)[0];m.push(`Flagship project: ${p.name}`)}let f=e.length,g=[`${f} merged pull requests`];if(c.length>0){let p=[...c].sort((h,y)=>y.points-h.points)[0];g.push(`Top upstream: ${p.repo}`)}if(f>0){let p=e.filter(C=>C.type==="code").length,h=e.filter(C=>C.type==="docs").length,y=e.filter(C=>C.type==="test").length;g.push(`Classified: ${p} code, ${h} docs, ${y} tests`)}let b=[`${n.toLocaleString()} total stargazers`,`${i.toLocaleString()} repository forks`];l>0&&b.push(`${l.toLocaleString()} weekly npm downloads`),u>0&&b.push(`Stack Overflow reputation: ${u.toLocaleString()}`);let S=[`${s.filter(p=>(p.publicRepos||0)>0||(p.followers||0)>0).length} managed organizations`,`${s.reduce((p,h)=>p+(h.publicRepos||0),0)} collective repositories`],w=s.reduce((p,h)=>p+(h.followers||0),0);return w>0&&S.push(`${w.toLocaleString()} total organization followers`),{maintainer:m,contributor:g,influence:b,organization:S}},D=r=>{let{allRepos:o,externalContributions:e,organizations:s,contributorBreakdown:c,maintainerScore:t,sustainedCount:a,totalStarsCount:n,totalNpmDownloads:i,soReputation:l,stackoverflowUser:u}=r,m=e.length,f=e.some(k=>k.targetRepoStars>=2e4),g=[];t>=80&&g.push("Active flagship projects with excellent health"),a>0&&g.push("Sustained long-term repository maintenance"),o.filter(k=>k.isArchived).length>0&&g.push("Some repositories are archived/inactive"),g.length===0&&g.push("Moderate repository activity and health");let d=[];m>0&&d.push(`+ ${m} merged pull requests`),f&&d.push("+ PRs merged into high-importance frameworks");let w=[...c].sort((k,M)=>M.points-k.points)[0];w&&e.filter(M=>M.repoFullName===w.repo).length>2&&d.push(`+ Repeat contributor bonus for ${w.repo}`),m===0?d.push("- No external repository contributions found"):e.some(M=>(Date.now()-new Date(M.createdAt).getTime())/864e5<90)||d.push("- No recent upstream activity (past 90 days)");let p=[];n>=500&&p.push(`+ High community interest (${n} stars)`),i>=1e4&&p.push(`+ Strong download footprint (${i.toLocaleString()} weekly downloads)`),l>=1e3&&p.push(`+ High developer authority (${l.toLocaleString()} Stack Overflow reputation)`),u||p.push("- No Stack Overflow activity linked"),p.length===0&&p.push("Growing library adoption and ecosystem footprint");let h=s.filter(k=>(k.publicRepos||0)>0||(k.followers||0)>0).length,y=s.reduce((k,M)=>k+(M.followers||0),0),C=[];return h>0&&C.push(`+ Active leadership in ${h} organizations`),y>100&&C.push(`+ High organization follower presence (${y})`),h===0&&C.push("- No active organization leadership detected"),{maintainer:g,contributor:d,influence:p,organization:C}};var O=(r,o,e,s)=>{let c=Math.log10(r+1)*20,t=Math.log10(o+1)*20,a=Math.min(100,Math.round(c+t)),n=e>0?Math.min(15,Math.log10(e+1)*2.5):0,i=s>0?Math.min(15,Math.log10(s+1)*2.5):0;return{score:Math.min(100,Math.round(a+n+i)),githubBase:a,npmBonus:n,soBonus:i}};var A=r=>{if(!r)return null;let o=r.reputation??0,e=Math.min(100,Math.log10(o+1)*20),s=Math.min(100,r.answerCount*2),c=r.acceptanceRate||0;return{score:Math.min(100,Math.round(e*.5+s*.3+c*.2)),breakdown:{reputation:Math.round(e),answers:Math.round(s),acceptance:Math.round(c)}}};var L=r=>{let o=r.stargazersCount,e=r.forksCount,s=r.watchersCount,c=Math.min(100,Math.log10(o+1)*20),t=Math.min(100,Math.log10(e+1)*25),a=Math.min(100,Math.log10(s+1)*30);return Math.round(c*.5+t*.35+a*.15)},W=(r,o)=>{if(r.isArchived)return 0;let e=new Date,s=new Date(r.pushedAt),c=e.getTime()-s.getTime(),t=Math.max(0,c/(1e3*60*60*24)),a=0;t<=1?a=100:t<=7?a=90:t<=30?a=80:t<=90?a=60:t<=180?a=40:t<=365&&(a=20);let n=50;if(o){let i=new Date;i.setFullYear(e.getFullYear()-1);let l=o.filter(u=>u.publishedAt&&new Date(u.publishedAt)>=i).length;n=Math.min(100,l*20)}return Math.round(a*.6+n*.4)},H=(r,o)=>{let e=o&&o.length>0?o.length:Math.max(1,Math.min(100,Math.floor(Math.sqrt(r.stargazersCount)))),s=Math.min(100,Math.log10(e+1)*50),c=r.topics.length>0?100:0,t=(r.description?50:0)+(r.homepage?50:0);return Math.round(s*.7+c*.15+t*.15)},q=r=>{if(r.isArchived)return 0;let o=r.stargazersCount+r.forksCount,e=100;if(r.openIssuesCount>0){let i=r.openIssuesCount/(o+10);e=Math.max(0,100-i*500)}let s=new Date(r.updatedAt),c=Date.now()-s.getTime(),t=Math.max(0,c/(1e3*60*60*24)),a=0;t<=30?a=100:t<=90?a=80:t<=180?a=60:t<=365?a=40:a=10;let n=r.isFork?50:100;return Math.round(e*.5+a*.3+n*.2)},K=(r,o)=>{let e=0,s=new Date(r.pushedAt),c=(Date.now()-s.getTime())/(1e3*60*60*24);c>365?e+=30:c>180?e+=20:c>90&&(e+=10);let t=o&&o.length>0?o.length:Math.max(1,Math.min(100,Math.floor(Math.sqrt(r.stargazersCount))));t<=1?e+=30:t<=3?e+=20:t<=5&&(e+=10),r.isFork&&(e+=20);let a=r.stargazersCount+r.forksCount;return r.openIssuesCount>50&&r.openIssuesCount>a?e+=20:r.openIssuesCount>20&&(e+=10),Math.min(100,Math.round(e))},$=r=>{let{repository:o,contributors:e,releases:s}=r,c=q(o),t=L(o),a=W(o,s),n=H(o,e),i=K(o,e);return{overall:Math.round(c*.3+t*.25+a*.2+n*.15+(100-i)*.1),health:c,impact:t,activity:a,community:n,risk:i}};var T=(r,o)=>{let e=r.filter(l=>!l.isArchived),s=0,c=0,t=0;for(let l of e){let m=$({repository:l}).health,f=(Date.now()-new Date(l.pushedAt).getTime())/(1e3*60*60*24),b=(Date.now()-new Date(l.createdAt).getTime())/(1e3*60*60*24)>365&&f<90,d=b?Math.min(100,m+10):m;b&&t++;let S=Math.log10(l.stargazersCount+l.forksCount+1)+1;c+=d*S,s+=S}let a=s>0?Math.round(c/s):0,n=0;return o>0&&(n=Math.min(10,Math.log10(o+1)*1.5+2)),{score:Math.min(100,Math.round(a+n)),githubBase:a,npmBonus:n,sustainedCount:t}};var F=r=>{let o=r.filter(t=>(t.publicRepos||0)>0||(t.followers||0)>0),e=o.length,s=0;for(let t of o){let a=t.stars||0,n=t.followers||0,i=t.publicRepos||0,l=Math.log10(n+i+a+1)*8;s+=l}return{score:Math.min(100,Math.round(e*20+s)),activeCount:e}};var j=r=>{if(!r)return null;let o=r.totalWeeklyDownloads??0,e=Math.min(100,Math.log10(o+1)*15),s=Math.min(100,r.activePackagesCount*10),c=r.isVerifiedPublisher?100:0;return{score:Math.min(100,Math.round(e*.5+s*.3+c*.2)),breakdown:{downloads:Math.round(e),packages:Math.round(s),verified:c}}};var v={React:["react","reactjs","jsx","react-dom","remix","nextjs","next.js"],TypeScript:["typescript","ts"],"Node.js":["nodejs","node","express","koa","npm","yarn","pnpm","nest","nestjs"],JavaScript:["javascript","js","ecmascript"],Python:["python","py","django","flask","fastapi","numpy","pandas"],Rust:["rust","cargo","wasm-bindgen"],"Next.js":["nextjs","next.js"],Vue:["vue","vuejs","nuxt","nuxtjs"],Docker:["docker","kubernetes","k8s","devops","aws","gcp"],Database:["sql","postgres","postgresql","mysql","mongodb","redis","sqlite","prisma"],CSS:["css","sass","scss","tailwind","tailwindcss","postcss"]},x=r=>{let o=r.toLowerCase().replace(/[^a-z0-9]/g,"");for(let[e,s]of Object.entries(v)){if(e.toLowerCase()===o)return e;for(let c of s)if(c.toLowerCase().replace(/[^a-z0-9]/g,"")===o)return e}return null};var B=r=>{let{repositories:o,externalContributions:e,npmUser:s,stackoverflowUser:c}=r,t={};for(let n of Object.keys(v))t[n]={githubStars:0,githubPrs:0,npmDownloads:0,npmPackages:0,stackoverflowScore:0,stackoverflowAnswers:0};for(let n of o){let i=[n.language,...n.topics||[]].filter(Boolean),l=new Set;for(let u of i){let m=x(u);m&&l.add(m)}for(let u of l)t[u].githubStars+=n.stargazersCount}for(let n of e){let i=`${n.repoFullName} ${n.title} ${n.labels.join(" ")}`,l=new Set;for(let[u,m]of Object.entries(v))for(let f of m)new RegExp(`\\b${f}\\b`,"i").test(i)&&l.add(u);for(let u of l)t[u].githubPrs+=1}if(s?.packages)for(let n of s.packages){let i=`${n.name} ${n.description||""} ${n.categories.join(" ")}`,l=new Set;for(let[u,m]of Object.entries(v))for(let f of m)new RegExp(`\\b${f}\\b`,"i").test(i)&&l.add(u);for(let u of l)t[u].npmPackages+=1,t[u].npmDownloads+=n.weeklyDownloads}if(c?.topTags)for(let n of c.topTags){let i=x(n.name);i&&(t[i].stackoverflowScore+=n.score,t[i].stackoverflowAnswers+=n.count)}let a=[];for(let[n,i]of Object.entries(t)){let l=Math.min(40,Math.log10(i.githubStars+1)*10),u=Math.min(25,i.githubPrs*2.5),m=Math.min(20,i.npmPackages*5),f=Math.min(30,Math.log10(i.npmDownloads+1)*5),g=Math.min(35,Math.log10(i.stackoverflowScore+1)*10),b=Math.min(25,i.stackoverflowAnswers*.5),d=l+u+m+f+g+b,S=Math.round(Math.min(100,d));S>0&&a.push({topic:n,score:S,evidence:i})}return a.sort((n,i)=>i.score-n.score),a};var ot=r=>{let{repositories:o=[],npmUser:e=null,stackoverflowUser:s=null,externalContributions:c=[],organizations:t=[]}=r,a=o.reduce((R,z)=>R+z.stargazersCount,0),n=o.reduce((R,z)=>R+z.forksCount,0),i=e?.totalWeeklyDownloads??0,l=s?.reputation??0;if(o.length===0&&c.length===0&&!e&&!s&&t.length===0)return{overall:0,maintainer:0,contributor:0,organization:0,influence:0,confidence:"Low",evidence:{maintainer:[],contributor:[],influence:[],organization:[]},factors:{maintainer:[],contributor:[],influence:[],organization:[]},badges:[],skills:[]};let u=T(o,i),m=N(c),f=F(t),g=O(a,n,i,l),b=o.length,d=c.length,S="Low";b>=10||d>=15||i>=5e3||l>=1e3?S="High":(b>=3||d>=3||l>=100)&&(S="Medium");let w=u.githubBase,p=m.score,h=f.score,y=g.githubBase,C=0;f.activeCount>0?C=Math.round(w*.35+p*.3+h*.15+y*.2):C=Math.round(w*.45+p*.35+y*.2);let k=j(e),M=A(s),U=1+(100-C)/100,G=8,Y=8,_=k?k.score/100*G*U:0,V=M?M.score/100*Y*U:0,J=Math.min(100,Math.round(C+_+V)),Q=o.filter(R=>!R.isArchived),X=I({externalContributions:c,activeOrgsCount:f.activeCount,totalStarsCount:a,totalNpmDownloads:i,npmUser:e,stackoverflowUser:s}),E={activeRepos:Q,allRepos:o,externalContributions:c,organizations:t,contributorBreakdown:m.breakdown,maintainerScore:u.score,sustainedCount:u.sustainedCount,totalStarsCount:a,totalForksCount:n,totalNpmDownloads:i,soReputation:l,npmUser:e,stackoverflowUser:s},Z=P(E),tt=D(E),et=B({repositories:o,externalContributions:c,npmUser:e,stackoverflowUser:s});return{overall:J,maintainer:u.score,contributor:m.score,organization:h,influence:g.score,confidence:S,evidence:Z,factors:tt,badges:X,skills:et}};export{v as TOPIC_MAPPINGS,W as calculateActivityScore,I as calculateBadges,H as calculateCommunityScore,N as calculateContributorScore,q as calculateHealthScore,ot as calculateIdentityScore,L as calculateImpactScore,O as calculateInfluenceScore,A as calculateKnowledgeScore,T as calculateMaintainerScore,F as calculateOrganizationScore,j as calculatePublishingScore,$ as calculateRepositoryScore,K as calculateRiskScore,B as calculateSkills,P as generateEvidence,D as generateFactors,x as matchTopic};
|
package/package.json
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
"name": "@ossintel/scoring",
|
|
3
3
|
"author": "Mayank Kumar Chaudhari <https://mayankchaudhari.com>",
|
|
4
4
|
"private": false,
|
|
5
|
-
"version": "0.
|
|
6
|
-
"description": "
|
|
5
|
+
"version": "0.1.0",
|
|
6
|
+
"description": "Mathematical, deterministic metrics calculator to evaluate open-source software health, impact, activity, community adoption, and risk profiles.",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"main": "./dist/index.js",
|
|
9
9
|
"module": "./dist/index.mjs",
|
|
@@ -33,15 +33,19 @@
|
|
|
33
33
|
"dist/**"
|
|
34
34
|
],
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@ossintel/github-normalizer": "0.
|
|
36
|
+
"@ossintel/github-normalizer": "0.1.0",
|
|
37
|
+
"@ossintel/npm": "0.0.1",
|
|
38
|
+
"@ossintel/stackoverflow": "0.0.1"
|
|
37
39
|
},
|
|
38
40
|
"devDependencies": {
|
|
39
41
|
"@types/node": "latest",
|
|
40
42
|
"tsup": "latest",
|
|
41
|
-
"typescript": "
|
|
43
|
+
"typescript": "^6.0.3"
|
|
42
44
|
},
|
|
43
45
|
"forge": {
|
|
44
|
-
"aliases": [
|
|
46
|
+
"aliases": [
|
|
47
|
+
"ossintel"
|
|
48
|
+
],
|
|
45
49
|
"icon": "LibraryBig",
|
|
46
50
|
"description": ""
|
|
47
51
|
},
|
|
@@ -52,7 +56,12 @@
|
|
|
52
56
|
}
|
|
53
57
|
],
|
|
54
58
|
"keywords": [
|
|
55
|
-
"
|
|
59
|
+
"deterministic-scoring",
|
|
60
|
+
"oss-health",
|
|
61
|
+
"software-metrics",
|
|
62
|
+
"risk-calculation",
|
|
63
|
+
"developer-impact",
|
|
64
|
+
"math-engine",
|
|
56
65
|
"turboforge"
|
|
57
66
|
],
|
|
58
67
|
"scripts": {
|