@urmzd/github-insights 2.1.0 → 2.3.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/.githooks/.sr-hooks-hash +1 -0
- package/.githooks/commit-msg +3 -0
- package/.githooks/pre-commit +3 -0
- package/AGENTS.md +32 -19
- package/CHANGELOG.md +62 -0
- package/CONTRIBUTING.md +18 -19
- package/README.md +21 -24
- package/action.yml +1 -1
- package/assets/insights/index.svg +45 -4
- package/assets/insights/metrics-constellation.svg +55 -0
- package/assets/insights/metrics-growth.svg +55 -0
- package/assets/insights/metrics-heatmap.svg +55 -0
- package/assets/insights/metrics-impact.svg +55 -0
- package/assets/insights/metrics-rhythm.svg +55 -0
- package/assets/insights/metrics-velocity.svg +55 -0
- package/examples/classic/README.md +36 -2
- package/examples/classic/index.svg +45 -4
- package/examples/classic/metrics-constellation.svg +55 -0
- package/examples/classic/metrics-growth.svg +55 -0
- package/examples/classic/metrics-heatmap.svg +55 -0
- package/examples/classic/metrics-impact.svg +55 -0
- package/examples/classic/metrics-rhythm.svg +55 -0
- package/examples/classic/metrics-velocity.svg +55 -0
- package/examples/ecosystem/README.md +39 -28
- package/examples/ecosystem/index.svg +45 -4
- package/examples/ecosystem/metrics-constellation.svg +55 -0
- package/examples/ecosystem/metrics-growth.svg +55 -0
- package/examples/ecosystem/metrics-heatmap.svg +55 -0
- package/examples/ecosystem/metrics-impact.svg +55 -0
- package/examples/ecosystem/metrics-rhythm.svg +55 -0
- package/examples/ecosystem/metrics-velocity.svg +55 -0
- package/examples/minimal/README.md +36 -2
- package/examples/minimal/index.svg +45 -4
- package/examples/minimal/metrics-constellation.svg +55 -0
- package/examples/minimal/metrics-growth.svg +55 -0
- package/examples/minimal/metrics-heatmap.svg +55 -0
- package/examples/minimal/metrics-impact.svg +55 -0
- package/examples/minimal/metrics-rhythm.svg +55 -0
- package/examples/minimal/metrics-velocity.svg +55 -0
- package/examples/modern/README.md +62 -50
- package/examples/modern/index.svg +45 -4
- package/examples/modern/metrics-constellation.svg +55 -0
- package/examples/modern/metrics-growth.svg +55 -0
- package/examples/modern/metrics-heatmap.svg +55 -0
- package/examples/modern/metrics-impact.svg +55 -0
- package/examples/modern/metrics-rhythm.svg +55 -0
- package/examples/modern/metrics-velocity.svg +55 -0
- package/llms.txt +4 -4
- package/package.json +1 -1
- package/skills/github-insights/SKILL.md +35 -81
- package/sr.yaml +9 -0
- package/src/api.ts +2 -140
- package/src/components/contribution-heatmap.tsx +43 -0
- package/src/components/contribution-rhythm.tsx +152 -0
- package/src/components/full-svg.test.tsx +4 -1
- package/src/components/full-svg.tsx +14 -7
- package/src/components/growth-arc.tsx +119 -0
- package/src/components/impact-trail.tsx +90 -0
- package/src/components/language-velocity.tsx +181 -0
- package/src/components/project-constellation.tsx +97 -0
- package/src/components/section.test.tsx +5 -3
- package/src/components/section.tsx +5 -13
- package/src/components/style-defs.tsx +44 -3
- package/src/index.ts +28 -47
- package/src/metrics.test.ts +50 -57
- package/src/metrics.ts +277 -95
- package/src/readme.test.ts +2 -4
- package/src/templates.test.ts +19 -16
- package/src/templates.ts +30 -16
- package/src/theme.ts +11 -1
- package/src/types.ts +34 -7
- package/assets/insights/metrics-calendar.svg +0 -14
- package/assets/insights/metrics-complexity.svg +0 -14
- package/assets/insights/metrics-contributions.svg +0 -14
- package/assets/insights/metrics-expertise.svg +0 -14
- package/assets/insights/metrics-languages.svg +0 -14
- package/assets/insights/metrics-pulse.svg +0 -14
- package/examples/classic/metrics-calendar.svg +0 -14
- package/examples/classic/metrics-complexity.svg +0 -14
- package/examples/classic/metrics-contributions.svg +0 -14
- package/examples/classic/metrics-expertise.svg +0 -14
- package/examples/classic/metrics-languages.svg +0 -14
- package/examples/classic/metrics-pulse.svg +0 -14
- package/examples/ecosystem/metrics-calendar.svg +0 -14
- package/examples/ecosystem/metrics-complexity.svg +0 -14
- package/examples/ecosystem/metrics-contributions.svg +0 -14
- package/examples/ecosystem/metrics-expertise.svg +0 -14
- package/examples/ecosystem/metrics-languages.svg +0 -14
- package/examples/ecosystem/metrics-pulse.svg +0 -14
- package/examples/minimal/metrics-calendar.svg +0 -14
- package/examples/minimal/metrics-complexity.svg +0 -14
- package/examples/minimal/metrics-contributions.svg +0 -14
- package/examples/minimal/metrics-expertise.svg +0 -14
- package/examples/minimal/metrics-languages.svg +0 -14
- package/examples/minimal/metrics-pulse.svg +0 -14
- package/examples/modern/metrics-calendar.svg +0 -14
- package/examples/modern/metrics-complexity.svg +0 -14
- package/examples/modern/metrics-contributions.svg +0 -14
- package/examples/modern/metrics-expertise.svg +0 -14
- package/examples/modern/metrics-languages.svg +0 -14
- package/examples/modern/metrics-pulse.svg +0 -14
- package/src/components/bar-chart.test.tsx +0 -38
- package/src/components/bar-chart.tsx +0 -54
- package/src/components/contribution-calendar.test.tsx +0 -44
- package/src/components/contribution-calendar.tsx +0 -94
- package/src/components/contribution-cards.test.tsx +0 -36
- package/src/components/contribution-cards.tsx +0 -58
- package/src/components/donut-chart.test.tsx +0 -36
- package/src/components/donut-chart.tsx +0 -102
- package/src/components/project-cards.test.tsx +0 -46
- package/src/components/project-cards.tsx +0 -66
- package/src/components/stat-cards.test.tsx +0 -32
- package/src/components/stat-cards.tsx +0 -57
- package/src/components/tech-highlights.test.tsx +0 -63
- package/src/components/tech-highlights.tsx +0 -109
- package/teasr.toml +0 -14
package/src/index.ts
CHANGED
|
@@ -6,10 +6,7 @@ import {
|
|
|
6
6
|
fetchAIPreamble,
|
|
7
7
|
fetchAllRepoData,
|
|
8
8
|
fetchContributionData,
|
|
9
|
-
fetchExpertiseAnalysis,
|
|
10
|
-
fetchManifestsForRepos,
|
|
11
9
|
fetchProjectClassifications,
|
|
12
|
-
fetchReadmeForRepos,
|
|
13
10
|
fetchUserProfile,
|
|
14
11
|
makeGraphql,
|
|
15
12
|
} from "./api.js";
|
|
@@ -20,8 +17,9 @@ import {
|
|
|
20
17
|
aggregateLanguages,
|
|
21
18
|
buildClassificationInputs,
|
|
22
19
|
buildSections,
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
computeConstellationLayout,
|
|
21
|
+
computeContributionRhythm,
|
|
22
|
+
computeLanguageVelocity,
|
|
25
23
|
getTopProjectsByComplexity,
|
|
26
24
|
SECTION_KEYS,
|
|
27
25
|
splitProjectsByRecency,
|
|
@@ -87,56 +85,33 @@ async function run(): Promise<void> {
|
|
|
87
85
|
const repos = await fetchAllRepoData(graphql, username);
|
|
88
86
|
core.info(`Found ${repos.length} public repos`);
|
|
89
87
|
|
|
90
|
-
core.info("Fetching dependency manifests...");
|
|
91
88
|
core.info("Fetching contribution data...");
|
|
92
|
-
core.info("Fetching READMEs...");
|
|
93
89
|
core.info("Fetching user profile...");
|
|
94
|
-
const [
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
fetchReadmeForRepos(graphql, username, repos),
|
|
99
|
-
fetchUserProfile(graphql, username),
|
|
100
|
-
]);
|
|
101
|
-
core.info(`Fetched manifests for ${manifests.size} repos`);
|
|
90
|
+
const [contributionData, userProfile] = await Promise.all([
|
|
91
|
+
fetchContributionData(graphql, username),
|
|
92
|
+
fetchUserProfile(graphql, username),
|
|
93
|
+
]);
|
|
102
94
|
core.info(
|
|
103
95
|
`Contributions: ${contributionData.contributions.totalCommitContributions} commits, ${contributionData.contributions.totalPullRequestContributions} PRs`,
|
|
104
96
|
);
|
|
105
|
-
core.info(`Fetched READMEs for ${readmeMap.size} repos`);
|
|
106
97
|
core.info(`User profile: ${userProfile.name || username}`);
|
|
107
98
|
|
|
108
99
|
// ── Transform ─────────────────────────────────────────────────────────
|
|
109
100
|
const languages = aggregateLanguages(repos);
|
|
110
101
|
const complexProjects = getTopProjectsByComplexity(repos);
|
|
111
|
-
const projects = complexProjects.slice(0, 5);
|
|
112
|
-
|
|
113
|
-
const allDeps = collectAllDependencies(repos, manifests);
|
|
114
|
-
const allTopics = collectAllTopics(repos);
|
|
115
102
|
|
|
116
103
|
core.info("Fetching project classifications from GitHub Models...");
|
|
117
104
|
const classificationInputs = buildClassificationInputs(
|
|
118
105
|
repos,
|
|
119
106
|
contributionData,
|
|
120
107
|
);
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
return fetchExpertiseAnalysis(
|
|
126
|
-
token,
|
|
127
|
-
languages,
|
|
128
|
-
allDeps,
|
|
129
|
-
allTopics,
|
|
130
|
-
repos,
|
|
131
|
-
readmeMap,
|
|
132
|
-
userConfig,
|
|
133
|
-
);
|
|
134
|
-
})(),
|
|
135
|
-
]);
|
|
108
|
+
const aiClassifications = await fetchProjectClassifications(
|
|
109
|
+
token,
|
|
110
|
+
classificationInputs,
|
|
111
|
+
);
|
|
136
112
|
core.info(
|
|
137
113
|
`Project classifications: ${aiClassifications.length} AI-classified (${repos.length - aiClassifications.length} heuristic fallback)`,
|
|
138
114
|
);
|
|
139
|
-
core.info(`Expertise analysis: ${techHighlights.length} categories`);
|
|
140
115
|
|
|
141
116
|
const {
|
|
142
117
|
active: activeProjects,
|
|
@@ -145,17 +120,20 @@ async function run(): Promise<void> {
|
|
|
145
120
|
archived: archivedProjects,
|
|
146
121
|
} = splitProjectsByRecency(repos, contributionData, aiClassifications);
|
|
147
122
|
|
|
123
|
+
// ── Compute new visualization data ───────────────────────────────────
|
|
124
|
+
const velocity = computeLanguageVelocity(contributionData, repos);
|
|
125
|
+
const rhythm = computeContributionRhythm(contributionData);
|
|
126
|
+
const constellation = computeConstellationLayout(complexProjects, repos);
|
|
127
|
+
|
|
148
128
|
const sectionDefs = buildSections({
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
129
|
+
velocity,
|
|
130
|
+
rhythm,
|
|
131
|
+
constellation,
|
|
152
132
|
contributionData,
|
|
153
133
|
});
|
|
154
134
|
|
|
155
135
|
// Filter sections by requested keys if specified
|
|
156
|
-
let activeSections = sectionDefs.filter(
|
|
157
|
-
(s) => s.renderBody || (s.items && s.items.length > 0),
|
|
158
|
-
);
|
|
136
|
+
let activeSections = sectionDefs.filter((s) => s.renderBody);
|
|
159
137
|
if (requestedSections.length > 0) {
|
|
160
138
|
const allowedFilenames = new Set(
|
|
161
139
|
requestedSections.map((key) => SECTION_KEYS[key]).filter(Boolean),
|
|
@@ -169,11 +147,11 @@ async function run(): Promise<void> {
|
|
|
169
147
|
mkdirSync(outputDir, { recursive: true });
|
|
170
148
|
|
|
171
149
|
for (const section of activeSections) {
|
|
150
|
+
if (!section.renderBody) continue;
|
|
172
151
|
const { svg, height } = renderSection(
|
|
173
152
|
section.title,
|
|
174
153
|
section.subtitle,
|
|
175
|
-
section.renderBody
|
|
176
|
-
section.options || {},
|
|
154
|
+
section.renderBody,
|
|
177
155
|
);
|
|
178
156
|
writeFileSync(
|
|
179
157
|
`${outputDir}/${section.filename}`,
|
|
@@ -199,7 +177,6 @@ async function run(): Promise<void> {
|
|
|
199
177
|
profile: userProfile,
|
|
200
178
|
userConfig,
|
|
201
179
|
languages,
|
|
202
|
-
techHighlights,
|
|
203
180
|
activeProjects,
|
|
204
181
|
complexProjects,
|
|
205
182
|
});
|
|
@@ -258,7 +235,9 @@ async function run(): Promise<void> {
|
|
|
258
235
|
allProjects: complexProjects,
|
|
259
236
|
categorizedProjects,
|
|
260
237
|
languages,
|
|
261
|
-
|
|
238
|
+
velocity,
|
|
239
|
+
rhythm,
|
|
240
|
+
constellation,
|
|
262
241
|
contributionData,
|
|
263
242
|
socialBadges,
|
|
264
243
|
svgDir,
|
|
@@ -323,7 +302,9 @@ async function run(): Promise<void> {
|
|
|
323
302
|
allProjects: complexProjects,
|
|
324
303
|
categorizedProjects,
|
|
325
304
|
languages,
|
|
326
|
-
|
|
305
|
+
velocity,
|
|
306
|
+
rhythm,
|
|
307
|
+
constellation,
|
|
327
308
|
contributionData,
|
|
328
309
|
socialBadges,
|
|
329
310
|
svgDir: ".",
|
package/src/metrics.test.ts
CHANGED
|
@@ -13,7 +13,11 @@ import {
|
|
|
13
13
|
SECTION_KEYS,
|
|
14
14
|
splitProjectsByRecency,
|
|
15
15
|
} from "./metrics.js";
|
|
16
|
-
import type {
|
|
16
|
+
import type {
|
|
17
|
+
ContributionRhythm,
|
|
18
|
+
ManifestMap,
|
|
19
|
+
MonthlyLanguageBucket,
|
|
20
|
+
} from "./types.js";
|
|
17
21
|
|
|
18
22
|
// ── aggregateLanguages ──────────────────────────────────────────────────────
|
|
19
23
|
|
|
@@ -534,37 +538,48 @@ describe("splitProjectsByRecency", () => {
|
|
|
534
538
|
|
|
535
539
|
describe("SECTION_KEYS", () => {
|
|
536
540
|
it("maps all known section names to filenames", () => {
|
|
537
|
-
expect(SECTION_KEYS.
|
|
538
|
-
expect(SECTION_KEYS.
|
|
539
|
-
expect(SECTION_KEYS.
|
|
540
|
-
expect(SECTION_KEYS.
|
|
541
|
-
expect(SECTION_KEYS.contributions).toBe("metrics-contributions.svg");
|
|
542
|
-
expect(SECTION_KEYS.calendar).toBe("metrics-calendar.svg");
|
|
541
|
+
expect(SECTION_KEYS.velocity).toBe("metrics-velocity.svg");
|
|
542
|
+
expect(SECTION_KEYS.rhythm).toBe("metrics-rhythm.svg");
|
|
543
|
+
expect(SECTION_KEYS.constellation).toBe("metrics-constellation.svg");
|
|
544
|
+
expect(SECTION_KEYS.impact).toBe("metrics-impact.svg");
|
|
543
545
|
});
|
|
544
546
|
});
|
|
545
547
|
|
|
546
548
|
// ── buildSections ───────────────────────────────────────────────────────────
|
|
547
549
|
|
|
548
550
|
describe("buildSections", () => {
|
|
549
|
-
const
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
551
|
+
const makeRhythm = (): ContributionRhythm => ({
|
|
552
|
+
dayTotals: [10, 20, 15, 25, 18, 12, 5],
|
|
553
|
+
longestStreak: 7,
|
|
554
|
+
stats: [
|
|
555
|
+
{ label: "COMMITS", value: "100" },
|
|
556
|
+
{ label: "PRS", value: "10" },
|
|
553
557
|
],
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
},
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
const makeVelocity = (): MonthlyLanguageBucket[] => [
|
|
561
|
+
{
|
|
562
|
+
month: "2025-01",
|
|
563
|
+
languages: [{ name: "TypeScript", commits: 50, color: "#3178c6" }],
|
|
564
|
+
},
|
|
565
|
+
{
|
|
566
|
+
month: "2025-02",
|
|
567
|
+
languages: [{ name: "TypeScript", commits: 60, color: "#3178c6" }],
|
|
568
|
+
},
|
|
569
|
+
];
|
|
570
|
+
|
|
571
|
+
const baseSectionsInput = () => ({
|
|
572
|
+
velocity: makeVelocity(),
|
|
573
|
+
rhythm: makeRhythm(),
|
|
574
|
+
constellation: [
|
|
563
575
|
{
|
|
564
576
|
name: "big-project",
|
|
565
577
|
url: "https://github.com/user/big-project",
|
|
566
|
-
|
|
567
|
-
|
|
578
|
+
x: 100,
|
|
579
|
+
y: 100,
|
|
580
|
+
radius: 10,
|
|
581
|
+
color: "#3178c6",
|
|
582
|
+
connections: [],
|
|
568
583
|
},
|
|
569
584
|
],
|
|
570
585
|
contributionData: makeContributionData(),
|
|
@@ -573,22 +588,21 @@ describe("buildSections", () => {
|
|
|
573
588
|
it("returns correct filenames", () => {
|
|
574
589
|
const sections = buildSections(baseSectionsInput());
|
|
575
590
|
const filenames = sections.map((s) => s.filename);
|
|
576
|
-
expect(filenames).toContain("metrics-
|
|
577
|
-
expect(filenames).toContain("metrics-
|
|
578
|
-
expect(filenames).toContain("metrics-
|
|
579
|
-
expect(filenames).toContain("metrics-pulse.svg");
|
|
591
|
+
expect(filenames).toContain("metrics-velocity.svg");
|
|
592
|
+
expect(filenames).toContain("metrics-rhythm.svg");
|
|
593
|
+
expect(filenames).toContain("metrics-constellation.svg");
|
|
580
594
|
});
|
|
581
595
|
|
|
582
|
-
it("
|
|
596
|
+
it("velocity section is conditional on non-empty velocity data", () => {
|
|
583
597
|
const input = baseSectionsInput();
|
|
584
|
-
input.
|
|
598
|
+
input.velocity = [];
|
|
585
599
|
const sections = buildSections(input);
|
|
586
600
|
expect(sections.map((s) => s.filename)).not.toContain(
|
|
587
|
-
"metrics-
|
|
601
|
+
"metrics-velocity.svg",
|
|
588
602
|
);
|
|
589
603
|
});
|
|
590
604
|
|
|
591
|
-
it("
|
|
605
|
+
it("impact section conditional on externalRepos", () => {
|
|
592
606
|
const input = baseSectionsInput();
|
|
593
607
|
input.contributionData = makeContributionData({
|
|
594
608
|
externalRepos: {
|
|
@@ -605,41 +619,20 @@ describe("buildSections", () => {
|
|
|
605
619
|
},
|
|
606
620
|
});
|
|
607
621
|
const sections = buildSections(input);
|
|
608
|
-
expect(sections.map((s) => s.filename)).toContain(
|
|
609
|
-
"metrics-contributions.svg",
|
|
610
|
-
);
|
|
622
|
+
expect(sections.map((s) => s.filename)).toContain("metrics-impact.svg");
|
|
611
623
|
});
|
|
612
624
|
|
|
613
|
-
it("
|
|
625
|
+
it("impact section omitted when no external repos", () => {
|
|
614
626
|
const sections = buildSections(baseSectionsInput());
|
|
615
|
-
expect(sections.map((s) => s.filename)).not.toContain(
|
|
616
|
-
"metrics-contributions.svg",
|
|
617
|
-
);
|
|
627
|
+
expect(sections.map((s) => s.filename)).not.toContain("metrics-impact.svg");
|
|
618
628
|
});
|
|
619
629
|
|
|
620
|
-
it("
|
|
630
|
+
it("constellation section conditional on non-empty nodes", () => {
|
|
621
631
|
const input = baseSectionsInput();
|
|
622
|
-
input.
|
|
623
|
-
contributionCalendar: makeContributionCalendar(),
|
|
624
|
-
});
|
|
632
|
+
input.constellation = [];
|
|
625
633
|
const sections = buildSections(input);
|
|
626
|
-
expect(sections.map((s) => s.filename)).toContain("metrics-calendar.svg");
|
|
627
|
-
});
|
|
628
|
-
|
|
629
|
-
it("calendar section omitted when no contributionCalendar", () => {
|
|
630
|
-
const sections = buildSections(baseSectionsInput());
|
|
631
634
|
expect(sections.map((s) => s.filename)).not.toContain(
|
|
632
|
-
"metrics-
|
|
633
|
-
);
|
|
634
|
-
});
|
|
635
|
-
|
|
636
|
-
it("uses complexity-based subtitle for signature projects", () => {
|
|
637
|
-
const sections = buildSections(baseSectionsInput());
|
|
638
|
-
const projectSection = sections.find(
|
|
639
|
-
(s) => s.filename === "metrics-complexity.svg",
|
|
640
|
-
);
|
|
641
|
-
expect(projectSection?.subtitle).toBe(
|
|
642
|
-
"Top projects by technical complexity",
|
|
635
|
+
"metrics-constellation.svg",
|
|
643
636
|
);
|
|
644
637
|
});
|
|
645
638
|
|